refactor(edges): visible even if source and target are not visible #677

This commit is contained in:
moklick
2020-11-15 14:15:35 +01:00
parent 3d9d05243e
commit b0500bc208
3 changed files with 83 additions and 22 deletions
+41 -19
View File
@@ -4,7 +4,7 @@ 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 MarkerDefinitions from './MarkerDefinitions';
import { getHandlePosition, getHandle } from './utils'; import { getHandlePosition, getHandle, isEdgeVisible } from './utils';
import { import {
Position, Position,
Edge, Edge,
@@ -13,6 +13,7 @@ import {
Elements, Elements,
ConnectionLineType, ConnectionLineType,
ConnectionLineComponent, ConnectionLineComponent,
Transform,
} from '../../types'; } from '../../types';
interface EdgeRendererProps { 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( function renderEdge(
edge: Edge, edge: Edge,
props: EdgeRendererProps, props: EdgeRendererProps,
visibleNodes: Node[],
nodes: Node[], nodes: Node[],
selectedElements: Elements | null, selectedElements: Elements | null,
elementsSelectable: boolean elementsSelectable: boolean,
transform: Transform,
width: number,
height: number
) { ) {
const sourceHandleId = edge.sourceHandle || null; const sourceHandleId = edge.sourceHandle || null;
const targetHandleId = edge.targetHandle || null; const targetHandleId = edge.targetHandle || null;
const { sourceNode, targetNode } = nodes.reduce((res, node) => {
const sourceNode = nodes.find((n) => n.id === edge.source); if (node.id === edge.source) {
const targetNode = nodes.find((n) => n.id === edge.target); res.sourceNode = node;
const renderEdge = visibleNodes.some((n) => n.id === edge.source || n.id == edge.target); } else if (node.id === edge.target) {
res.targetNode = node;
if (!renderEdge) { }
return null; return res;
} }, initialSourceTarget);
if (!sourceNode) { if (!sourceNode) {
console.warn(`couldn't create edge for source id: ${edge.source}`); console.warn(`couldn't create edge for source id: ${edge.source}`);
@@ -89,6 +99,18 @@ function renderEdge(
return null; 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 edgeType = edge.type || 'default';
const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default; const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default;
const sourceHandle = getHandle(sourceNode.__rf.handleBounds.source, sourceHandleId); const sourceHandle = getHandle(sourceNode.__rf.handleBounds.source, sourceHandleId);
@@ -153,7 +175,7 @@ function renderEdge(
} }
const EdgeRenderer = (props: EdgeRendererProps) => { 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 edges = useStoreState((state) => state.edges);
const connectionNodeId = useStoreState((state) => state.connectionNodeId); const connectionNodeId = useStoreState((state) => state.connectionNodeId);
const connectionHandleId = useStoreState((state) => state.connectionHandleId); const connectionHandleId = useStoreState((state) => state.connectionHandleId);
@@ -164,32 +186,32 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
const elementsSelectable = useStoreState((state) => state.elementsSelectable); const elementsSelectable = useStoreState((state) => state.elementsSelectable);
const width = useStoreState((state) => state.width); const width = useStoreState((state) => state.width);
const height = useStoreState((state) => state.height); const height = useStoreState((state) => state.height);
const visibleNodes = useStoreState((state) => state.visibleNodes);
const nodes = useStoreState((state) => state.nodes); const nodes = useStoreState((state) => state.nodes);
const { connectionLineType, arrowHeadColor, connectionLineStyle, connectionLineComponent } = props;
if (!width) { if (!width) {
return null; 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; const renderConnectionLine = connectionNodeId && connectionHandleType;
return ( return (
<svg width={width} height={height} className="react-flow__edges"> <svg width={width} height={height} className="react-flow__edges">
<MarkerDefinitions color={arrowHeadColor} /> <MarkerDefinitions color={arrowHeadColor} />
<g transform={transformStyle}> <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 && ( {renderConnectionLine && (
<ConnectionLine <ConnectionLine
nodes={visibleNodes} nodes={nodes}
connectionNodeId={connectionNodeId!} connectionNodeId={connectionNodeId!}
connectionHandleId={connectionHandleId} connectionHandleId={connectionHandleId}
connectionHandleType={connectionHandleType!} connectionHandleType={connectionHandleType!}
connectionPositionX={connectionPosition.x} connectionPositionX={connectionPosition.x}
connectionPositionY={connectionPosition.y} connectionPositionY={connectionPosition.y}
transform={[tX, tY, tScale]} transform={transform}
connectionLineStyle={connectionLineStyle} connectionLineStyle={connectionLineStyle}
connectionLineType={connectionLineType} connectionLineType={connectionLineType}
isConnectable={nodesConnectable} isConnectable={nodesConnectable}
+40 -1
View File
@@ -2,8 +2,9 @@ import { ComponentType } from 'react';
import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../components/Edges'; import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../components/Edges';
import wrapEdge from '../../components/Edges/wrapEdge'; 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 { export function createEdgeTypes(edgeTypes: EdgeTypesType): EdgeTypesType {
const standardTypes: EdgeTypesType = { const standardTypes: EdgeTypesType = {
@@ -79,3 +80,41 @@ export function getHandle(bounds: HandleElement[], handleId: ElementId | null):
return handle; 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
View File
@@ -154,14 +154,14 @@ const getBoundsOfBoxes = (box1: Box, box2: Box): Box => ({
y2: Math.max(box1.y2, box2.y2), 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, x,
y, y,
x2: x + width, x2: x + width,
y2: y + height, y2: y + height,
}); });
const boxToRect = ({ x, y, x2, y2 }: Box): Rect => ({ export const boxToRect = ({ x, y, x2, y2 }: Box): Rect => ({
x, x,
y, y,
width: x2 - x, width: x2 - x,