From 667b28beca55606a76d6fe724d83eede8a1355e4 Mon Sep 17 00:00:00 2001 From: Kit PANG Date: Wed, 23 Nov 2022 09:21:02 +0800 Subject: [PATCH] fix: respect minBarLength in stacked bar chart (#10766) --- src/controllers/controller.bar.js | 7 ++- src/core/core.datasetController.js | 8 ++- .../horizontal-stacked-no-overlap.js | 55 ++++++++++++++++++ .../horizontal-stacked-no-overlap.png | Bin 0 -> 8551 bytes .../vertical-stacked-no-overlap.js | 54 +++++++++++++++++ .../vertical-stacked-no-overlap.png | Bin 0 -> 12472 bytes test/specs/core.datasetController.tests.js | 34 +++++------ 7 files changed, 139 insertions(+), 19 deletions(-) create mode 100644 test/fixtures/controller.bar/minBarLength/horizontal-stacked-no-overlap.js create mode 100644 test/fixtures/controller.bar/minBarLength/horizontal-stacked-no-overlap.png create mode 100644 test/fixtures/controller.bar/minBarLength/vertical-stacked-no-overlap.js create mode 100644 test/fixtures/controller.bar/minBarLength/vertical-stacked-no-overlap.png diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index 20e053cc0..1221b64c3 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -538,7 +538,7 @@ export default class BarController extends DatasetController { * @private */ _calculateBarValuePixels(index) { - const {_cachedMeta: {vScale, _stacked}, options: {base: baseValue, minBarLength}} = this; + const {_cachedMeta: {vScale, _stacked, index: datasetIndex}, options: {base: baseValue, minBarLength}} = this; const actualBase = baseValue || 0; const parsed = this.getParsed(index); const custom = parsed._custom; @@ -586,6 +586,11 @@ export default class BarController extends DatasetController { const max = Math.max(startPixel, endPixel); base = Math.max(Math.min(base, max), min); head = base + size; + + if (_stacked && !floating) { + // visual data coordinates after applying minBarLength + parsed._stacks[vScale.axis]._visualValues[datasetIndex] = vScale.getValueForPixel(head) - vScale.getValueForPixel(base); + } } if (base === vScale.getPixelForValue(actualBase)) { diff --git a/src/core/core.datasetController.js b/src/core/core.datasetController.js index d5b43da8d..108460e2a 100644 --- a/src/core/core.datasetController.js +++ b/src/core/core.datasetController.js @@ -158,6 +158,9 @@ function updateStacks(controller, parsed) { stack._top = getLastIndexInStack(stack, vScale, true, meta.type); stack._bottom = getLastIndexInStack(stack, vScale, false, meta.type); + + const visualValues = stack._visualValues || (stack._visualValues = {}); + visualValues[datasetIndex] = value; } } @@ -207,6 +210,9 @@ function clearStacks(meta, items) { return; } delete stacks[axis][datasetIndex]; + if (stacks[axis]._visualValues !== undefined && stacks[axis]._visualValues[datasetIndex] !== undefined) { + delete stacks[axis]._visualValues[datasetIndex]; + } } } @@ -578,7 +584,7 @@ export default class DatasetController { const value = parsed[scale.axis]; const stack = { keys: getSortedDatasetIndices(chart, true), - values: parsed._stacks[scale.axis] + values: parsed._stacks[scale.axis]._visualValues }; return applyStack(stack, value, meta.index, {mode}); } diff --git a/test/fixtures/controller.bar/minBarLength/horizontal-stacked-no-overlap.js b/test/fixtures/controller.bar/minBarLength/horizontal-stacked-no-overlap.js new file mode 100644 index 000000000..57b831456 --- /dev/null +++ b/test/fixtures/controller.bar/minBarLength/horizontal-stacked-no-overlap.js @@ -0,0 +1,55 @@ +const minBarLength = 50; + +module.exports = { + config: { + type: 'bar', + data: { + labels: [1, 2, 3, 4], + datasets: [ + { + data: [1, -1, 1, 20], + backgroundColor: '#bb000066', + minBarLength + }, + { + data: [1, -1, -1, -20], + backgroundColor: '#00bb0066', + minBarLength + }, + { + data: [1, -1, 1, 40], + backgroundColor: '#0000bb66', + minBarLength + }, + { + data: [1, -1, -1, -40], + backgroundColor: '#00000066', + minBarLength + } + ] + }, + options: { + indexAxis: 'y', + scales: { + x: { + display: false, + stacked: true + }, + y: { + type: 'linear', + position: 'left', + stacked: true, + ticks: { + display: false + } + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/minBarLength/horizontal-stacked-no-overlap.png b/test/fixtures/controller.bar/minBarLength/horizontal-stacked-no-overlap.png new file mode 100644 index 0000000000000000000000000000000000000000..dfa3f87b4f392fc65203ceb8ddece5f2853d51f1 GIT binary patch literal 8551 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 BKW6{{ literal 0 HcmV?d00001 diff --git a/test/fixtures/controller.bar/minBarLength/vertical-stacked-no-overlap.js b/test/fixtures/controller.bar/minBarLength/vertical-stacked-no-overlap.js new file mode 100644 index 000000000..454e02735 --- /dev/null +++ b/test/fixtures/controller.bar/minBarLength/vertical-stacked-no-overlap.js @@ -0,0 +1,54 @@ +const minBarLength = 50; + +module.exports = { + config: { + type: 'bar', + data: { + labels: [1, 2, 3, 4], + datasets: [ + { + data: [1, -1, 1, 20], + backgroundColor: '#bb000066', + minBarLength + }, + { + data: [1, -1, -1, -20], + backgroundColor: '#00bb0066', + minBarLength + }, + { + data: [1, -1, 1, 40], + backgroundColor: '#0000bb66', + minBarLength + }, + { + data: [1, -1, -1, -40], + backgroundColor: '#00000066', + minBarLength + } + ] + }, + options: { + scales: { + x: { + display: false, + stacked: true + }, + y: { + type: 'linear', + position: 'left', + stacked: true, + ticks: { + display: false + } + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/minBarLength/vertical-stacked-no-overlap.png b/test/fixtures/controller.bar/minBarLength/vertical-stacked-no-overlap.png new file mode 100644 index 0000000000000000000000000000000000000000..f6b917af38af75118cc83278115dd3f07ea994ae GIT binary patch literal 12472 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 literal 0 HcmV?d00001 diff --git a/test/specs/core.datasetController.tests.js b/test/specs/core.datasetController.tests.js index c3835bcaf..e10c33d11 100644 --- a/test/specs/core.datasetController.tests.js +++ b/test/specs/core.datasetController.tests.js @@ -768,12 +768,12 @@ describe('Chart.DatasetController', function() { expect(chart._stacks).toEqual({ 'x.y.1': { - 0: {0: 1, 2: 3, _top: 2, _bottom: null}, - 1: {0: 10, 2: 30, _top: 2, _bottom: null} + 0: {0: 1, 2: 3, _top: 2, _bottom: null, _visualValues: {0: 1, 2: 3}}, + 1: {0: 10, 2: 30, _top: 2, _bottom: null, _visualValues: {0: 10, 2: 30}} }, 'x.y.2': { - 0: {1: 2, _top: 1, _bottom: null}, - 1: {1: 20, _top: 1, _bottom: null} + 0: {1: 2, _top: 1, _bottom: null, _visualValues: {1: 2}}, + 1: {1: 20, _top: 1, _bottom: null, _visualValues: {1: 20}} } }); @@ -782,12 +782,12 @@ describe('Chart.DatasetController', function() { expect(chart._stacks).toEqual({ 'x.y.1': { - 0: {0: 1, _top: 2, _bottom: null}, - 1: {0: 10, _top: 2, _bottom: null} + 0: {0: 1, _top: 2, _bottom: null, _visualValues: {0: 1}}, + 1: {0: 10, _top: 2, _bottom: null, _visualValues: {0: 10}} }, 'x.y.2': { - 0: {1: 2, 2: 3, _top: 2, _bottom: null}, - 1: {1: 20, 2: 30, _top: 2, _bottom: null} + 0: {1: 2, 2: 3, _top: 2, _bottom: null, _visualValues: {1: 2, 2: 3}}, + 1: {1: 20, 2: 30, _top: 2, _bottom: null, _visualValues: {1: 20, 2: 30}} } }); }); @@ -812,12 +812,12 @@ describe('Chart.DatasetController', function() { expect(chart._stacks).toEqual({ 'x.y.1': { - 0: {0: 1, 2: 3, _top: 2, _bottom: null}, - 1: {0: 10, 2: 30, _top: 2, _bottom: null} + 0: {0: 1, 2: 3, _top: 2, _bottom: null, _visualValues: {0: 1, 2: 3}}, + 1: {0: 10, 2: 30, _top: 2, _bottom: null, _visualValues: {0: 10, 2: 30}} }, 'x.y.2': { - 0: {1: 2, _top: 1, _bottom: null}, - 1: {1: 20, _top: 1, _bottom: null} + 0: {1: 2, _top: 1, _bottom: null, _visualValues: {1: 2}}, + 1: {1: 20, _top: 1, _bottom: null, _visualValues: {1: 20}} } }); @@ -826,12 +826,12 @@ describe('Chart.DatasetController', function() { expect(chart._stacks).toEqual({ 'x.y.1': { - 0: {0: 1, 2: 4, _top: 2, _bottom: null}, - 1: {0: 10, _top: 2, _bottom: null} + 0: {0: 1, 2: 4, _top: 2, _bottom: null, _visualValues: {0: 1, 2: 4}}, + 1: {0: 10, _top: 2, _bottom: null, _visualValues: {0: 10}} }, 'x.y.2': { - 0: {1: 2, _top: 1, _bottom: null}, - 1: {1: 20, _top: 1, _bottom: null} + 0: {1: 2, _top: 1, _bottom: null, _visualValues: {1: 2}}, + 1: {1: 20, _top: 1, _bottom: null, _visualValues: {1: 20}} } }); }); @@ -947,7 +947,7 @@ describe('Chart.DatasetController', function() { }); var meta = chart.getDatasetMeta(0); - expect(meta._parsed[0]._stacks).toEqual(jasmine.objectContaining({y: {0: 10, 1: 20, _top: 1, _bottom: null}})); + expect(meta._parsed[0]._stacks).toEqual(jasmine.objectContaining({y: {0: 10, 1: 20, _top: 1, _bottom: null, _visualValues: {0: 10, 1: 20}}})); }); describe('resolveDataElementOptions', function() {