From 26a833cea010f41fe950479a94b14a7bcb4f7a57 Mon Sep 17 00:00:00 2001 From: biubiukam Date: Fri, 7 Aug 2026 17:04:59 +0800 Subject: [PATCH] fix: keep crosshair visible for explicitly bound band axes --- .../fix-crosshair-binding-axes_20260807.json | 11 ++++ .../__tests__/unit/core/vchart-event.test.ts | 65 +++++++++++++++++++ .../vchart/src/component/crosshair/base.ts | 10 ++- 3 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 common/changes/@visactor/vchart/fix-crosshair-binding-axes_20260807.json diff --git a/common/changes/@visactor/vchart/fix-crosshair-binding-axes_20260807.json b/common/changes/@visactor/vchart/fix-crosshair-binding-axes_20260807.json new file mode 100644 index 0000000000..e66ab57890 --- /dev/null +++ b/common/changes/@visactor/vchart/fix-crosshair-binding-axes_20260807.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@visactor/vchart", + "comment": "fix: preserve crosshair visibility for explicitly bound multiple band axes (Issue #4127)", + "type": "patch" + } + ], + "packageName": "@visactor/vchart", + "email": "biukam.w@gmail.com" +} diff --git a/packages/vchart/__tests__/unit/core/vchart-event.test.ts b/packages/vchart/__tests__/unit/core/vchart-event.test.ts index ff055194e1..0421c4021f 100644 --- a/packages/vchart/__tests__/unit/core/vchart-event.test.ts +++ b/packages/vchart/__tests__/unit/core/vchart-event.test.ts @@ -38,6 +38,8 @@ type StageEventListener = { }; type CrosshairStateForTest = { + enable?: boolean; + _layoutCrosshair?: (x: number, y: number) => void; _stateByField?: { xField?: { currentValue?: Map; @@ -522,6 +524,69 @@ describe('vchart event test', () => { removeDom(lineContainer); }); + it('should keep crosshair enabled when bound to top and bottom band axes', () => { + const crosshairContainer = createDiv(); + const crosshairDom = createDiv(crosshairContainer); + crosshairDom.id = 'multiple-crosshair-axis-container'; + crosshairContainer.style.position = 'fixed'; + crosshairContainer.style.width = '500px'; + crosshairContainer.style.height = '500px'; + crosshairContainer.style.top = '0px'; + crosshairContainer.style.left = '0px'; + + const chart = new VChart( + { + type: 'common', + data: { + id: 'data', + values: [ + { x: 'Mon', type: 'Breakfast', y: 15 }, + { x: 'Mon', type: 'Lunch', y: 25 }, + { x: 'Tue', type: 'Breakfast', y: 12 }, + { x: 'Tue', type: 'Lunch', y: 30 } + ] + }, + series: [ + { + type: 'bar', + dataId: 'data', + xField: ['x', 'type'], + yField: 'y', + seriesField: 'type' + } + ], + axes: [ + { orient: 'left' }, + { orient: 'bottom', type: 'band' }, + { orient: 'top', type: 'band' }, + { orient: 'top', type: 'band' } + ], + crosshair: { + xField: { + visible: true, + bindingAxesIndex: [1, 2] + } + } + } as ICommonChartSpec, + { dom: crosshairDom, animation: false } + ); + + try { + chart.renderSync(); + const crosshair = chart.getComponents().find(component => component.type === 'cartesianCrosshair') as + | CrosshairStateForTest + | undefined; + + crosshair?._layoutCrosshair?.(250, 250); + + expect(crosshair?.enable).toBe(true); + expect(Array.from(crosshair?._stateByField?.xField?.currentValue?.keys() ?? [])).toEqual([1, 2]); + } finally { + chart.release(); + removeDom(crosshairContainer); + } + }); + it('should fire tooltipRelease before release chart', () => { const handleTooltipRelease = jest.fn(); vchart.on('tooltipRelease', handleTooltipRelease); diff --git a/packages/vchart/src/component/crosshair/base.ts b/packages/vchart/src/component/crosshair/base.ts index 90e7b56533..9192ad76b9 100644 --- a/packages/vchart/src/component/crosshair/base.ts +++ b/packages/vchart/src/component/crosshair/base.ts @@ -106,17 +106,23 @@ export abstract class BaseCrossHair, point: IPoint, field: string): boolean { - // 首先不能存在两个离散轴 + // 未显式绑定轴时,同方向的多个离散轴无法确定唯一的维度位置;显式绑定表示调用方已选择这些轴。 let discrete = false; + let multipleDiscreteAxes = false; + const bindingAxesIndex = get(this._spec, `${field}.bindingAxesIndex`); + const hasExplicitBinding = isArray(bindingAxesIndex) && bindingAxesIndex.length > 0; axisMap.forEach(item => { if (isDiscrete(item.axis.getScale().type)) { if (!discrete) { discrete = true; } else { - this.enable = false; + multipleDiscreteAxes = true; } } }); + if (multipleDiscreteAxes && !hasExplicitBinding) { + this.enable = false; + } if (!this.enable) { return false; }