Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 77 additions & 76 deletions plots/bar-race-animated/implementations/python/highcharts.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
""" pyplots.ai
""" anyplot.ai
bar-race-animated: Animated Bar Chart Race
Library: highcharts unknown | Python 3.13.11
Quality: 91/100 | Created: 2026-01-11
Library: highcharts unknown | Python 3.13.13
Quality: 83/100 | Updated: 2026-05-19
"""

import os
import tempfile
import time
import urllib.request
Expand All @@ -18,6 +19,27 @@
from selenium.webdriver.chrome.options import Options


# Theme
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"
GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)"

# Okabe-Ito palette (positions 1–7) + adaptive neutral for 8 entities
OI_COLORS = [
"#009E73", # position 1: bluish green
"#D55E00", # position 2: vermillion
"#0072B2", # position 3: blue
"#CC79A7", # position 4: reddish purple
"#E69F00", # position 5: orange
"#56B4E9", # position 6: sky blue
"#F0E442", # position 7: yellow
"#1A1A1A" if THEME == "light" else "#E8E8E0", # position 8: adaptive neutral
]

# Data - Global Technology Companies Market Value (in $B) 2019-2024
np.random.seed(42)

Expand All @@ -30,93 +52,73 @@
"QuantumByte",
"NetPrime",
"DigiWave",
"SmartSystems",
"ByteForce",
"NexGen",
"CoreLogic",
]

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

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

df = pd.DataFrame(data)

# Colors - consistent per company
colors = [
"#306998",
"#FFD43B",
"#9467BD",
"#17BECF",
"#8C564B",
"#E377C2",
"#7F7F7F",
"#BCBD22",
"#1F77B4",
"#FF7F0E",
"#2CA02C",
"#D62728",
]
company_colors = dict(zip(companies, colors, strict=False))

# Select 6 key time snapshots for small multiples grid (2x3)
snapshot_years = years
company_colors = dict(zip(companies, OI_COLORS, strict=True))

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

# Generate individual charts for each year
# Generate individual charts for each year snapshot
CHART_W = 1550
CHART_H = 1150
max_val = df["value"].max() * 1.1

chart_scripts = []
for idx, year in enumerate(snapshot_years):
year_data = df[df["year"] == year].sort_values("value", ascending=True).tail(10)
for idx, year in enumerate(years):
year_data = df[df["year"] == year].sort_values("value", ascending=True)

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

chart.options.chart = {
"type": "bar",
"width": 1520,
"height": 1280,
"backgroundColor": "#ffffff",
"marginLeft": 200,
"marginRight": 50,
"width": CHART_W,
"height": CHART_H,
"backgroundColor": ELEVATED_BG,
"marginLeft": 230,
"marginRight": 110,
"marginBottom": 80,
"marginTop": 100,
"marginTop": 80,
}

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

chart.options.x_axis = {
"categories": year_data["company"].tolist(),
"title": {"text": None},
"labels": {"style": {"fontSize": "20px"}},
"labels": {"style": {"fontSize": "22px", "color": INK_SOFT}},
"lineColor": INK_SOFT,
"tickColor": INK_SOFT,
}

chart.options.y_axis = {
"title": {"text": "Market Value ($B)", "style": {"fontSize": "20px"}},
"labels": {"style": {"fontSize": "18px"}},
"title": {"text": "Market Value ($B)", "style": {"fontSize": "22px", "color": INK}},
"labels": {"style": {"fontSize": "18px", "color": INK_SOFT}},
"min": 0,
"max": 400,
"max": max_val,
"gridLineColor": GRID,
}

chart.options.legend = {"enabled": False}

chart.options.credits = {"enabled": False}

# Create series with individual colors
series = BarSeries()
series.name = "Market Value"
series.data = [
Expand All @@ -125,16 +127,16 @@
series.data_labels = {
"enabled": True,
"format": "${point.y:.0f}B",
"style": {"fontSize": "16px", "fontWeight": "normal"},
"style": {"fontSize": "19px", "fontWeight": "normal", "color": INK, "textOutline": "none"},
}

chart.add_series(series)

chart.options.plot_options = {"bar": {"borderWidth": 0, "pointWidth": 50}}
chart.options.plot_options = {"bar": {"borderWidth": 0, "pointWidth": 58}}

chart_scripts.append(chart.to_js_literal())

# Create combined HTML with 2x3 grid
# Assemble full-page HTML with 2×3 small-multiples grid
GRID_GAP = 28
html_content = f"""<!DOCTYPE html>
<html>
<head>
Expand All @@ -144,41 +146,40 @@
body {{
margin: 0;
padding: 40px;
background: #ffffff;
background: {PAGE_BG};
font-family: Arial, sans-serif;
}}
.main-title {{
text-align: center;
font-size: 48px;
font-size: 52px;
font-weight: bold;
margin-bottom: 10px;
color: #333;
margin-bottom: 8px;
color: {INK};
}}
.subtitle {{
text-align: center;
font-size: 28px;
color: #666;
margin-bottom: 40px;
font-size: 30px;
color: {INK_MUTED};
margin-bottom: 36px;
}}
.grid {{
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(2, 1fr);
gap: 30px;
width: 4720px;
grid-template-columns: repeat(3, {CHART_W}px);
grid-template-rows: repeat(2, {CHART_H}px);
gap: {GRID_GAP}px;
width: {3 * CHART_W + 2 * GRID_GAP}px;
margin: 0 auto;
}}
.chart-container {{
background: #fafafa;
border: 2px solid #e0e0e0;
border-radius: 10px;
background: {ELEVATED_BG};
border-radius: 8px;
overflow: hidden;
}}
</style>
</head>
<body>
<div class="main-title">bar-race-animated · highcharts · pyplots.ai</div>
<div class="subtitle">Technology Companies Market Value Evolution (2019-2024)</div>
<div class="main-title">bar-race-animated · python · highcharts · anyplot.ai</div>
<div class="subtitle">Technology Companies Market Value Evolution (20192024)</div>
<div class="grid">
<div class="chart-container"><div id="chart0"></div></div>
<div class="chart-container"><div id="chart1"></div></div>
Expand All @@ -198,14 +199,14 @@
</body>
</html>"""

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

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

chrome_options = Options()
chrome_options.add_argument("--headless")
Expand All @@ -216,8 +217,8 @@

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

Path(temp_path).unlink()
Loading
Loading