diff --git a/__tests__/bugs/issue-2572-spec.ts b/__tests__/bugs/issue-2572-spec.ts
new file mode 100644
index 0000000000..41e6217b9d
--- /dev/null
+++ b/__tests__/bugs/issue-2572-spec.ts
@@ -0,0 +1,191 @@
+import { Column } from '../../src';
+import { createDiv, removeDom } from '../utils/dom';
+
+describe('#2572', () => {
+ const data = [
+ {
+ product_type: '办公用品',
+ sex: '男',
+ order_amt: 8,
+ product_sub_type: '橡皮擦',
+ },
+ {
+ product_type: '办公用品',
+ sex: '男',
+ order_amt: 10,
+ product_sub_type: '书架',
+ },
+ {
+ product_type: '办公用品',
+ sex: '男',
+ order_amt: 20,
+ product_sub_type: '砚台',
+ },
+ {
+ product_type: '办公用品',
+ sex: '女',
+ order_amt: 13,
+ product_sub_type: '砚台',
+ },
+ {
+ product_type: '办公用品',
+ sex: '女',
+ order_amt: 21,
+ product_sub_type: '橡皮擦',
+ },
+ {
+ product_type: '办公用品',
+ sex: '女',
+ order_amt: 21,
+ product_sub_type: '书架',
+ },
+ {
+ product_type: '家电家具',
+ sex: '男',
+ order_amt: 13,
+ product_sub_type: '洗衣机',
+ },
+ {
+ product_type: '家电家具',
+ sex: '女',
+ order_amt: 2,
+ product_sub_type: '洗衣机',
+ },
+ {
+ product_type: '家电家具',
+ sex: '男',
+ order_amt: 5,
+ product_sub_type: '微波炉',
+ },
+ {
+ product_type: '家电家具',
+ sex: '男',
+ order_amt: 14,
+ product_sub_type: '电磁炉',
+ },
+ {
+ product_type: '家电家具',
+ sex: '女',
+ order_amt: 23,
+ product_sub_type: '微波炉',
+ },
+ {
+ product_type: '家电家具',
+ sex: '女',
+ order_amt: 23,
+ product_sub_type: '电磁炉',
+ },
+ {
+ product_type: '电子产品',
+ sex: '男',
+ order_amt: 33,
+ product_sub_type: '电脑',
+ },
+ {
+ product_type: '电子产品',
+ sex: '女',
+ order_amt: 4,
+ product_sub_type: '电脑',
+ },
+ {
+ product_type: '电子产品',
+ sex: '女',
+ order_amt: 23,
+ product_sub_type: 'switch',
+ },
+ {
+ product_type: '电子产品',
+ sex: '男',
+ order_amt: 20.9,
+ product_sub_type: 'switch',
+ },
+ {
+ product_type: '电子产品',
+ sex: '男',
+ order_amt: 5.9,
+ product_sub_type: '鼠标',
+ },
+ {
+ product_type: '电子产品',
+ sex: '女',
+ order_amt: 5.9,
+ product_sub_type: '鼠标',
+ },
+ ];
+ const div = createDiv();
+ const plot = new Column(div, {
+ data,
+ xField: 'product_type',
+ yField: 'order_amt',
+ isGroup: true,
+ isStack: true,
+ seriesField: 'product_sub_type',
+ groupField: 'sex',
+ });
+
+ plot.render();
+ const box = plot.chart.geometries[0].elements[0].getBBox();
+ const point = { x: box.x + box.width / 2, y: box.y + box.height / 2 };
+
+ expect(plot.chart.getController('tooltip').getTooltipItems(point).length).toBe(3);
+
+ it('堆叠分组柱状图 tooltip 展示不正常', () => {
+ plot.chart.showTooltip(point);
+
+ expect(div.querySelectorAll('.g2-tooltip-list-item').length).toBe(3 * 2 /** 分组:男 + 女 */);
+ expect((div.querySelector('.g2-tooltip-name') as HTMLElement).innerText).toBe(
+ `${data[0].product_sub_type} - ${data[0].sex}`
+ );
+ expect((div.querySelector('.g2-tooltip-value') as HTMLElement).innerText).toBe(`${data[0].order_amt}`);
+ plot.chart.hideTooltip();
+ });
+
+ it('堆叠分组柱状图 tooltip 更新 formatter', () => {
+ plot.update({
+ tooltip: {
+ formatter: (datum) => ({ name: 'xx', value: 100 }),
+ },
+ });
+ plot.chart.showTooltip(point);
+
+ expect((div.querySelector('.g2-tooltip-name') as HTMLElement).innerText).toBe('xx');
+ expect((div.querySelector('.g2-tooltip-value') as HTMLElement).innerText).toBe('100');
+ plot.chart.hideTooltip();
+
+ plot.update({
+ tooltip: {
+ formatter: (datum) => ({ name: datum.sex, value: 100 }),
+ },
+ });
+
+ plot.chart.showTooltip(point);
+
+ expect((div.querySelectorAll('.g2-tooltip-name')[1] as HTMLElement).innerText).toBe('女');
+ expect((div.querySelectorAll('.g2-tooltip-value')[1] as HTMLElement).innerText).toBe('100');
+ plot.chart.hideTooltip();
+ });
+
+ it('堆叠分组柱状图 tooltip 更新 customContent', () => {
+ plot.update({
+ tooltip: {
+ customContent: (title, items) =>
+ `
${title}
${
+ items.length
+ }
${items.reduce((a, b) => a + b.data.order_amt, 0)}
`,
+ },
+ });
+ plot.chart.showTooltip(point);
+
+ expect((div.querySelector('.tooltip-title') as HTMLElement).innerText).toBe(data[0].product_type);
+ expect((div.querySelector('.tooltip-name') as HTMLElement).innerText).toBe('6');
+ expect((div.querySelector('.tooltip-value') as HTMLElement).innerText).toBe(
+ `${data.filter((d) => d.product_type === '办公用品').reduce((a, b) => a + b.order_amt, 0)}`
+ );
+ plot.chart.hideTooltip();
+ });
+
+ afterAll(() => {
+ plot.destroy();
+ removeDom(div);
+ });
+});
diff --git a/examples/column/grouped/demo/meta.json b/examples/column/grouped/demo/meta.json
index 910f52a0fe..3e37260703 100644
--- a/examples/column/grouped/demo/meta.json
+++ b/examples/column/grouped/demo/meta.json
@@ -21,20 +21,28 @@
"screenshot": "https://gw.alipayobjects.com/zos/antfincdn/vA%26pfMx397/0d8d9a6e-51ba-48f2-bc6d-091d77d94e2e.png"
},
{
- "filename": "stacked2.ts",
+ "filename": "stacked.ts",
"title": {
"zh": "堆叠分组柱状图",
"en": "Stacked grouped column plot"
},
- "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/mp10Ax%24Mvx/4c6f19b4-2549-4023-8756-bce61bf6c50e.png"
+ "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/9ExrwrpVrA/908eff62-1132-4dbb-8c3c-9cbe8183c6d8.png"
},
{
- "filename": "stacked.ts",
+ "filename": "tooltip-formatter.ts",
+ "title": {
+ "zh": "对 Tooltip 进行 formatter",
+ "en": "Formatter for tooltip"
+ },
+ "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*zDnZQLk2L5wAAAAAAAAAAAAAARQnAQ"
+ },
+ {
+ "filename": "stacked2.ts",
"title": {
"zh": "堆叠分组柱状图",
"en": "Stacked grouped column plot"
},
- "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/9ExrwrpVrA/908eff62-1132-4dbb-8c3c-9cbe8183c6d8.png"
+ "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/mp10Ax%24Mvx/4c6f19b4-2549-4023-8756-bce61bf6c50e.png"
},
{
"filename": "dodge-padding.ts",
diff --git a/examples/column/grouped/demo/tooltip-formatter.ts b/examples/column/grouped/demo/tooltip-formatter.ts
new file mode 100644
index 0000000000..4caa082266
--- /dev/null
+++ b/examples/column/grouped/demo/tooltip-formatter.ts
@@ -0,0 +1,23 @@
+import { Column } from '@antv/g2plot';
+
+fetch('https://gw.alipayobjects.com/os/antfincdn/mor%26R5yBI9/stack-group-column.json')
+ .then((data) => data.json())
+ .then((data) => {
+ const column = new Column('container', {
+ data,
+ xField: 'product_type',
+ yField: 'order_amt',
+ isGroup: true,
+ isStack: true,
+ seriesField: 'product_sub_type',
+ groupField: 'sex',
+ tooltip: {
+ formatter: (datum) => ({
+ name: `${datum.product_sub_type} ${datum.sex === '男' ? '👦' : '👧'}`,
+ value: datum.order_amt,
+ }),
+ },
+ });
+
+ column.render();
+ });
diff --git a/src/plots/column/adaptor.ts b/src/plots/column/adaptor.ts
index e192336984..152995d8c6 100644
--- a/src/plots/column/adaptor.ts
+++ b/src/plots/column/adaptor.ts
@@ -1,7 +1,7 @@
+import { Types } from '@antv/g2';
+import { each, filter, isMatch } from '@antv/util';
import { Params } from '../../core/adaptor';
-import { findGeometry } from '../../utils';
import {
- tooltip,
slider,
interaction,
animation,
@@ -15,9 +15,8 @@ import {
import { conversionTag } from '../../adaptor/conversion-tag';
import { connectedArea } from '../../adaptor/connected-area';
import { interval } from '../../adaptor/geometries';
-import { flow, transformLabel, deepAssign } from '../../utils';
+import { flow, transformLabel, deepAssign, findGeometry, adjustYMetaByZero, pick } from '../../utils';
import { getDataWhetherPecentage } from '../../utils/transform/percent';
-import { adjustYMetaByZero } from '../../utils/data';
import { Datum } from '../../types';
import { ColumnOptions } from './types';
@@ -197,6 +196,50 @@ function label(params: Params): Params {
return params;
}
+/**
+ * 柱形图 tooltip 配置 (对堆叠、分组做特殊处理)
+ * @param params
+ */
+function columnTooltip(params: Params): Params {
+ const { chart, options } = params;
+ const { tooltip, isGroup, isStack, groupField, data, xField, yField, seriesField } = options;
+
+ if (tooltip === false) {
+ chart.tooltip(false);
+ } else {
+ let tooltipOptions = tooltip;
+ // fix: https://github.com/antvis/G2Plot/issues/2572
+ if (isGroup && isStack) {
+ const tooltipFormatter =
+ tooltipOptions?.formatter ||
+ ((datum: Datum) => ({ name: `${datum[seriesField]} - ${datum[groupField]}`, value: datum[yField] }));
+ tooltipOptions = {
+ ...tooltipOptions,
+ customItems: (originalItems: Types.TooltipItem[]) => {
+ const items: Types.TooltipItem[] = [];
+ each(originalItems, (item: Types.TooltipItem) => {
+ // Find datas in same cluster
+ const datas = filter(data, (d) => isMatch(d, pick(item.data, [xField, seriesField])));
+ datas.forEach((datum) => {
+ items.push({
+ ...item,
+ value: datum[yField],
+ data: datum,
+ mappingData: { _origin: datum },
+ ...tooltipFormatter(datum),
+ });
+ });
+ });
+ return items;
+ },
+ };
+ }
+ chart.tooltip(tooltipOptions);
+ }
+
+ return params;
+}
+
/**
* 柱形图适配器
* @param params
@@ -212,7 +255,7 @@ export function adaptor(params: Params, isBar = false) {
meta,
axis,
legend,
- tooltip,
+ columnTooltip,
slider,
scrollbar,
label,
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 5249a43269..bb358ea422 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -12,4 +12,4 @@ export { kebabCase } from './kebab-case';
export { renderStatistic, renderGaugeStatistic } from './statistic';
export { measureTextWidth } from './measure-text';
export { isBetween, isRealNumber } from './number';
-export { processIllegalData } from './data';
+export * from './data';