2

This is my data : https://api.myjson.com/bins/b0m6s

I want to create a nested treemap that shows the parent element and the child elements inside it, like this example : https://bl.ocks.org/mbostock/911ad09bdead40ec0061

Here is my treemap code currently :

var defaultColors = d3.scale.ordinal().range(["#00AEEF", "#8DC63F", "#FFC20E", "#F06EAA", "#AE9986", "#009BA5", "#00A651", "#F7941D", "#B656AB", "#ABA000", "#F3716D", "#8D7B6B", "#EF413D", "#AD772B", "#878787"]);
var treemap;
var h_pad = 2, // 2 pixels vertical padding
    v_pad = 4; // 4 pixels of horizontal padding (2 px at each side)
var canvas = d3
    .select(id)
    .append("svg")
    .attr("class", "chart")
    .attr("width", cfg.width + cfg.margin.left + cfg.margin.right)
    .attr("height", cfg.height + cfg.margin.top + cfg.margin.bottom)
    .attr("viewBox", "0 0 960 500")
    .attr("preserveAspectRatio", "xMidYMid meet")
    .attr("id", "canvas")

 var innercanvas = canvas
    .append("g")
    .attr("class", "innercanvas")
    .attr("transform", "translate(" + cfg.margin.left + "," + cfg.margin.top + ")");

    treemap = d3.layout
    .treemap()
    .round(false)
    .size([cfg.width, cfg.height])
    .padding(.25)
    .sticky(true)
    .nodes(data);



  var cells = innercanvas
    .selectAll(".newcell")
    .data(treemap)
    .enter()
    .append("g")
    .attr("class", function (d, i) {
      return 'newcell _' + i       // i provides a unique identifier for each node 
        + ' cell-level-' + d.depth   // cell-level-0 for root, cell-level-1, cell-level-2, etc 
        + (d.name ? ' ' + safe_name(d.name) : '') // if d.name exists, use the 'safe' version
        + (!d.children
          ? ' leaf'                  // d has no children => it's a leaf node
          : (d.depth === 0
            ? ' root'                // d.depth = 0 => it's the root node
            : ' internal '));        // has children, depth > 0 => internal node
    })

 cells
    .append("rect")
    .attr("x", function (d) {
      return d.x;
    })
    .attr("y", function (d) {
      return d.y;
    })
    .attr("id", "rectangle")
    .attr("width", function (d) {
      return d.dx;
    })
    .attr("height", function (d) {
      return d.dy;
    })
    .style("fill", function (d) {
      return d.children && d.parent ? defaultColors(d.name) : cfg.color ? cfg.color(d.name) : null;
    })
    .attr("stroke", "#000000")
    .attr('pointer-events', 'all');

 cells
    .append("text")
    .attr("x", function (d) {
      return d.x + d.dx / 2;
    })
    .attr("y", function (d) {
      return d.y + d.dy / 2;
    })
    .attr("text-anchor", "middle")
    .text(function (d) { return d.parent ? d.name : '' })


cells
      .append('title')
      .text(function (d) {
        if (d.parent) {
          return categoryKey + " : " + d.parent.name + "\n" + groupKey + " : " + d.name + "\n" + sizeKey + " : " + toCommas(d.value.toFixed(2))
        }
        return d.name;
      });

Wrapping the texts into multiple lines

function groupAddText(selection) {

 var v_pad = 2, // vertical padding
  h_pad = 4 // horizontal padding

selection.selectAll('.leaf text')
  .classed('groupOversize', function (d) {
    if (!d.name) {
      return false;
    }
    var bbox = this.getBBox();
    if (d.dx <= bbox.width + h_pad || d.dy <= bbox.height + v_pad) {


      d3.select(this).node().textContent = "";
      var lines = wordwrap2(d.name, d.dx).split('\n');
      for (var i = 0; i < lines.length; i++) {
        d3.select(this)
          .append("tspan")
          .attr("dy", 15)
          .attr("x", d.x + d.dx / 2)
          .text(lines[i]);

      }
      d3.selectAll(".groupOversize").attr("y", function (d) {
        return (d.y + d.dy / 2) - 20;
      })
      return true;
    }
    return false;
  });

}

function wordwrap2(str, width, brk, cut) {
brk = brk || '\n';
width = width || 75;
cut = cut || false;
if (!str) { return str; }
var regex = '.{1,' + width + '}(\\s|$)' + (cut ? '|.{' + width + '}|.+$' : '|\\S+?(\\s|$)');
return str.match(RegExp(regex, 'g')).join(brk);

}

This produces the following treemap visualization :

enter image description here

As you can see, I separated my cells into 3 classes, root for the root node, internal for the parents and leaf for the children. Right now, it is just showing the children rects. How do I show the parent elements with the children nested inside them?

I want something like this : [![enter image description here][2]][2]

Syed Ariff
  • 722
  • 2
  • 8
  • 26

1 Answers1

2

You can view the parent cells by adding padding to your treemap:

  treemap = d3.layout
    .treemap()
    .round(false)
    .size([cfg.width, cfg.height])
    .padding(20) // 20px padding all around
    .sticky(true)
    .nodes(data);

or

  treemap = d3.layout
    .treemap()
    .round(false)
    .size([cfg.width, cfg.height])
    .padding([20,5,5,5]) // 20px top, 5px sides and bottom
    .sticky(true)
    .nodes(data);

I made a little demo showing the effects of altering the padding in a d3 treemap here - although note that that is d3 v5, so the options are slightly different.

Here's a demo with your code:

var data = {"children":[{"name":"Central","children":[{"name":"Cellophane Tape","value":"419141.4728"},{"name":"File Separator","value":"327285.0157"},{"name":"Hard Cover File","value":"422707.1194"},{"name":"Highlighter","value":"488978.5362"},{"name":"Office Chair","value":"453843.621"},{"name":"Pencil","value":"416819.1027"},{"name":"Tape Dispenser","value":"393290.5862"},{"name":"File Cabinet","value":"424647.6003"},{"name":"Plastic Comb Binding","value":"230299.6657"},{"name":"White Board Markers","value":"383157.5055"},{"name":"Binder","value":"415871.6793"},{"name":"Eraser","value":"477885.9162"},{"name":"Pen","value":"444834.4362"},{"name":"Pen Set","value":"434495.1303"},{"name":"Desk","value":"247046.3919"}]},{"name":"East","children":[{"name":"Pencil","value":"441970.1055"},{"name":"White Board Markers","value":"416822.5561"},{"name":"Eraser","value":"393738.4951"},{"name":"Hard Cover File","value":"407371.1911"},{"name":"Office Chair","value":"382574.6347"},{"name":"Tape Dispenser","value":"481960.7562"},{"name":"Cellophane Tape","value":"441438.7362"},{"name":"File Cabinet","value":"333187.8858"},{"name":"Binder","value":"462926.3793"},{"name":"File Separator","value":"441311.7555"},{"name":"Plastic Comb Binding","value":"330059.7762"},{"name":"Highlighter","value":"399332.0562"},{"name":"Pen","value":"492374.2362"},{"name":"Pen Set","value":"477206.7762"},{"name":"Desk","value":"254464.9453"}]},{"name":"North","children":[{"name":"Office Chair","value":"459306.6555"},{"name":"Pencil","value":"465763.0477"},{"name":"Eraser","value":"441687.1652"},{"name":"File Cabinet","value":"463598.5893"},{"name":"File Separator","value":"430346.1162"},
{"name":"Hard Cover File","value":"346325.0175"},{"name":"Highlighter","value":"223199.4072"},{"name":"Tape Dispenser","value":"311201.7216"},{"name":"Plastic Comb Binding","value":"445513.5762"},{"name":"Binder","value":"453219.921"},{"name":"White Board Markers","value":"334737.9189"},{"name":"Cellophane Tape","value":"372554.952"},{"name":"Pen","value":"435830.2872"},{"name":"Pen Set","value":"460001.8962"},{"name":"Desk","value":"260294.2303"}]},{"name":"South","children":[{"name":"Pencil","value":"457331.6055"},{"name":"Tape Dispenser","value":"442628.4555"},{"name":"Cellophane Tape","value":"468037.3351"},{"name":"Eraser","value":"341469.2127"},{"name":"File Cabinet","value":"408198.2058"},{"name":"File Separator","value":"416543.8893"},{"name":"Office Chair","value":"466438.7227"},{"name":"Plastic Comb Binding","value":"436440.1272"},{"name":"White Board Markers","value":"437968.1344"},{"name":"Highlighter","value":"411905.4555"},{"name":"Binder","value":"456806.1151"},{"name":"Hard Cover File","value":"493053.3762"},{"name":"Pen","value":"413820.3762"},{"name":"Pen Set","value":"488299.3962"},{"name":"Desk","value":"264499.5623"}]},{"name":"West","children":[{"name":"Pencil","value":"458648.3055"},{"name":"Cellophane Tape","value":"299045.7162"},{"name":"File Cabinet","value":"386045.352"},{"name":"File Separator","value":"435098.0403"},{"name":"Highlighter","value":"457454.0701"},{"name":"Office Chair","value":"262021.1055"},{"name":"Plastic Comb Binding","value":"413222.1555"},{"name":"Eraser","value":"449997.2978"},{"name":"Hard Cover File","value":"364335.5793"},{"name":"Binder","value":"467389.3801"},{"name":"Tape Dispenser","value":"394066.5845"},{"name":"White Board Markers","value":"408833.4789"},{"name":"Pen","value":"481281.6162"},{"name":"Pen Set","value":"398652.9162"},{"name":"Desk","value":"229482.2954"}]}]};
data.name = 'root'
var defaultColors = d3.scale.ordinal().range(["#00AEEF", "#8DC63F", "#FFC20E", "#F06EAA", "#AE9986", "#009BA5", "#00A651", "#F7941D", "#B656AB", "#ABA000", "#F3716D", "#8D7B6B", "#EF413D", "#AD772B", "#878787"]);
var treemap;
var h_pad = 2, // 2 pixels vertical padding
    v_pad = 4; // 4 pixels of horizontal padding (2 px at each side)
var id = 'treemap';
var cfg = { width: 960, height: 500, margin: { left: 10, right: 10, bottom: 10, top: 10 }, color: d3.scale.category20() }
var canvas = d3
    .select('#' + id)
    .append("svg")
    .attr("class", "chart")
    .attr("width", cfg.width + cfg.margin.left + cfg.margin.right)
    .attr("height", cfg.height + cfg.margin.top + cfg.margin.bottom)
    .attr("viewBox", "0 0 960 500")
    .attr("preserveAspectRatio", "xMidYMid meet")
    .attr("id", "canvas")

 var innercanvas = canvas
    .append("g")
    .attr("class", "innercanvas")
    .attr("transform", "translate(" + cfg.margin.left + "," + cfg.margin.top + ")");

  treemap = d3.layout
    .treemap()
    .round(false)
    .size([cfg.width, cfg.height])
    .padding([20,5,5,5])
    .sticky(true)
    .nodes(data);

  var cells = innercanvas
    .selectAll(".newcell")
    .data(treemap)
    .enter()
    .append("g")
    .attr("class", function (d, i) {
      return 'newcell _' + i       // i provides a unique identifier for each node 
        + ' cell-level-' + d.depth   // cell-level-0 for root, cell-level-1, cell-level-2, etc 
        + (d.name ? ' ' + d.name : '') // if d.name exists, use the 'safe' version
        + (!d.children
          ? ' leaf'                  // d has no children => it's a leaf node
          : (d.depth === 0
            ? ' root'                // d.depth = 0 => it's the root node
            : ' internal '));        // has children, depth > 0 => internal node
    })

 cells
    .append("rect")
    .attr("x", function (d) {
      return d.x;
    })
    .attr("y", function (d) {
      return d.y;
    })
    .attr("id", function(d,i){ return 'rect_' + i; })
    .attr("width", function (d) {
      return d.dx;
    })
    .attr("height", function (d) {
      return d.dy;
    })
    .style("fill", function (d) {
      return d.children && d.parent ? defaultColors(d.name) : cfg.color ? cfg.color(d.name) : null;
    })
    .attr("stroke", "#000000")
    .attr('pointer-events', 'all');

cells.append("clipPath")
    .attr("id", function(d,i) { return "clip_" + i ; })
    .append("use")
    .attr("xlink:href", function(d,i) { 
       return "#rect_" + i;
    });

cells
    .append("text")
    .attr("clip-path", function(d,i) { return "url(#clip_" + i })
    .attr("x", function (d) {
      return d.x + d.dx / 2;
    })
    .attr("y", function (d) {
      return d.children ? d.y + 12 : d.y + d.dy / 2 ;
    })
    .attr("text-anchor", "middle")
    .text(function (d) { return d.name })


cells
    .append('title')
    .text(function (d) {
      if (d.parent) {
        return "categoryKey : " + d.parent.name + "\ngroupKey : " + d.name + "\nsizeKey : " + d.value.toFixed(2)
      }
        return d.name;
    });
    
svg text {
  font-size: 10px;
}
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script><div id="treemap"></div>
i alarmed alien
  • 9,412
  • 3
  • 27
  • 40
  • The texts are not formatted right. Would Mike Bostock's example for wrapping labels : https://bl.ocks.org/mbostock/7555321 work? If so, when you call it .call(wrap,x.rangeBand()) . What do I put in for the width? getBbox.width? – Syed Ariff Sep 21 '18 at 07:13
  • hey dude. Did you managed to figure out the text wrapping yet? Your addText() function(reference is in my question) is the best I've got but it hides a lot of rects that could have just displayed the info by allowing multiple lines. – Syed Ariff Sep 24 '18 at 10:08
  • I have been busy with other stuff, sorry! You should be able to get the box width from the data properties (`d.dx`, etc.). I will have time later in the week to post an answer if you haven't found a solution by then. – i alarmed alien Sep 24 '18 at 11:35
  • nice. I updated my code with a function that adds tspans https://stackoverflow.com/questions/20810659/breaking-text-from-json-into-several-lines-for-displaying-labels-in-a-d3-force-l. However, I still need to hide texts if the rects are too small. You can help me with that :) – Syed Ariff Sep 25 '18 at 03:07
  • or maybe add a "..." to the text if the rects are too small. – Syed Ariff Sep 25 '18 at 03:14
  • You could also do something like I've used in the final column on [this chart](https://bl.ocks.org/ialarmedalien/790f25a892a18793b38c23fd69efbc7e), where mousing over reveals the full label. – i alarmed alien Sep 25 '18 at 08:00
  • Unfortunately , I could not render the chart in your link , some unexpected error "cannot read property sum of undefined." Regardless, Can u help me hide to hide the text if its overflowing using my updated code? Been having trouble implementing your oversize:display none on my tspans. – Syed Ariff Sep 28 '18 at 03:28