Use Cases (Aktualisiert: 2.6.2026)

D3.js-Datenvisualisierung mit Claude Code implementieren

D3.js mit Claude Code umsetzen: ausführbarer TypeScript-Code, Use Cases, Stolperfallen, Prüfung und Revenue-CTA.

D3.js-Datenvisualisierung mit Claude Code implementieren

Warum Claude Code bei D3.js hilft

Bei D3.js ist nicht das erste Rechteck der schwierige Teil. Schwierig wird es, wenn scaleBand, axisLeft, selection.join und transition zusammenpassen müssen und der Chart danach echte Daten, Responsive Layouts, Accessibility und Produktänderungen überstehen soll.

Claude Code kann vor dem Generieren den vorhandenen HTML-, CSS- und TypeScript-Code, Datenverträge und Projektkonventionen lesen. D3.js bindet Daten an DOM und SVG. Eine ungenaue Aufgabe erzeugt daher oft einen Chart, der einmal funktioniert, aber später schwer zu pflegen ist.

Nutze beim Anpassen die offiziellen Quellen: D3 Getting started, Joining data, d3-scale, d3-axis und d3-transition.

Einfaches Modell für Einsteiger

D3 übersetzt Daten in Pixel und SVG-Elemente. Wenn jede Schicht klar benannt ist, werden Prompts und Reviews präziser.

BausteinEinfache ErklärungRolle im Beispiel
selectionDOM-Elemente auswählenEin svg in #chart einfügen
scaleDatenwerte in Bildschirmpositionen umrechnenKanäle auf x, Conversions auf y abbilden
axisSkalenstriche und Labels zeichnenKanäle und Conversion-Zahlen anzeigen
markSichtbare FormBalken mit rect, Hilfslinie mit path
joinDatenzeilen und DOM-Knoten verbindenBalken bei Datenänderungen aktualisieren
transitionVisuelle Änderung animierenBalken von unten wachsen lassen

Ein guter Prompt enthält Datenform, Einheiten, Empty State, Tastaturbedienung und Prüfung.

Baue einen responsiven Balkenchart in Vite + TypeScript + D3 v7 für Besucher und Conversions je Akquisitionskanal. Nutze selection.join, Tooltip, Tastaturfokus, aria-label, Empty-State-Handling und einen Browser-Konsolen-Smoke-Test.

Ausführbares D3.js + TypeScript-Beispiel

Erstelle die App mit npm create vite@latest d3-demo -- --template vanilla-ts, installiere npm i d3 @types/d3, ersetze die Dateien und starte npm run dev.

{
  "scripts": {
    "dev": "vite"
  },
  "dependencies": {
    "d3": "^7.9.0"
  },
  "devDependencies": {
    "@types/d3": "^7.4.3",
    "typescript": "latest",
    "vite": "latest"
  }
}
<!doctype html>
<html lang="de">
  <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),
  )}.`;
}

Kurzer Smoke-Test in der Browser-Konsole:

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

Drei plus Use Cases

Use CaseWarum D3 passtAufgabe für Claude Code
Content-FunnelQuelle, Kategorie und CTA-Klicks gemeinsam vergleichenEventplan aus Analytics übernehmen
SaaS-DashboardNutzung, Retention und Plan-Unterschiede zeigenTypen, Loading, Empty State und Segmente fixieren
BetriebsreportsSchwellenwerte und Ausreißer hervorhebenRendering-Kosten mit Performance prüfen
A/B-Test-AuswertungConversion-Differenzen klarer als Tabellen zeigenBerechnung und Darstellung trennen wie bei A/B testing

Der Nutzen entsteht, wenn der Chart Umsatzhandlungen erklärt: Registrierung, Kauf, Kontakt, Verlängerung oder Aktivierung.

Typische Stolperfallen

FehlerSymptomLösung
Redraw ohne CleanupAchsen und Balken duplizieren sichContainer leeren oder Cleanup-Funktion zurückgeben
Skalen umgehenMobile Layouts oder neue Werte brechenWerte immer durch scaleLinear, scaleBand oder passende Skalen schicken
Nur enter zeichnenAlte Daten bleiben sichtbarselection.data(...).join(...) nutzen
Tooltip nur per MausTastaturnutzer verlieren Informationentabindex, aria-label und focus ergänzen
Framework-KonfliktReact oder Astro überschreibt D3-DOMD3 auf einen eigenen Container begrenzen
Bedeutung nur über FarbeSchwache AccessibilityLabels, Werte, Legende und Kontrast ergänzen; siehe Accessibility

Bitte Claude Code um eine konkrete Review: doppelte Redraws, leere Arrays, Division durch null, Tastatur, Screenreader, mobile Breite und 1000 Datenpunkte.

SEO, Monetarisierung und Verifikation

D3.js-Artikel können Suchtraffic bringen, aber der Code muss mit einem messbaren Ziel verbunden sein. Verlinke auf TypeScript-Tipps, SEO und Designsysteme.

ClaudeCodeLab bietet Setup-Material, Review-Prompts, CLAUDE.md-Vorlagen und Beratung. Wenn dein Team Chart-Standards statt einzelner Snippets braucht, starte mit den Produkten oder Training und Beratung.

Beim praktischen Test war der größte Gewinn, Accessibility und Verifikation schon im ersten Prompt zu verlangen. aria-label, Tastaturfokus, Empty State und Smoke-Test nachträglich einzubauen ist deutlich teurer.

#Claude Code #D3.js #Datenvisualisierung #Diagramm #Frontend
Kostenlos

Kostenloses PDF: Claude-Code-Cheatsheet

E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.

Wir schützen Ihre Daten und senden keinen Spam.

Masa

Über den Autor

Masa

Engineer für praktische Claude-Code-Workflows und Team-Einführung.