Skip to content

Commit c8a8e93

Browse files
committed
Fix remaining handleEvent issues
1 parent 2f17dbc commit c8a8e93

File tree

14 files changed

+267
-204
lines changed

14 files changed

+267
-204
lines changed

docs/getting-started/v3-migration.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,15 @@ 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`
96+
* `Chart.radiusLength`
9197
* `Chart.types`
9298
* `Chart.Tooltip` is now provided by the tooltip plugin. The positioners can be accessed from `tooltipPlugin.positioners`
9399
* `DatasetController.addElementAndReset`

src/controllers/controller.doughnut.js

Lines changed: 69 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,31 @@ 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+
const chart = me.chart;
178+
const {chartArea, options} = chart;
179+
const meta = me._cachedMeta;
180+
const arcs = meta.data;
181+
const cutout = options.cutoutPercentage / 100 || 0;
182+
const chartWeight = me._getRingWeight(me.index);
183+
const {ratioX, ratioY, offsetX, offsetY} = getRatioAndOffset(options.rotation, options.circumference, cutout);
184+
const borderWidth = me.getMaxBorderWidth();
185+
const maxWidth = (chartArea.right - chartArea.left - borderWidth) / ratioX;
186+
const maxHeight = (chartArea.bottom - chartArea.top - borderWidth) / ratioY;
187+
const outerRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0);
188+
const innerRadius = Math.max(outerRadius * cutout, 0);
189+
const radiusLength = (outerRadius - innerRadius) / (me._getVisibleDatasetWeightTotal() || 1);
190+
me.offsetX = offsetX * outerRadius;
191+
me.offsetY = offsetY * outerRadius;
187192

188193
meta.total = me.calculateTotal();
189194

190-
me.outerRadius = chart.outerRadius - chart.radiusLength * me._getRingWeightOffset(me.index);
191-
me.innerRadius = Math.max(me.outerRadius - chart.radiusLength * chartWeight, 0);
195+
me.outerRadius = outerRadius - radiusLength * me._getRingWeightOffset(me.index);
196+
me.innerRadius = Math.max(me.outerRadius - radiusLength * chartWeight, 0);
192197

193198
me.updateElements(arcs, 0, mode);
194199
}
@@ -214,6 +219,9 @@ class DoughnutController extends DatasetController {
214219
const centerY = (chartArea.top + chartArea.bottom) / 2;
215220
const innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
216221
const outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
222+
const firstOpts = me._resolveDataElementOptions(start, mode);
223+
const sharedOptions = me._getSharedOptions(mode, arcs[start], firstOpts);
224+
const includeOptions = me._includeOptions(mode, sharedOptions);
217225
let startAngle = opts.rotation;
218226
let i;
219227

@@ -225,21 +233,23 @@ class DoughnutController extends DatasetController {
225233
const index = start + i;
226234
const circumference = me._circumference(index, reset);
227235
const arc = arcs[i];
228-
const options = arc._options || {};
229236
const properties = {
230-
x: centerX + chart.offsetX,
231-
y: centerY + chart.offsetY,
237+
x: centerX + me.offsetX,
238+
y: centerY + me.offsetY,
232239
startAngle,
233240
endAngle: startAngle + circumference,
234241
circumference,
235242
outerRadius,
236-
innerRadius,
237-
options
243+
innerRadius
238244
};
245+
if (includeOptions) {
246+
properties.options = me._resolveDataElementOptions(index, mode);
247+
}
239248
startAngle += circumference;
240249

241250
me._updateElement(arc, index, properties, mode);
242251
}
252+
me._updateSharedOptions(sharedOptions, mode);
243253
}
244254

245255
calculateTotal() {
@@ -255,10 +265,6 @@ class DoughnutController extends DatasetController {
255265
}
256266
}
257267

258-
/* if (total === 0) {
259-
total = NaN;
260-
}*/
261-
262268
return total;
263269
}
264270

@@ -270,7 +276,6 @@ class DoughnutController extends DatasetController {
270276
return 0;
271277
}
272278

273-
// gets the max border or hover width to properly scale pie charts
274279
getMaxBorderWidth(arcs) {
275280
var me = this;
276281
var max = 0;
@@ -324,8 +329,8 @@ class DoughnutController extends DatasetController {
324329
/**
325330
* @private
326331
*/
327-
_getRingWeight(dataSetIndex) {
328-
return Math.max(valueOrDefault(this.chart.data.datasets[dataSetIndex].weight, 1), 0);
332+
_getRingWeight(datasetIndex) {
333+
return Math.max(valueOrDefault(this.chart.data.datasets[datasetIndex].weight, 1), 0);
329334
}
330335

331336
/**

src/controllers/controller.polarArea.js

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

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

137-
me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index);
138-
me.innerRadius = me.outerRadius - chart.radiusLength;
137+
me.outerRadius = outerRadius - (radiusLength * me.index);
138+
me.innerRadius = me.outerRadius - radiusLength;
139139
}
140140

141141
updateElements(arcs, start, mode) {

src/core/core.controller.js

Lines changed: 15 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 = [];
@@ -562,7 +562,7 @@ class Chart {
562562

563563
// Replay last event from before update
564564
if (me._lastEvent) {
565-
me._eventHandler(me._lastEvent);
565+
me._eventHandler(me._lastEvent, true);
566566
}
567567

568568
me.render();
@@ -789,10 +789,10 @@ class Chart {
789789
return Interaction.modes.index(this, e, {intersect: false});
790790
}
791791

792-
getElementsAtEventForMode(e, mode, options) {
792+
getElementsAtEventForMode(e, mode, options, useFinalPosition) {
793793
const method = Interaction.modes[mode];
794794
if (typeof method === 'function') {
795-
return method(this, e, options);
795+
return method(this, e, options, useFinalPosition);
796796
}
797797

798798
return [];
@@ -1001,16 +1001,16 @@ class Chart {
10011001
/**
10021002
* @private
10031003
*/
1004-
_eventHandler(e) {
1004+
_eventHandler(e, replay) {
10051005
const me = this;
10061006

1007-
if (plugins.notify(me, 'beforeEvent', [e]) === false) {
1007+
if (plugins.notify(me, 'beforeEvent', [e, replay]) === false) {
10081008
return;
10091009
}
10101010

1011-
me._handleEvent(e);
1011+
me._handleEvent(e, replay);
10121012

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

10151015
me.render();
10161016

@@ -1020,23 +1020,24 @@ class Chart {
10201020
/**
10211021
* Handle an event
10221022
* @param {IEvent} e the event to handle
1023+
* @param {boolean} [replay] - true if the event was replayed by `update`
10231024
* @return {boolean} true if the chart needs to re-render
10241025
* @private
10251026
*/
1026-
_handleEvent(e) {
1027+
_handleEvent(e, replay) {
10271028
const me = this;
1028-
const options = me.options || {};
1029+
const options = me.options;
10291030
const hoverOptions = options.hover;
1031+
// If the event is replayed from `update`, we should evaluate with the final positions.
1032+
const useFinalPosition = replay;
10301033
let changed = false;
10311034

1032-
me.lastActive = me.lastActive || [];
1033-
10341035
// Find Active Elements for hover and tooltips
10351036
if (e.type === 'mouseout') {
10361037
me.active = [];
10371038
me._lastEvent = null;
10381039
} else {
1039-
me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
1040+
me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, useFinalPosition);
10401041
me._lastEvent = e.type === 'click' ? me._lastEvent : e;
10411042
}
10421043

@@ -1052,7 +1053,7 @@ class Chart {
10521053
}
10531054

10541055
changed = !helpers._elementsEqual(me.active, me.lastActive);
1055-
if (changed) {
1056+
if (changed || replay) {
10561057
me._updateHoverStyles();
10571058
}
10581059

src/core/core.element.js

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

66
class Element {
77

8-
/**
9-
* @param {object} [cfg] optional configuration
10-
*/
118
constructor(cfg) {
129
this.x = undefined;
1310
this.y = undefined;
14-
this.hidden = undefined;
11+
this.hidden = false;
12+
this.active = false;
13+
this.options = undefined;
14+
this.$animations = undefined;
1515

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

3353
Element.extend = inherits;

0 commit comments

Comments
 (0)