Skip to content

Commit e650742

Browse files
feat(d3): implement errorbar-basic (#9529)
## Implementation: `errorbar-basic` - javascript/d3 Implements the **javascript/d3** version of `errorbar-basic`. **File:** `plots/errorbar-basic/implementations/javascript/d3.js` **Parent Issue:** #973 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/28474198351)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com>
1 parent 08d7118 commit e650742

2 files changed

Lines changed: 422 additions & 0 deletions

File tree

  • plots/errorbar-basic
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// anyplot.ai
2+
// errorbar-basic: Basic Error Bar Plot
3+
// Library: d3 7.9.0 | JavaScript 22.23.1
4+
// Quality: 86/100 | Created: 2026-06-30
5+
6+
//# anyplot-orientation: landscape
7+
8+
const t = window.ANYPLOT_TOKENS;
9+
const { width, height } = window.ANYPLOT_SIZE;
10+
11+
const margin = { top: 85, right: 55, bottom: 95, left: 105 };
12+
const iw = width - margin.left - margin.right;
13+
const ih = height - margin.top - margin.bottom;
14+
15+
// Crop yield (t/ha) by fertilizer treatment — field trial, n=20 plots per group
16+
const data = [
17+
{ treatment: "Control", mean: 3.2, sd: 0.38 },
18+
{ treatment: "Nitrogen", mean: 4.8, sd: 0.52 },
19+
{ treatment: "Phosphorus", mean: 4.1, sd: 0.45 },
20+
{ treatment: "Potassium", mean: 3.9, sd: 0.41 },
21+
{ treatment: "NPK", mean: 5.7, sd: 0.61 },
22+
{ treatment: "Organic", mean: 4.4, sd: 0.49 },
23+
];
24+
25+
// SVG mount
26+
const svg = d3.select("#container")
27+
.append("svg")
28+
.attr("width", width)
29+
.attr("height", height);
30+
const g = svg.append("g").attr("transform", `translate(${margin.left},${margin.top})`);
31+
32+
// Ordinal color scale — each treatment gets a distinct Imprint palette color
33+
const color = d3.scaleOrdinal()
34+
.domain(data.map((d) => d.treatment))
35+
.range(t.palette);
36+
37+
// Scales
38+
const x = d3.scaleBand()
39+
.domain(data.map((d) => d.treatment))
40+
.range([0, iw])
41+
.padding(0.35);
42+
43+
const yMin = d3.min(data, (d) => d.mean - d.sd) - 0.5;
44+
const y = d3.scaleLinear()
45+
.domain([yMin, d3.max(data, (d) => d.mean + d.sd) + 0.4])
46+
.nice()
47+
.range([ih, 0]);
48+
49+
// Helper: horizontal center of each band
50+
const cx = (d) => x(d.treatment) + x.bandwidth() / 2;
51+
const capHalf = x.bandwidth() * 0.22;
52+
53+
// Subtle column highlight behind the top-performing NPK treatment
54+
const npk = data.find((d) => d.treatment === "NPK");
55+
g.append("rect")
56+
.attr("x", x("NPK") - 8)
57+
.attr("y", 0)
58+
.attr("width", x.bandwidth() + 16)
59+
.attr("height", ih)
60+
.attr("fill", color("NPK"))
61+
.attr("opacity", 0.07)
62+
.attr("rx", 4);
63+
64+
// Horizontal gridlines (y-axis only)
65+
const gridG = g.append("g").attr("class", "grid");
66+
gridG.call(d3.axisLeft(y).tickSize(-iw).tickFormat("").ticks(7));
67+
gridG.selectAll("line").attr("stroke", t.grid).attr("stroke-width", 1);
68+
gridG.select(".domain").remove();
69+
70+
// Axes
71+
const xAxis = g.append("g")
72+
.attr("transform", `translate(0,${ih})`)
73+
.call(d3.axisBottom(x).tickSizeOuter(0));
74+
75+
const yAxis = g.append("g")
76+
.call(d3.axisLeft(y).ticks(7).tickFormat((d) => d.toFixed(1)).tickSizeOuter(0));
77+
78+
for (const ax of [xAxis, yAxis]) {
79+
ax.selectAll("text").attr("fill", t.inkSoft).style("font-size", "14px");
80+
ax.selectAll("line").attr("stroke", t.grid);
81+
ax.select(".domain").attr("stroke", t.inkSoft);
82+
}
83+
84+
// Vertical error bar stems
85+
g.selectAll(".errbar").data(data).join("line")
86+
.attr("class", "errbar")
87+
.attr("x1", cx).attr("x2", cx)
88+
.attr("y1", (d) => y(d.mean - d.sd))
89+
.attr("y2", (d) => y(d.mean + d.sd))
90+
.attr("stroke", (d) => color(d.treatment))
91+
.attr("stroke-width", 2.5);
92+
93+
// Upper caps
94+
g.selectAll(".cap-top").data(data).join("line")
95+
.attr("class", "cap-top")
96+
.attr("x1", (d) => cx(d) - capHalf).attr("x2", (d) => cx(d) + capHalf)
97+
.attr("y1", (d) => y(d.mean + d.sd)).attr("y2", (d) => y(d.mean + d.sd))
98+
.attr("stroke", (d) => color(d.treatment)).attr("stroke-width", 2.5);
99+
100+
// Lower caps
101+
g.selectAll(".cap-bot").data(data).join("line")
102+
.attr("class", "cap-bot")
103+
.attr("x1", (d) => cx(d) - capHalf).attr("x2", (d) => cx(d) + capHalf)
104+
.attr("y1", (d) => y(d.mean - d.sd)).attr("y2", (d) => y(d.mean - d.sd))
105+
.attr("stroke", (d) => color(d.treatment)).attr("stroke-width", 2.5);
106+
107+
// Mean value circles
108+
g.selectAll("circle").data(data).join("circle")
109+
.attr("cx", cx)
110+
.attr("cy", (d) => y(d.mean))
111+
.attr("r", 9)
112+
.attr("fill", (d) => color(d.treatment))
113+
.attr("stroke", t.pageBg)
114+
.attr("stroke-width", 2.5);
115+
116+
// Mean value labels — shown below each circle for quick reading
117+
g.selectAll(".mean-val").data(data).join("text")
118+
.attr("class", "mean-val")
119+
.attr("x", cx)
120+
.attr("y", (d) => y(d.mean) + 26)
121+
.attr("text-anchor", "middle")
122+
.attr("fill", (d) => color(d.treatment))
123+
.style("font-size", "13px")
124+
.style("font-weight", "500")
125+
.text((d) => d.mean.toFixed(1));
126+
127+
// NPK best-performer annotation
128+
g.append("text")
129+
.attr("x", cx(npk))
130+
.attr("y", y(npk.mean + npk.sd) - 16)
131+
.attr("text-anchor", "middle")
132+
.attr("fill", color("NPK"))
133+
.style("font-size", "13px")
134+
.style("font-weight", "700")
135+
.text("★ Top yield");
136+
137+
// Axis labels
138+
g.append("text")
139+
.attr("x", iw / 2)
140+
.attr("y", ih + 68)
141+
.attr("text-anchor", "middle")
142+
.attr("fill", t.ink)
143+
.style("font-size", "16px")
144+
.text("Fertilizer Treatment");
145+
146+
g.append("text")
147+
.attr("transform", "rotate(-90)")
148+
.attr("x", -ih / 2)
149+
.attr("y", -75)
150+
.attr("text-anchor", "middle")
151+
.attr("fill", t.ink)
152+
.style("font-size", "16px")
153+
.text("Crop Yield (t/ha)");
154+
155+
// Title
156+
svg.append("text")
157+
.attr("x", width / 2)
158+
.attr("y", 52)
159+
.attr("text-anchor", "middle")
160+
.attr("fill", t.ink)
161+
.style("font-size", "22px")
162+
.style("font-weight", "600")
163+
.text("errorbar-basic · javascript · d3 · anyplot.ai");

0 commit comments

Comments
 (0)