← Back to Module 09

Top Pages Bar Chart — D3.js

The same pageview data from Module 01's first-bar-chart.html, now rendered with D3 and SVG instead of Canvas. Each bar is a real <rect> element in the DOM — hover to see the tooltip.

Step-by-Step Code Walkthrough

Step 1: Define the Data and Margins

const data = [ { page: '/home', views: 1250 }, { page: '/about', views: 430 }, { page: '/products', views: 890 }, { page: '/contact', views: 320 }, { page: '/blog', views: 675 } ]; const margin = { top: 20, right: 20, bottom: 40, left: 50 }; const width = 600 - margin.left - margin.right; // 530 const height = 400 - margin.top - margin.bottom; // 340
The margin convention reserves space for axes and labels. The chart area is the inner rectangle.

Step 2: Create the SVG and Chart Group

const svg = d3.select('#chart') .append('svg') .attr('width', width + margin.left + margin.right) .attr('height', height + margin.top + margin.bottom); // Shift everything by the margins const g = svg.append('g') .attr('transform', `translate(${margin.left}, ${margin.top})`);
The <g> group acts as a local coordinate system. All coordinates inside it are relative to the chart area, not the SVG edges.

Step 3: Create the Scales

// X scale: page names → horizontal band positions const xScale = d3.scaleBand() .domain(data.map(d => d.page)) .range([0, width]) .padding(0.2); // Y scale: pageview counts → vertical pixel positions const yScale = d3.scaleLinear() .domain([0, d3.max(data, d => d.views)]) .nice() // round to 1300 .range([height, 0]); // inverted! // Color scale: page names → distinct colors const colorScale = d3.scaleOrdinal() .domain(data.map(d => d.page)) .range(['#16a085', '#2980b9', '#8e44ad', '#e67e22', '#e74c3c']);
.nice() rounds the domain max from 1250 to 1300, giving cleaner axis tick values. The y-range is inverted because SVG's y=0 is the top.

Step 4: Draw the Axes

// X axis at the bottom of the chart area g.append('g') .attr('transform', `translate(0, ${height})`) .call(d3.axisBottom(xScale)); // Y axis on the left side g.append('g') .call(d3.axisLeft(yScale));
D3 generates all the tick marks, labels, and axis lines automatically from the scale's domain and range.

Step 5: Bind Data and Draw Bars

g.selectAll('rect') .data(data) .join('rect') .attr('x', d => xScale(d.page)) .attr('y', d => yScale(d.views)) .attr('width', xScale.bandwidth()) .attr('height', d => height - yScale(d.views)) .attr('fill', d => colorScale(d.page));
This is the core D3 pattern. .data(data) binds the array; .join('rect') creates one <rect> per data item. Each attribute is a function of the bound datum d. The bar's y position is yScale(d.views) (top edge), and its height extends down to the baseline at height.

Step 6: Add Value Labels

g.selectAll('.bar-label') .data(data) .join('text') .attr('class', 'bar-label') .attr('x', d => xScale(d.page) + xScale.bandwidth() / 2) .attr('y', d => yScale(d.views) - 5) .attr('text-anchor', 'middle') .attr('fill', d => colorScale(d.page)) .text(d => d.views.toLocaleString());
A second data join creates <text> elements, one per bar, positioned 5px above each bar's top edge.