diff --git a/plots/dumbbell-basic/implementations/javascript/d3.js b/plots/dumbbell-basic/implementations/javascript/d3.js new file mode 100644 index 0000000000..2768955fa4 --- /dev/null +++ b/plots/dumbbell-basic/implementations/javascript/d3.js @@ -0,0 +1,196 @@ +// anyplot.ai +// dumbbell-basic: Basic Dumbbell Chart +// Library: d3 7.9.0 | JavaScript 22.23.1 +// Quality: 91/100 | Created: 2026-06-30 + +const t = window.ANYPLOT_TOKENS; +const { width, height } = window.ANYPLOT_SIZE; + +const margin = { top: 95, right: 130, bottom: 90, left: 175 }; +const iw = width - margin.left - margin.right; +const ih = height - margin.top - margin.bottom; + +// Employee satisfaction scores (0–100) before and after flexible work policy +const rawData = [ + { dept: "Research", before: 57, after: 79 }, + { dept: "Engineering", before: 72, after: 91 }, + { dept: "Product", before: 63, after: 81 }, + { dept: "IT Support", before: 65, after: 82 }, + { dept: "Design", before: 68, after: 84 }, + { dept: "Operations", before: 71, after: 83 }, + { dept: "Marketing", before: 60, after: 71 }, + { dept: "Customer Success", before: 66, after: 76 }, + { dept: "HR", before: 75, after: 83 }, + { dept: "Finance", before: 62, after: 69 }, +]; + +// Sort by improvement (largest gap at top) +const data = rawData.slice().sort((a, b) => (b.after - b.before) - (a.after - a.before)); +data.forEach(d => { d.delta = d.after - d.before; }); + +const svg = d3.select("#container").append("svg") + .attr("width", width) + .attr("height", height); + +// --- D3 SVG defs: linear gradient for connecting lines (green → lavender) --- +// Defined in SVG user-space so position tracks the data scale exactly +const defs = svg.append("defs"); +const grad = defs.append("linearGradient") + .attr("id", "line-grad") + .attr("gradientUnits", "userSpaceOnUse") + .attr("x1", margin.left) + .attr("x2", margin.left + iw) + .attr("y1", 0) + .attr("y2", 0); +grad.append("stop").attr("offset", "0%") + .attr("stop-color", t.palette[0]).attr("stop-opacity", 0.65); +grad.append("stop").attr("offset", "100%") + .attr("stop-color", t.palette[1]).attr("stop-opacity", 0.65); + +const g = svg.append("g").attr("transform", `translate(${margin.left},${margin.top})`); + +// Scales +const x = d3.scaleLinear().domain([50, 100]).range([0, iw]); +const y = d3.scaleBand() + .domain(data.map(d => d.dept)) + .range([0, ih]) + .padding(0.4); + +// Vertical gridlines +g.selectAll(".vgrid") + .data(x.ticks(6)) + .join("line") + .attr("x1", d => x(d)) + .attr("x2", d => x(d)) + .attr("y1", 0) + .attr("y2", ih) + .attr("stroke", t.grid) + .attr("stroke-width", 1); + +// X-axis — d3.format("d") produces clean integers with no decimal noise +const xAxisG = g.append("g") + .attr("transform", `translate(0,${ih})`) + .call(d3.axisBottom(x).ticks(6).tickSize(0).tickFormat(d3.format("d"))); + +xAxisG.select(".domain").attr("stroke", t.inkSoft); +xAxisG.selectAll(".tick text") + .attr("fill", t.inkSoft) + .style("font-size", "14px") + .attr("dy", "1.3em"); +xAxisG.selectAll(".tick line").remove(); + +// Y-axis (no domain line — categories self-label) +const yAxisG = g.append("g") + .call(d3.axisLeft(y).tickSize(0)); + +yAxisG.select(".domain").remove(); +yAxisG.selectAll(".tick text") + .attr("fill", t.inkSoft) + .style("font-size", "14px") + .attr("dx", "-0.6em"); + +// Connecting lines — gradient stroke encodes before→after direction +g.selectAll(".dumbbell-line") + .data(data) + .join("line") + .attr("x1", d => x(d.before)) + .attr("x2", d => x(d.after)) + .attr("y1", d => y(d.dept) + y.bandwidth() / 2) + .attr("y2", d => y(d.dept) + y.bandwidth() / 2) + .attr("stroke", "url(#line-grad)") + .attr("stroke-width", 3); + +// Before dots (Imprint palette[0] — brand green) +g.selectAll(".dot-before") + .data(data) + .join("circle") + .attr("cx", d => x(d.before)) + .attr("cy", d => y(d.dept) + y.bandwidth() / 2) + .attr("r", 10) + .attr("fill", t.palette[0]) + .attr("stroke", t.pageBg) + .attr("stroke-width", 2); + +// After dots (Imprint palette[1] — lavender) +g.selectAll(".dot-after") + .data(data) + .join("circle") + .attr("cx", d => x(d.after)) + .attr("cy", d => y(d.dept) + y.bandwidth() / 2) + .attr("r", 10) + .attr("fill", t.palette[1]) + .attr("stroke", t.pageBg) + .attr("stroke-width", 2); + +// Delta labels — d3.format("+d") shows signed improvement for each row +const deltaFmt = d3.format("+d"); +g.selectAll(".delta-label") + .data(data) + .join("text") + .attr("x", iw + 14) + .attr("y", d => y(d.dept) + y.bandwidth() / 2 + 5) + .attr("fill", (d, i) => i < 2 ? t.palette[1] : t.inkSoft) + .style("font-size", "14px") + .style("font-weight", (d, i) => i < 2 ? "700" : "400") + .text(d => deltaFmt(d.delta) + " pts"); + +// Callout annotations for top 2 improvements — placed in the gap below each band +const annLabels = ["▲ Highest gain", "▲ Runner-up"]; +data.slice(0, 2).forEach((d, i) => { + const xMid = (x(d.before) + x(d.after)) / 2; + const yAnn = y(d.dept) + y.bandwidth() + 6; + g.append("text") + .attr("x", xMid) + .attr("y", yAnn + 10) + .attr("text-anchor", "middle") + .attr("fill", t.amber) + .style("font-size", "12px") + .style("font-weight", "600") + .text(annLabels[i]); +}); + +// X-axis label +g.append("text") + .attr("x", iw / 2) + .attr("y", ih + 62) + .attr("text-anchor", "middle") + .attr("fill", t.inkSoft) + .style("font-size", "14px") + .text("Satisfaction Score (0–100)"); + +// Legend (horizontal, centered below title in top margin) +const legendItems = [ + { label: "Before Policy", color: t.palette[0] }, + { label: "After Policy", color: t.palette[1] }, +]; +const legendSpacing = 170; +const legendStartX = (width - legendItems.length * legendSpacing) / 2; +const legendY = 72; + +legendItems.forEach((item, i) => { + const lx = legendStartX + i * legendSpacing; + svg.append("circle") + .attr("cx", lx + 10).attr("cy", legendY) + .attr("r", 10) + .attr("fill", item.color) + .attr("stroke", t.pageBg) + .attr("stroke-width", 1.5); + svg.append("text") + .attr("x", lx + 26).attr("y", legendY + 5) + .attr("fill", t.inkSoft) + .style("font-size", "13px") + .text(item.label); +}); + +// Title — font-size scaled for long descriptive title +const title = "Employee Satisfaction · dumbbell-basic · javascript · d3 · anyplot.ai"; +const titleFontSize = title.length > 67 ? Math.round(22 * 67 / title.length) : 22; + +svg.append("text") + .attr("x", width / 2) + .attr("y", 42) + .attr("text-anchor", "middle") + .attr("fill", t.ink) + .style("font-size", `${titleFontSize}px`) + .style("font-weight", "600") + .text(title); diff --git a/plots/dumbbell-basic/metadata/javascript/d3.yaml b/plots/dumbbell-basic/metadata/javascript/d3.yaml new file mode 100644 index 0000000000..0bdf5b9d88 --- /dev/null +++ b/plots/dumbbell-basic/metadata/javascript/d3.yaml @@ -0,0 +1,260 @@ +library: d3 +language: javascript +specification_id: dumbbell-basic +created: '2026-06-30T23:07:35Z' +updated: '2026-06-30T23:28:23Z' +generated_by: claude-sonnet +workflow_run: 28481348050 +issue: 945 +language_version: 22.23.1 +library_version: 7.9.0 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/dumbbell-basic/javascript/d3/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/dumbbell-basic/javascript/d3/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/dumbbell-basic/javascript/d3/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/dumbbell-basic/javascript/d3/plot-dark.html +quality_score: 91 +review: + strengths: + - SVG linearGradient with gradientUnits='userSpaceOnUse' creates a gradient connecting + line that tracks the data scale precisely — a genuinely D3/SVG-distinctive technique + - 'Excellent storytelling: sorted by delta, top-2 gains highlighted with bold lavender + labels + amber callout annotations create immediate visual hierarchy' + - 'Full theme compliance: pageBg strokes on dots, all chrome tokens (ink, inkSoft, + grid, amber) correctly applied, data colors identical across both renders' + - 'Clean minimal design: y-axis domain removed, tick marks removed (tickSize(0)), + vertical-only subtle grid, generous margins' + - 'Dynamic title font scaling (title.length > 67 ? Math.round(22 * 67 / title.length) + : 22) correctly handles the long descriptive title without overflow' + - d3.format('+d') for signed delta labels and delta labels on the right margin provide + precise numerical storytelling without cluttering the main chart area + weaknesses: + - Callout annotation text is 12px, slightly below the 14px standard for consistency + at this canvas size — increasing to 13-14px would align it with tick/label sizing + - Gradient connecting line opacity is 0.65 — increasing to 0.80 would give slightly + better contrast on dark backgrounds while still looking subtle + - 'DE-03: The ''Runner-up'' amber annotation placement just below the Engineering + row''s dumbbell can visually crowd the gap between Engineering and Product rows + — a small dy offset or reduced font to 11px could help spacing clarity' + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) — correct, clearly not pure white + Chrome: Bold centered title "Employee Satisfaction · dumbbell-basic · javascript · d3 · anyplot.ai" in dark ink, readable. Y-axis category labels (Research, Engineering, Product, IT Support, Design, Operations, Marketing, Customer Success, HR, Finance) in inkSoft dark text, clearly readable. X-axis tick labels (50, 60, 70, 80, 90, 100) and "Satisfaction Score (0–100)" label in inkSoft, clearly readable. Legend "Before Policy" (green dot) and "After Policy" (lavender dot) centered below title. Delta labels on right in inkSoft, top-2 in bold lavender. + Data: Green (#009E73) dots for "before" values, lavender (#C475FD) dots for "after" values, gradient connecting lines (green→lavender at 0.65 opacity). Dots at r=10 are prominent. Amber (#DDCC77) callout annotations "▲ Highest gain" and "▲ Runner-up" visible below top 2 rows. + Legibility verdict: PASS — all text clearly readable against #FAF8F1 background + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17) — correct, clearly not pure black + Chrome: Title in light ink (#F0EFE8), clearly readable. Y-axis category labels in light inkSoft (#B8B7B0), clearly readable — no dark-on-dark issues. X-axis tick labels and axis label in light inkSoft, clearly readable. Legend text in light inkSoft, readable. Delta labels in light inkSoft; top-2 in bold lavender (high contrast on dark). Amber annotations clearly visible. + Data: Identical green (#009E73) and lavender (#C475FD) dots — colors unchanged from light render as required. Gradient connecting lines still visible. Dots with pageBg stroke (now near-black) still render with definition. + Legibility verdict: PASS — all text clearly readable against #1A1A17 background; no dark-on-dark failures detected + criteria_checklist: + visual_quality: + score: 29 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 7 + max: 8 + passed: true + comment: All font sizes explicitly set; tick/label/title all well-proportioned. + Callout annotations at 12px slightly below the 14px standard but still readable. + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No text overlaps. Delta labels on right margin, annotations in inter-row + gaps, legend above plot area — all clear. + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: 10 data points (sparse), dots at r=10 are prominent, connecting lines + at 3px stroke width. Perfectly sized for data density. + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Imprint palette (green + lavender), CVD-safe, pageBg stroke on dots + adds definition. Amber annotations use semantic anchor correctly. + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: Canvas gate passed. Generous margins (top:95 right:130 bottom:90 + left:175). Nothing cut off. Plot fills canvas well. + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: '''Satisfaction Score (0–100)'' with units. Y-axis has self-labeling + categories — no Y label needed. Title descriptive with optional prefix.' + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'First series = palette[0] #009E73, second = palette[1] #C475FD. + Backgrounds #FAF8F1/#1A1A17 correct. Amber for annotations. Data colors + identical across themes.' + design_excellence: + score: 14 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 6 + max: 8 + passed: true + comment: 'Strong design: SVG linearGradient with userSpaceOnUse encodes directionality + in the connecting line, top-2 highlight with bold lavender labels creates + intentional hierarchy. Clearly above defaults.' + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: Y-axis domain removed, tick marks removed (tickSize(0)), vertical-only + subtle grid at t.grid stroke, generous whitespace, legend without box frame. + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: Sorted by delta reveals the ranking immediately. Top-2 bold labels + + amber annotations guide the viewer. Gradient line encodes before→after + direction. Good visual hierarchy. + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: Correct horizontal dumbbell chart — two dots connected by a line, + categories on Y-axis, values on X-axis. + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: Two distinct colored dots, gradient connecting line, horizontal orientation, + sorted by difference (largest at top), distinct colors for start/end. + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: X-axis = satisfaction score (50–100 domain), Y-axis = department + categories. Correct mapping per spec. + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: 'Title: ''Employee Satisfaction · dumbbell-basic · javascript · d3 + · anyplot.ai'' — correct format with optional descriptive prefix. Legend: + ''Before Policy'' / ''After Policy''.' + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: Varied deltas (7–22 pts), different starting points (57–75 before, + 69–91 after), all aspects of dumbbell chart type demonstrated. + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Employee satisfaction scores before/after flexible work policy — + specific, realistic, neutral business context. + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: Scores in 57–91 range on 0–100 scale, deltas 7–22 pts, realistic + for a workplace satisfaction survey. + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: 'Flat script: data → SVG mount → scales → axes → connecting lines + → dots → delta labels → annotations → legend → title. No functions or classes.' + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: Hard-coded inline data array — fully deterministic, no RNG. + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: No explicit imports. D3 is a global from the harness, window.ANYPLOT_TOKENS + and window.ANYPLOT_SIZE used correctly. + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Clean, idiomatic D3. Appropriate complexity for the chart. No fake + UI elements or over-engineering. + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: D3 is in INTERACTIVE_LIBRARIES — harness generates plot-{theme}.png + and plot-{theme}.html. Current D3 7.9 API throughout. + library_mastery: + score: 8 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: true + comment: d3.scaleLinear / d3.scaleBand, d3.axisBottom / axisLeft, data joins + via .data().join(), d3.format('+d') — all idiomatic D3 patterns. + - id: LM-02 + name: Distinctive Features + score: 4 + max: 5 + passed: true + comment: 'SVG linearGradient with gradientUnits=''userSpaceOnUse'' is a D3/SVG-distinctive + technique: positions the gradient in user-space coordinates so it tracks + the data scale exactly.' + verdict: APPROVED +impl_tags: + dependencies: [] + techniques: + - custom-legend + - annotations + - html-export + patterns: + - data-generation + - iteration-over-groups + dataprep: [] + styling: + - alpha-blending + - edge-highlighting