Skip to content

Commit

Permalink
[IMP] web: comparisons in graph view
Browse files Browse the repository at this point in the history
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
  • Loading branch information
adr-odoo authored and Polymorphe57 committed Aug 14, 2018
1 parent d4461cd commit 89931d1
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 67 deletions.
32 changes: 25 additions & 7 deletions addons/web/static/src/js/views/graph/graph_model.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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,
};
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -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,
Expand Down
156 changes: 107 additions & 49 deletions addons/web/static/src/js/views/graph/graph_renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -117,34 +118,43 @@ 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: [{
x: measure,
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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -216,17 +227,17 @@ return AbstractRenderer.extend({
*
* @returns {nvd3 chart}
*/
_renderPieChart: function () {
_renderPieChart: function (stateData) {
var self = this;
var data = [];
var all_negative = true;
var some_negative = false;
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);
Expand All @@ -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);
Expand Down Expand Up @@ -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 = [];
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
}
},
/**
Expand Down
2 changes: 2 additions & 0 deletions addons/web/static/src/js/views/graph/graph_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var GraphView = AbstractView.extend({
Renderer: GraphRenderer,
},
viewType: 'graph',
enableTimeRangeMenu: 'true',
/**
* @override
*/
Expand Down Expand Up @@ -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;
},
});

Expand Down
12 changes: 8 additions & 4 deletions addons/web/static/src/scss/graph_view.scss
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
.o_graph {
.o_graph_svg_container {
direction: ltr#{"/*rtl:ignore*/"};
.o_graph_container {
height: 100%;
overflow: auto;
svg {
background-color: $o-view-background-color;
.o_graph_svg_container {
direction: ltr#{"/*rtl:ignore*/"};
height: 100%;
overflow: auto;
svg {
background-color: $o-view-background-color;
}
}
}
}
Expand Down
Loading

0 comments on commit 89931d1

Please sign in to comment.