react-network-canvas
v0.9.4
Published
A themeable and customizable react component that provides a visual canvas and api for rendering, creating, editing, importing and exporting directed network graphs.
Downloads
32
Readme
Network Canvas - React Component
This react component provides a visual canvas and api for creating, editing, importing and exporting directed network graphs.
Network diagrams are useful for modeling things such as audio synthesis, execution flow, render pipelines, scene graphs, data transformations, and many others.
This component aspires to be completely themeable and customizable in behavior and styles, but this is an ongoing effort.
How to use
Install with yarn
yarn add react-network-canvas
Install with NPM
npm install react-network-canvas
Render in application
import React from "react";
import { NetworkCanvas } from "react-network-canvas";
function MyApp() {
return (
<NetworkCanvas
nodes={nodes}
edges={edges}
theme={theme}
callbacks={callbacks}
options={options}
/>
);
}
export { MyApp };
Demos
Example Code
import React from "react";
import ReactDOM from "react-dom";
import { createElement } from "react";
import { v1 as generateUuid } from "uuid";
import { NetworkCanvas } from "react-network-canvas";
import { nodes, edges } from "./graph.json";
function App() {
const callbacks = {
onClickCanvas(event, position, graphManager) {
// create node at click position
const node = graphManager.createNode({
position,
inputPorts: [{ id: generateUuid() }],
outputPorts: [{ id: generateUuid() }, { id: generateUuid() }],
});
// select the newly created node
graphManager.selectedNodeIds = [node.id];
},
onClickPort(event, port, parentNode, graphManager) {
// create a connected node...
},
onKeyPress(event, key, graphManager) {
if (key === "Backspace" && graphManager.selectedNodeIds.length > 0) {
// delete selected nodes
graphManager.removeNodesByIds(graphManager.selectedNodeIds);
}
},
};
const options = {
gridSize: 20,
zoomWheelKey: "Shift",
selectBoxKey: "Shift",
isSnapToGridEnabled: true,
};
return (
<NetworkCanvas
nodes={nodes}
edges={edges}
callbacks={callbacks}
options={options}
/>
);
}
ReactDOM.render(createElement(App), document.getElementById("app"));
Example Graph Model
{
"nodes": [
{
"id": "504f852d-5b78-4f64-bd71-ab7f7f5dfb03",
"data": {
"foo": "bar"
},
"position": {
"x": 100,
"y": 100
},
"inputPorts": [
{
"id": "02fa1df9-7187-4b85-ad00-6b14a1317cda"
}
],
"outputPorts": [
{
"id": "b09c894d-332f-4f16-843c-4bc42681972b"
},
{
"id": "37a08870-8f72-4724-b8d2-206c211a2787"
}
]
},
{
"id": "b832f63f-0c6a-4977-8bb1-581f1c6cc1cb",
"data": {
"foo": "bar"
},
"position": {
"x": 300,
"y": 100
},
"inputPorts": [
{
"id": "63f48da0-6495-4268-a55f-03f83af70720"
}
],
"outputPorts": [
{
"id": "793bd3a5-5b7e-407c-afcd-758592c702c8"
},
{
"id": "12d89e19-dc5f-40af-a3e2-50637e2f9b62"
}
]
},
{
"id": "1c973de3-b2b8-49cc-843c-39a600843ec2",
"data": {
"foo": "bar"
},
"position": {
"x": 300,
"y": 200
},
"inputPorts": [
{
"id": "b2ab0321-00a9-4be8-b5d5-a51cce615b4f"
}
],
"outputPorts": [
{
"id": "7fe5c462-062f-4bfe-bd7b-f191d9ed5e57"
},
{
"id": "2e817c94-2fae-4ea6-bfb8-503c6825f3c9"
}
]
}
],
"edges": [
{
"id": "548633a2-47e8-4774-9031-3fcde1bba2be",
"from": {
"nodeId": "504f852d-5b78-4f64-bd71-ab7f7f5dfb03",
"portId": "b09c894d-332f-4f16-843c-4bc42681972b"
},
"to": {
"nodeId": "b832f63f-0c6a-4977-8bb1-581f1c6cc1cb",
"portId": "63f48da0-6495-4268-a55f-03f83af70720"
}
},
{
"id": "60122c7b-698c-443c-a29c-d80f5aba2837",
"from": {
"nodeId": "504f852d-5b78-4f64-bd71-ab7f7f5dfb03",
"portId": "37a08870-8f72-4724-b8d2-206c211a2787"
},
"to": {
"nodeId": "1c973de3-b2b8-49cc-843c-39a600843ec2",
"portId": "b2ab0321-00a9-4be8-b5d5-a51cce615b4f"
}
}
]
}
Component Props
interface Props {
nodes: Types.Node[];
edges: Types.Edge[];
callbacks?: Types.Bridge;
options?: Types.Options;
theme?: any;
}
Options
interface Options {
gridSize: number;
canvasSize: number;
isSnapToGridEnabled: boolean;
startAtCanvasCenter: boolean;
canvasMargin: number;
zoomSensitivity: number;
initialPanOffset: Types.Position;
selectBoxKey?: "Shift" | "Control" | "Alt" | "Meta";
zoomWheelKey?: "Shift" | "Control" | "Alt" | "Meta";
maxZoom: number;
minZoom: number;
NodeComponent: ReactComponent;
PortComponent: ReactComponent;
}
const DEFAULT_OPTIONS: Types.Options = {
gridSize: 10,
canvasSize: 2000,
isSnapToGridEnabled: false,
startAtCanvasCenter: true,
canvasMargin: 50,
initialPanOffset: { x: 50, y: 50 },
zoomSensitivity: 0.001,
zoomWheelKey: undefined,
selectBoxKey: "Shift",
maxZoom: Infinity,
minZoom: 0,
NodeComponent,
PortComponent,
};
const DEFAULT_THEME = {
workspace: {
backgroundSize: "",
backgroundImage: "",
backgroundColor: "#f6f6f6",
},
canvas: {
borderRadius: "5px",
boxShadow: "0 0 0 1px lightgrey",
backgroundColor: "white",
backgroundSize: "var(--NC-grid-size) var(--NC-grid-size)",
backgroundImage:
"radial-gradient(lightgray, lightgray 1px, transparent 1px)",
backgroundPosition: "50% 50%",
},
selectbox: {
backgroundColor: "rgba(100, 148, 237, 0.25)",
boxShadow: "0 0 0 1px cornflowerblue",
},
edge: {
stroke: "black",
strokeWidth: "3px",
hover: {
stroke: "red",
},
draft: {
stroke: "black",
},
},
};
API Bridge
interface Bridge {
connect(graphManager: Types.GraphManager): void;
onChangeZoom(zoom: number): void;
onMutateGraph(graphEvent: GraphEvent): void;
onClickCanvas(
event: React.SyntheticEvent,
position: Types.Position,
graphManager: Types.GraphManager
): void;
onClickNode(
event: React.SyntheticEvent,
node: Types.Node,
graphManager: Types.GraphManager
): void;
onClickPort(
event: React.SyntheticEvent,
port: Types.Port,
node: Types.Node,
graphManager: Types.GraphManager
): void;
onDropCanvas(
event: React.SyntheticEvent,
position: Types.Position,
graphManager: Types.GraphManager
): void;
onChangeSelectedNodeIds(
selectedNodesIds: string[],
graphManager: Types.GraphManager
): void;
onKeyPress(
event: React.SyntheticEvent,
key: string,
graphManager: Types.GraphManager
): void;
}
GraphManager
interface GraphManager {
nodes: Types.Node[];
edges: Types.Edge[];
callbacks: Types.Bridge;
workspace: Types.Workspace | undefined;
dragManager: Types.DragManager;
selectedNodeIds: string[];
getNodeById(id: string): Types.Node;
getNodesByEdgeId(id: string): { from?: Types.Node; to?: Types.Node };
createNode(nodeProps: Partial<Types.Node>): Types.Node | null;
removeNodeById(id: string): void;
removeNodesByIds(removedNodeIds: string[]): void;
subscribeToNodesChange(fn: () => void): void;
unsubscribeToNodesChange(fn: () => void): void;
handleDragMove(event, dragDelta: Types.Position, dragData: any): void;
handleDragEnd(event, dragDelta: Types.Position, dragData: any): void;
updateNodePositionById(id: string, dragDelta: Types.Position): void;
subscribeToDragDeltaById(id: string, fn: () => void): void;
unsubscribeToDragDeltaById(id: string, fn: () => void): void;
subscribeToNodePositionChangeById(id: string, fn: () => void): void;
unsubscribeToNodePositionChangeById(id: string, fn: () => void): void;
appendSelectedNodeId(id: string): void;
appendSelectedNodeIds(appendedNodeIds: string[]): void;
removeSelectedNodeId(id: string): void;
removeSelectedNodeIds(unselectedNodeIds: string[]): void;
subscribeToIsSelectedById(id: string, fn: () => void): void;
unsubscribeToIsSelectedById(id: string, fn: () => void): void;
getEdgeById(id: string): Types.Edge;
getEdgesByNodeId(id: string): Types.Edge[];
createEdge(edgeProps: Partial<Types.Edge>): Types.Edge | null;
removeEdgeById(id: string): void;
clearDraftEdgePath(): void;
updateDraftEdgePath(x1: number, y1: number, x2: number, y2: number): void;
subscribeToEdgesChange(id: string, fn: () => void): void;
unsubscribeToEdgesChange(id: string, fn: () => void): void;
import(graph: Types.Graph): void;
export(): Types.Graph;
}
Workspace
interface Workspace {
setPan(position: Position | ((position: Position) => Position)): void;
setZoom(zoom: number | ((zoom: number) => number)): void;
container?: HTMLDivElement;
isSelectBoxKeyDown: boolean;
getElementDimensions(HTMLElement): { width: number; height: number };
getCanvasPosition(object: HTMLElement | DOMRect | MouseEvent): Position;
mountContextScreenOffset: Position;
panZoom: {
zoom: number;
} & Position;
}