airbnb / visx

šŸÆ visx | visualization components
https://airbnb.io/visx
MIT License
19.27k stars 707 forks source link

is it possible to create a tree with nodes on either side #379

Open dagda1 opened 5 years ago

dagda1 commented 5 years ago

I am looking to create something like this:

I need a tree but with the nodes on either side. I've had create success with vxtree but that has one root node.

Is there a way I could spread them out like the diagram below?

tree

techniq commented 5 years ago

I believe the technique Iā€™ve seen using straight d3.js is to render 2 trees with each root at the middle/overlapping.

For example: http://bl.ocks.org/jdarling/2503502

You should be able to use 2 <Tree /> components to accomplish the same thing. See https://vx-demo.now.sh/linkTypes for an example to change direction (you should be able to swap the x-scaleā€™s min/max to go right/left.

dagda1 commented 5 years ago

@techniq the 2 trees approach did not work for me because I need zooming and panning which was difficult to keep the trees together so I ended up with 1 tree:

https://codesandbox.io/s/znj3ykqwj3

I ended up doing the calculations for the nodes in the ./src/components/NodesFanout component but this feels wrong, is there a better way:

import * as React from "react";
import NodeGroup from "react-move/NodeGroup";
import { StructureItem } from "../../types";
import { Node } from "../Node";
import { HierarchyPointNode } from "d3-hierarchy";
import { HierarchyLabelProps } from "../HierarchyLabel";
import { findIndex } from "lodash";
import { NodeHeight } from "../Tree";
import { findCollapsedParent } from "../../util/node";

const { Group } = require("@vx/group");

export interface NodesProps {
  nodes: HierarchyPointNode<StructureItem>[];
  clickHandler: any;
  shapeLength: number;
  collapse: boolean;
  Label: React.ComponentType<HierarchyLabelProps>;
  LabelCollapsed: React.ComponentType<HierarchyLabelProps>;
}

const positionNodes = (
  node: HierarchyPointNode<StructureItem>,
  nodes: HierarchyPointNode<StructureItem>[]
) => {
  let left: number;
  let top: number;

  if (!node.parent) {
    left = node.y / 2 - NodeHeight;
    top = node.x - NodeHeight;
  } else if (node.data && node.data.isRight) {
    const index = findIndex(nodes, node);
    const lastLeft = nodes[index - 1];

    top = lastLeft.x;
    left = NodeHeight;
  } else {
    top = node.x;
    left = node.y;
  }

  return {
    top: [top],
    left: [left],
    opacity: [1]
  };
};

export const NodesFanout: React.SFC<NodesProps> = ({
  Label,
  LabelCollapsed,
  nodes,
  shapeLength,
  clickHandler,
  collapse
}) => {
  return (
    <NodeGroup
      data={nodes}
      keyAccessor={(d: HierarchyPointNode<StructureItem>) => d.data.id}
      start={(node: HierarchyPointNode<StructureItem>) => {
        let left: number;
        let top: number;

        if (!node.parent) {
          left = node.y / 2 - NodeHeight;
          top = node.x - NodeHeight;
        } else {
          left = node.parent.y / 2;
          top = node.parent.x;
        }

        return {
          top,
          left,
          opacity: 0
        };
      }}
      enter={node => positionNodes(node, nodes)}
      update={node => positionNodes(node, nodes)}
      leave={node => {
        let left: number;
        let top: number;

        const collapsedParent = findCollapsedParent(
          node.parent
        ) as HierarchyPointNode<StructureItem>;

        if (!collapsedParent.parent) {
          left = collapsedParent.y / 2;
          top = collapsedParent.x - NodeHeight;
        } else {
          left = collapsedParent.parent.y / 2;
          top = collapsedParent.parent.x - NodeHeight;
        }

        return {
          top: [top],
          left: [left],
          opacity: [0]
        };
      }}
    >
      {nodes => (
        <Group>
          {nodes.map(({ key, data: node, state }, index) => {
            return (
              <Group
                top={state.top}
                left={state.left}
                key={key}
                opacity={state.opacity}
                className={`node__${index}`}
              >
                <Node
                  Label={Label}
                  LabelCollapsed={LabelCollapsed}
                  node={node}
                  shapeLength={shapeLength}
                  clickHandler={(e: any) => {
                    clickHandler({ e, node });
                  }}
                  key={key}
                  collapse={collapse}
                />
              </Group>
            );
          })}
        </Group>
      )}
    </NodeGroup>
  );
};

My animation is a bit wrong also.

techniq commented 5 years ago

Hmm, I've not done this myself so I can't be of much more help right now.

You might be able get away using a Polar coordinate system and adjust the size and separation props.

See: https://github.com/hshoff/vx/issues/162#issuecomment-365493922 for some experimentation with separation

aoloo commented 3 years ago

@dagda1 @techniq I know this is a old post, but I have a use case requiring a tree with nodes on each side using vx/hierarchy. Has anyone come up with a solution for this? I haven't had any luck finding a good example.

techniq commented 3 years ago

@aoloo Sorry I haven't had this use case myself thus far.

When I'm looking for new examples / approaches I'll typically look at d3 observables and port to React / visx.

This NCAA Bracket example might give you a start, I just quickly read through it but it does use d3.hierarchy / d3.tree:

It appears to be well written and has some good notes. I think it's rendering 4 trees (for each quadrant) and blending them together as 1, but I didn't analyze it deeply.

With hierarchies/trees, you need to have a single root node. You can sometimes work around this by creating a "pseudo" root that you hide visually. You could try that approach, but you'd have to create a tree left to right, as opposed to inside out (with the center being at the middle of the nodes, at opposed to at the root).

This layout might be better represented as a graph structure made of nodes and links, but I think searching for "bracket" helps to identify this type of layout.

Sorry I can't be of more help.