@gravity-ui/graph
Version:
Modern graph editor component
134 lines (133 loc) • 4.34 kB
JavaScript
import { DEFAULT_NODE_WIDTH } from "../layout";
function buildAdjacency(edges) {
const adjacency = new Map();
for (const edge of edges) {
const from = String(edge.from);
const to = String(edge.to);
const neighbors = adjacency.get(from);
if (neighbors) {
neighbors.push({ to, arrows: edge.arrows });
}
else {
adjacency.set(from, [{ to, arrows: edge.arrows }]);
}
}
return adjacency;
}
function getVirtualNodeCenter(nodePositions, virtualNodeSize) {
return (id) => {
const pos = nodePositions.get(id);
if (!pos)
return undefined;
const size = virtualNodeSize ?? DEFAULT_NODE_WIDTH;
return {
x: pos.x + size / 2,
y: pos.y + size / 2,
};
};
}
function getBlockRightEdge(id, nodePositions, blockSizes) {
const pos = nodePositions.get(id);
if (!pos)
return undefined;
const size = blockSizes.get(id);
if (size) {
return {
x: pos.x + size.width,
y: pos.y + size.height / 2,
};
}
return pos;
}
function getBlockLeftEdge(id, nodePositions, blockSizes) {
const pos = nodePositions.get(id);
if (!pos)
return undefined;
const size = blockSizes.get(id);
if (size) {
return {
x: pos.x,
y: pos.y + size.height / 2,
};
}
return pos;
}
/**
* Converts the result of layoutGraph() into the same format as ELK plugin (ConverterResult)
* so it can be used with setEntities(blocks, connections) the same way.
*/
// eslint-disable-next-line complexity
export function layeredConverter({ layoutResult, connectionIdBySourceTarget, blockSizes, virtualNodeSize, }) {
const { nodes, edges } = layoutResult;
const nodePositions = new Map();
const dotNodeIds = new Set();
for (const node of nodes) {
const id = String(node.id);
nodePositions.set(id, { x: node.x ?? 0, y: node.y ?? 0 });
if (node.shape === "dot") {
dotNodeIds.add(id);
}
}
const blocks = {};
for (const node of nodes) {
if (node.shape === "dot")
continue;
const id = node.id;
const pos = nodePositions.get(String(id));
if (pos) {
blocks[id] = pos;
}
}
const edgesResult = {};
const adjacency = buildAdjacency(edges);
const visitedEdges = new Set();
const getVirtualCenter = getVirtualNodeCenter(nodePositions, virtualNodeSize);
for (const edge of edges) {
const from = String(edge.from);
const to = String(edge.to);
const edgeKey = `${from}->${to}`;
if (visitedEdges.has(edgeKey))
continue;
if (dotNodeIds.has(from))
continue;
const chain = [from];
let current = to;
visitedEdges.add(edgeKey);
while (dotNodeIds.has(current)) {
chain.push(current);
const nextEdges = adjacency.get(current);
if (!nextEdges || nextEdges.length === 0)
break;
const nextEdge = nextEdges[0];
const nextEdgeKey = `${current}->${nextEdge.to}`;
visitedEdges.add(nextEdgeKey);
current = nextEdge.to;
}
chain.push(current);
const sourceId = chain[0];
const targetId = chain[chain.length - 1];
const key = `${sourceId}/${targetId}`;
const idQueue = connectionIdBySourceTarget.get(key);
const connectionId = (idQueue?.length ? idQueue.shift() : null) ?? key;
const points = [];
const sourceEdge = getBlockRightEdge(sourceId, nodePositions, blockSizes);
if (sourceEdge)
points.push(sourceEdge);
if (chain.length > 2) {
for (let i = 1; i < chain.length - 1; i++) {
const center = getVirtualCenter(chain[i]);
if (center)
points.push(center);
}
}
const targetEdge = getBlockLeftEdge(targetId, nodePositions, blockSizes);
if (targetEdge)
points.push(targetEdge);
if (points.length >= 2) {
edgesResult[connectionId] = {
points,
};
}
}
return { blocks, edges: edgesResult };
}