Skip to content

Commit

Permalink
feat(handle): add type and position
Browse files Browse the repository at this point in the history
  • Loading branch information
moklick committed Sep 11, 2019
1 parent a3d476c commit 54af33a
Show file tree
Hide file tree
Showing 13 changed files with 171 additions and 72 deletions.
18 changes: 10 additions & 8 deletions example/SimpleGraph.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
import React, { PureComponent } from 'react';

import Graph, { isEdge, removeElements, getOutgoers, SourceHandle, TargetHandle, MiniMap } from '../src';
// import Graph, { isEdge, removeElements, getOutgoers, SourceHandle, TargetHandle } from '../dist/ReactGraph';
import Graph, { isEdge, removeElements, getOutgoers, Handle, MiniMap } from '../src';
// import Graph, { isEdge, removeElements, getOutgoers, Handle } from '../dist/ReactGraph';

const SpecialNode = ({ data, styles }) => (
<div
style={{ background: '#FFCC00', padding: 10, borderRadius: 2, ...styles }}
>
<TargetHandle id="a" style={{ left: 10, background: '#999' }} />
<TargetHandle id="b" style={{ left: 30, background: '#999' }} />
<Handle type="target" position="top" id="a" style={{ left: 10, background: '#999' }} />
<Handle type="target" position="top" id="b" style={{ left: 30, background: '#999' }} />
<div>I am <strong>special</strong>!<br />{data.label}</div>
<select onChange={(e) => data.onChange(e.target.value, data)}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<SourceHandle style={{ left: 10, background: '#999' }} />
<Handle type="source" position="bottom" style={{ left: 10, background: '#999' }} />
</div>
);

const InputNode = ({ data, styles }) => (
<div
style={{ background: '#FFCC00', padding: 10, borderRadius: 2, ...styles }}
>
<TargetHandle style={{ left: 10, background: '#999' }} />
<Handle type="target" position="left" style={{ background: '#999' }} />
<div>{data.input}</div>
<input onChange={(e) => data.onChange(e.target.value, data)} />
<SourceHandle style={{ left: 10, background: '#999' }} />
<Handle type="source" position="right" style={{ background: '#999' }} />
</div>
);

Expand Down Expand Up @@ -72,9 +72,11 @@ class App extends PureComponent {
{ id: '5', type: 'default', data: { label: '5 Another node'}, position: { x: 400, y: 300 } },
{ id: '6', type: 'special', data: { onChange, label: '6 no option selected' }, position: { x: 425, y: 375 } },
{ id: '7', type: 'output', data: { label: '7 output' }, position: { x: 250, y: 500 } },
{ id: '8', type: 'text', data: { onChange: onChangeInput, input: 'write something' }, position: { x: 300, y: 100 } },
{ id: '8', type: 'text', data: { onChange: onChangeInput, input: 'write something' }, position: { x: 350, y: 100 } },
{ id: '9', type: 'text', data: { label: 'right' }, position: { x: 600, y: 100 } },
{ source: '1', target: '2', animated: true },
{ source: '1', target: '8', animated: true },
{ source: '8', target: '9', animated: true },
{ source: '2', target: '3' },
{ source: '3', target: '4', type: 'step' },
{ source: '3', target: '5' },
Expand Down
21 changes: 15 additions & 6 deletions src/EdgeRenderer/EdgeTypes/BezierEdge.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import React, { memo } from 'react';

export default memo((props) => {
const {
sourceX, sourceY, targetX, targetY, style = {}
} = props;

export default memo(({
sourceX, sourceY, targetX, targetY,
sourcePosition, targetPosition, style = {}
}) => {
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 = `M${sourceX},${sourceY} C${sourceX},${centerY} ${targetX},${centerY} ${targetX},${targetY}`;

if (['left', 'right'].includes(sourcePosition) && ['left', 'right'].includes(targetPosition)) {
const xOffset = Math.abs(targetX - sourceX) / 2;
const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;

dAttr = `M${sourceX},${sourceY} C${centerX},${sourceY} ${centerX},${targetY} ${targetX},${targetY}`;
} else if (['left', 'right'].includes(sourcePosition) || ['left', 'right'].includes(targetPosition)) {
dAttr = `M${sourceX},${sourceY} C${sourceX},${targetY} ${sourceX},${targetY} ${targetX},${targetY}`;
}

return (
<path
Expand Down
106 changes: 69 additions & 37 deletions src/EdgeRenderer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,52 +4,74 @@ import { useStoreState } from 'easy-peasy';
import ConnectionLine from '../ConnectionLine';
import { isEdge } from '../graph-utils';

function getEdgePositions({ sourceNode, targetNode, sourceHandleId, targetHandleId }) {
const hasSourceHandle = !!sourceNode.__rg.handleBounds.source;
const hasTargetHandle = !!targetNode.__rg.handleBounds.target;

let sourceHandle = null;
let targetHandle = null;

if (hasSourceHandle) {
// there is no sourceHandleId when there are no multiple handles/ handles with ids
// so we just pick the first one
if (sourceNode.__rg.handleBounds.source.length === 1 || !sourceHandleId) {
sourceHandle = sourceNode.__rg.handleBounds.source[0];
} else if (sourceHandleId) {
sourceHandle = sourceNode.__rg.handleBounds.source.find(d => d.id === sourceHandleId);
function getHandlePosition(position, node, handle = null) {
if (!handle) {
switch (position) {
case 'top': return {
x: node.__rg.width / 2,
y: 0
};
case 'right': return {
x: node.__rg.width,
y: node.__rg.height / 2
};
case 'bottom': return {
x: node.__rg.width / 2,
y: node.__rg.height
};
case 'left': return {
x: 0,
y: node.__rg.height / 2
};
}
}

if (hasTargetHandle) {
if (targetNode.__rg.handleBounds.target.length === 1 || !targetHandleId) {
targetHandle = targetNode.__rg.handleBounds.target[0];
} else if (targetHandleId) {
targetHandle = targetNode.__rg.handleBounds.target.find(d => d.id === targetHandleId);
}
switch (position) {
case 'top': return {
x: handle.x + (handle.width / 2),
y: handle.y
};
case 'right': return {
x: handle.x + handle.width,
y: handle.y + (handle.height / 2)
};
case 'bottom': return {
x: handle.x + (handle.width / 2),
y: handle.y + handle.height
};
case 'left': return {
x: handle.x,
y: handle.y + (handle.height / 2)
};
}
}

const sourceHandleX = hasSourceHandle ?
sourceHandle.x + (sourceHandle.width / 2) :
sourceNode.__rg.width / 2;
function getHandle(bounds, handleId) {
let handle = null;

const sourceHandleY = hasSourceHandle ?
sourceHandle.y + (sourceHandle.height / 2) :
sourceNode.__rg.height;
if (!bounds) {
return null;
}

const sourceX = sourceNode.__rg.position.x + sourceHandleX;
const sourceY = sourceNode.__rg.position.y + sourceHandleY;
// there is no handleId when there are no multiple handles/ handles with ids
// so we just pick the first one
if (bounds.length === 1 || !handleId) {
handle = bounds[0];
} else if (handleId) {
handle = bounds.find(d => d.id === handleId);
}

const targetHandleX = hasTargetHandle ?
targetHandle.x + (targetHandle.width / 2) :
targetNode.__rg.width / 2;
return handle;
}

const targetHandleY = hasTargetHandle ?
targetHandle.y + (targetHandle.height / 2) :
0;
function getEdgePositions({ sourceNode, sourceHandle, sourcePosition, targetNode, targetHandle, targetPosition }) {
const sourceHandlePos = getHandlePosition(sourcePosition, sourceNode, sourceHandle)
const sourceX = sourceNode.__rg.position.x + sourceHandlePos.x;
const sourceY = sourceNode.__rg.position.y + sourceHandlePos.y;

const targetX = targetNode.__rg.position.x + targetHandleX;
const targetY = targetNode.__rg.position.y + targetHandleY;
const targetHandlePos = getHandlePosition(targetPosition, targetNode, targetHandle);
const targetX = targetNode.__rg.position.x + targetHandlePos.x;
const targetY = targetNode.__rg.position.y + targetHandlePos.y;

return {
sourceX, sourceY, targetX, targetY
Expand Down Expand Up @@ -80,7 +102,15 @@ function renderEdge(e, props, state) {
}

const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default;
const { sourceX, sourceY, targetX, targetY } = getEdgePositions({ sourceNode, targetNode, sourceHandleId, targetHandleId });
const sourceHandle = getHandle(sourceNode.__rg.handleBounds.source, sourceHandleId);
const targetHandle = getHandle(targetNode.__rg.handleBounds.target, targetHandleId);
const sourcePosition = sourceHandle ? sourceHandle.position : 'bottom';
const targetPosition = targetHandle ? targetHandle.position : 'top';

const { sourceX, sourceY, targetX, targetY } = getEdgePositions({
sourceNode, sourceHandle, sourcePosition,
targetNode, targetHandle, targetPosition
});
const selected = state.selectedElements
.filter(isEdge)
.find(elm => elm.source === sourceId && elm.target === targetId);
Expand All @@ -102,6 +132,8 @@ function renderEdge(e, props, state) {
sourceY={sourceY}
targetX={targetX}
targetY={targetY}
sourcePosition={sourcePosition}
targetPosition={targetPosition}
/>
);
}
Expand Down
13 changes: 8 additions & 5 deletions src/NodeRenderer/HandleTypes/BaseHandle.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,26 @@ function onMouseDown(evt, { nodeId, setSourceId, setPosition, onConnect, isTarg
}

const BaseHandle = memo(({
source, target, nodeId, onConnect,
type, nodeId, onConnect, position,
setSourceId, setPosition, className,
id = false, ...rest
}) => {
const isTarget = type === 'target';
const handleClasses = cx(
'react-graph__handle',
className,
{ source, target }
position,
{ source: !isTarget, target: isTarget }
);

const handleId = id ? `${nodeId}__${id}` : nodeId;
const nodeIdWithHandleId = id ? `${nodeId}__${id}` : nodeId;

return (
<div
data-nodeid={handleId}
data-nodeid={nodeIdWithHandleId}
data-handlepos={position}
className={handleClasses}
onMouseDown={evt => onMouseDown(evt, { nodeId: handleId, setSourceId, setPosition, onConnect, isTarget: target })}
onMouseDown={evt => onMouseDown(evt, { nodeId: nodeIdWithHandleId, setSourceId, setPosition, onConnect, isTarget })}
{...rest}
/>
);
Expand Down
39 changes: 39 additions & 0 deletions src/NodeRenderer/HandleTypes/Handle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React, { memo, useContext } from 'react';
import PropTypes from 'prop-types';
import { useStoreActions, useStoreState } from 'easy-peasy';

import BaseHandle from './BaseHandle';
import NodeIdContext from '../NodeIdContext'

const Handle = memo((props) => {
const nodeId = useContext(NodeIdContext);
const { setPosition, setSourceId } = useStoreActions(a => ({
setPosition: a.setConnectionPosition,
setSourceId: a.setConnectionSourceId
}));
const onConnect = useStoreState(s => s.onConnect);

return (
<BaseHandle
nodeId={nodeId}
setPosition={setPosition}
setSourceId={setSourceId}
onConnect={onConnect}
{...props}
/>
);
});

Handle.displayName = 'Handle';

Handle.propTypes = {
type: PropTypes.oneOf(['source', 'target']),
position: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
};

Handle.defaultProps = {
type: 'source',
position: 'top'
};

export default Handle;
3 changes: 2 additions & 1 deletion src/NodeRenderer/HandleTypes/SourceHandle.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ const SourceHandle = memo((props) => {

return (
<BaseHandle
source
type="source"
position="bottom"
nodeId={nodeId}
setPosition={setPosition}
setSourceId={setSourceId}
Expand Down
3 changes: 2 additions & 1 deletion src/NodeRenderer/HandleTypes/TargetHandle.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ const TargetHandle = memo((props) => {

return (
<BaseHandle
target
type="target"
position="top"
nodeId={nodeId}
setPosition={setPosition}
setSourceId={setSourceId}
Expand Down
7 changes: 3 additions & 4 deletions src/NodeRenderer/NodeTypes/DefaultNode.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';

import TargetHandle from '../HandleTypes/TargetHandle';
import SourceHandle from '../HandleTypes/SourceHandle';
import Handle from '../HandleTypes/Handle';

const nodeStyles = {
background: '#ff6060',
Expand All @@ -12,8 +11,8 @@ const nodeStyles = {

export default ({ data, style }) => (
<div style={{ ...nodeStyles, ...style }}>
<TargetHandle />
<Handle type="target" position="top" />
{data.label}
<SourceHandle />
<Handle type="source" position="bottom" />
</div>
);
4 changes: 2 additions & 2 deletions src/NodeRenderer/NodeTypes/InputNode.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';

import SourceHandle from '../HandleTypes/SourceHandle';
import Handle from '../HandleTypes/Handle';

const nodeStyles = {
background: '#9999ff',
Expand All @@ -15,6 +15,6 @@ export default ({ data, style }) => (
className="react-graph__node-inner"
>
{data.label}
<SourceHandle />
<Handle type="source" position="bottom" />
</div>
);
4 changes: 2 additions & 2 deletions src/NodeRenderer/NodeTypes/OutputNode.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';

import TargetHandle from '../HandleTypes/TargetHandle';
import Handle from '../HandleTypes/TargetHandle';

const nodeStyles = {
background: '#55dd99',
Expand All @@ -11,7 +11,7 @@ const nodeStyles = {

export default ({ data, style }) => (
<div style={{ ...nodeStyles, ...style }}>
<TargetHandle />
<Handle type="target" position="top" />
{data.label}
</div>
);
2 changes: 2 additions & 0 deletions src/NodeRenderer/NodeTypes/wrapNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const getHandleBounds = (sel, nodeElement, parentBounds, k) => {
const bounds = handle.getBoundingClientRect();
const dimensions = getDimensions(handle);
const nodeIdAttr = handle.getAttribute('data-nodeid');
const handlePosition = handle.getAttribute('data-handlepos');
const nodeIdSplitted = nodeIdAttr.split('__');

let handleId = null;
Expand All @@ -33,6 +34,7 @@ const getHandleBounds = (sel, nodeElement, parentBounds, k) => {

return {
id: handleId,
position: handlePosition,
x: (bounds.x - parentBounds.x) * (1 / k),
y: (bounds.y - parentBounds.y) * (1 / k),
...dimensions
Expand Down
3 changes: 1 addition & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import ReactGraph from './ReactGraph';

export default ReactGraph;

export { default as SourceHandle } from './NodeRenderer/HandleTypes/SourceHandle';
export { default as TargetHandle } from './NodeRenderer/HandleTypes/TargetHandle';
export { default as Handle } from './NodeRenderer/HandleTypes/Handle';
export { default as MiniMap } from './Plugins/MiniMap';

export {
Expand Down

0 comments on commit 54af33a

Please sign in to comment.