12 Line charts
Read: IDVW2, Chapter 11 Using Paths
12.2 SVG <line>
element
(Use for two points only.)
<line x1="0" y1="80" x2="100" y2="20" stroke="black" />
const x1 = 0;
const y1 = 80;
const x2 = 100;
const y2 = 20;
.select("svg")
d3.append("line")
.attr("x1", x1)
.attr("x2", x2)
.attr("y1", y1)
.attr("y2", y2);
12.3 SVG <path>
element
(Use if you have more than two points.)
<svg width = "500" height = "400">
<path d="M 50 400 L 100 300 L 150 300 L 200 33 L 250 175
L 300 275 L 350 250 L 400 125" fill="none"
stroke="red" stroke-width="5">
</path>
</svg>
d
attribute:
M = move to
L = line to
More options: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
12.4 Data for line chart
Format that we have:
Day | High Temp |
---|---|
April 1 | 60 |
April 2 | 43 |
April 3 | 43 |
April 4 | 56 |
April 5 | 45 |
April 6 | 62 |
April 7 | 49 |
Format that we need looks something like this:
<path class="line" fill="none" d="M0,149.15254237288136L71.42857142857143,264.40677966101697L142.85714285714286,264.40677966101697L214.28571428571428,176.27118644067798L285.7142857142857,250.84745762711864L357.14285714285717,135.59322033898303L428.57142857142856,223.72881355932205"></path>
12.5 Create a line generator
Expects data in an array of 2-dimensional arrays, that is, an array of (x,y) pairs:
const dataset = [ [0, 60], [1, 43], [2, 43], [3, 56], [4, 45], [5, 62], [6, 49] ];
const mylinegen = d3.line()
Test it in the Console:
mylinegen(dataset);
Add an ordinal scale for x:
const xScale = d3.scaleBand()
.domain(d3.range(dataset.length))
.range([0, 500])
… and a linear scale for y:
const yScale = d3.scaleLinear()
.domain([d3.min(dataset, d => d[1]) - 20,
.max(dataset, d => d[1]) + 20])
d3.range([400, 0]);
*Why d[1]
instead of d
? (See p. 122)
Add accessor functions .x()
and .y()
:
mylinegen.x(d => xScale(d[0]))
.y(d => yScale(d[1]));
Test again:
mylinegen(dataset);
Now let’s add a <path>
element with that d
attribute: (this step is just for learning purposes…)
const mypath = mylinegen(dataset);
.select("svg").append("path").attr("d", mypath)
d3.attr("fill", "none").attr("stroke", "red")
.attr("stroke-width", "5");
12.6 Put the line generator to work
Now let’s do it the direct way: bind the datum and calculate the path in one step:
.select("svg").append("path")
d3.datum(dataset)
.attr("d", mylinegen)
.attr("fill", "none")
.attr("stroke", "teal")
.attr("stroke-width", "5");
Finally, we’ll add a class and style definitions:
<style>
.linestyle {
fill: none;
teal;
stroke: 5px;
stroke-width:
}</style>
The append("path")
line becomes:
.append("path")
svg.datum(dataset)
.attr("d", mylinegen)
.attr("class", "linestyle");
Putting it all together, we have:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Line generator</title>
<script src="https://d3js.org/d3.v7.js"></script>
<style type ="text/css">
.linestyle {
fill: none;
teal;
stroke: 5px;
stroke-width:
}</style>
</head>
<body>
<script>
const w = 500;
const h = 400;
const svg = d3.select("svg#noaxes");
const dataset = [ [0, 60], [1, 43], [2, 43], [3, 56],
4, 45], [5, 62], [6, 49] ];
[let xScale = d3.scaleBand()
.domain(d3.range(dataset.length))
.range([0, w]);
let yScale = d3.scaleLinear()
.domain([d3.min(dataset, d => d[1]) - 20,
.max(dataset, d => d[1]) + 20])
d3.range([h, 0]);
const mylinegen = d3.line()
.x(d => xScale(d[0]))
.y(d => yScale(d[1]));
.append("path")
svg.datum(dataset)
.attr("d", mylinegen)
.attr("class", "linestyle");
</script>
</body>
</html>
And another example with axes:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<body>
<svg id="withaxes" width="600" height="400"></svg>
<script>
const svg2 = d3.select("svg#withaxes")
const margin = {top: 20, right: 50, bottom: 30, left: 50}
const width = +svg2.attr("width") - margin.left - margin.right
const height = +svg2.attr("height") - margin.top - margin.bottom
const g = svg2.append("g").attr("transform", `translate(${margin.left}, ${margin.top})`);
const parseTime = d3.timeParse("%d-%b-%y");
= d3.scaleTime().range([0, width]);
xScale
= d3.scaleLinear()
yScale .domain([20, 80])
.range([height, 0]);
const xAxis = d3.axisBottom()
.scale(xScale)
.tickFormat(d3.timeFormat("%Y-%m-%d"));
const line = d3.line()
.x(d => xScale(d.date))
.y(d => yScale(d.high));
const data =
"date":"1-Apr-18","high":60},
[{"date":"2-Apr-18","high":43},
{"date":"3-Apr-18","high":43},
{"date":"4-Apr-18","high":56},
{"date":"5-Apr-18","high":45},
{"date":"6-Apr-18","high":62},
{"date":"7-Apr-18","high":49}];
{.forEach(function(d) {
data.date = parseTime(d.date);
d;
})
xScale.domain(d3.extent(data, d => d.date));
.append("g")
g.attr("transform", `translate(0, ${height})`)
.call(xAxis);
.append("g")
g.call(d3.axisLeft(yScale))
.append("path")
g.datum(data)
.attr("class", "line")
.attr("fill", "none")
.attr("stroke", "red")
.attr("stroke-width", 1.5)
.attr("d", line);
</script>
</body>
</html>
(Also uses: d3.timeParse()
and JavaScript Array.foreach()
)
12.7 Additional Resources
Multiple Time Series in D3 by Eric Boxer (EDAV 2018)