19 KiB
title | order |
---|---|
🎉 新功能怎么用 | 2 |
相较于 v4,G6 v5 的新能力体现在:
- 🎞 视觉与动画规范,使用 JSON spec 或映射函数的方式定义样式与动画;
- 📂信息分层能力;
- 🎨 简单灵活的主题配置能力;
- 🤖 灵活强大的数据处理能力;
- 🎄 树图和图结构的融合;
- 🏀 3D 大图;
- 🚀 性能飞跃,包括渲染与布局计算;
- 🌠 多渲染器,可运行时切换;
- 📦 包体积减少,支持 TreeShaking。
正式版即将来袭。如果上面 Feature 是您所期待的,现在就可以使用 G6 5.0 Beta 版本进行尝鲜!若遇到任何升级问题,请在 GitHub 给我们留言。
为了支持上述全新能力,G6 v5 相比于 v4 有比较大的 Breaking Change,这可能带来一定的升级成本。希望上面全新能力带来的收益远大于升级成本。
1️⃣. 视觉与动画规范
JSON spec 定义
【TODO JSON spec api 地址】
v5 中我们将所有节点/边/ combo 的图形进行规范化,每种类型的元素基本都有若干个规范的图形名称。包括自定义的元素,也应当遵循这样的规范。如果有额外的图形,统一放入 otherShapes 中。
- 节点:keyShape(主图形)、labelShape(文本图形)、haloShape(某些状态下出现的背景光晕)、labelBackgroundShape(文本背景图形)、iconShape(节点中心的 icon 图形)、badgeShapes(节点四周的徽标图形)、anchorShapes(代表锚点的圆点图形):
- 边:keyShape(主图形)、labelShape(文本图形)、haloShape(某些状态下出现的背景光晕)、labelBackgroundShape(文本背景图形):
因此,不论是什么类型的节点和边,都可以通过如下方式对其中的图形进行配置:
const graph = new Graph({
node: {
keyShape: {
fill: "#f00',
r: {
fields: ['size'],
formatter: model => Math.max(model.data.size[0], model.data.size[1]) / 2
}
// ...keyShape 的其他样式
},
labelShape: {
// 可以指定一个确定字符串,也可以使用下面的映射方式,映射到数据的某个字段中。其他属性也可以使用这种映射
text: {
fields: ['name'],
formatter: model => model.data.name
},
// ... labelShape 的其他样式
},
labelBackgroundShape: {
padding: [2,2,2,2],
fill: '#0f0'
// ... labelShape 的背景图形的其他样式
},
iconShape: {
// 内容可以是文本或图片, img 优先
// img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
text: 'label',
// ... iconShape 更多配置
},
anchorShapes: [
{
position: [0, 0.5],
r: 2,
fill: 'red',
},
{
position: [1, 0.5],
r: 2,
fill: 'green',
},
// 更多锚点图形(绘制)
],
badgeShapes: [
{
text: 'running',
position: 'rightTop',
color: 'blue',
},
{
text: 'error',
position: 'right',
color: 'blue',
},
// ... 更多 badge 图形
],
otherShapes: {
xShape: {...},
yShape: {...},
zShape: {...},
// ... 更多额外的图形
}
}
})
函数映射配置
有时,我们需要根据不同的数据,返回不同的样式配置。这种需求下,函数配置相比于可以写 field+formatter 的方式更加灵活:
const graph = new Graph({
node: (model) => {
const { id, data } = model;
const { size, showLabel } = data;
// 注意返回的数据结构为完整的节点数据类型
return {
id,
data: {
...data, // 注意将原数据中的原始 data 也返回,否则导致这些数据的丢失
keyShape: {
r: Math.max(size[0], size[1]) / 2,
// ...
},
labelShape: showLabel
? {
// 根据一个业务字段决定是否显示文本
text: id,
// ... 文本图形的其他配置
}
: undefined,
},
};
},
});
动画配置
在 v4 中需要为节点设置动画,必须使用自定义节点,再用图形的动画 API 进行配置。动画开始和结束的时机也难以控制。v5 提供了 JSON spec 的方式定义动画。您可以在上面介绍的 graph 配置的 node
/ edge
/ combo
字段中指定 animates
字段:
const graph = new Graph({
node: {
animates: {
buildIn: [...],
buildOut: [...],
update: [...],
show: [...],
hide: [...],
}
}
})
或 node
/ edge
/ combo
的函数式映射方式:
const graph = new Graph({
node: model => {
const { id, data } = model
return {
id,
data: {
...data,
// ... 其他样式配置
animates: {
buildIn: [...],
buildOut: [...],
update: [...],
show: [...],
hide: [...],
}
}
}
}
})
我们规范了动画的五个场景,发生在各个图形的不同时机:入场(buildId)、出场(buildOut)、update(数据/状态更新)、show(出现,相对于 hide)、hide(隐藏)。每个场景的可以为不同的图形、不同的字段指定动画,还可以指定动画的配置和执行顺序。例如,下面表达了指定各类更新时的各种图形的动画:
update: [
{
// 整个节点(shapeId: 'group')在 x、y 发生变化时,动画更新
fields: ['x', 'y'],
shapeId: 'group',
duration: 500,
},
{
// 在 selected 和 active 状态变化导致的 haloShape opacity 变化时,使 opacity 带动画地更新
fields: ['opacity'],
shapeId: 'haloShape',
states: ['selected', 'active'],
duration: 500,
},
// 当 keyShape 的 fill、r 同时发生变化时,按照 order 指定的顺序带动画地更新,可以实现依次动画的效果
{
fields: ['fill'],
shapeId: 'keyShape',
order: 0,
},
{
fields: ['r'],
shapeId: 'keyShape',
order: 1,
},
];
2️⃣. 信息分层
信息分层可以为复杂的图减少视觉干扰,在放大图后再显示详细信息。可以在上面介绍的 graph 配置的 node
/ edge
/ combo
字段中指定 lodStrategy
字段,如下面代码片段所示。其中 levels 定义了信息分层所响应的图缩放层级,animateCfg 配置由信息分层导致的图形变更时的动画方式。然后需要在不同的图形样式配置中配置 lod
字段,来指定该图形在 levels
对应的哪个层级显示。
const graph = new Graph({
node: {
lodStrategy: {
levels: [
{ zoomRange: [0, 0.5] }, // -1
{ zoomRange: [0.5, 1], primary: true }, // 0
{ zoomRange: [1, 1.5] }, // 1
{ zoomRange: [1.5, 1] }, // 2
{ zoomRange: [2, Infinity] }, // 3
],
animateCfg: {
duration: 500,
},
},
labelShape: {
lod: 1, // 图的缩放大于 levels 第一层定义的 zoomRange[0] 时展示,小于时隐藏
},
},
});
或使用 node
/ edge
/ combo
函数映射的方式配置:
const graph = new Graph({
node: (model) => {
const { id, data } = model;
const { isImportant } = data;
return {
id,
data: {
...data,
// ... 其他配置
lodStrategy: {
levels: [
{ zoomRange: [0, 0.5] }, // -1
{ zoomRange: [0.5, 1], primary: true }, // 0
{ zoomRange: [1, 1.5] }, // 1
{ zoomRange: [1.5, 1] }, // 2
{ zoomRange: [2, Infinity] }, // 3
],
animateCfg: {
duration: 500,
},
},
labelShape: {
lod: isImportant ? -1 : 2, // 可以根据业务属性来判断在什么层级显示 label。比如是重要节点,则在所有层级都显示文本,否则放大到一定程度后再显示
},
},
};
},
});
3️⃣. 主题配置
G6 内置了亮色、暗色主题,也可自定义。使用方式如下:
const graph = new Graph({
theme: {
type: 'spec', // 内置的主题解析器
base: 'light', // 使用亮色主题,暗色主题配置 base 为 'dark'
specification: {
node: {
dataTypeField: 'cluster', // 指定节点映射颜色的字段名称
// palette: ['#bae0ff', '#91caff', '#69b1ff', '#4096ff', '#1677ff', '#0958d9', '#003eb3', '#002c8c', '#001d66'], // 自定义色板
// palette: { a: '#f00', b: '#0f0', c: '#00f' }, // 可以为特殊的字段值指定颜色
// getStyleSets: (palette) => {
// // 更加自由的配置,针对不同的状态返回不同样式
// const styleSetsMap = {};
// Object.keys(palette).forEach((dataType) => {
// const color = palette[dataType];
// styleSetsMap[dataType] = {
// default: {
// keyShape: { fill: color },
// labelShape: { fill: color },
// },
// state1: {
// keyShape: { fill: '#000' },
// },
// state2: {
// keyShape: { stroke: '#f00' },
// },
// state3: {
// keyShape: { fill: '#ff0' },
// },
// };
// });
// return styleSetsMap;
// },
},
edge: {
dataTypeField: 'cluster', // 指定边映射颜色的字段
// ...其他
},
},
},
});
4️⃣. 数据处理
业务数据格式各异,可能不符合 G6 的数据格式。有时候,可能需要为数据提前计算一些字段,比如节点的度数等。此时,可以使用到 G6 v5 的数据处理模块。它作为 G6 v5 八大类型的扩展之一,在用户数据流入 Graph 之前执行。可以配置多个数据处理模块,它们将被线性执行。配置方法如下:
const graph = new Graph({
// ... 其他图配置
transforms: [
'transform-v4-data', // 内置的数据处理器,将 v4 的数据格式转换为 v5
{
// 内置的数据处理器,节点大小映射到节点数据的 value 字段上,大小范围归一化到 [4, 28]
type: 'map-node-size',
field: 'value',
range: [4, 28],
},
],
});
您可以根据自己的业务数据格式,自定义数据处理器,并注册到 Graph 上后使用:
import { Graph, extend } from '@antv/g6';
const CustomDataTransform = (data, options, userGraphCore) => {
data.nodes.forEach((node) => (node.data.cluster = node.data.bussinessState === '0' ? 'cluster1' : 'cluster2'));
data.edges.forEach((edge) => (edge.data.keyShape = { lineWidth: edge.data.weight / 2 }));
return data;
};
const ExtGraph = extend(Graph, {
transforms: {
'custom-data-transform': CustomDataTransform,
},
});
const graph = new ExtGraph({
// ... 其他图配置
transforms: [
'transform-v4-data', // 内置的数据处理器,将 v4 的数据格式转换为 v5
'custom-data-transform', // 使用自定义的数据处理器
],
});
5️⃣. 树图和图的融合
v5 新增树图相关 feature:
- 布局与 Graph 通用,Graph 可以指定根节点,使用最小生成树建立树结构后使用树图布局算法;
- 交互与 Graph 通用,Graph 也可以展开和收起“子树”了,即无回溯边的下游节点;
- 支持回溯边、环存在;
- 支持森林(多棵树)。
如果需要使用 TreeGraphData,只需要在配置 Graph 时给出一个数据类型的标记:
const graph = new Graph({
// ... 其他配置项
data: {
type: 'treeData', // type 可以是 'graphData'、'treeData'、'fetch',其中 fetch 将在正式版支持
value: data, // value 在 type 是 treeData 时,可以是 TreeGraphData 或 TreeGraphData[] 以支持森林的绘制
},
});
data
字段可以给 GraphData 类型的数据,那么 G6 将作为普通图处理,并在必要时(如使用树图布局、交互时)生成树图结构。也可以指定 type 为 'treeData' 后给 value 传入 TreeGraphData 类型的数据,那么 G6 将会存储树图结构,并转换为普通图数据进行存储。
6️⃣. 3D 大图
G6 v5 提供了 3D 大图渲染和交互能力,需要在 Graph 上配置 renderer: 'webgl-3d'
,并且配置对应的 3D 节点类型(目前仅支持 sphere-node)、3D 交互,即可使用:
import { Graph, Extensions, extend } from '@antv/g6';
const ExtGraph = extend(Graph, {
nodes: {
'sphere-node': Extensions.SphereNode,
},
behaviors: {
'orbit-canvas-3d': Extensions.OrbitCanvas3D,
'zoom-canvas-3d': Extensions.ZoomCanvas3D,
},
});
const graph = new ExtGraph({
renderer: 'webgl-3d', // 可以是 canvas, svg, webgl, webgl-3d
node: {
type: 'sphere-node',
},
modes: {
defaualt: ['orbit-canvas-3d', 'zoom-canvas-3d'],
},
// ...其他图配置
});
7️⃣. 性能飞跃 & 多渲染器
G6 支持了 WebGL 的 2D 和 3D 渲染,渲染性能得到极大提升。各个渲染器还可以在运行时切换。只需要在 Graph Shang 配置不同的 renderer 渲染器 DEMO。
const graph = new Graph({
// ...其他图配置
renderer: 'canvas', // 'canvas', 'svg', 'webgl', 'webgl-3d'
});
同时,G6 的布局包 @antv/layout 支持了 WASM 计算,使用时需要具体布局算法其从 @antv/layout-wasm 包引入,通过 extend
注册到 Graph 上,即可使用。WASM 布局 DEMO。
import { ForceLayout as ForceLayoutWASM, supportsThreads, initThreads } from '@antv/layout-wasm';
const ExtGraph = extend(Graph, {
layouts: {
'force-wasm': ForceLayoutWASM,
},
});
const supported = await supportsThreads();
const threads = await initThreads(supported);
const graph = new ExtGraph({
layout: {
type: 'force-wasm',
threads,
maxIteration: 200,
},
// ...其他图配置
});
8️⃣. 包体积减少
G6 v5 仅将最常用的功能默认注册到了 Graph 上,其他功能需要从 @antv/g6 或其他包中引入并注册到 Graph 上后,方可配置到 Graph 上.
import { Graph, extend, Extensions } from '@antv/g6';
// 外部引入的功能
import { ForceLayout as ForceLayoutWASM, supportsThreads, initThreads } from '@antv/layout-wasm';
// Class CustomBehaviorClass...
// Class CustomEdge...
const ExtGraph = extend(Graph, {
behaviors: {
'activate-relations': Extensions.ActivateRelations, // 内置的交互,未提前注册
'some-custom-behavior': CustomBehaviorClass, // 自定义交互
},
nodes: {
'modelRect-node': Extensions.ModelRectNode, // 内置的 modelRect 节点,未提前注册
},
edges: {
'custom-edge': CustomEdge, // 自定义边
},
layouts: {
'force-wasm': ForceLayoutWASM,
},
});
const supported = await supportsThreads();
const threads = await initThreads(supported);
// 使用 extend 后的图进行实例化
const graph = new ExtGraph({
// ... 其他配置项
modes: {
default: [
'drag-node', // 默认注册的交互
'activate-relations', // 刚刚引入并注册的内置交互
'some-custom-behavior', // 自定义并注册的交互
],
},
defaultNode: {
type: 'modelRect-node', // 刚刚引入并注册的内置节点类型
},
defaultEdge: {
type: 'custom-edge', // 自定义并注册的边类型
},
layout: {
type: 'force-wasm', // 刚刚从其他包引入并注册的布局算法
threads,
maxIteration: 200,
},
});
默认注册的功能有:
const stdLib = {
transforms: {
'validate-data': ValidateData, // 数据校验器,G6 内部将执行
'transform-v4-data': TransformV4Data, // 转换 v4 数据
'map-node-size': MapNodeSize, // 将节点大小映射到节点的某个指定的字段上
},
themes: {
light: LightTheme, // 亮色主题
dark: DarkTheme, // 暗色主题
},
themeSolvers: {
spec: SpecThemeSolver, // 默认的主题处理器
},
layouts: {
force: ForceLayout, // 力导向布局
grid: GridLayout, // 格子布局
circular: CircularLayout, // 环形布局
concentric: ConcentricLayout, // 同心圆布局
...Hierarchy, // 所有树图布局,包括 Dendropgram,Indented,Mindmap,CompactBox
},
behaviors: {
'drag-canvas': DragCanvas, // 拖拽画布
'zoom-canvas': ZoomCanvas, // 缩放画布
'drag-node': DragNode, // 拖拽节点
'drag-combo': DragCombo, // 拖拽 Combo
'collapse-expand-combo': CollapseExpandCombo, // 展开/收起 Combo
'collapse-expand-tree': CollapseExpandTree, // 展开/收起子树
'click-select': ClickSelect, // 点击选择
},
plugins: {
history: History, // 历史栈
},
nodes: {
'circle-node': CircleNode, // 圆形节点
'rect-node': RectNode, // 矩形边
},
edges: {
'line-edge': LineEdge, // 直线边
},
combos: {
'circle-combo': CircleCombo, // 圆形 Combo
'rect-combo': RectCombo, // 矩形 Combo
},
markers: {
// 一些常用的图标
collapse: (x, y, r) => {
return [
['M', x - r, y],
['a', r, r, 0, 1, 0, r * 2, 0],
['a', r, r, 0, 1, 0, -r * 2, 0],
['M', x - r + 4, y],
['L', x + r - 4, y],
];
},
expand: (x, y, r) => {
return [
['M', x - r, y],
['a', r, r, 0, 1, 0, r * 2, 0],
['a', r, r, 0, 1, 0, -r * 2, 0],
['M', x - r + 4, y],
['L', x - r + 2 * r - 4, y],
['M', x - r + r, y - r + 4],
['L', x, y + r - 4],
];
},
upTriangle: (x, y, r) => {
const l1 = r * Math.cos(Math.PI / 6);
const l2 = r * Math.sin(Math.PI / 6);
return [['M', x - l1, y + l2], ['L', x + l1, y + l2], ['L', x, y - r], ['Z']];
},
downTriangle: (x, y, r) => {
const l1 = r * Math.cos(Math.PI / 6);
const l2 = r * Math.sin(Math.PI / 6);
return [['M', x - l1, y - l2], ['L', x + l1, y - l2], ['L', x, y + r], ['Z']];
},
},
};