Skip to content
SqueezyDough edited this page Nov 15, 2019 · 6 revisions

About D3

D3 (Data-Driven Documents or D3.js) is a js library enables you to create dynamic data visualisations on the web.

How D3 works

Adding d3

To add d3 to your poject you need to include the library. You can add the entire library like this

<script type="text/javascript" src="https://d3js.org/d3.v4.min.js"></script>

However you now load all the modules of d3. It is cleaner to only add what you need. D3 is entirely modular so you can include specific packages.

import { select, json, geoPath } from 'd3

Keep in mind that you need a bundler like web pack or rolup to make this work.

Versions

Take a look at the version you are using because some examples use older versions which will not be compatible with your functions.

D3 Functions

Select

d3.select("body")

Select simply selects an element. You can use it to add more properties to it or do other stuff.

d3.select("body")
	.append("svg")
	.attr("width", diameter)
	.attr("height", diameter)
	.attr("class", "bubble");

selectAll

let node = svg.selectAll(".node")

Selects all elements from the DOM. Here I select all nodes.

Append

.append("g")

Appends new elements. Here I add <g> for every node.

Hierarchy

let nodes = d3.hierarchy(dataset)

.hierarchy creates a root node from my dataset structure.

Enter

.enter()

Selects the items that are missing.

Data

.data(bubble(nodes).descendants())

Get all data from the nodes descendants.

Text

.text(function(d) {
     return d.data.Amount;
})

Fills the text svg element with the Amount variable from the data.

Pack

let bubble = d3.pack(dataset)
	.size([diameter, diameter])
	.padding(2.5);

Pack creates a new circular layout. I changed the diameter so the diagram would be slightly bigger and I changed the padding so the bubbles wouldn't stick together.

scaleOrdinal

let color = d3.scaleOrdinal(d3.schemeCategory20);

Creates an ordinal color scheme.

How it all comes together

Create a bubble layout with .pack

let bubble = d3.pack(dataset)
	.size([diameter, diameter])
	.padding(2.5);

Pack creates a circular layout for you that needs a root (parent) to plot the bubbles (child). I do that by using the .hierarchy function. That contains my dataset and I use .sum to count all the amounts together so it has a total number (100%). It needs that so it know how big a bubble should be.

// create a node hierarchy
let nodes = d3.hierarchy(dataset)
	.sum(function(d) { return d.Amount; });

Create nodes and decide their positions

// Add the nodes
let node = svg.selectAll(".node")
	.data(bubble(nodes).descendants())
	.enter()
	.filter(function(d){
		return  !d.children
	})
	.append("g")
	.attr("class", "node")
	.attr("transform", function(d) {
		return "translate(" + d.x + "," + d.y + ")";
});

I select all nodes, create group from them and save their positions based on the amount property of the node. With .enter I add all the items that are missing.

Add a title

// Add titles
node.append("title")
	.text(function(d) {
	        return d.Name + ": " + d.Amount;
});

I append a title with the name and the amount

Draw the circle

node.append("circle")
.attr("r", function(d) {
	return d.r;
})
.style("fill", function(d,i) {
	return color(i);
});

Draw the circle based on its radius and fill it with a color from d3.scaleOrdinal(d3.schemeCategory20);

Create the view box

// select the body and add the svg element with its size
	let svg = d3.select("body")
	            .append("svg")
	 	    .attr("width", diameter)
		    .attr("height", diameter)
		    .attr("class", "bubble");

Creates the view box the chart will be appended in.

Return the element

d3.select(self.frameElement)
	.style("height", diameter + "px");

Return itself

Experiment Bubble map

At first I wanted to make the bubble map example first. However I couldn't find a good example that could proces my data. I tried multiple examples including the one from Laurens but none of them worked properly. In hindsight I know that I passed the wrong data. I thought the bubble map would calculate how bog a bubble should be based on the amount of items with the same name. However when I was working on the bubble diagram later I discovered that I should calculate these numbers beforehand, thus making it obvious I did the bubble map wrong.

Experiment bubble chart

badbubble [Image from this guy]

It was hard to find a good example that does exactly what I want. I first settled with this example. I was able to get my data in this chart, but it presented the data in a weird manner. The data sorted from small to large. This looked really weird with my data, because Indonesia is a really large bubble. There was also no text inside these bubbles which made it so you couldn't see what a bubble represented. I end up changing g this by adding attributes to the <circle> element, which I could than fetch with javascript by selecting the attribute.

data.forEach( (obj) => {
    newDataSet.push({
         name: obj.country, className: obj.country.toLocaleLowerCase(), size: obj.amount
    })
})

Here I add the name and the amount to the dataset object so I could later make attributes out of them.

.atr("name", d.name)

I discovered lots of other people were also experimenting with different bubble charts. Marc found a good example which presented the data the way I wanted to so I also used his source.

Changing the example

I ended up using code from this example. It did already what I wanted, but a the data was hard coded. So the first thing I did is request data from the server to use in my d3 visualisation.

let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
    if (this.readyState === 4 && this.status === 200) {
      let data = JSON.parse(this.responseText);

      drawChart(data)
    }
};
xhr.open("GET", `/fetch`, true);
xhr.send();

I used an XMLHttpRequest to call a function in my datacontroller. Once that is ready I call the function to draw the d3 chart.

function drawChart(data) {...}

I then transform the data so it can be used in the diagram.

const dataset = {
    children: data
};

I also changed the example so it is now es6. For then visualisation itself I wanted the diagram to be slightly bigger so I increased its radius and increased the padding so the bubbles wouldn't be so close to each other.

let diameter = 650;
let bubble = d3.pack(dataset)
     .size([diameter, diameter])
     .padding(2.5);

I changed the variable names so it would take an amount and a name. Here is an example.

return d.Name + ": " + d.Amount;