diff options
Diffstat (limited to 'addons/web/static/src/js/views/graph_widget.js')
| -rw-r--r-- | addons/web/static/src/js/views/graph_widget.js | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/addons/web/static/src/js/views/graph_widget.js b/addons/web/static/src/js/views/graph_widget.js new file mode 100644 index 00000000000..b06ab42f346 --- /dev/null +++ b/addons/web/static/src/js/views/graph_widget.js @@ -0,0 +1,303 @@ +odoo.define('web.GraphWidget', ['web.core', 'web.Model', 'web.Widget'], function (require) { +"use strict"; + +var core = require('web.core'); +var Model = require('web.Model'); +var Widget = require('web.Widget'); + +var _t = core._t; +var QWeb = core.qweb; + +return Widget.extend({ + init: function (parent, model, options) { + this._super(parent); + this.context = options.context; + this.fields = options.fields; + this.fields.__count__ = _t("Quantity"); + this.model = new Model(model, {group_by_no_leaf: true}); + + this.domain = options.domain || []; + this.groupbys = options.groupbys || []; + this.mode = options.mode || "bar"; + this.measure = options.measure || "__count__"; + }, + start: function () { + return this.load_data().then(this.proxy('display_graph')); + }, + update_data: function (domain, groupbys) { + this.domain = domain; + this.groupbys = groupbys; + return this.load_data().then(this.proxy('display_graph')); + }, + set_mode: function (mode) { + this.mode = mode; + this.display_graph(); + }, + set_measure: function (measure) { + this.measure = measure; + return this.load_data().then(this.proxy('display_graph')); + }, + load_data: function () { + var fields = this.groupbys.slice(0); + if (this.measure !== '__count__'.slice(0)) + fields = fields.concat(this.measure); + return this.model + .query(fields) + .filter(this.domain) + .context(this.context) + .lazy(false) + .group_by(this.groupbys.slice(0,2)) + .then(this.proxy('prepare_data')); + }, + prepare_data: function () { + var raw_data = arguments[0], + is_count = this.measure === '__count__'; + var data_pt, j, values, value; + + this.data = []; + for (var i = 0; i < raw_data.length; i++) { + data_pt = raw_data[i].attributes; + values = []; + if (this.groupbys.length === 1) data_pt.value = [data_pt.value]; + for (j = 0; j < data_pt.value.length; j++) { + values[j] = this.sanitize_value(data_pt.value[j], data_pt.grouped_on[j]); + } + value = is_count ? data_pt.length : data_pt.aggregates[this.measure]; + this.data.push({ + labels: values, + value: value + }); + } + }, + sanitize_value: function (value, field) { + if (value === false) return _t("Undefined"); + if (value instanceof Array) return value[1]; + if (field && this.fields[field] && (this.fields[field].type === 'selection')) { + var selected = _.where(this.fields[field].selection, {0: value})[0]; + return selected ? selected[1] : value; + } + return value; + }, + display_graph: function () { + if (this.to_remove) { + nv.utils.offWindowResize(this.to_remove); + } + this.$el.empty(); + if (!this.data.length) { + this.$el.append(QWeb.render('GraphView.error', { + title: _t("No data to display"), + description: _t("No data available for this chart. " + + "Try to add some records, or make sure that" + + "there is no active filter in the search bar."), + })); + } else { + this['display_' + this.mode](); + } + }, + display_bar: function () { + // prepare data for bar chart + var data, values, + measure = this.fields[this.measure].string; + // zero groupbys + if (this.groupbys.length === 0) { + data = [{ + values: [{ + x: this.ViewManager.title, + y: this.data[0].value}], + key: measure + }]; + } + // one groupby + if (this.groupbys.length === 1) { + values = this.data.map(function (datapt) { + return {x: datapt.labels, y: datapt.value}; + }); + data = [ + { + values: values, + key: measure, + } + ]; + } + if (this.groupbys.length > 1) { + var xlabels = [], + series = [], + label, serie, value; + values = {}; + for (var i = 0; i < this.data.length; i++) { + label = this.data[i].labels[0]; + serie = this.data[i].labels[1]; + value = this.data[i].value; + if ((!xlabels.length) || (xlabels[xlabels.length-1] !== label)) { + xlabels.push(label); + } + series.push(this.data[i].labels[1]); + if (!(serie in values)) {values[serie] = {};} + values[serie][label] = this.data[i].value; + } + series = _.uniq(series); + data = []; + var current_serie, j; + for (i = 0; i < series.length; i++) { + current_serie = {values: [], key: series[i]}; + for (j = 0; j < xlabels.length; j++) { + current_serie.values.push({ + x: xlabels[j], + y: values[series[i]][xlabels[j]] || 0, + }); + } + data.push(current_serie); + } + } + var svg = d3.select(this.$el[0]).append('svg'); + svg.datum(data); + + svg.transition().duration(0); + + var chart = nv.models.multiBarChart(); + chart.options({ + delay: 250, + transitionDuration: 10, + showLegend: true, + showXAxis: true, + showYAxis: true, + rightAlignYAxis: false, + stacked: true, + reduceXTicks: false, + // rotateLabels: 40, + showControls: (this.groupbys.length > 1) + }); + + chart(svg); + this.to_remove = chart.update; + nv.utils.onWindowResize(chart.update); + }, + display_pie: function () { + var data = [], + all_negative = true, + some_negative = false, + all_zero = true; + + this.data.forEach(function (datapt) { + all_negative = all_negative && (datapt.value < 0); + some_negative = some_negative || (datapt.value < 0); + all_zero = all_zero && (datapt.value === 0); + }); + if (some_negative && !all_negative) { + return this.$el.append(QWeb.render('GraphView.error', { + title: _t("Invalid data"), + description: _t("Pie chart cannot mix positive and negative numbers. " + + "Try to change your domain to only display positive results"), + })); + } + if (all_zero) { + return this.$el.append(QWeb.render('GraphView.error', { + title: _t("Invalid data"), + description: _t("Pie chart cannot display all zero numbers.. " + + "Try to change your domain to display positive results"), + })); + } + if (this.groupbys.length) { + data = this.data.map(function (datapt) { + return {x:datapt.labels.join("/"), y: datapt.value}; + }); + } + var svg = d3.select(this.$el[0]).append('svg'); + svg.datum(data); + + svg.transition().duration(100); + + var chart = nv.models.pieChart(); + chart.options({ + delay: 250, + transitionDuration: 100, + color: d3.scale.category10().range(), + }); + + chart(svg); + this.to_remove = chart.update; + nv.utils.onWindowResize(chart.update); + }, + display_line: function () { + if (this.data.length < 2) { + this.$el.append(QWeb.render('GraphView.error', { + title: _t("Not enough data points"), + description: "You need at least two data points to display a line chart." + })); + return; + } + var self = this, + data = [], + tickValues, + tickFormat, + measure = this.fields[this.measure].string; + if (this.groupbys.length === 1) { + var values = this.data.map(function (datapt, index) { + return {x: index, y: datapt.value}; + }); + data = [ + { + values: values, + key: measure, + } + ]; + tickValues = this.data.map(function (d, i) { return i;}); + tickFormat = function (d) {return self.data[d].labels;}; + } + if (this.groupbys.length > 1) { + data = []; + var data_dict = {}, + tick = 0, + tickLabels = [], + serie, tickLabel, + identity = function (p) {return p;}; + tickValues = []; + for (var i = 0; i < this.data.length; i++) { + if (this.data[i].labels[0] !== tickLabel) { + tickLabel = this.data[i].labels[0]; + tickValues.push(tick); + tickLabels.push(tickLabel); + tick++; + } + serie = this.data[i].labels[1]; + if (!data_dict[serie]) { + data_dict[serie] = { + values: [], + key: serie, + }; + } + data_dict[serie].values.push({ + x: tick, y: this.data[i].value, + }); + data = _.map(data_dict, identity); + } + tickFormat = function (d) {return tickLabels[d];}; + } + + var svg = d3.select(this.$el[0]).append('svg'); + svg.datum(data); + + svg.transition().duration(0); + + var chart = nv.models.lineChart(); + chart.options({ + margin: {left: 50, right: 50}, + useInteractiveGuideline: true, + showLegend: true, + showXAxis: true, + showYAxis: true, + }); + chart.xAxis.tickValues(tickValues) + .tickFormat(tickFormat); + + chart(svg); + this.to_remove = chart.update; + nv.utils.onWindowResize(chart.update); + }, + destroy: function () { + nv.utils.offWindowResize(this.to_remove); + return this._super(); + } +}); + +}); |
