Skip to content

Commit ed7db28

Browse files
feat(highcharts): implement bar-race-animated (#7327)
## Implementation: `bar-race-animated` - python/highcharts Implements the **python/highcharts** version of `bar-race-animated`. **File:** `plots/bar-race-animated/implementations/python/highcharts.py` **Parent Issue:** #3653 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/26071381995)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com>
1 parent 8bae28f commit ed7db28

2 files changed

Lines changed: 231 additions & 199 deletions

File tree

Lines changed: 77 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
bar-race-animated: Animated Bar Chart Race
3-
Library: highcharts unknown | Python 3.13.11
4-
Quality: 91/100 | Created: 2026-01-11
3+
Library: highcharts unknown | Python 3.13.13
4+
Quality: 83/100 | Updated: 2026-05-19
55
"""
66

7+
import os
78
import tempfile
89
import time
910
import urllib.request
@@ -18,6 +19,27 @@
1819
from selenium.webdriver.chrome.options import Options
1920

2021

22+
# Theme
23+
THEME = os.getenv("ANYPLOT_THEME", "light")
24+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
25+
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
26+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
27+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
28+
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"
29+
GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)"
30+
31+
# Okabe-Ito palette (positions 1–7) + adaptive neutral for 8 entities
32+
OI_COLORS = [
33+
"#009E73", # position 1: bluish green
34+
"#D55E00", # position 2: vermillion
35+
"#0072B2", # position 3: blue
36+
"#CC79A7", # position 4: reddish purple
37+
"#E69F00", # position 5: orange
38+
"#56B4E9", # position 6: sky blue
39+
"#F0E442", # position 7: yellow
40+
"#1A1A1A" if THEME == "light" else "#E8E8E0", # position 8: adaptive neutral
41+
]
42+
2143
# Data - Global Technology Companies Market Value (in $B) 2019-2024
2244
np.random.seed(42)
2345

@@ -30,93 +52,73 @@
3052
"QuantumByte",
3153
"NetPrime",
3254
"DigiWave",
33-
"SmartSystems",
34-
"ByteForce",
35-
"NexGen",
36-
"CoreLogic",
3755
]
3856

3957
years = [2019, 2020, 2021, 2022, 2023, 2024]
4058

41-
# Generate realistic market value evolution
42-
base_values = np.array([180, 150, 120, 100, 90, 80, 70, 60, 50, 45, 40, 35])
59+
base_values = np.array([180, 150, 120, 100, 90, 80, 70, 60])
4360
data = []
4461
for i, year in enumerate(years):
4562
growth = 1 + 0.15 * i + np.random.randn(len(companies)) * 0.2
4663
values = base_values * growth * (1 + np.random.randn(len(companies)) * 0.1)
47-
# Add some shuffling over time to make rankings change
4864
shuffle_factor = np.random.randn(len(companies)) * (20 + i * 10)
4965
values = values + shuffle_factor
50-
values = np.maximum(values, 10) # Minimum value
66+
values = np.maximum(values, 10)
5167
for j, company in enumerate(companies):
5268
data.append({"company": company, "year": year, "value": values[j]})
5369

5470
df = pd.DataFrame(data)
55-
56-
# Colors - consistent per company
57-
colors = [
58-
"#306998",
59-
"#FFD43B",
60-
"#9467BD",
61-
"#17BECF",
62-
"#8C564B",
63-
"#E377C2",
64-
"#7F7F7F",
65-
"#BCBD22",
66-
"#1F77B4",
67-
"#FF7F0E",
68-
"#2CA02C",
69-
"#D62728",
70-
]
71-
company_colors = dict(zip(companies, colors, strict=False))
72-
73-
# Select 6 key time snapshots for small multiples grid (2x3)
74-
snapshot_years = years
71+
company_colors = dict(zip(companies, OI_COLORS, strict=True))
7572

7673
# Download Highcharts JS
77-
highcharts_url = "https://code.highcharts.com/highcharts.js"
78-
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
74+
highcharts_url = "https://cdn.jsdelivr.net/npm/highcharts@12/highcharts.js"
75+
with urllib.request.urlopen(highcharts_url, timeout=60) as response:
7976
highcharts_js = response.read().decode("utf-8")
8077

81-
# Generate individual charts for each year
78+
# Generate individual charts for each year snapshot
79+
CHART_W = 1550
80+
CHART_H = 1150
81+
max_val = df["value"].max() * 1.1
82+
8283
chart_scripts = []
83-
for idx, year in enumerate(snapshot_years):
84-
year_data = df[df["year"] == year].sort_values("value", ascending=True).tail(10)
84+
for idx, year in enumerate(years):
85+
year_data = df[df["year"] == year].sort_values("value", ascending=True)
8586

8687
chart = Chart(container=f"chart{idx}")
8788
chart.options = HighchartsOptions()
8889

8990
chart.options.chart = {
9091
"type": "bar",
91-
"width": 1520,
92-
"height": 1280,
93-
"backgroundColor": "#ffffff",
94-
"marginLeft": 200,
95-
"marginRight": 50,
92+
"width": CHART_W,
93+
"height": CHART_H,
94+
"backgroundColor": ELEVATED_BG,
95+
"marginLeft": 230,
96+
"marginRight": 110,
9697
"marginBottom": 80,
97-
"marginTop": 100,
98+
"marginTop": 80,
9899
}
99100

100-
chart.options.title = {"text": f"Year {year}", "style": {"fontSize": "32px", "fontWeight": "bold"}}
101+
chart.options.title = {"text": str(year), "style": {"fontSize": "38px", "fontWeight": "bold", "color": INK}}
101102

102103
chart.options.x_axis = {
103104
"categories": year_data["company"].tolist(),
104105
"title": {"text": None},
105-
"labels": {"style": {"fontSize": "20px"}},
106+
"labels": {"style": {"fontSize": "22px", "color": INK_SOFT}},
107+
"lineColor": INK_SOFT,
108+
"tickColor": INK_SOFT,
106109
}
107110

108111
chart.options.y_axis = {
109-
"title": {"text": "Market Value ($B)", "style": {"fontSize": "20px"}},
110-
"labels": {"style": {"fontSize": "18px"}},
112+
"title": {"text": "Market Value ($B)", "style": {"fontSize": "22px", "color": INK}},
113+
"labels": {"style": {"fontSize": "18px", "color": INK_SOFT}},
111114
"min": 0,
112-
"max": 400,
115+
"max": max_val,
116+
"gridLineColor": GRID,
113117
}
114118

115119
chart.options.legend = {"enabled": False}
116-
117120
chart.options.credits = {"enabled": False}
118121

119-
# Create series with individual colors
120122
series = BarSeries()
121123
series.name = "Market Value"
122124
series.data = [
@@ -125,16 +127,16 @@
125127
series.data_labels = {
126128
"enabled": True,
127129
"format": "${point.y:.0f}B",
128-
"style": {"fontSize": "16px", "fontWeight": "normal"},
130+
"style": {"fontSize": "19px", "fontWeight": "normal", "color": INK, "textOutline": "none"},
129131
}
130-
131132
chart.add_series(series)
132133

133-
chart.options.plot_options = {"bar": {"borderWidth": 0, "pointWidth": 50}}
134+
chart.options.plot_options = {"bar": {"borderWidth": 0, "pointWidth": 58}}
134135

135136
chart_scripts.append(chart.to_js_literal())
136137

137-
# Create combined HTML with 2x3 grid
138+
# Assemble full-page HTML with 2×3 small-multiples grid
139+
GRID_GAP = 28
138140
html_content = f"""<!DOCTYPE html>
139141
<html>
140142
<head>
@@ -144,41 +146,40 @@
144146
body {{
145147
margin: 0;
146148
padding: 40px;
147-
background: #ffffff;
149+
background: {PAGE_BG};
148150
font-family: Arial, sans-serif;
149151
}}
150152
.main-title {{
151153
text-align: center;
152-
font-size: 48px;
154+
font-size: 52px;
153155
font-weight: bold;
154-
margin-bottom: 10px;
155-
color: #333;
156+
margin-bottom: 8px;
157+
color: {INK};
156158
}}
157159
.subtitle {{
158160
text-align: center;
159-
font-size: 28px;
160-
color: #666;
161-
margin-bottom: 40px;
161+
font-size: 30px;
162+
color: {INK_MUTED};
163+
margin-bottom: 36px;
162164
}}
163165
.grid {{
164166
display: grid;
165-
grid-template-columns: repeat(3, 1fr);
166-
grid-template-rows: repeat(2, 1fr);
167-
gap: 30px;
168-
width: 4720px;
167+
grid-template-columns: repeat(3, {CHART_W}px);
168+
grid-template-rows: repeat(2, {CHART_H}px);
169+
gap: {GRID_GAP}px;
170+
width: {3 * CHART_W + 2 * GRID_GAP}px;
169171
margin: 0 auto;
170172
}}
171173
.chart-container {{
172-
background: #fafafa;
173-
border: 2px solid #e0e0e0;
174-
border-radius: 10px;
174+
background: {ELEVATED_BG};
175+
border-radius: 8px;
175176
overflow: hidden;
176177
}}
177178
</style>
178179
</head>
179180
<body>
180-
<div class="main-title">bar-race-animated · highcharts · pyplots.ai</div>
181-
<div class="subtitle">Technology Companies Market Value Evolution (2019-2024)</div>
181+
<div class="main-title">bar-race-animated · python · highcharts · anyplot.ai</div>
182+
<div class="subtitle">Technology Companies Market Value Evolution (20192024)</div>
182183
<div class="grid">
183184
<div class="chart-container"><div id="chart0"></div></div>
184185
<div class="chart-container"><div id="chart1"></div></div>
@@ -198,14 +199,14 @@
198199
</body>
199200
</html>"""
200201

201-
# Write temp HTML and take screenshot
202-
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
202+
# Save HTML artifact
203+
with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f:
203204
f.write(html_content)
204-
temp_path = f.name
205205

206-
# Also save as plot.html for interactive version
207-
with open("plot.html", "w", encoding="utf-8") as f:
206+
# Screenshot via headless Chrome
207+
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
208208
f.write(html_content)
209+
temp_path = f.name
209210

210211
chrome_options = Options()
211212
chrome_options.add_argument("--headless")
@@ -216,8 +217,8 @@
216217

217218
driver = webdriver.Chrome(options=chrome_options)
218219
driver.get(f"file://{temp_path}")
219-
time.sleep(8) # Wait for all charts to render
220-
driver.save_screenshot("plot.png")
220+
time.sleep(8)
221+
driver.save_screenshot(f"plot-{THEME}.png")
221222
driver.quit()
222223

223224
Path(temp_path).unlink()

0 commit comments

Comments
 (0)