-
Notifications
You must be signed in to change notification settings - Fork 1
08. Visualizing the data
Having no experience at all using D3.js, I decided to start looking at examples of D3. The examples seemed pretty hard to understand at first. Eventually i found a pretty simple example of a bubble chart data visualisation, of which i could understand the code a bit.
The example i found was: Simple Bubble Chart D3 v4.
Since i was stressing out about finishing my visualisation on time, i decided to copy-paste the code onto my JavaScript file and tried adding my own data to it. It didn't work at first. After console logging everything and not seeming to get understandable feedback from it, I tried to understand every bit of code to see why it wouldn't work with my own data. It seemed that in the example the data was nested into another object with the property:
let dataset = {
children: [{
data: data
}, etc...]
}
So i tried to do the exact same by nesting my data into an object with the key: children
.
// To make a bubblechart the data needs to be nested into a parent object.
const dataset = {
children: data
}
To make my chart scale with the size of the browser, i used window.Innerheight and window.innerWidth. I didn't want to make the dimensions too big so i reduced these sizes by 100.
// Scale the svg dimensions based on the width and height of the browser
const height = window.innerHeight - 100;
const width = window.innerWidth - 100;
To get the colors of the bubbles the example used schemeCategory20, but apparently that didn't exist in my imported D3 object so I had to use schemeCategory10.
// Use a color scheme from D3
const color = scaleOrdinal(schemeCategory10);
To pack the information into a bubble the example used the following code:
// This packs the bubble
const bubble = pack(dataset)
.size([width, height])
.padding(10);
To determine the size of the svg containing the bubbles the example used a variable called diameter const diameter = 600
. I wanted to make the height and width based on the browser window so I used the variables width, height
:
// This determines the size of the svg
const svg = select("#svgcontainer")
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "bubble");
To select all the grouped data and put it in an array the example used the following code:
// this selects all the nodes and puts it in an array
const nodes = hierarchy(dataset)
.sum(function(d){ return d.amount });
To select an individual object and data and add "g" elements to it the example used the following code:
const 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 + ")";
});
To add a title that can be viewed when hovering over an individual bubble the example used the following code:
// This adds a title to every individual bubble
node.append("title")
.text(function(d) {
return d.data.key + ": " + d.data.amount;
});
I wanted to add an animation to a hover and when i only selected the circle the text wouldnt do anything so I grouped the bubble contents into a container like this
// this nests the bubble contents into a container. I did this so i could add transitions to my entire bubble
node.append("g").attr("class", "bubble-container").append("circle")
.attr("r", function(d) {
return d.r;
})
.style("fill", function(d,i) {
return color(i);
});
The following piece of code selects the container of an individual bubble and adds the text of the weapon type to it. This piece of code came from the example.
// This selects the container of an individual bubble and adds the text of the weapon type to it.
node.select(".bubble-container").append("text")
.attr("dy", ".2em")
.style("text-anchor", "middle")
.text(function(d) {
return d.data.key.substring(0, d.r / 1);
})
.attr("font-family", "sans-serif")
.attr("font-size", function(d){
return d.r/3;
})
.attr("fill", "white")
The following function is a function that i got from another example. This piece of code makes the bubbles clickable and displays the weapon type and value of the data on the website.
The example was mutating a variable like this:
let currentBubble = undefined
const selectBubble = d => {
if(currentBubble !== undefined){
svg.selectAll("#details-popup").remove();
}
currentBubble = select(this);
}
Since this is not allowed in functional programming i transformed the code to look like this:
const selectBubble = d => {
const currentBubble = select(this);
if(currentBubble !== this){
svg.selectAll("#details-popup").remove();
}
}
The following code snippet creates a textblock and adds the weapon type and value to that textblock.
const textblock = svg.selectAll("#details-popup")
.data([d])
.enter()
.append("g")
.attr("id", "details-popup")
.attr("font-size", 14)
.attr("font-family", "sans-serif")
.attr("text-anchor", "start")
.attr("transform", data => `translate(0, 20)`);
textblock.append("text")
.text(d.data.key + ": " + d.data.amount)
.attr("y", "16")
So the entire function looks like this:
const selectBubble = d => {
const currentBubble = select(this);
if(currentBubble !== this){
svg.selectAll("#details-popup").remove();
}
const textblock = svg.selectAll("#details-popup")
.data([d])
.enter()
.append("g")
.attr("id", "details-popup")
.attr("font-size", 14)
.attr("font-family", "sans-serif")
.attr("text-anchor", "start")
.attr("transform", data => `translate(0, 20)`);
textblock.append("text")
.text(d.data.key + ": " + d.data.amount)
.attr("y", "16")
}
node.on("click", selectBubble);
My complete code for the visualizeData function looks like this:
// This function visualizes the transformed data
function visualizeData(data) {
// To make a bubblechart the data needs to be nested into a parent object.
const dataset = {
children: data
}
// Scale the svg dimensions based on the width and height of the browser
const height = window.innerHeight - 100;
const width = window.innerWidth - 100;
// Use a color scheme from D3
const color = scaleOrdinal(schemeCategory10);
// This packs the bubble
const bubble = pack(dataset)
.size([width, height])
.padding(10);
// This decides the size of the svg
const svg = select("#svgcontainer")
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "bubble");
// this selects all the nodes and puts it in an array
const nodes = hierarchy(dataset)
.sum(function(d){ return d.amount });
// this selects all of the individual nodes, adds a "g" element to all the nodes and adds a class as well
const 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 + ")";
});
// This adds a title to every individual bubble
node.append("title")
.text(function(d) {
return d.data.key + ": " + d.data.amount;
});
// this nests the bubble contents into a container. I did this so i could add transitions to my entire bubble
node.append("g").attr("class", "bubble-container").append("circle")
.attr("r", function(d) {
return d.r;
})
.style("fill", function(d,i) {
return color(i);
});
// This selects the container of an individual bubble and adds the text of the weapon type to it.
node.select(".bubble-container").append("text")
.attr("dy", ".2em")
.style("text-anchor", "middle")
.text(function(d) {
return d.data.key.substring(0, d.r / 1);
})
.attr("font-family", "sans-serif")
.attr("font-size", function(d){
return d.r/3;
})
.attr("fill", "white")
// This is a click function that when clicked displays the weapon type and value of the clicked bubble
const selectBubble = d => {
const currentBubble = select(this);
if(currentBubble !== this){
svg.selectAll("#details-popup").remove();
}
const textblock = svg.selectAll("#details-popup")
.data([d])
.enter()
.append("g")
.attr("id", "details-popup")
.attr("font-size", 14)
.attr("font-family", "sans-serif")
.attr("text-anchor", "start")
.attr("transform", data => `translate(0, 20)`);
textblock.append("text")
.text(d.data.key + ": " + d.data.amount)
.attr("y", "16")
}
node.on("click", selectBubble);
select(self.frameElement)
.style("height", height + "px");
}