|
1 | | -""" pyplots.ai |
| 1 | +"""pyplots.ai |
2 | 2 | bump-basic: Basic Bump Chart |
3 | | -Library: altair 6.0.0 | Python 3.13.11 |
4 | | -Quality: 92/100 | Created: 2025-12-23 |
| 3 | +Library: altair 6.0.0 | Python 3.14.3 |
| 4 | +Quality: /100 | Updated: 2026-02-22 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import altair as alt |
8 | 8 | import pandas as pd |
9 | 9 |
|
10 | 10 |
|
11 | | -# Data - Sports league standings over 6 matchweeks |
12 | | -data = { |
13 | | - "Team": ( |
14 | | - ["Arsenal"] * 6 |
15 | | - + ["Chelsea"] * 6 |
16 | | - + ["Liverpool"] * 6 |
17 | | - + ["Man City"] * 6 |
18 | | - + ["Man United"] * 6 |
19 | | - + ["Tottenham"] * 6 |
20 | | - ), |
21 | | - "Week": ["Week 1", "Week 2", "Week 3", "Week 4", "Week 5", "Week 6"] * 6, |
22 | | - "Rank": [ |
23 | | - # Arsenal: Starts 3rd, rises to 1st, stays competitive |
24 | | - 3, |
25 | | - 2, |
26 | | - 1, |
27 | | - 1, |
28 | | - 2, |
29 | | - 1, |
30 | | - # Chelsea: Mid-table consistency |
31 | | - 4, |
32 | | - 4, |
33 | | - 3, |
34 | | - 4, |
35 | | - 3, |
36 | | - 3, |
37 | | - # Liverpool: Starts 1st, drops mid-season, recovers |
38 | | - 1, |
39 | | - 1, |
40 | | - 2, |
41 | | - 3, |
42 | | - 1, |
43 | | - 2, |
44 | | - # Man City: Slow start, climbs steadily |
45 | | - 5, |
46 | | - 5, |
47 | | - 4, |
48 | | - 2, |
49 | | - 4, |
50 | | - 4, |
51 | | - # Man United: Volatile rankings |
52 | | - 2, |
53 | | - 3, |
54 | | - 5, |
55 | | - 5, |
56 | | - 5, |
57 | | - 5, |
58 | | - # Tottenham: Bottom position throughout |
59 | | - 6, |
60 | | - 6, |
61 | | - 6, |
62 | | - 6, |
63 | | - 6, |
64 | | - 6, |
65 | | - ], |
66 | | -} |
| 11 | +# Data - Sports league standings over 6 matchweeks (no random data) |
| 12 | +teams = ["Arsenal", "Chelsea", "Liverpool", "Man City", "Man United", "Tottenham"] |
| 13 | +weeks = ["Week 1", "Week 2", "Week 3", "Week 4", "Week 5", "Week 6"] |
| 14 | +ranks = [ |
| 15 | + # Arsenal: Starts 3rd, rises to 1st, stays competitive |
| 16 | + 3, |
| 17 | + 2, |
| 18 | + 1, |
| 19 | + 1, |
| 20 | + 2, |
| 21 | + 1, |
| 22 | + # Chelsea: Mid-table consistency |
| 23 | + 4, |
| 24 | + 4, |
| 25 | + 3, |
| 26 | + 4, |
| 27 | + 3, |
| 28 | + 3, |
| 29 | + # Liverpool: Starts 1st, drops mid-season, recovers |
| 30 | + 1, |
| 31 | + 1, |
| 32 | + 2, |
| 33 | + 3, |
| 34 | + 1, |
| 35 | + 2, |
| 36 | + # Man City: Slow start, climbs steadily |
| 37 | + 5, |
| 38 | + 5, |
| 39 | + 4, |
| 40 | + 2, |
| 41 | + 4, |
| 42 | + 4, |
| 43 | + # Man United: Volatile rankings |
| 44 | + 2, |
| 45 | + 3, |
| 46 | + 5, |
| 47 | + 5, |
| 48 | + 5, |
| 49 | + 5, |
| 50 | + # Tottenham: Bottom position throughout |
| 51 | + 6, |
| 52 | + 6, |
| 53 | + 6, |
| 54 | + 6, |
| 55 | + 6, |
| 56 | + 6, |
| 57 | +] |
67 | 58 |
|
68 | | -df = pd.DataFrame(data) |
| 59 | +df = pd.DataFrame({"Team": [t for t in teams for _ in weeks], "Week": weeks * len(teams), "Rank": ranks}) |
69 | 60 |
|
70 | | -# Define color palette (Python Blue first, then colorblind-safe colors) |
71 | | -colors = ["#306998", "#FFD43B", "#E15759", "#59A14F", "#9C755F", "#BAB0AC"] |
| 61 | +# Colorblind-safe palette (Python Blue first) |
| 62 | +colors = ["#306998", "#FFD43B", "#E15759", "#59A14F", "#B07AA1", "#76B7B2"] |
72 | 63 |
|
73 | | -# Create bump chart - lines connecting rankings |
| 64 | +# Interactive highlight — distinctive Altair feature |
| 65 | +highlight = alt.selection_point(fields=["Team"], on="pointerover") |
| 66 | + |
| 67 | +# Shared encodings |
| 68 | +x = alt.X("Week:O", title="Match Week", axis=alt.Axis(labelFontSize=18, titleFontSize=22)) |
| 69 | +y = alt.Y( |
| 70 | + "Rank:Q", |
| 71 | + title="League Position", |
| 72 | + scale=alt.Scale(domain=[1, 6], reverse=True), |
| 73 | + axis=alt.Axis(labelFontSize=18, titleFontSize=22, tickMinStep=1, values=[1, 2, 3, 4, 5, 6]), |
| 74 | +) |
| 75 | +color = alt.Color( |
| 76 | + "Team:N", |
| 77 | + title="Team", |
| 78 | + scale=alt.Scale(domain=teams, range=colors), |
| 79 | + legend=alt.Legend(labelFontSize=16, titleFontSize=18, symbolSize=200), |
| 80 | +) |
| 81 | + |
| 82 | +# Lines — dim non-highlighted teams on hover |
74 | 83 | lines = ( |
75 | 84 | alt.Chart(df) |
76 | | - .mark_line(strokeWidth=4, opacity=0.8) |
77 | | - .encode( |
78 | | - x=alt.X("Week:O", title="Match Week", axis=alt.Axis(labelFontSize=18, titleFontSize=22)), |
79 | | - y=alt.Y( |
80 | | - "Rank:Q", |
81 | | - title="League Position", |
82 | | - scale=alt.Scale(domain=[1, 6], reverse=True), |
83 | | - axis=alt.Axis(labelFontSize=18, titleFontSize=22, tickMinStep=1, values=[1, 2, 3, 4, 5, 6]), |
84 | | - ), |
85 | | - color=alt.Color( |
86 | | - "Team:N", |
87 | | - title="Team", |
88 | | - scale=alt.Scale(domain=df["Team"].unique().tolist(), range=colors), |
89 | | - legend=alt.Legend(labelFontSize=16, titleFontSize=18), |
90 | | - ), |
91 | | - ) |
| 85 | + .mark_line(strokeWidth=4) |
| 86 | + .encode(x=x, y=y, color=color, opacity=alt.condition(highlight, alt.value(1), alt.value(0.3))) |
| 87 | + .add_params(highlight) |
92 | 88 | ) |
93 | 89 |
|
94 | | -# Add points at each period for clarity |
| 90 | +# Points at each period for clarity |
95 | 91 | points = ( |
96 | 92 | alt.Chart(df) |
97 | | - .mark_point(size=250, filled=True, opacity=1) |
| 93 | + .mark_point(size=250, filled=True) |
98 | 94 | .encode( |
99 | 95 | x=alt.X("Week:O"), |
100 | 96 | y=alt.Y("Rank:Q", scale=alt.Scale(domain=[1, 6], reverse=True)), |
101 | | - color=alt.Color("Team:N", scale=alt.Scale(domain=df["Team"].unique().tolist(), range=colors)), |
| 97 | + color=alt.Color("Team:N", scale=alt.Scale(domain=teams, range=colors)), |
| 98 | + opacity=alt.condition(highlight, alt.value(1), alt.value(0.3)), |
102 | 99 | tooltip=["Team:N", "Week:O", "Rank:Q"], |
103 | 100 | ) |
104 | 101 | ) |
|
107 | 104 | chart = ( |
108 | 105 | (lines + points) |
109 | 106 | .properties(width=1600, height=900, title=alt.Title("bump-basic · altair · pyplots.ai", fontSize=28)) |
110 | | - .configure_axis(grid=True, gridOpacity=0.3, gridDash=[4, 4]) |
| 107 | + .configure_axis(grid=True, gridOpacity=0.2, gridDash=[4, 4]) |
111 | 108 | .configure_view(strokeWidth=0) |
112 | 109 | ) |
113 | 110 |
|
114 | 111 | # Save as PNG (1600 × 900 × 3 = 4800 × 2700) |
115 | 112 | chart.save("plot.png", scale_factor=3.0) |
116 | 113 |
|
117 | | -# Save interactive HTML version |
118 | | -chart.interactive().save("plot.html") |
| 114 | +# Save interactive HTML version (hover highlights team) |
| 115 | +chart.save("plot.html") |
0 commit comments