Home

Flexible, Natural graph editing framework for React

sketch

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.

node 0

linkd nodes amount: 0

title
node 1

linkd nodes amount: 0

title
import { 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 with observer, 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>
Last updated on February 22, 2023
;