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
JavaScript
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
};