Skip to content
Open
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
93 changes: 74 additions & 19 deletions plotly/basedatatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1627,7 +1627,7 @@ def _add_domain(ax_letter, new_axref):
xref, yref = map(lambda t: _add_domain(*t), zip(["x", "y"], [xref, yref]))
new_obj.update(xref=xref, yref=yref)

self.layout[prop_plural] += (new_obj,)
self.layout._append_array_prop(prop_plural, new_obj)
# The 'new_obj.xref' and 'new_obj.yref' parameters need to be reset otherwise it
# will appear as if user supplied yref params when looping through subplots and
# will force annotation to be on the axis of the last drawn annotation
Expand Down Expand Up @@ -3290,13 +3290,20 @@ def _perform_batch_animate(self, animation_opts):

# Exports
# -------
def to_dict(self):
def to_dict(self, encode_base64=True):
"""
Convert figure to a dictionary

Note: the dictionary includes the properties explicitly set by the
user, it does not include default values of unspecified properties

Parameters
----------
encode_base64: bool (default True)
If True, large numerical arrays will be converted to a binary
base64 encoding (bdata). If False, these arrays will remain
as numpy arrays or lists.

Returns
-------
dict
Expand All @@ -3319,22 +3326,30 @@ def to_dict(self):
res["frames"] = frames

# Add base64 conversion before sending to the front-end
convert_to_base64(res)
if encode_base64:
convert_to_base64(res)

return res

def to_plotly_json(self):
def to_plotly_json(self, encode_base64=True):
"""
Convert figure to a JSON representation as a Python dict

Note: May include some JSON-invalid data types, use the `PlotlyJSONEncoder` util
or the `to_json` method to encode to a string.

Parameters
----------
encode_base64: bool (default True)
If True, large numerical arrays will be converted to a binary
base64 encoding (bdata). If False, these arrays will remain
as numpy arrays or lists.

Returns
-------
dict
"""
return self.to_dict()
return self.to_dict(encode_base64=encode_base64)

@staticmethod
def _to_ordered_dict(d, skip_uid=False):
Expand Down Expand Up @@ -4124,21 +4139,13 @@ def _process_multiple_axis_spanning_shapes(
self.layout[layout_obj][-1].update(xref="x")
if self.layout[layout_obj][-1].yref is None:
self.layout[layout_obj][-1].update(yref="y")
new_layout_objs = tuple(
filter(
lambda x: x is not None,
[
self._make_axis_spanning_layout_object(
direction,
self.layout[layout_obj][n],
)
for n in range(n_layout_objs_before, n_layout_objs_after)
],

# Update the new objects in-place to append " domain" to xref/yref
for n in range(n_layout_objs_before, n_layout_objs_after):
self._make_axis_spanning_layout_object(
direction,
self.layout[layout_obj][n],
)
)
self.layout[layout_obj] = (
self.layout[layout_obj][:n_layout_objs_before] + new_layout_objs
)

def add_vline(
self,
Expand Down Expand Up @@ -5380,6 +5387,54 @@ def _set_compound_prop(self, prop, val):
self._compound_props[prop] = val
return val

def _append_array_prop(self, prop, new_element):
"""
Append a single element to a compound array property without
re-validating all existing elements.

This provides O(1) appends instead of O(N) re-validation that
occurs with _set_array_prop when called via ``+=``.

Parameters
----------
prop : str
Name of a compound array property (e.g. 'annotations')
new_element : BasePlotlyType
The already-constructed element to append. Must be an instance
of the appropriate data class for this property.
"""
# Get or initialize the existing compound array
curr_val = self._compound_array_props.get(prop, ())
if curr_val is None:
curr_val = ()
elif isinstance(curr_val, list):
curr_val = tuple(curr_val)

# Create a copy of the new element (same pattern as
# CompoundArrayValidator.validate_coerce) so the caller can
# safely modify/reset the original without affecting the stored copy.
element_copy = type(new_element)(new_element)

# Make a deep copy of new element's props for _props storage
new_dict = deepcopy(element_copy._props) if element_copy._props else {}

# Update _props dict
if not self._in_batch_mode:
self._init_props()
if prop not in self._props:
self._props[prop] = []
self._props[prop].append(new_dict)

# Send update notification
self._send_prop_set(prop, self._props.get(prop))

# Reparent the copy
element_copy._orphan_props.clear()
element_copy._parent = self

# Append to _compound_array_props
self._compound_array_props[prop] = curr_val + (element_copy,)

def _set_array_prop(self, prop, val):
"""
Set the value of a compound property
Expand Down
79 changes: 79 additions & 0 deletions tests/test_optional/test_autoshapes/test_annotated_shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,5 +425,84 @@ def test_all_annotation_positions():
draw_all_annotation_positions(testing=True)



if __name__ == "__main__":
draw_all_annotation_positions()


# Tests for datetime axis annotation support (issue #3065)
import datetime


def test_vline_datetime_string_annotation():
"""add_vline with annotation_text on datetime x-axis should not crash."""
fig = go.Figure()
fig.add_trace(
go.Scatter(x=["2018-01-01", "2018-06-01", "2018-12-31"], y=[1, 2, 3])
)
fig.add_vline(x="2018-09-24", annotation_text="test")
assert len(fig.layout.annotations) == 1
assert fig.layout.annotations[0].text == "test"


def test_hline_with_datetime_vline():
"""add_hline should still work alongside datetime vline usage."""
fig = go.Figure()
fig.add_trace(
go.Scatter(x=["2018-01-01", "2018-06-01", "2018-12-31"], y=[1, 2, 3])
)
fig.add_hline(y=2, annotation_text="hline test")
assert len(fig.layout.annotations) == 1
assert fig.layout.annotations[0].text == "hline test"


def test_vrect_datetime_string_annotation():
"""add_vrect with annotation_text on datetime x-axis should not crash."""
fig = go.Figure()
fig.add_trace(
go.Scatter(x=["2018-01-01", "2018-06-01", "2018-12-31"], y=[1, 2, 3])
)
fig.add_vrect(x0="2018-03-01", x1="2018-09-01", annotation_text="rect test")
assert len(fig.layout.annotations) == 1
assert fig.layout.annotations[0].text == "rect test"


def test_vline_datetime_object_annotation():
"""add_vline with datetime.datetime object should not crash."""
fig = go.Figure()
fig.add_trace(
go.Scatter(
x=[
datetime.datetime(2018, 1, 1),
datetime.datetime(2018, 6, 1),
datetime.datetime(2018, 12, 31),
],
y=[1, 2, 3],
)
)
fig.add_vline(x=datetime.datetime(2018, 9, 24), annotation_text="dt test")
assert len(fig.layout.annotations) == 1
assert fig.layout.annotations[0].text == "dt test"


def test_vrect_datetime_object_annotation():
"""add_vrect with datetime.datetime objects should compute correct mean."""
fig = go.Figure()
fig.add_trace(
go.Scatter(
x=[
datetime.datetime(2018, 1, 1),
datetime.datetime(2018, 6, 1),
datetime.datetime(2018, 12, 31),
],
y=[1, 2, 3],
)
)
fig.add_vrect(
x0=datetime.datetime(2018, 3, 1),
x1=datetime.datetime(2018, 9, 1),
annotation_text="rect dt test",
)
assert len(fig.layout.annotations) == 1
assert fig.layout.annotations[0].text == "rect dt test"