Merge pull request #332 from wbkd/develop

Develop
This commit is contained in:
Moritz
2020-07-14 14:19:00 +02:00
committed by GitHub
27 changed files with 235 additions and 46 deletions
+4
View File
@@ -91,10 +91,12 @@ const BasicFlow = () => <ReactFlow elements={elements} />;
- `nodesDraggable`: default: `true` - `nodesDraggable`: default: `true`
- `nodesConnectable`: default: `true` - `nodesConnectable`: default: `true`
- `elementsSelectable`: default: `true` - `elementsSelectable`: default: `true`
- `zoomOnScroll`: default: `true`
- `selectNodesOnDrag`: default: `true` - `selectNodesOnDrag`: default: `true`
- `minZoom`: default: `0.5` - `minZoom`: default: `0.5`
- `maxZoom`: default: `2` - `maxZoom`: default: `2`
- `defaultZoom`: default: `1` - `defaultZoom`: default: `1`
- `arrowHeadColor`: default: `#bbb`
## React Flow Instance ## React Flow Instance
@@ -247,6 +249,8 @@ If you wanted to display this edge, you would need a node with id = 1 (source no
- `labelStyle`: css properties for the text - `labelStyle`: css properties for the text
- `labelShowBg`: boolean - default: `true` - `labelShowBg`: boolean - default: `true`
- `labelBgStyle`: css properties for the text background - `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). You can find an example with lots of different edges in the [edges example](https://react-flow.netlify.app/edges).
+9 -1
View File
@@ -25,13 +25,21 @@ const initialElements = [
{ id: 'e3-4', source: '3', target: '4', type: 'straight', label: 'straight edge' }, { 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-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: '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', id: 'e5-7',
source: '5', source: '5',
target: '7', target: '7',
label: 'label with styled bg', label: 'label with styled bg',
labelBgStyle: { fill: '#eee', fillOpacity: 0.7 }, labelBgStyle: { fill: '#eee', fillOpacity: 0.7 },
arrowHeadType: 'arrowclosed',
}, },
{ id: 'e5-8', source: '5', target: '8', type: 'custom', label: 'custom edge' }, { id: 'e5-8', source: '5', target: '8', type: 'custom', label: 'custom edge' },
]; ];
+14
View File
@@ -18,6 +18,7 @@ const InteractionFlow = () => {
const [isSelectable, setIsSelectable] = useState(false); const [isSelectable, setIsSelectable] = useState(false);
const [isDraggable, setIsDraggable] = useState(false); const [isDraggable, setIsDraggable] = useState(false);
const [isConnectable, setIsConnectable] = useState(false); const [isConnectable, setIsConnectable] = useState(false);
const [zoomOnScroll, setZoomOnScroll] = useState(true);
return ( return (
<ReactFlow <ReactFlow
@@ -25,6 +26,7 @@ const InteractionFlow = () => {
elementsSelectable={isSelectable} elementsSelectable={isSelectable}
nodesConnectable={isConnectable} nodesConnectable={isConnectable}
nodesDraggable={isDraggable} nodesDraggable={isDraggable}
zoomOnScroll={zoomOnScroll}
onConnect={onConnect} onConnect={onConnect}
> >
<MiniMap /> <MiniMap />
@@ -67,6 +69,18 @@ const InteractionFlow = () => {
/> />
</label> </label>
</div> </div>
<div>
<label htmlFor="zoomonscroll">
zoom on scroll
<input
id="zoomonscroll"
type="checkbox"
checked={zoomOnScroll}
onChange={(evt) => setZoomOnScroll(evt.target.checked)}
className="react-flow__zoomonscroll"
/>
</label>
</div>
</div> </div>
</ReactFlow> </ReactFlow>
); );
+2 -2
View File
@@ -70,7 +70,7 @@ const initialElements = [
</> </>
), ),
}, },
position: { x: 250, y: 300 }, position: { x: 250, y: 325 },
}, },
{ {
id: '6', id: '6',
@@ -88,7 +88,7 @@ const initialElements = [
{ 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' }, { 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-6', source: '5', target: '6', type: 'smoothstep', label: 'smooth step edge' },
{ {
id: 'e5-7', id: 'e5-7',
+5
View File
@@ -6177,6 +6177,11 @@
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
"dev": true "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": { "classnames": {
"version": "2.2.6", "version": "2.2.6",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
+1 -1
View File
@@ -24,7 +24,7 @@
}, },
"dependencies": { "dependencies": {
"@welldone-software/why-did-you-render": "^4.2.5", "@welldone-software/why-did-you-render": "^4.2.5",
"classnames": "^2.2.6", "classcat": "^4.1.0",
"d3-selection": "^1.4.1", "d3-selection": "^1.4.1",
"d3-zoom": "^1.8.3", "d3-zoom": "^1.8.3",
"easy-peasy": "^3.3.1", "easy-peasy": "^3.3.1",
@@ -1,5 +1,5 @@
import React, { memo, HTMLAttributes } from 'react'; import React, { memo, HTMLAttributes } from 'react';
import classnames from 'classnames'; import cc from 'classcat';
import { useStoreState } from '../../store/hooks'; import { useStoreState } from '../../store/hooks';
import { BackgroundVariant } from '../../types'; import { BackgroundVariant } from '../../types';
@@ -25,7 +25,7 @@ const Background = memo(
const height = useStoreState((s) => s.height); const height = useStoreState((s) => s.height);
const [x, y, scale] = useStoreState((s) => s.transform); 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 bgColor = color ? color : defaultColors[variant];
const scaledGap = gap * scale; const scaledGap = gap * scale;
const xOffset = x % scaledGap; const xOffset = x % scaledGap;
+2 -2
View File
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import classnames from 'classnames'; import cc from 'classcat';
import { useStoreState, useStoreActions } from '../../store/hooks'; 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 zoomOut = useStoreActions((actions) => actions.zoomOut);
const isInteractive = useStoreState((s) => s.nodesDraggable && s.nodesConnectable && s.elementsSelectable); 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 ( return (
<div className={mapClasses} style={style}> <div className={mapClasses} style={style}>
+2 -2
View File
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import classnames from 'classnames'; import cc from 'classcat';
import { useStoreState } from '../../store/hooks'; import { useStoreState } from '../../store/hooks';
import { getRectOfNodes, getBoundsofRects } from '../../utils/graph'; import { getRectOfNodes, getBoundsofRects } from '../../utils/graph';
@@ -31,7 +31,7 @@ const MiniMap = ({
const [tX, tY, tScale] = useStoreState((s) => s.transform); const [tX, tY, tScale] = useStoreState((s) => s.transform);
const nodes = useStoreState((s) => s.nodes); 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 elementWidth = (style.width || defaultWidth)! as number;
const elementHeight = (style.height || defaultHeight)! as number; const elementHeight = (style.height || defaultHeight)! as number;
const nodeColorFunc = (nodeColor instanceof Function ? nodeColor : () => nodeColor) as StringFunc; const nodeColorFunc = (nodeColor instanceof Function ? nodeColor : () => nodeColor) as StringFunc;
+2 -2
View File
@@ -1,5 +1,5 @@
import React, { useEffect, useState, CSSProperties } from 'react'; import React, { useEffect, useState, CSSProperties } from 'react';
import cx from 'classnames'; import cc from 'classcat';
import { getBezierPath } from '../Edges/BezierEdge'; import { getBezierPath } from '../Edges/BezierEdge';
import { getStepPath } from '../Edges/StepEdge'; import { getStepPath } from '../Edges/StepEdge';
@@ -46,7 +46,7 @@ export default ({
return null; return null;
} }
const connectionLineClasses: string = cx('react-flow__connection', className); const connectionLineClasses: string = cc(['react-flow__connection', className]);
const sourceHandle = handleId const sourceHandle = handleId
? sourceNode.__rf.handleBounds[connectionHandleType].find((d: HandleElement) => d.id === handleId) ? sourceNode.__rf.handleBounds[connectionHandleType].find((d: HandleElement) => d.id === handleId)
+6 -1
View File
@@ -1,6 +1,7 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import EdgeText from './EdgeText'; import EdgeText from './EdgeText';
import { getMarkerEnd } from './utils';
import { EdgeBezierProps, Position } from '../../types'; import { EdgeBezierProps, Position } from '../../types';
interface GetBezierPathParams { interface GetBezierPathParams {
@@ -52,6 +53,8 @@ export default memo(
labelShowBg, labelShowBg,
labelBgStyle, labelBgStyle,
style, style,
arrowHeadType,
markerEndId,
}: EdgeBezierProps) => { }: EdgeBezierProps) => {
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;
@@ -81,9 +84,11 @@ export default memo(
/> />
) : null; ) : null;
const markerEnd = getMarkerEnd(arrowHeadType, markerEndId);
return ( return (
<> <>
<path style={style} d={path} className="react-flow__edge-path" /> <path style={style} d={path} className="react-flow__edge-path" markerEnd={markerEnd} />
{text} {text}
</> </>
); );
+6 -1
View File
@@ -1,6 +1,7 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import EdgeText from './EdgeText'; import EdgeText from './EdgeText';
import { getMarkerEnd } from './utils';
import { EdgeBezierProps, Position } from '../../types'; import { EdgeBezierProps, Position } from '../../types';
// These are some helper methods for drawing the round corners // These are some helper methods for drawing the round corners
@@ -138,6 +139,8 @@ export default memo(
style, style,
sourcePosition = Position.Bottom, sourcePosition = Position.Bottom,
targetPosition = Position.Top, targetPosition = Position.Top,
arrowHeadType,
markerEndId,
}: EdgeBezierProps) => { }: EdgeBezierProps) => {
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;
@@ -158,6 +161,8 @@ export default memo(
targetPosition, targetPosition,
}); });
const markerEnd = getMarkerEnd(arrowHeadType, markerEndId);
const text = label ? ( const text = label ? (
<EdgeText <EdgeText
x={centerX} x={centerX}
@@ -171,7 +176,7 @@ export default memo(
return ( return (
<> <>
<path style={style} className="react-flow__edge-path" d={path} /> <path style={style} className="react-flow__edge-path" d={path} markerEnd={markerEnd} />
{text} {text}
</> </>
); );
+17 -2
View File
@@ -1,6 +1,7 @@
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';
interface GetStepPathParams { interface GetStepPathParams {
@@ -16,7 +17,19 @@ export function getStepPath({ centerY, sourceX, sourceY, targetX, targetY }: Get
} }
export default memo( 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 yOffset = Math.abs(targetY - sourceY) / 2;
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset; const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
@@ -25,6 +38,8 @@ export default memo(
const path = getStepPath({ centerY, sourceX, sourceY, targetX, targetY }); const path = getStepPath({ centerY, sourceX, sourceY, targetX, targetY });
const markerEnd = getMarkerEnd(arrowHeadType, markerEndId);
const text = label ? ( const text = label ? (
<EdgeText <EdgeText
x={centerX} x={centerX}
@@ -38,7 +53,7 @@ export default memo(
return ( return (
<> <>
<path style={style} className="react-flow__edge-path" d={path} /> <path style={style} className="react-flow__edge-path" d={path} markerEnd={markerEnd} />
{text} {text}
</> </>
); );
+21 -2
View File
@@ -1,15 +1,29 @@
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(
({ 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 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
@@ -24,7 +38,12 @@ export default memo(
return ( return (
<> <>
<path style={style} className="react-flow__edge-path" d={`M ${sourceX},${sourceY}L ${targetX},${targetY}`} /> <path
style={style}
className="react-flow__edge-path"
d={`M ${sourceX},${sourceY}L ${targetX},${targetY}`}
markerEnd={markerEnd}
/>
{text} {text}
</> </>
); );
+4
View File
@@ -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';
+9
View File
@@ -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';
};
+2 -2
View File
@@ -1,5 +1,5 @@
import React, { memo, ComponentType, CSSProperties } from 'react'; import React, { memo, ComponentType, CSSProperties } from 'react';
import cx from 'classnames'; import cc from 'classcat';
import { useStoreActions } from '../../store/hooks'; import { useStoreActions } from '../../store/hooks';
import { ElementId, Edge, EdgeCompProps } from '../../types'; import { ElementId, Edge, EdgeCompProps } from '../../types';
@@ -39,7 +39,7 @@ export default (EdgeComponent: ComponentType<EdgeCompProps>) => {
...rest ...rest
}: EdgeWrapperProps) => { }: EdgeWrapperProps) => {
const setSelectedElements = useStoreActions((a) => a.setSelectedElements); 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 = { const edgeGroupStyle: CSSProperties = {
pointerEvents: elementsSelectable ? 'all' : 'none', pointerEvents: elementsSelectable ? 'all' : 'none',
}; };
+11 -5
View File
@@ -1,5 +1,5 @@
import React, { memo, MouseEvent as ReactMouseEvent, CSSProperties } from 'react'; 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'; import { HandleType, ElementId, Position, XYPosition, OnConnectFunc, Connection, SetConnectionId } from '../../types';
@@ -144,10 +144,16 @@ const BaseHandle = memo(
...rest ...rest
}: BaseHandleProps) => { }: BaseHandleProps) => {
const isTarget = type === 'target'; const isTarget = type === 'target';
const handleClasses = cx('react-flow__handle', `react-flow__handle-${position}`, 'nodrag', className, { const handleClasses = cc([
source: !isTarget, 'react-flow__handle',
target: isTarget, `react-flow__handle-${position}`,
}); 'nodrag',
className,
{
source: !isTarget,
target: isTarget,
},
]);
const nodeIdWithHandleId = id ? `${nodeId}__${id}` : nodeId; const nodeIdWithHandleId = id ? `${nodeId}__${id}` : nodeId;
+2 -2
View File
@@ -1,5 +1,5 @@
import React, { memo, useContext } from 'react'; import React, { memo, useContext } from 'react';
import classnames from 'classnames'; import cc from 'classcat';
import { useStoreActions, useStoreState } from '../../store/hooks'; import { useStoreActions, useStoreState } from '../../store/hooks';
import BaseHandle from './BaseHandle'; import BaseHandle from './BaseHandle';
@@ -26,7 +26,7 @@ const Handle = memo(
onConnectAction(params); onConnectAction(params);
onConnect(params); onConnect(params);
}; };
const handleClasses = classnames(className, { connectable: isConnectable }); const handleClasses = cc([className, { connectable: isConnectable }]);
return ( return (
<BaseHandle <BaseHandle
+11 -5
View File
@@ -10,7 +10,8 @@ import React, {
useCallback, useCallback,
} from 'react'; } from 'react';
import { DraggableCore } from 'react-draggable'; import { DraggableCore } from 'react-draggable';
import cx from 'classnames'; import cc from 'classcat';
import { ResizeObserver } from 'resize-observer'; import { ResizeObserver } from 'resize-observer';
import { useStoreActions } from '../../store/hooks'; import { useStoreActions } from '../../store/hooks';
@@ -192,10 +193,15 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
const [offset, setOffset] = useState({ x: 0, y: 0 }); const [offset, setOffset] = useState({ x: 0, y: 0 });
const [isDragging, setDragging] = useState(false); const [isDragging, setDragging] = useState(false);
const position = { x: xPos, y: yPos }; const position = { x: xPos, y: yPos };
const nodeClasses = cx('react-flow__node', `react-flow__node-${type}`, className, { const nodeClasses = cc([
selected, 'react-flow__node',
selectable: isSelectable, `react-flow__node-${type}`,
}); className,
{
selected,
selectable: isSelectable,
},
]);
const node = { id, type, position, data }; const node = { id, type, position, data };
const onMouseEnterHandler = useMemo(() => { const onMouseEnterHandler = useMemo(() => {
if (!onMouseEnter || isDragging) { if (!onMouseEnter || isDragging) {
@@ -0,0 +1,56 @@
import React, { ReactNode } from 'react';
interface MarkerProps {
id: string;
children: ReactNode;
}
const Marker = ({ id, children }: MarkerProps) => (
<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;
}
const MarkerDefinitions = ({ color }: MarkerDefinitionsProps) => {
return (
<defs>
<Marker id="react-flow__arrowclosed">
<polyline
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>
);
};
MarkerDefinitions.displayName = 'MarkerDefinitions';
export default MarkerDefinitions;
+7 -1
View File
@@ -3,6 +3,7 @@ import React, { memo, CSSProperties } from 'react';
import { useStoreState } from '../../store/hooks'; import { useStoreState } from '../../store/hooks';
import ConnectionLine from '../../components/ConnectionLine/index'; import ConnectionLine from '../../components/ConnectionLine/index';
import { isEdge } from '../../utils/graph'; import { isEdge } from '../../utils/graph';
import MarkerDefinitions from './MarkerDefinitions';
import { XYPosition, Position, Edge, Node, ElementId, HandleElement, Elements, ConnectionLineType } from '../../types'; import { XYPosition, Position, Edge, Node, ElementId, HandleElement, Elements, ConnectionLineType } from '../../types';
interface EdgeRendererProps { interface EdgeRendererProps {
@@ -12,6 +13,8 @@ interface EdgeRendererProps {
connectionLineType: ConnectionLineType; connectionLineType: ConnectionLineType;
connectionLineStyle?: CSSProperties; connectionLineStyle?: CSSProperties;
onElementClick?: (element: Node | Edge) => void; onElementClick?: (element: Node | Edge) => void;
arrowHeadColor: string;
markerEndId?: string;
} }
interface EdgePositions { interface EdgePositions {
@@ -171,6 +174,7 @@ function renderEdge(
labelShowBg={edge.labelShowBg} labelShowBg={edge.labelShowBg}
labelBgStyle={edge.labelBgStyle} labelBgStyle={edge.labelBgStyle}
style={edge.style} style={edge.style}
arrowHeadType={edge.arrowHeadType}
source={sourceId} source={sourceId}
target={targetId} target={targetId}
sourceHandleId={sourceHandleId} sourceHandleId={sourceHandleId}
@@ -182,6 +186,7 @@ function renderEdge(
sourcePosition={sourcePosition} sourcePosition={sourcePosition}
targetPosition={targetPosition} targetPosition={targetPosition}
elementsSelectable={elementsSelectable} elementsSelectable={elementsSelectable}
markerEndId={props.markerEndId}
/> />
); );
} }
@@ -197,7 +202,7 @@ const EdgeRenderer = memo((props: EdgeRendererProps) => {
const nodesConnectable = useStoreState((s) => s.nodesConnectable); const nodesConnectable = useStoreState((s) => s.nodesConnectable);
const elementsSelectable = useStoreState((s) => s.elementsSelectable); const elementsSelectable = useStoreState((s) => s.elementsSelectable);
const { width, height, connectionLineStyle, connectionLineType } = props; const { width, height, connectionLineStyle, connectionLineType, arrowHeadColor } = props;
if (!width) { if (!width) {
return null; return null;
@@ -208,6 +213,7 @@ const EdgeRenderer = memo((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} />
<g transform={transformStyle}> <g transform={transformStyle}>
{edges.map((e: Edge) => renderEdge(e, props, nodes, selectedElements, elementsSelectable))} {edges.map((e: Edge) => renderEdge(e, props, nodes, selectedElements, elementsSelectable))}
{renderConnectionLine && ( {renderConnectionLine && (
+1 -4
View File
@@ -1,9 +1,6 @@
import { ComponentType } from 'react'; import { ComponentType } from 'react';
import StraightEdge from '../../components/Edges/StraightEdge'; import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../components/Edges';
import BezierEdge from '../../components/Edges/BezierEdge';
import StepEdge from '../../components/Edges/StepEdge';
import SmoothStepEdge from '../../components/Edges/SmoothStepEdge';
import wrapEdge from '../../components/Edges/wrapEdge'; import wrapEdge from '../../components/Edges/wrapEdge';
import { EdgeTypesType, EdgeCompProps } from '../../types'; import { EdgeTypesType, EdgeCompProps } from '../../types';
+9 -1
View File
@@ -52,6 +52,9 @@ export interface GraphViewProps {
minZoom: number; minZoom: number;
maxZoom: number; maxZoom: number;
defaultZoom: number; defaultZoom: number;
arrowHeadColor: string;
markerEndId?: string;
zoomOnScroll: boolean;
} }
const GraphView = memo( const GraphView = memo(
@@ -84,6 +87,9 @@ const GraphView = memo(
minZoom, minZoom,
maxZoom, maxZoom,
defaultZoom, defaultZoom,
arrowHeadColor,
markerEndId,
zoomOnScroll,
}: GraphViewProps) => { }: GraphViewProps) => {
const zoomPane = useRef<HTMLDivElement>(null); const zoomPane = useRef<HTMLDivElement>(null);
const rendererNode = useRef<HTMLDivElement>(null); const rendererNode = useRef<HTMLDivElement>(null);
@@ -154,7 +160,7 @@ const GraphView = memo(
}; };
}, []); }, []);
useD3Zoom({ zoomPane, onMove, selectionKeyPressed }); useD3Zoom({ zoomPane, onMove, selectionKeyPressed, zoomOnScroll });
useEffect(() => { useEffect(() => {
if (d3Initialised && onLoad) { if (d3Initialised && onLoad) {
@@ -212,6 +218,8 @@ const GraphView = memo(
onElementClick={onElementClick} onElementClick={onElementClick}
connectionLineType={connectionLineType} connectionLineType={connectionLineType}
connectionLineStyle={connectionLineStyle} connectionLineStyle={connectionLineStyle}
arrowHeadColor={arrowHeadColor}
markerEndId={markerEndId}
/> />
<UserSelection selectionKeyPressed={selectionKeyPressed} /> <UserSelection selectionKeyPressed={selectionKeyPressed} />
{nodesSelectionActive && <NodesSelection />} {nodesSelectionActive && <NodesSelection />}
+15 -6
View File
@@ -1,5 +1,5 @@
import React, { useMemo, CSSProperties, HTMLAttributes, MouseEvent } from 'react'; 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; const nodeEnv: string = process.env.NODE_ENV as string;
@@ -14,10 +14,7 @@ import InputNode from '../../components/Nodes/InputNode';
import OutputNode from '../../components/Nodes/OutputNode'; import OutputNode from '../../components/Nodes/OutputNode';
import { createNodeTypes } from '../NodeRenderer/utils'; import { createNodeTypes } from '../NodeRenderer/utils';
import SelectionListener from '../../components/SelectionListener'; import SelectionListener from '../../components/SelectionListener';
import BezierEdge from '../../components/Edges/BezierEdge'; import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../components/Edges';
import StraightEdge from '../../components/Edges/StraightEdge';
import StepEdge from '../../components/Edges/StepEdge';
import SmoothStepEdge from '../../components/Edges/SmoothStepEdge';
import { createEdgeTypes } from '../EdgeRenderer/utils'; import { createEdgeTypes } from '../EdgeRenderer/utils';
import Wrapper from './Wrapper'; import Wrapper from './Wrapper';
import { import {
@@ -63,6 +60,9 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
minZoom: number; minZoom: number;
maxZoom: number; maxZoom: number;
defaultZoom: number; defaultZoom: number;
arrowHeadColor: string;
markerEndId?: string;
zoomOnScroll: boolean
} }
const ReactFlow = ({ const ReactFlow = ({
@@ -98,12 +98,16 @@ const ReactFlow = ({
minZoom, minZoom,
maxZoom, maxZoom,
defaultZoom, defaultZoom,
arrowHeadColor,
markerEndId,
zoomOnScroll
}: ReactFlowProps) => { }: ReactFlowProps) => {
const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), []); const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), []);
const edgeTypesParsed = useMemo(() => createEdgeTypes(edgeTypes), []); const edgeTypesParsed = useMemo(() => createEdgeTypes(edgeTypes), []);
const reactFlowClasses = cc(['react-flow', className]);
return ( return (
<div style={style} className={cx('react-flow', className)}> <div style={style} className={reactFlowClasses}>
<Wrapper> <Wrapper>
<GraphView <GraphView
onLoad={onLoad} onLoad={onLoad}
@@ -134,6 +138,9 @@ const ReactFlow = ({
minZoom={minZoom} minZoom={minZoom}
maxZoom={maxZoom} maxZoom={maxZoom}
defaultZoom={defaultZoom} defaultZoom={defaultZoom}
arrowHeadColor={arrowHeadColor}
markerEndId={markerEndId}
zoomOnScroll={zoomOnScroll}
/> />
{onSelectionChange && <SelectionListener onSelectionChange={onSelectionChange} />} {onSelectionChange && <SelectionListener onSelectionChange={onSelectionChange} />}
{children} {children}
@@ -169,6 +176,8 @@ ReactFlow.defaultProps = {
minZoom: 0.5, minZoom: 0.5,
maxZoom: 2, maxZoom: 2,
defaultZoom: 1, defaultZoom: 1,
arrowHeadColor: '#bbb',
zoomOnScroll: true
}; };
export default ReactFlow; export default ReactFlow;
+7 -2
View File
@@ -6,10 +6,11 @@ import { useStoreState, useStoreActions } from '../store/hooks';
interface UseD3ZoomParams { interface UseD3ZoomParams {
zoomPane: MutableRefObject<Element | null>; zoomPane: MutableRefObject<Element | null>;
selectionKeyPressed: boolean; selectionKeyPressed: boolean;
zoomOnScroll: boolean;
onMove?: () => void; onMove?: () => void;
} }
export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): void => { export default ({ zoomPane, onMove, zoomOnScroll, selectionKeyPressed }: UseD3ZoomParams): void => {
const d3Zoom = useStoreState((s) => s.d3Zoom); const d3Zoom = useStoreState((s) => s.d3Zoom);
const initD3 = useStoreActions((actions) => actions.initD3); const initD3 = useStoreActions((actions) => actions.initD3);
@@ -31,6 +32,10 @@ export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): voi
return; return;
} }
if (!zoomOnScroll && event.sourceEvent.type === 'wheel') {
return;
}
updateTransform(event.transform); updateTransform(event.transform);
if (onMove) { if (onMove) {
@@ -39,5 +44,5 @@ export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): voi
}); });
} }
} }
}, [selectionKeyPressed, d3Zoom]); }, [selectionKeyPressed, zoomOnScroll, d3Zoom]);
}; };
+8
View File
@@ -42,6 +42,11 @@ export interface Node {
sourcePosition?: Position; sourcePosition?: Position;
} }
export enum ArrowHeadType {
Arrow = 'arrow',
ArrowClosed = 'arrowclosed',
}
export interface Edge { export interface Edge {
id: ElementId; id: ElementId;
type?: string; type?: string;
@@ -53,6 +58,7 @@ export interface Edge {
labelBgStyle?: CSSProperties; labelBgStyle?: CSSProperties;
style?: CSSProperties; style?: CSSProperties;
animated?: boolean; animated?: boolean;
arrowHeadType?: ArrowHeadType;
} }
export enum BackgroundVariant { export enum BackgroundVariant {
@@ -82,6 +88,8 @@ export interface EdgeProps {
labelShowBg?: boolean; labelShowBg?: boolean;
labelBgStyle?: CSSProperties; labelBgStyle?: CSSProperties;
style?: CSSProperties; style?: CSSProperties;
arrowHeadType?: ArrowHeadType;
markerEndId?: string;
} }
export interface EdgeBezierProps extends EdgeProps { export interface EdgeBezierProps extends EdgeProps {