UNPKG

mermaid

Version:

Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.

306 lines (301 loc) 9.18 kB
import { log } from "./chunk-X3CZISLH.mjs"; import { __name } from "./chunk-Y2CYZVJY.mjs"; // src/rendering-util/layout-algorithms/cose-bilkent/cytoscape-setup.ts import cytoscape from "cytoscape"; import coseBilkent from "cytoscape-cose-bilkent"; import { select } from "d3"; cytoscape.use(coseBilkent); function addNodes(nodes, cy) { nodes.forEach((node) => { const nodeData = { id: node.id, labelText: node.label, height: node.height, width: node.width, padding: node.padding ?? 0 }; Object.keys(node).forEach((key) => { if (!["id", "label", "height", "width", "padding", "x", "y"].includes(key)) { nodeData[key] = node[key]; } }); cy.add({ group: "nodes", data: nodeData, position: { x: node.x ?? 0, y: node.y ?? 0 } }); }); } __name(addNodes, "addNodes"); function addEdges(edges, cy) { edges.forEach((edge) => { const edgeData = { id: edge.id, source: edge.start, target: edge.end }; Object.keys(edge).forEach((key) => { if (!["id", "start", "end"].includes(key)) { edgeData[key] = edge[key]; } }); cy.add({ group: "edges", data: edgeData }); }); } __name(addEdges, "addEdges"); function createCytoscapeInstance(data) { return new Promise((resolve) => { const renderEl = select("body").append("div").attr("id", "cy").attr("style", "display:none"); const cy = cytoscape({ container: document.getElementById("cy"), // container to render in style: [ { selector: "edge", style: { "curve-style": "bezier" } } ] }); renderEl.remove(); addNodes(data.nodes, cy); addEdges(data.edges, cy); cy.nodes().forEach(function(n) { n.layoutDimensions = () => { const nodeData = n.data(); return { w: nodeData.width, h: nodeData.height }; }; }); const layoutConfig = { name: "cose-bilkent", // @ts-ignore Types for cose-bilkent are not correct? quality: "proof", styleEnabled: false, animate: false }; cy.layout(layoutConfig).run(); cy.ready((e) => { log.info("Cytoscape ready", e); resolve(cy); }); }); } __name(createCytoscapeInstance, "createCytoscapeInstance"); function extractPositionedNodes(cy) { return cy.nodes().map((node) => { const data = node.data(); const position = node.position(); const positionedNode = { id: data.id, x: position.x, y: position.y }; Object.keys(data).forEach((key) => { if (key !== "id") { positionedNode[key] = data[key]; } }); return positionedNode; }); } __name(extractPositionedNodes, "extractPositionedNodes"); function extractPositionedEdges(cy) { return cy.edges().map((edge) => { const data = edge.data(); const rscratch = edge._private.rscratch; const positionedEdge = { id: data.id, source: data.source, target: data.target, startX: rscratch.startX, startY: rscratch.startY, midX: rscratch.midX, midY: rscratch.midY, endX: rscratch.endX, endY: rscratch.endY }; Object.keys(data).forEach((key) => { if (!["id", "source", "target"].includes(key)) { positionedEdge[key] = data[key]; } }); return positionedEdge; }); } __name(extractPositionedEdges, "extractPositionedEdges"); // src/rendering-util/layout-algorithms/cose-bilkent/layout.ts async function executeCoseBilkentLayout(data, _config) { log.debug("Starting cose-bilkent layout algorithm"); try { validateLayoutData(data); const cy = await createCytoscapeInstance(data); const positionedNodes = extractPositionedNodes(cy); const positionedEdges = extractPositionedEdges(cy); log.debug(`Layout completed: ${positionedNodes.length} nodes, ${positionedEdges.length} edges`); return { nodes: positionedNodes, edges: positionedEdges }; } catch (error) { log.error("Error in cose-bilkent layout algorithm:", error); throw error; } } __name(executeCoseBilkentLayout, "executeCoseBilkentLayout"); function validateLayoutData(data) { if (!data) { throw new Error("Layout data is required"); } if (!data.config) { throw new Error("Configuration is required in layout data"); } if (!data.rootNode) { throw new Error("Root node is required"); } if (!data.nodes || !Array.isArray(data.nodes)) { throw new Error("No nodes found in layout data"); } if (!Array.isArray(data.edges)) { throw new Error("Edges array is required in layout data"); } return true; } __name(validateLayoutData, "validateLayoutData"); // src/rendering-util/layout-algorithms/cose-bilkent/render.ts var render = /* @__PURE__ */ __name(async (data4Layout, svg, { insertCluster, insertEdge, insertEdgeLabel, insertMarkers, insertNode, log: log2, positionEdgeLabel }, { algorithm: _algorithm }) => { const nodeDb = {}; const clusterDb = {}; const element = svg.select("g"); insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId); const subGraphsEl = element.insert("g").attr("class", "subgraphs"); const edgePaths = element.insert("g").attr("class", "edgePaths"); const edgeLabels = element.insert("g").attr("class", "edgeLabels"); const nodes = element.insert("g").attr("class", "nodes"); log2.debug("Inserting nodes into DOM for dimension calculation"); await Promise.all( data4Layout.nodes.map(async (node) => { if (node.isGroup) { const clusterNode = { ...node }; clusterDb[node.id] = clusterNode; nodeDb[node.id] = clusterNode; await insertCluster(subGraphsEl, node); } else { const nodeWithPosition = { ...node }; nodeDb[node.id] = nodeWithPosition; const nodeEl = await insertNode(nodes, node, { config: data4Layout.config, dir: data4Layout.direction || "TB" }); const boundingBox = nodeEl.node().getBBox(); nodeWithPosition.width = boundingBox.width; nodeWithPosition.height = boundingBox.height; nodeWithPosition.domId = nodeEl; log2.debug(`Node ${node.id} dimensions: ${boundingBox.width}x${boundingBox.height}`); } }) ); log2.debug("Running cose-bilkent layout algorithm"); const updatedLayoutData = { ...data4Layout, nodes: data4Layout.nodes.map((node) => { const nodeWithDimensions = nodeDb[node.id]; return { ...node, width: nodeWithDimensions.width, height: nodeWithDimensions.height }; }) }; const layoutResult = await executeCoseBilkentLayout(updatedLayoutData, data4Layout.config); log2.debug("Positioning nodes based on layout results"); layoutResult.nodes.forEach((positionedNode) => { const node = nodeDb[positionedNode.id]; if (node?.domId) { node.domId.attr( "transform", `translate(${positionedNode.x}, ${positionedNode.y})` ); node.x = positionedNode.x; node.y = positionedNode.y; log2.debug(`Positioned node ${node.id} at center (${positionedNode.x}, ${positionedNode.y})`); } }); layoutResult.edges.forEach((positionedEdge) => { const edge = data4Layout.edges.find((e) => e.id === positionedEdge.id); if (edge) { edge.points = [ { x: positionedEdge.startX, y: positionedEdge.startY }, { x: positionedEdge.midX, y: positionedEdge.midY }, { x: positionedEdge.endX, y: positionedEdge.endY } ]; } }); log2.debug("Inserting and positioning edges"); await Promise.all( data4Layout.edges.map(async (edge) => { const _edgeLabel = await insertEdgeLabel(edgeLabels, edge); const startNode = nodeDb[edge.start ?? ""]; const endNode = nodeDb[edge.end ?? ""]; if (startNode && endNode) { const positionedEdge = layoutResult.edges.find((e) => e.id === edge.id); if (positionedEdge) { log2.debug("APA01 positionedEdge", positionedEdge); const edgeWithPath = { ...edge }; const paths = insertEdge( edgePaths, edgeWithPath, clusterDb, data4Layout.type, startNode, endNode, data4Layout.diagramId ); positionEdgeLabel(edgeWithPath, paths); } else { const edgeWithPath = { ...edge, points: [ { x: startNode.x || 0, y: startNode.y || 0 }, { x: endNode.x || 0, y: endNode.y || 0 } ] }; const paths = insertEdge( edgePaths, edgeWithPath, clusterDb, data4Layout.type, startNode, endNode, data4Layout.diagramId ); positionEdgeLabel(edgeWithPath, paths); } } }) ); log2.debug("Cose-bilkent rendering completed"); }, "render"); // src/rendering-util/layout-algorithms/cose-bilkent/index.ts var render2 = render; export { render2 as render };