From: Evert Timberg Date: Sun, 18 Apr 2021 20:30:54 +0000 (-0400) Subject: Only enable the bar borderRadius at the end of the stacks (#8941) X-Git-Tag: v3.2.0~5 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=b92f9e52fd99b30ffdb9821a1588a2a73026a674;p=thirdparty%2FChart.js.git Only enable the bar borderRadius at the end of the stacks (#8941) * Only enable the bar borderRadius at the end of the stacks * Float bars always get borders enabled * Tests * Update documentation --- diff --git a/docs/charts/bar.md b/docs/charts/bar.md index d4d0a271f..71473b41d 100644 --- a/docs/charts/bar.md +++ b/docs/charts/bar.md @@ -171,6 +171,10 @@ If this value is a number, it is applied to all sides of the rectangle (left, to If this value is a number, it is applied to all corners of the rectangle (topLeft, topRight, bottomLeft, bottomRight), except corners touching the [`borderSkipped`](#borderskipped). If this value is an object, the `topLeft` property defines the top-left corners border radius. Similarly, the `topRight`, `bottomLeft`, and `bottomRight` properties can also be specified. Omitted corners and those touching the [`borderSkipped`](#borderskipped) are skipped. For example if the `top` border is skipped, the border radius for the corners `topLeft` and `topRight` will be skipped as well. +:::tip Stacked Charts +When the border radius is supplied as a number and the chart is stacked, the radius will only be applied to the bars that are at the edges of the stack or where the bar is floating. The object syntax can be used to override this behavior. +::: + ### Interactions The interaction with each bar can be controlled with the following properties: diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index abe98841c..0ed99b7ec 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -269,10 +269,12 @@ export default class BarController extends DatasetController { const parsed = me.getParsed(i); const vpixels = reset || isNullOrUndef(parsed[vScale.axis]) ? {base, head: base} : me._calculateBarValuePixels(i); const ipixels = me._calculateBarIndexPixels(i, ruler); + const stack = (parsed._stacks || {})[vScale.axis]; const properties = { horizontal, base: vpixels.base, + enableBorderRadius: !stack || isFloatBar(parsed._custom) || (me.index === stack._top || me.index === stack._bottom), x: horizontal ? vpixels.head : ipixels.center, y: horizontal ? ipixels.center : vpixels.head, height: horizontal ? ipixels.size : undefined, diff --git a/src/core/core.datasetController.js b/src/core/core.datasetController.js index a3882e9e6..cff882ab8 100644 --- a/src/core/core.datasetController.js +++ b/src/core/core.datasetController.js @@ -127,6 +127,17 @@ function getOrCreateStack(stacks, stackKey, indexValue) { return subStack[indexValue] || (subStack[indexValue] = {}); } +function getLastIndexInStack(stack, vScale, positive) { + for (const meta of vScale.getMatchingVisibleMetas('bar').reverse()) { + const value = stack[meta.index]; + if ((positive && value > 0) || (!positive && value < 0)) { + return meta.index; + } + } + + return null; +} + function updateStacks(controller, parsed) { const {chart, _cachedMeta: meta} = controller; const stacks = chart._stacks || (chart._stacks = {}); // map structure is {stackKey: {datasetIndex: value}} @@ -143,6 +154,9 @@ function updateStacks(controller, parsed) { const itemStacks = item._stacks || (item._stacks = {}); stack = itemStacks[vAxis] = getOrCreateStack(stacks, key, index); stack[datasetIndex] = value; + + stack._top = getLastIndexInStack(stack, vScale, true); + stack._bottom = getLastIndexInStack(stack, vScale, false); } } diff --git a/src/elements/element.bar.js b/src/elements/element.bar.js index 8d9006804..c7e82309e 100644 --- a/src/elements/element.bar.js +++ b/src/elements/element.bar.js @@ -1,4 +1,5 @@ import Element from '../core/core.element'; +import {isObject} from '../helpers'; import {addRoundedRectPath} from '../helpers/helpers.canvas'; import {toTRBL, toTRBLCorners} from '../helpers/helpers.options'; @@ -83,16 +84,21 @@ function parseBorderWidth(bar, maxW, maxH) { } function parseBorderRadius(bar, maxW, maxH) { + const {enableBorderRadius} = bar.getProps(['enableBorderRadius']); const value = bar.options.borderRadius; const o = toTRBLCorners(value); const maxR = Math.min(maxW, maxH); const skip = parseBorderSkipped(bar); + // If the value is an object, assume the user knows what they are doing + // and apply as directed. + const enableBorder = enableBorderRadius || isObject(value); + return { - topLeft: skipOrLimit(skip.top || skip.left, o.topLeft, 0, maxR), - topRight: skipOrLimit(skip.top || skip.right, o.topRight, 0, maxR), - bottomLeft: skipOrLimit(skip.bottom || skip.left, o.bottomLeft, 0, maxR), - bottomRight: skipOrLimit(skip.bottom || skip.right, o.bottomRight, 0, maxR) + topLeft: skipOrLimit(!enableBorder || skip.top || skip.left, o.topLeft, 0, maxR), + topRight: skipOrLimit(!enableBorder || skip.top || skip.right, o.topRight, 0, maxR), + bottomLeft: skipOrLimit(!enableBorder || skip.bottom || skip.left, o.bottomLeft, 0, maxR), + bottomRight: skipOrLimit(!enableBorder || skip.bottom || skip.right, o.bottomRight, 0, maxR) }; } @@ -224,6 +230,7 @@ BarElement.defaults = { borderSkipped: 'start', borderWidth: 0, borderRadius: 0, + enableBorderRadius: true, pointStyle: undefined }; diff --git a/test/fixtures/controller.bar/borderRadius/border-radius-stacked-number-mixed-chart.js b/test/fixtures/controller.bar/borderRadius/border-radius-stacked-number-mixed-chart.js new file mode 100644 index 000000000..897037932 --- /dev/null +++ b/test/fixtures/controller.bar/borderRadius/border-radius-stacked-number-mixed-chart.js @@ -0,0 +1,42 @@ +module.exports = { + threshold: 0.01, + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + backgroundColor: 'red', + data: [12, 19, 12, 5, 4, 12], + }, + { + backgroundColor: 'green', + data: [12, 19, -4, 5, 8, 3], + type: 'line' + }, + { + backgroundColor: 'blue', + data: [7, 11, -12, 12, 0, -7], + } + ] + }, + options: { + elements: { + bar: { + borderRadius: Number.MAX_VALUE, + borderWidth: 2, + } + }, + scales: { + x: {display: false, stacked: true}, + y: {display: false, stacked: true} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/borderRadius/border-radius-stacked-number-mixed-chart.png b/test/fixtures/controller.bar/borderRadius/border-radius-stacked-number-mixed-chart.png new file mode 100644 index 000000000..3aff7387b Binary files /dev/null and b/test/fixtures/controller.bar/borderRadius/border-radius-stacked-number-mixed-chart.png differ diff --git a/test/fixtures/controller.bar/borderRadius/border-radius-stacked-number-with-order.js b/test/fixtures/controller.bar/borderRadius/border-radius-stacked-number-with-order.js new file mode 100644 index 000000000..886e9c463 --- /dev/null +++ b/test/fixtures/controller.bar/borderRadius/border-radius-stacked-number-with-order.js @@ -0,0 +1,44 @@ +module.exports = { + threshold: 0.01, + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + backgroundColor: 'red', + data: [12, 19, 12, 5, 4, 12], + order: 2, + }, + { + backgroundColor: 'green', + data: [12, 19, -4, 5, 8, 3], + order: 1, + }, + { + backgroundColor: 'blue', + data: [7, 11, -12, 12, 0, -7], + order: 0, + } + ] + }, + options: { + elements: { + bar: { + borderRadius: Number.MAX_VALUE, + borderWidth: 2, + } + }, + scales: { + x: {display: false, stacked: true}, + y: {display: false, stacked: true} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/borderRadius/border-radius-stacked-number-with-order.png b/test/fixtures/controller.bar/borderRadius/border-radius-stacked-number-with-order.png new file mode 100644 index 000000000..24eb8e0ea Binary files /dev/null and b/test/fixtures/controller.bar/borderRadius/border-radius-stacked-number-with-order.png differ diff --git a/test/fixtures/controller.bar/borderRadius/border-radius-stacked-number.js b/test/fixtures/controller.bar/borderRadius/border-radius-stacked-number.js new file mode 100644 index 000000000..1aeac88cb --- /dev/null +++ b/test/fixtures/controller.bar/borderRadius/border-radius-stacked-number.js @@ -0,0 +1,41 @@ +module.exports = { + threshold: 0.01, + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + backgroundColor: 'red', + data: [12, 19, 12, 5, 4, 12], + }, + { + backgroundColor: 'green', + data: [12, 19, -4, 5, 8, 3], + }, + { + backgroundColor: 'blue', + data: [7, 11, -12, 12, 0, -7], + } + ] + }, + options: { + elements: { + bar: { + borderRadius: Number.MAX_VALUE, + borderWidth: 2, + } + }, + scales: { + x: {display: false, stacked: true}, + y: {display: false, stacked: true} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/borderRadius/border-radius-stacked-number.png b/test/fixtures/controller.bar/borderRadius/border-radius-stacked-number.png new file mode 100644 index 000000000..2b8af4bb8 Binary files /dev/null and b/test/fixtures/controller.bar/borderRadius/border-radius-stacked-number.png differ diff --git a/test/fixtures/controller.bar/border-radius.js b/test/fixtures/controller.bar/borderRadius/border-radius.js similarity index 100% rename from test/fixtures/controller.bar/border-radius.js rename to test/fixtures/controller.bar/borderRadius/border-radius.js diff --git a/test/fixtures/controller.bar/border-radius.png b/test/fixtures/controller.bar/borderRadius/border-radius.png similarity index 100% rename from test/fixtures/controller.bar/border-radius.png rename to test/fixtures/controller.bar/borderRadius/border-radius.png diff --git a/test/specs/core.datasetController.tests.js b/test/specs/core.datasetController.tests.js index ce7d121b6..e89aa0bf9 100644 --- a/test/specs/core.datasetController.tests.js +++ b/test/specs/core.datasetController.tests.js @@ -540,12 +540,12 @@ describe('Chart.DatasetController', function() { expect(chart._stacks).toEqual({ 'x.y.1': { - 0: {0: 1, 2: 3}, - 1: {0: 10, 2: 30} + 0: {0: 1, 2: 3, _top: 2, _bottom: null}, + 1: {0: 10, 2: 30, _top: 2, _bottom: null} }, 'x.y.2': { - 0: {1: 2}, - 1: {1: 20} + 0: {1: 2, _top: 1, _bottom: null}, + 1: {1: 20, _top: 1, _bottom: null} } }); @@ -554,12 +554,12 @@ describe('Chart.DatasetController', function() { expect(chart._stacks).toEqual({ 'x.y.1': { - 0: {0: 1}, - 1: {0: 10} + 0: {0: 1, _top: 2, _bottom: null}, + 1: {0: 10, _top: 2, _bottom: null} }, 'x.y.2': { - 0: {1: 2, 2: 3}, - 1: {1: 20, 2: 30} + 0: {1: 2, 2: 3, _top: 2, _bottom: null}, + 1: {1: 20, 2: 30, _top: 2, _bottom: null} } }); }); @@ -584,12 +584,12 @@ describe('Chart.DatasetController', function() { expect(chart._stacks).toEqual({ 'x.y.1': { - 0: {0: 1, 2: 3}, - 1: {0: 10, 2: 30} + 0: {0: 1, 2: 3, _top: 2, _bottom: null}, + 1: {0: 10, 2: 30, _top: 2, _bottom: null} }, 'x.y.2': { - 0: {1: 2}, - 1: {1: 20} + 0: {1: 2, _top: 1, _bottom: null}, + 1: {1: 20, _top: 1, _bottom: null} } }); @@ -598,12 +598,12 @@ describe('Chart.DatasetController', function() { expect(chart._stacks).toEqual({ 'x.y.1': { - 0: {0: 1, 2: 4}, - 1: {0: 10} + 0: {0: 1, 2: 4, _top: 2, _bottom: null}, + 1: {0: 10, _top: 2, _bottom: null} }, 'x.y.2': { - 0: {1: 2}, - 1: {1: 20} + 0: {1: 2, _top: 1, _bottom: null}, + 1: {1: 20, _top: 1, _bottom: null} } }); }); @@ -719,7 +719,7 @@ describe('Chart.DatasetController', function() { }); var meta = chart.getDatasetMeta(0); - expect(meta._parsed[0]._stacks).toEqual(jasmine.objectContaining({y: {0: 10, 1: 20}})); + expect(meta._parsed[0]._stacks).toEqual(jasmine.objectContaining({y: {0: 10, 1: 20, _top: null, _bottom: null}})); }); describe('resolveDataElementOptions', function() {