@antv/layout 2.0.0-alpha.2 → 2.0.0-alpha.4

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 (38) hide show
  1. package/dist/index.js +1207 -1247
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.min.js +2 -2
  4. package/dist/index.min.js.map +1 -1
  5. package/dist/worker.js +1 -1
  6. package/dist/worker.js.map +1 -1
  7. package/lib/algorithm/antv-dagre/index.js +10 -8
  8. package/lib/algorithm/antv-dagre/index.js.map +1 -1
  9. package/lib/algorithm/antv-dagre/types.d.ts +25 -3
  10. package/lib/algorithm/base-layout.d.ts +1 -0
  11. package/lib/algorithm/base-layout.js +5 -3
  12. package/lib/algorithm/base-layout.js.map +1 -1
  13. package/lib/algorithm/circular/index.js +2 -2
  14. package/lib/algorithm/circular/index.js.map +1 -1
  15. package/lib/algorithm/combo-combined/index.js +4 -4
  16. package/lib/algorithm/combo-combined/index.js.map +1 -1
  17. package/lib/algorithm/d3-force/index.d.ts +2 -2
  18. package/lib/algorithm/d3-force/index.js +60 -104
  19. package/lib/algorithm/d3-force/index.js.map +1 -1
  20. package/lib/index.d.ts +1 -1
  21. package/lib/index.js +1 -1
  22. package/lib/util/object.d.ts +1 -2
  23. package/lib/util/object.js +1 -4
  24. package/lib/util/object.js.map +1 -1
  25. package/lib/util/order.d.ts +1 -1
  26. package/lib/util/order.js +4 -1
  27. package/lib/util/order.js.map +1 -1
  28. package/lib/worker.js +768 -807
  29. package/lib/worker.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/algorithm/antv-dagre/index.ts +11 -12
  32. package/src/algorithm/antv-dagre/types.ts +25 -3
  33. package/src/algorithm/base-layout.ts +13 -8
  34. package/src/algorithm/circular/index.ts +2 -2
  35. package/src/algorithm/combo-combined/index.ts +5 -5
  36. package/src/algorithm/d3-force/index.ts +69 -122
  37. package/src/util/object.ts +0 -4
  38. package/src/util/order.ts +4 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antv/layout",
3
- "version": "2.0.0-alpha.2",
3
+ "version": "2.0.0-alpha.4",
4
4
  "description": "graph layout algorithm",
5
5
  "main": "dist/index.min.js",
6
6
  "module": "lib/index.js",
@@ -1,9 +1,9 @@
1
1
  import { isNumber } from '@antv/util';
2
- import { BaseLayout } from '../base-layout';
3
2
  import type { NodeData, PointObject } from '../../types';
4
3
  import { parsePoint } from '../../util';
5
4
  import { formatNumberFn, formatSizeFn } from '../../util/format';
6
5
  import { parseSize } from '../../util/size';
6
+ import { BaseLayout } from '../base-layout';
7
7
  import { DagreGraph, GraphNode } from './graph';
8
8
  import { layout } from './layout';
9
9
  import { AntVDagreLayoutOptions } from './types';
@@ -50,18 +50,15 @@ export class AntVDagreLayout extends BaseLayout<AntVDagreLayoutOptions> {
50
50
  sortByCombo,
51
51
  // focusNode,
52
52
  preset,
53
+ ranksepFunc,
54
+ nodesepFunc,
53
55
  } = options;
54
56
 
55
- const ranksepfunc = formatNumberFn(
56
- ranksep,
57
- DEFAULTS_LAYOUT_OPTIONS.ranksep as number,
58
- );
59
- const nodesepfunc = formatNumberFn(
60
- nodesep,
61
- DEFAULTS_LAYOUT_OPTIONS.nodesep as number,
62
- );
57
+ const ranksepfunc = formatNumberFn(ranksepFunc, ranksep ?? 50);
58
+ const nodesepfunc = formatNumberFn(nodesepFunc, nodesep ?? 50);
63
59
  let horisep: (d?: NodeData | undefined) => number = nodesepfunc;
64
60
  let vertisep: (d?: NodeData | undefined) => number = ranksepfunc;
61
+
65
62
  if (rankdir === 'LR' || rankdir === 'RL') {
66
63
  horisep = ranksepfunc;
67
64
  vertisep = nodesepfunc;
@@ -80,9 +77,10 @@ export class AntVDagreLayout extends BaseLayout<AntVDagreLayoutOptions> {
80
77
  const edges = this.model.edges();
81
78
 
82
79
  nodes.forEach((node) => {
83
- const size = parseSize(nodeSizeFunc(node));
84
- const verti = vertisep(node);
85
- const hori = horisep(node);
80
+ const raw = node._original;
81
+ const size = parseSize(nodeSizeFunc(raw));
82
+ const verti = vertisep(raw);
83
+ const hori = horisep(raw);
86
84
  const width = size[0] + 2 * hori;
87
85
  const height = size[1] + 2 * verti;
88
86
  const layer = node.data?.layer;
@@ -150,6 +148,7 @@ export class AntVDagreLayout extends BaseLayout<AntVDagreLayoutOptions> {
150
148
  acyclicer: 'greedy',
151
149
  ranker,
152
150
  rankdir,
151
+ nodesep,
153
152
  align,
154
153
  });
155
154
 
@@ -1,5 +1,5 @@
1
- import { BaseLayoutOptions } from '../base-layout';
2
1
  import { ID, NodeData, Point, Size } from '../../types';
2
+ import { BaseLayoutOptions } from '../base-layout';
3
3
 
4
4
  export type DagreRankdir =
5
5
  | 'TB'
@@ -83,7 +83,7 @@ export interface AntVDagreLayoutOptions extends BaseLayoutOptions {
83
83
  * <en/> The horizontal gap between nodes (px) in the case of rankdir is 'TB' or 'BT'. The vertical gap between nodes (px) in the case of rankdir is 'LR' or 'RL'. nodesepFunc has a higher priority
84
84
  * @defaultValue 50
85
85
  */
86
- nodesep?: number | ((d?: NodeData) => number);
86
+ nodesep?: number;
87
87
  /**
88
88
  * <zh/> 层间距(px)
89
89
  *
@@ -94,7 +94,29 @@ export interface AntVDagreLayoutOptions extends BaseLayoutOptions {
94
94
  * <en/> The vertical gap between levels (px) in the case of rankdir is 'TB' or 'BT'. The horizontal gap between levels (px) in the case of rankdir is 'LR' or 'RL'. ranksepFunc has a higher priority
95
95
  * @defaultValue 50
96
96
  */
97
- ranksep?: number | ((d?: NodeData) => number);
97
+ ranksep?: number;
98
+ /**
99
+ * <zh/> 节点间距(px)的回调函数,通过该参数可以对不同节点设置不同的节点间距
100
+ *
101
+ * <en/> The callback function of the node spacing (px), which can be used to set different node spacing for different nodes
102
+ * @remarks
103
+ * <zh/> 在 rankdir 为 'TB' 或 'BT' 时是节点的水平间距;在 rankdir 为 'LR' 或 'RL' 时代表节点的竖直方向间距。优先级高于 nodesep,即若设置了 nodesepFunc,则 nodesep 不生效
104
+ *
105
+ * <en/> The horizontal spacing of the node in the case of rankdir is 'TB' or 'BT', and the vertical spacing of the node in the case of rankdir is 'LR' or 'RL'. The priority is higher than nodesep, that is, if nodesepFunc is set, nodesep does not take effect
106
+ * @param d - <zh/> 节点实例 | <en/> Node instance
107
+ */
108
+ nodesepFunc?: (d?: NodeData) => number;
109
+ /**
110
+ * <zh/> 层间距(px)的回调函数
111
+ *
112
+ * <en/> The callback function of the layer spacing (px)
113
+ * @remarks
114
+ * <zh/> 在 rankdir 为 'TB' 或 'BT' 时是竖直方向相邻层间距;在 rankdir 为 'LR' 或 'RL' 时代表水平方向相邻层间距。优先级高于 nodesep,即若设置了 nodesepFunc,则 nodesep 不生效
115
+ *
116
+ * <en/> The vertical spacing of adjacent layers in the case of rankdir is 'TB' or 'BT', and the horizontal spacing of adjacent layers in the case of rankdir is 'LR' or 'RL'. The priority is higher than nodesep, that is, if nodesepFunc is set, nodesep does not take effect
117
+ * @param d - <zh/> 节点实例 | <en/> Node instance
118
+ */
119
+ ranksepFunc?: (d?: NodeData) => number;
98
120
  /**
99
121
  * <zh/> 是否同时计算边上的的控制点位置
100
122
  *
@@ -2,12 +2,7 @@ import type { GraphLib } from '../model/data';
2
2
  import { RuntimeContext } from '../runtime/context';
3
3
  import { Supervisor } from '../runtime/supervisor';
4
4
  import type { GraphData, GraphEdge, GraphNode, Point } from '../types';
5
- import { mergeOptions } from '../util';
6
- import type {
7
- BaseLayoutOptions,
8
- Layout,
9
- LayoutWithIterations,
10
- } from './types';
5
+ import type { BaseLayoutOptions, Layout, LayoutWithIterations } from './types';
11
6
 
12
7
  export type { BaseLayoutOptions };
13
8
 
@@ -35,18 +30,28 @@ export abstract class BaseLayout<
35
30
  protected supervisor: Supervisor | null = null;
36
31
 
37
32
  constructor(options?: Partial<O>) {
38
- this.initialOptions = mergeOptions<O>(this.getDefaultOptions(), options);
33
+ this.initialOptions = this.mergeOptions<O>(
34
+ this.getDefaultOptions(),
35
+ options,
36
+ );
39
37
  }
40
38
 
41
39
  get options(): O {
42
40
  return this.runtimeOptions || this.initialOptions;
43
41
  }
44
42
 
43
+ protected mergeOptions<O>(base: O, patch?: Partial<O>): O {
44
+ return Object.assign({}, base, patch || {});
45
+ }
46
+
45
47
  public async execute(
46
48
  data: GraphData,
47
49
  userOptions?: Partial<O>,
48
50
  ): Promise<void> {
49
- this.runtimeOptions = mergeOptions<O>(this.initialOptions, userOptions);
51
+ this.runtimeOptions = this.mergeOptions<O>(
52
+ this.initialOptions,
53
+ userOptions,
54
+ );
50
55
  const { node, edge, enableWorker } = this.runtimeOptions;
51
56
 
52
57
  this.context = new RuntimeContext(data, { node, edge });
@@ -1,8 +1,8 @@
1
1
  import { isNil } from '@antv/util';
2
- import { BaseLayout } from '../base-layout';
3
2
  import { normalizeViewport, orderByDegree, orderByTopology } from '../../util';
4
3
  import { applySingleNodeLayout } from '../../util/common';
5
4
  import { formatNodeSizeFn } from '../../util/format';
5
+ import { BaseLayout } from '../base-layout';
6
6
  import type { CircularLayoutOptions } from './types';
7
7
 
8
8
  export type { CircularLayoutOptions };
@@ -61,7 +61,7 @@ export class CircularLayout extends BaseLayout<CircularLayoutOptions> {
61
61
  orderByTopology(this.model, true);
62
62
  } else if (ordering === 'degree') {
63
63
  // layout according to the descent order of degrees
64
- orderByDegree(this.model);
64
+ orderByDegree(this.model, 'asc');
65
65
  }
66
66
 
67
67
  let { radius, startRadius, endRadius } = this.options;
@@ -1,5 +1,3 @@
1
- import { BaseLayout, isLayoutWithIterations } from '../base-layout';
2
- import type { Layout } from '../types';
3
1
  import { registry } from '../../registry';
4
2
  import type {
5
3
  GraphData,
@@ -11,6 +9,8 @@ import type {
11
9
  } from '../../types';
12
10
  import { normalizeViewport, parseSize } from '../../util';
13
11
  import { formatNodeSizeFn, formatNumberFn } from '../../util/format';
12
+ import { BaseLayout, isLayoutWithIterations } from '../base-layout';
13
+ import type { Layout } from '../types';
14
14
  import type {
15
15
  ComboCombinedLayoutConfig,
16
16
  ComboCombinedLayoutOptions,
@@ -34,9 +34,9 @@ const DEFAULT_OPTIONS: ComboCombinedLayoutOptions = {
34
34
  ? { type: 'force', preventOverlap: true }
35
35
  : { type: 'concentric', preventOverlap: true },
36
36
  nodeSize: 20,
37
- nodeSpacing: 10,
38
- comboPadding: 20,
39
- comboSpacing: 80,
37
+ nodeSpacing: 0,
38
+ comboPadding: 10,
39
+ comboSpacing: 0,
40
40
  };
41
41
 
42
42
  const ROOT_ID = 'root';
@@ -10,10 +10,10 @@ import {
10
10
  forceX,
11
11
  forceY,
12
12
  } from 'd3-force';
13
- import { BaseLayoutWithIterations } from '../base-layout';
14
13
  import type { ID, Position } from '../../types';
15
14
  import { assignDefined, normalizeViewport } from '../../util';
16
15
  import { formatNodeSizeFn } from '../../util/format';
16
+ import { BaseLayoutWithIterations } from '../base-layout';
17
17
  import forceInABox from './force-in-a-box';
18
18
  import type {
19
19
  D3ForceCommonOptions,
@@ -25,29 +25,20 @@ import type {
25
25
  export type { D3ForceLayoutOptions };
26
26
 
27
27
  const DEFAULTS_LAYOUT_OPTIONS: Partial<D3ForceLayoutOptions> = {
28
- centerStrength: 1,
28
+ link: {
29
+ id: (d) => String(d.id),
30
+ },
29
31
 
30
- edgeId: (d) => String(d.id),
31
- linkDistance: 30,
32
- edgeStrength: undefined,
33
- edgeIterations: 1,
32
+ manyBody: {
33
+ strength: -30,
34
+ },
34
35
 
35
36
  preventOverlap: false,
36
37
  nodeSize: 10,
37
38
  nodeSpacing: 0,
38
- collideStrength: 1,
39
- collideIterations: 1,
40
-
41
- nodeStrength: -30,
42
- distanceMin: undefined,
43
- distanceMax: undefined,
44
- theta: undefined,
45
39
 
46
- alpha: 1,
47
- alphaMin: 0.001,
48
- alphaDecay: 1 - Math.pow(0.001, 1 / 300),
49
- alphaTarget: 0,
50
- velocityDecay: 0.4,
40
+ x: false,
41
+ y: false,
51
42
 
52
43
  clustering: false,
53
44
  clusterNodeStrength: -1,
@@ -84,7 +75,7 @@ export class D3ForceLayout<
84
75
  return DEFAULTS_LAYOUT_OPTIONS as T;
85
76
  }
86
77
 
87
- protected mergeOptions(base: T, patch?: Partial<T>): T {
78
+ protected mergeOptions<T>(base: T, patch?: Partial<T>): T {
88
79
  return deepMix({}, base, patch) as T;
89
80
  }
90
81
 
@@ -191,12 +182,6 @@ export class D3ForceLayout<
191
182
 
192
183
  protected parseOptions(options: Partial<T>): T {
193
184
  const _ = options;
194
- // process nodeSize
195
- if (_.collide && _.collide?.radius === undefined) {
196
- _.collide = _.collide || {};
197
- // @ts-ignore
198
- _.collide.radius = _.nodeSize ?? 10;
199
- }
200
185
  // process iterations
201
186
  if (_.iterations === undefined) {
202
187
  if (_.link && _.link.iterations === undefined) {
@@ -301,22 +286,17 @@ export class D3ForceLayout<
301
286
  }
302
287
 
303
288
  private getCenterOptions(options: T): T['center'] | undefined {
304
- if (
305
- !options.width ||
306
- !options.height ||
307
- options.centerStrength !== undefined
308
- ) {
309
- const viewport = normalizeViewport({
310
- width: options.width,
311
- height: options.height,
312
- });
313
- return assignDefined({}, options.center || {}, {
314
- x: viewport.width / 2,
315
- y: viewport.height / 2,
316
- strength: options.centerStrength,
317
- }) as T['center'];
318
- }
319
- return undefined;
289
+ if (options.center === false) return undefined;
290
+
291
+ const viewport = normalizeViewport({
292
+ width: options.width,
293
+ height: options.height,
294
+ });
295
+ return assignDefined({}, options.center || {}, {
296
+ x: viewport.width / 2,
297
+ y: viewport.height / 2,
298
+ strength: options.centerStrength,
299
+ }) as T['center'];
320
300
  }
321
301
 
322
302
  protected setupCenterForce(simulation: Simulation<N, E>, options: T) {
@@ -342,21 +322,14 @@ export class D3ForceLayout<
342
322
  }
343
323
 
344
324
  private getManyBodyOptions(options: T): D3ForceLayoutOptions['manyBody'] {
345
- if (
346
- options.manyBody !== undefined ||
347
- options.nodeStrength !== undefined ||
348
- options.distanceMin !== undefined ||
349
- options.distanceMax !== undefined ||
350
- options.theta !== undefined
351
- ) {
352
- return assignDefined({}, options.manyBody || {}, {
353
- strength: options.nodeStrength,
354
- distanceMin: options.distanceMin,
355
- distanceMax: options.distanceMax,
356
- theta: options.theta,
357
- });
358
- }
359
- return undefined;
325
+ if (options.manyBody === false) return undefined;
326
+
327
+ return assignDefined({}, options.manyBody || {}, {
328
+ strength: options.nodeStrength,
329
+ distanceMin: options.distanceMin,
330
+ distanceMax: options.distanceMax,
331
+ theta: options.theta,
332
+ });
360
333
  }
361
334
 
362
335
  protected setupManyBodyForce(simulation: Simulation<N, E>, options: T) {
@@ -386,21 +359,14 @@ export class D3ForceLayout<
386
359
  }
387
360
 
388
361
  private getLinkOptions(options: T): D3ForceLayoutOptions['link'] {
389
- if (
390
- options.link ||
391
- options.edgeId !== undefined ||
392
- options.linkDistance !== undefined ||
393
- options.edgeStrength !== undefined ||
394
- options.edgeIterations !== undefined
395
- ) {
396
- return assignDefined({}, options.link || {}, {
397
- id: options.edgeId,
398
- distance: options.linkDistance,
399
- strength: options.edgeStrength,
400
- iterations: options.edgeIterations,
401
- });
402
- }
403
- return undefined;
362
+ if (options.link === false) return undefined;
363
+
364
+ return assignDefined({}, options.link || {}, {
365
+ id: options.edgeId,
366
+ distance: options.linkDistance,
367
+ strength: options.edgeStrength,
368
+ iterations: options.edgeIterations,
369
+ });
404
370
  }
405
371
 
406
372
  protected setupLinkForce(simulation: Simulation<N, E>, options: T) {
@@ -429,30 +395,26 @@ export class D3ForceLayout<
429
395
  }
430
396
 
431
397
  private getCollisionOptions(options: T): D3ForceLayoutOptions['collide'] {
432
- if (!options.preventOverlap) return undefined;
433
398
  if (
434
- options.collide !== undefined ||
435
- options.nodeSize !== undefined ||
436
- options.nodeSpacing !== undefined ||
437
- options.collideStrength !== undefined ||
438
- options.collideIterations !== undefined
439
- ) {
440
- const radius =
441
- options.nodeSize || options.nodeSpacing
442
- ? (d: NodeDatum) =>
443
- formatNodeSizeFn(
444
- options.nodeSize,
445
- options.nodeSpacing,
446
- )(d._original) / 2
447
- : undefined;
448
-
449
- return assignDefined({}, options.collide || {}, {
450
- radius,
451
- strength: options.collideStrength,
452
- iterations: options.collideIterations,
453
- });
454
- }
455
- return undefined;
399
+ options.preventOverlap === false &&
400
+ (options.collide === false || options.collide === undefined)
401
+ )
402
+ return undefined;
403
+
404
+ const radius =
405
+ options.nodeSize || options.nodeSpacing
406
+ ? (d: NodeDatum) =>
407
+ formatNodeSizeFn(
408
+ options.nodeSize,
409
+ options.nodeSpacing,
410
+ )(d._original) / 2
411
+ : undefined;
412
+
413
+ return assignDefined({}, options.collide || {}, {
414
+ radius: (options.collide && options.collide.radius) || radius,
415
+ strength: options.collideStrength,
416
+ iterations: options.collideIterations,
417
+ });
456
418
  }
457
419
 
458
420
  protected setupCollisionForce(simulation: Simulation<N, E>, options: T) {
@@ -479,20 +441,13 @@ export class D3ForceLayout<
479
441
  }
480
442
 
481
443
  private getXForceOptions(options: T): D3ForceLayoutOptions['x'] {
482
- if (
483
- options.x !== undefined ||
484
- options.forceXPosition !== undefined ||
485
- options.forceXStrength !== undefined ||
486
- options.width !== undefined ||
487
- options.height !== undefined
488
- ) {
489
- const center = this.getCenterOptions(options);
490
- return assignDefined({}, options.x || {}, {
491
- x: options.forceXPosition ?? (center && center.x),
492
- strength: options.forceXStrength,
493
- });
494
- }
495
- return undefined;
444
+ if (options.x === false) return undefined;
445
+
446
+ const center = this.getCenterOptions(options);
447
+ return assignDefined({}, options.x || {}, {
448
+ x: options.forceXPosition ?? (center && center.x),
449
+ strength: options.forceXStrength,
450
+ });
496
451
  }
497
452
 
498
453
  protected setupXForce(simulation: Simulation<N, E>, options: T) {
@@ -516,21 +471,13 @@ export class D3ForceLayout<
516
471
  }
517
472
 
518
473
  private getYForceOptions(options: T): D3ForceLayoutOptions['y'] {
519
- if (
520
- options.y !== undefined ||
521
- options.forceYPosition !== undefined ||
522
- options.forceYStrength !== undefined ||
523
- options.width !== undefined ||
524
- options.height !== undefined
525
- ) {
526
- const center = this.getCenterOptions(options);
527
- return assignDefined({}, options.y || {}, {
528
- y: options.forceYPosition ?? (center && center.y),
529
- strength: options.forceYStrength,
530
- });
531
- }
474
+ if (options.y === false) return undefined;
532
475
 
533
- return undefined;
476
+ const center = this.getCenterOptions(options);
477
+ return assignDefined({}, options.y || {}, {
478
+ y: options.forceYPosition ?? (center && center.y),
479
+ strength: options.forceYStrength,
480
+ });
534
481
  }
535
482
 
536
483
  protected setupYForce(simulation: Simulation<N, E>, options: T) {
@@ -48,7 +48,3 @@ export function assignDefined<T extends object>(
48
48
  });
49
49
  return target;
50
50
  }
51
-
52
- export function mergeOptions<T extends object>(base: T, patch?: Partial<T>): T {
53
- return Object.assign({}, base, patch || {});
54
- }
package/src/util/order.ts CHANGED
@@ -24,10 +24,14 @@ function sort<N extends NodeData = NodeData>(
24
24
 
25
25
  export function orderByDegree<N extends NodeData = NodeData>(
26
26
  model: GraphLib<N>,
27
+ order: 'asc' | 'desc' = 'desc',
27
28
  ): GraphLib<N> {
28
29
  return sort(model, (nodeA, nodeB) => {
29
30
  const degreeA = model.degree(nodeA.id);
30
31
  const degreeB = model.degree(nodeB.id);
32
+ if(order === 'asc') {
33
+ return degreeA - degreeB; // ascending order
34
+ }
31
35
  return degreeB - degreeA; // descending order
32
36
  });
33
37
  }