Inhaltsverzeichnis
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.
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
| Funktion | Zweck | Gibt zurück |
|---|---|---|
d3.select(selector) | Erstes passendes Element | Selection |
d3.selectAll(selector) | Alle passenden Elemente | Selection |
.data(array) | Daten binden | Update-Selection |
.enter() | Fehlende Elemente | Enter-Selection |
.join(enter, update, exit) | Kombinierter Data-Join | Selection |
.attr(name, value) | SVG/HTML-Attribut setzen | Selection |
.style(name, value) | CSS-Property setzen | Selection |
.append(tag) | Kind-Element anhängen | Selection (Kind) |
.text(string) | Text-Inhalt setzen | Selection |
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)');
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));
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
d3.curveLinear— Gerade Verbindungslinien (Standard)d3.curveMonotoneX— Glatte Kurve, keine Überschwinger (empfohlen für Zeitreihen)d3.curveCatmullRom— Weiche Kurve mit lokalem Einflussd3.curveStep/curveStepBefore/curveStepAfter— Treppenfunktiond3.curveBasis— B-Spline (sehr glatt, weicht von Datenpunkten ab)d3.curveCardinal— Cardinal Spline mit einstellbarer Spannung
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
});
}
forceLink — zieht verbundene Knoten zusammen (distance, strength)forceManyBody — abstoßend (negativ) oder anziehend (positiv) (strength, distanceMax)forceCenter — zieht alle Knoten zur MitteforceCollide — 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?
| Ansatz | Wann | Vorteile |
|---|---|---|
| useD3 Hook | Migration von Vanilla D3 | Maximale D3-Kontrolle |
| useRef + useEffect | Responsive, dynamische Daten | Sauber, testbar |
| @visx/visx | Komplexe Dashboards, Teams | Komponentenbasiert, TypeScript |
| Recharts/Nivo | Schnelle Standard-Charts | Minimal 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