o1sound
o1sound

Reputation: 500

d3.selectAll enter exit of nested key value pairs -- aka: how do array selections work?

I have a nested array of data that I am using to create a grouped bar chart. I am having trouble assigning a key to the datajoin of the nested array.

Below is the input data, the working datajoin for the top level, and two partial implementations of what I am trying to accomplish. I am so close!

// input data    

var data = [
      {"key":"cat1","value":[{"key":"subcatA","value":100},{"key":"subcatB","value":200}]},
      {"key":"cat2","value":[{"key":"subcatA","value":150},{"key":"subcatB","value":250}]}
    ];

To access the top level and build the main categories, I do this:

// top level (all good)
var plot = d3.select('#plot');

plot.selectAll(".category")
    .data(data,function(d) {return d.key;}) // <-- return key on datajoin
    .enter().append("g")
    .attr("class", "category")

Returning d.key on the datajoin keeps track of each category such that enter() and exit() is not a giant mess of animation. See Mike Bostock's General Update Pattern, II for reference on adding a key to a datajoin.

To plot the second level, I do this:

// second level (plots but no key is assigned)

plot.selectAll(".category").selectAll(".bar")
    .data(function (d) {return d.value; }) // <-- is this right?
    .enter().append("rect")
    .attr("class","bar")

There is no key assigned so d3 handles updates rather randomly. I can get what I want for only the first iteration if I do this:

 // second level (key is assigned but first iteration only)

 plot.selectAll(".g-category").selectAll(".bar")
    .data(data[0].value, function (d) {return d.key; }) // <-- first category only
    .enter().append("rect")
    .attr("class","bar")

Perfection except I need to iterate over data, not just data[0]. My problem is as soon as I try, I get into a conflict of interest with d3.selectAll and make a general mess of things.

The answer to this question may be simple, but I am missing it: How do I go about properly selecting and assigning keys to nested arrays in d3?

Upvotes: 3

Views: 376

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102198

The data method in your second level has just one argument:

.data(function (d) {
    return d.value; 
});

For assigning the key function you have to pass a second argument. For instance:

.data(function(d) {
    return d.value
}, function(d) {
    return d.key
});

You can better see it in one line:

.data(function(d) { return d.value }, function(d) { return d.key });
//2nd arg after the comma ----------^

However, to facilitate human reading, I'd advise you to use different property names for level 1 and level 2. Right now everything is value and key, which can be hard to understand (not for the machine, though). For instance, in the above snippet, value refers to the first level array, while key refers to the second level array, not the first one... do you see the mess?

Here is a demo with your data:

var data = [{
    "key": "cat1",
    "value": [{
      "key": "subcatA",
      "value": 100
    }, {
      "key": "subcatB",
      "value": 200
    }]
  },
  {
    "key": "cat2",
    "value": [{
      "key": "subcatA",
      "value": 150
    }, {
      "key": "subcatB",
      "value": 250
    }]
  }
];

var body = d3.select("body");
var outer = body.selectAll(null)
  .data(data, function(d) {
    return d.key
  })
  .enter()
  .append("div")
  .attr("class", "outer");

var inner = outer.selectAll(null)
  .data(function(d) {
    return d.value
  }, function(d) {
    console.log("the key is: " + d.key)
    return d.key
  })
  .enter()
  .append("div")
  .html(function(d) {
    return d.key + " - " + d.value
  });
<script src="https://d3js.org/d3.v4.min.js"></script>

Upvotes: 2

Related Questions