diff --git a/common/changes/@visactor/vchart/fix-bar-min-height-stack-corner-radius_2026-04-13-11-27.json b/common/changes/@visactor/vchart/fix-bar-min-height-stack-corner-radius_2026-04-13-11-27.json new file mode 100644 index 0000000000..bf29c3f6df --- /dev/null +++ b/common/changes/@visactor/vchart/fix-bar-min-height-stack-corner-radius_2026-04-13-11-27.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "fix: correct stacked bar corner clipping when barMinHeight is applied (Issue #4543)", + "type": "patch", + "packageName": "@visactor/vchart" + } + ], + "packageName": "@visactor/vchart", + "email": "lixuef1313@163.com" +} diff --git a/packages/vchart/__tests__/unit/chart/bar.test.ts b/packages/vchart/__tests__/unit/chart/bar.test.ts index 26cb7817ee..11a6533772 100644 --- a/packages/vchart/__tests__/unit/chart/bar.test.ts +++ b/packages/vchart/__tests__/unit/chart/bar.test.ts @@ -148,4 +148,71 @@ describe('Bar chart test', () => { expect(series.fieldY2).toBe('__VCHART_STACK_START'); expect(series.fieldX2).toBeUndefined(); }); + + test('stackCornerRadius should build valid clip paths when barMinHeight is enabled', () => { + const stackSpec = { + type: 'bar', + data: { + values: [ + { type: 'Autocracies', year: '1930', value: 129 }, + { type: 'Autocracies', year: '1940', value: 133 }, + { type: 'Democracies', year: '1930', value: 22 }, + { type: 'Democracies', year: '1940', value: 13 }, + { type: 'Price', year: '1930', value: 1 }, + { type: 'Price', year: '1940', value: 1 } + ] + }, + barMaxWidth: 16, + barGapInGroup: 2, + barMinHeight: 2, + stackCornerRadius: [0, 2, 2, 0], + height: 500, + xField: 'year', + yField: 'value', + seriesField: 'type' + }; + const transformer = new BarChart.transformerConstructor({ + type: 'bar', + seriesType: 'bar', + getTheme: getTheme, + mode: 'desktop-browser' + }); + const info = transformer.initChartSpec(stackSpec as any); + chart = new BarChart( + stackSpec as any, + { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + eventDispatcher: new EventDispatcher({} as any, { addEventListener: () => {} } as any), + globalInstance: { + isAnimationEnable: () => true, + getContainer: () => ({}), + getTooltipHandlerByUser: (() => undefined) as () => undefined + }, + render: {} as any, + dataSet, + map: new Map(), + container: null, + mode: 'desktop-browser', + getCompiler: getTestCompiler, + globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any), + getTheme: getTheme, + getSpecInfo: () => info + } as any + ); + chart.created(transformer); + chart.init(); + + const series: BarSeries = chart.getAllSeries()[0] as BarSeries; + const barMark = series.getMarkInName('bar') as any; + const clipPaths = barMark._markConfig.clipPath(); + + expect(clipPaths.length).toBeGreaterThan(0); + clipPaths.forEach((path: any) => { + expect(Number.isFinite(path.attribute.x)).toBe(true); + expect(Number.isFinite(path.attribute.y)).toBe(true); + expect(Number.isFinite(path.attribute.y1)).toBe(true); + expect(Number.isFinite(path.attribute.width)).toBe(true); + }); + }); }); diff --git a/packages/vchart/src/series/bar/bar.ts b/packages/vchart/src/series/bar/bar.ts index c29a3f0846..05c600305a 100644 --- a/packages/vchart/src/series/bar/bar.ts +++ b/packages/vchart/src/series/bar/bar.ts @@ -494,14 +494,21 @@ export class BarSeries extends Cartes const xScale = this._xAxisHelper?.getScale?.(0); const yScale = this._yAxisHelper?.getScale?.(0); + const isVertical = this.direction === Direction.vertical; this._barMark.setMarkConfig({ clip: true, clipPath: () => { + const usePreCalculatedRect = !!this._shouldDoPreCalculate(); + if (usePreCalculatedRect) { + this._calculateStackRectPosition(isVertical); + } const rectPaths: any[] = []; this._forEachStackGroup(node => { let min = Infinity; let max = -Infinity; + let rectMin = Infinity; + let rectMax = -Infinity; let hasPercent = false; let minPercent = Infinity; let maxPercent = -Infinity; @@ -512,6 +519,12 @@ export class BarSeries extends Cartes const endPercent = datum[STACK_FIELD_END_PERCENT]; min = Math.min(min, start, end); max = Math.max(max, start, end); + if (usePreCalculatedRect) { + const rectStart = datum[isVertical ? RECT_Y : RECT_X]; + const rectEnd = datum[isVertical ? RECT_Y1 : RECT_X1]; + rectMin = Math.min(rectMin, rectStart, rectEnd); + rectMax = Math.max(rectMax, rectStart, rectEnd); + } if (isValid(startPercent) && isValid(endPercent)) { hasPercent = true; minPercent = Math.min(minPercent, startPercent, endPercent); @@ -532,14 +545,14 @@ export class BarSeries extends Cartes const rectAttr = this.direction === Direction.horizontal ? { - x: this._getBarXStart(mockDatum, xScale), - x1: this._getBarXEnd(mockDatum, xScale), + x: usePreCalculatedRect ? rectMin : this._getBarXStart(mockDatum, xScale), + x1: usePreCalculatedRect ? rectMax : this._getBarXEnd(mockDatum, xScale), y: this._getPosition(this.direction, mockDatum), height: this._getBarWidth(this._yAxisHelper) } : { - y: this._getBarYStart(mockDatum, yScale), - y1: this._getBarYEnd(mockDatum, yScale), + y: usePreCalculatedRect ? rectMin : this._getBarYStart(mockDatum, yScale), + y1: usePreCalculatedRect ? rectMax : this._getBarYEnd(mockDatum, yScale), x: this._getPosition(this.direction, mockDatum), width: this._getBarWidth(this._xAxisHelper) };