diff --git a/plots/acf-pacf/implementations/javascript/chartjs.js b/plots/acf-pacf/implementations/javascript/chartjs.js new file mode 100644 index 0000000000..eba666ae02 --- /dev/null +++ b/plots/acf-pacf/implementations/javascript/chartjs.js @@ -0,0 +1,219 @@ +// anyplot.ai +// acf-pacf: Autocorrelation and Partial Autocorrelation (ACF/PACF) Plot +// Library: chartjs 4.4.7 | JavaScript 22.22.3 +// Quality: 84/100 | Created: 2026-06-10 + +//# anyplot-orientation: landscape + +const t = window.ANYPLOT_TOKENS; + +// Deterministic LCG for reproducible pseudo-random numbers (no seeded RNG in browser) +let _seed = 42; +function lcgRand() { + _seed = (Math.imul(1664525, _seed) + 1013904223) >>> 0; + return _seed / 4294967296; +} +function stdNormal() { + const u1 = lcgRand() || 1e-10; + return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * lcgRand()); +} + +// AR(2) process: x_t = 0.7*x_{t-1} + 0.2*x_{t-2} + ε_t +// Classic diagnostic: ACF decays exponentially; PACF cuts off exactly after lag 2 +const N = 240; +const series = [stdNormal(), stdNormal()]; +for (let i = 2; i < N; i++) { + series.push(0.7 * series[i - 1] + 0.2 * series[i - 2] + stdNormal()); +} + +// Sample ACF at lags 0..maxLag (biased estimator, consistent with standard practice) +function computeACF(s, maxLag) { + const n = s.length; + const mean = s.reduce((a, b) => a + b, 0) / n; + const variance = s.reduce((a, b) => a + (b - mean) ** 2, 0) / n; + return Array.from({ length: maxLag + 1 }, (_, k) => { + const cov = s + .slice(0, n - k) + .reduce((sum, v, i) => sum + (v - mean) * (s[i + k] - mean), 0); + return cov / (n * variance); + }); +} + +// PACF at lags 1..maxLag via Levinson-Durbin recursion +function computePACF(acfVals, maxLag) { + const result = [acfVals[1]]; + let phi = [acfVals[1]]; + for (let k = 2; k <= maxLag; k++) { + let num = acfVals[k], + den = 1; + for (let j = 0; j < k - 1; j++) { + num -= phi[j] * acfVals[k - j - 1]; + den -= phi[j] * acfVals[j + 1]; + } + const phikk = Math.abs(den) < 1e-12 ? 0 : num / den; + result.push(phikk); + phi = Array.from({ length: k }, (_, j) => + j === k - 1 ? phikk : phi[j] - phikk * phi[k - 2 - j] + ); + } + return result; +} + +const MAX_LAG = 30; +const acfData = computeACF(series, MAX_LAG); // lags 0..30 (lag 0 = 1.0) +const pacfData = computePACF(acfData, MAX_LAG); // lags 1..30 +const CI = 1.96 / Math.sqrt(N); // 95% confidence bound: ±1.96/√N + +// Brand green for positive correlations, matte red for negative +const stemColor = (v) => (v >= 0 ? t.palette[0] : t.palette[4]); + +// DOM: flex column with overall title + two equal-height chart panes +const container = document.getElementById("container"); +container.style.cssText = ` + display: flex; flex-direction: column; gap: 8px; + background: ${t.pageBg}; padding: 30px 64px 26px; + box-sizing: border-box; +`; + +const titleEl = document.createElement("div"); +titleEl.textContent = "acf-pacf · javascript · chartjs · anyplot.ai"; +titleEl.style.cssText = ` + color: ${t.ink}; font: 700 22px/1.3 system-ui, -apple-system, sans-serif; + text-align: center; flex-shrink: 0; +`; +container.appendChild(titleEl); + +function makePane() { + const wrap = document.createElement("div"); + wrap.style.cssText = "flex: 1; position: relative; min-height: 0;"; + const canvas = document.createElement("canvas"); + wrap.appendChild(canvas); + container.appendChild(wrap); + return canvas; +} +const acfCanvas = makePane(); +const pacfCanvas = makePane(); + +// Horizontal dashed CI line dataset (same constant value across all lags) +function ciLine(n, val, label) { + return { + type: "line", + label, + data: new Array(n).fill(val), + borderColor: t.inkSoft, + borderDash: [8, 5], + borderWidth: 1.5, + pointRadius: 0, + fill: false, + tension: 0, + }; +} + +// Highlight zero baseline gridline for clear significance reference +const zeroGridColor = (ctx) => (ctx.tick?.value === 0 ? t.inkSoft : t.grid); + +// Shared y-axis config for both subplots +function yAxis(label) { + return { + ticks: { color: t.inkSoft, font: { size: 14 }, maxTicksLimit: 7 }, + grid: { color: zeroGridColor }, + border: { display: false }, + title: { + display: true, + text: label, + color: t.ink, + font: { size: 14, weight: "600" }, + }, + suggestedMin: -1.1, + suggestedMax: 1.15, + }; +} + +// Legend: show only the "95% CI" dashed line entry +const legendConfig = { + labels: { + color: t.inkSoft, + font: { size: 13 }, + filter: (item) => item.text === "95% CI", + boxWidth: 24, + }, +}; + +// ACF chart — lags 0..30 (lag 0 = 1.0 always included per spec) +new Chart(acfCanvas, { + type: "bar", + data: { + labels: Array.from({ length: MAX_LAG + 1 }, (_, i) => i), + datasets: [ + { + label: "ACF", + data: acfData, + backgroundColor: acfData.map(stemColor), + borderWidth: 0, + barThickness: 7, + }, + ciLine(MAX_LAG + 1, CI, "95% CI"), + ciLine(MAX_LAG + 1, -CI, ""), + ], + }, + options: { + responsive: true, + maintainAspectRatio: false, + animation: false, + plugins: { + title: { display: false }, + legend: { display: false }, + }, + scales: { + x: { + ticks: { color: t.inkSoft, font: { size: 14 }, maxTicksLimit: 16 }, + grid: { display: false }, + border: { display: false }, + title: { display: false }, // "Lag" label shown on bottom chart only + }, + y: yAxis("ACF"), + }, + }, +}); + +// PACF chart — lags 1..30 (no lag 0 per spec) +new Chart(pacfCanvas, { + type: "bar", + data: { + labels: Array.from({ length: MAX_LAG }, (_, i) => i + 1), + datasets: [ + { + label: "PACF", + data: pacfData, + backgroundColor: pacfData.map(stemColor), + borderWidth: 0, + barThickness: 7, + }, + ciLine(MAX_LAG, CI, "95% CI"), + ciLine(MAX_LAG, -CI, ""), + ], + }, + options: { + responsive: true, + maintainAspectRatio: false, + animation: false, + plugins: { + title: { display: false }, + legend: legendConfig, + }, + scales: { + x: { + ticks: { color: t.inkSoft, font: { size: 14 } }, + grid: { display: false }, + border: { display: false }, + title: { + display: true, + text: "Lag", + color: t.ink, + font: { size: 14, weight: "600" }, + }, + }, + y: yAxis("PACF"), + }, + }, +}); diff --git a/plots/acf-pacf/metadata/javascript/chartjs.yaml b/plots/acf-pacf/metadata/javascript/chartjs.yaml new file mode 100644 index 0000000000..3cd7a7fa72 --- /dev/null +++ b/plots/acf-pacf/metadata/javascript/chartjs.yaml @@ -0,0 +1,257 @@ +library: chartjs +language: javascript +specification_id: acf-pacf +created: '2026-06-10T02:03:27Z' +updated: '2026-06-10T02:27:20Z' +generated_by: claude-sonnet +workflow_run: 27247770776 +issue: 4663 +language_version: 22.22.3 +library_version: 4.4.7 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/acf-pacf/javascript/chartjs/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/acf-pacf/javascript/chartjs/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/acf-pacf/javascript/chartjs/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/acf-pacf/javascript/chartjs/plot-dark.html +quality_score: 84 +review: + strengths: + - 'Textbook AR(2) dataset demonstrates all key ACF/PACF diagnostic properties: exponential + decay, PACF cutoff at model order, lag-0 = 1.0, positive and negative correlations' + - Semantic green/red color coding (Imprint palette) for positive/negative correlations + is statistically correct and visually clear + - Mathematically sound Levinson-Durbin PACF computation and biased ACF estimator + — correct statistical implementation + - Dual-canvas flex layout cleanly solves Chart.js's lack of native subplot support + - All theme tokens correctly applied; both light and dark renders pass theme-readability + checks with no chrome failures + - Scriptable grid color callback (zero-line highlight via zeroGridColor) is an elegant + Chart.js-idiomatic touch + - Fixed LCG seed (seed=42) with Box-Muller transform ensures fully deterministic + output + weaknesses: + - 'ACF x-axis uses maxTicksLimit:16 (even lags: 0, 2, 4…) while PACF shows all 30 + lags — inconsistent tick density between panels makes the shared x-axis harder + to read as a unified scale; apply same maxTicksLimit to both x-axes' + - Y-axis renders to approximately ±1.5 in practice (Chart.js padding beyond suggestedMin/Max), + creating unused vertical space since ACF is all-positive and most PACF values + cluster within ±0.15 for lags > 2; tighten suggestedMin to ~-0.4 and suggestedMax + to ~1.1 + - No real-world domain context — data is abstract statistical (AR(2) process) without + any label indicating what the time series represents + - 'DE-03: No annotation pointing to the key insight that the PACF cutoff at lag + 2 identifies this as an AR(2) process; a small annotation at lag 2 would elevate + storytelling' + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) — correct, not pure white. + Chrome: Title "acf-pacf · javascript · chartjs · anyplot.ai" in bold 22px dark text, clearly readable. Y-axis labels "ACF" and "PACF" in 14px/600 weight, readable. X-axis "Lag" label on bottom chart only, 14px/600 weight. Tick labels 14px, clearly readable. 95% CI legend between panels at 13px, readable. All text dark on light background. + Data: ACF bars in brand green (#009E73) showing exponential decay from lag 0 (=1.0) through lag 30. PACF bars in green for positive lags 1-2 (significant), red (#AE3030, Imprint palette position 5, semantic negative anchor) for small negative values. Horizontal dashed CI lines at ±0.127. ACF x-axis shows even lags (0, 2, 4…30); PACF shows all lags (1–30). + Legibility verdict: PASS — all text clearly readable; no element unreadable. + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17) — correct, not pure black. + Chrome: Title light-colored on dark background, clearly readable. Y-axis labels, tick labels, and x-axis label all correctly light-colored. 95% CI legend text light-colored. No dark-on-dark failures observed. Brand green #009E73 clearly visible on dark surface. + Data: Colors identical to light render — green bars and red bars are the same hex values. CI dashed lines remain visible. Data presentation unchanged from light to dark (only chrome flips). + Legibility verdict: PASS — all text clearly readable against dark background; no dark-on-dark failures; correct theme-adaptive chrome throughout. + criteria_checklist: + visual_quality: + score: 27 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 7 + max: 8 + passed: true + comment: 'All fonts explicitly set (title 22px DOM, axis labels 14px/600, + ticks 14px, legend 13px). Minor: ACF x-axis sparse ticks (maxTicksLimit:16) + vs PACF all-30-lags creates slight inconsistency.' + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No text or element overlaps in either render. + - id: VQ-03 + name: Element Visibility + score: 5 + max: 6 + passed: true + comment: ACF and significant PACF bars well-sized (barThickness:7). Very small + high-lag PACF bars statistically correct but barely visible — not a code + defect. + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: 'Semantic green/red CVD-safe: bar position (above/below zero) provides + redundant encoding.' + - id: VQ-05 + name: Layout & Canvas + score: 3 + max: 4 + passed: true + comment: 'Good flex two-panel layout with generous padding. Minor: y-axis + renders to ±1.5 (Chart.js padding beyond suggestedMin/Max), creating unused + vertical space for mostly-positive ACF and near-zero PACF data.' + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: '"ACF", "PACF", "Lag" are descriptive and correctly placed.' + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'First series #009E73 ✓; red #AE3030 for negative is semantic exception + ✓; backgrounds #FAF8F1/#1A1A17 ✓; theme-adaptive chrome both renders ✓.' + design_excellence: + score: 12 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 5 + max: 8 + passed: true + comment: Thoughtful semantic coloring, custom DOM title for layout control, + clean axis styling. Above well-configured default but not publication-ready. + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: X-axis grid removed, borders hidden, generous padding, custom zero-line + gridline highlight via scriptable callback. Solid refinements above defaults. + - id: DE-03 + name: Data Storytelling + score: 3 + max: 6 + passed: true + comment: AR(2) data choice perfectly demonstrates diagnostic insight; green/red + adds emphasis. No annotation pointing to the key PACF cutoff at lag 2. + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: Correct bar-based stem plot in two vertically stacked panels. + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: ACF top/PACF bottom, 95% CI dashed lines, lag 0 in ACF, PACF starts + at lag 1, 30 lags, axes labeled. + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: X = lag number, Y = correlation value, correctly mapped. + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title "acf-pacf · javascript · chartjs · anyplot.ai" exact format; + 95% CI legend correct. + data_quality: + score: 14 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: AR(2) demonstrates exponential ACF decay, PACF cutoff at model order, + lag-0=1.0, positive and negative correlations, significance bands. + - id: DQ-02 + name: Realistic Context + score: 4 + max: 5 + passed: true + comment: Plausible neutral statistical time series; slightly abstract without + explicit real-world domain label. + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: AR(2) with phi1=0.7, phi2=0.2; N=240; all values within [-1, 1] — + statistically correct. + code_quality: + score: 9 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 2 + max: 3 + passed: true + comment: Helper functions (lcgRand, stdNormal, computeACF, computePACF, makePane, + ciLine, yAxis) justified by browser LCG requirement and math complexity, + but deviate from flat-script KISS ideal. + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: Fixed LCG seed (_seed=42) ensures deterministic output. + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: No imports; all globals (Chart, ANYPLOT_TOKENS) used correctly. + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Clean, well-commented; no fake UI elements; appropriate complexity. + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Correct orientation directive; current Chart.js 4.4.7 API. + library_mastery: + score: 7 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: true + comment: Good use of mixed dataset types (bar+line), scriptable options API, + token-based color mapping, responsive/animation:false pattern. + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: true + comment: Uses Chart.js-specific scriptable options (grid color callback), + legend filter callback, and mixed chart type datasets. Could be replicated + with some effort in other libraries. + verdict: APPROVED +impl_tags: + dependencies: [] + techniques: + - subplots + - custom-legend + patterns: + - data-generation + dataprep: + - time-series + styling: + - grid-styling