@antv/layout 0.2.5 → 0.3.0-beta.2

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.
Files changed (74) hide show
  1. package/dist/layout.min.js +1 -1
  2. package/dist/layout.min.js.map +1 -1
  3. package/es/layout/circular.js +4 -4
  4. package/es/layout/circular.js.map +1 -1
  5. package/es/layout/concentric.js +1 -1
  6. package/es/layout/concentric.js.map +1 -1
  7. package/es/layout/constants.js +1 -0
  8. package/es/layout/constants.js.map +1 -1
  9. package/es/layout/dagre.js +3 -3
  10. package/es/layout/dagre.js.map +1 -1
  11. package/es/layout/force2/ForceNBody.d.ts +7 -0
  12. package/es/layout/force2/ForceNBody.js +94 -0
  13. package/es/layout/force2/ForceNBody.js.map +1 -0
  14. package/es/layout/force2/index.d.ts +123 -0
  15. package/es/layout/force2/index.js +609 -0
  16. package/es/layout/force2/index.js.map +1 -0
  17. package/es/layout/gForce.js +2 -2
  18. package/es/layout/gForce.js.map +1 -1
  19. package/es/layout/gpu/gForce.js +1 -1
  20. package/es/layout/gpu/gForce.js.map +1 -1
  21. package/es/layout/grid.js +1 -1
  22. package/es/layout/grid.js.map +1 -1
  23. package/es/layout/index.d.ts +2 -1
  24. package/es/layout/index.js +2 -1
  25. package/es/layout/index.js.map +1 -1
  26. package/es/layout/layout.js +2 -0
  27. package/es/layout/layout.js.map +1 -1
  28. package/es/layout/types.d.ts +61 -0
  29. package/es/util/math.d.ts +21 -2
  30. package/es/util/math.js +111 -4
  31. package/es/util/math.js.map +1 -1
  32. package/lib/layout/circular.js +4 -4
  33. package/lib/layout/circular.js.map +1 -1
  34. package/lib/layout/concentric.js +1 -1
  35. package/lib/layout/concentric.js.map +1 -1
  36. package/lib/layout/constants.js +1 -0
  37. package/lib/layout/constants.js.map +1 -1
  38. package/lib/layout/dagre.js +4 -4
  39. package/lib/layout/dagre.js.map +1 -1
  40. package/lib/layout/force2/ForceNBody.d.ts +7 -0
  41. package/lib/layout/force2/ForceNBody.js +98 -0
  42. package/lib/layout/force2/ForceNBody.js.map +1 -0
  43. package/lib/layout/force2/index.d.ts +123 -0
  44. package/lib/layout/force2/index.js +644 -0
  45. package/lib/layout/force2/index.js.map +1 -0
  46. package/lib/layout/gForce.js +2 -2
  47. package/lib/layout/gForce.js.map +1 -1
  48. package/lib/layout/gpu/gForce.js +1 -1
  49. package/lib/layout/gpu/gForce.js.map +1 -1
  50. package/lib/layout/grid.js +1 -1
  51. package/lib/layout/grid.js.map +1 -1
  52. package/lib/layout/index.d.ts +2 -1
  53. package/lib/layout/index.js +3 -1
  54. package/lib/layout/index.js.map +1 -1
  55. package/lib/layout/layout.js +2 -0
  56. package/lib/layout/layout.js.map +1 -1
  57. package/lib/layout/types.d.ts +61 -0
  58. package/lib/util/math.d.ts +21 -2
  59. package/lib/util/math.js +116 -6
  60. package/lib/util/math.js.map +1 -1
  61. package/package.json +4 -2
  62. package/src/layout/circular.ts +7 -6
  63. package/src/layout/concentric.ts +1 -1
  64. package/src/layout/constants.ts +1 -0
  65. package/src/layout/dagre.ts +1 -1
  66. package/src/layout/force2/ForceNBody.ts +128 -0
  67. package/src/layout/force2/index.ts +743 -0
  68. package/src/layout/gForce.ts +7 -6
  69. package/src/layout/gpu/gForce.ts +4 -3
  70. package/src/layout/grid.ts +1 -1
  71. package/src/layout/index.ts +2 -0
  72. package/src/layout/layout.ts +2 -0
  73. package/src/layout/types.ts +67 -0
  74. package/src/util/math.ts +122 -6
@@ -3,13 +3,14 @@
3
3
  * @author shiwu.wyy@antfin.com
4
4
  */
5
5
 
6
- import {
6
+ import {
7
7
  OutNode,
8
8
  Edge,
9
9
  PointTuple,
10
10
  IndexMap,
11
11
  Point,
12
- GForceLayoutOptions
12
+ GForceLayoutOptions,
13
+ Degree
13
14
  } from "./types";
14
15
  import { Base } from "./base";
15
16
  import { isNumber, isFunction, isArray, getDegree, isObject, getEdgeTerminal } from "../util";
@@ -128,7 +129,7 @@ export class GForceLayout extends Base {
128
129
  public animate: Boolean = true;
129
130
 
130
131
  /** 存储节点度数 */
131
- private degrees: number[];
132
+ private degrees: Degree[];
132
133
 
133
134
  /** 迭代中的标识 */
134
135
  private timeInterval: number;
@@ -234,7 +235,7 @@ export class GForceLayout extends Base {
234
235
  self.degrees = getDegree(nodes.length, self.nodeIdxMap, edges);
235
236
  if (!self.getMass) {
236
237
  self.getMass = (d) => {
237
- const mass = d.mass || self.degrees[self.nodeIdxMap[d.id]] || 1;
238
+ const mass = d.mass || self.degrees[self.nodeIdxMap[d.id]].all || 1;
238
239
  return mass;
239
240
  };
240
241
  }
@@ -406,7 +407,7 @@ export class GForceLayout extends Base {
406
407
  let gravity = defaultGravity;
407
408
 
408
409
  if (self.getCenter) {
409
- const customCenterOpt = self.getCenter(node, degrees[i]);
410
+ const customCenterOpt = self.getCenter(node, degrees[i].all);
410
411
  if (
411
412
  customCenterOpt &&
412
413
  isNumber(customCenterOpt[0]) &&
@@ -484,4 +485,4 @@ export class GForceLayout extends Base {
484
485
  public getType() {
485
486
  return "gForce";
486
487
  }
487
- }
488
+ }
@@ -9,7 +9,8 @@ import {
9
9
  OutNode,
10
10
  PointTuple,
11
11
  Edge,
12
- GForceGPULayoutOptions
12
+ GForceGPULayoutOptions,
13
+ Degree
13
14
  } from "../types";
14
15
  import { Base } from "../base";
15
16
  import { isNumber } from "../../util";
@@ -95,7 +96,7 @@ export class GForceGPULayout extends Base {
95
96
  public onLayoutEnd: () => void;
96
97
 
97
98
  /** 存储节点度数 */
98
- private degrees: number[];
99
+ private degrees: Degree[];
99
100
 
100
101
  constructor(options?: GForceGPULayoutOptions) {
101
102
  super();
@@ -220,7 +221,7 @@ export class GForceGPULayout extends Base {
220
221
  );
221
222
 
222
223
  // init degree for mass
223
- self.degrees = getDegree(nodes.length, self.nodeIdxMap, edges);
224
+ self.degrees = getDegree(nodes.length, self.nodeIdxMap, edges).map(degree => degree.all);
224
225
  const masses: number[] = [];
225
226
  const nodeStrengths: number[] = [];
226
227
  const centerXs: number[] = [];
@@ -154,7 +154,7 @@ export class GridLayout extends Base {
154
154
  if (isNaN(nodes[0].degree)) {
155
155
  const values = getDegree(layoutNodes.length, nodeIdxMap, edges);
156
156
  layoutNodes.forEach((node, i) => {
157
- node.degree = values[i];
157
+ node.degree = values[i].all;
158
158
  });
159
159
  }
160
160
  }
@@ -1,6 +1,7 @@
1
1
  import { GridLayout } from "./grid";
2
2
  import { RandomLayout } from "./random";
3
3
  import { GForceLayout } from "./gForce";
4
+ import { Force2Layout } from "./force2";
4
5
  import { ForceLayout } from "./force";
5
6
  import { CircularLayout } from "./circular";
6
7
  import { DagreLayout } from "./dagre";
@@ -25,6 +26,7 @@ export {
25
26
  GridLayout,
26
27
  RandomLayout,
27
28
  GForceLayout,
29
+ Force2Layout,
28
30
  ForceLayout,
29
31
  CircularLayout,
30
32
  DagreLayout,
@@ -3,6 +3,7 @@ import { Model, ILayout } from "./types";
3
3
  import { getLayoutByName } from "../registy";
4
4
  import { GridLayout } from "./grid";
5
5
  import { RandomLayout } from "./random";
6
+ import { Force2Layout } from "./force2";
6
7
  import { GForceLayout } from "./gForce";
7
8
  import { ForceLayout } from "./force";
8
9
  import { CircularLayout } from "./circular";
@@ -59,6 +60,7 @@ export const Layouts: { [key: string]: any } = {
59
60
  fruchterman: FruchtermanLayout,
60
61
  forceAtlas2: ForceAtlas2Layout,
61
62
  gForce: GForceLayout,
63
+ force2: Force2Layout,
62
64
  dagre: DagreLayout,
63
65
  dagreCompound: DagreCompoundLayout,
64
66
  circular: CircularLayout,
@@ -65,6 +65,11 @@ export type IndexMap = {
65
65
  [key: string]: number;
66
66
  };
67
67
 
68
+ export type NodeMap = {
69
+ [key: string]: INode;
70
+ };
71
+
72
+
68
73
  export type Matrix = number[];
69
74
 
70
75
  export type Point = {
@@ -72,6 +77,12 @@ export type Point = {
72
77
  y: number;
73
78
  };
74
79
 
80
+ export type Degree = {
81
+ in: number;
82
+ out: number;
83
+ all: number;
84
+ };
85
+
75
86
  export interface ComboTree {
76
87
  id: string;
77
88
  children?: ComboTree[];
@@ -215,6 +226,62 @@ export interface FruchtermanLayoutOptions {
215
226
  onLayoutEnd?: () => void;
216
227
  }
217
228
 
229
+ export interface CentripetalOptions {
230
+ /** 叶子节点的施加力的因子 */
231
+ leaf?: number | ((node: INode, nodes: INode[], edges: Edge[]) => number);
232
+ /** 孤立节点的施加力的因子 */
233
+ single?: number | ((node: INode) => number);
234
+ /** 其他节点的施加力的因子 */
235
+ others?: number | ((node: INode) => number);
236
+ /** 向心力的中心点,默认为画布的中心 */
237
+ center?: (
238
+ node: INode,
239
+ nodes: INode[],
240
+ edges: Edge[],
241
+ width: number,
242
+ height: number,
243
+ ) => {
244
+ x: number;
245
+ y: number;
246
+ centerStrength?: number;
247
+ };
248
+ }
249
+ export interface Force2LayoutOptions {
250
+ type?: "force2";
251
+ center?: PointTuple;
252
+ width?: number;
253
+ height?: number;
254
+ linkDistance?: number | ((edge?: any, source?: any, target?: any) => number) | undefined;
255
+ defSpringLen: number | ((edge?: any, source?: any, target?: any) => number) | undefined;
256
+ nodeStrength?: number | ((d?: any) => number) | undefined;
257
+ edgeStrength?: number | ((d?: any) => number) | undefined;
258
+ preventOverlap?: boolean;
259
+ nodeSize?: number | number[] | ((d?: any) => number) | undefined;
260
+ nodeSpacing?: number | number[] | ((d?: any) => number) | undefined;
261
+ minMovement?: number;
262
+ maxIteration?: number;
263
+ damping?: number;
264
+ maxSpeed?: number;
265
+ coulombDisScale?: number;
266
+ gravity?: number;
267
+ factor?: number;
268
+ workerEnabled?: boolean;
269
+ centripetalOptions?: CentripetalOptions
270
+ leafCluster?: boolean;
271
+ clustering?: boolean;
272
+ nodeClusterBy?: string;
273
+ clusterNodeStrength?: number | ((node: Node) => number);
274
+ collideStrength?: number;
275
+ distanceThresholdMode?: 'mean' | 'max' | 'min';
276
+ animate?: boolean;
277
+ tick?: () => void;
278
+ onLayoutEnd?: () => void;
279
+ getMass?: ((d?: any) => number) | undefined;
280
+ getCenter?: ((d?: any, degree?: number) => number[]) | undefined;
281
+ monitor?: (params: { energy: number, nodes: INode[], edge: Edge[], iterations: number }) => void;
282
+
283
+ }
284
+
218
285
  export interface GForceLayoutOptions {
219
286
  type?: "gForce";
220
287
  center?: PointTuple;
package/src/util/math.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Matrix, Model, IndexMap, Edge, OutNode } from '../layout/types';
1
+ import { Matrix, Model, IndexMap, Edge, Node, OutNode, Degree, NodeMap } from '../layout/types';
2
2
  import { isObject } from './object';
3
3
 
4
4
  export const getEdgeTerminal = (edge: Edge, type: 'source' | 'target') => {
@@ -10,22 +10,54 @@ export const getEdgeTerminal = (edge: Edge, type: 'source' | 'target') => {
10
10
  };
11
11
 
12
12
  export const getDegree = (n: number, nodeIdxMap: IndexMap, edges: Edge[] | null) => {
13
- const degrees: number[] = [];
13
+ const degrees: Degree[] = [];
14
14
  for (let i = 0; i < n; i++) {
15
- degrees[i] = 0;
15
+ degrees[i] = {
16
+ in: 0,
17
+ out: 0,
18
+ all: 0
19
+ };
16
20
  }
17
21
  if (!edges) return degrees;
22
+ edges.forEach((e) => {
23
+ const source = getEdgeTerminal(e, 'source');
24
+ const target = getEdgeTerminal(e, 'target');
25
+ if (source && degrees[nodeIdxMap[source]]) {
26
+ degrees[nodeIdxMap[source]].out += 1;
27
+ degrees[nodeIdxMap[source]].all += 1;
28
+ }
29
+ if (target && degrees[nodeIdxMap[target]]) {
30
+ degrees[nodeIdxMap[target]].in += 1;
31
+ degrees[nodeIdxMap[target]].all += 1;
32
+ }
33
+ });
34
+ return degrees;
35
+ };
36
+
37
+ export const getDegreeMap = (nodes: Node[], edges: Edge[] | null) => {
38
+ const degreesMap: { [id: string]: Degree } = {}
39
+ nodes.forEach(node => {
40
+ degreesMap[node.id] = {
41
+ in: 0,
42
+ out: 0,
43
+ all: 0
44
+ }
45
+ })
46
+
47
+ if (!edges) return degreesMap;
18
48
  edges.forEach((e) => {
19
49
  const source = getEdgeTerminal(e, 'source');
20
50
  const target = getEdgeTerminal(e, 'target');
21
51
  if (source) {
22
- degrees[nodeIdxMap[source]] += 1;
52
+ degreesMap[source].out += 1;
53
+ degreesMap[source].all += 1;
23
54
  }
24
55
  if (target) {
25
- degrees[nodeIdxMap[target]] += 1;
56
+ degreesMap[target].in += 1;
57
+ degreesMap[target].all += 1;
26
58
  }
27
59
  });
28
- return degrees;
60
+ return degreesMap;
29
61
  };
30
62
 
31
63
  export const floydWarshall = (adjMatrix: Matrix[]): Matrix[] => {
@@ -155,4 +187,88 @@ export const findMinMaxNodeXY = (nodes: OutNode[]) => {
155
187
  if (maxY < node.y) maxY = node.y;
156
188
  });
157
189
  return { minX, minY, maxX, maxY };
190
+ };
191
+
192
+ /**
193
+ * 获取节点集合的平均位置信息
194
+ * @param nodes 节点集合
195
+ * @returns 平局内置
196
+ */
197
+ export const getAvgNodePosition = (nodes: OutNode[]) => {
198
+ let totalNodes = { x: 0, y: 0 };
199
+ nodes.forEach(node => {
200
+ totalNodes.x += node.x || 0;
201
+ totalNodes.y += node.y || 0;
202
+ });
203
+ // 获取均值向量
204
+ const length = nodes.length || 1;
205
+ return {
206
+ x: totalNodes.x / length,
207
+ y: totalNodes.y / length,
208
+ };
209
+ };
210
+
211
+ // 找出指定节点关联的边的起点或终点
212
+ const getCoreNode = (type: 'source' | 'target', node: Node, edges: Edge[]) => {
213
+ if (type === 'source') {
214
+ return (edges?.find(edge => edge.target === node.id)?.source || {}) as Node;
215
+ }
216
+ return (edges?.find(edge => edge.source === node.id)?.target || {}) as Node;
217
+ };
218
+
219
+ // 找出指定节点为起点或终点的所有一度叶子节点
220
+ const getRelativeNodeIds = (type: 'source' | 'target' | 'both', coreNode: Node, edges: Edge[]) => {
221
+ let relativeNodes: string[] = []
222
+ switch (type) {
223
+ case 'source':
224
+ relativeNodes = edges?.filter(edge => edge.source === coreNode.id).map(edge => edge.target);
225
+ break;
226
+ case 'target':
227
+ relativeNodes = edges?.filter(edge => edge.target === coreNode.id).map(edge => edge.source);
228
+ break;
229
+ case 'both':
230
+ 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));
234
+ break;
235
+ default:
236
+ break;
237
+ }
238
+ // 去重
239
+ const set = new Set(relativeNodes);
240
+ return Array.from(set);
241
+ };
242
+ // 找出同类型的节点
243
+ const getSameTypeNodes = (type: 'leaf' | 'all', nodeClusterBy: string, node: Node, relativeNodes: Node[], degreesMap: { [id: string]: Degree }) => {
244
+ // @ts-ignore
245
+ const typeName = node[nodeClusterBy] || '';
246
+ // @ts-ignore
247
+ let sameTypeNodes = relativeNodes?.filter(item => item[nodeClusterBy] === typeName) || [];
248
+ if (type === 'leaf') {
249
+ sameTypeNodes = sameTypeNodes.filter(node => degreesMap[node.id]?.in === 0 ||degreesMap[node.id]?.out === 0);
250
+ }
251
+ return sameTypeNodes;
252
+ };
253
+
254
+
255
+ // 找出与指定节点关联的边的起点或终点出发的所有一度叶子节点
256
+ export const getCoreNodeAndRelativeLeafNodes = (type: 'leaf' | 'all', node: Node, edges: Edge[], nodeClusterBy: string, degreesMap: { [id: string]: Degree }, nodeMap: NodeMap) => {
257
+ const { in: inDegree, out: outDegree } = degreesMap[node.id];
258
+ let coreNode: Node = node;
259
+ let relativeLeafNodes: Node[] = [];
260
+ if (inDegree === 0) {
261
+ // 如果为没有出边的叶子节点,则找出与它关联的边的起点出发的所有一度节点
262
+ coreNode = getCoreNode('source', node, edges);
263
+ relativeLeafNodes = getRelativeNodeIds('both', coreNode, edges).map(nodeId => nodeMap[nodeId]);
264
+ } else if (outDegree === 0) {
265
+ // 如果为没有入边边的叶子节点,则找出与它关联的边的起点出发的所有一度节点
266
+ coreNode = getCoreNode('target', node, edges);
267
+ relativeLeafNodes = getRelativeNodeIds('both', coreNode, edges).map(nodeId => nodeMap[nodeId]);
268
+ }
269
+ relativeLeafNodes = relativeLeafNodes.filter(
270
+ node => degreesMap[node.id] && (degreesMap[node.id].in === 0 || degreesMap[node.id].out === 0),
271
+ );
272
+ const sameTypeLeafNodes = getSameTypeNodes(type, nodeClusterBy, node, relativeLeafNodes, degreesMap);
273
+ return { coreNode, relativeLeafNodes, sameTypeLeafNodes };
158
274
  };