From 4b14873d559af3b2df6ab7568267f0745a07341d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 26 Jul 2026 07:25:40 +0000 Subject: [PATCH 1/5] feat(echarts): implement swarm-basic --- .../implementations/javascript/echarts.js | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 plots/swarm-basic/implementations/javascript/echarts.js diff --git a/plots/swarm-basic/implementations/javascript/echarts.js b/plots/swarm-basic/implementations/javascript/echarts.js new file mode 100644 index 0000000000..44c4a6180d --- /dev/null +++ b/plots/swarm-basic/implementations/javascript/echarts.js @@ -0,0 +1,131 @@ +// anyplot.ai +// swarm-basic: Basic Swarm Plot +// Library: echarts 6.1.0 | JavaScript 22 +// Quality: pending | Created: 2026-07-26 + +const t = window.ANYPLOT_TOKENS; + +// --- Data (in-memory, deterministic) ---------------------------------------- +// CRP (C-reactive protein) biomarker levels across a dose-escalation trial. +function lcg(seed) { + let state = seed >>> 0; + return function () { + state = (state * 1664525 + 1013904223) >>> 0; + return state / 4294967296; + }; +} +const rng = lcg(42); +function randNormal() { + const u1 = Math.max(rng(), 1e-9); + const u2 = rng(); + return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2); +} + +const groups = [ + { name: "Placebo", n: 45, mean: 3.2, sd: 0.9 }, + { name: "Low Dose", n: 45, mean: 2.6, sd: 0.8 }, + { name: "Medium Dose", n: 45, mean: 1.9, sd: 0.7 }, + { name: "High Dose", n: 45, mean: 1.3, sd: 0.6 }, +]; +const categories = groups.map((g) => g.name); +const groupValues = groups.map((g) => + Array.from({ length: g.n }, () => Math.max(0.1, g.mean + randNormal() * g.sd)), +); + +// --- Beeswarm layout --------------------------------------------------------- +// ECharts has no native swarm/beeswarm series, so points are bucketed into +// fine value bins per category and stacked symmetrically outward within each +// bin — the classic histogram-based beeswarm construction. +function beeswarmOffsets(values) { + const n = values.length; + const order = values.map((_, i) => i).sort((a, b) => values[a] - values[b]); + const sorted = order.map((i) => values[i]); + const range = sorted[n - 1] - sorted[0] || 1; + const binCount = Math.max(10, Math.round(Math.sqrt(n) * 3)); + const binWidth = range / binCount; + const bins = new Map(); + sorted.forEach((v, sortedIdx) => { + const b = Math.min(binCount - 1, Math.floor((v - sorted[0]) / binWidth)); + if (!bins.has(b)) bins.set(b, []); + bins.get(b).push(sortedIdx); + }); + const spacing = 0.075; + const maxOffset = 0.42; + const offsetsBySortedIdx = new Array(n); + for (const members of bins.values()) { + members.forEach((sortedIdx, k) => { + const side = k % 2 === 0 ? 1 : -1; + const step = Math.ceil((k + 1) / 2); + offsetsBySortedIdx[sortedIdx] = Math.max(-maxOffset, Math.min(maxOffset, side * step * spacing)); + }); + } + const offsets = new Array(n); + order.forEach((origIdx, sortedIdx) => { + offsets[origIdx] = offsetsBySortedIdx[sortedIdx]; + }); + return offsets; +} + +const pointSeries = groups.map((g, ci) => { + const values = groupValues[ci]; + const offsets = beeswarmOffsets(values); + return { + name: g.name, + type: "scatter", + data: values.map((v, i) => [ci + offsets[i], v]), + symbolSize: 14, + itemStyle: { color: t.palette[ci], opacity: 0.8 }, + }; +}); + +const meanSeries = { + name: "Group mean", + type: "scatter", + data: groups.map((g, ci) => [ci, g.mean]), + symbol: "diamond", + symbolSize: 24, + itemStyle: { color: t.ink, borderColor: t.pageBg, borderWidth: 2 }, + z: 3, +}; + +// --- Init --------------------------------------------------------------- +const chart = echarts.init(document.getElementById("container")); + +// --- Option --------------------------------------------------------------- +chart.setOption({ + animation: false, + backgroundColor: "transparent", + title: { + text: "swarm-basic · javascript · echarts · anyplot.ai", + left: "center", + textStyle: { color: t.ink, fontSize: 22 }, + }, + grid: { left: 100, right: 60, top: 100, bottom: 80 }, + tooltip: { + trigger: "item", + formatter: (p) => `${categories[Math.round(p.value[0])] ?? p.seriesName}
CRP: ${p.value[1].toFixed(2)} mg/L`, + }, + xAxis: { + type: "value", + min: 0, + max: categories.length - 1, + interval: 1, + boundaryGap: ["12%", "12%"], + axisLabel: { color: t.inkSoft, fontSize: 14, formatter: (v) => categories[v] ?? "" }, + axisLine: { lineStyle: { color: t.inkSoft } }, + axisTick: { show: false }, + splitLine: { show: false }, + }, + yAxis: { + type: "value", + name: "CRP (mg/L)", + nameLocation: "middle", + nameGap: 60, + nameTextStyle: { color: t.inkSoft, fontSize: 14 }, + axisLabel: { color: t.inkSoft, fontSize: 14 }, + axisLine: { lineStyle: { color: t.inkSoft } }, + axisTick: { show: false }, + splitLine: { lineStyle: { color: t.grid } }, + }, + series: [...pointSeries, meanSeries], +}); From 78908f024af8b5b69ad2ffe0e0e062fa62a9debc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 26 Jul 2026 07:25:48 +0000 Subject: [PATCH 2/5] chore(echarts): add metadata for swarm-basic --- .../metadata/javascript/echarts.yaml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 plots/swarm-basic/metadata/javascript/echarts.yaml diff --git a/plots/swarm-basic/metadata/javascript/echarts.yaml b/plots/swarm-basic/metadata/javascript/echarts.yaml new file mode 100644 index 0000000000..6f70166d37 --- /dev/null +++ b/plots/swarm-basic/metadata/javascript/echarts.yaml @@ -0,0 +1,21 @@ +# Per-library metadata for echarts implementation of swarm-basic +# Auto-generated by impl-generate.yml + +library: echarts +language: javascript +specification_id: swarm-basic +created: '2026-07-26T07:25:48Z' +updated: '2026-07-26T07:25:48Z' +generated_by: claude-sonnet +workflow_run: 30192609034 +issue: 974 +language_version: 22.23.1 +library_version: 6.1.0 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/swarm-basic/javascript/echarts/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/swarm-basic/javascript/echarts/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/swarm-basic/javascript/echarts/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/swarm-basic/javascript/echarts/plot-dark.html +quality_score: null +review: + strengths: [] + weaknesses: [] From b0944d0590a33498485ab0ded5164d8b94f799eb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 26 Jul 2026 07:30:33 +0000 Subject: [PATCH 3/5] chore(echarts): update quality score 86 and review feedback for swarm-basic --- .../implementations/javascript/echarts.js | 4 +- .../metadata/javascript/echarts.yaml | 241 +++++++++++++++++- 2 files changed, 236 insertions(+), 9 deletions(-) diff --git a/plots/swarm-basic/implementations/javascript/echarts.js b/plots/swarm-basic/implementations/javascript/echarts.js index 44c4a6180d..a96c48717f 100644 --- a/plots/swarm-basic/implementations/javascript/echarts.js +++ b/plots/swarm-basic/implementations/javascript/echarts.js @@ -1,7 +1,7 @@ // anyplot.ai // swarm-basic: Basic Swarm Plot -// Library: echarts 6.1.0 | JavaScript 22 -// Quality: pending | Created: 2026-07-26 +// Library: echarts 6.1.0 | JavaScript 22.23.1 +// Quality: 86/100 | Created: 2026-07-26 const t = window.ANYPLOT_TOKENS; diff --git a/plots/swarm-basic/metadata/javascript/echarts.yaml b/plots/swarm-basic/metadata/javascript/echarts.yaml index 6f70166d37..da6a680ecb 100644 --- a/plots/swarm-basic/metadata/javascript/echarts.yaml +++ b/plots/swarm-basic/metadata/javascript/echarts.yaml @@ -1,11 +1,8 @@ -# Per-library metadata for echarts implementation of swarm-basic -# Auto-generated by impl-generate.yml - library: echarts language: javascript specification_id: swarm-basic created: '2026-07-26T07:25:48Z' -updated: '2026-07-26T07:25:48Z' +updated: '2026-07-26T07:30:33Z' generated_by: claude-sonnet workflow_run: 30192609034 issue: 974 @@ -15,7 +12,237 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/swarm-bas preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/swarm-basic/javascript/echarts/plot-dark.png preview_html_light: https://storage.googleapis.com/anyplot-images/plots/swarm-basic/javascript/echarts/plot-light.html preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/swarm-basic/javascript/echarts/plot-dark.html -quality_score: null +quality_score: 86 review: - strengths: [] - weaknesses: [] + strengths: + - Correct Imprint palette assignment across all 4 categories in canonical order + (Placebo=#009E73, Low Dose=#C475FD, Medium Dose=#4467A3, High Dose=#BD8233) + - Theme-adaptive chrome verified correct in both light and dark renders — no dark-on-dark + or light-on-light legibility failures + - Deterministic seeded LCG data generation, fully reproducible + - Implements the spec's suggested 'subtle mean marker' feature with a diamond whose + border color matches the page background, giving a clean halo effect in both themes + - Realistic, neutral CRP dose-escalation clinical trial dataset with a clear downward + dose-response trend, varied spread per group, and visible outliers (DQ-01 well + covered) + - Clean, KISS-adjacent code — the LCG/beeswarm helper functions are necessary complexity + for computing the layout, not gratuitous abstraction + weaknesses: + - Beeswarm point layout is quantized into discrete vertical columns from the fixed + bin/step offset formula, giving a blocky, grid-like silhouette rather than an + organic swarm curve — reduce bin width or use a smoother nearest-neighbor jitter + so density reads as a continuous shape. + - No legend or label identifies the dark diamond markers as the group mean — a viewer + of the static PNG (not hovering the interactive tooltip) cannot infer this. Add + a small legend entry or subtle annotation labeling 'Mean'. + - Y-axis tick marks (axisTick) are not explicitly hidden while x-axis ticks are + — inconsistent chrome minimalism; hide both for a cleaner L-shaped frame. + - Library Mastery is generic — the swarm is built with a plain scatter series and + manual offset math that any library could replicate; a custom renderItem or other + ECharts-distinctive technique would better showcase the library. + image_description: |- + Light render (plot-light.png): + Background: warm off-white, matches #FAF8F1 — not pure white. + Chrome: title "swarm-basic · javascript · echarts · anyplot.ai" centered at top in dark charcoal, clearly legible. Y-axis titled "CRP (mg/L)" (has units) in soft gray. X-axis shows category names (Placebo, Low Dose, Medium Dose, High Dose) directly as tick labels. Subtle horizontal gridlines only (no vertical grid, appropriate for categorical x). All text is readable against the light background. + Data: four groups rendered as vertically-spread swarms of dots — green (#009E73, Placebo), purple (#C475FD, Low Dose), blue (#4467A3, Medium Dose), ochre (#BD8233, High Dose) — plus a dark diamond per group marking the mean, with a light halo border matching the page background. Clear downward trend in CRP across dose groups. + Legibility verdict: PASS + + Dark render (plot-dark.png): + Background: warm near-black, matches #1A1A17 — not pure black. + Chrome: title, y-axis title, and all tick labels switch to light off-white/gray text and remain fully readable — no dark-on-dark failures detected. Gridlines remain subtle and visible against the dark surface. + Data: the four category colors (green, purple, blue, ochre) are pixel-identical to the light render — only chrome flipped. The mean-marker diamonds swap to a light fill with dark border to stay visible against the darker page, an appropriate theme-adaptive treatment. + Legibility verdict: PASS + criteria_checklist: + visual_quality: + score: 28 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 7 + max: 8 + passed: true + comment: All font sizes explicitly set (title 22px, axis/tick labels 14px), + readable in both themes, well proportioned + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No text overlap; dense point clustering is intentional swarm density, + not a defect + - id: VQ-03 + name: Element Visibility + score: 5 + max: 6 + passed: true + comment: symbolSize=14 with alpha=0.8 is visible and reasonable for ~45 pts/group, + though dense central bins get busy + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: 4 CVD-safe hues, no red-green-only signal + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: Balanced margins, plot fills a good portion of canvas, nothing cut + off + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: Y-axis 'CRP (mg/L)' has units + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: Canonical Imprint order across 4 series, theme-correct backgrounds + and text in both renders + design_excellence: + score: 14 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 6 + max: 8 + passed: true + comment: Custom beeswarm layout + mean markers clearly above a configured + default, though blocky column shape holds it back from top marks + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: Subtle single-axis grid, generous whitespace; y-axis tick marks not + hidden (minor inconsistency) + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: Dose-response trend and mean markers create visual hierarchy the + viewer can read at a glance + spec_compliance: + score: 14 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: Correct beeswarm/swarm construction despite no native ECharts series + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: Consistent point sizes, mean markers per category, color distinguishes + categories, clear spacing between groups + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: Category on x, value (CRP) on y, all data visible + - id: SC-04 + name: Title & Legend + score: 2 + max: 3 + passed: true + comment: Title format exactly correct; no legend/label explains what the diamond + markers represent + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: Shows spread variation, outliers, and a clear multi-group trend + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Neutral, comprehensible clinical dose-escalation CRP scenario + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: CRP mg/L values and dose-response direction are clinically plausible + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: Helper functions are necessary for the beeswarm layout, not gratuitous + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: Seeded LCG + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: No unused imports; only globals used + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Appropriate complexity, no fake UI + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Correct mount-node contract, animation:false + library_mastery: + score: 5 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: true + comment: Standard scatter-series + axis formatter approach, correct given + no native beeswarm support + - id: LM-02 + name: Distinctive Features + score: 1 + max: 5 + passed: false + comment: No ECharts-distinctive feature used (e.g. custom renderItem); generic + scatter-based construction + verdict: REJECTED +impl_tags: + dependencies: [] + techniques: + - manual-ticks + patterns: + - data-generation + - iteration-over-groups + dataprep: + - binning + styling: + - alpha-blending + - edge-highlighting From bdf2416a05a2696ce30d37444c44ab73e744cc4a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 26 Jul 2026 07:47:10 +0000 Subject: [PATCH 4/5] fix(echarts): address review feedback for swarm-basic Attempt 1/4 - fixes based on AI review --- .../implementations/javascript/echarts.js | 146 ++++++++++-------- 1 file changed, 82 insertions(+), 64 deletions(-) diff --git a/plots/swarm-basic/implementations/javascript/echarts.js b/plots/swarm-basic/implementations/javascript/echarts.js index a96c48717f..3b1de40543 100644 --- a/plots/swarm-basic/implementations/javascript/echarts.js +++ b/plots/swarm-basic/implementations/javascript/echarts.js @@ -32,66 +32,17 @@ const groupValues = groups.map((g) => Array.from({ length: g.n }, () => Math.max(0.1, g.mean + randNormal() * g.sd)), ); -// --- Beeswarm layout --------------------------------------------------------- -// ECharts has no native swarm/beeswarm series, so points are bucketed into -// fine value bins per category and stacked symmetrically outward within each -// bin — the classic histogram-based beeswarm construction. -function beeswarmOffsets(values) { - const n = values.length; - const order = values.map((_, i) => i).sort((a, b) => values[a] - values[b]); - const sorted = order.map((i) => values[i]); - const range = sorted[n - 1] - sorted[0] || 1; - const binCount = Math.max(10, Math.round(Math.sqrt(n) * 3)); - const binWidth = range / binCount; - const bins = new Map(); - sorted.forEach((v, sortedIdx) => { - const b = Math.min(binCount - 1, Math.floor((v - sorted[0]) / binWidth)); - if (!bins.has(b)) bins.set(b, []); - bins.get(b).push(sortedIdx); - }); - const spacing = 0.075; - const maxOffset = 0.42; - const offsetsBySortedIdx = new Array(n); - for (const members of bins.values()) { - members.forEach((sortedIdx, k) => { - const side = k % 2 === 0 ? 1 : -1; - const step = Math.ceil((k + 1) / 2); - offsetsBySortedIdx[sortedIdx] = Math.max(-maxOffset, Math.min(maxOffset, side * step * spacing)); - }); - } - const offsets = new Array(n); - order.forEach((origIdx, sortedIdx) => { - offsets[origIdx] = offsetsBySortedIdx[sortedIdx]; - }); - return offsets; -} - -const pointSeries = groups.map((g, ci) => { - const values = groupValues[ci]; - const offsets = beeswarmOffsets(values); - return { - name: g.name, - type: "scatter", - data: values.map((v, i) => [ci + offsets[i], v]), - symbolSize: 14, - itemStyle: { color: t.palette[ci], opacity: 0.8 }, - }; -}); - -const meanSeries = { - name: "Group mean", - type: "scatter", - data: groups.map((g, ci) => [ci, g.mean]), - symbol: "diamond", - symbolSize: 24, - itemStyle: { color: t.ink, borderColor: t.pageBg, borderWidth: 2 }, - z: 3, -}; - // --- Init --------------------------------------------------------------- const chart = echarts.init(document.getElementById("container")); -// --- Option --------------------------------------------------------------- +// Fix both axis extents up front so the coordinate system is fully +// deterministic before any point is placed. +const allValues = groupValues.flat(); +const yPad = (Math.max(...allValues) - Math.min(...allValues)) * 0.08; +const yMin = Math.max(0, Math.floor(Math.min(...allValues) - yPad)); +const yMax = Math.ceil(Math.max(...allValues) + yPad); +const xPad = 0.36; // 12% of the 3-unit category span, keeps swarms off the plot edge + chart.setOption({ animation: false, backgroundColor: "transparent", @@ -100,6 +51,14 @@ chart.setOption({ left: "center", textStyle: { color: t.ink, fontSize: 22 }, }, + legend: { + data: [{ name: "Group mean", icon: "diamond" }], + right: 60, + top: 56, + itemWidth: 12, + itemHeight: 12, + textStyle: { color: t.inkSoft, fontSize: 14 }, + }, grid: { left: 100, right: 60, top: 100, bottom: 80 }, tooltip: { trigger: "item", @@ -107,25 +66,84 @@ chart.setOption({ }, xAxis: { type: "value", - min: 0, - max: categories.length - 1, + min: -xPad, + max: categories.length - 1 + xPad, interval: 1, - boundaryGap: ["12%", "12%"], - axisLabel: { color: t.inkSoft, fontSize: 14, formatter: (v) => categories[v] ?? "" }, - axisLine: { lineStyle: { color: t.inkSoft } }, + axisLabel: { + color: t.inkSoft, + fontSize: 14, + formatter: (v) => categories[Math.round(v)] ?? "", + showMaxLabel: false, + }, + axisLine: { lineStyle: { color: t.inkSoft }, onZero: false }, axisTick: { show: false }, splitLine: { show: false }, }, yAxis: { type: "value", + min: yMin, + max: yMax, name: "CRP (mg/L)", nameLocation: "middle", nameGap: 60, nameTextStyle: { color: t.inkSoft, fontSize: 14 }, axisLabel: { color: t.inkSoft, fontSize: 14 }, - axisLine: { lineStyle: { color: t.inkSoft } }, + axisLine: { lineStyle: { color: t.inkSoft }, onZero: false }, axisTick: { show: false }, splitLine: { lineStyle: { color: t.grid } }, }, - series: [...pointSeries, meanSeries], }); + +// --- Beeswarm layout --------------------------------------------------------- +// ECharts has no native swarm/beeswarm series. Rather than bucketing points +// into fixed-width bins (which produces a blocky, grid-like silhouette), this +// greedily places each point at the nearest collision-free slot using the +// chart's own pixel projection (`convertToPixel`/`convertFromPixel`) — the +// same technique ECharts' own beeswarm/packed-scatter examples use — so the +// swarm reads as a continuous organic shape at any density. +const SYMBOL_SIZE = 14; +const GAP = SYMBOL_SIZE * 0.92; // slight overlap reads as a continuous swarm +function beeswarmLayout(ci, values) { + const basePx = values.map((v) => chart.convertToPixel({ xAxisIndex: 0, yAxisIndex: 0 }, [ci, v])); + const order = basePx.map((_, i) => i).sort((a, b) => basePx[a][1] - basePx[b][1]); + const placed = []; + const finalX = new Array(values.length); + order.forEach((i) => { + const [cx, cy] = basePx[i]; + let px = cx; + let k = 0; + while (k < 200 && placed.some((p) => Math.hypot(p.x - px, p.y - cy) < GAP)) { + k += 1; + const step = Math.ceil(k / 2); + const side = k % 2 === 1 ? 1 : -1; + px = cx + side * step * GAP; + } + placed.push({ x: px, y: cy }); + finalX[i] = chart.convertFromPixel({ xAxisIndex: 0, yAxisIndex: 0 }, [px, cy])[0]; + }); + return finalX; +} + +const pointSeries = groups.map((g, ci) => { + const values = groupValues[ci]; + const xs = beeswarmLayout(ci, values); + return { + name: g.name, + type: "scatter", + data: values.map((v, i) => [xs[i], v]), + symbolSize: SYMBOL_SIZE, + itemStyle: { color: t.palette[ci], opacity: 0.8 }, + }; +}); + +const meanSeries = { + name: "Group mean", + type: "scatter", + data: groups.map((g, ci) => [ci, g.mean]), + symbol: "diamond", + symbolSize: 24, + itemStyle: { color: t.ink, borderColor: t.pageBg, borderWidth: 2 }, + z: 3, +}; + +chart.setOption({ series: [...pointSeries, meanSeries] }); From c2fc6e598c5cb9e48c904e303f655d9ed6129f69 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 26 Jul 2026 07:51:50 +0000 Subject: [PATCH 5/5] chore(echarts): update quality score 89 and review feedback for swarm-basic --- .../implementations/javascript/echarts.js | 2 +- .../metadata/javascript/echarts.yaml | 131 ++++++++---------- 2 files changed, 57 insertions(+), 76 deletions(-) diff --git a/plots/swarm-basic/implementations/javascript/echarts.js b/plots/swarm-basic/implementations/javascript/echarts.js index 3b1de40543..130b37d97a 100644 --- a/plots/swarm-basic/implementations/javascript/echarts.js +++ b/plots/swarm-basic/implementations/javascript/echarts.js @@ -1,7 +1,7 @@ // anyplot.ai // swarm-basic: Basic Swarm Plot // Library: echarts 6.1.0 | JavaScript 22.23.1 -// Quality: 86/100 | Created: 2026-07-26 +// Quality: 89/100 | Created: 2026-07-26 const t = window.ANYPLOT_TOKENS; diff --git a/plots/swarm-basic/metadata/javascript/echarts.yaml b/plots/swarm-basic/metadata/javascript/echarts.yaml index da6a680ecb..c480c9f639 100644 --- a/plots/swarm-basic/metadata/javascript/echarts.yaml +++ b/plots/swarm-basic/metadata/javascript/echarts.yaml @@ -2,7 +2,7 @@ library: echarts language: javascript specification_id: swarm-basic created: '2026-07-26T07:25:48Z' -updated: '2026-07-26T07:30:33Z' +updated: '2026-07-26T07:51:49Z' generated_by: claude-sonnet workflow_run: 30192609034 issue: 974 @@ -12,45 +12,37 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/swarm-bas preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/swarm-basic/javascript/echarts/plot-dark.png preview_html_light: https://storage.googleapis.com/anyplot-images/plots/swarm-basic/javascript/echarts/plot-light.html preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/swarm-basic/javascript/echarts/plot-dark.html -quality_score: 86 +quality_score: 89 review: strengths: + - 'All three Attempt 1 weaknesses fixed: legend entry now labels the mean-diamond + markers, y-axis tick marks hidden to match x-axis chrome, and beeswarm layout + rewritten to a greedy nearest-slot placement in pixel space for an organic swarm + silhouette' - Correct Imprint palette assignment across all 4 categories in canonical order (Placebo=#009E73, Low Dose=#C475FD, Medium Dose=#4467A3, High Dose=#BD8233) - - Theme-adaptive chrome verified correct in both light and dark renders — no dark-on-dark - or light-on-light legibility failures + - Theme-adaptive chrome verified correct in both renders — no dark-on-dark or light-on-light + legibility failures - Deterministic seeded LCG data generation, fully reproducible - - Implements the spec's suggested 'subtle mean marker' feature with a diamond whose - border color matches the page background, giving a clean halo effect in both themes - Realistic, neutral CRP dose-escalation clinical trial dataset with a clear downward - dose-response trend, varied spread per group, and visible outliers (DQ-01 well - covered) - - Clean, KISS-adjacent code — the LCG/beeswarm helper functions are necessary complexity - for computing the layout, not gratuitous abstraction + dose-response trend, varied spread per group, and visible outliers weaknesses: - - Beeswarm point layout is quantized into discrete vertical columns from the fixed - bin/step offset formula, giving a blocky, grid-like silhouette rather than an - organic swarm curve — reduce bin width or use a smoother nearest-neighbor jitter - so density reads as a continuous shape. - - No legend or label identifies the dark diamond markers as the group mean — a viewer - of the static PNG (not hovering the interactive tooltip) cannot infer this. Add - a small legend entry or subtle annotation labeling 'Mean'. - - Y-axis tick marks (axisTick) are not explicitly hidden while x-axis ticks are - — inconsistent chrome minimalism; hide both for a cleaner L-shaped frame. - - Library Mastery is generic — the swarm is built with a plain scatter series and - manual offset math that any library could replicate; a custom renderItem or other - ECharts-distinctive technique would better showcase the library. + - Library Mastery is still fundamentally a generic scatter series; the custom layout + uses ECharts' coordinate-conversion API but doesn't showcase a truly ECharts-distinctive + series type (e.g. custom renderItem) + - Data storytelling is solid but static — no annotation or callout emphasizes the + dose-response trend beyond the visual pattern itself image_description: |- Light render (plot-light.png): - Background: warm off-white, matches #FAF8F1 — not pure white. - Chrome: title "swarm-basic · javascript · echarts · anyplot.ai" centered at top in dark charcoal, clearly legible. Y-axis titled "CRP (mg/L)" (has units) in soft gray. X-axis shows category names (Placebo, Low Dose, Medium Dose, High Dose) directly as tick labels. Subtle horizontal gridlines only (no vertical grid, appropriate for categorical x). All text is readable against the light background. - Data: four groups rendered as vertically-spread swarms of dots — green (#009E73, Placebo), purple (#C475FD, Low Dose), blue (#4467A3, Medium Dose), ochre (#BD8233, High Dose) — plus a dark diamond per group marking the mean, with a light halo border matching the page background. Clear downward trend in CRP across dose groups. + Background: Warm off-white matching #FAF8F1, not pure white. + Chrome: Title "swarm-basic · javascript · echarts · anyplot.ai" centered, bold dark charcoal text, fully legible. "Group mean" legend with diamond icon top-right. Y-axis "CRP (mg/L)" label with units. X-axis category labels (Placebo, Low Dose, Medium Dose, High Dose) directly beneath each swarm. Subtle horizontal-only gridlines; both axis tick marks hidden. All readable against the light background. + Data: Four swarms in Imprint canonical order — green #009E73 (Placebo), lavender #C475FD (Low Dose), blue #4467A3 (Medium Dose), ochre #BD8233 (High Dose) — each with a dark diamond (light halo border) marking the group mean. Clear downward dose-response trend visible. Legibility verdict: PASS Dark render (plot-dark.png): - Background: warm near-black, matches #1A1A17 — not pure black. - Chrome: title, y-axis title, and all tick labels switch to light off-white/gray text and remain fully readable — no dark-on-dark failures detected. Gridlines remain subtle and visible against the dark surface. - Data: the four category colors (green, purple, blue, ochre) are pixel-identical to the light render — only chrome flipped. The mean-marker diamonds swap to a light fill with dark border to stay visible against the darker page, an appropriate theme-adaptive treatment. + Background: Warm near-black matching #1A1A17, not pure black. + Chrome: Title, legend text, y-axis title, and all tick labels switch to light off-white/gray and remain fully readable — no dark-on-dark failures anywhere. Gridlines remain subtle but visible. + Data: Colors are pixel-identical to the light render — only chrome flipped. Mean-marker diamonds swap to a light fill with dark border to stay visible against the darker page, an appropriate theme-adaptive treatment. Legibility verdict: PASS criteria_checklist: visual_quality: @@ -62,50 +54,45 @@ review: score: 7 max: 8 passed: true - comment: All font sizes explicitly set (title 22px, axis/tick labels 14px), - readable in both themes, well proportioned + comment: All text readable in both themes, correctly sized - id: VQ-02 name: No Overlap score: 6 max: 6 passed: true - comment: No text overlap; dense point clustering is intentional swarm density, - not a defect + comment: No text or marker collisions - id: VQ-03 name: Element Visibility score: 5 max: 6 passed: true - comment: symbolSize=14 with alpha=0.8 is visible and reasonable for ~45 pts/group, - though dense central bins get busy + comment: Marker size/alpha appropriate for ~45 points per group - id: VQ-04 name: Color Accessibility score: 2 max: 2 passed: true - comment: 4 CVD-safe hues, no red-green-only signal + comment: CVD-safe Imprint palette, adequate contrast - id: VQ-05 name: Layout & Canvas score: 4 max: 4 passed: true - comment: Balanced margins, plot fills a good portion of canvas, nothing cut - off + comment: Good proportions, nothing cut off - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Y-axis 'CRP (mg/L)' has units + comment: Descriptive with units (CRP mg/L) - id: VQ-07 name: Palette Compliance score: 2 max: 2 passed: true - comment: Canonical Imprint order across 4 series, theme-correct backgrounds - and text in both renders + comment: Canonical Imprint order, correct theme backgrounds both renders design_excellence: - score: 14 + score: 15 max: 20 items: - id: DE-01 @@ -113,24 +100,22 @@ review: score: 6 max: 8 passed: true - comment: Custom beeswarm layout + mean markers clearly above a configured - default, though blocky column shape holds it back from top marks + comment: Custom pixel-space beeswarm layout + themed mean markers - id: DE-02 name: Visual Refinement - score: 4 + score: 5 max: 6 passed: true - comment: Subtle single-axis grid, generous whitespace; y-axis tick marks not - hidden (minor inconsistency) + comment: Subtle grid, generous whitespace, both axis ticks hidden (fixed from + Attempt 1) - id: DE-03 name: Data Storytelling score: 4 max: 6 passed: true - comment: Dose-response trend and mean markers create visual hierarchy the - viewer can read at a glance + comment: Dose-response trend + labeled mean markers create readable hierarchy spec_compliance: - score: 14 + score: 15 max: 15 items: - id: SC-01 @@ -138,27 +123,26 @@ review: score: 5 max: 5 passed: true - comment: Correct beeswarm/swarm construction despite no native ECharts series + comment: Correct swarm/beeswarm plot - id: SC-02 name: Required Features score: 4 max: 4 passed: true - comment: Consistent point sizes, mean markers per category, color distinguishes - categories, clear spacing between groups + comment: All spec features present - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: Category on x, value (CRP) on y, all data visible + comment: X/Y correct, axes show all data - id: SC-04 name: Title & Legend - score: 2 + score: 3 max: 3 passed: true - comment: Title format exactly correct; no legend/label explains what the diamond - markers represent + comment: Exact title format; legend now identifies mean markers (fixed from + Attempt 1) data_quality: score: 15 max: 15 @@ -168,19 +152,19 @@ review: score: 6 max: 6 passed: true - comment: Shows spread variation, outliers, and a clear multi-group trend + comment: Shows distribution shape, outliers, mean markers - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Neutral, comprehensible clinical dose-escalation CRP scenario + comment: Realistic, neutral CRP clinical trial data - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: CRP mg/L values and dose-response direction are clinically plausible + comment: Sensible CRP mg/L values code_quality: score: 10 max: 10 @@ -190,33 +174,33 @@ review: score: 3 max: 3 passed: true - comment: Helper functions are necessary for the beeswarm layout, not gratuitous + comment: Helper functions justified by lack of native ECharts swarm series - id: CQ-02 name: Reproducibility score: 2 max: 2 passed: true - comment: Seeded LCG + comment: Seeded LCG RNG, deterministic - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: No unused imports; only globals used + comment: No unused imports - id: CQ-04 name: Code Elegance score: 2 max: 2 passed: true - comment: Appropriate complexity, no fake UI + comment: Appropriate complexity, no fake functionality - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: Correct mount-node contract, animation:false + comment: Follows mount-node contract correctly library_mastery: - score: 5 + score: 6 max: 10 items: - id: LM-01 @@ -224,25 +208,22 @@ review: score: 4 max: 5 passed: true - comment: Standard scatter-series + axis formatter approach, correct given - no native beeswarm support + comment: Correct token usage, idiomatic setOption pattern - id: LM-02 name: Distinctive Features - score: 1 + score: 2 max: 5 passed: false - comment: No ECharts-distinctive feature used (e.g. custom renderItem); generic - scatter-based construction - verdict: REJECTED + comment: Uses convertToPixel/convertFromPixel coordinate API but still a plain + scatter series, not a distinctive ECharts technique like custom renderItem + verdict: APPROVED impl_tags: dependencies: [] techniques: - - manual-ticks + - custom-legend patterns: - data-generation - iteration-over-groups - dataprep: - - binning + dataprep: [] styling: - alpha-blending - - edge-highlighting