summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/views/graph/graph_model.js
diff options
context:
space:
mode:
authorMathieu Duckerts-Antoine <dam@odoo.com>2019-02-21 14:29:26 +0000
committerMathieu Duckerts-Antoine <dam@odoo.com>2019-04-01 14:40:51 +0000
commita9bcb12f6419aaf087bad4c8a3f7ed7c00dbf3c5 (patch)
tree5f25467a6a22ab75db6077eb35bf1b7ff83408a6 /addons/web/static/src/js/views/graph/graph_model.js
parentd23419fd86d5f8266c37526176c1bf79a09a1684 (diff)
[IMP] web: graph view improvements
This commit extends the support of comparison mode for the graph view and refine the tooltips and rendering in line mode. The graph view code has also been slightly refactored. Task ID: 1946138
Diffstat (limited to 'addons/web/static/src/js/views/graph/graph_model.js')
-rw-r--r--addons/web/static/src/js/views/graph/graph_model.js172
1 files changed, 89 insertions, 83 deletions
diff --git a/addons/web/static/src/js/views/graph/graph_model.js b/addons/web/static/src/js/views/graph/graph_model.js
index 91b017524d8..33b8896b29d 100644
--- a/addons/web/static/src/js/views/graph/graph_model.js
+++ b/addons/web/static/src/js/views/graph/graph_model.js
@@ -1,11 +1,13 @@
odoo.define('web.GraphModel', function (require) {
"use strict";
+var core = require('web.core');
+var _t = core._t;
+
/**
* The graph model is responsible for fetching and processing data from the
- * server. It basically just do a read_group and format/normalize data.
+ * server. It basically just do a(some) read_group(s) and format/normalize data.
*/
-
var AbstractModel = require('web.AbstractModel');
return AbstractModel.extend({
@@ -23,18 +25,12 @@ return AbstractModel.extend({
//--------------------------------------------------------------------------
/**
- * We defend against outside modifications by extending the chart data. It
- * may be overkill.
- *
- * @todo Adding the fields parameter looks wrong. If the renderer or the
- * controller need the fields, they should get it via their init method.
+ * override
*
* @returns {Object}
*/
get: function () {
- return _.extend({}, this.chart, {
- fields: this.fields
- });
+ return this.chart;
},
/**
* Initial loading.
@@ -43,13 +39,19 @@ return AbstractModel.extend({
* should be done by the graphView I think.
*
* @param {Object} params
- * @param {string} params.mode one of 'pie', 'bar', 'line
- * @param {string} params.measure a valid field name
- * @param {string[]} params.groupBys a list of valid field names
+ * @param {boolean} params.compare
* @param {Object} params.context
+ * @param {Object} params.fields
+ * @param {string[]} params.comparisonTimeRange
* @param {string[]} params.domain
- * @param {Object} params.intervalMapping object linking fieldNames with intervals.
- * this could be useful to simplify the code. For now this parameter is not used.
+ * @param {string[]} params.groupBy a list of valid field names
+ * @param {string[]} params.timeRange
+ * @param {string} params.comparisonField
+ * @param {string} params.comparisonTimeRangeDescription
+ * @param {string} params.measure a valid field name
+ * @param {'pie'|'bar'|'line'} params.mode
+ * @param {string} params.modelName
+ * @param {string} params.timeRangeDescription
* @returns {Promise} The promise does not return a handle, we don't need
* to keep track of various entities.
*/
@@ -59,25 +61,21 @@ return AbstractModel.extend({
this.fields = params.fields;
this.modelName = params.modelName;
this.chart = {
- compare: params.compare,
+ comparisonField: params.comparisonField,
comparisonTimeRange: params.comparisonTimeRange,
comparisonTimeRangeDescription: params.comparisonTimeRangeDescription,
- data: [],
- groupedBy: params.groupedBy.length ? params.groupedBy : groupBys,
- // this parameter is not used anywhere for now.
- // the idea would be to seperate intervals from
- // fieldnames in groupbys. This could be done
- // in graph view only or everywhere but this is
- // a big refactoring.
- intervalMapping: params.intervalMapping,
+ compare: params.compare,
+ context: params.context,
+ dataPoints: [],
+ domain: params.domain,
+ groupBy: params.groupedBy.length ? params.groupedBy : groupBys,
measure: params.context.graph_measure || params.measure,
mode: params.context.graph_mode || params.mode,
+ origins: [],
timeRange: params.timeRange,
timeRangeDescription: params.timeRangeDescription,
- domain: params.domain,
- context: params.context,
};
- return this._loadGraph();
+ return this._loadGraph(this._getDomains());
},
/**
* Reload data. It is similar to the load function. Note that we ignore the
@@ -88,31 +86,33 @@ return AbstractModel.extend({
*
* @param {any} handle ignored!
* @param {Object} params
+ * @param {Object} [params.context]
* @param {string[]} [params.domain]
* @param {string[]} [params.groupBy]
- * @param {string} [params.mode] one of 'bar', 'pie', 'line'
* @param {string} [params.measure] a valid field name
+ * @param {string} [params.mode] one of 'bar', 'pie', 'line'
* @returns {Promise}
*/
reload: function (handle, params) {
if ('context' in params) {
this.chart.context = params.context;
- this.chart.groupedBy = params.context.graph_groupbys || this.chart.groupedBy;
+ this.chart.groupBy = params.context.graph_groupbys || this.chart.groupBy;
this.chart.measure = params.context.graph_measure || this.chart.measure;
this.chart.mode = params.context.graph_mode || this.chart.mode;
var timeRangeMenuData = params.context.timeRangeMenuData;
if (timeRangeMenuData) {
+ this.chart.comparisonField = timeRangeMenuData.comparisonField || undefined;
this.chart.comparisonTimeRange = timeRangeMenuData.comparisonTimeRange || [];
this.chart.compare = this.chart.comparisonTimeRange.length > 0;
- this.chart.timeRange = timeRangeMenuData.timeRange || [];
this.chart.comparisonTimeRangeDescription = timeRangeMenuData.comparisonTimeRangeDescription;
+ this.chart.timeRange = timeRangeMenuData.timeRange || [];
this.chart.timeRangeDescription = timeRangeMenuData.timeRangeDescription;
} else {
- this.chart = _.omit(this.chart, 'comparisonData');
+ this.chart.comparisonField = undefined;
this.chart.comparisonTimeRange = [];
this.chart.compare = false;
- this.chart.timeRange = [];
this.chart.comparisonTimeRangeDescription = undefined;
+ this.chart.timeRange = [];
this.chart.timeRangeDescription = undefined;
}
}
@@ -120,10 +120,7 @@ return AbstractModel.extend({
this.chart.domain = params.domain;
}
if ('groupBy' in params) {
- this.chart.groupedBy = params.groupBy.length ? params.groupBy : this.initialGroupBys;
- }
- if ('intervalMapping' in params) {
- this.chart.intervalMapping = params.intervalMapping;
+ this.chart.groupBy = params.groupBy.length ? params.groupBy : this.initialGroupBys;
}
if ('measure' in params) {
this.chart.measure = params.measure;
@@ -132,7 +129,7 @@ return AbstractModel.extend({
this.chart.mode = params.mode;
return Promise.resolve();
}
- return this._loadGraph();
+ return this._loadGraph(this._getDomains());
},
//--------------------------------------------------------------------------
@@ -140,16 +137,32 @@ return AbstractModel.extend({
//--------------------------------------------------------------------------
/**
- * Fetch and process graph data. It is basically a read_group with correct
- * fields. We have to do some light processing to separate date groups
- * in the field list, because they can be defined with an aggregation
- * function, such as my_date:week
+ * @returns {Object[]}
+ */
+ _getDomains: function () {
+ var domains = [this.chart.domain.concat(this.chart.timeRange)];
+ this.chart.origins = [this.chart.timeRangeDescription];
+ if (this.chart.compare) {
+ domains.push(this.chart.domain.concat(this.chart.comparisonTimeRange));
+ this.chart.origins.push(this.chart.comparisonTimeRangeDescription);
+ }
+ return domains;
+ },
+
+ /**
+ * Fetch and process graph data. It is basically a(some) read_group(s)
+ * with correct fields for each domain. We have to do some light processing
+ * to separate date groups in the field list, because they can be defined
+ * with an aggregation function, such as my_date:week.
*
+ * @param {Array[]} domains
* @returns {Promise}
*/
- _loadGraph: function () {
- var groupedBy = this.chart.groupedBy;
- var fields = _.map(groupedBy, function (groupBy) {
+ _loadGraph: function (domains) {
+ var self = this;
+ this.chart.dataPoints = [];
+ var groupBy = this.chart.groupBy;
+ var fields = _.map(groupBy, function (groupBy) {
return groupBy.split(':')[0];
});
@@ -164,55 +177,46 @@ return AbstractModel.extend({
var context = _.extend({fill_temporal: true}, this.chart.context);
var defs = [];
- defs.push(this._rpc({
- model: this.modelName,
- method: 'read_group',
- context: context,
- domain: this.chart.domain.concat(this.chart.timeRange),
- fields: fields,
- groupBy: groupedBy,
- lazy: false,
- }).then(this._processData.bind(this, 'data')));
- if (this.chart.compare) {
- defs.push(this._rpc({
- model: this.modelName,
+ domains.forEach(function (domain, originIndex) {
+ defs.push(self._rpc({
+ model: self.modelName,
method: 'read_group',
context: context,
- domain: this.chart.domain.concat(this.chart.comparisonTimeRange),
+ domain: domain,
fields: fields,
- groupBy: groupedBy,
+ groupBy: groupBy,
lazy: false,
- }).then(this._processData.bind(this, 'comparisonData')));
- }
-
+ }).then(self._processData.bind(self, originIndex)));
+ });
return Promise.all(defs);
},
/**
* Since read_group is insane and returns its result on different keys
* depending of some input, we have to normalize the result.
- * The final chart data is added to this.chart object.
+ * Each group comming from the read_group produces a dataPoint
*
* @todo This is not good for race conditions. The processing should get
* the object this.chart in argument, or an array or something. We want to
* avoid writing on a this.chart object modified by a subsequent read_group
*
- * @param {String} dataKey
- * @param {any} raw_data result from the read_group
+ * @param {number} originIndex
+ * @param {any} rawData result from the read_group
*/
- _processData: function (dataKey, raw_data) {
+ _processData: function (originIndex, rawData) {
var self = this;
- var is_count = this.chart.measure === '__count__';
- var data_pt, labels;
+ var isCount = this.chart.measure === '__count__';
+ var labels;
- this.chart[dataKey] = [];
- for (var i = 0; i < raw_data.length; i++) {
- data_pt = raw_data[i];
- labels = _.map(this.chart.groupedBy, function (field) {
- return self._sanitizeValue(data_pt[field], field);
+ function getLabels (dataPt) {
+ return self.chart.groupBy.map(function (field) {
+ return self._sanitizeValue(dataPt[field], field.split(":")[0]);
});
- var count = data_pt.__count || data_pt[this.chart.groupedBy[0]+'_count'] || 0;
- var value = is_count ? count : data_pt[this.chart.measure];
+ }
+ rawData.forEach(function (dataPt){
+ labels = getLabels(dataPt);
+ var count = dataPt.__count || dataPt[self.chart.groupBy[0]+'_count'] || 0;
+ var value = isCount ? count : dataPt[self.chart.measure];
if (value instanceof Array) {
// when a many2one field is used as a measure AND as a grouped
// field, bad things happen. The server will only return the
@@ -223,28 +227,30 @@ return AbstractModel.extend({
// value for that field.
value = 1;
}
- this.chart[dataKey].push({
+ self.chart.dataPoints.push({
count: count,
value: value,
labels: labels,
+ originIndex: originIndex,
});
- }
+ });
},
/**
* Helper function (for _processData), turns various values in a usable
* string form, that we can display in the interface.
*
- * @param {any} value some value received by the read_group rpc
- * @param {string} field the name of the corresponding field
+ * @param {any} value value for the field fieldName received by the read_group rpc
+ * @param {string} fieldName
* @returns {string}
*/
- _sanitizeValue: function (value, field) {
- var fieldName = field.split(':')[0];
+ _sanitizeValue: function (value, fieldName) {
if (value === false && this.fields[fieldName].type !== 'boolean') {
- return undefined;
+ return _t("Undefined");
+ }
+ if (value instanceof Array) {
+ return value[1];
}
- if (value instanceof Array) return value[1];
- if (field && (this.fields[fieldName].type === 'selection')) {
+ if (fieldName && (this.fields[fieldName].type === 'selection')) {
var selected = _.where(this.fields[fieldName].selection, {0: value})[0];
return selected ? selected[1] : value;
}