[REF] large refactoring in progress. goal is to separate the data handling code ...
[odoo/odoo.git] / addons / web_graph / static / src / js / graph.js
1 /*---------------------------------------------------------
2  * OpenERP web_graph
3  *---------------------------------------------------------*/
4
5 /* jshint undef: false  */
6
7
8 openerp.web_graph = function (instance) {
9 'use strict';
10
11 var _lt = instance.web._lt;
12 var _t = instance.web._t;
13 var QWeb = instance.web.qweb;
14
15 instance.web.views.add('graph', 'instance.web_graph.GraphView');
16
17  /**
18   * GraphView view.  It mostly contains a widget (PivotTable), some data, and 
19   * calls to charts function.
20   */
21 instance.web_graph.GraphView = instance.web.View.extend({
22     template: 'GraphView',
23     display_name: _lt('Graph'),
24     view_type: 'graph',
25
26     events: {
27         'click .graph_mode_selection li' : function (event) {
28             event.preventDefault();
29             this.mode = event.target.attributes['data-mode'].nodeValue;
30         },
31
32         'click .web_graph_click' : function (event) {
33             event.preventDefault();
34             if (event.target.attributes['data-row-id'] !== undefined) {
35                 this.handle_header_event({type:'row', event:event});
36
37             }
38             if (event.target.attributes['data-col-id'] !== undefined) {
39                 this.handle_header_event({type:'col', event:event});
40             }
41         },
42
43         'click a.field-selection' : function (event) {
44             var id,
45                 field_id = event.target.attributes['data-field-id'].nodeValue;
46             event.preventDefault();
47             this.dropdown.remove();
48             if (event.target.attributes['data-row-id'] !== undefined) {
49                 id = event.target.attributes['data-row-id'].nodeValue;
50                 this.pivot_table.expand_row(id, field_id)
51                     .then(this.proxy('draw_table'));
52             } 
53             if (event.target.attributes['data-col-id'] !== undefined) {
54                 id = event.target.attributes['data-col-id'].nodeValue;
55                 this.pivot_table.expand_col(id, field_id)
56                     .then(this.proxy('draw_table'));
57             } 
58         },
59     },
60
61     view_loading: function (fields_view_get) {
62         var self = this,
63             model = new instance.web.Model(fields_view_get.model, {group_by_no_leaf: true}),
64             domain = [],
65             measure = null,
66             fields,
67             important_fields = [],
68             col_groupby = [], 
69             row_groupby = [];
70
71         this.pivot_table = null;
72
73         // get the default groupbys and measure defined in the field view
74         _.each(fields_view_get.arch.children, function (field) {
75             if ('name' in field.attrs) {
76                 if ('operator' in field.attrs) {
77                     measure = field.attrs.name;
78                 } else {
79                     row_groupby.push(field.attrs.name);
80                 }
81             }
82         });
83
84         // get the most important fields (of the model) by looking at the
85         // groupby filters defined in the search view
86         var load_view = instance.web.fields_view_get({
87             model: model,
88             view_type: 'search',
89         });
90
91         var important_fields_def = $.when(load_view).then(function (search_view) {
92             var groups = _.select(search_view.arch.children, function (c) {
93                 return (c.tag == 'group') && (c.attrs.string != 'Display');
94             });
95             _.each(groups, function(g) {
96                 _.each(g.children, function (g) {
97                     if (g.attrs.context) {
98                         var field_id = py.eval(g.attrs.context).group_by;
99                         important_fields.push(field_id);
100                     }
101                 });
102             });
103         });
104
105         // get the fields descriptions from the model
106         var field_descr_def = model.call('fields_get', [])
107             .then(function (fs) { fields = fs; });
108
109         return $.when(important_fields_def, field_descr_def)
110             .then(function () {
111                 self.data = {
112                     model: model,
113                     domain: domain,
114                     fields: fields,
115                     important_fields: important_fields,
116                     measure: measure,
117                     measure_label: fields[measure].string,
118                     col_groupby: [],
119                     row_groupby: row_groupby,
120                     groups: [],
121                     total: null,
122                 };
123             });
124     },
125
126     start: function () {
127         this.table = $('<table></table>');
128         this.svg = $('<svg></svg>');
129         this.$el.filter('.graph_main_content').append(this.table);
130         this.$el.filter('.graph_main_content').append(this.svg);
131         return this.load_view();
132     },
133
134     do_search: function (domain, context, group_by) {
135         var self = this;
136         this.data.domain = new instance.web.CompoundDomain(domain);
137
138         if (!this.pivot_table) {
139             self.pivot_table = new PivotTable(self.data);
140             self.pivot_table.start().then(self.proxy('draw_table'));
141         } else {
142             this.pivot_table.update_values.done(function () {
143                 self.draw_table();
144             });
145         }
146     },
147
148     do_show: function () {
149         this.do_push_state({});
150         return this._super();
151     },
152
153
154     handle_header_event: function (options) {
155         var pivot = this.pivot_table,
156             id = options.event.target.attributes['data-' + options.type + '-id'].nodeValue,
157             header = pivot['get_' + options.type](id);
158
159         if (header.is_expanded) {
160             pivot['fold_' + options.type](header);
161             this.draw_table();
162         } else {
163             if (header.path.length < pivot[options.type + '_groupby'].length) {
164                 // expand the corresponding header
165                 var field = pivot[options.type + '_groupby'][header.path.length];
166                 pivot['expand_' + options.type](id, field)
167                     .then(this.proxy('draw_table'));
168             } else {
169                 // display dropdown to query field to expand
170                 this.display_dropdown({id:header.id, 
171                                        type: options.type,
172                                        target: $(event.target), 
173                                        x: event.pageX, 
174                                        y: event.pageY});
175             }
176         }
177
178     },
179
180     display_dropdown: function (options) {
181         var self = this,
182             pivot = this.pivot_table,
183             already_grouped = pivot.row_groupby.concat(pivot.col_groupby),
184             possible_groups = _.difference(self.data.important_fields, already_grouped),
185             dropdown_options = {
186                 fields: _.map(possible_groups, function (field) {
187                     return {id: field, value: self.data.fields[field].string};
188             })};
189         dropdown_options[options.type + '_id'] = options.id;
190
191         this.dropdown = $(QWeb.render('field_selection', dropdown_options));
192         options.target.after(this.dropdown);
193         this.dropdown.css({position:'absolute',
194                            left:options.x,
195                            top:options.y});
196         $('.field-selection').next('.dropdown-menu').toggle();
197     },
198
199     draw_table: function () {
200         this.table.empty();
201         this.draw_top_headers();
202         _.each(this.pivot_table.rows_array(), this.proxy('draw_row'));
203     },
204
205     make_border_cell: function (colspan, rowspan) {
206         return $('<td></td>').addClass('graph_border')
207                              .attr('colspan', colspan)
208                              .attr('rowspan', rowspan);
209     },
210
211     make_header_title: function (header) {
212         return $('<span> </span>')
213             .addClass('web_graph_click')
214             .attr('href', '#')
215             .addClass((header.is_expanded) ? 'icon-minus-sign' : 'icon-plus-sign')
216             .append(header.name);
217     },
218
219     draw_top_headers: function () {
220         var self = this,
221             pivot = this.pivot_table,
222             height = pivot.get_max_path_length(pivot.cols),
223             header_cells = [[this.make_border_cell(1, height)]];
224
225         function set_dim (cols) {
226             _.each(cols.children, set_dim);
227             if (cols.children.length === 0) {
228                 cols.height = height - cols.path.length + 1;
229                 cols.width = 1;
230             } else {
231                 cols.height = 1;
232                 cols.width = _.reduce(cols.children, function (sum,c) { return sum + c.width;}, 0);
233             }
234         }
235
236         function make_col_header (col) {
237             var cell = self.make_border_cell(col.width, col.height);
238             return cell.append(self.make_header_title(col).attr('data-col-id', col.id));
239         }
240
241         function make_cells (queue, level) {
242             var col = queue.shift();
243             queue = queue.concat(col.children);
244             if (col.path.length == level) {
245                 _.last(header_cells).push(make_col_header(col));
246             } else {
247                 level +=1;
248                 header_cells.push([make_col_header(col)]);
249             }
250             if (queue.length !== 0) {
251                 make_cells(queue, level);
252             }
253         }
254
255         set_dim(pivot.cols);  // add width and height info to columns headers
256
257         if (pivot.cols.children.length === 0) {
258             make_cells([pivot.cols], 0);
259         } else {
260             make_cells(pivot.cols.children, 1);
261         }
262
263         _.each(header_cells, function (cells) {
264             self.table.append($("<tr></tr>").append(cells));
265         });
266     },
267
268     draw_row: function (row) {
269         var pivot = this.pivot_table,
270             html_row = $('<tr></tr>'),
271             row_header = this.make_border_cell(1,1)
272                 .append(this.make_header_title(row).attr('data-row-id', row.id))
273                 .addClass('graph_border');
274
275         for (var i in _.range(row.path.length)) {
276             row_header.prepend($('<span/>', {class:'web_graph_indent'}));
277         }
278
279         html_row.append(row_header);
280
281         _.each(pivot.cols_array(), function (col) {
282             var cell = $('<td></td>').append(pivot.get_value(row.id, col.id));
283             html_row.append(cell)
284         });
285         this.table.append(html_row);
286     }
287 });
288
289
290
291 //     make_top_headers : function () {
292 //         var self = this,
293 //             header;
294
295 //         function partition (columns) {
296 //             return _.reduce(columns, function (partial, col) {
297 //                 if (partial.length === 0) return [[col]];
298 //                 if (col.path.length > _.first(_.last(partial)).path.length) {
299 //                     _.last(partial).push(col);
300 //                 } else {
301 //                     partial.push([col]);
302 //                 }
303 //                 return partial;
304 //             }, []);
305 //         }
306
307 //         function side_by_side(blocks) {
308 //             var result = _.zip.apply(_,blocks);
309 //             result = _.map(result, function(line) {return _.compact(_.flatten(line))});
310 //             return result;
311 //         }
312
313 //         function make_header_cell(col, span) {
314 //             var options = {
315 //                 is_border:true,
316 //                 foldable:true,
317 //                 row_span: (span === undefined) ? 1 : span,
318 //                 col_id: col.id,
319 //             };
320 //             var result = self.make_cell(col.value, options);
321 //             if (col.expanded) {
322 //                 result.find('.icon-plus-sign')
323 //                     .removeClass('icon-plus-sign')
324 //                     .addClass('icon-minus-sign');
325 //             }
326 //             return result;
327 //         }
328
329 //         function calculate_width(cols) {
330 //             if (cols.length === 1) {
331 //                 return 1;
332 //             }
333 //             var p = partition(_.rest(cols));
334 //             return _.reduce(p, function(x, y){ return x + calculate_width(y); }, 0);
335 //         }
336
337 //         function make_header_cells(cols, height) {
338 //             var p = partition(cols);
339 //             if ((p.length === 1) && (p[0].length === 1)) {
340 //                 var result = [[make_header_cell(cols[0], height)]];
341 //                 return result;
342 //             }
343 //             if ((p.length === 1) && (p[0].length > 1)) {
344 //                 var cell = make_header_cell(p[0][0]);
345 //                 cell.attr('colspan', calculate_width(cols));
346 //                 return [[cell]].concat(make_header_cells(_.rest(cols), height - 1));
347 //             }
348 //             if (p.length > 1) {
349 //                 return side_by_side(_.map(p, function (group) {
350 //                     return make_header_cells(group, height);
351 //                 }));
352 //             }
353 //         }
354
355 //         if (this.cols.length === 1) {
356 //             header = $('<tr></tr>');
357 //             header.append(this.make_cell('', {is_border:true}));
358 //             header.append(this.make_cell(this.cols[0].value, 
359 //                 {is_border:true, foldable:true, col_id:this.cols[0].id}));
360 //             header.addClass('graph_top');
361 //             this.headers = [header];
362 //         } else {
363 //             var height = _.max(_.map(self.cols, function(g) {return g.path.length;}));
364 //             var header_rows = make_header_cells(_.rest(this.cols), height);
365
366 //             header_rows[0].splice(0,0,self.make_cell('', {is_border:true, }).attr('rowspan', height))
367 //             self.headers = [];
368 //             _.each(header_rows, function (cells) {
369 //                 header = $('<tr></tr>');
370 //                 header.append(cells);
371 //                 header.addClass('graph_top');
372 //                 self.headers.push(header);
373 //             });
374 //         }
375 //     },
376
377
378     // mode: 'pivot',   // pivot, bar_chart, line_chart or pie_chart
379     // pivot_table: null,
380
381     // events: {
382     //     'click .graph_mode_selection li' : function (event) {
383     //         event.preventDefault();
384     //         this.mode = event.target.attributes['data-mode'].nodeValue;
385     //         this.display_data();
386     //     },
387     // },
388
389     // display_data : function () {
390     //     var content = this.$el.filter('.graph_main_content');
391     //     content.find('svg').remove();
392     //     var self = this;
393     //     if (this.mode === 'pivot') {
394     //         this.pivot_table.show();
395     //     } else {
396     //         this.pivot_table.hide();
397     //         content.append('<svg></svg>');
398     //         var view_fields = this.data.row_groupby.concat(this.data.measure, this.data.col_groupby);
399     //         query_groups(this.data.model, view_fields, this.data.domain, this.data.row_groupby).then(function (groups) {
400     //             Charts[self.mode](groups, self.data.measure, self.data.measure_label);
401     //         });
402
403     //     }
404     // },
405
406
407         // if (this.pivot_table) {
408         //     this.pivot_table.draw(true);
409         // } else {
410
411         //     this.pivot_table = new PivotTable(this.data);
412         //     this.pivot_table.appendTo('.graph_main_content');
413         // }
414         // this.display_data();
415
416  /**
417   * PivotTable widget.  It displays the data in tabular data and allows the
418   * user to drill down and up in the table
419   */
420 // var PivotTable = instance.web.Widget.extend({
421 //     template: 'pivot_table',
422
423 //     init: function (data) {
424 //         var self = this;
425 //         makePivotTable(this.data).done(function (table) {
426 //             self.pivottable = table;
427 //         });
428 //     },
429
430 // });
431
432 // var PivotTable = instance.web.Widget.extend({
433 //     template: 'pivot_table',
434 //     data: null,
435 //     headers: [],
436 //     rows: [],
437 //     cols: [],
438 //     id_seed : 0,
439
440 //     events: {
441 //         'click .web_graph_click' : function (event) {
442 //             event.preventDefault();
443
444 //             if (event.target.attributes['data-row-id'] !== undefined) {
445 //                 this.handle_row_event(event);
446 //             }
447 //             if (event.target.attributes['data-col-id'] !== undefined) {
448 //                 this.handle_col_event(event);
449 //             }
450 //         },
451
452 //         'click a.field-selection' : function (event) {
453 //             var id,
454 //                 field_id = event.target.attributes['data-field-id'].nodeValue;
455 //             event.preventDefault();
456 //             this.dropdown.remove();
457 //             if (event.target.attributes['data-row-id'] !== undefined) {
458 //                 id = event.target.attributes['data-row-id'].nodeValue;
459 //                 this.expand_row(id, field_id);
460 //             } 
461 //             if (event.target.attributes['data-col-id'] !== undefined) {
462 //                 id = event.target.attributes['data-col-id'].nodeValue;
463 //                 this.expand_col(id, field_id);
464 //             } 
465 //         },
466 //     },
467
468 //     handle_row_event: function (event) {
469 //         var row_id = event.target.attributes['data-row-id'].nodeValue,
470 //             row = this.get_row(row_id);
471
472 //         if (row.expanded) {
473 //             this.fold_row(row_id);
474 //         } else {
475 //             if (row.path.length < this.data.row_groupby.length) {
476 //                 var field_to_expand = this.data.row_groupby[row.path.length];
477 //                 this.expand_row(row_id, field_to_expand);
478 //             } else {
479 //                 this.display_dropdown({row_id:row_id, 
480 //                                        target: $(event.target), 
481 //                                        x: event.pageX, 
482 //                                        y: event.pageY});
483 //             }
484 //         }
485 //     },
486
487 //     handle_col_event: function (event) {
488 //         var col_id = event.target.attributes['data-col-id'].nodeValue,
489 //             col = this.get_col(col_id);
490
491 //         if (col.expanded) {
492 //             this.fold_col(col_id);
493 //         } else {
494 //             if (col.path.length < this.data.col_groupby.length) {
495 //                 var field_to_expand = this.data.col_groupby[col.path.length];
496 //                 this.expand_col(col_id, field_to_expand);
497 //             } else {
498 //                 this.display_dropdown({col_id: col_id, 
499 //                                        target: $(event.target), 
500 //                                        x: event.pageX, 
501 //                                        y: event.pageY});
502 //             }
503 //         }
504 //     },
505
506 //     init: function (data) {
507 //         this.data = data;
508 //         makePivotTable(this.data).done(function (table) {
509 //             self.pivottable = table;
510 //         });
511
512 //     },
513
514 //     start: function () {
515 //         this.draw(true);
516 //     },
517
518 //     draw: function (load_data) {
519 //         var self = this;
520
521 //         if (load_data) {
522 //             var view_fields = this.data.row_groupby.concat(this.data.measure, this.data.col_groupby);
523 //             query_groups_data(this.data.model, view_fields, this.data.domain, this.data.col_groupby, this.data.row_groupby[0])
524 //                 .then(function (groups) {
525 //                     self.data.groups = groups;
526 //                     return self.get_groups([]);
527 //                 }).then(function (total) {
528 //                     total[0].path = [];
529 //                     self.data.total = [total];
530 //                     self.build_table();
531 //                     self.draw(false);
532 //                 });
533 //         } else {
534 //             this.$el.empty();
535
536 //             this.draw_top_headers();
537
538 //             _.each(this.rows, function (row) {
539 //                 self.$el.append(row.html);
540 //             });
541 //         }
542 //     },
543
544 //     show: function () {
545 //         this.$el.css('display', 'block');
546 //     },
547
548 //     hide: function () {
549 //         this.$el.css('display', 'none');
550 //     },
551
552 //     display_dropdown: function (options) {
553 //         var self = this,
554 //             already_grouped = self.data.row_groupby.concat(self.data.col_groupby),
555 //             possible_groups = _.difference(self.data.important_fields, already_grouped),
556 //             dropdown_options = {
557 //                 fields: _.map(possible_groups, function (field) {
558 //                     return {id: field, value: self.get_descr(field)};
559 //             })};
560 //         if (options.row_id) {
561 //             dropdown_options.row_id= options.row_id;
562 //         } else {
563 //             dropdown_options.col_id = options.col_id;
564 //         }
565
566 //         this.dropdown = $(QWeb.render('field_selection', dropdown_options));
567 //         options.target.after(this.dropdown);
568 //         this.dropdown.css({position:'absolute',
569 //                            left:options.x,
570 //                            top:options.y});
571 //         $('.field-selection').next('.dropdown-menu').toggle();
572 //     },
573
574 //     build_table: function () {
575 //         var self = this;
576 //         this.rows = [];
577
578
579 //         var col_id = this.generate_id();
580
581 //         this.cols= [{
582 //             id: col_id,
583 //             path: [],
584 //             value: this.data.measure_label,
585 //             expanded: false,
586 //             parent: null,
587 //             children: [],
588 //             cells: [],    // a cell is {td:<jquery td>, row_id:<some id>}
589 //             domain: this.data.domain,
590 //         }];
591
592 //         self.make_top_headers();
593
594 //         var main_row = this.make_row(this.data.total[0]);
595
596 //         _.each(this.data.groups, function (group) {
597 //             self.make_row(group, main_row.id);
598 //         });
599 //     },
600
601 //     get_descr: function (field_id) {
602 //         return this.data.fields[field_id].string;
603 //     },
604
605 //     get_groups: function (groupby) {
606 //         var view_fields = this.data.row_groupby.concat(this.data.measure, this.data.col_groupby);
607 //         return query_groups(this.data.model, view_fields, this.data.domain, groupby);
608 //     },
609
610 //     make_top_headers : function () {
611 //         var self = this,
612 //             header;
613
614 //         function partition (columns) {
615 //             return _.reduce(columns, function (partial, col) {
616 //                 if (partial.length === 0) return [[col]];
617 //                 if (col.path.length > _.first(_.last(partial)).path.length) {
618 //                     _.last(partial).push(col);
619 //                 } else {
620 //                     partial.push([col]);
621 //                 }
622 //                 return partial;
623 //             }, []);
624 //         }
625
626 //         function side_by_side(blocks) {
627 //             var result = _.zip.apply(_,blocks);
628 //             result = _.map(result, function(line) {return _.compact(_.flatten(line))});
629 //             return result;
630 //         }
631
632 //         function make_header_cell(col, span) {
633 //             var options = {
634 //                 is_border:true,
635 //                 foldable:true,
636 //                 row_span: (span === undefined) ? 1 : span,
637 //                 col_id: col.id,
638 //             };
639 //             var result = self.make_cell(col.value, options);
640 //             if (col.expanded) {
641 //                 result.find('.icon-plus-sign')
642 //                     .removeClass('icon-plus-sign')
643 //                     .addClass('icon-minus-sign');
644 //             }
645 //             return result;
646 //         }
647
648 //         function calculate_width(cols) {
649 //             if (cols.length === 1) {
650 //                 return 1;
651 //             }
652 //             var p = partition(_.rest(cols));
653 //             return _.reduce(p, function(x, y){ return x + calculate_width(y); }, 0);
654 //         }
655
656 //         function make_header_cells(cols, height) {
657 //             var p = partition(cols);
658 //             if ((p.length === 1) && (p[0].length === 1)) {
659 //                 var result = [[make_header_cell(cols[0], height)]];
660 //                 return result;
661 //             }
662 //             if ((p.length === 1) && (p[0].length > 1)) {
663 //                 var cell = make_header_cell(p[0][0]);
664 //                 cell.attr('colspan', calculate_width(cols));
665 //                 return [[cell]].concat(make_header_cells(_.rest(cols), height - 1));
666 //             }
667 //             if (p.length > 1) {
668 //                 return side_by_side(_.map(p, function (group) {
669 //                     return make_header_cells(group, height);
670 //                 }));
671 //             }
672 //         }
673
674 //         if (this.cols.length === 1) {
675 //             header = $('<tr></tr>');
676 //             header.append(this.make_cell('', {is_border:true}));
677 //             header.append(this.make_cell(this.cols[0].value, 
678 //                 {is_border:true, foldable:true, col_id:this.cols[0].id}));
679 //             header.addClass('graph_top');
680 //             this.headers = [header];
681 //         } else {
682 //             var height = _.max(_.map(self.cols, function(g) {return g.path.length;}));
683 //             var header_rows = make_header_cells(_.rest(this.cols), height);
684
685 //             header_rows[0].splice(0,0,self.make_cell('', {is_border:true, }).attr('rowspan', height))
686 //             self.headers = [];
687 //             _.each(header_rows, function (cells) {
688 //                 header = $('<tr></tr>');
689 //                 header.append(cells);
690 //                 header.addClass('graph_top');
691 //                 self.headers.push(header);
692 //             });
693 //         }
694 //     },
695
696 //     draw_top_headers: function () {
697 //         var self = this;
698 //         $("tr.graph_top").remove();
699 //         _.each(this.headers.reverse(), function (header) {
700 //             self.$el.prepend(header);
701 //         });
702
703 //     },
704
705 //     make_row: function (groups, parent_id) {
706 //         var self = this,
707 //             path,
708 //             value,
709 //             expanded,
710 //             domain,
711 //             parent,
712 //             has_parent = (parent_id !== undefined),
713 //             row_id = this.generate_id();
714
715 //         if (has_parent) {
716 //             parent = this.get_row(parent_id);
717 //             path = parent.path.concat(groups[0].attributes.value[1]);
718 //             value = groups[0].attributes.value[1];
719 //             expanded = false;
720 //             parent.children.push(row_id);
721 //             domain = groups[0].model._domain;
722 //         } else {
723 //             parent = null;
724 //             path = [];
725 //             value = 'Total';
726 //             expanded = true;
727 //             domain = this.data.domain;
728 //         }
729
730 //         var jquery_row = $('<tr></tr>');
731
732 //         var header = self.make_cell(value, {is_border:true, indent: path.length, foldable:true, row_id: row_id});
733 //         jquery_row.append(header);
734
735 //         var cell;
736
737 //         _.each(this.cols, function (col) {
738 //             var element = _.find(groups, function (group) {
739 //                 return _.isEqual(_.rest(group.path), col.path);
740 //             });
741 //             if (element === undefined) {
742 //                 cell = self.make_cell('');
743 //             } else {
744 //                 cell = self.make_cell(element.attributes.aggregates[self.data.measure]);                
745 //             }
746 //             if (col.expanded) {
747 //                 cell.css('display', 'none');
748 //             }
749 //             col.cells.push({td:cell, row_id:row_id});
750 //             jquery_row.append(cell);
751 //         });
752
753 //         if (!has_parent) {
754 //             header.find('.icon-plus-sign')
755 //                 .removeClass('icon-plus-sign')
756 //                 .addClass('icon-minus-sign');            
757 //         }
758
759 //         var row = {
760 //             id: row_id,
761 //             path: path,
762 //             value: value,
763 //             expanded: expanded,
764 //             parent: parent_id,
765 //             children: [],
766 //             html: jquery_row,
767 //             domain: domain,
768 //         };
769 //         this.rows.push(row);  // to do, insert it properly, after all childs of parent
770 //         return row;
771 //     },
772
773 //     generate_id: function () {
774 //         this.id_seed += 1;
775 //         return this.id_seed - 1;
776 //     },
777
778 //     get_row: function (id) {
779 //         return _.find(this.rows, function(row) {
780 //             return (row.id == id);
781 //         });
782 //     },
783
784 //     get_col: function (id) {
785 //         return _.find(this.cols, function(col) {
786 //             return (col.id == id);
787 //         });
788 //     },
789
790 //     make_cell: function (content, options) {
791 //         options = _.extend({is_border: false, indent:0, foldable:false}, options);
792 //         content = (content !== undefined) ? content : 'Undefined';
793
794 //         var cell = $('<td></td>');
795 //         if (options.is_border) cell.addClass('graph_border');
796 //         if (options.row_span) cell.attr('rowspan', options.row_span);
797 //         if (options.col_span) cell.attr('rowspan', options.col_span);
798 //         _.each(_.range(options.indent), function () {
799 //             cell.prepend($('<span/>', {class:'web_graph_indent'}));
800 //         });
801
802 //         if (options.foldable) {
803 //             var attrs = {class:'icon-plus-sign web_graph_click', href:'#'};
804 //             if (options.row_id !== undefined) attrs['data-row-id'] = options.row_id;
805 //             if (options.col_id !== undefined) attrs['data-col-id'] = options.col_id;
806 //             var plus = $('<span/>', attrs);
807 //             plus.append(' ');
808 //             plus.append(content);
809 //             cell.append(plus);
810 //         } else {
811 //             cell.append(content);
812 //         }
813 //         return cell;
814 //     },
815
816 //     expand_row: function (row_id, field_id) {
817 //         var self = this;
818 //         var row = this.get_row(row_id);
819
820 //         if (row.path.length == this.data.row_groupby.length) {
821 //             this.data.row_groupby.push(field_id);
822 //         }
823 //         row.expanded = true;
824 //         row.html.find('.icon-plus-sign')
825 //             .removeClass('icon-plus-sign')
826 //             .addClass('icon-minus-sign');
827
828 //         var visible_fields = this.data.row_groupby.concat(this.data.col_groupby, this.data.measure);
829 //         query_groups_data(this.data.model, visible_fields, row.domain, this.data.col_groupby, field_id)
830 //             .then(function (groups) {
831 //                 _.each(groups.reverse(), function (group) {
832 //                     var new_row = self.make_row(group, row_id);
833 //                     row.html.after(new_row.html);
834 //                 });
835 //         });
836
837 //     },
838
839 //     expand_col: function (col_id, field_id) {
840 //         var self = this;
841 //         var col = this.get_col(col_id);
842
843 //         if (col.path.length == this.data.col_groupby.length) {
844 //             this.data.col_groupby.push(field_id);
845 //         }
846 //         col.expanded = true;
847
848 //         var visible_fields = this.data.row_groupby.concat(this.data.col_groupby, this.data.measure);
849 //         query_groups_data(this.data.model, visible_fields, col.domain, this.data.row_groupby, field_id)
850 //             .then(function (groups) {
851 //                 _.each(groups, function (group) {
852 //                     var new_col = {
853 //                         id: self.generate_id(),
854 //                         path: col.path.concat(group[0].attributes.value[1]),
855 //                         value: group[0].attributes.value[1],
856 //                         expanded: false,
857 //                         parent: col_id,
858 //                         children: [],
859 //                         cells: [],    // a cell is {td:<jquery td>, row_id:<some id>}
860 //                         domain: group[0].model._domain,
861 //                     };
862 //                     col.children.push(new_col.id);
863 //                     insertAfter(self.cols, col, new_col)
864 //                     _.each(col.cells, function (cell) {
865 //                         var col_path = self.get_row(cell.row_id).path;
866
867 //                         var datapt = _.find(group, function (g) {
868 //                             return _.isEqual(g.path.slice(1), col_path);
869 //                         });
870
871 //                         var value;
872 //                         if (datapt === undefined) {
873 //                             value = '';
874 //                         } else {
875 //                             value = datapt.attributes.aggregates[self.data.measure];
876 //                         }
877 //                         var new_cell = {
878 //                             row_id: cell.row_id,
879 //                             td: self.make_cell(value)
880 //                         };
881 //                         new_col.cells.push(new_cell);
882 //                         cell.td.after(new_cell.td);
883 //                         cell.td.css('display','none');
884 //                     });
885
886 //                 });
887 //             self.make_top_headers();
888 //             self.draw_top_headers();
889 //         });
890 //     },
891
892 //     fold_row: function (row_id) {
893 //         var self = this;
894 //         var row = this.get_row(row_id);
895
896 //         _.each(row.children, function (child_row) {
897 //             self.remove_row(child_row);
898 //         });
899 //         row.children = [];
900
901 //         row.expanded = false;
902 //         row.html.find('.icon-minus-sign')
903 //             .removeClass('icon-minus-sign')
904 //             .addClass('icon-plus-sign');
905
906 //         var fold_levels = _.map(self.rows, function(g) {return g.path.length;});
907 //         var new_groupby_length = _.max(fold_levels); 
908
909 //         this.data.row_groupby.splice(new_groupby_length);
910 //     },
911
912 //     remove_row: function (row_id) {
913 //         var self = this;
914 //         var row = this.get_row(row_id);
915
916 //         _.each(row.children, function (child_row) {
917 //             self.remove_row(child_row);
918 //         });
919
920 //         row.html.remove();
921 //         removeFromArray(this.rows, row);
922
923 //         _.each(this.cols, function (col) {
924 //             col.cells = _.filter(col.cells, function (cell) {
925 //                 return cell.row_id !== row_id;
926 //             });
927 //         });
928 //     },
929
930 //     fold_col: function (col_id) {
931 //         var self = this;
932 //         var col = this.get_col(col_id);
933
934 //         _.each(col.children, function (child_col) {
935 //             self.remove_col(child_col);
936 //         });
937 //         col.children = [];
938
939 //         _.each(col.cells, function (cell) {
940 //             cell.td.css('display','table-cell');
941 //         });
942 //         col.expanded = false;
943 //         // row.html.find('.icon-minus-sign')
944 //         //     .removeClass('icon-minus-sign')
945 //         //     .addClass('icon-plus-sign');
946
947 //         var fold_levels = _.map(self.cols, function(g) {return g.path.length;});
948 //         var new_groupby_length = _.max(fold_levels); 
949
950 //         this.data.col_groupby.splice(new_groupby_length);
951 //         this.make_top_headers();
952 //         this.draw_top_headers();
953
954             
955 //     },
956     
957 //     remove_col: function (col_id) {
958 //         var self = this;
959 //         var col = this.get_col(col_id);
960
961 //         _.each(col.children, function (child_col) {
962 //             self.remove_col(child_col);
963 //         });
964
965 //         _.each(col.cells, function (cell) {
966 //             cell.td.remove();
967 //         });
968 //         // row.html.remove();
969 //         removeFromArray(this.cols, col);
970
971 //         // _.each(this.cols, function (col) {
972 //         //     col.cells = _.filter(col.cells, function (cell) {
973 //         //         return cell.row_id !== row_id;
974 //         //     });
975 //         // });
976 //     },
977
978
979 // });
980
981 };