From 24ee627d6ecfca85d204d934864d2bf64dc5372c Mon Sep 17 00:00:00 2001 From: Abhishek Rajpoot Date: Tue, 6 Oct 2026 14:33:34 +0530 Subject: [PATCH] Keep the stack order when the first dataset of a stack is hidden --- src/controllers/controller.bar.js | 24 ++++++++++++++++--- test/specs/controller.bar.tests.js | 37 ++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index 554497b3053..8a6e2799d33 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -17,6 +17,25 @@ function getAllScaleValues(scale, type) { return scale._cache.$bar; } +/** + * Returns the grouped metasets of the given type on the index scale, in sorted order. + * A hidden metaset is kept when its stack still has visible bars, so that it holds the + * position of that stack: hiding a dataset must not reorder the stacks. + * @private + */ +function getGroupedMetas(chart, iScale, type, hasBar) { + const axisID = iScale.axis + 'AxisID'; + const metasets = chart._getSortedDatasetMetas() + .filter(meta => meta[axisID] === iScale.id && meta.type === type && meta.controller.options.grouped); + + if (iScale.options.stacked === false) { + return metasets.filter(meta => meta.visible); + } + + const visibleStacks = new Set(metasets.filter(meta => meta.visible && hasBar(meta)).map(meta => meta.stack)); + return metasets.filter(meta => meta.visible || (meta.stack !== undefined && visibleStacks.has(meta.stack))); +} + /** * Computes the "optimal" sample size to maintain bars equally sized while preventing overlap. * @private @@ -433,8 +452,6 @@ export default class BarController extends DatasetController { */ _getStacks(last, dataIndex) { const {iScale} = this._cachedMeta; - const metasets = iScale.getMatchingVisibleMetas(this._type) - .filter(meta => meta.controller.options.grouped); const stacked = iScale.options.stacked; const stacks = []; const currentParsed = this._cachedMeta.controller.getParsed(dataIndex); @@ -448,9 +465,10 @@ export default class BarController extends DatasetController { return true; } }; + const metasets = getGroupedMetas(this.chart, iScale, this._type, meta => dataIndex === undefined || !skipNull(meta)); for (const meta of metasets) { - if (dataIndex !== undefined && skipNull(meta)) { + if (meta.visible && dataIndex !== undefined && skipNull(meta)) { continue; } diff --git a/test/specs/controller.bar.tests.js b/test/specs/controller.bar.tests.js index 128ef241d00..2c78d5198fd 100644 --- a/test/specs/controller.bar.tests.js +++ b/test/specs/controller.bar.tests.js @@ -326,6 +326,43 @@ describe('Chart.controllers.bar', function() { expect(meta.controller._getStackIndex(3)).toBe(1); }); + it('should keep the stack order when the first dataset of a stack is hidden', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: [10, 20], stack: 'stack1'}, + {data: [10, 20], stack: 'stack2'}, + {data: [10, 20], stack: 'stack1'}, + {data: [10, 20], stack: 'stack2'} + ], + labels: ['a', 'b'] + }, + options: { + scales: { + x: { + stacked: true + }, + y: { + stacked: true + } + } + } + }); + + var getX = (datasetIndex) => chart.getDatasetMeta(datasetIndex).data[0].getProps(['x'], true).x; + expect(getX(2)).toBeLessThan(getX(3)); + + chart.hide(0); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller._getStackCount()).toBe(2); + expect(meta.controller._getStackIndex(1, 'stack2')).toBe(1); + expect(meta.controller._getStackIndex(2, 'stack1')).toBe(0); + expect(meta.controller._getStackIndex(3, 'stack2')).toBe(1); + expect(getX(2)).toBeLessThan(getX(3)); + }); + it('should correctly get the stack index when a group is not specified', function() { var chart = window.acquireChart({ type: 'bar',