From 85123ac0748d92a1637ee97a37e8172fa85d547f Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Sun, 14 Mar 2021 17:27:57 +0200 Subject: [PATCH] Add option to turn off grouping of bar datasets (#8641) * Add option to turn off grouping of bar datasets * Disregard time offset --- docs/docs/charts/bar.mdx | 1 + src/controllers/controller.bar.js | 55 ++++--- .../controller.bar/not-grouped/on-time.js | 134 ++++++++++++++++++ .../controller.bar/not-grouped/on-time.png | Bin 0 -> 17240 bytes 4 files changed, 167 insertions(+), 23 deletions(-) create mode 100644 test/fixtures/controller.bar/not-grouped/on-time.js create mode 100644 test/fixtures/controller.bar/not-grouped/on-time.png diff --git a/docs/docs/charts/bar.mdx b/docs/docs/charts/bar.mdx index 555fe38e5..d3123a21f 100644 --- a/docs/docs/charts/bar.mdx +++ b/docs/docs/charts/bar.mdx @@ -173,6 +173,7 @@ The bar chart accepts the following configuration from the associated dataset op | `categoryPercentage` | `number` | `0.8` | Percent (0-1) of the available width each category should be within the sample width. [more...](#barpercentage-vs-categorypercentage) | `barThickness` | `number`\|`string` | | Manually set width of each bar in pixels. If set to `'flex'`, it computes "optimal" sample widths that globally arrange bars side by side. If not set (default), bars are equally sized based on the smallest interval. [more...](#barthickness) | `base` | `number` | | Base value for the bar in data units along the value axis. If not set, defaults to the value axis base value. +| `grouped` | `boolean` | `true` | Should the bars be grouped on index axis. When `true`, all the datasets at same index value will be placed next to each other centering on that index value. When `false`, each bar is placed on its actual index-axis value. | `maxBarThickness` | `number` | | Set this to ensure that bars are not sized thicker than this. | `minBarLength` | `number` | | Set this to ensure that bars have a minimum length in pixels. diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index 54be96a96..0d4176f27 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -255,9 +255,9 @@ export default class BarController extends DatasetController { updateElements(bars, start, count, mode) { const me = this; const reset = mode === 'reset'; - const vscale = me._cachedMeta.vScale; - const base = vscale.getBasePixel(); - const horizontal = vscale.isHorizontal(); + const vScale = me._cachedMeta.vScale; + const base = vScale.getBasePixel(); + const horizontal = vScale.isHorizontal(); const ruler = me._getRuler(); const firstOpts = me.resolveDataElementOptions(start, mode); const sharedOptions = me.getSharedOptions(firstOpts); @@ -266,14 +266,14 @@ export default class BarController extends DatasetController { me.updateSharedOptions(sharedOptions, mode, firstOpts); for (let i = start; i < start + count; i++) { - const vpixels = me._calculateBarValuePixels(i); + const vpixels = reset ? {base, head: base} : me._calculateBarValuePixels(i); const ipixels = me._calculateBarIndexPixels(i, ruler); const properties = { horizontal, - base: reset ? base : vpixels.base, - x: horizontal ? reset ? base : vpixels.head : ipixels.center, - y: horizontal ? ipixels.center : reset ? base : vpixels.head, + base: vpixels.base, + x: horizontal ? vpixels.head : ipixels.center, + y: horizontal ? ipixels.center : vpixels.head, height: horizontal ? ipixels.size : undefined, width: horizontal ? undefined : ipixels.size }; @@ -370,6 +370,7 @@ export default class BarController extends DatasetController { */ _getRuler() { const me = this; + const opts = me.options; const meta = me._cachedMeta; const iScale = meta.iScale; const pixels = []; @@ -379,11 +380,8 @@ export default class BarController extends DatasetController { pixels.push(iScale.getPixelForValue(me.getParsed(i)[iScale.axis], i)); } - // Note: a potential optimization would be to skip computing this - // only if the barThickness option is defined - // Since a scriptable option may return null or undefined that - // means the option would have to be of type number - const min = computeMinSampleSize(iScale); + const barThickness = opts.barThickness; + const min = barThickness || computeMinSampleSize(iScale); return { min, @@ -391,7 +389,10 @@ export default class BarController extends DatasetController { start: iScale._startPixel, end: iScale._endPixel, stackCount: me._getStackCount(), - scale: iScale + scale: iScale, + grouped: opts.grouped, + // bar thickness ratio used for non-grouped bars + ratio: barThickness ? 1 : opts.categoryPercentage * opts.barPercentage }; } @@ -459,17 +460,24 @@ export default class BarController extends DatasetController { */ _calculateBarIndexPixels(index, ruler) { const me = this; + const scale = ruler.scale; const options = me.options; - const stackCount = options.skipNull ? me._getStackCount(index) : ruler.stackCount; - const range = options.barThickness === 'flex' - ? computeFlexCategoryTraits(index, ruler, options, stackCount) - : computeFitCategoryTraits(index, ruler, options, stackCount); - - const stackIndex = me._getStackIndex(me.index, me._cachedMeta.stack); - const center = range.start + (range.chunk * stackIndex) + (range.chunk / 2); - const size = Math.min( - valueOrDefault(options.maxBarThickness, Infinity), - range.chunk * range.ratio); + const maxBarThickness = valueOrDefault(options.maxBarThickness, Infinity); + let center, size; + if (ruler.grouped) { + const stackCount = options.skipNull ? me._getStackCount(index) : ruler.stackCount; + const range = options.barThickness === 'flex' + ? computeFlexCategoryTraits(index, ruler, options, stackCount) + : computeFitCategoryTraits(index, ruler, options, stackCount); + + const stackIndex = me._getStackIndex(me.index, me._cachedMeta.stack); + center = range.start + (range.chunk * stackIndex) + (range.chunk / 2); + size = Math.min(maxBarThickness, range.chunk * range.ratio); + } else { + // For non-grouped bar charts, exact pixel values are used + center = scale.getPixelForValue(me.getParsed(index)[scale.axis], index); + size = Math.min(maxBarThickness, ruler.min * ruler.ratio); + } return { base: center - size / 2, @@ -512,6 +520,7 @@ BarController.defaults = { categoryPercentage: 0.8, barPercentage: 0.9, + grouped: true, animations: { numbers: { diff --git a/test/fixtures/controller.bar/not-grouped/on-time.js b/test/fixtures/controller.bar/not-grouped/on-time.js new file mode 100644 index 000000000..6c2d44cbb --- /dev/null +++ b/test/fixtures/controller.bar/not-grouped/on-time.js @@ -0,0 +1,134 @@ +const data1 = [ + { + x: '2017-11-02T20:30:00', + y: 27 + }, + { + x: '2017-11-03T20:53:00', + y: 30 + }, + { + x: '2017-11-06T05:46:00', + y: 19 + }, + { + x: '2017-11-06T21:03:00', + y: 28 + }, + { + x: '2017-11-07T20:49:00', + y: 29 + }, + { + x: '2017-11-08T21:52:00', + y: 33 + } +]; + +const data2 = [ + { + x: '2017-11-03T13:07:00', + y: 45 + }, + { + x: '2017-11-04T04:50:00', + y: 40 + }, + { + x: '2017-11-06T12:48:00', + y: 38 + }, + { + x: '2017-11-07T12:28:00', + y: 42 + }, + { + x: '2017-11-08T12:45:00', + y: 51 + }, + { + x: '2017-11-09T05:23:00', + y: 57 + } +]; + +const data3 = [ + { + x: '2017-11-03T16:30:00', + y: 32 + }, + { + x: '2017-11-04T11:50:00', + y: 34 + }, + { + x: '2017-11-06T18:30:00', + y: 28 + }, + { + x: '2017-11-07T15:51:00', + y: 31 + }, + { + x: '2017-11-08T17:27:00', + y: 36 + }, + { + x: '2017-11-09T06:53:00', + y: 31 + } +]; + +module.exports = { + description: 'https://github.com/chartjs/Chart.js/issues/5139', + config: { + type: 'bar', + data: { + datasets: [ + { + data: data1, + backgroundColor: 'rgb(0,0,255)', + }, + { + data: data2, + backgroundColor: 'rgb(255,0,0)', + }, + { + data: data3, + backgroundColor: 'rgb(0,255,0)', + }, + ] + }, + options: { + barThickness: 10, + grouped: false, + scales: { + x: { + bounds: 'ticks', + type: 'time', + offset: false, + position: 'bottom', + display: true, + time: { + isoWeekday: true, + unit: 'day' + }, + grid: { + offset: false + } + }, + y: { + beginAtZero: true, + display: false + } + }, + } + }, + options: { + spriteText: true, + canvas: { + width: 1000, + height: 300 + } + } +}; diff --git a/test/fixtures/controller.bar/not-grouped/on-time.png b/test/fixtures/controller.bar/not-grouped/on-time.png new file mode 100644 index 0000000000000000000000000000000000000000..92506bd3ae7b05f5d7f791ed195efbe3c361ccd3 GIT binary patch literal 17240 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 asG(rZ2akWaWrqCQaf{35!cF`C{Qm%t++q0u literal 0 Hc-jL100001 -- 2.47.3