custom-react-flowchart

custom-react-flowchart =========================

Usage no npm install needed!

<script type="module">
  import customReactFlowchart from 'https://cdn.skypack.dev/custom-react-flowchart';
</script>

README

custom-react-flowchart

English | 中文

Demo

  • Fully DOM based auto-scheduling flowchart component. (While not as free to drag and drop as the canvas implementation, it also retains the rendering and event capabilities of the DOM.)
  • Highly customizable.
  • it's lightweight. 14.5kb uncompressed, 3.6kb after compression (gzip).

I don't know what the use is. I think even if it could be used somewhere, it would be very scenario-specific and customizable. So rather than presenting it in the documentation from the perspective of a component, I'd prefer to talk about the implementation idea. Any suggestions or questions about self-contained scenes are welcome to be discussed in the issue.

Implementation Idea

Structure of data

interface NodeData {
    id: string;
    next?: string[];
    pre?: string[];

    [others: string]: any;
}

const data: Record<string, NodeData> = {
    head: {
        id: 'head',
        next: ['tail'],

        type: 'start',
    },
    tail: {
        id: 'tail',
        pre: ['head'],

        type: 'end',
    }
}

data is a chained object, with no pre at the head node and no next at the tail node.

Structure of the component

There are two types of component structures, Section and Node, which are mutually referenced and nested. Each Section is a wrapping container for a set of sibling nodes. Each Node is a rendering container for a node. If a Branch is created at a Section, i.e., it contains >= 2 Nodes, it will also contain the Section of the branch closing node. If the node represented by a Node has a successor node, and the successor node is not a branch closure node, then the Node will also contain a Section containing the successor node.

function Section() {
    return (
        <>
            {nodes.map(node =>
                <Node {...node} />
            )}
            {isBranchSection &&
                <Section {...combineNode} />
            }   
        </>
    );
}

function Node() {
    return (
        <>
            <NodeRender />
            {nextNode && nextNodeNotCombineNode &&
                <Section {...nextNode} />
            }   
        </>
    );
}

Each black boxed area in the figure below is a Section component. image

Each red boxed area in the figure below is a Node component. image

This nested structure also poses a problem: Strict Mode needs to be turned off for use in dev environment. hooks in Strict Mode will mount to incorrect component.

How are the connecting lines drawn?

The vertical line is relatively simple, using the preset height and the height of the DOM in the middle of the Section, Node on the line. The horizontal lines generated by the branch nodes belong to the Section, and the Node at the head and tail need to be covered with a line of the same thickness in the background color, as shown in the figure below. image

There are many lines, if you do not feel the right use, debug the code when commented out to see what will change.

Add, Branch and Combine button. onDeleteNode callback

The above illustrates how the flowchart is laid out and arranged. As an interactive flowchart, you also need to have buttons to click to add nodes. The following three buttons are the ones that I think are mandatory for flowcharts and have little to do with what kind of nodes I want to render, so they are preset in the component.

  • An Add button attributed to a Node that triggers an onAdd callback when clicked.
  • Branch button for adding branches, click to trigger the onExpandBranch callback.
  • Combine button for adding branch merge nodes, click to trigger onCombineNodes callback.

The button used to delete the node is not preset, and the callback onDeleteNode is obtained in the node's custom render function.

interface Callback {
    onAdd(nodeId: string): void;
    onExpandBranch(nodeId: string): void;
    onCombineNodes(nodesId: Array<string>): void;
    onDeleteNode(nodeId: string): void;
}

image

Install

npm install --save custom-react-flowchart
// import style then
import 'custom-react-flowchart/dist/style.css';

Basic Usage

To use this flowchart, you first need a data source that conforms to the rules, and then define callbacks for the three button operations. Then write a node rendering function RenderNode, RenderNode function will receive the callback function onDeleteNode for the delete operation. delete button will have to be written in RenderNode itself.

custom-react-flowchart only cares about the layout, not how the data is manipulated, as long as the data is conforming to the rules. Of course a set of methods for manipulating data is also provided, see the code of the demo for details.

function flowchart () {
    const [data, setData] = useState({
        head: {
            id: 'head',
            next: ['tail'],

            type: 'start',
        },
        tail: {
            id: 'tail',
            pre: ['head'],

            type: 'end',
        }
    });

    return (
        <FlowChart
            data={data}
            onAdd={onHandleAdd}
            onCombineNodes={onHandleCombineNodes}
            onExpandBranch={onHandleExpandBranch}
            styleConfig={{
                backgroundColor: '#fff',
                lineWidth: 1,
                lineColor: '#ccc',
                distance: { horizontal: 50, vertical: 40 },
            }}
            RenderNode={RenderNode}
            onDeleteNode={onHandleDeleteNode}
        />
    );
}

Props

data (required) :object

The head node does not have pre and the tail node does not have next.

interface NodeData {
    id: string;
    next?: string[];
    pre?: string[];
    
    [others: string]: any;
}

type data = Record<string, NodeData>;

styleConfig :object

interface StyleConfig  {
    /** Flowchart background color for masking horizontal line. */
    backgroundColor?: string;
    lineWidth?: number;
    lineColor?: string;
    distance?: { horizontal: number, vertical: number };
}

RenderNode (required) :ReactNode The render function for the node. The node's properties and the onDeleteNode callback can be received.

onAdd (required) (nodeId: string): void Callback for clicking the Add button

onExpandBranch (required) (nodeId: string): void Callback for clicking the Branch button

onCombineNodes (required) (nodesId: Array<string>): void Callback for clicking the Combine button

onDeleteNode (required) (nodeId: string): void The delete callback passed through to the RenderNode for the custom delete button.

RenderAddButton :ReactNode The custom rendering function for the Add button.

RenderBranchButton :ReactNode The custom rendering function for the Branch button.

RenderCombineButton :ReactNode The custom rendering function for the Combine button.

showAddButton :boolean = true Indicates if the Add button is displayed.

showBranchButton :boolean = true Indicates if the Branch button is displayed.

showCombineButton :boolean = true Indicates if the Combine button is displayed.

Running example

Run the demos:

npm install
npm run start

Build lib

npm install
npm run build:lib

License

MIT