@jalez/react-flow-smart-edge
Version:
Smart edge routing for @xyflow/react v12+ (maintained fork of @tisoap/react-flow-smart-edge)
137 lines (118 loc) • 3.02 kB
text/typescript
import {
createGrid,
getBoundingBoxes,
gridToGraphPoint,
pathfindingAStarDiagonal,
svgDrawSmoothLinePath,
toInteger
} from '../functions'
import type {
PointInfo,
PathFindingFunction,
SVGDrawFunction
} from '../functions'
import type { Node, EdgeProps } from '@xyflow/react'
export type EdgeParams = Pick<
EdgeProps,
| 'sourceX'
| 'sourceY'
| 'targetX'
| 'targetY'
| 'sourcePosition'
| 'targetPosition'
>
export type GetSmartEdgeOptions = {
gridRatio?: number
nodePadding?: number
drawEdge?: SVGDrawFunction
generatePath?: PathFindingFunction
}
export type GetSmartEdgeParams = EdgeParams & {
options?: GetSmartEdgeOptions
nodes: Node[]
}
export type GetSmartEdgeReturn = {
svgPathString: string
edgeCenterX: number
edgeCenterY: number
}
export const getSmartEdge = ({
options = {},
nodes = [],
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition
}: GetSmartEdgeParams): GetSmartEdgeReturn | null => {
try {
const {
drawEdge = svgDrawSmoothLinePath,
generatePath = pathfindingAStarDiagonal
} = options
let { gridRatio = 10, nodePadding = 10 } = options
gridRatio = toInteger(gridRatio)
nodePadding = toInteger(nodePadding)
// We use the node's information to generate bounding boxes for them
// and the graph
const { graphBox, nodeBoxes } = getBoundingBoxes(
nodes,
nodePadding,
gridRatio
)
const source: PointInfo = {
x: sourceX,
y: sourceY,
position: sourcePosition
}
const target: PointInfo = {
x: targetX,
y: targetY,
position: targetPosition
}
// With this information, we can create a 2D grid representation of
// our graph, that tells us where in the graph there is a "free" space or not
const { grid, start, end } = createGrid(
graphBox,
nodeBoxes,
source,
target,
gridRatio
)
// We then can use the grid representation to do pathfinding
const generatePathResult = generatePath(grid, start, end)
if (generatePathResult === null) {
return null
}
const { fullPath, smoothedPath } = generatePathResult
// Here we convert the grid path to a sequence of graph coordinates.
const graphPath = smoothedPath.map((gridPoint) => {
const [x, y] = gridPoint
const graphPoint = gridToGraphPoint(
{ x, y },
graphBox.xMin,
graphBox.yMin,
gridRatio
)
return [graphPoint.x, graphPoint.y]
})
// Finally, we can use the graph path to draw the edge
const svgPathString = drawEdge(source, target, graphPath)
// Compute the edge's middle point using the full path, so users can use
// it to position their custom labels
const index = Math.floor(fullPath.length / 2)
const middlePoint = fullPath[index]
const [middleX, middleY] = middlePoint
const { x: edgeCenterX, y: edgeCenterY } = gridToGraphPoint(
{ x: middleX, y: middleY },
graphBox.xMin,
graphBox.yMin,
gridRatio
)
return { svgPathString, edgeCenterX, edgeCenterY }
} catch {
return null
}
}
export type GetSmartEdgeFunction = typeof getSmartEdge