Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions plots/venn-labeled-items/implementations/javascript/d3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// anyplot.ai
// venn-labeled-items: Chartgeist-Style Venn Diagram with Labeled Items
// Library: d3 7.9.0 | JavaScript 22.23.0
// Quality: 87/100 | Created: 2026-06-25
//# anyplot-orientation: square

const t = window.ANYPLOT_TOKENS;
const { width, height } = window.ANYPLOT_SIZE;
const THEME = window.ANYPLOT_THEME || "light";
const inkMuted = THEME === "dark" ? "#A8A79F" : "#6B6A63";

// --- Data ---
const circleData = [
{ name: "Overhyped", cx: 600, cy: 480, r: 270 },
{ name: "Actually Useful", cx: 738, cy: 718, r: 270 },
{ name: "Secretly Loved", cx: 462, cy: 718, r: 270 },
];

// D3 ordinal scale maps category names to Imprint palette in canonical order
const colorScale = d3.scaleOrdinal()
.domain(circleData.map(d => d.name))
.range(t.palette.slice(0, 3));

// Centroid of each Venn zone for label placement
const ZONE_CENTROIDS = {
A: { x: 600, y: 314 },
B: { x: 878, y: 800 },
C: { x: 322, y: 800 },
AB: { x: 730, y: 566 },
AC: { x: 470, y: 566 },
BC: { x: 600, y: 840 },
ABC: { x: 600, y: 638 },
outside: { x: 975, y: 360 },
};

const items = [
{ label: "NFTs", zone: "A" },
{ label: "Metaverse", zone: "A" },
{ label: "Segway", zone: "A" },
{ label: "Google Glass", zone: "A" },
{ label: "Wikipedia", zone: "B" },
{ label: "GPS Navigation", zone: "B" },
{ label: "Cloud Backup", zone: "B" },
{ label: "Password Managers", zone: "B" },
{ label: "Spreadsheets", zone: "C" },
{ label: "Fax Machines", zone: "C" },
{ label: "Cable TV", zone: "C" },
{ label: "ChatGPT", zone: "AB" },
{ label: "Electric Scooters", zone: "AB" },
{ label: "TikTok", zone: "AC" },
{ label: "Gamification", zone: "AC" },
{ label: "Dark Mode", zone: "BC" },
{ label: "RSS Feeds", zone: "BC" },
{ label: "Sourdough", zone: "ABC" },
{ label: "Zoom", zone: "ABC" },
{ label: "Landlines", zone: "outside" },
];

// Use d3.group to partition items by zone, then compute vertical stacking positions
const SPACING = 24;
const itemsByZone = d3.group(items, d => d.zone);

itemsByZone.forEach((zoneItems, zone) => {
const { x: zx, y: zy } = ZONE_CENTROIDS[zone];
const totalH = (zoneItems.length - 1) * SPACING;
zoneItems.forEach((item, i) => {
item.x = zx;
item.y = zy - totalH / 2 + i * SPACING;
});
});

// --- SVG ---
const svg = d3.select("#container").append("svg")
.attr("width", width)
.attr("height", height);

// --- Circles (data join, semi-transparent fills, colored strokes) ---
svg.selectAll("circle.venn-circle").data(circleData).join("circle")
.attr("class", "venn-circle")
.attr("cx", d => d.cx).attr("cy", d => d.cy).attr("r", d => d.r)
.attr("fill", d => colorScale(d.name)).attr("fill-opacity", 0.12)
.attr("stroke", d => colorScale(d.name))
.attr("stroke-width", 2.5).attr("stroke-opacity", 0.65);

// --- Category labels (data join per circle, editorial serif) ---
const catFont = "Georgia, 'Times New Roman', serif";

const catLabelDefs = [
{ name: "Overhyped", lines: ["Overhyped"], x: circleData[0].cx, y: circleData[0].cy - circleData[0].r - 20, anchor: "middle" },
{ name: "Actually Useful", lines: ["Actually", "Useful"], x: circleData[1].cx + circleData[1].r + 26, y: circleData[1].cy - 13, anchor: "start" },
{ name: "Secretly Loved", lines: ["Secretly", "Loved"], x: circleData[2].cx - circleData[2].r - 26, y: circleData[2].cy - 13, anchor: "end" },
];

catLabelDefs.forEach(def => {
const catG = svg.append("g");
catG.selectAll("text").data(def.lines).join("text")
.attr("x", def.x)
.attr("y", (_, i) => def.y + i * 28)
.attr("text-anchor", def.anchor)
.attr("fill", colorScale(def.name))
.style("font-size", "20px").style("font-weight", "700")
.style("font-family", catFont)
.text(d => d);
});

// --- "outside all circles" annotation ---
const outsideItems = items.filter(d => d.zone === "outside");
if (outsideItems.length) {
svg.append("text")
.attr("x", ZONE_CENTROIDS.outside.x)
.attr("y", d3.min(outsideItems, d => d.y) - 22)
.attr("text-anchor", "middle").attr("fill", inkMuted)
.style("font-size", "14px").style("font-style", "italic")
.text("outside all circles");
}

// --- Items (data join over all items, positions from d3.group stacking) ---
svg.selectAll("text.venn-item").data(items).join("text")
.attr("class", "venn-item")
.attr("x", d => d.x)
.attr("y", d => d.y)
.attr("text-anchor", "middle")
.attr("dominant-baseline", "middle")
.attr("fill", d => d.zone === "outside" ? inkMuted : t.inkSoft)
.style("font-size", "14px")
.style("font-style", d => d.zone === "outside" ? "italic" : "normal")
.style("font-family", "system-ui, -apple-system, sans-serif")
.text(d => d.label);

// --- Title ---
svg.append("text")
.attr("x", width / 2).attr("y", 46)
.attr("text-anchor", "middle").attr("fill", t.ink)
.style("font-size", "22px").style("font-weight", "600")
.text("venn-labeled-items · javascript · d3 · anyplot.ai");
252 changes: 252 additions & 0 deletions plots/venn-labeled-items/metadata/javascript/d3.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
library: d3
language: javascript
specification_id: venn-labeled-items
created: '2026-06-25T11:44:36Z'
updated: '2026-06-25T12:15:23Z'
generated_by: claude-sonnet
workflow_run: 28167148901
issue: 5364
language_version: 22.23.0
library_version: 7.9.0
preview_url_light: https://storage.googleapis.com/anyplot-images/plots/venn-labeled-items/javascript/d3/plot-light.png
preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/venn-labeled-items/javascript/d3/plot-dark.png
preview_html_light: https://storage.googleapis.com/anyplot-images/plots/venn-labeled-items/javascript/d3/plot-light.html
preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/venn-labeled-items/javascript/d3/plot-dark.html
quality_score: 87
review:
strengths:
- 'Perfect spec compliance: all seven zones plus outside populated, semi-transparent
fills, category labels outside circles in their correct colors'
- 'Excellent data quality: real witty editorial items with believable zone assignments
that fit the WIRED Chartgeist aesthetic'
- Clean idiomatic D3 using d3.group data joins and ANYPLOT_TOKENS/ANYPLOT_SIZE correctly
- 'Full theme adaptation: both light and dark renders pass readability with no dark-on-dark
failures'
- Serif category labels (Georgia) reinforce the editorial magazine aesthetic called
for in the spec
weaknesses:
- Item labels at 14px are at the lower end for a 2400 px canvas — bumping to 15-16px
would improve mobile readability without crowding zones
- 'No visual hierarchy among items: all render identically (same color, size) —
consider slightly larger or bolder text for ABC intersection items (Sourdough,
Zoom) to make the triple-overlap punchline pop'
- Optional editorial subtitle absent — adding a light subtitle like 'A Cultural
Status Report' in smaller italic serif would elevate the editorial feel
- Zone centroids for outside items (x=975) sit close to the right canvas edge; a
small left-shift would provide more breathing room
image_description: |-
Light render (plot-light.png):
Background: Warm off-white (#FAF8F1) — correct theme surface
Chrome: Title "venn-labeled-items · javascript · d3 · anyplot.ai" at top in dark ink (22px, weight 600) — fully readable. Category labels in bold Georgia serif in their respective palette colors (green/lavender/blue) — clearly visible. No axis labels or tick labels (not applicable for Venn).
Data: Three overlapping circles — green (#009E73) for Overhyped, lavender (#C475FD) for Actually Useful, blue (#4467A3) for Secretly Loved. 12% opacity fills show overlapping regions. Item labels in 14px sans-serif using inkSoft token. Outside items in muted italic.
Legibility verdict: PASS — all text readable against light background; no light-on-light issues

Dark render (plot-dark.png):
Background: Warm near-black (#1A1A17) — correct theme surface
Chrome: Title and item labels switch to light-colored text (inkSoft token — light gray) — clearly readable against the dark surface. Category labels remain in the same palette colors (green/lavender/blue), all readable against dark background. No dark-on-dark failures detected.
Data: Data colors are identical to the light render — same green, lavender, and blue circle strokes and fills. The 12% opacity fills create subtle pools against the near-black background, maintaining overlap visibility.
Legibility verdict: PASS — all text readable; chrome correctly adapts to dark theme
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, category labels 20px
bold, items 14px). Readable in both themes. Minor: item labels at 14px are
at the lower bound for 2400px canvas.'
- id: VQ-02
name: No Overlap
score: 6
max: 6
passed: true
comment: Vertical stacking with 24px spacing prevents any text collision across
all seven zones plus outside.
- id: VQ-03
name: Element Visibility
score: 6
max: 6
passed: true
comment: Circle strokes clearly delineate rings; 12% fills appropriately transparent;
text-only items suit the editorial spec.
- id: VQ-04
name: Color Accessibility
score: 2
max: 2
passed: true
comment: Imprint palette is CVD-safe; semi-transparent fills let background
show through.
- id: VQ-05
name: Layout & Canvas
score: 3
max: 4
passed: true
comment: 'Well-centered diagram filling ~70% of canvas. Minor: upper-right
outside zone leaves some empty space.'
- id: VQ-06
name: Axis Labels & Title
score: 2
max: 2
passed: true
comment: No axes (correct for Venn); title in required format; category names
as clear zone labels.
- id: VQ-07
name: Palette Compliance
score: 2
max: 2
passed: true
comment: 'First circle #009E73; positions 2-3 canonical Imprint order; background
#FAF8F1 light / #1A1A17 dark; chrome adapts correctly.'
design_excellence:
score: 12
max: 20
items:
- id: DE-01
name: Aesthetic Sophistication
score: 5
max: 8
passed: true
comment: 'Intentional editorial choices: bold Georgia serif for category labels,
italic for outside items, color-matched labels. Above defaults but not publication-ready.'
- id: DE-02
name: Visual Refinement
score: 4
max: 6
passed: true
comment: Gridless, no spines, generous whitespace, semi-transparent fills
with matching strokes. Good refinement.
- id: DE-03
name: Data Storytelling
score: 3
max: 6
passed: false
comment: Good editorial item choices but no visual hierarchy — all items render
identically, missing opportunity to emphasize the ABC triple-overlap punchline.
spec_compliance:
score: 15
max: 15
items:
- id: SC-01
name: Plot Type
score: 5
max: 5
passed: true
comment: Correct three-circle Venn with symmetric layout, equally-sized circles,
clear overlaps.
- id: SC-02
name: Required Features
score: 4
max: 4
passed: true
comment: Semi-transparent fills, category names outside circles, items as
labeled text, all 7 zones + outside populated, gridless.
- id: SC-03
name: Data Mapping
score: 3
max: 3
passed: true
comment: All items placed in correct zones; outside items separated from circles.
- id: SC-04
name: Title & Legend
score: 3
max: 3
passed: true
comment: Title exactly 'venn-labeled-items · javascript · d3 · anyplot.ai'.
No series legend needed; category labels fulfill the role.
data_quality:
score: 15
max: 15
items:
- id: DQ-01
name: Feature Coverage
score: 6
max: 6
passed: true
comment: All 7 zones plus outside populated; good distribution across zone
types.
- id: DQ-02
name: Realistic Context
score: 5
max: 5
passed: true
comment: Real pop-culture items with believable editorial judgment; matches
WIRED Chartgeist aesthetic.
- id: DQ-03
name: Appropriate Scale
score: 4
max: 4
passed: true
comment: Zone assignments are plausible and witty; NFTs/Metaverse as Overhyped,
GPS/Wikipedia as Actually Useful, Sourdough in all three.
code_quality:
score: 10
max: 10
items:
- id: CQ-01
name: KISS Structure
score: 3
max: 3
passed: true
comment: Data → SVG → circles → category labels → outside annotation → items
→ title. No functions or classes.
- id: CQ-02
name: Reproducibility
score: 2
max: 2
passed: true
comment: All data hardcoded; fully deterministic.
- id: CQ-03
name: Clean Imports
score: 2
max: 2
passed: true
comment: d3 is the single global; no unused imports.
- id: CQ-04
name: Code Elegance
score: 2
max: 2
passed: true
comment: Uses d3.group for zone stacking, data joins throughout; clean and
idiomatic.
- id: CQ-05
name: Output & API
score: 1
max: 1
passed: true
comment: D3 interactive library; harness emits plot-{theme}.png + plot-{theme}.html
correctly.
library_mastery:
score: 7
max: 10
items:
- id: LM-01
name: Idiomatic Usage
score: 4
max: 5
passed: true
comment: Good use of d3.group (D3 v6+ API), d3.scaleOrdinal, .data().join()
data-join, d3.min, correct ANYPLOT_TOKENS/ANYPLOT_SIZE usage.
- id: LM-02
name: Distinctive Features
score: 3
max: 5
passed: true
comment: d3.group for grouping/stacking is D3-idiomatic, but Venn geometry
is entirely manually positioned rather than algorithmically derived using
D3 math utilities.
verdict: APPROVED
impl_tags:
dependencies: []
techniques:
- annotations
patterns:
- data-generation
- iteration-over-groups
dataprep: []
styling:
- alpha-blending
- minimal-chrome
Loading