npm package: @joshrp/react-flow-smart-edge
Description: Custom React Flow Edge that never intersects with other nodes
Author: Tiso Alvarez Puccinelli
Latest version: 4.0.3

# React Flow Smart Edge

Custom Edges for React Flow that never intersect with other nodes, using pathfinding.

![TypeScript](https://shields.io/badge/TypeScript-3178C6?logo=TypeScript&logoColor=white)
![Storybook](https://img.shields.io/badge/Storybook-FF4785?logo=storybook&logoColor=white)
![ESLint](https://img.shields.io/badge/ESLint-3A33D1?logo=eslint&logoColor=white)

![Smart Edge](./.github/images/example-image.gif)

## Install

With `npm`:

```bash
npm install @joshrp/react-flow-smart-edge
```

With `yarn`:

```bash
yarn add @joshrp/react-flow-smart-edge
```

This package is only compatible with [**version 12** of React Flow Edge](https://reactflow.dev/learn/troubleshooting/migrate-to-v12).

## Support

This project was originally made by @tisoap at https://github.com/tisoap/react-flow-smart-edge. 

## Usage

This package ships with the following Smart Edges components:

- `SmartBezierEdge`: A smart equivalent to React Flow's [BezierEdge](https://reactflow.dev/docs/api/edges/edge-types/)
- `SmartStraightEdge`: A smart equivalent to React Flow's [StraightEdge](https://reactflow.dev/docs/api/edges/edge-types/)
- `SmartStepEdge`: A smart equivalent to React Flow's [StepEdge](https://reactflow.dev/docs/api/edges/edge-types/)

Each one can be imported individually as a named export.

### Example

```jsx
import React from "react";
import { ReactFlow } from "reactflow";
import { SmartBezierEdge } from "@joshrp/react-flow-smart-edge";
import "@xyflow/react/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>
  );
};
```

## Edge Options

All smart edges will take the exact same options as a [React Flow Edge](https://reactflow.dev/docs/api/edges/edge-options/).

## Custom Smart Edges

You can have more control over how the edge is rerendered by creating a [custom edge](https://reactflow.dev/docs/api/edges/custom-edges/) and using the provided `getSmartEdge` function. It takes an object with the following keys:

- `sourcePosition`, `targetPosition`, `sourceX`, `sourceY`, `targetX` and `targetY`: The same values your [custom edge](https://reactflow.dev/docs/examples/edges/custom-edge/) will take as props
- `nodes`: An array containing all graph nodes, you can get it from the [`useNodes` hook](https://reactflow.dev/docs/api/hooks/use-nodes/)

### Example

Just like you can use `getBezierPath` from `reactflow` to create a [custom edge with a button](https://reactflow.dev/docs/examples/edges/edge-with-button/), you can do the same with `getSmartEdge`:

```jsx
import React from "react";
import { useNodes, BezierEdge } from "@xyflow/react";
import { getSmartEdge } from "@joshrp/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 an Error, it means "getSmartEdge" was unable
  // to find a valid path, and you should do something else instead
  if (smartResponse instanceof Error) {
    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>
    </>
  );
}
```

## Advanced Custom Smart Edges

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:

```js
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. Default `10`, minimum `2`.
- `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. Default `10`, minimum `2`.
- `drawEdge`: Allows you to change the function responsible to draw the SVG line, by default it's the same used by `SmartBezierEdge` ([more below](#drawedge))
- `generatePath`: Allows you to change the function for the path-finding, by default it's the same used by `SmartBezierEdge` ([more below](#generatepath))

### `drawEdge`

With the `drawEdge` option, you can change the function used to generate the final [SVG path string](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths), 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.

```jsx
import {
  getSmartEdge,
  // Available built-in SVG draw functions
  svgDrawSmoothLinePath,
  svgDrawStraightLinePath,
} from "@joshrp/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:

```ts
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](https://github.com/joshrp/react-flow-smart-edge/blob/main/src/functions/drawSvgPath.ts).

### `generatePath`

With the `generatePath` option, you can change the function used to do [Pathfinding](https://en.wikipedia.org/wiki/Pathfinding). By default, it's the `pathfindingAStarDiagonal` function (same as used by the `SmartBezierEdge`), but the package also includes `pathfindingAStarNoDiagonal` (used by `SmartStraightEdge` and `SmartStepEdge`), or your can provide your own.

```jsx
import {
  getSmartEdge,
  // Available built-in pathfinding functions
  pathfindingAStarDiagonal,
  pathfindingAStarNoDiagonal,
} from "@joshrp/react-flow-smart-edge";

// Using provided pathfinding functions:
const result = getSmartEdge({
  // ...
  options: {
    generatePath: pathfindingAStarDiagonal,
  },
});

// ...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:

```ts
type PathFindingFunction = (
  grid: Grid, // Grid representation of the graph
  start: XYPosition, // The starting {x, y} point
  end: XYPosition, // The ending  {x, y} point
) => number[][]; // Array of points [x, y] representing the full path with all points
```

For inspiration on how to implement your own, you can check the [`generatePath.ts` source code](https://github.com/joshrp/react-flow-smart-edge/blob/main/src/functions/generatePath.ts).

### Advanced Examples

```jsx
import {
	getSmartEdge,
	svgDrawSmoothLinePath,
	svgDrawStraightLinePath
	pathfindingAStarDiagonal,
	pathfindingAStarNoDiagonal,
} from '@joshrp/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: pathfindingAStarNoDiagonal,
	}
})

// Same as importing "SmartStraightEdge" directly
const straightResult = getSmartEdge({
	// ...
	options: {
		drawEdge: svgDrawStraightLinePath,
		generatePath: pathfindingAStarNoDiagonal,
	}
})
```

## Storybook

You can see live Storybook examples by visiting [this page](https://joshrp.github.io/react-flow-smart-edge/), and see their source code [here](https://github.com/joshrp/react-flow-smart-edge/blob/main/src/stories/SmartEdge.stories.tsx).

## License

This project is [MIT](https://github.com/joshrp/react-flow-smart-edge/blob/main/LICENSE) licensed.