Skip to content

Commit a7a4d56

Browse files
feat(highcharts): implement scatter-categorical (#2633)
## Implementation: `scatter-categorical` - highcharts Implements the **highcharts** version of `scatter-categorical`. **File:** `plots/scatter-categorical/implementations/highcharts.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20594558958)* --------- 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 965aa31 commit a7a4d56

2 files changed

Lines changed: 184 additions & 0 deletions

File tree

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
""" pyplots.ai
2+
scatter-categorical: Categorical Scatter Plot
3+
Library: highcharts unknown | Python 3.13.11
4+
Quality: 92/100 | Created: 2025-12-30
5+
"""
6+
7+
import tempfile
8+
import time
9+
import urllib.request
10+
from pathlib import Path
11+
12+
import numpy as np
13+
from highcharts_core.chart import Chart
14+
from highcharts_core.options import HighchartsOptions
15+
from highcharts_core.options.series.scatter import ScatterSeries
16+
from selenium import webdriver
17+
from selenium.webdriver.chrome.options import Options
18+
19+
20+
# Data - Plant growth study with three fertilizer types
21+
np.random.seed(42)
22+
23+
categories = ["Fertilizer A", "Fertilizer B", "Fertilizer C"]
24+
colors = ["#306998", "#FFD43B", "#9467BD"] # Python Blue, Python Yellow, Purple
25+
26+
# Generate data for each category with different patterns
27+
data_by_category = {}
28+
n_points = 40
29+
30+
# Fertilizer A: moderate growth, medium variance
31+
nitrogen_a = np.random.uniform(20, 80, n_points)
32+
growth_a = 0.4 * nitrogen_a + np.random.normal(10, 5, n_points)
33+
data_by_category["Fertilizer A"] = list(zip(nitrogen_a.tolist(), growth_a.tolist(), strict=True))
34+
35+
# Fertilizer B: high growth, low variance (best performer)
36+
nitrogen_b = np.random.uniform(25, 85, n_points)
37+
growth_b = 0.6 * nitrogen_b + np.random.normal(15, 3, n_points)
38+
data_by_category["Fertilizer B"] = list(zip(nitrogen_b.tolist(), growth_b.tolist(), strict=True))
39+
40+
# Fertilizer C: lower growth, higher variance
41+
nitrogen_c = np.random.uniform(15, 75, n_points)
42+
growth_c = 0.3 * nitrogen_c + np.random.normal(5, 8, n_points)
43+
data_by_category["Fertilizer C"] = list(zip(nitrogen_c.tolist(), growth_c.tolist(), strict=True))
44+
45+
# Create chart
46+
chart = Chart(container="container")
47+
chart.options = HighchartsOptions()
48+
49+
# Chart configuration
50+
chart.options.chart = {
51+
"type": "scatter",
52+
"width": 4800,
53+
"height": 2700,
54+
"backgroundColor": "#ffffff",
55+
"marginBottom": 300,
56+
"spacingBottom": 80,
57+
}
58+
59+
# Title
60+
chart.options.title = {
61+
"text": "scatter-categorical · highcharts · pyplots.ai",
62+
"style": {"fontSize": "48px", "fontWeight": "bold"},
63+
}
64+
65+
# Axes
66+
chart.options.x_axis = {
67+
"title": {"text": "Nitrogen Applied (kg/ha)", "style": {"fontSize": "36px"}, "margin": 30},
68+
"labels": {"style": {"fontSize": "28px"}},
69+
"gridLineWidth": 1,
70+
"gridLineColor": "rgba(0, 0, 0, 0.1)",
71+
}
72+
73+
chart.options.y_axis = {
74+
"title": {"text": "Plant Growth (cm)", "style": {"fontSize": "36px"}},
75+
"labels": {"style": {"fontSize": "28px"}},
76+
"gridLineWidth": 1,
77+
"gridLineColor": "rgba(0, 0, 0, 0.1)",
78+
}
79+
80+
# Legend
81+
chart.options.legend = {
82+
"enabled": True,
83+
"align": "right",
84+
"verticalAlign": "top",
85+
"layout": "vertical",
86+
"x": -50,
87+
"y": 100,
88+
"itemStyle": {"fontSize": "28px"},
89+
}
90+
91+
# Plot options for scatter series
92+
chart.options.plot_options = {
93+
"scatter": {"marker": {"radius": 12, "symbol": "circle"}, "states": {"hover": {"marker": {"enabled": True}}}}
94+
}
95+
96+
# Add series for each category
97+
for i, category in enumerate(categories):
98+
series = ScatterSeries()
99+
series.name = category
100+
series.data = data_by_category[category]
101+
series.color = colors[i]
102+
series.marker = {"radius": 12, "fillColor": colors[i], "lineWidth": 2, "lineColor": "#ffffff"}
103+
chart.add_series(series)
104+
105+
# Download Highcharts JS
106+
highcharts_url = "https://code.highcharts.com/highcharts.js"
107+
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
108+
highcharts_js = response.read().decode("utf-8")
109+
110+
# Generate HTML with inline scripts
111+
html_str = chart.to_js_literal()
112+
html_content = f"""<!DOCTYPE html>
113+
<html>
114+
<head>
115+
<meta charset="utf-8">
116+
<script>{highcharts_js}</script>
117+
</head>
118+
<body style="margin:0;">
119+
<div id="container" style="width: 4800px; height: 2700px;"></div>
120+
<script>{html_str}</script>
121+
</body>
122+
</html>"""
123+
124+
# Write temp HTML and take screenshot
125+
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
126+
f.write(html_content)
127+
temp_path = f.name
128+
129+
chrome_options = Options()
130+
chrome_options.add_argument("--headless")
131+
chrome_options.add_argument("--no-sandbox")
132+
chrome_options.add_argument("--disable-dev-shm-usage")
133+
chrome_options.add_argument("--disable-gpu")
134+
chrome_options.add_argument("--window-size=4800,2700")
135+
136+
driver = webdriver.Chrome(options=chrome_options)
137+
driver.get(f"file://{temp_path}")
138+
time.sleep(5)
139+
driver.save_screenshot("plot.png")
140+
driver.quit()
141+
142+
Path(temp_path).unlink()
143+
144+
# Also save HTML for interactive version
145+
with open("plot.html", "w", encoding="utf-8") as f:
146+
interactive_html = f"""<!DOCTYPE html>
147+
<html>
148+
<head>
149+
<meta charset="utf-8">
150+
<title>scatter-categorical · highcharts · pyplots.ai</title>
151+
<script src="https://code.highcharts.com/highcharts.js"></script>
152+
</head>
153+
<body style="margin:0; display:flex; justify-content:center; align-items:center; min-height:100vh; background:#f5f5f5;">
154+
<div id="container" style="width: 1200px; height: 675px;"></div>
155+
<script>{html_str}</script>
156+
</body>
157+
</html>"""
158+
f.write(interactive_html)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
library: highcharts
2+
specification_id: scatter-categorical
3+
created: '2025-12-30T10:40:05Z'
4+
updated: '2025-12-30T10:46:20Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20594558958
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: unknown
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/scatter-categorical/highcharts/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/scatter-categorical/highcharts/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/scatter-categorical/highcharts/plot.html
13+
quality_score: 92
14+
review:
15+
strengths:
16+
- Excellent colorblind-safe palette (blue/yellow/purple)
17+
- Clear visual separation between categories with distinct patterns
18+
- Proper title format following pyplots.ai convention
19+
- Realistic plant growth study scenario with meaningful data
20+
- Good marker sizing with white borders for distinction
21+
- Both static PNG and interactive HTML outputs generated
22+
weaknesses:
23+
- Interactive HTML reuses the 4800x2700 chart JS literal instead of regenerating
24+
at smaller size, causing potential sizing issues
25+
- No alpha transparency on markers despite spec suggestion for overlapping points
26+
- Legend positioned at fixed pixel offset rather than responsive positioning

0 commit comments

Comments
 (0)