/*--------------------------------------------------------- * OpenERP web_graph *---------------------------------------------------------*/ openerp.web_graph = function (instance) { var _lt = instance.web._lt; var _t = instance.web._t; // removed ``undefined`` values var filter_values = function (o) { var out = {}; for (var k in o) { if (!o.hasOwnProperty(k) || o[k] === undefined) { continue; } out[k] = o[k]; } return out; }; instance.web.views.add('graph', 'instance.web_graph.GraphView'); instance.web_graph.GraphView = instance.web.View.extend({ template: "GraphView", display_name: _lt('Graph'), view_type: "graph", init: function(parent, dataset, view_id, options) { var self = this; this._super(parent); this.set_default_options(options); this.dataset = dataset; this.view_id = view_id; this.mode = "bar"; // line, bar, area, pie, radar this.orientation = false; // true: horizontal, false: vertical this.stacked = true; this.spreadsheet = false; // Display data grid, allows copy to CSV this.forcehtml = false; this.legend = "top"; // top, inside, no this.domain = []; this.context = {}; this.group_by = []; this.graph = null; }, view_loading: function(r) { return this.load_graph(r); }, destroy: function () { if (this.graph) { this.graph.destroy(); } this._super(); }, load_graph: function(fields_view_get) { // TODO: move to load_view and document var self = this; this.fields_view = fields_view_get; this.$el.addClass(this.fields_view.arch.attrs['class']); this.mode = this.fields_view.arch.attrs.type || 'bar'; this.orientation = this.fields_view.arch.attrs.orientation == 'horizontal'; var width = this.$el.parent().width(); this.$el.css("width", width); this.container = this.$el.find("#editor-render-body").css({ width: width, height: Math.min(500, width * 0.8) })[0]; var graph_render = this.proxy('graph_render'); this.$el.on('click', '.oe_graph_options a', function (evt) { var $el = $(evt.target); self.graph_render({data: filter_values({ mode: $el.data('mode'), legend: $el.data('legend'), orientation: $el.data('orientation'), stacked: $el.data('stacked') })}); }); this.$el.find("#graph_show_data").click(function () { self.spreadsheet = ! self.spreadsheet; self.graph_render(); }); this.$el.find("#graph_switch").click(function () { if (self.mode != 'radar') { self.orientation = ! self.orientation; } self.graph_render(); }); this.$el.find("#graph_download").click(function () { if (self.legend == "top") { self.legend = "inside"; } self.forcehtml = true; self.graph_get_data().done(function (result) { self.graph_render_all(result).download.saveImage('png'); }).always(function () { self.forcehtml = false; }); }); this.trigger('graph_view_loaded', fields_view_get); }, get_format: function (options) { options = options || {}; var legend = { show: this.legend != 'no', }; switch (this.legend) { case 'top': legend.noColumns = 4; legend.container = this.$el.find("div.graph_header_legend")[0]; break; case 'inside': legend.position = 'nw'; legend.backgroundColor = '#D2E8FF'; break; } return _.extend({ legend: legend, mouse: { track: true, relative: true }, spreadsheet : { show: this.spreadsheet, initialTab: "data" }, HtmlText : (options.xaxis && options.xaxis.labelsAngle) ? false : !this.forcehtml, }, options); }, make_graph: function (mode, container, data) { if (mode === 'area') { mode = 'line'; } var format = this.get_format(this['options_' + mode](data)); return Flotr.draw(container, data.data, format); }, options_bar: function (data) { var min = _(data.data).chain() .map(function (record) { if (record.data.length > 0){ return _.min(record.data, function (item) { return item[1]; })[1]; } }).min().value(); return { bars : { show : true, stacked : this.stacked, horizontal : this.orientation, barWidth : 0.7, lineWidth : 1 }, grid : { verticalLines : this.orientation, horizontalLines : !this.orientation, outline : "sw", }, yaxis : { ticks: this.orientation ? data.ticks : false, min: !this.orientation ? (min < 0 ? min : 0) : null }, xaxis : { labelsAngle: 45, ticks: this.orientation ? false : data.ticks, min: this.orientation ? (min < 0 ? min : 0) : null } }; }, options_pie: function (data) { return { pie : { show: true }, grid : { verticalLines : false, horizontalLines : false, outline : "", }, xaxis : {showLabels: false}, yaxis : {showLabels: false}, }; }, options_radar: function (data) { return { radar : { show : true, stacked : this.stacked }, grid : { circular : true, minorHorizontalLines : true }, xaxis : { ticks: data.ticks }, }; }, options_line: function (data) { return { lines : { show : true, }, points: { show: true, }, grid : { verticalLines : this.orientation, horizontalLines : !this.orientation, outline : "sw", }, yaxis : { ticks: this.orientation ? data.ticks : false }, xaxis : { labelsAngle: 45, ticks: this.orientation ? false : data.ticks } }; }, graph_get_data: function () { var model = this.dataset.model, domain = new instance.web.CompoundDomain(this.domain || []), context = new instance.web.CompoundContext(this.context || {}), group_by = this.group_by || [], view_id = this.view_id || false, mode = this.mode || 'bar', orientation = this.orientation || false, stacked = this.stacked || false; var obj = new instance.web.Model(model); var view_get; var fields; var result = []; var ticks = {}; return this.alive(obj.call("fields_view_get", [view_id, 'graph', context]).then(function(tmp) { view_get = tmp; fields = view_get['fields']; var toload = _.select(group_by, function(x) { return fields[x] === undefined }); if (toload.length >= 1) return obj.call("fields_get", [toload, context]); else return $.when([]); }).then(function (fields_to_add) { _.extend(fields, fields_to_add); var tree = $($.parseXML(view_get['arch'])); var pos = 0; var xaxis = _.clone(group_by || []); var yaxis = []; tree.find("field").each(function() { var field = $(this); if (! field.attr("name")) return; if ((group_by.length == 0) && ((! pos) || instance.web.py_eval(field.attr('group') || "false"))) { xaxis.push(field.attr('name')); } if (pos && ! instance.web.py_eval(field.attr('group') || "false")) { yaxis.push(field.attr('name')); } pos += 1; }); if (xaxis.length === 0) throw new Error("No field for the X axis!"); if (yaxis.length === 0) throw new Error("No field for the Y axis!"); // Convert a field's data into a displayable string function _convert_key(field, data) { if (fields[field]['type'] === 'many2one') data = data && data[0]; return data; } function _convert(field, data, tick) { tick = tick === undefined ? true : false; try { data = instance.web.format_value(data, fields[field]); } catch(e) { data = "" + data; } if (tick) { if (ticks[data] === undefined) ticks[data] = _.size(ticks); return ticks[data]; } return data || 0; } function _orientation(x, y) { if (! orientation) return [x, y] return [y, x] } if (mode === "pie") { return obj.call("read_group", [domain, yaxis.concat([xaxis[0]]), [xaxis[0]]], {context: context}).then(function(res) { _.each(res, function(record) { result.push({ 'data': [[_convert(xaxis[0], record[xaxis[0]]), record[yaxis[0]]]], 'label': _convert(xaxis[0], record[xaxis[0]], false) }); }); }); } else if ((! stacked) || (xaxis.length < 2)) { var defs = []; _.each(xaxis, function(x) { defs.push(obj.call("read_group", [domain, yaxis.concat([x]), [x]], {context: context}).then(function(res) { return [x, res]; })); }); return $.when.apply($, defs).then(function() { _.each(_.toArray(arguments), function(res) { var x = res[0]; res = res[1]; result.push({ 'data': _.map(res, function(record) { return _orientation(_convert(x, record[x]), record[yaxis[0]] || 0); }), 'label': fields[x]['string'] }); }); }); } else { xaxis.reverse(); return obj.call("read_group", [domain, yaxis.concat(xaxis.slice(0, 1)), xaxis.slice(0, 1)], {context: context}).then(function(axis) { var defs = []; _.each(axis, function(x) { var key = x[xaxis[0]] defs.push(obj.call("read_group", [x['__domain'], yaxis.concat(xaxis.slice(1, 2)), xaxis.slice(1, 2)], {context: context}).then(function(res) { return [x, key, res]; })); }); return $.when.apply($, defs).then(function() { _.each(_.toArray(arguments), function(res) { var x = res[0]; var key = res[1]; res = res[2]; result.push({ 'data': _.map(res, function(record) { return _orientation(_convert(xaxis[1], record[xaxis[1]]), record[yaxis[0]] || 0); }), 'label': _convert(xaxis[0], key, false) }) }); }); }); } }).then(function() { var res = { 'data': result, 'ticks': _.map(ticks, function(el, key) { return [el, key || _t("Undefined")] }) }; return res; })); }, // Render the graph and update menu styles graph_render: function (options) { options = options || {}; _.extend(this, options.data); return this.graph_get_data() .done(this.proxy('graph_render_all')); }, graph_render_all: function (data) { var i; if (this.mode=='area') { for (i=0; i 1) { $active = $active.filter('[data-stacked=' + this.stacked + ']'); } $active = $active.add( this.$el.find('a:not([data-mode])[data-legend=' + this.legend + ']')); $active.addClass('active'); if (this.spreadsheet) { this.$el.find("#graph_show_data").addClass("active"); } return this.graph; }, // render the graph using the domain, context and group_by // calls the 'graph_data_get' python controller to process all data // TODO: check is group_by should better be in the context do_search: function(domain, context, group_by) { this.domain = domain; this.context = context; this.group_by = group_by; this.graph_render(); }, do_show: function() { this.do_push_state({}); return this._super(); }, }); };