diff --git a/README.md b/README.md index 008863bf..c2ab368a 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,7 @@ All props are detailed below. | `nodes` | `Array` | `true` | Array of graph nodes. | | `edges` | `Array` | `true` | Array of graph edges. | | `allowMultiselect` | `boolean` | `false` | Use Ctrl-Shift-LeftMouse to draw a multiple selection box | +| `allowLoopbackEdge` | `boolean` | `false` | When set to `true` edges that have the same start and end node are allowed to be set by the user.| | `selected` | `object` | `true` | The currently selected graph entity. | | `selectedNodes` | `Array` | `false` | If allowMultiselect is true, this should be the currently selected array of nodes. | | `selectedEdges` | `Array` | `true` | If allowMultiselect is true, this should be the currently selected array of edges. | diff --git a/src/components/graph-view.js b/src/components/graph-view.js index 3e75f492..6f79f452 100644 --- a/src/components/graph-view.js +++ b/src/components/graph-view.js @@ -89,6 +89,7 @@ class GraphView extends React.Component { zoomDur: 750, rotateEdgeHandle: true, centerNodeOnMove: true, + allowLoopbackEdge: false, }; static getDerivedStateFromProps( @@ -1023,7 +1024,6 @@ class GraphView extends React.Component { if ( edgesMap && - hoveredNodeData !== edgeEndNode && canCreateEdge && canCreateEdge(hoveredNodeData, edgeEndNode) && !edgesMap[mapId1] && @@ -1089,9 +1089,14 @@ class GraphView extends React.Component { handleNodeMouseEnter = (event: any, data: any) => { const { draggingEdge, hoveredNodeData } = this.state; + const { allowLoopbackEdge } = this.props; // hovered is false when creating edges - if (hoveredNodeData && data !== hoveredNodeData && draggingEdge) { + if ( + hoveredNodeData && + (data !== hoveredNodeData || allowLoopbackEdge) && + draggingEdge + ) { this.setState({ edgeEndNode: data, }); diff --git a/src/examples/graph.js b/src/examples/graph.js index 3945d280..44e7d482 100644 --- a/src/examples/graph.js +++ b/src/examples/graph.js @@ -408,14 +408,11 @@ class Graph extends React.Component { type, }; - // Only add the edge when the source node is not the same as the target - if (viewEdge.source !== viewEdge.target) { - graph.edges = [...graph.edges, viewEdge]; - this.setState({ - graph, - selected: viewEdge, - }); - } + graph.edges = [...graph.edges, viewEdge]; + this.setState({ + graph, + selected: viewEdge, + }); }; // Called when an edge is reattached to a different target. @@ -664,6 +661,7 @@ class Graph extends React.Component { onCopySelected={this.onCopySelected} onPasteSelected={this.onPasteSelected} layoutEngineType={this.state.layoutEngineType} + allowLoopbackEdge={true} /> diff --git a/src/helpers/edge-helpers.js b/src/helpers/edge-helpers.js index 3deb5cec..2132f4c8 100644 --- a/src/helpers/edge-helpers.js +++ b/src/helpers/edge-helpers.js @@ -222,6 +222,35 @@ export function getPathDescription( viewWrapperElem ); + if (sourceNode === targetNode) { + const arrowOffset = nodeSize * 0.8; + + const returningLinePoints = [ + { + x: srcX - srcOff.xOff + nodeSize / 2, + y: srcY - srcOff.yOff, + }, + { + x: srcX - srcOff.xOff + arrowOffset, + y: srcY - srcOff.yOff, + }, + { + x: srcX - srcOff.xOff + arrowOffset, + y: srcY - srcOff.yOff - arrowOffset, + }, + { + x: srcX - srcOff.xOff, + y: srcY - srcOff.yOff - arrowOffset, + }, + { + x: srcX - trgOff.xOff, + y: srcY - trgOff.yOff - nodeSize / 2, + }, + ]; + + return getLine(returningLinePoints); + } + const linePoints = [ { x: srcX - srcOff.xOff, diff --git a/src/styles/main.scss b/src/styles/main.scss index d85b562a..77371494 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss @@ -76,6 +76,7 @@ $button-size: 31px; } .edge { + fill: transparent; color: $light-color; stroke: $primary-color; stroke-width: 2px;