airbnb / visx

🐯 visx | visualization components
https://airbnb.io/visx
MIT License
19.5k stars 715 forks source link

Collapsible tree example #162

Closed techniq closed 6 years ago

techniq commented 7 years ago

I derived a simple example from https://vx-demo.now.sh/trees available here: https://codesandbox.io/s/n3w687vmqj

I'm not overly happy with the forceUpdate() and would like to have it animate similar to https://bl.ocks.org/mbostock/4339083, but it's a start

techniq commented 7 years ago

@sghall - any tips on how to use react-move to produce something similar to https://bl.ocks.org/mbostock/4339083?

techniq commented 7 years ago

@hshoff / @sghall - It seems like to be able to make this work we would need to be able to add a <NodeGroup /> here

Maybe if <Tree/> / <Cluster/> receives a children function, we call it instead? Such as:

<Tree
  top={margin.top}
  left={margin.left}
  root={hierarchy(data, d => d.isExpanded ? d.children : null)}
  size={[
    height - margin.top - margin.bottom,
    width - margin.left - margin.right
  ]}
>
  ({ links, descendants }) => (
    // magic here
  )
</Tree>
hshoff commented 7 years ago

@techniq yup that’s a great solution. I’ve been busy with interactions, will get to this when i can. Happy to a review a PR if anyone wants to take a swing at this.

techniq commented 7 years ago

@hshoff No promises, but I'll see if I can get a PR pushed up soonish (should be fairly trivial) but currently this is me just kicking around vx so my other tasks have higher priority.

Seems we would want the same with vx-network and maybe other layouts. I've only recently been getting into d3 beyond the basics so I'm not aware of all the different layouts and their intricacies and how vx has integrated them.

I would love to hear if @sghall has any feedback on this with regards to react-move.

Lastly, I wonder if the child function / render prop should be embraced whole-heartedly throughout vx, in place of some HOCs (such as replacing withTooltip, withParentSize, withScreenSize, ...). I've had good success with it in my react-fetch-component and downshift, and I know Michael Jackson has been a big proponent of it lately

techniq commented 7 years ago

Also, just leaving this for later reference, but it would be nice if the <Tree /> example could, along with being collapsible, show how to reveal a node, similar to Search Collapsible Tree. This drag and drop and pannable example would be nice as well (maybe after from of your current vx-drag / vx-zoom work)

sghall commented 7 years ago

Hey. Off the top of my head, I'd say it's two NodeGroups for nodes and links. Do the links first so the nodes are on top in the SVG...something like...

<Tree
  top={margin.top}
  left={margin.left}
  root={hierarchy(data, d => d.isExpanded ? d.children : null)}
  size={[
    height - margin.top - margin.bottom,
    width - margin.left - margin.right
  ]}
>
  ({ links, descendants }) => (
    <NodeGroup
      data={links} 
      ...
    </NodeGroup>
    <NodeGroup
      data={descendants} 
      ...
    </NodeGroup>
  )
</Tree>

He's doing some magic here with stashing the last layout and using the "diagonal" path generator to get those nice curved lines. You can pass a custom interpolator to react-move to handle that.

This flubber example is using a custom interpolator... https://react-move.js.org/#/documentation/animate

Also, this piece storing if the children are collapsed is a little sticky. Need some state somewhere with that.

  function collapse(d) {
    if (d.children) {
      d._children = d.children;
      d._children.forEach(collapse);
      d.children = null;
    }
  }

  root.children.forEach(collapse);
  update(root);

If you get something committed I can jump in there and hack on it a little if there's issues.

techniq commented 7 years ago

Thanks @sghall. I have the expand/collapse working currently in this codesandbox and was looking to moving to the next step of animating.

Once I or @hshoff get the child function in place I'll take a stab at the transition but maybe ping you with a WIP codesandbox if I get stuck (especially the the interpolation)?

Looking at the d3 example (linked in the first post), it looks like Mike is using...

var diagonal = d3.svg.diagonal()
    .projection(function(d) { return [d.y, d.x]; });

...for the interpolation so we might be able to do something similar...

techniq commented 7 years ago

Actually d3.svg.diagonal() was replaced in d3 v4 with linkVertical (or horizontal/radial) and is what vx's LinkVertical uses

techniq commented 7 years ago

@hshoff @sghall I've made some progress using react-move to animate the expand/collapse animations of a <Tree /> after the render prop was exposed (thanks again @hshoff )

vx_tree_react_move

There is still some work to do:

I don't know if I'll get much more time to iterate on this till next week so once I get it on codesandbox, feel free to fork and tweak if you want.

hshoff commented 7 years ago

awesome!

hshoff commented 7 years ago

just published v0.0.141 changelog

techniq commented 7 years ago

Some progress animating links and nodes now collapse to closing parent/grandparent/etc (not just the parent). I've updated my codesandbox with these changes if you want to take a look at the code.

vx_tree_react_move_20171008

A few items still needing addressed

Expanding grandchildren from grandparent

While collapsing a grandparent will fold all children/grandchildren/etc into the node, expanding currently only transitions from each children's parent's location. This is best demonstrated with an example. Notice when expanding T, nodes A1, A2, A3, C, and others (and their links) do not appear to be sourced from T.

vx_tree_react_move_20171008_expanding_grandparent_issue

Order of collapsing

Based on how I'm saving x0/y0 only when a node is expanding, if a sibling node is expanded and then the original node is collapsed, the transitions are incorrect. For example,

vx_tree_react_move_20171008_collasing_order_issue

There may be other issues as well, especially when compared to the canonical d3 example, but it's progressing. If anyone has any suggestions (alternative approaches) to resolve these issues I'm interested.

techniq commented 7 years ago

I created a radial tree example derived from the cartesian layout version and based on the Radial Tidy Tree d3 example

vx_tree_react_move_20171009_radial_example

The only changes needed to support this layout was to:

This example also has the same quirks/issues as mentioned in previous comment, but so far I'm really enjoying using vx and react-move together.

hshoff commented 7 years ago

@techniq nice! i added a link to your example to the in the wild section of the readme here: https://github.com/hshoff/vx/commit/ee2da22fdb246060b0536771330b599e5b339aa9

(looks like your radial example link goes to the same place as the cartesian example, happy to add that as well once you have an updated link)

techniq commented 7 years ago

Oops, link fixed above (and thanks)

Btw, if you or @sghall (probably more likely) have any ideas on the transition quirks, I'm all ears as I'm a bit stumped at the moment.

Vaga91 commented 6 years ago

Hello dears, I use this tree to display data, and I want to thank you for this work. There is one problem, please help. With large data When the layout is polar and the link is a curve nodes overlap each other (you can see here https://codesandbox.io/s/p5pkn9jwx on Expand A ). It is possible to do something to fix this problem? Thanks.

techniq commented 6 years ago

@Vaga91 you could adjust the separation prop. The larger you make the ratio, the greater the spread between sibling nodes.

Your example

separation={(a, b) => (a.parent == b.parent ? 1 : .5) / a.depth} image

Possible solution

separation={(a, b) => (a.parent == b.parent ? 1 : 5) / a.depth} image

Feel free to experiment with other values.

You might also want to look into enlarging your rendered size and allowing pan/zoom. We have a component coming soon, but you might want to look at this PanZoom component in the short term.

hshoff commented 6 years ago

Closing this as examples were added in https://github.com/hshoff/vx/pull/234. Can view them here: https://vx-demo.now.sh/linkTypes

@Vaga91 feel free to continue the discussion here.

Vaga91 commented 6 years ago

@techniq I've been experimenting these few days. And here is the result Tree I added separation={(a, b) => ((a.children === b.children) && (a.parent === b.parent) ? 1 : 7) / a.depth } It works, but needs to be improved.

Thank you :)

UltimateForm commented 5 years ago

hey @techniq do you remember this example? i've been working on top of what you had done, updating it (packages and to typescript(which in hindsight might have not been the best idea) )trying to expand it a little bit to fit some of my needs (node selection, adding removal, filtering, etc, click around to see) - https://codesandbox.io/s/vx-collapsible-tree-typescript-oxewc but i have a some concerns, mostly related to performance and possible over-complexity, see I need it to be "responsive" since the tree will be representing a tree data structure built and maintained by a backoffice user, right now i'm using react-measure for responsive Tree Canvas size and an injected ref into the Text element with getBoundingClientRect() for responsive node size -these things also require rerendering for the correction of positions, i know of #375 and also of https://vx-demo.now.sh/responsive (but this one seems quite similar to react-measure), i'm bothering you with this because i thought that maybe you developed it more since last time and could give me some tips

techniq commented 5 years ago

Hey @UltimateForm, I do remember that example, but it's been a while since I've looked at it. I like the features you've added to it, especially the node selection with path. Sadly, I haven't worked on this in some time, and my usage was mostly for a one off for a presentation. With that said, I do have plans to use this in an upcoming feature in one of our applications, but it's not currently on the roadmap (probably a few months out at least). If I make any improvements (I plan to push them down to vx if they make sense) I will try to remember to ping you in this thread.

With that said, the demo seems to interact well, but I'm assuming you have many more nodes than I'm creating using the tool? I know my tree was made up of ~3000 nodes (personnel structure of a decent size company) and it seems to function well (but I would never expand more than a handful of nodes at a time). I mostly ran into canvas size constraints as well, and might have opened more if I used @vx/zoom that it wasn't available at the time.

Let me know if you make any more progress on your side, and I'll be sure to do the same on mine.

UltimateForm commented 5 years ago

Thanks for the quick reply @techniq 🙏 I didn't quite understand what you meant by

I'm assuming you have many more nodes than I'm creating using the tool

on the condesandbox i sent the nodes are only created on user input, if you're talking about the growth potential, I have not tested the tool with that many nodes, mainly because the usecase im doing this for does not benefit from that many nodes, i'd even say it suffers, but i should, for test purposes at least use a pre-built data structure with tons of nodes, i'm sure the current performance issues would be more noticeable that way

besides vx/responsive i'm also considering using vx/zoom and vx/drag to add more maneuverability to the tree

Either way, i'm really happy to know you're planning on revisiting this, I'll keep working on my side as well. best of luck 🙏

edit: https://github.com/UltimateForm/vx-collapsible-tree-tsx the repo, in case you feel like checking any future work on it

mindceb commented 4 years ago

@techniq Hi. I using you sample, but i need help with collapse all tree's elements at init. How can i do this, with visible links?

techniq commented 4 years ago

@mindceb I'm having trouble following what you're asking, do you mean to only show the root node at the first (although this is what my example does so I don't think it's that.

Can you create a CodeSandbox with your current progress?

alexschachne commented 4 years ago

@techniq Any chance you've gotten the entire tree to animate out on page load? I know you can expand onClick but am looking to have it start expanded out and be collapsable. Let me know, thanks!

techniq commented 4 years ago

@alexschachne I do not. Currently that example stores the isExpanded as a prop on each Node, but I think it might be best to track it in a separate state array.

instead of...

const root = hierarchy(data, d => (d.isExpanded ? d.children : null))

something like...

const [expandedIds, setExpandedIds = useState([]);
const root = hierarchy(data, d => expandedIds.includes(d.id) ? d.children : null))

and then use an useEffect() to expand all the items (might also be able to use root.each:

useEffect(() => {
  setExpandedIds(d.map(x => x.id))
}, []);

and update the onClick on the node rect to add/remove that node's id from the array (would remove the forceUpdate as well).

Let me know if this works.


Side note, I've been experimenting with various hierarchy layouts and vx (including animations and such). Each layout is in a various state of experimentation (and none are by any means "done". I also haven't added all the layouts I plan to, include Tree, Circle Pack, Chord, etc). https://github.com/techniq/vx-hierarchy-examples https://nxmen.csb.app/

techniq commented 4 years ago

@alexschachne Actually it may be harder than this...

I added a quick Tree example (based on that old codesandbox and some some changes I made within my projects) and updated to store expandedNodeKeys instead of adding isExpanded to the node itself... this worked good, but attempting to expand all the nodes at startup causes a react-spring error:

image

I also tried not filtering any of the children so the full tree would be rendered, but it too threw the error.

Probably since it tween's from the parent/last position.

techniq commented 4 years ago

@alexschachne The problem was due to the Links (if you commented them out, the Nodes animated correctly). I think I have this fixed now, but the example tree I'm using is rather heavy. Need to test with a smaller tree (I have issues with react-spring and the tree when running in dev mode (seems to do better when using a prod build).

Anyways, it's not perfect, but might get you closer (and the pattern to track the ids in a separate array is a better pattern.. using a Set and Set.has() would be more performance than Array.includes and doing scans, but the number of expanded items should be pretty small.