]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Add option to turn off grouping of bar datasets (#8641)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Sun, 14 Mar 2021 15:27:57 +0000 (17:27 +0200)
committerGitHub <noreply@github.com>
Sun, 14 Mar 2021 15:27:57 +0000 (11:27 -0400)
* Add option to turn off grouping of bar datasets

* Disregard time offset

docs/docs/charts/bar.mdx
src/controllers/controller.bar.js
test/fixtures/controller.bar/not-grouped/on-time.js [new file with mode: 0644]
test/fixtures/controller.bar/not-grouped/on-time.png [new file with mode: 0644]

index 555fe38e544ad8a85856be70e3c753779fcb068d..d3123a21fff6c7c5e99d87c01b5128edba8a9d11 100644 (file)
@@ -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.
 
index 54be96a96a59fd7f6721a11a0c38d76bb5fc42c0..0d4176f279a48ab00c024e2eab9fd87b44fedf96 100644 (file)
@@ -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 (file)
index 0000000..6c2d44c
--- /dev/null
@@ -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 (file)
index 0000000..92506bd
Binary files /dev/null and b/test/fixtures/controller.bar/not-grouped/on-time.png differ