Skip to content

Commit 7b029aa

Browse files
authored
Doughnut / PolarArea cleanup (#7113)
Doughnut / PolarArea cleanup
1 parent 3682da7 commit 7b029aa

File tree

4 files changed

+81
-86
lines changed

4 files changed

+81
-86
lines changed

docs/getting-started/v3-migration.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,16 @@ Animation system was completely rewritten in Chart.js v3. Each property can now
8686

8787
### Removed
8888

89+
* `Chart.borderWidth`
8990
* `Chart.chart.chart`
9091
* `Chart.Controller`
92+
* `Chart.innerRadius`
93+
* `Chart.offsetX`
94+
* `Chart.offsetY`
95+
* `Chart.outerRadius`
9196
* `Chart.prototype.generateLegend`
9297
* `Chart.platform`. It only contained `disableCSSInjection`. CSS is never injected in v3.
98+
* `Chart.radiusLength`
9399
* `Chart.types`
94100
* `Chart.Tooltip` is now provided by the tooltip plugin. The positioners can be accessed from `tooltipPlugin.positioners`
95101
* `DatasetController.addElementAndReset`

src/controllers/controller.doughnut.js

Lines changed: 69 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import defaults from '../core/core.defaults';
33
import Arc from '../elements/element.arc';
44
import {isArray, valueOrDefault} from '../helpers/helpers.core';
55

6+
/**
7+
* @typedef { import("../core/core.controller").default } Chart
8+
*/
9+
610
const PI = Math.PI;
711
const DOUBLE_PI = PI * 2;
812
const HALF_PI = PI / 2;
@@ -94,13 +98,45 @@ defaults.set('doughnut', {
9498
}
9599
});
96100

101+
function getRatioAndOffset(rotation, circumference, cutout) {
102+
let ratioX = 1;
103+
let ratioY = 1;
104+
let offsetX = 0;
105+
let offsetY = 0;
106+
// If the chart's circumference isn't a full circle, calculate size as a ratio of the width/height of the arc
107+
if (circumference < DOUBLE_PI) {
108+
let startAngle = rotation % DOUBLE_PI;
109+
startAngle += startAngle >= PI ? -DOUBLE_PI : startAngle < -PI ? DOUBLE_PI : 0;
110+
const endAngle = startAngle + circumference;
111+
const startX = Math.cos(startAngle);
112+
const startY = Math.sin(startAngle);
113+
const endX = Math.cos(endAngle);
114+
const endY = Math.sin(endAngle);
115+
const contains0 = (startAngle <= 0 && endAngle >= 0) || endAngle >= DOUBLE_PI;
116+
const contains90 = (startAngle <= HALF_PI && endAngle >= HALF_PI) || endAngle >= DOUBLE_PI + HALF_PI;
117+
const contains180 = startAngle === -PI || endAngle >= PI;
118+
const contains270 = (startAngle <= -HALF_PI && endAngle >= -HALF_PI) || endAngle >= PI + HALF_PI;
119+
const minX = contains180 ? -1 : Math.min(startX, startX * cutout, endX, endX * cutout);
120+
const minY = contains270 ? -1 : Math.min(startY, startY * cutout, endY, endY * cutout);
121+
const maxX = contains0 ? 1 : Math.max(startX, startX * cutout, endX, endX * cutout);
122+
const maxY = contains90 ? 1 : Math.max(startY, startY * cutout, endY, endY * cutout);
123+
ratioX = (maxX - minX) / 2;
124+
ratioY = (maxY - minY) / 2;
125+
offsetX = -(maxX + minX) / 2;
126+
offsetY = -(maxY + minY) / 2;
127+
}
128+
return {ratioX, ratioY, offsetX, offsetY};
129+
}
130+
97131
class DoughnutController extends DatasetController {
98132

99133
constructor(chart, datasetIndex) {
100134
super(chart, datasetIndex);
101135

102136
this.innerRadius = undefined;
103137
this.outerRadius = undefined;
138+
this.offsetX = undefined;
139+
this.offsetY = undefined;
104140
}
105141

106142
linkScales() {}
@@ -131,61 +167,31 @@ class DoughnutController extends DatasetController {
131167
return ringIndex;
132168
}
133169

170+
/**
171+
* @param {string} mode
172+
*/
134173
update(mode) {
135174
const me = this;
136175
const chart = me.chart;
137-
const chartArea = chart.chartArea;
138-
const opts = chart.options;
139-
let ratioX = 1;
140-
let ratioY = 1;
141-
let offsetX = 0;
142-
let offsetY = 0;
176+
const {chartArea, options} = chart;
143177
const meta = me._cachedMeta;
144178
const arcs = meta.data;
145-
const cutout = opts.cutoutPercentage / 100 || 0;
146-
const circumference = opts.circumference;
179+
const cutout = options.cutoutPercentage / 100 || 0;
147180
const chartWeight = me._getRingWeight(me.index);
148-
149-
// If the chart's circumference isn't a full circle, calculate size as a ratio of the width/height of the arc
150-
if (circumference < DOUBLE_PI) {
151-
let startAngle = opts.rotation % DOUBLE_PI;
152-
startAngle += startAngle >= PI ? -DOUBLE_PI : startAngle < -PI ? DOUBLE_PI : 0;
153-
const endAngle = startAngle + circumference;
154-
const startX = Math.cos(startAngle);
155-
const startY = Math.sin(startAngle);
156-
const endX = Math.cos(endAngle);
157-
const endY = Math.sin(endAngle);
158-
const contains0 = (startAngle <= 0 && endAngle >= 0) || endAngle >= DOUBLE_PI;
159-
const contains90 = (startAngle <= HALF_PI && endAngle >= HALF_PI) || endAngle >= DOUBLE_PI + HALF_PI;
160-
const contains180 = startAngle === -PI || endAngle >= PI;
161-
const contains270 = (startAngle <= -HALF_PI && endAngle >= -HALF_PI) || endAngle >= PI + HALF_PI;
162-
const minX = contains180 ? -1 : Math.min(startX, startX * cutout, endX, endX * cutout);
163-
const minY = contains270 ? -1 : Math.min(startY, startY * cutout, endY, endY * cutout);
164-
const maxX = contains0 ? 1 : Math.max(startX, startX * cutout, endX, endX * cutout);
165-
const maxY = contains90 ? 1 : Math.max(startY, startY * cutout, endY, endY * cutout);
166-
ratioX = (maxX - minX) / 2;
167-
ratioY = (maxY - minY) / 2;
168-
offsetX = -(maxX + minX) / 2;
169-
offsetY = -(maxY + minY) / 2;
170-
}
171-
172-
for (let i = 0, ilen = arcs.length; i < ilen; ++i) {
173-
arcs[i]._options = me._resolveDataElementOptions(i, mode);
174-
}
175-
176-
chart.borderWidth = me.getMaxBorderWidth();
177-
const maxWidth = (chartArea.right - chartArea.left - chart.borderWidth) / ratioX;
178-
const maxHeight = (chartArea.bottom - chartArea.top - chart.borderWidth) / ratioY;
179-
chart.outerRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0);
180-
chart.innerRadius = Math.max(chart.outerRadius * cutout, 0);
181-
chart.radiusLength = (chart.outerRadius - chart.innerRadius) / (me._getVisibleDatasetWeightTotal() || 1);
182-
chart.offsetX = offsetX * chart.outerRadius;
183-
chart.offsetY = offsetY * chart.outerRadius;
181+
const {ratioX, ratioY, offsetX, offsetY} = getRatioAndOffset(options.rotation, options.circumference, cutout);
182+
const borderWidth = me.getMaxBorderWidth();
183+
const maxWidth = (chartArea.right - chartArea.left - borderWidth) / ratioX;
184+
const maxHeight = (chartArea.bottom - chartArea.top - borderWidth) / ratioY;
185+
const outerRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0);
186+
const innerRadius = Math.max(outerRadius * cutout, 0);
187+
const radiusLength = (outerRadius - innerRadius) / me._getVisibleDatasetWeightTotal();
188+
me.offsetX = offsetX * outerRadius;
189+
me.offsetY = offsetY * outerRadius;
184190

185191
meta.total = me.calculateTotal();
186192

187-
me.outerRadius = chart.outerRadius - chart.radiusLength * me._getRingWeightOffset(me.index);
188-
me.innerRadius = Math.max(me.outerRadius - chart.radiusLength * chartWeight, 0);
193+
me.outerRadius = outerRadius - radiusLength * me._getRingWeightOffset(me.index);
194+
me.innerRadius = Math.max(me.outerRadius - radiusLength * chartWeight, 0);
189195

190196
me.updateElements(arcs, 0, mode);
191197
}
@@ -209,8 +215,12 @@ class DoughnutController extends DatasetController {
209215
const animationOpts = opts.animation;
210216
const centerX = (chartArea.left + chartArea.right) / 2;
211217
const centerY = (chartArea.top + chartArea.bottom) / 2;
212-
const innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
213-
const outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
218+
const animateScale = reset && animationOpts.animateScale;
219+
const innerRadius = animateScale ? 0 : me.innerRadius;
220+
const outerRadius = animateScale ? 0 : me.outerRadius;
221+
const firstOpts = me._resolveDataElementOptions(start, mode);
222+
const sharedOptions = me._getSharedOptions(mode, arcs[start], firstOpts);
223+
const includeOptions = me._includeOptions(mode, sharedOptions);
214224
let startAngle = opts.rotation;
215225
let i;
216226

@@ -222,21 +232,23 @@ class DoughnutController extends DatasetController {
222232
const index = start + i;
223233
const circumference = me._circumference(index, reset);
224234
const arc = arcs[i];
225-
const options = arc._options || {};
226235
const properties = {
227-
x: centerX + chart.offsetX,
228-
y: centerY + chart.offsetY,
236+
x: centerX + me.offsetX,
237+
y: centerY + me.offsetY,
229238
startAngle,
230239
endAngle: startAngle + circumference,
231240
circumference,
232241
outerRadius,
233-
innerRadius,
234-
options
242+
innerRadius
235243
};
244+
if (includeOptions) {
245+
properties.options = me._resolveDataElementOptions(index, mode);
246+
}
236247
startAngle += circumference;
237248

238249
me._updateElement(arc, index, properties, mode);
239250
}
251+
me._updateSharedOptions(sharedOptions, mode);
240252
}
241253

242254
calculateTotal() {
@@ -252,10 +264,6 @@ class DoughnutController extends DatasetController {
252264
}
253265
}
254266

255-
/* if (total === 0) {
256-
total = NaN;
257-
}*/
258-
259267
return total;
260268
}
261269

@@ -267,7 +275,6 @@ class DoughnutController extends DatasetController {
267275
return 0;
268276
}
269277

270-
// gets the max border or hover width to properly scale pie charts
271278
getMaxBorderWidth(arcs) {
272279
const me = this;
273280
let max = 0;
@@ -321,16 +328,16 @@ class DoughnutController extends DatasetController {
321328
/**
322329
* @private
323330
*/
324-
_getRingWeight(dataSetIndex) {
325-
return Math.max(valueOrDefault(this.chart.data.datasets[dataSetIndex].weight, 1), 0);
331+
_getRingWeight(datasetIndex) {
332+
return Math.max(valueOrDefault(this.chart.data.datasets[datasetIndex].weight, 1), 0);
326333
}
327334

328335
/**
329-
* Returns the sum of all visibile data set weights. This value can be 0.
336+
* Returns the sum of all visibile data set weights.
330337
* @private
331338
*/
332339
_getVisibleDatasetWeightTotal() {
333-
return this._getRingWeightOffset(this.chart.data.datasets.length);
340+
return this._getRingWeightOffset(this.chart.data.datasets.length) || 1;
334341
}
335342
}
336343

src/controllers/controller.polarArea.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,12 @@ class PolarAreaController extends DatasetController {
128128
const opts = chart.options;
129129
const minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
130130

131-
chart.outerRadius = Math.max(minSize / 2, 0);
132-
chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
133-
chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
131+
const outerRadius = Math.max(minSize / 2, 0);
132+
const innerRadius = Math.max(opts.cutoutPercentage ? (outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
133+
const radiusLength = (outerRadius - innerRadius) / chart.getVisibleDatasetCount();
134134

135-
me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index);
136-
me.innerRadius = me.outerRadius - chart.radiusLength;
135+
me.outerRadius = outerRadius - (radiusLength * me.index);
136+
me.innerRadius = me.outerRadius - radiusLength;
137137
}
138138

139139
updateElements(arcs, start, mode) {

test/specs/controller.doughnut.tests.js

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,6 @@ describe('Chart.controllers.doughnut', function() {
4646
expect(meta.data[3] instanceof Chart.elements.Arc).toBe(true);
4747
});
4848

49-
it('should set the innerRadius to 0 if the config option is 0', function() {
50-
var chart = window.acquireChart({
51-
type: 'pie',
52-
data: {
53-
datasets: [{
54-
data: [10, 15, 0, 4]
55-
}],
56-
labels: []
57-
}
58-
});
59-
60-
expect(chart.innerRadius).toBe(0);
61-
});
62-
6349
it ('should reset and update elements', function() {
6450
var chart = window.acquireChart({
6551
type: 'doughnut',
@@ -302,13 +288,9 @@ describe('Chart.controllers.doughnut', function() {
302288

303289
chart.update();
304290

291+
var controller = chart.getDatasetMeta(0).controller;
305292
expect(chart.chartArea.bottom - chart.chartArea.top).toBe(512);
306-
expect(chart.borderWidth).toBe(8);
307-
expect(chart.outerRadius).toBe(252);
308-
expect(chart.innerRadius).toBe(126);
309-
expect(chart.radiusLength).toBe(63);
310293

311-
var controller = chart.getDatasetMeta(0).controller;
312294
expect(controller.getMaxBorderWidth()).toBe(8);
313295
expect(controller.outerRadius).toBe(252);
314296
expect(controller.innerRadius).toBe(189);

0 commit comments

Comments
 (0)