diff --git a/__tests__/unit/plots/sankey/state-spec.ts b/__tests__/unit/plots/sankey/state-spec.ts new file mode 100644 index 0000000000..e8d3fc626a --- /dev/null +++ b/__tests__/unit/plots/sankey/state-spec.ts @@ -0,0 +1,65 @@ +import { Datum, Sankey } from '../../../../src'; +import { createDiv, removeDom } from '../../../utils/dom'; +import { ENERGY_RELATIONS } from '../../../data/sankey-energy'; +import { EDGES_VIEW_ID, NODES_VIEW_ID } from '../../../../src/plots/sankey/constant'; + +describe('sankey', () => { + const div = createDiv(); + const sankey = new Sankey(div, { + height: 500, + data: ENERGY_RELATIONS, + sourceField: 'source', + targetField: 'target', + weightField: 'value', + }); + + sankey.render(); + it('state', () => { + sankey.update({ + nodeState: { + active: { + style: { + fill: 'red', + }, + }, + selected: { + style: { + stroke: 'red', + }, + }, + }, + edgeState: { + active: { + style: { + fill: 'blue', + }, + }, + }, + }); + + sankey.setState('active', (datum: Datum) => { + return datum.isNode; + }); + + let elements = sankey.chart.views.find((v) => v.id === NODES_VIEW_ID).geometries[0].elements; + elements.forEach((element) => expect(element.shape.attr('fill')).toBe('red')); + + sankey.setState('selected', (datum: Datum) => { + return datum.isNode; + }); + + elements = sankey.chart.views.find((v) => v.id === NODES_VIEW_ID).geometries[0].elements; + elements.forEach((element) => expect(element.shape.attr('stroke')).toBe('red')); + + sankey.setState('active', (datum: Datum) => { + return !datum.isNode; + }); + elements = sankey.chart.views.find((v) => v.id === EDGES_VIEW_ID).geometries[0].elements; + elements.forEach((element) => expect(element.shape.attr('fill')).toBe('blue')); + }); + + afterAll(() => { + sankey.destroy(); + removeDom(div); + }); +}); diff --git a/docs/api/plots/sankey.en.md b/docs/api/plots/sankey.en.md index 60d2ae81d2..149cc457b0 100644 --- a/docs/api/plots/sankey.en.md +++ b/docs/api/plots/sankey.en.md @@ -49,12 +49,28 @@ Raw fields of original data. With the 'rawsFields' definition, you can get the o Sankey diagram node style configuration. +#### nodeState + +**optional** _StyleAttr | Function_ + +State style configuration of Sankey node. + +`markdown:docs/common/state-style.zh.md` + #### edgeStyle **optional** _StyleAttr | Function_ Sankey diagram variable style configuration. +#### edgeState + +**optional** _StyleAttr | Function_ + +State style configuration of Sankey edge. + +`markdown:docs/common/state-style.zh.md` + `markdown:docs/common/color.en.md` #### nodeWidthRatio diff --git a/docs/api/plots/sankey.zh.md b/docs/api/plots/sankey.zh.md index e7c9013ebf..e32349cc04 100644 --- a/docs/api/plots/sankey.zh.md +++ b/docs/api/plots/sankey.zh.md @@ -49,6 +49,14 @@ order: 27 桑基图节点样式的配置。 +#### nodeState + +**optional** _StyleAttr | Function_ + +桑基图节点状态样式的配置。 + +`markdown:docs/common/state-style.zh.md` + #### edgeStyle **optional** _StyleAttr | Function_ @@ -57,6 +65,14 @@ order: 27 `markdown:docs/common/color.en.md` +#### edgeState + +**optional** _StyleAttr | Function_ + +桑基图边状态样式的配置。 + +`markdown:docs/common/state-style.zh.md` + #### nodeWidthRatio **optional** _number_ diff --git a/examples/relation-plots/sankey/demo/meta.json b/examples/relation-plots/sankey/demo/meta.json index c24c511711..4b5df3a200 100644 --- a/examples/relation-plots/sankey/demo/meta.json +++ b/examples/relation-plots/sankey/demo/meta.json @@ -12,6 +12,15 @@ }, "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*bLulSLk-VskAAAAAAAAAAAAAARQnAQ" }, + { + "filename": "set-state.ts", + "title": { + "zh": "桑基图状态交互", + "en": "Sankey with state" + }, + "new": true, + "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/rAuJKL%24Y6n/7a301a5e-4864-44f3-acb7-ebefb9c2402b.png" + }, { "filename": "energy.ts", "title": { diff --git a/examples/relation-plots/sankey/demo/set-state.ts b/examples/relation-plots/sankey/demo/set-state.ts new file mode 100644 index 0000000000..97d37f981a --- /dev/null +++ b/examples/relation-plots/sankey/demo/set-state.ts @@ -0,0 +1,40 @@ +import { Sankey } from '@antv/g2plot'; + +const DATA = [ + { source1: '首次打开', target: '首页 UV', value: 160 }, + { source1: '结果页', target: '首页 UV', value: 40 }, + { source1: '验证页', target: '首页 UV', value: 10 }, + { source1: '我的', target: '首页 UV', value: 10 }, + { source1: '朋友', target: '首页 UV', value: 8 }, + { source1: '其他来源', target: '首页 UV', value: 27 }, + { source1: '首页 UV', target: '理财', value: 30 }, + { source1: '首页 UV', target: '扫一扫', value: 40 }, + { source1: '首页 UV', target: '服务', value: 35 }, + { source1: '首页 UV', target: '蚂蚁森林', value: 25 }, + { source1: '首页 UV', target: '跳失', value: 10 }, + { source1: '首页 UV', target: '借呗', value: 30 }, + { source1: '首页 UV', target: '花呗', value: 40 }, + { source1: '首页 UV', target: '其他流向', value: 45 }, +]; + +const sankey = new Sankey('container', { + data: DATA, + sourceField: 'source1', + targetField: 'target', + weightField: 'value', + nodeWidthRatio: 0.018, + nodePaddingRatio: 0.03, + nodeState: { + active: { + style: { + linewidth: 1.5 + } + } + }, + tooltip: { showTitle: true } +}); + +sankey.render(); +sankey.setState('active', (datum) => { + return datum.isNode && datum.name === '首次打开' +}); diff --git a/src/plots/sankey/adaptor.ts b/src/plots/sankey/adaptor.ts index 3a6000684e..466b949e63 100644 --- a/src/plots/sankey/adaptor.ts +++ b/src/plots/sankey/adaptor.ts @@ -37,7 +37,7 @@ function defaultOptions(params: Params): Params { */ function geometry(params: Params): Params { const { chart, options } = params; - const { color, nodeStyle, edgeStyle, label, tooltip } = options; + const { color, nodeStyle, edgeStyle, label, tooltip, nodeState, edgeState } = options; // 1. 组件,优先设置,因为子 view 会继承配置 chart.legend(false); @@ -67,14 +67,7 @@ function geometry(params: Params): Params { shape: 'arc', }, tooltip, - state: { - active: { - style: { - opacity: 0.8, - lineWidth: 0, - }, - }, - }, + state: edgeState, }, }); @@ -93,6 +86,7 @@ function geometry(params: Params): Params { }, label, tooltip, + state: nodeState, }, }); diff --git a/src/plots/sankey/index.ts b/src/plots/sankey/index.ts index 16660b68d2..1352eacfd4 100644 --- a/src/plots/sankey/index.ts +++ b/src/plots/sankey/index.ts @@ -1,8 +1,9 @@ -import { get } from '@antv/util'; +import { get, each } from '@antv/util'; +import { Element } from '@antv/g2'; import { Plot } from '../../core/plot'; import { Adaptor } from '../../core/adaptor'; -import { Data, Datum } from '../../types'; -import { findViewById } from '../../utils'; +import { Data, Datum, StateCondition, StateName, StateObject } from '../../types'; +import { findViewById, getAllElementsRecursively } from '../../utils'; import { SankeyOptions } from './types'; import { adaptor } from './adaptor'; import { transformToViewsData } from './helper'; @@ -32,6 +33,14 @@ export class Sankey extends Plot { opacity: 0.3, lineWidth: 0, }, + edgeState: { + active: { + style: { + opacity: 0.8, + lineWidth: 0, + }, + }, + }, label: { formatter: ({ name }) => name, callback: (x: number[]) => { @@ -95,6 +104,40 @@ export class Sankey extends Plot { edgesView.changeData(edges); } + /** + * 设置状态 + * @param type 状态类型,支持 'active' | 'inactive' | 'selected' 三种 + * @param conditions 条件,支持数组 + * @param status 是否激活,默认 true + */ + public setState(type: StateName, condition: StateCondition, status: boolean = true) { + const elements = getAllElementsRecursively(this.chart); + + each(elements, (ele: Element) => { + if (condition(ele.getData())) { + ele.setState(type, status); + } + }); + } + + /** + * 获取状态 + */ + public getStates(): StateObject[] { + const elements = getAllElementsRecursively(this.chart); + + const stateObjects: StateObject[] = []; + each(elements, (element: Element) => { + const data = element.getData(); + const states = element.getStates(); + each(states, (state) => { + stateObjects.push({ data, state, geometry: element.geometry, element }); + }); + }); + + return stateObjects; + } + /** * 获取适配器 */ diff --git a/src/plots/sankey/types.ts b/src/plots/sankey/types.ts index 0766542482..75ad071592 100644 --- a/src/plots/sankey/types.ts +++ b/src/plots/sankey/types.ts @@ -1,4 +1,4 @@ -import { Data, Options, StyleAttr } from '../../types'; +import { Data, Options, State, StyleAttr } from '../../types'; import { NodeDepth, NodeSort } from './layout'; /** 配置类型定义 */ @@ -55,10 +55,18 @@ export interface SankeyOptions extends Omit