How to Make Stunning Data Visualizations With D3.js
By: Ferenc Almasi
Tags: Data Visualization, Web Development, Programming, JavaScript, D3js

Making Treemaps

First, we want to define the root of the treemap and get the SVG from the DOM. This is where we will place everything.

This can be done with d3.select . It works just like a jQuery selector. We can also manipulate attributes with the attr method. Since each d3 call returns the same object, we can chain calls one after the other. We want to make our SVG take up the full screen. This is why we set the viewBox to the width and height of the screen.

Also the root will now have information on the position and dimension of each node, so we know where to place them.

Creating SVG groups

Now we need to make the nodes for the groups which will hold the rectangles and their title. Add the following to the render function.

For the first sight, this may seem intimidating but let’s see what each of the functions does. First, we select the SVG and select all group elements inside it. Since we don’t have any, D3 will create this for us based on the length of the data we pass in.

The second data function is used for data binding. It expects an array. For each layer, we want to create a separate group. From the descendants of our treemap, d3.nest.key.entries will calculate how many depths we have. Based on the height property of the datum that is passed to key .

data returned from d3.nest().key(d => d.height).entries(root.descendants()), 4 groups

We then need to call join to update the elements to match the data that was previously bound by data .

Then we want to bind data once more. In order to do that, we have to call selectAll again. But this time, we will use the values returned by the previous data bind. This is so that for each array element, we create a new group item inside each top group. After that, we need to call join once more to update the elements.

So now we have the data available to add a transform to each element. This is used for positioning the rectangles inside the map.

The generated DOM

Creating rectangles

Now let’s get something showing on the screen. We first want to create the rectangles. Add the following after the node variable.

In each attr call, d represents datum, that is the properties of each element. For every node, we append a rectangle and choose the fill color based on its height property. This is where we use the color function which we defined. We can also calculate the width and height based on the starting and ending points. Now you should have some empty rectangles drawn on the screen:

Adding labels

Let’s add labels as a next step. Add the following lines after the rectangles.

Here we use the selectAll-data-join chain again. This will create two tspan elements inside the text node. One with the name and one with the value. On line:5, we also set the opacity of the value to 75%. As you can see, the callback functions can accept three arguments:

one for the datum, holding information about the node. Since we bound d.data.name and d.value previously, it will only hold those values.

and previously, it will only hold those values. one for the index in the loop

one for the nodes which hold references to the DOM nodes

So if the loop is at the very last element, which holds d.value , we reduce the opacity. Otherwise, we can leave it as is. If you refresh the page, we will now have some titles. But they are position pretty badly. Let’s fix it.

Positioning labels

We want to position the parents and children separately. To do that, add two new blocks to treemap.js .

This can be done by using filters. For the first block, anything that has children will be selected. The opposite is true for the second block. For the parents, we want the name and value to be on the same line. For the children, we want them on separate lines. We can follow the same logic we used for fill-opacity to correctly position both tspan elements.

It starting to come together, but we have one more problem. The texts are overflowing.

Fixing overflow

To fix this, we want to add a clipPath element, with the same dimensions as the rectangles. In SVG, this can be done using the use tag with a reference to the id of the rectangle.

Since we have quite a few elements on the page, we need to somehow generate unique ids. For this, I’ve turned to StackOverflow to find out what is the best approach. This is the function that I ended up using:

First, let’s extend the rectangles with an id attribute.

Adding an id for the existing rectangles

We can also add additional properties to datum. Later we’re going to reuse nodeId in the clipPath element. So let’s add that next. Add it just after we defined the rectangles.

Add clipPath before the text element, otherwise we won’t have access to d.clipId

We also want to set an id for the clip-path. This is so that we can reference it in the text. For the href we can use the previously set nodeId . Lastly, we need to reference this clip-path in our text element.

Adding the clip-path attribute to the text elements

If we did everything correctly, we should now have a pretty solid foundation.

Formatting values

We can add one more thing to make the labels prettier. The values are currently in bytes. This doesn’t mean too much for the average person, so let’s convert them to more readable formats. Once more, I’ve turned to StackOverflow to find out how we can convert bytes to KB and MB. This is the function we want to use:

To format the values, all we have to do is wrap d.value into this function, where we add the texts.

And now this looks way better.

Adding titles

To improve accessibility, let’s also add titles to the rectangles. We preferably want to see the whole path, like in the image below.

To do this, append the following to treemap.js :

Based on the return value of getPath — which we haven’t defined yet — we want to show a file or folder icon. This is followed by the full path of the file. On a new line, we can also show the size. The getPath function can be done in a single line.

It expects an element and a separator. With element.ancestors , we can get every parent. To start with the root first, we also need to call reverse . As we are only interested in their name, we can call a map and join the strings together with the passed separator.

Adding shadows

Lastly, to make the depth of each level more prominent, let’s add some shadows. For this, we will need to create a new filter on the SVG. At the beginning of the render function, add the following:

On its own, this won’t do much, but now we have a filter element with an id of shadow that we can reference for our nodes.

To use it, all we need to do is add a filter attribute for the group elements.

Now we should have some nice drop shadows showing.