JamesE
JamesE

Reputation: 3923

Enter, update and exit selections in a treemap

I have a D3 v5 treemap that I show three levels of the hierarchy via a filter. Showing all levels at once would make the map too busy. I'd like to employ a drill down or drill up feature such that when you click on a given rect (either child or parent) that re-sets that rect as the root node and then displays another three levels of the hierarchy from that new root node.

I think I'm getting the new root node data, but I can't seem to get the map to update correctly with the onclick (the console.log in the exit().remove() doesn't get called after the onclick). It seems like elements are not exiting correctly, or not getting to the exit().remove().

Here is the code:

const treemapLayout = d3.treemap()
  .size([1200, 700])
  .paddingOuter(16);

d3.json("test.json").then(function(data) {

  // update the view
  const update = (d) => {
  //console.log(d)

  let rootNode = d3.hierarchy(d)

  console.log(rootNode)

  rootNode
    .sum(function(d) { return d.value; })
    .sort(function(a, b) { return b.height - a.height || b.value - a.value; });

  treemapLayout(rootNode);


  let nodes = d3.select('svg g')
    .selectAll('g')
    .data(rootNode.descendants())

  nodes
    .enter()
    .filter(function(d) {
      return d.depth < 3;
    })
    .append('g')
    .merge(nodes)
    .attr('transform', function(d) {return 'translate(' + [d.x0, d.y0] + ')'})



  //nodes
    .append('rect')
    .attr('width', function(d) { return d.x1 - d.x0; })
    .attr('height', function(d) { return d.y1 - d.y0; })
    .attr('style', function(d) {
      return ('fill:' + d3.interpolateRdYlGn(d.data.health))
    })
    .on('click', function(d.parent) { console.log(d.data.name); update(d); })

  nodes
    .append('text')
    .attr('dx', 4)
    .attr('dy', 14)
    .text(function(d) {
      return d.data.name;
    })


  nodes
    .attr('style', function(d) { console.log('here'); return d; })
    .exit().remove()
  };

  update(data);
});

Here is the data in a simplified format:

{
  "name": "A1",
  "health": 0.521,
  "children": [
    {
      "name": "B1",
      "health": 0.521,
      "children": [
        {
          "name": "B1-C1",
          "health": 0.614,
          "children": [
            {
              "name": "B1-C1-D1",
              "health": 0.666,
              "children": [
                {
                  "name": "B1-C1-D1-E1",
                  "value": 30,
                  "health": 0.8
                },
                {
                  "name": "B1-C1-D1-E2",
                  "value": 35,
                  "health": 0.5
                },
                {
                  "name": "B1-C1-D1-E3",
                  "value": 20,
                  "health": 0.7
                }
              ]
            },
            {
              "name": "B1-C1-D2",
              "health": 0.45,
              "children": [
                {
                  "name": "B1-C1-D2-E1",
                  "value": 10,
                  "health": 0.8
                },
                {
                  "name": "B1-C1-D2-E1",
                  "value": 14,
                  "health": 0.1
                }
              ]
            },
            {
              "name": "B1-C1-D3",
              "health": 0.64,
              "children": [
                {
                  "name": "B1-C1-D3-E1",
                  "value": 10,
                  "health": 0.8
                },
                {
                  "name": "B1-C1-D3-E2",
                  "value": 14,
                  "health": 0.2
                },
                {
                  "name": "B1-C1-D3-E3",
                  "value": 7,
                  "health": 0.7
                },
                {
                  "name": "B1-C1-D3-E4",
                  "value": 9,
                  "health": 0.9
                },
                {
                  "name": "B1-C1-D3-E5",
                  "value": 5,
                  "health": 0.6
                }
              ]
            },
            {
              "name": "B1-C1-D4",
              "value": 2,
              "health": 0.7
            }
          ]
        },
        {
          "name": "B1-C2",
          "health": 0.45,
          "children": [
            {
              "name": "B1-C2-D1",
              "health": 0.45,
              "value": 12
            }
          ]
        },
        {
          "name": "B1-C3",
          "health": 0.5,
          "children": [
            {
              "name": "B1-C3-D1",
              "health": 0.5,
              "value": 10
            }
          ]
        }
      ]
    }
  ]
}

Upvotes: 1

Views: 135

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102174

D3 selections are immutable.

When you do this...

nodes.enter()
    .filter(function(d) {
      return d.depth < 3;
    })
    .append('g')
    .merge(nodes)
    //etc...

... the merge is not changing what nodes is, which is just the update selection.

You have to reassign it:

nodes = nodes.enter()
    //etc...

Here is your code with that change (in a smaller SVG):

const data = {
  "name": "A1",
  "health": 0.521,
  "children": [{
    "name": "B1",
    "health": 0.521,
    "children": [{
        "name": "B1-C1",
        "health": 0.614,
        "children": [{
            "name": "B1-C1-D1",
            "health": 0.666,
            "children": [{
                "name": "B1-C1-D1-E1",
                "value": 30,
                "health": 0.8
              },
              {
                "name": "B1-C1-D1-E2",
                "value": 35,
                "health": 0.5
              },
              {
                "name": "B1-C1-D1-E3",
                "value": 20,
                "health": 0.7
              }
            ]
          },
          {
            "name": "B1-C1-D2",
            "health": 0.45,
            "children": [{
                "name": "B1-C1-D2-E1",
                "value": 10,
                "health": 0.8
              },
              {
                "name": "B1-C1-D2-E1",
                "value": 14,
                "health": 0.1
              }
            ]
          },
          {
            "name": "B1-C1-D3",
            "health": 0.64,
            "children": [{
                "name": "B1-C1-D3-E1",
                "value": 10,
                "health": 0.8
              },
              {
                "name": "B1-C1-D3-E2",
                "value": 14,
                "health": 0.2
              },
              {
                "name": "B1-C1-D3-E3",
                "value": 7,
                "health": 0.7
              },
              {
                "name": "B1-C1-D3-E4",
                "value": 9,
                "health": 0.9
              },
              {
                "name": "B1-C1-D3-E5",
                "value": 5,
                "health": 0.6
              }
            ]
          },
          {
            "name": "B1-C1-D4",
            "value": 2,
            "health": 0.7
          }
        ]
      },
      {
        "name": "B1-C2",
        "health": 0.45,
        "children": [{
          "name": "B1-C2-D1",
          "health": 0.45,
          "value": 12
        }]
      },
      {
        "name": "B1-C3",
        "health": 0.5,
        "children": [{
          "name": "B1-C3-D1",
          "health": 0.5,
          "value": 10
        }]
      }
    ]
  }]
}

const treemapLayout = d3.treemap()
  .size([500, 300])
  .paddingOuter(16);

// update the view
const update = (d) => {
  //console.log(d)

  let rootNode = d3.hierarchy(d)

  console.log(rootNode)

  rootNode
    .sum(function(d) {
      return d.value;
    })
    .sort(function(a, b) {
      return b.height - a.height || b.value - a.value;
    });

  treemapLayout(rootNode);


  let nodes = d3.select('svg g')
    .selectAll('g')
    .data(rootNode.descendants())

  nodes
    .exit().remove()

  nodes = nodes
    .enter()
    .filter(function(d) {
      return d.depth < 3;
    })
    .append('g')
    .merge(nodes)
    .attr('transform', function(d) {
      return 'translate(' + [d.x0, d.y0] + ')'
    })



  //nodes
  nodes.append('rect')
    .attr('width', function(d) {
      return d.x1 - d.x0;
    })
    .attr('height', function(d) {
      return d.y1 - d.y0;
    })
    .attr('style', function(d) {
      return ('fill:' + d3.interpolateRdYlGn(d.data.health))
    })
    .on('click', function(d) {
      console.log(d.data.name);
      update(d);
    })

  nodes
    .append('text')
    .attr('dx', 4)
    .attr('dy', 14)
    .text(function(d) {
      return d.data.name;
    })

};

update(data);
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="500" height="300">
  <g></g>
</svg>

Upvotes: 1

Related Questions