Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accept iterables as children. #140

Closed
mbostock opened this issue Mar 21, 2019 · 12 comments · Fixed by #155
Closed

Accept iterables as children. #140

mbostock opened this issue Mar 21, 2019 · 12 comments · Fixed by #155

Comments

@mbostock
Copy link
Member

It’d be nice to make d3.hierarchy compatible with the new d3.group in d3-array.

@Fil
Copy link
Member

Fil commented Mar 27, 2019

Ha I'm exactly at this point
https://observablehq.com/d/d7ae2b6c52756a79#groupCounts3

@mbostock
Copy link
Member Author

Related #135, I often find I want to create the simple JSON data structure instead of a d3.hierarchy instance.

@Fil
Copy link
Member

Fil commented Mar 30, 2019

Here's my current solution

hierarchical = d3.group(data, d => d.x, d => d.y, d => d.z);

d3.hierarchy(
  { name: "root", map: hierarchical },
  d =>
    d.map &&
    Array.from(d.map.entries()).map(e =>
      e[1] instanceof Map
        ? { name: e[0], map: e[1] }
        : { name: e[0], values: e[1] }
    )
)

@mbostock
Copy link
Member Author

Here is my proposal to fix this and #135, d3.children:

function children(values, ...keys) {
  return {
    children: (function node(value, depth) {
      return depth >= keys.length ? value
          : Array.from(value, ([key, value]) => ({key, children: node(value, depth + 1)}));
    })(group(values, ...keys), 0)
  };
}

https://observablehq.com/d/59ab45a241d52b98

@mbostock
Copy link
Member Author

This also makes me wonder whether we still need a replacement for d3.nest that supports sorting of keys and values…

@Fil
Copy link
Member

Fil commented Mar 30, 2019

I've tried your code and had to rename key to name to use @d3/treemap directly. I probably missed something. https://observablehq.com/compare/59ab45a241d52b98...8d4ce02fcb6c6715

@mbostock
Copy link
Member Author

That’s expected, but the input format used by that treemap example isn’t really a standard, and the name “key” felt more appropriate. (Perhaps it should be key/values instead of name/children, but then the name d3.nest would be more appropriate than d3.children.)

@Fil
Copy link
Member

Fil commented Mar 31, 2019

I rewrote https://observablehq.com/d/8d4ce02fcb6c6715 so that the name of each node is computed after the groupby/children operation. Using d3.hierarchy to traverse the json and update the properties.

@Fil
Copy link
Member

Fil commented Mar 31, 2019

In this case I also needed a reduce operation, so rollup instead of group:

function childrenRollup(values, reduce, ...keys) {
  return {
    children: (function node(value, depth) {
      return depth >= keys.length
        ? value
        : Array.from(value, ([key, value]) => ({
            key,
            children: node(value, depth + 1)
          }));
    })(d3.rollup(values, reduce, ...keys), 0)
  };
}

@mbostock
Copy link
Member Author

Yeah. That’s tricky. We have the existing d3.group and d3.rollup primitives, so this works well:

rollup = d3.rollup(
  raw.filter(d => d.year === "2014").filter(d => d.age === "26+"),
  v => d3.sum(v, d => d.use),
  d => d.drug,
  d => d.state
)

Maybe we just need a way to turn that into {name, children} objects? Kind of like array.map but applying recursively to map entries… I mean, this works:

({
  children: Array.from(rollup, ([name, children]) => {
    return {
      name,
      children: Array.from(children, ([name, value]) => {
        return {name, value};
      })
    };
  })
})

@mbostock
Copy link
Member Author

mbostock commented Mar 31, 2019

Here’s a way to pass the result of d3.rollup to d3.hierarchy:

root = d3.hierarchy([, rollup], ([, value]) => value instanceof Map && Array.from(value))

The data for each node is [key, value], where the root key is undefined, the non-leaf value is a Map, and the leaf value is a number (the result of the previous d3.sum). So, you can node.sum as:

root.sum(([, value]) => value)

@mbostock
Copy link
Member Author

So, in other words, if d3.hierarchy allowed children to be an iterable and not just an array, and automatically ignored children that are not iterable, you would say:

root = d3.hierarchy([, rollup], ([, value]) => value)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging a pull request may close this issue.

2 participants