Get the FREE Ultimate OpenClaw Setup Guide →

d3js-data-visualization

npx machina-cli add skill besoeasy/open-skills/d3js-data-visualization --openclaw
Files (1)
SKILL.md
8.9 KB

D3.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

ScaleUse 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

ProblemSolution
Axes not appearingCheck for NaN in scale domain; verify group transform
Transitions not workingCall .transition() before attribute changes
Responsive sizing brokenUse ResizeObserver or update SVG width/height on resize
Performance issuesSwitch to Canvas, debounce resize, use .join()

Related Skills

  • generate-asset-price-chart — OHLC candlestick chart generation
  • trading-indicators-from-price-data — Compute indicators to feed into charts
  • free-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

  1. Step 1: Install or include D3 in your project (npm install d3 or via CDN)
  2. Step 2: Create an SVG container and import D3
  3. 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

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers