@antv/layout 2.0.0-beta.0 → 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.
- package/dist/index.js +296 -209
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +2 -2
- package/dist/index.min.js.map +1 -1
- package/dist/worker.js +1 -1
- package/dist/worker.js.map +1 -1
- package/lib/algorithm/antv-dagre/index.js +8 -7
- package/lib/algorithm/antv-dagre/index.js.map +1 -1
- package/lib/algorithm/antv-dagre/types.d.ts +3 -14
- package/lib/algorithm/circular/index.js +8 -7
- package/lib/algorithm/circular/index.js.map +1 -1
- package/lib/algorithm/circular/types.d.ts +0 -16
- package/lib/algorithm/combo-combined/index.js +9 -7
- package/lib/algorithm/combo-combined/index.js.map +1 -1
- package/lib/algorithm/combo-combined/types.d.ts +6 -18
- package/lib/algorithm/concentric/index.js +10 -16
- package/lib/algorithm/concentric/index.js.map +1 -1
- package/lib/algorithm/concentric/types.d.ts +3 -17
- package/lib/algorithm/d3-force/index.js +16 -12
- package/lib/algorithm/d3-force/index.js.map +1 -1
- package/lib/algorithm/d3-force/types.d.ts +10 -24
- package/lib/algorithm/d3-force-3d/index.js +1 -1
- package/lib/algorithm/d3-force-3d/index.js.map +1 -1
- package/lib/algorithm/d3-force-3d/types.d.ts +5 -4
- package/lib/algorithm/dagre/index.js +10 -7
- package/lib/algorithm/dagre/index.js.map +1 -1
- package/lib/algorithm/dagre/types.d.ts +8 -14
- package/lib/algorithm/force/index.d.ts +1 -1
- package/lib/algorithm/force/index.js +56 -44
- package/lib/algorithm/force/index.js.map +1 -1
- package/lib/algorithm/force/types.d.ts +39 -36
- package/lib/algorithm/force-atlas2/index.d.ts +1 -1
- package/lib/algorithm/force-atlas2/index.js +8 -7
- package/lib/algorithm/force-atlas2/index.js.map +1 -1
- package/lib/algorithm/force-atlas2/types.d.ts +0 -14
- package/lib/algorithm/fruchterman/index.d.ts +1 -1
- package/lib/algorithm/fruchterman/index.js +8 -10
- package/lib/algorithm/fruchterman/index.js.map +1 -1
- package/lib/algorithm/fruchterman/simulation.js +2 -1
- package/lib/algorithm/fruchterman/simulation.js.map +1 -1
- package/lib/algorithm/fruchterman/types.d.ts +2 -1
- package/lib/algorithm/grid/index.d.ts +1 -1
- package/lib/algorithm/grid/index.js +29 -34
- package/lib/algorithm/grid/index.js.map +1 -1
- package/lib/algorithm/grid/types.d.ts +5 -24
- package/lib/algorithm/mds/index.js +1 -0
- package/lib/algorithm/mds/index.js.map +1 -1
- package/lib/algorithm/radial/index.js +8 -5
- package/lib/algorithm/radial/index.js.map +1 -1
- package/lib/algorithm/radial/radial-nonoverlap-force.js +2 -4
- package/lib/algorithm/radial/radial-nonoverlap-force.js.map +1 -1
- package/lib/algorithm/radial/types.d.ts +3 -16
- package/lib/algorithm/random/index.js +1 -0
- package/lib/algorithm/random/index.js.map +1 -1
- package/lib/algorithm/types.d.ts +16 -0
- package/lib/index.d.ts +5 -3
- package/lib/index.js +3 -1
- package/lib/index.js.map +1 -1
- package/lib/node_modules/@antv/expr/dist/index.esm.js +4 -0
- package/lib/node_modules/@antv/expr/dist/index.esm.js.map +1 -0
- package/lib/types/common.d.ts +17 -1
- package/lib/types/data.d.ts +1 -1
- package/lib/util/expr.d.ts +12 -0
- package/lib/util/expr.js +26 -0
- package/lib/util/expr.js.map +1 -0
- package/lib/util/format.d.ts +37 -0
- package/lib/util/format.js +61 -17
- package/lib/util/format.js.map +1 -1
- package/lib/util/order.d.ts +3 -4
- package/lib/util/order.js.map +1 -1
- package/lib/util/size.d.ts +2 -1
- package/lib/util/size.js +9 -1
- package/lib/util/size.js.map +1 -1
- package/lib/worker.js +283 -227
- package/lib/worker.js.map +1 -1
- package/package.json +2 -1
- package/src/algorithm/antv-dagre/index.ts +15 -12
- package/src/algorithm/antv-dagre/types.ts +3 -14
- package/src/algorithm/circular/index.ts +5 -4
- package/src/algorithm/circular/types.ts +0 -15
- package/src/algorithm/combo-combined/index.ts +21 -17
- package/src/algorithm/combo-combined/types.ts +6 -21
- package/src/algorithm/concentric/index.ts +18 -16
- package/src/algorithm/concentric/types.ts +2 -16
- package/src/algorithm/d3-force/index.ts +25 -18
- package/src/algorithm/d3-force/types.ts +9 -22
- package/src/algorithm/d3-force-3d/index.ts +1 -1
- package/src/algorithm/d3-force-3d/types.ts +5 -0
- package/src/algorithm/dagre/index.ts +9 -7
- package/src/algorithm/dagre/types.ts +7 -15
- package/src/algorithm/force/index.ts +64 -40
- package/src/algorithm/force/types.ts +76 -45
- package/src/algorithm/force-atlas2/index.ts +13 -15
- package/src/algorithm/force-atlas2/types.ts +0 -12
- package/src/algorithm/fruchterman/index.ts +7 -15
- package/src/algorithm/fruchterman/simulation.ts +2 -2
- package/src/algorithm/fruchterman/types.ts +5 -2
- package/src/algorithm/grid/index.ts +45 -46
- package/src/algorithm/grid/types.ts +6 -35
- package/src/algorithm/radial/index.ts +14 -6
- package/src/algorithm/radial/radial-nonoverlap-force.ts +11 -6
- package/src/algorithm/radial/types.ts +2 -15
- package/src/algorithm/types.ts +18 -0
- package/src/types/common.ts +22 -0
- package/src/types/data.ts +1 -1
- package/src/util/expr.ts +26 -0
- package/src/util/format.ts +71 -27
- package/src/util/index.ts +2 -0
- package/src/util/order.ts +3 -9
- package/src/util/size.ts +8 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { BaseLayout } from '../base-layout';
|
|
2
|
-
import { LayoutNode, Point } from '../../types';
|
|
3
|
-
import { applySingleNodeLayout, normalizeViewport, parseSize } from '../../util';
|
|
4
|
-
import { formatNumberFn, formatSizeFn } from '../../util/format';
|
|
5
|
-
import { orderByDegree, orderById, orderBySorter } from '../../util/order';
|
|
6
1
|
import type { GraphLib } from '../../model/data';
|
|
2
|
+
import { LayoutNode, NodeData, Point, Sorter } from '../../types';
|
|
3
|
+
import { applySingleNodeLayout, normalizeViewport } from '../../util';
|
|
4
|
+
import { formatFn, formatNodeSizeFn } from '../../util/format';
|
|
5
|
+
import { orderByDegree, orderById, orderBySorter } from '../../util/order';
|
|
6
|
+
import { BaseLayout } from '../base-layout';
|
|
7
7
|
import type {
|
|
8
8
|
GridLayoutOptions,
|
|
9
9
|
IdMapRowAndCol,
|
|
10
|
-
|
|
10
|
+
ParsedGridLayoutOptions,
|
|
11
11
|
RowAndCol,
|
|
12
12
|
RowsAndCols,
|
|
13
13
|
VisitMap,
|
|
@@ -15,6 +15,20 @@ import type {
|
|
|
15
15
|
|
|
16
16
|
export type { GridLayoutOptions };
|
|
17
17
|
|
|
18
|
+
const DEFAULT_LAYOUT_OPTIONS: Partial<GridLayoutOptions> = {
|
|
19
|
+
begin: [0, 0],
|
|
20
|
+
preventOverlap: true,
|
|
21
|
+
condense: false,
|
|
22
|
+
rows: undefined,
|
|
23
|
+
cols: undefined,
|
|
24
|
+
position: undefined,
|
|
25
|
+
sortBy: 'degree',
|
|
26
|
+
nodeSize: 30,
|
|
27
|
+
nodeSpacing: 10,
|
|
28
|
+
width: 300,
|
|
29
|
+
height: 300,
|
|
30
|
+
};
|
|
31
|
+
|
|
18
32
|
/**
|
|
19
33
|
* <zh/> 网格布局
|
|
20
34
|
*
|
|
@@ -24,26 +38,19 @@ export class GridLayout extends BaseLayout<GridLayoutOptions> {
|
|
|
24
38
|
id = 'grid';
|
|
25
39
|
|
|
26
40
|
protected getDefaultOptions(): Partial<GridLayoutOptions> {
|
|
27
|
-
return
|
|
28
|
-
begin: [0, 0],
|
|
29
|
-
preventOverlap: true,
|
|
30
|
-
preventOverlapPadding: 10,
|
|
31
|
-
condense: false,
|
|
32
|
-
rows: undefined,
|
|
33
|
-
cols: undefined,
|
|
34
|
-
position: undefined,
|
|
35
|
-
sortBy: 'degree',
|
|
36
|
-
nodeSize: 30,
|
|
37
|
-
width: 300,
|
|
38
|
-
height: 300,
|
|
39
|
-
};
|
|
41
|
+
return DEFAULT_LAYOUT_OPTIONS;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
private
|
|
44
|
+
private parseOptions(
|
|
43
45
|
options: Partial<GridLayoutOptions> = {},
|
|
44
46
|
model: GraphLib,
|
|
45
|
-
):
|
|
46
|
-
const {
|
|
47
|
+
): ParsedGridLayoutOptions {
|
|
48
|
+
const {
|
|
49
|
+
rows: propRows,
|
|
50
|
+
cols: propCols,
|
|
51
|
+
position: propPosition,
|
|
52
|
+
sortBy: propSortBy,
|
|
53
|
+
} = options;
|
|
47
54
|
|
|
48
55
|
const { width, height, center } = normalizeViewport(options);
|
|
49
56
|
let rows = options.rows;
|
|
@@ -98,23 +105,20 @@ export class GridLayout extends BaseLayout<GridLayoutOptions> {
|
|
|
98
105
|
}
|
|
99
106
|
}
|
|
100
107
|
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
108
|
+
const sortBy = !propSortBy
|
|
109
|
+
? (DEFAULT_LAYOUT_OPTIONS.sortBy as 'degree')
|
|
110
|
+
: propSortBy === 'degree' || propSortBy === 'id'
|
|
111
|
+
? propSortBy
|
|
112
|
+
: (formatFn(propSortBy, ['nodeA', 'nodeB']) as Sorter<NodeData>);
|
|
105
113
|
|
|
106
114
|
return {
|
|
107
|
-
...options,
|
|
108
|
-
|
|
109
|
-
sortBy: options.sortBy || 'degree',
|
|
110
|
-
preventOverlapPadding: options.preventOverlapPadding ?? 0,
|
|
111
|
-
preventOverlap,
|
|
112
|
-
nodeSpacing,
|
|
113
|
-
nodeSize,
|
|
115
|
+
...(options as Required<GridLayoutOptions>),
|
|
116
|
+
sortBy,
|
|
114
117
|
rcs,
|
|
115
118
|
center,
|
|
116
119
|
width,
|
|
117
120
|
height,
|
|
121
|
+
position: formatFn(propPosition, ['node']),
|
|
118
122
|
};
|
|
119
123
|
}
|
|
120
124
|
|
|
@@ -126,12 +130,11 @@ export class GridLayout extends BaseLayout<GridLayoutOptions> {
|
|
|
126
130
|
width,
|
|
127
131
|
height,
|
|
128
132
|
condense,
|
|
129
|
-
preventOverlapPadding,
|
|
130
133
|
preventOverlap,
|
|
131
134
|
nodeSpacing,
|
|
132
135
|
nodeSize,
|
|
133
136
|
position,
|
|
134
|
-
} = this.
|
|
137
|
+
} = this.parseOptions(this.options, this.model);
|
|
135
138
|
|
|
136
139
|
const n = this.model.nodeCount();
|
|
137
140
|
|
|
@@ -152,18 +155,14 @@ export class GridLayout extends BaseLayout<GridLayoutOptions> {
|
|
|
152
155
|
let cellHeight = condense ? 0 : height / rcs.rows;
|
|
153
156
|
|
|
154
157
|
if (preventOverlap) {
|
|
158
|
+
const sizeFn = formatNodeSizeFn(
|
|
159
|
+
nodeSize,
|
|
160
|
+
nodeSpacing,
|
|
161
|
+
DEFAULT_LAYOUT_OPTIONS.nodeSize as number,
|
|
162
|
+
DEFAULT_LAYOUT_OPTIONS.nodeSpacing as number,
|
|
163
|
+
);
|
|
155
164
|
this.model.forEachNode((node) => {
|
|
156
|
-
const
|
|
157
|
-
const [nodeW, nodeH] = parseSize(nodeSize(nodeData) || 30);
|
|
158
|
-
|
|
159
|
-
const p =
|
|
160
|
-
nodeSpacing !== undefined
|
|
161
|
-
? nodeSpacing(nodeData)
|
|
162
|
-
: preventOverlapPadding;
|
|
163
|
-
|
|
164
|
-
const w = nodeW + p;
|
|
165
|
-
const h = nodeH + p;
|
|
166
|
-
|
|
165
|
+
const [w, h] = sizeFn(node._original);
|
|
167
166
|
cellWidth = Math.max(cellWidth, w);
|
|
168
167
|
cellHeight = Math.max(cellHeight, h);
|
|
169
168
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import type { Expr, NodeData, Point, Sorter } from '../../types';
|
|
1
2
|
import type { BaseLayoutOptions } from '../types';
|
|
2
|
-
import type { NodeData, Point, Size } from '../../types';
|
|
3
3
|
|
|
4
4
|
export interface GridLayoutOptions extends BaseLayoutOptions {
|
|
5
5
|
/**
|
|
@@ -21,25 +21,6 @@ export interface GridLayoutOptions extends BaseLayoutOptions {
|
|
|
21
21
|
* @defaultValue false
|
|
22
22
|
*/
|
|
23
23
|
preventOverlap?: boolean;
|
|
24
|
-
/**
|
|
25
|
-
* <zh/> 节点大小(直径)。用于防止节点重叠时的碰撞检测
|
|
26
|
-
*
|
|
27
|
-
* <en/> Node size (diameter). Used for collision detection when nodes overlap
|
|
28
|
-
*/
|
|
29
|
-
nodeSize?: Size | ((d?: NodeData) => Size);
|
|
30
|
-
/**
|
|
31
|
-
* <zh/> 环与环之间最小间距,用于调整半径
|
|
32
|
-
*
|
|
33
|
-
* <en/> Minimum spacing between rings, used to adjust the radius
|
|
34
|
-
*/
|
|
35
|
-
nodeSpacing?: number | ((d?: NodeData) => number);
|
|
36
|
-
/**
|
|
37
|
-
* <zh/> 避免重叠时节点的间距 padding。preventOverlap 为 true 时生效
|
|
38
|
-
*
|
|
39
|
-
* <en/> Padding between nodes to prevent overlap. It takes effect when preventOverlap is true
|
|
40
|
-
* @defaultValue 10
|
|
41
|
-
*/
|
|
42
|
-
preventOverlapPadding?: number;
|
|
43
24
|
/**
|
|
44
25
|
* <zh/> 为 false 时表示利用所有可用画布空间,为 true 时表示利用最小的画布空间
|
|
45
26
|
*
|
|
@@ -67,38 +48,28 @@ export interface GridLayoutOptions extends BaseLayoutOptions {
|
|
|
67
48
|
* <en/> Specify the basis for sorting (node attribute name). The higher the value, the more the node will be placed in the center. If it is undefined, the degree of the node will be calculated, and the higher the degree, the more the node will be placed in the center
|
|
68
49
|
* @defaultValue undefined
|
|
69
50
|
*/
|
|
70
|
-
sortBy?: 'id' | 'degree' |
|
|
51
|
+
sortBy?: 'id' | 'degree' | Expr | Sorter<NodeData>;
|
|
71
52
|
/**
|
|
72
53
|
* <zh/> 指定每个节点所在的行和列
|
|
73
54
|
*
|
|
74
55
|
* <en/> Specify the row and column where each node is located
|
|
75
56
|
* @defaultValue undefined
|
|
76
57
|
*/
|
|
77
|
-
position?: (node: NodeData) => { row?: number; col?: number };
|
|
58
|
+
position?: Expr | ((node: NodeData) => { row?: number; col?: number });
|
|
78
59
|
}
|
|
79
60
|
|
|
80
|
-
export interface
|
|
61
|
+
export interface ParsedGridLayoutOptions
|
|
81
62
|
extends Omit<
|
|
82
63
|
GridLayoutOptions,
|
|
83
|
-
| '
|
|
84
|
-
| 'nodeSize'
|
|
85
|
-
| 'nodeSpacing'
|
|
86
|
-
| 'preventOverlap'
|
|
87
|
-
| 'preventOverlapPadding'
|
|
88
|
-
| 'sortBy'
|
|
89
|
-
| 'rows'
|
|
90
|
-
| 'cols'
|
|
64
|
+
'begin' | 'preventOverlap' | 'sortBy' | 'rows' | 'cols'
|
|
91
65
|
> {
|
|
92
66
|
width: number;
|
|
93
67
|
height: number;
|
|
94
68
|
center: Point;
|
|
95
69
|
begin: Point;
|
|
96
70
|
rcs: { rows: number; cols: number };
|
|
97
|
-
nodeSize: (node?: NodeData) => Size;
|
|
98
|
-
nodeSpacing: (node?: NodeData) => number;
|
|
99
71
|
preventOverlap: boolean;
|
|
100
|
-
|
|
101
|
-
sortBy: 'id' | 'degree' | ((nodeA: NodeData, nodeB: NodeData) => -1 | 0 | 1);
|
|
72
|
+
sortBy: 'id' | 'degree' | Sorter<NodeData>;
|
|
102
73
|
}
|
|
103
74
|
|
|
104
75
|
export type RowsAndCols = {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { BaseLayout } from '../base-layout';
|
|
2
|
-
import { runMDS } from '../mds';
|
|
3
|
-
import type { ID, Matrix } from '../../types';
|
|
4
1
|
import type { GraphLib } from '../../model/data';
|
|
2
|
+
import type { ID, Matrix } from '../../types';
|
|
5
3
|
import { getAdjList, johnson, normalizeViewport } from '../../util';
|
|
6
4
|
import { applySingleNodeLayout } from '../../util/common';
|
|
7
|
-
import { formatNodeSizeFn } from '../../util/format';
|
|
5
|
+
import { formatFn, formatNodeSizeFn } from '../../util/format';
|
|
6
|
+
import { BaseLayout } from '../base-layout';
|
|
7
|
+
import { runMDS } from '../mds';
|
|
8
8
|
import {
|
|
9
9
|
radialNonoverlapForce,
|
|
10
10
|
RadialNonoverlapForceOptions,
|
|
@@ -22,6 +22,8 @@ const DEFAULTS_LAYOUT_OPTIONS: Partial<RadialLayoutOptions> = {
|
|
|
22
22
|
sortStrength: 10,
|
|
23
23
|
strictRadial: true,
|
|
24
24
|
unitRadius: null,
|
|
25
|
+
nodeSize: 10,
|
|
26
|
+
nodeSpacing: 0,
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
/**
|
|
@@ -126,7 +128,12 @@ export class RadialLayout extends BaseLayout<RadialLayoutOptions> {
|
|
|
126
128
|
|
|
127
129
|
// stagger the overlapped nodes
|
|
128
130
|
if (preventOverlap) {
|
|
129
|
-
const nodeSizeFunc = formatNodeSizeFn(
|
|
131
|
+
const nodeSizeFunc = formatNodeSizeFn(
|
|
132
|
+
nodeSize,
|
|
133
|
+
nodeSpacing,
|
|
134
|
+
DEFAULTS_LAYOUT_OPTIONS.nodeSize as number,
|
|
135
|
+
DEFAULTS_LAYOUT_OPTIONS.nodeSpacing as number,
|
|
136
|
+
);
|
|
130
137
|
const nonoverlapForceParams: RadialNonoverlapForceOptions = {
|
|
131
138
|
nodeSizeFunc,
|
|
132
139
|
radiiMap,
|
|
@@ -230,7 +237,8 @@ const eIdealDisMatrix = (
|
|
|
230
237
|
|
|
231
238
|
const baseLink = (linkDistance + unitRadius) / 2;
|
|
232
239
|
const sortCache = new Map<ID, number>();
|
|
233
|
-
const sortFn =
|
|
240
|
+
const sortFn =
|
|
241
|
+
!sortBy || sortBy === 'data' ? null : formatFn(sortBy, ['node']);
|
|
234
242
|
const isDataSort = sortBy === 'data';
|
|
235
243
|
|
|
236
244
|
for (let i = 0; i < n; i++) {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import type { DisplacementMap, ID, LayoutNode, NodeData, Size } from '../../types';
|
|
2
1
|
import type { GraphLib } from '../../model/data';
|
|
3
|
-
import {
|
|
2
|
+
import type {
|
|
3
|
+
DisplacementMap,
|
|
4
|
+
ID,
|
|
5
|
+
LayoutNode,
|
|
6
|
+
NodeData,
|
|
7
|
+
STDSize,
|
|
8
|
+
} from '../../types';
|
|
4
9
|
|
|
5
10
|
const SPEED_DIVISOR = 800;
|
|
6
11
|
|
|
@@ -22,7 +27,7 @@ export type RadialNonoverlapForceOptions = {
|
|
|
22
27
|
/** Gravity factor pulling nodes towards their target radius */
|
|
23
28
|
gravity?: number;
|
|
24
29
|
/** Function to get the size of a node (includes node self and spacing) */
|
|
25
|
-
nodeSizeFunc: (node
|
|
30
|
+
nodeSizeFunc: (node: NodeData) => STDSize;
|
|
26
31
|
};
|
|
27
32
|
|
|
28
33
|
const DEFAULTS_LAYOUT_OPTIONS: Partial<RadialNonoverlapForceOptions> = {
|
|
@@ -83,7 +88,7 @@ const getRepulsion = (
|
|
|
83
88
|
displacements: DisplacementMap,
|
|
84
89
|
k: number,
|
|
85
90
|
radiiMap: Map<ID, number>,
|
|
86
|
-
nodeSizeFunc: (
|
|
91
|
+
nodeSizeFunc: (node: NodeData) => STDSize,
|
|
87
92
|
) => {
|
|
88
93
|
let i = 0;
|
|
89
94
|
|
|
@@ -112,8 +117,8 @@ const getRepulsion = (
|
|
|
112
117
|
vecy = 0.01 * sign;
|
|
113
118
|
}
|
|
114
119
|
|
|
115
|
-
const nodeSizeU = Math.max(...
|
|
116
|
-
const nodeSizeV = Math.max(...
|
|
120
|
+
const nodeSizeU = Math.max(...nodeSizeFunc(nodeU._original));
|
|
121
|
+
const nodeSizeV = Math.max(...nodeSizeFunc(nodeV._original));
|
|
117
122
|
|
|
118
123
|
// these two nodes overlap
|
|
119
124
|
if (vecLength < nodeSizeV / 2 + nodeSizeU / 2) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import type { Expr, NodeData } from '../../types';
|
|
1
2
|
import type { BaseLayoutOptions } from '../base-layout';
|
|
2
|
-
import type { NodeData, Size } from '../../types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* <zh/> Radial 辐射布局的配置项
|
|
@@ -41,19 +41,6 @@ export interface RadialLayoutOptions extends BaseLayoutOptions {
|
|
|
41
41
|
* @defaultValue false
|
|
42
42
|
*/
|
|
43
43
|
preventOverlap?: boolean;
|
|
44
|
-
/**
|
|
45
|
-
* <zh/> 节点大小(直径)。用于防止节点重叠时的碰撞检测
|
|
46
|
-
*
|
|
47
|
-
* <en/> Node size (diameter). Used for collision detection when preventing node overlap
|
|
48
|
-
*/
|
|
49
|
-
nodeSize?: Size | ((d?: NodeData) => Size);
|
|
50
|
-
/**
|
|
51
|
-
* <zh/> preventOverlap 为 true 时生效, 防止重叠时节点边缘间距的最小值。可以是回调函数, 为不同节点设置不同的最小间距
|
|
52
|
-
*
|
|
53
|
-
* <en/> Effective when preventOverlap is true. The minimum edge spacing when preventing node overlap. It can be a callback function, and set different minimum spacing for different nodes
|
|
54
|
-
* @defaultValue 10
|
|
55
|
-
*/
|
|
56
|
-
nodeSpacing?: number | ((d?: NodeData) => number);
|
|
57
44
|
/**
|
|
58
45
|
* <zh/> 防止重叠步骤的最大迭代次数
|
|
59
46
|
*
|
|
@@ -82,7 +69,7 @@ export interface RadialLayoutOptions extends BaseLayoutOptions {
|
|
|
82
69
|
* <en/> The default is undefined, which means arranging based on the topological structure of the data (the shortest path between nodes). Nodes that are closer in proximity or have a smaller shortest path between them will be arranged as close together as possible. 'data' indicates arranging based on the order of nodes in the data, so nodes that are closer in the data order will be arranged as close together as possible. You can also specify a field name in the node data, such as 'cluster' or 'name' (it must exist in the data of the graph)
|
|
83
70
|
* @defaultValue undefined
|
|
84
71
|
*/
|
|
85
|
-
sortBy?: 'data' | ((
|
|
72
|
+
sortBy?: 'data' | ((node: NodeData) => number | string) | Expr;
|
|
86
73
|
/**
|
|
87
74
|
* <zh/> 同层节点根据 sortBy 排列的强度,数值越大,sortBy 指定的方式计算出距离越小的越靠近。sortBy 不为 undefined 时生效
|
|
88
75
|
*
|
package/src/algorithm/types.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
EdgeData,
|
|
3
|
+
Expr,
|
|
3
4
|
GraphData,
|
|
4
5
|
GraphEdge,
|
|
5
6
|
GraphNode,
|
|
6
7
|
ID,
|
|
7
8
|
NodeData,
|
|
8
9
|
Point,
|
|
10
|
+
Size,
|
|
9
11
|
} from '../types';
|
|
10
12
|
|
|
11
13
|
export interface DataOptions<
|
|
@@ -63,6 +65,22 @@ export interface BaseLayoutOptions<
|
|
|
63
65
|
*/
|
|
64
66
|
height?: number;
|
|
65
67
|
|
|
68
|
+
/**
|
|
69
|
+
* <zh/> 节点大小(直径)。用于防止节点重叠时的碰撞检测
|
|
70
|
+
*
|
|
71
|
+
* <en/> Node size (diameter). Used for collision detection when nodes overlap
|
|
72
|
+
* @defaultValue 10
|
|
73
|
+
*/
|
|
74
|
+
nodeSize?: Size | Expr | ((node: NodeData) => Size);
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* <zh/> 节点之间的最小间距
|
|
78
|
+
*
|
|
79
|
+
* <en/> Minimum spacing between nodes
|
|
80
|
+
* @defaultValue 0
|
|
81
|
+
*/
|
|
82
|
+
nodeSpacing?: Size | Expr | ((node: NodeData) => Size);
|
|
83
|
+
|
|
66
84
|
/**
|
|
67
85
|
* <zh/> 是否启用 WebWorker
|
|
68
86
|
*
|
package/src/types/common.ts
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
1
|
export type PlainObject = Record<string, any>;
|
|
2
2
|
|
|
3
3
|
export type Matrix = number[][];
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* String expression evaluated by `@antv/expr`.
|
|
7
|
+
*
|
|
8
|
+
* Notes:
|
|
9
|
+
* - `Expr` is structured-cloneable and can be passed into WebWorkers.
|
|
10
|
+
* - Function callbacks cannot be structured-cloned; prefer `Expr` when `enableWorker: true`.
|
|
11
|
+
*/
|
|
12
|
+
export type Expr = string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* CallableExpr<(node: NodeData) => number>
|
|
16
|
+
*
|
|
17
|
+
* => 'node.degree' | (node: NodeData) => number
|
|
18
|
+
*/
|
|
19
|
+
export type CallableExpr<TData = any, TResult = any> =
|
|
20
|
+
| Expr
|
|
21
|
+
| ((data: TData) => TResult);
|
|
22
|
+
|
|
23
|
+
export type ExprContext = Record<string, any>;
|
|
24
|
+
|
|
25
|
+
export type Sorter<T = any> = (a: T, b: T) => -1 | 0 | 1;
|
package/src/types/data.ts
CHANGED
package/src/util/expr.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { compile, evaluate } from '@antv/expr';
|
|
2
|
+
import type { ExprContext } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Evaluate an expression if (and only if) it's a valid string expression.
|
|
6
|
+
* - Returns `undefined` when `expression` is not a string, empty, or invalid.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* evaluateExpression('x + y', { x: 10, y: 20 }) // 30
|
|
10
|
+
*/
|
|
11
|
+
export function evaluateExpression(
|
|
12
|
+
expression: unknown,
|
|
13
|
+
context: ExprContext,
|
|
14
|
+
): unknown | undefined {
|
|
15
|
+
if (typeof expression !== 'string') return undefined;
|
|
16
|
+
|
|
17
|
+
const source = expression.trim();
|
|
18
|
+
if (!source) return undefined;
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
compile(source);
|
|
22
|
+
return evaluate(source, context);
|
|
23
|
+
} catch {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/util/format.ts
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
|
-
import { isFunction, isNumber,
|
|
2
|
-
import type { NodeData, Size } from '../types';
|
|
3
|
-
import {
|
|
1
|
+
import { isFunction, isNil, isNumber, isString } from '@antv/util';
|
|
2
|
+
import type { Expr, NodeData, Size, STDSize } from '../types';
|
|
3
|
+
import { evaluateExpression } from './expr';
|
|
4
|
+
import { isSize, parseSize } from './size';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Format a value into a callable function when it is a string expression.
|
|
8
|
+
* - `string` => `(context) => evaluateExpression(string, context)`
|
|
9
|
+
* - `function` => returned as-is
|
|
10
|
+
* - other => returned as-is
|
|
11
|
+
*/
|
|
12
|
+
export function formatFn<
|
|
13
|
+
TContext extends Record<string, any> = Record<string, any>,
|
|
14
|
+
>(value: unknown, argNames: (keyof TContext & string)[]) {
|
|
15
|
+
if (typeof value === 'function') return value;
|
|
16
|
+
if (typeof value === 'string') {
|
|
17
|
+
const expr = value;
|
|
18
|
+
return (...argv: any[]) => {
|
|
19
|
+
const ctx = {} as TContext;
|
|
20
|
+
for (let i = 0; i < argNames.length; i++) {
|
|
21
|
+
(ctx as any)[argNames[i]] = argv[i];
|
|
22
|
+
}
|
|
23
|
+
return evaluateExpression(expr, ctx);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return () => value;
|
|
27
|
+
}
|
|
4
28
|
|
|
5
29
|
/**
|
|
6
30
|
* Format value with multiple types into a function that returns a number
|
|
@@ -8,10 +32,26 @@ import { parseSize } from './size';
|
|
|
8
32
|
* @param defaultValue The default value when value is invalid
|
|
9
33
|
* @returns A function that returns a number
|
|
10
34
|
*/
|
|
11
|
-
export function formatNumberFn<T = NodeData>(
|
|
12
|
-
value: number | ((d
|
|
35
|
+
export function formatNumberFn<T extends NodeData = NodeData>(
|
|
36
|
+
value: number | Expr | ((d: T) => number) | undefined,
|
|
13
37
|
defaultValue: number,
|
|
14
|
-
|
|
38
|
+
type: 'node' | 'edge' | 'combo' = 'node',
|
|
39
|
+
): (d: T) => number {
|
|
40
|
+
// If value is undefined, return default value function
|
|
41
|
+
if (isNil(value)) {
|
|
42
|
+
return () => defaultValue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// If value is an expression, return a function that evaluates the expression
|
|
46
|
+
if (isString(value)) {
|
|
47
|
+
const numberFn = formatFn(value, [type]);
|
|
48
|
+
return (d: T) => {
|
|
49
|
+
const evaluated = numberFn(d);
|
|
50
|
+
if (isNumber(evaluated)) return evaluated;
|
|
51
|
+
return defaultValue;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
15
55
|
// If value is a function, return it directly
|
|
16
56
|
if (isFunction(value)) {
|
|
17
57
|
return value;
|
|
@@ -33,15 +73,26 @@ export function formatNumberFn<T = NodeData>(
|
|
|
33
73
|
* @param resultIsNumber Whether to return a number (max of width/height) or size array
|
|
34
74
|
* @returns A function that returns a size
|
|
35
75
|
*/
|
|
36
|
-
export function formatSizeFn<T extends NodeData>(
|
|
37
|
-
value?: Size |
|
|
76
|
+
export function formatSizeFn<T extends NodeData = NodeData>(
|
|
77
|
+
value?: Size | Expr | ((d: T) => Size),
|
|
38
78
|
defaultValue: number = 10,
|
|
39
|
-
|
|
79
|
+
type: 'node' | 'edge' | 'combo' = 'node',
|
|
80
|
+
): (d: T) => Size {
|
|
40
81
|
// If value is undefined, return default value function
|
|
41
|
-
if (
|
|
82
|
+
if (isNil(value)) {
|
|
42
83
|
return () => defaultValue;
|
|
43
84
|
}
|
|
44
85
|
|
|
86
|
+
// If value is an expression, return a function that evaluates the expression
|
|
87
|
+
if (isString(value)) {
|
|
88
|
+
const sizeFn = formatFn(value, [type]);
|
|
89
|
+
return (d: T) => {
|
|
90
|
+
const evaluated = sizeFn(d);
|
|
91
|
+
if (isSize(evaluated)) return evaluated;
|
|
92
|
+
return defaultValue;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
45
96
|
// If value is a function, return it directly
|
|
46
97
|
if (isFunction(value)) {
|
|
47
98
|
return value;
|
|
@@ -57,11 +108,6 @@ export function formatSizeFn<T extends NodeData>(
|
|
|
57
108
|
return () => value;
|
|
58
109
|
}
|
|
59
110
|
|
|
60
|
-
// If value is an object with width and height
|
|
61
|
-
if (isObject(value) && value.width && value.height) {
|
|
62
|
-
return () => [value.width, value.height];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
111
|
return () => defaultValue;
|
|
66
112
|
}
|
|
67
113
|
|
|
@@ -70,23 +116,21 @@ export function formatSizeFn<T extends NodeData>(
|
|
|
70
116
|
* @param nodeSize The size of the node
|
|
71
117
|
* @param nodeSpacing The spacing around the node
|
|
72
118
|
* @param defaultNodeSize The default node size when value is invalid
|
|
119
|
+
* @param defaultNodeSpacing The default node spacing when value is invalid
|
|
73
120
|
* @returns A function that returns the total size (node size + spacing)
|
|
74
121
|
*/
|
|
75
122
|
export const formatNodeSizeFn = (
|
|
76
|
-
nodeSize:
|
|
77
|
-
|
|
78
|
-
| { width: number; height: number }
|
|
79
|
-
| ((node?: NodeData) => Size)
|
|
80
|
-
| undefined,
|
|
81
|
-
nodeSpacing: number | ((node?: NodeData) => number) | undefined,
|
|
123
|
+
nodeSize?: Size | Expr | ((node: NodeData) => Size),
|
|
124
|
+
nodeSpacing?: Size | Expr | ((node: NodeData) => Size),
|
|
82
125
|
defaultNodeSize: number = 10,
|
|
83
|
-
|
|
84
|
-
|
|
126
|
+
defaultNodeSpacing: number = 0,
|
|
127
|
+
): ((d: NodeData) => STDSize) => {
|
|
128
|
+
const nodeSpacingFunc = formatSizeFn(nodeSpacing, defaultNodeSpacing);
|
|
85
129
|
const nodeSizeFunc = formatSizeFn(nodeSize, defaultNodeSize);
|
|
86
130
|
|
|
87
|
-
return (
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
return
|
|
131
|
+
return (d: NodeData) => {
|
|
132
|
+
const [sizeW, sizeH, sizeD] = parseSize(nodeSizeFunc(d));
|
|
133
|
+
const [spacingW, spacingH, spacingD] = parseSize(nodeSpacingFunc(d));
|
|
134
|
+
return [sizeW + spacingW, sizeH + spacingH, sizeD + spacingD];
|
|
91
135
|
};
|
|
92
136
|
};
|
package/src/util/index.ts
CHANGED
package/src/util/order.ts
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
import type { LayoutNode, NodeData } from '../types';
|
|
2
1
|
import type { GraphLib } from '../model/data';
|
|
3
|
-
|
|
4
|
-
export type SortComparator<N extends NodeData = NodeData> = (
|
|
5
|
-
nodeA: LayoutNode<N>,
|
|
6
|
-
nodeB: LayoutNode<N>,
|
|
7
|
-
nodes: LayoutNode<N>[],
|
|
8
|
-
) => -1 | 0 | 1;
|
|
2
|
+
import type { LayoutNode, NodeData, Sorter } from '../types';
|
|
9
3
|
|
|
10
4
|
/**
|
|
11
5
|
* 通用排序核心函数
|
|
@@ -29,7 +23,7 @@ export function orderByDegree<N extends NodeData = NodeData>(
|
|
|
29
23
|
return sort(model, (nodeA, nodeB) => {
|
|
30
24
|
const degreeA = model.degree(nodeA.id);
|
|
31
25
|
const degreeB = model.degree(nodeB.id);
|
|
32
|
-
if(order === 'asc') {
|
|
26
|
+
if (order === 'asc') {
|
|
33
27
|
return degreeA - degreeB; // ascending order
|
|
34
28
|
}
|
|
35
29
|
return degreeB - degreeA; // descending order
|
|
@@ -59,7 +53,7 @@ export function orderById<N extends NodeData = NodeData>(
|
|
|
59
53
|
*/
|
|
60
54
|
export function orderBySorter<N extends NodeData = NodeData>(
|
|
61
55
|
model: GraphLib<N>,
|
|
62
|
-
sorter:
|
|
56
|
+
sorter: Sorter<N>,
|
|
63
57
|
): GraphLib<N> {
|
|
64
58
|
return sort(model, (nodeA, nodeB) => {
|
|
65
59
|
const a = model.originalNode(nodeA.id);
|
package/src/util/size.ts
CHANGED
|
@@ -8,3 +8,11 @@ export function parseSize(size?: Size): STDSize {
|
|
|
8
8
|
const [x, y = x, z = x] = size;
|
|
9
9
|
return [x, y, z];
|
|
10
10
|
}
|
|
11
|
+
|
|
12
|
+
export function isSize(value: unknown): value is Size {
|
|
13
|
+
if (isNumber(value)) return true;
|
|
14
|
+
if (Array.isArray(value)) {
|
|
15
|
+
return value.every((item) => isNumber(item));
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|