From 7643ba9d28e7e0b93b50ae8bfd20746a076988ca Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 3 Jun 2026 01:29:09 +0000 Subject: [PATCH 1/5] feat(d3): implement waveform-audio --- .../implementations/javascript/d3.js | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 plots/waveform-audio/implementations/javascript/d3.js diff --git a/plots/waveform-audio/implementations/javascript/d3.js b/plots/waveform-audio/implementations/javascript/d3.js new file mode 100644 index 0000000000..a9b5c086b8 --- /dev/null +++ b/plots/waveform-audio/implementations/javascript/d3.js @@ -0,0 +1,141 @@ +// anyplot.ai +// waveform-audio: Audio Waveform Plot +// Library: d3 7.9.0 | JavaScript 22 +// Quality: pending | Created: 2026-06-03 + +const t = window.ANYPLOT_TOKENS; +const { width, height } = window.ANYPLOT_SIZE; +const margin = { top: 80, right: 50, bottom: 90, left: 100 }; +const iw = width - margin.left - margin.right; +const ih = height - margin.top - margin.bottom; + +// --- Data: synthetic vocal phrase — three syllables, fully deterministic, 1.7 s at 8 kHz --- +const SR = 8000; +const DUR = 1.7; +const N = Math.ceil(SR * DUR); // 13600 samples + +// Three voiced syllables: start/end seconds, fundamental F0 (Hz), attack/release durations (s) +const SYLS = [ + { start: 0.10, end: 0.55, f0: 130, atk: 0.04, rel: 0.12 }, + { start: 0.65, end: 1.10, f0: 155, atk: 0.04, rel: 0.12 }, + { start: 1.20, end: 1.60, f0: 145, atk: 0.04, rel: 0.10 }, +]; + +function envAt(time, syl) { + const tl = time - syl.start; + const dur = syl.end - syl.start; + if (tl < 0 || tl >= dur) return 0; + if (tl < syl.atk) return tl / syl.atk; + if (dur - tl < syl.rel) return (dur - tl) / syl.rel; + return 1; +} + +function signalAt(time) { + let s = 0; + for (const syl of SYLS) { + const env = envAt(time, syl); + if (env === 0) continue; + const tau = 2 * Math.PI * time; + const f = syl.f0; + s += env * ( + 0.45 * Math.sin(f * tau) + + 0.28 * Math.sin(2 * f * tau) + + 0.16 * Math.sin(3 * f * tau) + + 0.11 * Math.sin(4 * f * tau) + ); + } + return s; +} + +// Build raw signal, then downsample to display-width bins (min/max envelope per pixel column) +const rawAmp = new Float32Array(N); +for (let i = 0; i < N; i++) rawAmp[i] = signalAt(i / SR); + +const nBins = Math.round(iw); +const bSz = N / nBins; +const waveData = Array.from({ length: nBins }, (_, b) => { + const s = Math.floor(b * bSz); + const e = Math.min(Math.ceil((b + 1) * bSz), N); + let lo = Infinity, hi = -Infinity; + for (let j = s; j < e; j++) { + if (rawAmp[j] < lo) lo = rawAmp[j]; + if (rawAmp[j] > hi) hi = rawAmp[j]; + } + return { tPos: s / SR, lo, hi }; +}); + +// --- SVG mount --- +const svg = d3.select("#container").append("svg") + .attr("width", width).attr("height", height); +const g = svg.append("g").attr("transform", `translate(${margin.left},${margin.top})`); + +// --- Scales --- +const xScale = d3.scaleLinear().domain([0, DUR]).range([0, iw]); +const yScale = d3.scaleLinear().domain([-1.05, 1.05]).range([ih, 0]); + +// --- Horizontal gridlines (y-axis only, subtle) --- +g.selectAll(".hg").data([-1, -0.5, 0, 0.5, 1]).join("line") + .attr("class", "hg") + .attr("x1", 0).attr("x2", iw) + .attr("y1", d => yScale(d)).attr("y2", d => yScale(d)) + .attr("stroke", t.grid) + .attr("stroke-width", 0.5); + +// --- Waveform filled area (min/max envelope per display column) --- +g.append("path") + .datum(waveData) + .attr("d", d3.area() + .x(d => xScale(d.tPos)) + .y0(d => yScale(d.lo)) + .y1(d => yScale(d.hi))) + .attr("fill", t.palette[0]) + .attr("fill-opacity", 0.72) + .attr("stroke", t.palette[0]) + .attr("stroke-width", 0.4) + .attr("stroke-opacity", 0.9); + +// --- Zero reference line (slightly more visible than gridlines) --- +g.append("line") + .attr("x1", 0).attr("x2", iw) + .attr("y1", yScale(0)).attr("y2", yScale(0)) + .attr("stroke", t.inkSoft) + .attr("stroke-width", 1); + +// --- Axes --- +const xAxis = g.append("g") + .attr("transform", `translate(0,${ih})`) + .call(d3.axisBottom(xScale).ticks(9).tickFormat(d => d.toFixed(1))); + +const yAxis = g.append("g") + .call(d3.axisLeft(yScale).tickValues([-1, -0.5, 0, 0.5, 1])); + +for (const ax of [xAxis, yAxis]) { + ax.selectAll("text").attr("fill", t.inkSoft).style("font-size", "16px"); + ax.selectAll("line").attr("stroke", t.inkSoft).attr("stroke-width", 0.5); + ax.select(".domain").attr("stroke", t.inkSoft); +} + +// --- Axis labels --- +svg.append("text") + .attr("x", margin.left + iw / 2) + .attr("y", height - 18) + .attr("text-anchor", "middle") + .attr("fill", t.ink) + .style("font-size", "20px") + .text("Time (s)"); + +svg.append("text") + .attr("transform", `translate(22, ${margin.top + ih / 2}) rotate(-90)`) + .attr("text-anchor", "middle") + .attr("fill", t.ink) + .style("font-size", "20px") + .text("Amplitude"); + +// --- Title --- +svg.append("text") + .attr("x", width / 2).attr("y", 48) + .attr("text-anchor", "middle") + .attr("fill", t.ink) + .style("font-size", "28px") + .style("font-weight", "600") + .text("waveform-audio · javascript · d3 · anyplot.ai"); From 62e1e807ce24f73357c94ccb3a0cce5a63a64a1f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 3 Jun 2026 01:29:19 +0000 Subject: [PATCH 2/5] chore(d3): add metadata for waveform-audio --- .../metadata/javascript/d3.yaml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 plots/waveform-audio/metadata/javascript/d3.yaml diff --git a/plots/waveform-audio/metadata/javascript/d3.yaml b/plots/waveform-audio/metadata/javascript/d3.yaml new file mode 100644 index 0000000000..1b58b1b2f2 --- /dev/null +++ b/plots/waveform-audio/metadata/javascript/d3.yaml @@ -0,0 +1,21 @@ +# Per-library metadata for d3 implementation of waveform-audio +# Auto-generated by impl-generate.yml + +library: d3 +language: javascript +specification_id: waveform-audio +created: '2026-06-03T01:29:19Z' +updated: '2026-06-03T01:29:19Z' +generated_by: claude-sonnet +workflow_run: 26857934068 +issue: 4563 +language_version: 22.22.3 +library_version: 7.9.0 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/waveform-audio/javascript/d3/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/waveform-audio/javascript/d3/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/waveform-audio/javascript/d3/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/waveform-audio/javascript/d3/plot-dark.html +quality_score: null +review: + strengths: [] + weaknesses: [] From a6171507e3d507ad55380f4d8c20bc73150c0d5d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 3 Jun 2026 01:36:10 +0000 Subject: [PATCH 3/5] chore(d3): update quality score 88 and review feedback for waveform-audio --- .../implementations/javascript/d3.js | 4 +- .../metadata/javascript/d3.yaml | 246 +++++++++++++++++- 2 files changed, 241 insertions(+), 9 deletions(-) diff --git a/plots/waveform-audio/implementations/javascript/d3.js b/plots/waveform-audio/implementations/javascript/d3.js index a9b5c086b8..e60af871d8 100644 --- a/plots/waveform-audio/implementations/javascript/d3.js +++ b/plots/waveform-audio/implementations/javascript/d3.js @@ -1,7 +1,7 @@ // anyplot.ai // waveform-audio: Audio Waveform Plot -// Library: d3 7.9.0 | JavaScript 22 -// Quality: pending | Created: 2026-06-03 +// Library: d3 7.9.0 | JavaScript 22.22.3 +// Quality: 88/100 | Created: 2026-06-03 const t = window.ANYPLOT_TOKENS; const { width, height } = window.ANYPLOT_SIZE; diff --git a/plots/waveform-audio/metadata/javascript/d3.yaml b/plots/waveform-audio/metadata/javascript/d3.yaml index 1b58b1b2f2..2b14d06580 100644 --- a/plots/waveform-audio/metadata/javascript/d3.yaml +++ b/plots/waveform-audio/metadata/javascript/d3.yaml @@ -1,11 +1,8 @@ -# Per-library metadata for d3 implementation of waveform-audio -# Auto-generated by impl-generate.yml - library: d3 language: javascript specification_id: waveform-audio created: '2026-06-03T01:29:19Z' -updated: '2026-06-03T01:29:19Z' +updated: '2026-06-03T01:36:10Z' generated_by: claude-sonnet workflow_run: 26857934068 issue: 4563 @@ -15,7 +12,242 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/waveform- preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/waveform-audio/javascript/d3/plot-dark.png preview_html_light: https://storage.googleapis.com/anyplot-images/plots/waveform-audio/javascript/d3/plot-light.html preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/waveform-audio/javascript/d3/plot-dark.html -quality_score: null +quality_score: 88 review: - strengths: [] - weaknesses: [] + strengths: + - 'Perfect spec compliance: all required waveform features implemented (filled area, + semi-transparent fill, zero reference line, min/max envelope rendering, normalized + y-axis, time x-axis)' + - 'Sophisticated signal synthesis: 4-harmonic vocal synthesis with per-syllable + attack/sustain/release envelopes produces a genuinely realistic-looking audio + waveform' + - 'Correct D3 idioms throughout: d3.area() with datum binding, selection-based axis + styling, explicit tick values' + - Both themes adapt correctly — Imprint palette data color is identical between + renders; only chrome tokens flip + weaknesses: + - Helper functions (envAt, signalAt) add mild structural complexity beyond flat + code; the signal computation could be inlined at the cost of readability + - Design excellence ceiling limited by single-color, single-layer approach — no + annotations marking syllable boundaries or labeling the attack/sustain/release + phases (spec does not require these, but they would elevate storytelling) + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) — correct light theme surface, not pure white. + Chrome: Title "waveform-audio · javascript · d3 · anyplot.ai" in dark ink (#1A1A17) at top — clearly readable. "Time (s)" x-axis label and "Amplitude" y-axis label in dark ink — readable. Tick labels in inkSoft (#4A4A44) — readable. Subtle grey gridlines at y=-1, -0.5, 0, 0.5, 1 — not competing with data. + Data: Filled waveform area in Imprint brand green (#009E73) at fill-opacity 0.72. Min/max envelope rendering creates dense vertical oscillation bands showing three syllable sections with trapezoidal amplitude envelopes. Zero reference line in inkSoft. No other colors used. + Legibility verdict: PASS — all text clearly readable, no light-on-light issues. + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17) — correct dark theme surface, not pure black. + Chrome: Title, axis labels, and tick labels all render in light near-white ink — clearly readable against dark background. No dark-on-dark failures observed. Gridlines subtle but visible. + Data: Fill color is identical to light render — same Imprint brand green (#009E73). Three syllable sections with same shape and density. Zero reference line visible. Brand green reads clearly on dark surface. + Legibility verdict: PASS — all text clearly readable, no dark-on-dark issues detected. + criteria_checklist: + visual_quality: + score: 29 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 7 + max: 8 + passed: true + comment: 'Title 28px, axis labels 20px, tick labels 16px — all readable in + both themes. Minor deduction: axis labels slightly large relative to tick + labels but within proportional bounds.' + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No text overlaps with data, axes, or other labels. + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: Min/max envelope rendering handles 13,600-sample density correctly; + semi-transparent fill at 0.72 opacity reveals waveform structure without + visual noise. + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Single series in Imprint brand green; adequate contrast on both surfaces; + CVD-safe. + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: Canvas gate passed; good margins; no overflow; title spans ~70% of + width. + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: Time (s) and Amplitude are descriptive with correct units; title + follows mandated format. + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'Uses t.palette[0] (#009E73); light bg #FAF8F1; dark bg #1A1A17; + chrome tokens flip correctly.' + design_excellence: + score: 11 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 5 + max: 8 + passed: true + comment: 'Above default: harmonic synthesis + min/max envelope rendering is + DSP-informed; semi-transparent fill with thin stroke adds depth. Limited + by single-layer, no annotations.' + - id: DE-02 + name: Visual Refinement + score: 3 + max: 6 + passed: true + comment: 'Above default: no top/right spines; y-axis-only gridlines at stroke-width + 0.5; generous whitespace; zero reference line uses inkSoft for correct hierarchy.' + - id: DE-03 + name: Data Storytelling + score: 3 + max: 6 + passed: true + comment: 'Above default: three-syllable structure with trapezoidal envelopes + creates clear speech narrative. Gaps as visual pauses. Could be strengthened + with syllable annotations.' + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: 'Correct: filled area waveform via d3.area(), symmetric around zero.' + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: Semi-transparent fill, zero reference line, min/max envelope rendering, + time x-axis, normalized amplitude y-axis — all present. + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: 'X: time 0–1.7s; Y: amplitude [-1.05, 1.05]; all data visible.' + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title is waveform-audio · javascript · d3 · anyplot.ai exactly; no + legend (single series, correct omission). + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: 'All key aspects: voiced envelope (attack/sustain/release), harmonic + richness (4 partials), inter-syllable silence gaps, min/max envelope rendering.' + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Synthetic vocal phrase with 3 syllables at F0 130–155 Hz; plausible + sample rate (8 kHz), duration (1.7 s), sample count (13,600). + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: Amplitude normalized to ±1; 13,600 samples within spec range; syllable + timing and F0 are phonetically plausible. + code_quality: + score: 9 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 2 + max: 3 + passed: true + comment: 'Mild deduction: two helper functions (envAt, signalAt) defined. + Justified by signal complexity but exceeds flat-code ideal.' + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: 'Fully deterministic: only Math.sin/Math.PI with fixed parameters; + no RNG.' + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: No imports; correctly uses window.d3, window.ANYPLOT_TOKENS, window.ANYPLOT_SIZE + globals. + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: No fake interactivity; Float32Array for raw samples is idiomatic + DSP practice; d3.area() and datum() used correctly. + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: D3 harness emits plot-{theme}.png and plot-{theme}.html; no bare + plot.png. + library_mastery: + score: 9 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 5 + max: 5 + passed: true + comment: Correct use of d3.area() with .x()/.y0()/.y1(); .datum() for single-path + binding; .selectAll().data().join() for gridlines; axisBottom/axisLeft with + tickValues/tickFormat; unified axis styling loop. + - id: LM-02 + name: Distinctive Features + score: 4 + max: 5 + passed: true + comment: d3.area() with min/max envelope is quintessentially D3; .datum() + single-path binding is D3-specific idiom; selection-based axis styling. + Not full 5 — no advanced D3 capabilities leveraged. + verdict: APPROVED +impl_tags: + dependencies: [] + techniques: + - manual-ticks + patterns: + - data-generation + - iteration-over-groups + dataprep: + - binning + styling: + - alpha-blending From b033ffb8bea080e5b9cb710dc2abc59d7e68d7ec Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 3 Jun 2026 01:47:16 +0000 Subject: [PATCH 4/5] fix(d3): address review feedback for waveform-audio Attempt 1/3 - fixes based on AI review --- .../implementations/javascript/d3.js | 128 ++++++++++++------ 1 file changed, 88 insertions(+), 40 deletions(-) diff --git a/plots/waveform-audio/implementations/javascript/d3.js b/plots/waveform-audio/implementations/javascript/d3.js index e60af871d8..eea4383238 100644 --- a/plots/waveform-audio/implementations/javascript/d3.js +++ b/plots/waveform-audio/implementations/javascript/d3.js @@ -5,63 +5,73 @@ const t = window.ANYPLOT_TOKENS; const { width, height } = window.ANYPLOT_SIZE; -const margin = { top: 80, right: 50, bottom: 90, left: 100 }; +const margin = { top: 110, right: 50, bottom: 90, left: 100 }; const iw = width - margin.left - margin.right; const ih = height - margin.top - margin.bottom; -// --- Data: synthetic vocal phrase — three syllables, fully deterministic, 1.7 s at 8 kHz --- const SR = 8000; const DUR = 1.7; -const N = Math.ceil(SR * DUR); // 13600 samples +const N = Math.ceil(SR * DUR); -// Three voiced syllables: start/end seconds, fundamental F0 (Hz), attack/release durations (s) const SYLS = [ - { start: 0.10, end: 0.55, f0: 130, atk: 0.04, rel: 0.12 }, - { start: 0.65, end: 1.10, f0: 155, atk: 0.04, rel: 0.12 }, - { start: 1.20, end: 1.60, f0: 145, atk: 0.04, rel: 0.10 }, + { start: 0.10, end: 0.55, f0: 130, atk: 0.04, rel: 0.12, label: 'Syl. 1' }, + { start: 0.65, end: 1.10, f0: 155, atk: 0.04, rel: 0.12, label: 'Syl. 2' }, + { start: 1.20, end: 1.60, f0: 145, atk: 0.04, rel: 0.10, label: 'Syl. 3' }, ]; -function envAt(time, syl) { - const tl = time - syl.start; - const dur = syl.end - syl.start; - if (tl < 0 || tl >= dur) return 0; - if (tl < syl.atk) return tl / syl.atk; - if (dur - tl < syl.rel) return (dur - tl) / syl.rel; - return 1; -} - -function signalAt(time) { +// 4-harmonic vocal synthesis with per-syllable trapezoidal amplitude envelope +const rawAmp = new Float32Array(N); +for (let i = 0; i < N; i++) { + const time = i / SR; let s = 0; for (const syl of SYLS) { - const env = envAt(time, syl); - if (env === 0) continue; + const tl = time - syl.start; + const dur = syl.end - syl.start; + if (tl < 0 || tl >= dur) continue; + let env; + if (tl < syl.atk) env = tl / syl.atk; + else if (dur - tl < syl.rel) env = (dur - tl) / syl.rel; + else env = 1; const tau = 2 * Math.PI * time; - const f = syl.f0; s += env * ( - 0.45 * Math.sin(f * tau) + - 0.28 * Math.sin(2 * f * tau) + - 0.16 * Math.sin(3 * f * tau) + - 0.11 * Math.sin(4 * f * tau) + 0.45 * Math.sin(syl.f0 * tau) + + 0.28 * Math.sin(2 * syl.f0 * tau) + + 0.16 * Math.sin(3 * syl.f0 * tau) + + 0.11 * Math.sin(4 * syl.f0 * tau) ); } - return s; + rawAmp[i] = s; } -// Build raw signal, then downsample to display-width bins (min/max envelope per pixel column) -const rawAmp = new Float32Array(N); -for (let i = 0; i < N; i++) rawAmp[i] = signalAt(i / SR); - +// Downsample to display-width bins: min/max envelope per pixel column const nBins = Math.round(iw); const bSz = N / nBins; const waveData = Array.from({ length: nBins }, (_, b) => { - const s = Math.floor(b * bSz); - const e = Math.min(Math.ceil((b + 1) * bSz), N); + const s0 = Math.floor(b * bSz); + const e0 = Math.min(Math.ceil((b + 1) * bSz), N); let lo = Infinity, hi = -Infinity; - for (let j = s; j < e; j++) { + for (let j = s0; j < e0; j++) { if (rawAmp[j] < lo) lo = rawAmp[j]; if (rawAmp[j] > hi) hi = rawAmp[j]; } - return { tPos: s / SR, lo, hi }; + return { tPos: s0 / SR, lo, hi }; +}); + +// Smooth amplitude envelope (200 pts) for overlay curves +const envCurve = Array.from({ length: 200 }, (_, i) => { + const tv = i / 199 * DUR; + let peak = 0; + for (const syl of SYLS) { + const tl = tv - syl.start; + const dur = syl.end - syl.start; + if (tl < 0 || tl >= dur) continue; + let env; + if (tl < syl.atk) env = tl / syl.atk; + else if (dur - tl < syl.rel) env = (dur - tl) / syl.rel; + else env = 1; + peak = Math.max(peak, env); + } + return { t: tv, env: peak }; }); // --- SVG mount --- @@ -73,7 +83,7 @@ const g = svg.append("g").attr("transform", `translate(${margin.left},${margin.t const xScale = d3.scaleLinear().domain([0, DUR]).range([0, iw]); const yScale = d3.scaleLinear().domain([-1.05, 1.05]).range([ih, 0]); -// --- Horizontal gridlines (y-axis only, subtle) --- +// --- Horizontal gridlines --- g.selectAll(".hg").data([-1, -0.5, 0, 0.5, 1]).join("line") .attr("class", "hg") .attr("x1", 0).attr("x2", iw) @@ -81,7 +91,16 @@ g.selectAll(".hg").data([-1, -0.5, 0, 0.5, 1]).join("line") .attr("stroke", t.grid) .attr("stroke-width", 0.5); -// --- Waveform filled area (min/max envelope per display column) --- +// --- Syllable onset markers (dashed verticals at each syllable start) --- +g.selectAll(".sg").data(SYLS).join("line") + .attr("class", "sg") + .attr("x1", syl => xScale(syl.start)).attr("x2", syl => xScale(syl.start)) + .attr("y1", 0).attr("y2", ih) + .attr("stroke", t.grid) + .attr("stroke-width", 0.75) + .attr("stroke-dasharray", "5,3"); + +// --- Waveform filled area (min/max envelope per pixel column) --- g.append("path") .datum(waveData) .attr("d", d3.area() @@ -94,7 +113,22 @@ g.append("path") .attr("stroke-width", 0.4) .attr("stroke-opacity", 0.9); -// --- Zero reference line (slightly more visible than gridlines) --- +// --- Amplitude envelope overlay — positive and negative arms --- +for (const sign of [1, -1]) { + g.append("path") + .datum(envCurve) + .attr("d", d3.line() + .defined(d => d.env > 0.001) + .x(d => xScale(d.t)) + .y(d => yScale(sign * d.env)) + .curve(d3.curveCatmullRom.alpha(0.5))) + .attr("fill", "none") + .attr("stroke", t.palette[0]) + .attr("stroke-width", 2) + .attr("stroke-opacity", 0.9); +} + +// --- Zero reference line --- g.append("line") .attr("x1", 0).attr("x2", iw) .attr("y1", yScale(0)).attr("y2", yScale(0)) @@ -105,16 +139,31 @@ g.append("line") const xAxis = g.append("g") .attr("transform", `translate(0,${ih})`) .call(d3.axisBottom(xScale).ticks(9).tickFormat(d => d.toFixed(1))); - const yAxis = g.append("g") .call(d3.axisLeft(yScale).tickValues([-1, -0.5, 0, 0.5, 1])); - for (const ax of [xAxis, yAxis]) { ax.selectAll("text").attr("fill", t.inkSoft).style("font-size", "16px"); ax.selectAll("line").attr("stroke", t.inkSoft).attr("stroke-width", 0.5); ax.select(".domain").attr("stroke", t.inkSoft); } +// --- Syllable labels (mid-margin above chart, with small tick connectors) --- +g.selectAll(".syl-lbl").data(SYLS).join("text") + .attr("class", "syl-lbl") + .attr("x", syl => xScale((syl.start + syl.end) / 2)) + .attr("y", -26) + .attr("text-anchor", "middle") + .attr("fill", t.inkSoft) + .style("font-size", "14px") + .text(syl => `${syl.label} · ${syl.f0} Hz`); +g.selectAll(".syl-tick").data(SYLS).join("line") + .attr("class", "syl-tick") + .attr("x1", syl => xScale((syl.start + syl.end) / 2)) + .attr("x2", syl => xScale((syl.start + syl.end) / 2)) + .attr("y1", -14).attr("y2", -4) + .attr("stroke", t.grid) + .attr("stroke-width", 1); + // --- Axis labels --- svg.append("text") .attr("x", margin.left + iw / 2) @@ -123,7 +172,6 @@ svg.append("text") .attr("fill", t.ink) .style("font-size", "20px") .text("Time (s)"); - svg.append("text") .attr("transform", `translate(22, ${margin.top + ih / 2}) rotate(-90)`) .attr("text-anchor", "middle") @@ -133,7 +181,7 @@ svg.append("text") // --- Title --- svg.append("text") - .attr("x", width / 2).attr("y", 48) + .attr("x", width / 2).attr("y", 52) .attr("text-anchor", "middle") .attr("fill", t.ink) .style("font-size", "28px") From c1e91a23328b9927c780e9d1d242401e605ad58e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 3 Jun 2026 01:54:38 +0000 Subject: [PATCH 5/5] chore(d3): update quality score 88 and review feedback for waveform-audio --- .../metadata/javascript/d3.yaml | 183 ++++++++++-------- 1 file changed, 101 insertions(+), 82 deletions(-) diff --git a/plots/waveform-audio/metadata/javascript/d3.yaml b/plots/waveform-audio/metadata/javascript/d3.yaml index 2b14d06580..96a17cdc0f 100644 --- a/plots/waveform-audio/metadata/javascript/d3.yaml +++ b/plots/waveform-audio/metadata/javascript/d3.yaml @@ -2,7 +2,7 @@ library: d3 language: javascript specification_id: waveform-audio created: '2026-06-03T01:29:19Z' -updated: '2026-06-03T01:36:10Z' +updated: '2026-06-03T01:54:38Z' generated_by: claude-sonnet workflow_run: 26857934068 issue: 4563 @@ -15,34 +15,47 @@ preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/waveform- quality_score: 88 review: strengths: - - 'Perfect spec compliance: all required waveform features implemented (filled area, - semi-transparent fill, zero reference line, min/max envelope rendering, normalized - y-axis, time x-axis)' - - 'Sophisticated signal synthesis: 4-harmonic vocal synthesis with per-syllable - attack/sustain/release envelopes produces a genuinely realistic-looking audio - waveform' - - 'Correct D3 idioms throughout: d3.area() with datum binding, selection-based axis - styling, explicit tick values' - - Both themes adapt correctly — Imprint palette data color is identical between - renders; only chrome tokens flip + - Sophisticated 4-harmonic vocal synthesis produces a scientifically realistic speech + signal — three syllables with distinct frequencies (130/155/145 Hz) and trapezoidal + amplitude envelopes + - Min/max envelope downsampling per pixel column correctly avoids waveform aliasing + on dense data, exactly as specified + - Envelope overlay curves (d3.curveCatmullRom.alpha(0.5)) add a visually informative + layer showing amplitude modulation — a creative addition beyond the baseline spec + - Syllable onset dashed vertical markers and mid-label annotations with frequency + values make the chart self-documenting for a speech-analysis audience + - 'Full palette and theme compliance: t.palette[0] (#009E73) for data, t.ink/t.inkSoft/t.grid + for chrome, warm backgrounds correct in both renders' + - Fully deterministic code — no RNG, fixed SYLS parameters — guarantees reproducible + output across runs + - 'Clean D3 idioms: d3.area() y0/y1 for filled waveform, .datum() binding, d3.axisBottom/Left, + all idiomatic' weaknesses: - - Helper functions (envAt, signalAt) add mild structural complexity beyond flat - code; the signal computation could be inlined at the cost of readability - - Design excellence ceiling limited by single-color, single-layer approach — no - annotations marking syllable boundaries or labeling the attack/sustain/release - phases (spec does not require these, but they would elevate storytelling) + - Syllable annotation labels at 14px CSS (28 source px after 2× Playwright scale) + are noticeably smaller than the tick labels (16px) and axis labels (20px) — increase + to at least 16px to match tick-label sizing and improve readability at mobile + widths + - Design excellence is above default but not exceptional — single brand-green (#009E73) + dominates the entire visualization with no secondary visual layer; consider using + a slightly different opacity or stroke treatment for the envelope curves to create + visual hierarchy between the waveform fill and the envelope outline + - Top and right axis domain lines are present via the .domain stroke on xAxis/yAxis + — the L-shape is correct in D3 (only bottom + left axes created), but the domain + stroke on the bottom axis extends across the full width giving a box-like feel; + suppressing the bottom and left .domain paths would yield a cleaner floating-axis + look consistent with the style guide's spine-removal recommendation image_description: |- Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct light theme surface, not pure white. - Chrome: Title "waveform-audio · javascript · d3 · anyplot.ai" in dark ink (#1A1A17) at top — clearly readable. "Time (s)" x-axis label and "Amplitude" y-axis label in dark ink — readable. Tick labels in inkSoft (#4A4A44) — readable. Subtle grey gridlines at y=-1, -0.5, 0, 0.5, 1 — not competing with data. - Data: Filled waveform area in Imprint brand green (#009E73) at fill-opacity 0.72. Min/max envelope rendering creates dense vertical oscillation bands showing three syllable sections with trapezoidal amplitude envelopes. Zero reference line in inkSoft. No other colors used. - Legibility verdict: PASS — all text clearly readable, no light-on-light issues. + Background: Warm off-white matching #FAF8F1 — correctly set by the harness pageBg token. + Chrome: Title "waveform-audio · javascript · d3 · anyplot.ai" in dark ink at top, clearly readable. X-axis label "Time (s)" and Y-axis label "Amplitude" in dark ink at 20px CSS, readable. Tick labels at 16px CSS, readable. Syllable annotations "Syl. 1 · 130 Hz", "Syl. 2 · 155 Hz", "Syl. 3 · 145 Hz" at 14px — slightly small but legible. + Data: Three waveform segments rendered in brand green (#009E73) as a filled min/max envelope area with fill-opacity 0.72. Envelope outline curves (CatmullRom spline) overlay each segment in the same green at full opacity. Dashed vertical markers separate syllables. Horizontal gridlines at -1, -0.5, 0, 0.5, 1 are subtle. Zero reference line is a thin inkSoft horizontal rule. Y-axis spans -1.0 to 1.0; X-axis spans 0.0 to ~1.7s. + Legibility verdict: PASS — all text readable against light background, no light-on-light issues. Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct dark theme surface, not pure black. - Chrome: Title, axis labels, and tick labels all render in light near-white ink — clearly readable against dark background. No dark-on-dark failures observed. Gridlines subtle but visible. - Data: Fill color is identical to light render — same Imprint brand green (#009E73). Three syllable sections with same shape and density. Zero reference line visible. Brand green reads clearly on dark surface. - Legibility verdict: PASS — all text clearly readable, no dark-on-dark issues detected. + Background: Warm near-black matching #1A1A17 — correct dark surface. + Chrome: Title, axis labels, tick labels, and syllable annotations all render in light-colored text (inkSoft token), clearly readable against the dark background. No dark-on-dark failures observed. + Data: Same brand green (#009E73) filled waveform and envelope curves — data colors are identical to the light render as required. The green reads well on the dark surface. + Legibility verdict: PASS — all text readable against dark background, no issues detected. criteria_checklist: visual_quality: score: 29 @@ -53,53 +66,53 @@ review: score: 7 max: 8 passed: true - comment: 'Title 28px, axis labels 20px, tick labels 16px — all readable in - both themes. Minor deduction: axis labels slightly large relative to tick - labels but within proportional bounds.' + comment: All font sizes explicitly set via .style('font-size', '...px'). Title + 28px, axis labels 20px, tick labels 16px, syllable annotations 14px — all + readable in both themes. Syllable annotations at 14px CSS (28 source px) + slightly below style-guide target for this canvas size. - id: VQ-02 name: No Overlap score: 6 max: 6 passed: true - comment: No text overlaps with data, axes, or other labels. + comment: No overlapping text or data elements visible in either render. - id: VQ-03 name: Element Visibility score: 6 max: 6 passed: true - comment: Min/max envelope rendering handles 13,600-sample density correctly; - semi-transparent fill at 0.72 opacity reveals waveform structure without - visual noise. + comment: Waveform fill, envelope curves, zero reference line, and gridlines + all clearly visible. Alpha-blending (0.72) appropriate for dense waveform + data. - id: VQ-04 name: Color Accessibility score: 2 max: 2 passed: true - comment: Single series in Imprint brand green; adequate contrast on both surfaces; - CVD-safe. + comment: 'Single series, no CVD risk. Brand green #009E73 provides adequate + contrast on both surfaces.' - id: VQ-05 name: Layout & Canvas score: 4 max: 4 passed: true - comment: Canvas gate passed; good margins; no overflow; title spans ~70% of - width. + comment: Canvas gate passed. Margins (top=110, right=50, bottom=90, left=100) + are generous. Nothing cut off. Good use of canvas area. - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Time (s) and Amplitude are descriptive with correct units; title - follows mandated format. + comment: Time (s) includes unit, Amplitude is descriptive. Title format correct. - id: VQ-07 name: Palette Compliance score: 2 max: 2 passed: true - comment: 'Uses t.palette[0] (#009E73); light bg #FAF8F1; dark bg #1A1A17; - chrome tokens flip correctly.' + comment: Uses t.palette[0] (#009E73) for all data. Chrome tokens (t.ink, t.inkSoft, + t.grid) correctly applied. Backgrounds correct in both themes. design_excellence: - score: 11 + score: 12 max: 20 items: - id: DE-01 @@ -107,24 +120,26 @@ review: score: 5 max: 8 passed: true - comment: 'Above default: harmonic synthesis + min/max envelope rendering is - DSP-informed; semi-transparent fill with thin stroke adds depth. Limited - by single-layer, no annotations.' + comment: 'Clearly above defaults: 4-harmonic vocal synthesis, envelope overlay + curves, syllable segmentation with frequency labels, semi-transparent fill. + However, single color throughout with no secondary visual layer or multi-dimensional + design choices.' - id: DE-02 name: Visual Refinement score: 3 max: 6 passed: true - comment: 'Above default: no top/right spines; y-axis-only gridlines at stroke-width - 0.5; generous whitespace; zero reference line uses inkSoft for correct hierarchy.' + comment: Only bottom and left axes created (no top/right spines). Subtle gridlines + at stroke-width 0.5. Clean axis color styling via tokens. Above default + but not exceptional — axis domain lines still present, tick marks kept. - id: DE-03 name: Data Storytelling - score: 3 + score: 4 max: 6 passed: true - comment: 'Above default: three-syllable structure with trapezoidal envelopes - creates clear speech narrative. Gaps as visual pauses. Could be strengthened - with syllable annotations.' + comment: Three-syllable speech structure with frequency labels creates a clear + scientific narrative. Envelope curves guide the eye. Dashed separators aid + segmentation. Viewer immediately understands this is a 3-syllable utterance. spec_compliance: score: 15 max: 15 @@ -134,27 +149,30 @@ review: score: 5 max: 5 passed: true - comment: 'Correct: filled area waveform via d3.area(), symmetric around zero.' + comment: Correct waveform visualization with min/max envelope rendering as + specified. - id: SC-02 name: Required Features score: 4 max: 4 passed: true - comment: Semi-transparent fill, zero reference line, min/max envelope rendering, - time x-axis, normalized amplitude y-axis — all present. + comment: Filled area, semi-transparent fill, zero reference line, time X-axis, + amplitude Y-axis (-1 to +1), min/max envelope rendering, synthetic audio + data — all present. - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: 'X: time 0–1.7s; Y: amplitude [-1.05, 1.05]; all data visible.' + comment: Time on X-axis (seconds), normalized amplitude on Y-axis. All data + visible. - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Title is waveform-audio · javascript · d3 · anyplot.ai exactly; no - legend (single series, correct omission). + comment: 'Title: ''waveform-audio · javascript · d3 · anyplot.ai'' — correct + format. Single series, no legend required.' data_quality: score: 15 max: 15 @@ -164,85 +182,86 @@ review: score: 6 max: 6 passed: true - comment: 'All key aspects: voiced envelope (attack/sustain/release), harmonic - richness (4 partials), inter-syllable silence gaps, min/max envelope rendering.' + comment: 'Shows all waveform aspects: filled area, positive/negative amplitude, + envelope modulation, silence gaps, syllable dynamics.' - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Synthetic vocal phrase with 3 syllables at F0 130–155 Hz; plausible - sample rate (8 kHz), duration (1.7 s), sample count (13,600). + comment: 3-syllable speech signal is a real, neutral scientific scenario. + Frequency values (130-155 Hz) are in the human speech fundamental frequency + range. - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: Amplitude normalized to ±1; 13,600 samples within spec range; syllable - timing and F0 are phonetically plausible. + comment: Amplitude normalized to -1/+1, timing realistic (1.7s), frequencies + scientifically plausible for speech. code_quality: - score: 9 + score: 10 max: 10 items: - id: CQ-01 name: KISS Structure - score: 2 + score: 3 max: 3 passed: true - comment: 'Mild deduction: two helper functions (envAt, signalAt) defined. - Justified by signal complexity but exceeds flat-code ideal.' + comment: 'Linear procedural code: data → SVG mount → scales → gridlines → + waveform → envelope → axes → labels → title.' - id: CQ-02 name: Reproducibility score: 2 max: 2 passed: true - comment: 'Fully deterministic: only Math.sin/Math.PI with fixed parameters; - no RNG.' + comment: Fully deterministic — fixed SYLS parameters, no RNG. - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: No imports; correctly uses window.d3, window.ANYPLOT_TOKENS, window.ANYPLOT_SIZE - globals. + comment: No explicit imports; d3 is a global as required. All tokens used. - id: CQ-04 name: Code Elegance score: 2 max: 2 passed: true - comment: No fake interactivity; Float32Array for raw samples is idiomatic - DSP practice; d3.area() and datum() used correctly. + comment: Clean, appropriate complexity for the visualization. No over-engineering + or fake UI. - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: D3 harness emits plot-{theme}.png and plot-{theme}.html; no bare - plot.png. + comment: Harness handles plot-{theme}.png and plot-{theme}.html output. Current + D3 v7 API used throughout. library_mastery: - score: 9 + score: 7 max: 10 items: - id: LM-01 name: Idiomatic Usage - score: 5 + score: 4 max: 5 passed: true - comment: Correct use of d3.area() with .x()/.y0()/.y1(); .datum() for single-path - binding; .selectAll().data().join() for gridlines; axisBottom/axisLeft with - tickValues/tickFormat; unified axis styling loop. + comment: d3.area() with y0/y1 for waveform fill, d3.line() with CatmullRom + curve, .datum() binding for path data, d3.axisBottom/Left, .join() for elements + — all idiomatic D3 patterns. - id: LM-02 name: Distinctive Features - score: 4 + score: 3 max: 5 passed: true - comment: d3.area() with min/max envelope is quintessentially D3; .datum() - single-path binding is D3-specific idiom; selection-based axis styling. - Not full 5 — no advanced D3 capabilities leveraged. + comment: d3.area() y0/y1 for filled waveform is distinctively D3. d3.curveCatmullRom.alpha(0.5) + is a D3-specific curve interpolation. Min/max envelope feeding into area() + is a D3-idiomatic pattern for dense signal visualization. verdict: APPROVED impl_tags: dependencies: [] techniques: + - html-export + - annotations - manual-ticks patterns: - data-generation