Merge branch 'next' into feat/dragedge
This commit is contained in:
@@ -4,16 +4,15 @@ import { useStoreState } from '../../store/hooks';
|
||||
import ConnectionLine from '../../components/ConnectionLine/index';
|
||||
import { isEdge } from '../../utils/graph';
|
||||
import MarkerDefinitions from './MarkerDefinitions';
|
||||
import { getEdgePositions, getHandle, isEdgeVisible, getSourceTargetNodes } from './utils';
|
||||
import {
|
||||
XYPosition,
|
||||
Position,
|
||||
Edge,
|
||||
Node,
|
||||
ElementId,
|
||||
HandleElement,
|
||||
Elements,
|
||||
ConnectionLineType,
|
||||
ConnectionLineComponent,
|
||||
Transform,
|
||||
OnEdgeUpdateFunc,
|
||||
Connection,
|
||||
} from '../../types';
|
||||
@@ -26,134 +25,32 @@ interface EdgeRendererProps {
|
||||
arrowHeadColor: string;
|
||||
markerEndId?: string;
|
||||
connectionLineComponent?: ConnectionLineComponent;
|
||||
onlyRenderVisibleElements: boolean;
|
||||
onEdgeUpdate?: OnEdgeUpdateFunc;
|
||||
}
|
||||
|
||||
interface EdgePositions {
|
||||
sourceX: number;
|
||||
sourceY: number;
|
||||
targetX: number;
|
||||
targetY: number;
|
||||
}
|
||||
|
||||
function getHandlePosition(position: Position, node: Node, handle: any | null = null): XYPosition {
|
||||
if (!handle) {
|
||||
switch (position) {
|
||||
case Position.Top:
|
||||
return {
|
||||
x: node.__rf.width / 2,
|
||||
y: 0,
|
||||
};
|
||||
case Position.Right:
|
||||
return {
|
||||
x: node.__rf.width,
|
||||
y: node.__rf.height / 2,
|
||||
};
|
||||
case Position.Bottom:
|
||||
return {
|
||||
x: node.__rf.width / 2,
|
||||
y: node.__rf.height,
|
||||
};
|
||||
case Position.Left:
|
||||
return {
|
||||
x: 0,
|
||||
y: node.__rf.height / 2,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
switch (position) {
|
||||
case Position.Top:
|
||||
return {
|
||||
x: handle.x + handle.width / 2,
|
||||
y: handle.y,
|
||||
};
|
||||
case Position.Right:
|
||||
return {
|
||||
x: handle.x + handle.width,
|
||||
y: handle.y + handle.height / 2,
|
||||
};
|
||||
case Position.Bottom:
|
||||
return {
|
||||
x: handle.x + handle.width / 2,
|
||||
y: handle.y + handle.height,
|
||||
};
|
||||
case Position.Left:
|
||||
return {
|
||||
x: handle.x,
|
||||
y: handle.y + handle.height / 2,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getHandle(bounds: HandleElement[], handleId: ElementId | null): HandleElement | null | undefined {
|
||||
let handle = null;
|
||||
|
||||
if (!bounds) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// there is no handleId when there are no multiple handles/ handles with ids
|
||||
// so we just pick the first one
|
||||
if (bounds.length === 1 || !handleId) {
|
||||
handle = bounds[0];
|
||||
} else if (handleId) {
|
||||
handle = bounds.find((d) => d.id === handleId);
|
||||
}
|
||||
|
||||
if (typeof handle === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
function getEdgePositions(
|
||||
sourceNode: Node,
|
||||
sourceHandle: HandleElement | unknown,
|
||||
sourcePosition: Position,
|
||||
targetNode: Node,
|
||||
targetHandle: HandleElement | unknown,
|
||||
targetPosition: Position
|
||||
): EdgePositions {
|
||||
const sourceHandlePos = getHandlePosition(sourcePosition, sourceNode, sourceHandle);
|
||||
const sourceX = sourceNode.__rf.position.x + sourceHandlePos.x;
|
||||
const sourceY = sourceNode.__rf.position.y + sourceHandlePos.y;
|
||||
|
||||
const targetHandlePos = getHandlePosition(targetPosition, targetNode, targetHandle);
|
||||
const targetX = targetNode.__rf.position.x + targetHandlePos.x;
|
||||
const targetY = targetNode.__rf.position.y + targetHandlePos.y;
|
||||
|
||||
return {
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
};
|
||||
}
|
||||
|
||||
function renderEdge(
|
||||
edge: Edge,
|
||||
props: EdgeRendererProps,
|
||||
nodes: Node[],
|
||||
selectedElements: Elements | null,
|
||||
elementsSelectable: boolean
|
||||
elementsSelectable: boolean,
|
||||
transform: Transform,
|
||||
width: number,
|
||||
height: number,
|
||||
onlyRenderVisibleElements: boolean
|
||||
) {
|
||||
const sourceId = edge.source;
|
||||
const sourceHandleId = edge.sourceHandle || null;
|
||||
const targetId = edge.target;
|
||||
const targetHandleId = edge.targetHandle || null;
|
||||
|
||||
const sourceNode = nodes.find((n) => n.id === sourceId);
|
||||
const targetNode = nodes.find((n) => n.id === targetId);
|
||||
const { sourceNode, targetNode } = getSourceTargetNodes(edge, nodes);
|
||||
|
||||
if (!sourceNode) {
|
||||
console.warn(`couldn't create edge for source id: ${sourceId}`);
|
||||
console.warn(`couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!targetNode) {
|
||||
console.warn(`couldn't create edge for target id: ${targetId}`);
|
||||
console.warn(`couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -169,12 +66,12 @@ function renderEdge(
|
||||
const targetPosition = targetHandle ? targetHandle.position : Position.Top;
|
||||
|
||||
if (!sourceHandle) {
|
||||
console.warn(`couldn't create edge for source handle id: ${sourceHandleId}`);
|
||||
console.warn(`couldn't create edge for source handle id: ${sourceHandleId}; edge id: ${edge.id}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!targetHandle) {
|
||||
console.warn(`couldn't create edge for source handle id: ${targetHandleId}`);
|
||||
console.warn(`couldn't create edge for target handle id: ${targetHandleId}; edge id: ${edge.id}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -187,7 +84,21 @@ function renderEdge(
|
||||
targetPosition
|
||||
);
|
||||
|
||||
const isSelected = selectedElements ? selectedElements.some((elm) => isEdge(elm) && elm.id === edge.id) : false;
|
||||
const isVisible = onlyRenderVisibleElements
|
||||
? isEdgeVisible({
|
||||
sourcePos: { x: sourceX, y: sourceY },
|
||||
targetPos: { x: targetX, y: targetY },
|
||||
width,
|
||||
height,
|
||||
transform,
|
||||
})
|
||||
: true;
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isSelected = selectedElements?.some((elm) => isEdge(elm) && elm.id === edge.id) || false;
|
||||
|
||||
const onConnectEdge = (connection: Connection) => {
|
||||
props.onEdgeUpdate?.(edge, connection);
|
||||
@@ -231,9 +142,8 @@ function renderEdge(
|
||||
}
|
||||
|
||||
const EdgeRenderer = (props: EdgeRendererProps) => {
|
||||
const [tX, tY, tScale] = useStoreState((state) => state.transform);
|
||||
const transform = useStoreState((state) => state.transform);
|
||||
const edges = useStoreState((state) => state.edges);
|
||||
const nodes = useStoreState((state) => state.nodes);
|
||||
const connectionNodeId = useStoreState((state) => state.connectionNodeId);
|
||||
const connectionHandleId = useStoreState((state) => state.connectionHandleId);
|
||||
const connectionHandleType = useStoreState((state) => state.connectionHandleType);
|
||||
@@ -243,21 +153,39 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
|
||||
const elementsSelectable = useStoreState((state) => state.elementsSelectable);
|
||||
const width = useStoreState((state) => state.width);
|
||||
const height = useStoreState((state) => state.height);
|
||||
|
||||
const { connectionLineType, arrowHeadColor, connectionLineStyle, connectionLineComponent } = props;
|
||||
const nodes = useStoreState((state) => state.nodes);
|
||||
|
||||
if (!width) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const transformStyle = `translate(${tX},${tY}) scale(${tScale})`;
|
||||
const {
|
||||
connectionLineType,
|
||||
arrowHeadColor,
|
||||
connectionLineStyle,
|
||||
connectionLineComponent,
|
||||
onlyRenderVisibleElements,
|
||||
} = props;
|
||||
const transformStyle = `translate(${transform[0]},${transform[1]}) scale(${transform[2]})`;
|
||||
const renderConnectionLine = connectionNodeId && connectionHandleType;
|
||||
|
||||
return (
|
||||
<svg width={width} height={height} className="react-flow__edges">
|
||||
<MarkerDefinitions color={arrowHeadColor} />
|
||||
<g transform={transformStyle}>
|
||||
{edges.map((edge: Edge) => renderEdge(edge, props, nodes, selectedElements, elementsSelectable))}
|
||||
{edges.map((edge: Edge) =>
|
||||
renderEdge(
|
||||
edge,
|
||||
props,
|
||||
nodes,
|
||||
selectedElements,
|
||||
elementsSelectable,
|
||||
transform,
|
||||
width,
|
||||
height,
|
||||
onlyRenderVisibleElements
|
||||
)
|
||||
)}
|
||||
{renderConnectionLine && (
|
||||
<ConnectionLine
|
||||
nodes={nodes}
|
||||
@@ -266,7 +194,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
|
||||
connectionHandleType={connectionHandleType!}
|
||||
connectionPositionX={connectionPosition.x}
|
||||
connectionPositionY={connectionPosition.y}
|
||||
transform={[tX, tY, tScale]}
|
||||
transform={transform}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
connectionLineType={connectionLineType}
|
||||
isConnectable={nodesConnectable}
|
||||
|
||||
@@ -2,8 +2,19 @@ import { ComponentType } from 'react';
|
||||
|
||||
import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../components/Edges';
|
||||
import wrapEdge from '../../components/Edges/wrapEdge';
|
||||
import { rectToBox } from '../../utils/graph';
|
||||
|
||||
import { EdgeTypesType, EdgeProps } from '../../types';
|
||||
import {
|
||||
EdgeTypesType,
|
||||
EdgeProps,
|
||||
Position,
|
||||
Node,
|
||||
XYPosition,
|
||||
ElementId,
|
||||
HandleElement,
|
||||
Transform,
|
||||
Edge,
|
||||
} from '../../types';
|
||||
|
||||
export function createEdgeTypes(edgeTypes: EdgeTypesType): EdgeTypesType {
|
||||
const standardTypes: EdgeTypesType = {
|
||||
@@ -27,3 +38,133 @@ export function createEdgeTypes(edgeTypes: EdgeTypesType): EdgeTypesType {
|
||||
...specialTypes,
|
||||
};
|
||||
}
|
||||
|
||||
export function getHandlePosition(position: Position, node: Node, handle: any | null = null): XYPosition {
|
||||
const x = (handle?.x || 0) + node.__rf.position.x;
|
||||
const y = (handle?.y || 0) + node.__rf.position.y;
|
||||
const width = handle?.width || node.__rf.width;
|
||||
const height = handle?.height || node.__rf.height;
|
||||
|
||||
switch (position) {
|
||||
case Position.Top:
|
||||
return {
|
||||
x: x + width / 2,
|
||||
y,
|
||||
};
|
||||
case Position.Right:
|
||||
return {
|
||||
x: x + width,
|
||||
y: y + height / 2,
|
||||
};
|
||||
case Position.Bottom:
|
||||
return {
|
||||
x: x + width / 2,
|
||||
y: y + height,
|
||||
};
|
||||
case Position.Left:
|
||||
return {
|
||||
x,
|
||||
y: y + height / 2,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function getHandle(bounds: HandleElement[], handleId: ElementId | null): HandleElement | null {
|
||||
if (!bounds) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// there is no handleId when there are no multiple handles/ handles with ids
|
||||
// so we just pick the first one
|
||||
let handle = null;
|
||||
if (bounds.length === 1 || !handleId) {
|
||||
handle = bounds[0];
|
||||
} else if (handleId) {
|
||||
handle = bounds.find((d) => d.id === handleId);
|
||||
}
|
||||
|
||||
return typeof handle === 'undefined' ? null : handle;
|
||||
}
|
||||
|
||||
interface EdgePositions {
|
||||
sourceX: number;
|
||||
sourceY: number;
|
||||
targetX: number;
|
||||
targetY: number;
|
||||
}
|
||||
|
||||
export const getEdgePositions = (
|
||||
sourceNode: Node,
|
||||
sourceHandle: HandleElement | unknown,
|
||||
sourcePosition: Position,
|
||||
targetNode: Node,
|
||||
targetHandle: HandleElement | unknown,
|
||||
targetPosition: Position
|
||||
): EdgePositions => {
|
||||
const sourceHandlePos = getHandlePosition(sourcePosition, sourceNode, sourceHandle);
|
||||
const targetHandlePos = getHandlePosition(targetPosition, targetNode, targetHandle);
|
||||
|
||||
return {
|
||||
sourceX: sourceHandlePos.x,
|
||||
sourceY: sourceHandlePos.y,
|
||||
targetX: targetHandlePos.x,
|
||||
targetY: targetHandlePos.y,
|
||||
};
|
||||
};
|
||||
|
||||
interface IsEdgeVisibleParams {
|
||||
sourcePos: XYPosition;
|
||||
targetPos: XYPosition;
|
||||
width: number;
|
||||
height: number;
|
||||
transform: Transform;
|
||||
}
|
||||
|
||||
export function isEdgeVisible({ sourcePos, targetPos, width, height, transform }: IsEdgeVisibleParams): boolean {
|
||||
const edgeBox = {
|
||||
x: Math.min(sourcePos.x, targetPos.x),
|
||||
y: Math.min(sourcePos.y, targetPos.y),
|
||||
x2: Math.max(sourcePos.x, targetPos.x),
|
||||
y2: Math.max(sourcePos.y, targetPos.y),
|
||||
};
|
||||
|
||||
if (edgeBox.x === edgeBox.x2) {
|
||||
edgeBox.x2 += 1;
|
||||
}
|
||||
|
||||
if (edgeBox.y === edgeBox.y2) {
|
||||
edgeBox.y2 += 1;
|
||||
}
|
||||
|
||||
const viewBox = rectToBox({
|
||||
x: (0 - transform[0]) / transform[2],
|
||||
y: (0 - transform[1]) / transform[2],
|
||||
width: width / transform[2],
|
||||
height: height / transform[2],
|
||||
});
|
||||
|
||||
const xOverlap = Math.max(0, Math.min(viewBox.x2, edgeBox.x2) - Math.max(viewBox.x, edgeBox.x));
|
||||
const yOverlap = Math.max(0, Math.min(viewBox.y2, edgeBox.y2) - Math.max(viewBox.y, edgeBox.y));
|
||||
const overlappingArea = Math.ceil(xOverlap * yOverlap);
|
||||
|
||||
return overlappingArea > 0;
|
||||
}
|
||||
|
||||
type SourceTargetNode = {
|
||||
sourceNode: Node | null;
|
||||
targetNode: Node | null;
|
||||
};
|
||||
|
||||
export const getSourceTargetNodes = (edge: Edge, nodes: Node[]): SourceTargetNode => {
|
||||
return nodes.reduce(
|
||||
(res, node) => {
|
||||
if (node.id === edge.source) {
|
||||
res.sourceNode = node;
|
||||
} else if (node.id === edge.target) {
|
||||
res.targetNode = node;
|
||||
}
|
||||
return res;
|
||||
},
|
||||
{ sourceNode: null, targetNode: null } as SourceTargetNode
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user