Agent Skill
2/7/2026

d3js-visualization

Create interactive data visualizations using D3.js. Use when building charts, graphs, maps, or custom visualizations for dashboards and data analysis.

A
allanninal
0GitHub Stars
1Views
npx skills add allanninal/claude-code-skills

SKILL.md

Named3js-visualization
DescriptionCreate interactive data visualizations using D3.js. Use when building charts, graphs, maps, or custom visualizations for dashboards and data analysis.

name: d3js-visualization description: Create interactive data visualizations using D3.js. Use when building charts, graphs, maps, or custom visualizations for dashboards and data analysis.

D3.js Data Visualization

When to Use This Skill

  • Creating interactive charts and graphs
  • Building custom data visualizations
  • Implementing dashboards
  • Visualizing hierarchical or network data
  • Creating animated transitions
  • Building geographic visualizations

Core Concepts

Selection & Data Binding

import * as d3 from 'd3';

// Select elements
const svg = d3.select('#chart');
const circles = svg.selectAll('circle');

// Data binding (the enter-update-exit pattern)
const data = [10, 20, 30, 40, 50];

const circles = svg.selectAll('circle')
  .data(data)
  .join(
    enter => enter.append('circle')
      .attr('cy', 50)
      .attr('r', 0)
      .call(enter => enter.transition()
        .attr('r', d => d)),
    update => update
      .call(update => update.transition()
        .attr('r', d => d)),
    exit => exit
      .call(exit => exit.transition()
        .attr('r', 0)
        .remove())
  )
  .attr('cx', (d, i) => i * 60 + 30)
  .attr('fill', 'steelblue');

Common Charts

Bar Chart

function barChart(container, data, { width = 600, height = 400, margin = { top: 20, right: 20, bottom: 30, left: 40 } } = {}) {
  const svg = d3.select(container)
    .append('svg')
    .attr('viewBox', [0, 0, width, height]);

  const x = d3.scaleBand()
    .domain(data.map(d => d.name))
    .range([margin.left, width - margin.right])
    .padding(0.1);

  const y = d3.scaleLinear()
    .domain([0, d3.max(data, d => d.value)])
    .nice()
    .range([height - margin.bottom, margin.top]);

  // Bars
  svg.selectAll('rect')
    .data(data)
    .join('rect')
    .attr('x', d => x(d.name))
    .attr('y', d => y(d.value))
    .attr('width', x.bandwidth())
    .attr('height', d => y(0) - y(d.value))
    .attr('fill', 'steelblue');

  // X axis
  svg.append('g')
    .attr('transform', `translate(0,${height - margin.bottom})`)
    .call(d3.axisBottom(x));

  // Y axis
  svg.append('g')
    .attr('transform', `translate(${margin.left},0)`)
    .call(d3.axisLeft(y));

  return svg.node();
}

Line Chart

function lineChart(container, data, { width = 600, height = 400, margin = { top: 20, right: 20, bottom: 30, left: 40 } } = {}) {
  const svg = d3.select(container)
    .append('svg')
    .attr('viewBox', [0, 0, width, height]);

  const x = d3.scaleTime()
    .domain(d3.extent(data, d => d.date))
    .range([margin.left, width - margin.right]);

  const y = d3.scaleLinear()
    .domain([0, d3.max(data, d => d.value)])
    .nice()
    .range([height - margin.bottom, margin.top]);

  const line = d3.line()
    .x(d => x(d.date))
    .y(d => y(d.value))
    .curve(d3.curveMonotoneX);

  // Line path
  svg.append('path')
    .datum(data)
    .attr('fill', 'none')
    .attr('stroke', 'steelblue')
    .attr('stroke-width', 2)
    .attr('d', line);

  // Dots
  svg.selectAll('circle')
    .data(data)
    .join('circle')
    .attr('cx', d => x(d.date))
    .attr('cy', d => y(d.value))
    .attr('r', 4)
    .attr('fill', 'steelblue');

  // Axes
  svg.append('g')
    .attr('transform', `translate(0,${height - margin.bottom})`)
    .call(d3.axisBottom(x));

  svg.append('g')
    .attr('transform', `translate(${margin.left},0)`)
    .call(d3.axisLeft(y));

  return svg.node();
}

Pie/Donut Chart

function pieChart(container, data, { width = 400, height = 400, innerRadius = 0 } = {}) {
  const radius = Math.min(width, height) / 2;

  const svg = d3.select(container)
    .append('svg')
    .attr('viewBox', [-width / 2, -height / 2, width, height]);

  const color = d3.scaleOrdinal()
    .domain(data.map(d => d.name))
    .range(d3.schemeCategory10);

  const pie = d3.pie()
    .value(d => d.value)
    .sort(null);

  const arc = d3.arc()
    .innerRadius(innerRadius)
    .outerRadius(radius - 10);

  const labelArc = d3.arc()
    .innerRadius(radius * 0.6)
    .outerRadius(radius * 0.6);

  // Slices
  svg.selectAll('path')
    .data(pie(data))
    .join('path')
    .attr('d', arc)
    .attr('fill', d => color(d.data.name))
    .attr('stroke', 'white')
    .attr('stroke-width', 2);

  // Labels
  svg.selectAll('text')
    .data(pie(data))
    .join('text')
    .attr('transform', d => `translate(${labelArc.centroid(d)})`)
    .attr('text-anchor', 'middle')
    .text(d => d.data.name);

  return svg.node();
}

Scatter Plot

function scatterPlot(container, data, { width = 600, height = 400, margin = { top: 20, right: 20, bottom: 30, left: 40 } } = {}) {
  const svg = d3.select(container)
    .append('svg')
    .attr('viewBox', [0, 0, width, height]);

  const x = d3.scaleLinear()
    .domain(d3.extent(data, d => d.x))
    .nice()
    .range([margin.left, width - margin.right]);

  const y = d3.scaleLinear()
    .domain(d3.extent(data, d => d.y))
    .nice()
    .range([height - margin.bottom, margin.top]);

  const color = d3.scaleOrdinal(d3.schemeCategory10);

  // Points
  svg.selectAll('circle')
    .data(data)
    .join('circle')
    .attr('cx', d => x(d.x))
    .attr('cy', d => y(d.y))
    .attr('r', 5)
    .attr('fill', d => color(d.category))
    .attr('opacity', 0.7);

  // Axes
  svg.append('g')
    .attr('transform', `translate(0,${height - margin.bottom})`)
    .call(d3.axisBottom(x));

  svg.append('g')
    .attr('transform', `translate(${margin.left},0)`)
    .call(d3.axisLeft(y));

  return svg.node();
}

Interactivity

Tooltips

const tooltip = d3.select('body')
  .append('div')
  .attr('class', 'tooltip')
  .style('position', 'absolute')
  .style('visibility', 'hidden')
  .style('background', 'white')
  .style('padding', '8px')
  .style('border-radius', '4px')
  .style('box-shadow', '0 2px 4px rgba(0,0,0,0.2)');

svg.selectAll('circle')
  .on('mouseover', (event, d) => {
    tooltip
      .style('visibility', 'visible')
      .html(`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 & Pan

const zoom = d3.zoom()
  .scaleExtent([0.5, 10])
  .on('zoom', (event) => {
    g.attr('transform', event.transform);
  });

svg.call(zoom);

// Reset zoom
d3.select('#reset').on('click', () => {
  svg.transition()
    .duration(750)
    .call(zoom.transform, d3.zoomIdentity);
});

Brush Selection

const brush = d3.brush()
  .extent([[0, 0], [width, height]])
  .on('end', (event) => {
    if (!event.selection) return;
    const [[x0, y0], [x1, y1]] = event.selection;

    const selected = data.filter(d =>
      x(d.x) >= x0 && x(d.x) <= x1 &&
      y(d.y) >= y0 && y(d.y) <= y1
    );

    console.log('Selected:', selected);
  });

svg.append('g')
  .call(brush);

Transitions & Animation

// Animated entry
svg.selectAll('rect')
  .data(data)
  .join('rect')
  .attr('x', d => x(d.name))
  .attr('y', height - margin.bottom)
  .attr('width', x.bandwidth())
  .attr('height', 0)
  .transition()
  .duration(1000)
  .delay((d, i) => i * 100)
  .ease(d3.easeElastic)
  .attr('y', d => y(d.value))
  .attr('height', d => y(0) - y(d.value));

Responsive Design

function responsiveChart(container, data) {
  const container = d3.select(container);
  let svg;

  function render() {
    const { width } = container.node().getBoundingClientRect();
    const height = width * 0.6;

    container.selectAll('svg').remove();
    svg = container.append('svg')
      .attr('width', width)
      .attr('height', height);

    // Draw chart with current dimensions
    drawChart(svg, data, width, height);
  }

  render();

  // Debounced resize handler
  let resizeTimer;
  window.addEventListener('resize', () => {
    clearTimeout(resizeTimer);
    resizeTimer = setTimeout(render, 100);
  });
}

Scale Reference

ScaleUse Case
scaleLinearContinuous numeric data
scaleTimeDate/time data
scaleBandCategorical (bar charts)
scaleOrdinalCategorical colors
scaleLogExponential data
scaleSqrtArea-based encoding

Best Practices

  • Use viewBox for responsive SVGs
  • Implement proper enter/update/exit patterns
  • Add ARIA labels for accessibility
  • Use transitions for state changes
  • Debounce resize handlers
  • Test with different data sizes
  • Provide fallback for no-JS environments
Skills Info
Original Name:d3js-visualizationAuthor:allanninal