react-hierarchy-tree-graph
Version:
React component to create interactive D3 tree hierarchies
395 lines (328 loc) • 29.9 kB
Markdown
# React Hierarchy Tree Graph <!-- omit in toc -->
[](https://greenkeeper.io/)
[](https://travis-ci.org/bkrem/react-d3-tree)
[](https://coveralls.io/github/bkrem/react-d3-tree?branch=master)
[](https://www.codacy.com/app/ben.kremer/react-d3-tree?utm_source=github.com&utm_medium=referral&utm_content=bkrem/react-d3-tree&utm_campaign=Badge_Grade)
[](https://badge.fury.io/js/react-d3-tree)
[](https://www.npmjs.com/package/react-hierarchy-tree-graph)
React Hierarchy Tree Graph is a [React](http://facebook.github.io/react/) component that lets you represent hierarchical data (e.g. ancestor trees, organisational structure, package dependencies) as an animated & interactive tree graph by leveraging [D3](https://d3js.org/)'s `tree` layout.
## Contents <!-- omit in toc -->
- [Demo](#demo)
- [Installation](#installation)
- [Usage](#usage)
- [Props](#props)
- [Node shapes](#node-shapes)
- [Overridable `shapeProps`](#overridable-shapeprops)
- [Individual `shapeProps`](#individual-shapeprops)
- [Styling](#styling)
- [Pre-defining a node's `_collapsed` state](#pre-defining-a-nodes-_collapsed-state)
- [Keeping large trees responsive](#keeping-large-trees-responsive)
- [External data sources](#external-data-sources)
- [Example](#example)
- [Using foreignObjects](#using-foreignobjects)
- [`nodeLabelComponent`](#nodelabelcomponent)
- [Example](#example-1)
- [Recipes](#recipes)
- [Auto-centering inside `treeContainer`](#auto-centering-inside-treecontainer)
- [Adding & removing nodes dynamically](#adding--removing-nodes-dynamically)
## Demo
- Current release: https://bkrem.github.io/react-d3-tree-demo/
## Installation
```bash
npm i react-hierarchy-tree-graph
```
## Usage
```jsx
import React from 'react';
import Tree from 'react-hierarchy-tree-graph';
const myTreeData = [
{
name: 'Top Level',
attributes: {
keyA: 'val A',
keyB: 'val B',
keyC: 'val C',
},
children: [
{
name: 'Level 2: A',
attributes: {
keyA: 'val A',
keyB: 'val B',
keyC: 'val C',
},
},
{
name: 'Level 2: B',
},
],
},
];
class MyComponent extends React.Component {
render() {
return (
{/* <Tree /> will fill width/height of its container; in this case `#treeWrapper` */}
<div id="treeWrapper" style={{width: '50em', height: '20em'}}>
<Tree data={myTreeData} />
</div>
);
}
}
```
## Props
| Property | Type | Options | Required? | Default | Description |
|:------------------------------|:-----------------------|:---------------------------------------------------------------------------------------|:----------|:--------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `data` | `array`<br/>`object` | | required | `undefined` | Single-element array containing the root node object (see `myTreeData` above). <br /> Contains (at least) a `name` property. <br/> Passing the root node object without an array wrapping it is also possible. |
| `nodeSvgShape` | `object` | see [Node shapes](#node-shapes) | | `{shape: 'circle', shapeProps: {r: 10}}` | Sets a specific SVG shape element + shapeProps to be used for each node. |
| `nodeLabelComponent` | `object` | see [Using foreignObjects](#using-foreignobjects) | | `null` | Allows using a React component as a node label; requires `allowForeignObjects` to be set. |
| `onClick` | `func` | | | `undefined` | Callback function to be called when a node is clicked. <br /><br />Has the function signature `(nodeData, evt)`. The clicked node's data object is passed as first parameter, event object as second. |
| `onMouseOver` | `func` | | | `undefined` | Callback function to be called when mouse enters the space belonging to a node. <br /><br />Has the function signature `(nodeData, evt)`. The clicked node's data object is passed as first parameter, event object as second. |
| `onMouseOut` | `func` | | | `undefined` | Callback function to be called when mouse leaves the space belonging to a node. <br /><br />Has the function signature `(nodeData, evt)`. The clicked node's data object is passed as first parameter, event object as second. |
| `onUpdate` | `func` | | | `undefined` | Callback function to be called when the inner D3 component updates. That is - on every zoom or translate event, or when tree branches are toggled. The node's data object, as well as zoom level and coordinates are passed to the callback. |
| `orientation` | `string` (enum) | `horizontal` `vertical` | | `horizontal` | `horizontal` - Tree expands left-to-right. <br /><br /> `vertical` - Tree expands top-to-bottom. |
| `translate` | `object` | | | `{x: 0, y: 0}` | Translates the graph along the x/y axis by the specified amount of pixels (avoids the graph being stuck in the top left canvas corner). |
| `pathFunc` | `string (enum)`/`func` | `diagonal`<br/>`elbow`<br/>`straight`<br/>`customFunc(linkData, orientation)` | | `diagonal` | `diagonal` - Smooth, curved edges between parent-child nodes. <br /><br /> `elbow` - Sharp edges at right angles between parent-child nodes. <br /><br /> `straight` - Straight lines between parent-child nodes. <br /><br /> `customFunc` - Custom draw function that accepts `linkData` as its first param and `orientation` as its second. |
| `collapsible` | `bool` | | | `true` | Toggles ability to collapse/expand the tree's nodes by clicking them. |
| `useCollapseData` | `bool` | see [Pre-defining a node's `_collapsed` state](#pre-defining-a-nodes-_collapsed-state) | | `false` | Toggles whether the tree should automatically use any `_collapsed: bool` properties it finds on nodes in the passed data set to configure its initial layout. |
| `shouldCollapseNeighborNodes` | `bool` | | | `false` | If a node is currently being expanded, all other nodes at the same depth will be collapsed. |
| `initialDepth` | `number` | `0..n` | | `undefined` | Sets the maximum node depth to which the tree is expanded on its initial render. <br /> Tree renders to full depth if prop is omitted. |
| `depthFactor` | `number` | `-n..0..n` | | `undefined` | Ensures the tree takes up a fixed amount of space (`node.y = node.depth * depthFactor`), regardless of tree depth. <br /> **TIP**: Negative values invert the tree's direction. |
| `zoomable` | `bool` | | | `true` | Toggles ability to zoom in/out on the Tree by scaling it according to `props.scaleExtent`. |
| `zoom` | `number` | `0..n` | | `1` | A floating point number to set the initial zoom level. It is constrained by `props.scaleExtent`. `1` is the default "non-zoomed" level. |
| `scaleExtent` | `object` | `{min: 0..n, max: 0..n}` | | `{min: 0.1, max: 1}` | Sets the minimum/maximum extent to which the tree can be scaled if `props.zoomable` is true. |
| `nodeSize` | `object` | `{x: 0..n, y: 0..n}` | | `{x: 140, y: 140}` | Sets a fixed size for each node. <br /><br /> This does not affect node circle sizes, circle sizes are handled by the `circleRadius` prop. |
| `separation` | `object` | `{siblings: 0..n, nonSiblings: 0..n}` | | `{siblings: 1, nonSiblings: 2}` | Sets separation between neighbouring nodes, differentiating between siblings (same parent) and non-siblings. |
| `transitionDuration` | `number` | `0..n` | | `500` | Sets the animation duration (in ms) of each expansion/collapse of a tree node. <br /><br /> Set this to `0` to deactivate animations completely. |
| `textLayout` | `object` | `{textAnchor: enum, x: -n..0..n, y: -n..0..n, transform: string}` | | `{textAnchor: "start", x: 10, y: -10, transform: undefined }` | Configures the positioning of each node's text (name & attributes) relative to the node itself.<br/><br/>`textAnchor` enums mirror the [`text-anchor` spec](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor).<br/><br/>`x` & `y` accept integers denoting `px` values.<br/><br/> `transform` mirrors the [svg `transform` spec](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform). |
| `styles` | `object` | see [Styling](#styling) | | `Node`/`Link` CSS files | Overrides and/or enhances the tree's default styling. |
| `allowForeignObjects` | `bool` | see [Using foreignObjects](#using-foreignobjects) | | `false` | Allows use of partially supported `<foreignObject />` elements. |
| `circleRadius` (legacy) | `number` | `0..n` | | `undefined` | Sets the radius of each node's `<circle>` element.<br /><br /> **Will be deprecated in v2, please use `nodeSvgShape` instead.** |
| `id` | `number` | `0..n` | | `undefined` | Sets unique id for each tree node
## Node shapes
The `nodeSvgShape` prop allows specifying any [SVG shape primitive](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Basic_Shapes) to describe how the tree's nodes should be shaped.
> Note: `nodeSvgShape` and `circleRadius` are mutually exclusive props. `nodeSvgShape` will be used unless the legacy `circleRadius` is specified.
For example, assuming we want to use squares instead of the default circles, we can do:
```js
const svgSquare = {
shape: 'rect',
shapeProps: {
width: 20,
height: 20,
x: -10,
y: -10,
}
}
// ...
<Tree data={myTreeData} nodeSvgShape={svgSquare}>
```
To avoid rendering any node element, simply set `nodeSvgShape` to `{ shape: 'none' }`.
### Overridable `shapeProps`
`shapeProps` is currently merged with `node.circle`/`leafNode.circle` (see [Styling](#styling)).
This means any properties passed in `shapeProps` will be overridden by **properties with the same key** in the `node.circle`/`leafNode.circle` style props.
This is to prevent breaking the legacy usage of `circleRadius` + styling via `node/leafNode` properties until it is deprecated fully in v2.
**From v1.5.x onwards, it is therefore recommended to pass all node styling properties through `shapeProps`**.
### Individual `shapeProps`
`shapeProps` can be passed to a node individually by adding the `nodeSvgShape` property to the relevant node's data set. This allows setting each node's style, shape and size independently of the tree's overall `shapeProps` configuration (see [Styling](#styling)).
The usage example above can be extended to include individual `shapeProps`:
```jsx
import React from 'react';
import Tree from 'react-hierarchy-tree-graph';
const myTreeData = [
{
name: 'Parent Node',
attributes: {
keyA: 'val A',
keyB: 'val B',
keyC: 'val C',
},
nodeSvgShape: {
shapeProps: {
fill: 'blue',
},
},
children: [
{
name: 'Inner Node',
attributes: {
keyA: 'val A',
keyB: 'val B',
keyC: 'val C',
},
nodeSvgShape: {
shape: 'rect',
shapeProps: {
width: 20,
height: 20,
x: -10,
y: -10,
fill: 'red',
},
},
},
{
name: 'Level 2: B',
},
],
},
];
...
```
In the above, "Parent Node" will only be blue, but it will keep the default size and geometrical shape. "Inner Node", however, will completely change to a red rectangle with the given dimensions. Omitting `shape`, will keep node's default appearance.
## Styling
The tree's `styles` prop may be used to override any of the tree's default styling.
The following object shape is expected by `styles`:
```js
{
links: <svgStyleObject>,
nodes: {
node: {
circle: <svgStyleObject>,
name: <svgStyleObject>,
attributes: <svgStyleObject>,
},
leafNode: {
circle: <svgStyleObject>,
name: <svgStyleObject>,
attributes: <svgStyleObject>,
},
},
}
```
where `<svgStyleObject>` is any object containing CSS-like properties that are compatible with an `<svg>` element's `style` attribute, for example:
```js
{
stroke: 'blue',
strokeWidth: 3,
}
```
For more information on the SVG `style` attribute, [check this out](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/style).
## Pre-defining a node's `_collapsed` state
The tree's initial layout can be specified node-for-node by enabling the `useCollapseData` prop.
If activated, `react-hierarchy-tree-graph` will look for nodes specifying their own `_collapsed` property and structure the initial layout accordingly.
For example, given an input data set of the shape:
```js
const dataWithCollapsedProperties = [
{
name: 'Top Level',
children: [
{
name: '2: A',
children: [
{
name: '3: Collapsed son of A',
_collapsed: true,
children: [
{
name: '4: Son of A',
},
{
name: '4: Daughter of A',
},
],
},
{
name: '3: Daughter of A',
},
],
},
{
name: '2: B',
},
],
},
];
```
all nodes (with children) will be expanded except the `Collapsed son of A` node.
Clarifications:
* Why is a leading underscore required for `_collapsed`?
* D3 binds its own internal `collapsed` property to each node, `react-d3-tree` simply adds a leading underscore to create a separate namespace.
* Should `_collapsed: false` be explicitly set?
* No, there's no need to do this since `react-hierarchy-tree-graph` assumes a node to be expanded unless specified otherwise.
> **Note:** `props.useCollapseData` and `props.initialDepth` are mutually exclusive. If `useCollapseData` is set, `initialDepth` values will be ignored.
## Keeping large trees responsive
Attempting to render large trees with animated transitions may cause significant input lag. This is due to limitations related to the way D3's `select().transition()` enqueues calls to `requestAnimationFrame`, discussed [here](https://github.com/bkrem/react-hierarchy-tree-graph/issues/41#issuecomment-338425414).
Until a custom debounce for expand/collapse has been implemented, **it is therefore recommended to set `props.transitionDuration` to `0` for large tree graphs** if you're experiencing responsiveness issues.
## External data sources
Statically hosted JSON or CSV files can be used as data sources via the additional `treeUtil` module.
### Example
```jsx
import React from 'react';
import { Tree, treeUtil } from 'react-hierarchy-tree-graph';
const csvSource = 'https://raw.githubusercontent.com/bkrem/react-d3-tree/master/docs/examples/data/csv-example.csv';
constructor() {
super();
this.state = {
data: undefined,
};
}
componentWillMount() {
treeUtil.parseCSV(csvSource)
.then((data) => {
this.setState({ data })
})
.catch((err) => console.error(err));
}
class MyComponent extends React.Component {
render() {
return (
{/* <Tree /> will fill width/height of its container; in this case `#treeWrapper` */}
<div id="treeWrapper" style={{width: '50em', height: '20em'}}>
<Tree data={this.state.data} />
</div>
);
}
}
```
For details regarding the `treeUtil` module, please check the module's [API docs](docs/util/util.md).
For examples of each data type that can be parsed with `treeUtil`, please check the [data source examples](docs/examples/data).
## Using foreignObjects
> ⚠️ Requires `allowForeignObjects` prop to be set due to limited browser support: [IE does not currently support `foreignObject` elements](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject#Browser_compatibility).
The SVG spec's `foreignObject` element allows foreign XML content to be rendered into the SVG namespace, unlocking the ability to use regular React components for elements of the tree graph.
### `nodeLabelComponent`
The `nodeLabelComponent` prop provides a way to use a React component for each node's label. It accepts an object with the following signature:
```ts
{
render: ReactElement,
foreignObjectWrapper?: object
}
```
* `render` is the XML React-D3-Tree will use to render each node's label.
* `foreignObjectWrapper` contains a set of attributes that should be passed to the `<foreignObject />` that wraps `nodeLabelComponent`. For possible attributes please check the [spec](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject#Global_attributes).
**Note: By default, `foreignObjectWrapper` will set its width and height attributes to `nodeSize.x - 24px` and `nodeSize.y - 24px` respectively; where a base margin of 24px is subtracted to avoid the overlapping of elements.**
To override this behaviour for each attribute, specify `width` and/or `height` properties for your `foreignObjectWrapper`.
**Note:** The ReactElement passed to `render` is cloned with its existing props and **receives an additional `nodeData` object prop, containing information about the current node.**
#### Example
Assuming we have a React component `NodeLabel` and we want to avoid node's label overlapping with the node itself by moving its position along the Y-axis, we could implement `nodeLabelComponent` like so:
```jsx
class NodeLabel extends React.PureComponent {
render() {
const {className, nodeData} = this.props
return (
<div className={className}>
<h2>{nodeData.name}</h2>
{nodeData._children &&
<button>{nodeData._collapsed ? 'Expand' : 'Collapse'}</button>
}
</div>
)
}
}
/* ... */
render() {
return (
<Tree
data={myTreeData}
allowForeignObjects
nodeLabelComponent={{
render: <NodeLabel className='myLabelComponentInSvg' />,
foreignObjectWrapper: {
y: 24
}
}}
/>
)
}
```
## Recipes
#### [Auto-centering inside `treeContainer`](https://codesandbox.io/s/vvz51w5n63)
#### [Adding & removing nodes dynamically](https://codesandbox.io/s/jz1v7o2ryy)