1 /*---------------------------------------------------------
3 *---------------------------------------------------------*/
5 openerp.web_graph = function (instance) {
7 var _lt = instance.web._lt;
9 // removed ``undefined`` values
10 var filter_values = function (o) {
13 if (!o.hasOwnProperty(k) || o[k] === undefined) { continue; }
19 instance.web.views.add('graph', 'instance.web_graph.GraphView');
20 instance.web_graph.GraphView = instance.web.View.extend({
21 template: "GraphView",
22 display_name: _lt('Graph'),
25 init: function(parent, dataset, view_id, options) {
28 this.set_default_options(options);
29 this.dataset = dataset;
30 this.view_id = view_id;
32 this.mode = "bar"; // line, bar, area, pie, radar
33 this.orientation = false; // true: horizontal, false: vertical
36 this.spreadsheet = false; // Display data grid, allows copy to CSV
37 this.forcehtml = false;
38 this.legend = "top"; // top, inside, no
45 this.on('view_loaded', self, self.load_graph);
47 destroy: function () {
54 load_graph: function(fields_view_get) {
55 // TODO: move to load_view and document
57 this.fields_view = fields_view_get;
58 this.$el.addClass(this.fields_view.arch.attrs['class']);
60 this.mode = this.fields_view.arch.attrs.type || 'bar';
61 this.orientation = this.fields_view.arch.attrs.orientation == 'horizontal';
63 var width = this.$el.parent().width();
64 this.$el.css("width", width);
65 this.container = this.$el.find("#editor-render-body").css({
67 height: Math.min(500, width * 0.8)
70 var graph_render = this.proxy('graph_render');
71 this.$el.on('click', '.oe_graph_options a', function (evt) {
72 var $el = $(evt.target);
74 self.graph_render({data: filter_values({
75 mode: $el.data('mode'),
76 legend: $el.data('legend'),
77 orientation: $el.data('orientation'),
78 stacked: $el.data('stacked')
82 this.$el.find("#graph_show_data").click(function () {
83 self.spreadsheet = ! self.spreadsheet;
86 this.$el.find("#graph_switch").click(function () {
87 if (self.mode != 'radar') {
88 self.orientation = ! self.orientation;
93 this.$el.find("#graph_download").click(function () {
94 if (self.legend == "top") { self.legend = "inside"; }
95 self.forcehtml = true;
97 self.graph_get_data().then(function (result) {
98 self.graph_render_all(result).download.saveImage('png');
99 }).always(function () {
100 self.forcehtml = false;
103 this.trigger('graph_view_loaded', fields_view_get);
106 get_format: function (options) {
107 options = options || {};
109 show: this.legend != 'no',
112 switch (this.legend) {
114 legend.noColumns = 4;
115 legend.container = this.$el.find("div.graph_header_legend")[0];
118 legend.position = 'nw';
119 legend.backgroundColor = '#D2E8FF';
130 show: this.spreadsheet,
133 HtmlText : (options.xaxis && options.xaxis.labelsAngle) ? false : !this.forcehtml,
137 make_graph: function (mode, container, data) {
138 if (mode === 'area') { mode = 'line'; }
140 container, data.data,
141 this.get_format(this['options_' + mode](data)));
144 options_bar: function (data) {
145 var min = _(data.data).chain()
146 .map(function (record) {
147 if (record.data.length > 0){
148 return _.min(record.data, function (item) {
156 stacked : this.stacked,
157 horizontal : this.orientation,
162 verticalLines : this.orientation,
163 horizontalLines : !this.orientation,
167 ticks: this.orientation ? data.ticks : false,
168 min: !this.orientation ? (min < 0 ? min : 0) : null
172 ticks: this.orientation ? false : data.ticks,
173 min: this.orientation ? (min < 0 ? min : 0) : null
178 options_pie: function (data) {
184 verticalLines : false,
185 horizontalLines : false,
188 xaxis : {showLabels: false},
189 yaxis : {showLabels: false},
193 options_radar: function (data) {
197 stacked : this.stacked
201 minorHorizontalLines : true
209 options_line: function (data) {
213 stacked : this.stacked
216 verticalLines : this.orientation,
217 horizontalLines : !this.orientation,
221 ticks: this.orientation ? data.ticks : false
225 ticks: this.orientation ? false : data.ticks
230 graph_get_data: function () {
231 var model = this.dataset.model,
232 domain = new instance.web.CompoundDomain(this.domain || []),
233 context = new instance.web.CompoundContext(this.context || {}),
234 group_by = this.group_by || [],
235 view_id = this.view_id || false,
236 mode = this.mode || 'bar',
237 orientation = this.orientation || false,
238 stacked = this.stacked || false;
240 var obj = new instance.web.Model(model);
246 return obj.call("fields_view_get", [view_id, 'graph']).pipe(function(tmp) {
248 fields = view_get['fields'];
249 var toload = _.select(group_by, function(x) { return fields[x] === undefined });
250 if (toload.length >= 1)
251 return obj.call("fields_get", [toload, context]);
254 }).pipe(function (fields_to_add) {
255 _.extend(fields, fields_to_add);
257 var tree = $($.parseXML(view_get['arch']));
260 var xaxis = group_by || [];
262 tree.find("field").each(function() {
264 if (! field.attr("name"))
266 if ((group_by.length == 0) && ((! pos) || field.attr('group'))) {
267 xaxis.push(field.attr('name'));
269 if (pos && ! field.attr('group')) {
270 yaxis.push(field.attr('name'));
275 if (xaxis.length === 0)
276 throw new Error("No field for the X axis!");
277 if (yaxis.length === 0)
278 throw new Error("No field for the Y axis!");
280 // Convert a field's data into a displayable string
282 function _convert_key(field, data) {
283 if (fields[field]['type'] === 'many2one')
284 data = data && data[0];
288 function _convert(field, data, tick) {
289 tick = tick === undefined ? true : false;
290 if (fields[field]['type'] === 'many2one') {
291 data = data && data[1];
292 } else if ((fields[field]['type'] === 'selection') && (fields[field]['selection'] instanceof Array)) {
294 _.each(fields[field]['selection'], function(el) {
300 if (ticks[data] === undefined)
301 ticks[data] = _.size(ticks);
307 function _orientation(x, y) {
313 if (mode === "pie") {
314 return obj.call("read_group", [domain, yaxis.concat([xaxis[0]]), [xaxis[0]]], {context: context}).pipe(function(res) {
315 _.each(res, function(record) {
317 'data': [[_convert(xaxis[0], record[xaxis[0]]), record[yaxis[0]]]],
318 'label': _convert(xaxis[0], record[xaxis[0]], false)
322 } else if ((! stacked) || (xaxis.length < 2)) {
324 _.each(xaxis, function(x) {
325 defs.push(obj.call("read_group", [domain, yaxis.concat([x]), [x]], {context: context}).pipe(function(res) {
329 return $.when.apply($, defs).pipe(function() {
330 _.each(_.toArray(arguments), function(res) {
334 'data': _.map(res, function(record) {
335 return _orientation(_convert(x, record[x]), record[yaxis[0]] || 0);
337 'label': fields[x]['string']
343 return obj.call("read_group", [domain, yaxis.concat(xaxis.slice(0, 1)), xaxis.slice(0, 1)], {context: context}).pipe(function(axis) {
345 _.each(axis, function(x) {
346 var key = x[xaxis[0]]
347 defs.push(obj.call("read_group", [domain+[(xaxis[0],'=',_convert_key(xaxis[0], key))], yaxis.concat(xaxis.slice(1, 2)), xaxis.slice(1, 2)],
348 {context: context}).pipe(function(res) {
349 return [x, key, res];
352 return $.when.apply($, defs).pipe(function(res) {
357 'data': _.map(res, function(record) {
358 return _orientation(_convert(xaxis[1], record[xaxis[1]]), record[yaxis[0]] || 0);
360 'label': _convert(xaxis[0], key, false)
368 'ticks': _.map(ticks, function(el, key) { return [el, key] })
374 // Render the graph and update menu styles
375 graph_render: function (options) {
376 options = options || {};
377 _.extend(this, options.data);
379 return this.graph_get_data()
380 .then(this.proxy('graph_render_all'));
383 graph_render_all: function (data) {
385 if (this.mode=='area') {
386 for (i=0; i<data.data.length; i++) {
387 data.data[i].lines = {fill: true}
391 this.graph.destroy();
395 this.$el.find(".graph_header_legend").children().remove();
396 this.graph = this.make_graph(this.mode, this.container, data);
398 // Update styles of menus
400 this.$el.find("a").removeClass("active");
402 var $active = this.$el.find('a[data-mode=' + this.mode + ']');
403 if ($active.length > 1) {
404 $active = $active.filter('[data-stacked=' + this.stacked + ']');
406 $active = $active.add(
407 this.$el.find('a:not([data-mode])[data-legend=' + this.legend + ']'));
409 $active.addClass('active');
411 if (this.spreadsheet) {
412 this.$el.find("#graph_show_data").addClass("active");
417 // render the graph using the domain, context and group_by
418 // calls the 'graph_data_get' python controller to process all data
419 // TODO: check is group_by should better be in the context
420 do_search: function(domain, context, group_by) {
421 this.domain = domain;
422 this.context = context;
423 this.group_by = group_by;
428 do_show: function() {
429 this.do_push_state({});
430 return this._super();