Bar Chart

Lesson 1

d3.scaleBand() maps category names to X positions; d3.scaleLinear() maps values to bar heights. Bars start at height 0 and transition to full height via selection.transition().duration(600). Tooltips use position: fixed and update position on mousemove.

Source Code script.js
const data = [
  { month: 'Jan', value: 42 },
  { month: 'Feb', value: 58 },
  { month: 'Mar', value: 75 },
  { month: 'Apr', value: 63 },
  { month: 'May', value: 91 },
  { month: 'Jun', value: 84 },
  { month: 'Jul', value: 110 },
  { month: 'Aug', value: 97 },
  { month: 'Sep', value: 88 },
  { month: 'Oct', value: 120 },
  { month: 'Nov', value: 134 },
  { month: 'Dec', value: 156 },
];

const margin = { top: 20, right: 20, bottom: 40, left: 50 };
const chartEl = document.getElementById('chart');
const totalWidth = chartEl.clientWidth || 720;
const width = totalWidth - margin.left - margin.right;
const height = 360 - margin.top - margin.bottom;

const svg = d3.select('#chart')
  .append('svg')
  .attr('width', totalWidth)
  .attr('height', 360)
  .append('g')
  .attr('transform', `translate(${margin.left},${margin.top})`);

const x = d3.scaleBand()
  .domain(data.map(d => d.month))
  .range([0, width])
  .padding(0.3);

const maxValue = d3.max(data, d => d.value);
const y = d3.scaleLinear()
  .domain([0, maxValue + 20])
  .range([height, 0]);

// Horizontal grid lines
svg.append('g')
  .attr('class', 'grid')
  .call(
    d3.axisLeft(y)
      .tickSize(-width)
      .tickFormat('')
  );

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

// Y axis
svg.append('g')
  .attr('class', 'axis axis--y')
  .call(
    d3.axisLeft(y)
      .ticks(6)
      .tickSizeOuter(0)
      .tickFormat(d => `$${d}k`)
  );

// Bars — start at y=height with height=0, then transition
const bars = svg.selectAll('.bar')
  .data(data)
  .join('rect')
  .attr('class', 'bar')
  .attr('x', d => x(d.month))
  .attr('width', x.bandwidth())
  .attr('y', height)
  .attr('height', 0);

bars.transition()
  .duration(600)
  .delay((d, i) => i * 30)
  .attr('y', d => y(d.value))
  .attr('height', d => height - y(d.value));

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

svg.selectAll('.bar')
  .on('mouseover', function (event, d) {
    tooltip
      .style('opacity', 1)
      .html(`${d.month} · $${d.value}k`);
  })
  .on('mousemove', function (event) {
    tooltip
      .style('left', `${event.pageX + 12}px`)
      .style('top', `${event.pageY - 28}px`);
  })
  .on('mouseout', function () {
    tooltip.style('opacity', 0);
  });