@antv/layout 0.3.1 → 0.3.3
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/dagre/src/position/index.js +1 -1
- package/es/layout/dagre/src/position/index.js.map +1 -1
- package/es/layout/force2/ForceNBody.js +1 -1
- package/es/layout/force2/ForceNBody.js.map +1 -1
- package/es/layout/force2/index.js +9 -11
- package/es/layout/force2/index.js.map +1 -1
- package/es/layout/gForce.js +1 -1
- package/es/layout/gpu/gForce.js +1 -1
- package/es/layout/gpu/gForce.js.map +1 -1
- package/es/layout/layout.d.ts +12 -1
- package/es/layout/layout.js +30 -0
- package/es/layout/layout.js.map +1 -1
- package/es/util/gpu.js +0 -1
- package/es/util/gpu.js.map +1 -1
- package/es/util/math.js +25 -25
- package/es/util/math.js.map +1 -1
- package/es/util/object.js +1 -1
- package/es/util/object.js.map +1 -1
- package/lib/layout/dagre/src/position/index.js +10 -1
- package/lib/layout/dagre/src/position/index.js.map +1 -1
- package/lib/layout/force2/ForceNBody.js.map +1 -1
- package/lib/layout/force2/index.js +1 -3
- package/lib/layout/force2/index.js.map +1 -1
- package/lib/layout/gForce.js +1 -1
- package/lib/layout/gpu/gForce.js.map +1 -1
- package/lib/layout/layout.d.ts +12 -1
- package/lib/layout/layout.js +30 -0
- package/lib/layout/layout.js.map +1 -1
- package/lib/util/gpu.js +0 -1
- package/lib/util/gpu.js.map +1 -1
- package/lib/util/math.js +12 -12
- package/lib/util/math.js.map +1 -1
- package/lib/util/object.js +1 -1
- package/lib/util/object.js.map +1 -1
- package/package.json +2 -2
- package/src/layout/dagre/src/position/index.ts +1 -1
- package/src/layout/force2/ForceNBody.ts +2 -2
- package/src/layout/force2/index.ts +16 -16
- package/src/layout/gForce.ts +2 -2
- package/src/layout/gpu/gForce.ts +1 -1
- package/src/layout/layout.ts +34 -1
- package/src/layout/types.ts +1 -1
- package/src/util/gpu.ts +0 -1
- package/src/util/math.ts +30 -32
- package/src/util/object.ts +1 -1
|
@@ -69,7 +69,7 @@ export class Force2Layout extends Base {
|
|
|
69
69
|
public damping: number = 0.9;
|
|
70
70
|
|
|
71
71
|
/** 最大速度 */
|
|
72
|
-
public maxSpeed: number =
|
|
72
|
+
public maxSpeed: number = 100;
|
|
73
73
|
|
|
74
74
|
/** 一次迭代的平均移动距离小于该值时停止迭代 */
|
|
75
75
|
public minMovement: number = 0.4;
|
|
@@ -93,7 +93,7 @@ export class Force2Layout extends Base {
|
|
|
93
93
|
public linkDistance: number | ((edge?: any, source?: any, target?: any) => number) | undefined = 200;
|
|
94
94
|
|
|
95
95
|
/** 理想边长,兼容 graphin-force */
|
|
96
|
-
public defSpringLen: number | ((edge?: any, source?: any, target?: any) => number) | undefined
|
|
96
|
+
public defSpringLen: number | ((edge?: any, source?: any, target?: any) => number) | undefined;
|
|
97
97
|
|
|
98
98
|
/** 重力大小 */
|
|
99
99
|
public gravity: number = 0;
|
|
@@ -194,7 +194,7 @@ export class Force2Layout extends Base {
|
|
|
194
194
|
// 如果传入了需要叶子节点聚类
|
|
195
195
|
if (leafCluster) {
|
|
196
196
|
sameTypeLeafMap = this.getSameTypeLeafMap() || {};
|
|
197
|
-
const relativeNodesType = Array.from(new Set(nodes?.map(node => node[nodeClusterBy]))) || [];
|
|
197
|
+
const relativeNodesType = Array.from(new Set(nodes?.map((node) => node[nodeClusterBy]))) || [];
|
|
198
198
|
centripetalOptions = {
|
|
199
199
|
single: 100,
|
|
200
200
|
leaf: (node, nodes, edges) => {
|
|
@@ -245,18 +245,18 @@ export class Force2Layout extends Base {
|
|
|
245
245
|
const clusters: string[] = Array.from(new Set(nodes.map((node, i) => {
|
|
246
246
|
return node[nodeClusterBy];
|
|
247
247
|
}))).filter(
|
|
248
|
-
item => item !== undefined,
|
|
248
|
+
(item) => item !== undefined,
|
|
249
249
|
);
|
|
250
250
|
const centerNodeInfo: { [key: string]: { x: number; y: number } } = {};
|
|
251
|
-
clusters.forEach(cluster => {
|
|
252
|
-
const sameTypeNodes = nodes.filter(item => item[nodeClusterBy] === cluster).map(node => nodeMap[node.id]);
|
|
251
|
+
clusters.forEach((cluster) => {
|
|
252
|
+
const sameTypeNodes = nodes.filter((item) => item[nodeClusterBy] === cluster).map((node) => nodeMap[node.id]);
|
|
253
253
|
// 找出同类型节点平均位置节点的距离最近的节点作为中心节点
|
|
254
254
|
centerNodeInfo[cluster] = getAvgNodePosition(sameTypeNodes);
|
|
255
255
|
});
|
|
256
256
|
centripetalOptions = {
|
|
257
|
-
single: node => getClusterNodeStrength(node),
|
|
258
|
-
leaf: node => getClusterNodeStrength(node),
|
|
259
|
-
others: node => getClusterNodeStrength(node),
|
|
257
|
+
single: (node) => getClusterNodeStrength(node),
|
|
258
|
+
leaf: (node) => getClusterNodeStrength(node),
|
|
259
|
+
others: (node) => getClusterNodeStrength(node),
|
|
260
260
|
center: (node, nodes, edges) => {
|
|
261
261
|
// 找出同类型节点平均位置节点的距离最近的节点作为中心节点
|
|
262
262
|
const centerNode = centerNodeInfo[node[nodeClusterBy]];
|
|
@@ -407,7 +407,7 @@ export class Force2Layout extends Base {
|
|
|
407
407
|
|
|
408
408
|
|
|
409
409
|
self.edgeInfos = [];
|
|
410
|
-
edges?.forEach(edge => {
|
|
410
|
+
edges?.forEach((edge) => {
|
|
411
411
|
const sourceNode = nodeMap[edge.source];
|
|
412
412
|
const targetNode = nodeMap[edge.target];
|
|
413
413
|
if (!sourceNode || !targetNode) {
|
|
@@ -424,9 +424,9 @@ export class Force2Layout extends Base {
|
|
|
424
424
|
sourceNode,
|
|
425
425
|
targetNode
|
|
426
426
|
) : self.linkDistance(edge, sourceNode, targetNode) || 1 + ((nodeSize(sourceNode) + nodeSize(sourceNode)) || 0) / 2
|
|
427
|
-
})
|
|
427
|
+
});
|
|
428
428
|
}
|
|
429
|
-
})
|
|
429
|
+
});
|
|
430
430
|
|
|
431
431
|
this.getCentripetalOptions();
|
|
432
432
|
|
|
@@ -506,7 +506,7 @@ export class Force2Layout extends Base {
|
|
|
506
506
|
});
|
|
507
507
|
|
|
508
508
|
return energy;
|
|
509
|
-
}
|
|
509
|
+
}
|
|
510
510
|
|
|
511
511
|
// coulombs law
|
|
512
512
|
public calRepulsive(accArray: number[]) {
|
|
@@ -617,7 +617,7 @@ export class Force2Layout extends Base {
|
|
|
617
617
|
}
|
|
618
618
|
|
|
619
619
|
/** others */
|
|
620
|
-
const othersStrength = others(node)
|
|
620
|
+
const othersStrength = others(node);
|
|
621
621
|
if (!othersStrength) continue;
|
|
622
622
|
accArray[idx] -= othersStrength * vx;
|
|
623
623
|
accArray[idx + 1] -= othersStrength * vy;
|
|
@@ -731,11 +731,11 @@ export class Force2Layout extends Base {
|
|
|
731
731
|
// eslint-disable-next-line
|
|
732
732
|
const sameTypeLeafMap: { [nodeId: string]: any } = {};
|
|
733
733
|
nodes.forEach((node, i) => {
|
|
734
|
-
const degree = degreesMap[node.id].all
|
|
734
|
+
const degree = degreesMap[node.id].all;
|
|
735
735
|
if (degree === 1) {
|
|
736
736
|
sameTypeLeafMap[node.id] = getCoreNodeAndRelativeLeafNodes('leaf', node, edges, nodeClusterBy, degreesMap, nodeMap);
|
|
737
737
|
}
|
|
738
738
|
});
|
|
739
739
|
return sameTypeLeafMap;
|
|
740
|
-
}
|
|
740
|
+
}
|
|
741
741
|
}
|
package/src/layout/gForce.ts
CHANGED
|
@@ -379,7 +379,7 @@ export class GForceLayout extends Base {
|
|
|
379
379
|
const vecLength = Math.sqrt(vecX * vecX + vecY * vecY);
|
|
380
380
|
const direX = vecX / vecLength;
|
|
381
381
|
const direY = vecY / vecLength;
|
|
382
|
-
const length = (linkDistance as Function)(edge, sourceNode, targetNode) || 1 + ((nodeSize(sourceNode) + nodeSize(
|
|
382
|
+
const length = (linkDistance as Function)(edge, sourceNode, targetNode) || 1 + ((nodeSize(sourceNode) + nodeSize(targetNode)) || 0) / 2;
|
|
383
383
|
const diff = length - vecLength;
|
|
384
384
|
const param = diff * (edgeStrength as Function)(edge);
|
|
385
385
|
const sourceIdx = nodeIdxMap[source];
|
|
@@ -485,4 +485,4 @@ export class GForceLayout extends Base {
|
|
|
485
485
|
public getType() {
|
|
486
486
|
return "gForce";
|
|
487
487
|
}
|
|
488
|
-
}
|
|
488
|
+
}
|
package/src/layout/gpu/gForce.ts
CHANGED
|
@@ -221,7 +221,7 @@ export class GForceGPULayout extends Base {
|
|
|
221
221
|
);
|
|
222
222
|
|
|
223
223
|
// init degree for mass
|
|
224
|
-
self.degrees = getDegree(nodes.length, self.nodeIdxMap, edges).map(degree => degree.all);
|
|
224
|
+
self.degrees = getDegree(nodes.length, self.nodeIdxMap, edges).map((degree) => degree.all);
|
|
225
225
|
const masses: number[] = [];
|
|
226
226
|
const nodeStrengths: number[] = [];
|
|
227
227
|
const centerXs: number[] = [];
|
package/src/layout/layout.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Base } from "./base";
|
|
2
|
-
import { Model, ILayout } from "./types";
|
|
2
|
+
import { Model, ILayout, Node } from "./types";
|
|
3
3
|
import { getLayoutByName } from "../registy";
|
|
4
4
|
import { GridLayout } from "./grid";
|
|
5
5
|
import { RandomLayout } from "./random";
|
|
@@ -19,6 +19,12 @@ import { ComboCombinedLayout } from "./comboCombined";
|
|
|
19
19
|
import { ForceAtlas2Layout } from "./forceAtlas2";
|
|
20
20
|
import { ERLayout } from "./er";
|
|
21
21
|
import { DagreCompoundLayout } from "./dagreCompound";
|
|
22
|
+
import { isString } from "../util";
|
|
23
|
+
|
|
24
|
+
interface DagreNodeData extends Node {
|
|
25
|
+
layer?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
22
28
|
export class Layout {
|
|
23
29
|
public readonly layoutInstance: Base;
|
|
24
30
|
|
|
@@ -36,9 +42,36 @@ export class Layout {
|
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
init(data: Model) {
|
|
45
|
+
this.correctLayers(data.nodes);
|
|
39
46
|
this.layoutInstance.init(data);
|
|
40
47
|
}
|
|
41
48
|
|
|
49
|
+
/**
|
|
50
|
+
* correcting the layers on the node data
|
|
51
|
+
* if min(layer) <= 0, layers should begin from abs(min(layer)) + 1
|
|
52
|
+
* @param nodes
|
|
53
|
+
* @returns
|
|
54
|
+
*/
|
|
55
|
+
correctLayers(nodes: DagreNodeData[] | undefined) {
|
|
56
|
+
if (!nodes?.length) return;
|
|
57
|
+
let minLayer = Infinity;
|
|
58
|
+
const hasLayerNodes: DagreNodeData[] = [];
|
|
59
|
+
nodes.forEach((node) => {
|
|
60
|
+
if (isString(node.layer)) {
|
|
61
|
+
node.layer = parseInt(node.layer, 10);
|
|
62
|
+
}
|
|
63
|
+
// keep node.layer === undefined for TS problem
|
|
64
|
+
if (node.layer === undefined || isNaN(node.layer)) return;
|
|
65
|
+
hasLayerNodes.push(node);
|
|
66
|
+
if (node.layer < minLayer) minLayer = node.layer;
|
|
67
|
+
});
|
|
68
|
+
if (minLayer <= 0) {
|
|
69
|
+
const layerOffset = Math.abs(minLayer) + 1;
|
|
70
|
+
// @ts-ignore
|
|
71
|
+
hasLayerNodes.forEach((node) => node.layer += layerOffset);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
42
75
|
execute() {
|
|
43
76
|
this.layoutInstance.execute();
|
|
44
77
|
}
|
package/src/layout/types.ts
CHANGED
|
@@ -268,7 +268,7 @@ export interface Force2LayoutOptions {
|
|
|
268
268
|
gravity?: number;
|
|
269
269
|
factor?: number;
|
|
270
270
|
workerEnabled?: boolean;
|
|
271
|
-
centripetalOptions?: CentripetalOptions
|
|
271
|
+
centripetalOptions?: CentripetalOptions;
|
|
272
272
|
leafCluster?: boolean;
|
|
273
273
|
clustering?: boolean;
|
|
274
274
|
nodeClusterBy?: string;
|
package/src/util/gpu.ts
CHANGED
|
@@ -63,7 +63,6 @@ export const buildTextureData = (nodes: OutNode[], edges: Edge[]): {
|
|
|
63
63
|
const offset: number = dataArray.length;
|
|
64
64
|
const dests = nodeDict[i];
|
|
65
65
|
const len = dests.length;
|
|
66
|
-
console.log('dests', dests, len)
|
|
67
66
|
dataArray[i * 4 + 2] = offset;
|
|
68
67
|
dataArray[i * 4 + 3] = len;
|
|
69
68
|
maxEdgePerVetex = Math.max(maxEdgePerVetex, len);
|
package/src/util/math.ts
CHANGED
|
@@ -35,14 +35,14 @@ export const getDegree = (n: number, nodeIdxMap: IndexMap, edges: Edge[] | null)
|
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
export const getDegreeMap = (nodes: Node[], edges: Edge[] | null) => {
|
|
38
|
-
const degreesMap: { [id: string]: Degree } = {}
|
|
39
|
-
nodes.forEach(node => {
|
|
38
|
+
const degreesMap: { [id: string]: Degree } = {};
|
|
39
|
+
nodes.forEach((node) => {
|
|
40
40
|
degreesMap[node.id] = {
|
|
41
41
|
in: 0,
|
|
42
42
|
out: 0,
|
|
43
43
|
all: 0
|
|
44
|
-
}
|
|
45
|
-
})
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
46
|
|
|
47
47
|
if (!edges) return degreesMap;
|
|
48
48
|
edges.forEach((e) => {
|
|
@@ -111,19 +111,17 @@ export const getAdjMatrix = (data: Model, directed: boolean): Matrix[] => {
|
|
|
111
111
|
});
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
|
|
114
|
+
edges?.forEach((e) => {
|
|
115
|
+
const source = getEdgeTerminal(e, 'source');
|
|
116
|
+
const target = getEdgeTerminal(e, 'target');
|
|
117
|
+
const sIndex = nodeMap[source as string];
|
|
118
|
+
const tIndex = nodeMap[target as string];
|
|
119
|
+
if (sIndex === undefined || tIndex === undefined) return;
|
|
120
|
+
matrix[sIndex][tIndex] = 1;
|
|
121
|
+
if (!directed) {
|
|
122
|
+
matrix[tIndex][sIndex] = 1;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
127
125
|
return matrix;
|
|
128
126
|
};
|
|
129
127
|
|
|
@@ -195,8 +193,8 @@ export const findMinMaxNodeXY = (nodes: OutNode[]) => {
|
|
|
195
193
|
* @returns 平局内置
|
|
196
194
|
*/
|
|
197
195
|
export const getAvgNodePosition = (nodes: OutNode[]) => {
|
|
198
|
-
|
|
199
|
-
nodes.forEach(node => {
|
|
196
|
+
const totalNodes = { x: 0, y: 0 };
|
|
197
|
+
nodes.forEach((node) => {
|
|
200
198
|
totalNodes.x += node.x || 0;
|
|
201
199
|
totalNodes.y += node.y || 0;
|
|
202
200
|
});
|
|
@@ -211,26 +209,26 @@ export const getAvgNodePosition = (nodes: OutNode[]) => {
|
|
|
211
209
|
// 找出指定节点关联的边的起点或终点
|
|
212
210
|
const getCoreNode = (type: 'source' | 'target', node: Node, edges: Edge[]) => {
|
|
213
211
|
if (type === 'source') {
|
|
214
|
-
return (edges?.find(edge => edge.target === node.id)?.source || {}) as Node;
|
|
212
|
+
return (edges?.find((edge) => edge.target === node.id)?.source || {}) as Node;
|
|
215
213
|
}
|
|
216
|
-
return (edges?.find(edge => edge.source === node.id)?.target || {}) as Node;
|
|
214
|
+
return (edges?.find((edge) => edge.source === node.id)?.target || {}) as Node;
|
|
217
215
|
};
|
|
218
216
|
|
|
219
217
|
// 找出指定节点为起点或终点的所有一度叶子节点
|
|
220
218
|
const getRelativeNodeIds = (type: 'source' | 'target' | 'both', coreNode: Node, edges: Edge[]) => {
|
|
221
|
-
let relativeNodes: string[] = []
|
|
219
|
+
let relativeNodes: string[] = [];
|
|
222
220
|
switch (type) {
|
|
223
221
|
case 'source':
|
|
224
|
-
relativeNodes = edges?.filter(edge => edge.source === coreNode.id).map(edge => edge.target);
|
|
222
|
+
relativeNodes = edges?.filter((edge) => edge.source === coreNode.id).map((edge) => edge.target);
|
|
225
223
|
break;
|
|
226
224
|
case 'target':
|
|
227
|
-
relativeNodes = edges?.filter(edge => edge.target === coreNode.id).map(edge => edge.source);
|
|
225
|
+
relativeNodes = edges?.filter((edge) => edge.target === coreNode.id).map((edge) => edge.source);
|
|
228
226
|
break;
|
|
229
227
|
case 'both':
|
|
230
228
|
relativeNodes = edges
|
|
231
|
-
?.filter(edge => edge.source === coreNode.id)
|
|
232
|
-
.map(edge => edge.target)
|
|
233
|
-
.concat(edges?.filter(edge => edge.target === coreNode.id).map(edge => edge.source));
|
|
229
|
+
?.filter((edge) => edge.source === coreNode.id)
|
|
230
|
+
.map((edge) => edge.target)
|
|
231
|
+
.concat(edges?.filter((edge) => edge.target === coreNode.id).map((edge) => edge.source));
|
|
234
232
|
break;
|
|
235
233
|
default:
|
|
236
234
|
break;
|
|
@@ -244,9 +242,9 @@ const getSameTypeNodes = (type: 'leaf' | 'all', nodeClusterBy: string, node: Nod
|
|
|
244
242
|
// @ts-ignore
|
|
245
243
|
const typeName = node[nodeClusterBy] || '';
|
|
246
244
|
// @ts-ignore
|
|
247
|
-
let sameTypeNodes = relativeNodes?.filter(item => item[nodeClusterBy] === typeName) || [];
|
|
245
|
+
let sameTypeNodes = relativeNodes?.filter((item) => item[nodeClusterBy] === typeName) || [];
|
|
248
246
|
if (type === 'leaf') {
|
|
249
|
-
sameTypeNodes = sameTypeNodes.filter(node => degreesMap[node.id]?.in === 0 ||degreesMap[node.id]?.out === 0);
|
|
247
|
+
sameTypeNodes = sameTypeNodes.filter((node) => degreesMap[node.id]?.in === 0 ||degreesMap[node.id]?.out === 0);
|
|
250
248
|
}
|
|
251
249
|
return sameTypeNodes;
|
|
252
250
|
};
|
|
@@ -260,14 +258,14 @@ export const getCoreNodeAndRelativeLeafNodes = (type: 'leaf' | 'all', node: Node
|
|
|
260
258
|
if (inDegree === 0) {
|
|
261
259
|
// 如果为没有出边的叶子节点,则找出与它关联的边的起点出发的所有一度节点
|
|
262
260
|
coreNode = getCoreNode('source', node, edges);
|
|
263
|
-
relativeLeafNodes = getRelativeNodeIds('both', coreNode, edges).map(nodeId => nodeMap[nodeId]);
|
|
261
|
+
relativeLeafNodes = getRelativeNodeIds('both', coreNode, edges).map((nodeId) => nodeMap[nodeId]);
|
|
264
262
|
} else if (outDegree === 0) {
|
|
265
263
|
// 如果为没有入边边的叶子节点,则找出与它关联的边的起点出发的所有一度节点
|
|
266
264
|
coreNode = getCoreNode('target', node, edges);
|
|
267
|
-
relativeLeafNodes = getRelativeNodeIds('both', coreNode, edges).map(nodeId => nodeMap[nodeId]);
|
|
265
|
+
relativeLeafNodes = getRelativeNodeIds('both', coreNode, edges).map((nodeId) => nodeMap[nodeId]);
|
|
268
266
|
}
|
|
269
267
|
relativeLeafNodes = relativeLeafNodes.filter(
|
|
270
|
-
node => degreesMap[node.id] && (degreesMap[node.id].in === 0 || degreesMap[node.id].out === 0),
|
|
268
|
+
(node) => degreesMap[node.id] && (degreesMap[node.id].in === 0 || degreesMap[node.id].out === 0),
|
|
271
269
|
);
|
|
272
270
|
const sameTypeLeafNodes = getSameTypeNodes(type, nodeClusterBy, node, relativeLeafNodes, degreesMap);
|
|
273
271
|
return { coreNode, relativeLeafNodes, sameTypeLeafNodes };
|
package/src/util/object.ts
CHANGED
|
@@ -15,7 +15,7 @@ export const clone = <T>(target: T): T => {
|
|
|
15
15
|
});
|
|
16
16
|
return cp.map((n: any) => clone<any>(n)) as any;
|
|
17
17
|
}
|
|
18
|
-
if (typeof target === 'object' && target
|
|
18
|
+
if (typeof target === 'object' && Object.keys(target).length) {
|
|
19
19
|
const cp = { ...(target as { [key: string]: any }) } as {
|
|
20
20
|
[key: string]: any
|
|
21
21
|
};
|