Skip to content

Commit 3ddebe8

Browse files
committed
Fix remaining handleEvent issues
1 parent bfe3421 commit 3ddebe8

File tree

14 files changed

+261
-185
lines changed

14 files changed

+261
-185
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: 64 additions & 58 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() || 1);
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
}
@@ -211,6 +217,9 @@ class DoughnutController extends DatasetController {
211217
const centerY = (chartArea.top + chartArea.bottom) / 2;
212218
const innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
213219
const outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
220+
const firstOpts = me._resolveDataElementOptions(start, mode);
221+
const sharedOptions = me._getSharedOptions(mode, arcs[start], firstOpts);
222+
const includeOptions = me._includeOptions(mode, sharedOptions);
214223
let startAngle = opts.rotation;
215224
let i;
216225

@@ -222,21 +231,23 @@ class DoughnutController extends DatasetController {
222231
const index = start + i;
223232
const circumference = me._circumference(index, reset);
224233
const arc = arcs[i];
225-
const options = arc._options || {};
226234
const properties = {
227-
x: centerX + chart.offsetX,
228-
y: centerY + chart.offsetY,
235+
x: centerX + me.offsetX,
236+
y: centerY + me.offsetY,
229237
startAngle,
230238
endAngle: startAngle + circumference,
231239
circumference,
232240
outerRadius,
233-
innerRadius,
234-
options
241+
innerRadius
235242
};
243+
if (includeOptions) {
244+
properties.options = me._resolveDataElementOptions(index, mode);
245+
}
236246
startAngle += circumference;
237247

238248
me._updateElement(arc, index, properties, mode);
239249
}
250+
me._updateSharedOptions(sharedOptions, mode);
240251
}
241252

242253
calculateTotal() {
@@ -252,10 +263,6 @@ class DoughnutController extends DatasetController {
252263
}
253264
}
254265

255-
/* if (total === 0) {
256-
total = NaN;
257-
}*/
258-
259266
return total;
260267
}
261268

@@ -267,7 +274,6 @@ class DoughnutController extends DatasetController {
267274
return 0;
268275
}
269276

270-
// gets the max border or hover width to properly scale pie charts
271277
getMaxBorderWidth(arcs) {
272278
const me = this;
273279
let max = 0;
@@ -321,8 +327,8 @@ class DoughnutController extends DatasetController {
321327
/**
322328
* @private
323329
*/
324-
_getRingWeight(dataSetIndex) {
325-
return Math.max(valueOrDefault(this.chart.data.datasets[dataSetIndex].weight, 1), 0);
330+
_getRingWeight(datasetIndex) {
331+
return Math.max(valueOrDefault(this.chart.data.datasets[datasetIndex].weight, 1), 0);
326332
}
327333

328334
/**

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) {

src/core/core.controller.js

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ class Chart {
200200
this.chartArea = undefined;
201201
this.data = undefined;
202202
this.active = undefined;
203-
this.lastActive = undefined;
203+
this.lastActive = [];
204204
this._lastEvent = undefined;
205205
this._listeners = {resize: undefined};
206206
this._sortedMetasets = [];
@@ -568,7 +568,7 @@ class Chart {
568568

569569
// Replay last event from before update
570570
if (me._lastEvent) {
571-
me._eventHandler(me._lastEvent);
571+
me._eventHandler(me._lastEvent, true);
572572
}
573573

574574
me.render();
@@ -794,10 +794,10 @@ class Chart {
794794
return Interaction.modes.index(this, e, {intersect: false});
795795
}
796796

797-
getElementsAtEventForMode(e, mode, options) {
797+
getElementsAtEventForMode(e, mode, options, useFinalPosition) {
798798
const method = Interaction.modes[mode];
799799
if (typeof method === 'function') {
800-
return method(this, e, options);
800+
return method(this, e, options, useFinalPosition);
801801
}
802802

803803
return [];
@@ -1007,16 +1007,16 @@ class Chart {
10071007
/**
10081008
* @private
10091009
*/
1010-
_eventHandler(e) {
1010+
_eventHandler(e, replay) {
10111011
const me = this;
10121012

1013-
if (plugins.notify(me, 'beforeEvent', [e]) === false) {
1013+
if (plugins.notify(me, 'beforeEvent', [e, replay]) === false) {
10141014
return;
10151015
}
10161016

1017-
me._handleEvent(e);
1017+
me._handleEvent(e, replay);
10181018

1019-
plugins.notify(me, 'afterEvent', [e]);
1019+
plugins.notify(me, 'afterEvent', [e, replay]);
10201020

10211021
me.render();
10221022

@@ -1026,23 +1026,24 @@ class Chart {
10261026
/**
10271027
* Handle an event
10281028
* @param {IEvent} e the event to handle
1029+
* @param {boolean} [replay] - true if the event was replayed by `update`
10291030
* @return {boolean} true if the chart needs to re-render
10301031
* @private
10311032
*/
1032-
_handleEvent(e) {
1033+
_handleEvent(e, replay) {
10331034
const me = this;
1034-
const options = me.options || {};
1035+
const options = me.options;
10351036
const hoverOptions = options.hover;
1037+
// If the event is replayed from `update`, we should evaluate with the final positions.
1038+
const useFinalPosition = replay;
10361039
let changed = false;
10371040

1038-
me.lastActive = me.lastActive || [];
1039-
10401041
// Find Active Elements for hover and tooltips
10411042
if (e.type === 'mouseout') {
10421043
me.active = [];
10431044
me._lastEvent = null;
10441045
} else {
1045-
me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
1046+
me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, useFinalPosition);
10461047
me._lastEvent = e.type === 'click' ? me._lastEvent : e;
10471048
}
10481049

@@ -1058,7 +1059,7 @@ class Chart {
10581059
}
10591060

10601061
changed = !helpers._elementsEqual(me.active, me.lastActive);
1061-
if (changed) {
1062+
if (changed || replay) {
10621063
me._updateHoverStyles();
10631064
}
10641065

src/core/core.element.js

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import {isNumber} from '../helpers/helpers.math';
33

44
class Element {
55

6-
/**
7-
* @param {object} [cfg] optional configuration
8-
*/
96
constructor(cfg) {
107
this.x = undefined;
118
this.y = undefined;
12-
this.hidden = undefined;
9+
this.hidden = false;
10+
this.active = false;
11+
this.options = undefined;
12+
this.$animations = undefined;
1313

1414
if (cfg) {
1515
Object.assign(this, cfg);
@@ -26,6 +26,26 @@ class Element {
2626
hasValue() {
2727
return isNumber(this.x) && isNumber(this.y);
2828
}
29+
30+
/**
31+
* Gets the current or final value of each prop. Can return extra properties (whole object).
32+
* @param {string[]} props - properties to get
33+
* @param {boolean} [final] - get the final value (animation target)
34+
* @return {object}
35+
*/
36+
getProps(props, final) {
37+
const me = this;
38+
const anims = this.$animations;
39+
if (!final || !anims) {
40+
// let's not create an object, if not needed
41+
return me;
42+
}
43+
const ret = {};
44+
props.forEach(prop => {
45+
ret[prop] = anims[prop] && anims[prop].active ? anims[prop]._to : me[prop];
46+
});
47+
return ret;
48+
}
2949
}
3050

3151
Element.extend = inherits;

0 commit comments

Comments
 (0)