3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * @class Ext.chart.axis.Time
17 * @extends Ext.chart.axis.Axis
19 * A type of axis whose units are measured in time values. Use this axis
20 * for listing dates that you will want to group or dynamically change.
21 * If you just want to display dates as categories then use the
22 * Category class for axis instead.
32 * groupBy: 'year,month,day',
36 * fromDate: new Date('1/1/11'),
37 * toDate: new Date('1/7/11')
40 * In this example we're creating a time axis that has as title *Day*.
41 * The field the axis is bound to is `date`.
42 * The date format to use to display the text for the axis labels is `M d`
43 * which is a three letter month abbreviation followed by the day number.
44 * The time axis will show values for dates between `fromDate` and `toDate`.
45 * Since `constrain` is set to true all other values for other dates not between
46 * the fromDate and toDate will not be displayed.
49 Ext.define('Ext.chart.axis.Time', {
51 /* Begin Definitions */
53 extend: 'Ext.chart.axis.Category',
55 alternateClassName: 'Ext.chart.TimeAxis',
59 requires: ['Ext.data.Store', 'Ext.data.JsonStore'],
64 * The minimum value drawn by the axis. If not set explicitly, the axis
65 * minimum will be calculated automatically.
66 * @property calculateByLabelSize
69 calculateByLabelSize: true,
72 * Indicates the format the date will be rendered on.
73 * For example: 'M d' will render the dates as 'Jan 30', etc.
75 * @property dateFormat
76 * @type {String|Boolean}
81 * Indicates the time unit to use for each step. Can be 'day', 'month', 'year' or a comma-separated combination of all of them.
82 * Default's 'year,month,day'.
87 groupBy: 'year,month,day',
90 * Aggregation operation when grouping. Possible options are 'sum', 'avg', 'max', 'min'. Default's 'sum'.
92 * @property aggregateOp
98 * The starting date for the time axis.
105 * The ending date for the time axis.
112 * 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.).
113 * Default's [Ext.Date.DAY, 1].
118 step: [Ext.Date.DAY, 1],
121 * If true, the values of the chart will be rendered only if they belong between the fromDate and toDate.
122 * If false, the time axis will adapt to the new values by adding/removing steps.
123 * Default's [Ext.Date.DAY, 1].
125 * @property constrain
130 // @private a wrapper for date methods.
132 'year': function(date) {
133 return date.getFullYear();
135 'month': function(date) {
136 return date.getMonth() + 1;
138 'day': function(date) {
139 return date.getDate();
141 'hour': function(date) {
142 return date.getHours();
144 'minute': function(date) {
145 return date.getMinutes();
147 'second': function(date) {
148 return date.getSeconds();
150 'millisecond': function(date) {
151 return date.getMilliseconds();
155 // @private holds aggregate functions.
156 aggregateFn: (function() {
157 var etype = (function() {
158 var rgxp = /^\[object\s(.*)\]$/,
159 toString = Object.prototype.toString;
161 return toString.call(e).match(rgxp)[1];
165 'sum': function(list) {
166 var i = 0, l = list.length, acum = 0;
167 if (!list.length || etype(list[0]) != 'Number') {
175 'max': function(list) {
176 if (!list.length || etype(list[0]) != 'Number') {
179 return Math.max.apply(Math, list);
181 'min': function(list) {
182 if (!list.length || etype(list[0]) != 'Number') {
185 return Math.min.apply(Math, list);
187 'avg': function(list) {
188 var i = 0, l = list.length, acum = 0;
189 if (!list.length || etype(list[0]) != 'Number') {
200 // @private normalized the store to fill date gaps in the time interval.
201 constrainDates: function() {
202 var fromDate = Ext.Date.clone(this.fromDate),
203 toDate = Ext.Date.clone(this.toDate),
206 store = this.chart.store,
207 record, recObj, fieldNames = [],
208 newStore = Ext.create('Ext.data.Store', {
212 var getRecordByDate = (function() {
213 var index = 0, l = store.getCount();
214 return function(date) {
216 for (; index < l; index++) {
217 rec = store.getAt(index);
218 recDate = rec.get(field);
219 if (+recDate > +date) {
221 } else if (+recDate == +date) {
229 if (!this.constrain) {
230 this.chart.filteredStore = this.chart.store;
234 while(+fromDate <= +toDate) {
235 record = getRecordByDate(fromDate);
238 newStore.add(record.data);
240 newStore.model.prototype.fields.each(function(f) {
241 recObj[f.name] = false;
243 recObj.date = fromDate;
244 newStore.add(recObj);
246 fromDate = Ext.Date.add(fromDate, step[0], step[1]);
249 this.chart.filteredStore = newStore;
252 // @private aggregates values if multiple store elements belong to the same time step.
253 aggregate: function() {
255 aggKeys = [], key, value,
256 op = this.aggregateOp,
257 field = this.fields, i,
258 fields = this.groupBy.split(','),
266 dateMethods = this.dateMethods,
267 aggregateFn = this.aggregateFn,
268 store = this.chart.filteredStore || this.chart.store;
270 store.each(function(rec) {
271 //get all record field names in a simple array
272 if (!recFields.length) {
273 rec.fields.each(function(f) {
274 recFields.push(f.name);
276 recFieldsLen = recFields.length;
278 //get record date value
279 value = rec.get(field);
280 //generate key for grouping records
281 for (i = 0; i < l; i++) {
283 key = String(dateMethods[fields[i]](value));
285 key += '||' + dateMethods[fields[i]](value);
288 //get aggregation record from hash
289 if (key in aggStore) {
292 obj = aggStore[key] = {};
296 //append record values to an aggregation record
297 for (i = 0; i < recFieldsLen; i++) {
298 curField = recFields[i];
299 if (!obj[curField]) {
302 if (rec.get(curField) !== undefined) {
303 obj[curField].push(rec.get(curField));
307 //perform aggregation operations on fields
308 for (key in aggStore) {
310 for (i = 0; i < recFieldsLen; i++) {
311 curField = recFields[i];
312 obj[curField] = aggregateFn[op](obj[curField]);
316 this.chart.substore = Ext.create('Ext.data.JsonStore', {
324 // @private creates a label array to be used as the axis labels.
325 setLabels: function() {
326 var store = this.chart.substore,
327 fields = this.fields,
328 format = this.dateFormat,
329 labels, i, dates = this.dates,
330 formatFn = Ext.Date.format;
331 this.labels = labels = [];
332 store.each(function(record, i) {
334 labels.push(record.get(fields));
336 labels.push(formatFn(dates[i], format));
341 processView: function() {
342 //TODO(nico): fix this eventually...
343 if (this.constrain) {
344 this.constrainDates();
346 this.chart.substore = this.chart.filteredStore;
352 // @private modifies the store and creates the labels for the axes.
353 applyData: function() {
355 var count = this.chart.substore.getCount();