From ed5b71e22492b94d1157a6cb7c3f92e130ee1e45 Mon Sep 17 00:00:00 2001
From: Visiky <736929286@qq.com>
Date: Sat, 16 Oct 2021 00:11:16 +0800
Subject: [PATCH] =?UTF-8?q?feat(venn):=20=E9=9F=A6=E6=81=A9=E5=9B=BE?=
=?UTF-8?q?=E4=BA=A4=E4=BA=92=E5=A2=9E=E5=BC=BA=EF=BC=88=E4=BF=AE=E5=A4=8D?=
=?UTF-8?q?=E5=9B=BE=E4=BE=8B=E6=BF=80=E6=B4=BB=E5=85=83=E7=B4=A0=E4=BA=A4?=
=?UTF-8?q?=E4=BA=92=20&=20=E5=A2=9E=E5=BC=BAactive=E3=80=81highlight?=
=?UTF-8?q?=E3=80=81selected=E4=BA=A4=E4=BA=92=EF=BC=89=20(#2911)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
__tests__/bugs/issue-2908-spec.ts | 51 ++++++++
__tests__/unit/plots/venn/interaction-spec.ts | 119 +++++++++++++++---
docs/api/plots/venn.en.md | 12 +-
docs/api/plots/venn.zh.md | 25 ++--
.../more-plots/venn/demo/element-active.ts | 103 +++++++++++++++
.../more-plots/venn/demo/element-highlight.ts | 103 +++++++++++++++
examples/more-plots/venn/demo/interaction.ts | 15 ---
.../more-plots/venn/demo/legend-active.ts | 27 ++++
.../more-plots/venn/demo/legend-highlight.ts | 33 +++++
examples/more-plots/venn/demo/meta.json | 36 ++++++
package.json | 2 +-
src/core/plot.ts | 4 +-
src/plots/venn/adaptor.ts | 44 +++++--
src/plots/venn/constant.ts | 28 ++++-
src/plots/venn/interaction/action.ts | 67 ----------
src/plots/venn/interaction/index.ts | 17 ---
src/plots/venn/interactions/actions/active.ts | 31 +++++
.../venn/interactions/actions/highlight.ts | 37 ++++++
.../venn/interactions/actions/selected.ts | 65 ++++++++++
src/plots/venn/interactions/index.ts | 55 ++++++++
src/plots/venn/interactions/util.ts | 12 ++
src/plots/venn/utils.ts | 14 ++-
src/types/attr.ts | 2 +-
src/types/common.ts | 2 +
24 files changed, 758 insertions(+), 146 deletions(-)
create mode 100644 __tests__/bugs/issue-2908-spec.ts
create mode 100644 examples/more-plots/venn/demo/element-active.ts
create mode 100644 examples/more-plots/venn/demo/element-highlight.ts
create mode 100644 examples/more-plots/venn/demo/legend-active.ts
create mode 100644 examples/more-plots/venn/demo/legend-highlight.ts
delete mode 100644 src/plots/venn/interaction/action.ts
delete mode 100644 src/plots/venn/interaction/index.ts
create mode 100644 src/plots/venn/interactions/actions/active.ts
create mode 100644 src/plots/venn/interactions/actions/highlight.ts
create mode 100644 src/plots/venn/interactions/actions/selected.ts
create mode 100644 src/plots/venn/interactions/index.ts
create mode 100644 src/plots/venn/interactions/util.ts
diff --git a/__tests__/bugs/issue-2908-spec.ts b/__tests__/bugs/issue-2908-spec.ts
new file mode 100644
index 0000000000..7b315c5aad
--- /dev/null
+++ b/__tests__/bugs/issue-2908-spec.ts
@@ -0,0 +1,51 @@
+import { Venn } from '../../src';
+import { createDiv } from '../utils/dom';
+
+describe('#2908, venn', () => {
+ const plot = new Venn(createDiv(), {
+ width: 400,
+ height: 500,
+ setsField: 'sets',
+ sizeField: 'size',
+ data: [
+ { sets: ['A'], size: 10, label: 'A' },
+ { sets: ['B'], size: 10, label: 'B' },
+ ],
+ interactions: [{ type: 'legend-active', enable: true }],
+ });
+ plot.render();
+
+ it('legend interaction', () => {
+ let labels = plot.chart.geometries[0].elements[0].shape
+ .getParent()
+ .getChildren()
+ .map((c) => c.get('origin').data.label);
+
+ expect(labels[0]).toBe('A');
+ expect(labels[1]).toBe('B');
+
+ const legendComponent = plot.chart.getController('legend').getComponents()[0];
+ const legendContainer = legendComponent.component.get('container');
+
+ const legendTarget = legendContainer.findById('-legend-item-A');
+ const box = legendTarget.getBBox();
+ plot.chart.emit('legend-item:mouseenter', {
+ x: (box.x + box.maxX) / 2,
+ y: (box.y + box.maxY) / 2,
+ target: legendTarget,
+ });
+
+ // 图例交互,还是保持原序
+ labels = plot.chart.geometries[0].elements[0].shape
+ .getParent()
+ .getChildren()
+ .map((c) => c.get('origin').data.label);
+
+ expect(labels[0]).toBe('A');
+ expect(labels[1]).toBe('B');
+ });
+
+ afterAll(() => {
+ plot.destroy();
+ });
+});
diff --git a/__tests__/unit/plots/venn/interaction-spec.ts b/__tests__/unit/plots/venn/interaction-spec.ts
index 7560fc4a97..073485a7ae 100644
--- a/__tests__/unit/plots/venn/interaction-spec.ts
+++ b/__tests__/unit/plots/venn/interaction-spec.ts
@@ -1,7 +1,12 @@
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 { VennElementActive } from '../../../../src/plots/venn/interactions/actions/active';
+import { VennElementHighlight } from '../../../../src/plots/venn/interactions/actions/highlight';
+import {
+ VennElementSelected,
+ VennElementSingleSelected,
+} from '../../../../src/plots/venn/interactions/actions/selected';
import { createDiv } from '../../../utils/dom';
describe('venn', () => {
@@ -38,13 +43,12 @@ describe('venn', () => {
});
const context = new InteractionContext(plot.chart);
+ // @ts-ignore
const vennElementActive = new VennElementActive(context);
// 模拟 active
context.event = {
- data: {
- data: plot.chart.getData()[0],
- },
+ target: plot.chart.getElements()[0].shape,
};
vennElementActive.active();
@@ -60,9 +64,7 @@ describe('venn', () => {
// 模拟 第二次 active
context.event = {
- data: {
- data: plot.chart.getData()[1],
- },
+ target: plot.chart.getElements()[1].shape,
};
vennElementActive.active();
@@ -82,7 +84,7 @@ describe('venn', () => {
vennElementActive.destroy();
});
- it('venn: selected', () => {
+ it('venn-element-selected', () => {
plot.update({
state: {
selected: {
@@ -98,13 +100,12 @@ describe('venn', () => {
});
const context = new InteractionContext(plot.chart);
+ // @ts-ignore
const vennElementSelected = new VennElementSelected(context);
// 模拟 selected
context.event = {
- data: {
- data: plot.chart.getData()[0],
- },
+ target: plot.chart.getElements()[0].shape,
};
vennElementSelected.toggle();
@@ -124,9 +125,7 @@ describe('venn', () => {
// 模拟第二个元素的 selected
context.event = {
- data: {
- data: plot.chart.getData()[1],
- },
+ target: plot.chart.getElements()[1].shape,
};
vennElementSelected.toggle();
@@ -145,6 +144,98 @@ describe('venn', () => {
vennElementSelected.destroy();
});
+ it('venn-element-single-selected', () => {
+ plot.update({
+ state: {
+ selected: {
+ style: {
+ lineWidth: 2,
+ },
+ },
+ },
+ interactions: [
+ { type: 'venn-element-selected', enable: false },
+ { type: 'venn-element-single-selected', enable: true },
+ ],
+ });
+
+ const context = new InteractionContext(plot.chart);
+ // @ts-ignore
+ const vennElementSelected = new VennElementSingleSelected(context);
+
+ // 模拟 selected
+ context.event = {
+ target: plot.chart.getElements()[0].shape,
+ };
+ vennElementSelected.selected();
+
+ 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
+ context.event = {
+ target: plot.chart.getElements()[1].shape,
+ };
+ vennElementSelected.selected();
+ expect(plot.getStates().length).toBe(1);
+ expect(elements[0].getStates()[0]).toBeUndefined();
+ expect((elements[0].shape as IGroup).getChildren()[0].attr('lineWidth')).toBe(0);
+ expect(elements[1].getStates()[0]).toBe('selected');
+
+ // 所有元素的 selected state 为 false
+ vennElementSelected.reset();
+ vennElementSelected.destroy();
+ });
+
+ it('venn-element-highlight', () => {
+ plot.update({
+ state: {
+ inactive: {
+ style: {
+ fillOpacity: 0.3,
+ },
+ },
+ },
+ interactions: [
+ { type: 'venn-element-single-selected', enable: false },
+ { type: 'venn-element-active', enable: false },
+ { type: 'venn-element-highlight', enable: true },
+ ],
+ });
+
+ const context = new InteractionContext(plot.chart);
+ // @ts-ignore
+ const action = new VennElementHighlight(context);
+
+ // 模拟 selected
+ context.event = {
+ target: plot.chart.getElements()[0].shape,
+ };
+ action.highlight();
+ const elements = plot.chart.geometries[0].elements;
+ // 第一个元素 点击 有样式
+ expect(elements[0].getStates()[0]).toBe('active');
+ expect((elements[0].shape as IGroup).getChildren()[0].attr('fillOpacity')).not.toBe(0.3);
+ expect(elements[1].getStates()[0]).toBe('inactive');
+ expect((elements[1].shape as IGroup).getChildren()[0].attr('fillOpacity')).toBe(0.3);
+ action.toggle();
+
+ context.event = {
+ target: plot.chart.getElements()[1].shape,
+ };
+ action.toggle();
+ expect(elements[0].getStates().includes('inactive')).toBe(true);
+ expect((elements[0].shape as IGroup).getChildren()[0].attr('fillOpacity')).toBe(0.3);
+ expect((elements[1].shape as IGroup).getChildren()[0].attr('fillOpacity')).not.toBe(0.3);
+
+ action.destroy();
+ });
+
afterAll(() => {
plot.destroy();
});
diff --git a/docs/api/plots/venn.en.md b/docs/api/plots/venn.en.md
index 2e5c1d718a..99cd7b1ce5 100644
--- a/docs/api/plots/venn.en.md
+++ b/docs/api/plots/venn.en.md
@@ -24,6 +24,10 @@ Configure the chart data source. For example:
];
```
+```sign
+💡 注意:这里的数据是包含交集部分的数据量的。如上数据源,含有两个集合:`A` 和 `B`, 其中:`{ sets: ['A'], size: 5 }` 代表的是含有 A 集合的有 5 个(其实有 2 个是包含 B 集合的)
+```
+
#### setsField
**optional** _string_
@@ -135,14 +139,16 @@ Default configuration:
`markdown:docs/common/tooltip.en.md`
-### Plot Interactions
+### 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 }]` |
+| venn-element-active | enable the "mouse-over venn diagram element triggers active" interaction | `interactions:[{ type: 'venn-element-active'}]` |
+| venn-element-selected | enable the interaction "trigger selected when mouse clicked on venn diagram element", multiple options available | `interactions:[{ type: 'venn-element-selected'}]` |
+| venn-element-single-selected | enable the interaction "trigger selected when mouse clicked on venn diagram element", single selected | `interactions:[{ type: 'venn-element-single-selected'}]` |
+| venn-element-highlight | enable the interaction "trigger highlight when mouse clicked on venn diagram element" | `interactions:[{ type: 'venn-element-highlight'}]` |
`markdown:docs/common/interactions.en.md`
diff --git a/docs/api/plots/venn.zh.md b/docs/api/plots/venn.zh.md
index efdf06b673..5e1cd5177b 100644
--- a/docs/api/plots/venn.zh.md
+++ b/docs/api/plots/venn.zh.md
@@ -13,15 +13,18 @@ order: 12
**required** _object_
-设置图表数据源。数据源为对象集合,例如:
+设置图表数据源。数据源为对象集合. 例如:
```ts
- const data = [
- { sets: ['A'], size: 5 },
- { sets: ['B'], size: 10 },
- { sets: ['A', 'B'], size: 2 },
- ...
- ];
+const data = [
+ { sets: ['A'], size: 5 },
+ { sets: ['B'], size: 10 },
+ { sets: ['A', 'B'], size: 2 },
+];
+```
+
+```sign
+💡 注意:这里的数据是包含交集部分的数据量的。如上数据源,含有两个集合:`A` 和 `B`, 其中:`{ sets: ['A'], size: 5 }` 代表的是含有 A 集合的有 5 个(其实有 2 个是包含 B 集合的)
```
#### setsField
@@ -133,14 +136,16 @@ order: 12
`markdown:docs/common/tooltip.zh.md`
-### 图表交互
+### 图表交互 ✨
内置了针对 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 }]` |
+| venn-element-active | 开启「鼠标移入 venn 图元素时触发 active」的交互 | `interactions:[{ type: 'venn-element-active' }]` |
+| venn-element-selected | 开启「鼠标点击 venn 图元素时触发 selected」的交互,可多选 | `interactions:[{ type: 'venn-element-selected' }]` |
+| venn-element-single-selected | 开启「鼠标点击 venn 图元素时触发 selected」的交互,单选 | `interactions:[{ type: 'venn-element-single-selected' }]` |
+| venn-element-highlight | 开启「鼠标点击 venn 图元素时触发 高亮」的交互 | `interactions:[{ type: 'venn-element-highlight' }]` |
`markdown:docs/common/interactions.zh.md`
diff --git a/examples/more-plots/venn/demo/element-active.ts b/examples/more-plots/venn/demo/element-active.ts
new file mode 100644
index 0000000000..301ee36cf9
--- /dev/null
+++ b/examples/more-plots/venn/demo/element-active.ts
@@ -0,0 +1,103 @@
+import { Venn, G2 } from '@antv/g2plot';
+import { isEqual } from '@antv/util';
+
+const { getActionClass, registerAction } = G2;
+const ElementActiveAction = getActionClass('venn-element-active') as any;
+
+/** 用一个变量来存储 label 的初始化 visible 状态 */
+const VISIBLE_STATUS = 'visible-status';
+
+// toggle labels 的 visible 状态
+function toggleLabels(view, stateName) {
+ const activeElements = view.geometries[0].elements.filter((ele) => ele.getStates().includes(stateName));
+ const activeDatas = activeElements.map((ele) => ele.getData());
+ const labels = view.geometries[0].labelsContainer.getChildren();
+ labels.forEach((label) => {
+ label.set(VISIBLE_STATUS, label.get('visible'));
+ if (!activeDatas.find((d) => isEqual(d, label.get('origin').data))) {
+ label.hide();
+ } else {
+ label.show();
+ }
+ });
+}
+
+// 重置 labels 的 visible 状态
+function resetLabels(view) {
+ const labels = view.geometries[0].labelsContainer.getChildren();
+ labels.forEach((label) => {
+ const visible = label.get(VISIBLE_STATUS) !== undefined ? label.get(VISIBLE_STATUS) : true;
+ label.set('visible', visible);
+ label.set(VISIBLE_STATUS, undefined);
+ });
+}
+
+/**
+ * @override 自定义韦恩图 · 图形元素激活交互
+ */
+class VennElementActive extends ElementActiveAction {
+ /** 激活图形元素 */
+ active() {
+ super.active();
+ this.toggleLabels();
+ }
+
+ /** toggle 图形元素激活状态 */
+ toggle() {
+ super.toggle();
+ this.toggleLabels();
+ }
+
+ /** 重置 */
+ reset() {
+ super.reset();
+ this.resetLabels();
+ }
+
+ /**
+ * toggle labels 的 visible 状态
+ */
+ toggleLabels() {
+ toggleLabels(this.context.view, this.stateName);
+ }
+
+ /**
+ * 重置 labels 的 visible 状态
+ */
+ resetLabels() {
+ resetLabels(this.context.view);
+ }
+}
+// 自定义注册韦恩图 · 图形元素激活交互
+registerAction('venn-element-active', VennElementActive as any);
+
+const 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, label: 'A&B&C' },
+];
+
+const plot = new Venn('container', {
+ data,
+ setsField: 'sets',
+ sizeField: 'size',
+ pointStyle: { fillOpacity: 0.8 },
+ label: {
+ formatter: (datum) => {
+ let size = datum.size;
+ data.forEach((d) => {
+ if (d.label !== datum.label) {
+ const contains = datum.sets.reduce((a, b) => a && d.sets.includes(b), true);
+ size -= contains ? d.size : 0;
+ }
+ });
+ return `${datum.label} ${size}`;
+ },
+ },
+ interactions: [{ type: 'venn-element-active' }],
+});
+plot.render();
diff --git a/examples/more-plots/venn/demo/element-highlight.ts b/examples/more-plots/venn/demo/element-highlight.ts
new file mode 100644
index 0000000000..4c57b9747a
--- /dev/null
+++ b/examples/more-plots/venn/demo/element-highlight.ts
@@ -0,0 +1,103 @@
+import { Venn, G2 } from '@antv/g2plot';
+import { isEqual } from '@antv/util';
+
+const { getActionClass, registerAction } = G2;
+const ElementHighlightAction: any = getActionClass('venn-element-highlight');
+
+/** 用一个变量来存储 label 的初始化 visible 状态 */
+const VISIBLE_STATUS = 'visible-status';
+
+// toggle labels 的 visible 状态
+function toggleLabels(view, stateName) {
+ const activeElements = view.geometries[0].elements.filter((ele) => ele.getStates().includes(stateName));
+ const activeDatas = activeElements.map((ele) => ele.getData());
+ const labels = view.geometries[0].labelsContainer.getChildren();
+ labels.forEach((label) => {
+ label.set(VISIBLE_STATUS, label.get('visible'));
+ if (!activeDatas.find((d) => isEqual(d, label.get('origin').data))) {
+ label.hide();
+ } else {
+ label.show();
+ }
+ });
+}
+
+// 重置 labels 的 visible 状态
+function resetLabels(view) {
+ const labels = view.geometries[0].labelsContainer.getChildren();
+ labels.forEach((label) => {
+ const visible = label.get(VISIBLE_STATUS) !== undefined ? label.get(VISIBLE_STATUS) : true;
+ label.set('visible', visible);
+ label.set(VISIBLE_STATUS, undefined);
+ });
+}
+
+/**
+ * @override 自定义韦恩图 · 图形元素高亮交互
+ */
+class VennElementHighlight extends ElementHighlightAction {
+ /** 激活图形元素 */
+ highlight() {
+ super.highlight();
+ this.toggleLabels();
+ }
+
+ /** toggle 图形元素激活状态 */
+ toggle() {
+ super.toggle();
+ this.toggleLabels();
+ }
+
+ /** 重置 */
+ reset() {
+ super.reset();
+ this.resetLabels();
+ }
+
+ /**
+ * toggle labels 的 visible 状态
+ */
+ toggleLabels() {
+ toggleLabels(this.context.view, this.stateName);
+ }
+
+ /**
+ * 重置 labels 的 visible 状态
+ */
+ resetLabels() {
+ resetLabels(this.context.view);
+ }
+}
+
+// 自定义注册韦恩图 · 图形元素高亮交互
+registerAction('venn-element-highlight', VennElementHighlight as any);
+
+const 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 },
+];
+const plot = new Venn('container', {
+ data,
+ setsField: 'sets',
+ sizeField: 'size',
+ pointStyle: { fillOpacity: 0.8 },
+ label: {
+ formatter: (datum) => {
+ let size = datum.size;
+ data.forEach((d) => {
+ if (d.label !== datum.label) {
+ const contains = datum.sets.reduce((a, b) => a && d.sets.includes(b), true);
+ size -= contains ? d.size : 0;
+ }
+ });
+ return `${datum.label} ${size}`;
+ },
+ },
+ interactions: [{ type: 'venn-element-highlight' }],
+});
+plot.render();
diff --git a/examples/more-plots/venn/demo/interaction.ts b/examples/more-plots/venn/demo/interaction.ts
index eda85ddb92..2177a7c840 100644
--- a/examples/more-plots/venn/demo/interaction.ts
+++ b/examples/more-plots/venn/demo/interaction.ts
@@ -14,21 +14,6 @@ const plot = new Venn('container', {
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 },
diff --git a/examples/more-plots/venn/demo/legend-active.ts b/examples/more-plots/venn/demo/legend-active.ts
new file mode 100644
index 0000000000..cb68421756
--- /dev/null
+++ b/examples/more-plots/venn/demo/legend-active.ts
@@ -0,0 +1,27 @@
+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 },
+ interactions: [
+ {
+ type: 'legend-active',
+ cfg: {
+ // 自定义图例激活交互的触发行为和反馈
+ start: [{ trigger: 'legend-item:click', action: ['list-active:active', 'venn-element-active:active'] }],
+ end: [{ trigger: 'legend-item:dblclick', action: ['list-active:reset', 'venn-element-active:reset'] }],
+ },
+ },
+ ],
+});
+plot.render();
diff --git a/examples/more-plots/venn/demo/legend-highlight.ts b/examples/more-plots/venn/demo/legend-highlight.ts
new file mode 100644
index 0000000000..b0d2b3fe47
--- /dev/null
+++ b/examples/more-plots/venn/demo/legend-highlight.ts
@@ -0,0 +1,33 @@
+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',
+ interactions: [
+ {
+ type: 'legend-highlight',
+ cfg: {
+ // 自定义图例高亮交互的触发行为
+ start: [
+ {
+ trigger: 'legend-item:click',
+ action: ['legend-item-highlight:highlight', 'venn-element-highlight:highlight'],
+ },
+ ],
+ end: [
+ { trigger: 'legend-item:dblclick', action: ['legend-item-highlight:reset', 'venn-element-highlight:reset'] },
+ ],
+ },
+ },
+ ],
+});
+plot.render();
diff --git a/examples/more-plots/venn/demo/meta.json b/examples/more-plots/venn/demo/meta.json
index 774347f9b4..0f6d9091ae 100644
--- a/examples/more-plots/venn/demo/meta.json
+++ b/examples/more-plots/venn/demo/meta.json
@@ -59,6 +59,42 @@
"en": "venn plot - with element action"
},
"screenshot": "https://gw.alipayobjects.com/zos/antfincdn/x%24Jxds2L2T/0ff5000b-d23e-49a8-a02a-4eb02d7a8e47.png"
+ },
+ {
+ "filename": "element-active.ts",
+ "title": {
+ "zh": "自定义韦恩图元素激活交互",
+ "en": "Custom venn element active"
+ },
+ "new": true,
+ "screenshot": ""
+ },
+ {
+ "filename": "element-highlight.ts",
+ "title": {
+ "zh": "自定义韦恩图元素高亮交互",
+ "en": "Custom venn element highlight"
+ },
+ "new": true,
+ "screenshot": ""
+ },
+ {
+ "filename": "legend-active.ts",
+ "title": {
+ "zh": "自定义韦恩图图例激活交互",
+ "en": "Custom venn legend active"
+ },
+ "new": true,
+ "screenshot": ""
+ },
+ {
+ "filename": "legend-highlight.ts",
+ "title": {
+ "zh": "自定义韦恩图图例高亮交互",
+ "en": "Custom venn legend highlight"
+ },
+ "new": true,
+ "screenshot": ""
}
]
}
diff --git a/package.json b/package.json
index 550bbaa451..3a18687b6a 100644
--- a/package.json
+++ b/package.json
@@ -144,7 +144,7 @@
"limit-size": [
{
"path": "dist/g2plot.min.js",
- "limit": "990 Kb"
+ "limit": "995 Kb"
},
{
"path": "dist/g2plot.min.js",
diff --git a/src/core/plot.ts b/src/core/plot.ts
index 301518bffb..f2acc6a34c 100644
--- a/src/core/plot.ts
+++ b/src/core/plot.ts
@@ -20,6 +20,7 @@ export type PickOptions = Pick<
| 'supportCSSTransform'
| 'limitInPlot'
| 'locale'
+ | 'defaultInteractions'
>;
const SOURCE_ATTRIBUTE_NAME = 'data-chart-source-type';
@@ -90,7 +91,7 @@ export abstract class Plot extends EE {
* 创建 G2 实例
*/
private createG2() {
- const { width, height } = this.options;
+ const { width, height, defaultInteractions } = this.options;
this.chart = new Chart({
container: this.container,
@@ -98,6 +99,7 @@ export abstract class Plot extends EE {
...this.getChartSize(width, height),
localRefresh: false, // 默认关闭,目前 G 还有一些位置问题,难以排查!
...pick(this.options, PLOT_CONTAINER_OPTIONS),
+ defaultInteractions,
});
// 给容器增加标识,知道图表的来源区别于 G2
diff --git a/src/plots/venn/adaptor.ts b/src/plots/venn/adaptor.ts
index ecc55ccf05..3d68e4e94b 100644
--- a/src/plots/venn/adaptor.ts
+++ b/src/plots/venn/adaptor.ts
@@ -19,7 +19,7 @@ import { CustomInfo, VennData, VennOptions } from './types';
import { ID_FIELD } from './constant';
import './shape';
import './label';
-import './interaction';
+import './interactions';
/** 图例默认预留空间 */
export const LEGEND_SPACE = 40;
@@ -29,15 +29,15 @@ export const LEGEND_SPACE = 40;
*/
function colorMap(params: Params, data: VennData, colorPalette?: string[]) {
const { chart, options } = params;
- const { setsField } = options;
+ const { blendMode, setsField } = options;
const { colors10, colors20 } = chart.getTheme();
let palette = colorPalette;
if (!isArray(palette)) {
palette = data.filter((d) => d[setsField].length === 1).length <= 10 ? colors10 : colors20;
}
- const colorMap = getColorMap(palette, data, options);
+ const map = getColorMap(palette, data, blendMode, setsField);
- return (id: string) => colorMap.get(id) || palette[0];
+ return (id: string) => map.get(id) || palette[0];
}
/**
@@ -49,7 +49,8 @@ function transformColor(params: Params, data: VennData): VennOption
if (typeof color !== 'function') {
const colorPalette = typeof color === 'string' ? [color] : color;
- return (datum: Datum) => colorMap(params, data, colorPalette)(datum[ID_FIELD]);
+ const map = colorMap(params, data, colorPalette);
+ return (datum: Datum) => map(datum[ID_FIELD]);
}
return color;
}
@@ -173,7 +174,7 @@ function label(params: Params): Params {
const { label } = options;
// 获取容器大小
- const [t, r, b, l] = normalPadding(chart.appendPadding);
+ const [t, , , l] = normalPadding(chart.appendPadding);
// 传入 label 布局函数所需的 自定义参数
const customLabelInfo = { offsetX: l, offsetY: t };
@@ -223,6 +224,35 @@ export function axis(params: Params): Params {
return params;
}
+/**
+ * 韦恩图 interaction 交互适配器
+ */
+function vennInteraction(params: Params): Params {
+ const { options, chart } = params;
+ const { interactions } = options;
+
+ if (interactions) {
+ const MAP = {
+ 'legend-active': 'venn-legend-active',
+ 'legend-highlight': 'venn-legend-highlight',
+ };
+ interaction(
+ deepAssign({}, params, {
+ options: {
+ interactions: interactions.map((i) => ({
+ ...i,
+ type: MAP[i.type] || i.type,
+ })),
+ },
+ })
+ );
+ }
+
+ chart.removeInteraction('legend-active');
+ chart.removeInteraction('legend-highlight');
+ return params;
+}
+
/**
* 图适配器
* @param chart
@@ -240,7 +270,7 @@ export function adaptor(params: Params) {
legend,
axis,
tooltip,
- interaction,
+ vennInteraction,
animation
// ... 其他的 adaptor flow
)(params);
diff --git a/src/plots/venn/constant.ts b/src/plots/venn/constant.ts
index 2a29dcf4f3..470515f902 100644
--- a/src/plots/venn/constant.ts
+++ b/src/plots/venn/constant.ts
@@ -26,10 +26,26 @@ export const DEFAULT_OPTIONS: Partial = {
},
},
// 默认不开启 图例筛选交互
- interactions: [
- { type: 'legend-filter', enable: false },
- // hover 激活的时候,元素的层级展示不太好 先移除该交互
- { type: 'legend-highlight', enable: false },
- { type: 'legend-active', enable: false },
- ],
+ interactions: [{ type: 'legend-filter', enable: false }],
+ state: {
+ active: {
+ style: {
+ stroke: '#000',
+ },
+ },
+ selected: {
+ style: {
+ stroke: '#000',
+ lineWidth: 2,
+ },
+ },
+ inactive: {
+ style: {
+ fillOpacity: 0.3,
+ strokeOpacity: 0.3,
+ },
+ },
+ },
+ // 韦恩图的默认内置注册的交互
+ defaultInteractions: ['tooltip', 'venn-legend-active'],
};
diff --git a/src/plots/venn/interaction/action.ts b/src/plots/venn/interaction/action.ts
deleted file mode 100644
index ba6c95f5f4..0000000000
--- a/src/plots/venn/interaction/action.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-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
deleted file mode 100644
index 50669daaf9..0000000000
--- a/src/plots/venn/interaction/index.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-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'] }],
-});
diff --git a/src/plots/venn/interactions/actions/active.ts b/src/plots/venn/interactions/actions/active.ts
new file mode 100644
index 0000000000..decb845b32
--- /dev/null
+++ b/src/plots/venn/interactions/actions/active.ts
@@ -0,0 +1,31 @@
+import { getActionClass } from '@antv/g2';
+import { placeElementsOrdered } from '../util';
+
+const ElementActiveAction: any = getActionClass('element-active');
+
+export class VennElementActive extends ElementActiveAction {
+ /**
+ * 同步所有元素的位置
+ */
+ protected syncElementsPos() {
+ placeElementsOrdered(this.context.view);
+ }
+
+ /** 激活图形元素 */
+ public active() {
+ super.active();
+ this.syncElementsPos();
+ }
+
+ /** toggle 图形元素激活状态 */
+ public toggle() {
+ super.toggle();
+ this.syncElementsPos();
+ }
+
+ /** 重置 */
+ public reset() {
+ super.reset();
+ this.syncElementsPos();
+ }
+}
diff --git a/src/plots/venn/interactions/actions/highlight.ts b/src/plots/venn/interactions/actions/highlight.ts
new file mode 100644
index 0000000000..527b753334
--- /dev/null
+++ b/src/plots/venn/interactions/actions/highlight.ts
@@ -0,0 +1,37 @@
+import { getActionClass } from '@antv/g2';
+import { placeElementsOrdered } from '../util';
+
+const ElementHighlightAction: any = getActionClass('element-highlight');
+
+export class VennElementHighlight extends ElementHighlightAction {
+ /**
+ * 同步所有元素的位置
+ */
+ protected syncElementsPos() {
+ placeElementsOrdered(this.context.view);
+ }
+
+ /** 高亮图形元素 */
+ public highlight() {
+ super.highlight();
+ this.syncElementsPos();
+ }
+
+ /** toggle 图形元素高亮状态 */
+ public toggle() {
+ super.toggle();
+ this.syncElementsPos();
+ }
+
+ /** 清楚 */
+ public clear() {
+ super.clear();
+ this.syncElementsPos();
+ }
+
+ /** 重置 */
+ public reset() {
+ super.reset();
+ this.syncElementsPos();
+ }
+}
diff --git a/src/plots/venn/interactions/actions/selected.ts b/src/plots/venn/interactions/actions/selected.ts
new file mode 100644
index 0000000000..61c1fe6f0c
--- /dev/null
+++ b/src/plots/venn/interactions/actions/selected.ts
@@ -0,0 +1,65 @@
+import { getActionClass } from '@antv/g2';
+import { placeElementsOrdered } from '../util';
+
+const ElementSelectedAction: any = getActionClass('element-selected');
+const ElementSingleSelectedAction: any = getActionClass('element-single-selected');
+
+/**
+ * 韦恩图元素 多选交互
+ */
+export class VennElementSelected extends ElementSelectedAction {
+ /**
+ * 同步所有元素的位置
+ */
+ protected syncElementsPos() {
+ placeElementsOrdered(this.context.view);
+ }
+
+ /** 激活图形元素 */
+ public selected() {
+ super.selected();
+ this.syncElementsPos();
+ }
+
+ /** toggle 图形元素激活状态 */
+ public toggle() {
+ super.toggle();
+ this.syncElementsPos();
+ }
+
+ /** 重置 */
+ public reset() {
+ super.reset();
+ this.syncElementsPos();
+ }
+}
+
+/**
+ * 韦恩图元素 单选交互
+ */
+export class VennElementSingleSelected extends ElementSingleSelectedAction {
+ /**
+ * 同步所有元素的位置
+ */
+ protected syncElementsPos() {
+ placeElementsOrdered(this.context.view);
+ }
+
+ /** 激活图形元素 */
+ public selected() {
+ super.selected();
+ this.syncElementsPos();
+ }
+
+ /** toggle 图形元素激活状态 */
+ public toggle() {
+ super.toggle();
+ this.syncElementsPos();
+ }
+
+ /** 重置 */
+ public reset() {
+ super.reset();
+ this.syncElementsPos();
+ }
+}
diff --git a/src/plots/venn/interactions/index.ts b/src/plots/venn/interactions/index.ts
new file mode 100644
index 0000000000..05e8cef55c
--- /dev/null
+++ b/src/plots/venn/interactions/index.ts
@@ -0,0 +1,55 @@
+import { registerInteraction, registerAction } from '@antv/g2';
+import { VennElementActive } from './actions/active';
+import { VennElementHighlight } from './actions/highlight';
+import { VennElementSelected, VennElementSingleSelected } from './actions/selected';
+
+/** ================== 注册交互反馈 aciton ================== */
+
+registerAction('venn-element-active', VennElementActive as any);
+registerAction('venn-element-highlight', VennElementHighlight as any);
+registerAction('venn-element-selected', VennElementSelected as any);
+registerAction('venn-element-single-selected', VennElementSingleSelected as any);
+
+/** ================== 注册交互 ================== */
+
+// ========= Active 交互 =========
+registerInteraction('venn-element-active', {
+ start: [{ trigger: 'element:mouseenter', action: 'venn-element-active:active' }],
+ end: [{ trigger: 'element:mouseleave', action: 'venn-element-active:reset' }],
+});
+
+// ========= 高亮 交互 =========
+registerInteraction('venn-element-highlight', {
+ start: [{ trigger: 'element:mouseenter', action: 'venn-element-highlight:highlight' }],
+ end: [{ trigger: 'element:mouseleave', action: 'venn-element-highlight:reset' }],
+});
+
+// ========= Selected 交互 =========
+// 点击 venn element (可多选)
+registerInteraction('venn-element-selected', {
+ start: [{ trigger: 'element:click', action: 'venn-element-selected:toggle' }],
+ rollback: [{ trigger: 'dblclick', action: ['venn-element-selected:reset'] }],
+});
+// 点击 venn element (单选)
+registerInteraction('venn-element-single-selected', {
+ start: [{ trigger: 'element:click', action: 'venn-element-single-selected:toggle' }],
+ rollback: [{ trigger: 'dblclick', action: ['venn-element-single-selected:reset'] }],
+});
+
+// ========= 韦恩图的图例事件,单独注册 =========
+// legend hover,element active
+registerInteraction('venn-legend-active', {
+ start: [{ trigger: 'legend-item:mouseenter', action: ['list-active:active', 'venn-element-active:active'] }],
+ end: [{ trigger: 'legend-item:mouseleave', action: ['list-active:reset', 'venn-element-active:reset'] }],
+});
+
+// legend hover,element active
+registerInteraction('venn-legend-highlight', {
+ start: [
+ {
+ trigger: 'legend-item:mouseenter',
+ action: ['legend-item-highlight:highlight', 'venn-element-highlight:highlight'],
+ },
+ ],
+ end: [{ trigger: 'legend-item:mouseleave', action: ['legend-item-highlight:reset', 'venn-element-highlight:reset'] }],
+});
diff --git a/src/plots/venn/interactions/util.ts b/src/plots/venn/interactions/util.ts
new file mode 100644
index 0000000000..d6299e36e1
--- /dev/null
+++ b/src/plots/venn/interactions/util.ts
@@ -0,0 +1,12 @@
+import { View } from '@antv/g2';
+
+/** tofront: 同步所有元素的位置 */
+export function placeElementsOrdered(view: View) {
+ if (!view) {
+ return;
+ }
+ const elements = view.geometries[0].elements;
+ elements.forEach((elem) => {
+ elem.shape.toFront();
+ });
+}
diff --git a/src/plots/venn/utils.ts b/src/plots/venn/utils.ts
index 66564d490b..4ac6aa9957 100644
--- a/src/plots/venn/utils.ts
+++ b/src/plots/venn/utils.ts
@@ -6,6 +6,13 @@ import { intersectionAreaPath, computeTextCentres } from './layout/diagram';
import { ID_FIELD, PATH_FIELD } from './constant';
import { VennData, VennOptions } from './types';
+type ColorMapFunction = (
+ colorPalette: string[],
+ data: VennData,
+ blendMode: VennOptions['blendMode'],
+ setsField: VennOptions['setsField']
+) => Map;
+
/**
* 获取 颜色映射
* @usage colorMap.get(id) => color
@@ -13,8 +20,7 @@ import { VennData, VennOptions } from './types';
* @returns Map
*/
export const getColorMap = memoize(
- (colorPalette: string[], data: VennData, options: VennOptions) => {
- const { blendMode, setsField } = options;
+ ((colorPalette, data, blendMode, setsField) => {
const colorMap = new Map();
const colorPaletteLen = colorPalette.length;
data.forEach((d, idx) => {
@@ -31,9 +37,9 @@ export const getColorMap = memoize(
});
return colorMap;
- },
+ }) as ColorMapFunction,
(...params) => JSON.stringify(params)
-);
+) as ColorMapFunction;
/**
* 给韦恩图数据进行布局
diff --git a/src/types/attr.ts b/src/types/attr.ts
index ec98bb4be3..1818890334 100644
--- a/src/types/attr.ts
+++ b/src/types/attr.ts
@@ -6,7 +6,7 @@ import { Datum } from './common';
export type ShapeStyle = ShapeAttrs;
/** 颜色映射 */
-export type ColorAttr = string | string[] | ((datum: Datum) => string) | object;
+export type ColorAttr = string | string[] | ((datum: Datum, defaultColor?: string) => string);
/** pattern 映射*/
export type PatternAttr =
| CanvasPattern
diff --git a/src/types/common.ts b/src/types/common.ts
index 9361a979e7..01c91f5bab 100644
--- a/src/types/common.ts
+++ b/src/types/common.ts
@@ -132,4 +132,6 @@ export type Options = {
readonly state?: State;
/** 是否对超出坐标系范围的 Geometry 进行剪切 */
readonly limitInPlot?: boolean;
+ /** 内置注册的交互 */
+ readonly defaultInteractions?: string[];
};