summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/views/graph
diff options
context:
space:
mode:
authorAdrien Dieudonne <adr@odoo.com>2018-07-12 08:27:35 +0200
committerMathieu Duckerts-Antoine <dam@odoo.com>2018-08-14 12:37:36 +0200
commit89931d1749e35231cc054428e1c43801d5ef3ffa (patch)
treeb1451371b2097dac1a12ee2e47319ad779898903 /addons/web/static/src/js/views/graph
parentd4461cd4340274fdd284ecfeb451de7760ce40e2 (diff)
[IMP] web: comparisons in graph view
A previous commit [333ac27] has introduced a new widget TimeRangeMenu that allows to create two sets of data to compare relatively to a given date or datetime field. This commit allows the graph view to render appropriately two sets of data as would be expected in the different modes. Task Id: 1835644
Diffstat (limited to 'addons/web/static/src/js/views/graph')
-rw-r--r--addons/web/static/src/js/views/graph/graph_model.js32
-rw-r--r--addons/web/static/src/js/views/graph/graph_renderer.js156
-rw-r--r--addons/web/static/src/js/views/graph/graph_view.js2
3 files changed, 134 insertions, 56 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 6d50dd8c269..fa9a7180e06 100644
--- a/addons/web/static/src/js/views/graph/graph_model.js
+++ b/addons/web/static/src/js/views/graph/graph_model.js
@@ -62,6 +62,8 @@ return AbstractModel.extend({
this.fields = params.fields;
this.modelName = params.modelName;
this.chart = {
+ compare: params.compare,
+ comparisonTimeRange: params.comparisonTimeRange,
data: [],
groupedBy: params.groupedBy.length ? params.groupedBy : groupBys,
// this parameter is not used anywhere for now.
@@ -72,6 +74,7 @@ return AbstractModel.extend({
intervalMapping: params.intervalMapping,
measure: params.context.graph_measure || params.measure,
mode: params.context.graph_mode || params.mode,
+ timeRange: params.timeRange,
domain: params.domain,
context: params.context,
};
@@ -146,16 +149,30 @@ return AbstractModel.extend({
}
var context = _.extend({fill_temporal: true}, this.chart.context);
- return this._rpc({
+ 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,
method: 'read_group',
context: context,
- domain: this.chart.domain,
+ domain: this.chart.domain.concat(this.chart.comparisonTimeRange),
fields: fields,
groupBy: groupedBy,
lazy: false,
- })
- .then(this._processData.bind(this));
+ }).then(this._processData.bind(this, 'comparisonData')));
+ }
+
+ return $.when.apply($, defs);
},
/**
* Since read_group is insane and returns its result on different keys
@@ -166,14 +183,15 @@ return AbstractModel.extend({
* 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
*/
- _processData: function (raw_data) {
+ _processData: function (dataKey, raw_data) {
var self = this;
var is_count = this.chart.measure === '__count__';
var data_pt, labels;
- this.chart.data = [];
+ this.chart[dataKey] = [];
for (var i = 0; i < raw_data.length; i++) {
data_pt = raw_data[i];
labels = _.map(this.chart.groupedBy, function (field) {
@@ -191,7 +209,7 @@ return AbstractModel.extend({
// value for that field.
value = 1;
}
- this.chart.data.push({
+ this.chart[dataKey].push({
count: count,
value: value,
labels: labels,
diff --git a/addons/web/static/src/js/views/graph/graph_renderer.js b/addons/web/static/src/js/views/graph/graph_renderer.js
index d58bf2fcdf6..9f6fe65cd5a 100644
--- a/addons/web/static/src/js/views/graph/graph_renderer.js
+++ b/addons/web/static/src/js/views/graph/graph_renderer.js
@@ -23,7 +23,7 @@ var CHART_TYPES = ['pie', 'bar', 'line'];
var MAX_LEGEND_LENGTH = 25 * (Math.max(1, config.device.size_class));
return AbstractRenderer.extend({
- className: "o_graph_svg_container",
+ className: "o_graph_container",
/**
* @override
* @param {Widget} parent
@@ -33,7 +33,8 @@ return AbstractRenderer.extend({
*/
init: function (parent, state, params) {
this._super.apply(this, arguments);
- this.stacked = params.stacked;
+ this.isComparison = !!state.comparisonData;
+ this.stacked = this.isComparison ? false : params.stacked;
},
/**
* @override
@@ -117,13 +118,13 @@ return AbstractRenderer.extend({
_renderBarChart: function () {
// prepare data for bar chart
var self = this;
- var data, values;
+ var data = [];
+ var values;
var measure = this.state.fields[this.state.measure].string;
// undefined label value becomes a string 'Undefined' translated
this.state.data.forEach(self._sanitizeLabel);
- // zero groupbys
if (this.state.groupedBy.length === 0) {
data = [{
values: [{
@@ -131,20 +132,29 @@ return AbstractRenderer.extend({
y: this.state.data[0].value}],
key: measure
}];
- }
- // one groupby
- if (this.state.groupedBy.length === 1) {
- values = this.state.data.map(function (datapt) {
- return {x: datapt.labels, y: datapt.value};
+ } else if (this.state.groupedBy.length === 1) {
+ values = this.state.data.map(function (datapt, index) {
+ if (self.state.comparisonData) {
+ return {x: index, y: datapt.value};
+ } else {
+ return {x: datapt.labels, y: datapt.value};
+ }
+ });
+ data.push({
+ values: values,
+ key: measure,
});
- data = [
- {
+ if (this.state.comparisonData) {
+ values = this.state.comparisonData.map(function (datapt, index) {
+ return {x: index, y: datapt.value};
+ });
+ data.push({
values: values,
- key: measure,
- }
- ];
- }
- if (this.state.groupedBy.length > 1) {
+ key: measure + ' (compare)',
+ color: '#ff7f0e',
+ });
+ }
+ } else if (this.state.groupedBy.length > 1) {
var xlabels = [],
series = [],
label, serie, value;
@@ -174,14 +184,15 @@ return AbstractRenderer.extend({
data.push(current_serie);
}
}
-
+
// For Bar chart View, we keep only groups where count > 0
data[0].values = _.filter(data[0].values, function (elem, index) {
return self.state.data[index].count > 0;
});
- // SVG
- var svg = d3.select(this.$el[0]).append('svg');
+ var $svgContainer = $('<div/>', {class: 'o_graph_svg_container'});
+ this.$el.append($svgContainer);
+ var svg = d3.select($svgContainer[0]).append('svg');
svg.datum(data);
svg.transition().duration(0);
@@ -216,7 +227,7 @@ return AbstractRenderer.extend({
*
* @returns {nvd3 chart}
*/
- _renderPieChart: function () {
+ _renderPieChart: function (stateData) {
var self = this;
var data = [];
var all_negative = true;
@@ -224,9 +235,9 @@ return AbstractRenderer.extend({
var all_zero = true;
// undefined label value becomes a string 'Undefined' translated
- this.state.data.forEach(self._sanitizeLabel);
+ stateData.forEach(self._sanitizeLabel);
- this.state.data.forEach(function (datapt) {
+ stateData.forEach(function (datapt) {
all_negative = all_negative && (datapt.value < 0);
some_negative = some_negative || (datapt.value < 0);
all_zero = all_zero && (datapt.value === 0);
@@ -248,17 +259,19 @@ return AbstractRenderer.extend({
return;
}
if (this.state.groupedBy.length) {
- data = this.state.data.map(function (datapt) {
+ data = stateData.map(function (datapt) {
return {x:datapt.labels.join("/"), y: datapt.value};
});
}
// We only keep groups where count > 0
data = _.filter(data, function (elem, index) {
- return self.state.data[index].count > 0;
+ return stateData[index].count > 0;
});
- var svg = d3.select(this.$el[0]).append('svg');
+ var $svgContainer = $('<div/>', {class: 'o_graph_svg_container'});
+ this.$el.append($svgContainer);
+ var svg = d3.select($svgContainer[0]).append('svg');
svg.datum(data);
svg.transition().duration(100);
@@ -294,34 +307,67 @@ return AbstractRenderer.extend({
// undefined label value becomes a string 'Undefined' translated
this.state.data.forEach(self._sanitizeLabel);
- if (graphData.length < 2) {
- this.$el.append(qweb.render('GraphView.error', {
- title: _t("Not enough data points"),
- description: _t("You need at least two data points to display a line chart.")
- }));
- return;
- }
var data = [];
var tickValues;
var tickFormat;
var measure = this.state.fields[this.state.measure].string;
+ var values;
if (this.state.groupedBy.length === 1) {
- var values = graphData.map(function (datapt, index) {
+ values = graphData.map(function (datapt, index) {
return {x: index, y: datapt.value};
});
- data = [
- {
+ data.push({
+ area: true,
values: values,
key: measure,
- area: true,
+ });
+ if (this.state.comparisonData && this.state.comparisonData.length > 0) {
+ values = this.state.comparisonData.map(function (datapt, index) {
+ return {x: index, y: datapt.value};
+ });
+ data.push({
+ values: values,
+ key: measure + ' (compare)',
+ color: '#ff7f0e',
+ });
+
+ if (this.state.comparisonData.length > graphData.length) {
+ tickValues = this.state.comparisonData.map(function (d, i) {
+ return i;
+ });
}
- ];
- tickValues = graphData.map(function (d, i) { return i;});
- tickFormat = function (d) {return self.state.data[d].labels;};
- }
- if (this.state.groupedBy.length > 1) {
- data = [];
+ }
+
+ if (!tickValues) {
+ tickValues = graphData.map(function (d, i) {
+ return i;
+ });
+ }
+
+ var ticksLabels = [];
+ for (i = 0; i < graphData.length; i++) {
+ ticksLabels.push(graphData[i].labels);
+ }
+ if (this.state.comparisonData && this.state.comparisonData.length > this.state.data.length) {
+ var diff = this.state.comparisonData.length - this.state.data.length;
+ var length = self.state.data.length
+ var diffTime = 0;
+ if (length < self.state.data.length) {
+ var date1 = moment(self.state.data[length - 1].labels[0]);
+ var date2 = moment(self.state.data[length - 2].labels[0]);
+ diffTime = date1 - date2;
+ for (i = 0; i < diff; i++) {
+ var value = moment(date1).add(diffTime + i * diffTime).format('DD MMM YYYY');
+ ticksLabels.push([value]);
+ }
+ }
+ }
+
+ tickFormat = function (d) {
+ return ticksLabels[d];
+ };
+ } else if (this.state.groupedBy.length > 1) {
var data_dict = {};
var tick = 0;
var tickLabels = [];
@@ -349,8 +395,9 @@ return AbstractRenderer.extend({
}
tickFormat = function (d) {return tickLabels[d];};
}
-
- var svg = d3.select(this.$el[0]).append('svg');
+ var $svgContainer = $('<div/>', {class: 'o_graph_svg_container'});
+ this.$el.append($svgContainer);
+ var svg = d3.select($svgContainer[0]).append('svg');
svg.datum(data);
svg.transition().duration(0);
@@ -382,12 +429,23 @@ return AbstractRenderer.extend({
* @private
*/
_renderGraph: function () {
+ var self = this;
+
this.$el.empty();
- var chart = this['_render' + _.str.capitalize(this.state.mode) + 'Chart']();
- if (chart && chart.tooltip.chartContainer) {
- this.to_remove = chart.update;
- nv.utils.onWindowResize(chart.update);
- chart.tooltip.chartContainer(this.el);
+
+ var chartResize = function (chart){
+ if (chart && chart.tooltip.chartContainer) {
+ self.to_remove = chart.update;
+ nv.utils.onWindowResize(chart.update);
+ chart.tooltip.chartContainer(self.$('.o_graph_svg_container').last()[0]);
+ }
+ }
+ var chart1 = this['_render' + _.str.capitalize(this.state.mode) + 'Chart'](this.state.data);
+ chartResize(chart1);
+ if (this.state.mode === 'pie' && this.isComparison) {
+ var chart2 = this['_render' + _.str.capitalize(this.state.mode) + 'Chart'](this.state.comparisonData);
+ chartResize(chart2);
+ chart1.update();
}
},
/**
diff --git a/addons/web/static/src/js/views/graph/graph_view.js b/addons/web/static/src/js/views/graph/graph_view.js
index 07c486cf5b7..23ab4cecd01 100644
--- a/addons/web/static/src/js/views/graph/graph_view.js
+++ b/addons/web/static/src/js/views/graph/graph_view.js
@@ -36,6 +36,7 @@ var GraphView = AbstractView.extend({
Renderer: GraphRenderer,
},
viewType: 'graph',
+ enableTimeRangeMenu: 'true',
/**
* @override
*/
@@ -86,6 +87,7 @@ var GraphView = AbstractView.extend({
this.loadParams.groupBys = groupBys || [];
this.loadParams.intervalMapping = intervalMapping;
this.loadParams.fields = this.fields;
+ this.loadParams.comparisonDomain = params.comparisonDomain;
},
});