diff options
| author | Géry Debongnie <ged@odoo.com> | 2015-03-09 16:34:27 +0100 |
|---|---|---|
| committer | Géry Debongnie <ged@odoo.com> | 2015-03-18 09:01:49 +0100 |
| commit | e8a00bc50da435d3859118ca127d20a2eb9a0cab (patch) | |
| tree | 039f6ec40b37392e76c8830a8e518ac40f7e7fec /addons/web/static/src/js/views/graph_widget.js | |
| parent | 9036084f065c7aca3b2199903f4da67db3501ba7 (diff) | |
[REF] web: add module system to the web client
This commit introduces a new module system for the javascript code.
Instead of using global (namespaced) variables everywhere (like
openerp.web.whatever, ...), each file declare one (or more) module
with its dependencies. Each module will be loaded after its
dependencies, and will be given a reference to the return values.
This change is a massive departure from the way the client worked
previously. It will sadly break some/most history, but I am sure that
the future git ecosystem will allow you to easily browse the history
past this revision (one can dream).
This commit feature:
* main namespace is now odoo instead of openerp
* a way to define modules and dependencies (inspired by requirejs)
* many files have been splitted in simpler subfiles
* basic support for debugging in odoo.__DEBUG__
* no global variable (i.e. nothing in window.openerp)
* many bug fixes, many new bugs
Future vision: this modularization should simplify future code
development, simplify debugging and will allow a serious testing effort
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(); + } +}); + +}); |
