diff --git a/docs/charts/bar.md b/docs/charts/bar.md index 71473b41dd0..00358e849e2 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 cc050b64d3d..a8ec4f1789d 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 1ab4f2217fb..49b7b5e25ba 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 fc7ebd46b59..049a8984f0f 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 00000000000..f93c73a4e90 --- /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 00000000000..89796e0ca51 Binary files /dev/null and b/test/fixtures/controller.bar/borderSkipped/middle.png differ