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 zc-rk+dr(y86+ic~EEi$r8lnO#azPMmln|=f21PC}6-#-TOsokHJ4z(cx*{km(A~>J zp^gtKTErkA#Ybudi)jS|>{f|9Ou(qa`U;4vS#SY)$V(9V-TRSbCX-J8>0@k{8RnaF z?%DJBF8e#b^L_j_EM%o*n%y)2kOZv?SOY);|4WI{@=S5-Ia-MLH7os~v}sN!00Th* zzV9fay6^w_+?JI|yTAItX5fZRVT(yeu%)MMT3R0UDmRC0F828_yP$~9aAi4=)OzKP zz57)C(N{gCr5A>lET2n{w-44$rtGSYQorb{OGyKQ6jAh1r`{`<15GvLp@TGoK9?Fq zlIw50*Rz`ApZk#{BkHu_4H=Ih7J}cq7HQ7gv8!0fVC?+`tNG>>7qq$ZHNhc;1iEAc z#an5d3Bu#UMTssfZ|6u5bXtNX4(ojW6`1T#s-D6jlsO)g9 zshQ|5K0SEmrs!SrL3DJPRuz)$Fg5`rql6(-^ugn#IvMQW~su^ve)&fW-B9KdL7>g8uv3CVQ^sY$p z`Go+6tu73-x;#xZ|FK}2VHlZEJs+?VWMQK>2?2FBM_y^ruFIdyVyj4X=XH(9e6V&8vMGDdMZ zE%~DhkxoX0E#3pMQe6d7>gL-Ivq86Db7ucDKSlv&00tBM6d>soZp67LVp8Ep_u@Rpi#}vsGF{@DV&Se5iiAAJn z|Je#_K^WPHxK4xSfUCfl_LF2$+{{ELK`2BVZMp63J^_l05M5ea`Y;6Ja*k<@nI<=M zLQ*HtCxVP2Tj8+ep_lM@eI^IDPjtzI*lZpLZqnKvIFacDao+!q98ad61QGUTmH`X) zXiUv4Z~N9AS+s@LIts#4kw`N=M(ryU*(egRcbHN?ByIQAGq{hv8C7Se=W7g{Svm)bLe)-=rVqGV@LcV>&DdlL2DYWcT2>!3T zF<`eg&J~YF4TvD+q}qoHmj8PAk%=|Yc9{z0>F7e$$vξVPD~O6O7DygtnD= zQ!w3!TFrRIT#V7nX4BC8WeV;g`;ee>eFm7ERdS%%`VH;}>#I1h@i}3J`%^Q@H~yzn z%}R%a5p!>RYoKsvpy1&J-yJ#vA&v*H{`ktazqI-8||Q0wMz zW{xlTFyZ9sXcZAFt^Li^-QUJ1HbnNJ;Nv<8yZXjkA}RqXOTD9bsw;=PV&0o@t(fPv z0!p0U)+n-gk8+Cn89Svpux88_JUR+>@E|0eTZo@PpW0` z>+}k%shh=M`+2bD&?oU##Ht{#Oh|Pni0gmJoD>&rv6_^yUo-9jQ@56H*YP2Lm|zv* zcJGRmyW|^@1*zE_P`;-Q^obdlJg+(_ceo^;qB~xpAu?HE686_j8SF@}puY>ud-B|I zKn4qf^oRkwFmP$8mk%t5C*nBA?+U((JZ*e3M~?Dra%78s&O$ z48;2>&mH@ar-oZ^wW9qw?$5Eg9CX}8xs{_N&q!-`f4|Z4AE{UdF?^CTegkQgvSa`ylEo=!FKWp~aYMVC-ET9n&iK1t&S{fPDaHa2i4dh%E86|a|R4HtJek5ZVp zcm%LuY1_C2k9BKUa9(meP=KU8h?F_{SI!sgRw2AAIduVpHI2Y}qnp-QK$5$sbM2*(V}$1eA3)ov_$#=k)2qo#7V%{R9Pu1eE%% GPy0K>B|m2X literal 0 Hc-jL100001 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 Hc-jL100001 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() { -- 2.47.2