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

298 lines (294 loc) 10.1 kB
// src/layout/WebWorkerSupport.ts import { LayoutExecutorAsyncWorker, License, MinimumNodeSizeStage } from "@yfiles/yfiles"; // src/layout/layout-algorithms.ts import { CircularLayout, CircularLayoutData, EdgeLabelPreferredPlacement as YFilesEdgeLabelPreferredPlacement, EdgePortCandidates, EdgeRouter, EdgeRouterData, GenericLabeling, GenericLabelingData, GenericLayoutData, HierarchicalLayout, HierarchicalLayoutData, Insets as YFilesInsets, LayoutKeys, OrganicEdgeRouter, OrganicLayout, OrganicLayoutData, OrthogonalLayout, OrthogonalLayoutData, RadialLayout, RadialLayoutData, RadialTreeLayout, RadialTreeLayoutData, TreeLayout, TreeLayoutData, TreeReductionStage } from "@yfiles/yfiles"; async function getLayoutAlgorithm(layoutDescriptor) { switch (layoutDescriptor.name) { case "GenericLabeling": { return new GenericLabeling(layoutDescriptor.properties); } case "RadialTreeLayout": { return new TreeReductionStage({ coreLayout: new RadialTreeLayout(layoutDescriptor.properties), nonTreeEdgeRouter: new OrganicEdgeRouter() }); } case "HierarchicalLayout": { return new HierarchicalLayout(layoutDescriptor.properties); } case "CircularLayout": { return new CircularLayout(layoutDescriptor.properties); } case "OrganicLayout": { return new OrganicLayout(layoutDescriptor.properties); } case "OrthogonalLayout": { return new OrthogonalLayout(layoutDescriptor.properties); } case "EdgeRouter": { return new EdgeRouter(layoutDescriptor.properties); } case "RadialLayout": { return new RadialLayout(layoutDescriptor.properties); } case "TreeLayout": { return new TreeReductionStage({ coreLayout: new TreeLayout(layoutDescriptor.properties), nonTreeEdgeRouter: new EdgeRouter() }); } default: { return new OrganicLayout(); } } } function getLayoutData(layoutName, layoutDataDescriptor, reactFlowRef) { const layoutData = new GenericLayoutData(); const rootElement = getRootNode(reactFlowRef); layoutData.addItemMapping( LayoutKeys.GROUP_NODE_PADDING_DATA_KEY, (node) => ( // @ts-expect-error property-groupNodePadding-does-not-exist-on-type-LayoutDataProvider translatePadding(rootElement, node, layoutDataDescriptor?.groupNodePadding?.(node.tag)) ) ); switch (layoutName) { case "GenericLabeling": layoutData.add(new GenericLabelingData(translateLayoutDataDescriptor(layoutDataDescriptor))); break; case "RadialTreeLayout": layoutData.add(new RadialTreeLayoutData(translateLayoutDataDescriptor(layoutDataDescriptor))); break; case "HierarchicalLayout": layoutData.add( new HierarchicalLayoutData(translateLayoutDataDescriptor(layoutDataDescriptor)) ); break; case "CircularLayout": layoutData.add(new CircularLayoutData(translateLayoutDataDescriptor(layoutDataDescriptor))); break; case "OrganicLayout": layoutData.add(new OrganicLayoutData(translateLayoutDataDescriptor(layoutDataDescriptor))); break; case "OrthogonalLayout": layoutData.add(new OrthogonalLayoutData(translateLayoutDataDescriptor(layoutDataDescriptor))); break; case "EdgeRouter": layoutData.add(new EdgeRouterData(translateLayoutDataDescriptor(layoutDataDescriptor))); break; case "RadialLayout": layoutData.add(new RadialLayoutData(translateLayoutDataDescriptor(layoutDataDescriptor))); break; case "TreeLayout": layoutData.add(new TreeLayoutData(translateLayoutDataDescriptor(layoutDataDescriptor))); break; default: } return layoutData; } function translateLayoutDataDescriptor(layoutDataDescriptor) { if (!layoutDataDescriptor) { return {}; } const translatedLayoutDataDescriptor = {}; Object.keys(layoutDataDescriptor).forEach((key) => { const originalFunction = layoutDataDescriptor[key]; if (originalFunction) { if (layoutDataDescriptor.groupNodePadding) { return translatedLayoutDataDescriptor.groupNodePadding = (item) => translateNodeMargins(originalFunction(item.tag)); } else if (key === "edgeLabelPreferredPlacements") { translatedLayoutDataDescriptor[key] = (item) => translateEdgeLabelPreferredPlacement( originalFunction(item.tag) ); } else if (key === "nodeMargins") { translatedLayoutDataDescriptor[key] = (item) => translateNodeMargins(originalFunction(item.tag)); } else if (key === "ports") { translatedLayoutDataDescriptor[key] = translatePortDataProvider( // @ts-expect-error property-ports-does-not-exist-on-type-LayoutDataProvider layoutDataDescriptor[key] ); } else if (key === "childOrder") { translatedLayoutDataDescriptor[key] = translateChildOrderDataProvider( // @ts-expect-error property-childOrder-does-not-exist-on-type-LayoutDataProvider layoutDataDescriptor[key] ); } else if (key === "scope") { translatedLayoutDataDescriptor[key] = translateScopeDataProvider( // @ts-expect-error property-scope-does-not-exist-on-type-LayoutDataProvider layoutDataDescriptor[key] ); } else { translatedLayoutDataDescriptor[key] = (item) => originalFunction(item.tag); } } }); return translatedLayoutDataDescriptor; } function translateChildOrderDataProvider(childOrder) { const translatedChildOrderData = {}; Object.keys(childOrder).forEach((key) => { const originalFunction = childOrder[key]; translatedChildOrderData[key] = (node) => translateOutEdgeComparers( originalFunction(node.tag) ); }); return translatedChildOrderData; } function translateOutEdgeComparers(outEdgeComparer) { return (e1, e2) => outEdgeComparer(e1.tag, e2.tag); } function translatePortDataProvider(ports) { const translatedPortData = {}; Object.keys(ports).forEach((key) => { const originalFunction = ports[key]; translatedPortData[key] = key === "sourcePortCandidates" || key === "targetPortCandidates" ? (item) => translatePortCandidates(originalFunction(item.tag)) : (item) => originalFunction(item.tag); }); return translatedPortData; } function translatePortCandidates(sides) { const portCandidates = new EdgePortCandidates(); sides.forEach((side) => { portCandidates.addFreeCandidate(side); }); return portCandidates; } function translateScopeDataProvider(scope) { const translatedScopeData = {}; Object.keys(scope).forEach((key) => { const originalFunction = scope[key]; translatedScopeData[key] = (item) => originalFunction(item.tag); }); return translatedScopeData; } function translateNodeMargins(insets) { if (typeof insets === "number") { return new YFilesInsets(insets); } return new YFilesInsets(insets.top, insets.right, insets.bottom, insets.left); } function translatePadding(rootElement, node, insets) { if (insets) { if (typeof insets === "number") { return new YFilesInsets(insets, insets, insets, insets); } return new YFilesInsets(insets.top, insets.right, insets.bottom, insets.left); } return getGroupNodePadding(node, rootElement); } function translateEdgeLabelPreferredPlacement(descriptor) { return new YFilesEdgeLabelPreferredPlacement(descriptor); } function getGroupNodePadding(node, rootElement) { const nodeElement = rootElement.querySelector(`[data-id="${node.tag.id}"]`); if (nodeElement) { const computedStyle = window.getComputedStyle(nodeElement); const paddingTop = parseInt(computedStyle.getPropertyValue("padding-top")) ?? 0; const paddingLeft = parseInt(computedStyle.getPropertyValue("padding-left")) ?? 0; const paddingBottom = parseInt(computedStyle.getPropertyValue("padding-bottom")) ?? 0; const paddingRight = parseInt(computedStyle.getPropertyValue("padding-right")) ?? 0; return new YFilesInsets(paddingTop, paddingLeft, paddingBottom, paddingRight); } return new YFilesInsets(0, 0, 0, 0); } function getRootNode(reactFlowRef) { if (reactFlowRef?.current) { return reactFlowRef.current.getRootNode(); } const shadowRoots = Array.from(document.querySelectorAll("*")).filter((e) => !!e.shadowRoot).map((e) => e.shadowRoot); if (shadowRoots.length > 0) { if (shadowRoots.length > 1) { console.warn( "There are more than one shadow roots. For correct results, please call useLayout or useLayoutSupport with a reference to the corresponding reactflow component." ); } return shadowRoots.at(0); } return document; } // src/layout/WebWorkerSupport.ts var license = null; function setWebWorkerLicense(licensePar) { license = licensePar; } function registerWebWorker(worker) { if (license === null) { throw new Error("License not initialized."); } return new Promise((resolve) => { worker.onmessage = (event) => { if (event.data === "ready") { worker.postMessage({ license }); } else if (event.data === "licensed") { resolve(worker); } }; worker.postMessage("check-is-ready"); }); } function initializeWebWorker(self) { self.addEventListener( "message", (e) => { if (e.data.license) { License.value = e.data.license; self.postMessage("licensed"); return; } if (e.data === "check-is-ready") { self.postMessage("ready"); return; } new LayoutExecutorAsyncWorker(applyLayout).process(e.data).then((data) => { self.postMessage(data); }).catch((errorObj) => { self.postMessage(errorObj); }); }, false ); self.postMessage("ready"); } async function applyLayout(graph, layoutDescriptor) { const layout = await getLayoutAlgorithm(layoutDescriptor); new MinimumNodeSizeStage(layout).applyLayout(graph); } export { getLayoutAlgorithm, getLayoutData, getRootNode, setWebWorkerLicense, registerWebWorker, initializeWebWorker };