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 two widgets (PivotTable and ChartView)
21 instance.web_graph.GraphView = instance.web.View.extend({
22 template: 'GraphView',
23 display_name: _lt('Graph'),
25 mode: 'pivot', // pivot, bar_chart, line_chart or pie_chart
28 'click .graph_mode_selection li' : function (event) {
29 event.preventDefault();
30 this.mode = event.target.attributes['data-mode'].nodeValue;
33 'click .graph_clear_groups' : function (event) {
34 this.pivot_table.clear_groups();
38 view_loading: function (fields_view_get) {
40 var model = new instance.web.Model(fields_view_get.model, {group_by_no_leaf: true});
43 options.col_groupby = [];
45 // get the default groupbys and measure defined in the field view
46 options.measure = null;
47 options.row_groupby = [];
48 _.each(fields_view_get.arch.children, function (field) {
49 if ('name' in field.attrs) {
50 if ('operator' in field.attrs) {
51 options.measure = field.attrs.name;
53 options.row_groupby.push(field.attrs.name);
58 // get the most important fields (of the model) by looking at the
59 // groupby filters defined in the search view
60 options.important_fields = [];
61 var load_view = instance.web.fields_view_get({
66 var important_fields_def = $.when(load_view).then(function (search_view) {
67 var groups = _.select(search_view.arch.children, function (c) {
68 return (c.tag == 'group') && (c.attrs.string != 'Display');
71 _.each(groups, function(g) {
72 _.each(g.children, function (g) {
73 if (g.attrs.context) {
74 var field_id = py.eval(g.attrs.context).group_by;
75 options.important_fields.push(field_id);
81 // get the fields descriptions from the model
82 var field_descr_def = model.call('fields_get', [])
83 .then(function (fields) { options.fields = fields; });
86 return $.when(important_fields_def, field_descr_def)
91 domain: options.domain,
92 fields: options.fields,
93 important_fields: options.important_fields,
94 measure: options.measure,
95 measure_label: options.fields[options.measure].string,
97 row_groupby: options.row_groupby,
101 self.pivot_table = new PivotTable(model, options);
104 return self.pivot_table.appendTo('.graph_main_content');
109 display_data : function () {
110 var content = this.$el.filter('.graph_main_content');
111 content.find('svg').remove();
113 if (this.mode === 'pivot') {
114 this.pivot_table.show();
117 this.pivot_table.hide();
118 content.append('<svg></svg>');
119 var view_fields = this.data.row_groupby.concat(this.data.measure, this.data.col_groupby);
120 query_groups(this.data.model, view_fields, this.data.domain, this.data.row_groupby).then(function (groups) {
121 Charts[self.mode](groups, self.data.measure, self.data.measure_label);
127 do_search: function (domain, context, group_by) {
128 this.data.domain = new instance.web.CompoundDomain(domain);
129 this.pivot_table.set_domain(domain);
133 do_show: function () {
134 this.do_push_state({});
135 return this._super();
142 * PivotTable widget. It displays the data in tabular data and allows the
143 * user to drill down and up in the table
145 var PivotTable = instance.web.Widget.extend({
146 template: 'pivot_table',
153 // model: model to display
154 // fields: dictionary returned by field_get on model (desc of model)
155 // domain: constraints on model records
156 // row_groupby: groubys on rows (so, row headers in the pivot table)
157 // col_groupby: idem, but on col
158 // measure: quantity to display. either a field from the model, or
159 // null, in which case we use the "count" measure
160 init: function (model, options) {
163 this.fields = options.fields;
164 this.domain = options.domain;
166 row: options.row_groupby,
167 col: options.col_groupby,
170 this.measure = options.measure;
171 this.measure_label = options.measure ? options.fields[options.measure].string : 'Quantity';
173 this.need_redraw = true;
174 this.important_fields = options.important_fields;
177 get_descr: function (field_id) {
178 return this.fields[field_id].string;
181 set_domain: function (domain) {
182 this.domain = domain;
183 this.need_redraw = true;
186 set_row_groupby: function (row_groupby) {
187 this.groupby.row = row_groupby;
188 this.need_redraw = true;
191 set_col_groupby: function (col_groupby) {
192 this.groupby.col = col_groupby;
193 this.need_redraw = true;
196 set_measure: function (measure) {
197 this.measure = measure;
198 this.need_redraw = true;
202 if (this.need_redraw) {
204 this.need_redraw = false;
206 this.$el.css('display', 'block');
210 this.$el.css('display', 'none');
214 get_data: function (groupby) {
215 var view_fields = this.groupby.row.concat(this.measure, this.groupby.col);
216 return query_groups(this.model, view_fields, this.domain, groupby);
221 'click .web_graph_click' : function (event) {
223 event.preventDefault();
224 var row_id = event.target.attributes['data-row-id'].nodeValue;
226 var row = this.get_row(row_id);
228 this.fold_row(row_id);
230 if (row.path.length < this.groupby.row.length) {
231 var field_to_expand = this.groupby.row[row.path.length];
232 this.expand_row(row_id, field_to_expand);
234 var already_grouped = self.groupby.row.concat(self.groupby.col);
235 var possible_groups = _.difference(self.important_fields, already_grouped);
236 var dropdown_options = {
237 fields: _.map(possible_groups, function (field) {
238 return {id: field, value: self.get_descr(field)};
242 this.dropdown = $(QWeb.render('field_selection', dropdown_options));
243 $(event.target).after(this.dropdown);
244 this.dropdown.css({position:"absolute",
247 $('.field-selection').next('.dropdown-menu').toggle();
253 'click a.field-selection' : function (event) {
254 event.preventDefault();
255 this.dropdown.remove();
256 var row_id = event.target.attributes['data-row-id'].nodeValue;
257 var field_id = event.target.attributes['data-field-id'].nodeValue;
258 this.expand_row(row_id, field_id);
262 clear_groups: function () {
263 this.groupby.row = [];
264 this.groupby.col = [];
270 generate_id: function () {
271 this.current_row_id += 1;
272 return this.current_row_id - 1;
275 get_row: function (id) {
276 return _.find(this.rows, function(row) {
277 return (row.id == id);
281 make_cell: function (content, options) {
283 if (options && options.is_border) {
284 attrs.push('class="graph_border"');
288 if (options && options.indent) {
289 _.each(_.range(options.indent), function () {
290 attrs.push('<span class="web_graph_indent"></span>');
293 if (options && options.foldable) {
294 attrs.push('<span data-row-id="'+ options.row_id + '" href="#" class="icon-plus-sign web_graph_click">');
299 attrs.push('Undefined');
301 if (options && options.foldable) {
302 attrs.push('</span>');
305 return attrs.join(' ');
308 make_row: function (data, parent_id) {
309 var has_parent = (parent_id !== undefined);
310 var parent = has_parent ? this.get_row(parent_id) : null;
313 path = parent.path.concat(data.attributes.grouped_on);
314 } else if (data.attributes.grouped_on !== undefined) {
315 path = [data.attributes.grouped_on];
320 var indent_level = has_parent ? parent.path.length : 0;
321 var value = (this.groupby.row.length > 0) ? data.attributes.value[1] : 'Total';
324 var jquery_row = $('<tr></tr>');
325 var row_id = this.generate_id();
327 var header = $(this.make_cell(value, {is_border:true, indent: indent_level, foldable:true, row_id: row_id}));
328 jquery_row.html(header);
329 jquery_row.append(this.make_cell(data.attributes.aggregates[this.measure]));
339 domain: data.model._domain,
341 // rows.splice(index of parent if any,0,row);
342 this.rows.push(row); // to do, insert it properly
344 if (this.groupby.row.length === 0) {
345 row.remove_when_expanded = true;
346 row.domain = this.domain;
349 parent.children.push(row.id);
354 expand_row: function (row_id, field_id) {
356 var row = this.get_row(row_id);
358 if (row.path.length == this.groupby.row.length) {
359 this.groupby.row.push(field_id);
362 var visible_fields = this.groupby.row.concat(this.groupby.col, this.measure);
364 if (row.remove_when_expanded) {
368 row.html_tr.find('.icon-plus-sign')
369 .removeClass('icon-plus-sign')
370 .addClass('icon-minus-sign');
373 query_groups(this.model, visible_fields, row.domain, [field_id])
374 .then(function (data) {
375 _.each(data.reverse(), function (datapt) {
377 if (row.remove_when_expanded) {
378 new_row = self.make_row(datapt);
379 self.$('tr.graph_table_header').after(new_row.html_tr);
381 new_row = self.make_row(datapt, row_id);
382 row.html_tr.after(new_row.html_tr);
385 if (row.remove_when_expanded) {
386 row.html_tr.remove();
392 fold_row: function (row_id) {
394 var row = this.get_row(row_id);
396 _.each(row.children, function (child_row) {
397 self.remove_row(child_row);
401 row.expanded = false;
402 row.html_tr.find('.icon-minus-sign')
403 .removeClass('icon-minus-sign')
404 .addClass('icon-plus-sign');
406 var fold_levels = _.map(self.rows, function(g) {return g.path.length;});
407 var new_groupby_length = _.reduce(fold_levels, function (x, y) {
408 return Math.max(x,y);
411 this.groupby.row.splice(new_groupby_length);
414 remove_row: function (row_id) {
416 var row = this.get_row(row_id);
418 _.each(row.children, function (child_row) {
419 self.remove_row(child_row);
422 row.html_tr.remove();
423 removeFromArray(this.rows, row);
427 this.get_data(this.groupby.row)
428 .then(this.proxy('build_table'))
429 .done(this.proxy('_draw'));
432 build_table: function (data) {
438 value: this.measure_label,
444 header: $(this.make_cell(this.measure_label, {is_border:true})),
447 _.each(data, function (datapt) {
448 self.make_row(datapt);
458 if (this.groupby.row.length > 0) {
459 header = '<tr><td class="graph_border">' +
460 this.fields[this.groupby.row[0]].string +
461 '</td><td class="graph_border">' +
465 header = '<tr class="graph_table_header"><td class="graph_border">' +
466 '</td><td class="graph_border">' +
470 this.$el.append(header);
472 _.each(this.rows, function (row) {
473 self.$el.append(row.html_tr);