diff --git a/README.md b/README.md
index eb2c2341..e62481f3 100644
--- a/README.md
+++ b/README.md
@@ -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)
@@ -282,6 +283,7 @@ If you wanted to display this edge, you would need a node with id = 1 (source no
- `labelStyle`: css properties for the text
- `labelShowBg`: boolean - default: `true`
- `labelBgStyle`: css properties for the text background
+- `labelBgPadding`: [number, number] background rectangle padding - default: `[2, 4]`
- `arrowHeadType`: 'arrow' or 'arrowclosed' - defines the arrowhead of the edge
- `markerEndId`: custom marker end url - if this is used `arrowHeadType` gets ignored
- `isHidden`: if `true`, the edge will not be rendered
@@ -289,7 +291,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 +317,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
diff --git a/example/src/Edges/CustomEdge.js b/example/src/Edges/CustomEdge.js
index 0a26c7bd..1e351b75 100644
--- a/example/src/Edges/CustomEdge.js
+++ b/example/src/Edges/CustomEdge.js
@@ -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 (
<>
-
+
{data.text}
diff --git a/example/src/Edges/index.js b/example/src/Edges/index.js
index 10657aec..55a08d47 100644
--- a/example/src/Edges/index.js
+++ b/example/src/Edges/index.js
@@ -19,7 +19,7 @@ const initialElements = [
{ id: '6', type: 'output', data: { label: 'Output 6' }, position: { x: 50, y: 550 } },
{ id: '7', type: 'output', data: { label: 'Output 7' }, position: { x: 250, y: 550 } },
{ id: '8', type: 'output', data: { label: 'Output 8' }, position: { x: 525, y: 600 } },
- { id: 'e1-2', source: '1', target: '2', label: 'bezier edge (default)' },
+ { id: 'e1-2', source: '1', target: '2', label: 'bezier edge (default)', className: 'normal-edge' },
{ id: 'e2-2a', source: '2', target: '2a', type: 'smoothstep', label: 'smoothstep edge' },
{ id: 'e2-3', source: '2', target: '3', type: 'step', label: 'step edge' },
{ id: 'e3-4', source: '3', target: '4', type: 'straight', label: 'straight edge' },
@@ -38,10 +38,11 @@ const initialElements = [
source: '5',
target: '7',
label: 'label with styled bg',
- labelBgStyle: { fill: '#eee', fillOpacity: 0.7 },
+ labelBgStyle: { fill: '#555', color: '#fff', fillOpacity: 0.7 },
arrowHeadType: 'arrowclosed',
+ labelBgPadding: [8, 4],
},
- { 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 = {
diff --git a/src/components/ConnectionLine/index.tsx b/src/components/ConnectionLine/index.tsx
index 00cc037b..4d010e2f 100644
--- a/src/components/ConnectionLine/index.tsx
+++ b/src/components/ConnectionLine/index.tsx
@@ -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,
diff --git a/src/components/Edges/BezierEdge.tsx b/src/components/Edges/BezierEdge.tsx
index 9b27b6fc..59b93399 100644
--- a/src/components/Edges/BezierEdge.tsx
+++ b/src/components/Edges/BezierEdge.tsx
@@ -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)) {
@@ -55,16 +53,10 @@ export default memo(
style,
arrowHeadType,
markerEndId,
+ labelBgPadding,
}: 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,
@@ -81,6 +73,7 @@ export default memo(
labelStyle={labelStyle}
labelShowBg={labelShowBg}
labelBgStyle={labelBgStyle}
+ labelBgPadding={labelBgPadding}
/>
) : null;
diff --git a/src/components/Edges/EdgeText.tsx b/src/components/Edges/EdgeText.tsx
index 73b94871..e10e84d2 100644
--- a/src/components/Edges/EdgeText.tsx
+++ b/src/components/Edges/EdgeText.tsx
@@ -2,43 +2,44 @@ import React, { memo, useRef, useState, useEffect } from 'react';
import { EdgeTextProps, Rect } from '../../types';
-const textBgPadding = 2;
+export default memo(
+ ({ x, y, label, labelStyle = {}, labelShowBg = true, labelBgStyle = {}, labelBgPadding = [2, 4] }: EdgeTextProps) => {
+ const edgeRef = useRef(null);
+ const [edgeTextBbox, setEdgeTextBbox] = useState({ x: 0, y: 0, width: 0, height: 0 });
-export default memo(({ x, y, label, labelStyle = {}, labelShowBg = true, labelBgStyle = {} }: EdgeTextProps) => {
- const edgeRef = useRef(null);
- const [edgeTextBbox, setEdgeTextBbox] = useState({ x: 0, y: 0, width: 0, height: 0 });
+ useEffect(() => {
+ if (edgeRef.current) {
+ const textBbox = edgeRef.current.getBBox();
- useEffect(() => {
- if (edgeRef.current) {
- const textBbox = edgeRef.current.getBBox();
- setEdgeTextBbox({
- x: textBbox.x,
- y: textBbox.y,
- width: textBbox.width,
- height: textBbox.height,
- });
+ setEdgeTextBbox({
+ x: textBbox.x,
+ y: textBbox.y,
+ width: textBbox.width,
+ height: textBbox.height,
+ });
+ }
+ }, []);
+
+ if (typeof label === 'undefined' || !label) {
+ return null;
}
- }, []);
- if (typeof label === 'undefined' || !label) {
- return null;
+ return (
+
+ {labelShowBg && (
+
+ )}
+
+ {label}
+
+
+ );
}
-
- return (
-
- {labelShowBg && (
-
- )}
-
- {label}
-
-
- );
-});
+);
diff --git a/src/components/Edges/SmoothStepEdge.tsx b/src/components/Edges/SmoothStepEdge.tsx
index f0dc1996..989ccaf3 100644
--- a/src/components/Edges/SmoothStepEdge.tsx
+++ b/src/components/Edges/SmoothStepEdge.tsx
@@ -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];
@@ -138,6 +131,7 @@ export default memo(
labelStyle,
labelShowBg,
labelBgStyle,
+ labelBgPadding,
style,
sourcePosition = Position.Bottom,
targetPosition = Position.Top,
@@ -145,17 +139,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,
@@ -175,6 +161,7 @@ export default memo(
labelStyle={labelStyle}
labelShowBg={labelShowBg}
labelBgStyle={labelBgStyle}
+ labelBgPadding={labelBgPadding}
/>
) : null;
diff --git a/src/components/Edges/StraightEdge.tsx b/src/components/Edges/StraightEdge.tsx
index 8721cf44..17ebcb8b 100644
--- a/src/components/Edges/StraightEdge.tsx
+++ b/src/components/Edges/StraightEdge.tsx
@@ -14,6 +14,7 @@ export default memo(
labelStyle,
labelShowBg,
labelBgStyle,
+ labelBgPadding,
style,
arrowHeadType,
markerEndId,
@@ -33,6 +34,7 @@ export default memo(
labelStyle={labelStyle}
labelShowBg={labelShowBg}
labelBgStyle={labelBgStyle}
+ labelBgPadding={labelBgPadding}
/>
) : null;
diff --git a/src/components/Edges/utils.ts b/src/components/Edges/utils.ts
index a25f135c..cc217446 100644
--- a/src/components/Edges/utils.ts
+++ b/src/components/Edges/utils.ts
@@ -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];
+};
diff --git a/src/components/Edges/wrapEdge.tsx b/src/components/Edges/wrapEdge.tsx
index d8c67704..5fd3b728 100644
--- a/src/components/Edges/wrapEdge.tsx
+++ b/src/components/Edges/wrapEdge.tsx
@@ -12,7 +12,8 @@ interface EdgeWrapperProps {
label?: string;
labelStyle?: CSSProperties;
labelShowBg?: boolean;
- labelBgStyle: CSSProperties;
+ labelBgStyle?: CSSProperties;
+ labelBgPadding?: [number, number];
className?: string;
onClick?: (event: React.MouseEvent, edge: Edge) => void;
animated?: boolean;
@@ -36,6 +37,7 @@ export default (EdgeComponent: ComponentType) => {
labelStyle,
labelShowBg,
labelBgStyle,
+ labelBgPadding,
className,
isHidden,
data,
@@ -77,6 +79,7 @@ export default (EdgeComponent: ComponentType) => {
labelStyle={labelStyle}
labelShowBg={labelShowBg}
labelBgStyle={labelBgStyle}
+ labelBgPadding={labelBgPadding}
data={data}
{...rest}
/>
diff --git a/src/container/EdgeRenderer/index.tsx b/src/container/EdgeRenderer/index.tsx
index ab57b2d8..7b6efe86 100644
--- a/src/container/EdgeRenderer/index.tsx
+++ b/src/container/EdgeRenderer/index.tsx
@@ -161,6 +161,7 @@ function renderEdge(
{
edgeProps.labelShowBg = propEdge.labelShowBg;
}
+ if (typeof propEdge.labelBgPadding !== 'undefined') {
+ edgeProps.labelBgPadding = propEdge.labelBgPadding;
+ }
+
if (typeof labelBgStyle !== 'undefined') {
edgeProps.labelBgStyle = labelBgStyle;
}
diff --git a/src/index.ts b/src/index.ts
index e53cfcbd..9bce1399 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -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';
diff --git a/src/types/index.ts b/src/types/index.ts
index ad5ab4ad..a84d56fc 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -62,6 +62,7 @@ export interface Edge {
labelStyle?: CSSProperties;
labelShowBg?: boolean;
labelBgStyle?: CSSProperties;
+ labelBgPadding?: [number, number];
style?: CSSProperties;
animated?: boolean;
arrowHeadType?: ArrowHeadType;
@@ -96,6 +97,7 @@ export interface EdgeProps {
labelStyle?: CSSProperties;
labelShowBg?: boolean;
labelBgStyle?: CSSProperties;
+ labelBgPadding?: [number, number];
style?: CSSProperties;
arrowHeadType?: ArrowHeadType;
markerEndId?: string;
@@ -235,6 +237,7 @@ export interface EdgeCompProps {
labelStyle?: CSSProperties;
labelShowBg?: boolean;
labelBgStyle?: CSSProperties;
+ labelBgPadding?: [number, number];
onClick?: (event: ReactMouseEvent, edge: Edge) => void;
animated?: boolean;
selected?: boolean;
@@ -248,6 +251,7 @@ export interface EdgeTextProps {
labelStyle?: CSSProperties;
labelShowBg?: boolean;
labelBgStyle?: CSSProperties;
+ labelBgPadding?: [number, number];
}
export type NodePosUpdate = {