From a11a9a7c3d927db76b6e331b96ce2719ffc3f71c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 10 Jun 2026 02:03:47 +0000 Subject: [PATCH 1/3] feat(d3): implement acf-pacf --- .../acf-pacf/implementations/javascript/d3.js | 225 ++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 plots/acf-pacf/implementations/javascript/d3.js diff --git a/plots/acf-pacf/implementations/javascript/d3.js b/plots/acf-pacf/implementations/javascript/d3.js new file mode 100644 index 0000000000..715f491451 --- /dev/null +++ b/plots/acf-pacf/implementations/javascript/d3.js @@ -0,0 +1,225 @@ +// anyplot.ai +// acf-pacf: Autocorrelation and Partial Autocorrelation (ACF/PACF) Plot +// Library: d3 7.9.0 | JavaScript 22 +// Quality: pending | Created: 2026-06-10 +//# anyplot-orientation: landscape + +const t = window.ANYPLOT_TOKENS; +const { width, height } = window.ANYPLOT_SIZE; + +// Deterministic LCG (no seeded RNG in browser) +let _seed = 42; +function lcg() { + _seed = (Math.imul(_seed, 1664525) + 1013904223) >>> 0; + return _seed / 4294967296; +} +function randn() { + return Math.sqrt(-2 * Math.log(Math.max(lcg(), 1e-12))) * Math.cos(2 * Math.PI * lcg()); +} + +// AR(2) series: x_t = 0.7·x_{t-1} − 0.3·x_{t-2} + ε_t +// Stationary; PACF cuts off after lag 2 — canonical AR-order diagnostic +const N = 200; +const series = [randn(), 0.7 * randn() + randn()]; +for (let i = 2; i < N; i++) { + series.push(0.7 * series[i - 1] - 0.3 * series[i - 2] + randn()); +} + +// ACF: r(k) = Cov(x_t, x_{t-k}) / Var(x) +function computeACF(x, maxLag) { + const n = x.length; + const mean = x.reduce((s, v) => s + v, 0) / n; + const variance = x.reduce((s, v) => s + (v - mean) ** 2, 0) / n; + return Array.from({ length: maxLag + 1 }, (_, k) => { + if (k === 0) return 1.0; + let cov = 0; + for (let i = k; i < n; i++) cov += (x[i] - mean) * (x[i - k] - mean); + return cov / (n * variance); + }); +} + +// PACF via Durbin-Levinson: phi_{k,k} is partial autocorrelation at lag k +function computePACF(acf, maxLag) { + const phi = Array.from({ length: maxLag + 1 }, () => new Array(maxLag + 1).fill(0)); + const pacf = new Array(maxLag + 1).fill(0); + pacf[0] = 1.0; + for (let k = 1; k <= maxLag; k++) { + let num = acf[k], den = 1.0; + for (let j = 1; j < k; j++) { + num -= phi[k - 1][j] * acf[k - j]; + den -= phi[k - 1][j] * acf[j]; + } + phi[k][k] = Math.abs(den) > 1e-10 ? num / den : 0; + for (let j = 1; j < k; j++) { + phi[k][j] = phi[k - 1][j] - phi[k][k] * phi[k - 1][k - j]; + } + pacf[k] = phi[k][k]; + } + return pacf; +} + +const maxLag = 30; +const acfVals = computeACF(series, maxLag); +const pacfVals = computePACF(acfVals, maxLag); +const confBand = 1.96 / Math.sqrt(N); + +// Layout +const margin = { top: 70, right: 60, bottom: 70, left: 80 }; +const iw = width - margin.left - margin.right; +const panelGap = 40; +const panelH = (height - margin.top - margin.bottom - panelGap) / 2; + +const svg = d3 + .select("#container") + .append("svg") + .attr("width", width) + .attr("height", height); + +const root = svg.append("g").attr("transform", `translate(${margin.left},${margin.top})`); + +// Draw an ACF or PACF panel +function drawPanel(g, data, yLabel, color, yTop, showXLabels) { + const vals = data.map((d) => d.val); + const vMin = Math.min(d3.min(vals), -confBand) - 0.08; + const vMax = Math.max(d3.max(vals), confBand, yLabel === "ACF" ? 1.0 : 0) + 0.08; + + const xScale = d3.scaleLinear().domain([-0.5, maxLag + 0.5]).range([0, iw]); + const yScale = d3.scaleLinear().domain([vMin, vMax]).nice().range([panelH, 0]); + + const pg = g.append("g").attr("transform", `translate(0,${yTop})`); + + // Horizontal gridlines + for (const tick of yScale.ticks(5)) { + pg.append("line") + .attr("x1", 0) + .attr("x2", iw) + .attr("y1", yScale(tick)) + .attr("y2", yScale(tick)) + .attr("stroke", t.grid) + .attr("stroke-width", 1); + } + + // Zero baseline + pg.append("line") + .attr("x1", 0) + .attr("x2", iw) + .attr("y1", yScale(0)) + .attr("y2", yScale(0)) + .attr("stroke", t.inkSoft) + .attr("stroke-width", 1.5) + .attr("opacity", 0.5); + + // Confidence band fill (amber, very subtle) + pg.append("rect") + .attr("x", 0) + .attr("width", iw) + .attr("y", yScale(confBand)) + .attr("height", yScale(-confBand) - yScale(confBand)) + .attr("fill", "#DDCC77") + .attr("opacity", 0.08); + + // Confidence band dashed boundary lines + for (const cb of [confBand, -confBand]) { + pg.append("line") + .attr("x1", 0) + .attr("x2", iw) + .attr("y1", yScale(cb)) + .attr("y2", yScale(cb)) + .attr("stroke", "#DDCC77") + .attr("stroke-width", 1.5) + .attr("stroke-dasharray", "8,5"); + } + + // Stem lines + pg.selectAll(".stem") + .data(data) + .join("line") + .attr("class", "stem") + .attr("x1", (d) => xScale(d.lag)) + .attr("x2", (d) => xScale(d.lag)) + .attr("y1", yScale(0)) + .attr("y2", (d) => yScale(d.val)) + .attr("stroke", color) + .attr("stroke-width", 2.5); + + // Tip markers + pg.selectAll(".dot") + .data(data) + .join("circle") + .attr("class", "dot") + .attr("cx", (d) => xScale(d.lag)) + .attr("cy", (d) => yScale(d.val)) + .attr("r", 4.5) + .attr("fill", color) + .attr("stroke", t.pageBg) + .attr("stroke-width", 1); + + // Y axis + const yAx = pg.append("g").call(d3.axisLeft(yScale).ticks(5).tickSize(5)); + yAx.selectAll("text").attr("fill", t.inkSoft).style("font-size", "13px"); + yAx.selectAll(".tick line").attr("stroke", t.inkSoft); + yAx.select(".domain").attr("stroke", t.inkSoft); + + // X axis + const xAx = pg + .append("g") + .attr("transform", `translate(0,${panelH})`) + .call(d3.axisBottom(xScale).ticks(10).tickSize(5)); + if (showXLabels) { + xAx.selectAll("text").attr("fill", t.inkSoft).style("font-size", "13px"); + } else { + xAx.selectAll("text").remove(); + } + xAx.selectAll(".tick line").attr("stroke", t.inkSoft); + xAx.select(".domain").attr("stroke", t.inkSoft); + + // Y-axis label (rotated) + pg.append("text") + .attr("transform", `translate(${-60},${panelH / 2}) rotate(-90)`) + .attr("text-anchor", "middle") + .attr("fill", t.ink) + .style("font-size", "15px") + .style("font-weight", "600") + .text(yLabel); +} + +// ACF panel: lags 0 … maxLag (lag 0 = 1.0) +drawPanel( + root, + acfVals.map((v, k) => ({ lag: k, val: v })), + "ACF", + t.palette[0], + 0, + false +); + +// PACF panel: lags 1 … maxLag (skip lag 0) +drawPanel( + root, + pacfVals.slice(1).map((v, i) => ({ lag: i + 1, val: v })), + "PACF", + t.palette[2], + panelH + panelGap, + true +); + +// X-axis label +svg + .append("text") + .attr("x", margin.left + iw / 2) + .attr("y", height - 22) + .attr("text-anchor", "middle") + .attr("fill", t.inkSoft) + .style("font-size", "14px") + .text("Lag"); + +// Title +svg + .append("text") + .attr("x", width / 2) + .attr("y", 44) + .attr("text-anchor", "middle") + .attr("fill", t.ink) + .style("font-size", "22px") + .style("font-weight", "600") + .text("Monthly Retail Sales · acf-pacf · javascript · d3 · anyplot.ai"); From 52ced3b4a6f1492403f67f4c5018c1e6eb6d4893 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 10 Jun 2026 02:04:01 +0000 Subject: [PATCH 2/3] chore(d3): add metadata for acf-pacf --- plots/acf-pacf/metadata/javascript/d3.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 plots/acf-pacf/metadata/javascript/d3.yaml diff --git a/plots/acf-pacf/metadata/javascript/d3.yaml b/plots/acf-pacf/metadata/javascript/d3.yaml new file mode 100644 index 0000000000..4cba586b2e --- /dev/null +++ b/plots/acf-pacf/metadata/javascript/d3.yaml @@ -0,0 +1,21 @@ +# Per-library metadata for d3 implementation of acf-pacf +# Auto-generated by impl-generate.yml + +library: d3 +language: javascript +specification_id: acf-pacf +created: '2026-06-10T02:04:01Z' +updated: '2026-06-10T02:04:01Z' +generated_by: claude-sonnet +workflow_run: 27247843281 +issue: 4663 +language_version: 22.22.3 +library_version: 7.9.0 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/acf-pacf/javascript/d3/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/acf-pacf/javascript/d3/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/acf-pacf/javascript/d3/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/acf-pacf/javascript/d3/plot-dark.html +quality_score: null +review: + strengths: [] + weaknesses: [] From 455b5ce1086db6e946d8e2713c9c83f8f378120b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 10 Jun 2026 02:10:51 +0000 Subject: [PATCH 3/3] chore(d3): update quality score 90 and review feedback for acf-pacf --- .../acf-pacf/implementations/javascript/d3.js | 4 +- plots/acf-pacf/metadata/javascript/d3.yaml | 243 +++++++++++++++++- 2 files changed, 238 insertions(+), 9 deletions(-) diff --git a/plots/acf-pacf/implementations/javascript/d3.js b/plots/acf-pacf/implementations/javascript/d3.js index 715f491451..15a28e9c0c 100644 --- a/plots/acf-pacf/implementations/javascript/d3.js +++ b/plots/acf-pacf/implementations/javascript/d3.js @@ -1,7 +1,7 @@ // anyplot.ai // acf-pacf: Autocorrelation and Partial Autocorrelation (ACF/PACF) Plot -// Library: d3 7.9.0 | JavaScript 22 -// Quality: pending | Created: 2026-06-10 +// Library: d3 7.9.0 | JavaScript 22.22.3 +// Quality: 90/100 | Created: 2026-06-10 //# anyplot-orientation: landscape const t = window.ANYPLOT_TOKENS; diff --git a/plots/acf-pacf/metadata/javascript/d3.yaml b/plots/acf-pacf/metadata/javascript/d3.yaml index 4cba586b2e..41f3653291 100644 --- a/plots/acf-pacf/metadata/javascript/d3.yaml +++ b/plots/acf-pacf/metadata/javascript/d3.yaml @@ -1,11 +1,8 @@ -# Per-library metadata for d3 implementation of acf-pacf -# Auto-generated by impl-generate.yml - library: d3 language: javascript specification_id: acf-pacf created: '2026-06-10T02:04:01Z' -updated: '2026-06-10T02:04:01Z' +updated: '2026-06-10T02:10:51Z' generated_by: claude-sonnet workflow_run: 27247843281 issue: 4663 @@ -15,7 +12,239 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/acf-pacf/ preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/acf-pacf/javascript/d3/plot-dark.png preview_html_light: https://storage.googleapis.com/anyplot-images/plots/acf-pacf/javascript/d3/plot-light.html preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/acf-pacf/javascript/d3/plot-dark.html -quality_score: null +quality_score: 90 review: - strengths: [] - weaknesses: [] + strengths: + - Correct Durbin-Levinson PACF algorithm implemented entirely in browser JS — shows + statistical depth + - 'Perfect spec compliance: ACF top panel with lag 0=1.0, PACF bottom panel starting + at lag 1, 95% CI dashed amber lines with subtle fill' + - 'Semantically appropriate color choices: brand green #009E73 for ACF, Imprint + blue #4467A3 for PACF, amber anchor #DDCC77 for confidence bands' + - Deterministic LCG (seed=42) elegantly solves browser lack of seeded RNG + - AR(2) data choice creates canonical PACF cutoff pattern at lag 2 — excellent pedagogical + data that illustrates the diagnostic value of the plot + weaknesses: + - Helper functions (lcg, randn, drawPanel) technically diverge from the no-functions + CQ-01 rule, though pragmatically justified for browser-RNG limitation and DRY + panel rendering + - Tick label font size at 13px is 1px below the D3-recommended minimum of 14px; + minor readability improvement available + - 'Design Excellence headroom: top/right domain lines remain visible (acceptable + for stem plots), but no subtitle or annotation to guide the viewer to the AR(2) + cutoff story' + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) — correct anyplot light surface + Chrome: Title "Monthly Retail Sales · acf-pacf · javascript · d3 · anyplot.ai" in dark ink, bold 22px — fully visible, ~80% width (expected for mandated long title). Y-axis labels "ACF" and "PACF" in dark ink bold 15px, rotated. X-axis label "Lag" in inkSoft 14px at bottom. Tick labels at 13px in inkSoft. All chrome is dark text on warm-cream background. + Data: ACF panel (top) uses brand green #009E73 stems with dot tips; lag 0 spike at 1.0 dominant, gradual decay. PACF panel (bottom) uses Imprint blue #4467A3; clear cutoff after lag 2. Amber dashed confidence bands (±0.138) with very subtle amber fill in both panels. Horizontal grid lines in t.grid token. Zero baseline at 50% opacity. + Legibility verdict: PASS — all text readable, no contrast failures. + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17) — correct anyplot dark surface + Chrome: Title in light ink (#F0EFE8), axis labels and ticks in light inkSoft (#B8B7B0). All text clearly readable against dark background. No dark-on-dark failures detected. + Data: Colors identical to light render — ACF stems in #009E73, PACF stems in #4467A3, amber dashed confidence lines. Subtle amber fill is slightly more visible on dark background but not distracting. Data colors unchanged as required. + Legibility verdict: PASS — all text readable in dark theme, no contrast failures. + criteria_checklist: + visual_quality: + score: 30 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 8 + max: 8 + passed: true + comment: 'All font sizes explicitly set: title 22px, y-labels 15px bold, tick + labels 13px, x-label 14px. Well-proportioned, readable in both themes.' + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No text overlap detected. Stems are well-spaced across 30 lags. + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: Stems (2.5px) and dot tips (r=4.5) clearly visible. Appropriate for + 30-lag density. + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Green/blue are CVD-safe Imprint palette members. Amber confidence + bands distinct from both. + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: Both panels fill canvas well. Canvas gate passed. Balanced margins + (70/60/70/80). + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: ACF, PACF, and Lag labels all descriptive. Units not needed for dimensionless + correlation. + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'palette[0]=#009E73 for ACF, palette[2]=#4467A3 for PACF, #DDCC77 + amber anchor for CI bands. Correct backgrounds both themes. Chrome adapts.' + design_excellence: + score: 13 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 5 + max: 8 + passed: true + comment: Intentional color differentiation between panels, amber CI bands + with subtle fill, clean stem plot design. Above configured defaults. + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: Subtle grid, zero baseline at 0.5 opacity, CI fill at 0.08 opacity, + dot tips on stems. Good refinement beyond defaults. + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: AR(2) data creates canonical PACF cutoff after lag 2 — visually demonstrates + the diagnostic value. Good visual hierarchy. + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: Correct ACF/PACF stem plot with two vertically stacked panels. + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: ACF top with lag 0, PACF bottom starting lag 1, shared x-axis, 95% + CI dashed lines, correct labels. + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: Correct lag assignments, zero baseline, correlation values in [-1,1]. + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title 'Monthly Retail Sales · acf-pacf · javascript · d3 · anyplot.ai' + matches prescribed format with descriptive prefix. + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: ACF decay pattern, PACF cutoff at lag 2, lag 0=1.0, confidence bands, + both significant and insignificant lags visible. + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Monthly Retail Sales is a plausible real-world time series domain + for AR(2) modeling. + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: 'N=200 (in spec range 100-500), CI at ±1.96/sqrt(200)≈±0.138 correct, + 30 lags (spec: 30-40).' + code_quality: + score: 9 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 2 + max: 3 + passed: true + comment: Has lcg(), randn(), drawPanel() helpers. LCG necessary for browser-RNG + limitation; drawPanel justified for DRY two-panel rendering, but technically + diverges from no-functions rule. + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: Deterministic LCG with fixed seed _seed=42. Fully reproducible. + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: No imports needed; d3 global used throughout, all references are + to actually-used methods. + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Clean D3 patterns, well-commented math, no fake functionality. + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: D3 is interactive library; harness emits plot-light/dark.png + .html. + Current D3 7.9.0 API used. + library_mastery: + score: 8 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 5 + max: 5 + passed: true + comment: 'Excellent D3 idioms: data binding with .join(), chained selections, + axisLeft/axisBottom, scaleLinear with .nice(), ANYPLOT_TOKENS/SIZE contract.' + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: true + comment: D3 data-driven binding for parallel stem/dot creation, custom axis + styling with fill/stroke tokens, SVG transforms for panel layout. Core Durbin-Levinson + is plain JS. + verdict: APPROVED +impl_tags: + dependencies: [] + techniques: + - subplots + - manual-ticks + - annotations + patterns: + - data-generation + dataprep: + - time-series + styling: + - alpha-blending