1 /*---------------------------------------------------------
3 *---------------------------------------------------------*/
5 /* jshint undef: false */
7 openerp.web_graph = function (instance) {
10 var _lt = instance.web._lt;
11 var _t = instance.web._t;
12 var QWeb = instance.web.qweb;
14 instance.web.views.add('graph', 'instance.web_graph.GraphView');
16 instance.web_graph.GraphView = instance.web.View.extend({
17 display_name: _lt('Graph'),
20 init: function(parent, dataset, view_id, options) {
22 this.dataset = dataset;
23 this.model = new instance.web.Model(dataset.model, {group_by_no_leaf: true});
24 this.search_view = parent.searchview;
25 this.groupby_mode = 'default'; // 'default' or 'manual'
26 this.default_row_groupby = [];
27 this.default_col_groupby = [];
29 get_context: this.proxy('get_context'),
30 get_domain: function () {},
31 get_groupby: function () { },
35 get_context: function (facet) {
36 var col_group_by = _.map(facet.values.models, function (model) {
37 return model.attributes.value.attrs.context.col_group_by;
39 return {col_group_by : col_group_by};
43 var options = {enabled:false};
44 this.graph_widget = new openerp.web_graph.Graph(this, this.model, options);
45 this.graph_widget.appendTo(this.$el);
46 this.graph_widget.pivot.on('groupby_changed', this, this.proxy('register_groupby'));
47 return this.load_view();
50 view_loading: function (fields_view_get) {
54 if (fields_view_get.arch.attrs.type === 'bar') {
55 this.mode = 'bar_chart';
58 _.each(fields_view_get.arch.children, function (field) {
59 if ('name' in field.attrs) {
60 if ('operator' in field.attrs) {
61 measure = field.attrs.name;
64 self.default_col_groupby.push(field.attrs.name);
66 self.default_row_groupby.push(field.attrs.name);
71 this.graph_widget.pivot.config({measure:measure, update:false});
74 do_search: function (domain, context, group_by) {
76 col_groupby = context.col_group_by || [],
77 options = {domain:domain};
79 if (group_by.length || col_groupby.length) {
80 this.groupby_mode = 'manual';
82 if (!this.graph_widget.enabled) {
83 options.update = false;
84 options.silent = true;
87 if (this.groupby_mode === 'manual') {
88 options.row_groupby = group_by;
89 options.col_groupby = col_groupby;
91 options.row_groupby = _.toArray(this.default_row_groupby);
92 options.col_groupby = _.toArray(this.default_col_groupby);
94 this.graph_widget.pivot.config(options);
96 if (!this.graph_widget.enabled) {
97 this.graph_widget.activate_display();
101 do_show: function () {
102 this.do_push_state({});
103 return this._super();
106 register_groupby: function() {
108 query = this.search_view.query;
109 this.groupby_mode = 'manual';
111 var rows = _.map(this.graph_widget.pivot.rows.groupby, function (group) {
112 return make_facet('GroupBy', group);
114 var cols = _.map(this.graph_widget.pivot.cols.groupby, function (group) {
115 return make_facet('ColGroupBy', group);
118 query.reset(rows.concat(cols));
120 function make_facet (category, fields) {
125 if (!(fields instanceof Array)) { fields = [fields]; }
126 if (category === 'GroupBy') {
127 cat_name = 'group_by';
129 backbone_field = self.search_view._s_groupby;
131 cat_name = 'col_group_by';
133 backbone_field = self.search_field;
135 values = _.map(fields, function (field) {
137 context[cat_name] = field;
138 return {label: self.graph_widget.fields[field].string, value: {attrs:{domain: [], context: context}}};
140 return {category:category, values: values, icon:icon, field: backbone_field};
145 instance.web_graph.Graph = instance.web.Widget.extend({
146 template: "GraphWidget",
149 'click .graph_mode_selection li' : 'mode_selection',
150 'click .graph_measure_selection li' : 'measure_selection',
151 'click .graph_options_selection li' : 'option_selection',
152 'click .web_graph_click' : 'header_cell_clicked',
153 'click a.field-selection' : 'field_selection',
156 init: function(parent, model, options) {
160 this.important_fields = [];
161 this.measure_list = [];
164 this.dropdown = null;
166 this.pivot = new openerp.web_graph.PivotTable(model, []);
168 options = options || {};
170 // show_ui, hide ui ?, default stacked/grouped?
171 if (_.has(options, 'mode')) { this.mode = mode; }
172 if (_.has(options, 'measure')) { this.pivot.set_measure(options.measure); }
173 if (_.has(options, 'enabled')) { this.enabled = options.enabled; }
179 this.table = $('<table></table>');
180 this.$('.graph_main_content').append(this.table);
181 // get the most important fields (of the model) by looking at the
182 // groupby filters defined in the search view
183 var options = {model:this.model, view_type: 'search'},
184 deferred1 = instance.web.fields_view_get(options).then(function (search_view) {
185 var groups = _.select(search_view.arch.children, function (c) {
186 return (c.tag == 'group') && (c.attrs.string != 'Display');
188 _.each(groups, function(g) {
189 _.each(g.children, function (g) {
190 if (g.attrs.context) {
191 var field_id = py.eval(g.attrs.context).group_by;
192 self.important_fields.push(field_id);
198 // get the fields descriptions and measure list from the model
199 var deferred2 = this.model.call('fields_get', []).then(function (fs) {
201 var temp = _.map(fs, function (field, name) {
202 return {name:name, type: field.type};
204 temp = _.filter(temp, function (field) {
205 return (((field.type === 'integer') || (field.type === 'float')) && (field.name !== 'id'));
207 self.measure_list = _.map(temp, function (field) {
211 var measure_selection = self.$('.graph_measure_selection');
212 _.each(self.measure_list, function (measure) {
213 var choice = $('<a></a>').attr('data-choice', measure)
215 .append(self.fields[measure].string);
216 measure_selection.append($('<li></li>').append(choice));
221 return $.when(deferred1, deferred2).then(function () {
223 this.activate_display();
228 activate_display: function () {
229 this.pivot.on('redraw_required', this, this.proxy('display_data'));
230 this.pivot.update_data();
232 instance.web.bus.on('click', this, function (ev) {
234 this.dropdown.remove();
235 this.dropdown = null;
240 display_data: function () {
241 var pivot = this.pivot;
242 this.$('.graph_main_content svg').remove();
246 var msg = 'No data available. Try to remove any filter or add some data.';
247 this.table.append($('<tr><td>' + msg + '</td></tr>'));
249 var table_modes = ['pivot', 'heatmap', 'row_heatmap', 'col_heatmap'];
250 if (_.contains(table_modes, this.mode)) {
253 this.$('.graph_main_content').append($('<div><svg></svg></div>'));
255 svg: this.$('.graph_main_content svg')[0],
258 width: this.$el.width(),
259 height: Math.min(Math.max(document.documentElement.clientHeight - 116 - 60, 250), Math.round(0.8*this.$el.width())),
260 measure_label: this.measure_label()
262 openerp.web_graph.draw_chart(options);
267 mode_selection: function (event) {
268 event.preventDefault();
269 var mode = event.target.attributes['data-mode'].nodeValue;
274 measure_selection: function (event) {
275 event.preventDefault();
276 var measure = event.target.attributes['data-choice'].nodeValue;
277 var actual_measure = (measure === '__count') ? null : measure
278 this.pivot.config({measure:actual_measure});
281 option_selection: function (event) {
282 event.preventDefault();
283 switch (event.target.attributes['data-choice'].nodeValue) {
285 this.pivot.swap_axis();
288 this.pivot.rows.headers = null;
289 this.pivot.cols.headers = null;
290 this.pivot.update_data();
292 case 'update_values':
293 this.pivot.update_data();
296 // Export code... To do...
301 header_cell_clicked: function (event) {
302 event.preventDefault();
303 event.stopPropagation();
304 var id = event.target.attributes['data-id'].nodeValue,
305 header = this.pivot.get_header(id),
307 dim = header.root.groupby.length;
309 if (header.is_expanded) {
310 this.pivot.fold(header);
312 if (header.path.length < header.root.groupby.length) {
313 var field = header.root.groupby[header.path.length];
314 this.pivot.expand(id, field);
316 var fields = _.map(this.important_fields, function (field) {
317 return {id: field, value: self.fields[field].string};
319 this.dropdown = $(QWeb.render('field_selection', {fields:fields, header_id:id}));
320 $(event.target).after(this.dropdown);
321 this.dropdown.css({position:'absolute',
324 this.$('.field-selection').next('.dropdown-menu').toggle();
329 field_selection: function (event) {
331 id = event.target.attributes['data-id'].nodeValue,
332 field_id = event.target.attributes['data-field-id'].nodeValue;
333 event.preventDefault();
334 this.pivot.expand(id, field_id);
337 /******************************************************************************
338 * Drawing pivot table methods...
339 ******************************************************************************/
340 draw_table: function () {
341 this.pivot.rows.main.title = 'Total';
342 this.pivot.cols.main.title = this.measure_label();
343 this.draw_top_headers();
344 _.each(this.pivot.rows.headers, this.proxy('draw_row'));
347 measure_label: function () {
348 return (this.pivot.measure) ? this.fields[this.pivot.measure].string : 'Quantity';
351 make_border_cell: function (colspan, rowspan) {
352 return $('<td></td>').addClass('graph_border')
353 .attr('colspan', colspan)
354 .attr('rowspan', rowspan);
357 make_header_title: function (header) {
358 return $('<span> </span>')
359 .addClass('web_graph_click')
361 .addClass((header.is_expanded) ? 'fa fa-minus-square' : 'fa fa-plus-square')
362 .append((header.title !== undefined) ? header.title : 'Undefined');
365 draw_top_headers: function () {
368 height = _.max(_.map(pivot.cols.headers, function(g) {return g.path.length;})),
369 header_cells = [[this.make_border_cell(1, height)]];
371 function set_dim (cols) {
372 _.each(cols.children, set_dim);
373 if (cols.children.length === 0) {
374 cols.height = height - cols.path.length + 1;
378 cols.width = _.reduce(cols.children, function (sum,c) { return sum + c.width;}, 0);
382 function make_col_header (col) {
383 var cell = self.make_border_cell(col.width, col.height);
384 return cell.append(self.make_header_title(col).attr('data-id', col.id));
387 function make_cells (queue, level) {
389 queue = _.rest(queue).concat(col.children);
390 if (col.path.length == level) {
391 _.last(header_cells).push(make_col_header(col));
394 header_cells.push([make_col_header(col)]);
396 if (queue.length !== 0) {
397 make_cells(queue, level);
401 set_dim(pivot.cols.main); // add width and height info to columns headers
402 if (pivot.cols.main.children.length === 0) {
403 make_cells(pivot.cols.headers, 0);
405 make_cells(pivot.cols.main.children, 1);
406 header_cells[0].push(self.make_border_cell(1, height).append('Total').css('font-weight', 'bold'));
409 _.each(header_cells, function (cells) {
410 self.table.append($('<tr></tr>').append(cells));
414 get_measure_type: function () {
415 var measure = this.pivot.measure;
416 return (measure) ? this.fields[measure].type : 'integer';
419 draw_row: function (row) {
422 measure_type = this.get_measure_type(),
423 html_row = $('<tr></tr>'),
424 row_header = this.make_border_cell(1,1)
425 .append(this.make_header_title(row).attr('data-id', row.id))
426 .addClass('graph_border');
428 for (var i in _.range(row.path.length)) {
429 row_header.prepend($('<span/>', {class:'web_graph_indent'}));
432 html_row.append(row_header);
434 _.each(pivot.cols.headers, function (col) {
435 if (col.children.length === 0) {
436 var value = pivot.get_value(row.id, col.id),
437 cell = make_cell(value, col);
438 html_row.append(cell);
442 if (pivot.cols.main.children.length > 0) {
443 var cell = make_cell(pivot.get_total(row), pivot.cols.main)
444 .css('font-weight', 'bold');
445 html_row.append(cell);
448 this.table.append(html_row);
450 function make_cell (value, col) {
453 cell = $('<td></td>');
454 if ((self.mode === 'pivot') && (row.is_expanded) && (row.path.length <=2)) {
455 color = row.path.length * 5 + 240;
456 cell.css('background-color', $.Color(color, color, color));
458 if (value === undefined) {
461 cell.append(instance.web.format_value(value, {type: measure_type}));
462 if (self.mode === 'heatmap') {
463 total = pivot.get_total();
464 color = Math.floor(50 + 205*(total - value)/total);
465 cell.css('background-color', $.Color(255, color, color));
467 if (self.mode === 'row_heatmap') {
468 total = pivot.get_total(row);
469 color = Math.floor(50 + 205*(total - value)/total);
470 cell.css('background-color', $.Color(255, color, color));
472 if (self.mode === 'col_heatmap') {
473 total = pivot.get_total(col);
474 color = Math.floor(50 + 205*(total - value)/total);
475 cell.css('background-color', $.Color(255, color, color));