From: Evert Timberg Date: Tue, 6 Oct 2020 11:33:24 +0000 (-0400) Subject: Add new option, `skipNull` to bar charts that enables skipping null values (#7849) X-Git-Tag: v3.0.0-beta.4~18 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=56a8a23d549659efcfee00120bdb9c01907f23d4;p=thirdparty%2FChart.js.git Add new option, `skipNull` to bar charts that enables skipping null values (#7849) * Add new option, `skipNull` to bar charts that enables skipping null or undefined values. * Address code review feedback * Fix windows CI lint issues --- diff --git a/docs/docs/charts/bar.mdx b/docs/docs/charts/bar.mdx index 7d2fd1bd5..a69f40bda 100644 --- a/docs/docs/charts/bar.mdx +++ b/docs/docs/charts/bar.mdx @@ -186,6 +186,14 @@ If set to `'flex'`, the base sample widths are calculated automatically based on If not set (default), the base sample widths are calculated using the smallest interval that prevents bar overlapping, and bars are sized using `barPercentage` and `categoryPercentage`. This mode always generates bars equally sized. +## Config Options + +These are the customisation options specific to Bar charts. These options are merged with the global chart configuration options, and form the options of the chart. + +| Name | Type | Default | Description +| ---- | ---- | ------- | ----------- +| `skipNull` | `boolean` | `undefined` | If `true`, null or undefined values will not be drawn + ## Scale Configuration The bar chart sets unique default values for the following configuration from the associated `scale` options: diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index 30e092d89..dea91c865 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -30,9 +30,8 @@ function computeMinSampleSize(scale, pixels) { * mode currently always generates bars equally sized (until we introduce scriptable options?). * @private */ -function computeFitCategoryTraits(index, ruler, options) { +function computeFitCategoryTraits(index, ruler, options, stackCount) { const thickness = options.barThickness; - const count = ruler.stackCount; let size, ratio; if (isNullOrUndef(thickness)) { @@ -42,12 +41,12 @@ function computeFitCategoryTraits(index, ruler, options) { // When bar thickness is enforced, category and bar percentages are ignored. // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%') // and deprecate barPercentage since this value is ignored when thickness is absolute. - size = thickness * count; + size = thickness * stackCount; ratio = 1; } return { - chunk: size / count, + chunk: size / stackCount, ratio, start: ruler.pixels[index] - (size / 2) }; @@ -59,7 +58,7 @@ function computeFitCategoryTraits(index, ruler, options) { * generates bars with different widths when data are not evenly spaced. * @private */ -function computeFlexCategoryTraits(index, ruler, options) { +function computeFlexCategoryTraits(index, ruler, options, stackCount) { const pixels = ruler.pixels; const curr = pixels[index]; let prev = index > 0 ? pixels[index - 1] : null; @@ -81,7 +80,7 @@ function computeFlexCategoryTraits(index, ruler, options) { const size = Math.abs(next - prev) / 2 * percent; return { - chunk: size / ruler.stackCount, + chunk: size / stackCount, ratio: options.barPercentage, start }; @@ -271,10 +270,11 @@ export default class BarController extends DatasetController { /** * Returns the stacks based on groups and bar visibility. * @param {number} [last] - The dataset index + * @param {number} [dataIndex] - The data index of the ruler * @returns {string[]} The list of stack IDs * @private */ - _getStacks(last) { + _getStacks(last, dataIndex) { const me = this; const meta = me._cachedMeta; const iScale = meta.iScale; @@ -286,6 +286,17 @@ export default class BarController extends DatasetController { for (i = 0; i < ilen; ++i) { item = metasets[i]; + + if (typeof dataIndex !== 'undefined') { + const val = item.controller.getParsed(dataIndex)[ + item.controller._cachedMeta.vScale.axis + ]; + + if (isNullOrUndef(val) || isNaN(val)) { + continue; + } + } + // stacked | meta.stack // | found | not found | undefined // false | x | x | x @@ -314,8 +325,8 @@ export default class BarController extends DatasetController { * Returns the effective number of stacks based on groups and bar visibility. * @private */ - _getStackCount() { - return this._getStacks().length; + _getStackCount(index) { + return this._getStacks(undefined, index).length; } /** @@ -429,9 +440,10 @@ export default class BarController extends DatasetController { */ _calculateBarIndexPixels(index, ruler, options) { const me = this; + const stackCount = me.chart.options.skipNull ? me._getStackCount(index) : ruler.stackCount; const range = options.barThickness === 'flex' - ? computeFlexCategoryTraits(index, ruler, options) - : computeFitCategoryTraits(index, ruler, options); + ? 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); @@ -486,7 +498,7 @@ BarController.defaults = { 'barThickness', 'categoryPercentage', 'maxBarThickness', - 'minBarLength' + 'minBarLength', ], hover: { mode: 'index' diff --git a/test/fixtures/controller.bar/bar-skip-null-object-data.js b/test/fixtures/controller.bar/bar-skip-null-object-data.js new file mode 100644 index 000000000..3be0fc964 --- /dev/null +++ b/test/fixtures/controller.bar/bar-skip-null-object-data.js @@ -0,0 +1,35 @@ +module.exports = { + config: { + type: 'bar', + data: { + labels: [0, 1, 3, 4], + datasets: [ + { + data: {0: 5, 1: 20, 2: 1, 3: 10}, + backgroundColor: '#00ff00', + borderColor: '#ff0000' + }, + { + data: {0: 10, 1: null, 2: 1, 3: NaN}, + backgroundColor: '#ff0000', + borderColor: '#ff0000' + } + ] + }, + options: { + legend: false, + skipNull: true, + title: false, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/bar-skip-null-object-data.png b/test/fixtures/controller.bar/bar-skip-null-object-data.png new file mode 100644 index 000000000..0406b55a0 Binary files /dev/null and b/test/fixtures/controller.bar/bar-skip-null-object-data.png differ diff --git a/test/fixtures/controller.bar/bar-skip-null.js b/test/fixtures/controller.bar/bar-skip-null.js new file mode 100644 index 000000000..77f029044 --- /dev/null +++ b/test/fixtures/controller.bar/bar-skip-null.js @@ -0,0 +1,35 @@ +module.exports = { + config: { + type: 'bar', + data: { + labels: [0, 1, 3, 4], + datasets: [ + { + data: [5, 20, 1, 10], + backgroundColor: '#00ff00', + borderColor: '#ff0000' + }, + { + data: [10, null, 1, undefined], + backgroundColor: '#ff0000', + borderColor: '#ff0000' + } + ] + }, + options: { + legend: false, + skipNull: true, + title: false, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/bar-skip-null.png b/test/fixtures/controller.bar/bar-skip-null.png new file mode 100644 index 000000000..0406b55a0 Binary files /dev/null and b/test/fixtures/controller.bar/bar-skip-null.png differ diff --git a/types/controllers/index.d.ts b/types/controllers/index.d.ts index b24728cd8..02265071c 100644 --- a/types/controllers/index.d.ts +++ b/types/controllers/index.d.ts @@ -79,6 +79,10 @@ export interface IBarControllerDatasetOptions } export interface IBarControllerChartOptions { + /** + * Should null or undefined values be omitted from drawing + */ + skipNull?: boolean; } export interface BarController extends DatasetController {}