Skip to content

Commit b987161

Browse files
feat(echarts): implement scatter-categorical (#11620)
## Implementation: `scatter-categorical` - javascript/echarts Implements the **javascript/echarts** version of `scatter-categorical`. **File:** `plots/scatter-categorical/implementations/javascript/echarts.js` **Parent Issue:** #2597 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/33968764488)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com>
1 parent 90cad0d commit b987161

2 files changed

Lines changed: 348 additions & 0 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// anyplot.ai
2+
// scatter-categorical: Categorical Scatter Plot
3+
// Library: echarts 6.1.0 | JavaScript 22.23.2
4+
// Quality: 88/100 | Created: 2026-09-05
5+
6+
const t = window.ANYPLOT_TOKENS;
7+
8+
// --- Data (in-memory, deterministic) ----------------------------------------
9+
// Tiny fixed-seed LCG so both theme renders draw identical points.
10+
let seed = 42;
11+
function rand() {
12+
seed = (seed * 1103515245 + 12345) & 0x7fffffff;
13+
return seed / 0x7fffffff;
14+
}
15+
function gaussian(mean, std) {
16+
const u1 = Math.max(rand(), 1e-9);
17+
const u2 = rand();
18+
const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
19+
return mean + z * std;
20+
}
21+
22+
const species = [
23+
{ name: "Sepal Iris", leafLength: 4.4, leafWidth: 1.2, n: 45 },
24+
{ name: "Marsh Iris", leafLength: 5.6, leafWidth: 2.0, n: 45 },
25+
{ name: "Alpine Iris", leafLength: 6.3, leafWidth: 2.4, n: 45 },
26+
];
27+
28+
const series = species.map((sp, i) => ({
29+
name: sp.name,
30+
type: "scatter",
31+
symbolSize: 18,
32+
data: Array.from({ length: sp.n }, () => [
33+
Number(gaussian(sp.leafLength, 0.55).toFixed(2)),
34+
Number(gaussian(sp.leafWidth, 0.28).toFixed(2)),
35+
]),
36+
// Theme-adaptive edge separates overlapping markers in the Marsh/Alpine
37+
// overlap band (5.5-6.5 cm); page-bg stroke reads as a halo, not chartjunk.
38+
itemStyle: { opacity: 0.72, borderColor: t.pageBg, borderWidth: 1 },
39+
emphasis: {
40+
focus: "series",
41+
itemStyle: { opacity: 1, borderColor: t.ink, borderWidth: 1.5 },
42+
},
43+
// Overlap-zone callout lives on the middle (Marsh Iris) series only.
44+
markArea:
45+
i === 1
46+
? {
47+
silent: true,
48+
itemStyle: { color: t.inkSoft, opacity: 0.06 },
49+
label: { color: t.inkSoft, fontSize: 12, position: "insideBottom" },
50+
data: [
51+
[
52+
{ xAxis: 5.5, name: "Overlap zone" },
53+
{ xAxis: 6.5 },
54+
],
55+
],
56+
}
57+
: undefined,
58+
}));
59+
60+
// --- Init ---------------------------------------------------------------
61+
const chart = echarts.init(document.getElementById("container"));
62+
63+
// --- Option ---------------------------------------------------------------
64+
chart.setOption({
65+
animation: false,
66+
color: t.palette,
67+
backgroundColor: "transparent",
68+
title: {
69+
text: "scatter-categorical · javascript · echarts · anyplot.ai",
70+
left: "center",
71+
textStyle: { color: t.ink, fontSize: 22, fontWeight: 500 },
72+
},
73+
legend: {
74+
top: 56,
75+
textStyle: { color: t.ink, fontSize: 16 },
76+
itemWidth: 18,
77+
itemHeight: 12,
78+
},
79+
tooltip: {
80+
trigger: "item",
81+
formatter: (p) =>
82+
`${p.seriesName}<br/>Leaf Length: ${p.data[0]} cm<br/>Leaf Width: ${p.data[1]} cm`,
83+
},
84+
grid: { left: 90, right: 60, top: 130, bottom: 90 },
85+
xAxis: {
86+
type: "value",
87+
name: "Leaf Length (cm)",
88+
nameLocation: "middle",
89+
nameGap: 40,
90+
nameTextStyle: { color: t.ink, fontSize: 16 },
91+
axisLabel: { color: t.inkSoft, fontSize: 14 },
92+
axisLine: { lineStyle: { color: t.inkSoft } },
93+
splitLine: { lineStyle: { color: t.grid } },
94+
},
95+
yAxis: {
96+
type: "value",
97+
name: "Leaf Width (cm)",
98+
nameLocation: "middle",
99+
nameGap: 55,
100+
nameTextStyle: { color: t.ink, fontSize: 16 },
101+
axisLabel: { color: t.inkSoft, fontSize: 14 },
102+
axisLine: { lineStyle: { color: t.inkSoft } },
103+
splitLine: { lineStyle: { color: t.grid } },
104+
},
105+
series,
106+
});
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
library: echarts
2+
language: javascript
3+
specification_id: scatter-categorical
4+
created: '2026-09-05T13:27:09Z'
5+
updated: '2026-09-05T13:40:49Z'
6+
generated_by: claude-sonnet
7+
workflow_run: 33968764488
8+
issue: 2597
9+
language_version: 22.23.2
10+
library_version: 6.1.0
11+
preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-categorical/javascript/echarts/plot-light.png
12+
preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-categorical/javascript/echarts/plot-dark.png
13+
preview_html_light: https://storage.googleapis.com/anyplot-images/plots/scatter-categorical/javascript/echarts/plot-light.html
14+
preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-categorical/javascript/echarts/plot-dark.html
15+
quality_score: 88
16+
review:
17+
strengths:
18+
- 'All attempt-1 weaknesses addressed: theme-adaptive marker borders (itemStyle.borderColor:
19+
t.pageBg) now separate overlapping points, emphasis.focus:"series" was added,
20+
a markArea "Overlap zone" annotation now visually calls out the Marsh/Alpine overlap
21+
band, and marker size/opacity were tuned down (22→18px, 0.8→0.72) to reduce occlusion'
22+
- 'First categorical series is exactly #009E73 with canonical Imprint order at positions
23+
2-3, identical data colors across both renders'
24+
- All text (title, legend, axis names, tick labels) is legible in both themes with
25+
no dark-on-dark or light-on-light failures
26+
- 'Clean, idiomatic single-setOption ECharts usage with animation: false and correct
27+
mount-node contract; deterministic fixed-seed LCG data generation'
28+
- The markArea overlap-zone shading and "Overlap zone" label render subtly but legibly
29+
in both themes and add genuine data storytelling on top of the correlation pattern
30+
weaknesses:
31+
- Aesthetic polish is still close to a well-configured default beyond the overlap-zone
32+
callout — no additional typography/visual-hierarchy device lifts it further (DE-01
33+
has headroom)
34+
- markArea + emphasis.focus + a tooltip formatter are nice touches but are fairly
35+
common ECharts patterns; a more distinctive feature (e.g. visualMap-driven emphasis
36+
or custom graphic elements) would differentiate further (LM-02)
37+
- Only two continuous dimensions plus one categorical (color) are encoded; the spec's
38+
optional suggestion to vary marker shape by group is not used, which would add
39+
a second, color-independent channel for group distinction
40+
image_description: |-
41+
Light render (plot-light.png):
42+
Background: Warm off-white, consistent with #FAF8F1, not pure white.
43+
Chrome: Title "scatter-categorical · javascript · echarts · anyplot.ai" centered at top in dark ink; legend below lists three swatches (Sepal Iris green, Marsh Iris lavender, Alpine Iris blue-gray) in dark text; axis names "Leaf Length (cm)" / "Leaf Width (cm)" and numeric tick labels are dark/soft-dark ink; subtle light-gray gridlines; a light-gray "Overlap zone" shaded band (leaf length 5.5-6.5cm) with a legible label near the x-axis.
44+
Data: Three color-coded clusters (green lower-left, lavender mid-band, blue-gray upper-right) show a clear positive correlation between leaf length and width, with genuine overlap between the lavender and blue-gray clusters in the shaded band. Markers now carry a thin theme-adaptive (page-background-colored) border/halo that visibly separates individual overlapping circles even where several points of the same or different color touch.
45+
Legibility verdict: PASS
46+
47+
Dark render (plot-dark.png):
48+
Background: Warm near-black, consistent with #1A1A17, not pure black.
49+
Chrome: Title, legend text, axis names, and tick labels are rendered in light ink, clearly legible; gridlines are faint but visible; the "Overlap zone" band and label are legible against the dark surface (slightly darker shaded rectangle with light-gray text).
50+
Data: Colors are confirmed identical to the light render (same green/lavender/blue-gray hues and point positions) — only chrome flipped. The marker halo border is visible here too (a subtle darker/page-bg-colored ring), separating overlapping points in the dense zone.
51+
Legibility verdict: PASS — no dark-on-dark failures observed.
52+
criteria_checklist:
53+
visual_quality:
54+
score: 30
55+
max: 30
56+
items:
57+
- id: VQ-01
58+
name: Text Legibility
59+
score: 8
60+
max: 8
61+
passed: true
62+
comment: Title, legend, axis names, and ticks all legible in both themes;
63+
explicit font sizes throughout
64+
- id: VQ-02
65+
name: No Overlap
66+
score: 6
67+
max: 6
68+
passed: true
69+
comment: No text collides with other text, data, or the legend
70+
- id: VQ-03
71+
name: Element Visibility
72+
score: 6
73+
max: 6
74+
passed: true
75+
comment: Theme-adaptive marker borders (page-bg halo) now separate overlapping
76+
points in the dense Marsh/Alpine band; size/opacity tuned down from attempt
77+
1
78+
- id: VQ-04
79+
name: Color Accessibility
80+
score: 2
81+
max: 2
82+
passed: true
83+
comment: Green/lavender/blue-gray palette is CVD-safe; no red-green as sole
84+
signal
85+
- id: VQ-05
86+
name: Layout & Canvas
87+
score: 4
88+
max: 4
89+
passed: true
90+
comment: Canvas gate passed; title, legend, axes all well-proportioned
91+
- id: VQ-06
92+
name: Axis Labels & Title
93+
score: 2
94+
max: 2
95+
passed: true
96+
comment: Descriptive axis names with units (cm)
97+
- id: VQ-07
98+
name: Palette Compliance
99+
score: 2
100+
max: 2
101+
passed: true
102+
comment: 'First series #009E73, canonical Imprint order, identical across
103+
themes, correct chrome backgrounds'
104+
design_excellence:
105+
score: 13
106+
max: 20
107+
items:
108+
- id: DE-01
109+
name: Aesthetic Sophistication
110+
score: 5
111+
max: 8
112+
passed: false
113+
comment: Improved with borders/markArea, but still close to a well-configured
114+
default overall
115+
- id: DE-02
116+
name: Visual Refinement
117+
score: 4
118+
max: 6
119+
passed: false
120+
comment: Edge-highlighted markers and subtle markArea shading added; grid
121+
subtle, whitespace generous
122+
- id: DE-03
123+
name: Data Storytelling
124+
score: 4
125+
max: 6
126+
passed: false
127+
comment: '"Overlap zone" callout gives a clear visual hierarchy device pointing
128+
at the correlation/overlap story'
129+
spec_compliance:
130+
score: 15
131+
max: 15
132+
items:
133+
- id: SC-01
134+
name: Plot Type
135+
score: 5
136+
max: 5
137+
passed: true
138+
comment: Correct categorical scatter
139+
- id: SC-02
140+
name: Required Features
141+
score: 4
142+
max: 4
143+
passed: true
144+
comment: Distinct colors per category, legend present
145+
- id: SC-03
146+
name: Data Mapping
147+
score: 3
148+
max: 3
149+
passed: true
150+
comment: X/Y correctly mapped, full data range shown
151+
- id: SC-04
152+
name: Title & Legend
153+
score: 3
154+
max: 3
155+
passed: true
156+
comment: Title matches required format exactly; legend labels match series
157+
data_quality:
158+
score: 13
159+
max: 15
160+
items:
161+
- id: DQ-01
162+
name: Feature Coverage
163+
score: 5
164+
max: 6
165+
passed: true
166+
comment: Correlation, three groups, and genuine overlap all shown; no secondary
167+
channel (e.g. marker shape) used
168+
- id: DQ-02
169+
name: Realistic Context
170+
score: 4
171+
max: 5
172+
passed: true
173+
comment: Neutral, plausible botanical scenario
174+
- id: DQ-03
175+
name: Appropriate Scale
176+
score: 4
177+
max: 4
178+
passed: true
179+
comment: Sensible cm-scale values for leaf dimensions
180+
code_quality:
181+
score: 10
182+
max: 10
183+
items:
184+
- id: CQ-01
185+
name: KISS Structure
186+
score: 3
187+
max: 3
188+
passed: true
189+
comment: No unnecessary functions/classes
190+
- id: CQ-02
191+
name: Reproducibility
192+
score: 2
193+
max: 2
194+
passed: true
195+
comment: Fixed-seed LCG deterministic across renders
196+
- id: CQ-03
197+
name: Clean Imports
198+
score: 2
199+
max: 2
200+
passed: true
201+
comment: No imports beyond the global echarts
202+
- id: CQ-04
203+
name: Code Elegance
204+
score: 2
205+
max: 2
206+
passed: true
207+
comment: Appropriate complexity, no fake interactivity
208+
- id: CQ-05
209+
name: Output & API
210+
score: 1
211+
max: 1
212+
passed: true
213+
comment: Correct mount-node contract, animation disabled
214+
library_mastery:
215+
score: 8
216+
max: 10
217+
items:
218+
- id: LM-01
219+
name: Idiomatic Usage
220+
score: 5
221+
max: 5
222+
passed: true
223+
comment: Single setOption, correct high-level API usage
224+
- id: LM-02
225+
name: Distinctive Features
226+
score: 3
227+
max: 5
228+
passed: false
229+
comment: markArea, emphasis.focus, and a tuned tooltip formatter are used
230+
but are fairly common ECharts patterns
231+
verdict: APPROVED
232+
impl_tags:
233+
dependencies: []
234+
techniques:
235+
- annotations
236+
- hover-tooltips
237+
patterns:
238+
- data-generation
239+
dataprep: []
240+
styling:
241+
- alpha-blending
242+
- edge-highlighting

0 commit comments

Comments
 (0)