Skip to content

Commit 03ded89

Browse files
update(scatter-basic): highcharts — comprehensive quality review (#4206)
## Summary Updated **LIB_PLACEHOLDER** implementation for **scatter-basic**. **Changes:** Comprehensive quality review — improved variable naming, realistic data context, white marker edges for point definition, subtler grid styling, and explicit font sizing for large canvas. ### Changes - Descriptive variable names with realistic data context - White marker edges for better point definition - Subtler grid styling (opacity ~0.15-0.2) - Explicit font sizing for 4800x2700 canvas - Library-specific improvements for idiomatic usage ## Test Plan - [x] Preview images uploaded to GCS staging - [x] Implementation file passes ruff format/check - [x] Metadata YAML updated with current versions - [ ] Automated review triggered --- Generated with [Claude Code](https://claude.com/claude-code) `/update` command --------- 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 28edc66 commit 03ded89

File tree

2 files changed

+363
-153
lines changed

2 files changed

+363
-153
lines changed

plots/scatter-basic/implementations/highcharts.py

Lines changed: 223 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
""" pyplots.ai
22
scatter-basic: Basic Scatter Plot
3-
Library: highcharts unknown | Python 3.13.11
4-
Quality: 92/100 | Created: 2025-12-22
3+
Library: highcharts 1.10.3 | Python 3.14
4+
Quality: 91/100 | Created: 2025-12-22
55
"""
66

77
import tempfile
@@ -12,17 +12,40 @@
1212
import numpy as np
1313
from highcharts_core.chart import Chart
1414
from highcharts_core.options import HighchartsOptions
15+
from highcharts_core.options.annotations import Annotation
1516
from highcharts_core.options.series.scatter import ScatterSeries
17+
from highcharts_core.options.series.spline import SplineSeries
1618
from selenium import webdriver
1719
from selenium.webdriver.chrome.options import Options
1820

1921

20-
# Data
22+
# Data — height vs weight with moderate positive correlation
2123
np.random.seed(42)
22-
x = np.random.randn(100) * 2 + 10
23-
y = x * 0.8 + np.random.randn(100) * 2
24+
n_points = 100
25+
height_cm = np.random.normal(170, 10, n_points)
26+
weight_kg = height_cm * 0.65 + np.random.normal(0, 5, n_points) - 40
2427

25-
# Create chart
28+
# Compute linear regression for trend line
29+
slope, intercept = np.polyfit(height_cm, weight_kg, 1)
30+
r_squared = np.corrcoef(height_cm, weight_kg)[0, 1] ** 2
31+
32+
# Axis bounds — tight to data with small padding
33+
x_min, x_max = float(np.floor(height_cm.min() - 2)), float(np.ceil(height_cm.max() + 2))
34+
y_min, y_max = float(np.floor(weight_kg.min() - 3)), float(np.ceil(weight_kg.max() + 3))
35+
36+
# Trend line endpoints
37+
trend_x = np.array([x_min, x_max])
38+
trend_y = slope * trend_x + intercept
39+
40+
# Identify outlier points (beyond 2 std from regression line)
41+
predicted = slope * height_cm + intercept
42+
residuals = weight_kg - predicted
43+
std_resid = np.std(residuals)
44+
outlier_mask = np.abs(residuals) > 1.8 * std_resid
45+
outlier_heights = height_cm[outlier_mask]
46+
outlier_weights = weight_kg[outlier_mask]
47+
48+
# Create chart with typed API
2649
chart = Chart(container="container")
2750
chart.options = HighchartsOptions()
2851

@@ -31,49 +54,212 @@
3154
"type": "scatter",
3255
"width": 4800,
3356
"height": 2700,
34-
"backgroundColor": "#ffffff",
35-
"marginBottom": 150,
57+
"backgroundColor": "#fafbfc",
58+
"style": {"fontFamily": "'Segoe UI', Helvetica, Arial, sans-serif"},
59+
"marginTop": 160,
60+
"marginBottom": 310,
61+
"marginLeft": 220,
62+
"marginRight": 200,
3663
}
3764

38-
# Title (required format: spec-id · library · pyplots.ai)
65+
# Title with refined typography
3966
chart.options.title = {
40-
"text": "scatter-basic · highcharts · pyplots.ai",
41-
"style": {"fontSize": "72px", "fontWeight": "bold"},
67+
"text": "scatter-basic \u00b7 highcharts \u00b7 pyplots.ai",
68+
"style": {"fontSize": "64px", "fontWeight": "600", "color": "#2c3e50", "letterSpacing": "1px"},
69+
"margin": 50,
4270
}
4371

44-
# Axes (scaled for 4800x2700 px)
72+
# Subtitle for data storytelling
73+
chart.options.subtitle = {
74+
"text": "Height vs Weight — positive correlation across 100 subjects",
75+
"style": {"fontSize": "38px", "color": "#7f8c8d", "fontWeight": "400"},
76+
}
77+
78+
# X-axis with tight bounds and refined styling
4579
chart.options.x_axis = {
46-
"title": {"text": "X Value", "style": {"fontSize": "48px"}},
47-
"labels": {"style": {"fontSize": "36px"}},
80+
"title": {
81+
"text": "Height (cm)",
82+
"style": {"fontSize": "44px", "color": "#34495e", "fontWeight": "500"},
83+
"margin": 30,
84+
},
85+
"labels": {"style": {"fontSize": "34px", "color": "#7f8c8d"}},
86+
"min": x_min,
87+
"max": x_max,
88+
"tickInterval": 5,
89+
"startOnTick": False,
90+
"endOnTick": False,
4891
"gridLineWidth": 1,
49-
"gridLineColor": "rgba(0, 0, 0, 0.15)",
50-
"gridLineDashStyle": "Dash",
92+
"gridLineColor": "rgba(0, 0, 0, 0.06)",
93+
"gridLineDashStyle": "Dot",
94+
"lineColor": "#bdc3c7",
95+
"lineWidth": 2,
96+
"tickColor": "#bdc3c7",
97+
"tickLength": 10,
5198
}
99+
100+
# Y-axis with tight bounds and reduced tick density
52101
chart.options.y_axis = {
53-
"title": {"text": "Y Value", "style": {"fontSize": "48px"}},
54-
"labels": {"style": {"fontSize": "36px"}},
102+
"title": {
103+
"text": "Weight (kg)",
104+
"style": {"fontSize": "44px", "color": "#34495e", "fontWeight": "500"},
105+
"margin": 30,
106+
},
107+
"labels": {"style": {"fontSize": "34px", "color": "#7f8c8d"}},
108+
"min": y_min,
109+
"max": y_max,
110+
"tickInterval": 5,
111+
"startOnTick": False,
112+
"endOnTick": False,
55113
"gridLineWidth": 1,
56-
"gridLineColor": "rgba(0, 0, 0, 0.15)",
57-
"gridLineDashStyle": "Dash",
114+
"gridLineColor": "rgba(0, 0, 0, 0.06)",
115+
"gridLineDashStyle": "Dot",
116+
"lineColor": "#bdc3c7",
117+
"lineWidth": 2,
118+
"tickColor": "#bdc3c7",
119+
"tickLength": 10,
120+
"plotBands": [
121+
{
122+
"from": y_min,
123+
"to": float(np.percentile(weight_kg, 25)),
124+
"color": "rgba(48, 105, 152, 0.03)",
125+
"label": {
126+
"text": "Lower quartile",
127+
"style": {"fontSize": "32px", "color": "rgba(48, 105, 152, 0.55)"},
128+
"align": "left",
129+
"x": 20,
130+
"y": 16,
131+
},
132+
},
133+
{
134+
"from": float(np.percentile(weight_kg, 75)),
135+
"to": y_max,
136+
"color": "rgba(48, 105, 152, 0.03)",
137+
"label": {
138+
"text": "Upper quartile",
139+
"style": {"fontSize": "32px", "color": "rgba(48, 105, 152, 0.55)"},
140+
"align": "left",
141+
"x": 20,
142+
"y": 16,
143+
},
144+
},
145+
],
146+
}
147+
148+
# Legend — show to label trend line
149+
chart.options.legend = {
150+
"enabled": True,
151+
"align": "right",
152+
"verticalAlign": "top",
153+
"layout": "vertical",
154+
"x": -40,
155+
"y": 80,
156+
"floating": True,
157+
"backgroundColor": "rgba(255, 255, 255, 0.85)",
158+
"borderWidth": 1,
159+
"borderColor": "#e0e0e0",
160+
"borderRadius": 8,
161+
"itemStyle": {"fontSize": "30px", "fontWeight": "400", "color": "#34495e"},
162+
"padding": 16,
163+
"symbolRadius": 6,
58164
}
59165

60-
# Legend and credits
61-
chart.options.legend = {"enabled": False}
62166
chart.options.credits = {"enabled": False}
63167

64-
# Create scatter series with Python Blue color and transparency
65-
series = ScatterSeries()
66-
series.data = [[float(xi), float(yi)] for xi, yi in zip(x, y, strict=True)]
67-
series.name = "Data"
68-
series.color = "rgba(48, 105, 152, 0.7)" # Python Blue with alpha
69-
series.marker = {"radius": 18, "symbol": "circle"} # Larger markers for 4800x2700
168+
# Rich tooltip — Highcharts-distinctive feature
169+
chart.options.tooltip = {
170+
"headerFormat": "",
171+
"pointFormat": (
172+
'<span style="font-size:24px;color:{point.color}">\u25cf</span> '
173+
'<span style="font-size:26px">'
174+
"Height: <b>{point.x:.1f} cm</b><br/>"
175+
"Weight: <b>{point.y:.1f} kg</b></span>"
176+
),
177+
"backgroundColor": "rgba(255, 255, 255, 0.95)",
178+
"borderColor": "#306998",
179+
"borderRadius": 10,
180+
"borderWidth": 2,
181+
"shadow": {"color": "rgba(0,0,0,0.1)", "offsetX": 2, "offsetY": 2, "width": 4},
182+
"style": {"fontSize": "26px"},
183+
}
184+
185+
# Main scatter series — Python Blue with transparency
186+
scatter = ScatterSeries()
187+
scatter.data = [[float(h), float(w)] for h, w in zip(height_cm, weight_kg, strict=True)]
188+
scatter.name = "Subjects"
189+
scatter.color = "rgba(48, 105, 152, 0.65)"
190+
scatter.marker = {
191+
"radius": 12,
192+
"symbol": "circle",
193+
"lineWidth": 2,
194+
"lineColor": "#ffffff",
195+
"states": {"hover": {"radiusPlus": 4, "lineWidthPlus": 1, "lineColor": "#306998"}},
196+
}
197+
scatter.z_index = 2
198+
199+
# Outlier series — highlight extreme points with distinct marker
200+
outlier_series = ScatterSeries()
201+
outlier_series.data = [[float(h), float(w)] for h, w in zip(outlier_heights, outlier_weights, strict=True)]
202+
outlier_series.name = "Outliers"
203+
outlier_series.color = "rgba(211, 84, 0, 0.80)"
204+
outlier_series.marker = {
205+
"radius": 15,
206+
"symbol": "diamond",
207+
"lineWidth": 2,
208+
"lineColor": "#d35400",
209+
"states": {"hover": {"radiusPlus": 4}},
210+
}
211+
outlier_series.z_index = 3
212+
213+
# Trend line (linear regression) using SplineSeries
214+
trend = SplineSeries()
215+
trend.data = [[float(trend_x[0]), float(trend_y[0])], [float(trend_x[1]), float(trend_y[1])]]
216+
trend.name = f"Trend (R\u00b2 = {r_squared:.2f})"
217+
trend.color = "#e67e22"
218+
trend.line_width = 4
219+
trend.dash_style = "LongDash"
220+
trend.marker = {"enabled": False}
221+
trend.enable_mouse_tracking = False
222+
trend.z_index = 1
223+
224+
chart.add_series(scatter)
225+
chart.add_series(outlier_series)
226+
chart.add_series(trend)
70227

71-
chart.add_series(series)
228+
# Annotation — R² value and slope description
229+
chart.options.annotations = [
230+
Annotation.from_dict(
231+
{
232+
"draggable": "",
233+
"labelOptions": {
234+
"backgroundColor": "rgba(255, 255, 255, 0.9)",
235+
"borderColor": "#e67e22",
236+
"borderRadius": 8,
237+
"borderWidth": 2,
238+
"padding": 14,
239+
"style": {"fontSize": "34px", "color": "#2c3e50"},
240+
},
241+
"labels": [
242+
{
243+
"point": {
244+
"x": float(x_min + 8),
245+
"y": float(slope * (x_min + 8) + intercept - 5),
246+
"xAxis": 0,
247+
"yAxis": 0,
248+
},
249+
"text": f"y = {slope:.2f}x {intercept:+.1f} | R\u00b2 = {r_squared:.2f}",
250+
}
251+
],
252+
}
253+
)
254+
]
72255

73-
# Download Highcharts JS (required for headless Chrome)
256+
# Download Highcharts JS and annotations module (required for headless Chrome)
74257
highcharts_url = "https://code.highcharts.com/highcharts.js"
258+
annotations_url = "https://code.highcharts.com/modules/annotations.js"
75259
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
76260
highcharts_js = response.read().decode("utf-8")
261+
with urllib.request.urlopen(annotations_url, timeout=30) as response:
262+
annotations_js = response.read().decode("utf-8")
77263

78264
# Generate HTML with inline scripts
79265
html_str = chart.to_js_literal()
@@ -82,8 +268,9 @@
82268
<head>
83269
<meta charset="utf-8">
84270
<script>{highcharts_js}</script>
271+
<script>{annotations_js}</script>
85272
</head>
86-
<body style="margin:0;">
273+
<body style="margin:0; background:#fafbfc;">
87274
<div id="container" style="width: 4800px; height: 2700px;"></div>
88275
<script>{html_str}</script>
89276
</body>
@@ -99,28 +286,29 @@
99286
chrome_options.add_argument("--no-sandbox")
100287
chrome_options.add_argument("--disable-dev-shm-usage")
101288
chrome_options.add_argument("--disable-gpu")
102-
chrome_options.add_argument("--window-size=4800,2800")
289+
chrome_options.add_argument("--window-size=4800,2700")
103290

104291
driver = webdriver.Chrome(options=chrome_options)
105292
driver.get(f"file://{temp_path}")
106293
time.sleep(5)
107294

108-
# Take screenshot of just the chart container element
295+
# Screenshot the chart container for exact dimensions
109296
container = driver.find_element("id", "container")
110297
container.screenshot("plot.png")
111298
driver.quit()
112299

113300
Path(temp_path).unlink()
114301

115-
# Also save HTML for interactive version
302+
# Save HTML for interactive version
116303
with open("plot.html", "w", encoding="utf-8") as f:
117304
interactive_html = f"""<!DOCTYPE html>
118305
<html>
119306
<head>
120307
<meta charset="utf-8">
121308
<script src="https://code.highcharts.com/highcharts.js"></script>
309+
<script src="https://code.highcharts.com/modules/annotations.js"></script>
122310
</head>
123-
<body style="margin:0;">
311+
<body style="margin:0; background:#fafbfc;">
124312
<div id="container" style="width: 100%; height: 100vh;"></div>
125313
<script>{html_str}</script>
126314
</body>

0 commit comments

Comments
 (0)