/** * @class Ext.chart.axis.Time * @extends Ext.chart.axis.Axis * A type of axis whose units are measured in time-based values. * @constructor */ Ext.define('Ext.chart.axis.Time', { /* Begin Definitions */ extend: 'Ext.chart.axis.Category', requires: ['Ext.data.Store', 'Ext.data.JsonStore'], /* End Definitions */ /** * The minimum value drawn by the axis. If not set explicitly, the axis * minimum will be calculated automatically. * @property calculateByLabelSize * @type Boolean */ calculateByLabelSize: true, /** * Indicates the format the date will be rendered on. * For example: 'M d' will render the dates as 'Jan 30', etc. * * @property dateFormat * @type {String|Boolean} */ dateFormat: false, /** * Indicates the time unit to use for each step. Can be 'day', 'month', 'year' or a comma-separated combination of all of them. * Default's 'year,month,day'. * * @property timeUnit * @type {String} */ groupBy: 'year,month,day', /** * Aggregation operation when grouping. Possible options are 'sum', 'avg', 'max', 'min'. Default's 'sum'. * * @property aggregateOp * @type {String} */ aggregateOp: 'sum', /** * The starting date for the time axis. * @property fromDate * @type Date */ fromDate: false, /** * The ending date for the time axis. * @property toDate * @type Date */ toDate: false, /** * An array with two components: The first is the unit of the step (day, month, year, etc). The second one is the number of units for the step (1, 2, etc.). * Default's [Ext.Date.DAY, 1]. * * @property step * @type Array */ step: [Ext.Date.DAY, 1], /** * If true, the values of the chart will be rendered only if they belong between the fromDate and toDate. * If false, the time axis will adapt to the new values by adding/removing steps. * Default's [Ext.Date.DAY, 1]. * * @property constrain * @type Boolean */ constrain: false, // @private a wrapper for date methods. dateMethods: { 'year': function(date) { return date.getFullYear(); }, 'day': function(date) { return date.getDate(); }, 'month': function(date) { return date.getMonth() + 1; } }, // @private holds aggregate functions. aggregateFn: { 'sum': function(list) { var i = 0, l = list.length, acum = 0; if (!list.length || isNaN(list[0])) { return list[0]; } for (; i < l; i++) { acum += list[i]; } return acum; }, 'max': function(list) { if (!list.length || isNaN(list[0])) { return list[0]; } return Math.max.apply(Math, list); }, 'min': function(list) { if (!list.length || isNaN(list[0])) { return list[0]; } return Math.min.apply(Math, list); }, 'avg': function(list) { var i = 0, l = list.length, acum = 0; if (!list.length || isNaN(list[0])) { return list[0]; } for (; i < l; i++) { acum += list[i]; } return acum / l; } }, // @private normalized the store to fill date gaps in the time interval. constrainDates: function() { var fromDate = new Date(this.fromDate), toDate = new Date(this.toDate), step = this.step, field = this.fields, store = this.chart.store, record, recObj, fieldNames = [], newStore = new Ext.data.Store({ model: store.model }); var getRecordByDate = (function() { var index = 0, l = store.getCount(); return function(date) { var rec, recDate; for (; index < l; index++) { rec = store.getAt(index); recDate = new Date(rec.get(field)); if (+recDate > +date) { return false; } else if (+recDate == +date) { return rec; } } return false; }; })(); if (!this.constrain) { this.chart.filteredStore = this.chart.store; return; } while(+fromDate <= +toDate) { record = getRecordByDate(fromDate); recObj = {}; if (record) { newStore.add(record.data); } else { newStore.model.prototype.fields.each(function(f) { recObj[f.name] = false; }); recObj.date = fromDate; newStore.add(recObj); } fromDate = Ext.Date.add(fromDate, step[0], step[1]); } this.chart.filteredStore = newStore; }, // @private aggregates values if multiple store elements belong to the same time step. aggregate: function() { var aggStore = {}, aggKeys = [], key, value, op = this.aggregateOp, field = this.fields, i, fields = this.groupBy.split(','), curField, recFields = [], recFieldsLen = 0, obj, dates = [], json = [], l = fields.length, dateMethods = this.dateMethods, aggregateFn = this.aggregateFn, store = this.chart.filteredStore || this.chart.store; store.each(function(rec) { //get all record field names in a simple array if (!recFields.length) { rec.fields.each(function(f) { recFields.push(f.name); }); recFieldsLen = recFields.length; } //get record date value value = new Date(rec.get(field)); //generate key for grouping records for (i = 0; i < l; i++) { if (i == 0) { key = String(dateMethods[fields[i]](value)); } else { key += '||' + dateMethods[fields[i]](value); } } //get aggregation record from hash if (key in aggStore) { obj = aggStore[key]; } else { obj = aggStore[key] = {}; aggKeys.push(key); dates.push(value); } //append record values to an aggregation record for (i = 0; i < recFieldsLen; i++) { curField = recFields[i]; if (!obj[curField]) { obj[curField] = []; } if (rec.get(curField) !== undefined) { obj[curField].push(rec.get(curField)); } } }); //perform aggregation operations on fields for (key in aggStore) { obj = aggStore[key]; for (i = 0; i < recFieldsLen; i++) { curField = recFields[i]; obj[curField] = aggregateFn[op](obj[curField]); } json.push(obj); } this.chart.substore = new Ext.data.JsonStore({ fields: recFields, data: json }); this.dates = dates; }, // @private creates a label array to be used as the axis labels. setLabels: function() { var store = this.chart.substore, fields = this.fields, format = this.dateFormat, labels, i, dates = this.dates; formatFn = Ext.Date.format; this.labels = labels = []; store.each(function(record, i) { if (!format) { labels.push(record.get(fields)); } else { labels.push(formatFn(dates[i], format)); } }, this); }, // @private modifies the store and creates the labels for the axes. applyData: function() { //TODO(nico): fix this eventually... if (this.constrain) { this.constrainDates(); this.aggregate(); this.chart.substore = this.chart.filteredStore; } else { this.aggregate(); } this.setLabels(); var count = this.chart.substore.getCount(); return { from: 0, to: count, steps: count - 1 }; } });