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:
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,
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}}
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);
}
}
import Element from '../core/core.element';
+import {isObject} from '../helpers';
import {addRoundedRectPath} from '../helpers/helpers.canvas';
import {toTRBL, toTRBLCorners} from '../helpers/helpers.options';
}
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)
};
}
borderSkipped: 'start',
borderWidth: 0,
borderRadius: 0,
+ enableBorderRadius: true,
pointStyle: undefined
};
--- /dev/null
+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
+ }
+ }
+};
--- /dev/null
+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
+ }
+ }
+};
--- /dev/null
+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
+ }
+ }
+};
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}
}
});
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}
}
});
});
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}
}
});
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}
}
});
});
});
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() {