D3js Basic to Mastery Part 2 Mastery

Sai Kiran Goud
Analytics Vidhya
Published in
7 min readSep 7, 2020
Covid-19 active cases around the world, bubble comparison https://covidwithd3js.kiran.dev/worldbubble/

In the last article we learned about fundamentals of D3js such as selections, DOM Manipulation, SVG, Scales and data binding. If you are getting started with D3js, I highly recommend reading that article first and then come back here.

Let’s dive into the world of Let's dive into the world of, Data Manipulation and Layouts in this article.

Event Handling

Events can be attached by calling .on(event, function) on the selection element and by passing the type of listener.

Let's start with the most basic interactive functionalities.

In the above example, click on js tab. See what are all the event handlers attached on the circles. Reload page and open console by pressing F12 and click on “Run Pen”.
circles.on(‘mouseover’, console.log) This console logs all the parameters passed to callback functions.
circles.on(‘mouseover’, (event, data)=> {}) 1st Event details, 2nd bond Data. This event is used is get the current event details which can used for variety of things like drag and drop and zoom. Alternatively, you can directly access event in js like this circles.on(‘click’, () => console.log(“Event”, event))
circles.on(‘dblclick’, ondblClick) : On double click event, here in the example we passed a function which turns the element “red”.
circles.on(‘wheel’, (d, i, n) => console.log(“weeeee”, i)) : On scroll event, which is very handy for user friendly zooming event and last example ondrag event which is bit different where we have to call d3.drag() function on the element. Which has its on events like start, drag, end.

circles.call(d3.drag()
.on(“start”, console.log)
.on(“drag”, onDrag)
.on(“end”, console.log))

In onDrag function we select the element and change its cx property with x property from its event .

function onDrag(d,i,n) {
d3.select(this).attr(“cx”, event.x)
}

Remember only traditional function passes context in this while arrow function refers to its relative parent context. If its an arrow function it should look like.

const onDrag = (d,i,n) => {
d3.select(n[i]).attr(“cx”, event.x)
}

Here is the list of most used mouse events.

  • click: Mouse click on the element.
  • dblclick: Mouse double click on the element.
  • contextmenu: Mouse right click on the element.
  • mouseenter: Mouse enters the element.
  • mouseleave: Mouse leaves the element.
  • mousemove: Mouse movement over the element.
  • mouseout: Mouse leaves the element or any of its children.
  • mouseover: Mouse enters the element or any of its children.

for Touch interface read Here

Check more about it here.

There are more D3js provided interactive features

  • d3.drag: Drag event with mouse.
  • d3.zoom: Zoom event with mouse wheel.
  • d3.brush: Can be used for select area or zooming.
  • d3.force : To simulate gravitational animation

Follow me Sai Kiran Goud to learn about these interactive visualization developments.

Tooltip Example:

https://codepen.io/krngd2/pen/YzqYNRe

Data Manipulation

D3js comes with variety of data manipulation function which comes in pretty handy while dealing with any kind off data.

Take some example data of Covid-19 cases in India.

[{
“dailyconfirmed”: “78168”,
”dailydeceased”: “892”,
”dailyrecovered”: “62145”,
”date”: “01 September “,
”totalconfirmed”: “3766121”,
”totaldeceased”: “66337”,
”totalrecovered”: “2899515”
},
.......
......
.....
{
“dailyconfirmed”: “90600”,
”dailydeceased”: “916”,
”dailyrecovered”: “73161”,
”date”: “05 September “,
”totalconfirmed”: “4110852”,
”totaldeceased”: “70095”,
”totalrecovered”: “3177666”
}]

This is an array of Objects, If we want to get the maximum dailyconfirmed cases, you have d3.max(data, accessor) , example

d3.max(data,(p)=> p["dailyconfirmed"] ) // returns "90600"

Similarly we have

Max

d3.max(iterable[, accessor]) : returns max value
d3.maxIndex(iterable[, accessor]) : returns Index of max value
d3.greatest(iterable[, comparator]) : returns object of max index

Min

d3.min(iterable[, accessor]) : returns min value
d3.minIndex(iterable[, accessor]) : returns Index of min value
d3.least(iterable[, comparator]) : returns object of min index

Others

d3.sum(iterable[, accessor]) : returns Sum
d3.extent(iterable) : returns [min, max] combined in an array
d3.mean(iterable[, accessor]) : returns mean value
d3.median(iterable[, accessor]) : returns median value

Transformation

Here the interesting part begins. Transformation methods comes very handy when you want to modify your data to desired format. Lets learn about most used transformation methods.
Taken above data as example.

d3.group(data, d => d["date"]) 

output

This returns a Map with key of date and value of the rest of the values. This comes handy when you want to access values by passing the date and using Map instead Object for such data improves performance too. You can pass more callback functions to get more nested data. Convert to Array using Array.from(mapData) .

Note keys should be unique, otherwise they will be over ridden. If you want an error to be thrown, use d3.index(iterable, keyAccessory) with similar functionality. If you want to customized output of values array use d3.rollup(iterable, reduce, …keys) // Learn about .reduce() here You can check more about it Here.

d3.range([start, ]stop[, step]) // generates array of values
d3.merge([[1], [2, 3]]) //returns [1, 2, 3]
d3.pairs([1, 2, 3, 4]); // returns [[1, 2], [2, 3], [3, 4]]
d3.shuffle(array[, start[, stop]]) // shuffles the array

d3.ticks(start, stop, count)
// returns array of equal interval rounded values
// example
d3.ticks(1, 10, 5)
// returns [2, 4, 6, 8, 10]

Here I only listed out mostly used methods according my experience. You can read about all of them here Statistics, Search, Transform

Layouts

There are so many types of visualization. D3 gives us some handy inbuilt visualizations like Packed Circles, TreeMap, Network Graph etc.

In-order to develop them you need to understand an important data manipulation method to produce Hierarchical Data.

Hierarchical Data

Just like how Scales takes data and outputs the positional points, Hierarchical functions, takes hierarchical data and adds positional x,y and other points such that it can produces certain layout. We need to prepare some hierarchical data first. For that we have d3.stratify() .
Lets say we have data something like

const data = [
{"name": "Eve", "parent": ""},
{"name": "Cain", "parent": "Eve"},
{"name": "Seth", "parent": "Eve"},
{"name": "Enos", "parent": "Seth"},
{"name": "Noam", "parent": "Seth"},
{"name": "Abel", "parent": "Eve"},
{"name": "Awan", "parent": "Eve"},
{"name": "Enoch", "parent": "Awan"},
{"name": "Azura", "parent": "Eve"}
]

To convert this into hierarchical data we need pass this data to d3.stratify()

const hierarchicalDataGenrator = d3.stratify()
.id(d => d.name)
.parentId(d => d.parent)
hierarchicalDataGenrator(data)
Output

Above is output, we get Hierarchical data object with we can get hierarchicalDataGenrator.ancestors() , hierarchicalDataGenrator.descendants() , hierarchicalDataGenrator.leaves() . Typically you don’t need to use these directly. Let build some visualizations now.

Packed Circles

Covid-19 active cases around the world, bubble comparison https://covidwithd3js.kiran.dev/worldbubble/

Lets take a simple same data as above but add an extra point value. Value is the percentage of circle occupation under its parent. Lets keep everything 1. Like this {“name”: “Eve”, “parent”: “”, value: 1} .

  1. Make Data Hierarchical
const hDataGenerator = d3.stratify()
.id(d=>d.name)
.parentId(d => d.parent)
const hData = hDataGenerator(data)

2. Sum the value

hData.sum(d => d.value)
console.log(hData)

You can see extra value key added. That’s the sum of all its children values.

3. Prepare the pack layout

const packLayout = d3.pack()
.size([400, 400]);
packLayout(hData)

4. Use it to create layout

const nodes = d3.select('svg')
.append('g')
.selectAll('circle')
.data(hData.descendants())
.enter()
.append("g")
nodes.append('circle')
.attr('cx', (d) => d.x)
.attr('cy', (d) => d.y)
.attr('r', (d) => d.r)
.style("stroke", "black")
.style("fill", "none")
nodes.append('text')
.attr('y', (d) => d.y - d.r + 16)
.attr('x', (d) => d.x)
.text(d => d.data.name)

Output

TreeMap

Almost everything same as above but instead of d3.pack() we use d3.tree()

Prepare Layout

const treeLayout = d3.tree().size([400, 400]);
treeLayout(hData)

Create

const svg =  d3.select('svg')//circles
svg.append('g')
.selectAll('circle.node')
.data(hData.descendants())
.enter()
.append('circle')
.classed('node', true)
.attr('cx', (d) => d.x)
.attr('cy', (d) => d.y)
.attr('r', 4);
// Links
svg.append("g")
.selectAll('line.link')
.data(hData.links())
.enter()
.append('line')
.attr('x1', (d) => d.source.x)
.attr('y1', (d) => d.source.y)
.attr('x2', (d) => d.target.x)
.attr('y2', (d) => d.target.y)
.style('stroke', 'black')

Output

Learn More about Hierarchy here

I am hoping by now you got pretty good idea about how to play around with D3js. I know we haven't built any exciting chart in these series. In my next article we will build Racing Bar chart. So follow me for the more exciting stuff.

--

--