diff --git a/plots/waveform-audio/implementations/python/highcharts.py b/plots/waveform-audio/implementations/python/highcharts.py new file mode 100644 index 0000000000..15f7525997 --- /dev/null +++ b/plots/waveform-audio/implementations/python/highcharts.py @@ -0,0 +1,213 @@ +""" anyplot.ai +waveform-audio: Audio Waveform Plot +Library: highcharts unknown | Python 3.13.13 +Quality: 89/100 | Created: 2026-06-03 +""" + +import os +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 AreaSeries +from PIL import Image +from selenium import webdriver +from selenium.webdriver.chrome.options import Options + + +# Theme tokens — Imprint palette, see prompts/default-style-guide.md +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.15)" if THEME == "light" else "rgba(240,239,232,0.15)" +SYLLABLE_BAND = "rgba(0,158,115,0.09)" if THEME == "light" else "rgba(0,158,115,0.13)" + +IMPRINT_PALETTE = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477", "#99B314"] +BRAND = IMPRINT_PALETTE[0] # Imprint palette position 1 — ALWAYS first series + +# Data — synthetic speech waveform (two voiced syllables, 2 s at 44 100 Hz) +np.random.seed(42) +SAMPLE_RATE = 44100 +DURATION = 2.0 +n_samples = int(SAMPLE_RATE * DURATION) +t_full = np.linspace(0, DURATION, n_samples) + +# Voiced source: fundamental (130 Hz) + harmonics → vowel-like timbre +f0 = 130 +raw = ( + 0.50 * np.sin(2 * np.pi * 1 * f0 * t_full) + + 0.25 * np.sin(2 * np.pi * 2 * f0 * t_full) + + 0.15 * np.sin(2 * np.pi * 3 * f0 * t_full) + + 0.07 * np.sin(2 * np.pi * 4 * f0 * t_full) + + 0.03 * np.sin(2 * np.pi * 5 * f0 * t_full) +) + +# Amplitude envelope: two syllables separated by a brief pause +envelope = np.zeros(n_samples) +mask1 = (t_full >= 0.15) & (t_full <= 0.65) +envelope[mask1] = np.hanning(mask1.sum()) +mask2 = (t_full >= 0.85) & (t_full <= 1.55) +envelope[mask2] = np.hanning(mask2.sum()) * 0.80 + +raw = raw * envelope + 0.005 * np.random.randn(n_samples) +raw = np.clip(raw, -1.0, 1.0) + +# Min/max envelope downsampling — captures peaks and troughs per window, avoids aliasing +# 1500 windows × 2 samples (peak + trough) = 3000 display points +n_windows = 1500 +window_size = n_samples // n_windows +t_env = [] +for i in range(n_windows): + start = i * window_size + end = min(start + window_size, n_samples) + w = raw[start:end] + times = t_full[start:end] + max_i = int(np.argmax(w)) + min_i = int(np.argmin(w)) + t_env.append((float(times[max_i]), float(w[max_i]))) + t_env.append((float(times[min_i]), float(w[min_i]))) +t_env.sort() + +data_points = [[round(t, 6), round(a, 5)] for t, a in t_env] + +# Title — font size scaled to prevent overflow at 3200 px width +title = "Speech Waveform · waveform-audio · python · highcharts · anyplot.ai" +title_px = max(44, round(66 * 67 / len(title))) + +# Chart +chart = Chart(container="container") +chart.options = HighchartsOptions() + +chart.options.chart = { + "type": "area", + "width": 3200, + "height": 1800, + "backgroundColor": PAGE_BG, + "style": {"color": INK}, + "marginBottom": 130, + "marginLeft": 155, + "marginRight": 60, + "marginTop": 120, +} + +chart.options.title = {"text": title, "style": {"fontSize": f"{title_px}px", "color": INK, "fontWeight": "600"}} +chart.options.subtitle = { + "text": "Two voiced syllables with harmonic structure (F0 = 130 Hz) separated by a brief silence gap", + "style": {"fontSize": "40px", "color": INK_SOFT}, +} + +chart.options.x_axis = { + "title": {"text": "Time (seconds)", "style": {"fontSize": "56px", "color": INK}}, + "labels": {"style": {"fontSize": "44px", "color": INK_SOFT}}, + "tickInterval": 0.25, + "lineColor": INK_SOFT, + "tickColor": INK_SOFT, + "gridLineColor": GRID, + "gridLineWidth": 1, + "plotBands": [ + {"from": 0.15, "to": 0.65, "color": SYLLABLE_BAND}, + {"from": 0.85, "to": 1.55, "color": SYLLABLE_BAND}, + ], +} + +chart.options.y_axis = { + "title": {"text": "Amplitude", "style": {"fontSize": "56px", "color": INK}}, + "labels": {"style": {"fontSize": "44px", "color": INK_SOFT}}, + "min": -0.8, + "max": 0.8, + "tickInterval": 0.4, + "lineColor": INK_SOFT, + "tickColor": INK_SOFT, + "gridLineColor": GRID, + "gridLineWidth": 1, + "plotLines": [{"value": 0, "color": INK_SOFT, "width": 2, "zIndex": 5}], +} + +chart.options.colors = [BRAND] + +chart.options.plot_options = { + "area": { + "marker": {"enabled": False}, + "fillOpacity": 0.40, + "threshold": 0, + "lineWidth": 1.5, + "states": {"hover": {"lineWidth": 1.5}}, + } +} + +chart.options.legend = {"enabled": False} + +chart.options.tooltip = { + "backgroundColor": ELEVATED_BG, + "style": {"color": INK, "fontSize": "36px"}, + "borderColor": INK_SOFT, + "valueDecimals": 4, +} + +# Series +waveform = AreaSeries() +waveform.name = "Amplitude" +waveform.data = data_points +chart.add_series(waveform) + +# Download Highcharts JS — CDN unavailable from file:// in headless Chrome +req = urllib.request.Request( + "https://code.highcharts.com/highcharts.js", + headers={"User-Agent": "Mozilla/5.0", "Referer": "https://www.highcharts.com/"}, +) +with urllib.request.urlopen(req, timeout=30) as resp: + highcharts_js = resp.read().decode("utf-8") + +js_literal = chart.to_js_literal() +html_content = f""" + + + + + + +
+ + +""" + +# Save HTML artifact +with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f: + f.write(html_content) + +# Screenshot via headless Chrome with authoritative CDP viewport override +with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f: + f.write(html_content) + temp_path = f.name + +chrome_options = Options() +chrome_options.add_argument("--headless=new") +chrome_options.add_argument("--no-sandbox") +chrome_options.add_argument("--disable-dev-shm-usage") +chrome_options.add_argument("--disable-gpu") +chrome_options.add_argument("--hide-scrollbars") +chrome_options.add_argument("--window-size=3200,1800") + +driver = webdriver.Chrome(options=chrome_options) +driver.execute_cdp_cmd( + "Emulation.setDeviceMetricsOverride", {"width": 3200, "height": 1800, "deviceScaleFactor": 1, "mobile": False} +) +driver.get(f"file://{temp_path}") +time.sleep(5) +driver.save_screenshot(f"plot-{THEME}.png") +driver.quit() + +Path(temp_path).unlink() + +# Pin to exact 3200×1800 — belt-and-braces against ±1-2 px rounding +_img = Image.open(f"plot-{THEME}.png").convert("RGB") +if _img.size != (3200, 1800): + _norm = Image.new("RGB", (3200, 1800), PAGE_BG) + _norm.paste(_img, ((3200 - _img.size[0]) // 2, (1800 - _img.size[1]) // 2)) + _norm.save(f"plot-{THEME}.png") diff --git a/plots/waveform-audio/metadata/python/highcharts.yaml b/plots/waveform-audio/metadata/python/highcharts.yaml new file mode 100644 index 0000000000..6ea103369a --- /dev/null +++ b/plots/waveform-audio/metadata/python/highcharts.yaml @@ -0,0 +1,241 @@ +library: highcharts +language: python +specification_id: waveform-audio +created: '2026-06-03T01:19:29Z' +updated: '2026-06-03T01:38:48Z' +generated_by: claude-sonnet +workflow_run: 26857611677 +issue: 4563 +language_version: 3.13.13 +library_version: unknown +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/waveform-audio/python/highcharts/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/waveform-audio/python/highcharts/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/waveform-audio/python/highcharts/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/waveform-audio/python/highcharts/plot-dark.html +quality_score: 89 +review: + strengths: + - Syllable band overlays (plotBands) are a high-quality design choice that immediately + communicates speech structure to the viewer + - Min/max envelope downsampling (1500 windows x 2 samples) correctly handles the + 88200-sample waveform without aliasing artifacts + - 'Complete theme-adaptive chrome: all INK, INK_SOFT, GRID tokens applied throughout + — both renders look correct' + - Both PNG and HTML artifacts saved correctly; CDP + PIL belt-and-braces ensures + exact 3200x1800 output + - Acoustically realistic synthetic data (harmonics, hanning envelopes, noise floor, + silence gap) + weaknesses: + - 'Y-axis range is ±0.8 instead of the spec-mandated ±1.0 — fix: set min: -1.0, + max: 1.0, tickInterval: 0.5 on y_axis' + - LM-02 could be stronger — no use of Highcharts-specific advanced features beyond + plotBands/plotLines; consider Highcharts Boost module or navigator for dense waveform + data + image_description: |- + Light render (plot-light.png): + Background: Warm off-white #FAF8F1 — correct theme surface + Chrome: Title "Speech Waveform · waveform-audio · python · highcharts · anyplot.ai" in bold dark ink, spans ~75% canvas width; subtitle in INK_SOFT (muted dark); axis labels "Time (seconds)" and "Amplitude" in dark INK at 56px; tick labels in INK_SOFT at 44px — all readable + Data: Brand green #009E73 area fill with 40% opacity; two syllable bands in rgba(0,158,115,0.09) highlight voiced regions 0.15-0.65s and 0.85-1.55s; zero reference line clearly visible; dense waveform with two syllable peaks and silence gap + Legibility verdict: PASS + + Dark render (plot-dark.png): + Background: Warm near-black #1A1A17 — correct dark theme surface + Chrome: Title and axis labels shift to light cream F0EFE8; tick labels and subtitle shift to B8B7B0; all text clearly readable against dark background; no dark-on-dark failures detected + Data: Waveform color identical to light render — brand green #009E73 unchanged; syllable bands slightly more saturated (rgba(0,158,115,0.13)) but tasteful; zero line visible + Legibility verdict: PASS + criteria_checklist: + visual_quality: + score: 29 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 7 + max: 8 + passed: true + comment: All text explicitly sized; dynamically scaled title; both renders + readable; subtitle at 40px is at low end + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No text collisions; syllable bands do not obscure labels + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: Min/max envelope rendering produces clean dense waveform; fillOpacity + 0.40 appropriate + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Single series brand green; CVD-safe + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: Canvas 3200x1800 confirmed; generous margins; nothing clipped + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: Descriptive labels with units + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'First series #009E73; backgrounds #FAF8F1/#1A1A17; all chrome tokens + theme-adaptive' + design_excellence: + score: 13 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 5 + max: 8 + passed: true + comment: Syllable bands add narrative; subtitle and dynamic title scaling + show care; still within standard area-chart conventions + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: Semi-transparent fill, subtle grid tokens, zero reference line, clean + axis presentation + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: Syllable bands highlight voiced regions; silence gap immediately + visible; subtitle reinforces narrative + spec_compliance: + score: 14 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: Area series with threshold:0 produces mirrored filled waveform as + specified + - id: SC-02 + name: Required Features + score: 3 + max: 4 + passed: true + comment: Semi-transparent fill, zero line, time axis, synthetic data, envelope + rendering all present; y-axis range is +-0.8 not +-1.0 as spec requires + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: X=time in seconds, Y=normalized amplitude; all data visible + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Correct title format with descriptive prefix; legend disabled for + single series + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: Two syllables, harmonic structure, silence gap, noise floor, hanning + envelopes all demonstrated + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: F0=130 Hz typical male voice; 44100 Hz sample rate; acoustically + realistic and neutral + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: Normalized amplitude domain; 2s duration; standard audio sample rate + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: Linear script, no unnecessary functions + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: np.random.seed(42) + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: All imports actively used + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Min/max envelope algorithm clear; no fake UI + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Saves plot-{THEME}.png and plot-{THEME}.html; CDP+PIL ensures exact + 3200x1800 + library_mastery: + score: 8 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 5 + max: 5 + passed: true + comment: Chart(container), HighchartsOptions, AreaSeries, CDN-inline embedding, + CDP viewport override all correct + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: true + comment: plotBands and plotLines are Highcharts-specific; bidirectional fill + via threshold:0; could use Boost module or navigator for denser signal work + verdict: APPROVED +impl_tags: + dependencies: + - selenium + - pillow + techniques: + - html-export + patterns: + - data-generation + dataprep: + - binning + styling: + - alpha-blending