Skip to content

Commit

Permalink
[IMP] web: Handle date interval in graph charts
Browse files Browse the repository at this point in the history
This commit only applies for chart grouped on date or datetime fields.

In that case:
- Line Charts : fill missing groups and doesn't display undefined values.
- Bar Charts : doesn't fill missing groups but display undefined values.
- Pie Charts : doesn't fill missing groups but display undefined values.

This commit is part of task #1835644
  • Loading branch information
Francois Volral committed Jul 20, 2018
1 parent 3c6ca3b commit 0149776
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 31 deletions.
1 change: 1 addition & 0 deletions addons/web/static/src/js/core/rpc.js
Expand Up @@ -67,6 +67,7 @@ return {
var orderBy = options.orderBy || params.orderBy || params.kwargs.orderby;
params.kwargs.orderby = orderBy ? this._serializeSort(orderBy) : orderBy;
params.kwargs.lazy = 'lazy' in options ? options.lazy : params.lazy;
params.kwargs.fill_temporal = options.fill_temporal;
}

if (options.method === 'search_read') {
Expand Down
1 change: 1 addition & 0 deletions addons/web/static/src/js/views/basic/basic_model.js
Expand Up @@ -3963,6 +3963,7 @@ var BasicModel = AbstractModel.extend({
var groupByField = list.groupedBy[0];
var rawGroupBy = groupByField.split(':')[0];
var fields = _.uniq(list.getFieldNames().concat(rawGroupBy));

return this._rpc({
model: list.model,
method: 'read_group',
Expand Down
9 changes: 7 additions & 2 deletions addons/web/static/src/js/views/graph/graph_model.js
Expand Up @@ -135,6 +135,7 @@ return AbstractModel.extend({
var fields = _.map(groupedBy, function (groupBy) {
return groupBy.split(':')[0];
});

if (this.chart.measure !== '__count__') {
if (this.fields[this.chart.measure].type === 'many2one') {
fields = fields.concat(this.chart.measure + ":count_distinct");
Expand All @@ -143,10 +144,12 @@ return AbstractModel.extend({
fields = fields.concat(this.chart.measure);
}
}

var context = _.extend({fill_temporal: true}, this.chart.context);
return this._rpc({
model: this.modelName,
method: 'read_group',
context: this.chart.context,
context: context,
domain: this.chart.domain,
fields: fields,
groupBy: groupedBy,
Expand Down Expand Up @@ -176,7 +179,8 @@ return AbstractModel.extend({
labels = _.map(this.chart.groupedBy, function (field) {
return self._sanitizeValue(data_pt[field], field);
});
var value = is_count ? data_pt.__count || data_pt[this.chart.groupedBy[0]+'_count'] : data_pt[this.chart.measure];
var count = data_pt.__count || data_pt[this.chart.groupedBy[0]+'_count'] || 0;
var value = is_count ? count : data_pt[this.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
Expand All @@ -188,6 +192,7 @@ return AbstractModel.extend({
value = 1;
}
this.chart.data.push({
count: count,
value: value,
labels: labels
});
Expand Down
72 changes: 48 additions & 24 deletions addons/web/static/src/js/views/graph/graph_renderer.js
Expand Up @@ -171,24 +171,31 @@ 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');
svg.datum(data);

svg.transition().duration(0);

var chart = nv.models.multiBarChart();
chart.options({
margin: {left: 80, bottom: 100, top: 80, right: 0},
delay: 100,
transition: 10,
showLegend: _.size(data) <= MAX_LEGEND_LENGTH,
showXAxis: true,
showYAxis: true,
rightAlignYAxis: false,
stacked: this.stacked,
reduceXTicks: false,
rotateLabels: -20,
showControls: (this.state.groupedBy.length > 1)
margin: {left: 80, bottom: 100, top: 80, right: 0},
delay: 100,
transition: 10,
showLegend: _.size(data) <= MAX_LEGEND_LENGTH,
showXAxis: true,
showYAxis: true,
rightAlignYAxis: false,
stacked: this.stacked,
reduceXTicks: false,
rotateLabels: -20,
showControls: (this.state.groupedBy.length > 1)
});
chart.yAxis.tickFormat(function (d) {
var measure_field = self.state.fields[self.measure];
Expand All @@ -207,6 +214,7 @@ return AbstractRenderer.extend({
* @returns {nvd3 chart}
*/
_renderPieChart: function () {
var self = this;
var data = [];
var all_negative = true;
var some_negative = false;
Expand Down Expand Up @@ -238,6 +246,12 @@ return AbstractRenderer.extend({
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;
});

var svg = d3.select(this.$el[0]).append('svg');
svg.datum(data);

Expand All @@ -264,21 +278,29 @@ return AbstractRenderer.extend({
* @returns {nvd3 chart}
*/
_renderLineChart: function () {
if (this.state.data.length < 2) {
var self = this;

// Remove Undefined
var graphData = _.filter(this.state.data, function(elem){
return elem.labels[0] !== _t("Undefined");
});

if (graphData.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."
description: _t("You need at least two data points to display a line chart.")
}));
return;

return;
}
var self = this;

var data = [];
var tickValues;
var tickFormat;
var measure = this.state.fields[this.state.measure].string;

if (this.state.groupedBy.length === 1) {
var values = this.state.data.map(function (datapt, index) {
var values = graphData.map(function (datapt, index) {
return {x: index, y: datapt.value};
});
data = [
Expand All @@ -288,8 +310,10 @@ return AbstractRenderer.extend({
area: true
}
];
tickValues = this.state.data.map(function (d, i) { return i;});
tickFormat = function (d) {return self.state.data[d].labels;};
tickValues = graphData.map(function (d, i) { return i;});
tickFormat = function (d) {
return self.state.data[d].labels;
};
}
if (this.state.groupedBy.length > 1) {
data = [];
Expand All @@ -300,25 +324,27 @@ return AbstractRenderer.extend({
var identity = function (p) {return p;};
tickValues = [];
for (var i = 0; i < this.state.data.length; i++) {
if (this.state.data[i].labels[0] !== tickLabel) {
if (graphData[i].labels[0] !== tickLabel) {
tickLabel = this.state.data[i].labels[0];
tickValues.push(tick);
tickLabels.push(tickLabel);
tick++;
}
serie = this.state.data[i].labels[1];
serie = graphData[i].labels[1];
if (!data_dict[serie]) {
data_dict[serie] = {
values: [],
key: serie,
};
}
data_dict[serie].values.push({
x: tick, y: this.state.data[i].value,
x: tick, y: graphData[i].value,
});
data = _.map(data_dict, identity);
}
tickFormat = function (d) {return tickLabels[d];};
tickFormat = function (d) {
return tickLabels[d];
};
}

var svg = d3.select(this.$el[0]).append('svg');
Expand All @@ -335,15 +361,13 @@ return AbstractRenderer.extend({
showYAxis: true,

});

chart.xAxis.tickValues(tickValues)
.tickFormat(tickFormat);
chart.yAxis.tickFormat(function (d) {
return field_utils.format.float(d, {
digits : self.state.fields[self.state.measure] && self.state.fields[self.state.measure].digits || [69, 2],
});
});

chart(svg);
return chart;
},
Expand Down
2 changes: 2 additions & 0 deletions addons/web/static/tests/core/rpc_tests.js
Expand Up @@ -182,6 +182,7 @@ QUnit.module('core', {}, function () {
groupBy: ['product_id'],
context: {abc: 'def'},
lazy: true,
fill_temporal: true,
});

assert.deepEqual(query.params, {
Expand All @@ -192,6 +193,7 @@ QUnit.module('core', {}, function () {
fields: ['name'],
groupby: ['product_id'],
lazy: true,
fill_temporal: true,
},
method: 'read_group',
model: 'partner',
Expand Down
43 changes: 38 additions & 5 deletions addons/web/static/tests/views/graph_tests.js
Expand Up @@ -16,13 +16,14 @@ QUnit.module('Views', {
bar: {string: "bar", type: "boolean"},
product_id: {string: "Product", type: "many2one", relation: 'product', store: true},
color_id: {string: "Color", type: "many2one", relation: 'color'},
date: {string: "Date", type: 'date'},
},
records: [
{id: 1, foo: 3, bar: true, product_id: 37},
{id: 2, foo: 53, bar: true, product_id: 37, color_id: 7},
{id: 3, foo: 2, bar: true, product_id: 37},
{id: 4, foo: 24, bar: false, product_id: 37},
{id: 5, foo: 4, bar: false, product_id: 41},
{id: 1, foo: 3, bar: true, product_id: 37, date: "2016-01-01"},
{id: 2, foo: 53, bar: true, product_id: 37, color_id: 7, date: "2016-01-03"},
{id: 3, foo: 2, bar: true, product_id: 37, date: "2016-03-04"},
{id: 4, foo: 24, bar: false, product_id: 37, date: "2016-03-07"},
{id: 5, foo: 4, bar: false, product_id: 41, date: "2016-05-01"},
{id: 6, foo: 63, bar: false, product_id: 41},
{id: 7, foo: 42, bar: false, product_id: 41},
]
Expand Down Expand Up @@ -309,6 +310,9 @@ QUnit.module('Views', {
QUnit.test('correctly uses graph_ keys from the context', function (assert) {
var done = assert.async();
assert.expect(6);

var lastOne = _.last(this.data.foo.records);
lastOne.color_id = 14;

var graph = createView({
View: GraphView,
Expand Down Expand Up @@ -350,6 +354,9 @@ QUnit.module('Views', {
var done = assert.async();
assert.expect(2);

var lastOne = _.last(this.data.foo.records);
lastOne.color_id = 14;

var graph = createView({
View: GraphView,
model: 'foo',
Expand Down Expand Up @@ -377,6 +384,9 @@ QUnit.module('Views', {
var done = assert.async();
assert.expect(8);

var lastOne = _.last(this.data.foo.records);
lastOne.color_id = 14;

var graph = createView({
View: GraphView,
model: "foo",
Expand Down Expand Up @@ -643,6 +653,29 @@ QUnit.module('Views', {
"Bouh should be the first measure");
assert.strictEqual(graph.$buttons.find('.o_graph_measures_list li:last').data('field'), '__count__',
"Count should be the last measure");

graph.destroy();
});

QUnit.test('Undefined should appear in bar, pie graph but not in line graph', function (assert) {
assert.expect(4);

var graph = createView({
View: GraphView,
model: "foo",
groupBy:['date'],
data: this.data,
arch: '<graph string="Partners" type="line">' +
'<field name="bar"/>' +
'</graph>',
});

assert.strictEqual(graph.$("svg.nvd3-svg:contains('Undefined')").length, 0);
assert.strictEqual(graph.$("svg.nvd3-svg:contains('January')").length, 1);

graph.$buttons.find('.o_graph_button[data-mode=bar]').click();
assert.strictEqual(graph.$("svg.nvd3-svg:contains('Undefined')").length, 1);
assert.strictEqual(graph.$("svg.nvd3-svg:contains('January')").length, 1);

graph.destroy();
});
Expand Down

0 comments on commit 0149776

Please sign in to comment.