Custom Edges for React Flow that never intersect with other nodes, using pathfinding.
With npm
:
npm install @tisoap/react-flow-smart-edge
With yarn
:
yarn add @tisoap/react-flow-smart-edge
This package is only compatible with version 11 or newer of React Flow Edge.
Like this project and want to show your support? Buy me a coffee:
Really like this project? Sponsor me on GitHub:
This package ships with the following Smart Edges components:
SmartBezierEdge
: A smart equivalent to React Flow's BezierEdgeSmartStraightEdge
: A smart equivalent to React Flow's StraightEdgeSmartStepEdge
: A smart equivalent to React Flow's StepEdge
Each one can be imported individually as a named export.
import React from 'react'
import { ReactFlow } from 'reactflow'
import { SmartBezierEdge } from '@tisoap/react-flow-smart-edge'
import 'reactflow/dist/style.css'
const nodes = [
{
id: '1',
data: { label: 'Node 1' },
position: { x: 300, y: 100 }
},
{
id: '2',
data: { label: 'Node 2' },
position: { x: 300, y: 200 }
}
]
const edges = [
{
id: 'e21',
source: '2',
target: '1',
type: 'smart'
}
]
// You can give any name to your edge types
// https://reactflow.dev/docs/api/edges/custom-edges/
const edgeTypes = {
smart: SmartBezierEdge
}
export const Graph = (props) => {
const { children, ...rest } = props
return (
<ReactFlow
defaultNodes={nodes}
defaultEdges={edges}
edgeTypes={edgeTypes}
{...rest}
>
{children}
</ReactFlow>
)
}
All smart edges will take the exact same options as a React Flow Edge.
You can have more control over how the edge is rerendered by creating a custom edge and using the provided getSmartEdge
function. It takes an object with the following keys:
sourcePosition
,targetPosition
,sourceX
,sourceY
,targetX
andtargetY
: The same values your custom edge will take as propsnodes
: An array containing all graph nodes, you can get it from theuseNodes
hook
Just like you can use getBezierPath
from reactflow
to create a custom edge with a button, you can do the same with getSmartEdge
:
import React from 'react'
import { useNodes, BezierEdge } from 'reactflow'
import { getSmartEdge } from '@tisoap/react-flow-smart-edge'
const foreignObjectSize = 200
export function SmartEdgeWithButtonLabel(props) {
const {
id,
sourcePosition,
targetPosition,
sourceX,
sourceY,
targetX,
targetY,
style,
markerStart,
markerEnd
} = props
const nodes = useNodes()
const getSmartEdgeResponse = getSmartEdge({
sourcePosition,
targetPosition,
sourceX,
sourceY,
targetX,
targetY,
nodes
})
// If the value returned is null, it means "getSmartEdge" was unable to find
// a valid path, and you should do something else instead
if (getSmartEdgeResponse === null) {
return <BezierEdge {...props} />
}
const { edgeCenterX, edgeCenterY, svgPathString } = getSmartEdgeResponse
return (
<>
<path
style={style}
className='react-flow__edge-path'
d={svgPathString}
markerEnd={markerEnd}
markerStart={markerStart}
/>
<foreignObject
width={foreignObjectSize}
height={foreignObjectSize}
x={edgeCenterX - foreignObjectSize / 2}
y={edgeCenterY - foreignObjectSize / 2}
requiredExtensions='http://www.w3.org/1999/xhtml'
>
<button
onClick={(event) => {
event.stopPropagation()
alert(`remove ${id}`)
}}
>
X
</button>
</foreignObject>
</>
)
}
The getSmartEdge
function also accepts an optional object options
, which allows you to configure aspects of the path-finding algorithm. You may use it like so:
const myOptions = {
// your configuration goes here
nodePadding: 20,
gridRatio: 15
}
// ...
const getSmartEdgeResponse = getSmartEdge({
sourcePosition,
targetPosition,
sourceX,
sourceY,
targetX,
targetY,
nodes,
// Pass down options in the getSmartEdge object
options: myOptions
})
The options
object accepts the following keys (they're all optional):
nodePadding
: How many pixels of padding are added around nodes, or by how much should the edge avoid the walls of a node. Default10
, minimum2
.gridRatio
: The size in pixels of each square grid cell used for path-finding. Smaller values for a more accurate path, bigger for faster path-finding. Default10
, minimum2
.drawEdge
: Allows you to change the function responsible to draw the SVG line, by default it's the same used bySmartBezierEdge
(more below)generatePath
: Allows you to change the function for the path-finding, by default it's the same used bySmartBezierEdge
(more below)
With the drawEdge
option, you can change the function used to generate the final SVG path string, used to draw the line. By default it's the svgDrawSmoothLinePath
function (same as used by the SmartBezierEdge
), but the package also includes svgDrawStraightLinePath
(same as used by the SmartStraightEdge
and SmartStepEdge
), or you can provide your own.
import {
getSmartEdge,
// Available built-in SVG draw functions
svgDrawSmoothLinePath,
svgDrawStraightLinePath
} from '@tisoap/react-flow-smart-edge'
// Using provided SVG draw functions:
const result = getSmartEdge({
// ...
options: {
drawEdge: svgDrawSmoothLinePath
}
})
// ...or using your own custom function
const result = getSmartEdge({
// ...
options: {
drawEdge: (source, target, path) => {
// your code goes here
// ...
return svgPath
}
}
})
The function you provided must comply with this signature:
type SVGDrawFunction = (
source: XYPosition, // The starting {x, y} point
target: XYPosition, // The ending {x, y} point
path: number[][] // The sequence of points [x, y] the line must follow
) => string // A string to be used in the "d" property of the SVG line
For inspiration on how to implement your own, you can check the drawSvgPath.ts
source code.
With the generatePath
option, you can change the function used to do Pathfinding. By default, it's the pathfindingAStarDiagonal
function (same as used by the SmartBezierEdge
), but the package also includes pathfindingAStarNoDiagonal
(used by SmartStraightEdge
) and pathfindingJumpPointNoDiagonal
(used by SmartStepEdge
), or your can provide your own. The built-in functions use the pathfinding
dependency behind the scenes.
import {
getSmartEdge,
// Available built-in pathfinding functions
pathfindingAStarDiagonal,
pathfindingAStarNoDiagonal,
pathfindingJumpPointNoDiagonal
} from '@tisoap/react-flow-smart-edge'
// Using provided pathfinding functions:
const result = getSmartEdge({
// ...
options: {
generatePath: pathfindingJumpPointNoDiagonal
}
})
// ...or using your own custom function
const result = getSmartEdge({
// ...
options: {
generatePath: (grid, start, end) => {
// your code goes here
// ...
return { fullPath, smoothedPath }
}
}
})
The function you provide must comply with this signature:
type PathFindingFunction = (
grid: Grid, // Grid representation of the graph
start: XYPosition, // The starting {x, y} point
end: XYPosition // The ending {x, y} point
) => {
fullPath: number[][] // Array of points [x, y] representing the full path with all points
smoothedPath: number[][] // Array of points [x, y] representing a smaller, compressed path
} | null // The function should return null if it was unable to do pathfinding
For inspiration on how to implement your own, you can check the generatePath.ts
source code and the pathfinding
dependency documentation.
import {
getSmartEdge,
svgDrawSmoothLinePath,
svgDrawStraightLinePath
pathfindingAStarDiagonal,
pathfindingAStarNoDiagonal,
pathfindingJumpPointNoDiagonal
} from '@tisoap/react-flow-smart-edge'
// ...
// Same as importing "SmartBezierEdge" directly
const bezierResult = getSmartEdge({
// ...
options: {
drawEdge: svgDrawSmoothLinePath,
generatePath: pathfindingAStarDiagonal,
}
})
// Same as importing "SmartStepEdge" directly
const stepResult = getSmartEdge({
// ...
options: {
drawEdge: svgDrawStraightLinePath,
generatePath: pathfindingJumpPointNoDiagonal,
}
})
// Same as importing "SmartStraightEdge" directly
const straightResult = getSmartEdge({
// ...
options: {
drawEdge: svgDrawStraightLinePath,
generatePath: pathfindingAStarNoDiagonal,
}
})
You can see live Storybook examples by visiting this page, and see their source code here.
This project is MIT licensed.