-
Notifications
You must be signed in to change notification settings - Fork 0
D3 (Data-Driven Documents or D3.js) is a js library enables you to create dynamic data visualisations on the web.
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.
Take a look at the version you are using because some examples use older versions which will not be compatible with your functions.
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");
let node = svg.selectAll(".node")
Selects all elements from the DOM. Here I select all nodes.
.append("g")
Appends new elements. Here I add <g> for every node.
let nodes = d3.hierarchy(dataset)
.hierarchy creates a root node from my dataset structure.
.enter()
Selects the items that are missing.
.data(bubble(nodes).descendants())
Get all data from the nodes descendants.
.text(function(d) {
return d.data.Amount;
})
Fills the text svg element with the Amount variable from the data.
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.
let color = d3.scaleOrdinal(d3.schemeCategory20);
Creates an ordinal color scheme.
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; });
// 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 titles
node.append("title")
.text(function(d) {
return d.Name + ": " + d.Amount;
});
I append a title with the name and the amount
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);
// 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.
d3.select(self.frameElement)
.style("height", diameter + "px");
Return itself
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.
[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.
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;