@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,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileOverview random layout
|
|
3
|
+
* @author shiwu.wyy@antfin.com
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
OutNode,
|
|
8
|
+
Edge,
|
|
9
|
+
PointTuple,
|
|
10
|
+
IndexMap,
|
|
11
|
+
CircularLayoutOptions
|
|
12
|
+
} from "./types";
|
|
13
|
+
import { Base } from "./base";
|
|
14
|
+
import { getDegree, clone, getEdgeTerminal, getFuncByUnknownType } from "../util";
|
|
15
|
+
|
|
16
|
+
type INode = OutNode & {
|
|
17
|
+
degree: number;
|
|
18
|
+
size: number | PointTuple;
|
|
19
|
+
weight: number;
|
|
20
|
+
children: string[];
|
|
21
|
+
parent: string[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function initHierarchy(
|
|
25
|
+
nodes: INode[],
|
|
26
|
+
edges: Edge[],
|
|
27
|
+
nodeMap: IndexMap,
|
|
28
|
+
directed: boolean
|
|
29
|
+
) {
|
|
30
|
+
nodes.forEach((_, i: number) => {
|
|
31
|
+
nodes[i].children = [];
|
|
32
|
+
nodes[i].parent = [];
|
|
33
|
+
});
|
|
34
|
+
if (directed) {
|
|
35
|
+
edges.forEach((e) => {
|
|
36
|
+
const source = getEdgeTerminal(e, 'source');
|
|
37
|
+
const target = getEdgeTerminal(e, 'target');
|
|
38
|
+
let sourceIdx = 0;
|
|
39
|
+
if (source) {
|
|
40
|
+
sourceIdx = nodeMap[source];
|
|
41
|
+
}
|
|
42
|
+
let targetIdx = 0;
|
|
43
|
+
if (target) {
|
|
44
|
+
targetIdx = nodeMap[target];
|
|
45
|
+
}
|
|
46
|
+
const child = nodes[sourceIdx].children!;
|
|
47
|
+
const parent = nodes[targetIdx].parent!;
|
|
48
|
+
child.push(nodes[targetIdx].id);
|
|
49
|
+
parent.push(nodes[sourceIdx].id);
|
|
50
|
+
});
|
|
51
|
+
} else {
|
|
52
|
+
edges.forEach((e) => {
|
|
53
|
+
const source = getEdgeTerminal(e, 'source');
|
|
54
|
+
const target = getEdgeTerminal(e, 'target');
|
|
55
|
+
let sourceIdx = 0;
|
|
56
|
+
if (source) {
|
|
57
|
+
sourceIdx = nodeMap[source];
|
|
58
|
+
}
|
|
59
|
+
let targetIdx = 0;
|
|
60
|
+
if (target) {
|
|
61
|
+
targetIdx = nodeMap[target];
|
|
62
|
+
}
|
|
63
|
+
const sourceChildren = nodes[sourceIdx].children!;
|
|
64
|
+
const targetChildren = nodes[targetIdx].children!;
|
|
65
|
+
sourceChildren.push(nodes[targetIdx].id);
|
|
66
|
+
targetChildren.push(nodes[sourceIdx].id);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function connect(a: INode, b: INode, edges: Edge[]) {
|
|
72
|
+
const m = edges.length;
|
|
73
|
+
for (let i = 0; i < m; i++) {
|
|
74
|
+
const source = getEdgeTerminal(edges[i], 'source');
|
|
75
|
+
const target = getEdgeTerminal(edges[i], 'target');
|
|
76
|
+
if (
|
|
77
|
+
(a.id === source && b.id === target) ||
|
|
78
|
+
(b.id === source && a.id === target)
|
|
79
|
+
) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function compareDegree(a: INode, b: INode) {
|
|
87
|
+
const aDegree = a.degree!;
|
|
88
|
+
const bDegree = b.degree!;
|
|
89
|
+
if (aDegree < bDegree) {
|
|
90
|
+
return -1;
|
|
91
|
+
}
|
|
92
|
+
if (aDegree > bDegree) {
|
|
93
|
+
return 1;
|
|
94
|
+
}
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 圆形布局
|
|
100
|
+
*/
|
|
101
|
+
export class CircularLayout extends Base {
|
|
102
|
+
/** 布局中心 */
|
|
103
|
+
public center: PointTuple;
|
|
104
|
+
|
|
105
|
+
/** 固定半径,若设置了 radius,则 startRadius 与 endRadius 不起效 */
|
|
106
|
+
public radius: number | null = null;
|
|
107
|
+
|
|
108
|
+
/** 节点间距,若设置 nodeSpacing,则 radius 将被自动计算,即设置 radius 不生效 */
|
|
109
|
+
public nodeSpacing: ((d?: unknown) => number) | number | undefined;
|
|
110
|
+
|
|
111
|
+
/** 节点大小,配合 nodeSpacing,一起用于计算 radius。若不配置,节点大小默认为 30 */
|
|
112
|
+
public nodeSize: number | undefined = undefined;
|
|
113
|
+
|
|
114
|
+
/** 起始半径 */
|
|
115
|
+
public startRadius: number | null = null;
|
|
116
|
+
|
|
117
|
+
/** 终止半径 */
|
|
118
|
+
public endRadius: number | null = null;
|
|
119
|
+
|
|
120
|
+
/** 起始角度 */
|
|
121
|
+
public startAngle: number = 0;
|
|
122
|
+
|
|
123
|
+
/** 终止角度 */
|
|
124
|
+
public endAngle: number = 2 * Math.PI;
|
|
125
|
+
|
|
126
|
+
/** 是否顺时针 */
|
|
127
|
+
public clockwise: boolean = true;
|
|
128
|
+
|
|
129
|
+
/** 节点在环上分成段数(几个段将均匀分布),在 endRadius - startRadius != 0 时生效 */
|
|
130
|
+
public divisions: number = 1;
|
|
131
|
+
|
|
132
|
+
/** 节点在环上排序的依据,可选: 'topology', 'degree', 'null' */
|
|
133
|
+
public ordering: "topology" | "topology-directed" | "degree" | null = null;
|
|
134
|
+
|
|
135
|
+
/** how many 2*pi from first to last nodes */
|
|
136
|
+
public angleRatio = 1;
|
|
137
|
+
|
|
138
|
+
public nodes: INode[] = [];
|
|
139
|
+
|
|
140
|
+
public edges: Edge[] = [];
|
|
141
|
+
|
|
142
|
+
private nodeMap: IndexMap = {};
|
|
143
|
+
|
|
144
|
+
private degrees: number[] = [];
|
|
145
|
+
|
|
146
|
+
public width: number = 300;
|
|
147
|
+
|
|
148
|
+
public height: number = 300;
|
|
149
|
+
|
|
150
|
+
public onLayoutEnd: () => void;
|
|
151
|
+
|
|
152
|
+
constructor(options?: CircularLayoutOptions) {
|
|
153
|
+
super();
|
|
154
|
+
this.updateCfg(options);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public getDefaultCfg() {
|
|
158
|
+
return {
|
|
159
|
+
radius: null,
|
|
160
|
+
startRadius: null,
|
|
161
|
+
endRadius: null,
|
|
162
|
+
startAngle: 0,
|
|
163
|
+
endAngle: 2 * Math.PI,
|
|
164
|
+
clockwise: true,
|
|
165
|
+
divisions: 1,
|
|
166
|
+
ordering: null,
|
|
167
|
+
angleRatio: 1
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 执行布局
|
|
173
|
+
*/
|
|
174
|
+
public execute() {
|
|
175
|
+
const self = this;
|
|
176
|
+
const nodes = self.nodes;
|
|
177
|
+
const edges = self.edges;
|
|
178
|
+
const n = nodes.length;
|
|
179
|
+
if (n === 0) {
|
|
180
|
+
if (self.onLayoutEnd) self.onLayoutEnd();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!self.width && typeof window !== "undefined") {
|
|
185
|
+
self.width = window.innerWidth;
|
|
186
|
+
}
|
|
187
|
+
if (!self.height && typeof window !== "undefined") {
|
|
188
|
+
self.height = window.innerHeight;
|
|
189
|
+
}
|
|
190
|
+
if (!self.center) {
|
|
191
|
+
self.center = [self.width / 2, self.height / 2];
|
|
192
|
+
}
|
|
193
|
+
const center = self.center;
|
|
194
|
+
|
|
195
|
+
if (n === 1) {
|
|
196
|
+
nodes[0].x = center[0];
|
|
197
|
+
nodes[0].y = center[1];
|
|
198
|
+
if (self.onLayoutEnd) self.onLayoutEnd();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let { radius, startRadius, endRadius } = self;
|
|
203
|
+
const { divisions, startAngle, endAngle, angleRatio, ordering, clockwise, nodeSpacing: paramNodeSpacing, nodeSize: paramNodeSize } = self;
|
|
204
|
+
const angleStep = (endAngle - startAngle) / n;
|
|
205
|
+
// layout
|
|
206
|
+
const nodeMap: IndexMap = {};
|
|
207
|
+
nodes.forEach((node, i) => {
|
|
208
|
+
nodeMap[node.id] = i;
|
|
209
|
+
});
|
|
210
|
+
self.nodeMap = nodeMap;
|
|
211
|
+
const degrees = getDegree(nodes.length, nodeMap, edges);
|
|
212
|
+
self.degrees = degrees;
|
|
213
|
+
if (paramNodeSpacing) {
|
|
214
|
+
const nodeSpacing: Function = getFuncByUnknownType(10, paramNodeSpacing);
|
|
215
|
+
const nodeSize: Function = getFuncByUnknownType(10, paramNodeSize);
|
|
216
|
+
let maxNodeSize = -Infinity;
|
|
217
|
+
nodes.forEach((node) => {
|
|
218
|
+
const nSize = nodeSize(node);
|
|
219
|
+
if (maxNodeSize < nSize) maxNodeSize = nSize;
|
|
220
|
+
});
|
|
221
|
+
let length = 0;
|
|
222
|
+
nodes.forEach((node, i) => {
|
|
223
|
+
if (i === 0) length += (maxNodeSize || 10);
|
|
224
|
+
else length += (nodeSpacing(node) || 0) + (maxNodeSize || 10);
|
|
225
|
+
});
|
|
226
|
+
radius = length / (2 * Math.PI);
|
|
227
|
+
} else if (!radius && !startRadius && !endRadius) {
|
|
228
|
+
radius = self.height > self.width ? self.width / 2 : self.height / 2;
|
|
229
|
+
} else if (!startRadius && endRadius) {
|
|
230
|
+
startRadius = endRadius;
|
|
231
|
+
} else if (startRadius && !endRadius) {
|
|
232
|
+
endRadius = startRadius;
|
|
233
|
+
}
|
|
234
|
+
const astep = angleStep * angleRatio;
|
|
235
|
+
|
|
236
|
+
let layoutNodes = [];
|
|
237
|
+
if (ordering === "topology") {
|
|
238
|
+
// layout according to the topology
|
|
239
|
+
layoutNodes = self.topologyOrdering();
|
|
240
|
+
} else if (ordering === "topology-directed") {
|
|
241
|
+
// layout according to the topology
|
|
242
|
+
layoutNodes = self.topologyOrdering(true);
|
|
243
|
+
} else if (ordering === "degree") {
|
|
244
|
+
// layout according to the descent order of degrees
|
|
245
|
+
layoutNodes = self.degreeOrdering();
|
|
246
|
+
} else {
|
|
247
|
+
// layout according to the original order in the data.nodes
|
|
248
|
+
layoutNodes = nodes;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const divN = Math.ceil(n / divisions); // node number in each division
|
|
252
|
+
for (let i = 0; i < n; ++i) {
|
|
253
|
+
let r = radius;
|
|
254
|
+
if (!r && startRadius !== null && endRadius !== null) {
|
|
255
|
+
r = startRadius + (i * (endRadius - startRadius)) / (n - 1);
|
|
256
|
+
}
|
|
257
|
+
if (!r) {
|
|
258
|
+
r = 10 + (i * 100) / (n - 1);
|
|
259
|
+
}
|
|
260
|
+
let angle =
|
|
261
|
+
startAngle +
|
|
262
|
+
(i % divN) * astep +
|
|
263
|
+
((2 * Math.PI) / divisions) * Math.floor(i / divN);
|
|
264
|
+
if (!clockwise) {
|
|
265
|
+
angle =
|
|
266
|
+
endAngle -
|
|
267
|
+
(i % divN) * astep -
|
|
268
|
+
((2 * Math.PI) / divisions) * Math.floor(i / divN);
|
|
269
|
+
}
|
|
270
|
+
layoutNodes[i].x = center[0] + Math.cos(angle) * r;
|
|
271
|
+
layoutNodes[i].y = center[1] + Math.sin(angle) * r;
|
|
272
|
+
layoutNodes[i].weight = degrees[i];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
self.onLayoutEnd?.();
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
nodes: layoutNodes,
|
|
279
|
+
edges: this.edges
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* 根据节点的拓扑结构排序
|
|
285
|
+
* @return {array} orderedNodes 排序后的结果
|
|
286
|
+
*/
|
|
287
|
+
public topologyOrdering(directed: boolean = false) {
|
|
288
|
+
const self = this;
|
|
289
|
+
const degrees = self.degrees;
|
|
290
|
+
const edges = self.edges;
|
|
291
|
+
const nodes = self.nodes;
|
|
292
|
+
const cnodes = clone(nodes);
|
|
293
|
+
const nodeMap = self.nodeMap;
|
|
294
|
+
const orderedCNodes = [cnodes[0]];
|
|
295
|
+
const resNodes = [nodes[0]];
|
|
296
|
+
const pickFlags: boolean[] = [];
|
|
297
|
+
const n = nodes.length;
|
|
298
|
+
pickFlags[0] = true;
|
|
299
|
+
initHierarchy(cnodes, edges, nodeMap, directed);
|
|
300
|
+
let k = 0;
|
|
301
|
+
cnodes.forEach((cnode, i) => {
|
|
302
|
+
if (i !== 0) {
|
|
303
|
+
if (
|
|
304
|
+
(i === n - 1 ||
|
|
305
|
+
degrees[i] !== degrees[i + 1] ||
|
|
306
|
+
connect(
|
|
307
|
+
orderedCNodes[k],
|
|
308
|
+
cnode,
|
|
309
|
+
edges
|
|
310
|
+
)) &&
|
|
311
|
+
!pickFlags[i]
|
|
312
|
+
) {
|
|
313
|
+
orderedCNodes.push(cnode);
|
|
314
|
+
resNodes.push(nodes[nodeMap[cnode.id]]);
|
|
315
|
+
pickFlags[i] = true;
|
|
316
|
+
k++;
|
|
317
|
+
} else {
|
|
318
|
+
const children = orderedCNodes[k].children!;
|
|
319
|
+
let foundChild = false;
|
|
320
|
+
for (let j = 0; j < children.length; j++) {
|
|
321
|
+
const childIdx = nodeMap[children[j]];
|
|
322
|
+
if (degrees[childIdx] === degrees[i] && !pickFlags[childIdx]) {
|
|
323
|
+
orderedCNodes.push(cnodes[childIdx]);
|
|
324
|
+
resNodes.push(nodes[nodeMap[cnodes[childIdx].id]]);
|
|
325
|
+
pickFlags[childIdx] = true;
|
|
326
|
+
foundChild = true;
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
let ii = 0;
|
|
331
|
+
while (!foundChild) {
|
|
332
|
+
if (!pickFlags[ii]) {
|
|
333
|
+
orderedCNodes.push(cnodes[ii]);
|
|
334
|
+
resNodes.push(nodes[nodeMap[cnodes[ii].id]]);
|
|
335
|
+
pickFlags[ii] = true;
|
|
336
|
+
foundChild = true;
|
|
337
|
+
}
|
|
338
|
+
ii++;
|
|
339
|
+
if (ii === n) {
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
return resNodes;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* 根据节点度数大小排序
|
|
351
|
+
* @return {array} orderedNodes 排序后的结果
|
|
352
|
+
*/
|
|
353
|
+
public degreeOrdering(): INode[] {
|
|
354
|
+
const self = this;
|
|
355
|
+
const nodes = self.nodes;
|
|
356
|
+
const orderedNodes: INode[] = [];
|
|
357
|
+
const degrees = self.degrees;
|
|
358
|
+
nodes.forEach((node, i) => {
|
|
359
|
+
node.degree = degrees[i];
|
|
360
|
+
orderedNodes.push(node);
|
|
361
|
+
});
|
|
362
|
+
orderedNodes.sort(compareDegree);
|
|
363
|
+
return orderedNodes;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
public getType() {
|
|
367
|
+
return "circular";
|
|
368
|
+
}
|
|
369
|
+
}
|