Skip to content

Commit c0ec50f

Browse files
committed
Fix remaining handleEvent issues
1 parent f7d2bbd commit c0ec50f

File tree

11 files changed

+307
-164
lines changed

11 files changed

+307
-164
lines changed

docs/getting-started/v3-migration.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,13 @@ Animation system was completely rewritten in Chart.js v3. Each property can now
8585

8686
### Removed
8787

88+
* `Chart.borderWidth`
8889
* `Chart.chart.chart`
8990
* `Chart.Controller`
91+
* `Chart.innerRadius`
92+
* `Chart.offsetX`
93+
* `Chart.offsetY`
94+
* `Chart.outerRadius`
9095
* `Chart.prototype.generateLegend`
9196
* `Chart.types`
9297
* `Chart.Tooltip` is now provided by the tooltip plugin. The positioners can be accessed from `tooltipPlugin.positioners`

src/controllers/controller.doughnut.js

Lines changed: 86 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import defaults from '../core/core.defaults';
55
import Arc from '../elements/element.arc';
66
import {isArray, valueOrDefault} from '../helpers/helpers.core';
77

8+
/**
9+
* @typedef { import("../core/core.controller").default } Chart
10+
*/
11+
812
const PI = Math.PI;
913
const DOUBLE_PI = PI * 2;
1014
const HALF_PI = PI / 2;
@@ -96,13 +100,45 @@ defaults.set('doughnut', {
96100
}
97101
});
98102

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

101135
constructor(chart, datasetIndex) {
102136
super(chart, datasetIndex);
103137

104138
this.innerRadius = undefined;
105139
this.outerRadius = undefined;
140+
this.offsetX = undefined;
141+
this.offsetY = undefined;
106142
}
107143

108144
linkScales() {}
@@ -133,62 +169,33 @@ class DoughnutController extends DatasetController {
133169
return ringIndex;
134170
}
135171

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

188195
meta.total = me.calculateTotal();
189196

190-
me.outerRadius = chart.outerRadius - chart.radiusLength * me._getRingWeightOffset(me.index);
191-
me.innerRadius = Math.max(me.outerRadius - chart.radiusLength * chartWeight, 0);
197+
me.outerRadius = outerRadius - radiusLength * me._getRingWeightOffset(me.index);
198+
me.innerRadius = Math.max(me.outerRadius - radiusLength * chartWeight, 0);
192199

193200
me.updateElements(arcs, 0, mode);
194201
}
@@ -203,9 +210,15 @@ class DoughnutController extends DatasetController {
203210
return reset && opts.animation.animateRotate ? 0 : meta.data[i].hidden ? 0 : me.calculateCircumference(meta._parsed[i] * opts.circumference / DOUBLE_PI);
204211
}
205212

213+
/**
214+
* @param {Arc[]} arcs
215+
* @param {number} start
216+
* @param {string} mode
217+
*/
206218
updateElements(arcs, start, mode) {
207219
const me = this;
208220
const reset = mode === 'reset';
221+
/** @type {Chart} */
209222
const chart = me.chart;
210223
const chartArea = chart.chartArea;
211224
const opts = chart.options;
@@ -214,6 +227,9 @@ class DoughnutController extends DatasetController {
214227
const centerY = (chartArea.top + chartArea.bottom) / 2;
215228
const innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
216229
const outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
230+
const firstOpts = me._resolveDataElementOptions(start, mode);
231+
const sharedOptions = me._getSharedOptions(mode, arcs[start], firstOpts);
232+
const includeOptions = me._includeOptions(mode, sharedOptions);
217233
let startAngle = opts.rotation;
218234
let i;
219235

@@ -225,21 +241,23 @@ class DoughnutController extends DatasetController {
225241
const index = start + i;
226242
const circumference = me._circumference(index, reset);
227243
const arc = arcs[i];
228-
const options = arc._options || {};
229244
const properties = {
230-
x: centerX + chart.offsetX,
231-
y: centerY + chart.offsetY,
245+
x: centerX + me.offsetX,
246+
y: centerY + me.offsetY,
232247
startAngle,
233248
endAngle: startAngle + circumference,
234249
circumference,
235250
outerRadius,
236-
innerRadius,
237-
options
251+
innerRadius
238252
};
253+
if (includeOptions) {
254+
properties.options = me._resolveDataElementOptions(index, mode);
255+
}
239256
startAngle += circumference;
240257

241258
me._updateElement(arc, index, properties, mode);
242259
}
260+
me._updateSharedOptions(sharedOptions, mode);
243261
}
244262

245263
calculateTotal() {
@@ -255,13 +273,12 @@ class DoughnutController extends DatasetController {
255273
}
256274
}
257275

258-
/* if (total === 0) {
259-
total = NaN;
260-
}*/
261-
262276
return total;
263277
}
264278

279+
/**
280+
* @param {number} value
281+
*/
265282
calculateCircumference(value) {
266283
var total = this._cachedMeta.total;
267284
if (total > 0 && !isNaN(value)) {
@@ -270,7 +287,10 @@ class DoughnutController extends DatasetController {
270287
return 0;
271288
}
272289

273-
// gets the max border or hover width to properly scale pie charts
290+
/**
291+
* gets the max border or hover width to properly scale pie charts
292+
* @param {Arc[]} [arcs]
293+
*/
274294
getMaxBorderWidth(arcs) {
275295
var me = this;
276296
var max = 0;
@@ -307,6 +327,7 @@ class DoughnutController extends DatasetController {
307327

308328
/**
309329
* Get radius length offset of the dataset in relation to the visible datasets weights. This allows determining the inner and outer radius correctly
330+
* @param {number} datasetIndex
310331
* @private
311332
*/
312333
_getRingWeightOffset(datasetIndex) {
@@ -322,10 +343,11 @@ class DoughnutController extends DatasetController {
322343
}
323344

324345
/**
346+
* @param {number} datasetIndex
325347
* @private
326348
*/
327-
_getRingWeight(dataSetIndex) {
328-
return Math.max(valueOrDefault(this.chart.data.datasets[dataSetIndex].weight, 1), 0);
349+
_getRingWeight(datasetIndex) {
350+
return Math.max(valueOrDefault(this.chart.data.datasets[datasetIndex].weight, 1), 0);
329351
}
330352

331353
/**

src/core/core.controller.js

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ class Chart {
201201
this.chartArea = undefined;
202202
this.data = undefined;
203203
this.active = undefined;
204-
this.lastActive = undefined;
204+
this.lastActive = [];
205205
this._lastEvent = undefined;
206206
this._listeners = {resize: undefined};
207207
this._sortedMetasets = [];
@@ -561,7 +561,7 @@ class Chart {
561561

562562
// Replay last event from before update
563563
if (me._lastEvent) {
564-
me.eventHandler(me._lastEvent);
564+
me.eventHandler(me._lastEvent, true);
565565
}
566566

567567
me.render();
@@ -788,10 +788,16 @@ class Chart {
788788
return Interaction.modes.index(this, e, {intersect: false});
789789
}
790790

791-
getElementsAtEventForMode(e, mode, options) {
791+
/**
792+
* @param {IEvent} e
793+
* @param {string} mode
794+
* @param {any} options
795+
* @param {boolean} [useFinalPosition]
796+
*/
797+
getElementsAtEventForMode(e, mode, options, useFinalPosition) {
792798
const method = Interaction.modes[mode];
793799
if (typeof method === 'function') {
794-
return method(this, e, options);
800+
return method(this, e, options, useFinalPosition);
795801
}
796802

797803
return [];
@@ -961,6 +967,11 @@ class Chart {
961967
});
962968
}
963969

970+
/**
971+
* @param {{datasetIndex: number, index: number, element: Element}[]} items
972+
* @param {string} mode
973+
* @param {boolean} enabled
974+
*/
964975
updateHoverStyle(items, mode, enabled) {
965976
const prefix = enabled ? 'set' : 'remove';
966977
let meta, item, i, ilen;
@@ -998,18 +1009,20 @@ class Chart {
9981009
}
9991010

10001011
/**
1012+
* @param {IEvent} e
1013+
* @param {boolean} [replay]
10011014
* @private
10021015
*/
1003-
eventHandler(e) {
1016+
eventHandler(e, replay) {
10041017
const me = this;
10051018

1006-
if (plugins.notify(me, 'beforeEvent', [e]) === false) {
1019+
if (plugins.notify(me, 'beforeEvent', [e, replay]) === false) {
10071020
return;
10081021
}
10091022

1010-
me.handleEvent(e);
1023+
me.handleEvent(e, replay);
10111024

1012-
plugins.notify(me, 'afterEvent', [e]);
1025+
plugins.notify(me, 'afterEvent', [e, replay]);
10131026

10141027
me.render();
10151028

@@ -1020,22 +1033,21 @@ class Chart {
10201033
* Handle an event
10211034
* @private
10221035
* @param {IEvent} e the event to handle
1036+
* @param {boolean} [replay]
10231037
* @return {boolean} true if the chart needs to re-render
10241038
*/
1025-
handleEvent(e) {
1039+
handleEvent(e, replay) {
10261040
const me = this;
1027-
const options = me.options || {};
1041+
const options = me.options;
10281042
const hoverOptions = options.hover;
10291043
let changed = false;
10301044

1031-
me.lastActive = me.lastActive || [];
1032-
10331045
// Find Active Elements for hover and tooltips
10341046
if (e.type === 'mouseout') {
10351047
me.active = [];
10361048
me._lastEvent = null;
10371049
} else {
1038-
me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
1050+
me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, replay);
10391051
me._lastEvent = e.type === 'click' ? me._lastEvent : e;
10401052
}
10411053

@@ -1051,7 +1063,7 @@ class Chart {
10511063
}
10521064

10531065
changed = !helpers._elementsEqual(me.active, me.lastActive);
1054-
if (changed) {
1066+
if (changed || replay) {
10551067
me._updateHoverStyles();
10561068
}
10571069

0 commit comments

Comments
 (0)