feat(lib): export edge utils #418
This commit is contained in:
@@ -15,6 +15,7 @@ React Flow is a library for building node-based graphs. You can easily implement
|
||||
- [Edges](#edges)
|
||||
- [Options](#options)
|
||||
- [Edge Types & Custom Edges](#edge-types--custom-edges)
|
||||
- [Edge Utils](#edge-utils)
|
||||
- [Components](#components)
|
||||
- [Background](#background)
|
||||
- [Minimap](#minimap)
|
||||
@@ -289,7 +290,7 @@ If you wanted to display this edge, you would need a node with id = 1 (source no
|
||||
|
||||
You can find an example with different edges in the [edges example](https://reactflow.dev/edges).
|
||||
|
||||
### Edge Types & Custom Edges
|
||||
## Edge Types & Custom Edges
|
||||
|
||||
The basic edge types are `default` (bezier), `straight`, `step` and `smoothedge`. The default `edgeTypes` object looks like this:
|
||||
|
||||
@@ -315,6 +316,49 @@ Now you could use the new type `special` for an edge.
|
||||
The `straight`, `default` and `step` types would still be available unless you overwrote one of them.
|
||||
There is an implementation of a custom edge in the [edges example](/example/src/Edges/index.js).
|
||||
|
||||
## Edge Utils
|
||||
|
||||
There are several utils that help you to create a custom edge. They are used in the [custom edge](/example/src/Edges/CustomEdge.js) example.
|
||||
|
||||
### getBezierPath
|
||||
|
||||
Returns the path of a bezier edge.
|
||||
|
||||
`getBezierPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition = Position.Bottom,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition = Position.Top,
|
||||
}: GetBezierPathParams): string`
|
||||
|
||||
### getSmoothStepPath
|
||||
|
||||
Returns the path of a smooth step edge. You can set `borderRadius` = `0` to get a step edge path.
|
||||
|
||||
`getSmoothStepPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition = Position.Bottom,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition = Position.Top,
|
||||
borderRadius = 5,
|
||||
}: GetSmoothStepPathParams): string`
|
||||
|
||||
### getEdgeCenter
|
||||
|
||||
Returns the center poostion `[centerX, centerY]` of the edge.
|
||||
|
||||
`getEdgeCenter({ sourceX, sourceY, targetX, targetY }: GetCenterParams): [number, number, number, number]`
|
||||
|
||||
### getMarkerEnd
|
||||
|
||||
Returns the marker end url for displaying the arrow head.
|
||||
|
||||
`getMarkerEnd(arrowHeadType?: ArrowHeadType, markerEndId?: string): string`
|
||||
|
||||
# Components
|
||||
|
||||
## Background
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
import React from 'react';
|
||||
import { getBezierPath, getMarkerEnd } from 'react-flow-renderer';
|
||||
|
||||
export default function CustomEdge({
|
||||
id,
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
sourcePosition,
|
||||
targetPosition,
|
||||
style = {},
|
||||
data,
|
||||
arrowHeadType,
|
||||
markerEndId,
|
||||
}) {
|
||||
const edgePath = getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition });
|
||||
const markerEnd = getMarkerEnd(arrowHeadType, markerEndId);
|
||||
|
||||
export default function CustomEdge({ id, sourceX, sourceY, targetX, targetY, style = {}, data }) {
|
||||
return (
|
||||
<>
|
||||
<path
|
||||
id={id}
|
||||
style={style}
|
||||
className="react-flow__edge-path"
|
||||
d={`M ${sourceX},${sourceY}L ${targetX},${targetY}`}
|
||||
/>
|
||||
<path id={id} style={style} className="react-flow__edge-path" d={edgePath} markerEnd={markerEnd} />
|
||||
<text>
|
||||
<textPath href={`#${id}`} style={{ fontSize: '12px' }} startOffset="50%" textAnchor="middle">
|
||||
{data.text}
|
||||
|
||||
@@ -41,7 +41,7 @@ const initialElements = [
|
||||
labelBgStyle: { fill: '#eee', fillOpacity: 0.7 },
|
||||
arrowHeadType: 'arrowclosed',
|
||||
},
|
||||
{ id: 'e5-8', source: '5', target: '8', type: 'custom', data: { text: 'custom edge' } },
|
||||
{ id: 'e5-8', source: '5', target: '8', type: 'custom', data: { text: 'custom edge' }, arrowHeadType: 'arrowclosed' },
|
||||
];
|
||||
|
||||
const edgeTypes = {
|
||||
|
||||
@@ -59,18 +59,12 @@ export default ({
|
||||
const targetY = (connectionPositionY - transform[1]) * (1 / transform[2]);
|
||||
|
||||
let dAttr: string = '';
|
||||
const xOffset = Math.abs(targetX - sourceX) / 2;
|
||||
const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;
|
||||
|
||||
const yOffset = Math.abs(targetY - sourceY) / 2;
|
||||
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
|
||||
const isRightOrLeft = sourceHandle?.position === Position.Left || sourceHandle?.position === Position.Right;
|
||||
const targetPosition = isRightOrLeft ? Position.Left : Position.Top;
|
||||
|
||||
if (connectionLineType === ConnectionLineType.Bezier) {
|
||||
dAttr = getBezierPath({
|
||||
centerX,
|
||||
centerY,
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition: sourceHandle?.position,
|
||||
@@ -80,10 +74,6 @@ export default ({
|
||||
});
|
||||
} else if (connectionLineType === ConnectionLineType.Step) {
|
||||
dAttr = getSmoothStepPath({
|
||||
xOffset,
|
||||
yOffset,
|
||||
centerX,
|
||||
centerY,
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition: sourceHandle?.position,
|
||||
@@ -94,10 +84,6 @@ export default ({
|
||||
});
|
||||
} else if (connectionLineType === ConnectionLineType.SmoothStep) {
|
||||
dAttr = getSmoothStepPath({
|
||||
xOffset,
|
||||
yOffset,
|
||||
centerX,
|
||||
centerY,
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition: sourceHandle?.position,
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import EdgeText from './EdgeText';
|
||||
import { getMarkerEnd } from './utils';
|
||||
|
||||
import { getMarkerEnd, getCenter } from './utils';
|
||||
import { EdgeBezierProps, Position } from '../../types';
|
||||
|
||||
interface GetBezierPathParams {
|
||||
centerX: number;
|
||||
centerY: number;
|
||||
sourceX: number;
|
||||
sourceY: number;
|
||||
sourcePosition?: Position;
|
||||
@@ -16,8 +15,6 @@ interface GetBezierPathParams {
|
||||
}
|
||||
|
||||
export function getBezierPath({
|
||||
centerX,
|
||||
centerY,
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition = Position.Bottom,
|
||||
@@ -25,10 +22,11 @@ export function getBezierPath({
|
||||
targetY,
|
||||
targetPosition = Position.Top,
|
||||
}: GetBezierPathParams): string {
|
||||
let path = `M${sourceX},${sourceY} C${sourceX},${centerY} ${targetX},${centerY} ${targetX},${targetY}`;
|
||||
|
||||
const [centerX, centerY] = getCenter({ sourceX, sourceY, targetX, targetY });
|
||||
const leftAndRight = [Position.Left, Position.Right];
|
||||
|
||||
let path = `M${sourceX},${sourceY} C${sourceX},${centerY} ${targetX},${centerY} ${targetX},${targetY}`;
|
||||
|
||||
if (leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) {
|
||||
path = `M${sourceX},${sourceY} C${centerX},${sourceY} ${centerX},${targetY} ${targetX},${targetY}`;
|
||||
} else if (leftAndRight.includes(targetPosition)) {
|
||||
@@ -56,15 +54,8 @@ export default memo(
|
||||
arrowHeadType,
|
||||
markerEndId,
|
||||
}: EdgeBezierProps) => {
|
||||
const yOffset = Math.abs(targetY - sourceY) / 2;
|
||||
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
|
||||
|
||||
const xOffset = Math.abs(targetX - sourceX) / 2;
|
||||
const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;
|
||||
|
||||
const [centerX, centerY] = getCenter({ sourceX, sourceY, targetX, targetY });
|
||||
const path = getBezierPath({
|
||||
centerX,
|
||||
centerY,
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition,
|
||||
|
||||
@@ -11,6 +11,9 @@ export default memo(({ x, y, label, labelStyle = {}, labelShowBg = true, labelBg
|
||||
useEffect(() => {
|
||||
if (edgeRef.current) {
|
||||
const textBbox = edgeRef.current.getBBox();
|
||||
|
||||
console.log(textBbox);
|
||||
|
||||
setEdgeTextBbox({
|
||||
x: textBbox.x,
|
||||
y: textBbox.y,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import EdgeText from './EdgeText';
|
||||
import { getMarkerEnd } from './utils';
|
||||
import { getMarkerEnd, getCenter } from './utils';
|
||||
import { EdgeSmoothStepProps, Position } from '../../types';
|
||||
|
||||
// These are some helper methods for drawing the round corners
|
||||
@@ -34,10 +34,6 @@ const rightTopCorner = (cornerX: number, cornerY: number, cornerSize: number): s
|
||||
`L ${cornerX - cornerSize},${cornerY}Q ${cornerX},${cornerY} ${cornerX},${cornerY + cornerSize}`;
|
||||
|
||||
interface GetSmoothStepPathParams {
|
||||
xOffset: number;
|
||||
yOffset: number;
|
||||
centerX: number;
|
||||
centerY: number;
|
||||
sourceX: number;
|
||||
sourceY: number;
|
||||
sourcePosition?: Position;
|
||||
@@ -48,10 +44,6 @@ interface GetSmoothStepPathParams {
|
||||
}
|
||||
|
||||
export function getSmoothStepPath({
|
||||
xOffset,
|
||||
yOffset,
|
||||
centerX,
|
||||
centerY,
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition = Position.Bottom,
|
||||
@@ -60,9 +52,10 @@ export function getSmoothStepPath({
|
||||
targetPosition = Position.Top,
|
||||
borderRadius = 5,
|
||||
}: GetSmoothStepPathParams): string {
|
||||
const [centerX, centerY, offsetX, offsetY] = getCenter({ sourceX, sourceY, targetX, targetY });
|
||||
const cornerWidth = Math.min(borderRadius, Math.abs(targetX - sourceX));
|
||||
const cornerHeight = Math.min(borderRadius, Math.abs(targetY - sourceY));
|
||||
const cornerSize = Math.min(cornerWidth, cornerHeight, xOffset, yOffset);
|
||||
const cornerSize = Math.min(cornerWidth, cornerHeight, offsetX, offsetY);
|
||||
|
||||
const leftAndRight = [Position.Left, Position.Right];
|
||||
|
||||
@@ -145,17 +138,9 @@ export default memo(
|
||||
markerEndId,
|
||||
borderRadius = 5,
|
||||
}: EdgeSmoothStepProps) => {
|
||||
const yOffset = Math.abs(targetY - sourceY) / 2;
|
||||
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
|
||||
|
||||
const xOffset = Math.abs(targetX - sourceX) / 2;
|
||||
const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;
|
||||
const [centerX, centerY] = getCenter({ sourceX, sourceY, targetX, targetY });
|
||||
|
||||
const path = getSmoothStepPath({
|
||||
xOffset,
|
||||
yOffset,
|
||||
centerX,
|
||||
centerY,
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition,
|
||||
|
||||
@@ -7,3 +7,25 @@ export const getMarkerEnd = (arrowHeadType?: ArrowHeadType, markerEndId?: string
|
||||
|
||||
return typeof arrowHeadType !== 'undefined' ? `url(#react-flow__${arrowHeadType})` : 'none';
|
||||
};
|
||||
|
||||
export interface GetCenterParams {
|
||||
sourceX: number;
|
||||
sourceY: number;
|
||||
targetX: number;
|
||||
targetY: number;
|
||||
}
|
||||
|
||||
export const getCenter = ({
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
}: GetCenterParams): [number, number, number, number] => {
|
||||
const xOffset = Math.abs(targetX - sourceX) / 2;
|
||||
const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;
|
||||
|
||||
const yOffset = Math.abs(targetY - sourceY) / 2;
|
||||
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
|
||||
|
||||
return [centerX, centerY, xOffset, yOffset];
|
||||
};
|
||||
|
||||
@@ -4,6 +4,9 @@ export default ReactFlow;
|
||||
|
||||
export { default as Handle } from './components/Handle';
|
||||
export { default as EdgeText } from './components/Edges/EdgeText';
|
||||
export { getBezierPath } from './components/Edges/BezierEdge';
|
||||
export { getSmoothStepPath } from './components/Edges/SmoothStepEdge';
|
||||
export { getMarkerEnd, getCenter as getEdgeCenter } from './components/Edges/utils';
|
||||
|
||||
export { isNode, isEdge, removeElements, addEdge, getOutgoers } from './utils/graph';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user