diff --git a/plots/spectrogram-basic/implementations/python/highcharts.py b/plots/spectrogram-basic/implementations/python/highcharts.py index 80fc049a82..e248e04c08 100644 --- a/plots/spectrogram-basic/implementations/python/highcharts.py +++ b/plots/spectrogram-basic/implementations/python/highcharts.py @@ -1,12 +1,13 @@ -""" pyplots.ai +""" anyplot.ai spectrogram-basic: Spectrogram Time-Frequency Heatmap -Library: highcharts unknown | Python 3.13.11 -Quality: 91/100 | Created: 2025-12-31 +Library: highcharts unknown | Python 3.13.13 +Quality: 85/100 | Updated: 2026-05-15 """ -import tempfile +import os +import threading import time -import urllib.request +from http.server import HTTPServer, SimpleHTTPRequestHandler from pathlib import Path import numpy as np @@ -18,7 +19,15 @@ from selenium.webdriver.chrome.options import Options -# Generate chirp signal (frequency increases over time) +# Theme tokens +THEME = os.getenv("ANYPLOT_THEME", "light") +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" +GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)" + +# Data np.random.seed(42) sample_rate = 1000 # 1000 Hz sampling rate duration = 2.0 # 2 seconds @@ -28,7 +37,7 @@ f0, f1 = 10, 200 chirp_signal = signal.chirp(t, f0=f0, f1=f1, t1=duration, method="linear") -# Add some noise for realism +# Add noise for realism noise = 0.1 * np.random.randn(len(t)) combined_signal = chirp_signal + noise @@ -40,7 +49,7 @@ # Convert to dB scale for better visualization Sxx_db = 10 * np.log10(Sxx + 1e-10) -# Get dB range for colorbar (use actual dB values, not normalized) +# Get dB range for colorbar Sxx_min = float(Sxx_db.min()) Sxx_max = float(Sxx_db.max()) @@ -70,40 +79,46 @@ "type": "heatmap", "width": 4800, "height": 2700, - "backgroundColor": "#ffffff", - "marginTop": 160, # Increased for subtitle + "backgroundColor": PAGE_BG, + "marginTop": 160, "marginBottom": 250, "marginLeft": 200, - "marginRight": 320, # Increased for legend title spacing + "marginRight": 320, } -# Title and subtitle +# Title chart.options.title = { - "text": "spectrogram-basic · highcharts · pyplots.ai", - "style": {"fontSize": "48px", "fontWeight": "bold"}, + "text": "spectrogram-basic · highcharts · anyplot.ai", + "style": {"fontSize": "28px", "fontWeight": "bold", "color": INK}, } +# Subtitle chart.options.subtitle = { "text": "Linear chirp signal (10-200 Hz) with linear frequency axis", - "style": {"fontSize": "32px", "color": "#666666"}, + "style": {"fontSize": "22px", "color": INK_SOFT}, } # X-axis (time) time_labels = [f"{t:.2f}" for t in times_ds] chart.options.x_axis = { "categories": time_labels, - "title": {"text": "Time (seconds)", "style": {"fontSize": "36px"}}, - "labels": {"style": {"fontSize": "24px"}, "step": max(1, len(time_labels) // 10)}, - "tickLength": 10, + "title": {"text": "Time (seconds)", "style": {"fontSize": "22px", "color": INK}}, + "labels": {"style": {"fontSize": "18px", "color": INK_SOFT}, "step": max(1, len(time_labels) // 10)}, + "lineColor": INK_SOFT, + "tickColor": INK_SOFT, + "gridLineColor": GRID, } # Y-axis (frequency) freq_labels = [f"{f:.0f}" for f in frequencies_ds] chart.options.y_axis = { "categories": freq_labels, - "title": {"text": "Frequency (Hz)", "style": {"fontSize": "36px"}}, - "labels": {"style": {"fontSize": "24px"}, "step": max(1, len(freq_labels) // 10)}, + "title": {"text": "Frequency (Hz)", "style": {"fontSize": "22px", "color": INK}}, + "labels": {"style": {"fontSize": "18px", "color": INK_SOFT}, "step": max(1, len(freq_labels) // 10)}, "reversed": False, + "lineColor": INK_SOFT, + "tickColor": INK_SOFT, + "gridLineColor": GRID, } # Color axis (legend for heatmap intensity) - use actual dB values @@ -117,7 +132,7 @@ [0.75, "#5ec962"], # green [1, "#fde725"], # yellow ], - "labels": {"style": {"fontSize": "24px"}, "format": "{value:.0f} dB"}, + "labels": {"style": {"fontSize": "18px", "color": INK_SOFT}, "format": "{value:.0f} dB"}, } # Legend @@ -127,15 +142,19 @@ "verticalAlign": "middle", "symbolHeight": 800, "symbolWidth": 40, - "x": -20, # Shift legend left to avoid edge cramping - "title": {"text": "Power (dB)", "style": {"fontSize": "28px"}}, + "x": -20, + "title": {"text": "Power (dB)", "style": {"fontSize": "22px", "color": INK}}, + "itemStyle": {"color": INK_SOFT}, + "backgroundColor": ELEVATED_BG, + "borderColor": INK_SOFT, + "borderWidth": 1, } -# Tooltip - show actual dB values +# Tooltip chart.options.tooltip = { "headerFormat": "", "pointFormat": "Time: {point.x_label} s
Frequency: {point.y_label} Hz
Power: {point.value:.1f} dB", - "style": {"fontSize": "20px"}, + "style": {"fontSize": "16px"}, } # Series @@ -146,35 +165,41 @@ chart.add_series(series) -# Download Highcharts JS and heatmap module -highcharts_url = "https://code.highcharts.com/highcharts.js" -with urllib.request.urlopen(highcharts_url, timeout=30) as response: - highcharts_js = response.read().decode("utf-8") - -heatmap_url = "https://code.highcharts.com/modules/heatmap.js" -with urllib.request.urlopen(heatmap_url, timeout=30) as response: - heatmap_js = response.read().decode("utf-8") - -# Generate HTML with inline scripts +# Generate chart JavaScript html_str = chart.to_js_literal() + +# HTML with unpkg CDN (Cloudflare-friendly alternative) html_content = f""" - - + + - +
""" -# Write temp HTML and take screenshot -with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f: +# Save interactive HTML +with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f: f.write(html_content) - temp_path = f.name + +# Start simple HTTP server in background thread to serve HTML +class QuietHTTPRequestHandler(SimpleHTTPRequestHandler): + def log_message(self, format, *args): + pass + + +html_dir = Path.cwd() +server = HTTPServer(("127.0.0.1", 0), QuietHTTPRequestHandler) +port = server.server_port +server_thread = threading.Thread(target=server.serve_forever, daemon=True) +server_thread.start() + +# Take screenshot with Selenium via HTTP chrome_options = Options() chrome_options.add_argument("--headless") chrome_options.add_argument("--no-sandbox") @@ -182,27 +207,22 @@ chrome_options.add_argument("--disable-gpu") chrome_options.add_argument("--window-size=4800,2700") -driver = webdriver.Chrome(options=chrome_options) -driver.get(f"file://{temp_path}") -time.sleep(5) # Wait for chart to render -driver.save_screenshot("plot.png") -driver.quit() - -Path(temp_path).unlink() # Clean up temp file - -# Also save HTML for interactive version -with open("plot.html", "w", encoding="utf-8") as f: - interactive_html = f""" - - - - spectrogram-basic · highcharts · pyplots.ai - - - - -
- - -""" - f.write(interactive_html) +try: + driver = webdriver.Chrome(options=chrome_options) + driver.set_page_load_timeout(30) + driver.get(f"http://127.0.0.1:{port}/plot-{THEME}.html") + + # Wait for container to be visible and chart to render + time.sleep(20) + + # Additional wait to ensure rendering is complete + try: + driver.execute_script("return document.readyState === 'complete' && window.Highcharts !== undefined") + except Exception: + pass + + time.sleep(2) + driver.save_screenshot(f"plot-{THEME}.png") + driver.quit() +finally: + server.shutdown() diff --git a/plots/spectrogram-basic/metadata/python/highcharts.yaml b/plots/spectrogram-basic/metadata/python/highcharts.yaml index cacee0527b..53a94f4d18 100644 --- a/plots/spectrogram-basic/metadata/python/highcharts.yaml +++ b/plots/spectrogram-basic/metadata/python/highcharts.yaml @@ -1,207 +1,259 @@ library: highcharts +language: python specification_id: spectrogram-basic created: '2025-12-31T05:36:53Z' -updated: '2025-12-31T06:07:15Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 20612809126 +updated: '2026-05-15T22:12:25Z' +generated_by: claude-haiku +workflow_run: 25906243314 issue: 2927 -python_version: 3.13.11 +python_version: 3.13.13 library_version: unknown -preview_url: https://storage.googleapis.com/anyplot-images/plots/spectrogram-basic/highcharts/plot.png -preview_html: https://storage.googleapis.com/anyplot-images/plots/spectrogram-basic/highcharts/plot.html -quality_score: 91 -impl_tags: - dependencies: - - scipy - - selenium - techniques: - - colorbar - - html-export - patterns: - - data-generation - dataprep: [] - styling: [] +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/spectrogram-basic/python/highcharts/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/spectrogram-basic/python/highcharts/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/spectrogram-basic/python/highcharts/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/spectrogram-basic/python/highcharts/plot-dark.html +quality_score: 85 review: strengths: - - Clear visualization of the chirp signal with frequency increasing over time (diagonal - yellow band is unmistakable) - - Excellent use of viridis colormap for colorblind accessibility - - Proper dB scale labeling on colorbar with actual dB values (-100 to -10 dB range) - - Clean code structure with scipy spectrogram computation - - Both PNG and interactive HTML outputs generated - - Appropriate downsampling for Highcharts heatmap performance + - 'Perfect spec compliance: correct spectrogram type with time-frequency heatmap, + dB power scale, viridis colormap, and labeled colorbar' + - 'Excellent theme adaptation: both renders use correct background tokens (#FAF8F1/#1A1A17) + with fully theme-adaptive chrome (INK, INK_SOFT tokens throughout); no legibility + failures in either theme' + - Informative subtitle contextualizes the signal; the natural chirp sweep focal + point (bright yellow diagonal band) provides immediate visual storytelling without + annotations + - Realistic signal processing pipeline (scipy chirp + STFT spectrogram) with appropriate + dB scaling and noise for a scientifically credible example weaknesses: - - No grid lines on the heatmap area (would improve readability at specific time/frequency - points) - - Y-axis shows frequencies up to ~469 Hz but the chirp only goes to 200 Hz (wasted - vertical space showing noise) - image_description: The spectrogram displays a heatmap of a linear chirp signal. - The plot shows time (0.06 to 1.91 seconds) on the x-axis and frequency (0 to ~469 - Hz) on the y-axis. A clear diagonal yellow band sweeps from low frequencies at - early times to high frequencies at later times, representing the increasing frequency - of the chirp signal. The background is predominantly teal/green (mid-power levels) - with scattered darker blue/purple spots (low power). A vertical colorbar on the - right shows "Power (dB)" ranging from approximately -100 dB (purple) to -10 dB - (yellow), using a viridis colormap. The title reads "spectrogram-basic · highcharts - · pyplots.ai" with a subtitle "Linear chirp signal (10-200 Hz) with linear frequency - axis". + - CDN + HTTP server approach deviates from the highcharts.md guideline to embed + JS inline; introduces network dependency and extra complexity (threading, class + definition) + - Design sophistication is at the well-configured defaults tier (DE-01=4); lacks + distinctive typographic or layout choices that would push to publication-ready + quality + - Downsampling to 80x50 grid creates blocky cell appearance; higher resolution (e.g., + 120x70) would better represent the smooth frequency sweep and improve perceived + quality + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) - correct light theme surface, not pure white + Chrome: Title "spectrogram-basic · highcharts · anyplot.ai" in dark ink (#1A1A17) clearly readable at top. Subtitle "Linear chirp signal (10-200 Hz) with linear frequency axis" in INK_SOFT (#4A4A44) readable below title. X-axis label "Time (seconds)" and Y-axis label "Frequency (Hz)" in dark ink, clearly visible. Tick labels (0-469 Hz on Y; 0.06-1.91s on X) in INK_SOFT, readable. Colorbar "Power (dB)" title and dB labels (-70 to -10 dB) in INK_SOFT on right side, readable. + Data: Viridis colormap (dark purple low power → teal mid → bright yellow high power). Clear diagonal yellow band tracing chirp sweep from ~10 Hz at t=0 to ~188 Hz at t=1.91s. Background shows green/teal noise floor. First and only series uses viridis continuous scale (not categorical Okabe-Ito, correct for continuous data). + Legibility verdict: PASS + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17) - correct dark theme surface, not pure black + Chrome: Title in off-white (#F0EFE8) clearly readable against dark background. Subtitle in INK_SOFT dark token (#B8B7B0) clearly visible. X-axis and Y-axis labels in light off-white, clearly readable. Tick labels in #B8B7B0, visible against dark surface. Colorbar title and labels in #B8B7B0, readable. No dark-on-dark failures detected - all chrome text is appropriately light-colored. + Data: Viridis color stops identical to light render - same diagonal yellow chirp sweep, same teal/green noise floor. Data colors (positions in viridis) unchanged between themes, confirming only chrome flipped. + Legibility verdict: PASS criteria_checklist: visual_quality: - score: 36 - max: 40 + score: 28 + max: 30 items: - id: VQ-01 name: Text Legibility - score: 9 - max: 10 + score: 7 + max: 8 passed: true - comment: Title and axis labels are clearly readable; tick labels slightly - small but legible + comment: All font sizes explicitly set (title 28px, axis labels 22px, tick + labels 18px); readable in both themes; minor deduction for colorbar labels + being on smaller end within the margin column - id: VQ-02 name: No Overlap - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: No overlapping text elements + comment: No overlapping text; axis label stepping configured to avoid crowding - id: VQ-03 name: Element Visibility - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: Heatmap cells are appropriately sized for the data density + comment: Chirp sweep clearly visible as bright diagonal band; heatmap cells + well-sized at 80x50 grid; colorbar distinct - id: VQ-04 name: Color Accessibility - score: 5 - max: 5 + score: 2 + max: 2 passed: true - comment: Viridis colormap is colorblind-safe + comment: Viridis is perceptually uniform and fully CVD-safe; excellent contrast + across power range - id: VQ-05 - name: Layout Balance - score: 4 - max: 5 + name: Layout & Canvas + score: 3 + max: 4 passed: true - comment: Good canvas utilization; slight imbalance with right margin for legend + comment: Plot fills approximately 70-75% of canvas; margins balanced and accommodate + colorbar; slight bottom whitespace prevents perfect 4 - id: VQ-06 - name: Axis Labels + name: Axis Labels & Title score: 2 max: 2 passed: true - comment: '"Time (seconds)" and "Frequency (Hz)" with proper units' + comment: 'X: Time (seconds), Y: Frequency (Hz), colorbar: Power (dB) - all + descriptive with units' - id: VQ-07 - name: Grid & Legend - score: 0 + name: Palette Compliance + score: 2 max: 2 passed: true - comment: No visible grid lines; legend/colorbar is present but lacks subtle - gridlines on plot + comment: 'Viridis stops correctly applied to continuous data; light bg #FAF8F1, + dark bg #1A1A17; all chrome tokens theme-adaptive in both renders' + design_excellence: + score: 12 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 4 + max: 8 + passed: false + comment: Well-configured professional appearance with proper theme tokens + and viridis colormap, but falls in configured library default tier; no custom + typography or distinctive visual flourish + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: 'Custom margins, borderWidth: 0 for cleaner cells, subtle GRID tokens; + noticeably above bare defaults' + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: Subtitle contextualizes signal; viridis creates natural focal point + with bright yellow sweep; viewer reads chirp trajectory immediately 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 heatmap spectrogram visualization - - id: SC-02 - name: Data Mapping score: 5 max: 5 passed: true - comment: Time on x-axis, frequency on y-axis, power as color intensity - - id: SC-03 + comment: Correct spectrogram as time-frequency heatmap using HeatmapSeries + - id: SC-02 name: Required Features - score: 5 - max: 5 + score: 4 + max: 4 passed: true - comment: Colorbar with dB units, proper axis labels, perceptually uniform - colormap - - id: SC-04 - name: Data Range + comment: Color intensity encodes power; dB scale; time on X, frequency on + Y; colorbar with dB labels; chirp demonstrates non-stationary frequency + content + - id: SC-03 + name: Data Mapping score: 3 max: 3 passed: true - comment: Full time and frequency range displayed - - id: SC-05 - name: Legend Accuracy - score: 2 - max: 2 - passed: true - comment: Colorbar correctly labeled "Power (dB)" - - id: SC-06 - name: Title Format - score: 2 - max: 2 + comment: Time (seconds) on X, Frequency (Hz) on Y, Power (dB) in color - all + correct + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 passed: true - comment: Uses correct format "{spec-id} · {library} · pyplots.ai" + comment: Title 'spectrogram-basic · highcharts · anyplot.ai' matches required + format; colorbar labeled 'Power (dB)' data_quality: - score: 18 - max: 20 + score: 14 + max: 15 items: - id: DQ-01 name: Feature Coverage - score: 7 - max: 8 + score: 5 + max: 6 passed: true - comment: Chirp signal clearly shows frequency increasing over time; could - show additional signal features + comment: Demonstrates chirp sweep, background noise, power dynamic range, + full spectrogram structure; minor deduction for 80x50 downsampling losing + fine frequency resolution - id: DQ-02 name: Realistic Context - score: 7 - max: 7 + score: 5 + max: 5 passed: true - comment: Signal processing context is realistic and neutral + comment: Chirp signal with gaussian noise is a standard neutral signal processing + test case; scientifically realistic and non-controversial - id: DQ-03 name: Appropriate Scale score: 4 - max: 5 + max: 4 passed: true - comment: Frequencies 10-200 Hz appropriate; y-axis extends to ~500 Hz which - is fine but shows noise above signal range + comment: 1000 Hz sample rate, 2-second duration, 10-200 Hz sweep, dB scaling + - all physically realistic and domain-appropriate code_quality: score: 9 max: 10 items: - id: CQ-01 name: KISS Structure - score: 3 + score: 2 max: 3 passed: true - comment: 'Linear flow: imports → data → plot → save' + comment: Sequential flow clear but QuietHTTPRequestHandler class definition + deviates from pure imports-data-plot-save pattern - 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 before noise generation - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: All imports are used + comment: All imported modules 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 APIs + comment: HTTP server approach justified; spectrogram pipeline clean; try/finally + ensures server cleanup - id: CQ-05 - name: Output Correct - score: 0 + name: Output & API + score: 1 max: 1 - passed: false - comment: Saves as plot.png (correct) - library_features: - score: 3 - max: 5 + passed: true + comment: Saves plot-{THEME}.png and plot-{THEME}.html correctly for interactive + library + 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: Correctly uses HeatmapSeries with [x, y, value] format, color_axis + for colorbar, chart.to_js_literal(); deduction for CDN via HTTP server instead + of recommended inline embedding + - id: LM-02 + name: Distinctive Features score: 3 max: 5 passed: true - comment: Uses Highcharts heatmap with proper color axis and tooltip; could - leverage more interactive features + comment: Highcharts-specific color_axis with custom viridis stops, symbolHeight/symbolWidth + for colorbar legend, HeatmapSeries - not trivially portable to other libraries verdict: APPROVED +impl_tags: + dependencies: + - scipy + - selenium + techniques: + - colorbar + - html-export + patterns: + - data-generation + - matrix-construction + - iteration-over-groups + dataprep: + - stft + styling: + - custom-colormap