]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Bar: add 'middle' option for borderSkipped (#9452)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Wed, 21 Jul 2021 11:13:45 +0000 (14:13 +0300)
committerGitHub <noreply@github.com>
Wed, 21 Jul 2021 11:13:45 +0000 (07:13 -0400)
* Bar: add 'middle' option for borderSkipped
* Split in 2

docs/charts/bar.md
docs/configuration/elements.md
src/controllers/controller.bar.js
src/elements/element.bar.js
test/fixtures/controller.bar/borderSkipped/middle.js [new file with mode: 0644]
test/fixtures/controller.bar/borderSkipped/middle.png [new file with mode: 0644]

index 71473b41dd0369fd335eff1f696574e211d3d9e8..00358e849e22fc07d815085de2746cf815a515d6 100644 (file)
@@ -157,6 +157,7 @@ Options are:
 
 * `'start'`
 * `'end'`
+* `'middle'` (only valid on stacked bars: the borders between bars are skipped)
 * `'bottom'`
 * `'left'`
 * `'top'`
index cc050b64d3d7597cc1d7cf3004d9edbdee74accf..a8ec4f1789d5f56d1053ddec45e6b6af1be554de 100644 (file)
@@ -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.
 
index 1ab4f2217fb8417758c76d824409639392e41913..49b7b5e25ba6e040e5aa3a47ee676db0e2a6f1b2 100644 (file)
@@ -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);
     }
   }
index fc7ebd46b5973bddc0ac09a910d1945a642d472d..049a8984f0f848f652ff70bd1f71b731431f07ae 100644 (file)
@@ -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 (file)
index 0000000..f93c73a
--- /dev/null
@@ -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 (file)
index 0000000..89796e0
Binary files /dev/null and b/test/fixtures/controller.bar/borderSkipped/middle.png differ