2016-02-12 04:30:53 +01:00
|
|
|
"use strict";
|
2015-06-15 03:15:10 +02:00
|
|
|
|
2016-02-12 04:30:53 +01:00
|
|
|
module.exports = function(Chart) {
|
2015-06-15 03:15:10 +02:00
|
|
|
|
2016-02-14 23:06:00 +01:00
|
|
|
var helpers = Chart.helpers;
|
|
|
|
//Create a dictionary of chart types, to allow for extension of existing types
|
|
|
|
Chart.types = {};
|
2015-06-15 03:15:10 +02:00
|
|
|
|
2016-02-14 23:06:00 +01:00
|
|
|
//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 = {};
|
2015-06-15 03:15:10 +02:00
|
|
|
|
2016-02-14 23:06:00 +01:00
|
|
|
// 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, {
|
|
|
|
|
|
|
|
initialize: function initialize() {
|
2016-04-17 18:02:42 +02:00
|
|
|
// Before init plugin notification
|
2016-04-17 18:25:47 +02:00
|
|
|
Chart.pluginService.notifyPlugins('beforeInit', [this]);
|
2016-02-14 23:06:00 +01:00
|
|
|
|
|
|
|
this.bindEvents();
|
|
|
|
|
|
|
|
// Make sure controllers are built first so that each dataset is bound to an axis before the scales
|
|
|
|
// are built
|
|
|
|
this.ensureScalesHaveIDs();
|
|
|
|
this.buildOrUpdateControllers();
|
|
|
|
this.buildScales();
|
|
|
|
this.buildSurroundingItems();
|
|
|
|
this.updateLayout();
|
|
|
|
this.resetElements();
|
|
|
|
this.initToolTip();
|
|
|
|
this.update();
|
|
|
|
|
2016-04-17 18:02:42 +02:00
|
|
|
// After init plugin notification
|
2016-04-17 18:25:47 +02:00
|
|
|
Chart.pluginService.notifyPlugins('afterInit', [this]);
|
2016-02-14 23:06:00 +01:00
|
|
|
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
|
|
|
clear: function clear() {
|
|
|
|
helpers.clear(this.chart);
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
|
|
|
stop: function stop() {
|
|
|
|
// Stops any current animation loop occuring
|
|
|
|
Chart.animationService.cancelAnimation(this);
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
|
|
|
resize: function resize(silent) {
|
|
|
|
var canvas = this.chart.canvas;
|
|
|
|
var newWidth = helpers.getMaximumWidth(this.chart.canvas);
|
|
|
|
var newHeight = (this.options.maintainAspectRatio && isNaN(this.chart.aspectRatio) === false && isFinite(this.chart.aspectRatio) && this.chart.aspectRatio !== 0) ? newWidth / this.chart.aspectRatio : helpers.getMaximumHeight(this.chart.canvas);
|
|
|
|
|
|
|
|
var sizeChanged = this.chart.width !== newWidth || this.chart.height !== newHeight;
|
|
|
|
|
|
|
|
if (!sizeChanged)
|
|
|
|
return this;
|
|
|
|
|
|
|
|
canvas.width = this.chart.width = newWidth;
|
|
|
|
canvas.height = this.chart.height = newHeight;
|
|
|
|
|
|
|
|
helpers.retinaScale(this.chart);
|
|
|
|
|
|
|
|
if (!silent) {
|
|
|
|
this.stop();
|
|
|
|
this.update(this.options.responsiveAnimationDuration);
|
|
|
|
}
|
|
|
|
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
ensureScalesHaveIDs: function ensureScalesHaveIDs() {
|
|
|
|
var defaultXAxisID = 'x-axis-';
|
|
|
|
var defaultYAxisID = 'y-axis-';
|
|
|
|
|
|
|
|
if (this.options.scales) {
|
|
|
|
if (this.options.scales.xAxes && this.options.scales.xAxes.length) {
|
|
|
|
helpers.each(this.options.scales.xAxes, function(xAxisOptions, index) {
|
|
|
|
xAxisOptions.id = xAxisOptions.id || (defaultXAxisID + index);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.options.scales.yAxes && this.options.scales.yAxes.length) {
|
|
|
|
// Build the y axes
|
|
|
|
helpers.each(this.options.scales.yAxes, function(yAxisOptions, index) {
|
|
|
|
yAxisOptions.id = yAxisOptions.id || (defaultYAxisID + index);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
buildScales: function buildScales() {
|
|
|
|
// Map of scale ID to scale object so we can lookup later
|
|
|
|
this.scales = {};
|
|
|
|
|
|
|
|
// Build the x axes
|
|
|
|
if (this.options.scales) {
|
|
|
|
if (this.options.scales.xAxes && this.options.scales.xAxes.length) {
|
|
|
|
helpers.each(this.options.scales.xAxes, function(xAxisOptions, index) {
|
2016-03-19 13:50:50 +01:00
|
|
|
var xType = helpers.getValueOrDefault(xAxisOptions.type, 'category');
|
|
|
|
var ScaleClass = Chart.scaleService.getScaleConstructor(xType);
|
2016-02-14 23:06:00 +01:00
|
|
|
if (ScaleClass) {
|
|
|
|
var scale = new ScaleClass({
|
|
|
|
ctx: this.chart.ctx,
|
|
|
|
options: xAxisOptions,
|
|
|
|
chart: this,
|
|
|
|
id: xAxisOptions.id
|
|
|
|
});
|
|
|
|
|
|
|
|
this.scales[scale.id] = scale;
|
|
|
|
}
|
|
|
|
}, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.options.scales.yAxes && this.options.scales.yAxes.length) {
|
|
|
|
// Build the y axes
|
|
|
|
helpers.each(this.options.scales.yAxes, function(yAxisOptions, index) {
|
2016-03-19 13:50:50 +01:00
|
|
|
var yType = helpers.getValueOrDefault(yAxisOptions.type, 'linear');
|
|
|
|
var ScaleClass = Chart.scaleService.getScaleConstructor(yType);
|
2016-02-14 23:06:00 +01:00
|
|
|
if (ScaleClass) {
|
|
|
|
var scale = new ScaleClass({
|
|
|
|
ctx: this.chart.ctx,
|
|
|
|
options: yAxisOptions,
|
|
|
|
chart: this,
|
|
|
|
id: yAxisOptions.id
|
|
|
|
});
|
|
|
|
|
|
|
|
this.scales[scale.id] = scale;
|
|
|
|
}
|
|
|
|
}, this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (this.options.scale) {
|
|
|
|
// Build radial axes
|
|
|
|
var ScaleClass = Chart.scaleService.getScaleConstructor(this.options.scale.type);
|
|
|
|
if (ScaleClass) {
|
|
|
|
var scale = new ScaleClass({
|
|
|
|
ctx: this.chart.ctx,
|
|
|
|
options: this.options.scale,
|
|
|
|
chart: this
|
|
|
|
});
|
|
|
|
|
|
|
|
this.scale = scale;
|
|
|
|
|
|
|
|
this.scales.radialScale = scale;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Chart.scaleService.addScalesToLayout(this);
|
|
|
|
},
|
|
|
|
|
|
|
|
buildSurroundingItems: function() {
|
|
|
|
if (this.options.title) {
|
|
|
|
this.titleBlock = new Chart.Title({
|
|
|
|
ctx: this.chart.ctx,
|
|
|
|
options: this.options.title,
|
|
|
|
chart: this
|
|
|
|
});
|
|
|
|
|
|
|
|
Chart.layoutService.addBox(this, this.titleBlock);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.options.legend) {
|
|
|
|
this.legend = new Chart.Legend({
|
|
|
|
ctx: this.chart.ctx,
|
|
|
|
options: this.options.legend,
|
|
|
|
chart: this
|
|
|
|
});
|
|
|
|
|
|
|
|
Chart.layoutService.addBox(this, this.legend);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
updateLayout: function() {
|
|
|
|
Chart.layoutService.update(this, this.chart.width, this.chart.height);
|
|
|
|
},
|
|
|
|
|
|
|
|
buildOrUpdateControllers: function buildOrUpdateControllers() {
|
|
|
|
var types = [];
|
|
|
|
var newControllers = [];
|
|
|
|
|
|
|
|
helpers.each(this.data.datasets, function(dataset, datasetIndex) {
|
|
|
|
if (!dataset.type) {
|
|
|
|
dataset.type = this.config.type;
|
|
|
|
}
|
|
|
|
|
|
|
|
var type = dataset.type;
|
|
|
|
types.push(type);
|
|
|
|
|
|
|
|
if (dataset.controller) {
|
|
|
|
dataset.controller.updateIndex(datasetIndex);
|
|
|
|
} else {
|
|
|
|
dataset.controller = new Chart.controllers[type](this, datasetIndex);
|
|
|
|
newControllers.push(dataset.controller);
|
|
|
|
}
|
|
|
|
}, this);
|
|
|
|
|
|
|
|
if (types.length > 1) {
|
|
|
|
for (var i = 1; i < types.length; i++) {
|
|
|
|
if (types[i] !== types[i - 1]) {
|
|
|
|
this.isCombo = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return newControllers;
|
|
|
|
},
|
|
|
|
|
|
|
|
resetElements: function resetElements() {
|
|
|
|
helpers.each(this.data.datasets, function(dataset, datasetIndex) {
|
|
|
|
dataset.controller.reset();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
update: function update(animationDuration, lazy) {
|
2016-04-17 18:25:47 +02:00
|
|
|
Chart.pluginService.notifyPlugins('beforeUpdate', [this]);
|
2016-04-17 18:02:42 +02:00
|
|
|
|
2016-02-14 23:06:00 +01:00
|
|
|
// In case the entire data object changed
|
|
|
|
this.tooltip._data = this.data;
|
|
|
|
|
|
|
|
// Make sure dataset controllers are updated and new controllers are reset
|
|
|
|
var newControllers = this.buildOrUpdateControllers();
|
|
|
|
|
|
|
|
Chart.layoutService.update(this, this.chart.width, this.chart.height);
|
|
|
|
|
|
|
|
// Can only reset the new controllers after the scales have been updated
|
|
|
|
helpers.each(newControllers, function(controller) {
|
|
|
|
controller.reset();
|
|
|
|
});
|
|
|
|
|
|
|
|
// Make sure all dataset controllers have correct meta data counts
|
|
|
|
helpers.each(this.data.datasets, function(dataset, datasetIndex) {
|
|
|
|
dataset.controller.buildOrUpdateElements();
|
|
|
|
});
|
|
|
|
|
|
|
|
// This will loop through any data and do the appropriate element update for the type
|
|
|
|
helpers.each(this.data.datasets, function(dataset, datasetIndex) {
|
|
|
|
dataset.controller.update();
|
|
|
|
});
|
|
|
|
this.render(animationDuration, lazy);
|
2016-04-17 18:02:42 +02:00
|
|
|
|
2016-04-17 18:25:47 +02:00
|
|
|
Chart.pluginService.notifyPlugins('afterUpdate', [this]);
|
2016-02-14 23:06:00 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
render: function render(duration, lazy) {
|
2016-04-23 03:04:55 +02:00
|
|
|
Chart.pluginService.notifyPlugins('beforeRender', [this]);
|
2016-02-14 23:06:00 +01:00
|
|
|
|
|
|
|
if (this.options.animation && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && this.options.animation.duration !== 0))) {
|
|
|
|
var animation = new Chart.Animation();
|
|
|
|
animation.numSteps = (duration || this.options.animation.duration) / 16.66; //60 fps
|
|
|
|
animation.easing = this.options.animation.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 = this.options.animation.onProgress;
|
|
|
|
animation.onAnimationComplete = this.options.animation.onComplete;
|
|
|
|
|
|
|
|
Chart.animationService.addAnimation(this, animation, duration, lazy);
|
|
|
|
} else {
|
|
|
|
this.draw();
|
|
|
|
if (this.options.animation && this.options.animation.onComplete && this.options.animation.onComplete.call) {
|
|
|
|
this.options.animation.onComplete.call(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
|
|
|
draw: function(ease) {
|
|
|
|
var easingDecimal = ease || 1;
|
|
|
|
this.clear();
|
|
|
|
|
2016-04-17 18:25:47 +02:00
|
|
|
Chart.pluginService.notifyPlugins('beforeDraw', [this, easingDecimal]);
|
2016-04-17 18:02:42 +02:00
|
|
|
|
2016-02-14 23:06:00 +01:00
|
|
|
// Draw all the scales
|
|
|
|
helpers.each(this.boxes, function(box) {
|
|
|
|
box.draw(this.chartArea);
|
|
|
|
}, this);
|
|
|
|
if (this.scale) {
|
|
|
|
this.scale.draw();
|
|
|
|
}
|
|
|
|
|
2016-04-03 03:41:57 +02:00
|
|
|
// Clip out the chart area so that anything outside does not draw. This is necessary for zoom and pan to function
|
|
|
|
this.chart.ctx.save();
|
|
|
|
this.chart.ctx.beginPath();
|
|
|
|
this.chart.ctx.rect(this.chartArea.left, this.chartArea.top, this.chartArea.right - this.chartArea.left, this.chartArea.bottom - this.chartArea.top);
|
|
|
|
this.chart.ctx.clip();
|
|
|
|
|
2016-02-14 23:06:00 +01:00
|
|
|
// Draw each dataset via its respective controller (reversed to support proper line stacking)
|
|
|
|
helpers.each(this.data.datasets, function(dataset, datasetIndex) {
|
|
|
|
if (helpers.isDatasetVisible(dataset)) {
|
|
|
|
dataset.controller.draw(ease);
|
|
|
|
}
|
2016-02-17 20:41:32 +01:00
|
|
|
}, null, true);
|
2016-02-14 23:06:00 +01:00
|
|
|
|
2016-04-03 03:41:57 +02:00
|
|
|
// Restore from the clipping operation
|
|
|
|
this.chart.ctx.restore();
|
|
|
|
|
2016-02-14 23:06:00 +01:00
|
|
|
// Finally draw the tooltip
|
|
|
|
this.tooltip.transition(easingDecimal).draw();
|
2016-04-17 18:02:42 +02:00
|
|
|
|
2016-04-17 18:25:47 +02:00
|
|
|
Chart.pluginService.notifyPlugins('afterDraw', [this, easingDecimal]);
|
2016-02-14 23:06:00 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
// 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 eventPosition = helpers.getRelativePosition(e, this.chart);
|
|
|
|
var elementsArray = [];
|
|
|
|
|
|
|
|
helpers.each(this.data.datasets, function(dataset, datasetIndex) {
|
|
|
|
if (helpers.isDatasetVisible(dataset)) {
|
|
|
|
helpers.each(dataset.metaData, function(element, index) {
|
|
|
|
if (element.inRange(eventPosition.x, eventPosition.y)) {
|
|
|
|
elementsArray.push(element);
|
|
|
|
return elementsArray;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return elementsArray;
|
|
|
|
},
|
|
|
|
|
|
|
|
getElementsAtEvent: function(e) {
|
|
|
|
var eventPosition = helpers.getRelativePosition(e, this.chart);
|
|
|
|
var elementsArray = [];
|
|
|
|
|
|
|
|
var found = (function() {
|
2016-03-30 02:10:29 +02:00
|
|
|
if (this.data.datasets) {
|
|
|
|
for (var i = 0; i < this.data.datasets.length; i++) {
|
|
|
|
if (helpers.isDatasetVisible(this.data.datasets[i])) {
|
|
|
|
for (var j = 0; j < this.data.datasets[i].metaData.length; j++) {
|
|
|
|
if (this.data.datasets[i].metaData[j].inRange(eventPosition.x, eventPosition.y)) {
|
|
|
|
return this.data.datasets[i].metaData[j];
|
|
|
|
}
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}).call(this);
|
|
|
|
|
|
|
|
if (!found) {
|
|
|
|
return elementsArray;
|
|
|
|
}
|
|
|
|
|
|
|
|
helpers.each(this.data.datasets, function(dataset, dsIndex) {
|
|
|
|
if (helpers.isDatasetVisible(dataset)) {
|
|
|
|
elementsArray.push(dataset.metaData[found._index]);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return elementsArray;
|
|
|
|
},
|
|
|
|
|
|
|
|
getDatasetAtEvent: function(e) {
|
|
|
|
var elementsArray = this.getElementAtEvent(e);
|
|
|
|
|
|
|
|
if (elementsArray.length > 0) {
|
|
|
|
elementsArray = this.data.datasets[elementsArray[0]._datasetIndex].metaData;
|
|
|
|
}
|
|
|
|
|
|
|
|
return elementsArray;
|
|
|
|
},
|
|
|
|
|
|
|
|
generateLegend: function generateLegend() {
|
|
|
|
return this.options.legendCallback(this);
|
|
|
|
},
|
|
|
|
|
|
|
|
destroy: function destroy() {
|
|
|
|
this.clear();
|
|
|
|
helpers.unbindEvents(this, this.events);
|
|
|
|
helpers.removeResizeListener(this.chart.canvas.parentNode);
|
|
|
|
|
|
|
|
// Reset canvas height/width attributes
|
|
|
|
var canvas = this.chart.canvas;
|
|
|
|
canvas.width = this.chart.width;
|
|
|
|
canvas.height = this.chart.height;
|
|
|
|
|
|
|
|
// if we scaled the canvas in response to a devicePixelRatio !== 1, we need to undo that transform here
|
|
|
|
if (this.chart.originalDevicePixelRatio !== undefined) {
|
|
|
|
this.chart.ctx.scale(1 / this.chart.originalDevicePixelRatio, 1 / this.chart.originalDevicePixelRatio);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset to the old style since it may have been changed by the device pixel ratio changes
|
|
|
|
canvas.style.width = this.chart.originalCanvasStyleWidth;
|
|
|
|
canvas.style.height = this.chart.originalCanvasStyleHeight;
|
|
|
|
|
2016-04-21 17:25:37 +02:00
|
|
|
Chart.pluginService.notifyPlugins('destroy', [this]);
|
2016-04-18 00:55:20 +02:00
|
|
|
|
2016-02-14 23:06:00 +01:00
|
|
|
delete Chart.instances[this.id];
|
|
|
|
},
|
|
|
|
|
|
|
|
toBase64Image: function toBase64Image() {
|
|
|
|
return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
|
|
|
|
},
|
|
|
|
|
|
|
|
initToolTip: function initToolTip() {
|
|
|
|
this.tooltip = new Chart.Tooltip({
|
|
|
|
_chart: this.chart,
|
|
|
|
_chartInstance: this,
|
|
|
|
_data: this.data,
|
|
|
|
_options: this.options
|
|
|
|
}, this);
|
|
|
|
},
|
|
|
|
|
|
|
|
bindEvents: function bindEvents() {
|
|
|
|
helpers.bindEvents(this, this.options.events, function(evt) {
|
|
|
|
this.eventHandler(evt);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
eventHandler: function eventHandler(e) {
|
|
|
|
this.lastActive = this.lastActive || [];
|
|
|
|
this.lastTooltipActive = this.lastTooltipActive || [];
|
|
|
|
|
|
|
|
// Find Active Elements for hover and tooltips
|
|
|
|
if (e.type === 'mouseout') {
|
|
|
|
this.active = [];
|
|
|
|
this.tooltipActive = [];
|
|
|
|
} else {
|
|
|
|
|
|
|
|
var _this = this;
|
|
|
|
var getItemsForMode = function(mode) {
|
|
|
|
switch (mode) {
|
|
|
|
case 'single':
|
|
|
|
return _this.getElementAtEvent(e);
|
|
|
|
case 'label':
|
|
|
|
return _this.getElementsAtEvent(e);
|
|
|
|
case 'dataset':
|
|
|
|
return _this.getDatasetAtEvent(e);
|
|
|
|
default:
|
|
|
|
return e;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
this.active = getItemsForMode(this.options.hover.mode);
|
|
|
|
this.tooltipActive = getItemsForMode(this.options.tooltips.mode);
|
|
|
|
}
|
|
|
|
|
|
|
|
// On Hover hook
|
|
|
|
if (this.options.hover.onHover) {
|
|
|
|
this.options.hover.onHover.call(this, this.active);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (e.type === 'mouseup' || e.type === 'click') {
|
|
|
|
if (this.options.onClick) {
|
|
|
|
this.options.onClick.call(this, e, this.active);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.legend && this.legend.handleEvent) {
|
|
|
|
this.legend.handleEvent(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var dataset;
|
|
|
|
var index;
|
|
|
|
|
|
|
|
// Remove styling for last active (even if it may still be active)
|
|
|
|
if (this.lastActive.length) {
|
|
|
|
switch (this.options.hover.mode) {
|
|
|
|
case 'single':
|
|
|
|
this.data.datasets[this.lastActive[0]._datasetIndex].controller.removeHoverStyle(this.lastActive[0], this.lastActive[0]._datasetIndex, this.lastActive[0]._index);
|
|
|
|
break;
|
|
|
|
case 'label':
|
|
|
|
case 'dataset':
|
|
|
|
for (var i = 0; i < this.lastActive.length; i++) {
|
|
|
|
if (this.lastActive[i])
|
|
|
|
this.data.datasets[this.lastActive[i]._datasetIndex].controller.removeHoverStyle(this.lastActive[i], this.lastActive[i]._datasetIndex, this.lastActive[i]._index);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// Don't change anything
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Built in hover styling
|
|
|
|
if (this.active.length && this.options.hover.mode) {
|
|
|
|
switch (this.options.hover.mode) {
|
|
|
|
case 'single':
|
|
|
|
this.data.datasets[this.active[0]._datasetIndex].controller.setHoverStyle(this.active[0]);
|
|
|
|
break;
|
|
|
|
case 'label':
|
|
|
|
case 'dataset':
|
|
|
|
for (var j = 0; j < this.active.length; j++) {
|
|
|
|
if (this.active[j])
|
|
|
|
this.data.datasets[this.active[j]._datasetIndex].controller.setHoverStyle(this.active[j]);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// Don't change anything
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Built in Tooltips
|
|
|
|
if (this.options.tooltips.enabled || this.options.tooltips.custom) {
|
|
|
|
|
|
|
|
// The usual updates
|
|
|
|
this.tooltip.initialize();
|
|
|
|
this.tooltip._active = this.tooltipActive;
|
|
|
|
this.tooltip.update();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Hover animations
|
|
|
|
this.tooltip.pivot();
|
|
|
|
|
|
|
|
if (!this.animating) {
|
|
|
|
var changed;
|
|
|
|
|
|
|
|
helpers.each(this.active, function(element, index) {
|
|
|
|
if (element !== this.lastActive[index]) {
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
}, this);
|
|
|
|
|
|
|
|
helpers.each(this.tooltipActive, function(element, index) {
|
|
|
|
if (element !== this.lastTooltipActive[index]) {
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
}, this);
|
|
|
|
|
|
|
|
// If entering, leaving, or changing elements, animate the change via pivot
|
|
|
|
if ((this.lastActive.length !== this.active.length) ||
|
|
|
|
(this.lastTooltipActive.length !== this.tooltipActive.length) ||
|
|
|
|
changed) {
|
|
|
|
|
|
|
|
this.stop();
|
|
|
|
|
|
|
|
if (this.options.tooltips.enabled || this.options.tooltips.custom) {
|
|
|
|
this.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.
|
|
|
|
this.render(this.options.hover.animationDuration, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remember Last Actives
|
|
|
|
this.lastActive = this.active;
|
|
|
|
this.lastTooltipActive = this.tooltipActive;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
});
|
2016-02-17 20:41:32 +01:00
|
|
|
};
|