feat(edges): add arrow head options

This commit is contained in:
moklick
2020-07-14 12:57:12 +02:00
parent 1b3f58486a
commit 0593db289a
13 changed files with 157 additions and 10 deletions
+3
View File
@@ -95,6 +95,7 @@ const BasicFlow = () => <ReactFlow elements={elements} />;
- `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).
+9 -1
View File
@@ -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' },
];
+2 -2
View File
@@ -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',
+6 -1
View File
@@ -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 (
<>
<path style={style} d={path} className="react-flow__edge-path" />
<path style={style} d={path} className="react-flow__edge-path" markerEnd={markerEnd} />
{text}
</>
);
+6 -1
View File
@@ -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 ? (
<EdgeText
x={centerX}
@@ -171,7 +176,7 @@ export default memo(
return (
<>
<path style={style} className="react-flow__edge-path" d={path} />
<path style={style} className="react-flow__edge-path" d={path} markerEnd={markerEnd} />
{text}
</>
);
+17 -2
View File
@@ -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 ? (
<EdgeText
x={centerX}
@@ -38,7 +53,7 @@ export default memo(
return (
<>
<path style={style} className="react-flow__edge-path" d={path} />
<path style={style} className="react-flow__edge-path" d={path} markerEnd={markerEnd} />
{text}
</>
);
+21 -2
View File
@@ -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 ? (
<EdgeText
@@ -24,7 +38,12 @@ export default memo(
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}
</>
);
+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';
};
@@ -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 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 (
<svg width={width} height={height} className="react-flow__edges">
<MarkerDefinitions color={arrowHeadColor} />
<g transform={transformStyle}>
{edges.map((e: Edge) => renderEdge(e, props, nodes, selectedElements, elementsSelectable))}
{renderConnectionLine && (
+6
View File
@@ -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<HTMLDivElement>(null);
const rendererNode = useRef<HTMLDivElement>(null);
@@ -212,6 +216,8 @@ const GraphView = memo(
onElementClick={onElementClick}
connectionLineType={connectionLineType}
connectionLineStyle={connectionLineStyle}
arrowHeadColor={arrowHeadColor}
markerEndId={markerEndId}
/>
<UserSelection selectionKeyPressed={selectionKeyPressed} />
{nodesSelectionActive && <NodesSelection />}
+7
View File
@@ -60,6 +60,8 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, '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 && <SelectionListener onSelectionChange={onSelectionChange} />}
{children}
@@ -167,6 +173,7 @@ ReactFlow.defaultProps = {
minZoom: 0.5,
maxZoom: 2,
defaultZoom: 1,
arrowHeadColor: '#bbb',
};
export default ReactFlow;
+8
View File
@@ -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 {