Skip to content

Commit

Permalink
refactor(elements): render only visible elements
Browse files Browse the repository at this point in the history
  • Loading branch information
moklick committed Oct 19, 2021
1 parent 410b129 commit 1525af3
Show file tree
Hide file tree
Showing 21 changed files with 376 additions and 217 deletions.
64 changes: 1 addition & 63 deletions cypress/integration/flow/graph-utils.spec.js
@@ -1,4 +1,4 @@
import { isNode, isEdge, getOutgoers, getIncomers, removeElements, addEdge } from '../../../dist/ReactFlow.js';
import { isNode, isEdge, getOutgoers, getIncomers, addEdge } from '../../../dist/ReactFlow.js';

const nodes = [
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
Expand Down Expand Up @@ -69,66 +69,4 @@ describe('Graph Utils Testing', () => {
}
});
});

describe('tests removeElements function', () => {
it('removes a node', () => {
const nextElements = removeElements([nodes[0]], elements);

const nextNodes = nextElements.filter((e) => isNode(e));
const nextEdges = nextElements.filter((e) => isEdge(e));

expect(nextNodes.length).to.be.equal(nodes.length - 1);
expect(nextEdges.length).to.be.equal(edges.length - 2);
});

it('removes multiple nodes', () => {
const elementsToRemove = [nodes[0], nodes[1]];
const nextElements = removeElements(elementsToRemove, elements);
const nextNodes = nextElements.filter((e) => isNode(e));
const nextEdges = nextElements.filter((e) => isEdge(e));

expect(nextNodes.length).to.be.equal(nodes.length - 2);
expect(nextEdges.length).to.be.equal(0);
});

it('removes no node', () => {
const nextElementsNoRemove = removeElements([], elements);
expect(nextElementsNoRemove.length).to.be.equal(elements.length);
});

it('tries to removes node that does not exist', () => {
const nextElementsNoRemove = removeElements([{ id: 'id-that-does-not-exist' }], elements);
expect(nextElementsNoRemove.length).to.be.equal(elements.length);
});

it('removes an edge', () => {
const nextElements = removeElements([edges[0]], elements);

const nextNodes = nextElements.filter((e) => isNode(e));
const nextEdges = nextElements.filter((e) => isEdge(e));

expect(nextNodes.length).to.be.equal(nodes.length);
expect(nextEdges.length).to.be.equal(edges.length - 1);
});

it('removes multiple edges', () => {
const nextElements = removeElements([edges[0], edges[1]], elements);

const nextNodes = nextElements.filter((e) => isNode(e));
const nextEdges = nextElements.filter((e) => isEdge(e));

expect(nextNodes.length).to.be.equal(nodes.length);
expect(nextEdges.length).to.be.equal(edges.length - 2);
});

it('removes node and edge', () => {
const nextElements = removeElements([nodes[0], edges[0]], elements);

const nextNodes = nextElements.filter((e) => isNode(e));
const nextEdges = nextElements.filter((e) => isEdge(e));

expect(nextNodes.length).to.be.equal(nodes.length - 1);
expect(nextEdges.length).to.be.equal(edges.length - 2);
});
});
});
4 changes: 2 additions & 2 deletions example/src/Basic/index.tsx
Expand Up @@ -36,8 +36,8 @@ const BasicFlow = () => {
const [nodes, setNodes] = useState<Node[]>(initialNodes);
const [edges, setEdges] = useState<Edge[]>(initialEdges);

const onConnect = useCallback((params: Edge | Connection, nds: Node[]) => {
setEdges((eds) => addEdge(params, nds, eds));
const onConnect = useCallback((params: Edge | Connection) => {
setEdges((eds) => addEdge(params, eds));
}, []);
const onLoad = useCallback((reactFlowInstance: OnLoadParams) => setRfInstance(reactFlowInstance), []);

Expand Down
25 changes: 25 additions & 0 deletions example/src/CustomNode/ColorSelectorNode.tsx
@@ -0,0 +1,25 @@
import React, { memo, FC, CSSProperties } from 'react';

import { Handle, Position, NodeProps, Connection, Edge } from 'react-flow-renderer';

const targetHandleStyle: CSSProperties = { background: '#555' };
const sourceHandleStyleA: CSSProperties = { ...targetHandleStyle, top: 10 };
const sourceHandleStyleB: CSSProperties = { ...targetHandleStyle, bottom: 10, top: 'auto' };

const onConnect = (params: Connection | Edge) => console.log('handle onConnect', params);

const ColorSelectorNode: FC<NodeProps> = ({ data, isConnectable }) => {
return (
<>
<Handle type="target" position={Position.Left} style={targetHandleStyle} onConnect={onConnect} />
<div>
Custom Color Picker Node: <strong>{data.color}</strong>
</div>
<input className="nodrag" type="color" onChange={data.onChange} defaultValue={data.color} />
<Handle type="source" position={Position.Right} id="a" style={sourceHandleStyleA} isConnectable={isConnectable} />
<Handle type="source" position={Position.Right} id="b" style={sourceHandleStyleB} isConnectable={isConnectable} />
</>
);
};

export default memo(ColorSelectorNode);
147 changes: 147 additions & 0 deletions example/src/CustomNode/index.tsx
@@ -0,0 +1,147 @@
import { useState, useEffect, MouseEvent, useCallback } from 'react';
import { ChangeEvent } from 'react';

import ReactFlow, {
addEdge,
MiniMap,
Controls,
Node,
OnLoadParams,
Position,
SnapGrid,
Connection,
Edge,
NodeChange,
applyNodeChanges,
applyEdgeChanges,
EdgeChange,
} from 'react-flow-renderer';

import ColorSelectorNode from './ColorSelectorNode';

const onLoad = (reactFlowInstance: OnLoadParams) => console.log('flow loaded:', reactFlowInstance);
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);

const initBgColor = '#1A192B';

const connectionLineStyle = { stroke: '#fff' };
const snapGrid: SnapGrid = [16, 16];
const nodeTypes = {
selectorNode: ColorSelectorNode,
};

const CustomNodeFlow = () => {
const [nodes, setNodes] = useState<Node[]>([]);
const [edges, setEdges] = useState<Edge[]>([]);
const [bgColor, setBgColor] = useState<string>(initBgColor);

useEffect(() => {
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
setNodes((nds) =>
nds.map((node) => {
if (node.id !== '2') {
return node;
}

const color = event.target.value;

setBgColor(color);

return {
...node,
data: {
...node.data,
color,
},
};
})
);
};

setNodes([
{
id: '1',
type: 'input',
data: { label: 'An input node' },
position: { x: 0, y: 50 },
sourcePosition: Position.Right,
},
{
id: '2',
type: 'selectorNode',
data: { onChange: onChange, color: initBgColor },
style: { border: '1px solid #777', padding: 10 },
position: { x: 250, y: 50 },
},
{
id: '3',
type: 'output',
data: { label: 'Output A' },
position: { x: 550, y: 25 },
targetPosition: Position.Left,
},
{
id: '4',
type: 'output',
data: { label: 'Output B' },
position: { x: 550, y: 100 },
targetPosition: Position.Left,
},
]);

setEdges([
{ id: 'e1-2', source: '1', target: '2', animated: true, style: { stroke: '#fff' } },
{ id: 'e2a-3', source: '2', sourceHandle: 'a', target: '3', animated: true, style: { stroke: '#fff' } },
{ id: 'e2b-4', source: '2', sourceHandle: 'b', target: '4', animated: true, style: { stroke: '#fff' } },
]);
}, []);

const onConnect = (params: Connection | Edge) =>
setEdges((eds) => addEdge({ ...params, animated: true, style: { stroke: '#fff' } }, eds));

const onNodesChange = useCallback((changes: NodeChange[]) => {
setNodes((ns) => applyNodeChanges(changes, ns));
}, []);

const onEdgesChange = useCallback((changes: EdgeChange[]) => {
setEdges((es) => applyEdgeChanges(changes, es));
}, []);

return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onNodeClick={onNodeClick}
onConnect={onConnect}
onNodeDragStop={onNodeDragStop}
style={{ background: bgColor }}
onLoad={onLoad}
nodeTypes={nodeTypes}
connectionLineStyle={connectionLineStyle}
snapToGrid={true}
snapGrid={snapGrid}
defaultZoom={1.5}
>
<MiniMap
nodeStrokeColor={(n: Node): string => {
if (n.type === 'input') return '#0041d0';
if (n.type === 'selectorNode') return bgColor;
if (n.type === 'output') return '#ff0072';

return '#eee';
}}
nodeColor={(n: Node): string => {
if (n.type === 'selectorNode') return bgColor;

return '#fff';
}}
/>
<Controls />
</ReactFlow>
);
};

export default CustomNodeFlow;
29 changes: 19 additions & 10 deletions example/src/Stress/index.tsx
Expand Up @@ -8,25 +8,27 @@ import ReactFlow, {
Node,
NodeChange,
applyNodeChanges,
Connection,
addEdge,
} from 'react-flow-renderer';

import { getElements } from './utils';
import { getNodesAndEdges } from './utils';

const buttonWrapperStyles: CSSProperties = { position: 'absolute', right: 10, top: 10, zIndex: 4 };

const onLoad = (reactFlowInstance: OnLoadParams) => {
reactFlowInstance.fitView();
console.log(reactFlowInstance.getElements());
console.log(reactFlowInstance.getNodes());
};

const initialElements = getElements(30, 30);
const { nodes: initialNodes, edges: initialEdges } = getNodesAndEdges(30, 30);

const StressFlow = () => {
const [nodes, setNodes] = useState<Node[]>(initialElements.nodes);
const [edges, setEdges] = useState<Edge[]>(initialElements.edges);
// const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els));
// const onConnect = (params: Connection | Edge, nds: Node[]) => setElements((els) => addEdge(params, els));

const [nodes, setNodes] = useState<Node[]>(initialNodes);
const [edges, setEdges] = useState<Edge[]>(initialEdges);
const onConnect = useCallback((params: Edge | Connection) => {
setEdges((eds) => addEdge(params, eds));
}, []);
const updatePos = () => {
setNodes((nds) => {
return nds.map((n) => {
Expand All @@ -43,7 +45,7 @@ const StressFlow = () => {

const updateElements = () => {
const grid = Math.ceil(Math.random() * 10);
const initialElements = getElements(grid, grid);
const initialElements = getNodesAndEdges(grid, grid);
setNodes(initialElements.nodes);
setEdges(initialElements.edges);
};
Expand All @@ -53,7 +55,14 @@ const StressFlow = () => {
}, []);

return (
<ReactFlow nodes={nodes} edges={edges} onLoad={onLoad} onNodesChange={onNodesChange}>
<ReactFlow
onlyRenderVisibleElements
nodes={nodes}
edges={edges}
onLoad={onLoad}
onConnect={onConnect}
onNodesChange={onNodesChange}
>
<MiniMap />
<Controls />
<Background />
Expand Down
2 changes: 1 addition & 1 deletion example/src/Stress/utils.ts
Expand Up @@ -5,7 +5,7 @@ type ElementsCollection = {
edges: Edge[];
};

export function getElements(xElements: number = 10, yElements: number = 10): ElementsCollection {
export function getNodesAndEdges(xElements: number = 10, yElements: number = 10): ElementsCollection {
const initialNodes = [];
const initialEdges: Edge[] = [];
let nodeId = 1;
Expand Down
5 changes: 5 additions & 0 deletions example/src/index.tsx
Expand Up @@ -5,6 +5,7 @@ import { BrowserRouter as Router, Route, Switch, withRouter } from 'react-router
import Basic from './Basic';
import UpdateNode from './UpdateNode';
import Stress from './Stress';
import CustomNode from './CustomNode';

import './index.css';

Expand All @@ -21,6 +22,10 @@ const routes = [
path: '/stress',
component: Stress,
},
{
path: '/custom-node',
component: CustomNode,
},
];

const Header = withRouter(({ history, location }) => {
Expand Down
8 changes: 2 additions & 6 deletions src/components/Handle/handler.ts
@@ -1,8 +1,6 @@
import { MouseEvent as ReactMouseEvent } from 'react';
import { GetState } from 'zustand';

import { getHostForElement } from '../../utils';
import { ReactFlowState } from '../../types';

import {
ElementId,
Expand Down Expand Up @@ -105,8 +103,7 @@ export function onMouseDown(
onEdgeUpdateEnd?: (evt: MouseEvent) => void,
onConnectStart?: OnConnectStartFunc,
onConnectStop?: OnConnectStopFunc,
onConnectEnd?: OnConnectEndFunc,
getState?: GetState<ReactFlowState>
onConnectEnd?: OnConnectEndFunc
): void {
const reactFlowNode = (event.target as Element).closest('.react-flow');
// when react-flow is used inside a shadow root we can't use document
Expand Down Expand Up @@ -180,8 +177,7 @@ export function onMouseDown(
onConnectStop?.(event);

if (isValid) {
const nodes = getState?.().nodes;
onConnect?.(connection, nodes || []);
onConnect?.(connection);
}

onConnectEnd?.(event);
Expand Down
8 changes: 4 additions & 4 deletions src/components/Handle/index.tsx
Expand Up @@ -4,7 +4,7 @@ import shallow from 'zustand/shallow';

import { useStore } from '../../store';
import NodeIdContext from '../../contexts/NodeIdContext';
import { HandleProps, Connection, ElementId, Position, Node, ReactFlowState } from '../../types';
import { HandleProps, Connection, ElementId, Position, ReactFlowState } from '../../types';

import { onMouseDown, SetSourceIdFunc, SetPosition } from './handler';

Expand Down Expand Up @@ -52,9 +52,9 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
const isTarget = type === 'target';

const onConnectExtended = useCallback(
(params: Connection, nodes: Node[]) => {
onConnectAction?.(params, nodes);
onConnect?.(params, nodes);
(params: Connection) => {
onConnectAction?.(params);
onConnect?.(params);
},
[onConnectAction, onConnect]
);
Expand Down

0 comments on commit 1525af3

Please sign in to comment.