UNPKG

forerunnerdb

Version:

A NoSQL document store database for browsers and Node.js.

625 lines (527 loc) 16.3 kB
"use strict"; // Import external names locally var Shared, Collection, CollectionInit, Overload; Shared = require('./Shared'); Overload = require('./Overload'); /** * The constructor. * * @constructor */ var Highchart = function (collection, options) { this.init.apply(this, arguments); }; Highchart.prototype.init = function (collection, options) { this._options = options; this._selector = window.jQuery(this._options.selector); if (!this._selector[0]) { throw(this.classIdentifier() + ' "' + collection.name() + '": Chart target element does not exist via selector: ' + this._options.selector); } this._listeners = {}; this._collection = collection; // Setup the chart this._options.series = []; // Disable attribution on highcharts options.chartOptions = options.chartOptions || {}; options.chartOptions.credits = false; // Set the data for the chart var data, seriesObj, chartData; switch (this._options.type) { case 'pie': // Create chart from data this._selector.highcharts(this._options.chartOptions); this._chart = this._selector.highcharts(); // Generate graph data from collection data data = this._collection.find(); seriesObj = { allowPointSelect: true, cursor: 'pointer', dataLabels: { enabled: true, format: '<b>{point.name}</b>: {y} ({point.percentage:.0f}%)', style: { color: (window.Highcharts.theme && window.Highcharts.theme.contrastTextColor) || 'black' } } }; chartData = this.pieDataFromCollectionData(data, this._options.keyField, this._options.valField); window.jQuery.extend(seriesObj, this._options.seriesOptions); window.jQuery.extend(seriesObj, { name: this._options.seriesName, data: chartData }); this._chart.addSeries(seriesObj, true, true); break; case 'line': case 'area': case 'column': case 'bar': // Generate graph data from collection data chartData = this.seriesDataFromCollectionData( this._options.seriesField, this._options.keyField, this._options.valField, this._options.orderBy ); this._options.chartOptions.xAxis = chartData.xAxis; this._options.chartOptions.series = chartData.series; this._selector.highcharts(this._options.chartOptions); this._chart = this._selector.highcharts(); break; default: throw(this.classIdentifier() + ' "' + collection.name() + '": Chart type specified is not currently supported by ForerunnerDB: ' + this._options.type); } // Hook the collection events to auto-update the chart this._hookEvents(); }; Shared.addModule('Highchart', Highchart); Collection = Shared.modules.Collection; CollectionInit = Collection.prototype.init; Shared.mixin(Highchart.prototype, 'Mixin.Common'); Shared.mixin(Highchart.prototype, 'Mixin.Events'); /** * Gets / sets the current state. * @param {String=} val The name of the state to set. * @returns {*} */ Shared.synthesize(Highchart.prototype, 'state'); /** * Generate pie-chart series data from the given collection data array. * @param data * @param keyField * @param valField * @returns {Array} */ Highchart.prototype.pieDataFromCollectionData = function (data, keyField, valField) { var graphData = [], i; for (i = 0; i < data.length; i++) { graphData.push([data[i][keyField], data[i][valField]]); } return graphData; }; /** * Generate line-chart series data from the given collection data array. * @param seriesField * @param keyField * @param valField * @param orderBy */ Highchart.prototype.seriesDataFromCollectionData = function (seriesField, keyField, valField, orderBy) { var data = this._collection.distinct(seriesField), seriesData = [], xAxis = { categories: [] }, seriesName, query, dataSearch, seriesValues, i, k; // What we WANT to output: /*series: [{ name: 'Responses', data: [7.0, 6.9, 9.5, 14.5, 18.2, 21.5, 25.2, 26.5, 23.3, 18.3, 13.9, 9.6] }]*/ // Loop keys for (i = 0; i < data.length; i++) { seriesName = data[i]; query = {}; query[seriesField] = seriesName; seriesValues = []; dataSearch = this._collection.find(query, { orderBy: orderBy }); // Loop the keySearch data and grab the value for each item for (k = 0; k < dataSearch.length; k++) { xAxis.categories.push(dataSearch[k][keyField]); seriesValues.push(dataSearch[k][valField]); } seriesData.push({ name: seriesName, data: seriesValues }); } return { xAxis: xAxis, series: seriesData }; }; /** * Hook the events the chart needs to know about from the internal collection. * @private */ Highchart.prototype._hookEvents = function () { var self = this; self._collection.on('change', function () { self._changeListener.apply(self, arguments); }); // If the collection is dropped, clean up after ourselves self._collection.on('drop', function () { self.drop.apply(self, arguments); }); }; /** * Handles changes to the collection data that the chart is reading from and then * updates the data in the chart display. * @private */ Highchart.prototype._changeListener = function () { var self = this; // Update the series data on the chart if(typeof self._collection !== 'undefined' && self._chart) { var data = self._collection.find(), i; switch (self._options.type) { case 'pie': self._chart.series[0].setData( self.pieDataFromCollectionData( data, self._options.keyField, self._options.valField ), true, true ); break; case 'bar': case 'line': case 'area': case 'column': var seriesData = self.seriesDataFromCollectionData( self._options.seriesField, self._options.keyField, self._options.valField, self._options.orderBy ); self._chart.xAxis[0].setCategories( seriesData.xAxis.categories ); for (i = 0; i < seriesData.series.length; i++) { if (self._chart.series[i]) { // Series exists, set it's data self._chart.series[i].setData( seriesData.series[i].data, true, true ); } else { // Series data does not yet exist, add a new series self._chart.addSeries( seriesData.series[i], true, true ); } } break; default: break; } } }; /** * Destroys the chart and all internal references. * @returns {Boolean} */ Highchart.prototype.drop = function () { if (!this.isDropped()) { this._state = 'dropped'; if (this._chart) { this._chart.destroy(); } if (this._collection) { this._collection.off('change', this._changeListener); this._collection.off('drop', this.drop); if (this._collection._highcharts) { delete this._collection._highcharts[this._options.selector]; } } delete this._chart; delete this._options; delete this._collection; this.emit('drop', this); return true; } else { return true; } }; // Extend collection with highchart init Collection.prototype.init = function () { this._highcharts = {}; CollectionInit.apply(this, arguments); }; /** * Creates a pie chart from the collection. * @type {Overload} */ Collection.prototype.pieChart = new Overload({ /** * Chart via options object. * @func pieChart * @memberof Highchart * @param {Object} options The options object. * @returns {*} */ 'object': function (options) { options.type = 'pie'; options.chartOptions = options.chartOptions || {}; options.chartOptions.chart = options.chartOptions.chart || {}; options.chartOptions.chart.type = 'pie'; if (!this._highcharts[options.selector]) { // Store new chart in charts array this._highcharts[options.selector] = new Highchart(this, options); } return this._highcharts[options.selector]; }, /** * Chart via defined params and an options object. * @func pieChart * @memberof Highchart * @param {String|jQuery} selector The element to render the chart to. * @param {String} keyField The field to use as the data key. * @param {String} valField The field to use as the data value. * @param {String} seriesName The name of the series to display on the chart. * @param {Object} options The options object. */ '*, string, string, string, ...': function (selector, keyField, valField, seriesName, options) { options = options || {}; options.selector = selector; options.keyField = keyField; options.valField = valField; options.seriesName = seriesName; // Call the main chart method this.pieChart(options); } }); /** * Creates a line chart from the collection. * @type {Overload} */ Collection.prototype.lineChart = new Overload({ /** * Chart via options object. * @func lineChart * @memberof Highchart * @param {Object} options The options object. * @returns {*} */ 'object': function (options) { options.type = 'line'; options.chartOptions = options.chartOptions || {}; options.chartOptions.chart = options.chartOptions.chart || {}; options.chartOptions.chart.type = 'line'; if (!this._highcharts[options.selector]) { // Store new chart in charts array this._highcharts[options.selector] = new Highchart(this, options); } return this._highcharts[options.selector]; }, /** * Chart via defined params and an options object. * @func lineChart * @memberof Highchart * @param {String|jQuery} selector The element to render the chart to. * @param {String} seriesField The name of the series to plot. * @param {String} keyField The field to use as the data key. * @param {String} valField The field to use as the data value. * @param {Object} options The options object. */ '*, string, string, string, ...': function (selector, seriesField, keyField, valField, options) { options = options || {}; options.seriesField = seriesField; options.selector = selector; options.keyField = keyField; options.valField = valField; // Call the main chart method this.lineChart(options); } }); /** * Creates an area chart from the collection. * @type {Overload} */ Collection.prototype.areaChart = new Overload({ /** * Chart via options object. * @func areaChart * @memberof Highchart * @param {Object} options The options object. * @returns {*} */ 'object': function (options) { options.type = 'area'; options.chartOptions = options.chartOptions || {}; options.chartOptions.chart = options.chartOptions.chart || {}; options.chartOptions.chart.type = 'area'; if (!this._highcharts[options.selector]) { // Store new chart in charts array this._highcharts[options.selector] = new Highchart(this, options); } return this._highcharts[options.selector]; }, /** * Chart via defined params and an options object. * @func areaChart * @memberof Highchart * @param {String|jQuery} selector The element to render the chart to. * @param {String} seriesField The name of the series to plot. * @param {String} keyField The field to use as the data key. * @param {String} valField The field to use as the data value. * @param {Object} options The options object. */ '*, string, string, string, ...': function (selector, seriesField, keyField, valField, options) { options = options || {}; options.seriesField = seriesField; options.selector = selector; options.keyField = keyField; options.valField = valField; // Call the main chart method this.areaChart(options); } }); /** * Creates a column chart from the collection. * @type {Overload} */ Collection.prototype.columnChart = new Overload({ /** * Chart via options object. * @func columnChart * @memberof Highchart * @param {Object} options The options object. * @returns {*} */ 'object': function (options) { options.type = 'column'; options.chartOptions = options.chartOptions || {}; options.chartOptions.chart = options.chartOptions.chart || {}; options.chartOptions.chart.type = 'column'; if (!this._highcharts[options.selector]) { // Store new chart in charts array this._highcharts[options.selector] = new Highchart(this, options); } return this._highcharts[options.selector]; }, /** * Chart via defined params and an options object. * @func columnChart * @memberof Highchart * @param {String|jQuery} selector The element to render the chart to. * @param {String} seriesField The name of the series to plot. * @param {String} keyField The field to use as the data key. * @param {String} valField The field to use as the data value. * @param {Object} options The options object. */ '*, string, string, string, ...': function (selector, seriesField, keyField, valField, options) { options = options || {}; options.seriesField = seriesField; options.selector = selector; options.keyField = keyField; options.valField = valField; // Call the main chart method this.columnChart(options); } }); /** * Creates a bar chart from the collection. * @type {Overload} */ Collection.prototype.barChart = new Overload({ /** * Chart via options object. * @func barChart * @memberof Highchart * @param {Object} options The options object. * @returns {*} */ 'object': function (options) { options.type = 'bar'; options.chartOptions = options.chartOptions || {}; options.chartOptions.chart = options.chartOptions.chart || {}; options.chartOptions.chart.type = 'bar'; if (!this._highcharts[options.selector]) { // Store new chart in charts array this._highcharts[options.selector] = new Highchart(this, options); } return this._highcharts[options.selector]; }, /** * Chart via defined params and an options object. * @func barChart * @memberof Highchart * @param {String|jQuery} selector The element to render the chart to. * @param {String} seriesField The name of the series to plot. * @param {String} keyField The field to use as the data key. * @param {String} valField The field to use as the data value. * @param {Object} options The options object. */ '*, string, string, string, ...': function (selector, seriesField, keyField, valField, options) { options = options || {}; options.seriesField = seriesField; options.selector = selector; options.keyField = keyField; options.valField = valField; // Call the main chart method this.barChart(options); } }); /** * Creates a stacked bar chart from the collection. * @type {Overload} */ Collection.prototype.stackedBarChart = new Overload({ /** * Chart via options object. * @func stackedBarChart * @memberof Highchart * @param {Object} options The options object. * @returns {*} */ 'object': function (options) { options.type = 'bar'; options.chartOptions = options.chartOptions || {}; options.chartOptions.chart = options.chartOptions.chart || {}; options.chartOptions.chart.type = 'bar'; options.plotOptions = options.plotOptions || {}; options.plotOptions.series = options.plotOptions.series || {}; options.plotOptions.series.stacking = options.plotOptions.series.stacking || 'normal'; if (!this._highcharts[options.selector]) { // Store new chart in charts array this._highcharts[options.selector] = new Highchart(this, options); } return this._highcharts[options.selector]; }, /** * Chart via defined params and an options object. * @func stackedBarChart * @memberof Highchart * @param {String|jQuery} selector The element to render the chart to. * @param {String} seriesField The name of the series to plot. * @param {String} keyField The field to use as the data key. * @param {String} valField The field to use as the data value. * @param {Object} options The options object. */ '*, string, string, string, ...': function (selector, seriesField, keyField, valField, options) { options = options || {}; options.seriesField = seriesField; options.selector = selector; options.keyField = keyField; options.valField = valField; // Call the main chart method this.stackedBarChart(options); } }); /** * Removes a chart from the page by it's selector. * @memberof Collection * @param {String} selector The chart selector. */ Collection.prototype.dropChart = function (selector) { if (this._highcharts && this._highcharts[selector]) { this._highcharts[selector].drop(); } }; Shared.finishModule('Highchart'); module.exports = Highchart;