@antv/layout 2.0.0-alpha.4 → 2.0.0-beta.1

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 (111) hide show
  1. package/README.md +85 -23
  2. package/dist/index.js +296 -209
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.min.js +2 -2
  5. package/dist/index.min.js.map +1 -1
  6. package/dist/worker.js +1 -1
  7. package/dist/worker.js.map +1 -1
  8. package/lib/algorithm/antv-dagre/index.js +8 -7
  9. package/lib/algorithm/antv-dagre/index.js.map +1 -1
  10. package/lib/algorithm/antv-dagre/types.d.ts +3 -14
  11. package/lib/algorithm/circular/index.js +8 -7
  12. package/lib/algorithm/circular/index.js.map +1 -1
  13. package/lib/algorithm/circular/types.d.ts +0 -16
  14. package/lib/algorithm/combo-combined/index.js +9 -7
  15. package/lib/algorithm/combo-combined/index.js.map +1 -1
  16. package/lib/algorithm/combo-combined/types.d.ts +6 -18
  17. package/lib/algorithm/concentric/index.js +10 -16
  18. package/lib/algorithm/concentric/index.js.map +1 -1
  19. package/lib/algorithm/concentric/types.d.ts +3 -17
  20. package/lib/algorithm/d3-force/index.js +16 -12
  21. package/lib/algorithm/d3-force/index.js.map +1 -1
  22. package/lib/algorithm/d3-force/types.d.ts +10 -24
  23. package/lib/algorithm/d3-force-3d/index.js +1 -1
  24. package/lib/algorithm/d3-force-3d/index.js.map +1 -1
  25. package/lib/algorithm/d3-force-3d/types.d.ts +5 -4
  26. package/lib/algorithm/dagre/index.js +10 -7
  27. package/lib/algorithm/dagre/index.js.map +1 -1
  28. package/lib/algorithm/dagre/types.d.ts +8 -14
  29. package/lib/algorithm/force/index.d.ts +1 -1
  30. package/lib/algorithm/force/index.js +56 -44
  31. package/lib/algorithm/force/index.js.map +1 -1
  32. package/lib/algorithm/force/types.d.ts +39 -36
  33. package/lib/algorithm/force-atlas2/index.d.ts +1 -1
  34. package/lib/algorithm/force-atlas2/index.js +8 -7
  35. package/lib/algorithm/force-atlas2/index.js.map +1 -1
  36. package/lib/algorithm/force-atlas2/types.d.ts +0 -14
  37. package/lib/algorithm/fruchterman/index.d.ts +1 -1
  38. package/lib/algorithm/fruchterman/index.js +8 -10
  39. package/lib/algorithm/fruchterman/index.js.map +1 -1
  40. package/lib/algorithm/fruchterman/simulation.js +2 -1
  41. package/lib/algorithm/fruchterman/simulation.js.map +1 -1
  42. package/lib/algorithm/fruchterman/types.d.ts +2 -1
  43. package/lib/algorithm/grid/index.d.ts +1 -1
  44. package/lib/algorithm/grid/index.js +29 -34
  45. package/lib/algorithm/grid/index.js.map +1 -1
  46. package/lib/algorithm/grid/types.d.ts +5 -24
  47. package/lib/algorithm/mds/index.js +1 -0
  48. package/lib/algorithm/mds/index.js.map +1 -1
  49. package/lib/algorithm/radial/index.js +8 -5
  50. package/lib/algorithm/radial/index.js.map +1 -1
  51. package/lib/algorithm/radial/radial-nonoverlap-force.js +2 -4
  52. package/lib/algorithm/radial/radial-nonoverlap-force.js.map +1 -1
  53. package/lib/algorithm/radial/types.d.ts +3 -16
  54. package/lib/algorithm/random/index.js +1 -0
  55. package/lib/algorithm/random/index.js.map +1 -1
  56. package/lib/algorithm/types.d.ts +16 -0
  57. package/lib/index.d.ts +5 -3
  58. package/lib/index.js +3 -1
  59. package/lib/index.js.map +1 -1
  60. package/lib/node_modules/@antv/expr/dist/index.esm.js +4 -0
  61. package/lib/node_modules/@antv/expr/dist/index.esm.js.map +1 -0
  62. package/lib/types/common.d.ts +17 -1
  63. package/lib/types/data.d.ts +1 -1
  64. package/lib/util/expr.d.ts +12 -0
  65. package/lib/util/expr.js +26 -0
  66. package/lib/util/expr.js.map +1 -0
  67. package/lib/util/format.d.ts +37 -0
  68. package/lib/util/format.js +61 -17
  69. package/lib/util/format.js.map +1 -1
  70. package/lib/util/order.d.ts +3 -4
  71. package/lib/util/order.js.map +1 -1
  72. package/lib/util/size.d.ts +2 -1
  73. package/lib/util/size.js +9 -1
  74. package/lib/util/size.js.map +1 -1
  75. package/lib/worker.js +283 -227
  76. package/lib/worker.js.map +1 -1
  77. package/package.json +6 -3
  78. package/src/algorithm/antv-dagre/index.ts +15 -12
  79. package/src/algorithm/antv-dagre/types.ts +3 -14
  80. package/src/algorithm/circular/index.ts +5 -4
  81. package/src/algorithm/circular/types.ts +0 -15
  82. package/src/algorithm/combo-combined/index.ts +21 -17
  83. package/src/algorithm/combo-combined/types.ts +6 -21
  84. package/src/algorithm/concentric/index.ts +18 -16
  85. package/src/algorithm/concentric/types.ts +2 -16
  86. package/src/algorithm/d3-force/index.ts +25 -18
  87. package/src/algorithm/d3-force/types.ts +9 -22
  88. package/src/algorithm/d3-force-3d/index.ts +1 -1
  89. package/src/algorithm/d3-force-3d/types.ts +5 -0
  90. package/src/algorithm/dagre/index.ts +9 -7
  91. package/src/algorithm/dagre/types.ts +7 -15
  92. package/src/algorithm/force/index.ts +64 -40
  93. package/src/algorithm/force/types.ts +76 -45
  94. package/src/algorithm/force-atlas2/index.ts +13 -15
  95. package/src/algorithm/force-atlas2/types.ts +0 -12
  96. package/src/algorithm/fruchterman/index.ts +7 -15
  97. package/src/algorithm/fruchterman/simulation.ts +2 -2
  98. package/src/algorithm/fruchterman/types.ts +5 -2
  99. package/src/algorithm/grid/index.ts +45 -46
  100. package/src/algorithm/grid/types.ts +6 -35
  101. package/src/algorithm/radial/index.ts +14 -6
  102. package/src/algorithm/radial/radial-nonoverlap-force.ts +11 -6
  103. package/src/algorithm/radial/types.ts +2 -15
  104. package/src/algorithm/types.ts +18 -0
  105. package/src/types/common.ts +22 -0
  106. package/src/types/data.ts +1 -1
  107. package/src/util/expr.ts +26 -0
  108. package/src/util/format.ts +71 -27
  109. package/src/util/index.ts +2 -0
  110. package/src/util/order.ts +3 -9
  111. package/src/util/size.ts +8 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antv/layout",
3
- "version": "2.0.0-alpha.4",
3
+ "version": "2.0.0-beta.1",
4
4
  "description": "graph layout algorithm",
5
5
  "main": "dist/index.min.js",
6
6
  "module": "lib/index.js",
@@ -35,6 +35,7 @@
35
35
  ],
36
36
  "dependencies": {
37
37
  "@antv/event-emitter": "^0.1.3",
38
+ "@antv/expr": "^1.0.2",
38
39
  "@antv/graphlib": "^2.0.0",
39
40
  "@antv/util": "^3.3.2",
40
41
  "comlink": "^4.4.1",
@@ -66,6 +67,7 @@
66
67
  "eslint-config-airbnb-base": "^14.2.1",
67
68
  "eslint-config-prettier": "^6.15.0",
68
69
  "eslint-plugin-import": "^2.29.1",
70
+ "gh-pages": "^6.3.0",
69
71
  "husky": "^7.0.4",
70
72
  "interactjs": "^1.10.27",
71
73
  "jest": "^30.2.0",
@@ -101,11 +103,12 @@
101
103
  "size": "limit-size",
102
104
  "test:coverage:open": "open-cli coverage/lcov-report/index.html",
103
105
  "test:coverage": "jest --coverage",
104
- "test": "jest"
106
+ "test": "jest",
107
+ "deploy": "npm --prefix site run build && gh-pages -d site/doc_build"
105
108
  },
106
109
  "publishConfig": {
107
110
  "access": "public",
108
- "tag": "alpha",
111
+ "tag": "beta",
109
112
  "registry": "https://registry.npmjs.org/"
110
113
  },
111
114
  "limit-size": [
@@ -1,8 +1,7 @@
1
1
  import { isNumber } from '@antv/util';
2
2
  import type { NodeData, PointObject } from '../../types';
3
3
  import { parsePoint } from '../../util';
4
- import { formatNumberFn, formatSizeFn } from '../../util/format';
5
- import { parseSize } from '../../util/size';
4
+ import { formatNodeSizeFn, formatNumberFn } from '../../util/format';
6
5
  import { BaseLayout } from '../base-layout';
7
6
  import { DagreGraph, GraphNode } from './graph';
8
7
  import { layout } from './layout';
@@ -12,6 +11,7 @@ export type { AntVDagreLayoutOptions };
12
11
 
13
12
  const DEFAULTS_LAYOUT_OPTIONS: Partial<AntVDagreLayoutOptions> = {
14
13
  nodeSize: 10,
14
+ nodeSpacing: 0,
15
15
  rankdir: 'TB',
16
16
  nodesep: 50, // 节点水平间距(px)
17
17
  ranksep: 50, // 每一层节点之间间距
@@ -37,6 +37,7 @@ export class AntVDagreLayout extends BaseLayout<AntVDagreLayoutOptions> {
37
37
  protected async layout(options: AntVDagreLayoutOptions): Promise<void> {
38
38
  const {
39
39
  nodeSize,
40
+ nodeSpacing,
40
41
  align,
41
42
  rankdir = 'TB',
42
43
  ranksep,
@@ -54,21 +55,16 @@ export class AntVDagreLayout extends BaseLayout<AntVDagreLayoutOptions> {
54
55
  nodesepFunc,
55
56
  } = options;
56
57
 
57
- const ranksepfunc = formatNumberFn(ranksepFunc, ranksep ?? 50);
58
- const nodesepfunc = formatNumberFn(nodesepFunc, nodesep ?? 50);
59
- let horisep: (d?: NodeData | undefined) => number = nodesepfunc;
60
- let vertisep: (d?: NodeData | undefined) => number = ranksepfunc;
58
+ const ranksepfunc = formatNumberFn(ranksepFunc, ranksep ?? 50, 'node');
59
+ const nodesepfunc = formatNumberFn(nodesepFunc, nodesep ?? 50, 'node');
60
+ let horisep: (node: NodeData) => number = nodesepfunc;
61
+ let vertisep: (node: NodeData) => number = ranksepfunc;
61
62
 
62
63
  if (rankdir === 'LR' || rankdir === 'RL') {
63
64
  horisep = ranksepfunc;
64
65
  vertisep = nodesepfunc;
65
66
  }
66
67
 
67
- const nodeSizeFunc = formatSizeFn(
68
- nodeSize,
69
- DEFAULTS_LAYOUT_OPTIONS.nodeSize as number,
70
- );
71
-
72
68
  // Create internal graph
73
69
  const g = new DagreGraph<NodeData, any>({ tree: [] });
74
70
 
@@ -76,9 +72,16 @@ export class AntVDagreLayout extends BaseLayout<AntVDagreLayoutOptions> {
76
72
  const nodes = this.model.nodes();
77
73
  const edges = this.model.edges();
78
74
 
75
+ const sizeFn = formatNodeSizeFn(
76
+ nodeSize,
77
+ nodeSpacing,
78
+ DEFAULTS_LAYOUT_OPTIONS.nodeSize as number,
79
+ DEFAULTS_LAYOUT_OPTIONS.nodeSpacing as number,
80
+ );
81
+
79
82
  nodes.forEach((node) => {
80
83
  const raw = node._original;
81
- const size = parseSize(nodeSizeFunc(raw));
84
+ const size = sizeFn(raw);
82
85
  const verti = vertisep(raw);
83
86
  const hori = horisep(raw);
84
87
  const width = size[0] + 2 * hori;
@@ -1,4 +1,4 @@
1
- import { ID, NodeData, Point, Size } from '../../types';
1
+ import type { Expr, ID, NodeData, Point } from '../../types';
2
2
  import { BaseLayoutOptions } from '../base-layout';
3
3
 
4
4
  export type DagreRankdir =
@@ -62,17 +62,6 @@ export interface AntVDagreLayoutOptions extends BaseLayoutOptions {
62
62
  * @defaultValue undefined
63
63
  */
64
64
  begin?: Point;
65
- /**
66
- * <zh/> 节点大小(直径)。
67
- *
68
- * <en/> The diameter of the node
69
- * @remarks
70
- * <zh/> 用于防止节点重叠时的碰撞检测
71
- *
72
- * <en/> Used for collision detection when nodes overlap
73
- * @defaultValue undefined
74
- */
75
- nodeSize?: Size | ((d?: NodeData) => Size);
76
65
  /**
77
66
  * <zh/> 节点间距(px)
78
67
  *
@@ -105,7 +94,7 @@ export interface AntVDagreLayoutOptions extends BaseLayoutOptions {
105
94
  * <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
95
  * @param d - <zh/> 节点实例 | <en/> Node instance
107
96
  */
108
- nodesepFunc?: (d?: NodeData) => number;
97
+ nodesepFunc?: Expr | ((node: NodeData) => number);
109
98
  /**
110
99
  * <zh/> 层间距(px)的回调函数
111
100
  *
@@ -116,7 +105,7 @@ export interface AntVDagreLayoutOptions extends BaseLayoutOptions {
116
105
  * <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
106
  * @param d - <zh/> 节点实例 | <en/> Node instance
118
107
  */
119
- ranksepFunc?: (d?: NodeData) => number;
108
+ ranksepFunc?: Expr | ((node: NodeData) => number);
120
109
  /**
121
110
  * <zh/> 是否同时计算边上的的控制点位置
122
111
  *
@@ -1,4 +1,3 @@
1
- import { isNil } from '@antv/util';
2
1
  import { normalizeViewport, orderByDegree, orderByTopology } from '../../util';
3
2
  import { applySingleNodeLayout } from '../../util/common';
4
3
  import { formatNodeSizeFn } from '../../util/format';
@@ -18,6 +17,7 @@ const DEFAULT_LAYOUT_OPTIONS: CircularLayoutOptions = {
18
17
  ordering: null,
19
18
  angleRatio: 1,
20
19
  nodeSize: 10,
20
+ nodeSpacing: 0,
21
21
  };
22
22
 
23
23
  /**
@@ -67,16 +67,17 @@ export class CircularLayout extends BaseLayout<CircularLayoutOptions> {
67
67
  let { radius, startRadius, endRadius } = this.options;
68
68
 
69
69
  const nodes = this.model.nodes();
70
- const format = formatNodeSizeFn(
70
+ const sizeFn = formatNodeSizeFn(
71
71
  nodeSize,
72
72
  nodeSpacing,
73
73
  DEFAULT_LAYOUT_OPTIONS.nodeSize as number,
74
+ DEFAULT_LAYOUT_OPTIONS.nodeSpacing as number,
74
75
  );
75
76
 
76
- if (!isNil(nodeSpacing)) {
77
+ if (nodeSpacing) {
77
78
  let perimeter = 0;
78
79
  for (const node of nodes) {
79
- perimeter += format(node._original);
80
+ perimeter += Math.max(...sizeFn(node._original));
80
81
  }
81
82
  radius = perimeter / (2 * Math.PI);
82
83
  } else if (!radius && !startRadius && !endRadius) {
@@ -1,5 +1,4 @@
1
1
  import type { BaseLayoutOptions } from '../types';
2
- import type { NodeData, Size } from '../../types';
3
2
 
4
3
  /**
5
4
  * <zh/> 环形 Circular 布局配置
@@ -85,20 +84,6 @@ export interface CircularLayoutOptions extends BaseLayoutOptions {
85
84
  * @defaultValue 2 * Math.PI
86
85
  */
87
86
  endAngle?: number;
88
- /**
89
- * <zh/> 环与环之间最小间距,用于调整半径
90
- *
91
- * <en/> Minimum spacing between rings, used to adjust the radius
92
- * @defaultValue 0
93
- */
94
- nodeSpacing?: number | ((d?: NodeData) => number);
95
- /**
96
- * <zh/> 节点大小(直径)。用于防止节点重叠时的碰撞检测
97
- *
98
- * <en/> Node size (diameter). Used for collision detection when nodes overlap
99
- * @defaultValue 10
100
- */
101
- nodeSize?: Size | ((d?: NodeData) => Size);
102
87
  }
103
88
 
104
89
  export interface ParsedCircularLayoutOptions
@@ -1,14 +1,7 @@
1
1
  import { registry } from '../../registry';
2
- import type {
3
- GraphData,
4
- ID,
5
- LayoutNode,
6
- NodeData,
7
- Point,
8
- STDSize,
9
- } from '../../types';
10
- import { normalizeViewport, parseSize } from '../../util';
11
- import { formatNodeSizeFn, formatNumberFn } from '../../util/format';
2
+ import type { GraphData, ID, LayoutNode, Point, STDSize } from '../../types';
3
+ import { normalizeViewport } from '../../util';
4
+ import { formatFn, formatNodeSizeFn, formatNumberFn } from '../../util/format';
12
5
  import { BaseLayout, isLayoutWithIterations } from '../base-layout';
13
6
  import type { Layout } from '../types';
14
7
  import type {
@@ -205,7 +198,10 @@ export class ComboCombinedLayout extends BaseLayout<ComboCombinedLayoutOptions>
205
198
  }
206
199
 
207
200
  private getLayoutConfig(combo: HierarchyNode) {
208
- const { layout } = this.options;
201
+ const layout =
202
+ typeof this.options.layout === 'object'
203
+ ? this.options.layout
204
+ : formatFn(this.options.layout, ['comboId']);
209
205
 
210
206
  if (typeof layout === 'function') {
211
207
  const comboId = combo.id === ROOT_ID ? null : combo.id!;
@@ -219,7 +215,7 @@ export class ComboCombinedLayout extends BaseLayout<ComboCombinedLayoutOptions>
219
215
  const base = {
220
216
  type: 'concentric',
221
217
  ...normalizeViewport(this.options),
222
- nodeSize: (d: NodeData) => d.size,
218
+ nodeSize: 'node.size',
223
219
  nodeSpacing: 0,
224
220
  };
225
221
 
@@ -334,8 +330,12 @@ export class ComboCombinedLayout extends BaseLayout<ComboCombinedLayoutOptions>
334
330
  return { center: [0, 0], width: 0, height: 0 };
335
331
  }
336
332
 
337
- const comboPaddingFn = formatNumberFn(this.options.comboPadding, 20);
338
- const padding = comboPaddingFn(combo._original);
333
+ const comboPaddingFn = formatNumberFn(
334
+ this.options.comboPadding,
335
+ 20,
336
+ 'combo',
337
+ );
338
+ const padding = comboPaddingFn(combo._original!);
339
339
 
340
340
  return {
341
341
  center: [(minX + maxX) / 2, (minY + maxY) / 2],
@@ -359,15 +359,19 @@ export class ComboCombinedLayout extends BaseLayout<ComboCombinedLayoutOptions>
359
359
  ): STDSize {
360
360
  const { nodeSize, nodeSpacing } = this.options;
361
361
  const sizeFn = formatNodeSizeFn(nodeSize, includeSpacing ? nodeSpacing : 0);
362
- return parseSize(sizeFn(node._original));
362
+ return sizeFn(node._original!);
363
363
  }
364
364
 
365
365
  private getComboSize(
366
366
  combo: HierarchyNode,
367
367
  includeSpacing: boolean = true,
368
368
  ): STDSize {
369
- const comboSpacingFn = formatNumberFn(this.options.comboSpacing, 0);
370
- const spacing = includeSpacing ? comboSpacingFn(combo._original) : 0;
369
+ const comboSpacingFn = formatNumberFn(
370
+ this.options.comboSpacing,
371
+ 0,
372
+ 'combo',
373
+ );
374
+ const spacing = includeSpacing ? comboSpacingFn(combo._original!) : 0;
371
375
  const [width, height] = combo.size as STDSize;
372
376
  return [width + spacing / 2, height + spacing / 2, 0];
373
377
  }
@@ -1,9 +1,7 @@
1
+ import type { Expr, ID, NodeData } from '../../types';
1
2
  import type { BaseLayoutOptions } from '../types';
2
- import type { ID, NodeData, Size } from '../../types';
3
3
 
4
- export type ComboCombinedLayoutConfig =
5
- | string
6
- | { type: string; [key: string]: any };
4
+ export type ComboCombinedLayoutConfig = { type: string; [key: string]: any };
7
5
 
8
6
  export interface ComboCombinedLayoutOptions extends BaseLayoutOptions {
9
7
  /**
@@ -11,29 +9,16 @@ export interface ComboCombinedLayoutOptions extends BaseLayoutOptions {
11
9
  */
12
10
  layout?:
13
11
  | ComboCombinedLayoutConfig
14
- | ((comboId: ID | null) => ComboCombinedLayoutConfig);
15
-
16
- /**
17
- * <zh/> 节点尺寸
18
- *
19
- * <en/> Node size
20
- */
21
- nodeSize?: Size | ((node?: NodeData) => Size);
22
-
23
- /**
24
- * <zh/> 节点间距
25
- *
26
- * <en/> Node spacing
27
- */
28
- nodeSpacing?: number | ((node?: NodeData) => number);
12
+ | ((comboId: ID | null) => ComboCombinedLayoutConfig)
13
+ | Expr;
29
14
 
30
15
  /**
31
16
  * Combo 之间的间距
32
17
  */
33
- comboSpacing?: number | ((combo?: NodeData) => number);
18
+ comboSpacing?: number | ((combo: NodeData) => number) | Expr;
34
19
 
35
20
  /**
36
21
  * Combo 内部的边距
37
22
  */
38
- comboPadding?: number | ((combo?: NodeData) => number);
23
+ comboPadding?: number | ((combo: NodeData) => number) | Expr;
39
24
  }
@@ -1,4 +1,3 @@
1
- import { BaseLayout } from '../base-layout';
2
1
  import type { LayoutNode, NodeData } from '../../types';
3
2
  import {
4
3
  applySingleNodeLayout,
@@ -6,7 +5,8 @@ import {
6
5
  orderByDegree,
7
6
  orderBySorter,
8
7
  } from '../../util';
9
- import { formatNodeSizeFn } from '../../util/format';
8
+ import { formatFn, formatNodeSizeFn } from '../../util/format';
9
+ import { BaseLayout } from '../base-layout';
10
10
  import type { ConcentricLayoutOptions } from './types';
11
11
 
12
12
  export type { ConcentricLayoutOptions };
@@ -52,25 +52,21 @@ export class ConcentricLayout extends BaseLayout<ConcentricLayoutOptions> {
52
52
  equidistant,
53
53
  preventOverlap,
54
54
  startAngle = DEFAULTS_LAYOUT_OPTIONS.startAngle,
55
- nodeSize = DEFAULTS_LAYOUT_OPTIONS.nodeSize,
55
+ nodeSize,
56
56
  nodeSpacing,
57
57
  } = this.options;
58
58
 
59
- let sortBy: ConcentricLayoutOptions['sortBy'] = propsSortBy;
60
- if (propsSortBy && typeof propsSortBy === 'function') {
61
- const testNode = this.model.firstNode()!;
62
- const testValue = propsSortBy(testNode._original);
63
- if (typeof testValue !== 'number') sortBy = 'degree';
64
- } else {
65
- sortBy = 'degree';
66
- }
59
+ const sortBy =
60
+ !propsSortBy || propsSortBy === 'degree'
61
+ ? ('degree' as const)
62
+ : (formatFn(propsSortBy, ['node']) as (node: NodeData) => number);
67
63
 
68
64
  if (sortBy === 'degree') {
69
65
  orderByDegree(this.model);
70
66
  } else {
71
67
  const sorter = (nodeA: NodeData, nodeB: NodeData) => {
72
- const a = (sortBy as (node: NodeData) => number)(nodeA);
73
- const b = (sortBy as (node: NodeData) => number)(nodeB);
68
+ const a = sortBy(nodeA);
69
+ const b = sortBy(nodeB);
74
70
  return a === b ? 0 : a > b ? -1 : 1;
75
71
  };
76
72
  orderBySorter(this.model, sorter);
@@ -83,17 +79,23 @@ export class ConcentricLayout extends BaseLayout<ConcentricLayoutOptions> {
83
79
  const v =
84
80
  sortBy === 'degree'
85
81
  ? this.model.degree(node.id)
86
- : sortBy?.(node._original);
82
+ : sortBy(node._original);
87
83
  sortKeys.set(node.id, v);
88
84
  }
89
85
 
90
86
  const maxValueNode = this.model.firstNode()!;
91
87
  const maxLevelDiff = propsMaxLevelDiff || sortKeys.get(maxValueNode.id) / 4;
92
88
 
93
- const nodeSizeFn = formatNodeSizeFn(nodeSize, nodeSpacing);
89
+ const sizeFn = formatNodeSizeFn(
90
+ nodeSize,
91
+ nodeSpacing,
92
+ DEFAULTS_LAYOUT_OPTIONS.nodeSize as number,
93
+ DEFAULTS_LAYOUT_OPTIONS.nodeSpacing as number,
94
+ );
95
+
94
96
  const nodeDistances = new Map<LayoutNode['id'], number>();
95
97
  for (const node of nodes) {
96
- nodeDistances.set(node.id, nodeSizeFn(node._original));
98
+ nodeDistances.set(node.id, Math.max(...sizeFn(node._original)));
97
99
  }
98
100
 
99
101
  // put the values into levels
@@ -1,5 +1,5 @@
1
+ import type { Expr, NodeData } from '../../types';
1
2
  import type { BaseLayoutOptions } from '../types';
2
- import type { NodeData, Size } from '../../types';
3
3
 
4
4
  /**
5
5
  * <zh/> Concentric 同心圆布局配置
@@ -18,20 +18,6 @@ export interface ConcentricLayoutOptions extends BaseLayoutOptions {
18
18
  * @defaultValue false
19
19
  */
20
20
  preventOverlap?: boolean;
21
- /**
22
- * <zh/> 节点大小(直径)。用于防止节点重叠时的碰撞检测
23
- *
24
- * <en/> Node size (diameter). Used for collision detection when preventing node overlap
25
- * @defaultValue 30
26
- */
27
- nodeSize?: Size | ((d?: NodeData) => Size);
28
- /**
29
- * <zh/> 环与环之间最小间距,用于调整半径
30
- *
31
- * <en/> Minimum spacing between rings, used to adjust the radius
32
- * @defaultValue 10
33
- */
34
- nodeSpacing?: number | ((d?: NodeData) => number);
35
21
  /**
36
22
  * <zh/> 第一个节点与最后一个节点之间的弧度差
37
23
  *
@@ -85,5 +71,5 @@ export interface ConcentricLayoutOptions extends BaseLayoutOptions {
85
71
  * - ((node) => ...): Custom sorting function, returns a number, the higher the value, the more the node will be placed in the center
86
72
  * @defaultValue degree
87
73
  */
88
- sortBy?: 'degree' | ((d?: NodeData) => number);
74
+ sortBy?: 'degree' | Expr | ((node: NodeData) => number);
89
75
  }
@@ -12,7 +12,7 @@ import {
12
12
  } from 'd3-force';
13
13
  import type { ID, Position } from '../../types';
14
14
  import { assignDefined, normalizeViewport } from '../../util';
15
- import { formatNodeSizeFn } from '../../util/format';
15
+ import { formatFn, formatNodeSizeFn } from '../../util/format';
16
16
  import { BaseLayoutWithIterations } from '../base-layout';
17
17
  import forceInABox from './force-in-a-box';
18
18
  import type {
@@ -25,9 +25,7 @@ import type {
25
25
  export type { D3ForceLayoutOptions };
26
26
 
27
27
  const DEFAULTS_LAYOUT_OPTIONS: Partial<D3ForceLayoutOptions> = {
28
- link: {
29
- id: (d) => String(d.id),
30
- },
28
+ edgeId: 'edge.id',
31
29
 
32
30
  manyBody: {
33
31
  strength: -30,
@@ -325,7 +323,9 @@ export class D3ForceLayout<
325
323
  if (options.manyBody === false) return undefined;
326
324
 
327
325
  return assignDefined({}, options.manyBody || {}, {
328
- strength: options.nodeStrength,
326
+ strength: options.nodeStrength
327
+ ? formatFn(options.nodeStrength, ['node'])
328
+ : undefined,
329
329
  distanceMin: options.distanceMin,
330
330
  distanceMax: options.distanceMax,
331
331
  theta: options.theta,
@@ -362,9 +362,13 @@ export class D3ForceLayout<
362
362
  if (options.link === false) return undefined;
363
363
 
364
364
  return assignDefined({}, options.link || {}, {
365
- id: options.edgeId,
366
- distance: options.linkDistance,
367
- strength: options.edgeStrength,
365
+ id: options.edgeId ? formatFn(options.edgeId, ['edge']) : undefined,
366
+ distance: options.linkDistance
367
+ ? formatFn(options.linkDistance, ['edge'])
368
+ : undefined,
369
+ strength: options.edgeStrength
370
+ ? formatFn(options.edgeStrength, ['edge'])
371
+ : undefined,
368
372
  iterations: options.edgeIterations,
369
373
  });
370
374
  }
@@ -401,14 +405,13 @@ export class D3ForceLayout<
401
405
  )
402
406
  return undefined;
403
407
 
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;
408
+ const sizeFn = formatNodeSizeFn(
409
+ options.nodeSize,
410
+ options.nodeSpacing,
411
+ DEFAULTS_LAYOUT_OPTIONS.nodeSize as number,
412
+ DEFAULTS_LAYOUT_OPTIONS.nodeSpacing as number,
413
+ );
414
+ const radius = (d: NodeDatum) => Math.max(...sizeFn(d._original)) / 2;
412
415
 
413
416
  return assignDefined({}, options.collide || {}, {
414
417
  radius: (options.collide && options.collide.radius) || radius,
@@ -524,7 +527,11 @@ export class D3ForceLayout<
524
527
  if (radial) {
525
528
  let force = simulation.force('radial');
526
529
  if (!force) {
527
- force = forceRadial(radial.radius || 100, radial.x, radial.y);
530
+ force = forceRadial(
531
+ (radial.radius as () => number) || 100,
532
+ radial.x,
533
+ radial.y,
534
+ );
528
535
  simulation.force('radial', force as any);
529
536
  }
530
537
 
@@ -567,7 +574,7 @@ export class D3ForceLayout<
567
574
  ['centerY', center && center.y],
568
575
  ['template', 'force'],
569
576
  ['strength', clusterFociStrength],
570
- ['groupBy', clusterBy],
577
+ ['groupBy', clusterBy ? formatFn(clusterBy, ['node']) : undefined],
571
578
  ['nodes', this.model.nodes()],
572
579
  ['links', this.model.edges()],
573
580
  ['forceLinkDistance', clusterEdgeDistance],
@@ -3,8 +3,8 @@ import type {
3
3
  SimulationLinkDatum,
4
4
  SimulationNodeDatum,
5
5
  } from 'd3-force';
6
+ import type { Expr, LayoutEdge, LayoutNode } from '../../types';
6
7
  import type { BaseLayoutOptions, Layout } from '../types';
7
- import type { LayoutEdge, LayoutNode } from '../../types';
8
8
 
9
9
  export interface D3ForceCommonOptions
10
10
  extends Omit<BaseLayoutOptions, 'center'> {
@@ -42,21 +42,21 @@ export interface D3ForceCommonOptions
42
42
  * <en/> Unique identifier field or function for edges
43
43
  * @defaultValue (edge) => String(edge.id)
44
44
  */
45
- edgeId?: (edge: EdgeDatum) => string;
45
+ edgeId?: Expr | ((edge: EdgeDatum) => string);
46
46
  /**
47
47
  * <zh/> 边的理想长度,可以是数值或根据边数据返回长度的函数
48
48
  *
49
49
  * <en/> Ideal length of edges, can be a number or a function that returns length based on edge data
50
50
  * @defaultValue 50
51
51
  */
52
- linkDistance?: number | ((edge: EdgeDatum) => number);
52
+ linkDistance?: number | Expr | ((edge: EdgeDatum) => number);
53
53
  /**
54
54
  * <zh/> 边的强度,可以是数值或根据边数据返回强度的函数。值范围为 [0, 1]
55
55
  *
56
56
  * <en/> Strength of edges, can be a number or a function that returns strength based on edge data. Value range is [0, 1]
57
57
  * @defaultValue null
58
58
  */
59
- edgeStrength?: number | ((edge: EdgeDatum) => number) | null;
59
+ edgeStrength?: number | Expr | ((edge: EdgeDatum) => number) | null;
60
60
  /**
61
61
  * <zh/> 链接力的迭代次数
62
62
  *
@@ -70,7 +70,7 @@ export interface D3ForceCommonOptions
70
70
  * <en/> Strength of node force, negative for repulsion, positive for attraction
71
71
  * @defaultValue -30
72
72
  */
73
- nodeStrength?: number | ((node: NodeDatum) => number);
73
+ nodeStrength?: number | Expr | ((node: NodeDatum) => number);
74
74
  /**
75
75
  * <zh/> 多体力的近似参数,值范围为 (0, 1]
76
76
  *
@@ -113,21 +113,6 @@ export interface D3ForceCommonOptions
113
113
  * @defaultValue 1
114
114
  */
115
115
  collideIterations?: number;
116
- /**
117
- * <zh/> 节点大小(直径)。用于防止节点重叠时的碰撞检测
118
- *
119
- * <en/> Node size (diameter). Used for collision detection when nodes overlap
120
- *
121
- * @defaultValue 10
122
- */
123
- nodeSize?: number | ((d?: NodeDatum) => number);
124
- /**
125
- * <zh/> 节点之间的最小间距
126
- *
127
- * <en/> Minimum spacing between nodes
128
- * @defaultValue 0
129
- */
130
- nodeSpacing?: number | ((d?: NodeDatum) => number);
131
116
  /**
132
117
  * <zh/> 径向力的理想半径,可以是数值或根据节点数据返回半径的函数
133
118
  *
@@ -169,7 +154,7 @@ export interface D3ForceCommonOptions
169
154
  * <en/> Field or function used for clustering
170
155
  * @defaultValue (d) => d.cluster
171
156
  */
172
- clusterBy?: (d: NodeDatum) => string | number;
157
+ clusterBy?: Expr | ((node: NodeDatum) => string | number);
173
158
  /**
174
159
  * <zh/> 聚类内节点之间的作用力强度
175
160
  *
@@ -257,7 +242,7 @@ export interface D3ForceCommonOptions
257
242
  * <en/> Set the function for generating random numbers
258
243
  * @returns <zh/> 随机数 | <en/> Random number
259
244
  */
260
- randomSource?: () => number;
245
+ randomSource?: Expr | (() => number);
261
246
  /**
262
247
  * <zh/> 碰撞力
263
248
  *
@@ -360,9 +345,11 @@ export interface D3ForceLayoutOptions extends D3ForceCommonOptions {
360
345
  | {
361
346
  strength?:
362
347
  | number
348
+ | Expr
363
349
  | ((node: NodeDatum, index: number, nodes: NodeDatum[]) => number);
364
350
  radius?:
365
351
  | number
352
+ | Expr
366
353
  | ((node: NodeDatum, index: number, nodes: NodeDatum[]) => number);
367
354
  x?: number;
368
355
  y?: number;
@@ -49,7 +49,7 @@ export class D3Force3DLayout extends D3ForceLayout<
49
49
  return {
50
50
  numDimensions: 3,
51
51
  link: {
52
- id: (edge) => String(edge.id),
52
+ id: (edge) => edge.id!,
53
53
  },
54
54
  manyBody: {},
55
55
  center: {
@@ -1,3 +1,4 @@
1
+ import type { Expr } from '../../types';
1
2
  import type {
2
3
  D3ForceCommonOptions,
3
4
  EdgeDatum as _EdgeDatum,
@@ -32,9 +33,11 @@ export interface D3Force3DLayoutOptions extends D3ForceCommonOptions {
32
33
  | {
33
34
  strength?:
34
35
  | number
36
+ | Expr
35
37
  | ((node: NodeDatum, index: number, nodes: NodeDatum[]) => number);
36
38
  radius?:
37
39
  | number
40
+ | Expr
38
41
  | ((node: NodeDatum, index: number, nodes: NodeDatum[]) => number);
39
42
  x?: number;
40
43
  y?: number;
@@ -50,9 +53,11 @@ export interface D3Force3DLayoutOptions extends D3ForceCommonOptions {
50
53
  | {
51
54
  strength?:
52
55
  | number
56
+ | Expr
53
57
  | ((node: NodeDatum, index: number, nodes: NodeDatum[]) => number);
54
58
  z?:
55
59
  | number
60
+ | Expr
56
61
  | ((node: NodeDatum, index: number, nodes: NodeDatum[]) => number);
57
62
  };
58
63
  }