Skip to content

Commit 7b90547

Browse files
feat(chartjs): implement venn-labeled-items (#9508)
## Implementation: `venn-labeled-items` - javascript/chartjs Implements the **javascript/chartjs** version of `venn-labeled-items`. **File:** `plots/venn-labeled-items/implementations/javascript/chartjs.js` **Parent Issue:** #5364 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/28167044348)* --------- 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 cc24289 commit 7b90547

2 files changed

Lines changed: 436 additions & 0 deletions

File tree

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// anyplot.ai
2+
// venn-labeled-items: Chartgeist-Style Venn Diagram with Labeled Items
3+
// Library: chartjs 4.4.7 | JavaScript 22.23.0
4+
// Quality: 85/100 | Created: 2026-06-25
5+
6+
const t = window.ANYPLOT_TOKENS;
7+
8+
// --- Data ------------------------------------------------------------------
9+
const circles = [
10+
{ name: "Hyped Online", color: t.palette[0] }, // #009E73 — Imprint palette pos 1
11+
{ name: "Actually Useful", color: t.palette[1] }, // #C475FD — Imprint palette pos 2
12+
{ name: "Secretly Beloved", color: t.palette[2] }, // #4467A3 — Imprint palette pos 3
13+
];
14+
15+
const items = [
16+
{ label: "NFTs", zone: "A" },
17+
{ label: "Metaverse", zone: "A" },
18+
{ label: "Spreadsheets", zone: "B" },
19+
{ label: "Google Maps", zone: "B" },
20+
{ label: "RSS Feeds", zone: "C" },
21+
{ label: "Fax Machines", zone: "C" },
22+
{ label: "ChatGPT", zone: "AB" },
23+
{ label: "Slack", zone: "AB" },
24+
{ label: "TikTok", zone: "AC" },
25+
{ label: "Vinyl Records", zone: "AC" },
26+
{ label: "Wikipedia", zone: "BC" },
27+
{ label: "Duct Tape", zone: "BC" },
28+
{ label: "The Internet", zone: "ABC" },
29+
{ label: "Coffee", zone: "ABC" },
30+
];
31+
32+
// --- Mount -----------------------------------------------------------------
33+
const canvas = document.createElement("canvas");
34+
document.getElementById("container").appendChild(canvas);
35+
36+
// --- Venn drawing plugin ---------------------------------------------------
37+
const vennPlugin = {
38+
id: "vennLabeled",
39+
afterDraw(chart) {
40+
const ctx = chart.ctx;
41+
const W = chart.width;
42+
const H = chart.height;
43+
44+
// Background
45+
ctx.fillStyle = t.pageBg;
46+
ctx.fillRect(0, 0, W, H);
47+
48+
// Layout geometry
49+
const cx = W * 0.5;
50+
const cy = H * 0.50;
51+
const r = Math.min(W, H) * 0.32;
52+
const d = r * 0.58;
53+
54+
// Circle centers — equilateral-triangle arrangement
55+
const A = { x: cx - d * Math.cos(Math.PI / 6), y: cy - d * 0.55 };
56+
const B = { x: cx + d * Math.cos(Math.PI / 6), y: cy - d * 0.55 };
57+
const C = { x: cx, y: cy + d * 0.80 };
58+
const ctrs = [A, B, C];
59+
60+
// Semi-transparent circle fills
61+
for (let i = 0; i < 3; i++) {
62+
ctx.save();
63+
ctx.globalAlpha = 0.21;
64+
ctx.beginPath();
65+
ctx.arc(ctrs[i].x, ctrs[i].y, r, 0, 2 * Math.PI);
66+
ctx.fillStyle = circles[i].color;
67+
ctx.fill();
68+
ctx.restore();
69+
}
70+
71+
// Circle outlines
72+
for (let i = 0; i < 3; i++) {
73+
ctx.save();
74+
ctx.globalAlpha = 0.60;
75+
ctx.beginPath();
76+
ctx.arc(ctrs[i].x, ctrs[i].y, r, 0, 2 * Math.PI);
77+
ctx.strokeStyle = circles[i].color;
78+
ctx.lineWidth = 2.5;
79+
ctx.stroke();
80+
ctx.restore();
81+
}
82+
83+
// Zone centers — verified to lie inside the correct regions
84+
const z = {
85+
A: { x: A.x - r * 0.50, y: A.y - r * 0.20 },
86+
B: { x: B.x + r * 0.50, y: B.y - r * 0.20 },
87+
C: { x: cx, y: C.y + r * 0.40 },
88+
AB: { x: cx, y: cy - d * 1.10 },
89+
AC: { x: cx - d * 0.75, y: cy + d * 0.30 },
90+
BC: { x: cx + d * 0.75, y: cy + d * 0.30 },
91+
ABC: { x: cx, y: cy },
92+
};
93+
94+
// Category name labels (outside each circle)
95+
const catFontSize = Math.round(r * 0.13);
96+
ctx.font = `bold ${catFontSize}px Georgia, serif`;
97+
ctx.textBaseline = "middle";
98+
const catPos = [
99+
{ x: A.x - r * 0.95, y: A.y - r * 0.68, align: "center" },
100+
{ x: B.x + r * 0.95, y: B.y - r * 0.68, align: "center" },
101+
{ x: cx, y: C.y + r * 1.05, align: "center" },
102+
];
103+
for (let i = 0; i < 3; i++) {
104+
ctx.fillStyle = circles[i].color;
105+
ctx.textAlign = catPos[i].align;
106+
ctx.fillText(circles[i].name, catPos[i].x, catPos[i].y);
107+
}
108+
109+
// Group items by zone
110+
const byZone = {};
111+
for (const item of items) {
112+
(byZone[item.zone] = byZone[item.zone] || []).push(item.label);
113+
}
114+
115+
// Item labels inside each zone
116+
const itemFontSize = Math.round(r * 0.095);
117+
const lineH = itemFontSize * 1.65;
118+
ctx.font = `${itemFontSize}px Georgia, serif`;
119+
ctx.textAlign = "center";
120+
ctx.textBaseline = "middle";
121+
ctx.fillStyle = t.inkSoft;
122+
123+
for (const [zone, labels] of Object.entries(byZone)) {
124+
const base = z[zone];
125+
if (!base) continue;
126+
const startY = base.y - ((labels.length - 1) * lineH) / 2;
127+
for (let i = 0; i < labels.length; i++) {
128+
ctx.fillText(labels[i], base.x, startY + i * lineH);
129+
}
130+
}
131+
132+
// Title (scaled for length)
133+
const titleText = "Tech Zeitgeist · venn-labeled-items · javascript · chartjs · anyplot.ai";
134+
const titleSize = Math.max(12, Math.round(22 * 67 / titleText.length));
135+
ctx.font = `bold ${titleSize}px Georgia, serif`;
136+
ctx.fillStyle = t.ink;
137+
ctx.textAlign = "center";
138+
ctx.textBaseline = "top";
139+
ctx.fillText(titleText, W / 2, H * 0.022);
140+
141+
// Subtitle
142+
const subtitleSize = Math.max(10, Math.round(titleSize * 0.72));
143+
ctx.font = `italic ${subtitleSize}px Georgia, serif`;
144+
ctx.fillStyle = t.inkSoft;
145+
ctx.fillText("Where does it live — hype, utility, or quiet devotion?", W / 2, H * 0.022 + titleSize * 1.5);
146+
},
147+
};
148+
149+
// --- Chart (blank scatter used as canvas container + custom Venn plugin) ---
150+
new Chart(canvas, {
151+
type: "scatter",
152+
data: { datasets: [] },
153+
options: {
154+
responsive: true,
155+
maintainAspectRatio: false,
156+
animation: false,
157+
layout: { padding: 0 },
158+
plugins: {
159+
legend: { display: false },
160+
title: { display: false },
161+
},
162+
scales: {
163+
x: { display: false },
164+
y: { display: false },
165+
},
166+
},
167+
plugins: [vennPlugin],
168+
});

0 commit comments

Comments
 (0)