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-06-11 00:14:27 +02:00
|
|
|
/**
|
|
|
|
* @class Chart.Controller
|
|
|
|
* The main controller of a chart.
|
|
|
|
*/
|
2016-02-14 23:06:00 +01:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2016-06-11 00:14:27 +02:00
|
|
|
helpers.extend(Chart.Controller.prototype, /** @lends Chart.Controller */ {
|
2016-02-14 23:06:00 +01:00
|
|
|
|
2016-06-18 11:15:25 +02:00
|
|
|
initialize: function() {
|
2016-06-04 20:14:16 +02:00
|
|
|
var me = this;
|
2016-04-17 18:02:42 +02:00
|
|
|
// Before init plugin notification
|
2016-06-10 22:26:35 +02:00
|
|
|
Chart.plugins.notify('beforeInit', [me]);
|
2016-02-14 23:06:00 +01:00
|
|
|
|
2016-06-04 20:14:16 +02:00
|
|
|
me.bindEvents();
|
2016-02-14 23:06:00 +01:00
|
|
|
|
|
|
|
// Make sure controllers are built first so that each dataset is bound to an axis before the scales
|
|
|
|
// are built
|
2016-06-04 20:14:16 +02:00
|
|
|
me.ensureScalesHaveIDs();
|
|
|
|
me.buildOrUpdateControllers();
|
|
|
|
me.buildScales();
|
|
|
|
me.updateLayout();
|
|
|
|
me.resetElements();
|
|
|
|
me.initToolTip();
|
|
|
|
me.update();
|
2016-02-14 23:06:00 +01:00
|
|
|
|
2016-04-17 18:02:42 +02:00
|
|
|
// After init plugin notification
|
2016-06-10 22:26:35 +02:00
|
|
|
Chart.plugins.notify('afterInit', [me]);
|
2016-02-14 23:06:00 +01:00
|
|
|
|
2016-06-04 20:14:16 +02:00
|
|
|
return me;
|
2016-02-14 23:06:00 +01:00
|
|
|
},
|
|
|
|
|
2016-06-18 11:15:25 +02:00
|
|
|
clear: function() {
|
2016-02-14 23:06:00 +01:00
|
|
|
helpers.clear(this.chart);
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
2016-06-18 11:15:25 +02:00
|
|
|
stop: function() {
|
2016-02-14 23:06:00 +01:00
|
|
|
// Stops any current animation loop occuring
|
|
|
|
Chart.animationService.cancelAnimation(this);
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
|
|
|
resize: function resize(silent) {
|
2016-06-04 20:14:16 +02:00
|
|
|
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);
|
2016-06-10 22:26:35 +02:00
|
|
|
|
2016-06-04 20:14:16 +02:00
|
|
|
var sizeChanged = chart.width !== newWidth || chart.height !== newHeight;
|
2016-02-14 23:06:00 +01:00
|
|
|
|
2016-06-04 20:14:16 +02:00
|
|
|
if (!sizeChanged) {
|
|
|
|
return me;
|
|
|
|
}
|
|
|
|
|
|
|
|
canvas.width = chart.width = newWidth;
|
|
|
|
canvas.height = chart.height = newHeight;
|
2016-02-14 23:06:00 +01:00
|
|
|
|
2016-06-04 20:14:16 +02:00
|
|
|
helpers.retinaScale(chart);
|
2016-02-14 23:06:00 +01:00
|
|
|
|
2016-06-04 20:14:16 +02:00
|
|
|
// Notify any plugins about the resize
|
|
|
|
var newSize = { width: newWidth, height: newHeight };
|
2016-06-10 22:26:35 +02:00
|
|
|
Chart.plugins.notify('resize', [me, newSize]);
|
2016-06-04 20:14:16 +02:00
|
|
|
|
|
|
|
// Notify of resize
|
|
|
|
if (me.options.onResize) {
|
|
|
|
me.options.onResize(me, newSize);
|
|
|
|
}
|
2016-02-14 23:06:00 +01:00
|
|
|
|
|
|
|
if (!silent) {
|
2016-06-04 20:14:16 +02:00
|
|
|
me.stop();
|
|
|
|
me.update(me.options.responsiveAnimationDuration);
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
|
|
|
|
2016-06-04 20:14:16 +02:00
|
|
|
return me;
|
2016-02-14 23:06:00 +01:00
|
|
|
},
|
2016-05-21 22:53:58 +02:00
|
|
|
|
2016-06-18 11:15:25 +02:00
|
|
|
ensureScalesHaveIDs: function() {
|
2016-05-21 22:53:58 +02:00
|
|
|
var options = this.options;
|
|
|
|
var scalesOptions = options.scales || {};
|
|
|
|
var scaleOptions = options.scale;
|
2016-02-14 23:06:00 +01:00
|
|
|
|
2016-05-21 22:53:58 +02:00
|
|
|
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';
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
|
|
|
},
|
2016-05-21 22:53:58 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Builds a map of scale ID to scale object for future lookup.
|
|
|
|
*/
|
2016-06-18 11:15:25 +02:00
|
|
|
buildScales: function() {
|
2016-05-21 22:53:58 +02:00
|
|
|
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' }; }));
|
|
|
|
}
|
2016-02-14 23:06:00 +01:00
|
|
|
|
2016-05-21 22:53:58 +02:00
|
|
|
if (options.scale) {
|
|
|
|
items.push({ options: options.scale, dtype: 'radialLinear', isDefault: true });
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
2016-05-21 22:53:58 +02:00
|
|
|
|
2016-06-18 11:00:11 +02:00
|
|
|
helpers.each(items, function(item) {
|
2016-05-21 22:53:58 +02:00
|
|
|
var scaleOptions = item.options;
|
|
|
|
var scaleType = helpers.getValueOrDefault(scaleOptions.type, item.dtype);
|
|
|
|
var scaleClass = Chart.scaleService.getScaleConstructor(scaleType);
|
|
|
|
if (!scaleClass) {
|
|
|
|
return;
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
2016-05-21 22:53:58 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
});
|
2016-02-14 23:06:00 +01:00
|
|
|
|
|
|
|
Chart.scaleService.addScalesToLayout(this);
|
|
|
|
},
|
|
|
|
|
|
|
|
updateLayout: function() {
|
|
|
|
Chart.layoutService.update(this, this.chart.width, this.chart.height);
|
|
|
|
},
|
|
|
|
|
2016-06-18 11:15:25 +02:00
|
|
|
buildOrUpdateControllers: function() {
|
2016-06-04 20:14:16 +02:00
|
|
|
var me = this;
|
2016-02-14 23:06:00 +01:00
|
|
|
var types = [];
|
|
|
|
var newControllers = [];
|
|
|
|
|
2016-06-04 20:14:16 +02:00
|
|
|
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
|
|
|
|
var meta = me.getDatasetMeta(datasetIndex);
|
2016-04-21 23:43:47 +02:00
|
|
|
if (!meta.type) {
|
2016-06-04 20:14:16 +02:00
|
|
|
meta.type = dataset.type || me.config.type;
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
|
|
|
|
2016-04-21 23:43:47 +02:00
|
|
|
types.push(meta.type);
|
2016-02-14 23:06:00 +01:00
|
|
|
|
2016-04-21 17:11:52 +02:00
|
|
|
if (meta.controller) {
|
|
|
|
meta.controller.updateIndex(datasetIndex);
|
2016-02-14 23:06:00 +01:00
|
|
|
} else {
|
2016-06-04 20:14:16 +02:00
|
|
|
meta.controller = new Chart.controllers[meta.type](me, datasetIndex);
|
2016-04-21 17:11:52 +02:00
|
|
|
newControllers.push(meta.controller);
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
2016-06-04 20:14:16 +02:00
|
|
|
}, me);
|
2016-02-14 23:06:00 +01:00
|
|
|
|
|
|
|
if (types.length > 1) {
|
|
|
|
for (var i = 1; i < types.length; i++) {
|
|
|
|
if (types[i] !== types[i - 1]) {
|
2016-06-04 20:14:16 +02:00
|
|
|
me.isCombo = true;
|
2016-02-14 23:06:00 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return newControllers;
|
|
|
|
},
|
|
|
|
|
2016-06-18 11:15:25 +02:00
|
|
|
resetElements: function() {
|
2016-06-04 20:14:16 +02:00
|
|
|
var me = this;
|
|
|
|
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
|
|
|
|
me.getDatasetMeta(datasetIndex).controller.reset();
|
|
|
|
}, me);
|
2016-02-14 23:06:00 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
update: function update(animationDuration, lazy) {
|
2016-06-04 20:14:16 +02:00
|
|
|
var me = this;
|
2016-06-10 22:26:35 +02:00
|
|
|
Chart.plugins.notify('beforeUpdate', [me]);
|
2016-04-17 18:02:42 +02:00
|
|
|
|
2016-02-14 23:06:00 +01:00
|
|
|
// In case the entire data object changed
|
2016-06-04 20:14:16 +02:00
|
|
|
me.tooltip._data = me.data;
|
2016-02-14 23:06:00 +01:00
|
|
|
|
|
|
|
// Make sure dataset controllers are updated and new controllers are reset
|
2016-06-04 20:14:16 +02:00
|
|
|
var newControllers = me.buildOrUpdateControllers();
|
2016-02-14 23:06:00 +01:00
|
|
|
|
2016-04-23 16:52:02 +02:00
|
|
|
// Make sure all dataset controllers have correct meta data counts
|
2016-06-04 20:14:16 +02:00
|
|
|
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
|
|
|
|
me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements();
|
|
|
|
}, me);
|
2016-04-23 16:52:02 +02:00
|
|
|
|
2016-06-04 20:14:16 +02:00
|
|
|
Chart.layoutService.update(me, me.chart.width, me.chart.height);
|
2016-02-14 23:06:00 +01:00
|
|
|
|
2016-05-10 16:01:30 +02:00
|
|
|
// Apply changes to the dataets that require the scales to have been calculated i.e BorderColor chages
|
2016-06-10 22:26:35 +02:00
|
|
|
Chart.plugins.notify('afterScaleUpdate', [me]);
|
2016-05-10 16:01:30 +02:00
|
|
|
|
2016-02-14 23:06:00 +01:00
|
|
|
// Can only reset the new controllers after the scales have been updated
|
|
|
|
helpers.each(newControllers, function(controller) {
|
|
|
|
controller.reset();
|
|
|
|
});
|
|
|
|
|
2016-06-11 00:14:27 +02:00
|
|
|
me.updateDatasets();
|
2016-04-21 17:11:52 +02:00
|
|
|
|
2016-05-10 00:14:15 +02:00
|
|
|
// Do this before render so that any plugins that need final scale updates can use it
|
2016-06-10 22:26:35 +02:00
|
|
|
Chart.plugins.notify('afterUpdate', [me]);
|
2016-05-10 00:14:15 +02:00
|
|
|
|
2016-06-04 20:14:16 +02:00
|
|
|
me.render(animationDuration, lazy);
|
2016-02-14 23:06:00 +01:00
|
|
|
},
|
|
|
|
|
2016-06-11 00:14:27 +02:00
|
|
|
/**
|
|
|
|
* @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 ]);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2016-02-14 23:06:00 +01:00
|
|
|
render: function render(duration, lazy) {
|
2016-06-04 20:14:16 +02:00
|
|
|
var me = this;
|
2016-06-10 22:26:35 +02:00
|
|
|
Chart.plugins.notify('beforeRender', [me]);
|
2016-02-14 23:06:00 +01:00
|
|
|
|
2016-06-04 20:14:16 +02:00
|
|
|
var animationOptions = me.options.animation;
|
2016-05-14 05:07:39 +02:00
|
|
|
if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) {
|
2016-02-14 23:06:00 +01:00
|
|
|
var animation = new Chart.Animation();
|
2016-05-14 05:07:39 +02:00
|
|
|
animation.numSteps = (duration || animationOptions.duration) / 16.66; //60 fps
|
|
|
|
animation.easing = animationOptions.easing;
|
2016-02-14 23:06:00 +01:00
|
|
|
|
|
|
|
// 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
|
2016-05-14 05:07:39 +02:00
|
|
|
animation.onAnimationProgress = animationOptions.onProgress;
|
|
|
|
animation.onAnimationComplete = animationOptions.onComplete;
|
2016-02-14 23:06:00 +01:00
|
|
|
|
2016-06-04 20:14:16 +02:00
|
|
|
Chart.animationService.addAnimation(me, animation, duration, lazy);
|
2016-02-14 23:06:00 +01:00
|
|
|
} else {
|
2016-06-04 20:14:16 +02:00
|
|
|
me.draw();
|
2016-05-14 05:07:39 +02:00
|
|
|
if (animationOptions && animationOptions.onComplete && animationOptions.onComplete.call) {
|
2016-06-04 20:14:16 +02:00
|
|
|
animationOptions.onComplete.call(me);
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
|
|
|
}
|
2016-06-04 20:14:16 +02:00
|
|
|
return me;
|
2016-02-14 23:06:00 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
draw: function(ease) {
|
2016-06-04 20:14:16 +02:00
|
|
|
var me = this;
|
2016-02-14 23:06:00 +01:00
|
|
|
var easingDecimal = ease || 1;
|
2016-06-04 20:14:16 +02:00
|
|
|
me.clear();
|
2016-02-14 23:06:00 +01:00
|
|
|
|
2016-06-10 22:26:35 +02:00
|
|
|
Chart.plugins.notify('beforeDraw', [me, easingDecimal]);
|
2016-04-17 18:02:42 +02:00
|
|
|
|
2016-02-14 23:06:00 +01:00
|
|
|
// Draw all the scales
|
2016-06-04 20:14:16 +02:00
|
|
|
helpers.each(me.boxes, function(box) {
|
|
|
|
box.draw(me.chartArea);
|
|
|
|
}, me);
|
|
|
|
if (me.scale) {
|
|
|
|
me.scale.draw();
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
|
|
|
|
2016-06-11 00:14:27 +02:00
|
|
|
Chart.plugins.notify('beforeDatasetsDraw', [me, easingDecimal]);
|
2016-04-03 03:41:57 +02:00
|
|
|
|
2016-02-14 23:06:00 +01:00
|
|
|
// Draw each dataset via its respective controller (reversed to support proper line stacking)
|
2016-06-04 20:14:16 +02:00
|
|
|
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
|
|
|
|
if (me.isDatasetVisible(datasetIndex)) {
|
|
|
|
me.getDatasetMeta(datasetIndex).controller.draw(ease);
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
2016-06-04 20:14:16 +02:00
|
|
|
}, me, true);
|
2016-02-14 23:06:00 +01:00
|
|
|
|
2016-06-11 00:14:27 +02:00
|
|
|
Chart.plugins.notify('afterDatasetsDraw', [me, easingDecimal]);
|
2016-04-03 03:41:57 +02:00
|
|
|
|
2016-02-14 23:06:00 +01:00
|
|
|
// Finally draw the tooltip
|
2016-06-04 20:14:16 +02:00
|
|
|
me.tooltip.transition(easingDecimal).draw();
|
2016-04-17 18:02:42 +02:00
|
|
|
|
2016-06-10 22:26:35 +02:00
|
|
|
Chart.plugins.notify('afterDraw', [me, 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) {
|
2016-06-04 20:14:16 +02:00
|
|
|
var me = this;
|
|
|
|
var eventPosition = helpers.getRelativePosition(e, me.chart);
|
2016-02-14 23:06:00 +01:00
|
|
|
var elementsArray = [];
|
|
|
|
|
2016-06-04 20:14:16 +02:00
|
|
|
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
|
|
|
|
if (me.isDatasetVisible(datasetIndex)) {
|
|
|
|
var meta = me.getDatasetMeta(datasetIndex);
|
2016-06-18 11:00:11 +02:00
|
|
|
helpers.each(meta.data, function(element) {
|
2016-02-14 23:06:00 +01:00
|
|
|
if (element.inRange(eventPosition.x, eventPosition.y)) {
|
|
|
|
elementsArray.push(element);
|
|
|
|
return elementsArray;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2016-06-04 20:14:16 +02:00
|
|
|
});
|
2016-02-14 23:06:00 +01:00
|
|
|
|
|
|
|
return elementsArray;
|
|
|
|
},
|
|
|
|
|
|
|
|
getElementsAtEvent: function(e) {
|
2016-06-04 20:14:16 +02:00
|
|
|
var me = this;
|
|
|
|
var eventPosition = helpers.getRelativePosition(e, me.chart);
|
2016-02-14 23:06:00 +01:00
|
|
|
var elementsArray = [];
|
|
|
|
|
|
|
|
var found = (function() {
|
2016-06-04 20:14:16 +02:00
|
|
|
if (me.data.datasets) {
|
|
|
|
for (var i = 0; i < me.data.datasets.length; i++) {
|
|
|
|
var meta = me.getDatasetMeta(i);
|
|
|
|
if (me.isDatasetVisible(i)) {
|
2016-04-21 17:11:52 +02:00
|
|
|
for (var j = 0; j < meta.data.length; j++) {
|
|
|
|
if (meta.data[j].inRange(eventPosition.x, eventPosition.y)) {
|
|
|
|
return meta.data[j];
|
2016-03-30 02:10:29 +02:00
|
|
|
}
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
|
|
|
}
|
2016-04-21 23:43:47 +02:00
|
|
|
}
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
2016-06-04 20:14:16 +02:00
|
|
|
}).call(me);
|
2016-02-14 23:06:00 +01:00
|
|
|
|
|
|
|
if (!found) {
|
|
|
|
return elementsArray;
|
|
|
|
}
|
|
|
|
|
2016-06-04 20:14:16 +02:00
|
|
|
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
|
|
|
|
if (me.isDatasetVisible(datasetIndex)) {
|
2016-07-18 23:05:44 +02:00
|
|
|
var meta = me.getDatasetMeta(datasetIndex),
|
|
|
|
element = meta.data[found._index];
|
|
|
|
if(element && !element._view.skip){
|
|
|
|
elementsArray.push(element);
|
2016-06-30 18:34:53 +02:00
|
|
|
}
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
2016-06-04 20:14:16 +02:00
|
|
|
}, me);
|
2016-02-14 23:06:00 +01:00
|
|
|
|
|
|
|
return elementsArray;
|
|
|
|
},
|
|
|
|
|
2016-07-27 21:09:13 +02:00
|
|
|
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;
|
|
|
|
},
|
2016-06-28 21:15:12 +02:00
|
|
|
|
2016-05-22 00:21:59 +02:00
|
|
|
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);
|
2016-06-28 21:15:12 +02:00
|
|
|
case 'x-axis':
|
|
|
|
return me.getElementsAtXAxis(e);
|
2016-05-22 00:21:59 +02:00
|
|
|
default:
|
|
|
|
return e;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2016-02-14 23:06:00 +01:00
|
|
|
getDatasetAtEvent: function(e) {
|
|
|
|
var elementsArray = this.getElementAtEvent(e);
|
|
|
|
|
|
|
|
if (elementsArray.length > 0) {
|
2016-04-21 17:11:52 +02:00
|
|
|
elementsArray = this.getDatasetMeta(elementsArray[0]._datasetIndex).data;
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return elementsArray;
|
|
|
|
},
|
|
|
|
|
2016-04-21 17:11:52 +02:00
|
|
|
getDatasetMeta: function(datasetIndex) {
|
2016-06-04 20:14:16 +02:00
|
|
|
var me = this;
|
|
|
|
var dataset = me.data.datasets[datasetIndex];
|
2016-04-21 17:11:52 +02:00
|
|
|
if (!dataset._meta) {
|
|
|
|
dataset._meta = {};
|
|
|
|
}
|
|
|
|
|
2016-06-04 20:14:16 +02:00
|
|
|
var meta = dataset._meta[me.id];
|
2016-04-21 17:11:52 +02:00
|
|
|
if (!meta) {
|
2016-06-04 20:14:16 +02:00
|
|
|
meta = dataset._meta[me.id] = {
|
2016-04-21 23:43:47 +02:00
|
|
|
type: null,
|
|
|
|
data: [],
|
|
|
|
dataset: null,
|
|
|
|
controller: null,
|
2016-04-21 19:12:12 +02:00
|
|
|
hidden: null, // See isDatasetVisible() comment
|
2016-04-21 23:43:47 +02:00
|
|
|
xAxisID: null,
|
|
|
|
yAxisID: null
|
|
|
|
};
|
2016-04-21 17:11:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return meta;
|
|
|
|
},
|
|
|
|
|
2016-04-21 19:12:12 +02:00
|
|
|
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;
|
|
|
|
},
|
|
|
|
|
2016-06-18 11:15:25 +02:00
|
|
|
generateLegend: function() {
|
2016-02-14 23:06:00 +01:00
|
|
|
return this.options.legendCallback(this);
|
|
|
|
},
|
|
|
|
|
2016-06-18 11:15:25 +02:00
|
|
|
destroy: function() {
|
2016-06-04 20:14:16 +02:00
|
|
|
var me = this;
|
|
|
|
me.stop();
|
|
|
|
me.clear();
|
|
|
|
helpers.unbindEvents(me, me.events);
|
|
|
|
helpers.removeResizeListener(me.chart.canvas.parentNode);
|
2016-02-14 23:06:00 +01:00
|
|
|
|
|
|
|
// Reset canvas height/width attributes
|
2016-06-04 20:14:16 +02:00
|
|
|
var canvas = me.chart.canvas;
|
|
|
|
canvas.width = me.chart.width;
|
|
|
|
canvas.height = me.chart.height;
|
2016-02-14 23:06:00 +01:00
|
|
|
|
|
|
|
// if we scaled the canvas in response to a devicePixelRatio !== 1, we need to undo that transform here
|
2016-06-04 20:14:16 +02:00
|
|
|
if (me.chart.originalDevicePixelRatio !== undefined) {
|
|
|
|
me.chart.ctx.scale(1 / me.chart.originalDevicePixelRatio, 1 / me.chart.originalDevicePixelRatio);
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Reset to the old style since it may have been changed by the device pixel ratio changes
|
2016-06-04 20:14:16 +02:00
|
|
|
canvas.style.width = me.chart.originalCanvasStyleWidth;
|
|
|
|
canvas.style.height = me.chart.originalCanvasStyleHeight;
|
2016-02-14 23:06:00 +01:00
|
|
|
|
2016-06-10 22:26:35 +02:00
|
|
|
Chart.plugins.notify('destroy', [me]);
|
2016-04-18 00:55:20 +02:00
|
|
|
|
2016-06-04 20:14:16 +02:00
|
|
|
delete Chart.instances[me.id];
|
2016-02-14 23:06:00 +01:00
|
|
|
},
|
|
|
|
|
2016-06-18 11:15:25 +02:00
|
|
|
toBase64Image: function() {
|
2016-02-14 23:06:00 +01:00
|
|
|
return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
|
|
|
|
},
|
|
|
|
|
2016-06-18 11:15:25 +02:00
|
|
|
initToolTip: function() {
|
2016-06-04 20:14:16 +02:00
|
|
|
var me = this;
|
|
|
|
me.tooltip = new Chart.Tooltip({
|
|
|
|
_chart: me.chart,
|
|
|
|
_chartInstance: me,
|
|
|
|
_data: me.data,
|
|
|
|
_options: me.options.tooltips
|
|
|
|
}, me);
|
2016-02-14 23:06:00 +01:00
|
|
|
},
|
|
|
|
|
2016-06-18 11:15:25 +02:00
|
|
|
bindEvents: function() {
|
2016-06-04 20:14:16 +02:00
|
|
|
var me = this;
|
|
|
|
helpers.bindEvents(me, me.options.events, function(evt) {
|
|
|
|
me.eventHandler(evt);
|
2016-02-14 23:06:00 +01:00
|
|
|
});
|
|
|
|
},
|
2016-05-22 00:21:59 +02:00
|
|
|
|
|
|
|
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':
|
2016-06-28 21:15:12 +02:00
|
|
|
case 'x-axis':
|
2016-05-22 00:21:59 +02:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2016-02-14 23:06:00 +01:00
|
|
|
eventHandler: function eventHandler(e) {
|
2016-05-22 00:21:59 +02:00
|
|
|
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 || [];
|
2016-02-14 23:06:00 +01:00
|
|
|
|
|
|
|
// Find Active Elements for hover and tooltips
|
|
|
|
if (e.type === 'mouseout') {
|
2016-05-22 00:21:59 +02:00
|
|
|
me.active = [];
|
|
|
|
me.tooltipActive = [];
|
2016-02-14 23:06:00 +01:00
|
|
|
} else {
|
2016-05-22 00:21:59 +02:00
|
|
|
me.active = me.getElementsAtEventForMode(e, hoverOptions.mode);
|
|
|
|
me.tooltipActive = me.getElementsAtEventForMode(e, tooltipsOptions.mode);
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// On Hover hook
|
2016-05-22 00:21:59 +02:00
|
|
|
if (hoverOptions.onHover) {
|
|
|
|
hoverOptions.onHover.call(me, me.active);
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (e.type === 'mouseup' || e.type === 'click') {
|
2016-05-22 00:21:59 +02:00
|
|
|
if (options.onClick) {
|
|
|
|
options.onClick.call(me, e, me.active);
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
2016-05-22 00:21:59 +02:00
|
|
|
if (me.legend && me.legend.handleEvent) {
|
|
|
|
me.legend.handleEvent(e);
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove styling for last active (even if it may still be active)
|
2016-05-22 00:21:59 +02:00
|
|
|
if (me.lastActive.length) {
|
|
|
|
me.updateHoverStyle(me.lastActive, hoverOptions.mode, false);
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Built in hover styling
|
2016-05-22 00:21:59 +02:00
|
|
|
if (me.active.length && hoverOptions.mode) {
|
|
|
|
me.updateHoverStyle(me.active, hoverOptions.mode, true);
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Built in Tooltips
|
2016-05-22 00:21:59 +02:00
|
|
|
if (tooltipsOptions.enabled || tooltipsOptions.custom) {
|
2016-05-14 05:07:39 +02:00
|
|
|
tooltip.initialize();
|
2016-05-22 00:21:59 +02:00
|
|
|
tooltip._active = me.tooltipActive;
|
2016-05-14 05:07:39 +02:00
|
|
|
tooltip.update(true);
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Hover animations
|
2016-05-14 05:07:39 +02:00
|
|
|
tooltip.pivot();
|
2016-02-14 23:06:00 +01:00
|
|
|
|
2016-05-22 00:21:59 +02:00
|
|
|
if (!me.animating) {
|
2016-02-14 23:06:00 +01:00
|
|
|
// If entering, leaving, or changing elements, animate the change via pivot
|
2016-05-22 00:21:59 +02:00
|
|
|
if (!helpers.arrayEquals(me.active, me.lastActive) ||
|
|
|
|
!helpers.arrayEquals(me.tooltipActive, me.lastTooltipActive)) {
|
2016-02-14 23:06:00 +01:00
|
|
|
|
2016-05-22 00:21:59 +02:00
|
|
|
me.stop();
|
2016-02-14 23:06:00 +01:00
|
|
|
|
2016-05-22 00:21:59 +02:00
|
|
|
if (tooltipsOptions.enabled || tooltipsOptions.custom) {
|
2016-05-14 05:07:39 +02:00
|
|
|
tooltip.update(true);
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
|
|
|
|
2016-05-22 00:21:59 +02:00
|
|
|
// 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);
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remember Last Actives
|
2016-05-22 00:21:59 +02:00
|
|
|
me.lastActive = me.active;
|
|
|
|
me.lastTooltipActive = me.tooltipActive;
|
|
|
|
return me;
|
2016-02-14 23:06:00 +01:00
|
|
|
}
|
|
|
|
});
|
2016-02-17 20:41:32 +01:00
|
|
|
};
|