Skip to content

Commit 9d90f39

Browse files
Annotations can be added to hline, vline, hrect, vrect
They can be added using the annotation keyword argument: fig.add_hline(y=1,annotation=go.layout.Annotation(text="example 1")) or you can specify their parameters using the annotation_ prefixed keywords fig.add_hline(y=1,annotation_text="example 2") There's also a quick way to specify some commonly encountered positions, through the annotation_position argument fig.add_vrect(y=1,annotation_text="example 3", annotation_position="inside bottom left")
1 parent f80e2a0 commit 9d90f39

File tree

2 files changed

+146
-137
lines changed

2 files changed

+146
-137
lines changed

Diff for: packages/python/plotly/plotly/basedatatypes.py

+62-36
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
from _plotly_utils.utils import _natural_sort_strings, _get_int_type
1414
from .optional_imports import get_module
1515

16+
from . import shapeannotation
17+
1618
# Create Undefined sentinel value
1719
# - Setting a property to None removes any existing value
1820
# - Setting a property to Undefined leaves existing value unmodified
@@ -3548,7 +3550,9 @@ def _index_is(iterable, val):
35483550

35493551
return index_list[0]
35503552

3551-
def _make_axis_spanning_shape(self, direction, shape, none_if_no_trace=True):
3553+
def _make_axis_spanning_layout_object(
3554+
self, direction, shape, none_if_no_trace=True
3555+
):
35523556
"""
35533557
Convert a shape drawn on a plot or a subplot into one whose yref or xref
35543558
ends with " domain" and has coordinates so that the shape will seem to
@@ -3559,6 +3563,7 @@ def _make_axis_spanning_shape(self, direction, shape, none_if_no_trace=True):
35593563
corresponding axis reference referring to an actual axis (e.g., 'x',
35603564
'y2' etc. are accepted, but not 'paper'). This will be the case if the
35613565
shape was added with "add_shape".
3566+
Shape must have the x0, x1, y0, y1 fields already initialized.
35623567
"""
35633568
if direction == "vertical":
35643569
# fix y points to top and bottom of subplot
@@ -3575,12 +3580,6 @@ def _make_axis_spanning_shape(self, direction, shape, none_if_no_trace=True):
35753580
"Bad direction: %s. Permissible values are 'vertical' and 'horizontal'."
35763581
% (direction,)
35773582
)
3578-
# vline and hline span the whole axis
3579-
domain = [0, 1]
3580-
try:
3581-
shape[axis + "0"], shape[axis + "1"] = domain
3582-
except KeyError as e:
3583-
raise e("Shape does not support axis spanning.")
35843583
if none_if_no_trace:
35853584
# iterate through all the traces and check to see if one with the
35863585
# same xref and yref pair is there, if not, we return None (we don't
@@ -3608,42 +3607,69 @@ def _process_multiple_axis_spanning_shapes(
36083607
shape_args,
36093608
row,
36103609
col,
3611-
direction,
3610+
shape_type,
36123611
exclude_empty_subplots=True,
36133612
annotation=None,
36143613
**kwargs
36153614
):
36163615
"""
3617-
Add a shape or multiple shapes and call _make_axis_spanning_shape on
3616+
Add a shape or multiple shapes and call _make_axis_spanning_layout_object on
36183617
all the new shapes.
36193618
"""
3619+
if shape_type in ["vline", "vrect"]:
3620+
direction = "vertical"
3621+
elif shape_type in ["hline", "hrect"]:
3622+
direction = "horizontal"
3623+
else:
3624+
raise ValueError(
3625+
"Bad shape_type %s, needs to be one of 'vline', 'hline', 'vrect', 'hrect'"
3626+
% (shape_type,)
3627+
)
3628+
3629+
n_shapes_before = len(self.layout["shapes"])
3630+
n_annotations_before = len(self.layout["annotations"])
36203631
# shapes are always added at the end of the tuple of shapes, so we see
36213632
# how long the tuple is before the call and after the call, and adjust
36223633
# the new shapes that were added at the end
3623-
n_shapes_before = len(self.layout["shapes"])
3624-
self.add_shape(row=row, col=col, **_combine_dicts([shape_args, kwargs]))
3625-
if row == None and col == None:
3626-
# this was called intending to add to a single plot (and
3627-
# self.add_shape succeeded)
3628-
# however, in the case of a single plot, xref and yref are not
3629-
# specified, so we specify them here so the following routines can work
3630-
# (they need to append " domain" to xref or yref)
3631-
self.layout["shapes"][-1].update(xref="x", yref="y")
3632-
n_shapes_after = len(self.layout["shapes"])
3633-
new_shapes = tuple(
3634-
filter(
3635-
lambda x: x is not None,
3636-
[
3637-
self._make_axis_spanning_shape(
3638-
direction,
3639-
self.layout["shapes"][n],
3640-
none_if_no_trace=exclude_empty_subplots,
3641-
)
3642-
for n in range(n_shapes_before, n_shapes_after)
3643-
],
3644-
)
3634+
# extract annotation prefixed kwargs
3635+
# annotation with extra parameters based on the annotation_position
3636+
# argument and other annotation_ prefixed kwargs
3637+
shape_kwargs, annotation_kwargs = shapeannotation.split_dict_by_key_prefix(
3638+
kwargs, "annotation_"
36453639
)
3646-
self.layout["shapes"] = self.layout["shapes"][:n_shapes_before] + new_shapes
3640+
augmented_annotation = shapeannotation.axis_spanning_shape_annotation(
3641+
annotation, shape_type, shape_args, annotation_kwargs
3642+
)
3643+
self.add_shape(row=row, col=col, **_combine_dicts([shape_args, shape_kwargs]))
3644+
self.add_annotation(augmented_annotation, row=row, col=col)
3645+
# update xref and yref for the new shapes and annotations
3646+
for layout_obj, n_layout_objs_before in zip(
3647+
["shapes", "annotations"], [n_shapes_before, n_annotations_before]
3648+
):
3649+
if row == None and col == None:
3650+
# this was called intending to add to a single plot (and
3651+
# self.add_{layout_obj} succeeded)
3652+
# however, in the case of a single plot, xref and yref are not
3653+
# specified, so we specify them here so the following routines can work
3654+
# (they need to append " domain" to xref or yref)
3655+
self.layout[layout_obj][-1].update(xref="x", yref="y")
3656+
n_layout_objs_after = len(self.layout[layout_obj])
3657+
new_layout_objs = tuple(
3658+
filter(
3659+
lambda x: x is not None,
3660+
[
3661+
self._make_axis_spanning_layout_object(
3662+
direction,
3663+
self.layout[layout_obj][n],
3664+
none_if_no_trace=exclude_empty_subplots,
3665+
)
3666+
for n in range(n_layout_objs_before, n_layout_objs_after)
3667+
],
3668+
)
3669+
)
3670+
self.layout[layout_obj] = (
3671+
self.layout[layout_obj][:n_layout_objs_before] + new_layout_objs
3672+
)
36473673

36483674
def add_vline(
36493675
self,
@@ -3678,7 +3704,7 @@ def add_vline(
36783704
dict(type="line", x0=x, x1=x, y0=0, y1=1),
36793705
row,
36803706
col,
3681-
"vertical",
3707+
"vline",
36823708
exclude_empty_subplots=exclude_empty_subplots,
36833709
annotation=annotation,
36843710
**kwargs
@@ -3710,7 +3736,7 @@ def add_hline(self, y, row=None, col=None, exclude_empty_subplots=True, **kwargs
37103736
dict(type="line", x0=0, x1=1, y0=y, y1=y,),
37113737
row,
37123738
col,
3713-
"horizontal",
3739+
"hline",
37143740
exclude_empty_subplots=exclude_empty_subplots,
37153741
**kwargs
37163742
)
@@ -3745,7 +3771,7 @@ def add_vrect(
37453771
dict(type="rect", x0=x0, x1=x1, y0=0, y1=1),
37463772
row,
37473773
col,
3748-
"vertical",
3774+
"vrect",
37493775
exclude_empty_subplots=exclude_empty_subplots,
37503776
**kwargs
37513777
)
@@ -3780,7 +3806,7 @@ def add_hrect(
37803806
dict(type="rect", x0=0, x1=1, y0=y0, y1=y1),
37813807
row,
37823808
col,
3783-
"horizontal",
3809+
"hrect",
37843810
exclude_empty_subplots=exclude_empty_subplots,
37853811
**kwargs
37863812
)

0 commit comments

Comments
 (0)