From bf4b248f71b5dca1c1aaf7c46689a4bba422f84a Mon Sep 17 00:00:00 2001 From: Susan <527971893@qq.com> Date: Sun, 26 Sep 2021 20:33:30 +0800 Subject: [PATCH] =?UTF-8?q?fix(interaction):=20=E6=B7=BB=E5=8A=A0=20venn?= =?UTF-8?q?=E5=9B=BE=20selected=20=E5=92=8C=20active=20=E7=9A=84=E4=BA=A4?= =?UTF-8?q?=E4=BA=92=20(#2871)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(interaction): 添加venn图的selected和active的交互 * test(venn): 添加 venn 图交互的单测和文档 * refactor(venn): 规范 venn interaction 的写法 Co-authored-by: 酥云 --- __tests__/unit/plots/venn/interaction-spec.ts | 151 ++++++++++++++++++ docs/api/plots/venn.en.md | 9 +- docs/api/plots/venn.zh.md | 9 +- examples/more-plots/venn/demo/interaction.ts | 37 +++++ examples/more-plots/venn/demo/meta.json | 9 ++ src/plots/venn/adaptor.ts | 1 + src/plots/venn/interaction/action.ts | 67 ++++++++ src/plots/venn/interaction/index.ts | 17 ++ 8 files changed, 298 insertions(+), 2 deletions(-) create mode 100644 __tests__/unit/plots/venn/interaction-spec.ts create mode 100644 examples/more-plots/venn/demo/interaction.ts create mode 100644 src/plots/venn/interaction/action.ts create mode 100644 src/plots/venn/interaction/index.ts diff --git a/__tests__/unit/plots/venn/interaction-spec.ts b/__tests__/unit/plots/venn/interaction-spec.ts new file mode 100644 index 0000000000..7560fc4a97 --- /dev/null +++ b/__tests__/unit/plots/venn/interaction-spec.ts @@ -0,0 +1,151 @@ +import { IGroup } from '@antv/g-base'; +import InteractionContext from '@antv/g2/lib/interaction/context'; +import { Venn } from '../../../../src'; +import { VennElementActive, VennElementSelected } from '../../../../src/plots/venn/interaction/action'; +import { createDiv } from '../../../utils/dom'; + +describe('venn', () => { + const data = [ + { sets: ['A'], size: 12, label: 'A' }, + { sets: ['B'], size: 12, label: 'B' }, + { sets: ['A', 'B'], size: 2, label: 'A&B' }, + ]; + + const plot = new Venn(createDiv(), { + data, + width: 400, + height: 500, + setsField: 'sets', + sizeField: 'size', + legend: false, + pointStyle: { + lineWidth: 0, + stroke: 'black', + }, + }); + plot.render(); + + it('venn: active', () => { + plot.update({ + state: { + active: { + style: { + lineWidth: 1, + }, + }, + }, + interactions: [{ type: 'venn-element-active', enable: true }], + }); + + const context = new InteractionContext(plot.chart); + const vennElementActive = new VennElementActive(context); + + // 模拟 active + context.event = { + data: { + data: plot.chart.getData()[0], + }, + }; + vennElementActive.active(); + + const elements = plot.chart.geometries[0].elements; + + expect(plot.getStates().length).toBe(1); + expect(plot.getStates()[0].state).toBe('active'); + expect(elements[0].getStates()[0]).toBe('active'); + expect((elements[0].shape as IGroup).getChildren()[0].attr('lineWidth')).toBe(1); + + vennElementActive.reset(); + expect(plot.getStates().length).toBe(0); + + // 模拟 第二次 active + context.event = { + data: { + data: plot.chart.getData()[1], + }, + }; + vennElementActive.active(); + + expect(plot.getStates().length).toBe(1); + expect(plot.getStates()[0].state).toBe('active'); + // 第一个元素 样式恢复 + expect(elements[0].getStates()[0]).toBeUndefined(); + expect((elements[0].shape as IGroup).getChildren()[0].attr('lineWidth')).toBe(0); + + // 第二个元素 有样式 + expect(elements[1].getStates()[0]).toBe('active'); + expect((elements[1].shape as IGroup).getChildren()[0].attr('lineWidth')).toBe(1); + + vennElementActive.reset(); + expect(plot.getStates().length).toBe(0); + + vennElementActive.destroy(); + }); + + it('venn: selected', () => { + plot.update({ + state: { + selected: { + style: { + lineWidth: 2, + }, + }, + }, + interactions: [ + { type: 'venn-element-active', enable: false }, + { type: 'venn-element-selected', enable: true }, + ], + }); + + const context = new InteractionContext(plot.chart); + const vennElementSelected = new VennElementSelected(context); + + // 模拟 selected + context.event = { + data: { + data: plot.chart.getData()[0], + }, + }; + vennElementSelected.toggle(); + + const elements = plot.chart.geometries[0].elements; + + // 第一个元素 点击 有样式 + expect(plot.getStates().length).toBe(1); + expect(plot.getStates()[0].state).toBe('selected'); + expect(elements[0].getStates()[0]).toBe('selected'); + expect((elements[0].shape as IGroup).getChildren()[0].attr('lineWidth')).toBe(2); + + // 再次点击 取消 selected + vennElementSelected.toggle(); + expect(plot.getStates().length).toBe(0); + expect(elements[0].getStates()[0]).toBeUndefined(); + expect((elements[0].shape as IGroup).getChildren()[0].attr('lineWidth')).toBe(0); + + // 模拟第二个元素的 selected + context.event = { + data: { + data: plot.chart.getData()[1], + }, + }; + vennElementSelected.toggle(); + + expect(plot.getStates().length).toBe(1); + expect(plot.getStates()[0].state).toBe('selected'); + expect(elements[1].getStates()[0]).toBe('selected'); + expect((elements[1].shape as IGroup).getChildren()[0].attr('lineWidth')).toBe(2); + + // 所有元素的 selected state 为 false + vennElementSelected.reset(); + expect(plot.getStates().length).toBe(0); + expect(elements[0].getStates().length).toBe(0); + expect(elements[1].getStates().length).toBe(0); + expect(elements[2].getStates().length).toBe(0); + + vennElementSelected.destroy(); + }); + + afterAll(() => { + plot.destroy(); + }); +}); diff --git a/docs/api/plots/venn.en.md b/docs/api/plots/venn.en.md index 1f8fde70f6..cfe9902037 100644 --- a/docs/api/plots/venn.en.md +++ b/docs/api/plots/venn.en.md @@ -44,7 +44,7 @@ The name of the data field corresponding to the point size map. **optional** _string_ -Color blend mode of the intersection area, default: `multiply`. Other: `darken`, `lighten`, `screen`, `overlay`, `burn`, and `dodge`. +Color blend mode of the intersection area, default: `multiply`. Other: `normal`, `darken`, `lighten`, `screen`, `overlay`, `burn`, and `dodge`. reference:https://gka.github.io/chroma.js/#chroma-blend #### pointStyle @@ -110,6 +110,13 @@ Default configuration: ### Plot Interactions +There are interactions for venn diagrams, listed below: + +| interaction | description | configuration method | +| ---|--|--| +| venn-element-active | enable the "mouse-over venn diagram element triggers active" interaction | `interactions:[{ type: 'venn-element-active', enabled: true }]` | +| venn-element-selected | enable the interaction "trigger selected when mouse clicked on venn diagram element", multiple options available | `interactions:[{ type: 'venn-element-selected', enabled: true }]` | + `markdown:docs/common/interactions.en.md` ### Plot Event diff --git a/docs/api/plots/venn.zh.md b/docs/api/plots/venn.zh.md index 14ccb22acc..1f9ff3f5ab 100644 --- a/docs/api/plots/venn.zh.md +++ b/docs/api/plots/venn.zh.md @@ -45,7 +45,7 @@ order: 12 **optional** _string_ -交集区域的颜色混合方式, 默认: `multiply`(正片叠底)。可选项: `multiply`, `darken`, `lighten`, `screen`, `overlay`, `burn`, and `dodge`. +交集区域的颜色混合方式, 默认: `multiply`(正片叠底)。可选项: `multiply`, `normal`, `darken`, `lighten`, `screen`, `overlay`, `burn`, and `dodge`. 参考:https://gka.github.io/chroma.js/#chroma-blend #### pointStyle @@ -110,6 +110,13 @@ order: 12 ### 图表交互 +内置了针对 venn 图交互,列表如下: + +| 交互 | 描述 | 配置方式 | +| ---|---|---| +| venn-element-active | 开启「鼠标移入 venn 图元素时触发 active」的交互 | `interactions:[{ type: 'venn-element-active', enabled: true }]` | +| venn-element-selected | 开启「鼠标点击 venn 图元素时触发 selected」的交互,可多选 | `interactions:[{ type: 'venn-element-selected', enabled: true }]` | + `markdown:docs/common/interactions.zh.md` ### 图表事件 diff --git a/examples/more-plots/venn/demo/interaction.ts b/examples/more-plots/venn/demo/interaction.ts new file mode 100644 index 0000000000..eda85ddb92 --- /dev/null +++ b/examples/more-plots/venn/demo/interaction.ts @@ -0,0 +1,37 @@ +import { Venn } from '@antv/g2plot'; + +const plot = new Venn('container', { + data: [ + { sets: ['A'], size: 12, label: 'A' }, + { sets: ['B'], size: 12, label: 'B' }, + { sets: ['C'], size: 12, label: 'C' }, + { sets: ['A', 'B'], size: 2, label: 'A&B' }, + { sets: ['A', 'C'], size: 2, label: 'A&C' }, + { sets: ['B', 'C'], size: 2, label: 'B&C' }, + { sets: ['A', 'B', 'C'], size: 1 }, + ], + setsField: 'sets', + sizeField: 'size', + pointStyle: { fillOpacity: 0.8 }, + padding: [0, 10], + state: { + active: { + style: { + fillOpacity: 1, + stroke: 'black', + lineWidth: 1, + }, + }, + selected: { + style: { + stroke: 'black', + lineWidth: 2, + }, + }, + }, + interactions: [ + { type: 'venn-element-active', enable: true }, + { type: 'venn-element-selected', enable: true }, + ], +}); +plot.render(); diff --git a/examples/more-plots/venn/demo/meta.json b/examples/more-plots/venn/demo/meta.json index de83076285..f1847faf33 100644 --- a/examples/more-plots/venn/demo/meta.json +++ b/examples/more-plots/venn/demo/meta.json @@ -48,6 +48,15 @@ }, "new": true, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/T6cgHx5BHB/f2137e3b-5784-4626-a986-109fc8cb5feb.png" + }, + { + "filename": "interaction.ts", + "title": { + "zh": "韦恩图 - 元素交互", + "en": "venn plot - with element action" + }, + "new": true, + "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/x%24Jxds2L2T/0ff5000b-d23e-49a8-a02a-4eb02d7a8e47.png" } ] } \ No newline at end of file diff --git a/src/plots/venn/adaptor.ts b/src/plots/venn/adaptor.ts index bcf1ad161a..dbd149acf6 100644 --- a/src/plots/venn/adaptor.ts +++ b/src/plots/venn/adaptor.ts @@ -19,6 +19,7 @@ import { CustomInfo, VennData, VennOptions } from './types'; import { ID_FIELD } from './constant'; import './shape'; import './label'; +import './interaction'; /** 图例默认预留空间 */ export const LEGEND_SPACE = 40; diff --git a/src/plots/venn/interaction/action.ts b/src/plots/venn/interaction/action.ts new file mode 100644 index 0000000000..ba6c95f5f4 --- /dev/null +++ b/src/plots/venn/interaction/action.ts @@ -0,0 +1,67 @@ +import { InteractionAction } from '@antv/g2'; + +class VennElementState extends InteractionAction { + /** tofront: 同步所有元素的位置 */ + protected syncElementsPos() { + const elements = this.context.view.geometries[0].elements; + elements.forEach((elem) => { + elem.shape.toFront(); + }); + } +} + +export class VennElementActive extends VennElementState { + /** hover */ + public active() { + const { data } = this.context.event.data; + const elements = this.context.view.geometries[0].elements; + + elements.forEach((elem) => { + const activeState = data === elem.getData(); + elem.setState('active', activeState); + }); + // tofront: 同步所有元素的位置 + this.syncElementsPos(); + } + + /** 重置 */ + public reset() { + const elements = this.context.view.geometries[0].elements; + + elements.forEach((elem) => { + // 所有元素的 state 统一 false + elem.setState('active', false); + }); + // tofront: 同步所有元素的位置 + this.syncElementsPos(); + } +} + +export class VennElementSelected extends VennElementState { + /** 切换 */ + public toggle() { + const { data } = this.context.event.data; + const elements = this.context.view.geometries[0].elements; + + elements.forEach((elem) => { + if (data === elem.getData()) { + const selectedState = elem.getStates().includes('selected'); + elem.setState('selected', !selectedState); + } + }); + // tofront: 同步所有元素的位置 + this.syncElementsPos(); + } + + /** 重置 */ + public reset() { + const elements = this.context.view.geometries[0].elements; + + elements.forEach((elem) => { + // 所有元素的 state 统一 false + elem.setState('selected', false); + }); + // tofront: 同步所有元素的位置 + this.syncElementsPos(); + } +} diff --git a/src/plots/venn/interaction/index.ts b/src/plots/venn/interaction/index.ts new file mode 100644 index 0000000000..50669daaf9 --- /dev/null +++ b/src/plots/venn/interaction/index.ts @@ -0,0 +1,17 @@ +import { registerInteraction, registerAction } from '@antv/g2'; +import { VennElementActive, VennElementSelected } from './action'; + +registerAction('venn-element-active', VennElementActive); +registerAction('venn-element-selected', VennElementSelected); + +// 移动到 venn elment 上的 active +registerInteraction('venn-element-active', { + start: [{ trigger: 'element:mouseenter', action: 'venn-element-active:active' }], + end: [{ trigger: 'element:mouseleave', action: 'venn-element-active:reset' }], +}); + +// 点击 venn element (可多选) +registerInteraction('venn-element-selected', { + start: [{ trigger: 'element:click', action: 'venn-element-selected:toggle' }], + rollback: [{ trigger: 'dblclick', action: ['venn-element-selected:reset'] }], +});