PHP Classes

File: vendors/chart.js/src/core/core.controller.js

Recommend this page to a friend!
  Classes of Jorge Castro   Gentelella BladeOne   vendors/chart.js/src/core/core.controller.js   Download  
File: vendors/chart.js/src/core/core.controller.js
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: Gentelella BladeOne
Render templates using Bootstrap for presentation
Author: By
Last change:
Date: 2 years ago
Size: 17,528 bytes
 

Contents

Class file image Download
"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 = {}; // 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() { // Before init plugin notification Chart.pluginService.notifyPlugins('beforeInit', [this]); 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(); // After init plugin notification Chart.pluginService.notifyPlugins('afterInit', [this]); 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 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 buildScales() { 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, index) { 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); }, 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) { var meta = this.getDatasetMeta(datasetIndex); if (!meta.type) { meta.type = dataset.type || this.config.type; } types.push(meta.type); if (meta.controller) { meta.controller.updateIndex(datasetIndex); } else { meta.controller = new Chart.controllers[meta.type](this, datasetIndex); newControllers.push(meta.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) { this.getDatasetMeta(datasetIndex).controller.reset(); }, this); }, update: function update(animationDuration, lazy) { Chart.pluginService.notifyPlugins('beforeUpdate', [this]); // 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(); // Make sure all dataset controllers have correct meta data counts helpers.each(this.data.datasets, function(dataset, datasetIndex) { this.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements(); }, this); Chart.layoutService.update(this, this.chart.width, this.chart.height); // Apply changes to the dataets that require the scales to have been calculated i.e BorderColor chages Chart.pluginService.notifyPlugins('afterScaleUpdate', [this]); // Can only reset the new controllers after the scales have been updated helpers.each(newControllers, function(controller) { controller.reset(); }); // This will loop through any data and do the appropriate element update for the type helpers.each(this.data.datasets, function(dataset, datasetIndex) { this.getDatasetMeta(datasetIndex).controller.update(); }, this); // Do this before render so that any plugins that need final scale updates can use it Chart.pluginService.notifyPlugins('afterUpdate', [this]); this.render(animationDuration, lazy); }, render: function render(duration, lazy) { Chart.pluginService.notifyPlugins('beforeRender', [this]); var animationOptions = this.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(this, animation, duration, lazy); } else { this.draw(); if (animationOptions && animationOptions.onComplete && animationOptions.onComplete.call) { animationOptions.onComplete.call(this); } } return this; }, draw: function(ease) { var easingDecimal = ease || 1; this.clear(); Chart.pluginService.notifyPlugins('beforeDraw', [this, easingDecimal]); // Draw all the scales helpers.each(this.boxes, function(box) { box.draw(this.chartArea); }, this); if (this.scale) { this.scale.draw(); } // Clip out the chart area so that anything outside does not draw. This is necessary for zoom and pan to function var context = this.chart.ctx; context.save(); context.beginPath(); context.rect(this.chartArea.left, this.chartArea.top, this.chartArea.right - this.chartArea.left, this.chartArea.bottom - this.chartArea.top); context.clip(); // Draw each dataset via its respective controller (reversed to support proper line stacking) helpers.each(this.data.datasets, function(dataset, datasetIndex) { if (this.isDatasetVisible(datasetIndex)) { this.getDatasetMeta(datasetIndex).controller.draw(ease); } }, this, true); // Restore from the clipping operation context.restore(); // Finally draw the tooltip this.tooltip.transition(easingDecimal).draw(); Chart.pluginService.notifyPlugins('afterDraw', [this, 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 eventPosition = helpers.getRelativePosition(e, this.chart); var elementsArray = []; helpers.each(this.data.datasets, function(dataset, datasetIndex) { if (this.isDatasetVisible(datasetIndex)) { var meta = this.getDatasetMeta(datasetIndex); helpers.each(meta.data, function(element, index) { if (element.inRange(eventPosition.x, eventPosition.y)) { elementsArray.push(element); return elementsArray; } }); } }, this); return elementsArray; }, getElementsAtEvent: function(e) { var eventPosition = helpers.getRelativePosition(e, this.chart); var elementsArray = []; var found = (function() { if (this.data.datasets) { for (var i = 0; i < this.data.datasets.length; i++) { var meta = this.getDatasetMeta(i); if (this.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(this); if (!found) { return elementsArray; } helpers.each(this.data.datasets, function(dataset, datasetIndex) { if (this.isDatasetVisible(datasetIndex)) { var meta = this.getDatasetMeta(datasetIndex); elementsArray.push(meta.data[found._index]); } }, this); 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); 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 dataset = this.data.datasets[datasetIndex]; if (!dataset._meta) { dataset._meta = {}; } var meta = dataset._meta[this.id]; if (!meta) { meta = dataset._meta[this.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 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; Chart.pluginService.notifyPlugins('destroy', [this]); 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); }); }, 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': // 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 (e.type === 'mouseup' || e.type === 'click') { if (options.onClick) { options.onClick.call(me, e, me.active); } if (me.legend && me.legend.handleEvent) { me.legend.handleEvent(e); } } // 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; } }); };