sankey
桑基图 (Sankey Diagram),是一种特定类型的流图,用于描述一组值到另一组值的流向。桑基图的特点如下:
起始流量和结束流量相同,所有主支宽度的总和与所有分出去的分支宽度总和相等,保持能量的平衡;
在内部,不同的线条代表了不同的流量分流情况,它的宽度成比例地显示此分支占有的流量;
节点不同的宽度代表了特定状态下的流量大小。
桑基图通常应用于能源、材料成分、金融等数据的可视化分析。
开始使用
import { Chart } from '@antv/g2';
const chart = new Chart({
container: 'container',
padding: 10,
});
chart.options({
type: 'sankey',
data: {
type: 'fetch',
value: 'https://assets.antv.antgroup.com/g2/energy.json',
transform: [
{
type: 'custom',
callback: (data) => ({ links: data }),
},
],
},
layout: {
nodeAlign: 'center',
nodePadding: 0.03,
},
scale: { color: { range: schemeTableau10 } },
style: {
labelSpacing: 3,
labelFontWeight: 'bold',
nodeStrokeWidth: 1.2,
linkFillOpacity: 0.4,
},
});
chart.render();
G2 中布局(Layout) 用于指定一些有特定布局函数标记的布局方法的参数,比如 Snakey, WordCloud, ForceGraph 等。
({
type: 'sankey',
layout: {
nodeAlign: 'center',
nodePadding: 0.03,
},
});
// API
chart.options({
type: 'sankey',
layout: { nodeAlign: 'center', nodePadding: 0.03 },
});
数据更新
桑基图支持动态更新数据,使用 G2 内置的 API changeData() 更新数据:
const newData = {
links: [
{ source: 'A', target: 'B', value: 10 },
{ source: 'A', target: 'C', value: 15 },
{ source: 'B', target: 'D', value: 8 },
{ source: 'C', target: 'D', value: 12 },
],
};
chart.changeData({ type: 'inline', value: newData });
语法糖(推荐)
同时 G2 也提供了便捷的语法糖,你可以像配置其他图表一样,直接传递数组数据:
const newData = [
{ source: 'A', target: 'X', value: 10 },
{ source: 'A', target: 'Y', value: 15 },
{ source: 'B', target: 'X', value: 20 },
{ source: 'B', target: 'Y', value: 25 },
];
// 直接传递数组
chart.changeData(newData);
空数据处理
当传入空数组时或者不传入 links,图表将显示为空白状态:
// 清空图表 - 图表将显示为空白状态
chart.changeData([]);
// 或者
chart.changeData({ links: [] });
桑基图数据更新示例
import { Chart } from '@antv/g2';
const chart = new Chart({ container: 'container' });
const initialData = [
{ source: 'A', target: 'X', value: 10 },
{ source: 'A', target: 'Y', value: 15 },
{ source: 'B', target: 'X', value: 20 },
];
chart.options({
type: 'sankey',
data: {
type: 'inline',
value: initialData,
},
});
chart.render();
// 点击事件:随机更新数据
chart.on('element:click', () => {
const randomData = initialData.map((d) => ({
...d,
value: Math.random() * 30 + 5,
}));
// 使用简化语法更新数据
chart.changeData(randomData);
});
选项
| 属性 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| tooltip | 配置桑基图的 tooltip,详见 tooltip 配置 | object | 详见 tooltip 配置 |
| layout | 配置桑基图的布局方式,详见 layout 配置 | object | 详见 layout 配置 |
| style | 配置图形样式和标签样式,详见 style 配置 | object | 详见 style 配置 |
| nodeLabels | 自定义节点数据标签的配置,详见 nodeLabels 配置 | label[] | [] |
| linkLabels | 自定义连接线数据标签的配置,详见 linkLabels 配置 | label[] | [] |
tooltip
桑基图作为复合图形,配置 tooltip 的时候需要区分节点和连接线。
title
不同于单一标记配置 title,桑基图需要分别配置 nodeTitle 和 linkTitle 。
({
tooltip: {
nodeTitle: (d) => d.key,
linkTitle: (d) => 'link',
},
});
items
不同于单一标记配置 items,桑基图需要同时配置 nodeItems 和 linkItems 。
和一般的 items 一样, nodeItems 和 linkItems 也支持自定义配置:
({
tooltip: {
nodeItems: [
(d, index, data, column) => {
return {
color: 'red', // 指定 item 的颜色
name: '节点', // 指定 item 的名字
value: d.key, // 使用 y 通道的值
content: '节点自定义属性',
};
},
],
linkItems: [
(d, index, data, column) => {
return {
color: 'red', // 指定 item 的颜色
name: '连接线', // 指定 item 的名字
value: `${d.source.key}-${d.target.key}`, // 使用 y 通道的值
content: '连接线自定义属性',
};
},
],
},
});
💡 桑基图怎么使用 data 中的补充属性实现自定义 tooltip 的展示?
和一般 Mark 自定义 tooltip 交互的方法类似,先在图形的 tooltip 里传入自定义属性,然后在 interaction 里使用。
示例:
import { Chart } from '@antv/g2';
const chart = new Chart({
container: 'container',
});
const data = {
nodes: [
{ id: 'a', key: '首页', des: '节点自定义属性' },
{ id: 'b', key: '页面1', des: '节点自定义属性' },
{ id: 'b_1', key: '页面1', des: '节点自定义属性' },
{ id: 'c', key: '页面2', des: '节点自定义属性' },
{ id: 'c_1', key: '页面2', des: '节点自定义属性' },
{ id: 'd', key: '页面3', des: '节点自定义属性' },
{ id: 'd_1', key: '页面3', des: '节点自定义属性' },
],
links: [
{ source: 'a', target: 'b', value: 100 },
{ source: 'b', target: 'c', value: 80 },
{ source: 'b', target: 'd', value: 20 },
{ source: 'c', target: 'b_1', value: 80 },
{ source: 'b_1', target: 'c_1', value: 40 },
{ source: 'b_1', target: 'd_1', value: 40 },
],
};
chart.options({
type: 'sankey',
width: 900,
height: 600,
data: {
value: data,
transform: [
{
type: 'custom',
callback: (data) => ({
nodes: data.nodes,
links: data.links,
}),
},
],
},
tooltip: {
nodeItems: [
(d, index, data, column) => {
return {
content: d.des,
};
},
],
linkItems: [
(d, index, data, column) => {
return {
color: 'red', // 指定 item 的颜色
name: '连接线', // 指定 item 的名字
value: `${d.source.key}-${d.target.key}`, // 使用 y 通道的值
content: '连接线自定义属性',
};
},
],
},
layout: {
nodeId: (d) => d.id,
nodeAlign: 'center',
nodePadding: 0.03,
iterations: 25,
},
style: {
labelSpacing: 3,
labelFontWeight: 'bold',
// linkFillOpacity: 0.2,
// linkFill: '#3F96FF',
},
interaction: {
tooltip: {
render: (e, { items, title }) => {
return `<div>${items[0].content}</div>`;
},
},
},
});
chart.render();
layout
桑基图的布局方式。具体配置项如下:
nodeId
<description>optional function </description>
回调的方式为:(node: any) => string,如果未指定 nodeId,默认为 (node) => node.key。
节点绑定字段,在布局中作为唯一标识。
💡 桑基图不支持成环,那在页面流向图这种会出现重复节点的情况应该怎么配置?
对于多次出现的节点,设置 id 作为唯一标识,并配置 nodeId 的回调方法为 (node) => node.id。
示例:
import { Chart } from '@antv/g2';
const chart = new Chart({
container: 'container',
});
const data = {
nodes: [
{ id: 'a', key: '首页' },
{ id: 'b', key: '页面1' },
{ id: 'b_1', key: '页面1' },
{ id: 'c', key: '页面2' },
{ id: 'c_1', key: '页面2' },
{ id: 'd', key: '页面3' },
{ id: 'd_1', key: '页面3' },
],
links: [
{ source: 'a', target: 'b', value: 100 },
{ source: 'b', target: 'c', value: 80 },
{ source: 'b', target: 'd', value: 20 },
{ source: 'c', target: 'b_1', value: 80 },
{ source: 'b_1', target: 'c_1', value: 40 },
{ source: 'b_1', target: 'd_1', value: 40 },
],
};
chart.options({
type: 'sankey',
width: 900,
height: 600,
data: {
value: data,
transform: [
{
type: 'custom',
callback: (data) => ({
nodes: data.nodes,
links: data.links,
}),
},
],
},
layout: {
nodeId: (d) => d.id,
nodeAlign: 'center',
nodePadding: 0.03,
iterations: 25,
},
style: {
labelSpacing: 3,
labelFontWeight: 'bold',
linkFillOpacity: 0.2,
linkFill: '#3F96FF',
},
});
chart.render();
nodeSort
<description>optional function </description>
回调的方式为:((a: any, b: any) => number)
节点排序方式。如果未指定 nodeSort ,则返回当前节点排序方法,默认为 undefined,表示每列内的节点垂直顺序将由布局自动确定。如果 nodeSort 为 null,则顺序由输入固定。否则,由指定的排序函数确定顺序;该函数传递两个节点,如果第一个节点应位于第二个节点上方,则必须返回小于 0 的值,如果第二个节点应位于第一个节点上方,则必须返回大于 0 的值,如果未指定顺序,则返回 0。
linkSort
<description> optional function </description>
回调的方式为:((a: any, b: any) => number)
连接线排序方式。如果未指定 linkSort ,则返回当前连接线排序方法,默认为 undefined,表示每个节点内的连接线的垂直顺序将由布局自动确定。如果 linkSort 为 null,则顺序由输入固定。否则,由指定的排序函数确定顺序;该函数传递两个连接线,如果第一个连接线应位于第二个连接线上方,则必须返回小于 0 的值,如果第二个连接线应位于第一个连接线上方,则必须返回大于 0 的值,如果未指定顺序,则返回 0。
nodeAlign
<description>optional string | function </description>
内置支持的类型有: 'left' | 'right' | 'center' | 'justify'
回调的方式为:((node: any, n: number) => number
当前节点的对齐方法。除了内置的几种类型外,还可以传递当前节点和图的总深度 n (最大的节点深度+1 ),并且必须返回 0 到 n - 1 之间的整数,指示节点在生成图中所需的水平位置。
nodeWidth
<description>optional number </description>
节点的宽度。默认为 0.02。
nodePadding
<description>optional number </description>
节点的间距。默认为 0.02。
nodeDepth
<description>optional function </description>
回调的方式为:(datum: any, maxDepth: number) => number
节点的深度。
iterations
<description>optional number </description>
布局计算迭代次数,默认为 6, 次数越多,布局越合理。
更多 layout 配置,详见 d3-sankey
style
默认 style 配置:
({
// label
labelText: (d) => d.key,
labelSpacing: 5,
labelFontSize: 10,
// node
nodeStroke: '#000',
// link
linkFillOpacity: 0.5,
linkStroke: undefined,
});
复合图形标记需要通过不同的前缀来区分图形的配置。
<label>: 配置数据标签的前缀。
| 属性名 | 类型 | 介绍 |
|---|---|---|
| labelText | (d: any) => string | 桑基图配置默认的数据标签的值,默认为 (d) => d.key |
| labelSpacing | number | 桑基图配置数据标签的间距,默认为 5 |
| labelFontSize | number | 桑基图数据标签文字大小 |
| labelFontFamily | string | 桑基图数据标签文字字体 |
| labelFontWeight | number | 桑基图数据标签字体粗细 |
| labelLineHeight | number | 桑基图数据标签文字的行高 |
| labelTextAlign | string | 设置桑基图数据标签文本内容的当前对齐方式, 支持的属性:center | end | left | right | start,默认值为start |
| labelTextBaseline | string | 设置桑基图数据标签在绘制文本时使用的当前文本基线, 支持的属性:top | middle | bottom | alphabetic | hanging。默认值为bottom |
| labelFill | string | 桑基图数据标签文字的填充色 |
| labelFillOpacity | number | 桑基图数据标签文字的填充透明度 |
| labelStroke | string | 桑基图数据标签文字的描边 |
| labelLineWidth | number | 桑基图数据标签文字描边的宽度 |
| labelLineDash | [number,number] | 桑基图数据标签描边的虚线配置,第一个值为虚线每个分段的长度,第二个值为分段间隔的距离。lineDash 设为[0,0]的效果为没有描边。 |
| labelStrokeOpacity | number | 桑基图数据标签描边透明度 |
| labelOpacity | number | 桑基图数据标签文字的整体透明度 |
| labelShadowColor | string | 桑基图数据标签文字阴影颜色 |
| labelShadowBlur | number | 桑基图数据标签文字阴影的高斯模糊系数 |
| labelShadowOffsetX | number | 设置桑基图数据标签阴影距文字的水平距离 |
| labelShadowOffsetY | number | 设置桑基图数据标签阴影距文字的垂直距离 |
| labelCursor | string | 桑基图数据标签鼠标样式。同 css 的鼠标样式,默认 ‘default’。 |
<node>: 配置节点的前缀。
| 属性名 | 类型 | 介绍 |
|---|---|---|
| nodeFill | string | 桑基图节点填充色 |
| nodeFillOpacity | number | 桑基图节点填充透明度 |
| nodeStroke | string | 桑基图节点的描边 |
| nodeStrokeOpacity | number | 桑基图节点描边透明度 |
| nodeLineWidth | number | 桑基图节点描边的宽度 |
| nodeLineDash | [number,number] | 桑基图节点描边的虚线配置,第一个值为虚线每个分段的长度,第二个值为分段间隔的距离。lineDash 设为[0,0]的效果为没有描边。 |
| nodeOpacity | number | 桑基图节点的整体透明度 |
| nodeShadowColor | string | 桑基图节点阴影颜色 |
| nodeShadowBlur | number | 桑基图节点阴影的高斯模糊系数 |
| nodeShadowOffsetX | number | 设置阴影距桑基图节点的水平距离 |
| nodeShadowOffsetY | number | 设置阴影距桑基图节点的垂直距离 |
| nodeCursor | string | 桑基图节点鼠标样式。同 css 的鼠标样式,默认 ‘default’。 |
<link>: 配置连接线的前缀。
| 属性名 | 类型 | 介绍 |
|---|---|---|
| linkFill | string | 桑基图连接线填充色 |
| linkFillOpacity | number | 桑基图连接线填充透明度 |
| linkStroke | string | 桑基图连接线的描边 |
| linkStrokeOpacity | number | 桑基图连接线描边透明度 |
| linkLineWidth | number | 桑基图连接线描边的宽度 |
| linkLineDash | [number,number] | 桑基图连接线描边的虚线配置,第一个值为虚线每个分段的长度,第二个值为分段间隔的距离。lineDash 设为[0,0]的效果为没有描边。 |
| linkOpacity | number | 桑基图连接线的整体透明度 |
| linkShadowColor | string | 桑基图连接线阴影颜色 |
| linkShadowBlur | number | 桑基图连接线阴影的高斯模糊系数 |
| linkShadowOffsetX | number | 设置阴影距桑基图连接线的水平距离 |
| linkShadowOffsetY | number | 设置阴影距桑基图连接线的垂直距离 |
| linkCursor | string | 桑基图连接线鼠标样式。同 css 的鼠标样式,默认 ‘default’。 |
nodeLabels
<description>optional Label[] </description>
内置数据标签的配置如下。
({
labels: [
{
text,
dx: (d) => (d.x[0] < 0.5 ? spacing : -spacing),
...labelStyle, // 用户传入的数据标签自定义样式
},
...nodeLabels, // 用户传入的自定义数据标签
],
});
除了节点内置的数据标签以外,你还可以自定义节点数据标签的配置。
({
nodeLabels: [
{
text: (d) => d.key,
fontSize: 10, // 注意!!!这里的绘图属性不再需要加label前缀
fill: 'red',
},
],
});
linkLabels
<description>optional Label[] </description>
连接线没有内置的数据标签,你可以自定义连接线数据标签的配置。
({
linkLabels: [
{
text: (d) => d.key,
fontSize: 10, // 注意!!!这里的绘图属性不再需要加label前缀
fill: 'yellow',
},
],
});
state
state 配置和 style 类似,使用不同的前缀来区分不同的图形配置,没有前缀的配置两种图形都会生效。
示例:
import * as G2 from '@antv/g2';
(() => {
const chart = new G2.Chart({ container: 'container' });
const data = {
links: [
{ source: 'a', target: 'b', value: 100 },
{ source: 'b', target: 'c', value: 80 },
{ source: 'b', target: 'd', value: 20 },
{ source: 'c', target: 'b_1', value: 80 },
{ source: 'b_1', target: 'c_1', value: 40 },
{ source: 'b_1', target: 'd_1', value: 40 },
],
};
chart.options({
type: 'sankey',
width: 900,
height: 600,
data: {
value: data,
},
style: {
labelSpacing: 3,
labelFontWeight: 'bold',
linkFillOpacity: 0.5,
nodeFillOpacity: 0.5, //默认透明度都是 0.5
},
state: {
active: {
fillOpacity: 0.8, // 鼠标悬浮状态下透明度都是 0.8
linkFill: 'red', // link 会变成红色
nodeFill: 'blue', // node 会变成蓝色
},
inactive: {
linkFillOpacity: 0.4,
nodeFillOpacity: 0.2, // node 颜色比 link 浅
},
},
interaction: {
elementHighlight: true,
},
});
chart.render();
return chart.getContainer();
})();