Skip to content

Commit e095d0b

Browse files
feat(plotly): implement line-annotated-events (#3032)
## Implementation: `line-annotated-events` - plotly Implements the **plotly** version of `line-annotated-events`. **File:** `plots/line-annotated-events/implementations/plotly.py` **Parent Issue:** #2997 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20617489094)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent f7d239a commit e095d0b

2 files changed

Lines changed: 153 additions & 0 deletions

File tree

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
""" pyplots.ai
2+
line-annotated-events: Annotated Line Plot with Event Markers
3+
Library: plotly 6.5.0 | Python 3.13.11
4+
Quality: 92/100 | Created: 2025-12-31
5+
"""
6+
7+
import numpy as np
8+
import pandas as pd
9+
import plotly.graph_objects as go
10+
11+
12+
# Data - Simulated stock price data with events
13+
np.random.seed(42)
14+
15+
# Generate daily stock prices over a year
16+
dates = pd.date_range("2024-01-01", periods=252, freq="B") # Business days
17+
returns = np.random.normal(0.0005, 0.015, len(dates))
18+
prices = 100 * np.exp(np.cumsum(returns))
19+
20+
# Define significant events (earnings announcements, product launches)
21+
events = [
22+
{"date": pd.Timestamp("2024-01-25"), "label": "Q4 Earnings"},
23+
{"date": pd.Timestamp("2024-03-15"), "label": "Product Launch"},
24+
{"date": pd.Timestamp("2024-04-24"), "label": "Q1 Earnings"},
25+
{"date": pd.Timestamp("2024-06-10"), "label": "Expansion Announced"},
26+
{"date": pd.Timestamp("2024-07-25"), "label": "Q2 Earnings"},
27+
{"date": pd.Timestamp("2024-09-20"), "label": "Partnership Deal"},
28+
{"date": pd.Timestamp("2024-10-24"), "label": "Q3 Earnings"},
29+
]
30+
31+
# Create figure
32+
fig = go.Figure()
33+
34+
# Add main price line
35+
fig.add_trace(
36+
go.Scatter(
37+
x=dates,
38+
y=prices,
39+
mode="lines",
40+
name="Stock Price",
41+
line={"color": "#306998", "width": 4},
42+
hovertemplate="Date: %{x|%Y-%m-%d}<br>Price: $%{y:.2f}<extra></extra>",
43+
)
44+
)
45+
46+
# Add vertical lines and markers for events with alternating heights
47+
y_range = prices.max() - prices.min()
48+
heights = [0.92, 0.82, 0.92, 0.82, 0.92, 0.82, 0.92] # Alternating heights
49+
50+
for i, event in enumerate(events):
51+
event_date = event["date"]
52+
event_label = event["label"]
53+
y_position = prices.min() + y_range * heights[i]
54+
55+
# Find the closest price at event date
56+
closest_idx = np.abs(dates - event_date).argmin()
57+
price_at_event = prices[closest_idx]
58+
59+
# Add vertical dashed line
60+
fig.add_shape(
61+
type="line",
62+
x0=event_date,
63+
x1=event_date,
64+
y0=prices.min() - y_range * 0.02,
65+
y1=y_position - y_range * 0.02,
66+
line={"color": "#FFD43B", "width": 3, "dash": "dash"},
67+
)
68+
69+
# Add marker at the event date on the price line
70+
fig.add_trace(
71+
go.Scatter(
72+
x=[event_date],
73+
y=[price_at_event],
74+
mode="markers",
75+
marker={"size": 18, "color": "#FFD43B", "symbol": "diamond", "line": {"color": "#306998", "width": 2}},
76+
showlegend=False,
77+
hovertemplate=f"{event_label}<br>Date: %{{x|%Y-%m-%d}}<br>Price: $%{{y:.2f}}<extra></extra>",
78+
)
79+
)
80+
81+
# Add annotation label
82+
fig.add_annotation(
83+
x=event_date,
84+
y=y_position,
85+
text=event_label,
86+
showarrow=False,
87+
font={"size": 20, "color": "#333333"},
88+
bgcolor="rgba(255, 212, 59, 0.9)",
89+
bordercolor="#FFD43B",
90+
borderwidth=2,
91+
borderpad=8,
92+
)
93+
94+
# Update layout
95+
fig.update_layout(
96+
title={"text": "line-annotated-events · plotly · pyplots.ai", "font": {"size": 40}, "x": 0.5, "xanchor": "center"},
97+
xaxis={
98+
"title": {"text": "Date", "font": {"size": 36}},
99+
"tickfont": {"size": 28},
100+
"showgrid": True,
101+
"gridcolor": "rgba(0,0,0,0.1)",
102+
"gridwidth": 1,
103+
},
104+
yaxis={
105+
"title": {"text": "Stock Price (USD)", "font": {"size": 36}},
106+
"tickfont": {"size": 28},
107+
"tickprefix": "$",
108+
"showgrid": True,
109+
"gridcolor": "rgba(0,0,0,0.1)",
110+
"gridwidth": 1,
111+
},
112+
template="plotly_white",
113+
showlegend=True,
114+
legend={
115+
"x": 0.02,
116+
"y": 0.98,
117+
"font": {"size": 24},
118+
"bgcolor": "rgba(255,255,255,0.9)",
119+
"bordercolor": "rgba(0,0,0,0.2)",
120+
"borderwidth": 1,
121+
},
122+
margin={"l": 120, "r": 50, "t": 120, "b": 100},
123+
plot_bgcolor="white",
124+
)
125+
126+
# Save as PNG and HTML
127+
fig.write_image("plot.png", width=1600, height=900, scale=3)
128+
fig.write_html("plot.html", include_plotlyjs=True)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
library: plotly
2+
specification_id: line-annotated-events
3+
created: '2025-12-31T10:56:37Z'
4+
updated: '2025-12-31T11:08:03Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20617489094
7+
issue: 2997
8+
python_version: 3.13.11
9+
library_version: 6.5.0
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/line-annotated-events/plotly/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/line-annotated-events/plotly/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/line-annotated-events/plotly/plot.html
13+
quality_score: 92
14+
review:
15+
strengths:
16+
- Excellent use of alternating annotation heights to prevent label overlap
17+
- High-quality visual design with appropriate font sizes for 4800x2700 canvas
18+
- Clean color scheme with good contrast (blue line, yellow annotations)
19+
- Interactive hover templates provide rich information
20+
- Realistic stock price scenario with appropriate event types
21+
- Proper use of Plotly shape and annotation features
22+
weaknesses:
23+
- Grid lines are very subtle (alpha 0.1) - could be slightly more visible at 0.2-0.3
24+
- Does not leverage Plotly-specific interactive features like rangeslider
25+
- Creates both PNG and HTML outputs when only PNG is required

0 commit comments

Comments
 (0)