Skip to content

Commit 6c01f58

Browse files
feat(altair): implement timeline-basic (#2483)
## Implementation: `timeline-basic` - altair Implements the **altair** version of `timeline-basic`. **File:** `plots/timeline-basic/implementations/altair.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20584329330)* --------- 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 ed598ef commit 6c01f58

2 files changed

Lines changed: 183 additions & 0 deletions

File tree

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
""" pyplots.ai
2+
timeline-basic: Event Timeline
3+
Library: altair 6.0.0 | Python 3.13.11
4+
Quality: 91/100 | Created: 2025-12-29
5+
"""
6+
7+
import altair as alt
8+
import pandas as pd
9+
10+
11+
# Data: Software project milestones
12+
data = pd.DataFrame(
13+
{
14+
"date": pd.to_datetime(
15+
[
16+
"2024-01-15",
17+
"2024-02-20",
18+
"2024-03-10",
19+
"2024-04-05",
20+
"2024-05-01",
21+
"2024-06-15",
22+
"2024-07-20",
23+
"2024-08-30",
24+
"2024-09-15",
25+
"2024-10-25",
26+
"2024-11-10",
27+
"2024-12-01",
28+
]
29+
),
30+
"event": [
31+
"Project Kickoff",
32+
"Requirements Complete",
33+
"Design Review",
34+
"Development Start",
35+
"Alpha Release",
36+
"Beta Testing",
37+
"Security Audit",
38+
"Performance Testing",
39+
"User Acceptance",
40+
"Release Candidate",
41+
"Documentation",
42+
"Production Launch",
43+
],
44+
"category": [
45+
"Planning",
46+
"Planning",
47+
"Planning",
48+
"Development",
49+
"Development",
50+
"Testing",
51+
"Testing",
52+
"Testing",
53+
"Testing",
54+
"Release",
55+
"Release",
56+
"Release",
57+
],
58+
}
59+
)
60+
61+
# Alternate label positions to prevent overlap (above/below axis)
62+
data["y_offset"] = [1.5 if i % 2 == 0 else -1.5 for i in range(len(data))]
63+
data["y_zero"] = 0
64+
data["y_label"] = [2.3 if i % 2 == 0 else -2.3 for i in range(len(data))]
65+
66+
# Color palette for categories (Python Blue and complementary colors with good contrast)
67+
category_colors = {"Planning": "#306998", "Development": "#E5A000", "Testing": "#4ECDC4", "Release": "#E8575A"}
68+
69+
# Shared y scale
70+
y_scale = alt.Scale(domain=[-3.5, 3.5])
71+
72+
# Color scale for consistency
73+
color_scale = alt.Scale(domain=list(category_colors.keys()), range=list(category_colors.values()))
74+
75+
# Vertical connector lines from axis to points
76+
connectors = (
77+
alt.Chart(data)
78+
.mark_rule(strokeWidth=3, opacity=0.7)
79+
.encode(
80+
x="date:T",
81+
y=alt.Y("y_zero:Q", scale=y_scale),
82+
y2="y_offset:Q",
83+
color=alt.Color("category:N", scale=color_scale, legend=None),
84+
)
85+
)
86+
87+
# Event markers on the timeline
88+
points = (
89+
alt.Chart(data)
90+
.mark_circle(size=600, stroke="white", strokeWidth=3)
91+
.encode(
92+
x=alt.X(
93+
"date:T",
94+
axis=alt.Axis(title="Date", format="%b %Y", labelFontSize=18, titleFontSize=22, labelAngle=-45, grid=False),
95+
),
96+
y=alt.Y("y_offset:Q", scale=y_scale),
97+
color=alt.Color("category:N", scale=color_scale, legend=None),
98+
tooltip=[
99+
alt.Tooltip("date:T", title="Date", format="%B %d, %Y"),
100+
alt.Tooltip("event:N", title="Event"),
101+
alt.Tooltip("category:N", title="Phase"),
102+
],
103+
)
104+
)
105+
106+
# Central timeline axis line using rule from min to max date
107+
timeline_line = alt.Chart(data).mark_rule(color="#666666", strokeWidth=4).encode(y=alt.Y("y_zero:Q", scale=y_scale))
108+
109+
# Event labels positioned above/below points
110+
labels = (
111+
alt.Chart(data)
112+
.mark_text(align="center", fontSize=16, fontWeight="bold")
113+
.encode(x="date:T", y=alt.Y("y_label:Q", scale=y_scale), text="event:N", color=alt.value("#333333"))
114+
)
115+
116+
# Create inline legend using text and point marks
117+
legend_data = pd.DataFrame(
118+
{
119+
"category": list(category_colors.keys()),
120+
"x_pos": [
121+
pd.Timestamp("2024-02-01"),
122+
pd.Timestamp("2024-05-01"),
123+
pd.Timestamp("2024-08-01"),
124+
pd.Timestamp("2024-11-01"),
125+
],
126+
"y_pos": [3.2, 3.2, 3.2, 3.2],
127+
}
128+
)
129+
130+
legend_points = (
131+
alt.Chart(legend_data)
132+
.mark_circle(size=300, stroke="white", strokeWidth=2)
133+
.encode(
134+
x=alt.X("x_pos:T"),
135+
y=alt.Y("y_pos:Q", scale=y_scale),
136+
color=alt.Color("category:N", scale=color_scale, legend=None),
137+
)
138+
)
139+
140+
legend_labels = (
141+
alt.Chart(legend_data)
142+
.mark_text(align="left", fontSize=16, fontWeight="bold", dx=15)
143+
.encode(x=alt.X("x_pos:T"), y=alt.Y("y_pos:Q", scale=y_scale), text="category:N", color=alt.value("#333333"))
144+
)
145+
146+
# Combine all layers
147+
chart = (
148+
alt.layer(timeline_line, connectors, points, labels, legend_points, legend_labels)
149+
.properties(
150+
width=1600, height=900, title=alt.Title("timeline-basic · altair · pyplots.ai", fontSize=28, anchor="middle")
151+
)
152+
.configure_view(strokeWidth=0)
153+
.configure_axisY(disable=True)
154+
)
155+
156+
# Save outputs
157+
chart.save("plot.png", scale_factor=3.0)
158+
chart.save("plot.html")
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
library: altair
2+
specification_id: timeline-basic
3+
created: '2025-12-29T22:51:19Z'
4+
updated: '2025-12-29T23:10:41Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20584329330
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: 6.0.0
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/timeline-basic/altair/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/timeline-basic/altair/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/timeline-basic/altair/plot.html
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent alternating label placement above/below axis prevents overlap perfectly
17+
- Creative inline legend implementation that integrates with the chart
18+
- Clean layered chart architecture using alt.layer() for composability
19+
- Good use of Altair declarative encoding system with proper type annotations
20+
- Appropriate color palette with good accessibility
21+
- Interactive tooltips provide additional detail on hover
22+
weaknesses:
23+
- Event labels at 16pt fontSize could be slightly larger (18pt) for better readability
24+
- Inline legend positioned close to top event labels, could use more vertical separation
25+
- Could add .interactive() to enable zoom/pan for exploring dense timelines

0 commit comments

Comments
 (0)