feat(renderer): add connection line

This commit is contained in:
moklick
2019-07-31 11:32:56 +02:00
parent 915cba94a7
commit 2d5e2d9241
18 changed files with 336 additions and 175 deletions
@@ -7,12 +7,13 @@ export default (props) => {
setSourceNode(props.nodes.find(n => n.id === props.connectionSourceId));
}, []);
if (!sourceNode) {
return null;
}
const style = props.style || {};
const className = cx('react-graph__edge', 'connector', props.className);
const style = props.connectionLineStyle || {};
const className = cx('react-graph__edge', 'connection', props.className);
const sourceHandle = sourceNode.__rg.handleBounds.source;
const sourceHandleX = sourceHandle ? sourceHandle.x + (sourceHandle.width / 2) : sourceNode.__rg.width / 2;
@@ -23,15 +24,22 @@ export default (props) => {
const targetX = (props.connectionPosition.x * (1 / props.transform[2])) - (props.transform[0] * (1 / props.transform[2]));
const targetY = (props.connectionPosition.y * (1 / props.transform[2])) - (props.transform[1] * (1 / props.transform[2]));
const yOffset = Math.abs(targetY - sourceY) / 2;
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
const dAttr = `M${sourceX},${sourceY} C${sourceX},${centerY} ${targetX},${centerY} ${targetX},${targetY}`;
let dAttr = '';
if (props.connectionLineType === 'bezier') {
const yOffset = Math.abs(targetY - sourceY) / 2;
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
dAttr = `M${sourceX},${sourceY} C${sourceX},${centerY} ${targetX},${centerY} ${targetX},${targetY}`;
} else {
dAttr = `M${sourceX},${sourceY} ${targetX},${targetY}`;
}
return (
<path
className={className}
d={dAttr}
{...style}
/>
<g className={className}>
<path
d={dAttr}
{...style}
/>
</g>
);
};
+9 -4
View File
@@ -1,7 +1,7 @@
import React, { PureComponent } from 'react';
import { Consumer } from '../GraphContext';
import ConnectorEdge from '../ConnectorEdge';
import ConnectionLine from '../ConnectionLine';
class EdgeRenderer extends PureComponent {
renderEdge(e, nodes, onElementClick) {
@@ -31,7 +31,10 @@ class EdgeRenderer extends PureComponent {
}
render() {
const { width, height, onElementClick } = this.props;
const {
width, height, onElementClick,
connectionLineStyle, connectionLineType
} = this.props;
if (!width) {
return null;
@@ -49,12 +52,14 @@ class EdgeRenderer extends PureComponent {
transform={`translate(${state.transform[0]},${state.transform[1]}) scale(${state.transform[2]})`}
>
{state.edges.map(e => this.renderEdge(e, state.nodes, onElementClick))}
{state.isConnecting && (
<ConnectorEdge
{state.connectionSourceId && (
<ConnectionLine
nodes={state.nodes}
connectionSourceId={state.connectionSourceId}
connectionPosition={state.connectionPosition}
transform={state.transform}
connectionLineStyle={connectionLineStyle}
connectionLineType={connectionLineType}
/>
)}
</g>
+3 -1
View File
@@ -9,7 +9,8 @@ export const GraphContext = createContext({});
export const Provider = (props) => {
const {
children
children,
onConnect
} = props;
const [state, dispatch] = useReducer(reducer, initialState);
@@ -42,6 +43,7 @@ export const Provider = (props) => {
const graphContext = {
state,
onConnect,
dispatch
};
+2 -1
View File
@@ -83,13 +83,14 @@ const GraphView = memo((props) => {
nodeTypes={props.nodeTypes}
onElementClick={props.onElementClick}
onNodeDragStop={props.onNodeDragStop}
onConnect={props.onConnect}
/>
<EdgeRenderer
width={state.width}
height={state.height}
edgeTypes={props.edgeTypes}
onElementClick={props.onElementClick}
connectionLineType={props.connectionLineType}
connectionLineStyle={props.connectionLineStyle}
/>
{shiftPressed && <UserSelection />}
{state.nodesSelectionActive && <NodesSelection />}
+24 -13
View File
@@ -5,23 +5,34 @@ import NodeIdContext from '../NodeIdContext'
import { GraphContext } from '../../GraphContext';
import { setConnecting, setConnectionPos } from '../../state/actions';
function onDragStart(evt, nodeId, dispatch) {
evt.dataTransfer.setData('text/plain', nodeId);
function onMouseDown(evt, nodeId, dispatch, onConnect) {
const connectionPosition = { x: evt.clientX, y: evt.clientY };
dispatch(setConnecting({ connectionPosition, connectionSourceId: nodeId }))
// dispatch(setConnecting({ isConnecting: true, connectionSourceId: nodeId }));
}
function onMouseMove(evt) {
dispatch(setConnectionPos({ x: evt.clientX, y: evt.clientY }));
}
function onDragStop(evt, dispatch) {
// dispatch(setConnecting({ isConnecting: false }));
}
function onMouseUp(evt) {
const elementBelow = document.elementFromPoint(evt.clientX, evt.clientY);
function onDrag(evt, dispatch) {
// dispatch(setConnectionPos({ x: evt.clientX, y: evt.clientY }));
if (elementBelow && elementBelow.classList.contains('target')) {
const targetId = elementBelow.getAttribute('data-nodeid');
onConnect({ source: nodeId, target: targetId });
}
dispatch(setConnecting({ connectionSourceId: false }));
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
}
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp)
}
export default memo(({ source, target, className = null, ...rest }) => {
const nodeId = useContext(NodeIdContext);
const { dispatch } = useContext(GraphContext);
const { dispatch, onConnect } = useContext(GraphContext);
const handleClasses = cx(
'react-graph__handle',
className,
@@ -31,6 +42,7 @@ export default memo(({ source, target, className = null, ...rest }) => {
if (target) {
return (
<div
data-nodeid={nodeId}
className={handleClasses}
{...rest}
/>
@@ -39,10 +51,9 @@ export default memo(({ source, target, className = null, ...rest }) => {
return (
<div
draggable
onDragStart={evt => onDragStart(evt, nodeId, dispatch)}
onDragEnd={evt => onDragStop(evt, dispatch)}
data-nodeid={nodeId}
className={handleClasses}
onMouseDown={evt => onMouseDown(evt, nodeId, dispatch, onConnect)}
{...rest}
/>
);
+2 -11
View File
@@ -8,7 +8,7 @@ import { isNode } from '../../graph-utils';
import { Provider } from '../NodeIdContext';
const isInput = e => ['INPUT', 'SELECT', 'TEXTAREA'].includes(e.target.nodeName);
const isHandle = e => e.target.className && e.target.className.includes('source');
const isHandle = e => e.target.className && e.target.className.includes && e.target.className.includes('source');
const getHandleBounds = (sel, nodeElement, parentBounds, k) => {
const handle = nodeElement.querySelector(sel);
@@ -40,7 +40,7 @@ export default NodeComponent => memo((props) => {
const { state, dispatch } = useContext(GraphContext);
const [offset, setOffset] = useState({ x: 0, y: 0 });
const {
data, onClick, type, id, __rg, onConnect, onNodeDragStop
data, onClick, type, id, __rg, onNodeDragStop
} = props;
const { position } = __rg;
const [ x, y, k ] = state.transform;
@@ -104,14 +104,6 @@ export default NodeComponent => memo((props) => {
});
}
const onDrop = (evt) => {
evt.preventDefault();
const source = evt.dataTransfer.getData('text/plain');
onConnect({ source, target: id });
};
return (
<ReactDraggable.DraggableCore
grid={[1, 1]}
@@ -121,7 +113,6 @@ export default NodeComponent => memo((props) => {
scale={k}
>
<div
onDrop={onDrop}
onDragOver={onDragOver}
className={nodeClasses}
ref={nodeElement}
+8 -4
View File
@@ -28,7 +28,8 @@ class ReactGraph extends PureComponent {
const {
style, onElementClick, children, onLoad,
onMove, onChange, elements, onElementsRemove,
onConnect, onNodeDragStop
onConnect, onNodeDragStop, connectionLineType,
connectionLineStyle
} = this.props;
const { nodes, edges } = elements
@@ -37,16 +38,17 @@ class ReactGraph extends PureComponent {
return (
<div style={style} className="react-graph">
<Provider nodes={nodes} edges={edges}>
<Provider nodes={nodes} edges={edges} onConnect={onConnect}>
<GraphView
onLoad={onLoad}
onMove={onMove}
onChange={onChange}
onElementClick={onElementClick}
onConnect={onConnect}
onNodeDragStop={onNodeDragStop}
nodeTypes={this.nodeTypes}
edgeTypes={this.edgeTypes}
connectionLineType={connectionLineType}
connectionLineStyle={connectionLineStyle}
/>
<GlobalKeyHandler
onElementsRemove={onElementsRemove}
@@ -74,7 +76,9 @@ ReactGraph.defaultProps = {
edgeTypes: {
default: BezierEdge,
straight: StraightEdge
}
},
connectionLineType: 'bezier',
connectionLineStyle: {}
};
export default ReactGraph;
+2 -2
View File
@@ -102,8 +102,8 @@ export const updateSelection = (selection) => {
};
};
export const setConnecting = ({ isConnecting, connectionSourceId }) => {
return { type: SET_CONNECTING, payload: { isConnecting, connectionSourceId }};
export const setConnecting = ({ connectionSourceId, connectionPosition = false}) => {
return { type: SET_CONNECTING, payload: { connectionSourceId, connectionPosition }};
};
export const setConnectionPos = ({ x, y }) => {
+7 -2
View File
@@ -37,7 +37,6 @@ export const initialState = {
selectionActive: false,
selection: {},
isConnecting: false,
connectionSourceId: null,
connectionPosition: { x: 0, y: 0 }
};
@@ -125,6 +124,13 @@ export const reducer = (state, action) => {
return { ...state, nodes: nextNodes, edges: nextEdges };
}
case SET_CONNECTING: {
if (!action.payload.connectionPosition) {
return { ...state, connectionSourceId: action.payload.connectionSourceId };
}
return { ...state, ...action.payload };
}
case SET_NODES:
case SET_EDGES:
case UPDATE_TRANSFORM:
@@ -132,7 +138,6 @@ export const reducer = (state, action) => {
case UPDATE_SIZE:
case SET_SELECTION:
case SET_SELECTED_ELEMENTS:
case SET_CONNECTING:
case SET_CONNECTION_POS:
return { ...state, ...action.payload };
default:
+2 -1
View File
@@ -60,7 +60,8 @@
animation: dashdraw 0.5s linear infinite;
}
&.connector {
&.connection {
stroke: '#ddd';
pointer-events: none;
}
}