feat(markers): implement more generic api for markerEnd and markerStart handling

This commit is contained in:
Christopher Möller
2021-10-14 17:53:50 +02:00
parent eed5717821
commit d3b1148bca
18 changed files with 192 additions and 120 deletions
+2 -5
View File
@@ -1,5 +1,5 @@
import { FC } from 'react'; import { FC } from 'react';
import { EdgeProps, getBezierPath, getMarkerEnd } from 'react-flow-renderer'; import { EdgeProps, getBezierPath } from 'react-flow-renderer';
const CustomEdge: FC<EdgeProps> = ({ const CustomEdge: FC<EdgeProps> = ({
id, id,
@@ -10,15 +10,12 @@ const CustomEdge: FC<EdgeProps> = ({
sourcePosition, sourcePosition,
targetPosition, targetPosition,
data, data,
arrowHeadType,
markerEndId,
}) => { }) => {
const edgePath = getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition }); const edgePath = getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition });
const markerEnd = getMarkerEnd(arrowHeadType, markerEndId);
return ( return (
<> <>
<path id={id} className="react-flow__edge-path" d={edgePath} markerEnd={markerEnd} /> <path id={id} className="react-flow__edge-path" d={edgePath} />
<text> <text>
<textPath href={`#${id}`} style={{ fontSize: '12px' }} startOffset="50%" textAnchor="middle"> <textPath href={`#${id}`} style={{ fontSize: '12px' }} startOffset="50%" textAnchor="middle">
{data.text} {data.text}
+2 -5
View File
@@ -1,5 +1,5 @@
import { FC } from 'react'; import { FC } from 'react';
import { EdgeProps, getBezierPath, getMarkerEnd, EdgeText, getEdgeCenter } from 'react-flow-renderer'; import { EdgeProps, getBezierPath, EdgeText, getEdgeCenter } from 'react-flow-renderer';
const CustomEdge: FC<EdgeProps> = ({ const CustomEdge: FC<EdgeProps> = ({
id, id,
@@ -10,11 +10,8 @@ const CustomEdge: FC<EdgeProps> = ({
sourcePosition, sourcePosition,
targetPosition, targetPosition,
data, data,
arrowHeadType,
markerEndId,
}) => { }) => {
const edgePath = getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition }); const edgePath = getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition });
const markerEnd = getMarkerEnd(arrowHeadType, markerEndId);
const [centerX, centerY] = getEdgeCenter({ const [centerX, centerY] = getEdgeCenter({
sourceX, sourceX,
sourceY, sourceY,
@@ -24,7 +21,7 @@ const CustomEdge: FC<EdgeProps> = ({
return ( return (
<> <>
<path id={id} className="react-flow__edge-path" d={edgePath} markerEnd={markerEnd} /> <path id={id} className="react-flow__edge-path" d={edgePath} />
<EdgeText <EdgeText
x={centerX} x={centerX}
y={centerY} y={centerY}
+12 -3
View File
@@ -54,7 +54,14 @@ const initialElements: Elements = [
</> </>
), ),
labelStyle: { fill: 'red', fontWeight: 700 }, labelStyle: { fill: 'red', fontWeight: 700 },
arrowHeadType: ArrowHeadType.Arrow, style: { stroke: '#ffcc00' },
markerEnd: {
type: ArrowHeadType.ArrowClosed,
},
markerStart: {
type: ArrowHeadType.Arrow,
color: '#FFCC00',
},
}, },
{ {
id: 'e5-7', id: 'e5-7',
@@ -64,7 +71,10 @@ const initialElements: Elements = [
labelBgPadding: [8, 4], labelBgPadding: [8, 4],
labelBgBorderRadius: 4, labelBgBorderRadius: 4,
labelBgStyle: { fill: '#FFCC00', color: '#fff', fillOpacity: 0.7 }, labelBgStyle: { fill: '#FFCC00', color: '#fff', fillOpacity: 0.7 },
arrowHeadType: ArrowHeadType.ArrowClosed, markerEnd: {
type: ArrowHeadType.ArrowClosed,
color: '#FFCC00',
},
}, },
{ {
id: 'e5-8', id: 'e5-8',
@@ -72,7 +82,6 @@ const initialElements: Elements = [
target: '8', target: '8',
type: 'custom', type: 'custom',
data: { text: 'custom edge' }, data: { text: 'custom edge' },
arrowHeadType: ArrowHeadType.ArrowClosed,
}, },
{ {
id: 'e5-9', id: 'e5-9',
+3 -4
View File
@@ -1,11 +1,10 @@
import { FC, useMemo, CSSProperties } from 'react'; import { FC, useMemo, CSSProperties } from 'react';
import { EdgeProps, getMarkerEnd, useStoreState, getBezierPath } from 'react-flow-renderer'; import { EdgeProps, useStoreState, getBezierPath } from 'react-flow-renderer';
import { getEdgeParams } from './utils'; import { getEdgeParams } from './utils';
const FloatingEdge: FC<EdgeProps> = ({ id, source, target, arrowHeadType, markerEndId, style }) => { const FloatingEdge: FC<EdgeProps> = ({ id, source, target, style }) => {
const nodes = useStoreState((state) => state.nodes); const nodes = useStoreState((state) => state.nodes);
const markerEnd = getMarkerEnd(arrowHeadType, markerEndId);
const sourceNode = useMemo(() => nodes.find((n) => n.id === source), [source, nodes]); const sourceNode = useMemo(() => nodes.find((n) => n.id === source), [source, nodes]);
const targetNode = useMemo(() => nodes.find((n) => n.id === target), [target, nodes]); const targetNode = useMemo(() => nodes.find((n) => n.id === target), [target, nodes]);
@@ -27,7 +26,7 @@ const FloatingEdge: FC<EdgeProps> = ({ id, source, target, arrowHeadType, marker
return ( return (
<g className="react-flow__connection"> <g className="react-flow__connection">
<path id={id} className="react-flow__edge-path" d={d} markerEnd={markerEnd} style={style as CSSProperties} /> <path id={id} className="react-flow__edge-path" d={d} style={style as CSSProperties} />
</g> </g>
); );
}; };
+1 -3
View File
@@ -9,7 +9,6 @@ import ReactFlow, {
Elements, Elements,
Connection, Connection,
Edge, Edge,
ArrowHeadType,
} from 'react-flow-renderer'; } from 'react-flow-renderer';
import './style.css'; import './style.css';
@@ -31,8 +30,7 @@ const NodeAsHandleFlow = () => {
const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els));
const onConnect = (params: Connection | Edge) => const onConnect = (params: Connection | Edge) => setElements((els) => addEdge({ ...params, type: 'floating' }, els));
setElements((els) => addEdge({ ...params, type: 'floating', arrowHeadType: ArrowHeadType.Arrow }, els));
return ( return (
<div className="floatingedges"> <div className="floatingedges">
+1 -2
View File
@@ -13,7 +13,6 @@ import ReactFlow, {
OnLoadParams, OnLoadParams,
FlowTransform, FlowTransform,
SnapGrid, SnapGrid,
ArrowHeadType,
Connection, Connection,
Edge, Edge,
} from 'react-flow-renderer'; } from 'react-flow-renderer';
@@ -125,7 +124,7 @@ const initialElements: Elements = [
{ id: 'e1-2', source: '1', target: '2', label: 'this is an edge label' }, { id: 'e1-2', source: '1', target: '2', label: 'this is an edge label' },
{ id: 'e1-3', source: '1', target: '3' }, { id: 'e1-3', source: '1', target: '3' },
{ id: 'e3-4', source: '3', target: '4', animated: true, label: 'animated edge' }, { id: 'e3-4', source: '3', target: '4', animated: true, label: 'animated edge' },
{ id: 'e4-5', source: '4', target: '5', arrowHeadType: ArrowHeadType.Arrow, label: 'edge with arrow head' }, { id: 'e4-5', source: '4', target: '5', label: 'edge with arrow head' },
{ id: 'e5-6', source: '5', target: '6', type: 'smoothstep', label: 'smooth step edge' }, { id: 'e5-6', source: '5', target: '6', type: 'smoothstep', label: 'smooth step edge' },
{ {
id: 'e5-7', id: 'e5-7',
-13
View File
@@ -12,7 +12,6 @@ import ReactFlow, {
ConnectionLineType, ConnectionLineType,
ConnectionMode, ConnectionMode,
updateEdge, updateEdge,
ArrowHeadType,
} from 'react-flow-renderer'; } from 'react-flow-renderer';
import CustomNode from './CustomNode'; import CustomNode from './CustomNode';
@@ -69,7 +68,6 @@ const initialElements: Elements = [
sourceHandle: 'left', sourceHandle: 'left',
targetHandle: 'bottom', targetHandle: 'bottom',
type: 'default', type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
}, },
{ {
id: 'e0-1b', id: 'e0-1b',
@@ -78,7 +76,6 @@ const initialElements: Elements = [
sourceHandle: 'top', sourceHandle: 'top',
targetHandle: 'right', targetHandle: 'right',
type: 'default', type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
}, },
{ {
id: 'e0-2a', id: 'e0-2a',
@@ -87,7 +84,6 @@ const initialElements: Elements = [
sourceHandle: 'top', sourceHandle: 'top',
targetHandle: 'left', targetHandle: 'left',
type: 'default', type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
}, },
{ {
id: 'e0-2b', id: 'e0-2b',
@@ -96,7 +92,6 @@ const initialElements: Elements = [
sourceHandle: 'right', sourceHandle: 'right',
targetHandle: 'bottom', targetHandle: 'bottom',
type: 'default', type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
}, },
{ {
id: 'e0-3a', id: 'e0-3a',
@@ -105,7 +100,6 @@ const initialElements: Elements = [
sourceHandle: 'right', sourceHandle: 'right',
targetHandle: 'top', targetHandle: 'top',
type: 'default', type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
}, },
{ {
id: 'e0-3b', id: 'e0-3b',
@@ -114,7 +108,6 @@ const initialElements: Elements = [
sourceHandle: 'bottom', sourceHandle: 'bottom',
targetHandle: 'left', targetHandle: 'left',
type: 'default', type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
}, },
{ {
id: 'e0-4a', id: 'e0-4a',
@@ -123,7 +116,6 @@ const initialElements: Elements = [
sourceHandle: 'bottom', sourceHandle: 'bottom',
targetHandle: 'right', targetHandle: 'right',
type: 'default', type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
}, },
{ {
id: 'e0-4b', id: 'e0-4b',
@@ -132,7 +124,6 @@ const initialElements: Elements = [
sourceHandle: 'left', sourceHandle: 'left',
targetHandle: 'top', targetHandle: 'top',
type: 'default', type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
}, },
{ {
id: 'e0-10', id: 'e0-10',
@@ -141,7 +132,6 @@ const initialElements: Elements = [
sourceHandle: 'top', sourceHandle: 'top',
targetHandle: 'bottom', targetHandle: 'bottom',
type: 'default', type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
}, },
{ {
id: 'e0-20', id: 'e0-20',
@@ -150,7 +140,6 @@ const initialElements: Elements = [
sourceHandle: 'right', sourceHandle: 'right',
targetHandle: 'left', targetHandle: 'left',
type: 'default', type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
}, },
{ {
id: 'e0-30', id: 'e0-30',
@@ -159,7 +148,6 @@ const initialElements: Elements = [
sourceHandle: 'bottom', sourceHandle: 'bottom',
targetHandle: 'top', targetHandle: 'top',
type: 'default', type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
}, },
{ {
id: 'e0-40', id: 'e0-40',
@@ -168,7 +156,6 @@ const initialElements: Elements = [
sourceHandle: 'left', sourceHandle: 'left',
targetHandle: 'right', targetHandle: 'right',
type: 'default', type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
}, },
]; ];
+10 -6
View File
@@ -2,7 +2,7 @@ import React, { memo } from 'react';
import EdgeText from './EdgeText'; import EdgeText from './EdgeText';
import { getMarkerEnd, getCenter } from './utils'; import { getCenter } from './utils';
import { EdgeProps, Position } from '../../types'; import { EdgeProps, Position } from '../../types';
interface GetBezierPathParams { interface GetBezierPathParams {
@@ -60,8 +60,8 @@ export default memo(
labelBgPadding, labelBgPadding,
labelBgBorderRadius, labelBgBorderRadius,
style, style,
arrowHeadType, markerEnd,
markerEndId, markerStart,
}: EdgeProps) => { }: EdgeProps) => {
const [centerX, centerY] = getCenter({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition }); const [centerX, centerY] = getCenter({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition });
const path = getBezierPath({ const path = getBezierPath({
@@ -86,11 +86,15 @@ export default memo(
/> />
) : null; ) : null;
const markerEnd = getMarkerEnd(arrowHeadType, markerEndId);
return ( return (
<> <>
<path style={style} d={path} className="react-flow__edge-path" markerEnd={markerEnd} /> <path
style={style}
d={path}
className="react-flow__edge-path"
markerEnd={markerEnd}
markerStart={markerStart}
/>
{text} {text}
</> </>
); );
+12 -8
View File
@@ -1,7 +1,7 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import EdgeText from './EdgeText'; import EdgeText from './EdgeText';
import { getMarkerEnd, getCenter } from './utils'; import { getCenter } from './utils';
import { EdgeSmoothStepProps, Position } from '../../types'; import { EdgeSmoothStepProps, Position } from '../../types';
// These are some helper methods for drawing the round corners // These are some helper methods for drawing the round corners
@@ -73,8 +73,8 @@ export function getSmoothStepPath({
sourceY <= targetY ? rightTopCorner(cX, sourceY, cornerSize) : rightBottomCorner(cX, sourceY, cornerSize); sourceY <= targetY ? rightTopCorner(cX, sourceY, cornerSize) : rightBottomCorner(cX, sourceY, cornerSize);
secondCornerPath = secondCornerPath =
sourceY <= targetY ? bottomLeftCorner(cX, targetY, cornerSize) : topLeftCorner(cX, targetY, cornerSize); sourceY <= targetY ? bottomLeftCorner(cX, targetY, cornerSize) : topLeftCorner(cX, targetY, cornerSize);
} else if (sourcePosition === Position.Right && targetPosition === Position.Left){ } else if (sourcePosition === Position.Right && targetPosition === Position.Left) {
// and sourceX > targetX // and sourceX > targetX
firstCornerPath = firstCornerPath =
sourceY <= targetY ? leftTopCorner(cX, sourceY, cornerSize) : leftBottomCorner(cX, sourceY, cornerSize); sourceY <= targetY ? leftTopCorner(cX, sourceY, cornerSize) : leftBottomCorner(cX, sourceY, cornerSize);
secondCornerPath = secondCornerPath =
@@ -126,8 +126,8 @@ export default memo(
style, style,
sourcePosition = Position.Bottom, sourcePosition = Position.Bottom,
targetPosition = Position.Top, targetPosition = Position.Top,
arrowHeadType, markerEnd,
markerEndId, markerStart,
borderRadius = 5, borderRadius = 5,
}: EdgeSmoothStepProps) => { }: EdgeSmoothStepProps) => {
const [centerX, centerY] = getCenter({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition }); const [centerX, centerY] = getCenter({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition });
@@ -142,8 +142,6 @@ export default memo(
borderRadius, borderRadius,
}); });
const markerEnd = getMarkerEnd(arrowHeadType, markerEndId);
const text = label ? ( const text = label ? (
<EdgeText <EdgeText
x={centerX} x={centerX}
@@ -159,7 +157,13 @@ export default memo(
return ( return (
<> <>
<path style={style} className="react-flow__edge-path" d={path} markerEnd={markerEnd} /> <path
style={style}
className="react-flow__edge-path"
d={path}
markerEnd={markerEnd}
markerStart={markerStart}
/>
{text} {text}
</> </>
); );
+3 -4
View File
@@ -1,7 +1,6 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import EdgeText from './EdgeText'; import EdgeText from './EdgeText';
import { getMarkerEnd } from './utils';
import { EdgeProps } from '../../types'; import { EdgeProps } from '../../types';
export default memo( export default memo(
@@ -17,15 +16,14 @@ export default memo(
labelBgPadding, labelBgPadding,
labelBgBorderRadius, labelBgBorderRadius,
style, style,
arrowHeadType, markerEnd,
markerEndId, markerStart,
}: EdgeProps) => { }: EdgeProps) => {
const yOffset = Math.abs(targetY - sourceY) / 2; const yOffset = Math.abs(targetY - sourceY) / 2;
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset; const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
const xOffset = Math.abs(targetX - sourceX) / 2; const xOffset = Math.abs(targetX - sourceX) / 2;
const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset; const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;
const markerEnd = getMarkerEnd(arrowHeadType, markerEndId);
const text = label ? ( const text = label ? (
<EdgeText <EdgeText
@@ -47,6 +45,7 @@ export default memo(
className="react-flow__edge-path" className="react-flow__edge-path"
d={`M ${sourceX},${sourceY}L ${targetX},${targetY}`} d={`M ${sourceX},${sourceY}L ${targetX},${targetY}`}
markerEnd={markerEnd} markerEnd={markerEnd}
markerStart={markerStart}
/> />
{text} {text}
</> </>
+19 -5
View File
@@ -5,6 +5,7 @@ import { useStoreActions, useStoreState } from '../../store/hooks';
import { Edge, EdgeProps, WrapEdgeProps } from '../../types'; import { Edge, EdgeProps, WrapEdgeProps } from '../../types';
import { onMouseDown } from '../../components/Handle/handler'; import { onMouseDown } from '../../components/Handle/handler';
import { EdgeAnchor } from './EdgeAnchor'; import { EdgeAnchor } from './EdgeAnchor';
import { getMarkerId } from '../../utils/graph';
export default (EdgeComponent: ComponentType<EdgeProps>) => { export default (EdgeComponent: ComponentType<EdgeProps>) => {
const EdgeWrapper = ({ const EdgeWrapper = ({
@@ -23,7 +24,6 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
labelBgPadding, labelBgPadding,
labelBgBorderRadius, labelBgBorderRadius,
style, style,
arrowHeadType,
source, source,
target, target,
sourceX, sourceX,
@@ -33,7 +33,6 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
sourcePosition, sourcePosition,
targetPosition, targetPosition,
elementsSelectable, elementsSelectable,
markerEndId,
isHidden, isHidden,
sourceHandleId, sourceHandleId,
targetHandleId, targetHandleId,
@@ -46,6 +45,8 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
edgeUpdaterRadius, edgeUpdaterRadius,
onEdgeUpdateStart, onEdgeUpdateStart,
onEdgeUpdateEnd, onEdgeUpdateEnd,
markerEnd,
markerStart,
}: WrapEdgeProps): JSX.Element | null => { }: WrapEdgeProps): JSX.Element | null => {
const addSelectedElements = useStoreActions((actions) => actions.addSelectedElements); const addSelectedElements = useStoreActions((actions) => actions.addSelectedElements);
const setConnectionNodeId = useStoreActions((actions) => actions.setConnectionNodeId); const setConnectionNodeId = useStoreActions((actions) => actions.setConnectionNodeId);
@@ -160,7 +161,18 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
_onEdgeUpdate _onEdgeUpdate
); );
}, },
[id, source, target, type, sourceHandleId, targetHandleId, setConnectionNodeId, setPosition, edgeElement, onConnectEdge] [
id,
source,
target,
type,
sourceHandleId,
targetHandleId,
setConnectionNodeId,
setPosition,
edgeElement,
onConnectEdge,
]
); );
const onEdgeUpdaterSourceMouseDown = useCallback( const onEdgeUpdaterSourceMouseDown = useCallback(
@@ -179,6 +191,8 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
const onEdgeUpdaterMouseEnter = useCallback(() => setUpdating(true), [setUpdating]); const onEdgeUpdaterMouseEnter = useCallback(() => setUpdating(true), [setUpdating]);
const onEdgeUpdaterMouseOut = useCallback(() => setUpdating(false), [setUpdating]); const onEdgeUpdaterMouseOut = useCallback(() => setUpdating(false), [setUpdating]);
const markerStartUrl = useMemo(() => `url(#${getMarkerId(markerStart)})`, [markerStart]);
const markerEndUrl = useMemo(() => `url(#${getMarkerId(markerEnd)})`, [markerEnd]);
if (isHidden) { if (isHidden) {
return null; return null;
@@ -208,16 +222,16 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
labelBgBorderRadius={labelBgBorderRadius} labelBgBorderRadius={labelBgBorderRadius}
data={data} data={data}
style={style} style={style}
arrowHeadType={arrowHeadType}
sourceX={sourceX} sourceX={sourceX}
sourceY={sourceY} sourceY={sourceY}
targetX={targetX} targetX={targetX}
targetY={targetY} targetY={targetY}
sourcePosition={sourcePosition} sourcePosition={sourcePosition}
targetPosition={targetPosition} targetPosition={targetPosition}
markerEndId={markerEndId}
sourceHandleId={sourceHandleId} sourceHandleId={sourceHandleId}
targetHandleId={targetHandleId} targetHandleId={targetHandleId}
markerStart={markerStartUrl}
markerEnd={markerEndUrl}
/> />
{handleEdgeUpdate && ( {handleEdgeUpdate && (
<g <g
@@ -1,52 +1,98 @@
import React, { ReactNode } from 'react'; import React, { useMemo } from 'react';
import { useStoreState } from '../../store/hooks';
import { EdgeMarker, ArrowHeadType } from '../../types';
import { getMarkerId } from '../../utils/graph';
interface MarkerProps { interface MarkerProps {
id: string; id: string;
children: ReactNode; type: ArrowHeadType;
color: string;
}
interface MarkerDefinitionsProps {
defaultColor: string;
} }
const Marker = ({ id, children }: MarkerProps) => ( interface SymbolProps {
<marker
className="react-flow__arrowhead"
id={id}
markerWidth="12.5"
markerHeight="12.5"
viewBox="-10 -10 20 20"
orient="auto"
refX="0"
refY="0"
>
{children}
</marker>
);
interface MarkerDefinitionsProps {
color: string; color: string;
} }
const MarkerDefinitions = ({ color }: MarkerDefinitionsProps) => { interface EdgeMarkerExtended extends EdgeMarker {
id: string;
}
const ArrowSymbol = ({ color }: SymbolProps) => {
return (
<polyline
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1"
fill={color}
points="-5,-4 0,0 -5,4 -5,-4"
/>
);
};
const ArrowClosedSymbol = ({ color }: SymbolProps) => {
return (
<polyline
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1"
fill={color}
points="-5,-4 0,0 -5,4 -5,-4"
/>
);
};
const markerSymbols = {
[ArrowHeadType.Arrow]: ArrowSymbol,
[ArrowHeadType.ArrowClosed]: ArrowClosedSymbol,
};
const Marker = ({ id, type, color }: MarkerProps) => {
const Symbol = markerSymbols[type];
return (
<marker
className="react-flow__arrowhead"
id={id}
markerWidth="12.5"
markerHeight="12.5"
viewBox="-10 -10 20 20"
orient="auto"
refX="0"
refY="0"
>
<Symbol color={color} />
</marker>
);
};
const MarkerDefinitions = ({ defaultColor }: MarkerDefinitionsProps) => {
const edges = useStoreState((state) => state.edges);
const markers = useMemo(() => {
const ids: string[] = [];
return edges.reduce<EdgeMarkerExtended[]>((markers, edge) => {
[edge.markerStart, edge.markerEnd].forEach((marker) => {
if (marker && typeof marker === 'object') {
const markerId = getMarkerId(marker);
if (!ids.includes(markerId)) {
markers.push({ id: markerId, ...marker });
ids.push(markerId);
}
}
});
return markers.sort((a, b) => a.id.localeCompare(b.id));
}, []);
}, [edges]);
return ( return (
<defs> <defs>
<Marker id="react-flow__arrowclosed"> {markers.map((marker: EdgeMarkerExtended) => (
<polyline <Marker id={marker.id} key={marker.id} type={marker.type} color={marker.color || defaultColor} />
stroke={color} ))}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1"
fill={color}
points="-5,-4 0,0 -5,4 -5,-4"
/>
</Marker>
<Marker id="react-flow__arrow">
<polyline
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
fill="none"
points="-5,-4 0,0 -5,4"
/>
</Marker>
</defs> </defs>
); );
}; };
+5 -6
View File
@@ -26,8 +26,7 @@ interface EdgeRendererProps {
connectionMode?: ConnectionMode; connectionMode?: ConnectionMode;
onElementClick?: (event: React.MouseEvent, element: Node | Edge) => void; onElementClick?: (event: React.MouseEvent, element: Node | Edge) => void;
onEdgeDoubleClick?: (event: React.MouseEvent, edge: Edge) => void; onEdgeDoubleClick?: (event: React.MouseEvent, edge: Edge) => void;
arrowHeadColor: string; defaultMarkerColor: string;
markerEndId?: string;
onlyRenderVisibleElements: boolean; onlyRenderVisibleElements: boolean;
onEdgeUpdate?: OnEdgeUpdateFunc; onEdgeUpdate?: OnEdgeUpdateFunc;
onEdgeContextMenu?: (event: React.MouseEvent, edge: Edge) => void; onEdgeContextMenu?: (event: React.MouseEvent, edge: Edge) => void;
@@ -155,7 +154,8 @@ const Edge = ({
labelBgPadding={edge.labelBgPadding} labelBgPadding={edge.labelBgPadding}
labelBgBorderRadius={edge.labelBgBorderRadius} labelBgBorderRadius={edge.labelBgBorderRadius}
style={edge.style} style={edge.style}
arrowHeadType={edge.arrowHeadType} markerEnd={edge.markerEnd}
markerStart={edge.markerStart}
source={edge.source} source={edge.source}
target={edge.target} target={edge.target}
sourceHandleId={sourceHandleId} sourceHandleId={sourceHandleId}
@@ -167,7 +167,6 @@ const Edge = ({
sourcePosition={sourcePosition} sourcePosition={sourcePosition}
targetPosition={targetPosition} targetPosition={targetPosition}
elementsSelectable={elementsSelectable} elementsSelectable={elementsSelectable}
markerEndId={props.markerEndId}
isHidden={edge.isHidden} isHidden={edge.isHidden}
onConnectEdge={onConnectEdge} onConnectEdge={onConnectEdge}
handleEdgeUpdate={typeof props.onEdgeUpdate !== 'undefined'} handleEdgeUpdate={typeof props.onEdgeUpdate !== 'undefined'}
@@ -203,7 +202,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
const { const {
connectionLineType, connectionLineType,
arrowHeadColor, defaultMarkerColor,
connectionLineStyle, connectionLineStyle,
connectionLineComponent, connectionLineComponent,
onlyRenderVisibleElements, onlyRenderVisibleElements,
@@ -213,7 +212,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
return ( return (
<svg width={width} height={height} className="react-flow__edges"> <svg width={width} height={height} className="react-flow__edges">
<MarkerDefinitions color={arrowHeadColor} /> <MarkerDefinitions defaultColor={defaultMarkerColor} />
<g transform={transformStyle}> <g transform={transformStyle}>
{edges.map((edge: Edge) => ( {edges.map((edge: Edge) => (
<Edge <Edge
+1
View File
@@ -21,6 +21,7 @@ interface FlowRendererProps
| 'arrowHeadColor' | 'arrowHeadColor'
| 'onlyRenderVisibleElements' | 'onlyRenderVisibleElements'
| 'selectNodesOnDrag' | 'selectNodesOnDrag'
| 'defaultMarkerColor'
> { > {
children: ReactNode; children: ReactNode;
} }
+3 -5
View File
@@ -23,7 +23,7 @@ export interface GraphViewProps extends Omit<ReactFlowProps, 'onSelectionChange'
onlyRenderVisibleElements: boolean; onlyRenderVisibleElements: boolean;
defaultZoom: number; defaultZoom: number;
defaultPosition: [number, number]; defaultPosition: [number, number];
arrowHeadColor: string; defaultMarkerColor: string;
selectNodesOnDrag: boolean; selectNodesOnDrag: boolean;
} }
@@ -75,8 +75,7 @@ const GraphView = ({
translateExtent, translateExtent,
preventScrolling, preventScrolling,
nodeExtent, nodeExtent,
arrowHeadColor, defaultMarkerColor,
markerEndId,
zoomOnScroll, zoomOnScroll,
zoomOnPinch, zoomOnPinch,
panOnScroll, panOnScroll,
@@ -271,8 +270,7 @@ const GraphView = ({
connectionLineStyle={connectionLineStyle} connectionLineStyle={connectionLineStyle}
connectionLineComponent={connectionLineComponent} connectionLineComponent={connectionLineComponent}
connectionMode={connectionMode} connectionMode={connectionMode}
arrowHeadColor={arrowHeadColor} defaultMarkerColor={defaultMarkerColor}
markerEndId={markerEndId}
onEdgeUpdate={onEdgeUpdate} onEdgeUpdate={onEdgeUpdate}
onlyRenderVisibleElements={onlyRenderVisibleElements} onlyRenderVisibleElements={onlyRenderVisibleElements}
onEdgeContextMenu={onEdgeContextMenu} onEdgeContextMenu={onEdgeContextMenu}
+3 -6
View File
@@ -109,8 +109,7 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
translateExtent?: TranslateExtent; translateExtent?: TranslateExtent;
preventScrolling?: boolean; preventScrolling?: boolean;
nodeExtent?: NodeExtent; nodeExtent?: NodeExtent;
arrowHeadColor?: string; defaultMarkerColor?: string;
markerEndId?: string;
zoomOnScroll?: boolean; zoomOnScroll?: boolean;
zoomOnPinch?: boolean; zoomOnPinch?: boolean;
panOnScroll?: boolean; panOnScroll?: boolean;
@@ -184,8 +183,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
translateExtent, translateExtent,
preventScrolling = true, preventScrolling = true,
nodeExtent, nodeExtent,
arrowHeadColor = '#b1b1b7', defaultMarkerColor = '#b1b1b7',
markerEndId,
zoomOnScroll = true, zoomOnScroll = true,
zoomOnPinch = true, zoomOnPinch = true,
panOnScroll = false, panOnScroll = false,
@@ -262,8 +260,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
translateExtent={translateExtent} translateExtent={translateExtent}
preventScrolling={preventScrolling} preventScrolling={preventScrolling}
nodeExtent={nodeExtent} nodeExtent={nodeExtent}
arrowHeadColor={arrowHeadColor} defaultMarkerColor={defaultMarkerColor}
markerEndId={markerEndId}
zoomOnScroll={zoomOnScroll} zoomOnScroll={zoomOnScroll}
zoomOnPinch={zoomOnPinch} zoomOnPinch={zoomOnPinch}
zoomOnDoubleClick={zoomOnDoubleClick} zoomOnDoubleClick={zoomOnDoubleClick}
+13 -5
View File
@@ -57,6 +57,13 @@ export enum ArrowHeadType {
ArrowClosed = 'arrowclosed', ArrowClosed = 'arrowclosed',
} }
export interface EdgeMarker {
type: ArrowHeadType;
color?: string;
}
export type EdgeMarkerType = string | EdgeMarker;
export interface Edge<T = any> { export interface Edge<T = any> {
id: ElementId; id: ElementId;
type?: string; type?: string;
@@ -72,10 +79,11 @@ export interface Edge<T = any> {
labelBgBorderRadius?: number; labelBgBorderRadius?: number;
style?: CSSProperties; style?: CSSProperties;
animated?: boolean; animated?: boolean;
arrowHeadType?: ArrowHeadType;
isHidden?: boolean; isHidden?: boolean;
data?: T; data?: T;
className?: string; className?: string;
markerStart?: EdgeMarkerType;
markerEnd?: EdgeMarkerType;
} }
export enum BackgroundVariant { export enum BackgroundVariant {
@@ -111,7 +119,6 @@ export interface WrapEdgeProps<T = any> {
labelBgPadding?: [number, number]; labelBgPadding?: [number, number];
labelBgBorderRadius?: number; labelBgBorderRadius?: number;
style?: CSSProperties; style?: CSSProperties;
arrowHeadType?: ArrowHeadType;
source: ElementId; source: ElementId;
target: ElementId; target: ElementId;
sourceHandleId: ElementId | null; sourceHandleId: ElementId | null;
@@ -123,7 +130,6 @@ export interface WrapEdgeProps<T = any> {
sourcePosition: Position; sourcePosition: Position;
targetPosition: Position; targetPosition: Position;
elementsSelectable?: boolean; elementsSelectable?: boolean;
markerEndId?: string;
isHidden?: boolean; isHidden?: boolean;
handleEdgeUpdate: boolean; handleEdgeUpdate: boolean;
onConnectEdge: OnConnectFunc; onConnectEdge: OnConnectFunc;
@@ -134,6 +140,8 @@ export interface WrapEdgeProps<T = any> {
edgeUpdaterRadius?: number; edgeUpdaterRadius?: number;
onEdgeUpdateStart?: (event: React.MouseEvent, edge: Edge) => void; onEdgeUpdateStart?: (event: React.MouseEvent, edge: Edge) => void;
onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge) => void; onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge) => void;
markerStart?: EdgeMarkerType;
markerEnd?: EdgeMarkerType;
} }
export interface EdgeProps<T = any> { export interface EdgeProps<T = any> {
@@ -155,11 +163,11 @@ export interface EdgeProps<T = any> {
labelBgPadding?: [number, number]; labelBgPadding?: [number, number];
labelBgBorderRadius?: number; labelBgBorderRadius?: number;
style?: CSSProperties; style?: CSSProperties;
arrowHeadType?: ArrowHeadType;
markerEndId?: string;
data?: T; data?: T;
sourceHandleId?: ElementId | null; sourceHandleId?: ElementId | null;
targetHandleId?: ElementId | null; targetHandleId?: ElementId | null;
markerStart?: string;
markerEnd?: string;
} }
export interface EdgeSmoothStepProps<T = any> extends EdgeProps<T> { export interface EdgeSmoothStepProps<T = any> extends EdgeProps<T> {
borderRadius?: number; borderRadius?: number;
+16
View File
@@ -15,6 +15,7 @@ import {
FlowExportObject, FlowExportObject,
ReactFlowState, ReactFlowState,
NodeExtent, NodeExtent,
EdgeMarkerType,
} from '../types'; } from '../types';
export const isEdge = (element: Node | Connection | Edge): element is Edge => export const isEdge = (element: Node | Connection | Edge): element is Edge =>
@@ -57,6 +58,21 @@ export const removeElements = (elementsToRemove: Elements, elements: Elements):
const getEdgeId = ({ source, sourceHandle, target, targetHandle }: Connection): ElementId => const getEdgeId = ({ source, sourceHandle, target, targetHandle }: Connection): ElementId =>
`reactflow__edge-${source}${sourceHandle}-${target}${targetHandle}`; `reactflow__edge-${source}${sourceHandle}-${target}${targetHandle}`;
export const getMarkerId = (marker: EdgeMarkerType | undefined): string => {
if (typeof marker === 'undefined') {
return '';
}
if (typeof marker === 'string') {
return marker;
}
return Object.keys(marker)
.sort()
.map((key: string) => `${key}=${(marker as any)[key]}`)
.join('&');
};
const connectionExists = (edge: Edge, elements: Elements) => { const connectionExists = (edge: Edge, elements: Elements) => {
return elements.some( return elements.some(
(el) => (el) =>