Skip to content

Commit cd0a4db

Browse files
Merge branch 'main' into implementation/bump-basic/highcharts
2 parents 7349d17 + f4ca12b commit cd0a4db

16 files changed

Lines changed: 1803 additions & 1306 deletions

File tree

Lines changed: 99 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,133 @@
11
""" pyplots.ai
22
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: 90/100 | Updated: 2026-02-22
55
"""
66

77
import altair as alt
88
import pandas as pd
99

1010

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 — the featured narrative
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: Volatile mid-table
37+
5,
38+
5,
39+
4,
40+
2,
41+
4,
42+
4,
43+
# Man United: Declines from 2nd to 5th
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+
]
6758

68-
df = pd.DataFrame(data)
59+
df = pd.DataFrame({"Team": [t for t in teams for _ in weeks], "Week": weeks * len(teams), "Rank": ranks})
6960

70-
# Define color palette (Python Blue first, then colorblind-safe colors)
71-
colors = ["#306998", "#FFD43B", "#E15759", "#59A14F", "#9C755F", "#BAB0AC"]
61+
# Cohesive palette Python Blue first, muted gold replaces bright yellow for harmony
62+
colors = ["#306998", "#EDC948", "#E15759", "#59A14F", "#B07AA1", "#76B7B2"]
7263

73-
# Create bump chart - lines connecting rankings
64+
# Interactive highlight — distinctive Altair feature
65+
highlight = alt.selection_point(fields=["Team"], on="pointerover")
66+
67+
# Arsenal predicate for visual hierarchy (data storytelling)
68+
is_arsenal = alt.datum.Team == "Arsenal"
69+
70+
# Shared encodings
71+
x = alt.X("Week:O", title="Match Week", axis=alt.Axis(labelFontSize=18, titleFontSize=22, labelAngle=0))
72+
y = alt.Y(
73+
"Rank:Q",
74+
title="League Position",
75+
scale=alt.Scale(domain=[1, 6], reverse=True),
76+
axis=alt.Axis(labelFontSize=18, titleFontSize=22, tickMinStep=1, values=[1, 2, 3, 4, 5, 6]),
77+
)
78+
color = alt.Color("Team:N", scale=alt.Scale(domain=teams, range=colors), legend=None)
79+
80+
# Lines — Arsenal emphasized with thicker stroke for visual hierarchy
7481
lines = (
7582
alt.Chart(df)
76-
.mark_line(strokeWidth=4, opacity=0.8)
83+
.mark_line()
7784
.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-
),
85+
x=x,
86+
y=y,
87+
color=color,
88+
strokeWidth=alt.condition(is_arsenal, alt.value(5), alt.value(2.5)),
89+
opacity=alt.condition(highlight, alt.value(1), alt.value(0.25)),
9190
)
91+
.add_params(highlight)
9292
)
9393

94-
# Add points at each period for clarity
94+
# Points — Arsenal gets larger markers for emphasis
9595
points = (
9696
alt.Chart(df)
97-
.mark_point(size=250, filled=True, opacity=1)
97+
.mark_point(filled=True)
9898
.encode(
9999
x=alt.X("Week:O"),
100100
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)),
101+
color=alt.Color("Team:N", scale=alt.Scale(domain=teams, range=colors)),
102+
size=alt.condition(is_arsenal, alt.value(350), alt.value(180)),
103+
opacity=alt.condition(highlight, alt.value(1), alt.value(0.25)),
102104
tooltip=["Team:N", "Week:O", "Rank:Q"],
103105
)
104106
)
105107

108+
# End-of-line labels for direct identification (replaces legend)
109+
last_week = df[df["Week"] == "Week 6"]
110+
labels = (
111+
alt.Chart(last_week)
112+
.mark_text(align="left", dx=14, fontSize=16)
113+
.encode(
114+
x=alt.X("Week:O"),
115+
y=alt.Y("Rank:Q", scale=alt.Scale(domain=[1, 6], reverse=True)),
116+
text="Team:N",
117+
color=alt.Color("Team:N", scale=alt.Scale(domain=teams, range=colors)),
118+
)
119+
)
120+
106121
# Combine layers and configure
107122
chart = (
108-
(lines + points)
123+
(lines + points + labels)
109124
.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])
125+
.configure_axis(grid=True, gridOpacity=0.2, gridDash=[4, 4])
111126
.configure_view(strokeWidth=0)
112127
)
113128

114-
# Save as PNG (1600 × 900 × 3 = 4800 × 2700)
129+
# Save as PNG (1600 × 900 × 3 4800 × 2700)
115130
chart.save("plot.png", scale_factor=3.0)
116131

117-
# Save interactive HTML version
118-
chart.interactive().save("plot.html")
132+
# Save interactive HTML version (hover highlights team)
133+
chart.save("plot.html")

plots/bump-basic/implementations/bokeh.py

Lines changed: 94 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,120 @@
11
""" pyplots.ai
22
bump-basic: Basic Bump Chart
3-
Library: bokeh 3.8.1 | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-23
3+
Library: bokeh 3.8.2 | Python 3.14.3
4+
Quality: 90/100 | Updated: 2026-02-22
55
"""
66

7-
from bokeh.io import export_png, output_file, save
8-
from bokeh.models import ColumnDataSource, Legend
9-
from bokeh.palettes import Category10
7+
from bokeh.io import export_png
8+
from bokeh.models import ColumnDataSource, CustomJSTickFormatter, FixedTicker, Label
109
from bokeh.plotting import figure
1110

1211

13-
# Data - Sports league standings over a season
14-
entities = ["Team Alpha", "Team Beta", "Team Gamma", "Team Delta", "Team Epsilon"]
15-
periods = ["Week 1", "Week 2", "Week 3", "Week 4", "Week 5", "Week 6"]
12+
# Data - Formula 1 constructor standings over a 6-race stretch
13+
entities = ["Red Bull Racing", "McLaren", "Ferrari", "Mercedes", "Aston Martin"]
14+
periods = ["Race 1", "Race 2", "Race 3", "Race 4", "Race 5", "Race 6"]
1615

17-
# Rankings for each team across weeks (1 = best)
16+
# Rankings for each team across races (1 = best)
1817
rankings = {
19-
"Team Alpha": [3, 2, 1, 1, 2, 1],
20-
"Team Beta": [1, 1, 2, 3, 3, 4],
21-
"Team Gamma": [5, 4, 4, 2, 1, 2],
22-
"Team Delta": [2, 3, 3, 4, 4, 3],
23-
"Team Epsilon": [4, 5, 5, 5, 5, 5],
18+
"Red Bull Racing": [3, 2, 1, 1, 2, 1],
19+
"McLaren": [1, 1, 2, 3, 3, 4],
20+
"Ferrari": [5, 4, 4, 2, 1, 2],
21+
"Mercedes": [2, 3, 3, 4, 4, 3],
22+
"Aston Martin": [4, 5, 5, 5, 5, 5],
2423
}
2524

26-
# Colors for each team
27-
colors = Category10[5]
25+
# Cohesive palette starting with Python Blue — colorblind-safe
26+
colors = ["#306998", "#E6894A", "#D44D5C", "#5BA67D", "#8B6DB0"]
27+
28+
# Emphasis: highlight the two teams with dramatic rank changes
29+
# Ferrari rises from 5th to 1st; McLaren falls from 1st to 4th
30+
highlight = {"Ferrari", "Red Bull Racing", "McLaren"}
2831

2932
# Create figure with inverted y-axis (rank 1 at top)
3033
p = figure(
3134
width=4800,
3235
height=2700,
3336
title="bump-basic · bokeh · pyplots.ai",
3437
x_range=periods,
35-
y_range=(5.5, 0.5), # Inverted: rank 1 at top
36-
x_axis_label="Week",
37-
y_axis_label="Rank Position",
38+
y_range=(5.8, 0.4),
39+
x_axis_label="Constructor Standings by Race",
40+
y_axis_label="Championship Position",
41+
toolbar_location=None,
3842
)
3943

40-
# Plot lines and markers for each entity
41-
legend_items = []
44+
# Plot lines and markers for each entity with visual hierarchy
4245
for i, (entity, ranks) in enumerate(rankings.items()):
43-
source = ColumnDataSource(data={"x": periods, "y": ranks})
44-
45-
# Draw connecting lines
46-
line = p.line(x="x", y="y", source=source, line_width=4, line_color=colors[i], line_alpha=0.8)
47-
48-
# Draw dot markers at each period
49-
scatter = p.scatter(x="x", y="y", source=source, size=20, color=colors[i], alpha=0.9)
50-
51-
legend_items.append((entity, [line, scatter]))
52-
53-
# Add legend outside the plot
54-
legend = Legend(items=legend_items, location="center")
55-
legend.label_text_font_size = "18pt"
56-
legend.spacing = 10
57-
p.add_layout(legend, "right")
58-
59-
# Style title
60-
p.title.text_font_size = "28pt"
61-
62-
# Style axes
63-
p.xaxis.axis_label_text_font_size = "22pt"
64-
p.yaxis.axis_label_text_font_size = "22pt"
65-
p.xaxis.major_label_text_font_size = "18pt"
66-
p.yaxis.major_label_text_font_size = "18pt"
67-
68-
# Grid styling
69-
p.xgrid.grid_line_alpha = 0.3
70-
p.ygrid.grid_line_alpha = 0.3
71-
p.xgrid.grid_line_dash = "dashed"
72-
p.ygrid.grid_line_dash = "dashed"
46+
source = ColumnDataSource(data={"x": periods, "y": ranks, "team": [entity] * len(periods)})
47+
48+
is_highlight = entity in highlight
49+
lw = 10 if is_highlight else 5
50+
alpha_line = 0.95 if is_highlight else 0.55
51+
alpha_marker = 1.0 if is_highlight else 0.6
52+
marker_size = 38 if is_highlight else 22
53+
54+
line = p.line(x="x", y="y", source=source, line_width=lw, line_color=colors[i], line_alpha=alpha_line)
55+
scatter = p.scatter(x="x", y="y", source=source, size=marker_size, color=colors[i], alpha=alpha_marker)
56+
57+
# End-of-line labels using Bokeh's Label annotation
58+
label = Label(
59+
x=5,
60+
y=ranks[-1],
61+
text=entity,
62+
text_font_size="20pt",
63+
text_color=colors[i],
64+
text_alpha=alpha_line,
65+
text_font_style="bold" if is_highlight else "normal",
66+
x_offset=18,
67+
y_offset=-8,
68+
)
69+
p.add_layout(label)
70+
71+
# Title styling
72+
p.title.text_font_size = "32pt"
73+
p.title.text_font_style = "bold"
74+
p.title.text_color = "#2c3e50"
75+
76+
# Axis styling
77+
p.xaxis.axis_label_text_font_size = "24pt"
78+
p.yaxis.axis_label_text_font_size = "24pt"
79+
p.xaxis.major_label_text_font_size = "20pt"
80+
p.yaxis.major_label_text_font_size = "20pt"
81+
p.xaxis.axis_label_text_color = "#555555"
82+
p.yaxis.axis_label_text_color = "#555555"
83+
p.xaxis.major_label_text_color = "#444444"
84+
p.yaxis.major_label_text_color = "#444444"
85+
86+
# Remove spines for clean look
87+
p.xaxis.axis_line_color = None
88+
p.yaxis.axis_line_color = None
89+
p.xaxis.major_tick_line_color = None
90+
p.yaxis.major_tick_line_color = None
91+
p.xaxis.minor_tick_line_color = None
92+
p.yaxis.minor_tick_line_color = None
93+
94+
# Grid styling - subtle dashed lines
95+
p.xgrid.grid_line_alpha = 0.15
96+
p.ygrid.grid_line_alpha = 0.25
97+
p.ygrid.grid_line_dash = [4, 4]
98+
99+
# Y-axis: FixedTicker at rank positions with CustomJSTickFormatter for ordinals
100+
p.yaxis.ticker = FixedTicker(ticks=[1, 2, 3, 4, 5])
101+
p.yaxis.formatter = CustomJSTickFormatter(
102+
code="""
103+
const suffixes = {1: 'st', 2: 'nd', 3: 'rd', 4: 'th', 5: 'th'};
104+
return tick + (suffixes[tick] || 'th');
105+
"""
106+
)
73107

74-
# Background styling
75-
p.background_fill_color = "#fafafa"
108+
# Background
109+
p.background_fill_color = "#f8f9fa"
76110
p.border_fill_color = "white"
111+
p.outline_line_color = None
112+
113+
# Generous padding for balanced layout
114+
p.min_border_left = 100
115+
p.min_border_right = 300
116+
p.min_border_top = 80
117+
p.min_border_bottom = 80
77118

78119
# Save as PNG
79120
export_png(p, filename="plot.png")
80-
81-
# Save as HTML (interactive version)
82-
output_file("plot.html")
83-
save(p)

0 commit comments

Comments
 (0)