1 /*---------------------------------------------------------
3 *---------------------------------------------------------*/
5 /* jshint undef: false */
8 openerp.web_graph = function (instance) {
11 var _lt = instance.web._lt;
12 var _t = instance.web._t;
13 var QWeb = instance.web.qweb;
15 instance.web.views.add('graph', 'instance.web_graph.GraphView');
18 * GraphView view. It mostly contains a widget (PivotTable), some data, and
19 * calls to charts function.
21 instance.web_graph.GraphView = instance.web.View.extend({
22 template: 'GraphView',
23 display_name: _lt('Graph'),
27 'click .graph_mode_selection li' : function (event) {
28 event.preventDefault();
29 this.mode = event.target.attributes['data-mode'].nodeValue;
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});
38 if (event.target.attributes['data-col-id'] !== undefined) {
39 this.handle_header_event({type:'col', event:event});
43 'click a.field-selection' : function (event) {
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'));
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'));
61 view_loading: function (fields_view_get) {
63 model = new instance.web.Model(fields_view_get.model, {group_by_no_leaf: true}),
67 important_fields = [],
71 this.pivot_table = null;
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;
79 row_groupby.push(field.attrs.name);
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({
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');
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);
105 // get the fields descriptions from the model
106 var field_descr_def = model.call('fields_get', [])
107 .then(function (fs) { fields = fs; });
109 return $.when(important_fields_def, field_descr_def)
115 important_fields: important_fields,
117 measure_label: fields[measure].string,
119 row_groupby: row_groupby,
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();
134 do_search: function (domain, context, group_by) {
136 this.data.domain = new instance.web.CompoundDomain(domain);
138 if (!this.pivot_table) {
139 self.pivot_table = new PivotTable(self.data);
140 self.pivot_table.start().then(self.proxy('draw_table'));
142 this.pivot_table.update_values.done(function () {
148 do_show: function () {
149 this.do_push_state({});
150 return this._super();
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);
159 if (header.is_expanded) {
160 pivot['fold_' + options.type](header);
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'));
169 // display dropdown to query field to expand
170 this.display_dropdown({id:header.id,
172 target: $(event.target),
180 display_dropdown: function (options) {
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),
186 fields: _.map(possible_groups, function (field) {
187 return {id: field, value: self.data.fields[field].string};
189 dropdown_options[options.type + '_id'] = options.id;
191 this.dropdown = $(QWeb.render('field_selection', dropdown_options));
192 options.target.after(this.dropdown);
193 this.dropdown.css({position:'absolute',
196 $('.field-selection').next('.dropdown-menu').toggle();
199 draw_table: function () {
201 this.draw_top_headers();
202 _.each(this.pivot_table.rows_array(), this.proxy('draw_row'));
205 make_border_cell: function (colspan, rowspan) {
206 return $('<td></td>').addClass('graph_border')
207 .attr('colspan', colspan)
208 .attr('rowspan', rowspan);
211 make_header_title: function (header) {
212 return $('<span> </span>')
213 .addClass('web_graph_click')
215 .addClass((header.is_expanded) ? 'icon-minus-sign' : 'icon-plus-sign')
216 .append(header.name);
219 draw_top_headers: function () {
221 pivot = this.pivot_table,
222 height = pivot.get_max_path_length(pivot.cols),
223 header_cells = [[this.make_border_cell(1, height)]];
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;
232 cols.width = _.reduce(cols.children, function (sum,c) { return sum + c.width;}, 0);
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));
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));
248 header_cells.push([make_col_header(col)]);
250 if (queue.length !== 0) {
251 make_cells(queue, level);
255 set_dim(pivot.cols); // add width and height info to columns headers
257 if (pivot.cols.children.length === 0) {
258 make_cells([pivot.cols], 0);
260 make_cells(pivot.cols.children, 1);
263 _.each(header_cells, function (cells) {
264 self.table.append($("<tr></tr>").append(cells));
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');
275 for (var i in _.range(row.path.length)) {
276 row_header.prepend($('<span/>', {class:'web_graph_indent'}));
279 html_row.append(row_header);
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)
285 this.table.append(html_row);
291 // make_top_headers : function () {
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);
301 // partial.push([col]);
307 // function side_by_side(blocks) {
308 // var result = _.zip.apply(_,blocks);
309 // result = _.map(result, function(line) {return _.compact(_.flatten(line))});
313 // function make_header_cell(col, span) {
317 // row_span: (span === undefined) ? 1 : span,
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');
329 // function calculate_width(cols) {
330 // if (cols.length === 1) {
333 // var p = partition(_.rest(cols));
334 // return _.reduce(p, function(x, y){ return x + calculate_width(y); }, 0);
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)]];
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));
348 // if (p.length > 1) {
349 // return side_by_side(_.map(p, function (group) {
350 // return make_header_cells(group, height);
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];
363 // var height = _.max(_.map(self.cols, function(g) {return g.path.length;}));
364 // var header_rows = make_header_cells(_.rest(this.cols), height);
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);
378 // mode: 'pivot', // pivot, bar_chart, line_chart or pie_chart
379 // pivot_table: null,
382 // 'click .graph_mode_selection li' : function (event) {
383 // event.preventDefault();
384 // this.mode = event.target.attributes['data-mode'].nodeValue;
385 // this.display_data();
389 // display_data : function () {
390 // var content = this.$el.filter('.graph_main_content');
391 // content.find('svg').remove();
393 // if (this.mode === 'pivot') {
394 // this.pivot_table.show();
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);
407 // if (this.pivot_table) {
408 // this.pivot_table.draw(true);
411 // this.pivot_table = new PivotTable(this.data);
412 // this.pivot_table.appendTo('.graph_main_content');
414 // this.display_data();
417 * PivotTable widget. It displays the data in tabular data and allows the
418 * user to drill down and up in the table
420 // var PivotTable = instance.web.Widget.extend({
421 // template: 'pivot_table',
423 // init: function (data) {
425 // makePivotTable(this.data).done(function (table) {
426 // self.pivottable = table;
432 // var PivotTable = instance.web.Widget.extend({
433 // template: 'pivot_table',
441 // 'click .web_graph_click' : function (event) {
442 // event.preventDefault();
444 // if (event.target.attributes['data-row-id'] !== undefined) {
445 // this.handle_row_event(event);
447 // if (event.target.attributes['data-col-id'] !== undefined) {
448 // this.handle_col_event(event);
452 // 'click a.field-selection' : function (event) {
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);
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);
468 // handle_row_event: function (event) {
469 // var row_id = event.target.attributes['data-row-id'].nodeValue,
470 // row = this.get_row(row_id);
472 // if (row.expanded) {
473 // this.fold_row(row_id);
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);
479 // this.display_dropdown({row_id:row_id,
480 // target: $(event.target),
487 // handle_col_event: function (event) {
488 // var col_id = event.target.attributes['data-col-id'].nodeValue,
489 // col = this.get_col(col_id);
491 // if (col.expanded) {
492 // this.fold_col(col_id);
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);
498 // this.display_dropdown({col_id: col_id,
499 // target: $(event.target),
506 // init: function (data) {
508 // makePivotTable(this.data).done(function (table) {
509 // self.pivottable = table;
514 // start: function () {
518 // draw: function (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();
536 // this.draw_top_headers();
538 // _.each(this.rows, function (row) {
539 // self.$el.append(row.html);
544 // show: function () {
545 // this.$el.css('display', 'block');
548 // hide: function () {
549 // this.$el.css('display', 'none');
552 // display_dropdown: function (options) {
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)};
560 // if (options.row_id) {
561 // dropdown_options.row_id= options.row_id;
563 // dropdown_options.col_id = options.col_id;
566 // this.dropdown = $(QWeb.render('field_selection', dropdown_options));
567 // options.target.after(this.dropdown);
568 // this.dropdown.css({position:'absolute',
571 // $('.field-selection').next('.dropdown-menu').toggle();
574 // build_table: function () {
579 // var col_id = this.generate_id();
584 // value: this.data.measure_label,
588 // cells: [], // a cell is {td:<jquery td>, row_id:<some id>}
589 // domain: this.data.domain,
592 // self.make_top_headers();
594 // var main_row = this.make_row(this.data.total[0]);
596 // _.each(this.data.groups, function (group) {
597 // self.make_row(group, main_row.id);
601 // get_descr: function (field_id) {
602 // return this.data.fields[field_id].string;
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);
610 // make_top_headers : function () {
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);
620 // partial.push([col]);
626 // function side_by_side(blocks) {
627 // var result = _.zip.apply(_,blocks);
628 // result = _.map(result, function(line) {return _.compact(_.flatten(line))});
632 // function make_header_cell(col, span) {
636 // row_span: (span === undefined) ? 1 : span,
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');
648 // function calculate_width(cols) {
649 // if (cols.length === 1) {
652 // var p = partition(_.rest(cols));
653 // return _.reduce(p, function(x, y){ return x + calculate_width(y); }, 0);
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)]];
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));
667 // if (p.length > 1) {
668 // return side_by_side(_.map(p, function (group) {
669 // return make_header_cells(group, height);
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];
682 // var height = _.max(_.map(self.cols, function(g) {return g.path.length;}));
683 // var header_rows = make_header_cells(_.rest(this.cols), height);
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);
696 // draw_top_headers: function () {
698 // $("tr.graph_top").remove();
699 // _.each(this.headers.reverse(), function (header) {
700 // self.$el.prepend(header);
705 // make_row: function (groups, parent_id) {
712 // has_parent = (parent_id !== undefined),
713 // row_id = this.generate_id();
716 // parent = this.get_row(parent_id);
717 // path = parent.path.concat(groups[0].attributes.value[1]);
718 // value = groups[0].attributes.value[1];
720 // parent.children.push(row_id);
721 // domain = groups[0].model._domain;
727 // domain = this.data.domain;
730 // var jquery_row = $('<tr></tr>');
732 // var header = self.make_cell(value, {is_border:true, indent: path.length, foldable:true, row_id: row_id});
733 // jquery_row.append(header);
737 // _.each(this.cols, function (col) {
738 // var element = _.find(groups, function (group) {
739 // return _.isEqual(_.rest(group.path), col.path);
741 // if (element === undefined) {
742 // cell = self.make_cell('');
744 // cell = self.make_cell(element.attributes.aggregates[self.data.measure]);
746 // if (col.expanded) {
747 // cell.css('display', 'none');
749 // col.cells.push({td:cell, row_id:row_id});
750 // jquery_row.append(cell);
753 // if (!has_parent) {
754 // header.find('.icon-plus-sign')
755 // .removeClass('icon-plus-sign')
756 // .addClass('icon-minus-sign');
763 // expanded: expanded,
764 // parent: parent_id,
769 // this.rows.push(row); // to do, insert it properly, after all childs of parent
773 // generate_id: function () {
774 // this.id_seed += 1;
775 // return this.id_seed - 1;
778 // get_row: function (id) {
779 // return _.find(this.rows, function(row) {
780 // return (row.id == id);
784 // get_col: function (id) {
785 // return _.find(this.cols, function(col) {
786 // return (col.id == id);
790 // make_cell: function (content, options) {
791 // options = _.extend({is_border: false, indent:0, foldable:false}, options);
792 // content = (content !== undefined) ? content : 'Undefined';
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'}));
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);
808 // plus.append(content);
809 // cell.append(plus);
811 // cell.append(content);
816 // expand_row: function (row_id, field_id) {
818 // var row = this.get_row(row_id);
820 // if (row.path.length == this.data.row_groupby.length) {
821 // this.data.row_groupby.push(field_id);
823 // row.expanded = true;
824 // row.html.find('.icon-plus-sign')
825 // .removeClass('icon-plus-sign')
826 // .addClass('icon-minus-sign');
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);
839 // expand_col: function (col_id, field_id) {
841 // var col = this.get_col(col_id);
843 // if (col.path.length == this.data.col_groupby.length) {
844 // this.data.col_groupby.push(field_id);
846 // col.expanded = true;
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) {
853 // id: self.generate_id(),
854 // path: col.path.concat(group[0].attributes.value[1]),
855 // value: group[0].attributes.value[1],
859 // cells: [], // a cell is {td:<jquery td>, row_id:<some id>}
860 // domain: group[0].model._domain,
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;
867 // var datapt = _.find(group, function (g) {
868 // return _.isEqual(g.path.slice(1), col_path);
872 // if (datapt === undefined) {
875 // value = datapt.attributes.aggregates[self.data.measure];
878 // row_id: cell.row_id,
879 // td: self.make_cell(value)
881 // new_col.cells.push(new_cell);
882 // cell.td.after(new_cell.td);
883 // cell.td.css('display','none');
887 // self.make_top_headers();
888 // self.draw_top_headers();
892 // fold_row: function (row_id) {
894 // var row = this.get_row(row_id);
896 // _.each(row.children, function (child_row) {
897 // self.remove_row(child_row);
899 // row.children = [];
901 // row.expanded = false;
902 // row.html.find('.icon-minus-sign')
903 // .removeClass('icon-minus-sign')
904 // .addClass('icon-plus-sign');
906 // var fold_levels = _.map(self.rows, function(g) {return g.path.length;});
907 // var new_groupby_length = _.max(fold_levels);
909 // this.data.row_groupby.splice(new_groupby_length);
912 // remove_row: function (row_id) {
914 // var row = this.get_row(row_id);
916 // _.each(row.children, function (child_row) {
917 // self.remove_row(child_row);
920 // row.html.remove();
921 // removeFromArray(this.rows, row);
923 // _.each(this.cols, function (col) {
924 // col.cells = _.filter(col.cells, function (cell) {
925 // return cell.row_id !== row_id;
930 // fold_col: function (col_id) {
932 // var col = this.get_col(col_id);
934 // _.each(col.children, function (child_col) {
935 // self.remove_col(child_col);
937 // col.children = [];
939 // _.each(col.cells, function (cell) {
940 // cell.td.css('display','table-cell');
942 // col.expanded = false;
943 // // row.html.find('.icon-minus-sign')
944 // // .removeClass('icon-minus-sign')
945 // // .addClass('icon-plus-sign');
947 // var fold_levels = _.map(self.cols, function(g) {return g.path.length;});
948 // var new_groupby_length = _.max(fold_levels);
950 // this.data.col_groupby.splice(new_groupby_length);
951 // this.make_top_headers();
952 // this.draw_top_headers();
957 // remove_col: function (col_id) {
959 // var col = this.get_col(col_id);
961 // _.each(col.children, function (child_col) {
962 // self.remove_col(child_col);
965 // _.each(col.cells, function (cell) {
968 // // row.html.remove();
969 // removeFromArray(this.cols, col);
971 // // _.each(this.cols, function (col) {
972 // // col.cells = _.filter(col.cells, function (cell) {
973 // // return cell.row_id !== row_id;