@antv/layout 0.2.2 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/layout.min.js +1 -1
- package/dist/layout.min.js.map +1 -1
- package/es/layout/comboCombined.js +6 -21
- package/es/layout/comboCombined.js.map +1 -1
- package/es/layout/grid.js +2 -0
- package/es/layout/grid.js.map +1 -1
- package/es/layout/types.d.ts +7 -3
- package/lib/layout/comboCombined.js +6 -21
- package/lib/layout/comboCombined.js.map +1 -1
- package/lib/layout/grid.js +2 -0
- package/lib/layout/grid.js.map +1 -1
- package/lib/layout/types.d.ts +7 -3
- package/package.json +2 -1
- package/src/index.ts +7 -0
- package/src/layout/base.ts +54 -0
- package/src/layout/circular.ts +369 -0
- package/src/layout/comboCombined.ts +391 -0
- package/src/layout/comboForce.ts +873 -0
- package/src/layout/concentric.ts +289 -0
- package/src/layout/constants.ts +21 -0
- package/src/layout/dagre/graph.ts +104 -0
- package/src/layout/dagre/index.ts +31 -0
- package/src/layout/dagre/src/acyclic.ts +58 -0
- package/src/layout/dagre/src/add-border-segments.ts +47 -0
- package/src/layout/dagre/src/coordinate-system.ts +77 -0
- package/src/layout/dagre/src/data/list.ts +60 -0
- package/src/layout/dagre/src/debug.ts +30 -0
- package/src/layout/dagre/src/greedy-fas.ts +144 -0
- package/src/layout/dagre/src/layout.ts +580 -0
- package/src/layout/dagre/src/nesting-graph.ts +143 -0
- package/src/layout/dagre/src/normalize.ts +96 -0
- package/src/layout/dagre/src/order/add-subgraph-constraints.ts +29 -0
- package/src/layout/dagre/src/order/barycenter.ts +26 -0
- package/src/layout/dagre/src/order/build-layer-graph.ts +82 -0
- package/src/layout/dagre/src/order/cross-count.ts +77 -0
- package/src/layout/dagre/src/order/index.ts +105 -0
- package/src/layout/dagre/src/order/init-data-order.ts +27 -0
- package/src/layout/dagre/src/order/init-order.ts +56 -0
- package/src/layout/dagre/src/order/resolve-conflicts.ts +152 -0
- package/src/layout/dagre/src/order/sort-subgraph.ts +105 -0
- package/src/layout/dagre/src/order/sort.ts +76 -0
- package/src/layout/dagre/src/parent-dummy-chains.ts +102 -0
- package/src/layout/dagre/src/position/bk.ts +494 -0
- package/src/layout/dagre/src/position/index.ts +82 -0
- package/src/layout/dagre/src/rank/feasible-tree.ts +165 -0
- package/src/layout/dagre/src/rank/index.ts +54 -0
- package/src/layout/dagre/src/rank/network-simplex.ts +225 -0
- package/src/layout/dagre/src/rank/util.ts +157 -0
- package/src/layout/dagre/src/util.ts +308 -0
- package/src/layout/dagre.ts +423 -0
- package/src/layout/dagreCompound.ts +518 -0
- package/src/layout/er/core.ts +117 -0
- package/src/layout/er/forceGrid.ts +95 -0
- package/src/layout/er/grid.ts +185 -0
- package/src/layout/er/index.ts +68 -0
- package/src/layout/er/mysqlWorkbench.ts +345 -0
- package/src/layout/er/type.ts +39 -0
- package/src/layout/force/force-in-a-box.ts +400 -0
- package/src/layout/force/force.ts +391 -0
- package/src/layout/force/index.ts +1 -0
- package/src/layout/forceAtlas2/body.ts +115 -0
- package/src/layout/forceAtlas2/index.ts +556 -0
- package/src/layout/forceAtlas2/quad.ts +115 -0
- package/src/layout/forceAtlas2/quadTree.ts +107 -0
- package/src/layout/fruchterman.ts +361 -0
- package/src/layout/gForce.ts +487 -0
- package/src/layout/gpu/fruchterman.ts +314 -0
- package/src/layout/gpu/fruchtermanShader.ts +204 -0
- package/src/layout/gpu/gForce.ts +406 -0
- package/src/layout/gpu/gForceShader.ts +221 -0
- package/src/layout/grid.ts +393 -0
- package/src/layout/index.ts +45 -0
- package/src/layout/layout.ts +75 -0
- package/src/layout/mds.ts +140 -0
- package/src/layout/radial/index.ts +1 -0
- package/src/layout/radial/mds.ts +51 -0
- package/src/layout/radial/radial.ts +500 -0
- package/src/layout/radial/radialNonoverlapForce.ts +189 -0
- package/src/layout/random.ts +75 -0
- package/src/layout/types.ts +425 -0
- package/src/registy/index.ts +43 -0
- package/src/util/array.ts +1 -0
- package/src/util/function.ts +64 -0
- package/src/util/gpu.ts +254 -0
- package/src/util/index.ts +6 -0
- package/src/util/math.ts +158 -0
- package/src/util/number.ts +8 -0
- package/src/util/object.ts +28 -0
- package/src/util/string.ts +18 -0
- package/CHANGELOG.md +0 -88
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileOverview dagre layout
|
|
3
|
+
* @author shiwu.wyy@antfin.com
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Edge, OutNode, DagreLayoutOptions, PointTuple, Point, Node } from "./types";
|
|
7
|
+
import dagre from "./dagre/index";
|
|
8
|
+
import { isArray, isNumber, isObject, getEdgeTerminal, getFunc, isString } from "../util";
|
|
9
|
+
import { Base } from "./base";
|
|
10
|
+
import { Graph as DagreGraph } from './dagre/graph';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 层次布局
|
|
14
|
+
*/
|
|
15
|
+
export class DagreLayout extends Base {
|
|
16
|
+
/** layout 方向, 可选 TB, BT, LR, RL */
|
|
17
|
+
public rankdir: "TB" | "BT" | "LR" | "RL" = "TB";
|
|
18
|
+
|
|
19
|
+
/** 节点对齐方式,可选 UL, UR, DL, DR */
|
|
20
|
+
public align: undefined | "UL" | "UR" | "DL" | "DR";
|
|
21
|
+
|
|
22
|
+
/** 布局的起始(左上角)位置 */
|
|
23
|
+
public begin: PointTuple;
|
|
24
|
+
|
|
25
|
+
/** 节点大小 */
|
|
26
|
+
public nodeSize: number | number[] | undefined;
|
|
27
|
+
|
|
28
|
+
/** 节点水平间距(px) */
|
|
29
|
+
public nodesepFunc: ((d?: any) => number) | undefined;
|
|
30
|
+
|
|
31
|
+
/** 每一层节点之间间距 */
|
|
32
|
+
public ranksepFunc: ((d?: any) => number) | undefined;
|
|
33
|
+
|
|
34
|
+
/** 节点水平间距(px) */
|
|
35
|
+
public nodesep: number = 50;
|
|
36
|
+
|
|
37
|
+
/** 每一层节点之间间距 */
|
|
38
|
+
public ranksep: number = 50;
|
|
39
|
+
|
|
40
|
+
/** 是否保留布局连线的控制点 */
|
|
41
|
+
public controlPoints: boolean = false;
|
|
42
|
+
|
|
43
|
+
/** 每层节点是否根据节点数据中的 comboId 进行排序,以防止同层 combo 重叠 */
|
|
44
|
+
public sortByCombo: boolean = false;
|
|
45
|
+
|
|
46
|
+
/** 是否保留每条边上的dummy node */
|
|
47
|
+
public edgeLabelSpace: boolean = true;
|
|
48
|
+
|
|
49
|
+
/** 是否基于 dagre 进行辐射布局,若是,第一层节点将被放置在最内环上,其余层依次向外辐射 */
|
|
50
|
+
public radial: boolean = false;
|
|
51
|
+
|
|
52
|
+
/** radial 下生效,中心节点,被指定的节点及其同层节点将被放置在最内环上 */
|
|
53
|
+
public focusNode: string | Node | null;
|
|
54
|
+
|
|
55
|
+
/** 给定的节点顺序,配合keepNodeOrder使用 */
|
|
56
|
+
public nodeOrder: string[];
|
|
57
|
+
|
|
58
|
+
/** 上次的布局结果 */
|
|
59
|
+
public preset: {
|
|
60
|
+
nodes: OutNode[],
|
|
61
|
+
edges: any[],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
public nodes: OutNode[] = [];
|
|
65
|
+
|
|
66
|
+
public edges: Edge[] = [];
|
|
67
|
+
|
|
68
|
+
/** 迭代结束的回调函数 */
|
|
69
|
+
public onLayoutEnd: () => void = () => {};
|
|
70
|
+
|
|
71
|
+
constructor(options?: DagreLayoutOptions) {
|
|
72
|
+
super();
|
|
73
|
+
this.updateCfg(options);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public getDefaultCfg() {
|
|
77
|
+
return {
|
|
78
|
+
rankdir: "TB", // layout 方向, 可选 TB, BT, LR, RL
|
|
79
|
+
align: undefined, // 节点对齐方式,可选 UL, UR, DL, DR
|
|
80
|
+
nodeSize: undefined, // 节点大小
|
|
81
|
+
nodesepFunc: undefined, // 节点水平间距(px)
|
|
82
|
+
ranksepFunc: undefined, // 每一层节点之间间距
|
|
83
|
+
nodesep: 50, // 节点水平间距(px)
|
|
84
|
+
ranksep: 50, // 每一层节点之间间距
|
|
85
|
+
controlPoints: false, // 是否保留布局连线的控制点
|
|
86
|
+
radial: false, // 是否基于 dagre 进行辐射布局
|
|
87
|
+
focusNode: null, // radial 为 true 时生效,关注的节点
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public layoutNode = (nodeId: string) => {
|
|
92
|
+
const self = this;
|
|
93
|
+
const { nodes } = self;
|
|
94
|
+
const node = nodes.find((node) => node.id === nodeId);
|
|
95
|
+
if (node) {
|
|
96
|
+
const layout = node.layout !== false;
|
|
97
|
+
return layout;
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 执行布局
|
|
104
|
+
*/
|
|
105
|
+
public execute() {
|
|
106
|
+
const self = this;
|
|
107
|
+
const { nodes, nodeSize, rankdir, combos, begin, radial } = self;
|
|
108
|
+
if (!nodes) return;
|
|
109
|
+
const edges = (self.edges as any[]) || [];
|
|
110
|
+
const g = new DagreGraph({
|
|
111
|
+
multigraph: true,
|
|
112
|
+
compound: true,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
let nodeSizeFunc: (d?: any) => number[];
|
|
116
|
+
if (!nodeSize) {
|
|
117
|
+
nodeSizeFunc = (d: any) => {
|
|
118
|
+
if (d.size) {
|
|
119
|
+
if (isArray(d.size)) {
|
|
120
|
+
return d.size;
|
|
121
|
+
} if (isObject(d.size)) {
|
|
122
|
+
return [d.size.width || 40, d.size.height || 40];
|
|
123
|
+
}
|
|
124
|
+
return [d.size, d.size];
|
|
125
|
+
}
|
|
126
|
+
return [40, 40];
|
|
127
|
+
};
|
|
128
|
+
} else if (isArray(nodeSize)) {
|
|
129
|
+
nodeSizeFunc = () => nodeSize;
|
|
130
|
+
} else {
|
|
131
|
+
nodeSizeFunc = () => [nodeSize, nodeSize];
|
|
132
|
+
}
|
|
133
|
+
const ranksepfunc = getFunc(self.ranksep, 50, self.ranksepFunc);
|
|
134
|
+
const nodesepfunc = getFunc(self.nodesep, 50, self.nodesepFunc);
|
|
135
|
+
let horisep: Function = nodesepfunc;
|
|
136
|
+
let vertisep: Function = ranksepfunc;
|
|
137
|
+
|
|
138
|
+
if (rankdir === "LR" || rankdir === "RL") {
|
|
139
|
+
horisep = ranksepfunc;
|
|
140
|
+
vertisep = nodesepfunc;
|
|
141
|
+
}
|
|
142
|
+
g.setDefaultEdgeLabel(() => ({}));
|
|
143
|
+
g.setGraph(self);
|
|
144
|
+
|
|
145
|
+
const comboMap: { [key: string]: boolean } = {};
|
|
146
|
+
|
|
147
|
+
if (this.sortByCombo && combos) {
|
|
148
|
+
combos.forEach((combo) => {
|
|
149
|
+
if (!combo.parentId) return;
|
|
150
|
+
if (!comboMap[combo.parentId]) {
|
|
151
|
+
comboMap[combo.parentId] = true;
|
|
152
|
+
g.setNode(combo.parentId, {});
|
|
153
|
+
}
|
|
154
|
+
g.setParent(combo.id, combo.parentId);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
nodes.filter((node) => node.layout !== false).forEach((node) => {
|
|
159
|
+
const size = nodeSizeFunc(node);
|
|
160
|
+
const verti = vertisep(node);
|
|
161
|
+
const hori = horisep(node);
|
|
162
|
+
const width = size[0] + 2 * hori;
|
|
163
|
+
const height = size[1] + 2 * verti;
|
|
164
|
+
const layer = node.layer;
|
|
165
|
+
if (isNumber(layer)) {
|
|
166
|
+
// 如果有layer属性,加入到node的label中
|
|
167
|
+
g.setNode(node.id, { width, height, layer });
|
|
168
|
+
} else {
|
|
169
|
+
g.setNode(node.id, { width, height });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (this.sortByCombo && node.comboId) {
|
|
173
|
+
if (!comboMap[node.comboId]) {
|
|
174
|
+
comboMap[node.comboId] = true;
|
|
175
|
+
g.setNode(node.comboId, {});
|
|
176
|
+
}
|
|
177
|
+
g.setParent(node.id, node.comboId);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
edges.forEach((edge) => {
|
|
184
|
+
// dagrejs Wiki https://github.com/dagrejs/dagre/wiki#configuring-the-layout
|
|
185
|
+
const source = getEdgeTerminal(edge, 'source');
|
|
186
|
+
const target = getEdgeTerminal(edge, 'target');
|
|
187
|
+
if (this.layoutNode(source) && this.layoutNode(target)) {
|
|
188
|
+
g.setEdge(source, target, {
|
|
189
|
+
weight: edge.weight || 1,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// 考虑增量图中的原始图
|
|
195
|
+
let prevGraph: DagreGraph | undefined = undefined;
|
|
196
|
+
if (self.preset) {
|
|
197
|
+
prevGraph = new DagreGraph({
|
|
198
|
+
multigraph: true,
|
|
199
|
+
compound: true,
|
|
200
|
+
});
|
|
201
|
+
self.preset.nodes.forEach((node) => {
|
|
202
|
+
prevGraph?.setNode(node.id, node);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
dagre.layout(g, {
|
|
207
|
+
prevGraph,
|
|
208
|
+
edgeLabelSpace: self.edgeLabelSpace,
|
|
209
|
+
keepNodeOrder: Boolean(!!self.nodeOrder),
|
|
210
|
+
nodeOrder: self.nodeOrder,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const dBegin = [0, 0];
|
|
214
|
+
if (begin) {
|
|
215
|
+
let minX = Infinity;
|
|
216
|
+
let minY = Infinity;
|
|
217
|
+
g.nodes().forEach((node) => {
|
|
218
|
+
const coord = g.node(node)!;
|
|
219
|
+
if (minX > coord.x!) minX = coord.x!;
|
|
220
|
+
if (minY > coord.y!) minY = coord.y!;
|
|
221
|
+
});
|
|
222
|
+
g.edges().forEach((edge) => {
|
|
223
|
+
const coord = g.edge(edge)!;
|
|
224
|
+
coord.points?.forEach((point: any) => {
|
|
225
|
+
if (minX > point.x) minX = point.x;
|
|
226
|
+
if (minY > point.y) minY = point.y;
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
dBegin[0] = begin[0] - minX;
|
|
230
|
+
dBegin[1] = begin[1] - minY;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// 变形为辐射
|
|
234
|
+
if (radial) {
|
|
235
|
+
const { focusNode, ranksep, getRadialPos } = this;
|
|
236
|
+
const focusId = isString(focusNode) ? focusNode: focusNode?.id;
|
|
237
|
+
const focusLayer = focusId ? g.node(focusId)?._rank : 0;
|
|
238
|
+
const layers: any[] = [];
|
|
239
|
+
const isHorizontal = rankdir === 'LR' || rankdir === 'RL';
|
|
240
|
+
const dim = isHorizontal ? 'y' : 'x';
|
|
241
|
+
const sizeDim = isHorizontal ? 'height' : 'width';
|
|
242
|
+
// 找到整个图作为环的坐标维度(dim)的最大、最小值,考虑节点宽度
|
|
243
|
+
let min = Infinity;
|
|
244
|
+
let max = -Infinity;
|
|
245
|
+
g.nodes().forEach((node: any) => {
|
|
246
|
+
const coord = g.node(node)! as any;
|
|
247
|
+
const i = nodes.findIndex((it) => it.id === node);
|
|
248
|
+
if (!nodes[i]) return;
|
|
249
|
+
const currentNodesep = nodesepfunc(nodes[i]);
|
|
250
|
+
|
|
251
|
+
if (focusLayer === 0) {
|
|
252
|
+
if (!layers[coord._rank]) layers[coord._rank] = { nodes: [], totalWidth: 0, maxSize: -Infinity };
|
|
253
|
+
layers[coord._rank].nodes.push(node);
|
|
254
|
+
layers[coord._rank].totalWidth += currentNodesep * 2 + coord[sizeDim];
|
|
255
|
+
if (layers[coord._rank].maxSize < Math.max(coord.width, coord.height)) layers[coord._rank].maxSize = Math.max(coord.width, coord.height);
|
|
256
|
+
} else {
|
|
257
|
+
const diffLayer = coord._rank - focusLayer!;
|
|
258
|
+
if (diffLayer === 0) {
|
|
259
|
+
if (!layers[diffLayer]) layers[diffLayer] = { nodes: [], totalWidth: 0, maxSize: -Infinity };
|
|
260
|
+
layers[diffLayer].nodes.push(node);
|
|
261
|
+
layers[diffLayer].totalWidth += currentNodesep * 2 + coord[sizeDim];
|
|
262
|
+
if (layers[diffLayer].maxSize < Math.max(coord.width, coord.height)) layers[diffLayer].maxSize = Math.max(coord.width, coord.height);
|
|
263
|
+
} else {
|
|
264
|
+
const diffLayerAbs = Math.abs(diffLayer);
|
|
265
|
+
if (!layers[diffLayerAbs]) layers[diffLayerAbs] = { left: [], right: [], totalWidth: 0, maxSize: -Infinity };
|
|
266
|
+
layers[diffLayerAbs].totalWidth += currentNodesep * 2 + coord[sizeDim];
|
|
267
|
+
if (layers[diffLayerAbs].maxSize < Math.max(coord.width, coord.height)) layers[diffLayerAbs].maxSize = Math.max(coord.width, coord.height);
|
|
268
|
+
if (diffLayer < 0) {
|
|
269
|
+
layers[diffLayerAbs].left.push(node);
|
|
270
|
+
} else {
|
|
271
|
+
layers[diffLayerAbs].right.push(node);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const leftPos = coord[dim] - coord[sizeDim] / 2 - currentNodesep;
|
|
276
|
+
const rightPos = coord[dim] + coord[sizeDim] / 2 + currentNodesep;
|
|
277
|
+
if (leftPos < min) min = leftPos;
|
|
278
|
+
if (rightPos > max) max = rightPos;
|
|
279
|
+
});
|
|
280
|
+
// const padding = (max - min) * 0.1; // TODO
|
|
281
|
+
// 初始化为第一圈的半径,后面根据每层 ranksep 叠加
|
|
282
|
+
let radius = ranksep || 50; // TODO;
|
|
283
|
+
const radiusMap: any = {};
|
|
284
|
+
|
|
285
|
+
// 扩大最大最小值范围,以便为环上留出接缝处的空隙
|
|
286
|
+
const rangeLength = (max - min) / 0.9;
|
|
287
|
+
const range = [ (min + max - rangeLength) * 0.5 , (min + max + rangeLength) * 0.5 ];
|
|
288
|
+
|
|
289
|
+
// 根据半径、分布比例,计算节点在环上的位置,并返回该组节点中最大的 ranksep 值
|
|
290
|
+
const processNodes = (layerNodes: any, radius: number, propsMaxRanksep = -Infinity, arcRange = [0, 1]) => {
|
|
291
|
+
let maxRanksep = propsMaxRanksep;
|
|
292
|
+
layerNodes.forEach((node: any) => {
|
|
293
|
+
const coord = g.node(node);
|
|
294
|
+
radiusMap[node] = radius;
|
|
295
|
+
// 获取变形为 radial 后的直角坐标系坐标
|
|
296
|
+
const { x: newX, y: newY } = getRadialPos(coord![dim]!, range, rangeLength, radius, arcRange);
|
|
297
|
+
// 将新坐标写入源数据
|
|
298
|
+
const i = nodes.findIndex((it) => it.id === node);
|
|
299
|
+
if (!nodes[i]) return;
|
|
300
|
+
nodes[i].x = newX + dBegin[0];
|
|
301
|
+
nodes[i].y = newY + dBegin[1];
|
|
302
|
+
// @ts-ignore: pass layer order to data for increment layout use
|
|
303
|
+
nodes[i]._order = coord._order;
|
|
304
|
+
|
|
305
|
+
// 找到本层最大的一个 ranksep,作为下一层与本层的间隙,叠加到下一层的半径上
|
|
306
|
+
const currentNodeRanksep = ranksepfunc(nodes[i]);
|
|
307
|
+
if (maxRanksep < currentNodeRanksep) maxRanksep = currentNodeRanksep;
|
|
308
|
+
});
|
|
309
|
+
return maxRanksep;
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
let isFirstLevel = true;
|
|
313
|
+
const lastLayerMaxNodeSize = 0;
|
|
314
|
+
layers.forEach((layerNodes) => {
|
|
315
|
+
if (!layerNodes?.nodes?.length && !layerNodes?.left?.length && !layerNodes?.right?.length) return;
|
|
316
|
+
// 第一层只有一个节点,直接放在圆心,初始半径设定为 0
|
|
317
|
+
if (isFirstLevel && layerNodes.nodes.length === 1) {
|
|
318
|
+
// 将新坐标写入源数据
|
|
319
|
+
const i = nodes.findIndex((it) => it.id === layerNodes.nodes[0]);
|
|
320
|
+
nodes[i].x = dBegin[0];
|
|
321
|
+
nodes[i].y = dBegin[1];
|
|
322
|
+
radiusMap[layerNodes.nodes[0]] = 0;
|
|
323
|
+
radius = ranksepfunc(nodes[i]);
|
|
324
|
+
isFirstLevel = false;
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// 为接缝留出空隙,半径也需要扩大
|
|
329
|
+
radius = Math.max(radius, layerNodes.totalWidth / (2 * Math.PI)); // / 0.9;
|
|
330
|
+
|
|
331
|
+
let maxRanksep = -Infinity;
|
|
332
|
+
if (focusLayer === 0 || layerNodes.nodes?.length) {
|
|
333
|
+
maxRanksep = processNodes(layerNodes.nodes, radius, maxRanksep, [0, 1]); // 0.8
|
|
334
|
+
} else {
|
|
335
|
+
const leftRatio = layerNodes.left?.length / (layerNodes.left?.length + layerNodes.right?.length);
|
|
336
|
+
maxRanksep= processNodes(layerNodes.left, radius, maxRanksep, [0, leftRatio]); // 接缝留出 0.05 的缝隙
|
|
337
|
+
maxRanksep = processNodes(layerNodes.right, radius, maxRanksep, [leftRatio + 0.05, 1]); // 接缝留出 0.05 的缝隙
|
|
338
|
+
}
|
|
339
|
+
radius += maxRanksep;
|
|
340
|
+
isFirstLevel = false;
|
|
341
|
+
lastLayerMaxNodeSize - layerNodes.maxSize;
|
|
342
|
+
});
|
|
343
|
+
g.edges().forEach((edge: any) => {
|
|
344
|
+
const coord = g.edge(edge);
|
|
345
|
+
const i = edges.findIndex((it) => {
|
|
346
|
+
const source = getEdgeTerminal(it, 'source');
|
|
347
|
+
const target = getEdgeTerminal(it, 'target');
|
|
348
|
+
return source === edge.v && target === edge.w;
|
|
349
|
+
});
|
|
350
|
+
if ((self.edgeLabelSpace) && self.controlPoints && edges[i].type !== "loop") {
|
|
351
|
+
const otherDim = dim === 'x' ? 'y' : 'x';
|
|
352
|
+
const controlPoints = coord?.points?.slice(1, coord.points.length - 1);
|
|
353
|
+
const newControlPoints: Point[] = [];
|
|
354
|
+
const sourceOtherDimValue = g.node(edge.v)?.[otherDim]!;
|
|
355
|
+
const otherDimDist = sourceOtherDimValue - g.node(edge.w)?.[otherDim]!;
|
|
356
|
+
const sourceRadius = radiusMap[edge.v];
|
|
357
|
+
const radiusDist = sourceRadius - radiusMap[edge.w];
|
|
358
|
+
controlPoints?.forEach((point: any) => {
|
|
359
|
+
// 根据该边的起点、终点半径,及起点、终点、控制点位置关系,确定该控制点的半径
|
|
360
|
+
const cRadius = (point[otherDim] - sourceOtherDimValue) / otherDimDist * radiusDist + sourceRadius;
|
|
361
|
+
// 获取变形为 radial 后的直角坐标系坐标
|
|
362
|
+
const newPos = getRadialPos(point[dim], range, rangeLength, cRadius);
|
|
363
|
+
newControlPoints.push({
|
|
364
|
+
x: newPos.x + dBegin[0],
|
|
365
|
+
y: newPos.y + dBegin[1]
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
edges[i].controlPoints = newControlPoints;
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
} else {
|
|
372
|
+
g.nodes().forEach((node: any) => {
|
|
373
|
+
const coord = g.node(node)!;
|
|
374
|
+
const i = nodes.findIndex((it) => it.id === node);
|
|
375
|
+
if (!nodes[i]) return;
|
|
376
|
+
nodes[i].x = coord.x! + dBegin[0];
|
|
377
|
+
nodes[i].y = coord.y! + dBegin[1];
|
|
378
|
+
// @ts-ignore: pass layer order to data for increment layout use
|
|
379
|
+
nodes[i]._order = coord._order;
|
|
380
|
+
});
|
|
381
|
+
g.edges().forEach((edge: any) => {
|
|
382
|
+
const coord = g.edge(edge);
|
|
383
|
+
const i = edges.findIndex((it) => {
|
|
384
|
+
const source = getEdgeTerminal(it, 'source');
|
|
385
|
+
const target = getEdgeTerminal(it, 'target');
|
|
386
|
+
return source === edge.v && target === edge.w;
|
|
387
|
+
});
|
|
388
|
+
if ((self.edgeLabelSpace) && self.controlPoints && edges[i].type !== "loop") {
|
|
389
|
+
edges[i].controlPoints = coord?.points?.slice(1, coord.points.length - 1); // 去掉头尾
|
|
390
|
+
edges[i].controlPoints.forEach((point: any) => {
|
|
391
|
+
point.x += dBegin[0];
|
|
392
|
+
point.y += dBegin[1];
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (self.onLayoutEnd) self.onLayoutEnd();
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
nodes,
|
|
402
|
+
edges,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
private getRadialPos(dimValue: number, range: number[], rangeLength: number, radius: number, arcRange: number[] = [0, 1]) {
|
|
407
|
+
// dimRatio 占圆弧的比例
|
|
408
|
+
let dimRatio = (dimValue - range[0]) / rangeLength;
|
|
409
|
+
// 再进一步归一化到指定的范围上
|
|
410
|
+
dimRatio = dimRatio * (arcRange[1] - arcRange[0]) + arcRange[0];
|
|
411
|
+
// 使用最终归一化后的范围计算角度
|
|
412
|
+
const angle = dimRatio * 2 * Math.PI; // 弧度
|
|
413
|
+
// 将极坐标系转换为直角坐标系
|
|
414
|
+
return {
|
|
415
|
+
x: Math.cos(angle) * radius,
|
|
416
|
+
y: Math.sin(angle) * radius
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
public getType() {
|
|
421
|
+
return "dagre";
|
|
422
|
+
}
|
|
423
|
+
}
|