import { __awaiter } from "tslib";
import { Graph } from '@antv/graphlib';
import { isNumber } from '@antv/util';
import { layout } from './antv-dagre/layout';
import { cloneFormatData, formatNumberFn, formatSizeFn } from './util';
import { parseSize } from './util/size';
const DEFAULTS_LAYOUT_OPTIONS = {
rankdir: 'TB',
nodesep: 50,
ranksep: 50,
edgeLabelSpace: true,
ranker: 'tight-tree',
controlPoints: false,
radial: false,
focusNode: null, // radial 为 true 时生效,关注的节点
};
/**
* AntV 实现的 Dagre 布局
*
* AntV implementation of Dagre layout
*/
export class AntVDagreLayout {
constructor(options = {}) {
this.options = options;
this.id = 'antv-dagre';
this.options = Object.assign(Object.assign({}, DEFAULTS_LAYOUT_OPTIONS), options);
}
/**
* Return the positions of nodes and edges(if needed).
*/
execute(graph, options) {
return __awaiter(this, void 0, void 0, function* () {
return this.genericDagreLayout(false, graph, options);
});
}
/**
* To directly assign the positions to the nodes.
*/
assign(graph, options) {
return __awaiter(this, void 0, void 0, function* () {
yield this.genericDagreLayout(true, graph, options);
});
}
genericDagreLayout(assign, graph, options) {
return __awaiter(this, void 0, void 0, function* () {
const mergedOptions = Object.assign(Object.assign({}, this.options), options);
const { nodeSize, align, rankdir = 'TB', ranksep, nodesep, ranksepFunc, nodesepFunc, edgeLabelSpace, ranker, nodeOrder, begin, controlPoints, radial, sortByCombo,
// focusNode,
preset, } = mergedOptions;
const g = new Graph({
tree: [],
});
const ranksepfunc = formatNumberFn(ranksep || 50, ranksepFunc);
const nodesepfunc = formatNumberFn(nodesep || 50, nodesepFunc);
let horisep = nodesepfunc;
let vertisep = ranksepfunc;
if (rankdir === 'LR' || rankdir === 'RL') {
horisep = ranksepfunc;
vertisep = nodesepfunc;
}
const nodeSizeFunc = formatSizeFn(10, nodeSize, false);
// copy graph to g
const nodes = graph.getAllNodes();
const edges = graph.getAllEdges();
nodes.forEach((node) => {
const size = parseSize(nodeSizeFunc(node));
const verti = vertisep(node);
const hori = horisep(node);
const width = size[0] + 2 * hori;
const height = size[1] + 2 * verti;
const layer = node.data.layer;
if (isNumber(layer)) {
// 如果有layer属性,加入到node的label中
g.addNode({
id: node.id,
data: { width, height, layer },
});
}
else {
g.addNode({
id: node.id,
data: { width, height },
});
}
});
if (sortByCombo) {
g.attachTreeStructure('combo');
nodes.forEach((node) => {
const { parentId } = node.data;
if (parentId === undefined)
return;
if (g.hasNode(parentId)) {
g.setParent(node.id, parentId, 'combo');
}
});
}
edges.forEach((edge) => {
// dagrejs Wiki https://github.com/dagrejs/dagre/wiki#configuring-the-layout
g.addEdge({
id: edge.id,
source: edge.source,
target: edge.target,
data: {
weight: edge.data.weight || 1,
},
});
});
let prevGraph = undefined;
if (preset === null || preset === void 0 ? void 0 : preset.length) {
prevGraph = new Graph({
nodes: preset,
});
}
layout(g, {
prevGraph,
edgeLabelSpace,
keepNodeOrder: !!nodeOrder,
nodeOrder: nodeOrder || [],
acyclicer: 'greedy',
ranker,
rankdir,
nodesep,
align,
});
const layoutTopLeft = [0, 0];
if (begin) {
let minX = Infinity;
let minY = Infinity;
g.getAllNodes().forEach((node) => {
if (minX > node.data.x)
minX = node.data.x;
if (minY > node.data.y)
minY = node.data.y;
});
g.getAllEdges().forEach((edge) => {
var _a;
(_a = edge.data.points) === null || _a === void 0 ? void 0 : _a.forEach((point) => {
if (minX > point.x)
minX = point.x;
if (minY > point.y)
minY = point.y;
});
});
layoutTopLeft[0] = begin[0] - minX;
layoutTopLeft[1] = begin[1] - minY;
}
const isHorizontal = rankdir === 'LR' || rankdir === 'RL';
if (radial) {
// const focusId = (isString(focusNode) ? focusNode : focusNode?.id) as ID;
// const focusLayer = focusId ? g.getNode(focusId)?.data._rank as number : 0;
// const layers: any[] = [];
// const dim = isHorizontal ? "y" : "x";
// const sizeDim = isHorizontal ? "height" : "width";
// // 找到整个图作为环的坐标维度(dim)的最大、最小值,考虑节点宽度
// let min = Infinity;
// let max = -Infinity;
// g.getAllNodes().forEach((node) => {
// const currentNodesep = nodesepfunc(node);
// if (focusLayer === 0) {
// if (!layers[node.data._rank!]) {
// layers[node.data._rank!] = {
// nodes: [],
// totalWidth: 0,
// maxSize: -Infinity,
// };
// }
// layers[node.data._rank!].nodes.push(node);
// layers[node.data._rank!].totalWidth += currentNodesep * 2 + node.data[sizeDim]!;
// if (
// layers[node.data._rank!].maxSize < Math.max(node.data.width!, node.data.height!)
// ) {
// layers[node.data._rank!].maxSize = Math.max(node.data.width!, node.data.height!);
// }
// } else {
// const diffLayer = node.data._rank! - focusLayer!;
// if (diffLayer === 0) {
// if (!layers[diffLayer]) {
// layers[diffLayer] = {
// nodes: [],
// totalWidth: 0,
// maxSize: -Infinity,
// };
// }
// layers[diffLayer].nodes.push(node);
// layers[diffLayer].totalWidth += currentNodesep * 2 + node.data[sizeDim]!;
// if (
// layers[diffLayer].maxSize < Math.max(node.data.width!, node.data.height!)
// ) {
// layers[diffLayer].maxSize = Math.max(node.data.width!, node.data.height!);
// }
// } else {
// const diffLayerAbs = Math.abs(diffLayer);
// if (!layers[diffLayerAbs]) {
// layers[diffLayerAbs] = {
// left: [],
// right: [],
// totalWidth: 0,
// maxSize: -Infinity,
// };
// }
// layers[diffLayerAbs].totalWidth +=
// currentNodesep * 2 + node.data[sizeDim]!;
// if (
// layers[diffLayerAbs].maxSize < Math.max(node.data.width!, node.data.height!)
// ) {
// layers[diffLayerAbs].maxSize = Math.max(
// node.data.width!,
// node.data.height!
// );
// }
// if (diffLayer < 0) {
// layers[diffLayerAbs].left.push(node);
// } else {
// layers[diffLayerAbs].right.push(node);
// }
// }
// }
// const leftPos = node.data[dim]! - node.data[sizeDim]! / 2 - currentNodesep;
// const rightPos = node.data[dim]! + node.data[sizeDim]! / 2 + currentNodesep;
// if (leftPos < min) min = leftPos;
// if (rightPos > max) max = rightPos;
// });
// // const padding = (max - min) * 0.1; // TODO
// // 初始化为第一圈的半径,后面根据每层 ranksep 叠加
// let radius = ranksep || 50; // TODO;
// const radiusMap: any = {};
// // 扩大最大最小值范围,以便为环上留出接缝处的空隙
// const rangeLength = (max - min) / 0.9;
// const range = [
// (min + max - rangeLength) * 0.5,
// (min + max + rangeLength) * 0.5,
// ];
// // 根据半径、分布比例,计算节点在环上的位置,并返回该组节点中最大的 ranksep 值
// const processNodes = (
// layerNodes: any,
// radius: number,
// propsMaxRanksep = -Infinity,
// arcRange = [0, 1]
// ) => {
// let maxRanksep = propsMaxRanksep;
// layerNodes.forEach((node: any) => {
// const coord = g.node(node);
// radiusMap[node] = radius;
// // 获取变形为 radial 后的直角坐标系坐标
// const { x: newX, y: newY } = getRadialPos(
// coord![dim]!,
// range,
// rangeLength,
// radius,
// arcRange
// );
// // 将新坐标写入源数据
// const i = nodes.findIndex((it) => it.id === node);
// if (!nodes[i]) return;
// nodes[i].x = newX + dBegin[0];
// nodes[i].y = newY + dBegin[1];
// // @ts-ignore: pass layer order to data for increment layout use
// nodes[i]._order = coord._order;
// // 找到本层最大的一个 ranksep,作为下一层与本层的间隙,叠加到下一层的半径上
// const currentNodeRanksep = ranksepfunc(nodes[i]);
// if (maxRanksep < currentNodeRanksep) maxRanksep = currentNodeRanksep;
// });
// return maxRanksep;
// };
// let isFirstLevel = true;
// const lastLayerMaxNodeSize = 0;
// layers.forEach((layerNodes) => {
// if (
// !layerNodes?.nodes?.length &&
// !layerNodes?.left?.length &&
// !layerNodes?.right?.length
// ) {
// return;
// }
// // 第一层只有一个节点,直接放在圆心,初始半径设定为 0
// if (isFirstLevel && layerNodes.nodes.length === 1) {
// // 将新坐标写入源数据
// const i = nodes.findIndex((it) => it.id === layerNodes.nodes[0]);
// if (i <= -1) return;
// nodes[i].x = dBegin[0];
// nodes[i].y = dBegin[1];
// radiusMap[layerNodes.nodes[0]] = 0;
// radius = ranksepfunc(nodes[i]);
// isFirstLevel = false;
// return;
// }
// // 为接缝留出空隙,半径也需要扩大
// radius = Math.max(radius, layerNodes.totalWidth / (2 * Math.PI)); // / 0.9;
// let maxRanksep = -Infinity;
// if (focusLayer === 0 || layerNodes.nodes?.length) {
// maxRanksep = processNodes(
// layerNodes.nodes,
// radius,
// maxRanksep,
// [0, 1]
// ); // 0.8
// } else {
// const leftRatio =
// layerNodes.left?.length /
// (layerNodes.left?.length + layerNodes.right?.length);
// maxRanksep = processNodes(layerNodes.left, radius, maxRanksep, [
// 0,
// leftRatio,
// ]); // 接缝留出 0.05 的缝隙
// maxRanksep = processNodes(layerNodes.right, radius, maxRanksep, [
// leftRatio + 0.05,
// 1,
// ]); // 接缝留出 0.05 的缝隙
// }
// radius += maxRanksep;
// isFirstLevel = false;
// lastLayerMaxNodeSize - layerNodes.maxSize;
// });
// g.edges().forEach((edge: any) => {
// const coord = g.edge(edge);
// const i = edges.findIndex((it) => {
// const source = getEdgeTerminal(it, "source");
// const target = getEdgeTerminal(it, "target");
// return source === edge.v && target === edge.w;
// });
// if (i <= -1) return;
// if (
// self.edgeLabelSpace &&
// self.controlPoints &&
// edges[i].type !== "loop"
// ) {
// const otherDim = dim === "x" ? "y" : "x";
// const controlPoints = coord?.points?.slice(
// 1,
// coord.points.length - 1
// );
// const newControlPoints: Point[] = [];
// const sourceOtherDimValue = g.node(edge.v)?.[otherDim]!;
// const otherDimDist =
// sourceOtherDimValue - g.node(edge.w)?.[otherDim]!;
// const sourceRadius = radiusMap[edge.v];
// const radiusDist = sourceRadius - radiusMap[edge.w];
// controlPoints?.forEach((point: any) => {
// // 根据该边的起点、终点半径,及起点、终点、控制点位置关系,确定该控制点的半径
// const cRadius =
// ((point[otherDim] - sourceOtherDimValue) / otherDimDist) *
// radiusDist +
// sourceRadius;
// // 获取变形为 radial 后的直角坐标系坐标
// const newPos = getRadialPos(
// point[dim],
// range,
// rangeLength,
// cRadius
// );
// newControlPoints.push({
// x: newPos.x + dBegin[0],
// y: newPos.y + dBegin[1],
// });
// });
// edges[i].controlPoints = newControlPoints;
// }
// });
}
else {
const layerCoords = new Set();
const isInvert = rankdir === 'BT' || rankdir === 'RL';
const layerCoordSort = isInvert
? (a, b) => b - a
: (a, b) => a - b;
g.getAllNodes().forEach((node) => {
// let ndata: any = this.nodeMap[node];
// if (!ndata) {
// ndata = combos?.find((it) => it.id === node);
// }
// if (!ndata) return;
// ndata.x = node.data.x! + dBegin[0];
// ndata.y = node.data.y! + dBegin[1];
// // pass layer order to data for increment layout use
// ndata._order = node.data._order;
// layerCoords.add(isHorizontal ? ndata.x : ndata.y);
node.data.x = node.data.x + layoutTopLeft[0];
node.data.y = node.data.y + layoutTopLeft[1];
layerCoords.add(isHorizontal ? node.data.x : node.data.y);
});
const layerCoordsArr = Array.from(layerCoords).sort(layerCoordSort);
// pre-define the isHorizontal related functions to avoid redundant calc in interations
const isDifferentLayer = isHorizontal
? (point1, point2) => point1.x !== point2.x
: (point1, point2) => point1.y !== point2.y;
const filterControlPointsOutOfBoundary = isHorizontal
? (ps, point1, point2) => {
const max = Math.max(point1.y, point2.y);
const min = Math.min(point1.y, point2.y);
return ps.filter((point) => point.y <= max && point.y >= min);
}
: (ps, point1, point2) => {
const max = Math.max(point1.x, point2.x);
const min = Math.min(point1.x, point2.x);
return ps.filter((point) => point.x <= max && point.x >= min);
};
g.getAllEdges().forEach((edge, i) => {
var _a;
// const i = edges.findIndex((it) => {
// return it.source === edge.source && it.target === edge.target;
// });
// if (i <= -1) return;
if (edgeLabelSpace && controlPoints && edge.data.type !== 'loop') {
edge.data.controlPoints = getControlPoints((_a = edge.data.points) === null || _a === void 0 ? void 0 : _a.map(({ x, y }) => ({
x: x + layoutTopLeft[0],
y: y + layoutTopLeft[1],
})), g.getNode(edge.source), g.getNode(edge.target), layerCoordsArr, isHorizontal, isDifferentLayer, filterControlPointsOutOfBoundary);
}
});
}
// calculated nodes as temporary result
let layoutNodes = [];
// layout according to the original order in the data.nodes
layoutNodes = g
.getAllNodes()
.map((node) => cloneFormatData(node));
const layoutEdges = g.getAllEdges();
if (assign) {
layoutNodes.forEach((node) => {
graph.mergeNodeData(node.id, {
x: node.data.x,
y: node.data.y,
});
});
layoutEdges.forEach((edge) => {
graph.mergeEdgeData(edge.id, {
controlPoints: edge.data.controlPoints,
});
});
}
const result = {
nodes: layoutNodes,
edges: layoutEdges,
};
return result;
});
}
}
/**
* Format controlPoints to avoid polylines crossing nodes
* @param points
* @param sourceNode
* @param targetNode
* @param layerCoordsArr
* @param isHorizontal
* @returns
*/
const getControlPoints = (points, sourceNode, targetNode, layerCoordsArr, isHorizontal, isDifferentLayer, filterControlPointsOutOfBoundary) => {
let controlPoints = (points === null || points === void 0 ? void 0 : points.slice(1, points.length - 1)) || []; // 去掉头尾
// 酌情增加控制点,使折线不穿过跨层的节点
if (sourceNode && targetNode) {
let { x: sourceX, y: sourceY } = sourceNode.data;
let { x: targetX, y: targetY } = targetNode.data;
if (isHorizontal) {
sourceX = sourceNode.data.y;
sourceY = sourceNode.data.x;
targetX = targetNode.data.y;
targetY = targetNode.data.x;
}
// 为跨层级的边增加第一个控制点。忽略垂直的/横向的边。
// 新控制点 = {
// x: 终点x,
// y: (起点y + 下一层y) / 2, #下一层y可能不等于终点y
// }
if (targetY !== sourceY && sourceX !== targetX) {
const sourceLayer = layerCoordsArr.indexOf(sourceY);
const sourceNextLayerCoord = layerCoordsArr[sourceLayer + 1];
if (sourceNextLayerCoord) {
const firstControlPoint = controlPoints[0];
const insertStartControlPoint = (isHorizontal
? {
x: (sourceY + sourceNextLayerCoord) / 2,
y: (firstControlPoint === null || firstControlPoint === void 0 ? void 0 : firstControlPoint.y) || targetX,
}
: {
x: (firstControlPoint === null || firstControlPoint === void 0 ? void 0 : firstControlPoint.x) || targetX,
y: (sourceY + sourceNextLayerCoord) / 2,
});
// 当新增的控制点不存在(!=当前第一个控制点)时添加
if (!firstControlPoint ||
isDifferentLayer(firstControlPoint, insertStartControlPoint)) {
controlPoints.unshift(insertStartControlPoint);
}
}
const targetLayer = layerCoordsArr.indexOf(targetY);
const layerDiff = Math.abs(targetLayer - sourceLayer);
if (layerDiff === 1) {
controlPoints = filterControlPointsOutOfBoundary(controlPoints, sourceNode.data, targetNode.data);
// one controlPoint at least
if (!controlPoints.length) {
controlPoints.push((isHorizontal
? {
x: (sourceY + targetY) / 2,
y: sourceX,
}
: {
x: sourceX,
y: (sourceY + targetY) / 2,
}));
}
}
else if (layerDiff > 1) {
const targetLastLayerCoord = layerCoordsArr[targetLayer - 1];
if (targetLastLayerCoord) {
const lastControlPoints = controlPoints[controlPoints.length - 1];
const insertEndControlPoint = (isHorizontal
? {
x: (targetY + targetLastLayerCoord) / 2,
y: (lastControlPoints === null || lastControlPoints === void 0 ? void 0 : lastControlPoints.y) || targetX,
}
: {
x: (lastControlPoints === null || lastControlPoints === void 0 ? void 0 : lastControlPoints.x) || sourceX,
y: (targetY + targetLastLayerCoord) / 2,
});
// 当新增的控制点不存在(!=当前最后一个控制点)时添加
if (!lastControlPoints ||
isDifferentLayer(lastControlPoints, insertEndControlPoint)) {
controlPoints.push(insertEndControlPoint);
}
}
}
}
}
return controlPoints;
};
//# sourceMappingURL=antv-dagre.js.map