From b32fb48574e52e31e7fc129d0ac7c25310323f95 Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Wed, 21 Jul 2021 14:13:45 +0300 Subject: [PATCH] Bar: add 'middle' option for borderSkipped (#9452) * Bar: add 'middle' option for borderSkipped * Split in 2 --- docs/charts/bar.md | 1 + docs/configuration/elements.md | 2 +- src/controllers/controller.bar.js | 71 +++++++++++++++++- src/elements/element.bar.js | 38 +--------- .../controller.bar/borderSkipped/middle.js | 38 ++++++++++ .../controller.bar/borderSkipped/middle.png | Bin 0 -> 11207 bytes 6 files changed, 111 insertions(+), 39 deletions(-) create mode 100644 test/fixtures/controller.bar/borderSkipped/middle.js create mode 100644 test/fixtures/controller.bar/borderSkipped/middle.png diff --git a/docs/charts/bar.md b/docs/charts/bar.md index 71473b41d..00358e849 100644 --- a/docs/charts/bar.md +++ b/docs/charts/bar.md @@ -157,6 +157,7 @@ Options are: * `'start'` * `'end'` +* `'middle'` (only valid on stacked bars: the borders between bars are skipped) * `'bottom'` * `'left'` * `'top'` diff --git a/docs/configuration/elements.md b/docs/configuration/elements.md index cc050b64d..a8ec4f178 100644 --- a/docs/configuration/elements.md +++ b/docs/configuration/elements.md @@ -77,7 +77,7 @@ Namespace: `options.elements.bar`, global bar options: `Chart.defaults.elements. | `backgroundColor` | [`Color`](/general/colors.md) | `Chart.defaults.backgroundColor` | Bar fill color. | `borderWidth` | `number` | `0` | Bar stroke width. | `borderColor` | [`Color`](/general/colors.md) | `Chart.defaults.borderColor` | Bar stroke color. -| `borderSkipped` | `string` | `'start'` | Skipped (excluded) border: `'start'`, `'end'`, `'bottom'`, `'left'`, `'top'` or `'right'`. +| `borderSkipped` | `string` | `'start'` | Skipped (excluded) border: `'start'`, `'end'`, `'middle'`, `'bottom'`, `'left'`, `'top'`, `'right'` or `false`. | `borderRadius` | `number`\|`object` | `0` | The bar border radius (in pixels). | [`pointStyle`](#point-styles) | `string`\|`Image` | `'circle'` | Style of the point for legend. diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index 1ab4f2217..49b7b5e25 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -177,6 +177,72 @@ function barSign(size, vScale, actualBase) { return (vScale.isHorizontal() ? 1 : -1) * (vScale.min >= actualBase ? 1 : -1); } +function borderProps(properties) { + let reverse, start, end, top, bottom; + if (properties.horizontal) { + reverse = properties.base > properties.x; + start = 'left'; + end = 'right'; + } else { + reverse = properties.base < properties.y; + start = 'bottom'; + end = 'top'; + } + if (reverse) { + top = 'end'; + bottom = 'start'; + } else { + top = 'start'; + bottom = 'end'; + } + return {start, end, reverse, top, bottom}; +} + +function setBorderSkipped(properties, options, stack, index) { + let edge = options.borderSkipped; + const res = {}; + + if (!edge) { + properties.borderSkipped = res; + return; + } + + const {start, end, reverse, top, bottom} = borderProps(properties); + + if (edge === 'middle' && stack) { + properties.enableBorderRadius = true; + if ((stack._top || 0) === index) { + edge = top; + } else if ((stack._bottom || 0) === index) { + edge = bottom; + } else { + res[parseEdge(bottom, start, end, reverse)] = true; + edge = top; + } + } + + res[parseEdge(edge, start, end, reverse)] = true; + properties.borderSkipped = res; +} + +function parseEdge(edge, a, b, reverse) { + if (reverse) { + edge = swap(edge, a, b); + edge = startEnd(edge, b, a); + } else { + edge = startEnd(edge, a, b); + } + return edge; +} + +function swap(orig, v1, v2) { + return orig === v1 ? v2 : orig === v2 ? v1 : orig; +} + +function startEnd(v, start, end) { + return v === 'start' ? start : v === 'end' ? end : v; +} + export default class BarController extends DatasetController { /** @@ -278,7 +344,7 @@ export default class BarController extends DatasetController { updateElements(bars, start, count, mode) { const me = this; const reset = mode === 'reset'; - const vScale = me._cachedMeta.vScale; + const {index, _cachedMeta: {vScale}} = me; const base = vScale.getBasePixel(); const horizontal = vScale.isHorizontal(); const ruler = me._getRuler(); @@ -297,7 +363,7 @@ export default class BarController extends DatasetController { const properties = { horizontal, base: vpixels.base, - enableBorderRadius: !stack || isFloatBar(parsed._custom) || (me.index === stack._top || me.index === stack._bottom), + enableBorderRadius: !stack || isFloatBar(parsed._custom) || (index === stack._top || index === stack._bottom), x: horizontal ? vpixels.head : ipixels.center, y: horizontal ? ipixels.center : vpixels.head, height: horizontal ? ipixels.size : Math.abs(vpixels.size), @@ -307,6 +373,7 @@ export default class BarController extends DatasetController { if (includeOptions) { properties.options = sharedOptions || me.resolveDataElementOptions(i, bars[i].active ? 'active' : mode); } + setBorderSkipped(properties, properties.options || bars[i].options, stack, index); me.updateElement(bars[i], i, properties, mode); } } diff --git a/src/elements/element.bar.js b/src/elements/element.bar.js index fc7ebd46b..049a8984f 100644 --- a/src/elements/element.bar.js +++ b/src/elements/element.bar.js @@ -32,47 +32,13 @@ function getBarBounds(bar, useFinalPosition) { return {left, top, right, bottom}; } -function parseBorderSkipped(bar) { - let edge = bar.options.borderSkipped; - const res = {}; - - if (!edge) { - return res; - } - - edge = bar.horizontal - ? parseEdge(edge, 'left', 'right', bar.base > bar.x) - : parseEdge(edge, 'bottom', 'top', bar.base < bar.y); - - res[edge] = true; - return res; -} - -function parseEdge(edge, a, b, reverse) { - if (reverse) { - edge = swap(edge, a, b); - edge = startEnd(edge, b, a); - } else { - edge = startEnd(edge, a, b); - } - return edge; -} - -function swap(orig, v1, v2) { - return orig === v1 ? v2 : orig === v2 ? v1 : orig; -} - -function startEnd(v, start, end) { - return v === 'start' ? start : v === 'end' ? end : v; -} - function skipOrLimit(skip, value, min, max) { return skip ? 0 : _limitValue(value, min, max); } function parseBorderWidth(bar, maxW, maxH) { const value = bar.options.borderWidth; - const skip = parseBorderSkipped(bar); + const skip = bar.borderSkipped; const o = toTRBL(value); return { @@ -88,7 +54,7 @@ function parseBorderRadius(bar, maxW, maxH) { const value = bar.options.borderRadius; const o = toTRBLCorners(value); const maxR = Math.min(maxW, maxH); - const skip = parseBorderSkipped(bar); + const skip = bar.borderSkipped; // If the value is an object, assume the user knows what they are doing // and apply as directed. diff --git a/test/fixtures/controller.bar/borderSkipped/middle.js b/test/fixtures/controller.bar/borderSkipped/middle.js new file mode 100644 index 000000000..f93c73a4e --- /dev/null +++ b/test/fixtures/controller.bar/borderSkipped/middle.js @@ -0,0 +1,38 @@ +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: { + borderRadius: Number.MAX_VALUE, + borderSkipped: 'middle', + 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/borderSkipped/middle.png b/test/fixtures/controller.bar/borderSkipped/middle.png new file mode 100644 index 0000000000000000000000000000000000000000..89796e0ca51fcaacbb8a872c1debef2ad1c4505c GIT binary patch literal 11207 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 literal 0 Hc-jL100001 -- 2.47.2