Line Chart

Lesson 2

d3.line() builds the SVG d attribute from data arrays. The stroke-dashoffset reveal trick: set stroke-dasharray and stroke-dashoffset to the path’s total length, then tween stroke-dashoffset to 0. d3.curveMonotoneX produces smooth monotone curves.

Source Code script.js
const data = [
  { week: 'W1',  temp: 3 },
  { week: 'W2',  temp: 5 },
  { week: 'W3',  temp: 2 },
  { week: 'W4',  temp: 8 },
  { week: 'W5',  temp: 12 },
  { week: 'W6',  temp: 15 },
  { week: 'W7',  temp: 18 },
  { week: 'W8',  temp: 22 },
  { week: 'W9',  temp: 19 },
  { week: 'W10', temp: 24 },
];

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.scalePoint()
  .domain(data.map(d => d.week))
  .range([0, width])
  .padding(0.5);

const maxTemp = d3.max(data, d => d.temp);
const y = d3.scaleLinear()
  .domain([0, maxTemp + 5])
  .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}°C`)
  );

// Area generator
const areaGen = d3.area()
  .x(d => x(d.week))
  .y0(height)
  .y1(d => y(d.temp))
  .curve(d3.curveCatmullRom);

// Line generator
const lineGen = d3.line()
  .x(d => x(d.week))
  .y(d => y(d.temp))
  .curve(d3.curveCatmullRom);

// Area path (no animation needed — reveals with line)
svg.append('path')
  .datum(data)
  .attr('class', 'area')
  .attr('d', areaGen);

// Line path
const linePath = svg.append('path')
  .datum(data)
  .attr('class', 'line')
  .attr('d', lineGen);

// Animate line using stroke-dashoffset trick
const totalLength = linePath.node().getTotalLength();

linePath
  .attr('stroke-dasharray', totalLength)
  .attr('stroke-dashoffset', totalLength)
  .transition()
  .duration(1200)
  .ease(d3.easeInOut)
  .attr('stroke-dashoffset', 0);

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

// Dots at each data point
svg.selectAll('.dot')
  .data(data)
  .join('circle')
  .attr('class', 'dot')
  .attr('cx', d => x(d.week))
  .attr('cy', d => y(d.temp))
  .attr('r', 4)
  .on('mouseover', function (event, d) {
    tooltip
      .style('opacity', 1)
      .html(`${d.week} · ${d.temp}°C`);
  })
  .on('mousemove', function (event) {
    tooltip
      .style('left', `${event.pageX + 12}px`)
      .style('top', `${event.pageY - 28}px`);
  })
  .on('mouseout', function () {
    tooltip.style('opacity', 0);
  });