Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
44 changes: 43 additions & 1 deletion plotly/shapeannotation.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,52 @@
# some functions defined here to avoid numpy import

import datetime


def _is_date_string(val):
"""Check if a value is a date/datetime string."""
if not isinstance(val, str):
return False
try:
datetime.datetime.fromisoformat(val.replace("Z", "+00:00"))
return True
except (ValueError, AttributeError):
return False


def _datetime_str_to_ms(val):
"""Convert a datetime string to milliseconds since epoch."""
dt = datetime.datetime.fromisoformat(val.replace("Z", "+00:00"))
if dt.tzinfo is None:
dt = dt.replace(tzinfo=datetime.timezone.utc)
return dt.timestamp() * 1000


def _ms_to_datetime_str(ms):
"""Convert milliseconds since epoch back to a datetime string."""
dt = datetime.datetime.fromtimestamp(ms / 1000, tz=datetime.timezone.utc)
return dt.strftime("%Y-%m-%d %H:%M:%S")


def _mean(x):
if len(x) == 0:
raise ValueError("x must have positive length")
return float(sum(x)) / len(x)
try:
return float(sum(x)) / len(x)
except TypeError:
# Handle non-numeric types like datetime strings or datetime objects
if all(_is_date_string(v) for v in x):
ms_values = [_datetime_str_to_ms(v) for v in x]
mean_ms = sum(ms_values) / len(ms_values)
return _ms_to_datetime_str(mean_ms)
# Handle datetime.datetime, pd.Timestamp, or similar objects
if all(hasattr(v, "timestamp") for v in x):
ts_values = [v.timestamp() * 1000 for v in x]
mean_ms = sum(ts_values) / len(ts_values)
return datetime.datetime.fromtimestamp(
mean_ms / 1000, tz=datetime.timezone.utc
).isoformat()
raise


def _argmin(x):
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__":
Comment thread
emilykl marked this conversation as resolved.
Outdated
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])
)
Comment thread
emilykl marked this conversation as resolved.
Outdated
fig.add_vline(x="2018-09-24", annotation_text="test")
assert len(fig.layout.annotations) == 1
assert fig.layout.annotations[0].text == "test"
Comment thread
emilykl marked this conversation as resolved.


def test_hline_with_datetime_vline():
"""add_hline should still work alongside datetime vline usage."""
Comment thread
emilykl marked this conversation as resolved.
Outdated
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"
Comment thread
emilykl marked this conversation as resolved.


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])
)
Comment thread
emilykl marked this conversation as resolved.
Outdated
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"
Comment thread
emilykl marked this conversation as resolved.


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"
Comment thread
emilykl marked this conversation as resolved.


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"
Comment thread
emilykl marked this conversation as resolved.

Loading