Use Cases (Updated: 6/2/2026)

Claude Code D3.js Data Visualization Implementation Guide

Build D3.js charts with Claude Code: runnable TypeScript, use cases, pitfalls, verification, and revenue CTA.

Claude Code D3.js Data Visualization Implementation Guide

Why Claude Code Helps With D3.js

The hard part of D3.js is rarely drawing the first rectangle. The hard part is understanding how scaleBand, axisLeft, selection.join, and transition fit together before a chart has to survive real data, resizing, accessibility review, and product changes.

Claude Code is useful because it can read the surrounding HTML, CSS, TypeScript, data contracts, and project conventions before generating code. D3.js is a library for binding data to the DOM and SVG, so vague instructions often produce a chart that works once but becomes painful to maintain.

This guide uses a copy-paste Vite + TypeScript example and shows what to ask Claude Code for. Keep the official docs open while adapting it: D3 Getting started, Joining data, d3-scale, d3-axis, and d3-transition.

The Mental Model for Beginners

D3 turns data into pixels and SVG elements. If you name each layer clearly, your prompts and reviews become much sharper.

PartPlain-English meaningRole in this example
selectionPick DOM elementsAdd an svg inside #chart
scaleConvert data values to screen positionsMap channels to x positions and conversions to y positions
axisDraw ticks and labelsShow channels and conversion counts
markVisible shapeDraw bars with rect and a guide line with path
joinMatch data rows to DOM elementsUpdate bars when the dataset changes
transitionAnimate a visual changeGrow bars from the baseline

Do not ask Claude Code only for “a D3 chart.” Give it the data shape, units, empty states, keyboard behavior, and verification steps. A useful prompt is:

Build a responsive bar chart in Vite + TypeScript + D3 v7 for acquisition-channel visitors and conversions. Use selection.join, tooltips, keyboard focus, aria-label, empty-data handling, and a browser-console smoke test.

Runnable D3.js + TypeScript Example

Create a vanilla TypeScript Vite app with npm create vite@latest d3-demo -- --template vanilla-ts, then install D3 with npm i d3 @types/d3. Replace the files below and run npm run dev. The D3 dependency targets the v7 line shown in the official docs.

{
  "scripts": {
    "dev": "vite"
  },
  "dependencies": {
    "d3": "^7.9.0"
  },
  "devDependencies": {
    "@types/d3": "^7.4.3",
    "typescript": "latest",
    "vite": "latest"
  }
}
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>D3 Conversion Chart</title>
  </head>
  <body>
    <main class="page">
      <h1>D3.js Conversion Dashboard</h1>
      <section class="chart-shell" aria-describedby="chart-summary">
        <div id="chart"></div>
        <p id="chart-summary" class="sr-only"></p>
      </section>
    </main>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>
:root {
  color: #172033;
  background: #f7f7f3;
  font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

body {
  margin: 0;
}

.page {
  width: min(920px, calc(100vw - 32px));
  margin: 40px auto;
}

.chart-shell {
  border: 1px solid #d8d5cc;
  border-radius: 8px;
  background: #ffffff;
  padding: 20px;
}

#chart {
  min-height: 320px;
  position: relative;
}

#chart svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

.axis-label {
  fill: #475569;
  font-size: 12px;
}

.bar {
  fill: #2563eb;
  outline: none;
}

.bar:hover,
.bar:focus {
  fill: #dc2626;
}

.trend-line {
  fill: none;
  stroke: #0f172a;
  stroke-width: 2;
  pointer-events: none;
}

.chart-tooltip {
  position: absolute;
  top: 0;
  left: 0;
  max-width: 220px;
  border-radius: 6px;
  background: #172033;
  color: #ffffff;
  font-size: 13px;
  line-height: 1.5;
  opacity: 0;
  padding: 8px 10px;
  pointer-events: none;
  transform: translate(-9999px, -9999px);
  transition: opacity 120ms ease;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
}
import * as d3 from "d3";
import "./style.css";

type ChannelDatum = {
  channel: string;
  visitors: number;
  conversions: number;
};

const data: ChannelDatum[] = [
  { channel: "Search", visitors: 4200, conversions: 168 },
  { channel: "Newsletter", visitors: 2600, conversions: 182 },
  { channel: "Social", visitors: 3100, conversions: 96 },
  { channel: "Partner", visitors: 1400, conversions: 84 },
];

const numberFormat = new Intl.NumberFormat(undefined);
const percentFormat = new Intl.NumberFormat(undefined, {
  style: "percent",
  maximumFractionDigits: 1,
});

function conversionRate(datum: ChannelDatum): number {
  return datum.visitors === 0 ? 0 : datum.conversions / datum.visitors;
}

function drawConversionChart(container: HTMLElement, items: ChannelDatum[]): void {
  container.replaceChildren();

  if (items.length === 0) {
    container.textContent = "No data to display.";
    return;
  }

  const margin = { top: 28, right: 24, bottom: 56, left: 64 };
  const outerWidth = 760;
  const outerHeight = 420;
  const width = outerWidth - margin.left - margin.right;
  const height = outerHeight - margin.top - margin.bottom;

  const svg = d3
    .select(container)
    .append("svg")
    .attr("viewBox", `0 0 ${outerWidth} ${outerHeight}`)
    .attr("role", "img")
    .attr("aria-labelledby", "chart-title chart-desc");

  svg.append("title").attr("id", "chart-title").text("Conversions by channel");
  svg
    .append("desc")
    .attr("id", "chart-desc")
    .text("Bar chart comparing conversions from each acquisition channel.");

  const plot = svg
    .append("g")
    .attr("transform", `translate(${margin.left},${margin.top})`);

  const x = d3
    .scaleBand<string>()
    .domain(items.map((d) => d.channel))
    .range([0, width])
    .padding(0.28);

  const y = d3
    .scaleLinear()
    .domain([0, d3.max(items, (d) => d.conversions) ?? 0])
    .nice()
    .range([height, 0]);

  plot
    .append("g")
    .attr("transform", `translate(0,${height})`)
    .call(d3.axisBottom(x))
    .call((axis) => axis.selectAll("text").attr("dy", "0.85em"));

  plot.append("g").call(d3.axisLeft(y).ticks(5));

  plot
    .append("text")
    .attr("class", "axis-label")
    .attr("x", -margin.left + 4)
    .attr("y", -10)
    .text("Conversions");

  const tooltip = d3.select(container).append("div").attr("class", "chart-tooltip");

  function showTooltip(event: PointerEvent | FocusEvent, datum: ChannelDatum): void {
    const xCenter = (x(datum.channel) ?? 0) + x.bandwidth() / 2 + margin.left;
    const yTop = y(datum.conversions) + margin.top;
    const [left, top] =
      "clientX" in event ? d3.pointer(event, container) : [xCenter, yTop];

    tooltip
      .style("opacity", "1")
      .style("transform", `translate(${left + 12}px, ${top - 28}px)`)
      .html(
        `<strong>${datum.channel}</strong><br />` +
          `Visitors: ${numberFormat.format(datum.visitors)}<br />` +
          `Conversions: ${numberFormat.format(datum.conversions)}<br />` +
          `CVR: ${percentFormat.format(conversionRate(datum))}`,
      );
  }

  function hideTooltip(): void {
    tooltip.style("opacity", "0").style("transform", "translate(-9999px, -9999px)");
  }

  const bars = plot
    .selectAll<SVGRectElement, ChannelDatum>("rect.bar")
    .data(items, (d) => d.channel)
    .join((enter) =>
      enter
        .append("rect")
        .attr("class", "bar")
        .attr("x", (d) => x(d.channel) ?? 0)
        .attr("width", x.bandwidth())
        .attr("y", height)
        .attr("height", 0),
    )
    .attr("tabindex", 0)
    .attr("role", "img")
    .attr(
      "aria-label",
      (d) =>
        `${d.channel}: ${numberFormat.format(d.conversions)} conversions, ${percentFormat.format(
          conversionRate(d),
        )} conversion rate`,
    )
    .on("pointerenter pointermove", showTooltip)
    .on("focus", showTooltip)
    .on("pointerleave blur", hideTooltip);

  bars
    .transition()
    .duration(700)
    .delay((_d, index) => index * 80)
    .attr("x", (d) => x(d.channel) ?? 0)
    .attr("width", x.bandwidth())
    .attr("y", (d) => y(d.conversions))
    .attr("height", (d) => height - y(d.conversions));

  const trendLine = d3
    .line<ChannelDatum>()
    .x((d) => (x(d.channel) ?? 0) + x.bandwidth() / 2)
    .y((d) => y(d.conversions))
    .curve(d3.curveMonotoneX);

  plot
    .append("path")
    .datum(items)
    .attr("class", "trend-line")
    .attr("d", trendLine);
}

const chart = document.querySelector<HTMLElement>("#chart");

if (!chart) {
  throw new Error("Missing #chart element.");
}

drawConversionChart(chart, data);

const summary = document.querySelector<HTMLElement>("#chart-summary");

if (summary) {
  const best = data.reduce((current, item) =>
    conversionRate(item) > conversionRate(current) ? item : current,
  );

  summary.textContent = `Highest conversion rate: ${best.channel}, ${percentFormat.format(
    conversionRate(best),
  )}.`;
}

After the page renders, run this in the browser console as a quick smoke test.

console.log(document.querySelectorAll("#chart rect.bar").length);
console.log(document.querySelector("#chart svg")?.getAttribute("role"));
console.log(document.querySelector("#chart-summary")?.textContent);

Practical Use Cases

Use caseWhy D3 fitsWhat Claude Code should handle
Content funnel analysisCompare traffic source, article category, and CTA clicks on one screenReuse the event plan from analytics implementation
SaaS product dashboardShow feature usage, retention, and plan-level differencesLock data types, loading states, empty states, and segment labels in TypeScript
Operations and incident reportsHighlight time ranges, thresholds, and outliersReview rendering cost with performance optimization
A/B test reportingShow segment differences and conversion deltas more clearly than a tableKeep aggregation separate from the visualization, as in A/B testing

The business value comes when the chart explains behavior that affects revenue: signup, purchase, contact, renewal, or product activation. D3 is strongest when a generic chart library cannot express that shape cleanly.

Pitfalls to Review Critically

PitfallSymptomFix
Redrawing without cleanupAxes and bars duplicate after navigation or state changesClear the container or return a cleanup function
Bypassing scalesThe chart breaks on mobile or new data rangesRoute every data value through scaleLinear, scaleBand, or another D3 scale
Drawing only enter selectionsRemoved data leaves stale DOM behindUse selection.data(...).join(...) as the default update pattern
Mouse-only tooltipsKeyboard and screen-reader users miss the same informationAdd tabindex, aria-label, and focus behavior
Framework conflictsReact or Astro overwrites DOM that D3 changedConfine D3 to one owned container
Color-only meaningThe chart fails for color-vision differences or printoutsAdd labels, values, legends, and accessible contrast; see accessibility

Ask Claude Code for a focused review: “Check duplicate redraws, empty arrays, divide-by-zero, keyboard access, screen-reader output, mobile width, and performance with 1,000 data points.” That produces much better feedback than “find bugs.”

SEO and Revenue Path

D3.js content can attract search traffic, but readers often copy the code and leave. Connect the chart to a measurable business outcome and link onward to TypeScript tips, SEO optimization, and design systems.

ClaudeCodeLab offers Claude Code setup material, review prompts, CLAUDE.md templates, and implementation consulting. For teams that need chart standards rather than a one-off snippet, start with the products page or the training and consultation page.

Hands-On Verification Note

When I tested this structure, the biggest quality improvement came from asking for accessibility and verification before the first implementation pass. Adding aria-label, keyboard focus, empty-data behavior, and a console smoke test upfront was cheaper than retrofitting them later.

The practical lesson is simple: use Claude Code and D3.js to capture the data meaning, display intent, verification checklist, and revenue path in one specification. That is what turns a chart from a visual decoration into a useful product surface.

#Claude Code #D3.js #data visualization #chart #frontend
Free

Free PDF: Claude Code Cheatsheet

Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.

We handle your data with care and never send spam.

Level up your Claude Code workflow

Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.