Flexible, Natural graph editing framework for React

React-based
Based on react, it allows you to fully use the capabilities of react to build flowchart/relational diagram applications. Whether it is a Node, an Edge or a Port, they are all react components, and you can completely customize them!
Model-driven
moa-flow decouples the state model from view. The model is based on the dependency collection capability of mobx
. After the model state changes, it will automatically re-render. And other models can be referenced in any view component or model, complex inter-node logic can be easily written.
Install
npm i moa-flow // npm
yarn add moa-flow // yarn
Quick start
Let's build a graph application using moa-flow and antd (of course you can use no component library). Try dragging an edge from the out
port to the in
port, and right-click to add/delete nodes.
linkd nodes amount: 0
titlelinkd nodes amount: 0
titleimport { useRef } from "react";
import { Flow, ContextMenu, FlowModel } from "moa-flow";
import { BizNode } from "./BizNode";
import { BizNodeModel } from "./BizNodeModel";
import { Button } from "antd";
export const App = () => {
const flowModelRef = useRef<FlowModel>();
return (
<Flow
flowModelRef={flowModelRef}
components={{
BizNode: BizNode,
}}
models={{
BizNode: BizNodeModel,
}}
canvasData={{
cells: [
{
component: "BizNode",
nodeName: "node 0",
x: 50,
y: 50,
},
{
component: "BizNode",
nodeName: "node 1",
x: 500,
y: 200,
},
],
}}
>
<ContextMenu>
<Button
onClick={() => {
const flowModel = flowModelRef.current;
flowModel.addCell("BizNode", {
x: 300,
y: 300,
});
}}
>
add node
</Button>
<Button
onClick={() => {
const flowModel = flowModelRef.current;
const { selectCells, deleCell } = flowModel;
deleCell(selectCells[0]);
}}
>
delete
</Button>
</ContextMenu>
</Flow>
);
};
Code example analysis
If you have seen the example code and you already have a general understanding of how moa-flow works, then you can jump directly to API or examples for more information, if you still have some doubts, You can see the code analysis below, or click the feedback (opens in a new tab) to contact us directly.
First, we will import the root component <Flow />
, and use a ref to get the context of the graph, so that we can manipulate the canvas from the outer layer.
import { Flow, ContextMenu, FlowModel } from "moa-flow";
...
const flowModelRef = useRef<FlowModel>();
<Flow flowModelRef={flowModelRef}>...</Flow>;
At the same time, the right-click menu component is also imported in the code to implemente right-click deletion/addition of nodes in the canvas. The BizNode
in addCell will be defined later.
<Flow>
...
<ContextMenu>
<Button
onClick={() => {
const flowModel = flowModelRef.current;
flowModel.addCell("BizNode", {
x: 300,
y: 300,
});
}}
>
add node
</Button>
<Button
onClick={() => {
const flowModel = flowModelRef.current;
const { selectCells, deleCell } = flowModel;
deleCell(selectCells[0]);
}}
>
delete
</Button>
</ContextMenu>
</Flow>
Next we define the nodes
- In moa-flow, all Nodes and Edges are Cells. An Cell has two parts:
Model
: state model, which is a Class for loading state and various methods, NodeModel and EdgeModel are both inherited from CellModel, and can be inherited to implement your own business model.Component
: The React component that renders the view. Its props will pass in the Model we defined above. After wrapping the component withobserver
, all state changes of the model will be automatically re-rendered on demand.
First define the Model of the node, which will be injected into the props of the corresponding React component
import { NodeModel } from "moa-flow";
export class BizNodeModel extends NodeModel {
defaultData = () => ({
// define a nodeName to represent the title of the node
nodeName: "Im a node",
// define portType to represent the two categories of port: "in" and "out"
// (this is just an additional property defined by us in business)
ports: [{ portType: "in" }, { portType: "out" }],
});
}
Next, define the Component of the node, which is a React component, but the corresponding model will be injected into the props
export const BizNode: React.FC<{ model: BizNodeModel }> = observer(
({ model }) => {
const { data, isSelect } = model;
return <div>...</div>;
}
);
Next, let's define the Port. After any common element is wrapped by the Port component, configure the required properties, and it becomes a Port can be connected.
export const BizNode: React.FC<{ model: BizNodeModel }> = observer(
({ model }) => {
const { data, isSelect } = model;
return (
<div>
...
<Port
dir="left" // the direction of the Port, will affect the generation of the Bezier curve
anchor={() => ({
// link anchor of port
x: -25,
y: 55,
})}
data={inPort} // insert the Port's data into props
style={{
position: "absolute",
top: 40,
left: -20,
}}
>
<Button shape="circle">in</Button>
</Port>
</div>
);
}
);
After all are defined, register both Model and Component
<Flow
components={{
BizNode: BizNode,
}}
models={{
BizNode: BizNodeModel,
}}
>
...
</Flow>
Finally, you can preset some canvas data, a fully customized node, fully customized right-click menu graph is complete!
<Flow
canvasData={{
cells: [
{
component: "BizNode",
nodeName: "node 0",
x: 50,
y: 50,
},
{
component: "BizNode",
nodeName: "node 1",
x: 500,
y: 200,
},
],
}}
>
...
</Flow>