Include origin directly in positionAbsolute of node

This commit is contained in:
peterkogo
2024-05-22 15:38:08 +02:00
parent 02250e972d
commit e81194d58a
15 changed files with 7354 additions and 5920 deletions
@@ -37,6 +37,7 @@ const initialNodes: Node[] = [
type: 'defaultResizer', type: 'defaultResizer',
data: { label: 'default resizer' }, data: { label: 'default resizer' },
position: { x: 0, y: 0 }, position: { x: 0, y: 0 },
origin: [1, 1],
style: { ...nodeStyle }, style: { ...nodeStyle },
}, },
{ {
@@ -129,6 +130,7 @@ const initialNodes: Node[] = [
width: 300, width: 300,
height: 400, height: 400,
style: { ...nodeStyle }, style: { ...nodeStyle },
origin: [0, 0],
}, },
{ {
id: '5a', id: '5a',
@@ -184,6 +186,7 @@ const CustomNodeFlow = () => {
minZoom={0.2} minZoom={0.2}
maxZoom={5} maxZoom={5}
snapToGrid={snapToGrid} snapToGrid={snapToGrid}
nodeOrigin={[1, 1]}
fitView fitView
onlyRenderVisibleElements onlyRenderVisibleElements
> >
+14 -5
View File
@@ -14,6 +14,8 @@ import {
Background, Background,
Panel, Panel,
NodeOrigin, NodeOrigin,
useUpdateNodeInternals,
ReactFlowProvider,
} from '@xyflow/react'; } from '@xyflow/react';
import DebugNode from './DebugNode'; import DebugNode from './DebugNode';
@@ -27,7 +29,7 @@ const defaultViewport = { x: 0, y: 0, zoom: 1.5 };
const initialNodes: Node[] = [ const initialNodes: Node[] = [
{ {
id: '1', id: '1',
type: 'input', // type: 'input',
data: { label: 'Node 1' }, data: { label: 'Node 1' },
position: { x: 250, y: 5 }, position: { x: 250, y: 5 },
className: 'light', className: 'light',
@@ -88,10 +90,11 @@ const initialNodes: Node[] = [
id: '5', id: '5',
type: 'group', type: 'group',
data: { label: 'Node 5' }, data: { label: 'Node 5' },
position: { x: 650, y: 250 }, position: { x: 900, y: 250 },
className: 'light', className: 'light',
style: { width: 100, height: 100 }, style: { width: 100, height: 100 },
zIndex: 1000, zIndex: 1000,
origin: [1, 0],
}, },
{ {
id: '5a', id: '5a',
@@ -118,9 +121,9 @@ const initialNodes: Node[] = [
{ {
id: '3', id: '3',
data: { label: 'Node 3' }, data: { label: 'Node 3' },
position: { x: 400, y: 100 }, position: { x: 250, y: 100 },
className: 'light', className: 'light',
extent: 'parent', // extent: 'parent',
}, },
]; ];
@@ -151,6 +154,7 @@ const nodeTypes = {
const Subflow = () => { const Subflow = () => {
const [rfInstance, setRfInstance] = useState<ReactFlowInstance | null>(null); const [rfInstance, setRfInstance] = useState<ReactFlowInstance | null>(null);
const updateNodeInternals = useUpdateNodeInternals();
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
@@ -228,9 +232,14 @@ const Subflow = () => {
<button onClick={toggleChildNodes}>toggleChildNodes</button> <button onClick={toggleChildNodes}>toggleChildNodes</button>
<button onClick={logToObject}>toObject</button> <button onClick={logToObject}>toObject</button>
<button onClick={() => setNodes(initialNodes)}>setNodes</button> <button onClick={() => setNodes(initialNodes)}>setNodes</button>
<button onClick={() => updateNodeInternals(nodes.map((node) => node.id))}>updateNodeInternals</button>
</Panel> </Panel>
</ReactFlow> </ReactFlow>
); );
}; };
export default Subflow; export default () => (
<ReactFlowProvider>
<Subflow />
</ReactFlowProvider>
);
@@ -86,7 +86,7 @@ function NodeComponentWrapperInner<NodeType extends Node>({
}) { }) {
const { node, x, y } = useStore((s) => { const { node, x, y } = useStore((s) => {
const node = s.nodeLookup.get(id) as InternalNode<NodeType>; const node = s.nodeLookup.get(id) as InternalNode<NodeType>;
const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute; const { x, y } = node.internals.positionAbsolute;
return { return {
node, node,
@@ -73,20 +73,25 @@ function ResizeControl({
const node = nodeLookup.get(id); const node = nodeLookup.get(id);
if (node && node.expandParent && node.parentId) { if (node && node.expandParent && node.parentId) {
const origin = node.origin ?? nodeOrigin;
const width = change.width ?? node.measured.width!;
const height = change.height ?? node.measured.height!;
const child: ParentExpandChild = { const child: ParentExpandChild = {
id: node.id, id: node.id,
parentId: node.parentId, parentId: node.parentId,
rect: { rect: {
width: change.width ?? node.measured.width!, width,
height: change.height ?? node.measured.height!, height,
...evaluateAbsolutePosition( ...evaluateAbsolutePosition(
{ {
x: change.x ?? node.position.x, x: change.x ?? node.internals.positionAbsolute.x,
y: change.y ?? node.position.y, y: change.y ?? node.internals.positionAbsolute.y,
}, },
{ width, height },
node.parentId, node.parentId,
nodeLookup, nodeLookup,
node.origin ?? nodeOrigin origin
), ),
}, },
}; };
@@ -94,9 +99,10 @@ function ResizeControl({
const parentExpandChanges = handleExpandParent([child], nodeLookup, parentLookup, nodeOrigin); const parentExpandChanges = handleExpandParent([child], nodeLookup, parentLookup, nodeOrigin);
changes.push(...parentExpandChanges); changes.push(...parentExpandChanges);
// when the parent was expanded by the child node, its position will be clamped at 0,0 // when the parent was expanded by the child node, its position will be clamped at
nextPosition.x = change.x ? Math.max(0, change.x) : undefined; // 0,0 when node origin is 0,0 and to width, height if it's 1,1
nextPosition.y = change.y ? Math.max(0, change.y) : undefined; nextPosition.x = change.x ? Math.max(origin[0] * width, change.x) : undefined;
nextPosition.y = change.y ? Math.max(origin[1] * height, change.y) : undefined;
} }
if (nextPosition.x !== undefined && nextPosition.y !== undefined) { if (nextPosition.x !== undefined && nextPosition.y !== undefined) {
@@ -6,7 +6,6 @@ import {
elementSelectionKeys, elementSelectionKeys,
errorMessages, errorMessages,
getNodeDimensions, getNodeDimensions,
getPositionWithOrigin,
isInputDOMNode, isInputDOMNode,
nodeHasDimensions, nodeHasDimensions,
} from '@xyflow/system'; } from '@xyflow/system';
@@ -87,15 +86,9 @@ export function NodeWrapper<NodeType extends Node>({
const nodeDimensions = getNodeDimensions(node); const nodeDimensions = getNodeDimensions(node);
const inlineDimensions = getNodeInlineStyleDimensions(node); const inlineDimensions = getNodeInlineStyleDimensions(node);
const clampedPosition = nodeExtent // TODO: clamping should happen earlier
? clampPosition(internals.positionAbsolute, nodeExtent) let clampedPosition = nodeExtent ? clampPosition(internals.positionAbsolute, nodeExtent) : internals.positionAbsolute;
: internals.positionAbsolute;
const positionWithOrigin = getPositionWithOrigin({
...clampedPosition,
...nodeDimensions,
origin: node.origin || nodeOrigin,
});
const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave; const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
const onMouseEnterHandler = onMouseEnter const onMouseEnterHandler = onMouseEnter
@@ -181,9 +174,9 @@ export function NodeWrapper<NodeType extends Node>({
ref={nodeRef} ref={nodeRef}
style={{ style={{
zIndex: internals.z, zIndex: internals.z,
transform: `translate(${positionWithOrigin.x}px,${positionWithOrigin.y}px)`, transform: `translate(${clampedPosition.x}px,${clampedPosition.y}px)`,
pointerEvents: hasPointerEvents ? 'all' : 'none', pointerEvents: hasPointerEvents ? 'all' : 'none',
visibility: hasDimensions ? 'visible' : 'hidden', visibility: node.measured.width ? 'visible' : 'hidden',
...node.style, ...node.style,
...inlineDimensions, ...inlineDimensions,
}} }}
+1 -1
View File
@@ -46,7 +46,7 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
const nodeToUse = isNode<NodeType>(node) ? node : nodeLookup.get(node.id)!; const nodeToUse = isNode<NodeType>(node) ? node : nodeLookup.get(node.id)!;
const position = nodeToUse.parentId const position = nodeToUse.parentId
? evaluateAbsolutePosition(nodeToUse.position, nodeToUse.parentId, nodeLookup, nodeOrigin) ? evaluateAbsolutePosition(nodeToUse.position, nodeToUse.measured, nodeToUse.parentId, nodeLookup, nodeOrigin)
: nodeToUse.position; : nodeToUse.position;
const nodeWithPosition = { const nodeWithPosition = {
+3 -3
View File
@@ -98,7 +98,7 @@ const createStore = ({
return; return;
} }
updateAbsolutePositions(nodeLookup, { nodeOrigin }); updateAbsolutePositions(nodeLookup, parentLookup, { nodeOrigin });
// we call fitView once initially after all dimensions are set // we call fitView once initially after all dimensions are set
let nextFitViewDone = fitViewDone; let nextFitViewDone = fitViewDone;
@@ -155,8 +155,8 @@ const createStore = ({
} }
if (parentExpandChildren.length > 0) { if (parentExpandChildren.length > 0) {
const { nodeLookup, parentLookup } = get(); const { nodeLookup, parentLookup, nodeOrigin } = get();
const parentExpandChanges = handleExpandParent(parentExpandChildren, nodeLookup, parentLookup); const parentExpandChanges = handleExpandParent(parentExpandChildren, nodeLookup, parentLookup, nodeOrigin);
changes.push(...parentExpandChanges); changes.push(...parentExpandChanges);
} }
@@ -134,7 +134,7 @@
{#each $nodes as userNode (userNode.id)} {#each $nodes as userNode (userNode.id)}
{@const node = $nodeLookup.get(userNode.id)} {@const node = $nodeLookup.get(userNode.id)}
{#if node && nodeHasDimensions(node)} {#if node && nodeHasDimensions(node)}
{@const pos = getNodePositionWithOrigin(node).positionAbsolute} {@const pos = getNodePositionWithOrigin(node)}
{@const nodeDimesions = getNodeDimensions(node)} {@const nodeDimesions = getNodeDimensions(node)}
<MinimapNode <MinimapNode
x={pos.x} x={pos.x}
+1
View File
@@ -77,6 +77,7 @@ export type InternalNodeBase<NodeType extends NodeBase = NodeBase> = NodeType &
* Used as an optimization to avoid certain operations. */ * Used as an optimization to avoid certain operations. */
userNode: NodeType; userNode: NodeType;
handleBounds?: NodeHandleBounds; handleBounds?: NodeHandleBounds;
bounds?: NodeBounds;
}; };
}; };
+11 -1
View File
@@ -1,4 +1,13 @@
import type { Transform, XYPosition, SnapGrid, Dimensions, NodeOrigin, HandleElement, Position } from '../types'; import type {
Transform,
XYPosition,
SnapGrid,
Dimensions,
NodeOrigin,
HandleElement,
Position,
InternalNodeBase,
} from '../types';
import { snapPosition, pointToRendererPoint } from './general'; import { snapPosition, pointToRendererPoint } from './general';
export type GetPointerPositionParams = { export type GetPointerPositionParams = {
@@ -60,6 +69,7 @@ export const getEventPosition = (event: MouseEvent | TouchEvent, bounds?: DOMRec
// unnecessary recalculations. // unnecessary recalculations.
export const getHandleBounds = ( export const getHandleBounds = (
selector: string, selector: string,
node: InternalNodeBase,
nodeElement: HTMLDivElement, nodeElement: HTMLDivElement,
zoom: number, zoom: number,
nodeOrigin: NodeOrigin = [0, 0] nodeOrigin: NodeOrigin = [0, 0]
+13 -16
View File
@@ -12,7 +12,7 @@ import type {
NodeLookup, NodeLookup,
} from '../types'; } from '../types';
import { type Viewport } from '../types'; import { type Viewport } from '../types';
import { getNodePositionWithOrigin } from './graph'; import { getNodePositionWithOrigin, isInternalNodeBase } from './graph';
export const clamp = (val: number, min = 0, max = 1): number => Math.min(Math.max(val, min), max); export const clamp = (val: number, min = 0, max = 1): number => Math.min(Math.max(val, min), max);
@@ -68,7 +68,9 @@ export const boxToRect = ({ x, y, x2, y2 }: Box): Rect => ({
}); });
export const nodeToRect = (node: InternalNodeBase | NodeBase, nodeOrigin: NodeOrigin = [0, 0]): Rect => { export const nodeToRect = (node: InternalNodeBase | NodeBase, nodeOrigin: NodeOrigin = [0, 0]): Rect => {
const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute; const { x, y } = isInternalNodeBase(node)
? node.internals.positionAbsolute
: getNodePositionWithOrigin(node, nodeOrigin);
return { return {
x, x,
@@ -79,7 +81,9 @@ export const nodeToRect = (node: InternalNodeBase | NodeBase, nodeOrigin: NodeOr
}; };
export const nodeToBox = (node: InternalNodeBase | NodeBase, nodeOrigin: NodeOrigin = [0, 0]): Box => { export const nodeToBox = (node: InternalNodeBase | NodeBase, nodeOrigin: NodeOrigin = [0, 0]): Box => {
const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute; const { x, y } = isInternalNodeBase(node)
? node.internals.positionAbsolute
: getNodePositionWithOrigin(node, nodeOrigin);
return { return {
x, x,
@@ -239,24 +243,17 @@ export function nodeHasDimensions<NodeType extends NodeBase = NodeBase>(node: No
*/ */
export function evaluateAbsolutePosition( export function evaluateAbsolutePosition(
position: XYPosition, position: XYPosition,
dimensions: { width: number; height: number },
parentId: string, parentId: string,
nodeLookup: NodeLookup, nodeLookup: NodeLookup,
nodeOrigin: NodeOrigin = [0, 0] nodeOrigin: NodeOrigin
): XYPosition { ): XYPosition {
let nextParentId: string | undefined = parentId;
const positionAbsolute = { ...position }; const positionAbsolute = { ...position };
while (nextParentId) { const parent = nodeLookup.get(parentId);
const parent = nodeLookup.get(parentId); if (parent) {
nextParentId = parent?.parentId; positionAbsolute.x += parent.internals.positionAbsolute.x - dimensions.width * nodeOrigin[0];
positionAbsolute.y += parent.internals.positionAbsolute.y - dimensions.height * nodeOrigin[1];
if (parent) {
const origin = parent.origin || nodeOrigin;
const xOffset = (parent.measured.width ?? 0) * origin[0];
const yOffset = (parent.measured.height ?? 0) * origin[1];
positionAbsolute.x += parent.position.x - xOffset;
positionAbsolute.y += parent.position.y - yOffset;
}
} }
return positionAbsolute; return positionAbsolute;
+11 -25
View File
@@ -106,25 +106,15 @@ export const getIncomers = <NodeType extends NodeBase = NodeBase, EdgeType exten
return nodes.filter((n) => incomersIds.has(n.id)); return nodes.filter((n) => incomersIds.has(n.id));
}; };
export const getNodePositionWithOrigin = ( export const getNodePositionWithOrigin = (node: NodeBase, nodeOrigin: NodeOrigin = [0, 0]): XYPosition => {
node: InternalNodeBase | NodeBase,
nodeOrigin: NodeOrigin = [0, 0]
): { position: XYPosition; positionAbsolute: XYPosition } => {
const { width, height } = getNodeDimensions(node); const { width, height } = getNodeDimensions(node);
const positionAbsolute = 'internals' in node ? node.internals.positionAbsolute : node.position; const origin = node.origin ?? nodeOrigin;
const origin = node.origin || nodeOrigin;
const offsetX = width * origin[0]; const offsetX = width * origin[0];
const offsetY = height * origin[1]; const offsetY = height * origin[1];
return { return {
position: { x: node.position.x - offsetX,
x: node.position.x - offsetX, y: node.position.y - offsetY,
y: node.position.y - offsetY,
},
positionAbsolute: {
x: positionAbsolute.x - offsetX,
y: positionAbsolute.y - offsetY,
},
}; };
}; };
@@ -323,9 +313,8 @@ export function calculateNodePosition<NodeType extends NodeBase>({
}): { position: XYPosition; positionAbsolute: XYPosition } { }): { position: XYPosition; positionAbsolute: XYPosition } {
const node = nodeLookup.get(nodeId)!; const node = nodeLookup.get(nodeId)!;
const parentNode = node.parentId ? nodeLookup.get(node.parentId) : undefined; const parentNode = node.parentId ? nodeLookup.get(node.parentId) : undefined;
const { x: parentX, y: parentY } = parentNode const { x: parentX, y: parentY } = parentNode ? parentNode.internals.positionAbsolute : { x: 0, y: 0 };
? getNodePositionWithOrigin(parentNode, parentNode.origin || nodeOrigin).positionAbsolute const origin = node.origin ?? nodeOrigin;
: { x: 0, y: 0 };
let currentExtent = clampNodeExtent(node, node.extent || nodeExtent); let currentExtent = clampNodeExtent(node, node.extent || nodeExtent);
@@ -339,13 +328,9 @@ export function calculateNodePosition<NodeType extends NodeBase>({
const parentHeight = parentNode.measured.height; const parentHeight = parentNode.measured.height;
if (nodeWidth && nodeHeight && parentWidth && parentHeight) { if (nodeWidth && nodeHeight && parentWidth && parentHeight) {
const currNodeOrigin = node.origin || nodeOrigin;
const extentX = parentX + nodeWidth * currNodeOrigin[0];
const extentY = parentY + nodeHeight * currNodeOrigin[1];
currentExtent = [ currentExtent = [
[extentX, extentY], [parentX, parentY],
[extentX + parentWidth - nodeWidth, extentY + parentHeight - nodeHeight], [parentX + parentWidth - nodeWidth, parentY + parentHeight - nodeHeight],
]; ];
} }
} }
@@ -362,8 +347,9 @@ export function calculateNodePosition<NodeType extends NodeBase>({
return { return {
position: { position: {
x: positionAbsolute.x - parentX, // TODO: is there a better way to do this?
y: positionAbsolute.y - parentY, x: positionAbsolute.x - parentX + node.measured.width! * origin[0],
y: positionAbsolute.y - parentY + node.measured.height! * origin[1],
}, },
positionAbsolute, positionAbsolute,
}; };
+101 -83
View File
@@ -6,7 +6,6 @@ import {
PanZoomInstance, PanZoomInstance,
Transform, Transform,
XYPosition, XYPosition,
XYZPosition,
ConnectionLookup, ConnectionLookup,
EdgeBase, EdgeBase,
EdgeLookup, EdgeLookup,
@@ -16,6 +15,7 @@ import {
NodeDimensionChange, NodeDimensionChange,
NodePositionChange, NodePositionChange,
ParentLookup, ParentLookup,
Dimensions,
} from '../types'; } from '../types';
import { getDimensions, getHandleBounds } from './dom'; import { getDimensions, getHandleBounds } from './dom';
import { getBoundsOfRects, getNodeDimensions, isNumeric, nodeToRect } from './general'; import { getBoundsOfRects, getNodeDimensions, isNumeric, nodeToRect } from './general';
@@ -24,46 +24,19 @@ import { ParentExpandChild } from './types';
export function updateAbsolutePositions<NodeType extends NodeBase>( export function updateAbsolutePositions<NodeType extends NodeBase>(
nodeLookup: Map<string, InternalNodeBase<NodeType>>, nodeLookup: Map<string, InternalNodeBase<NodeType>>,
parentLookup: Map<string, InternalNodeBase<NodeType>[]>,
options: UpdateNodesOptions<NodeType> = { options: UpdateNodesOptions<NodeType> = {
nodeOrigin: [0, 0] as NodeOrigin, nodeOrigin: [0, 0] as NodeOrigin,
elevateNodesOnSelect: true, elevateNodesOnSelect: true,
defaults: {}, defaults: {},
} }
) { ) {
const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0; for (const node of nodeLookup.values()) {
if (!node.parentId) {
for (const [, node] of nodeLookup) {
const parentId = node.parentId;
if (!parentId) {
continue; continue;
} }
if (!nodeLookup.has(parentId)) { updateChildPosition(node, nodeLookup, parentLookup, options);
throw new Error(`Parent node ${parentId} not found`);
}
const parentNode = nodeLookup.get(parentId);
const { x, y, z } = calculateXYZPosition(
node,
nodeLookup,
{
...node.position,
z: (isNumeric(node.zIndex) ? node.zIndex : 0) + (node.selected ? selectedNodeZ : 0),
},
parentNode?.origin ?? options.nodeOrigin
);
const currPosition = node.internals.positionAbsolute;
const positionChanged = x !== currPosition.x || y !== currPosition.y;
if (positionChanged || z !== node.internals.z) {
node.internals = {
...node.internals,
positionAbsolute: positionChanged ? { x, y } : currPosition,
z,
};
}
} }
} }
@@ -91,9 +64,8 @@ export function adoptUserNodes<NodeType extends NodeBase>(
const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0; const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0;
nodes.forEach((userNode) => { for (const userNode of nodes) {
let internalNode = tmpLookup.get(userNode.id); let internalNode = tmpLookup.get(userNode.id);
if (options.checkEquality && userNode === internalNode?.internals.userNode) { if (options.checkEquality && userNode === internalNode?.internals.userNode) {
nodeLookup.set(userNode.id, internalNode); nodeLookup.set(userNode.id, internalNode);
} else { } else {
@@ -105,9 +77,9 @@ export function adoptUserNodes<NodeType extends NodeBase>(
height: userNode.measured?.height, height: userNode.measured?.height,
}, },
internals: { internals: {
positionAbsolute: userNode.position, positionAbsolute: getNodePositionWithOrigin(userNode, options.nodeOrigin),
handleBounds: internalNode?.internals.handleBounds, handleBounds: internalNode?.internals.handleBounds,
z: (isNumeric(userNode.zIndex) ? userNode.zIndex : 0) + (userNode.selected ? selectedNodeZ : 0), z: calculateZ(userNode, selectedNodeZ),
userNode, userNode,
}, },
}; };
@@ -115,50 +87,76 @@ export function adoptUserNodes<NodeType extends NodeBase>(
} }
if (userNode.parentId) { if (userNode.parentId) {
const childNodes = parentLookup.get(userNode.parentId); updateChildPosition(internalNode, nodeLookup, parentLookup, options);
if (childNodes) {
childNodes.push(internalNode);
} else {
parentLookup.set(userNode.parentId, [internalNode]);
}
} }
});
if (parentLookup.size > 0) {
updateAbsolutePositions(nodeLookup, options);
} }
} }
function calculateXYZPosition<NodeType extends NodeBase>( function updateChildPosition<NodeType extends NodeBase>(
node: NodeType, node: InternalNodeBase<NodeType>,
nodeLookup: Map<string, InternalNodeBase<NodeType>>, nodeLookup: NodeLookup,
result: XYZPosition, parentLookup: ParentLookup,
nodeOrigin: NodeOrigin = [0, 0] options: UpdateNodesOptions<NodeType> = {
): XYZPosition { nodeOrigin: [0, 0] as NodeOrigin,
if (!node.parentId) { elevateNodesOnSelect: true,
return result; defaults: {},
}
) {
const parentId = node.parentId!;
const parentNode = nodeLookup.get(parentId);
if (!parentNode) {
throw new Error(`Parent node ${parentId} not found`);
} }
const parent = nodeLookup.get(node.parentId)!; // update the parentLookup
const parentPosition = getNodePositionWithOrigin(parent, nodeOrigin).position; const childNodes = parentLookup.get(parentId);
if (childNodes) {
childNodes.push(node);
} else {
parentLookup.set(parentId, [node]);
}
return calculateXYZPosition( const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0;
parent,
nodeLookup, const { x, y, z } = calculateChildXYZ(node, parentNode, options.nodeOrigin!, selectedNodeZ);
{
x: (result.x ?? 0) + parentPosition.x, const currPosition = node.internals.positionAbsolute;
y: (result.y ?? 0) + parentPosition.y, const positionChanged = x !== currPosition.x || y !== currPosition.y;
z: (parent.internals.z ?? 0) > (result.z ?? 0) ? parent.internals.z ?? 0 : result.z ?? 0,
}, if (positionChanged || z !== node.internals.z) {
parent.origin || nodeOrigin node.internals = {
); ...node.internals,
positionAbsolute: positionChanged ? { x, y } : currPosition,
z,
};
}
}
function calculateZ(node: NodeBase, selectedNodeZ: number) {
return (isNumeric(node.zIndex) ? node.zIndex : 0) + (node.selected ? selectedNodeZ : 0);
}
function calculateChildXYZ<NodeType extends NodeBase>(
childNode: InternalNodeBase<NodeType>,
parentNode: InternalNodeBase<NodeType>,
nodeOrigin: NodeOrigin,
selectedNodeZ: number
) {
const position = getNodePositionWithOrigin(childNode, nodeOrigin);
const childZ = calculateZ(childNode, selectedNodeZ);
const parentZ = parentNode.internals.z ?? 0;
return {
x: parentNode.internals.positionAbsolute.x + position.x,
y: parentNode.internals.positionAbsolute.y + position.y,
z: parentZ > childZ ? parentZ : childZ,
};
} }
export function handleExpandParent( export function handleExpandParent(
children: ParentExpandChild[], children: ParentExpandChild[],
nodeLookup: NodeLookup, nodeLookup: NodeLookup,
parentLookup: ParentLookup, parentLookup: ParentLookup,
nodeOrigin?: NodeOrigin nodeOrigin: NodeOrigin = [0, 0]
): (NodeDimensionChange | NodePositionChange)[] { ): (NodeDimensionChange | NodePositionChange)[] {
const changes: (NodeDimensionChange | NodePositionChange)[] = []; const changes: (NodeDimensionChange | NodePositionChange)[] = [];
const parentExpansions = new Map<string, { expandedRect: Rect; parent: InternalNodeBase }>(); const parentExpansions = new Map<string, { expandedRect: Rect; parent: InternalNodeBase }>();
@@ -172,6 +170,7 @@ export function handleExpandParent(
const parentRect = const parentRect =
parentExpansions.get(child.parentId)?.expandedRect ?? nodeToRect(parent, parent.origin ?? nodeOrigin); parentExpansions.get(child.parentId)?.expandedRect ?? nodeToRect(parent, parent.origin ?? nodeOrigin);
const expandedRect = getBoundsOfRects(parentRect, child.rect); const expandedRect = getBoundsOfRects(parentRect, child.rect);
parentExpansions.set(child.parentId, { expandedRect, parent }); parentExpansions.set(child.parentId, { expandedRect, parent });
} }
@@ -179,20 +178,30 @@ export function handleExpandParent(
if (parentExpansions.size > 0) { if (parentExpansions.size > 0) {
parentExpansions.forEach(({ expandedRect, parent }, parentId) => { parentExpansions.forEach(({ expandedRect, parent }, parentId) => {
// determine the position & dimensions of the parent // determine the position & dimensions of the parent
const { position } = getNodePositionWithOrigin(parent, parent.origin); const positionAbsolute = parent.internals.positionAbsolute;
const dimensions = getNodeDimensions(parent); const dimensions = getNodeDimensions(parent);
const origin = parent.origin ?? nodeOrigin;
// determine how much the parent expands by moving the position // determine how much the parent expands in width and position
const xChange = expandedRect.x < position.x ? Math.round(Math.abs(position.x - expandedRect.x)) : 0; const xChange =
const yChange = expandedRect.y < position.y ? Math.round(Math.abs(position.y - expandedRect.y)) : 0; expandedRect.x < positionAbsolute.x ? Math.round(Math.abs(positionAbsolute.x - expandedRect.x)) : 0;
const yChange =
expandedRect.y < positionAbsolute.y ? Math.round(Math.abs(positionAbsolute.y - expandedRect.y)) : 0;
if (xChange > 0 || yChange > 0) { const newWidth = Math.max(dimensions.width, Math.round(expandedRect.width));
const newHeight = Math.max(dimensions.height, Math.round(expandedRect.height));
const widthChange = (newWidth - dimensions.width) * origin[0];
const heightChange = (newHeight - dimensions.height) * origin[1];
// We need to correct the position of the parent node if the origin is not [0,0]
if (xChange > 0 || yChange > 0 || widthChange || heightChange) {
changes.push({ changes.push({
id: parentId, id: parentId,
type: 'position', type: 'position',
position: { position: {
x: position.x - xChange, x: parent.position.x - xChange + widthChange,
y: position.y - yChange, y: parent.position.y - yChange + heightChange,
}, },
}); });
@@ -213,14 +222,15 @@ export function handleExpandParent(
}); });
} }
if (dimensions.width < expandedRect.width || dimensions.height < expandedRect.height) { // We need to correct the dimensions of the parent node if the origin is not [0,0]
if (dimensions.width < expandedRect.width || dimensions.height < expandedRect.height || xChange || yChange) {
changes.push({ changes.push({
id: parentId, id: parentId,
type: 'dimensions', type: 'dimensions',
setAttributes: true, setAttributes: true,
dimensions: { dimensions: {
width: Math.max(dimensions.width, Math.round(expandedRect.width)), width: newWidth + (xChange ? origin[0] * xChange - widthChange : 0),
height: Math.max(dimensions.height, Math.round(expandedRect.height)), height: newHeight + (yChange ? origin[1] * yChange - heightChange : 0),
}, },
}); });
} }
@@ -250,16 +260,19 @@ export function updateNodeInternals<NodeType extends InternalNodeBase>(
// in this array we collect nodes, that might trigger changes (like expanding parent) // in this array we collect nodes, that might trigger changes (like expanding parent)
const parentExpandChildren: ParentExpandChild[] = []; const parentExpandChildren: ParentExpandChild[] = [];
updates.forEach((update) => { for (const update of updates.values()) {
const node = nodeLookup.get(update.id); const node = nodeLookup.get(update.id);
if (!node) {
continue;
}
if (node?.hidden) { if (node.hidden) {
node.internals = { node.internals = {
...node.internals, ...node.internals,
handleBounds: undefined, handleBounds: undefined,
}; };
updatedInternals = true; updatedInternals = true;
} else if (node) { } else {
const dimensions = getDimensions(update.nodeElement); const dimensions = getDimensions(update.nodeElement);
const dimensionChanged = node.measured.width !== dimensions.width || node.measured.height !== dimensions.height; const dimensionChanged = node.measured.width !== dimensions.width || node.measured.height !== dimensions.height;
const doUpdate = !!( const doUpdate = !!(
@@ -269,14 +282,19 @@ export function updateNodeInternals<NodeType extends InternalNodeBase>(
); );
if (doUpdate) { if (doUpdate) {
const positionAbsolute = getNodePositionWithOrigin(node, nodeOrigin);
node.measured = dimensions; node.measured = dimensions;
node.internals = { node.internals = {
...node.internals, ...node.internals,
positionAbsolute: getNodePositionWithOrigin(node, nodeOrigin),
handleBounds: { handleBounds: {
source: getHandleBounds('.source', update.nodeElement, zoom, node.origin || nodeOrigin), source: getHandleBounds('.source', node, update.nodeElement, zoom),
target: getHandleBounds('.target', update.nodeElement, zoom, node.origin || nodeOrigin), target: getHandleBounds('.target', node, update.nodeElement, zoom),
}, },
}; };
if (node.parentId) {
updateChildPosition(node, nodeLookup, parentLookup, { nodeOrigin });
}
updatedInternals = true; updatedInternals = true;
@@ -297,7 +315,7 @@ export function updateNodeInternals<NodeType extends InternalNodeBase>(
} }
} }
} }
}); }
if (parentExpandChildren.length > 0) { if (parentExpandChildren.length > 0) {
const parentExpandChanges = handleExpandParent(parentExpandChildren, nodeLookup, parentLookup, nodeOrigin); const parentExpandChanges = handleExpandParent(parentExpandChildren, nodeLookup, parentLookup, nodeOrigin);
-1
View File
@@ -129,7 +129,6 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
for (const [id, dragItem] of dragItems) { for (const [id, dragItem] of dragItems) {
let nextPosition = { x: x - dragItem.distance.x, y: y - dragItem.distance.y }; let nextPosition = { x: x - dragItem.distance.x, y: y - dragItem.distance.y };
if (snapToGrid) { if (snapToGrid) {
nextPosition = snapPosition(nextPosition, snapGrid); nextPosition = snapPosition(nextPosition, snapGrid);
} }
+7176 -5764
View File
File diff suppressed because it is too large Load Diff