@antv/layout 0.3.20 → 0.3.22-beta.0

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.
@@ -13,17 +13,25 @@ import {
13
13
  GForceLayoutOptions,
14
14
  Degree,
15
15
  NodeMap,
16
- CentripetalOptions
17
- } from "../types";
18
- import { Base } from "../base";
19
- import { isNumber, isFunction, isArray, getDegreeMap, isObject, getEdgeTerminal, getAvgNodePosition, getCoreNodeAndRelativeLeafNodes } from "../../util";
20
- import { forceNBody } from "./ForceNBody";
16
+ CentripetalOptions,
17
+ } from '../types';
18
+ import { Base } from '../base';
19
+ import {
20
+ isNumber,
21
+ isFunction,
22
+ isArray,
23
+ getDegreeMap,
24
+ isObject,
25
+ getEdgeTerminal,
26
+ getAvgNodePosition,
27
+ getCoreNodeAndRelativeLeafNodes,
28
+ } from '../../util';
29
+ import { forceNBody } from './ForceNBody';
21
30
 
22
31
  type INode = OutNode & {
23
32
  size: number | PointTuple;
24
33
  };
25
34
 
26
-
27
35
  const proccessToFunc = (
28
36
  value: number | Function | undefined,
29
37
  defaultV?: number
@@ -60,7 +68,10 @@ export class Force2Layout extends Base {
60
68
  public edgeStrength: number | ((d?: any) => number) | undefined = 200;
61
69
 
62
70
  /** 斥力系数 */
63
- public nodeStrength: number | ((d?: any) => number) | undefined = 1000;
71
+ public nodeStrength:
72
+ | number
73
+ | ((d?: any, edges?: any[]) => number)
74
+ | undefined = 1000;
64
75
 
65
76
  /** 库伦系数 */
66
77
  public coulombDisScale: number = 0.005;
@@ -87,13 +98,19 @@ export class Force2Layout extends Base {
87
98
  public getCenter: ((d?: any, degree?: number) => number[]) | undefined;
88
99
 
89
100
  /** 计算画布上下两侧对节点吸引力大小 */
90
- public defSideCoe?: (node: Node) => number;
101
+ public defSideCoe?: (node: Node, edges: Edge[]) => number;
91
102
 
92
103
  /** 理想边长 */
93
- public linkDistance: number | ((edge?: any, source?: any, target?: any) => number) | undefined = 200;
104
+ public linkDistance:
105
+ | number
106
+ | ((edge?: any, source?: any, target?: any) => number)
107
+ | undefined = 200;
94
108
 
95
109
  /** 理想边长,兼容 graphin-force */
96
- public defSpringLen: number | ((edge?: any, source?: any, target?: any) => number) | undefined;
110
+ public defSpringLen:
111
+ | number
112
+ | ((edge?: any, source?: any, target?: any) => number)
113
+ | undefined;
97
114
 
98
115
  /** 重力大小 */
99
116
  public gravity: number = 0;
@@ -126,7 +143,7 @@ export class Force2Layout extends Base {
126
143
  public distanceThresholdMode: 'mean' | 'max' | 'min' = 'mean';
127
144
 
128
145
  /** 每次迭代结束的回调函数 */
129
- public tick: (() => void) | null = () => { };
146
+ public tick: (() => void) | null = () => {};
130
147
 
131
148
  /** 是否允许每次迭代结束调用回调函数 */
132
149
  public enableTick: boolean;
@@ -151,7 +168,12 @@ export class Force2Layout extends Base {
151
168
  public animate: Boolean;
152
169
 
153
170
  /** 监控信息,不配置则不计算 */
154
- public monitor: (params: { energy: number, nodes: INode[], edge: Edge[], iterations: number }) => void;
171
+ public monitor: (params: {
172
+ energy: number;
173
+ nodes: INode[];
174
+ edge: Edge[];
175
+ iterations: number;
176
+ }) => void;
155
177
 
156
178
  /** 存储节点度数 */
157
179
  private degreesMap: { [id: string]: Degree };
@@ -184,24 +206,38 @@ export class Force2Layout extends Base {
184
206
  }
185
207
 
186
208
  public getCentripetalOptions() {
187
- const { leafCluster, clustering, nodeClusterBy, nodes, nodeMap, clusterNodeStrength: propsClusterNodeStrength } = this;
209
+ const {
210
+ leafCluster,
211
+ clustering,
212
+ nodeClusterBy,
213
+ nodes,
214
+ nodeMap,
215
+ clusterNodeStrength: propsClusterNodeStrength,
216
+ } = this;
188
217
 
189
218
  const getClusterNodeStrength = (node: Node) =>
190
- typeof propsClusterNodeStrength === 'function' ? propsClusterNodeStrength(node) : propsClusterNodeStrength;
219
+ typeof propsClusterNodeStrength === 'function'
220
+ ? propsClusterNodeStrength(node)
221
+ : propsClusterNodeStrength;
191
222
 
192
223
  let centripetalOptions = {};
193
224
  let sameTypeLeafMap: any;
194
225
  // 如果传入了需要叶子节点聚类
195
226
  if (leafCluster) {
196
227
  sameTypeLeafMap = this.getSameTypeLeafMap() || {};
197
- const relativeNodesType = Array.from(new Set(nodes?.map((node) => node[nodeClusterBy]))) || [];
228
+ const relativeNodesType =
229
+ Array.from(new Set(nodes?.map((node) => node[nodeClusterBy]))) || [];
198
230
  centripetalOptions = {
199
231
  single: 100,
200
232
  leaf: (node, nodes, edges) => {
201
233
  // 找出与它关联的边的起点或终点出发的所有一度节点中同类型的叶子节点
202
- const { relativeLeafNodes, sameTypeLeafNodes } = sameTypeLeafMap[node.id] || {};
234
+ const { relativeLeafNodes, sameTypeLeafNodes } =
235
+ sameTypeLeafMap[node.id] || {};
203
236
  // 如果都是同一类型或者每种类型只有1个,则施加默认向心力
204
- if (sameTypeLeafNodes?.length === relativeLeafNodes?.length || relativeNodesType?.length === 1) {
237
+ if (
238
+ sameTypeLeafNodes?.length === relativeLeafNodes?.length ||
239
+ relativeNodesType?.length === 1
240
+ ) {
205
241
  return 1;
206
242
  }
207
243
  return getClusterNodeStrength(node);
@@ -242,14 +278,18 @@ export class Force2Layout extends Base {
242
278
  // 如果传入了全局节点聚类
243
279
  if (clustering) {
244
280
  if (!sameTypeLeafMap) sameTypeLeafMap = this.getSameTypeLeafMap();
245
- const clusters: string[] = Array.from(new Set(nodes.map((node, i) => {
246
- return node[nodeClusterBy];
247
- }))).filter(
248
- (item) => item !== undefined,
249
- );
281
+ const clusters: string[] = Array.from(
282
+ new Set(
283
+ nodes.map((node, i) => {
284
+ return node[nodeClusterBy];
285
+ })
286
+ )
287
+ ).filter((item) => item !== undefined);
250
288
  const centerNodeInfo: { [key: string]: { x: number; y: number } } = {};
251
289
  clusters.forEach((cluster) => {
252
- const sameTypeNodes = nodes.filter((item) => item[nodeClusterBy] === cluster).map((node) => nodeMap[node.id]);
290
+ const sameTypeNodes = nodes
291
+ .filter((item) => item[nodeClusterBy] === cluster)
292
+ .map((node) => nodeMap[node.id]);
253
293
  // 找出同类型节点平均位置节点的距离最近的节点作为中心节点
254
294
  centerNodeInfo[cluster] = getAvgNodePosition(sameTypeNodes);
255
295
  });
@@ -274,9 +314,12 @@ export class Force2Layout extends Base {
274
314
  };
275
315
 
276
316
  const { leaf, single, others } = this.centripetalOptions;
277
- if (leaf && typeof leaf !== 'function') this.centripetalOptions.leaf = () => leaf;
278
- if (single && typeof single !== 'function') this.centripetalOptions.single = () => single;
279
- if (others && typeof others !== 'function') this.centripetalOptions.others = () => others;
317
+ if (leaf && typeof leaf !== 'function')
318
+ this.centripetalOptions.leaf = () => leaf;
319
+ if (single && typeof single !== 'function')
320
+ this.centripetalOptions.single = () => single;
321
+ if (others && typeof others !== 'function')
322
+ this.centripetalOptions.others = () => others;
280
323
  }
281
324
 
282
325
  public updateCfg(cfg: any) {
@@ -296,6 +339,7 @@ export class Force2Layout extends Base {
296
339
  * 执行布局
297
340
  */
298
341
  public execute() {
342
+ console.log('here');
299
343
  const self = this;
300
344
  self.stop();
301
345
  const { nodes, edges, defSpringLen } = self;
@@ -307,10 +351,10 @@ export class Force2Layout extends Base {
307
351
  return;
308
352
  }
309
353
 
310
- if (!self.width && typeof window !== "undefined") {
354
+ if (!self.width && typeof window !== 'undefined') {
311
355
  self.width = window.innerWidth;
312
356
  }
313
- if (!self.height && typeof window !== "undefined") {
357
+ if (!self.height && typeof window !== 'undefined') {
314
358
  self.height = window.innerHeight;
315
359
  }
316
360
  if (!self.center) {
@@ -332,11 +376,10 @@ export class Force2Layout extends Base {
332
376
  let massWeight = 1;
333
377
  if (isNumber(d.mass)) massWeight = d.mass;
334
378
  const degree = self.degreesMap[d.id].all;
335
- return (!degree || degree < 5) ? massWeight : degree * 5 * massWeight;
379
+ return !degree || degree < 5 ? massWeight : degree * 5 * massWeight;
336
380
  };
337
381
  }
338
382
 
339
-
340
383
  // node size function
341
384
  const nodeSize = self.nodeSize;
342
385
  let nodeSizeFunc;
@@ -355,7 +398,8 @@ export class Force2Layout extends Base {
355
398
  if (d.size) {
356
399
  if (isArray(d.size)) {
357
400
  return Math.max(d.size[0], d.size[1]) + nodeSpacingFunc(d);
358
- } if (isObject(d.size)) {
401
+ }
402
+ if (isObject(d.size)) {
359
403
  return Math.max(d.size.width, d.size.height) + nodeSpacingFunc(d);
360
404
  }
361
405
  return (d.size as number) + nodeSpacingFunc(d);
@@ -395,17 +439,16 @@ export class Force2Layout extends Base {
395
439
  sDegree: degree.out,
396
440
  force: {
397
441
  mass: self.getMass(node),
398
- nodeStrength: self.nodeStrength(node)
399
- }
400
- }
401
- }
442
+ nodeStrength: self.nodeStrength(node, edges),
443
+ },
444
+ },
445
+ },
402
446
  };
403
447
  nodeIdxMap[node.id] = i;
404
448
  });
405
449
  self.nodeMap = nodeMap;
406
450
  self.nodeIdxMap = nodeIdxMap;
407
451
 
408
-
409
452
  self.edgeInfos = [];
410
453
  edges?.forEach((edge) => {
411
454
  const sourceNode = nodeMap[edge.source];
@@ -415,29 +458,39 @@ export class Force2Layout extends Base {
415
458
  } else {
416
459
  self.edgeInfos.push({
417
460
  edgeStrength: self.edgeStrength(edge),
418
- linkDistance: defSpringLen ? defSpringLen(
419
- {
420
- ...edge,
421
- source: sourceNode,
422
- target: targetNode
423
- },
424
- sourceNode,
425
- targetNode
426
- ) : self.linkDistance(edge, sourceNode, targetNode) || 1 + ((nodeSize(sourceNode) + nodeSize(sourceNode)) || 0) / 2
461
+ linkDistance: defSpringLen
462
+ ? defSpringLen(
463
+ {
464
+ ...edge,
465
+ source: sourceNode,
466
+ target: targetNode,
467
+ },
468
+ sourceNode,
469
+ targetNode
470
+ )
471
+ : self.linkDistance(edge, sourceNode, targetNode) ||
472
+ 1 + (nodeSize(sourceNode) + nodeSize(sourceNode) || 0) / 2,
427
473
  });
428
474
  }
429
475
  });
430
476
 
431
477
  this.getCentripetalOptions();
432
478
 
433
- self.onLayoutEnd = self.onLayoutEnd || (() => { });
479
+ self.onLayoutEnd = self.onLayoutEnd || (() => {});
434
480
 
435
481
  self.run();
436
482
  }
437
483
 
438
484
  public run() {
439
485
  const self = this;
440
- const { maxIteration, nodes, workerEnabled, minMovement, animate, nodeMap } = self;
486
+ const {
487
+ maxIteration,
488
+ nodes,
489
+ workerEnabled,
490
+ minMovement,
491
+ animate,
492
+ nodeMap,
493
+ } = self;
441
494
 
442
495
  if (!nodes) return;
443
496
 
@@ -451,13 +504,17 @@ export class Force2Layout extends Base {
451
504
  const silence = !animate;
452
505
  if (workerEnabled || silence) {
453
506
  let usedIter = 0;
454
- for (let i = 0; (self.judgingDistance > minMovement || i < 1) && i < maxIter; i++) {
507
+ for (
508
+ let i = 0;
509
+ (self.judgingDistance > minMovement || i < 1) && i < maxIter;
510
+ i++
511
+ ) {
455
512
  usedIter = i;
456
513
  self.runOneStep(i, velArray);
457
514
  }
458
515
  self.onLayoutEnd(Object.values(nodeMap));
459
516
  } else {
460
- if (typeof window === "undefined") return;
517
+ if (typeof window === 'undefined') return;
461
518
  let iter = 0;
462
519
  // interval for render the result after each iteration
463
520
  this.timeInterval = window.setInterval(() => {
@@ -513,7 +570,13 @@ export class Force2Layout extends Base {
513
570
  const self = this;
514
571
  const { nodes, nodeMap, factor, coulombDisScale } = self;
515
572
  const nodeSize = self.nodeSize as Function;
516
- forceNBody(nodes, nodeMap, factor, coulombDisScale * coulombDisScale, accArray);
573
+ forceNBody(
574
+ nodes,
575
+ nodeMap,
576
+ factor,
577
+ coulombDisScale * coulombDisScale,
578
+ accArray
579
+ );
517
580
  }
518
581
 
519
582
  // hooks law
@@ -559,7 +622,17 @@ export class Force2Layout extends Base {
559
622
  // attract to center
560
623
  public calGravity(accArray: number[]) {
561
624
  const self = this;
562
- const { nodes, edges = [], nodeMap, width, height, center, gravity: defaultGravity, degreesMap, centripetalOptions } = self;
625
+ const {
626
+ nodes,
627
+ edges = [],
628
+ nodeMap,
629
+ width,
630
+ height,
631
+ center,
632
+ gravity: defaultGravity,
633
+ degreesMap,
634
+ centripetalOptions,
635
+ } = self;
563
636
  if (!nodes) return;
564
637
  const nodeLength = nodes.length;
565
638
  for (let i = 0; i < nodeLength; i++) {
@@ -583,13 +656,26 @@ export class Force2Layout extends Base {
583
656
  }
584
657
 
585
658
  if (gravity) {
586
- accArray[idx] -= gravity * vecX / mass;
587
- accArray[idx + 1] -= gravity * vecY / mass;
659
+ accArray[idx] -= (gravity * vecX) / mass;
660
+ accArray[idx + 1] -= (gravity * vecY) / mass;
588
661
  }
589
662
 
590
663
  if (centripetalOptions) {
591
- const { leaf, single, others, center: centriCenter } = centripetalOptions;
592
- const { x: centriX, y: centriY, centerStrength } = centriCenter?.(node, nodes, edges, width, height) || { x: 0, y: 0, centerStrength: 0 };
664
+ const {
665
+ leaf,
666
+ single,
667
+ others,
668
+ center: centriCenter,
669
+ } = centripetalOptions;
670
+ const {
671
+ x: centriX,
672
+ y: centriY,
673
+ centerStrength,
674
+ } = centriCenter?.(node, nodes, edges, width, height) || {
675
+ x: 0,
676
+ y: 0,
677
+ centerStrength: 0,
678
+ };
593
679
  if (!isNumber(centriX) || !isNumber(centriY)) continue;
594
680
  const vx = (node.x - centriX) / mass;
595
681
  const vy = (node.y - centriY) / mass;
@@ -626,17 +712,26 @@ export class Force2Layout extends Base {
626
712
  }
627
713
 
628
714
  // TODO: 待 graphin 修改正确
629
- // public attractToSide(accArray: number[]) {
630
- // const { defSideCoe, height, nodes } = this;
631
- // if (!defSideCoe || typeof defSideCoe !== 'function' || !nodes?.length) return;
632
- // nodes.forEach((node, i) => {
633
- // const sideCoe = defSideCoe!(node);
634
- // if (sideCoe === 0) return;
635
- // const targetY = sideCoe > 0 ? 0 : height;
636
- // const strength = Math.abs(sideCoe);
637
- // accArray[2 * i + 1] -= strength * (targetY - node.y);
638
- // });
639
- // };
715
+ public attractToSide(accArray: number[]) {
716
+ const { defSideCoe, height, nodes, edges } = this;
717
+ if (!defSideCoe || typeof defSideCoe !== 'function' || !nodes?.length)
718
+ return;
719
+ const relatedEdges: { [nodeId: string]: Edge[] } = {};
720
+ edges.forEach((edge) => {
721
+ const { source, target } = edge;
722
+ relatedEdges[source.id] = relatedEdges[source.id] || [];
723
+ relatedEdges[source.id].push(edge);
724
+ relatedEdges[target.id] = relatedEdges[target.id] || [];
725
+ relatedEdges[target.id].push(edge);
726
+ });
727
+ nodes.forEach((node, i) => {
728
+ const sideCoe = defSideCoe!(node, relatedEdges[node.id] || []);
729
+ if (sideCoe === 0) return;
730
+ const targetY = sideCoe > 0 ? 0 : height;
731
+ const strength = Math.abs(sideCoe);
732
+ accArray[2 * i + 1] -= strength * (targetY - node.y);
733
+ });
734
+ }
640
735
 
641
736
  public updateVelocity(
642
737
  accArray: number[],
@@ -647,8 +742,11 @@ export class Force2Layout extends Base {
647
742
  const { nodes, damping, maxSpeed } = self;
648
743
  if (!nodes?.length) return;
649
744
  nodes.forEach((_, i) => {
650
- let vx = (velArray[2 * i] + accArray[2 * i] * stepInterval) * damping || 0.01;
651
- let vy = (velArray[2 * i + 1] + accArray[2 * i + 1] * stepInterval) * damping || 0.01;
745
+ let vx =
746
+ (velArray[2 * i] + accArray[2 * i] * stepInterval) * damping || 0.01;
747
+ let vy =
748
+ (velArray[2 * i + 1] + accArray[2 * i + 1] * stepInterval) * damping ||
749
+ 0.01;
652
750
  const vLength = Math.sqrt(vx * vx + vy * vy);
653
751
  if (vLength > maxSpeed) {
654
752
  const param2 = maxSpeed / vLength;
@@ -660,10 +758,7 @@ export class Force2Layout extends Base {
660
758
  });
661
759
  }
662
760
 
663
- public updatePosition(
664
- velArray: number[],
665
- stepInterval: number,
666
- ) {
761
+ public updatePosition(velArray: number[], stepInterval: number) {
667
762
  const self = this;
668
763
  const { nodes, distanceThresholdMode, nodeMap } = self;
669
764
  if (!nodes?.length) {
@@ -693,21 +788,24 @@ export class Force2Layout extends Base {
693
788
  const distanceMagnitude = Math.sqrt(distX * distX + distY * distY);
694
789
  switch (distanceThresholdMode) {
695
790
  case 'max':
696
- if (self.judgingDistance < distanceMagnitude) self.judgingDistance = distanceMagnitude;
791
+ if (self.judgingDistance < distanceMagnitude)
792
+ self.judgingDistance = distanceMagnitude;
697
793
  break;
698
794
  case 'min':
699
- if (self.judgingDistance > distanceMagnitude) self.judgingDistance = distanceMagnitude;
795
+ if (self.judgingDistance > distanceMagnitude)
796
+ self.judgingDistance = distanceMagnitude;
700
797
  break;
701
798
  default:
702
799
  sum = sum + distanceMagnitude;
703
800
  break;
704
801
  }
705
802
  });
706
- if (!distanceThresholdMode || distanceThresholdMode === 'mean') self.judgingDistance = sum / nodes.length;
803
+ if (!distanceThresholdMode || distanceThresholdMode === 'mean')
804
+ self.judgingDistance = sum / nodes.length;
707
805
  }
708
806
 
709
807
  public stop() {
710
- if (this.timeInterval && typeof window !== "undefined") {
808
+ if (this.timeInterval && typeof window !== 'undefined') {
711
809
  window.clearInterval(this.timeInterval);
712
810
  }
713
811
  }
@@ -722,7 +820,7 @@ export class Force2Layout extends Base {
722
820
  }
723
821
 
724
822
  public getType() {
725
- return "force2";
823
+ return 'force2';
726
824
  }
727
825
 
728
826
  private getSameTypeLeafMap() {
@@ -733,7 +831,14 @@ export class Force2Layout extends Base {
733
831
  nodes.forEach((node, i) => {
734
832
  const degree = degreesMap[node.id].all;
735
833
  if (degree === 1) {
736
- sameTypeLeafMap[node.id] = getCoreNodeAndRelativeLeafNodes('leaf', node, edges, nodeClusterBy, degreesMap, nodeMap);
834
+ sameTypeLeafMap[node.id] = getCoreNodeAndRelativeLeafNodes(
835
+ 'leaf',
836
+ node,
837
+ edges,
838
+ nodeClusterBy,
839
+ degreesMap,
840
+ nodeMap
841
+ );
737
842
  }
738
843
  });
739
844
  return sameTypeLeafMap;