D3.js version 5 Prediction Line Chart

Today I am writing about the Prediction Line Chart I have created in D3.js version 5. Here I provide the full code how to create it yourself and how it works.

What is a Prediction Line Chart?

A Prediction Line Chart is an extension of a basic line chart where the current data is charted alongside a couple predictions for its movement.

The use of these graphs is typically to show the current data and allow comparison to the current predictions.

Preparing the Prediction Data

First I prepare the data to be used for the Prediction Line chart. There are two parts to the data.

The first part is the current data that the predictions are made from. This will form the first half of the chart.

The second part is the set of predictions. These comprise of a value and name of the person who guessed it.

var data = [
    {"date": new Date(2017, 8, 1), "val": 400 },
    {"date": new Date(2017, 10, 1), "val": 500 },
    {"date": new Date(2018, 2, 1), "val": 600 },
    {"date": new Date(2018, 7, 1), "val": 700 },
    {"date": new Date(2018, 10, 29), "val": 800 }
];

var predictionValueStart = 800;
var predictionStartDate = new Date(2018, 10, 29);
var predictionEndDate = new Date(2018, 12, 24);
var predictions = [
    {"person": "Bob", "val": 90},
    {"person": "Alice", "val": 999},
    {"person": "Clarence", "val": 850}
];

var predictionDataSeries = [];
predictions.forEach(function(p) {
    predictionDataSeries.push([
        {"date": predictionStartDate, "val": predictionValueStart},
        {"date": predictionEndDate, "val": p.val}
    ]);
});

In addition to storing the current data and predictions, the predication start date, end date, and start value are also saved to variables.

The final foreach loop in this code snippet transforms the predictions into a more useful format for d3 to process.

Defining the Charts basic size parameters

To make it easy to modify the size of the chart I am adding its basic parameters as variables.

var s = d3.select("#mainSvg");
var height = 250;
var width = 700;
var margin = {top: 20, right: 30, bottom: 30, left: 40};

The SVG selector will be used later to add elements to the chart. The margin variable is also used to add some padding around the graph.

Scaling the data to fit the chart

To ensure that all the data appears on the chart two scaling functions are created. These are used to map a value on the chart to a location on the screen.

var y = d3.scaleLinear()
    .domain([d3.min(data, function(d) { return d.val; }), d3.max(predictions, function(d) { return d.val; })]).nice()
    .range([height - margin.bottom, margin.top]);

var x = d3.scaleTime()
    .domain([d3.min(data.map(function(d) { return d.date;})), predictionEndDate])
    .range([margin.left, width - margin.right])
;

The first is the linear scale of the y axis which holds the prediction data. The minimum is scaled to the minimum value of the data. The maximum is chosen using the largest value of the predictions.

The x axis uses a d3 time scale to map our dates onto the chart. Again the minimum is taken from the minimum date from the data. However the maximum is taken from prediction end date as this is fixed.

The ranges of these functions have been taken from the height and widths of the chart minus the margin regions. This will ensure that our scaling places the chart nicely in the middle of the svg.

Creating the d3 line function

The line function allows easy mapping from the data values to something that produces path information. This allows us to create a path element and use the line function to generate the line.

var line = d3.line()
    .defined(function(d) { return !isNaN(d.val); }) // Defined lets you decide whether this value is valid
    .x(function(d) { return x(d.date); })
    .y(function(d) { return y(d.val); })
;

Here .defined() is used to ignore any data that isnt valid for the line. While the data here is clean it is good practice to use.

The x and y functions use the previously created scales to turn the data into the pixel locations on screen. Here I use the values of d.date for the x values and d.val for the y values.

Creating the X and Y axis functions

D3 includes a wide range of helper functions including those to help deal with drawing graph axis.

var yAxis = function(g) {
    g.attr("transform", "translate(" + margin.left + " ,0)")
        .call(d3.axisLeft(y))
        .call(function(g) { g.select(".domain").remove(); })
        .call(function(g) {
            g.select(".tick:last-of-type text").clone()
                .attr("x", 3)
                .attr("text-anchor", "start")
                .attr("font-weight", "bold")
                .text(data.y)
        });
};

var xAxis = function(d) {
    d.attr("transform", "translate(0, " + (height - margin.bottom) + ")")
        .call(d3.axisBottom(x).ticks(width / 80).tickSizeOuter(0))
};

The main helper functions I am using are d3.axisLeft and d3.axisBottom. Here I use ticks() to define the number of ticks across the axis.

Creating the base data line

Once we have set up our helper functions we can draw the graph.

The first two calls append a grouping element and are drawn using the xAxis and yAxis functions.

s.append("g")
    .call(xAxis);

s.append("g")
    .call(yAxis);

s.append("path")
    .datum(data)
    .attr("fill", "none")
    .attr("stroke", "steelblue")
    .attr("stroke-width", 1.5)
    .attr("stroke-linejoin", "round")
    .attr("stroke-linecap", "round")
    .attr("d", line);

Finally the line is created by creating a path with the data in our data variable. The line function created earlier is used to lay out the data according to our scaling functions.

Creating the prediction lines

Once the main body of the graph is created the prediction lines are created.

var colours = d3.schemeCategory10;

predictionDataSeries.forEach(function(p, index) {
    s.append("path")
        .datum(p)
        .attr("fill", "none")
        .attr("stroke", colours[index])
        .attr("stroke-width", 1.5)
        .attr("stroke-linejoin", "round")
        .attr("stroke-linecap", "round")
        .attr("d", line);
})

The d3.schemeCategory10 variable holds 10 categorical colours that I am going to use to colour the predictions.

For each of the prediction data sets, a path is appended and styled. Again the d attribute of the line is created based on our line function created above.

Summary

Today we have went through the process of creating a prediction line chart to show how different predictions may be made for the data.

The full code for this is available on my website which includes a live example.

All my D3 examples and related blogposts are available by searching the d3.js keyword or by viewing all examples graphically on the d3 section of my website.

If you have any questions feel free to comment on the following blog post.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.