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