Skip to content

Commit faafb9c

Browse files
feat(highcharts): implement lollipop-basic (#5449)
## Implementation: `lollipop-basic` - python/highcharts Implements the **python/highcharts** version of `lollipop-basic`. **File:** `plots/lollipop-basic/implementations/python/highcharts.py` **Parent Issue:** #934 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/24957289019)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent f44e168 commit faafb9c

2 files changed

Lines changed: 253 additions & 172 deletions

File tree

Lines changed: 87 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
lollipop-basic: Basic Lollipop Chart
3-
Library: highcharts unknown | Python 3.13.11
4-
Quality: 92/100 | Created: 2025-12-23
3+
Library: highcharts unknown | Python 3.14.4
4+
Quality: 87/100 | Updated: 2026-04-26
55
"""
66

77
import json
8+
import os
89
import tempfile
910
import time
1011
import urllib.request
@@ -14,68 +15,113 @@
1415
from selenium.webdriver.chrome.options import Options
1516

1617

17-
# Data - Product sales by category, sorted by value for readability
18-
categories = ["Electronics", "Clothing", "Home & Garden", "Sports", "Books", "Toys", "Jewelry", "Automotive"]
19-
values = [8500, 6200, 5100, 4300, 3800, 2900, 2400, 1800]
18+
# Theme tokens (see prompts/default-style-guide.md "Theme-adaptive Chrome")
19+
THEME = os.getenv("ANYPLOT_THEME", "light")
20+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
21+
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
22+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
23+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
24+
GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)"
25+
BRAND = "#009E73" # Okabe-Ito position 1 — ALWAYS first series
2026

21-
# Sort data by value (descending) for better visualization
22-
sorted_data = sorted(zip(categories, values, strict=True), key=lambda x: x[1], reverse=True)
23-
categories = [item[0] for item in sorted_data]
24-
values = [item[1] for item in sorted_data]
27+
# Data — Product sales by category (deterministic, sorted descending)
28+
categories = [
29+
"Electronics",
30+
"Clothing",
31+
"Home & Garden",
32+
"Sports",
33+
"Books",
34+
"Toys",
35+
"Beauty",
36+
"Automotive",
37+
"Food & Grocery",
38+
"Health",
39+
]
40+
values = [124820, 97340, 86715, 75260, 64480, 53905, 47620, 41370, 37815, 30945]
2541

26-
# Chart options - using scatter for dots and column with very thin width for stems
42+
# Plot — combine column (thin stems) and scatter (round markers) for the lollipop look
2743
chart_options = {
2844
"chart": {
2945
"type": "scatter",
3046
"width": 4800,
3147
"height": 2700,
32-
"backgroundColor": "#ffffff",
33-
"marginBottom": 250,
34-
"style": {"fontFamily": "Arial, sans-serif"},
48+
"backgroundColor": PAGE_BG,
49+
"spacingTop": 60,
50+
"spacingBottom": 60,
51+
"spacingLeft": 60,
52+
"spacingRight": 80,
53+
"marginBottom": 220,
54+
"style": {"fontFamily": "Inter, system-ui, sans-serif", "color": INK},
55+
},
56+
"title": {
57+
"text": "Product Sales by Category · lollipop-basic · highcharts · anyplot.ai",
58+
"style": {"fontSize": "56px", "color": INK, "fontWeight": "500"},
59+
"margin": 40,
3560
},
36-
"title": {"text": "lollipop-basic · highcharts · pyplots.ai", "style": {"fontSize": "48px", "fontWeight": "bold"}},
3761
"xAxis": {
3862
"categories": categories,
39-
"title": {"text": "Product Category", "style": {"fontSize": "36px"}},
40-
"labels": {"style": {"fontSize": "28px"}},
63+
"title": {
64+
"text": "Product Category",
65+
"style": {"fontSize": "36px", "fontWeight": "500", "color": INK},
66+
"margin": 28,
67+
},
68+
"labels": {"style": {"fontSize": "28px", "color": INK_SOFT}, "y": 50},
69+
"lineColor": INK_SOFT,
70+
"tickColor": INK_SOFT,
71+
"gridLineWidth": 0,
4172
},
4273
"yAxis": {
4374
"min": 0,
44-
"title": {"text": "Sales (Units)", "style": {"fontSize": "36px"}},
45-
"labels": {"style": {"fontSize": "28px"}},
46-
"gridLineColor": "#e0e0e0",
47-
"gridLineDashStyle": "Dash",
75+
"tickAmount": 8,
76+
"title": {
77+
"text": "Sales (Units)",
78+
"style": {"fontSize": "36px", "fontWeight": "500", "color": INK},
79+
"margin": 28,
80+
},
81+
"labels": {"style": {"fontSize": "26px", "color": INK_SOFT}, "format": "{value:,.0f}", "x": -12},
82+
"gridLineColor": GRID,
83+
"gridLineWidth": 1,
84+
"lineColor": INK_SOFT,
85+
"tickColor": INK_SOFT,
4886
},
4987
"legend": {"enabled": False},
88+
"credits": {"enabled": False},
5089
"plotOptions": {
51-
"scatter": {"marker": {"radius": 20, "symbol": "circle"}},
52-
"column": {"pointWidth": 4, "borderWidth": 0},
90+
"scatter": {"marker": {"radius": 36, "symbol": "circle"}},
91+
"column": {"pointWidth": 8, "borderWidth": 0},
5392
},
5493
"series": [
55-
# Stems (thin columns from 0 to value)
56-
{"type": "column", "name": "Sales", "data": values, "color": "#306998", "enableMouseTracking": False},
57-
# Dots (scatter points at the top of each stem)
94+
{"type": "column", "name": "Sales", "data": values, "color": BRAND, "enableMouseTracking": False},
5895
{
5996
"type": "scatter",
6097
"name": "Sales",
6198
"data": values,
62-
"color": "#306998",
63-
"marker": {"radius": 20, "fillColor": "#306998", "lineWidth": 3, "lineColor": "#ffffff"},
99+
"color": BRAND,
100+
"marker": {"radius": 36, "fillColor": BRAND, "lineWidth": 5, "lineColor": PAGE_BG},
64101
"dataLabels": {
65102
"enabled": True,
66103
"format": "{y:,.0f}",
67-
"style": {"fontSize": "22px", "fontWeight": "bold"},
104+
"style": {"fontSize": "24px", "color": INK, "fontWeight": "500", "textOutline": "none"},
68105
"verticalAlign": "bottom",
69-
"y": -15,
106+
"y": -28,
70107
},
71108
},
72109
],
73110
}
74111

75-
# Download Highcharts JS for inline embedding
76-
highcharts_url = "https://code.highcharts.com/highcharts.js"
77-
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
78-
highcharts_js = response.read().decode("utf-8")
112+
# Download Highcharts JS for inline embedding (headless Chrome can't load CDN from file://)
113+
cdn_urls = ["https://code.highcharts.com/highcharts.js", "https://cdn.jsdelivr.net/npm/highcharts@11/highcharts.js"]
114+
highcharts_js = None
115+
for url in cdn_urls:
116+
try:
117+
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
118+
with urllib.request.urlopen(req, timeout=30) as response:
119+
highcharts_js = response.read().decode("utf-8")
120+
break
121+
except Exception:
122+
continue
123+
if highcharts_js is None:
124+
raise RuntimeError("Failed to download Highcharts JS from all CDN sources")
79125

80126
# Generate HTML with inline scripts
81127
chart_options_json = json.dumps(chart_options)
@@ -85,7 +131,7 @@
85131
<meta charset="utf-8">
86132
<script>{highcharts_js}</script>
87133
</head>
88-
<body style="margin:0;">
134+
<body style="margin:0; background:{PAGE_BG};">
89135
<div id="container" style="width: 4800px; height: 2700px;"></div>
90136
<script>
91137
document.addEventListener('DOMContentLoaded', function() {{
@@ -95,16 +141,15 @@
95141
</body>
96142
</html>"""
97143

98-
# Write temp HTML file
99-
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
144+
# Save HTML artifact (theme-suffixed)
145+
with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f:
100146
f.write(html_content)
101-
temp_path = f.name
102147

103-
# Also save the HTML for interactive viewing
104-
with open("plot.html", "w", encoding="utf-8") as f:
148+
# Render PNG via headless Chrome
149+
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
105150
f.write(html_content)
151+
temp_path = f.name
106152

107-
# Take screenshot with headless Chrome
108153
chrome_options = Options()
109154
chrome_options.add_argument("--headless")
110155
chrome_options.add_argument("--no-sandbox")
@@ -115,8 +160,7 @@
115160
driver = webdriver.Chrome(options=chrome_options)
116161
driver.get(f"file://{temp_path}")
117162
time.sleep(5)
118-
driver.save_screenshot("plot.png")
163+
driver.save_screenshot(f"plot-{THEME}.png")
119164
driver.quit()
120165

121-
# Clean up temp file
122166
Path(temp_path).unlink()

0 commit comments

Comments
 (0)