refactor(edges): visible even if source and target are not visible #677
This commit is contained in:
@@ -4,7 +4,7 @@ import { useStoreState } from '../../store/hooks';
|
||||
import ConnectionLine from '../../components/ConnectionLine/index';
|
||||
import { isEdge } from '../../utils/graph';
|
||||
import MarkerDefinitions from './MarkerDefinitions';
|
||||
import { getHandlePosition, getHandle } from './utils';
|
||||
import { getHandlePosition, getHandle, isEdgeVisible } from './utils';
|
||||
import {
|
||||
Position,
|
||||
Edge,
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
Elements,
|
||||
ConnectionLineType,
|
||||
ConnectionLineComponent,
|
||||
Transform,
|
||||
} from '../../types';
|
||||
|
||||
interface EdgeRendererProps {
|
||||
@@ -56,24 +57,33 @@ function getEdgePositions(
|
||||
};
|
||||
}
|
||||
|
||||
type SourceTargetNode = {
|
||||
sourceNode: Node | null;
|
||||
targetNode: Node | null;
|
||||
};
|
||||
|
||||
const initialSourceTarget: SourceTargetNode = { sourceNode: null, targetNode: null };
|
||||
|
||||
function renderEdge(
|
||||
edge: Edge,
|
||||
props: EdgeRendererProps,
|
||||
visibleNodes: Node[],
|
||||
nodes: Node[],
|
||||
selectedElements: Elements | null,
|
||||
elementsSelectable: boolean
|
||||
elementsSelectable: boolean,
|
||||
transform: Transform,
|
||||
width: number,
|
||||
height: number
|
||||
) {
|
||||
const sourceHandleId = edge.sourceHandle || null;
|
||||
const targetHandleId = edge.targetHandle || null;
|
||||
|
||||
const sourceNode = nodes.find((n) => n.id === edge.source);
|
||||
const targetNode = nodes.find((n) => n.id === edge.target);
|
||||
const renderEdge = visibleNodes.some((n) => n.id === edge.source || n.id == edge.target);
|
||||
|
||||
if (!renderEdge) {
|
||||
return null;
|
||||
}
|
||||
const { sourceNode, targetNode } = nodes.reduce((res, node) => {
|
||||
if (node.id === edge.source) {
|
||||
res.sourceNode = node;
|
||||
} else if (node.id === edge.target) {
|
||||
res.targetNode = node;
|
||||
}
|
||||
return res;
|
||||
}, initialSourceTarget);
|
||||
|
||||
if (!sourceNode) {
|
||||
console.warn(`couldn't create edge for source id: ${edge.source}`);
|
||||
@@ -89,6 +99,18 @@ function renderEdge(
|
||||
return null;
|
||||
}
|
||||
|
||||
const renderEdge = isEdgeVisible({
|
||||
sourcePos: sourceNode.__rf.position,
|
||||
targetPos: targetNode.__rf.position,
|
||||
width,
|
||||
height,
|
||||
transform,
|
||||
});
|
||||
|
||||
if (!renderEdge) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const edgeType = edge.type || 'default';
|
||||
const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default;
|
||||
const sourceHandle = getHandle(sourceNode.__rf.handleBounds.source, sourceHandleId);
|
||||
@@ -153,7 +175,7 @@ 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 connectionNodeId = useStoreState((state) => state.connectionNodeId);
|
||||
const connectionHandleId = useStoreState((state) => state.connectionHandleId);
|
||||
@@ -164,32 +186,32 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
|
||||
const elementsSelectable = useStoreState((state) => state.elementsSelectable);
|
||||
const width = useStoreState((state) => state.width);
|
||||
const height = useStoreState((state) => state.height);
|
||||
const visibleNodes = useStoreState((state) => state.visibleNodes);
|
||||
const nodes = useStoreState((state) => state.nodes);
|
||||
|
||||
const { connectionLineType, arrowHeadColor, connectionLineStyle, connectionLineComponent } = props;
|
||||
|
||||
if (!width) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const transformStyle = `translate(${tX},${tY}) scale(${tScale})`;
|
||||
const { connectionLineType, arrowHeadColor, connectionLineStyle, connectionLineComponent } = 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, visibleNodes, nodes, selectedElements, elementsSelectable))}
|
||||
{edges.map((edge: Edge) =>
|
||||
renderEdge(edge, props, nodes, selectedElements, elementsSelectable, transform, width, height)
|
||||
)}
|
||||
{renderConnectionLine && (
|
||||
<ConnectionLine
|
||||
nodes={visibleNodes}
|
||||
nodes={nodes}
|
||||
connectionNodeId={connectionNodeId!}
|
||||
connectionHandleId={connectionHandleId}
|
||||
connectionHandleType={connectionHandleType!}
|
||||
connectionPositionX={connectionPosition.x}
|
||||
connectionPositionY={connectionPosition.y}
|
||||
transform={[tX, tY, tScale]}
|
||||
transform={transform}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
connectionLineType={connectionLineType}
|
||||
isConnectable={nodesConnectable}
|
||||
|
||||
@@ -2,8 +2,9 @@ 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, Position, Node, XYPosition, ElementId, HandleElement } from '../../types';
|
||||
import { EdgeTypesType, EdgeProps, Position, Node, XYPosition, ElementId, HandleElement, Transform } from '../../types';
|
||||
|
||||
export function createEdgeTypes(edgeTypes: EdgeTypesType): EdgeTypesType {
|
||||
const standardTypes: EdgeTypesType = {
|
||||
@@ -79,3 +80,41 @@ export function getHandle(bounds: HandleElement[], handleId: ElementId | null):
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
+2
-2
@@ -154,14 +154,14 @@ const getBoundsOfBoxes = (box1: Box, box2: Box): Box => ({
|
||||
y2: Math.max(box1.y2, box2.y2),
|
||||
});
|
||||
|
||||
const rectToBox = ({ x, y, width, height }: Rect): Box => ({
|
||||
export const rectToBox = ({ x, y, width, height }: Rect): Box => ({
|
||||
x,
|
||||
y,
|
||||
x2: x + width,
|
||||
y2: y + height,
|
||||
});
|
||||
|
||||
const boxToRect = ({ x, y, x2, y2 }: Box): Rect => ({
|
||||
export const boxToRect = ({ x, y, x2, y2 }: Box): Rect => ({
|
||||
x,
|
||||
y,
|
||||
width: x2 - x,
|
||||
|
||||
Reference in New Issue
Block a user