How to make interactive line chart in D3.js
By: Rohit Raj
Tags: Covid 19, D3js, JavaScript

D3.js is javascript library used to make interactive data driven charts. It can be used to make the coolest charts. It has a very steep learning curve. But once you understand the basics of D3.js, you can make the coolest charts work for you.

In my last article I wrote how to make plot of covid19 cases in India using matplotlib library. But charts made using matplotlib library are static. They can only contain only one story. Often there is more than one story to tell. User wants to interact with data to see more than one view.

There are many ways to make interactive interactive charts. Plotly library is one way to great way to make interactive chart in python. But I don’t want to restrict myself to standard charts possible in a library. So I learnt D3.js library.

Once you get past initial discomfort of learning the underpinning of D3 you can chose among thousands of chart types in D3 from D3 example galleries and modify them to your own needs.

I wanted to display district wise chart of covid cases. So I selected this line chart as the base.

This chart reads data from csv file and makes line chart for one category. It allows you to change category using drop down menu.

Javascript code for chart is given below:

Scalable Vector Graphics (SVG) are used to define shapes in browser. Instead of drawing images pixel by pixel, you can tell location and radius of circle to browser. Same is possible to be done for other shapes likes lines, arcs and many more. D3 library allows to assign shapes and attributes to your data. Further with enter and exit methods, it allows you to create spectacular transition with your data.

In the above code, lines 1 to 15 define svg element and its size in browser. D3.CSV reads the csv file given in the link and executes the code for the data.

For understanding D3 in depth your can go through the following link

I will only go through what changes I have done to the chart to make my chart.

This is the base chart at code give above:

I want to change the code so that it reads directly from JSON data available at Covid19india API. X axis needs to be changed from integer scale to datetime scale. In the current chart axes don’t update with change in data, so I changed the code to make the axes auto update with data. I also wanted to add a second filter so that we have one filter for districts and other for states.

D3.csv function reads csv file and converts data into list of dictionaries where csv headers are keys. Covid19india api district data api gives data in format where district updates are nested in states object and states object are further nested in top level.

So I parsed the json received from API as follows:

d3.json('districts_daily.json', function(data) {

var data2 = [];

for (state in data["districtsDaily"]) {

for (district in data["districtsDaily"][state]) {

for (day in data["districtsDaily"][state][district]) {

days = data["districtsDaily"][state][district][day]

var data3 = {};

data3.states = state;

data3.name = district;

data3.date = days['date'];

data3.active = days['active'];

data3.confirmed = days['confirmed'];

data3.deceased =days['deceased'];

data3.recovered = days['recovered'];

data2.push(data3)

}}}

D3 reads json data as string. You have to format it as number or date time format. So I formatted dates data as following

var parseTime = d3.timeParse("%Y-%m-%d"); data2.forEach(function(d) {

d.date = parseTime(d.date);

});

Foreach function of javascript applies datetime transformation to each value of date column.

I extracted list of states and districts using map function of javascript as follows:

var districtlist = d3.map(data2, function(d){return(d.name)}).keys();

var statelist = d3.map(data2, function(d){return(d.states)}).keys();

Map function of javascript extract states columns data and extracts its unique values.

I added drop down menu for district and states using following code:

// add the options to the button

d3.select("#districtButton")

.selectAll('option')

.data(districtlist)

.enter()

.append('option')

.text(function (d) { return d; }) // text showed in the menu

.attr("value", function (d) { return d; }) // corresponding value returned by the button d3.select("#stateButton")

.selectAll('myOptions')

.data(statelist)

.enter()

.append('option')

.text(function (d) { return d; }) // text showed in the menu

.attr("value", function (d) { return d; }) // corresponding value returned by the button

This creates a select element and associates it with data of districts and states that we extracted above.

We create date time x axis using following code:

var x = d3.scaleTime()

.domain([d3.min(firstdata, function(d) { return d.date; }), d3.max(firstdata, function(d) { return d.date; })])

.range([ 0, width ]);



svg.append("g")

.attr('class', 'xaxis')

.attr("transform", "translate(0," + height + ")")

.call(d3.axisBottom(x));

It creates datetime scale and uses that to scale x axis.

I added xlabel and ylabel using following code:

//add xlabel

// text label for the x axis

svg.append("text")

.attr("transform",

"translate(" + (width/2) + " ," +

(height + margin.top + 20) + ")")

.style("text-anchor", "middle")

.text("Date"); //add y label

// text label for the y axis

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("Confirmed coronavirus cases");

It appends text element to svg and translate it relative to position of axes.

I modified update function to modify axes when district is changed and show a tool tip:

// When the button is changed, run the updateChart function

d3.select("#districtButton").on("change", function(d) {

// recover the option that has been chosen

var selectedOption = d3.select(this).property("value")

// run the updateChart function with this selected option

update(selectedOption)

})

// A function that update the chart

function update(selectedGroup) { // Create new data with the selection?

var dataFilter = data2.filter(function(d){return d.name==selectedGroup}) // Scale X axis

x.domain([ d3.min(dataFilter, function(d) { return d.date; }), d3.max(dataFilter, function(d) { return d.date; })]); // Scale Y axis

y.domain([0, d3.max(dataFilter, function(d) { return +d.confirmed; })]); // Give these new data to update line

line

.datum(dataFilter)

.transition()

.duration(1000)

.attr("d", d3.line()

.x(function(d) { return x(d.date) })

.y(function(d) { return y(+d.confirmed) })

)

.attr("stroke", function(d){ return myColor(selectedGroup) }) //update x axis

svg.select('.xaxis')

.transition()

.duration(1000)

.call(d3.axisBottom(x)); //update y axis svg.select('.yaxis')

.transition()

.duration(1000)

.call(d3.axisLeft(y)); }

Every time district is changed, update function is called with the value of the district. It filters the data for district and assigns it to line and axis element.

Transition method allows method allows smooth transition due to impact of data.

I wanted to show tooltip on line. So I added following code in update function given above

// add the dots with tooltips

var dot = svg.selectAll("circle")

.data(dataFilter); dot.exit().remove(); dot.enter().append("circle")

.attr("r", 3)

.merge(dot)

.transition()

.duration(1000)

.attr("cx", function(d) { return x(d.date); })

.attr("cy", function(d) { return y(+d.confirmed); })

.on("mouseover", function(d) {

tip.transition()

.duration(200)

.style("opacity", .9);

tip.html(formatTime(d.date) + "<br/>" + d.confirmed)

.style("left", (d3.event.pageX) + "px")

.style("top", (d3.event.pageY - 28) + "px");

})

.on("mouseout", function(d) {

tip.transition()

.duration(500)

.style("opacity", 0);

});

Once data is assigned to a svg element, D3 returns enter, update and exit selection. In exit selection you define what happens to svg element which have now no data assigned to it.

So we remove dots no longer needed as they belong in exit selection using dot.exit().remove(). Remove method is applied to those elements which do not data assigned to it after updating data assigned to circles using data method.

Dot which have data assigned but have no circle belong to enter selection. We create new circle using append method on enter selection.

In D3 v4 update selection is created by using merge method on enter selection. Now you can change attributes of element which have been newly created using enter method and which already were there in html.

If you are using D3 v3, then using merge method is not required.

Now in merge method I assigned a html element to the circle which is activated on mouseover and is deactivated when mouse moves out of element.

I added a second update function which is triggered when state is changed:

// When the state button is changed, run the updategroup function

d3.select("#stateButton").on("change", function(d) {

// recover the option that has been chosen

var selectedOption = d3.select(this).property("value")

// run the updateChart function with this selected option

update2(selectedOption)

})

function update2(selectedGroup) {

var newgroup = data2.filter(function(d){return d.states==selectedGroup});

var newGroup = d3.map(newgroup, function(d){return(d.name)}).keys();

console.log(newGroup) d3.select("#districtButton")

.selectAll('option')

.remove(); // add the options to the button

var newbutton = d3.select("#districtButton")

.selectAll('option')

.data(newGroup);



newbutton

.enter()

.append('option')

.text(function (d) { return d; }) // text showed in the menu

.attr("value", function (d) { return d; }); update(newGroup[0]) }

Whenever state is changed using state button, then districts of that state are assigned to district button options and entire chart is updated with first district of that state.

My final chart is given below:

If you have problem viewing it in medium, you can view it in following link

https://jsfiddle.net/rohitrajjsfiddle/o5tpq9s0/show

It is linked to api of covid19india website. You can view chart of coronavirus cases of your districts.

If you like the article please show your appreciation by clapping for the article.