feat(renderer): add connection line
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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 />}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -60,7 +60,8 @@
|
||||
animation: dashdraw 0.5s linear infinite;
|
||||
}
|
||||
|
||||
&.connector {
|
||||
&.connection {
|
||||
stroke: '#ddd';
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user