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
24 changes: 23 additions & 1 deletion plotly/shapeannotation.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
# some functions defined here to avoid numpy import

from datetime import datetime
from numbers import Number


def _mean(x):
if len(x) == 0:
raise ValueError("x must have positive length")
return float(sum(x)) / len(x)

# Numeric sequence: default behaviour
if all(isinstance(v, Number) for v in x):
return float(sum(x)) / len(x)

# Datetime sequence: delegate to _mean_datetime
if all(isinstance(v, datetime) for v in x):
return _mean_datetime(x)

# Fallback for non-numeric and non-datetime types: return first element
return x[0]


def _mean_datetime(x):
"""Return midpoint of a sequence of datetime objects (assumes homogenous)."""

timestamps = [v.timestamp() for v in x] # convert to POSIX timestamps
avg = sum(timestamps) / len(timestamps)
tzinfo = x[0].tzinfo # extract timezone info from first element
return datetime.fromtimestamp(avg, tz=tzinfo)


def _argmin(x):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Tests for annotated axis-spanning shapes with datetime coordinates.

These tests cover the regression described in GitHub issue #3065:
https://github.com/plotly/plotly.py/issues/3065

"""

from datetime import datetime

import plotly.express as px
import plotly.graph_objects as go


def test_add_vline_with_date_annotation_express():
"""MWE from Github issue https://github.com/plotly/plotly.py/issues/3065"""
df = px.data.stocks(indexed=True)
fig = px.line(df)
fig.add_vline(x="2018-09-24", annotation_text="test")


def test_add_vline_with_date_annotation():
fig = go.Figure()
# Provide a couple of traces so axis type is inferred as date from data
dates = [datetime(2025, 9, 23), datetime(2025, 9, 24), datetime(2025, 9, 25)]
fig.add_scatter(x=dates, y=[1, 2, 3])
fig.add_vline(x=dates[1], annotation_text="Test")

# Ensure one annotation was added and x coordinate preserved
annotations = getattr(fig.layout, "annotations", [])
assert len(annotations) == 1
ann = annotations[0]
assert ann.x == dates[1]
assert ann.text == "Test"


def test_add_hline_with_date_annotation():
fig = go.Figure()
# Provide a couple of traces so axis type is inferred as date from data
dates = [datetime(2025, 9, 23), datetime(2025, 9, 24), datetime(2025, 9, 25)]
fig.add_scatter(y=dates, x=[1, 2, 3])
fig.add_hline(y=dates[1], annotation_text="Test")

# Ensure one annotation was added and x coordinate preserved
annotations = getattr(fig.layout, "annotations", [])
assert len(annotations) == 1
ann = annotations[0]
assert ann.y == dates[1]
assert ann.text == "Test"