d3js-data-visualization
npx machina-cli add skill besoeasy/open-skills/d3js-data-visualization --openclawD3.js Data Visualization
Build sophisticated, interactive data visualizations using d3.js (Data-Driven Documents). D3 binds data to DOM elements and applies data-driven transformations to produce publication-quality, fully customizable visuals.
When to Use This Skill
- Custom charts requiring unique visual encodings or layouts
- Interactive visualizations with pan, zoom, or brush behaviors
- Network/graph visualizations (force-directed, tree, hierarchy, chord diagrams)
- Geographic visualizations with custom projections
- Smooth, choreographed transitions and animations
- Novel chart types not available in standard libraries (Recharts, Chart.js, etc.)
- Fine-grained SVG styling and accessibility control
Consider alternatives for:
- 3D visualizations → use Three.js
- Simple standard charts with minimal customization → use Chart.js or Recharts
Required Tools / Libraries
No backend required. Runs entirely in the browser or Node.js (with jsdom/canvas).
# Install via npm
npm install d3
# Or use CDN in HTML
<script src="https://d3js.org/d3.v7.min.js"></script>
Core Workflow
1. Set Up D3
import * as d3 from 'd3';
2. Standard Chart Structure
Every d3 visualization follows this pattern:
function drawChart(data) {
if (!data || data.length === 0) return;
const svg = d3.select('#chart');
svg.selectAll("*").remove(); // clear previous render
const width = 800, height = 400;
const margin = { top: 20, right: 30, bottom: 40, left: 50 };
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
// Define scales
const xScale = d3.scaleLinear().domain([0, d3.max(data, d => d.x)]).range([0, innerWidth]);
const yScale = d3.scaleLinear().domain([0, d3.max(data, d => d.y)]).range([innerHeight, 0]);
// Axes
g.append("g").attr("transform", `translate(0,${innerHeight})`).call(d3.axisBottom(xScale));
g.append("g").call(d3.axisLeft(yScale));
// Data elements
g.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => xScale(d.x))
.attr("cy", d => yScale(d.y))
.attr("r", 5)
.attr("fill", "steelblue");
}
Common Chart Patterns
Bar Chart
const xScale = d3.scaleBand().domain(data.map(d => d.category)).range([0, innerWidth]).padding(0.1);
const yScale = d3.scaleLinear().domain([0, d3.max(data, d => d.value)]).range([innerHeight, 0]);
g.selectAll("rect")
.data(data)
.join("rect")
.attr("x", d => xScale(d.category))
.attr("y", d => yScale(d.value))
.attr("width", xScale.bandwidth())
.attr("height", d => innerHeight - yScale(d.value))
.attr("fill", "steelblue");
Line Chart
const line = d3.line()
.x(d => xScale(d.date))
.y(d => yScale(d.value))
.curve(d3.curveMonotoneX);
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 2)
.attr("d", line);
Scatter Plot
g.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => xScale(d.x))
.attr("cy", d => yScale(d.y))
.attr("r", d => sizeScale(d.size))
.attr("fill", d => colorScale(d.category))
.attr("opacity", 0.7);
Pie / Donut Chart
const pie = d3.pie().value(d => d.value).sort(null);
const arc = d3.arc().innerRadius(0).outerRadius(Math.min(width, height) / 2 - 20);
const colorScale = d3.scaleOrdinal(d3.schemeCategory10);
const g = svg.append("g").attr("transform", `translate(${width / 2},${height / 2})`);
g.selectAll("path")
.data(pie(data))
.join("path")
.attr("d", arc)
.attr("fill", (d, i) => colorScale(i))
.attr("stroke", "white")
.attr("stroke-width", 2);
Force-Directed Network Graph
const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id).distance(100))
.force("charge", d3.forceManyBody().strength(-300))
.force("center", d3.forceCenter(width / 2, height / 2));
const link = g.selectAll("line").data(links).join("line").attr("stroke", "#999");
const node = g.selectAll("circle").data(nodes).join("circle")
.attr("r", 8).attr("fill", "steelblue")
.call(d3.drag()
.on("start", (e) => { if (!e.active) simulation.alphaTarget(0.3).restart(); e.subject.fx = e.subject.x; e.subject.fy = e.subject.y; })
.on("drag", (e) => { e.subject.fx = e.x; e.subject.fy = e.y; })
.on("end", (e) => { if (!e.active) simulation.alphaTarget(0); e.subject.fx = null; e.subject.fy = null; }));
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);
});
Heatmap
// data: [{ row, column, value }, ...]
const rows = [...new Set(data.map(d => d.row))];
const cols = [...new Set(data.map(d => d.column))];
const xScale = d3.scaleBand().domain(cols).range([0, innerWidth]).padding(0.01);
const yScale = d3.scaleBand().domain(rows).range([0, innerHeight]).padding(0.01);
const colorScale = d3.scaleSequential(d3.interpolateYlOrRd).domain([0, d3.max(data, d => d.value)]);
g.selectAll("rect")
.data(data)
.join("rect")
.attr("x", d => xScale(d.column))
.attr("y", d => yScale(d.row))
.attr("width", xScale.bandwidth())
.attr("height", yScale.bandwidth())
.attr("fill", d => colorScale(d.value));
Interactivity
Tooltips
const tooltip = d3.select("body").append("div")
.style("position", "absolute")
.style("visibility", "hidden")
.style("background", "white")
.style("border", "1px solid #ddd")
.style("padding", "10px")
.style("border-radius", "4px")
.style("pointer-events", "none");
elements
.on("mouseover", (event, d) => tooltip.style("visibility", "visible").html(`<strong>${d.label}</strong><br/>Value: ${d.value}`))
.on("mousemove", (event) => tooltip.style("top", (event.pageY - 10) + "px").style("left", (event.pageX + 10) + "px"))
.on("mouseout", () => tooltip.style("visibility", "hidden"));
Zoom and Pan
const zoom = d3.zoom()
.scaleExtent([0.5, 10])
.on("zoom", (event) => g.attr("transform", event.transform));
svg.call(zoom);
Transitions & Animations
// Basic
circles.transition().duration(750).attr("r", 10);
// Staggered
circles.transition().delay((d, i) => i * 50).duration(500).attr("cy", d => yScale(d.value));
// Custom easing
circles.transition().duration(1000).ease(d3.easeBounceOut).attr("r", 10);
Responsive Sizing
function setupResponsiveChart(containerId, data) {
const container = document.getElementById(containerId);
const svg = d3.select(`#${containerId}`).append('svg');
const updateChart = () => {
const { width, height } = container.getBoundingClientRect();
svg.attr('width', width).attr('height', height);
drawChart(data, svg, width, height);
};
updateChart();
window.addEventListener('resize', updateChart);
return () => window.removeEventListener('resize', updateChart);
}
Scale Reference
| Scale | Use case |
|---|---|
d3.scaleLinear() | Continuous numeric data |
d3.scaleLog() | Exponential/logarithmic data |
d3.scaleTime() | Date/time axes |
d3.scaleBand() | Bar chart categories |
d3.scaleOrdinal() | Categorical colors |
d3.scaleSequential() | Single-hue color gradients |
d3.scaleDiverging() | Diverging color scales |
Best Practices
- Always validate data: filter nulls and NaN before binding
- Clear previous render with
svg.selectAll("*").remove()before redrawing - Use
.join()(enter/update/exit in one call) instead of separate selections - Add ARIA labels (
role="img",aria-label) for accessibility - For >1000 elements, consider Canvas rendering instead of SVG
- Debounce resize handlers to avoid excessive redraws
- Define color palettes upfront for visual consistency
Troubleshooting
| Problem | Solution |
|---|---|
| Axes not appearing | Check for NaN in scale domain; verify group transform |
| Transitions not working | Call .transition() before attribute changes |
| Responsive sizing broken | Use ResizeObserver or update SVG width/height on resize |
| Performance issues | Switch to Canvas, debounce resize, use .join() |
Related Skills
generate-asset-price-chart— OHLC candlestick chart generationtrading-indicators-from-price-data— Compute indicators to feed into chartsfree-geocoding-and-maps— Geographic data for map visualizations
Source
git clone https://github.com/besoeasy/open-skills/blob/main/skills/d3js-data-visualization/SKILL.mdView on GitHub Overview
D3.js Data Visualization lets you build sophisticated, interactive visuals by binding data to DOM elements and applying data-driven transformations. It enables publication-quality, fully customizable charts, graphs, networks, and maps with fine-grained control over styling, transitions, and interactions. Use it across vanilla JS, React, Vue, Svelte, or Node.js environments when standard libraries don’t meet your needs.
How This Skill Works
D3 binds data to DOM elements and uses scales, layouts, and generators to render visuals, enabling data-driven transformations. The common workflow starts with importing d3, setting up an SVG container, defining scales and axes, and then binding data to shapes using enter/update/exit patterns and transitions for smooth animations.
When to Use It
- Custom charts requiring unique visual encodings or layouts
- Interactive visualizations with pan, zoom, or brush behaviors
- Network/graph visualizations (force-directed, tree, hierarchy, chord diagrams)
- Geographic visualizations with custom projections
- Smooth, choreographed transitions and animations or novel chart types not available in standard libraries
Quick Start
- Step 1: Install or include D3 in your project (npm install d3 or via CDN)
- Step 2: Create an SVG container and import D3
- Step 3: Bind data to SVG elements, define scales, and render with transitions
Best Practices
- Plan the data model and binding approach before coding to keep rendering logic clean
- Use enter/update/exit selections to manage dynamic data efficiently
- Define clear scales and layouts before drawing shapes to ensure accuracy
- Leverage D3 transitions for smooth, choreographed animations
- Keep SVG visuals accessible with labels and keyboard support, and optimize performance for large datasets
Example Use Cases
- A bespoke bar chart with nonstandard categories for a business dashboard
- A pan/zoomable geographic map using custom projections
- A force-directed network graph illustrating social connections
- An interactive geographic choropleth with tooltips and transitions
- A data storytelling visualization that smoothly transitions between states