Skip to content

Commit 08d7118

Browse files
feat(chartjs): implement errorbar-basic (#9526)
## Implementation: `errorbar-basic` - javascript/chartjs Implements the **javascript/chartjs** version of `errorbar-basic`. **File:** `plots/errorbar-basic/implementations/javascript/chartjs.js` **Parent Issue:** #973 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/28474085811)* --------- 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 4151df1 commit 08d7118

2 files changed

Lines changed: 401 additions & 0 deletions

File tree

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// anyplot.ai
2+
// errorbar-basic: Basic Error Bar Plot
3+
// Library: chartjs 4.4.7 | JavaScript 22.23.0
4+
// Quality: 90/100 | Created: 2026-06-30
5+
6+
const t = window.ANYPLOT_TOKENS;
7+
8+
// Temperature × plant growth study: mean stem length (cm) ± 1 SD across 5 conditions
9+
const labels = ["15 °C", "20 °C", "25 °C", "30 °C", "35 °C"];
10+
const means = [12.3, 18.7, 24.2, 21.5, 16.1];
11+
const errors = [2.1, 1.8, 2.3, 2.9, 3.4];
12+
13+
// Peak at 25°C — full opacity to draw the eye to the inverted-U apex
14+
const peakIdx = 2;
15+
16+
// Plugin: fill canvas with theme background before anything is drawn
17+
const bgPlugin = {
18+
id: "bg",
19+
beforeDraw(chart) {
20+
const { ctx } = chart;
21+
ctx.save();
22+
ctx.fillStyle = t.pageBg;
23+
ctx.fillRect(0, 0, chart.width, chart.height);
24+
ctx.restore();
25+
},
26+
};
27+
28+
// Plugin: draw L-shaped frame (left + bottom spines only) — Chart.js border.display is
29+
// disabled on both scales so this replaces the default 4-sided box with a clean L-shape
30+
const spinePlugin = {
31+
id: "spine",
32+
afterDraw(chart) {
33+
const { ctx, chartArea } = chart;
34+
ctx.save();
35+
ctx.strokeStyle = t.ink;
36+
ctx.lineWidth = 1.5;
37+
ctx.beginPath();
38+
ctx.moveTo(chartArea.left, chartArea.top);
39+
ctx.lineTo(chartArea.left, chartArea.bottom);
40+
ctx.lineTo(chartArea.right, chartArea.bottom);
41+
ctx.stroke();
42+
ctx.restore();
43+
},
44+
};
45+
46+
// Plugin: I-bar error whiskers drawn over each bar after bars are rendered
47+
const errorBarPlugin = {
48+
id: "errorBar",
49+
afterDatasetsDraw(chart) {
50+
const { ctx, scales } = chart;
51+
const meta = chart.getDatasetMeta(0);
52+
53+
ctx.save();
54+
ctx.strokeStyle = t.ink;
55+
ctx.lineWidth = 2.5;
56+
ctx.lineCap = "square";
57+
58+
meta.data.forEach((bar, i) => {
59+
const x = bar.x;
60+
const CAP = bar.width * 0.35; // cap width proportional to bar width
61+
const yHi = scales.y.getPixelForValue(means[i] + errors[i]);
62+
const yLo = scales.y.getPixelForValue(means[i] - errors[i]);
63+
64+
// Vertical stem
65+
ctx.beginPath();
66+
ctx.moveTo(x, yHi);
67+
ctx.lineTo(x, yLo);
68+
ctx.stroke();
69+
70+
// Top cap
71+
ctx.beginPath();
72+
ctx.moveTo(x - CAP, yHi);
73+
ctx.lineTo(x + CAP, yHi);
74+
ctx.stroke();
75+
76+
// Bottom cap
77+
ctx.beginPath();
78+
ctx.moveTo(x - CAP, yLo);
79+
ctx.lineTo(x + CAP, yLo);
80+
ctx.stroke();
81+
});
82+
83+
ctx.restore();
84+
},
85+
};
86+
87+
// Mount
88+
const canvas = document.createElement("canvas");
89+
document.getElementById("container").appendChild(canvas);
90+
91+
// Chart
92+
new Chart(canvas, {
93+
type: "bar",
94+
plugins: [bgPlugin, spinePlugin, errorBarPlugin],
95+
data: {
96+
labels,
97+
datasets: [
98+
{
99+
label: "Mean stem length (cm)",
100+
data: means,
101+
// Peak bar (25°C) at full opacity; remaining bars at 60% to guide attention
102+
backgroundColor: means.map((_, i) => i === peakIdx ? t.palette[0] : t.palette[0] + "99"),
103+
borderColor: t.palette[0],
104+
borderWidth: means.map((_, i) => i === peakIdx ? 3 : 1),
105+
borderRadius: 3,
106+
barPercentage: 0.55,
107+
categoryPercentage: 0.85,
108+
},
109+
],
110+
},
111+
options: {
112+
responsive: true,
113+
maintainAspectRatio: false,
114+
animation: false,
115+
layout: { padding: { top: 10, right: 24, bottom: 10, left: 10 } },
116+
plugins: {
117+
title: {
118+
display: true,
119+
text: "errorbar-basic · javascript · chartjs · anyplot.ai",
120+
color: t.ink,
121+
font: { size: 22, weight: "500" },
122+
padding: { top: 12, bottom: 18 },
123+
},
124+
legend: {
125+
labels: { color: t.inkSoft, font: { size: 16 }, boxWidth: 14 },
126+
},
127+
},
128+
scales: {
129+
x: {
130+
border: { display: false }, // spinePlugin draws the L-frame instead
131+
ticks: { color: t.inkSoft, font: { size: 14 } },
132+
grid: { display: false }, // Y-axis only grid for bar charts
133+
title: {
134+
display: true,
135+
text: "Growth Temperature",
136+
color: t.ink,
137+
font: { size: 15 },
138+
},
139+
},
140+
y: {
141+
border: { display: false }, // spinePlugin draws the L-frame instead
142+
min: 0,
143+
suggestedMax: 30,
144+
ticks: { color: t.inkSoft, font: { size: 14 } },
145+
grid: { color: t.grid },
146+
title: {
147+
display: true,
148+
text: "Stem Length (cm)",
149+
color: t.ink,
150+
font: { size: 15 },
151+
},
152+
},
153+
},
154+
},
155+
});

0 commit comments

Comments
 (0)