diff --git a/plots/band-basic/implementations/highcharts.py b/plots/band-basic/implementations/highcharts.py index b3e7a0a05f..7868c80216 100644 --- a/plots/band-basic/implementations/highcharts.py +++ b/plots/band-basic/implementations/highcharts.py @@ -1,152 +1,184 @@ """ pyplots.ai band-basic: Basic Band Plot -Library: highcharts unknown | Python 3.13.11 -Quality: 92/100 | Created: 2025-12-23 +Library: highcharts 1.10.3 | Python 3.14 +Quality: 91/100 | Updated: 2026-02-23 """ -import json import tempfile import time import urllib.request from pathlib import Path import numpy as np +from highcharts_core.chart import Chart +from highcharts_core.options import HighchartsOptions +from highcharts_core.options.series.area import AreaRangeSeries, LineSeries from PIL import Image from selenium import webdriver from selenium.webdriver.chrome.options import Options -# Data - Time series with 95% confidence interval +# Data - Daily temperature forecast with 95% prediction interval np.random.seed(42) -x = np.linspace(0, 10, 50) -# Central trend with sinusoidal pattern -y_center = 50 + 20 * np.sin(x) + x * 2 -# Uncertainty increases with x (heteroscedastic) -uncertainty = 3 + 0.5 * x -# Upper and lower bounds -y_lower = y_center - 1.96 * uncertainty -y_upper = y_center + 1.96 * uncertainty - -# Prepare data for Highcharts -# arearange series expects [[x, low, high], ...] -band_data = [[float(xi), float(lo), float(hi)] for xi, lo, hi in zip(x, y_lower, y_upper, strict=True)] -# line series expects [[x, y], ...] -line_data = [[float(xi), float(yi)] for xi, yi in zip(x, y_center, strict=True)] - -# Chart options using arearange for band and line for center -chart_options = { - "chart": { - "width": 4800, - "height": 2700, - "backgroundColor": "#ffffff", - "marginBottom": 180, - "marginLeft": 200, - "marginRight": 100, - "style": {"fontFamily": "Arial, sans-serif"}, - }, - "title": {"text": "band-basic · highcharts · pyplots.ai", "style": {"fontSize": "64px", "fontWeight": "bold"}}, - "subtitle": {"text": "Time series with 95% confidence interval", "style": {"fontSize": "38px", "color": "#666666"}}, - "xAxis": { - "title": {"text": "Time", "style": {"fontSize": "48px"}, "margin": 20}, - "labels": {"style": {"fontSize": "36px"}}, - "gridLineWidth": 1, - "gridLineColor": "rgba(0, 0, 0, 0.1)", - "gridLineDashStyle": "Dash", - "tickInterval": 1, - }, - "yAxis": { - "title": {"text": "Value", "style": {"fontSize": "48px"}, "margin": 20}, - "labels": {"style": {"fontSize": "36px"}}, - "gridLineWidth": 1, - "gridLineColor": "rgba(0, 0, 0, 0.1)", - "gridLineDashStyle": "Dash", - }, - "legend": { - "enabled": True, - "align": "right", - "verticalAlign": "top", - "layout": "vertical", - "x": -50, - "y": 100, - "itemStyle": {"fontSize": "36px"}, +days = np.arange(1, 31) +temp_center = 12 + 0.3 * days + 4 * np.sin(days * 0.4) +uncertainty = 1.5 + 0.08 * days +temp_lower = temp_center - 1.96 * uncertainty +temp_upper = temp_center + 1.96 * uncertainty + +band_data = [ + [int(d), round(float(lo), 1), round(float(hi), 1)] for d, lo, hi in zip(days, temp_lower, temp_upper, strict=True) +] +line_data = [[int(d), round(float(t), 1)] for d, t in zip(days, temp_center, strict=True)] + +# Build chart using highcharts-core Python wrapper +font_family = "'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif" + +chart = Chart(container="container") +chart.options = HighchartsOptions() + +chart.options.chart = { + "width": 4800, + "height": 2700, + "backgroundColor": "#ffffff", + "marginBottom": 180, + "marginLeft": 220, + "marginRight": 100, + "spacing": [40, 40, 40, 40], + "style": {"fontFamily": font_family}, +} + +chart.options.title = { + "text": "30-Day Temperature Forecast \u00b7 band-basic \u00b7 highcharts \u00b7 pyplots.ai", + "style": {"fontSize": "48px", "fontWeight": "bold", "fontFamily": font_family}, +} + +chart.options.subtitle = { + "text": "Daily forecast with 95% prediction interval", + "style": {"fontSize": "30px", "color": "#666666", "fontFamily": font_family}, +} + +chart.options.x_axis = { + "title": { + "text": "Forecast Day", + "style": {"fontSize": "36px", "color": "#444444", "fontFamily": font_family}, + "margin": 20, }, - "plotOptions": { - "arearange": {"fillOpacity": 0.3, "lineWidth": 0, "marker": {"enabled": False}}, - "line": {"lineWidth": 6, "marker": {"enabled": False}}, + "labels": {"style": {"fontSize": "28px", "color": "#555555", "fontFamily": font_family}}, + "gridLineWidth": 0, + "tickInterval": 5, + "lineColor": "rgba(0, 0, 0, 0.12)", + "lineWidth": 1, + "tickColor": "rgba(0, 0, 0, 0.12)", + "tickLength": 8, +} + +chart.options.y_axis = { + "title": { + "text": "Temperature (\u00b0C)", + "style": {"fontSize": "36px", "color": "#444444", "fontFamily": font_family}, + "margin": 20, }, - "series": [ - { - "name": "95% Confidence Interval", - "type": "arearange", - "data": band_data, - "color": "#306998", - "fillOpacity": 0.3, - "zIndex": 0, - }, - {"name": "Mean Value", "type": "line", "data": line_data, "color": "#FFD43B", "lineWidth": 6, "zIndex": 1}, - ], + "labels": {"format": "{value}\u00b0", "style": {"fontSize": "28px", "color": "#555555", "fontFamily": font_family}}, + "gridLineWidth": 1, + "gridLineColor": "rgba(0, 0, 0, 0.06)", + "gridLineDashStyle": "Dot", + "lineColor": "rgba(0, 0, 0, 0.12)", + "lineWidth": 1, } -# Download Highcharts JS and highcharts-more (needed for arearange) -highcharts_url = "https://code.highcharts.com/highcharts.js" -highcharts_more_url = "https://code.highcharts.com/highcharts-more.js" +chart.options.legend = { + "enabled": True, + "align": "right", + "verticalAlign": "top", + "layout": "vertical", + "x": -60, + "y": 60, + "floating": True, + "backgroundColor": "rgba(255, 255, 255, 0.85)", + "borderWidth": 0, + "shadow": False, + "itemStyle": {"fontSize": "28px", "fontWeight": "normal", "fontFamily": font_family}, + "itemMarginBottom": 8, + "symbolRadius": 4, +} -with urllib.request.urlopen(highcharts_url, timeout=30) as response: - highcharts_js = response.read().decode("utf-8") -with urllib.request.urlopen(highcharts_more_url, timeout=30) as response: - highcharts_more_js = response.read().decode("utf-8") +chart.options.plot_options = { + "arearange": {"fillOpacity": 0.25, "lineWidth": 0, "marker": {"enabled": False}}, + "line": {"lineWidth": 5, "marker": {"enabled": False}}, +} -# Generate HTML with inline scripts -chart_options_json = json.dumps(chart_options) +chart.options.credits = {"enabled": False} + +# Band series using AreaRangeSeries +band = AreaRangeSeries() +band.data = band_data +band.name = "95% Prediction Interval" +band.color = "#306998" +band.fill_opacity = 0.25 +band.z_index = 0 + +# Forecast line using LineSeries with refined deep amber color +forecast = LineSeries() +forecast.data = line_data +forecast.name = "Forecast" +forecast.color = "#C49000" +forecast.line_width = 5 +forecast.z_index = 1 + +chart.add_series(band) +chart.add_series(forecast) + +# Generate JS via highcharts-core wrapper +chart_js = chart.to_js_literal() + +# Download Highcharts JS files for inline embedding (headless Chrome cannot load CDN) +cdn_base = "https://cdn.jsdelivr.net/npm/highcharts@11.4" +js_urls = {"highcharts": f"{cdn_base}/highcharts.js", "highcharts_more": f"{cdn_base}/highcharts-more.js"} +js_modules = {} +for name, url in js_urls.items(): + req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) + with urllib.request.urlopen(req, timeout=30) as response: + js_modules[name] = response.read().decode("utf-8") + +# Build HTML with inline Highcharts JS and chart literal from wrapper html_content = f""" - - + +
- + """ -# Write temp HTML file with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f: f.write(html_content) temp_path = f.name -# Also save the HTML for interactive viewing with open("plot.html", "w", encoding="utf-8") as f: f.write(html_content) # Take screenshot with headless Chrome chrome_options = Options() -chrome_options.add_argument("--headless=new") +chrome_options.add_argument("--headless") chrome_options.add_argument("--no-sandbox") chrome_options.add_argument("--disable-dev-shm-usage") chrome_options.add_argument("--disable-gpu") -chrome_options.add_argument("--force-device-scale-factor=1") +chrome_options.add_argument("--window-size=4800,2900") driver = webdriver.Chrome(options=chrome_options) -driver.set_window_size(4900, 2900) driver.get(f"file://{temp_path}") time.sleep(5) - -# Take screenshot driver.save_screenshot("plot_raw.png") driver.quit() -# Crop/resize to exact 4800x2700 using PIL img = Image.open("plot_raw.png") -final_img = Image.new("RGB", (4800, 2700), (255, 255, 255)) -final_img.paste(img.crop((0, 0, min(img.width, 4800), min(img.height, 2700))), (0, 0)) -final_img.save("plot.png") - -# Clean up +img_cropped = img.crop((0, 0, 4800, 2700)) +img_cropped.save("plot.png") Path("plot_raw.png").unlink() + Path(temp_path).unlink() diff --git a/plots/band-basic/metadata/highcharts.yaml b/plots/band-basic/metadata/highcharts.yaml index 39f2d5145e..744becb772 100644 --- a/plots/band-basic/metadata/highcharts.yaml +++ b/plots/band-basic/metadata/highcharts.yaml @@ -1,20 +1,20 @@ library: highcharts specification_id: band-basic created: '2025-12-23T09:09:27Z' -updated: '2025-12-23T09:11:45Z' -generated_by: claude-opus-4-5-20251101 +updated: '2026-02-23T14:07:12Z' +generated_by: claude-opus-4-6 workflow_run: 20456388074 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/band-basic/highcharts/plot.png preview_thumb: https://storage.googleapis.com/pyplots-images/plots/band-basic/highcharts/plot_thumb.png preview_html: https://storage.googleapis.com/pyplots-images/plots/band-basic/highcharts/plot.html -quality_score: 92 +quality_score: 91 impl_tags: dependencies: - - pillow - selenium + - pillow techniques: - html-export patterns: @@ -22,151 +22,166 @@ impl_tags: dataprep: [] styling: - alpha-blending + - grid-styling review: strengths: - - Excellent use of Highcharts arearange series for band visualization with proper - upper/lower bounds - - Very clear visual hierarchy with contrasting band (blue, 0.3 alpha) and center - line (yellow, solid) - - Heteroscedastic uncertainty pattern demonstrates the value of band plots effectively - - Proper zIndex layering ensures center line renders on top of band - - Large, readable text sizes throughout (64px title, 48px axis labels, 36px tick - labels) - - Clean Selenium-based PNG export with proper image cropping + - Excellent visual quality with all font sizes explicitly set and readable + - Harmonious blue-amber color pairing with strong contrast and colorblind accessibility + - Data storytelling through widening uncertainty band communicates increasing forecast + uncertainty over time + - Clean well-refined chart with subtle dotted gridlines and minimal visual noise + - Realistic temperature forecasting context with statistically plausible 95% prediction + intervals + - Clean code structure with proper CDN download pattern for headless Chrome weaknesses: - - Axis labels lack units (e.g., "Time (s)" or "Value (units)" would be more informative) - - Uses raw dictionary configuration instead of highcharts-core Python library classes - as shown in library rules - - Legend could be slightly larger for the 4800x2700 canvas size - image_description: 'The plot displays a band chart with a light blue semi-transparent - area representing the 95% confidence interval, and a golden-yellow center line - showing the mean value. The visualization follows a sinusoidal pattern over time - (x-axis: 0-10), with values ranging approximately from 24 to 104 on the y-axis. - The uncertainty band visibly widens as time increases, demonstrating heteroscedastic - behavior. The title "band-basic · highcharts · pyplots.ai" appears at the top - in bold, with a subtitle "Time series with 95% confidence interval". A legend - in the top-right corner identifies both the confidence interval band and mean - value line. The background is clean white with subtle dashed grid lines.' + - Library mastery could be deeper — could leverage more Highcharts-specific features + like gradient fills or custom tooltip formatting + - Axis lines could be further minimized or removed for a more modern look + - Legend symbol (circle) does not perfectly represent the band series visually + image_description: The plot displays a 30-day temperature forecast as a band plot + on a clean white background. A semi-transparent light blue band (#306998 at 25% + opacity) represents the 95% prediction interval, stretching from approximately + 5°C to 28°C at its widest. A bold amber/gold center line (#C49000) traces the + forecast temperature, following a sinusoidal pattern with peaks near days 4 (~17°C) + and 20 (~22°C) and troughs near days 10 (~11.5°C) and 27 (~16°C). The band visibly + widens over time, illustrating increasing forecast uncertainty. The title reads + "30-Day Temperature Forecast · band-basic · highcharts · pyplots.ai" in bold at + the top, with a smaller subtitle "Daily forecast with 95% prediction interval." + The y-axis is labeled "Temperature (°C)" with degree-symbol tick labels every + 1°C from 5° to 30°, and uses subtle dotted gridlines at very low opacity. The + x-axis is labeled "Forecast Day" with ticks at intervals of 5. A floating legend + in the upper-right corner identifies "95% Prediction Interval" and "Forecast." + No content is cut off and the layout is well-balanced. criteria_checklist: visual_quality: - score: 37 - max: 40 + score: 30 + max: 30 items: - id: VQ-01 name: Text Legibility - score: 10 - max: 10 + score: 8 + max: 8 passed: true - comment: Title at 64px, axis labels at 48px, tick labels at 36px - all perfectly - readable at 4800x2700 + comment: 'All font sizes explicitly set: title 48px, subtitle 30px, axis titles + 36px, tick labels 28px, legend 28px. All perfectly readable.' - id: VQ-02 name: No Overlap - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: No overlapping text elements anywhere + comment: No overlapping text. Legend well-positioned in upper right, clear + of data. Axis labels well-spaced. - id: VQ-03 name: Element Visibility - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: Band and line clearly visible with appropriate opacity (0.3 for band), - line width of 6 + comment: Band clearly visible with semi-transparent fill. Center line at width + 5 is prominent. Smooth curves, no markers. - id: VQ-04 name: Color Accessibility - score: 5 - max: 5 + score: 4 + max: 4 passed: true - comment: Blue (#306998) and yellow (#FFD43B) are colorblind-safe and high - contrast + comment: Blue and amber are colorblind-safe, distinguishable by hue and luminance. + No red-green dependency. - id: VQ-05 - name: Layout Balance + name: Layout & Canvas score: 4 - max: 5 + max: 4 passed: true - comment: Good proportions with appropriate margins, slight excess whitespace - on right side + comment: Chart fills ~65-70% of canvas. Balanced margins. No content cut off. - id: VQ-06 - name: Axis Labels - score: 1 + name: Axis Labels & Title + score: 2 max: 2 passed: true - comment: Labels are descriptive ("Time", "Value") but lack units - - id: VQ-07 - name: Grid & Legend - score: 1 - max: 2 + comment: 'Y-axis: Temperature (°C) with units. X-axis: Forecast Day. Tick + labels include degree symbols.' + design_excellence: + score: 14 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 6 + max: 8 + passed: true + comment: Harmonious cool/warm color pairing, custom font stack, clear typographic + hierarchy. Clearly above library defaults. + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: Dotted y-grid at low opacity, no x-grid, subtle axis lines, credits + disabled, no markers. Clean but axis lines present rather than removed. + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 passed: true - comment: Grid is subtle with dashed style and 0.1 alpha, legend well-placed - but could be larger + comment: Widening band communicates increasing uncertainty over time. Warm/cool + color contrast creates visual hierarchy. Subtitle contextualizes the visualization. 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 band/arearange chart type - - id: SC-02 - name: Data Mapping score: 5 max: 5 passed: true - comment: X mapped to time, Y to value with proper upper/lower bounds - - id: SC-03 + comment: Correct band/area-range plot with AreaRangeSeries for band and LineSeries + for center line. + - id: SC-02 name: Required Features - score: 5 - max: 5 + score: 4 + max: 4 passed: true - comment: Band with upper/lower bounds, center line, semi-transparent fill - (0.3 alpha) - - id: SC-04 - name: Data Range + comment: Semi-transparent fill at 0.25, contrasting center line, 30 data points, + smooth interpolation, uncertainty context. + - id: SC-03 + name: Data Mapping score: 3 max: 3 passed: true - comment: All data visible, axes auto-scaled appropriately - - id: SC-05 - name: Legend Accuracy - score: 2 - max: 2 - passed: true - comment: Legend correctly labels "95% Confidence Interval" and "Mean Value" - - id: SC-06 - name: Title Format - score: 2 - max: 2 + comment: X maps to days, Y maps to temperature. Full data range visible on + both axes. + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 passed: true - comment: 'Uses exact format: "band-basic · highcharts · pyplots.ai"' + comment: Title includes band-basic · highcharts · pyplots.ai format. Legend + labels accurately describe series. 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 sinusoidal trend with heteroscedastic uncertainty (widening - with x), demonstrates key band plot features + comment: 'Shows all band plot features: filled region, varying band width, + center line, smooth data, multiple oscillation cycles.' - id: DQ-02 name: Realistic Context - score: 6 - max: 7 + score: 5 + max: 5 passed: true - comment: Time series with confidence interval is plausible; generic "Value" - label slightly reduces real-world applicability + comment: Temperature forecasting with 95% prediction intervals is a genuine, + neutral, real-world scientific application. - id: DQ-03 name: Appropriate Scale - score: 5 - max: 5 + score: 4 + max: 4 passed: true - comment: Values in reasonable 24-104 range, 50 data points appropriate for - smooth band + comment: Temperatures 5-28°C realistic for temperate climate. 30-day period + standard. Uncertainty widths statistically plausible. code_quality: - score: 9 + score: 10 max: 10 items: - id: CQ-01 @@ -174,40 +189,51 @@ review: score: 3 max: 3 passed: true - comment: 'Linear structure: imports → data → chart config → render → save' + comment: 'Linear script: imports, data generation, chart config, series, HTML + export, screenshot. No functions or classes.' - 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 at start for deterministic data generation. - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: Only necessary imports used + comment: 'All imports used: tempfile, time, urllib.request, Path, numpy, highcharts-core + classes, PIL, selenium.' - id: CQ-04 - name: No Deprecated API - score: 0 - max: 1 + name: Code Elegance + score: 2 + max: 2 passed: true - comment: Using raw dict config instead of highcharts-core library classes + comment: Clean, well-organized. Appropriate complexity. Vectorized numpy operations + for data generation. - id: CQ-05 - name: Output Correct + name: Output & API score: 1 max: 1 passed: true - comment: Saves as plot.png - library_features: - score: 3 - max: 5 + comment: Saves as plot.png. Uses current highcharts-core API. Proper temp + file cleanup. + library_mastery: + score: 7 + max: 10 items: - - id: LF-01 - name: Uses distinctive library features + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: true + comment: 'Uses highcharts-core properly: Chart with container, HighchartsOptions, + typed series classes, plot_options, to_js_literal().' + - id: LM-02 + name: Distinctive Features score: 3 max: 5 passed: true - comment: Uses arearange series type correctly, but doesn't use highcharts-core - Python library as recommended + comment: Uses AreaRangeSeries, z_index layering, floating legend with transparency, + declarative plot_options. Leverages Highcharts strengths. verdict: APPROVED