UNPKG

zettapi_client

Version:

Client side CRUD operations in angular to use with zettapi_server rest api to get started quickly in any CMS project

691 lines (562 loc) 19.1 kB
'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(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(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(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(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; } }); };