Skip to content

D3.js - Graphs

D3.js - SVG Coordinate Space and Basic Charting

Section titled “D3.js - SVG Coordinate Space and Basic Charting”

Understanding the SVG coordinate space is crucial for drawing with D3.js. While mathematical Cartesian coordinates typically have (0,0) at the bottom-left with Y increasing upwards, SVG’s coordinate system is different.

The SVG coordinate space has two key characteristics:

  • The origin (0,0) is at the top-left corner of the SVG viewport.
  • The Y-coordinate increases downwards, while the X-coordinate increases to the right (similar to mathematical graphs).

So, if you draw a circle at cx=30, cy=30, it will be 30 units to the right and 30 units down from the top-left corner of the SVG element.

// Create an SVG container with a border to visualize its boundaries
const svgContainer = d3.select("body")
.append("svg")
.attr("width", 200)
.attr("height", 200)
.style("border", "1px solid black");
// A circle at (30,30) within this SVG
svgContainer.append("circle")
.attr("cx", 30)
.attr("cy", 30)
.attr("r", 20)
.style("fill", "steelblue");

A line graph is a common visualization that shows trends over time or a continuous variable. It typically has X and Y axes, with data points connected by lines.

Let’s create a line graph showing a fictional population growth. First, prepare a population-data.csv file:

year,population
2006,40
2008,45
2010,48
2012,51
2014,53
2016,57
2017,62

Here are the steps to draw the line graph:

Create an HTML file and add basic styles for the line and axes.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3 Line Graph</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 10px;
}
</style>
</head>
<body>
<svg id="line-chart" width="600" height="400"></svg>
<script>
// D3 code will go here
</script>
</body>
</html>

Use the margin convention to create space for axes.

const margin = {top: 20, right: 30, bottom: 40, left: 50};
const width = 600 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
const svg = d3.select("#line-chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);

Scales map data values to pixel positions. For ‘year’, we’ll use d3.scaleTime (assuming years are parsed as dates). For ‘population’, d3.scaleLinear.

const xScale = d3.scaleTime().range([0, width]);
const yScale = d3.scaleLinear().range([height, 0]); // Note: 0 is at the bottom for y-axis mapping

d3.line() will generate the path string for our line.

const lineGenerator = d3.line()
.x(d => xScale(d.year))
.y(d => yScale(d.population));

Load population-data.csv and parse the data types.

d3.csv("population-data.csv", d => {
// Custom row accessor to parse data
return {
year: new Date(+d.year, 0, 1), // Convert year to a Date object (Jan 1st of that year)
population: +d.population // Convert population to a number
};
}).then(data => {
// Steps 6-9 will be inside this .then() block
console.log(data);
// Step 6: Set Scale Domains
xScale.domain(d3.extent(data, d => d.year));
yScale.domain([0, d3.max(data, d => d.population)]);
// Step 7: Draw the Line
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", lineGenerator);
// Step 8: Add X-Axis
svg.append("g")
.attr("class", "axis x-axis")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(xScale).ticks(d3.timeYear.every(2)).tickFormat(d3.timeFormat("%Y")));
// X-axis Label
svg.append("text")
.attr("transform", `translate(${width / 2}, ${height + margin.bottom - 5})`)
.style("text-anchor", "middle")
.text("Year");
// Step 9: Add Y-Axis
svg.append("g")
.attr("class", "axis y-axis")
.call(d3.axisLeft(yScale));
// Y-axis Label
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Population");
}).catch(error => {
console.error("Error loading or processing data:", error);
});

Make sure population-data.csv is in the same directory as your HTML file, or adjust the path. When you open the HTML, you’ll see the line graph.

  • SVG Viewport & Grouping: Using <svg> and <g> elements, and the transform attribute.
  • Margins: The margin convention for chart layout.
  • Scales (d3-scale): Mapping data values to visual representations (pixels). d3.scaleTime for time-based data, d3.scaleLinear for quantitative data.
  • Axes (d3-axis): Generating human-readable axis labels and ticks from scales.
  • Shapes (d3-shape): Using d3.line() to generate SVG path data from an array of points.
  • Data Loading (d3-fetch): Asynchronously loading and parsing CSV data.

This example provides a foundation for building more complex charts. You can extend it with tooltips, interactivity, multiple lines, etc. For detailed information on SVG, refer to the MDN SVG Tutorial.