agentleFS
allanninalallanninal/claude-code-skills/skills/d3js-visualization/SKILL.md

d3js-visualization

Skill1 starsChanged 9 months agoMITView on GitHub

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

Files
SKILL.md

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

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

Reviews can't be read right now.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.