|
| 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") |
0 commit comments