mirror of
https://github.com/chartjs/Chart.js.git
synced 2024-10-08 05:09:08 +02:00
38f85c98b5
Add "onHover" to the legend options that will hold a user defined function (default is null) and called when a "mousemove" event is registered on top of a label item, with same parameters as the "onClick" option. Also introduced logic that determines if the type of event passed to the legend "handleEvent" function is one we can handle. Currently allowing "click" and "mousemove" events. If the event is not one of those we stop the function execution (this is for the sake of reusing the legend hitbox calculations).
688 lines
19 KiB
JavaScript
688 lines
19 KiB
JavaScript
"use strict";
|
|
|
|
module.exports = function(Chart) {
|
|
|
|
var helpers = Chart.helpers;
|
|
//Create a dictionary of chart types, to allow for extension of existing types
|
|
Chart.types = {};
|
|
|
|
//Store a reference to each instance - allowing us to globally resize chart instances on window resize.
|
|
//Destroy method on the chart will remove the instance of the chart from this reference.
|
|
Chart.instances = {};
|
|
|
|
// Controllers available for dataset visualization eg. bar, line, slice, etc.
|
|
Chart.controllers = {};
|
|
|
|
/**
|
|
* @class Chart.Controller
|
|
* The main controller of a chart.
|
|
*/
|
|
Chart.Controller = function(instance) {
|
|
|
|
this.chart = instance;
|
|
this.config = instance.config;
|
|
this.options = this.config.options = helpers.configMerge(Chart.defaults.global, Chart.defaults[this.config.type], this.config.options || {});
|
|
this.id = helpers.uid();
|
|
|
|
Object.defineProperty(this, 'data', {
|
|
get: function() {
|
|
return this.config.data;
|
|
}
|
|
});
|
|
|
|
//Add the chart instance to the global namespace
|
|
Chart.instances[this.id] = this;
|
|
|
|
if (this.options.responsive) {
|
|
// Silent resize before chart draws
|
|
this.resize(true);
|
|
}
|
|
|
|
this.initialize();
|
|
|
|
return this;
|
|
};
|
|
|
|
helpers.extend(Chart.Controller.prototype, /** @lends Chart.Controller */ {
|
|
|
|
initialize: function() {
|
|
var me = this;
|
|
// Before init plugin notification
|
|
Chart.plugins.notify('beforeInit', [me]);
|
|
|
|
me.bindEvents();
|
|
|
|
// Make sure controllers are built first so that each dataset is bound to an axis before the scales
|
|
// are built
|
|
me.ensureScalesHaveIDs();
|
|
me.buildOrUpdateControllers();
|
|
me.buildScales();
|
|
me.updateLayout();
|
|
me.resetElements();
|
|
me.initToolTip();
|
|
me.update();
|
|
|
|
// After init plugin notification
|
|
Chart.plugins.notify('afterInit', [me]);
|
|
|
|
return me;
|
|
},
|
|
|
|
clear: function() {
|
|
helpers.clear(this.chart);
|
|
return this;
|
|
},
|
|
|
|
stop: function() {
|
|
// Stops any current animation loop occuring
|
|
Chart.animationService.cancelAnimation(this);
|
|
return this;
|
|
},
|
|
|
|
resize: function resize(silent) {
|
|
var me = this;
|
|
var chart = me.chart;
|
|
var canvas = chart.canvas;
|
|
var newWidth = helpers.getMaximumWidth(canvas);
|
|
var aspectRatio = chart.aspectRatio;
|
|
var newHeight = (me.options.maintainAspectRatio && isNaN(aspectRatio) === false && isFinite(aspectRatio) && aspectRatio !== 0) ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas);
|
|
|
|
var sizeChanged = chart.width !== newWidth || chart.height !== newHeight;
|
|
|
|
if (!sizeChanged) {
|
|
return me;
|
|
}
|
|
|
|
canvas.width = chart.width = newWidth;
|
|
canvas.height = chart.height = newHeight;
|
|
|
|
helpers.retinaScale(chart);
|
|
|
|
// Notify any plugins about the resize
|
|
var newSize = { width: newWidth, height: newHeight };
|
|
Chart.plugins.notify('resize', [me, newSize]);
|
|
|
|
// Notify of resize
|
|
if (me.options.onResize) {
|
|
me.options.onResize(me, newSize);
|
|
}
|
|
|
|
if (!silent) {
|
|
me.stop();
|
|
me.update(me.options.responsiveAnimationDuration);
|
|
}
|
|
|
|
return me;
|
|
},
|
|
|
|
ensureScalesHaveIDs: function() {
|
|
var options = this.options;
|
|
var scalesOptions = options.scales || {};
|
|
var scaleOptions = options.scale;
|
|
|
|
helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) {
|
|
xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index);
|
|
});
|
|
|
|
helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) {
|
|
yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index);
|
|
});
|
|
|
|
if (scaleOptions) {
|
|
scaleOptions.id = scaleOptions.id || 'scale';
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Builds a map of scale ID to scale object for future lookup.
|
|
*/
|
|
buildScales: function() {
|
|
var me = this;
|
|
var options = me.options;
|
|
var scales = me.scales = {};
|
|
var items = [];
|
|
|
|
if (options.scales) {
|
|
items = items.concat(
|
|
(options.scales.xAxes || []).map(function(xAxisOptions) {
|
|
return { options: xAxisOptions, dtype: 'category' }; }),
|
|
(options.scales.yAxes || []).map(function(yAxisOptions) {
|
|
return { options: yAxisOptions, dtype: 'linear' }; }));
|
|
}
|
|
|
|
if (options.scale) {
|
|
items.push({ options: options.scale, dtype: 'radialLinear', isDefault: true });
|
|
}
|
|
|
|
helpers.each(items, function(item) {
|
|
var scaleOptions = item.options;
|
|
var scaleType = helpers.getValueOrDefault(scaleOptions.type, item.dtype);
|
|
var scaleClass = Chart.scaleService.getScaleConstructor(scaleType);
|
|
if (!scaleClass) {
|
|
return;
|
|
}
|
|
|
|
var scale = new scaleClass({
|
|
id: scaleOptions.id,
|
|
options: scaleOptions,
|
|
ctx: me.chart.ctx,
|
|
chart: me
|
|
});
|
|
|
|
scales[scale.id] = scale;
|
|
|
|
// TODO(SB): I think we should be able to remove this custom case (options.scale)
|
|
// and consider it as a regular scale part of the "scales"" map only! This would
|
|
// make the logic easier and remove some useless? custom code.
|
|
if (item.isDefault) {
|
|
me.scale = scale;
|
|
}
|
|
});
|
|
|
|
Chart.scaleService.addScalesToLayout(this);
|
|
},
|
|
|
|
updateLayout: function() {
|
|
Chart.layoutService.update(this, this.chart.width, this.chart.height);
|
|
},
|
|
|
|
buildOrUpdateControllers: function() {
|
|
var me = this;
|
|
var types = [];
|
|
var newControllers = [];
|
|
|
|
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
|
|
var meta = me.getDatasetMeta(datasetIndex);
|
|
if (!meta.type) {
|
|
meta.type = dataset.type || me.config.type;
|
|
}
|
|
|
|
types.push(meta.type);
|
|
|
|
if (meta.controller) {
|
|
meta.controller.updateIndex(datasetIndex);
|
|
} else {
|
|
meta.controller = new Chart.controllers[meta.type](me, datasetIndex);
|
|
newControllers.push(meta.controller);
|
|
}
|
|
}, me);
|
|
|
|
if (types.length > 1) {
|
|
for (var i = 1; i < types.length; i++) {
|
|
if (types[i] !== types[i - 1]) {
|
|
me.isCombo = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return newControllers;
|
|
},
|
|
|
|
resetElements: function() {
|
|
var me = this;
|
|
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
|
|
me.getDatasetMeta(datasetIndex).controller.reset();
|
|
}, me);
|
|
},
|
|
|
|
update: function update(animationDuration, lazy) {
|
|
var me = this;
|
|
Chart.plugins.notify('beforeUpdate', [me]);
|
|
|
|
// In case the entire data object changed
|
|
me.tooltip._data = me.data;
|
|
|
|
// Make sure dataset controllers are updated and new controllers are reset
|
|
var newControllers = me.buildOrUpdateControllers();
|
|
|
|
// Make sure all dataset controllers have correct meta data counts
|
|
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
|
|
me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements();
|
|
}, me);
|
|
|
|
Chart.layoutService.update(me, me.chart.width, me.chart.height);
|
|
|
|
// Apply changes to the dataets that require the scales to have been calculated i.e BorderColor chages
|
|
Chart.plugins.notify('afterScaleUpdate', [me]);
|
|
|
|
// Can only reset the new controllers after the scales have been updated
|
|
helpers.each(newControllers, function(controller) {
|
|
controller.reset();
|
|
});
|
|
|
|
me.updateDatasets();
|
|
|
|
// Do this before render so that any plugins that need final scale updates can use it
|
|
Chart.plugins.notify('afterUpdate', [me]);
|
|
|
|
me.render(animationDuration, lazy);
|
|
},
|
|
|
|
/**
|
|
* @method beforeDatasetsUpdate
|
|
* @description Called before all datasets are updated. If a plugin returns false,
|
|
* the datasets update will be cancelled until another chart update is triggered.
|
|
* @param {Object} instance the chart instance being updated.
|
|
* @returns {Boolean} false to cancel the datasets update.
|
|
* @memberof Chart.PluginBase
|
|
* @since version 2.1.5
|
|
* @instance
|
|
*/
|
|
|
|
/**
|
|
* @method afterDatasetsUpdate
|
|
* @description Called after all datasets have been updated. Note that this
|
|
* extension will not be called if the datasets update has been cancelled.
|
|
* @param {Object} instance the chart instance being updated.
|
|
* @memberof Chart.PluginBase
|
|
* @since version 2.1.5
|
|
* @instance
|
|
*/
|
|
|
|
/**
|
|
* Updates all datasets unless a plugin returns false to the beforeDatasetsUpdate
|
|
* extension, in which case no datasets will be updated and the afterDatasetsUpdate
|
|
* notification will be skipped.
|
|
* @protected
|
|
* @instance
|
|
*/
|
|
updateDatasets: function() {
|
|
var me = this;
|
|
var i, ilen;
|
|
|
|
if (Chart.plugins.notify('beforeDatasetsUpdate', [ me ])) {
|
|
for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
|
|
me.getDatasetMeta(i).controller.update();
|
|
}
|
|
|
|
Chart.plugins.notify('afterDatasetsUpdate', [ me ]);
|
|
}
|
|
},
|
|
|
|
render: function render(duration, lazy) {
|
|
var me = this;
|
|
Chart.plugins.notify('beforeRender', [me]);
|
|
|
|
var animationOptions = me.options.animation;
|
|
if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) {
|
|
var animation = new Chart.Animation();
|
|
animation.numSteps = (duration || animationOptions.duration) / 16.66; //60 fps
|
|
animation.easing = animationOptions.easing;
|
|
|
|
// render function
|
|
animation.render = function(chartInstance, animationObject) {
|
|
var easingFunction = helpers.easingEffects[animationObject.easing];
|
|
var stepDecimal = animationObject.currentStep / animationObject.numSteps;
|
|
var easeDecimal = easingFunction(stepDecimal);
|
|
|
|
chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep);
|
|
};
|
|
|
|
// user events
|
|
animation.onAnimationProgress = animationOptions.onProgress;
|
|
animation.onAnimationComplete = animationOptions.onComplete;
|
|
|
|
Chart.animationService.addAnimation(me, animation, duration, lazy);
|
|
} else {
|
|
me.draw();
|
|
if (animationOptions && animationOptions.onComplete && animationOptions.onComplete.call) {
|
|
animationOptions.onComplete.call(me);
|
|
}
|
|
}
|
|
return me;
|
|
},
|
|
|
|
draw: function(ease) {
|
|
var me = this;
|
|
var easingDecimal = ease || 1;
|
|
me.clear();
|
|
|
|
Chart.plugins.notify('beforeDraw', [me, easingDecimal]);
|
|
|
|
// Draw all the scales
|
|
helpers.each(me.boxes, function(box) {
|
|
box.draw(me.chartArea);
|
|
}, me);
|
|
if (me.scale) {
|
|
me.scale.draw();
|
|
}
|
|
|
|
Chart.plugins.notify('beforeDatasetsDraw', [me, easingDecimal]);
|
|
|
|
// Draw each dataset via its respective controller (reversed to support proper line stacking)
|
|
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
|
|
if (me.isDatasetVisible(datasetIndex)) {
|
|
me.getDatasetMeta(datasetIndex).controller.draw(ease);
|
|
}
|
|
}, me, true);
|
|
|
|
Chart.plugins.notify('afterDatasetsDraw', [me, easingDecimal]);
|
|
|
|
// Finally draw the tooltip
|
|
me.tooltip.transition(easingDecimal).draw();
|
|
|
|
Chart.plugins.notify('afterDraw', [me, easingDecimal]);
|
|
},
|
|
|
|
// Get the single element that was clicked on
|
|
// @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
|
|
getElementAtEvent: function(e) {
|
|
var me = this;
|
|
var eventPosition = helpers.getRelativePosition(e, me.chart);
|
|
var elementsArray = [];
|
|
|
|
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
|
|
if (me.isDatasetVisible(datasetIndex)) {
|
|
var meta = me.getDatasetMeta(datasetIndex);
|
|
helpers.each(meta.data, function(element) {
|
|
if (element.inRange(eventPosition.x, eventPosition.y)) {
|
|
elementsArray.push(element);
|
|
return elementsArray;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
return elementsArray.slice(0, 1);
|
|
},
|
|
|
|
getElementsAtEvent: function(e) {
|
|
var me = this;
|
|
var eventPosition = helpers.getRelativePosition(e, me.chart);
|
|
var elementsArray = [];
|
|
|
|
var found = (function() {
|
|
if (me.data.datasets) {
|
|
for (var i = 0; i < me.data.datasets.length; i++) {
|
|
var meta = me.getDatasetMeta(i);
|
|
if (me.isDatasetVisible(i)) {
|
|
for (var j = 0; j < meta.data.length; j++) {
|
|
if (meta.data[j].inRange(eventPosition.x, eventPosition.y)) {
|
|
return meta.data[j];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}).call(me);
|
|
|
|
if (!found) {
|
|
return elementsArray;
|
|
}
|
|
|
|
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
|
|
if (me.isDatasetVisible(datasetIndex)) {
|
|
var meta = me.getDatasetMeta(datasetIndex),
|
|
element = meta.data[found._index];
|
|
if(element && !element._view.skip){
|
|
elementsArray.push(element);
|
|
}
|
|
}
|
|
}, me);
|
|
|
|
return elementsArray;
|
|
},
|
|
|
|
getElementsAtXAxis: function(e) {
|
|
var me = this;
|
|
var eventPosition = helpers.getRelativePosition(e, me.chart);
|
|
var elementsArray = [];
|
|
|
|
var found = (function() {
|
|
if (me.data.datasets) {
|
|
for (var i = 0; i < me.data.datasets.length; i++) {
|
|
var meta = me.getDatasetMeta(i);
|
|
if (me.isDatasetVisible(i)) {
|
|
for (var j = 0; j < meta.data.length; j++) {
|
|
if (meta.data[j].inLabelRange(eventPosition.x, eventPosition.y)) {
|
|
return meta.data[j];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}).call(me);
|
|
|
|
if (!found) {
|
|
return elementsArray;
|
|
}
|
|
|
|
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
|
|
if (me.isDatasetVisible(datasetIndex)) {
|
|
var meta = me.getDatasetMeta(datasetIndex);
|
|
var index = helpers.findIndex(meta.data, function (it) {
|
|
return found._model.x === it._model.x;
|
|
});
|
|
if(index !== -1 && !meta.data[index]._view.skip) {
|
|
elementsArray.push(meta.data[index]);
|
|
}
|
|
}
|
|
}, me);
|
|
|
|
return elementsArray;
|
|
},
|
|
|
|
getElementsAtEventForMode: function(e, mode) {
|
|
var me = this;
|
|
switch (mode) {
|
|
case 'single':
|
|
return me.getElementAtEvent(e);
|
|
case 'label':
|
|
return me.getElementsAtEvent(e);
|
|
case 'dataset':
|
|
return me.getDatasetAtEvent(e);
|
|
case 'x-axis':
|
|
return me.getElementsAtXAxis(e);
|
|
default:
|
|
return e;
|
|
}
|
|
},
|
|
|
|
getDatasetAtEvent: function(e) {
|
|
var elementsArray = this.getElementAtEvent(e);
|
|
|
|
if (elementsArray.length > 0) {
|
|
elementsArray = this.getDatasetMeta(elementsArray[0]._datasetIndex).data;
|
|
}
|
|
|
|
return elementsArray;
|
|
},
|
|
|
|
getDatasetMeta: function(datasetIndex) {
|
|
var me = this;
|
|
var dataset = me.data.datasets[datasetIndex];
|
|
if (!dataset._meta) {
|
|
dataset._meta = {};
|
|
}
|
|
|
|
var meta = dataset._meta[me.id];
|
|
if (!meta) {
|
|
meta = dataset._meta[me.id] = {
|
|
type: null,
|
|
data: [],
|
|
dataset: null,
|
|
controller: null,
|
|
hidden: null, // See isDatasetVisible() comment
|
|
xAxisID: null,
|
|
yAxisID: null
|
|
};
|
|
}
|
|
|
|
return meta;
|
|
},
|
|
|
|
getVisibleDatasetCount: function() {
|
|
var count = 0;
|
|
for (var i = 0, ilen = this.data.datasets.length; i<ilen; ++i) {
|
|
if (this.isDatasetVisible(i)) {
|
|
count++;
|
|
}
|
|
}
|
|
return count;
|
|
},
|
|
|
|
isDatasetVisible: function(datasetIndex) {
|
|
var meta = this.getDatasetMeta(datasetIndex);
|
|
|
|
// meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false,
|
|
// the dataset.hidden value is ignored, else if null, the dataset hidden state is returned.
|
|
return typeof meta.hidden === 'boolean'? !meta.hidden : !this.data.datasets[datasetIndex].hidden;
|
|
},
|
|
|
|
generateLegend: function() {
|
|
return this.options.legendCallback(this);
|
|
},
|
|
|
|
destroy: function() {
|
|
var me = this;
|
|
me.stop();
|
|
me.clear();
|
|
helpers.unbindEvents(me, me.events);
|
|
helpers.removeResizeListener(me.chart.canvas.parentNode);
|
|
|
|
// Reset canvas height/width attributes
|
|
var canvas = me.chart.canvas;
|
|
canvas.width = me.chart.width;
|
|
canvas.height = me.chart.height;
|
|
|
|
// if we scaled the canvas in response to a devicePixelRatio !== 1, we need to undo that transform here
|
|
if (me.chart.originalDevicePixelRatio !== undefined) {
|
|
me.chart.ctx.scale(1 / me.chart.originalDevicePixelRatio, 1 / me.chart.originalDevicePixelRatio);
|
|
}
|
|
|
|
// Reset to the old style since it may have been changed by the device pixel ratio changes
|
|
canvas.style.width = me.chart.originalCanvasStyleWidth;
|
|
canvas.style.height = me.chart.originalCanvasStyleHeight;
|
|
|
|
Chart.plugins.notify('destroy', [me]);
|
|
|
|
delete Chart.instances[me.id];
|
|
},
|
|
|
|
toBase64Image: function() {
|
|
return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
|
|
},
|
|
|
|
initToolTip: function() {
|
|
var me = this;
|
|
me.tooltip = new Chart.Tooltip({
|
|
_chart: me.chart,
|
|
_chartInstance: me,
|
|
_data: me.data,
|
|
_options: me.options.tooltips
|
|
}, me);
|
|
},
|
|
|
|
bindEvents: function() {
|
|
var me = this;
|
|
helpers.bindEvents(me, me.options.events, function(evt) {
|
|
me.eventHandler(evt);
|
|
});
|
|
},
|
|
|
|
updateHoverStyle: function(elements, mode, enabled) {
|
|
var method = enabled? 'setHoverStyle' : 'removeHoverStyle';
|
|
var element, i, ilen;
|
|
|
|
switch (mode) {
|
|
case 'single':
|
|
elements = [ elements[0] ];
|
|
break;
|
|
case 'label':
|
|
case 'dataset':
|
|
case 'x-axis':
|
|
// elements = elements;
|
|
break;
|
|
default:
|
|
// unsupported mode
|
|
return;
|
|
}
|
|
|
|
for (i=0, ilen=elements.length; i<ilen; ++i) {
|
|
element = elements[i];
|
|
if (element) {
|
|
this.getDatasetMeta(element._datasetIndex).controller[method](element);
|
|
}
|
|
}
|
|
},
|
|
|
|
eventHandler: function eventHandler(e) {
|
|
var me = this;
|
|
var tooltip = me.tooltip;
|
|
var options = me.options || {};
|
|
var hoverOptions = options.hover;
|
|
var tooltipsOptions = options.tooltips;
|
|
|
|
me.lastActive = me.lastActive || [];
|
|
me.lastTooltipActive = me.lastTooltipActive || [];
|
|
|
|
// Find Active Elements for hover and tooltips
|
|
if (e.type === 'mouseout') {
|
|
me.active = [];
|
|
me.tooltipActive = [];
|
|
} else {
|
|
me.active = me.getElementsAtEventForMode(e, hoverOptions.mode);
|
|
me.tooltipActive = me.getElementsAtEventForMode(e, tooltipsOptions.mode);
|
|
}
|
|
|
|
// On Hover hook
|
|
if (hoverOptions.onHover) {
|
|
hoverOptions.onHover.call(me, me.active);
|
|
}
|
|
|
|
if (me.legend && me.legend.handleEvent) {
|
|
me.legend.handleEvent(e);
|
|
}
|
|
|
|
if (e.type === 'mouseup' || e.type === 'click') {
|
|
if (options.onClick) {
|
|
options.onClick.call(me, e, me.active);
|
|
}
|
|
}
|
|
|
|
// Remove styling for last active (even if it may still be active)
|
|
if (me.lastActive.length) {
|
|
me.updateHoverStyle(me.lastActive, hoverOptions.mode, false);
|
|
}
|
|
|
|
// Built in hover styling
|
|
if (me.active.length && hoverOptions.mode) {
|
|
me.updateHoverStyle(me.active, hoverOptions.mode, true);
|
|
}
|
|
|
|
// Built in Tooltips
|
|
if (tooltipsOptions.enabled || tooltipsOptions.custom) {
|
|
tooltip.initialize();
|
|
tooltip._active = me.tooltipActive;
|
|
tooltip.update(true);
|
|
}
|
|
|
|
// Hover animations
|
|
tooltip.pivot();
|
|
|
|
if (!me.animating) {
|
|
// If entering, leaving, or changing elements, animate the change via pivot
|
|
if (!helpers.arrayEquals(me.active, me.lastActive) ||
|
|
!helpers.arrayEquals(me.tooltipActive, me.lastTooltipActive)) {
|
|
|
|
me.stop();
|
|
|
|
if (tooltipsOptions.enabled || tooltipsOptions.custom) {
|
|
tooltip.update(true);
|
|
}
|
|
|
|
// We only need to render at this point. Updating will cause scales to be
|
|
// recomputed generating flicker & using more memory than necessary.
|
|
me.render(hoverOptions.animationDuration, true);
|
|
}
|
|
}
|
|
|
|
// Remember Last Actives
|
|
me.lastActive = me.active;
|
|
me.lastTooltipActive = me.tooltipActive;
|
|
return me;
|
|
}
|
|
});
|
|
};
|