From dec1f973cb4afcac2a19a5f724e4e36a8adfed6d Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 14 Jul 2020 09:54:39 +0200 Subject: [PATCH 1/4] refactor(edges): create index for all edge types --- src/components/Edges/index.ts | 4 ++++ src/container/EdgeRenderer/utils.ts | 5 +---- src/container/ReactFlow/index.tsx | 5 +---- 3 files changed, 6 insertions(+), 8 deletions(-) create mode 100644 src/components/Edges/index.ts diff --git a/src/components/Edges/index.ts b/src/components/Edges/index.ts new file mode 100644 index 00000000..65beb017 --- /dev/null +++ b/src/components/Edges/index.ts @@ -0,0 +1,4 @@ +export { default as BezierEdge } from './BezierEdge'; +export { default as StepEdge } from './StepEdge'; +export { default as SmoothStepEdge } from './SmoothStepEdge'; +export { default as StraightEdge } from './StraightEdge'; diff --git a/src/container/EdgeRenderer/utils.ts b/src/container/EdgeRenderer/utils.ts index d3b85667..fa651b29 100644 --- a/src/container/EdgeRenderer/utils.ts +++ b/src/container/EdgeRenderer/utils.ts @@ -1,9 +1,6 @@ import { ComponentType } from 'react'; -import StraightEdge from '../../components/Edges/StraightEdge'; -import BezierEdge from '../../components/Edges/BezierEdge'; -import StepEdge from '../../components/Edges/StepEdge'; -import SmoothStepEdge from '../../components/Edges/SmoothStepEdge'; +import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../components/Edges'; import wrapEdge from '../../components/Edges/wrapEdge'; import { EdgeTypesType, EdgeCompProps } from '../../types'; diff --git a/src/container/ReactFlow/index.tsx b/src/container/ReactFlow/index.tsx index 343e2964..b3d5ff11 100644 --- a/src/container/ReactFlow/index.tsx +++ b/src/container/ReactFlow/index.tsx @@ -14,10 +14,7 @@ import InputNode from '../../components/Nodes/InputNode'; import OutputNode from '../../components/Nodes/OutputNode'; import { createNodeTypes } from '../NodeRenderer/utils'; import SelectionListener from '../../components/SelectionListener'; -import BezierEdge from '../../components/Edges/BezierEdge'; -import StraightEdge from '../../components/Edges/StraightEdge'; -import StepEdge from '../../components/Edges/StepEdge'; -import SmoothStepEdge from '../../components/Edges/SmoothStepEdge'; +import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../components/Edges'; import { createEdgeTypes } from '../EdgeRenderer/utils'; import Wrapper from './Wrapper'; import { From 1b3f58486a3b842d1919a1e044a533508ab6f5d4 Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 14 Jul 2020 10:11:26 +0200 Subject: [PATCH 2/4] refactor(classnames): use faster classcat alternative --- package-lock.json | 5 +++++ package.json | 2 +- src/additional-components/Background/index.tsx | 4 ++-- src/additional-components/Controls/index.tsx | 4 ++-- src/additional-components/MiniMap/index.tsx | 4 ++-- src/components/ConnectionLine/index.tsx | 4 ++-- src/components/Edges/wrapEdge.tsx | 4 ++-- src/components/Handle/BaseHandle.tsx | 16 +++++++++++----- src/components/Handle/index.tsx | 4 ++-- src/components/Nodes/wrapNode.tsx | 16 +++++++++++----- src/container/ReactFlow/index.tsx | 5 +++-- 11 files changed, 43 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6c7af995..015fd92b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6177,6 +6177,11 @@ "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true }, + "classcat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-4.1.0.tgz", + "integrity": "sha512-RA8O5oCi1I1CF6rR4cRBROh8MtZzM4w7xKLm0jd+S6UN2G4FIto+9DVOeFc46JEZFN5PVe/EZWLQO1VU/AUH4A==" + }, "classnames": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", diff --git a/package.json b/package.json index fcfa20a8..c09688a8 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ }, "dependencies": { "@welldone-software/why-did-you-render": "^4.2.5", - "classnames": "^2.2.6", + "classcat": "^4.1.0", "d3-selection": "^1.4.1", "d3-zoom": "^1.8.3", "easy-peasy": "^3.3.1", diff --git a/src/additional-components/Background/index.tsx b/src/additional-components/Background/index.tsx index 4228a089..86a24a09 100644 --- a/src/additional-components/Background/index.tsx +++ b/src/additional-components/Background/index.tsx @@ -1,5 +1,5 @@ import React, { memo, HTMLAttributes } from 'react'; -import classnames from 'classnames'; +import cc from 'classcat'; import { useStoreState } from '../../store/hooks'; import { BackgroundVariant } from '../../types'; @@ -25,7 +25,7 @@ const Background = memo( const height = useStoreState((s) => s.height); const [x, y, scale] = useStoreState((s) => s.transform); - const bgClasses = classnames('react-flow__background', className); + const bgClasses = cc(['react-flow__background', className]); const bgColor = color ? color : defaultColors[variant]; const scaledGap = gap * scale; const xOffset = x % scaledGap; diff --git a/src/additional-components/Controls/index.tsx b/src/additional-components/Controls/index.tsx index 0852f340..d1c78370 100644 --- a/src/additional-components/Controls/index.tsx +++ b/src/additional-components/Controls/index.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import classnames from 'classnames'; +import cc from 'classcat'; import { useStoreState, useStoreActions } from '../../store/hooks'; @@ -24,7 +24,7 @@ const Controls = ({ style, showZoom = true, showFitView = true, showInteractive const zoomOut = useStoreActions((actions) => actions.zoomOut); const isInteractive = useStoreState((s) => s.nodesDraggable && s.nodesConnectable && s.elementsSelectable); - const mapClasses = classnames('react-flow__controls', className); + const mapClasses = cc(['react-flow__controls', className]); return (
diff --git a/src/additional-components/MiniMap/index.tsx b/src/additional-components/MiniMap/index.tsx index 021ed7c1..6c109d7f 100644 --- a/src/additional-components/MiniMap/index.tsx +++ b/src/additional-components/MiniMap/index.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import classnames from 'classnames'; +import cc from 'classcat'; import { useStoreState } from '../../store/hooks'; import { getRectOfNodes, getBoundsofRects } from '../../utils/graph'; @@ -31,7 +31,7 @@ const MiniMap = ({ const [tX, tY, tScale] = useStoreState((s) => s.transform); const nodes = useStoreState((s) => s.nodes); - const mapClasses = classnames('react-flow__minimap', className); + const mapClasses = cc(['react-flow__minimap', className]); const elementWidth = (style.width || defaultWidth)! as number; const elementHeight = (style.height || defaultHeight)! as number; const nodeColorFunc = (nodeColor instanceof Function ? nodeColor : () => nodeColor) as StringFunc; diff --git a/src/components/ConnectionLine/index.tsx b/src/components/ConnectionLine/index.tsx index d7b7033d..6daba9cb 100644 --- a/src/components/ConnectionLine/index.tsx +++ b/src/components/ConnectionLine/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState, CSSProperties } from 'react'; -import cx from 'classnames'; +import cc from 'classcat'; import { getBezierPath } from '../Edges/BezierEdge'; import { getStepPath } from '../Edges/StepEdge'; @@ -46,7 +46,7 @@ export default ({ return null; } - const connectionLineClasses: string = cx('react-flow__connection', className); + const connectionLineClasses: string = cc(['react-flow__connection', className]); const sourceHandle = handleId ? sourceNode.__rf.handleBounds[connectionHandleType].find((d: HandleElement) => d.id === handleId) diff --git a/src/components/Edges/wrapEdge.tsx b/src/components/Edges/wrapEdge.tsx index 2b45535e..01eda6b7 100644 --- a/src/components/Edges/wrapEdge.tsx +++ b/src/components/Edges/wrapEdge.tsx @@ -1,5 +1,5 @@ import React, { memo, ComponentType, CSSProperties } from 'react'; -import cx from 'classnames'; +import cc from 'classcat'; import { useStoreActions } from '../../store/hooks'; import { ElementId, Edge, EdgeCompProps } from '../../types'; @@ -39,7 +39,7 @@ export default (EdgeComponent: ComponentType) => { ...rest }: EdgeWrapperProps) => { const setSelectedElements = useStoreActions((a) => a.setSelectedElements); - const edgeClasses = cx('react-flow__edge', `react-flow__edge-${type}`, className, { selected, animated }); + const edgeClasses = cc(['react-flow__edge', `react-flow__edge-${type}`, className, { selected, animated }]); const edgeGroupStyle: CSSProperties = { pointerEvents: elementsSelectable ? 'all' : 'none', }; diff --git a/src/components/Handle/BaseHandle.tsx b/src/components/Handle/BaseHandle.tsx index 57cc5443..7a16a001 100644 --- a/src/components/Handle/BaseHandle.tsx +++ b/src/components/Handle/BaseHandle.tsx @@ -1,5 +1,5 @@ import React, { memo, MouseEvent as ReactMouseEvent, CSSProperties } from 'react'; -import cx from 'classnames'; +import cc from 'classcat'; import { HandleType, ElementId, Position, XYPosition, OnConnectFunc, Connection, SetConnectionId } from '../../types'; @@ -144,10 +144,16 @@ const BaseHandle = memo( ...rest }: BaseHandleProps) => { const isTarget = type === 'target'; - const handleClasses = cx('react-flow__handle', `react-flow__handle-${position}`, 'nodrag', className, { - source: !isTarget, - target: isTarget, - }); + const handleClasses = cc([ + 'react-flow__handle', + `react-flow__handle-${position}`, + 'nodrag', + className, + { + source: !isTarget, + target: isTarget, + }, + ]); const nodeIdWithHandleId = id ? `${nodeId}__${id}` : nodeId; diff --git a/src/components/Handle/index.tsx b/src/components/Handle/index.tsx index 4c4d8875..adfdacd2 100644 --- a/src/components/Handle/index.tsx +++ b/src/components/Handle/index.tsx @@ -1,5 +1,5 @@ import React, { memo, useContext } from 'react'; -import classnames from 'classnames'; +import cc from 'classcat'; import { useStoreActions, useStoreState } from '../../store/hooks'; import BaseHandle from './BaseHandle'; @@ -26,7 +26,7 @@ const Handle = memo( onConnectAction(params); onConnect(params); }; - const handleClasses = classnames(className, { connectable: isConnectable }); + const handleClasses = cc([className, { connectable: isConnectable }]); return ( ) => { const [offset, setOffset] = useState({ x: 0, y: 0 }); const [isDragging, setDragging] = useState(false); const position = { x: xPos, y: yPos }; - const nodeClasses = cx('react-flow__node', `react-flow__node-${type}`, className, { - selected, - selectable: isSelectable, - }); + const nodeClasses = cc([ + 'react-flow__node', + `react-flow__node-${type}`, + className, + { + selected, + selectable: isSelectable, + }, + ]); const node = { id, type, position, data }; const onMouseEnterHandler = useMemo(() => { if (!onMouseEnter || isDragging) { diff --git a/src/container/ReactFlow/index.tsx b/src/container/ReactFlow/index.tsx index b3d5ff11..44a23d39 100644 --- a/src/container/ReactFlow/index.tsx +++ b/src/container/ReactFlow/index.tsx @@ -1,5 +1,5 @@ import React, { useMemo, CSSProperties, HTMLAttributes, MouseEvent } from 'react'; -import cx from 'classnames'; +import cc from 'classcat'; const nodeEnv: string = process.env.NODE_ENV as string; @@ -98,9 +98,10 @@ const ReactFlow = ({ }: ReactFlowProps) => { const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), []); const edgeTypesParsed = useMemo(() => createEdgeTypes(edgeTypes), []); + const reactFlowClasses = cc(['react-flow', className]); return ( -
+
Date: Tue, 14 Jul 2020 12:57:12 +0200 Subject: [PATCH 3/4] feat(edges): add arrow head options --- README.md | 3 + example/src/Edges/index.js | 10 +++- example/src/Overview/index.js | 4 +- src/components/Edges/BezierEdge.tsx | 7 ++- src/components/Edges/SmoothStepEdge.tsx | 7 ++- src/components/Edges/StepEdge.tsx | 19 ++++++- src/components/Edges/StraightEdge.tsx | 23 +++++++- src/components/Edges/utils.ts | 9 +++ .../EdgeRenderer/MarkerDefinitions.tsx | 56 +++++++++++++++++++ src/container/EdgeRenderer/index.tsx | 8 ++- src/container/GraphView/index.tsx | 6 ++ src/container/ReactFlow/index.tsx | 7 +++ src/types/index.ts | 8 +++ 13 files changed, 157 insertions(+), 10 deletions(-) create mode 100644 src/components/Edges/utils.ts create mode 100644 src/container/EdgeRenderer/MarkerDefinitions.tsx diff --git a/README.md b/README.md index 92de8c8f..e8ecb13f 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ const BasicFlow = () => ; - `minZoom`: default: `0.5` - `maxZoom`: default: `2` - `defaultZoom`: default: `1` +- `arrowHeadColor`: default: `#bbb` ## React Flow Instance @@ -247,6 +248,8 @@ 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 +- `arrowHeadType`: 'arrow' or 'arrowclosed' - defines the arrowhead of the edge +- `markerEndId`: custom marker end url - if this is used `arrowHeadType` gets ignored You can find an example with lots of different edges in the [edges example](https://react-flow.netlify.app/edges). diff --git a/example/src/Edges/index.js b/example/src/Edges/index.js index 81f87136..545d2e4a 100644 --- a/example/src/Edges/index.js +++ b/example/src/Edges/index.js @@ -25,13 +25,21 @@ const initialElements = [ { id: 'e3-4', source: '3', target: '4', type: 'straight', label: 'straight edge' }, { id: 'e3-3a', source: '3', target: '3a', type: 'straight', label: 'label only edge', style: { stroke: 'none' } }, { id: 'e3-5', source: '4', target: '5', animated: true, label: 'animated styled edge', style: { stroke: 'red' } }, - { id: 'e5-6', source: '5', target: '6', label: 'styled label', labelStyle: { fill: 'red', fontWeight: 700 } }, + { + id: 'e5-6', + source: '5', + target: '6', + label: 'styled label', + labelStyle: { fill: 'red', fontWeight: 700 }, + arrowHeadType: 'arrow', + }, { id: 'e5-7', source: '5', target: '7', label: 'label with styled bg', labelBgStyle: { fill: '#eee', fillOpacity: 0.7 }, + arrowHeadType: 'arrowclosed', }, { id: 'e5-8', source: '5', target: '8', type: 'custom', label: 'custom edge' }, ]; diff --git a/example/src/Overview/index.js b/example/src/Overview/index.js index dffbb66f..6969995a 100644 --- a/example/src/Overview/index.js +++ b/example/src/Overview/index.js @@ -70,7 +70,7 @@ const initialElements = [ ), }, - position: { x: 250, y: 300 }, + position: { x: 250, y: 325 }, }, { id: '6', @@ -88,7 +88,7 @@ const initialElements = [ { id: 'e1-2', source: '1', target: '2', label: 'this is an edge label' }, { id: 'e1-3', source: '1', target: '3' }, { id: 'e3-4', source: '3', target: '4', animated: true, label: 'animated edge' }, - { id: 'e4-5', source: '4', target: '5' }, + { id: 'e4-5', source: '4', target: '5', arrowHeadType: 'arrowclosed', label: 'edge with arrow head' }, { id: 'e5-6', source: '5', target: '6', type: 'smoothstep', label: 'smooth step edge' }, { id: 'e5-7', diff --git a/src/components/Edges/BezierEdge.tsx b/src/components/Edges/BezierEdge.tsx index 816ed3db..9b27b6fc 100644 --- a/src/components/Edges/BezierEdge.tsx +++ b/src/components/Edges/BezierEdge.tsx @@ -1,6 +1,7 @@ import React, { memo } from 'react'; import EdgeText from './EdgeText'; +import { getMarkerEnd } from './utils'; import { EdgeBezierProps, Position } from '../../types'; interface GetBezierPathParams { @@ -52,6 +53,8 @@ export default memo( labelShowBg, labelBgStyle, style, + arrowHeadType, + markerEndId, }: EdgeBezierProps) => { const yOffset = Math.abs(targetY - sourceY) / 2; const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset; @@ -81,9 +84,11 @@ export default memo( /> ) : null; + const markerEnd = getMarkerEnd(arrowHeadType, markerEndId); + return ( <> - + {text} ); diff --git a/src/components/Edges/SmoothStepEdge.tsx b/src/components/Edges/SmoothStepEdge.tsx index 8f61e7c8..a203f28c 100644 --- a/src/components/Edges/SmoothStepEdge.tsx +++ b/src/components/Edges/SmoothStepEdge.tsx @@ -1,6 +1,7 @@ import React, { memo } from 'react'; import EdgeText from './EdgeText'; +import { getMarkerEnd } from './utils'; import { EdgeBezierProps, Position } from '../../types'; // These are some helper methods for drawing the round corners @@ -138,6 +139,8 @@ export default memo( style, sourcePosition = Position.Bottom, targetPosition = Position.Top, + arrowHeadType, + markerEndId, }: EdgeBezierProps) => { const yOffset = Math.abs(targetY - sourceY) / 2; const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset; @@ -158,6 +161,8 @@ export default memo( targetPosition, }); + const markerEnd = getMarkerEnd(arrowHeadType, markerEndId); + const text = label ? ( - + {text} ); diff --git a/src/components/Edges/StepEdge.tsx b/src/components/Edges/StepEdge.tsx index 6a0af45d..9475685b 100644 --- a/src/components/Edges/StepEdge.tsx +++ b/src/components/Edges/StepEdge.tsx @@ -1,6 +1,7 @@ import React, { memo } from 'react'; import EdgeText from './EdgeText'; +import { getMarkerEnd } from './utils'; import { EdgeProps } from '../../types'; interface GetStepPathParams { @@ -16,7 +17,19 @@ export function getStepPath({ centerY, sourceX, sourceY, targetX, targetY }: Get } export default memo( - ({ sourceX, sourceY, targetX, targetY, label, labelStyle, labelShowBg, labelBgStyle, style }: EdgeProps) => { + ({ + sourceX, + sourceY, + targetX, + targetY, + label, + labelStyle, + labelShowBg, + labelBgStyle, + style, + arrowHeadType, + markerEndId, + }: EdgeProps) => { const yOffset = Math.abs(targetY - sourceY) / 2; const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset; @@ -25,6 +38,8 @@ export default memo( const path = getStepPath({ centerY, sourceX, sourceY, targetX, targetY }); + const markerEnd = getMarkerEnd(arrowHeadType, markerEndId); + const text = label ? ( - + {text} ); diff --git a/src/components/Edges/StraightEdge.tsx b/src/components/Edges/StraightEdge.tsx index 13b62620..8721cf44 100644 --- a/src/components/Edges/StraightEdge.tsx +++ b/src/components/Edges/StraightEdge.tsx @@ -1,15 +1,29 @@ import React, { memo } from 'react'; import EdgeText from './EdgeText'; +import { getMarkerEnd } from './utils'; import { EdgeProps } from '../../types'; export default memo( - ({ sourceX, sourceY, targetX, targetY, label, labelStyle, labelShowBg, labelBgStyle, style }: EdgeProps) => { + ({ + sourceX, + sourceY, + targetX, + targetY, + label, + labelStyle, + labelShowBg, + labelBgStyle, + style, + arrowHeadType, + markerEndId, + }: EdgeProps) => { 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 markerEnd = getMarkerEnd(arrowHeadType, markerEndId); const text = label ? ( - + {text} ); diff --git a/src/components/Edges/utils.ts b/src/components/Edges/utils.ts new file mode 100644 index 00000000..a25f135c --- /dev/null +++ b/src/components/Edges/utils.ts @@ -0,0 +1,9 @@ +import { ArrowHeadType } from '../../types'; + +export const getMarkerEnd = (arrowHeadType?: ArrowHeadType, markerEndId?: string): string => { + if (typeof markerEndId !== 'undefined' && markerEndId) { + return `url(#${markerEndId})`; + } + + return typeof arrowHeadType !== 'undefined' ? `url(#react-flow__${arrowHeadType})` : 'none'; +}; diff --git a/src/container/EdgeRenderer/MarkerDefinitions.tsx b/src/container/EdgeRenderer/MarkerDefinitions.tsx new file mode 100644 index 00000000..2fceee6c --- /dev/null +++ b/src/container/EdgeRenderer/MarkerDefinitions.tsx @@ -0,0 +1,56 @@ +import React, { ReactNode } from 'react'; + +interface MarkerProps { + id: string; + children: ReactNode; +} + +const Marker = ({ id, children }: MarkerProps) => ( + + {children} + +); + +interface MarkerDefinitionsProps { + color: string; +} + +const MarkerDefinitions = ({ color }: MarkerDefinitionsProps) => { + return ( + + + + + + + + + ); +}; + +MarkerDefinitions.displayName = 'MarkerDefinitions'; + +export default MarkerDefinitions; diff --git a/src/container/EdgeRenderer/index.tsx b/src/container/EdgeRenderer/index.tsx index 3ecfd03a..f1a215f1 100644 --- a/src/container/EdgeRenderer/index.tsx +++ b/src/container/EdgeRenderer/index.tsx @@ -3,6 +3,7 @@ import React, { memo, CSSProperties } from 'react'; import { useStoreState } from '../../store/hooks'; import ConnectionLine from '../../components/ConnectionLine/index'; import { isEdge } from '../../utils/graph'; +import MarkerDefinitions from './MarkerDefinitions'; import { XYPosition, Position, Edge, Node, ElementId, HandleElement, Elements, ConnectionLineType } from '../../types'; interface EdgeRendererProps { @@ -12,6 +13,8 @@ interface EdgeRendererProps { connectionLineType: ConnectionLineType; connectionLineStyle?: CSSProperties; onElementClick?: (element: Node | Edge) => void; + arrowHeadColor: string; + markerEndId?: string; } interface EdgePositions { @@ -171,6 +174,7 @@ function renderEdge( labelShowBg={edge.labelShowBg} labelBgStyle={edge.labelBgStyle} style={edge.style} + arrowHeadType={edge.arrowHeadType} source={sourceId} target={targetId} sourceHandleId={sourceHandleId} @@ -182,6 +186,7 @@ function renderEdge( sourcePosition={sourcePosition} targetPosition={targetPosition} elementsSelectable={elementsSelectable} + markerEndId={props.markerEndId} /> ); } @@ -197,7 +202,7 @@ const EdgeRenderer = memo((props: EdgeRendererProps) => { const nodesConnectable = useStoreState((s) => s.nodesConnectable); const elementsSelectable = useStoreState((s) => s.elementsSelectable); - const { width, height, connectionLineStyle, connectionLineType } = props; + const { width, height, connectionLineStyle, connectionLineType, arrowHeadColor } = props; if (!width) { return null; @@ -208,6 +213,7 @@ const EdgeRenderer = memo((props: EdgeRendererProps) => { return ( + {edges.map((e: Edge) => renderEdge(e, props, nodes, selectedElements, elementsSelectable))} {renderConnectionLine && ( diff --git a/src/container/GraphView/index.tsx b/src/container/GraphView/index.tsx index 322884c9..cc939b2b 100644 --- a/src/container/GraphView/index.tsx +++ b/src/container/GraphView/index.tsx @@ -52,6 +52,8 @@ export interface GraphViewProps { minZoom: number; maxZoom: number; defaultZoom: number; + arrowHeadColor: string; + markerEndId?: string; } const GraphView = memo( @@ -84,6 +86,8 @@ const GraphView = memo( minZoom, maxZoom, defaultZoom, + arrowHeadColor, + markerEndId, }: GraphViewProps) => { const zoomPane = useRef(null); const rendererNode = useRef(null); @@ -212,6 +216,8 @@ const GraphView = memo( onElementClick={onElementClick} connectionLineType={connectionLineType} connectionLineStyle={connectionLineStyle} + arrowHeadColor={arrowHeadColor} + markerEndId={markerEndId} /> {nodesSelectionActive && } diff --git a/src/container/ReactFlow/index.tsx b/src/container/ReactFlow/index.tsx index 44a23d39..277b8b1d 100644 --- a/src/container/ReactFlow/index.tsx +++ b/src/container/ReactFlow/index.tsx @@ -60,6 +60,8 @@ export interface ReactFlowProps extends Omit, 'on minZoom: number; maxZoom: number; defaultZoom: number; + arrowHeadColor: string; + markerEndId?: string; } const ReactFlow = ({ @@ -95,6 +97,8 @@ const ReactFlow = ({ minZoom, maxZoom, defaultZoom, + arrowHeadColor, + markerEndId, }: ReactFlowProps) => { const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), []); const edgeTypesParsed = useMemo(() => createEdgeTypes(edgeTypes), []); @@ -132,6 +136,8 @@ const ReactFlow = ({ minZoom={minZoom} maxZoom={maxZoom} defaultZoom={defaultZoom} + arrowHeadColor={arrowHeadColor} + markerEndId={markerEndId} /> {onSelectionChange && } {children} @@ -167,6 +173,7 @@ ReactFlow.defaultProps = { minZoom: 0.5, maxZoom: 2, defaultZoom: 1, + arrowHeadColor: '#bbb', }; export default ReactFlow; diff --git a/src/types/index.ts b/src/types/index.ts index 47a27e58..269f83fa 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -42,6 +42,11 @@ export interface Node { sourcePosition?: Position; } +export enum ArrowHeadType { + Arrow = 'arrow', + ArrowClosed = 'arrowclosed', +} + export interface Edge { id: ElementId; type?: string; @@ -53,6 +58,7 @@ export interface Edge { labelBgStyle?: CSSProperties; style?: CSSProperties; animated?: boolean; + arrowHeadType?: ArrowHeadType; } export enum BackgroundVariant { @@ -82,6 +88,8 @@ export interface EdgeProps { labelShowBg?: boolean; labelBgStyle?: CSSProperties; style?: CSSProperties; + arrowHeadType?: ArrowHeadType; + markerEndId?: string; } export interface EdgeBezierProps extends EdgeProps { From 65764fbefb135eeeda4a8c310cba289ed83dead9 Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 14 Jul 2020 13:28:59 +0200 Subject: [PATCH 4/4] feat(interaction): add zoomOnScroll property closes #331 --- README.md | 1 + example/src/Interaction/index.js | 14 ++++++++++++++ src/container/GraphView/index.tsx | 4 +++- src/container/ReactFlow/index.tsx | 4 ++++ src/hooks/useD3Zoom.ts | 9 +++++++-- 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e8ecb13f..d7b513e9 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ const BasicFlow = () => ; - `nodesDraggable`: default: `true` - `nodesConnectable`: default: `true` - `elementsSelectable`: default: `true` +- `zoomOnScroll`: default: `true` - `selectNodesOnDrag`: default: `true` - `minZoom`: default: `0.5` - `maxZoom`: default: `2` diff --git a/example/src/Interaction/index.js b/example/src/Interaction/index.js index 26281ec4..406405f0 100644 --- a/example/src/Interaction/index.js +++ b/example/src/Interaction/index.js @@ -18,6 +18,7 @@ const InteractionFlow = () => { const [isSelectable, setIsSelectable] = useState(false); const [isDraggable, setIsDraggable] = useState(false); const [isConnectable, setIsConnectable] = useState(false); + const [zoomOnScroll, setZoomOnScroll] = useState(true); return ( { elementsSelectable={isSelectable} nodesConnectable={isConnectable} nodesDraggable={isDraggable} + zoomOnScroll={zoomOnScroll} onConnect={onConnect} > @@ -67,6 +69,18 @@ const InteractionFlow = () => { />
+
+ +
); diff --git a/src/container/GraphView/index.tsx b/src/container/GraphView/index.tsx index cc939b2b..9758f65d 100644 --- a/src/container/GraphView/index.tsx +++ b/src/container/GraphView/index.tsx @@ -54,6 +54,7 @@ export interface GraphViewProps { defaultZoom: number; arrowHeadColor: string; markerEndId?: string; + zoomOnScroll: boolean; } const GraphView = memo( @@ -88,6 +89,7 @@ const GraphView = memo( defaultZoom, arrowHeadColor, markerEndId, + zoomOnScroll, }: GraphViewProps) => { const zoomPane = useRef(null); const rendererNode = useRef(null); @@ -158,7 +160,7 @@ const GraphView = memo( }; }, []); - useD3Zoom({ zoomPane, onMove, selectionKeyPressed }); + useD3Zoom({ zoomPane, onMove, selectionKeyPressed, zoomOnScroll }); useEffect(() => { if (d3Initialised && onLoad) { diff --git a/src/container/ReactFlow/index.tsx b/src/container/ReactFlow/index.tsx index 277b8b1d..e2bc9243 100644 --- a/src/container/ReactFlow/index.tsx +++ b/src/container/ReactFlow/index.tsx @@ -62,6 +62,7 @@ export interface ReactFlowProps extends Omit, 'on defaultZoom: number; arrowHeadColor: string; markerEndId?: string; + zoomOnScroll: boolean } const ReactFlow = ({ @@ -99,6 +100,7 @@ const ReactFlow = ({ defaultZoom, arrowHeadColor, markerEndId, + zoomOnScroll }: ReactFlowProps) => { const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), []); const edgeTypesParsed = useMemo(() => createEdgeTypes(edgeTypes), []); @@ -138,6 +140,7 @@ const ReactFlow = ({ defaultZoom={defaultZoom} arrowHeadColor={arrowHeadColor} markerEndId={markerEndId} + zoomOnScroll={zoomOnScroll} /> {onSelectionChange && } {children} @@ -174,6 +177,7 @@ ReactFlow.defaultProps = { maxZoom: 2, defaultZoom: 1, arrowHeadColor: '#bbb', + zoomOnScroll: true }; export default ReactFlow; diff --git a/src/hooks/useD3Zoom.ts b/src/hooks/useD3Zoom.ts index ec2028bd..7c920d20 100644 --- a/src/hooks/useD3Zoom.ts +++ b/src/hooks/useD3Zoom.ts @@ -6,10 +6,11 @@ import { useStoreState, useStoreActions } from '../store/hooks'; interface UseD3ZoomParams { zoomPane: MutableRefObject; selectionKeyPressed: boolean; + zoomOnScroll: boolean; onMove?: () => void; } -export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): void => { +export default ({ zoomPane, onMove, zoomOnScroll, selectionKeyPressed }: UseD3ZoomParams): void => { const d3Zoom = useStoreState((s) => s.d3Zoom); const initD3 = useStoreActions((actions) => actions.initD3); @@ -31,6 +32,10 @@ export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): voi return; } + if (!zoomOnScroll && event.sourceEvent.type === 'wheel') { + return; + } + updateTransform(event.transform); if (onMove) { @@ -39,5 +44,5 @@ export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): voi }); } } - }, [selectionKeyPressed, d3Zoom]); + }, [selectionKeyPressed, zoomOnScroll, d3Zoom]); };