Skip to content

Commit

Permalink
refactor(nodes): only re render changed nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
moklick committed Jul 31, 2019
1 parent 6e71591 commit 3403203
Show file tree
Hide file tree
Showing 9 changed files with 34,726 additions and 34,647 deletions.
50,293 changes: 25,153 additions & 25,140 deletions dist/ReactGraph.js

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions example/SimpleGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const SpecialNode = ({ data, styles }) => (
</div>
);

const onNodeDragStop = node => console.log('drag stop', node);

class App extends PureComponent {
constructor() {
super();
Expand Down Expand Up @@ -58,6 +60,9 @@ class App extends PureComponent {
{ source: '6', target: '7', style: { stroke: '#FFCC00' }},
]
};

this.onElementClick = this.onElementClick.bind(this);
this.onConnect = this.onConnect.bind(this);
}

onLoad(graphInstance) {
Expand Down Expand Up @@ -123,10 +128,10 @@ class App extends PureComponent {
return (
<Graph
elements={this.state.elements}
onElementClick={element => this.onElementClick(element)}
onElementClick={this.onElementClick}
onElementsRemove={elements => this.onElementsRemove(elements)}
onConnect={params => this.onConnect(params)}
onNodeDragStop={node => console.log('drag stop', node)}
onConnect={this.onConnect}
onNodeDragStop={onNodeDragStop}
style={{ width: '100%', height: '100%' }}
onLoad={graphInstance => this.onLoad(graphInstance)}
onChange={(elements) => this.onChange(elements)}
Expand Down
18,849 changes: 9,438 additions & 9,411 deletions example/build/example.e31bb0bc.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion example/build/example.e31bb0bc.js.map

Large diffs are not rendered by default.

55 changes: 31 additions & 24 deletions src/EdgeRenderer/EdgeTypes/wrapEdge.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,36 @@ import { isEdge } from '../../graph-utils';

const isInput = e => ['INPUT', 'SELECT', 'TEXTAREA'].includes(e.target.nodeName);

export default EdgeComponent => memo((props) => {
const {
source, target, animated, type,
dispatch, selectedElements, onClick
} = props;
const selected = selectedElements
.filter(e => isEdge(e))
.find(e => e.source === source && e.target === target);
const edgeClasses = cx('react-graph__edge', { selected, animated: animated });
export default EdgeComponent => {
const WrappedEdge = memo((props) => {
const {
source, target, animated, type,
dispatch, selectedElements, onClick
} = props;
const selected = selectedElements
.filter(e => isEdge(e))
.find(e => e.source === source && e.target === target);
const edgeClasses = cx('react-graph__edge', { selected, animated: animated });

return (
<g
className={edgeClasses}
onClick={(e) => {
if (isInput(e)) {
return false;
}
return (
<g
className={edgeClasses}
onClick={(e) => {
if (isInput(e)) {
return false;
}

dispatch(setSelectedElements({ source, target }));
onClick({ source, target, type });
}}
>
<EdgeComponent {...props} />
</g>
);
});
dispatch(setSelectedElements({ source, target }));
onClick({ source, target, type });
}}
>
<EdgeComponent {...props} />
</g>
);
});

WrappedEdge.displayName = 'Wrapped Edge';
WrappedEdge.whyDidYouRender = false;

return WrappedEdge;
};
5 changes: 4 additions & 1 deletion src/GraphContext/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ export const Provider = (props) => {
const existingNode = state.nodes.find(n => n.id === propNode.id);

if (existingNode) {
const data = !isEqual(existingNode.data, propNode.data) ?
{ ...existingNode.data, ...propNode.data } : existingNode.data;

return {
...existingNode,
data: { ...existingNode.data, ...propNode.data }
data
};
}

Expand Down
100 changes: 52 additions & 48 deletions src/NodeRenderer/NodeTypes/wrapNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,52 +76,56 @@ const onStop = ({ onNodeDragStop, id, type, position, data }) => {
});
};

export default NodeComponent => memo((props) => {
const nodeElement = useRef(null);
const [offset, setOffset] = useState({ x: 0, y: 0 });
const {
id, type, data, transform, xPos, yPos, selectedElements,
dispatch, getNodeById, onClick, onNodeDragStop
} = props;

console.log('render node', id);

const position = { x: xPos, y: yPos };
const [ x, y, k ] = transform;
const selected = selectedElements.filter(isNode).map(e => e.id).includes(id);
const nodeClasses = cx('react-graph__node', { selected });
const nodeStyle = { zIndex: selected ? 10 : 3, transform: `translate(${xPos}px,${yPos}px)` };

useEffect(() => {
const bounds = nodeElement.current.getBoundingClientRect();
const unscaledWith = Math.round(bounds.width * (1 / k));
const unscaledHeight = Math.round(bounds.height * (1 / k));
const handleBounds = {
source: getHandleBounds('.source', nodeElement.current, bounds, k),
target: getHandleBounds('.target', nodeElement.current, bounds, k)
};

dispatch(updateNodeData(id, { width: unscaledWith, height: unscaledHeight, handleBounds }));
}, []);

return (
<ReactDraggable.DraggableCore
onStart={evt => onStart(evt, { setOffset, transform, position })}
onDrag={evt => onDrag(evt, { dispatch, id, offset, transform })}
onStop={() => onStop({ onNodeDragStop, id, type, position, data })}
scale={transform[2]}
>
<div
className={nodeClasses}
ref={nodeElement}
style={nodeStyle}
onClick={evt => onNodeClick(evt, { getNodeById, onClick, dispatch, id, type, position, data })}
export default NodeComponent => {
const WrappedComp = memo((props) => {
const nodeElement = useRef(null);
const [offset, setOffset] = useState({ x: 0, y: 0 });
const {
id, type, data, transform, xPos, yPos, selectedElements,
dispatch, getNodeById, onClick, onNodeDragStop
} = props;

const position = { x: xPos, y: yPos };
const [ x, y, k ] = transform;
const selected = selectedElements.filter(isNode).map(e => e.id).includes(id);
const nodeClasses = cx('react-graph__node', { selected });
const nodeStyle = { zIndex: selected ? 10 : 3, transform: `translate(${xPos}px,${yPos}px)` };

useEffect(() => {
const bounds = nodeElement.current.getBoundingClientRect();
const unscaledWith = Math.round(bounds.width * (1 / k));
const unscaledHeight = Math.round(bounds.height * (1 / k));
const handleBounds = {
source: getHandleBounds('.source', nodeElement.current, bounds, k),
target: getHandleBounds('.target', nodeElement.current, bounds, k)
};

dispatch(updateNodeData(id, { width: unscaledWith, height: unscaledHeight, handleBounds }));
}, []);

return (
<ReactDraggable.DraggableCore
onStart={evt => onStart(evt, { setOffset, transform, position })}
onDrag={evt => onDrag(evt, { dispatch, id, offset, transform })}
onStop={() => onStop({ onNodeDragStop, id, type, position, data })}
scale={transform[2]}
>
<Provider value={id}>
<NodeComponent {...props} selected={selected} />
</Provider>
</div>
</ReactDraggable.DraggableCore>
);
}
);
<div
className={nodeClasses}
ref={nodeElement}
style={nodeStyle}
onClick={evt => onNodeClick(evt, { getNodeById, onClick, dispatch, id, type, position, data })}
>
<Provider value={id}>
<NodeComponent {...props} selected={selected} />
</Provider>
</div>
</ReactDraggable.DraggableCore>
);
});

WrappedComp.displayName = 'Wrapped Node';
WrappedComp.whyDidYouRender = false;

return WrappedComp;
};
38 changes: 29 additions & 9 deletions src/ReactGraph/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { PureComponent } from 'react';
import isEqual from 'lodash.isequal';

if (process.env.NODE_ENV !== 'production') {
const whyDidYouRender = require('@welldone-software/why-did-you-render');
whyDidYouRender(React);
}

import { parseElements, separateElements } from '../graph-utils';
import { parseElements, isNode, isEdge } from '../graph-utils';
import GraphView from '../GraphView';
import GlobalKeyHandler from '../GlobalKeyHandler';
import { Provider } from '../GraphContext';
Expand All @@ -27,23 +28,42 @@ class ReactGraph extends PureComponent {

this.nodeTypes = createNodeTypes(props.nodeTypes);
this.edgeTypes = createEdgeTypes(props.edgeTypes);

this.state = {
nodes: [],
edges: []
};
}

componentDidMount() {
this.updateElements(this.props.elements);
}

componentDidUpdate(prevProps) {
if (!isEqual(prevProps.elements, this.props.elements)) {
this.updateElements(this.props.elements);
}
}

updateElements(elements) {
const parsedElements = elements.map(parseElements);

this.setState({
nodes: parsedElements.filter(isNode),
edges: parsedElements.filter(isEdge),
});
}

render() {
const {
style, onElementClick, children, onLoad,
onMove, onChange, elements, onElementsRemove,
onConnect, onNodeDragStop, connectionLineType,
connectionLineStyle
onMove, onChange, onElementsRemove, onConnect, onNodeDragStop,
connectionLineType, connectionLineStyle
} = this.props;

const { nodes, edges } = elements
.map(parseElements)
.reduce(separateElements, {});

return (
<div style={style} className="react-graph">
<Provider nodes={nodes} edges={edges} onConnect={onConnect}>
<Provider nodes={this.state.nodes} edges={this.state.edges} onConnect={onConnect}>
<GraphView
onLoad={onLoad}
onMove={onMove}
Expand Down
20 changes: 10 additions & 10 deletions src/graph-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,23 @@ let internalNodeId = 0;

const getId = () => internalNodeId++;

export const parseElements = e => {
export const parseElements = (e) => {
e.type = e.type || 'default';
e.id = e.id ? e.id : getId();

if (isEdge(e)) {
return e;
}

return {
...e,
id: e.id.toString(),
__rg: {
position: e.position,
width: null,
height: null
}
}
e.id = e.id.toString();
e.__rg = {
position: e.position,
width: null,
height: null,
handleBounds : {}
};

return { ...e };
};

export const separateElements = (res, element) => {
Expand Down

0 comments on commit 3403203

Please sign in to comment.