D3.js Bar Chart Line Chart Force Layout React SVG

D3.js mit Claude Code: Datenvisualisierung 2026

📅 6. Mai 2026 ⏱ 12 min Lesezeit 🏷 Datenvisualisierung

D3.js für interaktive Datenvisualisierungen — Claude Code baut Bar Charts, Line Charts, Force-Layouts und Tooltips mit Scales und Axes aus einer einzigen Beschreibung.

Inhaltsverzeichnis

  1. D3.js Grundlagen — select, data, enter/join, SVG
  2. Scales und Axes — scaleLinear, scaleBand, axisBottom
  3. Bar Chart — rect, Tooltip, Transitions
  4. Line Chart — d3.line(), d3.area(), Zoom/Pan
  5. Force Layout — Simulation, Netzwerk-Graph
  6. React-Integration — useD3, useRef, Resize-Observer

D3.js (Data-Driven Documents) ist die mächtigste JavaScript-Bibliothek für interaktive Datenvisualisierungen im Browser. Mit Claude Code lassen sich komplexe SVG-basierte Charts, Force-Simulationen und vollständige React-Dashboards innerhalb von Minuten generieren — ohne stundenlange API-Recherche. Dieser Guide zeigt alle wichtigen D3.js-Konzepte mit produktionsreifen Code-Beispielen.

Warum D3.js 2026?
D3.js v7 ist der Standard für maßgeschneiderte Datenvisualisierungen. Bibliotheken wie Chart.js oder Recharts bieten fertige Komponenten — D3.js gibt dir vollständige Kontrolle über jeden Pixel, jede Animation und jede Interaktion. Ideal für Dashboards, wissenschaftliche Visualisierungen und datengetriebene Storytelling-Apps.

1. D3.js Grundlagen — d3.select, d3.selectAll, data(), enter/join, attr, style, SVG-Grundstruktur

Das Kernkonzept von D3.js ist das Data-Join: Du bindest Daten an DOM-Elemente und lässt D3 entscheiden, welche Elemente erstellt, aktualisiert oder entfernt werden müssen. Die Selektion ist der Einstiegspunkt für alle D3-Operationen.

Selektionen und DOM-Manipulation

// Einzelnes Element selektieren
const svg = d3.select('#chart');

// Mehrere Elemente selektieren
const circles = d3.selectAll('circle');

// Attribute und Styles setzen (Method Chaining)
d3.select('#my-svg')
  .attr('width', 800)
  .attr('height', 400)
  .style('background', '#0f0f1a')
  .style('border-radius', '8px');

SVG-Grundstruktur mit Margins

// Standard-Margin-Konvention (Mike Bostock)
const margin = { top: 40, right: 30, bottom: 60, left: 70 };
const width  = 800 - margin.left - margin.right;
const height = 450 - margin.top  - margin.bottom;

const svg = d3.select('#chart')
  .append('svg')
  .attr('width',  width  + margin.left + margin.right)
  .attr('height', height + margin.top  + margin.bottom)
  .append('g')
  .attr('transform', `translate(${margin.left},${margin.top})`);

Data-Join — enter / update / exit

const data = [10, 25, 40, 15, 60];

// Moderner Ansatz: selection.join() (D3 v5+)
svg.selectAll('circle')
  .data(data)
  .join(
    // Enter: neue Elemente
    enter => enter.append('circle')
      .attr('r', 0)
      .call(enter => enter.transition().attr('r', d => d)),
    // Update: bestehende Elemente
    update => update.attr('fill', '#a78bfa'),
    // Exit: entfernte Elemente
    exit => exit.transition().attr('r', 0).remove()
  )
  .attr('cx', (d, i) => i * 120 + 60)
  .attr('cy', 200)
  .attr('fill', '#6366f1');

// Klassischer Ansatz: .enter().append()
const rects = svg.selectAll('rect')
  .data(data)
  .enter()
  .append('rect')
  .attr('x', (d, i) => i * 50)
  .attr('width', 40)
  .attr('height', d => d * 3);

D3.js Kern-API auf einen Blick

FunktionZweckGibt zurück
d3.select(selector)Erstes passendes ElementSelection
d3.selectAll(selector)Alle passenden ElementeSelection
.data(array)Daten bindenUpdate-Selection
.enter()Fehlende ElementeEnter-Selection
.join(enter, update, exit)Kombinierter Data-JoinSelection
.attr(name, value)SVG/HTML-Attribut setzenSelection
.style(name, value)CSS-Property setzenSelection
.append(tag)Kind-Element anhängenSelection (Kind)
.text(string)Text-Inhalt setzenSelection

2. Scales und Axes — d3.scaleLinear, scaleBand, scaleTime, axisBottom/Left, tickFormat, margin

Scales sind Funktionen, die Datenwerte (Domain) auf visuelle Größen (Range) abbilden. Axes nutzen Scales, um Achsen mit Ticks und Labels zu rendern. Zusammen bilden sie das Rückgrat jedes D3-Charts.

Lineare und Band-Scales

// scaleLinear: kontinuierliche Werte (z.B. Umsatz, Temperatur)
const yScale = d3.scaleLinear()
  .domain([0, d3.max(data, d => d.value)])  // Datenwerte
  .range([height, 0])                          // Pixel oben/unten (invertiert!)
  .nice();                                        // Rundet auf schöne Ticks

// scaleBand: diskrete Kategorien (Balkenbreite automatisch)
const xScale = d3.scaleBand()
  .domain(data.map(d => d.label))
  .range([0, width])
  .padding(0.2);                               // 20% Lücke zwischen Balken

// scaleTime: Zeitreihen mit Date-Objekten
const xTime = d3.scaleTime()
  .domain(d3.extent(data, d => new Date(d.date)))
  .range([0, width]);

// scaleOrdinal: Farb-Mapping
const colorScale = d3.scaleOrdinal()
  .domain(['A', 'B', 'C'])
  .range(d3.schemeTableau10);

Axes rendern

// X-Achse unten
const xAxis = d3.axisBottom(xScale)
  .tickSize(-height)                            // Gridlines nach oben
  .tickPadding(10);

svg.append('g')
  .attr('class', 'x-axis')
  .attr('transform', `translate(0, ${height})`)
  .call(xAxis)
  .selectAll('text')
  .attr('transform', 'rotate(-40)')           // Labels schräg
  .style('text-anchor', 'end');

// Y-Achse links mit Formatierung
const yAxis = d3.axisLeft(yScale)
  .ticks(6)
  .tickFormat(d => `${d3.format('.2s')(d)}€`); // "1.2k€", "2.5M€"

svg.append('g')
  .attr('class', 'y-axis')
  .call(yAxis);

// Achsen stylen (Gridlines transparent)
svg.selectAll('.x-axis .tick line')
  .style('stroke', 'rgba(255,255,255,0.06)');
svg.selectAll('.domain')
  .style('stroke', 'rgba(255,255,255,0.15)');
Tipp: d3.format() Cheat Sheet
d3.format('.2f') → 3.14 | d3.format('.2s') → 3.1k | d3.format(',.0f') → 3,141 | d3.format('+.1%') → +12.3% | d3.format('$,.2f') → $3,141.59

3. Bar Chart — SVG rect, Tooltip mit mouseover/mouseout, transition().duration(), Farben

Der Bar Chart ist das Arbeitspferd der Datenvisualisierung. Mit D3.js kannst du ihn vollständig anpassen — von animierten Einblendungen bis zu interaktiven Tooltips, die präzise Datenpunkte anzeigen.

Vollständiger Bar Chart mit Tooltip

// Daten
const salesData = [
  { month: 'Jan', revenue: 12400 },
  { month: 'Feb', revenue: 18200 },
  { month: 'Mär', revenue: 15800 },
  { month: 'Apr', revenue: 24100 },
  { month: 'Mai', revenue: 31500 },
  { month: 'Jun', revenue: 28700 },
];

// Tooltip-DIV (HTML, kein SVG — ermöglicht Rich Content)
const tooltip = d3.select('body')
  .append('div')
  .attr('class', 'tooltip')
  .style('position', 'fixed')
  .style('background', 'rgba(15,15,26,0.95)')
  .style('border', '1px solid #1e1e3a')
  .style('border-radius', '8px')
  .style('padding', '10px 14px')
  .style('pointer-events', 'none')
  .style('opacity', 0);

// Farbskala
const colorFn = d3.scaleSequential()
  .domain([0, salesData.length - 1])
  .interpolator(d3.interpolateViridis);

// Balken zeichnen
svg.selectAll('.bar')
  .data(salesData)
  .join('rect')
  .attr('class', 'bar')
  .attr('x',     d => xScale(d.month))
  .attr('width', xScale.bandwidth())
  .attr('y',      height)           // Start unten für Animation
  .attr('height', 0)
  .attr('rx', 4)                    // Abgerundete Ecken
  .attr('fill', (d, i) => colorFn(i))
  .// Einblend-Animation
  .transition()
  .duration(800)
  .delay((d, i) => i * 100)         // Gestaffelt
  .ease(d3.easeCubicOut)
  .attr('y',      d => yScale(d.revenue))
  .attr('height', d => height - yScale(d.revenue));

// Hover-Events (nach Transition auf '.bar' setzen)
svg.selectAll('.bar')
  .on('mouseover', function(event, d) {
    d3.select(this)
      .transition().duration(150)
      .attr('opacity', 0.8)
      .attr('transform', 'scaleY(1.02)');
    tooltip
      .html(`<strong>${d.month}</strong><br>${d3.format(',.0f')(d.revenue)} €`)
      .style('left',  event.clientX + 12 + 'px')
      .style('top',   event.clientY - 28 + 'px')
      .transition().duration(200)
      .style('opacity', 1);
  })
  .on('mouseout', function() {
    d3.select(this)
      .transition().duration(150)
      .attr('opacity', 1);
    tooltip.transition().duration(300).style('opacity', 0);
  });

// Wert-Labels über den Balken
svg.selectAll('.bar-label')
  .data(salesData)
  .join('text')
  .attr('class', 'bar-label')
  .attr('x', d => xScale(d.month) + xScale.bandwidth() / 2)
  .attr('y', d => yScale(d.revenue) - 6)
  .attr('text-anchor', 'middle')
  .style('fill', '#94a3b8')
  .style('font-size', '11px')
  .text(d => d3.format('.1s')(d.revenue));
Wichtig: Transition und Events
Event-Handler müssen NACH einer .transition() neu gesetzt werden, da .transition() die Event-Bindung nicht überträgt. Alternativ: Event-Handler vor der Transition setzen und in der Selection (nicht der Transition) belassen.

4. Line Chart — d3.line(), x/y-Accessor, d3.curveMonotoneX, d3.area() für Fläche, Zoom/Pan

Line Charts sind ideal für Zeitreihen. D3.js generiert den SVG-Pfad automatisch aus deinen Datenpunkten. Mit d3.area() kannst du Flächen unter der Linie füllen, mit d3.zoom() Zoom und Pan implementieren.

Line und Area Generator

// Zeitreihen-Daten
const timeData = [
  { date: '2026-01-01', value: 420 },
  { date: '2026-02-01', value: 580 },
  { date: '2026-03-01', value: 510 },
  { date: '2026-04-01', value: 740 },
  { date: '2026-05-01', value: 890 },
].map(d => ({ ...d, date: new Date(d.date) }));

// Linie-Generator
const line = d3.line()
  .x(d => xScale(d.date))
  .y(d => yScale(d.value))
  .curve(d3.curveMonotoneX);          // Weiche Kurve, keine Oszillation

// Area-Generator (Fläche unter der Linie)
const area = d3.area()
  .x(d  => xScale(d.date))
  .y0(height)                          // Baseline = X-Achse
  .y1(d => yScale(d.value))
  .curve(d3.curveMonotoneX);

// Gradient-Fläche zeichnen
const defs = svg.append('defs');
const grad = defs.append('linearGradient')
  .attr('id', 'area-gradient')
  .attr('gradientUnits', 'userSpaceOnUse')
  .attr('x1', 0).attr('y1', 0)
  .attr('x2', 0).attr('y2', height);

grad.append('stop')
  .attr('offset', '0%')
  .style('stop-color', '#6366f1')
  .style('stop-opacity', 0.3);
grad.append('stop')
  .attr('offset', '100%')
  .style('stop-color', '#6366f1')
  .style('stop-opacity', 0);

svg.append('path')
  .datum(timeData)
  .attr('fill', 'url(#area-gradient)')
  .attr('d', area);

svg.append('path')
  .datum(timeData)
  .attr('fill', 'none')
  .attr('stroke', '#6366f1')
  .attr('stroke-width', 2.5)
  .attr('d', line);

Zoom und Pan

// Clip-Path für saubere Zoom-Grenzen
defs.append('clipPath')
  .attr('id', 'clip')
  .append('rect')
  .attr('width', width).attr('height', height);

// Zoom-Behavior definieren
const zoom = d3.zoom()
  .scaleExtent([1, 20])                 // Min/Max Zoom-Faktor
  .translateExtent([[0,0],[width,height]])
  .on('zoom', ({ transform }) => {
    const newX = transform.rescaleX(xScale);
    // Achse und Pfad aktualisieren
    svg.select('.x-axis').call(d3.axisBottom(newX));
    linePath.attr('d', line.x(d => newX(d.date)));
    areaPath.attr('d', area.x(d => newX(d.date)));
  });

// Zoom auf SVG-Rect (nicht auf g) registrieren
svg.append('rect')
  .attr('width', width).attr('height', height)
  .attr('fill', 'none')
  .attr('pointer-events', 'all')
  .call(zoom);

Kurven-Typen in D3.js


5. Force Layout — d3.forceSimulation, forceLink/Charge/Center/Collision, tick-Event, Netzwerk-Graph

Force Layouts simulieren physikalische Kräfte zwischen Knoten. Sie sind ideal für Netzwerk-Graphen, Cluster-Visualisierungen und organische Layouts. D3.js berechnet die Positionen iterativ über viele Frames.

Netzwerk-Graph aufbauen

// Graph-Daten
const nodes = [
  { id: 'CEO',      group: 1, size: 20 },
  { id: 'ICT',      group: 2, size: 14 },
  { id: 'Strategy', group: 2, size: 14 },
  { id: 'Marketing',group: 2, size: 14 },
  { id: 'Worker-1', group: 3, size: 8  },
  { id: 'Worker-2', group: 3, size: 8  },
];
const links = [
  { source: 'CEO',      target: 'ICT',       value: 5 },
  { source: 'CEO',      target: 'Strategy',  value: 4 },
  { source: 'CEO',      target: 'Marketing', value: 3 },
  { source: 'ICT',      target: 'Worker-1', value: 2 },
  { source: 'Strategy', target: 'Worker-2', value: 2 },
];

// Farb-Mapping nach Gruppe
const groupColor = d3.scaleOrdinal()
  .domain([1, 2, 3])
  .range(['#a78bfa', '#f68b1f', '#22c55e']);

// Simulation erstellen
const simulation = d3.forceSimulation(nodes)
  .force('link', d3.forceLink(links)
    .id(d => d.id)
    .distance(120)
    .strength(0.5))
  .force('charge', d3.forceManyBody()
    .strength(-300))               // Negativ = abstoßend
  .force('center', d3.forceCenter(width / 2, height / 2))
  .force('collision', d3.forceCollide()
    .radius(d => d.size + 10)
    .strength(0.8));

// Links zeichnen
const link = svg.append('g')
  .selectAll('line')
  .data(links)
  .join('line')
  .attr('stroke', '#1e1e3a')
  .attr('stroke-width', d => Math.sqrt(d.value) * 1.5);

// Knoten zeichnen
const node = svg.append('g')
  .selectAll('circle')
  .data(nodes)
  .join('circle')
  .attr('r',    d => d.size)
  .attr('fill', d => groupColor(d.group))
  .call(drag(simulation)); // Drag-Behavior

// Labels
const label = svg.append('g')
  .selectAll('text')
  .data(nodes)
  .join('text')
  .text(d => d.id)
  .attr('font-size', 11)
  .attr('fill', '#94a3b8')
  .attr('text-anchor', 'middle')
  .attr('dy', '0.35em');

// tick-Event: Positionen aktualisieren
simulation.on('tick', () => {
  link
    .attr('x1', d => d.source.x).attr('y1', d => d.source.y)
    .attr('x2', d => d.target.x).attr('y2', d => d.target.y);
  node
    .attr('cx', d => d.x)
    .attr('cy', d => d.y);
  label
    .attr('x', d => d.x)
    .attr('y', d => d.y + d.size + 14);
});

// Drag-Helper-Funktion
function drag(simulation) {
  return d3.drag()
    .on('start', (event, d) => {
      if (!event.active) simulation.alphaTarget(0.3).restart();
      d.fx = d.x; d.fy = d.y;          // Fix Position
    })
    .on('drag', (event, d) => {
      d.fx = event.x; d.fy = event.y;
    })
    .on('end', (event, d) => {
      if (!event.active) simulation.alphaTarget(0);
      d.fx = null; d.fy = null;       // Unfix → Simulation übernimmt
    });
}
Force-Typen und ihre Parameter
forceLink — zieht verbundene Knoten zusammen (distance, strength)
forceManyBody — abstoßend (negativ) oder anziehend (positiv) (strength, distanceMax)
forceCenter — zieht alle Knoten zur Mitte
forceCollide — verhindert Überlappungen (radius, strength)
forceX / forceY — zieht zu einer bestimmten Koordinate

6. React-Integration — useD3 Hook, useRef für SVG, useEffect für D3, Resize-Observer, @visx/visx

D3.js und React teilen sich die DOM-Kontrolle — D3 manipuliert direkt, React rendert deklarativ. Die sauberste Integration: React rendert das SVG-Element, D3 bekommt eine Referenz darauf und zeichnet alles darunter.

useD3 Custom Hook

// hooks/useD3.js — wiederverwendbarer D3-Hook
import { useRef, useEffect } from 'react';
import * as d3 from 'd3';

export function useD3(renderChartFn, dependencies) {
  const ref = useRef();

  useEffect(() => {
    renderChartFn(d3.select(ref.current));
    return () => {};       // Cleanup: D3 räumt SVG-Inhalt selbst auf
  }, dependencies);

  return ref;
}

// Verwendung in einer React-Komponente
export function BarChart({ data }) {
  const ref = useD3(
    (svg) => {
      // D3-Code hier — genau wie im Vanilla-Beispiel
      const height = 300;
      const width  = 600;
      svg.attr('width', width).attr('height', height);
      // ... Scales, Axes, Bars ...
    },
    [data]  // Re-render wenn data sich ändert
  );

  return <svg ref={ref} />;
}

Resize-Observer für responsive Charts

import { useRef, useEffect, useState, useCallback } from 'react';
import * as d3 from 'd3';

function ResponsiveChart({ data }) {
  const containerRef = useRef(null);
  const svgRef       = useRef(null);
  const [dims, setDims] = useState({ width: 0, height: 0 });

  // Resize-Observer: misst Container und triggert Re-Render
  useEffect(() => {
    const ro = new ResizeObserver(entries => {
      const { width, height } = entries[0].contentRect;
      setDims({ width: Math.floor(width), height: 400 });
    });
    ro.observe(containerRef.current);
    return () => ro.disconnect();
  }, []);

  // D3-Zeichnung wenn dims oder data sich ändern
  useEffect(() => {
    if (!dims.width || !data.length) return;
    const svg = d3.select(svgRef.current);
    svg.selectAll('*').remove();  // Vorherigen Chart löschen

    const { width, height } = dims;
    const margin = { top: 20, right: 20, bottom: 40, left: 50 };
    const innerW  = width  - margin.left - margin.right;
    const innerH  = height - margin.top  - margin.bottom;

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

    // Scales und Chart basierend auf aktueller Breite
    const xScale = d3.scaleBand()
      .domain(data.map(d => d.label))
      .range([0, innerW])
      .padding(0.25);
    const yScale = d3.scaleLinear()
      .domain([0, d3.max(data, d => d.value)])
      .range([innerH, 0]).nice();

    g.append('g').attr('transform', `translate(0,${innerH})`).call(d3.axisBottom(xScale));
    g.append('g').call(d3.axisLeft(yScale).ticks(5));

    g.selectAll('.bar')
      .data(data).join('rect')
      .attr('class', 'bar')
      .attr('x', d => xScale(d.label))
      .attr('y', d => yScale(d.value))
      .attr('width', xScale.bandwidth())
      .attr('height', d => innerH - yScale(d.value))
      .attr('fill', '#6366f1').attr('rx', 3);
  }, [dims, data]);

  return (
    <div ref={containerRef} style={{ width: '100%' }}>
      <svg ref={svgRef} />
    </div>
  );
}

@visx/visx — D3-basierte React-Komponenten

// @visx = Airbnb's D3-React-Brücke (empfohlen für komplexe Apps)
// npm install @visx/scale @visx/axis @visx/shape @visx/group

import { scaleLinear, scaleBand } from '@visx/scale';
import { AxisBottom, AxisLeft }  from '@visx/axis';
import { Bar }                   from '@visx/shape';
import { Group }                 from '@visx/group';
import { useTooltip }            from '@visx/tooltip';

function VisxBarChart({ data, width, height }) {
  const margin = { top: 20, right: 20, bottom: 40, left: 50 };
  const xMax   = width  - margin.left - margin.right;
  const yMax   = height - margin.top  - margin.bottom;

  const xScale = scaleBand({ range: [0, xMax], domain: data.map(d => d.label), padding: 0.2 });
  const yScale = scaleLinear({ range: [yMax, 0], domain: [0, Math.max(...data.map(d => d.value))] });

  const { tooltipData, tooltipLeft, tooltipTop, showTooltip, hideTooltip } = useTooltip();

  return (
    <svg width={width} height={height}>
      <Group left={margin.left} top={margin.top}>
        {data.map(d => (
          <Bar
            key={d.label}
            x={xScale(d.label)}
            y={yScale(d.value)}
            width={xScale.bandwidth()}
            height={yMax - yScale(d.value)}
            fill="#6366f1"
            rx={3}
            onMouseMove={(e) => showTooltip({ tooltipData: d, tooltipLeft: e.clientX, tooltipTop: e.clientY })}
            onMouseLeave={hideTooltip}
          />
        ))}
        <AxisBottom top={yMax} scale={xScale} />
        <AxisLeft   scale={yScale} />
      </Group>
    </svg>
  );
}

D3 + React: Wann welcher Ansatz?

AnsatzWannVorteile
useD3 HookMigration von Vanilla D3Maximale D3-Kontrolle
useRef + useEffectResponsive, dynamische DatenSauber, testbar
@visx/visxKomplexe Dashboards, TeamsKomponentenbasiert, TypeScript
Recharts/NivoSchnelle Standard-ChartsMinimal Code, gute Docs

D3.js Visualisierungen mit Claude Code generieren

Beschreibe deinen Chart — Claude Code generiert den vollständigen D3.js-Code mit Scales, Axes, Tooltips und Animationen. Kein API-Studium, kein Debugging auf Stack Overflow.

Kostenlos testen — ohne Kreditkarte
← Alle Blog-Artikel Mehr Artikel →