react-d3-dag
Version:
React component to create interactive D3 directed acyclic graphs (DAGs)
327 lines (267 loc) • 12.7 kB
Markdown
<h1 align="center">React D3 DAG</h1>
<p align="center">
<a href="#buildstatus">
<img alt="build status" src="https://github.com/forivall/react-d3-dag/workflows/Build/badge.svg">
</a>
<a href="https://coveralls.io/github/forivall/react-d3-dag?branch=master">
<img alt="coverage status" src="https://coveralls.io/repos/github/forivall/react-d3-dag/badge.svg?branch=master">
</a>
<a href="https://www.npmjs.com/package/react-d3-dag">
<img alt="npm package" src="https://img.shields.io/npm/v/react-d3-dag?style=flat">
</a>
<a href="https://www.npmjs.com/package/react-d3-dag">
<img alt="npm package: downloads monthly" src="https://img.shields.io/npm/dm/react-d3-dag.svg">
</a>
<a href="https://bundlephobia.com/result?p=react-d3-dag">
<img alt="npm package: minzipped size" src="https://img.shields.io/bundlephobia/minzip/react-d3-dag">
</a>
<a href="https://www.npmjs.com/package/react-d3-dag">
<img alt="npm package: types" src="https://img.shields.io/npm/types/react-d3-dag">
</a>
<a href="https://github.com/prettier/prettier">
<img alt="code style: prettier" src="https://img.shields.io/badge/code_style-prettier-ff69b4.svg">
</a>
</p>
<p align="center">
<h3 align="center"><a href="https://forivall.github.io/react-d3-dag">👾 Playground</a></h3>
<h3 align="center"><a href="https://forivall.github.io/react-d3-dag/docs">📖 API Documentation (v3)</a></h3>
</p>
NOTE: this is a friendly fork of [react-d3-tree], and will pull upstream when possible.
As such, you may see some references to "tree" in the documentation.
React D3 DAG is a [React](http://facebook.github.io/react/) component that
lets you represent directed acyclical data (e.g. dependency graphs, git history)
as an interactive graph with minimal setup, by leveraging
the [D3](https://d3js.org/)-[`dag`](https://github.com/erikbrinkman/d3-dag)
layout.
> **Upgrading from v1? Check out the [v2 release notes](https://github.com/forivall/react-d3-dag/releases/tag/v2.0.0).**
## Contents <!-- omit in toc -->
- [Installation](#installation)
- [Usage](#usage)
- [Props](#props)
- [Working with the default Tree](#working-with-the-default-tree)
- [Providing `data`](#providing-data)
- [Styling Nodes](#styling-nodes)
- [Styling Links](#styling-links)
- [Event Handlers](#event-handlers)
- [Customizing the Tree](#customizing-the-tree)
- [`renderCustomNodeElement`](#rendercustomnodeelement)
- [`pathFunc`](#pathfunc)
- [Providing your own `pathFunc`](#providing-your-own-pathfunc)
- [Development](#development)
- [Setup](#setup)
- [Hot reloading](#hot-reloading)
- [Contributors](#contributors)
## Installation
```bash
npm i --save react-d3-dag
```
## Usage
```jsx
import React from 'react';
import Tree from 'react-d3-dag';
// This is a simplified example of an org chart with a depth of 2.
// Note how deeper levels are defined recursively via the `children` property.
const orgChart = {
name: 'CEO',
children: [
{
name: 'Manager',
attributes: {
department: 'Production',
},
children: [
{
name: 'Foreman',
attributes: {
department: 'Fabrication',
},
children: [
{
name: 'Worker',
},
],
},
{
name: 'Foreman',
attributes: {
department: 'Assembly',
},
children: [
{
name: 'Worker',
},
],
},
],
},
],
};
export default function OrgChartTree() {
return (
// `<Tree />` will fill width/height of its container; in this case `#treeWrapper`.
<div id="treeWrapper" style={{ width: '50em', height: '20em' }}>
<Tree data={orgChart} />
</div>
);
}
```
## Props
For details on all props accepted by `Tree`, check out the [TreeProps reference docs](https://forivall.github.io/react-d3-tree/docs/interfaces/_src_tree_types_.treeprops.html).
The only required prop is [data](https://forivall.github.io/react-d3-tree/docs/interfaces/_src_tree_types_.treeprops.html#data), all other props on `Tree` are optional/pre-defined (see "Default value" on each prop definition).
## Working with the default Tree
`react-d3-dag` provides default implementations for `Tree`'s nodes & links, which are intended to get you up & running with a working tree quickly.
This section is focused on explaining **how to provide data, styles and event handlers for the default `Tree` implementation**.
> Need more fine-grained control over how nodes & links appear/behave? Check out the [Customizing the Tree](#customizing-the-tree) section below.
### Providing `data`
By default, `Tree` expects each node object in `data` to implement the [`RawNodeDatum` interface](https://forivall.github.io/react-d3-tree/docs/interfaces/_src_types_common_.rawnodedatum.html):
```ts
interface RawNodeDatum {
name: string;
attributes?: Record<string, string | number | boolean>;
children?: RawNodeDatum[];
}
```
The `orgChart` example in the [Usage](#usage) section above is an example of this:
- Every node has at least a `name`. This is rendered as the **node's primary label**.
- Some nodes have `attributes` defined (the `CEO` node does not). **The key-value pairs in `attributes` are rendered as a list of secondary labels**.
- Nodes can have further `RawNodeDatum` objects nested inside them via the `children` key, creating a hierarchy from which the tree graph can be generated.
### Styling Nodes
`Tree` provides the following props to style different types of nodes, all of which use an SVG `circle` by default:
- `rootNodeClassName` - applied to the root node.
- `branchNodeClassName` - applied to any node with 1+ children.
- `leafNodeClassName` - applied to any node without children.
To visually distinguish these three types of nodes from each other by color, we could provide each with their own class:
```css
/* custom-tree.css */
.node__root > circle {
fill: red;
}
.node__branch > circle {
fill: yellow;
}
.node__leaf > circle {
fill: green;
/* Let's also make the radius of leaf nodes larger */
r: 40;
}
```
```jsx
import React from 'react';
import Tree from 'react-d3-dag';
import './custom-tree.css';
// ...
export default function StyledNodesTree() {
return (
<div id="treeWrapper" style={{ width: '50em', height: '20em' }}>
<Tree
data={data}
rootNodeClassName="node__root"
branchNodeClassName="node__branch"
leafNodeClassName="node__leaf"
/>
</div>
);
}
```
> For more details on the `className` props for nodes, see the [TreeProps reference docs](https://forivall.github.io/react-d3-tree/docs/interfaces/_src_tree_types_.treeprops.html).
### Styling Links
`Tree` provides the `pathClassFunc` property to pass additional classNames to every link to be rendered.
Each link calls `pathClassFunc` with its own `TreeLinkDatum` and the tree's current `orientation`. `Tree` expects `pathClassFunc` to return a `className` string.
```jsx
function StyledLinksTree() {
const getDynamicPathClass = ({ source, target }, orientation) => {
if (!target.children) {
// Target node has no children -> this link leads to a leaf node.
return 'link__to-leaf';
}
// Style it as a link connecting two branch nodes by default.
return 'link__to-branch';
};
return (
<Tree
data={data}
// Statically apply same className(s) to all links
pathClassFunc={() => 'custom-link'}
// Want to apply multiple static classes? `Array.join` is your friend :)
pathClassFunc={() => ['custom-link', 'extra-custom-link'].join(' ')}
// Dynamically determine which `className` to pass based on the link's properties.
pathClassFunc={getDynamicPathClass}
/>
);
}
```
> For more details, see the `PathClassFunction` [reference docs](https://forivall.github.io/react-d3-tree/docs/modules/_src_types_common_.html#pathclassfunction).
### Event Handlers
`Tree` exposes the following event handler callbacks by default:
- [onLinkClick](https://forivall.github.io/react-d3-tree/docs/interfaces/_src_tree_types_.treeprops.html#onlinkclick)
- [onLinkMouseOut](https://forivall.github.io/react-d3-tree/docs/interfaces/_src_tree_types_.treeprops.html#onlinkmouseout)
- [onLinkMouseOver](https://forivall.github.io/react-d3-tree/docs/interfaces/_src__tree_types_.treeprops.html#onlinkmouseover)
- [onNodeClick](https://forivall.github.io/react-d3-tree/docs/interfaces/_src_tree_types_.treeprops.html#onnodeclick)
- [onNodeMouseOut](https://forivall.github.io/react-d3-tree/docs/interfaces/_src_tree_types_.treeprops.html#onnodemouseout)
- [onNodeMouseOver](https://forivall.github.io/react-d3-tree/docs/interfaces/_src_tree_types_.treeprops.html#onnodemouseover)
> **Note:** Nodes are expanded/collapsed whenever `onNodeClick` fires. To prevent this, set the [`collapsible` prop](https://forivall.github.io/react-d3-tree/docs/interfaces/_src_tree_types_.treeprops.html#collapsible) to `false`.
> `onNodeClick` will still fire, but it will not change the target node's expanded/collapsed state.
## Customizing the Tree
<!-- Using the `<nodeType>NodeClassName` and `pathClassFunc` approaches above should give -->
### `renderCustomNodeElement`
The [`renderCustomNodeElement` prop](https://forivall.github.io/react-d3-tree/docs/interfaces/_src_tree_types_.treeprops.html#rendercustomnodeelement) accepts a **custom render function that will be used for every node in the tree.**
Cases where you may find rendering your own `Node` element useful include:
- Using a **different SVG tag for your nodes** (instead of the default `<circle>`) - [Example (codesandbox.io)](https://codesandbox.io/s/rd3t-v2-custom-svg-tag-1bq1e?file=/src/App.js)
- Gaining **fine-grained control over event handling** (e.g. to implement events not covered by the default API) - [Example (codesandbox.io)](https://codesandbox.io/s/rd3t-v2-custom-event-handlers-5pwxw?file=/src/App.js)
- Building **richer & more complex nodes/labels** by leveraging the `foreignObject` tag to render HTML inside the SVG namespace - [Example (codesandbox.io)](https://codesandbox.io/s/rd3t-v2-custom-with-foreignobject-0mfj8?file=/src/App.js)
### `pathFunc`
The [`pathFunc` prop](https://forivall.github.io/react-d3-tree/docs/interfaces/_src_tree_types_.treeprops.html#pathfunc) accepts a predefined `PathFunctionOption` enum or a user-defined `PathFunction`.
By changing or providing your own `pathFunc`, you are able to change how links between nodes of the tree (which are SVG `path` tags under the hood) are drawn.
The currently [available enums](https://forivall.github.io/react-d3-tree/docs/modules/_src_types_common_.html#pathfunctionoption) are:
- `diagonal` (default)
- `elbow`
- `straight`
- `step`
> Want to see how each option looks? [Try them out on the playground](https://forivall.github.io/react-d3-dag).
#### Providing your own `pathFunc`
If none of the available path functions suit your needs, you're also able to provide a custom `PathFunction`:
```jsx
function CustomPathFuncTree() {
const straightPathFunc = (linkDatum, orientation) => {
const { source, target } = linkDatum;
return orientation === 'horizontal'
? `M${source.y},${source.x}L${target.y},${target.x}`
: `M${source.x},${source.y}L${target.x},${target.y}`;
};
return (
<Tree
data={data}
// Passing `straight` function as a custom `PathFunction`.
pathFunc={straightPathFunc}
/>
);
}
```
> For more details, see the [`PathFunction` reference docs](https://forivall.github.io/react-d3-dag/docs/modules/_types_common_.html#pathfunction).
## Development
### Setup
To set up `react-d3-dag` for local development, clone the repo and follow the steps below:
```bash
# 1. Set up the library, create a reference to it for symlinking.
cd react-d3-dag
npm i
npm link
# 2. Set up the demo/playground, symlink to the local copy of `react-d3-dag`.
cd demo
npm i
npm link react-d3-dag
```
> **Tip:** If you'd prefer to use your own app for development instead of the demo, simply run `npm link react-d3-dag` in your app's root folder instead of the demo's :)
### Hot reloading
```bash
npm run build:watch
```
If you're using `react-d3-dag/demo` for development, open up another terminal window in the `demo` directory and call:
```bash
npm start
```
## Contributors
A huge thank you [Ben Kremer](https://github.com/bkrem), author of
[react-d3-tree], which I forked to create this. And thank you to all of the
[contributors](https://github.com/bkrem/react-d3-tree/graphs/contributors) to his project!
[react-d3-tree]: https://github.com/bkrem/react-d3-tree