UNPKG

@yworks/yfiles-layout-reactflow

Version:

yFiles Layouts for React Flow - A layout library for React Flow providing powerful yFiles layout algorithms and supporting components

894 lines (885 loc) 29.5 kB
import { getLayoutAlgorithm, getLayoutData, getRootNode, initializeWebWorker, registerWebWorker, setWebWorkerLicense } from "./chunk-MI7N5RQH.js"; // src/components/LabelText.tsx import { useEffect, useRef, useState } from "react"; import { jsx, jsxs } from "react/jsx-runtime"; function LabelText({ x, y, width, height, angle, transform, label, labelStyle, dataId, isNodeLabel, className, children }) { const [textBox, setTextBox] = useState({ x: 0, y: 0, width: 0, height: 0 }); const hasLayout = typeof width !== "undefined" && typeof height !== "undefined"; const labelRef = useRef(null); const containerRef = useRef(null); useEffect(() => { if (!labelRef || !labelRef.current) return; if (!width || !height) { const bbox = labelRef.current.getBoundingClientRect(); setTextBox({ x: bbox.x, y: bbox.y, width: bbox.width, height: bbox.height }); } else { setTextBox({ x, y, width: width ?? 0, height: height ?? 0 }); } }, [x, y, width, height]); const defaultStyle = { position: "absolute", whiteSpace: "nowrap", boxSizing: "content-box" }; let style = { ...defaultStyle, ...isNodeLabel && { top: "50%", left: "50%", transform: "translate(-50%, -50%)" } }; if (hasLayout) { let hOffset = 0; let vOffset = 0; if (isNodeLabel) { const { offset, wDiff, hDiff } = calculateOffsets(textBox.width, textBox.height, containerRef.current); hOffset += wDiff * 0.5 + (textBox.x >= 0 ? offset.left : offset.right); vOffset += hDiff * 0.5 + (textBox.y >= 0 ? offset.top : offset.bottom); } style = { ...defaultStyle, width: textBox.width, height: textBox.height, transform: (isNodeLabel ? `translate(${textBox.x - hOffset}px,${textBox.y - vOffset}px) ` : "") + (transform ? ` ${transform}` : ""), ...!isNodeLabel && { transformOrigin: `0px 0px` } }; } return /* @__PURE__ */ jsxs("div", { "data-id": dataId, style, className, ref: containerRef, children: [ /* @__PURE__ */ jsx( "div", { style: { ...labelStyle, ...shouldFlipText(angle, isNodeLabel ?? false) && { transform: `rotate(${Math.PI}rad)` } }, ref: labelRef, children: isLabelType(label) ? label.label : label } ), children ] }); } function calculateOffsets(width, height, labelElement) { if (labelElement) { const parentElement = labelElement.parentElement; if (parentElement) { const parentWidth = parentElement.clientWidth; const parentHeight = parentElement.clientHeight; const parentPadding = calculatePadding(parentElement); const labelPadding = calculatePadding(labelElement); return { offset: { top: parentPadding.top + labelPadding.top, bottom: parentPadding.bottom + labelPadding.bottom, right: parentPadding.right + labelPadding.right, left: parentPadding.left + labelPadding.left }, wDiff: width - parentWidth, hDiff: height - parentHeight }; } } return { offset: { top: 0, right: 0, bottom: 0, left: 0 }, wDiff: -1, hDiff: -1 }; } function calculatePadding(element) { if (!element) { return { top: 0, right: 0, bottom: 0, left: 0 }; } const elementStyle = window.getComputedStyle(element); return { top: parseFloat(elementStyle.paddingTop) ?? 0, right: parseFloat(elementStyle.paddingRight) ?? 0, bottom: parseFloat(elementStyle.paddingBottom) ?? 0, left: parseFloat(elementStyle.paddingLeft) ?? 0 }; } function shouldFlipText(angle, isNodeLabel) { return angle && (isNodeLabel ? angle >= -Math.PI && angle <= -Math.PI / 2 || angle >= Math.PI / 2 && angle <= Math.PI : angle < -Math.PI && angle > -Math.PI / 2 || angle < Math.PI / 2 && angle > Math.PI); } // src/components/Labels.tsx import { jsx as jsx2 } from "react/jsx-runtime"; import { createElement } from "react"; function NodeLabel({ data, id }) { if (!data?.label) { return; } const dataId = `node-label-${id}-0`; const labelBox = data?.yData?.labelBoxes?.find( (layout) => layout.id === dataId ); const nodeLabelProps = getTextProps(data?.label, labelBox, data?.labelStyle, data?.className); return /* @__PURE__ */ jsx2(LabelText, { ...nodeLabelProps, dataId, isNodeLabel: true }); } function EdgeLabels({ labels, ownerId, labelBoxes, labelStyle }) { return /* @__PURE__ */ jsx2("div", { style: { position: "relative" }, children: labels.map((label, index) => { if (!label) { return; } const dataId = `edge-label-${ownerId}-${index}`; const labelBox = labelBoxes.find( (labelBox2) => labelBox2.id === dataId ); const edgeTextProps = getTextProps(label, labelBox, labelStyle); return /* @__PURE__ */ createElement( LabelText, { ...edgeTextProps, dataId, key: `${dataId}-text-${index}`, isNodeLabel: false } ); }) }); } function getTextProps(label, labelBox, labelStyle, className) { if (isLabelType(label)) { return { x: labelBox?.x ?? 0, y: labelBox?.y ?? 0, width: labelBox?.width, height: labelBox?.height, angle: labelBox?.angle ?? 0, transform: labelBox?.transform, label: label.label, labelStyle: label.labelStyle ?? labelStyle, className: label?.className ?? className }; } return { x: labelBox?.x ?? 0, y: labelBox?.y ?? 0, width: labelBox?.width, height: labelBox?.height, angle: labelBox?.angle ?? 0, transform: labelBox?.transform, label, labelStyle, className }; } function isLabelType(label) { return label.label !== void 0; } // src/components/MultiHandleNode.tsx import { Handle, Position, useUpdateNodeInternals } from "reactflow"; import { useEffect as useEffect2, useRef as useRef2 } from "react"; import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime"; function MultiHandleNode(props) { return /* @__PURE__ */ jsxs2(Fragment, { children: [ /* @__PURE__ */ jsx3(NodeLabel, { ...props }), /* @__PURE__ */ jsx3(MultiHandles, { ...props }) ] }); } function withMultiHandles(Component) { return (props) => { return /* @__PURE__ */ jsxs2(Fragment, { children: [ /* @__PURE__ */ jsx3(Component, { ...props }), /* @__PURE__ */ jsx3(MultiHandles, { ...props }) ] }); }; } function MultiHandles(props) { const { data, id } = props; const updateNodeInternals = useUpdateNodeInternals(); const sourceHandlesRef = useRef2(null); const targetHandlesRef = useRef2(null); useEffect2(() => { updateNodeInternals(id); }, [data, id, updateNodeInternals]); useEffect2(() => { const { leftBorder, topBorder } = getParentBorders(sourceHandlesRef); setHandlesPosition(sourceHandlesRef, leftBorder, topBorder); setHandlesPosition(targetHandlesRef, leftBorder, topBorder); }, [data, id]); return /* @__PURE__ */ jsxs2(Fragment, { children: [ /* @__PURE__ */ jsxs2("div", { ref: sourceHandlesRef, className: "handles", style: { position: "absolute" }, children: [ /* @__PURE__ */ jsx3( Handle, { type: "source", position: Position.Top, style: { left: "50%", top: "50%", transform: "translate(-50%, -50%)", opacity: 0 } } ), " ", data?.yData?.sourceHandles?.map( (handle) => /* @__PURE__ */ jsx3( Handle, { id: handle.id, type: "source", position: handle.position, style: { left: handle.location.x, top: handle.location.y, transform: "translate(-50%, -50%)" } }, handle.id ) ) ] }), /* @__PURE__ */ jsxs2("div", { ref: targetHandlesRef, className: "handles", style: { position: "absolute" }, children: [ /* @__PURE__ */ jsx3( Handle, { type: "target", position: Position.Top, style: { left: "50%", top: "50%", transform: "translate(-50%, -50%)", opacity: 0 } } ), data?.yData?.targetHandles?.map( (handle) => /* @__PURE__ */ jsx3( Handle, { id: handle.id, type: "target", position: handle.position, style: { left: handle.location.x, top: handle.location.y, transform: "translate(-50%, -50%)" } }, handle.id ) ) ] }) ] }); } function getParentBorders(handlesRef) { let leftBorderWidth = 1; let topBorderWidth = 1; if (handlesRef.current) { const parent = handlesRef.current.parentElement; if (parent) { const computedStyle = getComputedStyle(parent); leftBorderWidth = parseInt(computedStyle.getPropertyValue("border-left-width")); topBorderWidth = parseInt(computedStyle.getPropertyValue("border-top-width")); } } return { leftBorder: leftBorderWidth, topBorder: topBorderWidth }; } function setHandlesPosition(handlesRef, leftBorder, topBorder) { if (handlesRef && handlesRef.current) { const handlesDiv = handlesRef.current; handlesDiv.style.left = `-${leftBorder}px`; handlesDiv.style.top = `-${topBorder}px`; } } // src/components/PolylineEdge.tsx import { BaseEdge, EdgeLabelRenderer } from "reactflow"; import { GeneralPath } from "@yfiles/yfiles"; import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime"; function PolylineEdge(props) { const [edgePath] = getPolylinePath({ sourceX: props.sourceX, sourceY: props.sourceY, targetX: props.targetX, targetY: props.targetY, bends: props.data?.yData?.bends ?? [] }); let labels = []; if (props.label) { labels.push(props.label); } if (props.data?.labels?.length > 0) { labels = labels.concat(props.data?.labels); } return /* @__PURE__ */ jsxs3(Fragment2, { children: [ /* @__PURE__ */ jsx4(BaseEdge, { path: edgePath, ...props }), /* @__PURE__ */ jsx4(EdgeLabelRenderer, { children: /* @__PURE__ */ jsx4( EdgeLabels, { labels, ownerId: props.id, labelBoxes: props.data?.yData?.labelBoxes ?? [], labelStyle: props.labelStyle }, `${props.id}-edgeLabels` ) }) ] }); } function getPolylinePath(params) { const { sourceX, sourceY, targetX, targetY, bends } = params; const generalPath = new GeneralPath(); generalPath.moveTo(sourceX, sourceY); bends.forEach((bend) => { generalPath.lineTo(bend.x, bend.y); }); generalPath.lineTo(targetX, targetY); let centerX; let centerY; if (bends.length === 0) { centerX = (sourceX + targetX) * 0.5; centerY = (sourceY + targetY) * 0.5; } else if (bends.length === 1) { centerX = (sourceX + bends[0].x) * 0.5; centerY = (sourceY + bends[0].y) * 0.5; } else { const bend0 = bends[Math.floor((bends.length - 1) / 2)]; const bend1 = bends[Math.floor((bends.length - 1) / 2) + 1]; centerX = (bend0.x + bend1.x) * 0.5; centerY = (bend0.x + bend1.y) * 0.5; } return [ generalPath.createSvgPathData(), centerX, centerY, Math.abs(centerX - sourceX), Math.abs(centerY - sourceY) ]; } // src/layout/layout-hooks.ts import { useCallback, useEffect as useEffect3, useMemo, useState as useState2 } from "react"; import { FreeEdgeLabelModel, FreeNodeLabelModel, Graph as Graph2, GraphBuilder, GroupingSupport, Matrix, OrientedRectangle, Point, Rect, Size } from "@yfiles/yfiles"; import { Position as Position2, useNodesInitialized, useReactFlow } from "reactflow"; // src/license/registerLicense.ts import { Graph, License } from "@yfiles/yfiles"; function registerLicense(licenseKey) { License.value = licenseKey; setWebWorkerLicense(licenseKey); } function checkLicense() { const g = new Graph(); g.createNode(); if (g.nodes.size !== 1) { console.error( "yFiles Layout Algorithms for React Flow requires a valid yFiles for HTML license. You can evaluate yFiles for 60 days free of charge on my.yworks.com." ); return false; } return true; } // src/layout/LayoutSupport.ts import { GraphComponent, LayoutExecutor, LayoutExecutorAsync } from "@yfiles/yfiles"; var LayoutSupport = class { workerPromise = null; executor = null; constructor(layoutWorker) { if (layoutWorker) { this.workerPromise = registerWebWorker(layoutWorker); } } async applyLayout(graph, layoutConfiguration, layoutDataProvider, reactFlowRef) { const executor = this.workerPromise !== null ? await this.createLayoutExecutorAsync( graph, layoutConfiguration, layoutDataProvider, reactFlowRef ) : await this.createLayoutExecutor( graph, layoutConfiguration, layoutDataProvider, reactFlowRef ); try { await executor.start(); } catch (e) { if (e.name === "AlgorithmAbortedError") { console.error("Layout calculation was aborted because maximum duration time was exceeded."); } else { console.error("Something went wrong during the layout calculation"); console.error(e); } } } async createLayoutExecutor(graph, layoutConfiguration, layoutDataProvider, reactFlowRef) { const layoutAlgorithm = await getLayoutAlgorithm(layoutConfiguration); return Promise.resolve( new LayoutExecutor({ graphComponent: new GraphComponent({ graph }), layout: layoutAlgorithm, layoutData: getLayoutData(layoutConfiguration.name, layoutDataProvider, reactFlowRef) }) ); } async createLayoutExecutorAsync(graph, layoutConfiguration, layoutDataProvider, reactFlowRef) { if (this.executor) { await this.executor.cancel(); } const worker = await this.workerPromise; const webWorkerMessageHandler = (data) => { const thisRequest = data.token; return new Promise((resolve, reject) => { worker.onmessage = (e) => { if (e.data && thisRequest === e.data.token) { if (e.data.name === "AlgorithmAbortedError") { reject(e.data); } else { resolve(e.data); } } }; worker.postMessage(data); }); }; this.executor = new LayoutExecutorAsync({ messageHandler: webWorkerMessageHandler, graph, layoutDescriptor: layoutConfiguration, layoutData: getLayoutData(layoutConfiguration.name, layoutDataProvider, reactFlowRef) }); return this.executor; } }; // src/layout/layout-hooks.ts function useLayout(context) { const { fitView, getZoom, getNodes, setNodes, getEdges, setEdges } = useReactFlow(); const [running, setRunning] = useState2(false); const layoutSupport = useMemo(() => { return new LayoutSupport(context?.layoutWorker); }, [context?.layoutWorker]); return { runLayout: useCallback( (configuration) => { if (!checkLicense()) { return; } setRunning(true); const graph = buildGraph(getNodes(), getEdges(), getZoom(), context?.reactFlowRef); const { name, layoutOptions, layoutData } = getConfiguration( configuration ); layoutSupport.applyLayout( graph, { name, properties: layoutOptions }, layoutData, context?.reactFlowRef ).then(() => { const { arrangedNodes, arrangedEdges } = transferLayout(graph, context?.reactFlowRef); setNodes(arrangedNodes); setEdges(arrangedEdges); setTimeout(() => fitView()); }).catch((e) => { context?.onError?.(e); }).finally(() => { setRunning(false); }); }, [getNodes, getEdges, getZoom, context, layoutSupport, setNodes, setEdges, fitView] ), running }; } function useLayoutSupport(reactFlowRef) { return { buildGraph: useCallback( (nodes, edges, zoom) => buildGraph(nodes, edges, zoom, reactFlowRef), [reactFlowRef] ), transferLayout: useCallback((graph) => transferLayout(graph, reactFlowRef), [reactFlowRef]) }; } function useNodesMeasuredEffect(callback) { const { getNodes } = useReactFlow(); const [shouldLayout, setShouldLayout] = useState2(true); const nodesInitialized = useNodesInitialized({ includeHiddenNodes: false }); return useEffect3(() => { const nodes = getNodes(); if (nodesInitialized && shouldLayout && nodes.every((node) => typeof node.width !== "undefined" && typeof node.height !== "undefined")) { setShouldLayout(false); callback(); } }, [getNodes, shouldLayout, callback, nodesInitialized]); } function buildGraph(nodes, edges, zoom, reactFlowRef) { if (!checkLicense()) { return new Graph2(); } const builder = new GraphBuilder(); const nodesSource = builder.createNodesSource({ data: nodes, id: (node) => node.id, parentId: (node) => node.parentNode, layout: (node) => Rect.from([node.position.x, node.position.y, node.width ?? 1, node.height ?? 1]) }); nodesSource.nodeCreator.createLabelsSource({ data: (node) => [node.data.label] }); nodesSource.nodeCreator.defaults.labels.layoutParameter = FreeNodeLabelModel.CENTER; const edgesSource = builder.createEdgesSource({ data: edges, sourceId: (edge) => edge.source, targetId: (edge) => edge.target }); const edgeCreator = edgesSource.edgeCreator; edgeCreator.bendsProvider = (edge) => edge.data?.yData?.bends?.map((bend) => Point.from(bend)); const labelsSource = edgeCreator.createLabelsSource({ data: (edge) => { let labels = []; if (edge.label) { labels.push(edge.label); } if (edge.data?.labels) { labels = labels.concat(edge.data.labels); } return labels; } }); labelsSource.labelCreator.tagProvider = (dataItem) => dataItem; edgeCreator.defaults.labels.layoutParameter = FreeEdgeLabelModel.INSTANCE.createParameter(); const graph = builder.buildGraph(); const rootElement = getRootNode(reactFlowRef); graph.nodes.forEach((node) => { node.labels.forEach((label, index) => { const preferredSize = measureLabel(`node-label-${node.tag.id}-${index}`, zoom, rootElement); if (preferredSize.width > 0 && preferredSize.height > 0) { graph.setLabelPreferredSize(label, preferredSize); } if (node.tag.data?.yData) { const yData = node.tag.data?.yData; const labelBoxes = yData?.labelBoxes; if (labelBoxes && labelBoxes.length > 0) { const labelBox = yData.labelBoxes[0]; if (labelBox) { const labelRectangle = new OrientedRectangle( labelBox.x + node.layout.center.x - labelBox.width * 0.5, labelBox.y + node.layout.center.y + labelBox.height * 0.5, labelBox.width, labelBox.height ); const center = labelRectangle.center; labelRectangle.angle = labelBox.angle; labelRectangle.center = center; graph.setLabelLayoutParameter( label, FreeNodeLabelModel.INSTANCE.findBestParameter(label, labelRectangle) ); } } } }); }); graph.edges.forEach((edge) => { edge.labels.forEach((label, index) => { const preferredSize = measureLabel(`edge-label-${edge.tag.id}-${index}`, zoom, rootElement); if (preferredSize.width > 0 && preferredSize.height > 0) { graph.setLabelPreferredSize(label, preferredSize); } label.tag = { ownerId: edge.tag.id, labelIndex: index, data: label.tag }; if (edge.tag.data?.yData) { const yData = edge.tag.data?.yData; const labelBoxes = yData?.labelBoxes; if (labelBoxes && labelBoxes.length > 0) { const labelBox = yData.labelBoxes[index]; if (labelBox) { const labelRectangle = new OrientedRectangle( labelBox.x, labelBox.y, preferredSize.width, preferredSize.height ); labelRectangle.angle = labelBox.angle; graph.setLabelLayoutParameter( label, FreeEdgeLabelModel.INSTANCE.findBestParameter(label, labelRectangle) ); } } } }); if (edge.tag.data?.yData?.sourcePoint && edge.tag.data?.yData?.targetPoint) { const sourceNode = edge.sourceNode; const targetNode = edge.targetNode; graph.setEdgePorts( edge, graph.addPortAt(sourceNode, { x: sourceNode.layout.x + edge.tag.data?.yData?.sourcePoint.x, y: sourceNode.layout.y + edge.tag.data?.yData?.sourcePoint.y }), graph.addPortAt(targetNode, { x: targetNode.layout.x + edge.tag.data?.yData?.targetPoint.x, y: targetNode.layout.y + edge.tag.data?.yData?.targetPoint.y }) ); } }); return graph; } function transferLayout(graph, reactFlowRef) { if (!checkLicense()) { return { arrangedNodes: [], arrangedEdges: [] }; } const nodeOffsets = /* @__PURE__ */ new Map(); const hierarchicNodes = new GroupingSupport(graph).getDescendantsBottomUp(null); hierarchicNodes.forEach((node) => { const parent = graph.getParent(node); if (parent) { nodeOffsets.set(node, { dx: -parent.layout.x, dy: -parent.layout.y }); } }); const rootElement = getRootNode(reactFlowRef); const arrangedNodes = graph.nodes.map((node) => { const offset = nodeOffsets.get(node) ?? { dx: 0, dy: 0 }; return { ...node.tag, position: { x: node.layout.x + offset.dx, y: node.layout.y + offset.dy }, style: { ...node.tag.style, width: node.layout.width, height: node.layout.height }, data: { ...node.tag.data, yData: { sourceHandles: graph.outEdgesAt(node).map((edge) => ({ id: getSourceHandleId(edge), location: { x: edge.sourcePort.location.x - edge.sourceNode.layout.x, y: edge.sourcePort.location.y - edge.sourceNode.layout.y }, position: getHandlePosition(edge, true) })).toArray(), targetHandles: graph.inEdgesAt(node).map((edge) => ({ id: getTargetHandleId(edge), location: { x: edge.targetPort.location.x - edge.targetNode.layout.x, y: edge.targetPort.location.y - edge.targetNode.layout.y }, position: getHandlePosition(edge, false) })).toArray(), labelBoxes: node.labels.map((label, index) => { const rect = label.layout; const labelId = `node-label-${node.tag.id}-${index}`; const padding = calculatePadding2(labelId, rootElement); const angle = rect.angle; return { id: labelId, x: rect.center.x - node.layout.center.x, y: rect.center.y - node.layout.center.y, width: rect.width - padding.right - padding.left, height: rect.height - padding.top - padding.bottom, angle, transform: `rotate(${angle}rad)` }; }).toArray() } } }; }).toArray(); const arrangedEdges = graph.edges.map((edge) => { return { ...edge.tag, sourceHandle: getSourceHandleId(edge), targetHandle: getTargetHandleId(edge), data: { ...edge.tag.data, yData: { bends: edge.bends.map((bend) => ({ x: bend.location.x, y: bend.location.y })).toArray(), sourcePoint: { x: edge.sourcePort.location.x - edge.sourceNode.layout.x, y: edge.sourcePort.location.y - edge.sourceNode.layout.y }, targetPoint: { x: edge.targetPort.location.x - edge.targetNode.layout.x, y: edge.targetPort.location.y - edge.targetNode.layout.y }, labelBoxes: edge.labels.map((label, index) => { const rect = label.layout; const labelId = `edge-label-${edge.tag.id}-${index}`; const padding = calculatePadding2(labelId, rootElement); return { id: labelId, x: rect.anchorX, y: rect.anchorY, width: rect.width - padding.left - padding.right, height: rect.height - padding.top - padding.bottom, angle: rect.angle, transform: getTransformMatrix(rect).toCssTransform() }; }).toArray() } } }; }).toArray(); return { arrangedNodes, arrangedEdges }; } function getSourceHandleId(edge) { return `${edge.tag.id}-source`; } function getTargetHandleId(edge) { return `${edge.tag.id}-target`; } function getHandlePosition(edge, isSourceHandle) { const sourcePortLocation = edge.sourcePort.location.toPoint(); const targetPortLocation = edge.targetPort.location.toPoint(); const port = (isSourceHandle ? sourcePortLocation : targetPortLocation).toPoint(); const nodeRect = (isSourceHandle ? edge.sourceNode.layout : edge.targetNode.layout).toRect(); let side = getSide(nodeRect, port); if (side === "center") { const oppositePort = isSourceHandle ? targetPortLocation : sourcePortLocation; const firstEdgePoint = edge.bends.size > 0 ? (isSourceHandle ? edge.bends.at(0).location : edge.bends.at(-1).location).toPoint() : oppositePort; const intersectionPoint = nodeRect.findLineIntersection(port, firstEdgePoint); if (intersectionPoint) { side = getSide(nodeRect, intersectionPoint.toPoint()); } } return translateSide(side); } function getSide(rect, point) { const EPSILON = 1e-3; if (Math.abs(point.x - rect.topLeft.x) < EPSILON) { return "left"; } else if (Math.abs(point.x - rect.bottomRight.x) < EPSILON) { return "right"; } if (Math.abs(point.y - rect.topLeft.y) < EPSILON) { return "top"; } else if (Math.abs(point.y - rect.bottomLeft.y) < EPSILON) { return "bottom"; } if (Math.abs(point.x - rect.center.x) < EPSILON || Math.abs(point.y - rect.center.y) < EPSILON) { return "center"; } return "other"; } function translateSide(side) { switch (side) { case "left": return Position2.Right; case "right": return Position2.Left; case "top": return Position2.Bottom; case "bottom": return Position2.Top; case "other": default: return Position2.Top; } } function measureLabel(labelId, zoom, rootElement) { const labelElement = rootElement.querySelector(`[data-id="${labelId}"]`); if (labelElement) { const oldTransform = labelElement.style.transform; labelElement.style.transform = ""; const oldBoxSizing = labelElement.style.boxSizing; labelElement.style.boxSizing = "content-box"; const computedStyle = window.getComputedStyle(labelElement); const labelPadding = calculatePadding2(labelId, rootElement); let width = parseFloat(computedStyle.width); let height = parseFloat(computedStyle.height); if (width <= 0 || height <= 0) { const clientRect = labelElement.getBoundingClientRect(); width = width > 0 ? width : clientRect.width / zoom; height = height > 0 ? height : clientRect.height / zoom; } labelElement.style.transform = oldTransform; labelElement.style.boxSizing = oldBoxSizing; return new Size( width + labelPadding.left + labelPadding.right, height + labelPadding.top + labelPadding.bottom ); } return Size.ZERO; } function calculatePadding2(labelId, rootElement) { const element = rootElement.querySelector(`[data-id="${labelId}"]`); if (element) { const elementStyle = window.getComputedStyle(element); return { top: parseFloat(elementStyle.paddingTop) ?? 0, right: parseFloat(elementStyle.paddingRight) ?? 0, bottom: parseFloat(elementStyle.paddingBottom) ?? 0, left: parseFloat(elementStyle.paddingLeft) ?? 0 }; } return { top: 0, bottom: 0, left: 0, right: 0 }; } function getTransformMatrix(layout) { const projectedBaseline = new Point(-layout.upY, layout.upX); const width = layout.width; const height = layout.height; const anchorX = layout.anchorX; const anchorY = layout.anchorY; const upY = layout.upY; const upX = layout.upX; if (layout.upY == -1) { return new Matrix(1, 0, 0, 1, anchorX, anchorY - height); } if (projectedBaseline.x < 0) { return new Matrix(upY, upX, -upX, upY, anchorX - upY * width, anchorY + upX * width); } return new Matrix(-upY, -upX, upX, -upY, anchorX + upX * height, anchorY + upY * height); } function getConfiguration(configuration) { if (typeof configuration === "string") { return { name: configuration }; } return { name: configuration.name, layoutOptions: configuration.layoutOptions, layoutData: configuration.layoutData ?? {} }; } export { EdgeLabels, MultiHandleNode, NodeLabel, PolylineEdge, getPolylinePath, initializeWebWorker, registerLicense, useLayout, useLayoutSupport, useNodesMeasuredEffect, withMultiHandles };