From 3d74b519b5ff2599bdb6eaff1aa0b932d46203de Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 21 Jun 2022 17:15:58 +0200 Subject: [PATCH] refactor(markers): optimze marker creation, pass ids closes #2188 --- example/src/Layouting/index.tsx | 23 +++++++-- example/src/Layouting/initial-elements.ts | 20 ++++---- example/src/MultiFlows/index.tsx | 12 +++-- src/components/Edges/wrapEdge.tsx | 5 +- .../EdgeRenderer/MarkerDefinitions.tsx | 49 ++++++++++++------- src/container/EdgeRenderer/index.tsx | 4 +- src/container/GraphView/index.tsx | 2 + src/container/ReactFlow/index.tsx | 1 + src/types/edges.ts | 1 + src/utils/graph.ts | 8 +-- 10 files changed, 82 insertions(+), 43 deletions(-) diff --git a/example/src/Layouting/index.tsx b/example/src/Layouting/index.tsx index 8e4ce5bf..a3b0e7eb 100644 --- a/example/src/Layouting/index.tsx +++ b/example/src/Layouting/index.tsx @@ -8,6 +8,8 @@ import ReactFlow, { Position, useNodesState, useEdgesState, + MarkerType, + EdgeMarker, } from 'react-flow-renderer'; import dagre from 'dagre'; @@ -27,9 +29,12 @@ const LayoutFlow = () => { const [nodes, setNodes, onNodesChange] = useNodesState(initialItems.nodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialItems.edges); - const onConnect = useCallback((connection: Connection) => { - setEdges((eds) => addEdge(connection, eds)); - }, []); + const onConnect = useCallback( + (connection: Connection) => { + setEdges((eds) => addEdge(connection, eds)); + }, + [setEdges] + ); const onLayout = (direction: string) => { const isHorizontal = direction === 'LR'; @@ -63,6 +68,17 @@ const LayoutFlow = () => { setNodes((nds) => nds.map((n) => ({ ...n, selected: false }))); }; + const changeMarker = () => { + setEdges((eds) => + eds.map((e) => ({ + ...e, + markerEnd: { + type: (e.markerEnd as EdgeMarker)?.type === MarkerType.Arrow ? MarkerType.ArrowClosed : MarkerType.Arrow, + }, + })) + ); + }; + return (
@@ -85,6 +101,7 @@ const LayoutFlow = () => { horizontal layout +
diff --git a/example/src/Layouting/initial-elements.ts b/example/src/Layouting/initial-elements.ts index ce763320..539a4561 100644 --- a/example/src/Layouting/initial-elements.ts +++ b/example/src/Layouting/initial-elements.ts @@ -1,4 +1,4 @@ -import { Node, Edge, XYPosition } from 'react-flow-renderer'; +import { Node, Edge, XYPosition, MarkerType } from 'react-flow-renderer'; const position: XYPosition = { x: 0, y: 0 }; @@ -59,16 +59,16 @@ const nodes: Node[] = [ ]; const edges: Edge[] = [ - { id: 'e12', source: '1', target: '2', type: 'smoothstep' }, - { id: 'e13', source: '1', target: '3', type: 'smoothstep' }, - { id: 'e22a', source: '2', target: '2a', type: 'smoothstep' }, - { id: 'e22b', source: '2', target: '2b', type: 'smoothstep' }, - { id: 'e22c', source: '2', target: '2c', type: 'smoothstep' }, - { id: 'e2c2d', source: '2c', target: '2d', type: 'smoothstep' }, + { id: 'e12', source: '1', target: '2', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } }, + { id: 'e13', source: '1', target: '3', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } }, + { id: 'e22a', source: '2', target: '2a', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } }, + { id: 'e22b', source: '2', target: '2b', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } }, + { id: 'e22c', source: '2', target: '2c', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } }, + { id: 'e2c2d', source: '2c', target: '2d', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } }, - { id: 'e45', source: '4', target: '5', type: 'smoothstep' }, - { id: 'e56', source: '5', target: '6', type: 'smoothstep' }, - { id: 'e57', source: '5', target: '7', type: 'smoothstep' }, + { id: 'e45', source: '4', target: '5', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } }, + { id: 'e56', source: '5', target: '6', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } }, + { id: 'e57', source: '5', target: '7', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } }, ]; const nodesAndEdges = { nodes, edges }; diff --git a/example/src/MultiFlows/index.tsx b/example/src/MultiFlows/index.tsx index 0a247146..d3213fca 100644 --- a/example/src/MultiFlows/index.tsx +++ b/example/src/MultiFlows/index.tsx @@ -9,6 +9,7 @@ import ReactFlow, { ReactFlowProvider, useNodesState, useEdgesState, + MarkerType, } from 'react-flow-renderer'; import './multiflows.css'; @@ -21,12 +22,12 @@ const initialNodes: Node[] = [ ]; const initialEdges: Edge[] = [ - { id: 'e1-2', source: '1', target: '2', animated: true }, + { id: 'e1-2', source: '1', target: '2', animated: true, markerEnd: { type: MarkerType.Arrow } }, { id: 'e1-3', source: '1', target: '3' }, ]; -const Flow: FC = () => { - const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); +const Flow: FC<{ id: string }> = ({ id }) => { + const [nodes, , onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); const onConnect = (params: Edge | Connection) => setEdges((eds) => addEdge(params, eds)); @@ -39,6 +40,7 @@ const Flow: FC = () => { onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} onConnect={onConnect} + id={id} > @@ -48,8 +50,8 @@ const Flow: FC = () => { const MultiFlows: FC = () => (
- - + +
); diff --git a/src/components/Edges/wrapEdge.tsx b/src/components/Edges/wrapEdge.tsx index 7f2a66b9..4bc74835 100644 --- a/src/components/Edges/wrapEdge.tsx +++ b/src/components/Edges/wrapEdge.tsx @@ -53,6 +53,7 @@ export default (EdgeComponent: ComponentType) => { onEdgeUpdateEnd, markerEnd, markerStart, + rfId, }: WrapEdgeProps): JSX.Element | null => { const [updating, setUpdating] = useState(false); const { addSelectedEdges, connectionMode } = useStore(selector, shallow); @@ -120,8 +121,8 @@ export default (EdgeComponent: ComponentType) => { const onEdgeUpdaterMouseEnter = () => setUpdating(true); const onEdgeUpdaterMouseOut = () => setUpdating(false); - const markerStartUrl = useMemo(() => `url(#${getMarkerId(markerStart)})`, [markerStart]); - const markerEndUrl = useMemo(() => `url(#${getMarkerId(markerEnd)})`, [markerEnd]); + const markerStartUrl = useMemo(() => `url(#${getMarkerId(markerStart, rfId)})`, [markerStart, rfId]); + const markerEndUrl = useMemo(() => `url(#${getMarkerId(markerEnd, rfId)})`, [markerEnd, rfId]); if (hidden) { return null; diff --git a/src/container/EdgeRenderer/MarkerDefinitions.tsx b/src/container/EdgeRenderer/MarkerDefinitions.tsx index dcae27bf..4075ec2e 100644 --- a/src/container/EdgeRenderer/MarkerDefinitions.tsx +++ b/src/container/EdgeRenderer/MarkerDefinitions.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { memo, useCallback } from 'react'; import { useStore } from '../../store'; import { EdgeMarker, ReactFlowState } from '../../types'; @@ -9,6 +9,7 @@ interface MarkerProps extends EdgeMarker { } interface MarkerDefinitionsProps { defaultColor: string; + rfId?: string; } const Marker = ({ @@ -40,26 +41,36 @@ const Marker = ({ ); }; -const edgesSelector = (s: ReactFlowState) => s.edges; - -const MarkerDefinitions = ({ defaultColor }: MarkerDefinitionsProps) => { - const edges = useStore(edgesSelector); - const markers = useMemo(() => { +const markerSelector = + ({ defaultColor, rfId }: { defaultColor: string; rfId?: string }) => + (s: ReactFlowState) => { const ids: string[] = []; - return edges.reduce((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, color: marker.color || defaultColor, ...marker }); - ids.push(markerId); + return s.edges + .reduce((markers, edge) => { + [edge.markerStart, edge.markerEnd].forEach((marker) => { + if (marker && typeof marker === 'object') { + const markerId = getMarkerId(marker, rfId); + if (!ids.includes(markerId)) { + markers.push({ id: markerId, color: marker.color || defaultColor, ...marker }); + ids.push(markerId); + } } - } - }); - return markers.sort((a, b) => a.id.localeCompare(b.id)); - }, []); - }, [edges, defaultColor]); + }); + return markers; + }, []) + .sort((a, b) => a.id.localeCompare(b.id)); + }; + +// when you have multiple flows on a page and you hide the first one, the other ones have no markers anymore +// when they do have markers with the same ids. To prevent this the user can pass a unique id to the react flow wrapper +// that we can then use for creating our unique marker ids +const MarkerDefinitions = ({ defaultColor, rfId }: MarkerDefinitionsProps) => { + const markers = useStore( + useCallback(markerSelector({ defaultColor, rfId }), [defaultColor, rfId]), + // the id includes all marker options, so we just need to look at that part of the marker + (a, b) => !(a.length !== b.length || a.some((m, i) => m.id !== b[i].id)) + ); return ( @@ -82,4 +93,4 @@ const MarkerDefinitions = ({ defaultColor }: MarkerDefinitionsProps) => { MarkerDefinitions.displayName = 'MarkerDefinitions'; -export default MarkerDefinitions; +export default memo(MarkerDefinitions); diff --git a/src/container/EdgeRenderer/index.tsx b/src/container/EdgeRenderer/index.tsx index 4ddb9909..98f90b1d 100644 --- a/src/container/EdgeRenderer/index.tsx +++ b/src/container/EdgeRenderer/index.tsx @@ -40,6 +40,7 @@ interface EdgeRendererProps { edgeUpdaterRadius?: number; noPanClassName?: string; elevateEdgesOnSelect: boolean; + rfId?: string; } const selector = (s: ReactFlowState) => ({ @@ -93,7 +94,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => { height={height} className="react-flow__edges react-flow__container" > - {isMaxLevel && } + {isMaxLevel && } {edges.map((edge: Edge) => { const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(nodeInternals, edge.source); @@ -184,6 +185,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => { onEdgeDoubleClick={props.onEdgeDoubleClick} onEdgeUpdateStart={props.onEdgeUpdateStart} onEdgeUpdateEnd={props.onEdgeUpdateEnd} + rfId={props.rfId} /> ); })} diff --git a/src/container/GraphView/index.tsx b/src/container/GraphView/index.tsx index e5db5604..3ef21baf 100644 --- a/src/container/GraphView/index.tsx +++ b/src/container/GraphView/index.tsx @@ -84,6 +84,7 @@ const GraphView = ({ noWheelClassName, noPanClassName, elevateEdgesOnSelect, + id, }: GraphViewProps) => { useOnInitHandler(onInit); @@ -139,6 +140,7 @@ const GraphView = ({ defaultMarkerColor={defaultMarkerColor} noPanClassName={noPanClassName} elevateEdgesOnSelect={!!elevateEdgesOnSelect} + rfId={id} /> ( noWheelClassName={noWheelClassName} noPanClassName={noPanClassName} elevateEdgesOnSelect={elevateEdgesOnSelect} + id={rest?.id} /> { onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge, handleType: HandleType) => void; markerStart?: EdgeMarkerType; markerEnd?: EdgeMarkerType; + rfId?: string; } export interface EdgeSmoothStepProps extends EdgeProps { diff --git a/src/utils/graph.ts b/src/utils/graph.ts index a426baed..fce21b3c 100644 --- a/src/utils/graph.ts +++ b/src/utils/graph.ts @@ -30,7 +30,7 @@ export const getIncomers = (node: Node, nodes: Node const getEdgeId = ({ source, sourceHandle, target, targetHandle }: Connection): string => `reactflow__edge-${source}${sourceHandle || ''}-${target}${targetHandle || ''}`; -export const getMarkerId = (marker: EdgeMarkerType | undefined): string => { +export const getMarkerId = (marker: EdgeMarkerType | undefined, rfId?: string): string => { if (typeof marker === 'undefined') { return ''; } @@ -39,10 +39,12 @@ export const getMarkerId = (marker: EdgeMarkerType | undefined): string => { return marker; } - return Object.keys(marker) + const idPrefix = rfId ? `${rfId}__` : ''; + + return `${idPrefix}${Object.keys(marker) .sort() .map((key: string) => `${key}=${(marker as any)[key]}`) - .join('&'); + .join('&')}`; }; const connectionExists = (edge: Edge, edges: Edge[]) => {