X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/src/chart/axis/Time.js?ds=sidebyside diff --git a/src/chart/axis/Time.js b/src/chart/axis/Time.js new file mode 100644 index 00000000..aff3c317 --- /dev/null +++ b/src/chart/axis/Time.js @@ -0,0 +1,353 @@ +/** + * @class Ext.chart.axis.Time + * @extends Ext.chart.axis.Axis + * + * A type of axis whose units are measured in time values. Use this axis + * for listing dates that you will want to group or dynamically change. + * If you just want to display dates as categories then use the + * Category class for axis instead. + * + * For example: + * +

+    axes: [{
+        type: 'Time',
+        position: 'bottom',
+        fields: 'date',
+        title: 'Day',
+        dateFormat: 'M d',
+        groupBy: 'year,month,day',
+        aggregateOp: 'sum',
+
+        constrain: true,
+        fromDate: new Date('1/1/11'),
+        toDate: new Date('1/7/11')
+    }]
+  
+ * + * In this example we're creating a time axis that has as title Day. + * The field the axis is bound to is date. + * The date format to use to display the text for the axis labels is M d + * which is a three letter month abbreviation followed by the day number. + * The time axis will show values for dates betwee fromDate and toDate. + * Since constrain is set to true all other values for other dates not between + * the fromDate and toDate will not be displayed. + * + * @constructor + */ +Ext.define('Ext.chart.axis.Time', { + + /* Begin Definitions */ + + extend: 'Ext.chart.axis.Category', + + alternateClassName: 'Ext.chart.TimeAxis', + + alias: 'axis.time', + + 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(); + }, + 'month': function(date) { + return date.getMonth() + 1; + }, + 'day': function(date) { + return date.getDate(); + }, + 'hour': function(date) { + return date.getHours(); + }, + 'minute': function(date) { + return date.getMinutes(); + }, + 'second': function(date) { + return date.getSeconds(); + }, + 'millisecond': function(date) { + return date.getMilliseconds(); + } + }, + + // @private holds aggregate functions. + aggregateFn: (function() { + var etype = (function() { + var rgxp = /^\[object\s(.*)\]$/, + toString = Object.prototype.toString; + return function(e) { + return toString.call(e).match(rgxp)[1]; + }; + })(); + return { + 'sum': function(list) { + var i = 0, l = list.length, acum = 0; + if (!list.length || etype(list[0]) != 'Number') { + return list[0]; + } + for (; i < l; i++) { + acum += list[i]; + } + return acum; + }, + 'max': function(list) { + if (!list.length || etype(list[0]) != 'Number') { + return list[0]; + } + return Math.max.apply(Math, list); + }, + 'min': function(list) { + if (!list.length || etype(list[0]) != 'Number') { + return list[0]; + } + return Math.min.apply(Math, list); + }, + 'avg': function(list) { + var i = 0, l = list.length, acum = 0; + if (!list.length || etype(list[0]) != 'Number') { + 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 = Ext.Date.clone(this.fromDate), + toDate = Ext.Date.clone(this.toDate), + step = this.step, + field = this.fields, + store = this.chart.store, + record, recObj, fieldNames = [], + newStore = Ext.create('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 = 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 = 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 = Ext.create('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); + }, + + processView: function() { + //TODO(nico): fix this eventually... + if (this.constrain) { + this.constrainDates(); + this.aggregate(); + this.chart.substore = this.chart.filteredStore; + } else { + this.aggregate(); + } + }, + + // @private modifies the store and creates the labels for the axes. + applyData: function() { + this.setLabels(); + var count = this.chart.substore.getCount(); + return { + from: 0, + to: count, + steps: count - 1, + step: 1 + }; + } + }); +