Upgrade to ExtJS 3.3.0 - Released 10/06/2010
[extjs.git] / docs / source / PivotGrid.html
1 <html>
2 <head>
3   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />    
4   <title>The source code</title>
5     <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
6     <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
7 </head>
8 <body  onload="prettyPrint();">
9     <pre class="prettyprint lang-js">/*!
10  * Ext JS Library 3.3.0
11  * Copyright(c) 2006-2010 Ext JS, Inc.
12  * licensing@extjs.com
13  * http://www.extjs.com/license
14  */
15 <div id="cls-Ext.grid.PivotGrid"></div>/**
16  * @class Ext.grid.PivotGrid
17  * @extends Ext.grid.GridPanel
18  * <p>The PivotGrid component enables rapid summarization of large data sets. It provides a way to reduce a large set of
19  * data down into a format where trends and insights become more apparent. A classic example is in sales data; a company
20  * will often have a record of all sales it makes for a given period - this will often encompass thousands of rows of
21  * data. The PivotGrid allows you to see how well each salesperson performed, which cities generate the most revenue, 
22  * how products perform between cities and so on.</p>
23  * <p>A PivotGrid is composed of two axes (left and top), one {@link #measure} and one {@link #aggregator aggregation}
24  * function. Each axis can contain one or more {@link #dimension}, which are ordered into a hierarchy. Dimensions on the 
25  * left axis can also specify a width. Each dimension in each axis can specify its sort ordering, defaulting to "ASC", 
26  * and must specify one of the fields in the {@link Ext.data.Record Record} used by the PivotGrid's 
27  * {@link Ext.data.Store Store}.</p>
28 <pre><code>
29 // This is the record representing a single sale
30 var SaleRecord = Ext.data.Record.create([
31     {name: 'person',   type: 'string'},
32     {name: 'product',  type: 'string'},
33     {name: 'city',     type: 'string'},
34     {name: 'state',    type: 'string'},
35     {name: 'year',     type: 'int'},
36     {name: 'value',    type: 'int'}
37 ]);
38
39 // A simple store that loads SaleRecord data from a url
40 var myStore = new Ext.data.Store({
41     url: 'data.json',
42     autoLoad: true,
43     reader: new Ext.data.JsonReader({
44         root: 'rows',
45         idProperty: 'id'
46     }, SaleRecord)
47 });
48
49 // Create the PivotGrid itself, referencing the store
50 var pivot = new Ext.grid.PivotGrid({
51     store     : myStore,
52     aggregator: 'sum',
53     measure   : 'value',
54
55     leftAxis: [
56         {
57             width: 60,
58             dataIndex: 'product'
59         },
60         {
61             width: 120,
62             dataIndex: 'person',
63             direction: 'DESC'
64         }
65     ],
66
67     topAxis: [
68         {
69             dataIndex: 'year'
70         }
71     ]
72 });
73 </code></pre>
74  * <p>The specified {@link #measure} is the field from SaleRecord that is extracted from each combination
75  * of product and person (on the left axis) and year on the top axis. There may be several SaleRecords in the 
76  * data set that share this combination, so an array of measure fields is produced. This array is then 
77  * aggregated using the {@link #aggregator} function.</p>
78  * <p>The default aggregator function is sum, which simply adds up all of the extracted measure values. Other
79  * built-in aggregator functions are count, avg, min and max. In addition, you can specify your own function.
80  * In this example we show the code used to sum the measures, but you can return any value you like. See
81  * {@link #aggregator} for more details.</p>
82 <pre><code>
83 new Ext.grid.PivotGrid({
84     aggregator: function(records, measure) {
85         var length = records.length,
86             total  = 0,
87             i;
88
89         for (i = 0; i < length; i++) {
90             total += records[i].get(measure);
91         }
92
93         return total;
94     },
95     
96     renderer: function(value) {
97         return Math.round(value);
98     },
99     
100     //your normal config here
101 });
102 </code></pre>
103  * <p><u>Renderers</u></p>
104  * <p>PivotGrid optionally accepts a {@link #renderer} function which can modify the data in each cell before it
105  * is rendered. The renderer is passed the value that would usually be placed in the cell and is expected to return
106  * the new value. For example let's imagine we had height data expressed as a decimal - here's how we might use a
107  * renderer to display the data in feet and inches notation:</p>
108 <pre><code>
109 new Ext.grid.PivotGrid({
110     //in each case the value is a decimal number of feet
111     renderer  : function(value) {
112         var feet   = Math.floor(value),
113             inches = Math.round((value - feet) * 12);
114
115         return String.format("{0}' {1}\"", feet, inches);
116     },
117     //normal config here
118 });
119 </code></pre>
120  * <p><u>Reconfiguring</u></p>
121  * <p>All aspects PivotGrid's configuration can be updated at runtime. It is easy to change the {@link #setMeasure measure}, 
122  * {@link #setAggregator aggregation function}, {@link #setLeftAxis left} and {@link #setTopAxis top} axes and refresh the grid.</p>
123  * <p>In this case we reconfigure the PivotGrid to have city and year as the top axis dimensions, rendering the average sale
124  * value into the cells:</p>
125 <pre><code>
126 //the left axis can also be changed
127 pivot.topAxis.setDimensions([
128     {dataIndex: 'city', direction: 'DESC'},
129     {dataIndex: 'year', direction: 'ASC'}
130 ]);
131
132 pivot.setMeasure('value');
133 pivot.setAggregator('avg');
134
135 pivot.view.refresh(true);
136 </code></pre>
137  * <p>See the {@link Ext.grid.PivotAxis PivotAxis} documentation for further detail on reconfiguring axes.</p>
138  */
139 Ext.grid.PivotGrid = Ext.extend(Ext.grid.GridPanel, {
140     
141     <div id="cfg-Ext.grid.PivotGrid-aggregator"></div>/**
142      * @cfg {String|Function} aggregator The aggregation function to use to combine the measures extracted
143      * for each dimension combination. Can be any of the built-in aggregators (sum, count, avg, min, max).
144      * Can also be a function which accepts two arguments (an array of Records to aggregate, and the measure 
145      * to aggregate them on) and should return a String.
146      */
147     aggregator: 'sum',
148     
149     <div id="cfg-Ext.grid.PivotGrid-renderer"></div>/**
150      * @cfg {Function} renderer Optional renderer to pass values through before they are rendered to the dom. This
151      * gives an opportunity to modify cell contents after the value has been computed.
152      */
153     renderer: undefined,
154     
155     <div id="cfg-Ext.grid.PivotGrid-measure"></div>/**
156      * @cfg {String} measure The field to extract from each Record when pivoting around the two axes. See the class
157      * introduction docs for usage
158      */
159     
160     <div id="cfg-Ext.grid.PivotGrid-leftAxis"></div>/**
161      * @cfg {Array|Ext.grid.PivotAxis} leftAxis Either and array of {@link #dimension} to use on the left axis, or
162      * a {@link Ext.grid.PivotAxis} instance. If an array is passed, it is turned into a PivotAxis internally.
163      */
164     
165     <div id="cfg-Ext.grid.PivotGrid-topAxis"></div>/**
166      * @cfg {Array|Ext.grid.PivotAxis} topAxis Either and array of {@link #dimension} to use on the top axis, or
167      * a {@link Ext.grid.PivotAxis} instance. If an array is passed, it is turned into a PivotAxis internally.
168      */
169     
170     //inherit docs
171     initComponent: function() {
172         Ext.grid.PivotGrid.superclass.initComponent.apply(this, arguments);
173         
174         this.initAxes();
175         
176         //no resizing of columns is allowed yet in PivotGrid
177         this.enableColumnResize = false;
178         
179         this.viewConfig = Ext.apply(this.viewConfig || {}, {
180             forceFit: true
181         });
182         
183         //TODO: dummy col model that is never used - GridView is too tightly integrated with ColumnModel
184         //in 3.x to remove this altogether.
185         this.colModel = new Ext.grid.ColumnModel({});
186     },
187     
188     <div id="method-Ext.grid.PivotGrid-getAggregator"></div>/**
189      * Returns the function currently used to aggregate the records in each Pivot cell
190      * @return {Function} The current aggregator function
191      */
192     getAggregator: function() {
193         if (typeof this.aggregator == 'string') {
194             return Ext.grid.PivotAggregatorMgr.types[this.aggregator];
195         } else {
196             return this.aggregator;
197         }
198     },
199     
200     <div id="method-Ext.grid.PivotGrid-setAggregator"></div>/**
201      * Sets the function to use when aggregating data for each cell.
202      * @param {String|Function} aggregator The new aggregator function or named function string
203      */
204     setAggregator: function(aggregator) {
205         this.aggregator = aggregator;
206     },
207     
208     <div id="method-Ext.grid.PivotGrid-setMeasure"></div>/**
209      * Sets the field name to use as the Measure in this Pivot Grid
210      * @param {String} measure The field to make the measure
211      */
212     setMeasure: function(measure) {
213         this.measure = measure;
214     },
215     
216     <div id="method-Ext.grid.PivotGrid-setLeftAxis"></div>/**
217      * Sets the left axis of this pivot grid. Optionally refreshes the grid afterwards.
218      * @param {Ext.grid.PivotAxis} axis The pivot axis
219      * @param {Boolean} refresh True to immediately refresh the grid and its axes (defaults to false)
220      */
221     setLeftAxis: function(axis, refresh) {
222         <div id="prop-Ext.grid.PivotGrid-leftAxis"></div>/**
223          * The configured {@link Ext.grid.PivotAxis} used as the left Axis for this Pivot Grid
224          * @property leftAxis
225          * @type Ext.grid.PivotAxis
226          */
227         this.leftAxis = axis;
228         
229         if (refresh) {
230             this.view.refresh();
231         }
232     },
233     
234     <div id="method-Ext.grid.PivotGrid-setTopAxis"></div>/**
235      * Sets the top axis of this pivot grid. Optionally refreshes the grid afterwards.
236      * @param {Ext.grid.PivotAxis} axis The pivot axis
237      * @param {Boolean} refresh True to immediately refresh the grid and its axes (defaults to false)
238      */
239     setTopAxis: function(axis, refresh) {
240         <div id="prop-Ext.grid.PivotGrid-topAxis"></div>/**
241          * The configured {@link Ext.grid.PivotAxis} used as the top Axis for this Pivot Grid
242          * @property topAxis
243          * @type Ext.grid.PivotAxis
244          */
245         this.topAxis = axis;
246         
247         if (refresh) {
248             this.view.refresh();
249         }
250     },
251     
252     /**
253      * @private
254      * Creates the top and left axes. Should usually only need to be called once from initComponent
255      */
256     initAxes: function() {
257         var PivotAxis = Ext.grid.PivotAxis;
258         
259         if (!(this.leftAxis instanceof PivotAxis)) {
260             this.setLeftAxis(new PivotAxis({
261                 orientation: 'vertical',
262                 dimensions : this.leftAxis || [],
263                 store      : this.store
264             }));
265         };
266         
267         if (!(this.topAxis instanceof PivotAxis)) {
268             this.setTopAxis(new PivotAxis({
269                 orientation: 'horizontal',
270                 dimensions : this.topAxis || [],
271                 store      : this.store
272             }));
273         };
274     },
275     
276     /**
277      * @private
278      * @return {Array} 2-dimensional array of cell data
279      */
280     extractData: function() {
281         var records  = this.store.data.items,
282             recCount = records.length,
283             cells    = [],
284             record, i, j, k;
285         
286         if (recCount == 0) {
287             return [];
288         }
289         
290         var leftTuples = this.leftAxis.getTuples(),
291             leftCount  = leftTuples.length,
292             topTuples  = this.topAxis.getTuples(),
293             topCount   = topTuples.length,
294             aggregator = this.getAggregator();
295         
296         for (i = 0; i < recCount; i++) {
297             record = records[i];
298             
299             for (j = 0; j < leftCount; j++) {
300                 cells[j] = cells[j] || [];
301                 
302                 if (leftTuples[j].matcher(record) === true) {
303                     for (k = 0; k < topCount; k++) {
304                         cells[j][k] = cells[j][k] || [];
305                         
306                         if (topTuples[k].matcher(record)) {
307                             cells[j][k].push(record);
308                         }
309                     }
310                 }
311             }
312         }
313         
314         var rowCount = cells.length,
315             colCount, row;
316         
317         for (i = 0; i < rowCount; i++) {
318             row = cells[i];
319             colCount = row.length;
320             
321             for (j = 0; j < colCount; j++) {
322                 cells[i][j] = aggregator(cells[i][j], this.measure);
323             }
324         }
325         
326         return cells;
327     },
328     
329     <div id="method-Ext.grid.PivotGrid-getView"></div>/**
330      * Returns the grid's GridView object.
331      * @return {Ext.grid.PivotGridView} The grid view
332      */
333     getView: function() {
334         if (!this.view) {
335             this.view = new Ext.grid.PivotGridView(this.viewConfig);
336         }
337         
338         return this.view;
339     }
340 });
341
342 Ext.reg('pivotgrid', Ext.grid.PivotGrid);
343
344
345 Ext.grid.PivotAggregatorMgr = new Ext.AbstractManager();
346
347 Ext.grid.PivotAggregatorMgr.registerType('sum', function(records, measure) {
348     var length = records.length,
349         total  = 0,
350         i;
351     
352     for (i = 0; i < length; i++) {
353         total += records[i].get(measure);
354     }
355     
356     return total;
357 });
358
359 Ext.grid.PivotAggregatorMgr.registerType('avg', function(records, measure) {
360     var length = records.length,
361         total  = 0,
362         i;
363     
364     for (i = 0; i < length; i++) {
365         total += records[i].get(measure);
366     }
367     
368     return (total / length) || 'n/a';
369 });
370
371 Ext.grid.PivotAggregatorMgr.registerType('min', function(records, measure) {
372     var data   = [],
373         length = records.length,
374         i;
375     
376     for (i = 0; i < length; i++) {
377         data.push(records[i].get(measure));
378     }
379     
380     return Math.min.apply(this, data) || 'n/a';
381 });
382
383 Ext.grid.PivotAggregatorMgr.registerType('max', function(records, measure) {
384     var data   = [],
385         length = records.length,
386         i;
387     
388     for (i = 0; i < length; i++) {
389         data.push(records[i].get(measure));
390     }
391     
392     return Math.max.apply(this, data) || 'n/a';
393 });
394
395 Ext.grid.PivotAggregatorMgr.registerType('count', function(records, measure) {
396     return records.length;
397 });</pre>    
398 </body>
399 </html>