Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Fix Plotly Express mutating lists passed to the `x` or `y` arguments in wide mode [[#5727](https://github.com/plotly/plotly.py/pull/5727)], with thanks to @cpruijsen for the contribution!
- Fix `px.sunburst`, `px.treemap` and `px.icicle` listing sectors in a different order on every run when `path` is used with a Polars DataFrame; sectors now follow their order of first appearance for all dataframe backends [[#5766](https://github.com/plotly/plotly.py/pull/5766)], with thanks to @Irahan2 for the contribution!
- Fix `mpl_to_plotly` conversion of matplotlib contour lines and line collections: close contour rings ending with `Z` codes, support dash styles, convert date x-axes, separate disjoint subpaths with `None` separators, hide line collection traces from the legend by default, and group consecutive same-style lines into single traces [[#5770](https://github.com/plotly/plotly.py/pull/5770)], with thanks to @robertoffmoura for the contribution!
- Fix `add_vline`, `add_hline`, `add_vrect` and `add_hrect` modifying the dict or `go.layout.Annotation` passed as `annotation`, which placed every later shape's annotation at the first shape's position when the same `annotation` was reused [[#3547](https://github.com/plotly/plotly.py/issues/3547)], with thanks to @adarshsm for the contribution!

## [7.1.0] - 2026-09-15

Expand Down
5 changes: 5 additions & 0 deletions plotly/shapeannotation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# some functions defined here to avoid numpy import

import copy
import datetime


Expand Down Expand Up @@ -244,6 +245,10 @@ def axis_spanning_shape_annotation(annotation, shape_type, shape_args, kwargs):
# go.layout.Annotation ?
if annotation is None:
annotation = dict()
else:
# Fill in a copy, so that an annotation passed in can be reused for
# other shapes without keeping this shape's text and position.
annotation = copy.copy(annotation)
for k in annotation_keys:
if k == "annotation_position":
# don't set so that Annotation constructor doesn't complain
Expand Down
25 changes: 25 additions & 0 deletions tests/test_optional/test_autoshapes/test_annotated_shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from itertools import product
import copy
import os
import pytest
import json
Expand Down Expand Up @@ -282,6 +283,30 @@ def test_specify_annotation_as_dict(multi_plot_fixture):
assert ret


# Test that the annotation passed in isn't modified, so it can be reused for
# several shapes
@pytest.mark.parametrize(
"annotation",
[dict(font_color="red"), go.layout.Annotation(font_color="red")],
ids=["dict", "Annotation"],
)
def test_reuse_annotation(single_plot_fixture, annotation):
before = copy.deepcopy(annotation)
single_plot_fixture.add_vline(x=2, annotation_text="A", annotation=annotation)
single_plot_fixture.add_vline(x=3, annotation_text="B", annotation=annotation)
assert annotation == before
ret = len(single_plot_fixture.layout.annotations) == 2
for sh, d in zip(
single_plot_fixture.layout.annotations,
[
{"text": "A", "x": 2, "font.color": "red"},
{"text": "B", "x": 3, "font.color": "red"},
],
):
ret &= _cmp_partial_dict(sh, d)
assert ret


# Test the default and coerced positions work
def test_default_annotation_positions(multi_plot_fixture):
# default position is (inside) top right
Expand Down