Skip to content

Commit

Permalink
feat(sankey): changeData API for sankey (#2367)
Browse files Browse the repository at this point in the history
* feat(sankey): add change data API

* test: add test case for change data

* chore: update code

* chore: merge master

* fix: ci
  • Loading branch information
hustcc authored Mar 1, 2021
1 parent a8e8219 commit 63eb818
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 86 deletions.
26 changes: 26 additions & 0 deletions __tests__/unit/plots/sankey/change-data-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Sankey } from '../../../../src';
import { createDiv } from '../../../utils/dom';
import { delay } from '../../../utils/delay';
import { ALIPAY_DATA } from '../../../data/sankey-energy';

describe('sankey', () => {
it('changeData', async () => {
const data = ALIPAY_DATA.slice(0, ALIPAY_DATA.length - 5);
const sankey = new Sankey(createDiv(), {
height: 500,
data,
sourceField: 'source',
targetField: 'target',
weightField: 'value',
});

sankey.render();
await delay(50);
sankey.changeData(ALIPAY_DATA);

expect(sankey.options.data).toEqual(ALIPAY_DATA);
expect(sankey.chart.views[0].getOptions().data.length).toBe(ALIPAY_DATA.length);

sankey.destroy();
});
});
17 changes: 15 additions & 2 deletions __tests__/unit/plots/sankey/index-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ describe('sankey', () => {

expect(sankey.options.appendPadding).toEqual(8);

expect(sankey.options.animation).toEqual({
appear: {
animation: 'wave-in',
},
enter: {
animation: 'wave-in',
},
});

// node
expect(sankey.chart.views[1].geometries[0].type).toBe('polygon');
expect(sankey.chart.views[1].geometries[0].data.length).toBe(48);
Expand Down Expand Up @@ -60,10 +69,14 @@ describe('sankey', () => {
);
expect(sankey.chart.views[0].geometries[0].labelsContainer.getChildren().length).toBe(0);

sankey.update({
animation: false,
});

// tooltip
sankey.chart.showTooltip({ x: 100, y: 100 });
expect(document.querySelector('.g2-tooltip-name').textContent).toBe('Nuclear -> Thermal generation');
expect(document.querySelector('.g2-tooltip-value').textContent).toBe('839.978');
expect(sankey.chart.ele.querySelector('.g2-tooltip-name').textContent).toBe('Nuclear -> Thermal generation');
expect(sankey.chart.ele.querySelector('.g2-tooltip-value').textContent).toBe('839.978');

sankey.destroy();
});
Expand Down
7 changes: 6 additions & 1 deletion __tests__/unit/plots/word-cloud/index-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,12 @@ describe('word-cloud', () => {
expect(chart.width).toBe(400);

chart.ele.style.width = `410px`;
await delay();

// @ts-ignore
cloud.triggerResize();

await delay(10);

expect(chart.width).toBe(410);
cloud.destroy();
});
Expand Down
10 changes: 6 additions & 4 deletions __tests__/unit/utils/transform/sankey-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ import { ENERGY } from '../../../data/sankey-energy';

describe('sankeyLayout', () => {
it('getNodeAlignFunction', () => {
expect(getNodeAlignFunction(null)).toBe(sankeyJustify);
expect(getNodeAlignFunction(undefined)).toBe(sankeyJustify);
expect(getNodeAlignFunction(null, null)).toBe(sankeyJustify);
expect(getNodeAlignFunction(undefined, null)).toBe(sankeyJustify);
// @ts-ignore
expect(getNodeAlignFunction('middle')).toBe(sankeyJustify);
expect(getNodeAlignFunction('middle', null)).toBe(sankeyJustify);

expect(getNodeAlignFunction('left')).toBe(sankeyLeft);
expect(getNodeAlignFunction('left', null)).toBe(sankeyLeft);

const fn = jest.fn();
// @ts-ignore
expect(getNodeAlignFunction(fn)).toBe(fn);

expect(getNodeAlignFunction(sankeyLeft, () => 1)).not.toBe(sankeyLeft);

// @ts-ignore
expect(getNodeAlignFunction(123)).toBe(sankeyJustify);
});
Expand Down
106 changes: 35 additions & 71 deletions src/plots/sankey/adaptor.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,18 @@
import { interaction, animation, theme } from '../../adaptor/common';
import { interaction, theme } from '../../adaptor/common';
import { Params } from '../../core/adaptor';
import { flow } from '../../utils';
import { sankeyLayout } from '../../utils/transform/sankey';
import { polygon, edge } from '../../adaptor/geometries';
import { transformDataToNodeLinkData } from '../../utils/data';
import { SankeyOptions } from './types';
import { X_FIELD, Y_FIELD, COLOR_FIELD } from './constant';
import { cutoffCircle } from './circle';
import { getNodePaddingRatio, getNodeWidthRatio } from './helper';
import { transformToViewsData } from './helper';

/**
* geometry 处理
* @param params
*/
function geometry(params: Params<SankeyOptions>): Params<SankeyOptions> {
const { chart, options } = params;
const {
data,
sourceField,
targetField,
weightField,
color,
nodeStyle,
edgeStyle,
label,
tooltip,
nodeAlign,
nodePaddingRatio,
nodePadding,
nodeWidthRatio,
nodeWidth,
nodeSort,
nodeDepth,
} = options;
const { color, nodeStyle, edgeStyle, label, tooltip } = options;

// 1. 组件,优先设置,因为子 view 会继承配置
chart.legend(false);
Expand All @@ -41,54 +21,13 @@ function geometry(params: Params<SankeyOptions>): Params<SankeyOptions> {
// y 镜像一下,防止图形顺序和数据顺序反了
chart.coordinate().reflect('y');

// 2. 转换出 layout 前数据
const sankeyLayoutInputData = transformDataToNodeLinkData(
cutoffCircle(data, sourceField, targetField),
sourceField,
targetField,
weightField
);

// 3. layout 之后的数据
const { nodes, links } = sankeyLayout(
{
nodeAlign,
// @ts-ignore
nodePadding: getNodePaddingRatio(nodePadding, nodePaddingRatio, chart.height),
// @ts-ignore
nodeWidth: getNodeWidthRatio(nodeWidth, nodeWidthRatio, chart.width),
nodeSort,
nodeDepth,
},
sankeyLayoutInputData
);

// 4. 生成绘图数据
const nodesData = nodes.map((node) => {
return {
x: node.x,
y: node.y,
name: node.name,
isNode: true,
};
});
const edgesData = links.map((link) => {
return {
source: link.source.name,
target: link.target.name,
name: link.source.name || link.target.name,
x: link.x,
y: link.y,
value: link.value,
isNode: false,
};
});

// 5. node edge views
// 2. node edge views
// @ts-ignore
const { nodes, edges } = transformToViewsData(options, chart.width, chart.height);

// edge view
const edgeView = chart.createView();
edgeView.data(edgesData);
const edgeView = chart.createView({ id: 'views' });
edgeView.data(edges);

edge({
chart: edgeView,
Expand All @@ -114,8 +53,8 @@ function geometry(params: Params<SankeyOptions>): Params<SankeyOptions> {
},
});

const nodeView = chart.createView();
nodeView.data(nodesData);
const nodeView = chart.createView({ id: 'nodes' });
nodeView.data(nodes);

polygon({
chart: nodeView,
Expand Down Expand Up @@ -144,6 +83,31 @@ function geometry(params: Params<SankeyOptions>): Params<SankeyOptions> {
return params;
}

/**
* 动画
* @param params
*/
export function animation(params: Params<SankeyOptions>): Params<SankeyOptions> {
const { chart, options } = params;
const { animation } = options;

// 同时设置整个 view 动画选项
if (typeof animation === 'boolean') {
chart.animate(animation);
} else {
chart.animate(true);
}

const geometries = [...chart.views[0].geometries, ...chart.views[1].geometries];

// 所有的 Geometry 都使用同一动画(各个图形如有区别,自行覆盖)
geometries.forEach((g) => {
g.animate(animation);
});

return params;
}

/**
* 图适配器
* @param chart
Expand Down
2 changes: 2 additions & 0 deletions src/plots/sankey/constant.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const X_FIELD = 'x';
export const Y_FIELD = 'y';
export const COLOR_FIELD = 'name';
export const NODES_VIEW_ID = 'nodes';
export const EDGES_VIEW_ID = 'views';
68 changes: 68 additions & 0 deletions src/plots/sankey/helper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { isRealNumber } from '../../utils/number';
import { transformDataToNodeLinkData } from '../../utils/data';
import { sankeyLayout } from '../../utils/transform/sankey';
import { cutoffCircle } from './circle';
import { SankeyOptions } from './types';

export function getNodeWidthRatio(nodeWidth: number, nodeWidthRatio: number, width: number) {
return isRealNumber(nodeWidth) ? nodeWidth / width : nodeWidthRatio;
Expand All @@ -7,3 +11,67 @@ export function getNodeWidthRatio(nodeWidth: number, nodeWidthRatio: number, wid
export function getNodePaddingRatio(nodePadding: number, nodePaddingRatio: number, height: number) {
return isRealNumber(nodePadding) ? nodePadding / height : nodePaddingRatio;
}

/**
* 将桑基图配置经过 layout,生成最终的 view 数据
* @param options
* @param width
* @param height
*/
export function transformToViewsData(options: SankeyOptions, width: number, height: number) {
const {
data,
sourceField,
targetField,
weightField,
nodeAlign,
nodeSort,
nodePadding,
nodePaddingRatio,
nodeWidth,
nodeWidthRatio,
nodeDepth,
} = options;

const sankeyLayoutInputData = transformDataToNodeLinkData(
cutoffCircle(data, sourceField, targetField),
sourceField,
targetField,
weightField
);

// 3. layout 之后的数据
const { nodes, links } = sankeyLayout(
{
nodeAlign,
nodePadding: getNodePaddingRatio(nodePadding, nodePaddingRatio, height),
nodeWidth: getNodeWidthRatio(nodeWidth, nodeWidthRatio, width),
nodeSort,
nodeDepth,
},
sankeyLayoutInputData
);

// 4. 生成绘图数据
return {
nodes: nodes.map((node) => {
return {
x: node.x,
y: node.y,
name: node.name,
isNode: true,
};
}),
edges: links.map((link) => {
return {
source: link.source.name,
target: link.target.name,
name: link.source.name || link.target.name,
x: link.x,
y: link.y,
value: link.value,
isNode: false,
};
}),
};
}
38 changes: 36 additions & 2 deletions src/plots/sankey/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { get } from '@antv/util';
import { Plot } from '../../core/plot';
import { Adaptor } from '../../core/adaptor';
import { Datum } from '../../types';
import { Data, Datum } from '../../types';
import { findViewById } from '../../utils';
import { SankeyOptions } from './types';
import { adaptor } from './adaptor';
import { transformToViewsData } from './helper';
import { EDGES_VIEW_ID, NODES_VIEW_ID } from './constant';

export { SankeyOptions };

Expand All @@ -14,7 +17,7 @@ export class Sankey extends Plot<SankeyOptions> {
/** 图表类型 */
public type: string = 'sankey';

protected getDefaultOptions() {
static getDefaultOptions(): Partial<SankeyOptions> {
return {
appendPadding: 8,
syncViewPadding: true,
Expand Down Expand Up @@ -65,13 +68,44 @@ export class Sankey extends Plot<SankeyOptions> {
},
nodeWidthRatio: 0.008,
nodePaddingRatio: 0.01,
animation: {
appear: {
animation: 'wave-in',
},
enter: {
animation: 'wave-in',
},
},
};
}

/**
* @override
* @param data
*/
public changeData(data: Data) {
this.updateOption({ data });

const { nodes, edges } = transformToViewsData(this.options, this.chart.width, this.chart.height);

const nodesView = findViewById(this.chart, NODES_VIEW_ID);
const edgesView = findViewById(this.chart, EDGES_VIEW_ID);

nodesView.changeData(nodes);
edgesView.changeData(edges);
}

/**
* 获取适配器
*/
protected getSchemaAdaptor(): Adaptor<SankeyOptions> {
return adaptor;
}

/**
* 获取 条形图 默认配置
*/
protected getDefaultOptions() {
return Sankey.getDefaultOptions();
}
}
Loading

0 comments on commit 63eb818

Please sign in to comment.