react-digraph

A library for creating directed graph editors

README

react-digraph

Demo

Overview


A React component which makes it easy to create a directed graph editor without implementing any of the SVG drawing or event handling logic.

Important v8.0.0 Information

Version 8.0.0 introduces multi-select nodes and edges using Ctrl-Shift-Mouse events (Cmd-Shift-mouse for Mac). This requires a breaking change. Instead of onSelectNode/Edge, you'll only provide one onSelect function callback and a selected object with { nodes: Map, and edges: Map } as the parameter format. The typings folder has the exact type definition for these attributes. When either edges or nodes are selected the onSelect function will fire with the object. You will have to handle all nodes and edges selected, or if there is only one then you will have to determine if it's a node or edge within the onSelect function.

To disable multi-select you can set allowMultiselect to false, which disables the Ctrl-Shift-mouse event, but we will still use the onSelect function. Both onSelectNode and onSelectEdge are deprecated.

Breaking changes:

- onPasteSelected now accepts a SelectionT object for the first parameter
- onPasteSelected now accepts an IPoint instead
of a XYCoords array for the second parameter.
- onDeleteSelected is added which takes a SelectionT parameter.
- onSelect is added, which accepts SelectionT and Event parameters.
- onUpdateNode accepts a Map of updated nodes in the second parameter (for example, if multiple nodes are moved).
- selected is a new property to track selected nodes and edges. It is a SelectionT type.
- canDeleteSelected takes the place of canDeleteNode and canDeleteEdge. It accepts a SelectionT type as a parameter.
- onDeleteNode is removed
- onDeleteEdge is removed
- selectedNode is removed
- selectedEdge is removed
- canDeleteNode is removed
- canDeleteEdge is removed
- selectedNodes is removed
- selectedEdges is removed

Installation


  1. ``` sh
  2. npm install --save react-digraph
  3. ```

If you don't have the following peerDependenies, make sure to install them:

  1. ``` sh
  2. npm install --save react react-dom
  3. ```

Usage



The default export is a component called GraphView; it provides a multitude of hooks for various graph editing operations and a set of controls for zooming. Typically, it should be wrapped in a higher order component that supplies various callbacks (onCreateNode, onCreateEdge etc...).

GraphView expects several properties to exist on your nodes and edges. If these types conflict with existing properties on your data, you must transform your data to re-key these properties under different names and to add the expected properties. All nodes and edges can have a type attribute set - nodes also support a subtype attribute. For a full description of node and edge properties, see the sections for INode and IEdge below.

Configuration for nodes and edges can be passed to GraphView via the nodeTypes, nodeSubtypes, and edgeTypes props. Custom SVG elements can be defined here for the node's type/subtype and the edge's type.

It is often convenient to combine these types into a configuration object that can be referred to elsewhere in the application and used to associate events fired from nodes/edges in the GraphView with other actions in the application. Here is an abbreviated example:

  1. ``` js
  2. import {
  3.   GraphView, // required
  4.   Edge, // optional
  5.   type IEdge, // optional
  6.   Node, // optional
  7.   type INode, // optional
  8.   type LayoutEngineType, // required to change the layoutEngineType, otherwise optional
  9.   BwdlTransformer, // optional, Example JSON transformer
  10.   GraphUtils // optional, useful utility functions
  11. } from 'react-digraph';

  12. const GraphConfig =  {
  13.   NodeTypes: {
  14.     empty: { // required to show empty nodes
  15.       typeText: "None",
  16.       shapeId: "#empty", // relates to the type property of a node
  17.       shape: (
  18.         <symbol viewBox="0 0 100 100" id="empty" key="0">
  19.           <circle cx="50" cy="50" r="45"></circle>
  20.         </symbol>
  21.       )
  22.     },
  23.     custom: { // required to show empty nodes
  24.       typeText: "Custom",
  25.       shapeId: "#custom", // relates to the type property of a node
  26.       shape: (
  27.         <symbol viewBox="0 0 50 25" id="custom" key="0">
  28.           <ellipse cx="50" cy="25" rx="50" ry="25"></ellipse>
  29.         </symbol>
  30.       )
  31.     }
  32.   },
  33.   NodeSubtypes: {},
  34.   EdgeTypes: {
  35.     emptyEdge: {  // required to show empty edges
  36.       shapeId: "#emptyEdge",
  37.       shape: (
  38.         <symbol viewBox="0 0 50 50" id="emptyEdge" key="0">
  39.           <circle cx="25" cy="25" r="8" fill="currentColor"> </circle>
  40.         </symbol>
  41.       )
  42.     }
  43.   }
  44. }

  45. const NODE_KEY = "id"       // Allows D3 to correctly update DOM

  46. class Graph extends Component {

  47.   constructor(props) {
  48.     super(props);

  49.     this.state = {
  50.       graph: sample,
  51.       selected: {}
  52.     }
  53.   }

  54.   /* Define custom graph editing methods here */

  55.   render() {
  56.     const nodes = this.state.graph.nodes;
  57.     const edges = this.state.graph.edges;
  58.     const selected = this.state.selected;

  59.     const NodeTypes = GraphConfig.NodeTypes;
  60.     const NodeSubtypes = GraphConfig.NodeSubtypes;
  61.     const EdgeTypes = GraphConfig.EdgeTypes;

  62.     return (
  63.       <div id='graph' style={styles.graph}>
  64. <GraphView  ref='GraphView'
  65.                     nodeKey={NODE_KEY}
  66.                     nodes={nodes}
  67.                     edges={edges}
  68.                     selected={selected}
  69.                     nodeTypes={NodeTypes}
  70.                     nodeSubtypes={NodeSubtypes}
  71.                     edgeTypes={EdgeTypes}
  72.                     allowMultiselect={true} // true by default, set to false to disable multi select.
  73.                     onSelect={this.onSelect}
  74.                     onCreateNode={this.onCreateNode}
  75.                     onUpdateNode={this.onUpdateNode}
  76.                     onDeleteNode={this.onDeleteNode}
  77.                     onCreateEdge={this.onCreateEdge}
  78.                     onSwapEdge={this.onSwapEdge}
  79.                     onDeleteEdge={this.onDeleteEdge}/>
  80.       </div>
  81.     );
  82.   }

  83. }
  84. ```

A typical graph that would be stored in the Graph component's state looks something like this:

  1. ``` json
  2. {
  3.   "nodes": [
  4.     {
  5.       "id": 1,
  6.       "title": "Node A",
  7.       "x": 258.3976135253906,
  8.       "y": 331.9783248901367,
  9.       "type": "empty"
  10.     },
  11.     {
  12.       "id": 2,
  13.       "title": "Node B",
  14.       "x": 593.9393920898438,
  15.       "y": 260.6060791015625,
  16.       "type": "empty"
  17.     },
  18.     {
  19.       "id": 3,
  20.       "title": "Node C",
  21.       "x": 237.5757598876953,
  22.       "y": 61.81818389892578,
  23.       "type": "custom"
  24.     },
  25.     {
  26.       "id": 4,
  27.       "title": "Node C",
  28.       "x": 600.5757598876953,
  29.       "y": 600.81818389892578,
  30.       "type": "custom"
  31.     }
  32.   ],
  33.   "edges": [
  34.     {
  35.       "source": 1,
  36.       "target": 2,
  37.       "type": "emptyEdge"
  38.     },
  39.     {
  40.       "source": 2,
  41.       "target": 4,
  42.       "type": "emptyEdge"
  43.     }
  44.   ]
  45. }

  46. ```

For a detailed example, check out src/examples/graph.js.
To run the example:

  1. ``` sh
  2. npm install
  3. npm run example
  4. ```

A webpage will open in your default browser automatically.

- To add nodes, hold shift and click on the grid.
- To add edges, hold shift and click/drag to between nodes.
- To delete a node or edge, click on it and press delete.
- Click and drag nodes to change their position.
- To select multiple nodes, press Ctrl+Shift then click and drag the mouse.
- You may copy and paste selected nodes and edges with Ctrl+C and Ctrl+V

Note: On Mac computers, use Cmd instead of Ctrl.

All props are detailed below.


Props


PropTypeRequiredNotes
----------------------:------------------------::----------::----------------------------------------------------------:
`nodeKey``string``true`Key
`nodes``Array``true`Array
`edges``Array``true`Array
`allowCopyEdges``boolean``false`(default
`allowMultiselect``boolean``false`(default
`selected``object``true`The
`nodeTypes``object``true`Config
`nodeSubtypes``object``true`Config
`edgeTypes``object``true`Config
`onSelect``func``false`Called
`onCreateNode``func``true`Called
`onContextMenu``func``true`Called
`onUpdateNode``func``true`Called
`onCreateEdge``func``true`Called
`onSwapEdge``func``true`Called
`onBackgroundClick``func``false`Called
`onArrowClicked``func``false`Called
`onUndo``func``false`A
`onCopySelected``func``false`A
`onPasteSelected``func``false`A
`canDeleteSelected``func``false`takes
`canCreateEdge``func``false`Called
`canSwapEdge``func``false`Called
`afterRenderEdge``func``false`Called
`renderNode``func``false`Called
`renderNodeText``func``false`Called
`renderDefs``func``false`Called
`renderBackground``func``false`Called
`readOnly``bool``false`Disables
`disableBackspace``bool``false`Disables
`maxTitleChars``number``false`Truncates
`gridSize``number``false`Overall
`gridSpacing``number``false`Grid
`gridDotSize``number``false`Grid
`minZoom``number``false`Minimum
`maxZoom``number``false`Maximum
`nodeSize``number``false`Node
`edgeHandleSize``number``false`Edge
`edgeArrowSize``number``false`Edge
`zoomDelay``number``false`Delay
`zoomDur``number``false`Duration
`showGraphControls``boolean``false`Whether
`layoutEngineType``typeof`false`Uses
`rotateEdgeHandle``boolean``false`Whether
`centerNodeOnMove``boolean``false`Whether
`initialBBox``typeof`false`If
`graphConfig``object``false`[dagre](https://github.com/dagrejs/dagre/wiki#configuring-the-layout)
`nodeSizeOverridesAllowed``boolean``false`Flag
`nodeLocationOverrides``object``false`Nodes

onCreateNode

You have access to d3 mouse event in onCreateNode function.
  1. ``` js
  2.   onCreateNode = (x, y, mouseEvent) => {
  3.     // we can get the exact mouse position when click happens with this line
  4.     const {pageX, pageY} = mouseEvent;
  5.     // rest of the code for adding a new node ...
  6.   };
  7. ```


Prop Types:


See prop types in the typings folder.


INode


PropTypeRequiredNotes
----------------------:------------------------::----------::-----------------------------------------------:
`anyIDKey``string``true`An
`title``string``true`Used
`x``number``false`X
`y``number``false`Y
`type``string``false`Node
`subtype``string``false`Node


title


The title attribute is used for the IDs in the SVG nodes in the graph.

IEdge


PropTypeRequiredNotes
----------------------:------------------------::----------::-----------------------------------------------:
`source``string``true`The
`target``string``true`The
`type``string``false`Edge
`handleText``string``false`Text
`handleTooltipText``string``false`Used
`label_from``string``false`Text
`label_to``string``false`Text


Imperative API

You can call these methods on the GraphView class using a ref.

MethodTypeNotes
------------------:---------------------------------------------------------::-------------------------------------------------------------------------:
`panToNode``(id:Center
`panToEdge``(source:Center

Deprecation Notes


PropTypeRequiredNotes
--------------------:-------::--------::----------------------------------------:
`emptyType``string``true`'Default'
`getViewNode``func``true`Node
`renderEdge``func``false`Called
`enableFocus``bool``false`Adds
`transitionTime``number``false`Fade-in/Fade-out
`primary``string``false`Primary
`light``string``false`Light
`dark``string``false`Dark
`style``object``false`Style
`gridDot``number``false`Grid
`graphControls``boolean``true`Whether