diff --git a/plots/scatter-basic/implementations/highcharts.py b/plots/scatter-basic/implementations/highcharts.py
index 01cdc651e1..c7859256df 100644
--- a/plots/scatter-basic/implementations/highcharts.py
+++ b/plots/scatter-basic/implementations/highcharts.py
@@ -1,7 +1,7 @@
""" pyplots.ai
scatter-basic: Basic Scatter Plot
-Library: highcharts unknown | Python 3.13.11
-Quality: 92/100 | Created: 2025-12-22
+Library: highcharts 1.10.3 | Python 3.14
+Quality: 91/100 | Created: 2025-12-22
"""
import tempfile
@@ -12,17 +12,40 @@
import numpy as np
from highcharts_core.chart import Chart
from highcharts_core.options import HighchartsOptions
+from highcharts_core.options.annotations import Annotation
from highcharts_core.options.series.scatter import ScatterSeries
+from highcharts_core.options.series.spline import SplineSeries
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
-# Data
+# Data — height vs weight with moderate positive correlation
np.random.seed(42)
-x = np.random.randn(100) * 2 + 10
-y = x * 0.8 + np.random.randn(100) * 2
+n_points = 100
+height_cm = np.random.normal(170, 10, n_points)
+weight_kg = height_cm * 0.65 + np.random.normal(0, 5, n_points) - 40
-# Create chart
+# Compute linear regression for trend line
+slope, intercept = np.polyfit(height_cm, weight_kg, 1)
+r_squared = np.corrcoef(height_cm, weight_kg)[0, 1] ** 2
+
+# Axis bounds — tight to data with small padding
+x_min, x_max = float(np.floor(height_cm.min() - 2)), float(np.ceil(height_cm.max() + 2))
+y_min, y_max = float(np.floor(weight_kg.min() - 3)), float(np.ceil(weight_kg.max() + 3))
+
+# Trend line endpoints
+trend_x = np.array([x_min, x_max])
+trend_y = slope * trend_x + intercept
+
+# Identify outlier points (beyond 2 std from regression line)
+predicted = slope * height_cm + intercept
+residuals = weight_kg - predicted
+std_resid = np.std(residuals)
+outlier_mask = np.abs(residuals) > 1.8 * std_resid
+outlier_heights = height_cm[outlier_mask]
+outlier_weights = weight_kg[outlier_mask]
+
+# Create chart with typed API
chart = Chart(container="container")
chart.options = HighchartsOptions()
@@ -31,49 +54,212 @@
"type": "scatter",
"width": 4800,
"height": 2700,
- "backgroundColor": "#ffffff",
- "marginBottom": 150,
+ "backgroundColor": "#fafbfc",
+ "style": {"fontFamily": "'Segoe UI', Helvetica, Arial, sans-serif"},
+ "marginTop": 160,
+ "marginBottom": 310,
+ "marginLeft": 220,
+ "marginRight": 200,
}
-# Title (required format: spec-id · library · pyplots.ai)
+# Title with refined typography
chart.options.title = {
- "text": "scatter-basic · highcharts · pyplots.ai",
- "style": {"fontSize": "72px", "fontWeight": "bold"},
+ "text": "scatter-basic \u00b7 highcharts \u00b7 pyplots.ai",
+ "style": {"fontSize": "64px", "fontWeight": "600", "color": "#2c3e50", "letterSpacing": "1px"},
+ "margin": 50,
}
-# Axes (scaled for 4800x2700 px)
+# Subtitle for data storytelling
+chart.options.subtitle = {
+ "text": "Height vs Weight — positive correlation across 100 subjects",
+ "style": {"fontSize": "38px", "color": "#7f8c8d", "fontWeight": "400"},
+}
+
+# X-axis with tight bounds and refined styling
chart.options.x_axis = {
- "title": {"text": "X Value", "style": {"fontSize": "48px"}},
- "labels": {"style": {"fontSize": "36px"}},
+ "title": {
+ "text": "Height (cm)",
+ "style": {"fontSize": "44px", "color": "#34495e", "fontWeight": "500"},
+ "margin": 30,
+ },
+ "labels": {"style": {"fontSize": "34px", "color": "#7f8c8d"}},
+ "min": x_min,
+ "max": x_max,
+ "tickInterval": 5,
+ "startOnTick": False,
+ "endOnTick": False,
"gridLineWidth": 1,
- "gridLineColor": "rgba(0, 0, 0, 0.15)",
- "gridLineDashStyle": "Dash",
+ "gridLineColor": "rgba(0, 0, 0, 0.06)",
+ "gridLineDashStyle": "Dot",
+ "lineColor": "#bdc3c7",
+ "lineWidth": 2,
+ "tickColor": "#bdc3c7",
+ "tickLength": 10,
}
+
+# Y-axis with tight bounds and reduced tick density
chart.options.y_axis = {
- "title": {"text": "Y Value", "style": {"fontSize": "48px"}},
- "labels": {"style": {"fontSize": "36px"}},
+ "title": {
+ "text": "Weight (kg)",
+ "style": {"fontSize": "44px", "color": "#34495e", "fontWeight": "500"},
+ "margin": 30,
+ },
+ "labels": {"style": {"fontSize": "34px", "color": "#7f8c8d"}},
+ "min": y_min,
+ "max": y_max,
+ "tickInterval": 5,
+ "startOnTick": False,
+ "endOnTick": False,
"gridLineWidth": 1,
- "gridLineColor": "rgba(0, 0, 0, 0.15)",
- "gridLineDashStyle": "Dash",
+ "gridLineColor": "rgba(0, 0, 0, 0.06)",
+ "gridLineDashStyle": "Dot",
+ "lineColor": "#bdc3c7",
+ "lineWidth": 2,
+ "tickColor": "#bdc3c7",
+ "tickLength": 10,
+ "plotBands": [
+ {
+ "from": y_min,
+ "to": float(np.percentile(weight_kg, 25)),
+ "color": "rgba(48, 105, 152, 0.03)",
+ "label": {
+ "text": "Lower quartile",
+ "style": {"fontSize": "32px", "color": "rgba(48, 105, 152, 0.55)"},
+ "align": "left",
+ "x": 20,
+ "y": 16,
+ },
+ },
+ {
+ "from": float(np.percentile(weight_kg, 75)),
+ "to": y_max,
+ "color": "rgba(48, 105, 152, 0.03)",
+ "label": {
+ "text": "Upper quartile",
+ "style": {"fontSize": "32px", "color": "rgba(48, 105, 152, 0.55)"},
+ "align": "left",
+ "x": 20,
+ "y": 16,
+ },
+ },
+ ],
+}
+
+# Legend — show to label trend line
+chart.options.legend = {
+ "enabled": True,
+ "align": "right",
+ "verticalAlign": "top",
+ "layout": "vertical",
+ "x": -40,
+ "y": 80,
+ "floating": True,
+ "backgroundColor": "rgba(255, 255, 255, 0.85)",
+ "borderWidth": 1,
+ "borderColor": "#e0e0e0",
+ "borderRadius": 8,
+ "itemStyle": {"fontSize": "30px", "fontWeight": "400", "color": "#34495e"},
+ "padding": 16,
+ "symbolRadius": 6,
}
-# Legend and credits
-chart.options.legend = {"enabled": False}
chart.options.credits = {"enabled": False}
-# Create scatter series with Python Blue color and transparency
-series = ScatterSeries()
-series.data = [[float(xi), float(yi)] for xi, yi in zip(x, y, strict=True)]
-series.name = "Data"
-series.color = "rgba(48, 105, 152, 0.7)" # Python Blue with alpha
-series.marker = {"radius": 18, "symbol": "circle"} # Larger markers for 4800x2700
+# Rich tooltip — Highcharts-distinctive feature
+chart.options.tooltip = {
+ "headerFormat": "",
+ "pointFormat": (
+ '\u25cf '
+ ''
+ "Height: {point.x:.1f} cm
"
+ "Weight: {point.y:.1f} kg"
+ ),
+ "backgroundColor": "rgba(255, 255, 255, 0.95)",
+ "borderColor": "#306998",
+ "borderRadius": 10,
+ "borderWidth": 2,
+ "shadow": {"color": "rgba(0,0,0,0.1)", "offsetX": 2, "offsetY": 2, "width": 4},
+ "style": {"fontSize": "26px"},
+}
+
+# Main scatter series — Python Blue with transparency
+scatter = ScatterSeries()
+scatter.data = [[float(h), float(w)] for h, w in zip(height_cm, weight_kg, strict=True)]
+scatter.name = "Subjects"
+scatter.color = "rgba(48, 105, 152, 0.65)"
+scatter.marker = {
+ "radius": 12,
+ "symbol": "circle",
+ "lineWidth": 2,
+ "lineColor": "#ffffff",
+ "states": {"hover": {"radiusPlus": 4, "lineWidthPlus": 1, "lineColor": "#306998"}},
+}
+scatter.z_index = 2
+
+# Outlier series — highlight extreme points with distinct marker
+outlier_series = ScatterSeries()
+outlier_series.data = [[float(h), float(w)] for h, w in zip(outlier_heights, outlier_weights, strict=True)]
+outlier_series.name = "Outliers"
+outlier_series.color = "rgba(211, 84, 0, 0.80)"
+outlier_series.marker = {
+ "radius": 15,
+ "symbol": "diamond",
+ "lineWidth": 2,
+ "lineColor": "#d35400",
+ "states": {"hover": {"radiusPlus": 4}},
+}
+outlier_series.z_index = 3
+
+# Trend line (linear regression) using SplineSeries
+trend = SplineSeries()
+trend.data = [[float(trend_x[0]), float(trend_y[0])], [float(trend_x[1]), float(trend_y[1])]]
+trend.name = f"Trend (R\u00b2 = {r_squared:.2f})"
+trend.color = "#e67e22"
+trend.line_width = 4
+trend.dash_style = "LongDash"
+trend.marker = {"enabled": False}
+trend.enable_mouse_tracking = False
+trend.z_index = 1
+
+chart.add_series(scatter)
+chart.add_series(outlier_series)
+chart.add_series(trend)
-chart.add_series(series)
+# Annotation — R² value and slope description
+chart.options.annotations = [
+ Annotation.from_dict(
+ {
+ "draggable": "",
+ "labelOptions": {
+ "backgroundColor": "rgba(255, 255, 255, 0.9)",
+ "borderColor": "#e67e22",
+ "borderRadius": 8,
+ "borderWidth": 2,
+ "padding": 14,
+ "style": {"fontSize": "34px", "color": "#2c3e50"},
+ },
+ "labels": [
+ {
+ "point": {
+ "x": float(x_min + 8),
+ "y": float(slope * (x_min + 8) + intercept - 5),
+ "xAxis": 0,
+ "yAxis": 0,
+ },
+ "text": f"y = {slope:.2f}x {intercept:+.1f} | R\u00b2 = {r_squared:.2f}",
+ }
+ ],
+ }
+ )
+]
-# Download Highcharts JS (required for headless Chrome)
+# Download Highcharts JS and annotations module (required for headless Chrome)
highcharts_url = "https://code.highcharts.com/highcharts.js"
+annotations_url = "https://code.highcharts.com/modules/annotations.js"
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
highcharts_js = response.read().decode("utf-8")
+with urllib.request.urlopen(annotations_url, timeout=30) as response:
+ annotations_js = response.read().decode("utf-8")
# Generate HTML with inline scripts
html_str = chart.to_js_literal()
@@ -82,8 +268,9 @@
+
-
+
@@ -99,28 +286,29 @@
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage")
chrome_options.add_argument("--disable-gpu")
-chrome_options.add_argument("--window-size=4800,2800")
+chrome_options.add_argument("--window-size=4800,2700")
driver = webdriver.Chrome(options=chrome_options)
driver.get(f"file://{temp_path}")
time.sleep(5)
-# Take screenshot of just the chart container element
+# Screenshot the chart container for exact dimensions
container = driver.find_element("id", "container")
container.screenshot("plot.png")
driver.quit()
Path(temp_path).unlink()
-# Also save HTML for interactive version
+# Save HTML for interactive version
with open("plot.html", "w", encoding="utf-8") as f:
interactive_html = f"""
+
-
+
diff --git a/plots/scatter-basic/metadata/highcharts.yaml b/plots/scatter-basic/metadata/highcharts.yaml
index 4ae75bf5d9..de81332ae4 100644
--- a/plots/scatter-basic/metadata/highcharts.yaml
+++ b/plots/scatter-basic/metadata/highcharts.yaml
@@ -1,165 +1,181 @@
library: highcharts
specification_id: scatter-basic
created: '2025-12-22T23:43:32Z'
-updated: '2025-12-23T00:06:43Z'
-generated_by: claude-opus-4-5-20251101
+updated: '2026-02-14T14:53:29Z'
+generated_by: claude-opus-4-6
workflow_run: 20446960278
issue: 0
-python_version: 3.13.11
-library_version: unknown
+python_version: '3.14'
+library_version: 1.10.3
preview_url: https://storage.googleapis.com/pyplots-images/plots/scatter-basic/highcharts/plot.png
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/scatter-basic/highcharts/plot_thumb.png
preview_html: https://storage.googleapis.com/pyplots-images/plots/scatter-basic/highcharts/plot.html
-quality_score: 92
+quality_score: 91
impl_tags:
dependencies:
- - selenium
+ - selenium
techniques:
- - html-export
+ - annotations
+ - html-export
patterns:
- - data-generation
- dataprep: []
+ - data-generation
+ dataprep:
+ - regression
styling:
- - alpha-blending
- - grid-styling
+ - alpha-blending
+ - edge-highlighting
+ - grid-styling
review:
strengths:
- - Excellent text sizing scaled appropriately for 4800x2700 resolution (72px title,
- 48px axis titles, 36px labels)
- - Clean implementation following library rules with inline Highcharts JS for headless
- Chrome compatibility
- - Proper use of Python Blue color with transparency (rgba 0.7) for overlapping point
- visibility
- - Correct title format following the spec-id · library · pyplots.ai convention
- - Both PNG and interactive HTML outputs generated
- - Subtle dashed grid lines enhance readability without distraction
+ - Excellent data storytelling with regression equation annotation, R-squared display,
+ outlier highlighting, and quartile plot bands
+ - Publication-quality typography and color scheme with intentional hierarchy (title,
+ subtitle, axis, tick labels)
+ - 'Strong use of Highcharts-distinctive features: rich HTML tooltips, annotations
+ module, plot bands, marker hover states, shadow tooltips'
+ - Dual output (static PNG + interactive HTML) showcasing Highcharts interactive
+ capability
+ - Realistic health/biometric dataset with plausible values and meaningful statistical
+ overlay
weaknesses:
- - Axis labels are generic (X Value, Y Value) rather than contextual with units
- - Marker radius (18) could be slightly smaller for better distinction between nearby
- points
- - Does not leverage Highcharts tooltip/hover interactivity features visible in the
- static image
- image_description: The plot displays a scatter plot on a white background with approximately
- 100 data points. The points are rendered as filled circles in a muted blue color
- (Python Blue) with transparency (alpha ~0.7). The title "scatter-basic · highcharts
- · pyplots.ai" appears at the top in bold black text. The X-axis is labeled "X
- Value" (ranging from ~4.8 to ~13.6) and the Y-axis is labeled "Y Value" (ranging
- from ~2 to ~15). Both axes have subtle gray dashed grid lines. The data shows
- a clear positive correlation pattern with some scatter/noise. The overall layout
- is clean and professional with good proportions.
+ - 'Margins are generous (especially marginBottom: 310) leading to slightly suboptimal
+ canvas utilization'
+ - Some chart options are set as raw dicts rather than using Highcharts-Core typed
+ objects consistently
+ image_description: 'The plot displays a scatter chart of Height (cm) vs Weight (kg)
+ on a light gray (#fafbfc) background. Blue circular markers with white outlines
+ represent 100 "Subjects" data points, while orange diamond markers highlight statistical
+ outliers. An orange dashed trend line runs diagonally from lower-left to upper-right,
+ showing the positive correlation. The title reads "scatter-basic · highcharts
+ · pyplots.ai" in dark text with a subtitle "Height vs Weight — positive correlation
+ across 100 subjects" in gray. A floating legend in the upper-right lists three
+ series: Subjects (blue circle), Outliers (orange diamond), and Trend (R² = 0.55).
+ An annotation box near the lower-left displays the regression equation "y = 0.58x
+ -27.8 | R² = 0.55" with an orange border. Subtle light-blue horizontal plot bands
+ mark the "Lower quartile" and "Upper quartile" regions with labeled text. Both
+ axes have dotted grid lines, labeled ticks at 5-unit intervals, and descriptive
+ titles with units.'
criteria_checklist:
visual_quality:
- score: 36
- max: 40
+ score: 27
+ max: 30
items:
- id: VQ-01
name: Text Legibility
- score: 10
- max: 10
+ score: 8
+ max: 8
passed: true
- comment: Title, axis labels, and tick marks are all clearly readable at the
- high resolution
+ comment: 'All font sizes explicitly set: title 64px, axis titles 44px, tick
+ labels 34px, legend 30px, annotations 34px'
- id: VQ-02
name: No Overlap
- score: 8
- max: 8
+ score: 6
+ max: 6
passed: true
comment: No overlapping text elements anywhere
- id: VQ-03
name: Element Visibility
- score: 7
- max: 8
+ score: 5
+ max: 6
passed: true
- comment: Markers are appropriately sized for 100 data points with good transparency
+ comment: Markers radius 12 with alpha 0.65 well-sized for 100 points; outliers
+ distinguished with larger diamonds
- id: VQ-04
name: Color Accessibility
- score: 5
- max: 5
+ score: 4
+ max: 4
passed: true
- comment: Single color (Python Blue) is colorblind-safe
+ comment: Blue and orange are colorblind-safe with strong luminance contrast
- id: VQ-05
name: Layout Balance
- score: 4
- max: 5
- passed: true
- comment: Good proportions, though slight extra whitespace at top
+ score: 2
+ max: 4
+ passed: false
+ comment: Large explicit margins (marginBottom 310, marginLeft 220) consume
+ significant canvas space
- id: VQ-06
- name: Axis Labels
- score: 1
+ name: Axis Labels & Title
+ score: 2
max: 2
passed: true
- comment: Labels are descriptive ("X Value", "Y Value") but lack units
- - id: VQ-07
- name: Grid & Legend
- score: 1
- max: 2
+ comment: 'Descriptive labels with units: Height (cm) and Weight (kg)'
+ design_excellence:
+ score: 16
+ max: 20
+ items:
+ - id: DE-01
+ name: Aesthetic Sophistication
+ score: 7
+ max: 8
+ passed: true
+ comment: Custom palette, refined typography, white marker outlines, dotted
+ grid, styled tooltips — approaching publication quality
+ - id: DE-02
+ name: Visual Refinement
+ score: 5
+ max: 6
+ passed: true
+ comment: Dotted grid with low opacity, subtle borders, rounded corners, generous
+ whitespace
+ - id: DE-03
+ name: Data Storytelling
+ score: 4
+ max: 6
passed: true
- comment: Grid is subtle with dashed style and appropriate alpha; legend disabled
- (appropriate for single series)
+ comment: Regression annotation with equation and R-squared, outlier highlighting,
+ quartile bands, subtitle context
spec_compliance:
- score: 25
- max: 25
+ score: 15
+ max: 15
items:
- id: SC-01
name: Plot Type
- score: 8
- max: 8
- passed: true
- comment: Correct scatter plot type
- - id: SC-02
- name: Data Mapping
score: 5
max: 5
passed: true
- comment: X/Y correctly mapped to axes
- - id: SC-03
+ comment: Correct scatter plot chart type
+ - id: SC-02
name: Required Features
- score: 5
- max: 5
+ score: 4
+ max: 4
passed: true
- comment: 'All spec features present: transparency, axis labels, title, grid
- lines'
- - id: SC-04
- name: Data Range
+ comment: Transparency, axis labels, title, grid lines, appropriate point sizing
+ all present
+ - id: SC-03
+ name: Data Mapping
score: 3
max: 3
passed: true
- comment: Axes show all data points with appropriate padding
- - id: SC-05
- name: Legend Accuracy
- score: 2
- max: 2
- passed: true
- comment: Legend appropriately disabled for single-series scatter
- - id: SC-06
- name: Title Format
- score: 2
- max: 2
+ comment: X=Height, Y=Weight correctly mapped with tight axis bounds
+ - id: SC-04
+ name: Title & Legend
+ score: 3
+ max: 3
passed: true
- comment: 'Correct format: "scatter-basic · highcharts · pyplots.ai"'
+ comment: Title format correct, legend labels descriptive and match data
data_quality:
- score: 18
- max: 20
+ score: 15
+ max: 15
items:
- id: DQ-01
name: Feature Coverage
- score: 7
- max: 8
+ score: 6
+ max: 6
passed: true
- comment: Shows correlation pattern with noise, demonstrates typical scatter
- behavior
+ comment: Shows correlation, outliers, trend line, density variation, quartile
+ distribution
- id: DQ-02
name: Realistic Context
- score: 6
- max: 7
+ score: 5
+ max: 5
passed: true
- comment: Plausible data range, shows meaningful positive correlation
+ comment: Height vs Weight health/biometric context is real and neutral
- id: DQ-03
name: Appropriate Scale
- score: 5
- max: 5
+ score: 4
+ max: 4
passed: true
- comment: Sensible numeric values
+ comment: Heights 140-190cm, weights 54-90kg are realistic human measurements
code_quality:
score: 10
max: 10
@@ -169,13 +185,13 @@ review:
score: 3
max: 3
passed: true
- comment: 'Linear script: imports → data → chart config → export'
+ comment: 'Linear flow: imports, data, config, series, export'
- id: CQ-02
name: Reproducibility
- score: 3
- max: 3
+ score: 2
+ max: 2
passed: true
- comment: Uses np.random.seed(42)
+ comment: np.random.seed(42) set
- id: CQ-03
name: Clean Imports
score: 2
@@ -183,27 +199,33 @@ review:
passed: true
comment: All imports are used
- id: CQ-04
- name: No Deprecated API
- score: 1
- max: 1
+ name: Code Elegance
+ score: 2
+ max: 2
passed: true
- comment: Uses current highcharts-core API
+ comment: Clean, well-organized, appropriate complexity
- id: CQ-05
- name: Output Correct
+ name: Output & API
score: 1
max: 1
passed: true
- comment: Saves as plot.png and plot.html
+ comment: Saves as plot.png via container.screenshot()
library_features:
- score: 3
- max: 5
+ score: 8
+ max: 10
items:
- - id: LF-01
- name: Uses distinctive library features
- score: 3
+ - id: LM-01
+ name: Idiomatic Usage
+ score: 4
+ max: 5
+ passed: true
+ comment: Typed API (ScatterSeries, SplineSeries), chart.options pattern, add_series(),
+ to_js_literal()
+ - id: LM-02
+ name: Distinctive Features
+ score: 4
max: 5
passed: true
- comment: Uses highcharts-core properly with ScatterSeries, but doesn't leverage
- advanced Highcharts features like tooltips or interactive hover states in
- the static output
+ comment: Rich HTML tooltips, Annotation module, plot bands, marker hover states,
+ shadow config, interactive HTML export
verdict: APPROVED