Develop (#52)
* docs(readme): add plugin section * refactor(examples): use func components * docs(readme): format * refactor: NodeRenderer (#51) * refactor: NodeRenderer * fix getNodesInside partially param * add `skipInvisible` prop * refactor(noderenderer): rename skipInvisble to onlyRenderVisibleNodes closes #40
This commit is contained in:
+2
-1
@@ -1 +1,2 @@
|
|||||||
example/src
|
example/src
|
||||||
|
*.md
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# :ocean: React Flow
|
# :ocean: React Flow
|
||||||
|
|
||||||
React library for rendering an interactive node-based flow editor
|
React library for building node-based graphs.
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
@@ -8,6 +8,27 @@ React library for rendering an interactive node-based flow editor
|
|||||||
npm install github:wbkd/react-flow
|
npm install github:wbkd/react-flow
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
This is a very basic example of how to use react-flow. There are more advanced examples in the [example](/example/src) folder.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import React from 'react';
|
||||||
|
import Graph from 'react-flow';
|
||||||
|
|
||||||
|
const elements = [
|
||||||
|
{ id: '1', data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
|
||||||
|
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
|
||||||
|
{ id: 'e1-2', source: '1', target: '2', animated: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
const graphStyles = { width: '100%', height: '100%' };
|
||||||
|
|
||||||
|
const BasicGraph = () => (
|
||||||
|
<Graph elements={elements} style={graphStyles} />
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
# Props
|
# Props
|
||||||
|
|
||||||
- `elements`: array with nodes and edges
|
- `elements`: array with nodes and edges
|
||||||
@@ -29,3 +50,8 @@ npm install github:wbkd/react-flow
|
|||||||
- `backgroundType`: background type = `dots` or `lines`
|
- `backgroundType`: background type = `dots` or `lines`
|
||||||
- `snapToGrid`: boolean
|
- `snapToGrid`: boolean
|
||||||
- `snapGrid`: [x, y] array
|
- `snapGrid`: [x, y] array
|
||||||
|
|
||||||
|
# Plugins
|
||||||
|
|
||||||
|
- Mini map
|
||||||
|
- Controls: zoom & fit view
|
||||||
|
|||||||
Vendored
+130
-1153
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+130
-1153
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+2
-1
@@ -20,6 +20,7 @@ export interface GraphViewProps {
|
|||||||
backgroundType: GridType;
|
backgroundType: GridType;
|
||||||
snapToGrid: boolean;
|
snapToGrid: boolean;
|
||||||
snapGrid: [number, number];
|
snapGrid: [number, number];
|
||||||
|
onlyRenderVisibleNodes: boolean;
|
||||||
}
|
}
|
||||||
declare const GraphView: React.MemoExoticComponent<({ nodeTypes, edgeTypes, onMove, onLoad, onElementClick, onNodeDragStop, connectionLineType, connectionLineStyle, selectionKeyCode, onElementsRemove, deleteKeyCode, elements, showBackground, backgroundGap, backgroundColor, backgroundType, onConnect, snapToGrid, snapGrid, }: GraphViewProps) => JSX.Element>;
|
declare const GraphView: React.MemoExoticComponent<({ nodeTypes, edgeTypes, onMove, onLoad, onElementClick, onNodeDragStop, connectionLineType, connectionLineStyle, selectionKeyCode, onElementsRemove, deleteKeyCode, elements, showBackground, backgroundGap, backgroundColor, backgroundType, onConnect, snapToGrid, snapGrid, onlyRenderVisibleNodes }: GraphViewProps) => JSX.Element>;
|
||||||
export default GraphView;
|
export default GraphView;
|
||||||
|
|||||||
+2
-1
@@ -4,6 +4,7 @@ interface NodeRendererProps {
|
|||||||
nodeTypes: NodeTypesType;
|
nodeTypes: NodeTypesType;
|
||||||
onElementClick: () => void;
|
onElementClick: () => void;
|
||||||
onNodeDragStop: () => void;
|
onNodeDragStop: () => void;
|
||||||
|
onlyRenderVisibleNodes?: boolean;
|
||||||
}
|
}
|
||||||
declare const NodeRenderer: React.MemoExoticComponent<(props: NodeRendererProps) => JSX.Element>;
|
declare const NodeRenderer: React.MemoExoticComponent<({ onlyRenderVisibleNodes, ...props }: NodeRendererProps) => JSX.Element>;
|
||||||
export default NodeRenderer;
|
export default NodeRenderer;
|
||||||
|
|||||||
Vendored
+4
-2
@@ -20,10 +20,11 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
|
|||||||
backgroundColor: string;
|
backgroundColor: string;
|
||||||
backgroundType: GridType;
|
backgroundType: GridType;
|
||||||
snapToGrid: boolean;
|
snapToGrid: boolean;
|
||||||
snapGrid: [16, 16];
|
snapGrid: [number, number];
|
||||||
|
onlyRenderVisibleNodes: boolean;
|
||||||
}
|
}
|
||||||
declare const ReactFlow: {
|
declare const ReactFlow: {
|
||||||
({ style, onElementClick, elements, children, nodeTypes, edgeTypes, onLoad, onMove, onElementsRemove, onConnect, onNodeDragStop, connectionLineType, connectionLineStyle, deleteKeyCode, selectionKeyCode, showBackground, backgroundGap, backgroundType, backgroundColor, snapToGrid, snapGrid, }: ReactFlowProps): JSX.Element;
|
({ style, onElementClick, elements, children, nodeTypes, edgeTypes, onLoad, onMove, onElementsRemove, onConnect, onNodeDragStop, connectionLineType, connectionLineStyle, deleteKeyCode, selectionKeyCode, showBackground, backgroundGap, backgroundType, backgroundColor, snapToGrid, snapGrid, onlyRenderVisibleNodes }: ReactFlowProps): JSX.Element;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
defaultProps: {
|
defaultProps: {
|
||||||
onElementClick: () => void;
|
onElementClick: () => void;
|
||||||
@@ -52,6 +53,7 @@ declare const ReactFlow: {
|
|||||||
backgroundType: GridType;
|
backgroundType: GridType;
|
||||||
snapToGrid: boolean;
|
snapToGrid: boolean;
|
||||||
snapGrid: number[];
|
snapGrid: number[];
|
||||||
|
onlyRenderVisibleNodes: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export default ReactFlow;
|
export default ReactFlow;
|
||||||
|
|||||||
Vendored
+6
-1
@@ -2,7 +2,12 @@ import { CSSProperties, SVGAttributes } from 'react';
|
|||||||
export declare type ElementId = string;
|
export declare type ElementId = string;
|
||||||
export declare type Elements = Array<Node | Edge>;
|
export declare type Elements = Array<Node | Edge>;
|
||||||
export declare type Transform = [number, number, number];
|
export declare type Transform = [number, number, number];
|
||||||
export declare type Position = 'left' | 'top' | 'right' | 'bottom';
|
export declare enum Position {
|
||||||
|
Left = "left",
|
||||||
|
Top = "top",
|
||||||
|
Right = "right",
|
||||||
|
Bottom = "bottom"
|
||||||
|
}
|
||||||
export declare type XYPosition = {
|
export declare type XYPosition = {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
|
|||||||
Vendored
+1
-1
@@ -7,7 +7,7 @@ export declare const addEdge: (edgeParams: Edge, elements: (Node | Edge)[]) => (
|
|||||||
export declare const parseElement: (element: Node | Edge, transform: [number, number, number], snapToGrid: boolean, snapGrid: [number, number]) => Node | Edge;
|
export declare const parseElement: (element: Node | Edge, transform: [number, number, number], snapToGrid: boolean, snapGrid: [number, number]) => Node | Edge;
|
||||||
export declare const getBoundsofRects: (rect1: Rect, rect2: Rect) => Rect;
|
export declare const getBoundsofRects: (rect1: Rect, rect2: Rect) => Rect;
|
||||||
export declare const getRectOfNodes: (nodes: Node[]) => Rect;
|
export declare const getRectOfNodes: (nodes: Node[]) => Rect;
|
||||||
export declare const graphPosToZoomedPos: (pos: XYPosition, transform: [number, number, number]) => XYPosition;
|
export declare const graphPosToZoomedPos: ({ x, y }: XYPosition, [tx, ty, tScale]: [number, number, number]) => XYPosition;
|
||||||
export declare const getNodesInside: (nodes: Node[], rect: Rect, [tx, ty, tScale]?: [number, number, number], partially?: boolean) => Node[];
|
export declare const getNodesInside: (nodes: Node[], rect: Rect, [tx, ty, tScale]?: [number, number, number], partially?: boolean) => Node[];
|
||||||
export declare const getConnectedEdges: (nodes: Node[], edges: Edge[]) => Edge[];
|
export declare const getConnectedEdges: (nodes: Node[], edges: Edge[]) => Edge[];
|
||||||
export declare const fitView: ({ padding }?: FitViewParams) => void;
|
export declare const fitView: ({ padding }?: FitViewParams) => void;
|
||||||
|
|||||||
+30
-59
@@ -1,67 +1,38 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import Graph, { removeElements, addEdge, getOutgoers } from 'react-flow';
|
import Graph, { removeElements, addEdge } from 'react-flow';
|
||||||
|
|
||||||
const onNodeDragStop = node => console.log('drag stop', node);
|
const onNodeDragStop = node => console.log('drag stop', node);
|
||||||
|
const onLoad = graphInstance => console.log('graph loaded:', graphInstance);
|
||||||
|
const onElementClick = element => console.log('click', element);
|
||||||
|
|
||||||
class App extends PureComponent {
|
const initialElements = [
|
||||||
constructor() {
|
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
|
||||||
super();
|
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
|
||||||
|
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
|
||||||
|
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
|
||||||
|
{ id: 'e1-2', source: '1', target: '2', animated: true },
|
||||||
|
{ id: 'e1-3', source: '1', target: '3' },
|
||||||
|
];
|
||||||
|
|
||||||
this.state = {
|
const BasicGraph = () => {
|
||||||
graphLoaded: false,
|
const [elements, setElements] = useState(initialElements);
|
||||||
elements: [
|
const onElementsRemove = (elementsToRemove) =>
|
||||||
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
|
setElements(els => removeElements(elementsToRemove, els));
|
||||||
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
|
const onConnect = (params) => setElements(els => addEdge(params, els));
|
||||||
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
|
|
||||||
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
|
|
||||||
{ id: 'e1-2', source: '1', target: '2', animated: true },
|
|
||||||
{ id: 'e1-3', source: '1', target: '3' },
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoad(graphInstance) {
|
return (
|
||||||
console.log('graph loaded:', graphInstance);
|
<Graph
|
||||||
|
elements={elements}
|
||||||
this.graphInstance = graphInstance;
|
onLoad={onLoad}
|
||||||
this.setState({
|
onElementClick={onElementClick}
|
||||||
graphLoaded: true
|
onElementsRemove={onElementsRemove}
|
||||||
});
|
onConnect={onConnect}
|
||||||
}
|
onNodeDragStop={onNodeDragStop}
|
||||||
|
style={{ width: '100%', height: '100%' }}
|
||||||
onElementClick(element) {
|
backgroundType="lines"
|
||||||
console.log('click', element);
|
/>
|
||||||
console.log('outgoers', getOutgoers(element, this.state.elements));
|
);
|
||||||
}
|
|
||||||
|
|
||||||
onElementsRemove(elementsToRemove) {
|
|
||||||
this.setState(prevState => ({
|
|
||||||
elements: removeElements(elementsToRemove, prevState.elements)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
onConnect(params) {
|
|
||||||
console.log('connect', params);
|
|
||||||
this.setState(prevState => ({
|
|
||||||
elements: addEdge(params, prevState.elements)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Graph
|
|
||||||
elements={this.state.elements}
|
|
||||||
onLoad={graphInstance => this.onLoad(graphInstance)}
|
|
||||||
onElementClick={element => this.onElementClick(element)}
|
|
||||||
onElementsRemove={elements => this.onElementsRemove(elements)}
|
|
||||||
onConnect={params => this.onConnect(params)}
|
|
||||||
onNodeDragStop={onNodeDragStop}
|
|
||||||
style={{ width: '100%', height: '100%' }}
|
|
||||||
backgroundType="lines"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default BasicGraph;
|
||||||
|
|||||||
+40
-76
@@ -1,84 +1,48 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import Graph, { removeElements, addEdge, getOutgoers, MiniMap } from 'react-flow';
|
import Graph, { removeElements, addEdge, MiniMap, Controls } from 'react-flow';
|
||||||
|
|
||||||
const onNodeDragStop = node => console.log('drag stop', node);
|
const onNodeDragStop = node => console.log('drag stop', node);
|
||||||
|
const onLoad = graphInstance => console.log('graph loaded:', graphInstance);
|
||||||
|
const onElementClick = element => console.log('click', element);
|
||||||
|
|
||||||
class App extends PureComponent {
|
const EmptyGraph = () => {
|
||||||
constructor() {
|
const [elements, setElements] = useState([]);
|
||||||
super();
|
const onElementsRemove = (elementsToRemove) =>
|
||||||
|
setElements(els => removeElements(elementsToRemove, els));
|
||||||
this.state = {
|
const onConnect = (params) => setElements(els => addEdge(params, els));
|
||||||
graphLoaded: false,
|
const onAdd = () => {
|
||||||
elements: []
|
const nodeId = (elements.length + 1).toString();
|
||||||
|
const newNode = {
|
||||||
|
id: nodeId,
|
||||||
|
data: { label: `Node: ${nodeId}` },
|
||||||
|
position: { x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight }
|
||||||
};
|
};
|
||||||
}
|
setElements(els => els.concat(newNode));
|
||||||
|
};
|
||||||
|
|
||||||
onLoad(graphInstance) {
|
return (
|
||||||
console.log('graph loaded:', graphInstance);
|
<Graph
|
||||||
|
elements={elements}
|
||||||
this.graphInstance = graphInstance;
|
onLoad={onLoad}
|
||||||
this.setState({
|
onElementClick={onElementClick}
|
||||||
graphLoaded: true
|
onElementsRemove={onElementsRemove}
|
||||||
});
|
onConnect={p => onConnect(p)}
|
||||||
}
|
onNodeDragStop={onNodeDragStop}
|
||||||
|
style={{ width: '100%', height: '100%' }}
|
||||||
onElementClick(element) {
|
backgroundType="lines"
|
||||||
console.log('click', element);
|
>
|
||||||
console.log('outgoers', getOutgoers(element, this.state.elements));
|
<MiniMap />
|
||||||
}
|
<Controls />
|
||||||
|
<button
|
||||||
onElementsRemove(elementsToRemove) {
|
type="button"
|
||||||
this.setState(prevState => ({
|
onClick={onAdd}
|
||||||
elements: removeElements(elementsToRemove, prevState.elements)
|
style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}
|
||||||
}));
|
>
|
||||||
}
|
add
|
||||||
|
</button>
|
||||||
onConnect(params) {
|
</Graph>
|
||||||
console.log('connect', params);
|
);
|
||||||
this.setState(prevState => ({
|
|
||||||
elements: addEdge(params, prevState.elements)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
onAdd() {
|
|
||||||
this.setState((prevState) => {
|
|
||||||
const nodeId = (prevState.elements.length + 1).toString();
|
|
||||||
|
|
||||||
return {
|
|
||||||
...prevState,
|
|
||||||
elements: prevState.elements.concat({
|
|
||||||
id: nodeId,
|
|
||||||
data: { label: `Node: ${nodeId}` },
|
|
||||||
position: { x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight }
|
|
||||||
})
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Graph
|
|
||||||
elements={this.state.elements}
|
|
||||||
onLoad={graphInstance => this.onLoad(graphInstance)}
|
|
||||||
onElementClick={element => this.onElementClick(element)}
|
|
||||||
onElementsRemove={elements => this.onElementsRemove(elements)}
|
|
||||||
onConnect={params => this.onConnect(params)}
|
|
||||||
onNodeDragStop={onNodeDragStop}
|
|
||||||
style={{ width: '100%', height: '100%' }}
|
|
||||||
backgroundType="lines"
|
|
||||||
>
|
|
||||||
<MiniMap />
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => this.onAdd()}
|
|
||||||
style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}
|
|
||||||
>
|
|
||||||
add
|
|
||||||
</button>
|
|
||||||
</Graph>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default EmptyGraph;
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export interface GraphViewProps {
|
|||||||
backgroundType: GridType;
|
backgroundType: GridType;
|
||||||
snapToGrid: boolean;
|
snapToGrid: boolean;
|
||||||
snapGrid: [number, number];
|
snapGrid: [number, number];
|
||||||
|
onlyRenderVisibleNodes: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const GraphView = memo(
|
const GraphView = memo(
|
||||||
@@ -63,6 +64,7 @@ const GraphView = memo(
|
|||||||
onConnect,
|
onConnect,
|
||||||
snapToGrid,
|
snapToGrid,
|
||||||
snapGrid,
|
snapGrid,
|
||||||
|
onlyRenderVisibleNodes
|
||||||
}: GraphViewProps) => {
|
}: GraphViewProps) => {
|
||||||
const zoomPane = useRef<HTMLDivElement>(null);
|
const zoomPane = useRef<HTMLDivElement>(null);
|
||||||
const rendererNode = useRef<HTMLDivElement>(null);
|
const rendererNode = useRef<HTMLDivElement>(null);
|
||||||
@@ -136,6 +138,7 @@ const GraphView = memo(
|
|||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
onElementClick={onElementClick}
|
onElementClick={onElementClick}
|
||||||
onNodeDragStop={onNodeDragStop}
|
onNodeDragStop={onNodeDragStop}
|
||||||
|
onlyRenderVisibleNodes={onlyRenderVisibleNodes}
|
||||||
/>
|
/>
|
||||||
<EdgeRenderer
|
<EdgeRenderer
|
||||||
width={state.width}
|
width={state.width}
|
||||||
|
|||||||
@@ -1,45 +1,24 @@
|
|||||||
import React, { memo, ComponentType } from 'react';
|
import React, { memo, ComponentType } from 'react';
|
||||||
|
|
||||||
import { useStoreState } from '../../store/hooks';
|
import { useStoreState } from '../../store/hooks';
|
||||||
import { isNode } from '../../utils/graph';
|
import { getNodesInside } from '../../utils/graph';
|
||||||
import {
|
import { Node, Transform, NodeTypesType, NodeComponentProps, Elements } from '../../types';
|
||||||
Node,
|
|
||||||
Transform,
|
|
||||||
NodeTypesType,
|
|
||||||
NodeComponentProps,
|
|
||||||
} from '../../types';
|
|
||||||
|
|
||||||
interface NodeRendererProps {
|
interface NodeRendererProps {
|
||||||
nodeTypes: NodeTypesType;
|
nodeTypes: NodeTypesType;
|
||||||
onElementClick: () => void;
|
onElementClick: () => void;
|
||||||
onNodeDragStop: () => void;
|
onNodeDragStop: () => void;
|
||||||
|
onlyRenderVisibleNodes?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NodeRendererState {
|
function renderNode(node: Node, props: NodeRendererProps, transform: Transform, selectedElements: Elements) {
|
||||||
nodes: Node[];
|
|
||||||
transform: Transform;
|
|
||||||
selectedElements: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderNode(
|
|
||||||
node: Node,
|
|
||||||
props: NodeRendererProps,
|
|
||||||
state: NodeRendererState
|
|
||||||
) {
|
|
||||||
const nodeType = node.type || 'default';
|
const nodeType = node.type || 'default';
|
||||||
|
const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType<NodeComponentProps>;
|
||||||
if (!props.nodeTypes[nodeType]) {
|
if (!props.nodeTypes[nodeType]) {
|
||||||
console.warn(
|
console.warn(`No node type found for type "${nodeType}". Using fallback type "default".`);
|
||||||
`No node type found for type "${nodeType}". Using fallback type "default".`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const NodeComponent = (props.nodeTypes[nodeType] ||
|
const isSelected = selectedElements.some(({ id }) => id === node.id);
|
||||||
props.nodeTypes.default) as ComponentType<NodeComponentProps>;
|
|
||||||
const selected = state.selectedElements
|
|
||||||
.filter(isNode)
|
|
||||||
.map((e: Node) => e.id)
|
|
||||||
.includes(node.id);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeComponent
|
<NodeComponent
|
||||||
@@ -51,28 +30,26 @@ function renderNode(
|
|||||||
yPos={node.__rg.position.y}
|
yPos={node.__rg.position.y}
|
||||||
onClick={props.onElementClick}
|
onClick={props.onElementClick}
|
||||||
onNodeDragStop={props.onNodeDragStop}
|
onNodeDragStop={props.onNodeDragStop}
|
||||||
transform={state.transform}
|
transform={transform}
|
||||||
selected={selected}
|
selected={isSelected}
|
||||||
style={node.style}
|
style={node.style}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const NodeRenderer = memo((props: NodeRendererProps) => {
|
const NodeRenderer = memo(({ onlyRenderVisibleNodes = true, ...props }: NodeRendererProps) => {
|
||||||
const state: NodeRendererState = useStoreState(s => ({
|
const { nodes, transform, selectedElements, width, height } = useStoreState(s => s);
|
||||||
nodes: s.nodes,
|
|
||||||
transform: s.transform,
|
|
||||||
selectedElements: s.selectedElements,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const { transform, nodes } = state;
|
const [tx, ty, tScale] = transform;
|
||||||
const transformStyle = {
|
const transformStyle = {
|
||||||
transform: `translate(${transform[0]}px,${transform[1]}px) scale(${transform[2]})`,
|
transform: `translate(${tx}px,${ty}px) scale(${tScale})`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderNodes = onlyRenderVisibleNodes ? getNodesInside(nodes, { x: 0, y: 0, width, height }, transform, true) : nodes;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="react-flow__nodes" style={transformStyle}>
|
<div className="react-flow__nodes" style={transformStyle}>
|
||||||
{nodes.map(node => renderNode(node, props, state))}
|
{renderNodes.map(node => renderNode(node, props, transform, selectedElements))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ export interface ReactFlowProps
|
|||||||
backgroundColor: string;
|
backgroundColor: string;
|
||||||
backgroundType: GridType;
|
backgroundType: GridType;
|
||||||
snapToGrid: boolean;
|
snapToGrid: boolean;
|
||||||
snapGrid: [16, 16];
|
snapGrid: [number, number];
|
||||||
|
onlyRenderVisibleNodes: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReactFlow = ({
|
const ReactFlow = ({
|
||||||
@@ -73,6 +74,7 @@ const ReactFlow = ({
|
|||||||
backgroundColor,
|
backgroundColor,
|
||||||
snapToGrid,
|
snapToGrid,
|
||||||
snapGrid,
|
snapGrid,
|
||||||
|
onlyRenderVisibleNodes
|
||||||
}: ReactFlowProps) => {
|
}: ReactFlowProps) => {
|
||||||
const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), []);
|
const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), []);
|
||||||
const edgeTypesParsed = useMemo(() => createEdgeTypes(edgeTypes), []);
|
const edgeTypesParsed = useMemo(() => createEdgeTypes(edgeTypes), []);
|
||||||
@@ -100,6 +102,7 @@ const ReactFlow = ({
|
|||||||
backgroundType={backgroundType}
|
backgroundType={backgroundType}
|
||||||
snapToGrid={snapToGrid}
|
snapToGrid={snapToGrid}
|
||||||
snapGrid={snapGrid}
|
snapGrid={snapGrid}
|
||||||
|
onlyRenderVisibleNodes={onlyRenderVisibleNodes}
|
||||||
/>
|
/>
|
||||||
{children}
|
{children}
|
||||||
</StoreProvider>
|
</StoreProvider>
|
||||||
@@ -136,6 +139,7 @@ ReactFlow.defaultProps = {
|
|||||||
backgroundType: GridType.Dots,
|
backgroundType: GridType.Dots,
|
||||||
snapToGrid: false,
|
snapToGrid: false,
|
||||||
snapGrid: [16, 16],
|
snapGrid: [16, 16],
|
||||||
|
onlyRenderVisibleNodes: true
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ReactFlow;
|
export default ReactFlow;
|
||||||
|
|||||||
+2
-2
@@ -157,10 +157,10 @@ export const getNodesInside = (
|
|||||||
const overlappingArea =
|
const overlappingArea =
|
||||||
(Math.max(rBox.x, nBox.x) - Math.min(rBox.x2, nBox.x2)) * (Math.max(rBox.y, nBox.y) - Math.min(rBox.y2, nBox.y2));
|
(Math.max(rBox.x, nBox.x) - Math.min(rBox.x2, nBox.x2)) * (Math.max(rBox.y, nBox.y) - Math.min(rBox.y2, nBox.y2));
|
||||||
if (partially) {
|
if (partially) {
|
||||||
return overlappingArea > 0;
|
return overlappingArea >= 0;
|
||||||
}
|
}
|
||||||
const area = width * height;
|
const area = width * height;
|
||||||
return overlappingArea === area;
|
return overlappingArea >= area;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user