Treemaps with D3.js
Tags: firstpost, d3js, javascript, datavisualization
Published: 2020-08-14 20:10:39

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.)
```javascript
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`. 
```javascript
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.
```javascript
 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.

```javascript
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. 
```javascript
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()`.

```javascript
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:
![A treemap with single color](https://i.ibb.co/WySVGXr/treemap1.jpg)

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](https://github.com/d3/d3-scale-chromatic) to choose from but I am choosing different colors. 

```javascript
  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.

```javascript
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.

```javascript
  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:
![A treemap with multiple colors](https://i.ibb.co/zhRVp2c/treemap2.jpg)

**Note**: You can add text on the rectangles to make the treemap more informative. I am not adding text here but [this stackoverflow answer](https://stackoverflow.com/questions/56623476/how-to-get-d3-treemap-cell-text-to-wrap-and-not-overflow-other-cells) helped me a lot when I needed to add wrapped text.

*Final Code*
```javascript
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));
}                
```