I think the first thing to say about treemaps is that they're just a way to visualize your data in a nice, more structured way. And that the first question to ask is: How should I structure my data in a way that d3.treemap
can work with?
Before writing about how to structure data for d3.treemap
to use, you should know that there are two different input formats to use when building a treemap with d3.js
:
-
.csv
files. -
.json
files.
And since I've only worked with the .json
format, that's what I am writing about.
So let's fetch do data. (I am using the freeCodeCamp top 100 most sold video games data.)
document.addEventListener('DOMContentLoaded', () =>{
fetch("https://cdn.freecodecamp.org/testable-projects-fcc/data/tree_map/video-game-sales-data.json")
.then(res=>res.json())
.then(res=>{
drawTreeMap(res);
});
});
const drawTreeMap = (dataset)=>{
// pass for now
}
Now that we have our json
data, let's work on how we should structure our data in a way that d3.treemap
can work with. And to do so we should pass our data to d3.hierarchy
.
const drawTreeMap = (dataset)=>{
const hierarchy = d3.hierarchy(dataset);
}
What d3. hierarchy
does is take the data and add to it: depth, height, and parent.
- depth: counts how many parents every node has.
- height: counts how many levels of children every node has.
- parent: the parent of the node or null for the root node.
The data we've fetched has a height of 2 because it consists of 18 children (first level of children). And every child of the first level has its own children (second level of children).
And each of the first-level children has a height of 1 and a depth of 1 (they have children and a parent). And each child of the second-level has a depth of 2 and a height of 0 (two higher parents and no children).
We now have a new version of the data but still, it feels something is missing here. I mean, how would d3.treemap
know each child's value so that it'd make room for that child depending on that value?
So we need to use sum
and sort
methods with d3.hierarchy
to calculate that value and sort the children according to it.
const drawTreeMap = (dataset)=>{
const hierarchy = d3.hierarchy(dataset)
.sum(d=>d.value) //sum every child's values
.sort((a,b)=>b.value-a.value) // and sort them in descending order
}
Now, this new version of the data (which has a total value for each child) is ready to be placed on a treemap.
So let's create a treemap.
const treemap = d3.treemap()
.size([400, 450]) // width: 400px, height:450px
.padding(1); // set padding to 1
Finally, we can pass the data to the treemap.
const root = treemap(hierarchy);
treemap
now knows the worth of every node and the hierarchy of the data --which node is a parent and which is a child. And with that knowledge it's able to structure the data, it's able to determine the x
and y
attributes for each node.
If you inspect the root
variable now, you'll notice that treemap
did you a huge favor and added x0
, x1
, y0
, and y
attributes to every node of the data. And with those attributes, you can make rect
elements of these nodes and append them to an svg
element to see them on your screen.
To make an array of these nodes and to access them we use root.leaves()
.
const svg = d3.select("svg"); //make sure there's a svg element in your html file.
svg.selectAll("rect")
.data(root.leaves())
.enter()
.append("rect")
.attr("x", d=>d.x0)
.attr("y", d=>d.y0)
.attr("width", d=>d.x1 - d.x0)
.attr("height", d=>d.y1 - d.y0)
.attr("fill", "#5AB7A9")
Now the treemap should be like this:
It looks nice, but specifying a different color to each category would make it more helpful, right? So let's add more colors.
d3.js
has a lot of color schemes to choose from but I am choosing different colors.
const colors = ['#1C1832', '#9E999D', '#F2259C', '#347EB4',
'#08ACB6', '#91BB91', '#BCD32F', '#75EDB8',
"#89EE4B", '#AD4FE8', '#D5AB61', '#BC3B3A',
'#F6A1F9', '#87ABBB', '#412433', '#56B870',
'#FDAB41', '#64624F']
To use these colors on our nodes we need to scale them first. And to scale something in d3.js
, we need to use a scaling function and to provide a domain
and range
to it.
I think the simplest explanation for the domain
and range
methods is that the domain
is the data we have and that the range
is the form we need that data to be shown in.
For example, we here need to use colors
to scale the data categories. So our data is the categories and the form we need these categories to be shown in is colors
. Every category should be colored with color from colors
.
Let's see how this looks in code.
const categories = dataset.children.map(d=>d.name);
const colorScale = d3.scaleOrdinal() // the scale function
.domain(categories) // the data
.range(colors) // the way the data should be shown
So now we should change the fill
attribute we used earlier and use it with colorScale
instead.
svg.selectAll("rect")
.data(root.leaves())
.enter()
.append("rect")
.attr("x", d=>d.x0)
.attr("y", d=>d.y0)
.attr("width", d=>d.x1 - d.x0)
.attr("height", d=>d.y1 - d.y0)
.attr("fill", d=>colorScale(d.data.category)) //new
Here's how it should look now:
Note: You can add text on the rectangles to make the treemap more informative. I am not adding text here but this stackoverflow answer helped me a lot when I needed to add wrapped text.
Final Code
document.addEventListener('DOMContentLoaded', () =>{
fetch("https://cdn.freecodecamp.org/testable-projects-fcc/data/tree_map/video-game-sales-data.json")
.then(res=>res.json())
.then(res=>{
drawTreeMap(res);
});
});
const drawTreeMap = (dataset)=>{
const hierarchy = d3.hierarchy(dataset)
.sum(d=>d.value) //sums every child values
.sort((a,b)=>b.value-a.value), // and sort them in descending order
treemap = d3.treemap()
.size([500, 450])
.padding(1),
root = treemap(hierarchy);
const categories = dataset.children.map(d=>d.name),
colors = ['#1C1832', '#9E999D', '#F2259C', '#347EB4',
'#08ACB6', '#91BB91', '#BCD32F', '#75EDB8',
"#89EE4B", '#AD4FE8', '#D5AB61', '#BC3B3A',
'#F6A1F9', '#87ABBB', '#412433', '#56B870',
'#FDAB41', '#64624F'],
colorScale = d3.scaleOrdinal() // the scale function
.domain(categories) // the data
.range(colors); // the way the data should be shown
const svg = d3.select("svg"); //make sure there's a svg element in your html file
svg.selectAll("rect")
.data(root.leaves())
.enter()
.append("rect")
.attr("x", d=>d.x0)
.attr("y", d=>d.y0)
.attr("width", d=>d.x1 - d.x0)
.attr("height", d=>d.y1 - d.y0)
.attr("fill", d=>colorScale(d.data.category));
}
Top comments (0)