d3js-data-visualization

Create interactive, custom data visualizations using d3.js — including charts, graphs, network diagrams, and geographic maps. Use when you need fine-grained control over visual elements, transitions, or interactions beyond what standard charting libraries offer, in any JavaScript environment (vanilla JS, React, Vue, Svelte, etc.).

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "d3js-data-visualization" with this command: npx skills add besoeasy/open-skills/besoeasy-open-skills-d3js-data-visualization

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 Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

news-aggregation

No summary provided by upstream source.

Repository SourceNeeds Review
General

anonymous-file-upload

No summary provided by upstream source.

Repository SourceNeeds Review
General

free-geocoding-and-maps

No summary provided by upstream source.

Repository SourceNeeds Review