Search code examples
javascriptd3.jshierarchytreemap

d3 treemap make each rectangle size represent it's value


I'm making a treemap in d3 referring those two articles:

I'm testing using simple data like this:

country, numbers
US, 100
UK, 50
Germany, 30
France, 20

I've managed to render the treemap but the size of each rectangle looks so similar.

I'd like to make each size of rectangle to represent it's value. For example, US's rectangle is the biggiest and UK's one is as big as its value 50.

Could you inform me how to fix it?

Here is my code:

    <!doctype html>
    <html>
    <meta charset = "utf-8">

    <head>
<style>
    .rect {
      fill: cadetblue;
      opacity: 0.3;
      stroke: white;
    }
</style>
    </head>
    <body>
    <svg width="900" height="600">
        <g></g>
    </svg>

    <script src="https://d3js.org/d3.v5.min.js"></script>
    <script>

    d3.csv("practice_numberOnly.csv", function(error, data) {

        if (error) throw error;

        data.forEach(function(d) {
            d.country = d.country;
            d.numbers = +d.numbers;
        }) 

        var nest = d3.nest()
                    .key(function(d) { return d.country; }) 
                    .rollup(function(d) { return d3.sum(data, function(d) { return d.numbers; }); }); 

        var treemapLayout = d3.treemap()
            .size([700, 500])
            .paddingOuter(10);

        var root = d3.hierarchy({values: nest.entries(data)}, function(d) { return d.values; })
                .sum(function(d) { return d.value; })
                .sort(function(a, b) { return b.value - a.value ; });

        treemapLayout(root);

        d3.select('svg g')
            .selectAll('rect')
            .data(root.descendants())
            .enter()
            .append('rect')
            .attr("class", "rect")
            .attr('x', function(d) { return d.x0; })
            .attr('y', function(d) { return d.y0; })
            .attr('width', function(d) { return d.x1 - d.x0; })
            .attr('height', function(d) { return d.y1 - d.y0; });  

    });

    </script>
    </body>
    </html>

Solution

  • If you log your nested data you'll find that the value of each item in the array is equal, hence the represented area will be equal:

    console.log(nest.entries(data) // [{"key": "US","value": 200},{"key": "UK","value": 200},{"key": "Germany","value": 200},{"key": "France","value": 200}]
    

    This is not the input data, but it arises from this line:

    .rollup(function(d) { return d3.sum(data, function(d) { return d.numbers; }); });
    

    Here we assign a rolled up sum of all the data's numbers, not based on key, since we pass d3.sum() the entire dataset, data.

    Instead, we only want to sum the values where data shares the specified key (values below), so we want to use:

    var nest = d3.nest()
       .key(function(d) { return d.country; }) 
       .rollup(function(values) { return d3.sum(values, function(value) { return value.numbers; }); });
    

    Here's the result:

    var data = [
    {country:"US",numbers:100},
    {country:"UK",numbers:50},
    {country:"Germany",numbers:30},
    {country:"France",numbers:20}
    ];
    
    data.forEach(function(d) {
      d.country = d.country;
      d.numbers = +d.numbers;
    }) 
    
    var nest = d3.nest()
      .key(function(d) { return d.country; }) 
      .rollup(function(values) { return d3.sum(values, function(value) { return value.numbers; }); }); 
    
    var treemapLayout = d3.treemap()
      .size([700, 500])
      .paddingOuter(10);
    
    var root = d3.hierarchy({values: nest.entries(data)}, function(d) { return d.values; })
      .sum(function(d) { return d.value; })
      .sort(function(a, b) { return b.value - a.value ; });
    
    treemapLayout(root);
    
    d3.select('g')
     .selectAll('rect')
     .data(root.descendants())
     .enter()
     .append('rect')
     .attr("class", "rect")
     .attr('x', function(d) { return d.x0; })
     .attr('y', function(d) { return d.y0; })
     .attr('width', function(d) { return d.x1 - d.x0; })
     .attr('height', function(d) { return d.y1 - d.y0; });
    .rect {
          fill: cadetblue;
          opacity: 0.3;
          stroke: white;
        }
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <svg width="900" height="600">
    <g></g>
    </svg>