Skip to content

Commit db8f6c3

Browse files
nagixsimonbrunel
authored andcommitted
Add support for 'inner' border for arc elements (#5841)
1 parent 4fb259e commit db8f6c3

21 files changed

+511
-68
lines changed

docs/charts/doughnut.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,21 @@ The doughnut/pie chart allows a number of properties to be specified for each da
5555

5656
| Name | Type | Description
5757
| ---- | ---- | -----------
58-
| `backgroundColor` | `Color[]` | The fill color of the arcs in the dataset. See [Colors](../general/colors.md#colors).
59-
| `borderColor` | `Color[]` | The border color of the arcs in the dataset. See [Colors](../general/colors.md#colors).
60-
| `borderWidth` | `Number[]` | The border width of the arcs in the dataset.
61-
| `hoverBackgroundColor` | `Color[]` | The fill colour of the arcs when hovered.
62-
| `hoverBorderColor` | `Color[]` | The stroke colour of the arcs when hovered.
63-
| `hoverBorderWidth` | `Number[]` | The stroke width of the arcs when hovered.
58+
| `backgroundColor` | `Color/Color[]` | The fill color of the arcs in the dataset. See [Colors](../general/colors.md#colors).
59+
| `borderColor` | `Color/Color[]` | The border color of the arcs in the dataset. See [Colors](../general/colors.md#colors).
60+
| `borderWidth` | `Number/Number[]` | The border width of the arcs in the dataset.
61+
| `borderAlign` | `String/String[]` | The border alignment of the arcs in the dataset. [more...](#border-alignment)
62+
| `hoverBackgroundColor` | `Color/Color[]` | The fill colour of the arcs when hovered.
63+
| `hoverBorderColor` | `Color/Color[]` | The stroke colour of the arcs when hovered.
64+
| `hoverBorderWidth` | `Number/Number[]` | The stroke width of the arcs when hovered.
65+
66+
### Border Alignment
67+
68+
The following values are supported for `borderAlign`.
69+
* `'center'` (default)
70+
* `'inner'`
71+
72+
When `'center'` is set, the borders of arcs next to each other will overlap. When `'inner'` is set, it is guaranteed that all the borders are not overlap.
6473

6574
## Config Options
6675

docs/charts/polar.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,21 @@ The following options can be included in a polar area chart dataset to configure
4646

4747
| Name | Type | Description
4848
| ---- | ---- | -----------
49-
| `backgroundColor` | `Color[]` | The fill color of the arcs in the dataset. See [Colors](../general/colors.md#colors).
50-
| `borderColor` | `Color[]` | The border color of the arcs in the dataset. See [Colors](../general/colors.md#colors).
51-
| `borderWidth` | `Number[]` | The border width of the arcs in the dataset.
52-
| `hoverBackgroundColor` | `Color[]` | The fill colour of the arcs when hovered.
53-
| `hoverBorderColor` | `Color[]` | The stroke colour of the arcs when hovered.
54-
| `hoverBorderWidth` | `Number[]` | The stroke width of the arcs when hovered.
49+
| `backgroundColor` | `Color/Color[]` | The fill color of the arcs in the dataset. See [Colors](../general/colors.md#colors).
50+
| `borderColor` | `Color/Color[]` | The border color of the arcs in the dataset. See [Colors](../general/colors.md#colors).
51+
| `borderWidth` | `Number/Number[]` | The border width of the arcs in the dataset.
52+
| `borderAlign` | `String/String[]` | The border alignment of the arcs in the dataset. [more...](#border-alignment)
53+
| `hoverBackgroundColor` | `Color/Color[]` | The fill colour of the arcs when hovered.
54+
| `hoverBorderColor` | `Color/Color[]` | The stroke colour of the arcs when hovered.
55+
| `hoverBorderWidth` | `Number/Number[]` | The stroke width of the arcs when hovered.
56+
57+
### Border Alignment
58+
59+
The following values are supported for `borderAlign`.
60+
* `'center'` (default)
61+
* `'inner'`
62+
63+
When `'center'` is set, the borders of arcs next to each other will overlap. When `'inner'` is set, it is guaranteed that all the borders are not overlap.
5564

5665
## Config Options
5766

src/controllers/controller.doughnut.js

Lines changed: 66 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -143,14 +143,15 @@ module.exports = DatasetController.extend({
143143
var chart = me.chart;
144144
var chartArea = chart.chartArea;
145145
var opts = chart.options;
146-
var arcOpts = opts.elements.arc;
147-
var availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth;
148-
var availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth;
146+
var availableWidth = chartArea.right - chartArea.left;
147+
var availableHeight = chartArea.bottom - chartArea.top;
149148
var minSize = Math.min(availableWidth, availableHeight);
150149
var offset = {x: 0, y: 0};
151150
var meta = me.getMeta();
151+
var arcs = meta.data;
152152
var cutoutPercentage = opts.cutoutPercentage;
153153
var circumference = opts.circumference;
154+
var i, ilen;
154155

155156
// If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc
156157
if (circumference < Math.PI * 2.0) {
@@ -171,7 +172,11 @@ module.exports = DatasetController.extend({
171172
offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5};
172173
}
173174

174-
chart.borderWidth = me.getMaxBorderWidth(meta.data);
175+
for (i = 0, ilen = arcs.length; i < ilen; ++i) {
176+
arcs[i]._options = me._resolveElementOptions(arcs[i], i, reset);
177+
}
178+
179+
chart.borderWidth = me.getMaxBorderWidth();
175180
chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0);
176181
chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0);
177182
chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
@@ -183,9 +188,9 @@ module.exports = DatasetController.extend({
183188
me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index));
184189
me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0);
185190

186-
helpers.each(meta.data, function(arc, index) {
187-
me.updateElement(arc, index, reset);
188-
});
191+
for (i = 0, ilen = arcs.length; i < ilen; ++i) {
192+
me.updateElement(arcs[i], i, reset);
193+
}
189194
},
190195

191196
updateElement: function(arc, index, reset) {
@@ -202,7 +207,7 @@ module.exports = DatasetController.extend({
202207
var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI));
203208
var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
204209
var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
205-
var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
210+
var options = arc._options || {};
206211

207212
helpers.extend(arc, {
208213
// Utility
@@ -211,27 +216,23 @@ module.exports = DatasetController.extend({
211216

212217
// Desired view properties
213218
_model: {
219+
backgroundColor: options.backgroundColor,
220+
borderColor: options.borderColor,
221+
borderWidth: options.borderWidth,
222+
borderAlign: options.borderAlign,
214223
x: centerX + chart.offsetX,
215224
y: centerY + chart.offsetY,
216225
startAngle: startAngle,
217226
endAngle: endAngle,
218227
circumference: circumference,
219228
outerRadius: outerRadius,
220229
innerRadius: innerRadius,
221-
label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])
230+
label: helpers.valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])
222231
}
223232
});
224233

225234
var model = arc._model;
226235

227-
// Resets the visual styles
228-
var custom = arc.custom || {};
229-
var valueOrDefault = helpers.valueAtIndexOrDefault;
230-
var elementOpts = this.chart.options.elements.arc;
231-
model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor);
232-
model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor);
233-
model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth);
234-
235236
// Set correct angles if not resetting
236237
if (!reset || !animationOpts.animateRotate) {
237238
if (index === 0) {
@@ -276,19 +277,58 @@ module.exports = DatasetController.extend({
276277

277278
// gets the max border or hover width to properly scale pie charts
278279
getMaxBorderWidth: function(arcs) {
280+
var me = this;
279281
var max = 0;
280-
var index = this.index;
281-
var length = arcs.length;
282-
var borderWidth;
283-
var hoverWidth;
282+
var chart = me.chart;
283+
var i, ilen, meta, arc, controller, options, borderWidth, hoverWidth;
284+
285+
if (!arcs) {
286+
// Find the outmost visible dataset
287+
for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
288+
if (chart.isDatasetVisible(i)) {
289+
meta = chart.getDatasetMeta(i);
290+
arcs = meta.data;
291+
if (i !== me.index) {
292+
controller = meta.controller;
293+
}
294+
break;
295+
}
296+
}
297+
}
298+
299+
if (!arcs) {
300+
return 0;
301+
}
284302

285-
for (var i = 0; i < length; i++) {
286-
borderWidth = arcs[i]._model ? arcs[i]._model.borderWidth : 0;
287-
hoverWidth = arcs[i]._chart ? arcs[i]._chart.config.data.datasets[index].hoverBorderWidth : 0;
303+
for (i = 0, ilen = arcs.length; i < ilen; ++i) {
304+
arc = arcs[i];
305+
options = controller ? controller._resolveElementOptions(arc, i) : arc._options;
306+
if (options.borderAlign !== 'inner') {
307+
borderWidth = options.borderWidth;
308+
hoverWidth = options.hoverBorderWidth;
288309

289-
max = borderWidth > max ? borderWidth : max;
290-
max = hoverWidth > max ? hoverWidth : max;
310+
max = borderWidth > max ? borderWidth : max;
311+
max = hoverWidth > max ? hoverWidth : max;
312+
}
291313
}
292314
return max;
315+
},
316+
317+
/**
318+
* @private
319+
*/
320+
_resolveElementOptions: function(arc, index) {
321+
var me = this;
322+
var dataset = me.getDataset();
323+
var custom = arc.custom || {};
324+
var options = me.chart.options.elements.arc;
325+
var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
326+
327+
return {
328+
backgroundColor: custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(dataset.backgroundColor, index, options.backgroundColor),
329+
borderColor: custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(dataset.borderColor, index, options.borderColor),
330+
borderWidth: custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(dataset.borderWidth, index, options.borderWidth),
331+
borderAlign: custom.borderAlign ? custom.borderAlign : valueAtIndexOrDefault(dataset.borderAlign, index, options.borderAlign)
332+
};
293333
}
294334
});

src/controllers/controller.polarArea.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,9 @@ module.exports = DatasetController.extend({
148148
var chart = me.chart;
149149
var chartArea = chart.chartArea;
150150
var opts = chart.options;
151-
var arcOpts = opts.elements.arc;
152151
var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
153152

154-
chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0);
153+
chart.outerRadius = Math.max(minSize / 2, 0);
155154
chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
156155
chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
157156

@@ -206,6 +205,7 @@ module.exports = DatasetController.extend({
206205
model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor);
207206
model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor);
208207
model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth);
208+
model.borderAlign = custom.borderAlign ? custom.borderAlign : valueOrDefault(dataset.borderAlign, index, elementOpts.borderAlign);
209209

210210
arc.pivot();
211211
},

src/elements/element.arc.js

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ defaults._set('global', {
99
arc: {
1010
backgroundColor: defaults.global.defaultColor,
1111
borderColor: '#fff',
12-
borderWidth: 2
12+
borderWidth: 2,
13+
borderAlign: 'center'
1314
}
1415
}
1516
});
@@ -85,23 +86,51 @@ module.exports = Element.extend({
8586
var vm = this._view;
8687
var sA = vm.startAngle;
8788
var eA = vm.endAngle;
89+
var pixelMargin = (vm.borderAlign === 'inner') ? 0.33 : 0;
90+
var angleMargin;
8891

89-
ctx.beginPath();
92+
ctx.save();
9093

91-
ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA);
94+
ctx.beginPath();
95+
ctx.arc(vm.x, vm.y, vm.outerRadius - pixelMargin, sA, eA);
9296
ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true);
93-
9497
ctx.closePath();
95-
ctx.strokeStyle = vm.borderColor;
96-
ctx.lineWidth = vm.borderWidth;
9798

9899
ctx.fillStyle = vm.backgroundColor;
99-
100100
ctx.fill();
101-
ctx.lineJoin = 'bevel';
102101

103102
if (vm.borderWidth) {
103+
if (vm.borderAlign === 'inner') {
104+
// Draw an inner border by cliping the arc and drawing a double-width border
105+
// Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders
106+
ctx.beginPath();
107+
angleMargin = pixelMargin / vm.outerRadius;
108+
ctx.arc(vm.x, vm.y, vm.outerRadius, sA - angleMargin, eA + angleMargin);
109+
if (vm.innerRadius > pixelMargin) {
110+
angleMargin = pixelMargin / vm.innerRadius;
111+
ctx.arc(vm.x, vm.y, vm.innerRadius - pixelMargin, eA + angleMargin, sA - angleMargin, true);
112+
} else {
113+
ctx.arc(vm.x, vm.y, pixelMargin, eA + Math.PI / 2, sA - Math.PI / 2);
114+
}
115+
ctx.closePath();
116+
ctx.clip();
117+
118+
ctx.beginPath();
119+
ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA);
120+
ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true);
121+
ctx.closePath();
122+
123+
ctx.lineWidth = vm.borderWidth * 2;
124+
ctx.lineJoin = 'round';
125+
} else {
126+
ctx.lineWidth = vm.borderWidth;
127+
ctx.lineJoin = 'bevel';
128+
}
129+
130+
ctx.strokeStyle = vm.borderColor;
104131
ctx.stroke();
105132
}
133+
134+
ctx.restore();
106135
}
107136
});

test/context.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ Context.prototype._initMethods = function() {
7979
beginPath: function() {},
8080
bezierCurveTo: function() {},
8181
clearRect: function() {},
82+
clip: function() {},
8283
closePath: function() {},
8384
fill: function() {},
8485
fillRect: function() {},
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"config": {
3+
"type": "doughnut",
4+
"data": {
5+
"labels": ["A", "B", "C", "D", "E"],
6+
"datasets": [{
7+
"data": [1, 5, 10, 50, 100],
8+
"backgroundColor": [
9+
"rgba(255, 99, 132, 0.8)",
10+
"rgba(54, 162, 235, 0.8)",
11+
"rgba(255, 206, 86, 0.8)",
12+
"rgba(75, 192, 192, 0.8)",
13+
"rgba(153, 102, 255, 0.8)"
14+
],
15+
"borderWidth": 20,
16+
"borderColor": [
17+
"rgb(255, 99, 132)",
18+
"rgb(54, 162, 235)",
19+
"rgb(255, 206, 86)",
20+
"rgb(75, 192, 192)",
21+
"rgb(153, 102, 255)"
22+
]
23+
}]
24+
},
25+
"options": {
26+
"responsive": false,
27+
"legend": false,
28+
"title": false
29+
}
30+
}
31+
}
Loading
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"config": {
3+
"type": "doughnut",
4+
"data": {
5+
"labels": ["A", "B", "C", "D", "E"],
6+
"datasets": [{
7+
"data": [1, 5, 10, 50, 100],
8+
"backgroundColor": [
9+
"rgba(255, 99, 132, 0.8)",
10+
"rgba(54, 162, 235, 0.8)",
11+
"rgba(255, 206, 86, 0.8)",
12+
"rgba(75, 192, 192, 0.8)",
13+
"rgba(153, 102, 255, 0.8)"
14+
],
15+
"borderWidth": 20,
16+
"borderColor": [
17+
"rgb(255, 99, 132)",
18+
"rgb(54, 162, 235)",
19+
"rgb(255, 206, 86)",
20+
"rgb(75, 192, 192)",
21+
"rgb(153, 102, 255)"
22+
],
23+
"borderAlign": "inner"
24+
}]
25+
},
26+
"options": {
27+
"responsive": false,
28+
"legend": false,
29+
"title": false
30+
}
31+
}
32+
}
Loading

0 commit comments

Comments
 (0)