mirror of
https://github.com/chartjs/Chart.js.git
synced 2025-12-08 20:36:08 +00:00
Issue 4991 (#7084)
* Fix remaining handleEvent issues * Reduce lines * Update tooltip always on replay * Address issues * Fix test * More tooltip fixing * Extend comment
This commit is contained in:
parent
a9ae64f1e2
commit
5e489f16f6
@ -212,7 +212,7 @@ export default class Chart {
|
||||
this.chartArea = undefined;
|
||||
this.data = undefined;
|
||||
this.active = undefined;
|
||||
this.lastActive = undefined;
|
||||
this.lastActive = [];
|
||||
this._lastEvent = undefined;
|
||||
/** @type {{resize?: function}} */
|
||||
this._listeners = {};
|
||||
@ -581,7 +581,7 @@ export default class Chart {
|
||||
|
||||
// Replay last event from before update
|
||||
if (me._lastEvent) {
|
||||
me._eventHandler(me._lastEvent);
|
||||
me._eventHandler(me._lastEvent, true);
|
||||
}
|
||||
|
||||
me.render();
|
||||
@ -808,10 +808,10 @@ export default class Chart {
|
||||
return Interaction.modes.index(this, e, {intersect: false});
|
||||
}
|
||||
|
||||
getElementsAtEventForMode(e, mode, options) {
|
||||
getElementsAtEventForMode(e, mode, options, useFinalPosition) {
|
||||
const method = Interaction.modes[mode];
|
||||
if (typeof method === 'function') {
|
||||
return method(this, e, options);
|
||||
return method(this, e, options, useFinalPosition);
|
||||
}
|
||||
|
||||
return [];
|
||||
@ -1021,16 +1021,16 @@ export default class Chart {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_eventHandler(e) {
|
||||
_eventHandler(e, replay) {
|
||||
const me = this;
|
||||
|
||||
if (plugins.notify(me, 'beforeEvent', [e]) === false) {
|
||||
if (plugins.notify(me, 'beforeEvent', [e, replay]) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
me._handleEvent(e);
|
||||
me._handleEvent(e, replay);
|
||||
|
||||
plugins.notify(me, 'afterEvent', [e]);
|
||||
plugins.notify(me, 'afterEvent', [e, replay]);
|
||||
|
||||
me.render();
|
||||
|
||||
@ -1040,23 +1040,38 @@ export default class Chart {
|
||||
/**
|
||||
* Handle an event
|
||||
* @param {IEvent} e the event to handle
|
||||
* @param {boolean} [replay] - true if the event was replayed by `update`
|
||||
* @return {boolean} true if the chart needs to re-render
|
||||
* @private
|
||||
*/
|
||||
_handleEvent(e) {
|
||||
_handleEvent(e, replay) {
|
||||
const me = this;
|
||||
const options = me.options || {};
|
||||
const options = me.options;
|
||||
const hoverOptions = options.hover;
|
||||
let changed = false;
|
||||
|
||||
me.lastActive = me.lastActive || [];
|
||||
// If the event is replayed from `update`, we should evaluate with the final positions.
|
||||
//
|
||||
// The `replay`:
|
||||
// It's the last event (excluding click) that has occured before `update`.
|
||||
// So mouse has not moved. It's also over the chart, because there is a `replay`.
|
||||
//
|
||||
// The why:
|
||||
// If animations are active, the elements haven't moved yet compared to state before update.
|
||||
// But if they will, we are activating the elements that would be active, if this check
|
||||
// was done after the animations have completed. => "final positions".
|
||||
// If there is no animations, the "final" and "current" positions are equal.
|
||||
// This is done so we do not have to evaluate the active elements each animation frame
|
||||
// - it would be expensive.
|
||||
const useFinalPosition = replay;
|
||||
|
||||
let changed = false;
|
||||
|
||||
// Find Active Elements for hover and tooltips
|
||||
if (e.type === 'mouseout') {
|
||||
me.active = [];
|
||||
me._lastEvent = null;
|
||||
} else {
|
||||
me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
|
||||
me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, useFinalPosition);
|
||||
me._lastEvent = e.type === 'click' ? me._lastEvent : e;
|
||||
}
|
||||
|
||||
@ -1072,7 +1087,7 @@ export default class Chart {
|
||||
}
|
||||
|
||||
changed = !helpers._elementsEqual(me.active, me.lastActive);
|
||||
if (changed) {
|
||||
if (changed || replay) {
|
||||
me._updateHoverStyles();
|
||||
}
|
||||
|
||||
|
||||
@ -5,27 +5,48 @@ export default class Element {
|
||||
|
||||
static extend = inherits;
|
||||
|
||||
/**
|
||||
* @param {object} [cfg] optional configuration
|
||||
*/
|
||||
constructor(cfg) {
|
||||
this.x = undefined;
|
||||
this.y = undefined;
|
||||
this.hidden = undefined;
|
||||
this.hidden = false;
|
||||
this.active = false;
|
||||
this.options = undefined;
|
||||
this.$animations = undefined;
|
||||
|
||||
if (cfg) {
|
||||
Object.assign(this, cfg);
|
||||
}
|
||||
}
|
||||
|
||||
tooltipPosition() {
|
||||
return {
|
||||
x: this.x,
|
||||
y: this.y
|
||||
};
|
||||
/**
|
||||
* @param {boolean} [useFinalPosition]
|
||||
*/
|
||||
tooltipPosition(useFinalPosition) {
|
||||
const {x, y} = this.getProps(['x', 'y'], useFinalPosition);
|
||||
return {x, y};
|
||||
}
|
||||
|
||||
hasValue() {
|
||||
return isNumber(this.x) && isNumber(this.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current or final value of each prop. Can return extra properties (whole object).
|
||||
* @param {string[]} props - properties to get
|
||||
* @param {boolean} [final] - get the final value (animation target)
|
||||
* @return {object}
|
||||
*/
|
||||
getProps(props, final) {
|
||||
const me = this;
|
||||
const anims = this.$animations;
|
||||
if (!final || !anims) {
|
||||
// let's not create an object, if not needed
|
||||
return me;
|
||||
}
|
||||
const ret = {};
|
||||
props.forEach(prop => {
|
||||
ret[prop] = anims[prop] && anims[prop].active ? anims[prop]._to : me[prop];
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,8 @@ import {_lookupByKey, _rlookupByKey} from '../helpers/helpers.collection';
|
||||
/**
|
||||
* @typedef { import("./core.controller").default } Chart
|
||||
* @typedef { import("../platform/platform.base").IEvent } IEvent
|
||||
* @typedef {{axis?:'x'|'y'|'xy', intersect:boolean}} IInteractionOptions
|
||||
* @typedef {{axis?: string, intersect?: boolean}} InteractionOptions
|
||||
* @typedef {{datasetIndex: number, index: number, element: import("../core/core.element").default}} InteractionItem
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -121,9 +122,10 @@ function getDistanceMetricForAxis(axis) {
|
||||
* @param {Chart} chart - the chart
|
||||
* @param {object} position - the point to be nearest to
|
||||
* @param {string} axis - the axis mode. x|y|xy
|
||||
* @return {object[]} the nearest items
|
||||
* @param {boolean} [useFinalPosition] - use the element's animation target instead of current position
|
||||
* @return {InteractionItem[]} the nearest items
|
||||
*/
|
||||
function getIntersectItems(chart, position, axis) {
|
||||
function getIntersectItems(chart, position, axis, useFinalPosition) {
|
||||
const items = [];
|
||||
|
||||
if (!_isPointInArea(position, chart.chartArea)) {
|
||||
@ -131,7 +133,7 @@ function getIntersectItems(chart, position, axis) {
|
||||
}
|
||||
|
||||
const evaluationFunc = function(element, datasetIndex, index) {
|
||||
if (element.inRange(position.x, position.y)) {
|
||||
if (element.inRange(position.x, position.y, useFinalPosition)) {
|
||||
items.push({element, datasetIndex, index});
|
||||
}
|
||||
};
|
||||
@ -146,9 +148,10 @@ function getIntersectItems(chart, position, axis) {
|
||||
* @param {object} position - the point to be nearest to
|
||||
* @param {string} axis - the axes along which to measure distance
|
||||
* @param {boolean} [intersect] - if true, only consider items that intersect the position
|
||||
* @return {object[]} the nearest items
|
||||
* @param {boolean} [useFinalPosition] - use the elements animation target instead of current position
|
||||
* @return {InteractionItem[]} the nearest items
|
||||
*/
|
||||
function getNearestItems(chart, position, axis, intersect) {
|
||||
function getNearestItems(chart, position, axis, intersect, useFinalPosition) {
|
||||
const distanceMetric = getDistanceMetricForAxis(axis);
|
||||
let minDistance = Number.POSITIVE_INFINITY;
|
||||
let items = [];
|
||||
@ -158,11 +161,11 @@ function getNearestItems(chart, position, axis, intersect) {
|
||||
}
|
||||
|
||||
const evaluationFunc = function(element, datasetIndex, index) {
|
||||
if (intersect && !element.inRange(position.x, position.y)) {
|
||||
if (intersect && !element.inRange(position.x, position.y, useFinalPosition)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const center = element.getCenterPoint();
|
||||
const center = element.getCenterPoint(useFinalPosition);
|
||||
const distance = distanceMetric(position, center);
|
||||
if (distance < minDistance) {
|
||||
items = [{element, datasetIndex, index}];
|
||||
@ -191,14 +194,17 @@ export default {
|
||||
* @since v2.4.0
|
||||
* @param {Chart} chart - the chart we are returning items from
|
||||
* @param {Event} e - the event we are find things at
|
||||
* @param {IInteractionOptions} options - options to use during interaction
|
||||
* @return {Object[]} Array of elements that are under the point. If none are found, an empty array is returned
|
||||
* @param {InteractionOptions} options - options to use
|
||||
* @param {boolean} [useFinalPosition] - use final element position (animation target)
|
||||
* @return {InteractionItem[]} - items that are found
|
||||
*/
|
||||
index(chart, e, options) {
|
||||
index(chart, e, options, useFinalPosition) {
|
||||
const position = getRelativePosition(e, chart);
|
||||
// Default axis for index mode is 'x' to match old behaviour
|
||||
const axis = options.axis || 'x';
|
||||
const items = options.intersect ? getIntersectItems(chart, position, axis) : getNearestItems(chart, position, axis);
|
||||
const items = options.intersect
|
||||
? getIntersectItems(chart, position, axis, useFinalPosition)
|
||||
: getNearestItems(chart, position, axis, false, useFinalPosition);
|
||||
const elements = [];
|
||||
|
||||
if (!items.length) {
|
||||
@ -224,13 +230,16 @@ export default {
|
||||
* @function Chart.Interaction.modes.dataset
|
||||
* @param {Chart} chart - the chart we are returning items from
|
||||
* @param {Event} e - the event we are find things at
|
||||
* @param {IInteractionOptions} options - options to use during interaction
|
||||
* @return {Object[]} Array of elements that are under the point. If none are found, an empty array is returned
|
||||
* @param {InteractionOptions} options - options to use
|
||||
* @param {boolean} [useFinalPosition] - use final element position (animation target)
|
||||
* @return {InteractionItem[]} - items that are found
|
||||
*/
|
||||
dataset(chart, e, options) {
|
||||
dataset(chart, e, options, useFinalPosition) {
|
||||
const position = getRelativePosition(e, chart);
|
||||
const axis = options.axis || 'xy';
|
||||
let items = options.intersect ? getIntersectItems(chart, position, axis) : getNearestItems(chart, position, axis);
|
||||
let items = options.intersect
|
||||
? getIntersectItems(chart, position, axis, useFinalPosition) :
|
||||
getNearestItems(chart, position, axis, false, useFinalPosition);
|
||||
|
||||
if (items.length > 0) {
|
||||
const datasetIndex = items[0].datasetIndex;
|
||||
@ -250,13 +259,14 @@ export default {
|
||||
* @function Chart.Interaction.modes.intersect
|
||||
* @param {Chart} chart - the chart we are returning items from
|
||||
* @param {Event} e - the event we are find things at
|
||||
* @param {IInteractionOptions} options - options to use
|
||||
* @return {Object[]} Array of elements that are under the point. If none are found, an empty array is returned
|
||||
* @param {InteractionOptions} options - options to use
|
||||
* @param {boolean} [useFinalPosition] - use final element position (animation target)
|
||||
* @return {InteractionItem[]} - items that are found
|
||||
*/
|
||||
point(chart, e, options) {
|
||||
point(chart, e, options, useFinalPosition) {
|
||||
const position = getRelativePosition(e, chart);
|
||||
const axis = options.axis || 'xy';
|
||||
return getIntersectItems(chart, position, axis);
|
||||
return getIntersectItems(chart, position, axis, useFinalPosition);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -264,13 +274,14 @@ export default {
|
||||
* @function Chart.Interaction.modes.intersect
|
||||
* @param {Chart} chart - the chart we are returning items from
|
||||
* @param {Event} e - the event we are find things at
|
||||
* @param {IInteractionOptions} options - options to use
|
||||
* @return {Object[]} Array of elements that are under the point. If none are found, an empty array is returned
|
||||
* @param {InteractionOptions} options - options to use
|
||||
* @param {boolean} [useFinalPosition] - use final element position (animation target)
|
||||
* @return {InteractionItem[]} - items that are found
|
||||
*/
|
||||
nearest(chart, e, options) {
|
||||
nearest(chart, e, options, useFinalPosition) {
|
||||
const position = getRelativePosition(e, chart);
|
||||
const axis = options.axis || 'xy';
|
||||
return getNearestItems(chart, position, axis, options.intersect);
|
||||
return getNearestItems(chart, position, axis, options.intersect, useFinalPosition);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -278,20 +289,21 @@ export default {
|
||||
* @function Chart.Interaction.modes.x
|
||||
* @param {Chart} chart - the chart we are returning items from
|
||||
* @param {Event} e - the event we are find things at
|
||||
* @param {IInteractionOptions} options - options to use
|
||||
* @return {Object[]} Array of elements that are under the point. If none are found, an empty array is returned
|
||||
* @param {InteractionOptions} options - options to use
|
||||
* @param {boolean} [useFinalPosition] - use final element position (animation target)
|
||||
* @return {InteractionItem[]} - items that are found
|
||||
*/
|
||||
x(chart, e, options) {
|
||||
x(chart, e, options, useFinalPosition) {
|
||||
const position = getRelativePosition(e, chart);
|
||||
const items = [];
|
||||
let intersectsItem = false;
|
||||
|
||||
evaluateAllVisibleItems(chart, (element, datasetIndex, index) => {
|
||||
if (element.inXRange(position.x)) {
|
||||
if (element.inXRange(position.x, useFinalPosition)) {
|
||||
items.push({element, datasetIndex, index});
|
||||
}
|
||||
|
||||
if (element.inRange(position.x, position.y)) {
|
||||
if (element.inRange(position.x, position.y, useFinalPosition)) {
|
||||
intersectsItem = true;
|
||||
}
|
||||
});
|
||||
@ -309,20 +321,21 @@ export default {
|
||||
* @function Chart.Interaction.modes.y
|
||||
* @param {Chart} chart - the chart we are returning items from
|
||||
* @param {Event} e - the event we are find things at
|
||||
* @param {IInteractionOptions} options - options to use
|
||||
* @return {Object[]} Array of elements that are under the point. If none are found, an empty array is returned
|
||||
* @param {InteractionOptions} options - options to use
|
||||
* @param {boolean} [useFinalPosition] - use final element position (animation target)
|
||||
* @return {InteractionItem[]} - items that are found
|
||||
*/
|
||||
y(chart, e, options) {
|
||||
y(chart, e, options, useFinalPosition) {
|
||||
const position = getRelativePosition(e, chart);
|
||||
const items = [];
|
||||
let intersectsItem = false;
|
||||
|
||||
evaluateAllVisibleItems(chart, (element, datasetIndex, index) => {
|
||||
if (element.inYRange(position.y)) {
|
||||
if (element.inYRange(position.y, useFinalPosition)) {
|
||||
items.push({element, datasetIndex, index});
|
||||
}
|
||||
|
||||
if (element.inRange(position.x, position.y)) {
|
||||
if (element.inRange(position.x, position.y, useFinalPosition)) {
|
||||
intersectsItem = true;
|
||||
}
|
||||
});
|
||||
|
||||
@ -365,6 +365,7 @@ export default new PluginService();
|
||||
* @param {Chart} chart - The chart instance.
|
||||
* @param {IEvent} event - The event object.
|
||||
* @param {object} options - The plugin options.
|
||||
* @param {boolean} replay - True if this event is replayed from `Chart.update`
|
||||
*/
|
||||
/**
|
||||
* @method IPlugin#afterEvent
|
||||
@ -373,6 +374,7 @@ export default new PluginService();
|
||||
* @param {Chart} chart - The chart instance.
|
||||
* @param {IEvent} event - The event object.
|
||||
* @param {object} options - The plugin options.
|
||||
* @param {boolean} replay - True if this event is replayed from `Chart.update`
|
||||
*/
|
||||
/**
|
||||
* @method IPlugin#resize
|
||||
|
||||
@ -106,38 +106,49 @@ export default class Arc extends Element {
|
||||
/**
|
||||
* @param {number} chartX
|
||||
* @param {number} chartY
|
||||
* @param {boolean} [useFinalPosition]
|
||||
*/
|
||||
inRange(chartX, chartY) {
|
||||
const me = this;
|
||||
|
||||
const {angle, distance} = getAngleFromPoint(me, {x: chartX, y: chartY});
|
||||
|
||||
// Check if within the range of the open/close angle
|
||||
const betweenAngles = _angleBetween(angle, me.startAngle, me.endAngle);
|
||||
const withinRadius = (distance >= me.innerRadius && distance <= me.outerRadius);
|
||||
inRange(chartX, chartY, useFinalPosition) {
|
||||
const point = this.getProps(['x', 'y'], useFinalPosition);
|
||||
const {angle, distance} = getAngleFromPoint(point, {x: chartX, y: chartY});
|
||||
const {startAngle, endAngle, innerRadius, outerRadius, circumference} = this.getProps([
|
||||
'startAngle',
|
||||
'endAngle',
|
||||
'innerRadius',
|
||||
'outerRadius',
|
||||
'circumference'
|
||||
], useFinalPosition);
|
||||
const betweenAngles = circumference >= TAU || _angleBetween(angle, startAngle, endAngle);
|
||||
const withinRadius = (distance >= innerRadius && distance <= outerRadius);
|
||||
|
||||
return (betweenAngles && withinRadius);
|
||||
}
|
||||
|
||||
getCenterPoint() {
|
||||
const me = this;
|
||||
const halfAngle = (me.startAngle + me.endAngle) / 2;
|
||||
const halfRadius = (me.innerRadius + me.outerRadius) / 2;
|
||||
/**
|
||||
* @param {boolean} [useFinalPosition]
|
||||
*/
|
||||
getCenterPoint(useFinalPosition) {
|
||||
const {x, y, startAngle, endAngle, innerRadius, outerRadius} = this.getProps([
|
||||
'x',
|
||||
'y',
|
||||
'startAngle',
|
||||
'endAngle',
|
||||
'innerRadius',
|
||||
'outerRadius'
|
||||
], useFinalPosition);
|
||||
const halfAngle = (startAngle + endAngle) / 2;
|
||||
const halfRadius = (innerRadius + outerRadius) / 2;
|
||||
return {
|
||||
x: me.x + Math.cos(halfAngle) * halfRadius,
|
||||
y: me.y + Math.sin(halfAngle) * halfRadius
|
||||
x: x + Math.cos(halfAngle) * halfRadius,
|
||||
y: y + Math.sin(halfAngle) * halfRadius
|
||||
};
|
||||
}
|
||||
|
||||
tooltipPosition() {
|
||||
const me = this;
|
||||
const centreAngle = me.startAngle + ((me.endAngle - me.startAngle) / 2);
|
||||
const rangeFromCentre = (me.outerRadius - me.innerRadius) / 2 + me.innerRadius;
|
||||
|
||||
return {
|
||||
x: me.x + (Math.cos(centreAngle) * rangeFromCentre),
|
||||
y: me.y + (Math.sin(centreAngle) * rangeFromCentre)
|
||||
};
|
||||
/**
|
||||
* @param {boolean} [useFinalPosition]
|
||||
*/
|
||||
tooltipPosition(useFinalPosition) {
|
||||
return this.getCenterPoint(useFinalPosition);
|
||||
}
|
||||
|
||||
draw(ctx) {
|
||||
|
||||
@ -34,23 +34,28 @@ export default class Point extends Element {
|
||||
}
|
||||
}
|
||||
|
||||
inRange(mouseX, mouseY) {
|
||||
inRange(mouseX, mouseY, useFinalPosition) {
|
||||
const options = this.options;
|
||||
return ((Math.pow(mouseX - this.x, 2) + Math.pow(mouseY - this.y, 2)) < Math.pow(options.hitRadius + options.radius, 2));
|
||||
const {x, y} = this.getProps(['x', 'y'], useFinalPosition);
|
||||
return ((Math.pow(mouseX - x, 2) + Math.pow(mouseY - y, 2)) < Math.pow(options.hitRadius + options.radius, 2));
|
||||
}
|
||||
|
||||
inXRange(mouseX) {
|
||||
inXRange(mouseX, useFinalPosition) {
|
||||
const options = this.options;
|
||||
return (Math.abs(mouseX - this.x) < options.radius + options.hitRadius);
|
||||
const {x} = this.getProps(['x'], useFinalPosition);
|
||||
|
||||
return (Math.abs(mouseX - x) < options.radius + options.hitRadius);
|
||||
}
|
||||
|
||||
inYRange(mouseY) {
|
||||
inYRange(mouseY, useFinalPosition) {
|
||||
const options = this.options;
|
||||
return (Math.abs(mouseY - this.y) < options.radius + options.hitRadius);
|
||||
const {y} = this.getProps(['x'], useFinalPosition);
|
||||
return (Math.abs(mouseY - y) < options.radius + options.hitRadius);
|
||||
}
|
||||
|
||||
getCenterPoint() {
|
||||
return {x: this.x, y: this.y};
|
||||
getCenterPoint(useFinalPosition) {
|
||||
const {x, y} = this.getProps(['x', 'y'], useFinalPosition);
|
||||
return {x, y};
|
||||
}
|
||||
|
||||
size() {
|
||||
@ -60,15 +65,6 @@ export default class Point extends Element {
|
||||
return (radius + borderWidth) * 2;
|
||||
}
|
||||
|
||||
tooltipPosition() {
|
||||
const options = this.options;
|
||||
return {
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
padding: options.radius + options.borderWidth
|
||||
};
|
||||
}
|
||||
|
||||
draw(ctx, chartArea) {
|
||||
const me = this;
|
||||
const options = me.options;
|
||||
|
||||
@ -15,33 +15,31 @@ defaults.set('elements', {
|
||||
|
||||
/**
|
||||
* Helper function to get the bounds of the bar regardless of the orientation
|
||||
* @param bar {Rectangle} the bar
|
||||
* @param {Rectangle} bar the bar
|
||||
* @param {boolean} [useFinalPosition]
|
||||
* @return {object} bounds of the bar
|
||||
* @private
|
||||
*/
|
||||
function getBarBounds(bar) {
|
||||
let x1, x2, y1, y2, half;
|
||||
function getBarBounds(bar, useFinalPosition) {
|
||||
const {x, y, base, width, height} = bar.getProps(['x', 'y', 'base', 'width', 'height'], useFinalPosition);
|
||||
|
||||
let left, right, top, bottom, half;
|
||||
|
||||
if (bar.horizontal) {
|
||||
half = bar.height / 2;
|
||||
x1 = Math.min(bar.x, bar.base);
|
||||
x2 = Math.max(bar.x, bar.base);
|
||||
y1 = bar.y - half;
|
||||
y2 = bar.y + half;
|
||||
half = height / 2;
|
||||
left = Math.min(x, base);
|
||||
right = Math.max(x, base);
|
||||
top = y - half;
|
||||
bottom = y + half;
|
||||
} else {
|
||||
half = bar.width / 2;
|
||||
x1 = bar.x - half;
|
||||
x2 = bar.x + half;
|
||||
y1 = Math.min(bar.y, bar.base);
|
||||
y2 = Math.max(bar.y, bar.base);
|
||||
half = width / 2;
|
||||
left = x - half;
|
||||
right = x + half;
|
||||
top = Math.min(y, base);
|
||||
bottom = Math.max(y, base);
|
||||
}
|
||||
|
||||
return {
|
||||
left: x1,
|
||||
top: y1,
|
||||
right: x2,
|
||||
bottom: y2
|
||||
};
|
||||
return {left, top, right, bottom};
|
||||
}
|
||||
|
||||
function swap(orig, v1, v2) {
|
||||
@ -116,10 +114,10 @@ function boundingRects(bar) {
|
||||
};
|
||||
}
|
||||
|
||||
function inRange(bar, x, y) {
|
||||
function inRange(bar, x, y, useFinalPosition) {
|
||||
const skipX = x === null;
|
||||
const skipY = y === null;
|
||||
const bounds = !bar || (skipX && skipY) ? false : getBarBounds(bar);
|
||||
const bounds = !bar || (skipX && skipY) ? false : getBarBounds(bar, useFinalPosition);
|
||||
|
||||
return bounds
|
||||
&& (skipX || x >= bounds.left && x <= bounds.right)
|
||||
@ -165,33 +163,26 @@ export default class Rectangle extends Element {
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
inRange(mouseX, mouseY) {
|
||||
return inRange(this, mouseX, mouseY);
|
||||
inRange(mouseX, mouseY, useFinalPosition) {
|
||||
return inRange(this, mouseX, mouseY, useFinalPosition);
|
||||
}
|
||||
|
||||
inXRange(mouseX) {
|
||||
return inRange(this, mouseX, null);
|
||||
inXRange(mouseX, useFinalPosition) {
|
||||
return inRange(this, mouseX, null, useFinalPosition);
|
||||
}
|
||||
|
||||
inYRange(mouseY) {
|
||||
return inRange(this, null, mouseY);
|
||||
inYRange(mouseY, useFinalPosition) {
|
||||
return inRange(this, null, mouseY, useFinalPosition);
|
||||
}
|
||||
|
||||
getCenterPoint() {
|
||||
const {x, y, base, horizontal} = this;
|
||||
getCenterPoint(useFinalPosition) {
|
||||
const {x, y, base, horizontal} = this.getProps(['x', 'y', 'base', 'horizontal', useFinalPosition]);
|
||||
return {
|
||||
x: horizontal ? (x + base) / 2 : x,
|
||||
y: horizontal ? y : (y + base) / 2
|
||||
};
|
||||
}
|
||||
|
||||
tooltipPosition() {
|
||||
return {
|
||||
x: this.x,
|
||||
y: this.y
|
||||
};
|
||||
}
|
||||
|
||||
getRange(axis) {
|
||||
return axis === 'x' ? this.width / 2 : this.height / 2;
|
||||
}
|
||||
|
||||
@ -478,6 +478,7 @@ export class Tooltip extends Element {
|
||||
this.height = undefined;
|
||||
this.width = undefined;
|
||||
this.caretX = undefined;
|
||||
this.caretY = undefined;
|
||||
this.labelColors = undefined;
|
||||
this.labelTextColors = undefined;
|
||||
|
||||
@ -916,15 +917,22 @@ export class Tooltip extends Element {
|
||||
const anims = me.$animations;
|
||||
const animX = anims && anims.x;
|
||||
const animY = anims && anims.y;
|
||||
if (animX && animX.active() || animY && animY.active()) {
|
||||
if (animX || animY) {
|
||||
const position = positioners[options.position].call(me, me._active, me._eventPosition);
|
||||
if (!position) {
|
||||
return;
|
||||
}
|
||||
const size = me._size = getTooltipSize(me);
|
||||
const positionAndSize = Object.assign({}, position, me._size);
|
||||
const alignment = determineAlignment(chart, options, positionAndSize);
|
||||
const point = getBackgroundPoint(options, positionAndSize, alignment, chart);
|
||||
if (animX._to !== point.x || animY._to !== point.y) {
|
||||
me.xAlign = alignment.xAlign;
|
||||
me.yAlign = alignment.yAlign;
|
||||
me.width = size.width;
|
||||
me.height = size.height;
|
||||
me.caretX = position.x;
|
||||
me.caretY = position.y;
|
||||
me._resolveAnimations().update(me, point);
|
||||
}
|
||||
}
|
||||
@ -985,9 +993,10 @@ export class Tooltip extends Element {
|
||||
/**
|
||||
* Handle an event
|
||||
* @param {IEvent} e - The event to handle
|
||||
* @param {boolean} [replay] - This is a replayed event (from update)
|
||||
* @returns {boolean} true if the tooltip changed
|
||||
*/
|
||||
handleEvent(e) {
|
||||
handleEvent(e, replay) {
|
||||
const me = this;
|
||||
const options = me.options;
|
||||
const lastActive = me._active || [];
|
||||
@ -996,14 +1005,14 @@ export class Tooltip extends Element {
|
||||
|
||||
// Find Active Elements for tooltips
|
||||
if (e.type !== 'mouseout') {
|
||||
active = me._chart.getElementsAtEventForMode(e, options.mode, options);
|
||||
active = me._chart.getElementsAtEventForMode(e, options.mode, options, replay);
|
||||
if (options.reverse) {
|
||||
active.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
// Remember Last Actives
|
||||
changed = !helpers._elementsEqual(active, lastActive);
|
||||
changed = replay || !helpers._elementsEqual(active, lastActive);
|
||||
|
||||
// Only handle target event on tooltip change
|
||||
if (changed) {
|
||||
@ -1068,9 +1077,11 @@ export default {
|
||||
plugins.notify(chart, 'afterTooltipDraw', [args]);
|
||||
},
|
||||
|
||||
afterEvent(chart, e) {
|
||||
afterEvent(chart, e, replay) {
|
||||
if (chart.tooltip) {
|
||||
chart.tooltip.handleEvent(e);
|
||||
// If the event is replayed from `update`, we should evaluate with the final positions.
|
||||
const useFinalPosition = replay;
|
||||
chart.tooltip.handleEvent(e, useFinalPosition);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -19,6 +19,21 @@ describe('Arc element tests', function() {
|
||||
expect(arc.inRange(-1.0 * Math.sqrt(7), Math.sqrt(7))).toBe(false);
|
||||
});
|
||||
|
||||
it ('should determine if in range, when full circle', function() {
|
||||
// Mock out the arc as if the controller put it there
|
||||
var arc = new Chart.elements.Arc({
|
||||
startAngle: -Math.PI,
|
||||
endAngle: Math.PI * 1.5,
|
||||
x: 0,
|
||||
y: 0,
|
||||
innerRadius: 0,
|
||||
outerRadius: 10,
|
||||
circumference: Math.PI * 2
|
||||
});
|
||||
|
||||
expect(arc.inRange(7, 7)).toBe(true);
|
||||
});
|
||||
|
||||
it ('should get the tooltip position', function() {
|
||||
// Mock out the arc as if the controller put it there
|
||||
var arc = new Chart.elements.Arc({
|
||||
|
||||
@ -31,8 +31,7 @@ describe('Chart.elements.Point', function() {
|
||||
|
||||
expect(point.tooltipPosition()).toEqual({
|
||||
x: 10,
|
||||
y: 15,
|
||||
padding: 8
|
||||
y: 15
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -107,14 +107,14 @@ describe('Default Configs', function() {
|
||||
var expected = [{
|
||||
text: 'label1',
|
||||
fillStyle: 'red',
|
||||
hidden: undefined,
|
||||
hidden: false,
|
||||
index: 0,
|
||||
strokeStyle: '#000',
|
||||
lineWidth: 2
|
||||
}, {
|
||||
text: 'label2',
|
||||
fillStyle: 'green',
|
||||
hidden: undefined,
|
||||
hidden: false,
|
||||
index: 1,
|
||||
strokeStyle: '#000',
|
||||
lineWidth: 2
|
||||
@ -205,14 +205,14 @@ describe('Default Configs', function() {
|
||||
var expected = [{
|
||||
text: 'label1',
|
||||
fillStyle: 'red',
|
||||
hidden: undefined,
|
||||
hidden: false,
|
||||
index: 0,
|
||||
strokeStyle: '#000',
|
||||
lineWidth: 2
|
||||
}, {
|
||||
text: 'label2',
|
||||
fillStyle: 'green',
|
||||
hidden: undefined,
|
||||
hidden: false,
|
||||
index: 1,
|
||||
strokeStyle: '#000',
|
||||
lineWidth: 2
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user