diff --git a/packages/react/src/hooks/useVisibleEdges.ts b/packages/react/src/hooks/useVisibleEdges.ts index 10a387fb..bb1ac052 100644 --- a/packages/react/src/hooks/useVisibleEdges.ts +++ b/packages/react/src/hooks/useVisibleEdges.ts @@ -9,30 +9,25 @@ function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boole const edges = useStore( useCallback( (s: ReactFlowState) => { - const visibleEdges = onlyRenderVisible - ? s.edges.filter((e) => { - const sourceNode = s.nodeInternals.get(e.source); - const targetNode = s.nodeInternals.get(e.target); + const visibleEdges = + onlyRenderVisible && s.width && s.height + ? s.edges.filter((e) => { + const sourceNode = s.nodeInternals.get(e.source); + const targetNode = s.nodeInternals.get(e.target); - return ( - sourceNode?.width && - sourceNode?.height && - targetNode?.width && - targetNode?.height && - isEdgeVisible({ - sourcePos: sourceNode.positionAbsolute || { x: 0, y: 0 }, - targetPos: targetNode.positionAbsolute || { x: 0, y: 0 }, - sourceWidth: sourceNode.width, - sourceHeight: sourceNode.height, - targetWidth: targetNode.width, - targetHeight: targetNode.height, - width: s.width, - height: s.height, - transform: s.transform, - }) - ); - }) - : s.edges; + return ( + sourceNode && + targetNode && + isEdgeVisible({ + sourceNode, + targetNode, + width: s.width, + height: s.height, + transform: s.transform, + }) + ); + }) + : s.edges; return groupEdgesByZLevel(visibleEdges, s.nodeInternals, elevateEdgesOnSelect); }, diff --git a/packages/svelte/src/lib/store/edge-tree.ts b/packages/svelte/src/lib/store/edge-tree.ts index 411a233f..c4b3f53d 100644 --- a/packages/svelte/src/lib/store/edge-tree.ts +++ b/packages/svelte/src/lib/store/edge-tree.ts @@ -15,30 +15,25 @@ export function getEdgeTree(store: SvelteFlowStoreState, onError: OnError) { store.height ], ([edges, nodes, onlyRenderVisibleElements, transform, width, height]) => { - const visibleEdges = onlyRenderVisibleElements - ? edges.filter((edge) => { - const sourceNode = nodes.find((node) => node.id === edge.source); - const targetNode = nodes.find((node) => node.id === edge.target); + const visibleEdges = + onlyRenderVisibleElements && width && height + ? edges.filter((edge) => { + const sourceNode = nodes.find((node) => node.id === edge.source); + const targetNode = nodes.find((node) => node.id === edge.target); - return ( - sourceNode?.width && - sourceNode?.height && - targetNode?.width && - targetNode?.height && - isEdgeVisible({ - sourcePos: sourceNode.positionAbsolute || { x: 0, y: 0 }, - targetPos: targetNode.positionAbsolute || { x: 0, y: 0 }, - sourceWidth: sourceNode.width, - sourceHeight: sourceNode.height, - targetWidth: targetNode.width, - targetHeight: targetNode.height, - width, - height, - transform - }) - ); - }) - : edges; + return ( + sourceNode && + targetNode && + isEdgeVisible({ + sourceNode, + targetNode, + width, + height, + transform + }) + ); + }) + : edges; return visibleEdges; } diff --git a/packages/system/src/utils/edges/general.ts b/packages/system/src/utils/edges/general.ts index e27a2043..5fa63349 100644 --- a/packages/system/src/utils/edges/general.ts +++ b/packages/system/src/utils/edges/general.ts @@ -1,14 +1,6 @@ import { Transform, internalsSymbol } from '../..'; -import { - Position, - type HandleElement, - type MarkerType, - type Rect, - type XYPosition, - BaseEdge, - BaseNode, -} from '../../types'; -import { isNumeric, rectToBox } from '../utils'; +import { type MarkerType, BaseEdge, BaseNode } from '../../types'; +import { isNumeric, getOverlappingArea, boxToRect, nodeToBox, getBoundsOfBoxes } from '../utils'; // this is used for straight edges and simple smoothstep edges (LTR, RTL, BTT, TTB) export function getEdgeCenter({ @@ -39,50 +31,6 @@ export const getMarkerEnd = (markerType?: MarkerType, markerEndId?: string): str return typeof markerType !== 'undefined' ? `url(#react-flow__${markerType})` : 'none'; }; -export function getHandlePosition(position: Position, nodeRect: Rect, handle: HandleElement | null = null): XYPosition { - const x = (handle?.x || 0) + nodeRect.x; - const y = (handle?.y || 0) + nodeRect.y; - const width = handle?.width || nodeRect.width; - const height = handle?.height || nodeRect.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?: string | null): HandleElement | null { - if (!bounds) { - return null; - } - - if (bounds.length === 1 || !handleId) { - return bounds[0]; - } else if (handleId) { - return bounds.find((d) => d.id === handleId) || null; - } - - return null; -} - const defaultEdgeTree = [{ level: 0, isMaxLevel: true, edges: [] }]; export type GroupedEdges = { @@ -144,34 +92,15 @@ export function groupEdgesByZLevel( } type IsEdgeVisibleParams = { - sourcePos: XYPosition; - targetPos: XYPosition; - sourceWidth: number; - sourceHeight: number; - targetWidth: number; - targetHeight: number; + sourceNode: BaseNode; + targetNode: BaseNode; width: number; height: number; transform: Transform; }; -export function isEdgeVisible({ - sourcePos, - targetPos, - sourceWidth, - sourceHeight, - targetWidth, - targetHeight, - 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 + sourceWidth, targetPos.x + targetWidth), - y2: Math.max(sourcePos.y + sourceHeight, targetPos.y + targetHeight), - }; +export function isEdgeVisible({ sourceNode, targetNode, width, height, transform }: IsEdgeVisibleParams): boolean { + const edgeBox = getBoundsOfBoxes(nodeToBox(sourceNode), nodeToBox(targetNode)); if (edgeBox.x === edgeBox.x2) { edgeBox.x2 += 1; @@ -181,16 +110,12 @@ export function isEdgeVisible({ edgeBox.y2 += 1; } - const viewBox = rectToBox({ - x: (0 - transform[0]) / transform[2], - y: (0 - transform[1]) / transform[2], + const viewRect = { + x: -transform[0] / transform[2], + y: -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; + return getOverlappingArea(viewRect, boxToRect(edgeBox)) > 0; } diff --git a/packages/system/src/utils/edges/positions.ts b/packages/system/src/utils/edges/positions.ts index ff506799..f4354882 100644 --- a/packages/system/src/utils/edges/positions.ts +++ b/packages/system/src/utils/edges/positions.ts @@ -1,31 +1,9 @@ import { EdgePosition } from '../../types/edges'; import { ConnectionMode, OnError } from '../../types/general'; import { BaseNode, NodeHandleBounds } from '../../types/nodes'; -import { Position, Rect } from '../../types/utils'; +import { Position, Rect, XYPosition } from '../../types/utils'; import { errorMessages, internalsSymbol } from '../../constants'; -import { getHandle, getHandlePosition } from './general'; - -export function getHandleDataByNode(node?: BaseNode): [Rect, NodeHandleBounds | null, boolean] { - const handleBounds = node?.[internalsSymbol]?.handleBounds || null; - - const isValid = - handleBounds && - node?.width && - node?.height && - typeof node?.positionAbsolute?.x !== 'undefined' && - typeof node?.positionAbsolute?.y !== 'undefined'; - - return [ - { - x: node?.positionAbsolute?.x || 0, - y: node?.positionAbsolute?.y || 0, - width: node?.width || 0, - height: node?.height || 0, - }, - handleBounds, - !!isValid, - ]; -} +import { HandleElement } from '../../types'; export type GetEdgePositionParams = { id: string; @@ -80,3 +58,69 @@ export function getEdgePosition(params: GetEdgePositionParams): EdgePosition | n targetPosition, }; } + +function getHandleDataByNode(node?: BaseNode): [Rect, NodeHandleBounds | null, boolean] { + const handleBounds = node?.[internalsSymbol]?.handleBounds || null; + + const isValid = + handleBounds && + node?.width && + node?.height && + typeof node?.positionAbsolute?.x !== 'undefined' && + typeof node?.positionAbsolute?.y !== 'undefined'; + + return [ + { + x: node?.positionAbsolute?.x || 0, + y: node?.positionAbsolute?.y || 0, + width: node?.width || 0, + height: node?.height || 0, + }, + handleBounds, + !!isValid, + ]; +} + +function getHandlePosition(position: Position, nodeRect: Rect, handle: HandleElement | null = null): XYPosition { + const x = (handle?.x || 0) + nodeRect.x; + const y = (handle?.y || 0) + nodeRect.y; + const width = handle?.width || nodeRect.width; + const height = handle?.height || nodeRect.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, + }; + } +} + +function getHandle(bounds: HandleElement[], handleId?: string | null): HandleElement | null { + if (!bounds) { + return null; + } + + if (bounds.length === 1 || !handleId) { + return bounds[0]; + } else if (handleId) { + return bounds.find((d) => d.id === handleId) || null; + } + + return null; +} diff --git a/packages/system/src/utils/graph.ts b/packages/system/src/utils/graph.ts index db0445ce..dda06687 100644 --- a/packages/system/src/utils/graph.ts +++ b/packages/system/src/utils/graph.ts @@ -8,6 +8,7 @@ import { getOverlappingArea, isNumeric, rectToBox, + nodeToRect, } from './utils'; import { type Connection, @@ -248,17 +249,8 @@ export const getNodesInside = ( return res; } - const { positionAbsolute } = getNodePositionWithOrigin(node, node.origin || nodeOrigin); - - const nodeRect = { - x: positionAbsolute.x, - y: positionAbsolute.y, - width: width || 0, - height: height || 0, - }; - const overlappingArea = getOverlappingArea(paneRect, nodeRect); - const notInitialized = - typeof width === 'undefined' || typeof height === 'undefined' || width === null || height === null; + const overlappingArea = getOverlappingArea(paneRect, nodeToRect(node, nodeOrigin)); + const notInitialized = width === undefined || height === undefined || width === null || height === null; const partiallyVisible = partially && overlappingArea > 0; const area = (width || 0) * (height || 0); @@ -374,53 +366,49 @@ export function calcNextPosition( onError?: OnError ): { position: XYPosition; positionAbsolute: XYPosition } { let currentExtent = node.extent || nodeExtent; + let parentNode: NodeType; + let parentPos = { x: 0, y: 0 }; + + if (node.parentNode) { + parentNode = nodes.find((n) => n.id === node.parentNode); + parentPos = parentNode + ? getNodePositionWithOrigin(parentNode, parentNode.origin || nodeOrigin).positionAbsolute + : parentPos; + } if (node.extent === 'parent') { if (node.parentNode && node.width && node.height) { - const parent = nodes.find((n) => n.id === node.parentNode); - const parentOrigin = parent?.origin || nodeOrigin; const currNodeOrigin = node.origin || nodeOrigin; - const { x: parentX, y: parentY } = getNodePositionWithOrigin(parent, parentOrigin).positionAbsolute; currentExtent = - parent && isNumeric(parentX) && isNumeric(parentY) && isNumeric(parent.width) && isNumeric(parent.height) + parentNode && isNumeric(parentNode.width) && isNumeric(parentNode.height) ? [ - [parentX + node.width * currNodeOrigin[0], parentY + node.height * currNodeOrigin[1]], + [parentPos.x + node.width * currNodeOrigin[0], parentPos.y + node.height * currNodeOrigin[1]], [ - parentX + parent.width - node.width + node.width * currNodeOrigin[0], - parentY + parent.height - node.height + node.height * currNodeOrigin[1], + parentPos.x + parentNode.width - node.width + node.width * currNodeOrigin[0], + parentPos.y + parentNode.height - node.height + node.height * currNodeOrigin[1], ], ] : currentExtent; } else { onError?.('005', errorMessages['error005']()); - currentExtent = nodeExtent; } } else if (node.extent && node.parentNode) { - const parent = nodes.find((n) => n.id === node.parentNode); - const { x: parentX, y: parentY } = getNodePositionWithOrigin(parent, parent?.origin || nodeOrigin).positionAbsolute; currentExtent = [ - [node.extent[0][0] + parentX, node.extent[0][1] + parentY], - [node.extent[1][0] + parentX, node.extent[1][1] + parentY], + [node.extent[0][0] + parentPos.x, node.extent[0][1] + parentPos.y], + [node.extent[1][0] + parentPos.x, node.extent[1][1] + parentPos.y], ]; } - let parentPosition = { x: 0, y: 0 }; - - if (node.parentNode) { - const parentNode = nodes.find((n) => n.id === node.parentNode); - parentPosition = getNodePositionWithOrigin(parentNode, parentNode?.origin || nodeOrigin).positionAbsolute; - } - const positionAbsolute = currentExtent ? clampPosition(nextPosition, currentExtent as CoordinateExtent) : nextPosition; return { position: { - x: positionAbsolute.x - parentPosition.x, - y: positionAbsolute.y - parentPosition.y, + x: positionAbsolute.x - parentPos.x, + y: positionAbsolute.y - parentPos.y, }, positionAbsolute, }; diff --git a/packages/system/src/utils/utils.ts b/packages/system/src/utils/utils.ts index f02f95aa..f0d639c3 100644 --- a/packages/system/src/utils/utils.ts +++ b/packages/system/src/utils/utils.ts @@ -10,7 +10,7 @@ import type { HandleElement, Position, } from '../types'; -import { getConnectedEdgesBase } from './graph'; +import { getConnectedEdgesBase, getNodePositionWithOrigin } from './graph'; export const getDimensions = (node: HTMLDivElement): Dimensions => ({ width: node.offsetWidth, @@ -67,11 +67,25 @@ export const boxToRect = ({ x, y, x2, y2 }: Box): Rect => ({ height: y2 - y, }); -export const nodeToRect = (node: BaseNode): Rect => ({ - ...(node.positionAbsolute || { x: 0, y: 0 }), - width: node.width || 0, - height: node.height || 0, -}); +export const nodeToRect = (node: BaseNode, nodeOrigin: NodeOrigin = [0, 0]): Rect => { + const { positionAbsolute } = getNodePositionWithOrigin(node, node.origin || nodeOrigin); + + return { + ...positionAbsolute, + width: node.width || 0, + height: node.height || 0, + }; +}; + +export const nodeToBox = (node: BaseNode, nodeOrigin: NodeOrigin = [0, 0]): Box => { + const { positionAbsolute } = getNodePositionWithOrigin(node, node.origin || nodeOrigin); + + return { + ...positionAbsolute, + x2: positionAbsolute.x + (node.width || 0), + y2: positionAbsolute.y + (node.height || 0), + }; +}; export const getBoundsOfRects = (rect1: Rect, rect2: Rect): Rect => boxToRect(getBoundsOfBoxes(rectToBox(rect1), rectToBox(rect2))); @@ -182,11 +196,7 @@ export const getPositionWithOrigin = ({ height: number; origin?: NodeOrigin; }): XYPosition => { - if (!width || !height) { - return { x, y }; - } - - if (origin[0] < 0 || origin[1] < 0 || origin[0] > 1 || origin[1] > 1) { + if (!width || !height || origin[0] < 0 || origin[1] < 0 || origin[0] > 1 || origin[1] > 1) { return { x, y }; }