Skip to content

Commit e7ccfd8

Browse files
committed
docs: update docs
1 parent a5f5486 commit e7ccfd8

File tree

9 files changed

+149
-14
lines changed

9 files changed

+149
-14
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
["getting-started", "installation", "data-mapping", "layout-configuration"]
1+
["getting-started", "installation", "webworker", "data-mapping", "layout-configuration"]
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { Badge } from '@theme';
2+
3+
# Using WebWorker <Badge text="Performance" type="info" />
4+
5+
When `enableWorker: true` and `Worker` is available, layout computation runs in a WebWorker to reduce main-thread blocking.
6+
7+
However, if you pass **function callbacks** in options (e.g. `(node) => ...`), Worker mode may throw errors like:
8+
9+
```txt
10+
DataCloneError: Failed to execute 'postMessage' on 'Worker': function could not be cloned
11+
```
12+
13+
## Why it happens
14+
15+
The library transfers `data/options` between the main thread and the Worker via structured clone. **Functions are not structured-cloneable**, so any function inside options can break the transfer.
16+
17+
## How to fix
18+
19+
### Option 1: Use `Expr` (string expressions) instead of callbacks
20+
21+
`@antv/layout` supports `Expr` (powered by `@antv/expr`) for many numeric/size fields. If a field’s type includes `Expr`, you can pass a string expression instead of a function.
22+
23+
Example (ForceLayout):
24+
25+
```ts
26+
import { ForceLayout } from '@antv/layout';
27+
28+
const layout = new ForceLayout({
29+
enableWorker: true,
30+
nodeStrength: 'node.data.strength ?? -30',
31+
edgeStrength: 'edge.data.weight ?? 0.1',
32+
nodeSize: 'node.data.size ?? 10',
33+
});
34+
35+
await layout.execute(data);
36+
```
37+
38+
Expression variable names depend on the field:
39+
40+
- Node fields usually use `node`
41+
- Edge fields usually use `edge`
42+
- Combo fields usually use `combo`
43+
44+
### Option 2: Pre-compute values into your data
45+
46+
If you used to do:
47+
48+
```ts
49+
nodeSize: (node) => node.data.size
50+
```
51+
52+
You can pre-fill `data.nodes[i].data.size` and then use `nodeSize: 'node.data.size'` (or rely on defaults).
53+
54+
### Option 3: If you need callbacks, don’t enable Worker
55+
56+
Some capabilities inherently require functions and cannot be passed into Workers:
57+
58+
- `onTick`-style callbacks
59+
- `node` / `edge` mapping functions
60+
- Any custom logic without an `Expr` alternative
61+
62+
In these cases, run the layout on the main thread by keeping `enableWorker` off.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
["getting-started", "installation", "data-mapping", "layout-configuration"]
1+
["getting-started", "installation", "webworker", "data-mapping", "layout-configuration"]
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { Badge } from '@theme';
2+
3+
# 使用 WebWorker <Badge text="Performance" type="info" />
4+
5+
当你配置 `enableWorker: true` 且运行环境支持 `Worker` 时,布局计算会在 Worker 中执行,从而减少主线程阻塞。
6+
7+
但如果你在配置项里传入了**回调函数**(例如 `(node) => ...`),在 Worker 模式下可能会直接报错,常见报错类似:
8+
9+
```txt
10+
DataCloneError: Failed to execute 'postMessage' on 'Worker': function could not be cloned
11+
```
12+
13+
## 为什么会报错
14+
15+
库在主线程与 Worker 之间传递 `data/options` 时依赖结构化克隆(structured clone)。**函数无法被结构化克隆**,因此当 options 中包含函数时,就会触发 `postMessage`/Comlink 的传输错误。
16+
17+
## 怎么解决
18+
19+
### 方案 1:用 `Expr`(字符串表达式)替代回调
20+
21+
`@antv/layout` 内置了基于 `@antv/expr` 的表达式能力:对于类型里标注为 `Expr` 的字段,你可以传入字符串表达式来代替函数。
22+
23+
示例(ForceLayout):
24+
25+
```ts
26+
import { ForceLayout } from '@antv/layout';
27+
28+
const layout = new ForceLayout({
29+
enableWorker: true,
30+
nodeStrength: 'node.data.strength ?? -30',
31+
edgeStrength: 'edge.data.weight ?? 0.1',
32+
nodeSize: 'node.data.size ?? 10',
33+
});
34+
35+
await layout.execute(data);
36+
```
37+
38+
表达式里的变量名取决于字段类型:
39+
40+
- 节点相关字段通常使用 `node`(例如 `nodeStrength` / `nodeSize`
41+
- 边相关字段通常使用 `edge`(例如 `edgeStrength` / `edgeLabelOffset`
42+
- combo 相关字段通常使用 `combo`
43+
44+
### 方案 2:把自定义计算结果“提前算好”放进数据里
45+
46+
例如你原本想写:
47+
48+
```ts
49+
nodeSize: (node) => node.data.size;
50+
```
51+
52+
可以改为在业务数据里把 `size` 填好,直接传 `nodeSize: 'node.data.size'` 或者不传(让布局从 data 读取)。
53+
54+
### 方案 3:需要回调就不要开 Worker
55+
56+
以下类型的能力天然需要函数回调,Worker 模式下无法传入:
57+
58+
- `onTick` 这类过程回调
59+
- `node` / `edge` 数据映射函数
60+
- 任何只有函数形态的自定义逻辑(未提供 `Expr` 字段的场景)
61+
62+
此时建议把 `enableWorker` 关掉,或者只在主线程跑布局。

src/algorithm/antv-dagre/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Expr, ID, NodeData, Point } from '../../types';
1+
import type { Expr, ID, NodeData, Point } from '../../types';
22
import { BaseLayoutOptions } from '../base-layout';
33

44
export type DagreRankdir =

src/algorithm/d3-force-3d/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Expr } from '../../types';
1+
import type { Expr } from '../../types';
22
import type {
33
D3ForceCommonOptions,
44
EdgeDatum as _EdgeDatum,

src/algorithm/dagre/types.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { GraphLabel } from 'dagre';
2-
import type { EdgeData, Size } from '../../types';
2+
import type { EdgeData, Expr, Size } from '../../types';
33
import type { EdgeLabelPos } from '../../types/edge-label';
44
import type { BaseLayoutOptions } from '../types';
55

@@ -44,33 +44,33 @@ export interface DagreLayoutOptions extends BaseLayoutOptions, GraphLabel {
4444
* <en/> Sets minimum number of layers an edge spans; larger values create more distance between nodes, controlling layout compactness
4545
* @defaultValue 1
4646
*/
47-
edgeMinLen?: number | ((edge: EdgeData) => number);
47+
edgeMinLen?: number | Expr | ((edge: EdgeData) => number);
4848

4949
/**
5050
* <zh/> 边的权重,影响边的长度优化优先级,权重大的边倾向于更短
5151
*
5252
* <en/> Edge weight affecting length optimization priority; higher weight edges tend to be shorter
5353
*/
54-
edgeWeight?: number | ((edge: EdgeData) => number);
54+
edgeWeight?: number | Expr | ((edge: EdgeData) => number);
5555

5656
/**
5757
* <zh/> 边标签的尺寸,用于为标签预留空间,避免与节点重叠
5858
*
5959
* <en/> Size of edge labels for reserving space to prevent overlap with nodes
6060
*/
61-
edgeLabelSize?: Size | ((edge: EdgeData) => Size);
61+
edgeLabelSize?: Size | Expr | ((edge: EdgeData) => Size);
6262

6363
/**
6464
* <zh/> 标签在边上的位置,控制标签相对于边的对齐方式
6565
*
6666
* <en/> Label position on edge, controlling label alignment relative to the edge
6767
*/
68-
edgeLabelPos?: EdgeLabelPos | ((edge: EdgeData) => EdgeLabelPos);
68+
edgeLabelPos?: EdgeLabelPos | Expr | ((edge: EdgeData) => EdgeLabelPos);
6969

7070
/**
7171
* <zh/> 标签与边的偏移距离,用于微调标签位置避免视觉重叠
7272
*
7373
* <en/> Offset distance between label and edge for fine-tuning label position to avoid visual overlap
7474
*/
75-
edgeLabelOffset?: number | ((edge: EdgeData) => number);
75+
edgeLabelOffset?: number | Expr | ((edge: EdgeData) => number);
7676
}

src/types/common.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,24 @@ export type PlainObject = Record<string, any>;
22

33
export type Matrix = number[][];
44

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+
*/
512
export type Expr = string;
613

714
/**
815
* CallableExpr<(node: NodeData) => number>
916
*
1017
* => 'node.degree' | (node: NodeData) => number
1118
*/
12-
export type CallableExpr<T = any> = Expr | ((data: T) => any);
19+
export type CallableExpr<TData = any, TResult = any> =
20+
| Expr
21+
| ((data: TData) => TResult);
22+
23+
export type ExprContext = Record<string, any>;
1324

1425
export type Sorter<T = any> = (a: T, b: T) => -1 | 0 | 1;

src/util/expr.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { compile, evaluate } from '@antv/expr';
2-
import type { Context } from '@antv/expr/dist/interpreter';
2+
import type { ExprContext } from '../types';
33

44
/**
55
* Evaluate an expression if (and only if) it's a valid string expression.
@@ -10,8 +10,8 @@ import type { Context } from '@antv/expr/dist/interpreter';
1010
*/
1111
export function evaluateExpression(
1212
expression: unknown,
13-
context: Context,
14-
): Function | undefined {
13+
context: ExprContext,
14+
): unknown | undefined {
1515
if (typeof expression !== 'string') return undefined;
1616

1717
const source = expression.trim();

0 commit comments

Comments
 (0)