Pie Chart

Lesson 3

d3.pie() computes arc start/end angles from values. d3.arc() generates the path d attribute for each slice. On enter, arcs tween from zero-angle via attrTween('d', ...) using an interpolated arc function. An inner radius > 0 creates the donut hole.

Source Code script.js
const data = [
  { label: 'Chrome',  value: 65.1 },
  { label: 'Safari',  value: 18.7 },
  { label: 'Edge',    value: 5.3  },
  { label: 'Firefox', value: 4.0  },
  { label: 'Other',   value: 6.9  },
];

// Monochrome shades from dark to light
const colors = ['#222', '#444', '#666', '#888', '#aaa'];

const size = 280;
const radius = 120;
const innerRadius = 70;

const tooltip = d3.select('#tooltip');

// Build SVG
const svg = d3.select('#chart')
  .append('svg')
  .attr('width', size)
  .attr('height', size);

const g = svg.append('g')
  .attr('transform', `translate(${size / 2}, ${size / 2})`);

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

// Arc generator
const arc = d3.arc()
  .innerRadius(innerRadius)
  .outerRadius(radius);

const arcs = pie(data);

// Draw arcs with animation
const arcGroups = g.selectAll('.arc')
  .data(arcs)
  .enter()
  .append('g')
  .attr('class', 'arc');

arcGroups.append('path')
  .attr('fill', (d, i) => colors[i])
  .transition()
  .duration(800)
  .delay((d, i) => i * 60)
  .attrTween('d', function(d) {
    const interpolate = d3.interpolate({ startAngle: 0, endAngle: 0 }, d);
    return function(t) {
      return arc(interpolate(t));
    };
  });

// Tooltip interactions on the group element
arcGroups
  .style('cursor', 'pointer')
  .on('mouseover', function(event, d) {
    tooltip
      .style('opacity', 1)
      .html(`${d.data.label}   ${d.data.value}%`);
  })
  .on('mousemove', function(event) {
    tooltip
      .style('left', (event.clientX + 14) + 'px')
      .style('top', (event.clientY - 28) + 'px');
  })
  .on('mouseout', function() {
    tooltip.style('opacity', 0);
  });

// Legend
const legend = d3.select('#legend');

data.forEach((d, i) => {
  const item = legend.append('div').attr('class', 'legend-item');
  item.append('div')
    .attr('class', 'legend-swatch')
    .style('background', colors[i]);
  item.append('span')
    .attr('class', 'legend-label')
    .text(`${d.label} (${d.value}%)`);
});