summaryrefslogtreecommitdiff
path: root/addons/web_graph/static/src/js/pivot_table.js
diff options
context:
space:
mode:
authorGéry Debongnie <ged@odoo.com>2014-12-19 13:38:37 +0100
committerGéry Debongnie <ged@odoo.com>2014-12-19 14:34:23 +0100
commit2c08dc0ba610db61fe349d1602c12528ca8fc715 (patch)
tree143cb7f61e45e027c63be321ee3d4031963df1e7 /addons/web_graph/static/src/js/pivot_table.js
parent9f7a0c096f0d3deabf8baf7be33b108f5b7884b5 (diff)
[IMP] web: new pivot view, full rewrite of graph view
This commit is a split of the former graphview into a pivot view (the tabular view) and graph view (the various charts). The code was fully rewritten. * pivot view should be faster (better underlying data structure and better algorithm) * better integration with the web client (use of sidebar, of menu buttons, bettor style) * new feature: pivot view can be sorted * new feature: pivot view cells can be clicked to open a list view with the corresponding domain * web_graph addon was removed, and the graph view code has been moved to the addon web. * the interaction with the searchbar was changed (no more column facet) * better and simpler code for graph view * mary corner cases in graph charts have been corrected (for ex, pie chart with positive and negative numbers) * graph/pivot correctly save their state when added to dashboard/saved (state such as current measure, chart mode, column groupbys, ...)
Diffstat (limited to 'addons/web_graph/static/src/js/pivot_table.js')
-rw-r--r--addons/web_graph/static/src/js/pivot_table.js468
1 files changed, 0 insertions, 468 deletions
diff --git a/addons/web_graph/static/src/js/pivot_table.js b/addons/web_graph/static/src/js/pivot_table.js
deleted file mode 100644
index a99bae1756f..00000000000
--- a/addons/web_graph/static/src/js/pivot_table.js
+++ /dev/null
@@ -1,468 +0,0 @@
-
-/* jshint undef: false */
-
-(function () {
-'use strict';
-
-var _lt = openerp.web._lt;
-var _t = openerp.web._t;
-
-// PivotTable requires a call to update_data after initialization
-openerp.web_graph.PivotTable = openerp.web.Class.extend({
-
- init: function (model, domain, fields, options) {
- this.cells = [];
- this.domain = domain;
- this.context = options.context;
- this.no_data = true;
- this.updating = false;
- this.model = model;
- this.fields = fields;
- this.fields.__count = {type: 'integer', string:_t('Count')};
- this.measures = options.measures || [];
- this.rows = { groupby: options.row_groupby, headers: null };
- this.cols = { groupby: options.col_groupby, headers: null };
- },
-
- // ----------------------------------------------------------------------
- // Configuration methods
- // ----------------------------------------------------------------------
- // this.measures: list of measure [measure], measure = {field: _, string: _, type: _}
- // this.rows.groupby, this.cols.groupby : list of groupbys used for describing rows (...),
- // a groupby is also {field:_, string:_, type:_}
- // If its type is date/datetime, field can have the corresponding interval in its description,
- // for example 'create_date:week'.
- set_measures: function (measures) {
- this.measures = measures;
- return this.update_data();
- },
-
- toggle_measure: function (measure) {
- var current_measure = _.findWhere(this.measures, measure);
- if (current_measure) { // remove current_measure
- var index = this.measures.indexOf(current_measure);
- this.measures = _.without(this.measures, current_measure);
- if (this.measures.length === 0) {
- this.no_data = true;
- } else {
- _.each(this.cells, function (cell) {
- cell.values.splice(index, 1);
- });
- }
- return $.Deferred().resolve();
- } else { // add a new measure
- this.measures.push(measure);
- return this.update_data();
- }
- },
-
- set: function (domain, row_groupby, col_groupby, measures_groupby) {
- var self = this;
- if (this.updating) {
- return this.updating.then(function () {
- self.updating = false;
- return self.set(domain, row_groupby, col_groupby, measures_groupby);
- });
- }
- var row_gb_changed = !_.isEqual(row_groupby, this.rows.groupby),
- col_gb_changed = !_.isEqual(col_groupby, this.cols.groupby),
- measures_gb_changed = !_.isEqual(measures_groupby, this.measures);
-
- this.domain = domain;
- this.rows.groupby = row_groupby;
- this.cols.groupby = col_groupby;
-
- if (measures_groupby.length) { this.measures = measures_groupby; }
-
- if (row_gb_changed) { this.rows.headers = null; }
- if (col_gb_changed) { this.cols.headers = null; }
- if (measures_gb_changed && measures_groupby.length) { this.set_measures(measures_groupby); }
-
- return this.update_data();
- },
-
- // ----------------------------------------------------------------------
- // Cells manipulation methods
- // ----------------------------------------------------------------------
- // cells are objects {x:_, y:_, values:_} where x < y and values is an array
- // of values (one for each measure). The condition x < y might look
- // unnecessary, but it makes the rest of the code simpler: headers
- // don't krow if they are rows or cols, they just know their id, so
- // it is useful that a call get_values(id1, id2) is the same as get_values(id2, id1)
- add_cell : function (id1, id2, values) {
- this.cells.push({x: Math.min(id1, id2), y: Math.max(id1, id2), values: values});
- },
-
- get_values: function (id1, id2, default_values) {
- var cells = this.cells,
- x = Math.min(id1, id2),
- y = Math.max(id1, id2);
- for (var i = 0; i < cells.length; i++) {
- if (cells[i].x == x && cells[i].y == y) {
- return cells[i].values;
- }
- }
- return (default_values || new Array(this.measures.length));
- },
-
- // ----------------------------------------------------------------------
- // Headers/Rows/Cols manipulation methods
- // ----------------------------------------------------------------------
- // this.rows.headers, this.cols.headers = [header] describe the tree structure
- // of rows/cols. Headers are objects
- // {
- // id:_, (unique id obviously)
- // path: [...], (array of all parents title, with its own title at the end)
- // title:_, (name of the row/col)
- // children:[_], (subrows or sub cols of this row/col)
- // domain:_, (domain of data in this row/col)
- // root:_ (ref to this.rows or this.cols corresponding to the header)
- // expanded:_ (boolean, true if it has been expanded)
- // }
- is_row: function (id) {
- return !!_.findWhere(this.rows.headers, {id:id});
- },
-
- is_col: function (id) {
- return !!_.findWhere(this.cols.headers, {id:id});
- },
-
- get_header: function (id) {
- return _.findWhere(this.rows.headers.concat(this.cols.headers), {id:id});
- },
-
- _get_headers_with_depth: function (headers, depth) {
- return _.filter(headers, function (header) {
- return header.path.length === depth;
- });
- },
-
- // return all columns with a path length of 'depth'
- get_cols_with_depth: function (depth) {
- return this._get_headers_with_depth(this.cols.headers, depth);
- },
-
- // return all rows with a path length of 'depth'
- get_rows_with_depth: function (depth) {
- return this._get_headers_with_depth(this.rows.headers, depth);
- },
-
- get_ancestor_leaves: function (header) {
- return _.where(this.get_ancestors_and_self(header), {expanded:false});
- },
-
- // return all non expanded rows
- get_rows_leaves: function () {
- return _.where(this.rows.headers, {expanded:false});
- },
-
- // return all non expanded cols
- get_cols_leaves: function () {
- return _.where(this.cols.headers, {expanded:false});
- },
-
- get_ancestors: function (header) {
- var self = this;
- if (!header.children) return [];
- return [].concat.apply([], _.map(header.children, function (c) {
- return self.get_ancestors_and_self(c);
- }));
- },
-
- get_ancestors_and_self: function (header) {
- var self = this;
- return [].concat.apply([header], _.map(header.children, function (c) {
- return self.get_ancestors_and_self(c);
- }));
- },
-
- get_total: function (header) {
- return (header) ? this.get_values(header.id, this.get_other_root(header).headers[0].id)
- : this.get_values(this.rows.headers[0].id, this.cols.headers[0].id);
- },
-
- get_other_root: function (header) {
- return (header.root === this.rows) ? this.cols : this.rows;
- },
-
- main_row: function () { return this.rows.headers[0]; },
-
- main_col: function () { return this.cols.headers[0]; },
-
- // ----------------------------------------------------------------------
- // Table manipulation methods : fold/expand/swap
- // ----------------------------------------------------------------------
- // return true if the folding changed the groupbys, false if not
- fold: function (header) {
- var ancestors = this.get_ancestors(header),
- removed_ids = _.pluck(ancestors, 'id');
-
- header.root.headers = _.difference(header.root.headers, ancestors);
- header.children = [];
- header.expanded = false;
-
- this.cells = _.reject(this.cells, function (cell) {
- return (_.contains(removed_ids, cell.x) || _.contains(removed_ids, cell.y));
- });
-
- var new_groupby_length = _.max(_.pluck(_.pluck(header.root.headers, 'path'), 'length'));
- if (new_groupby_length < header.root.groupby.length) {
- header.root.groupby.splice(new_groupby_length);
- return true;
- }
- return false;
- },
-
- fold_with_depth: function (root, depth) {
- var self = this;
- _.each(this._get_headers_with_depth(root.headers, depth), function (header) {
- self.fold(header);
- });
- },
-
- expand_all: function () {
- this.rows.headers = null;
- this.cols.headers = null;
- return this.update_data();
- },
-
- expand: function (header_id, groupby) {
- var self = this,
- header = this.get_header(header_id),
- other_root = this.get_other_root(header),
- this_gb = [groupby.field],
- other_gbs = _.pluck(other_root.groupby, 'field');
-
- if (header.path.length === header.root.groupby.length) {
- header.root.groupby.push(groupby);
- }
- return this.perform_requests(this_gb, other_gbs, header.domain).then(function () {
- var data = Array.prototype.slice.call(arguments).slice(other_gbs.length + 1);
- _.each(data, function (data_pt) {
- self.make_headers_and_cell(
- data_pt, header.root.headers, other_root.headers, 1, header.path, true);
- });
- header.expanded = true;
- header.children.forEach(function (child) {
- child.expanded = false;
- child.root = header.root;
- });
- });
- },
-
- swap_axis: function () {
- var temp = this.rows;
- this.rows = this.cols;
- this.cols = temp;
- },
-
- // ----------------------------------------------------------------------
- // Data updating methods
- // ----------------------------------------------------------------------
- // update_data will try to preserve the expand/not expanded status of each
- // column/row. If you want to expand all, then set this.cols.headers/this.rows.headers
- // to null before calling update_data.
- update_data: function () {
- var self = this;
- this.updating = this.perform_requests().then (function () {
- var data = Array.prototype.slice.call(arguments);
- self.no_data = !data[0].length;
- if (self.no_data) {
- return;
- }
- var row_headers = [],
- col_headers = [];
- self.cells = [];
-
- var dim_col = self.cols.groupby.length,
- i, j, index;
-
- for (i = 0; i < self.rows.groupby.length + 1; i++) {
- for (j = 0; j < dim_col + 1; j++) {
- index = i*(dim_col + 1) + j;
- self.make_headers_and_cell(data[index], row_headers, col_headers, i);
- }
- }
- self.set_headers(row_headers, self.rows);
- self.set_headers(col_headers, self.cols);
- });
- return this.updating;
- },
-
- make_headers_and_cell: function (data_pts, row_headers, col_headers, index, prefix, expand) {
- var self = this;
- data_pts.forEach(function (data_pt) {
- var row_value = (prefix || []).concat(data_pt.attributes.value.slice(0,index));
- var col_value = data_pt.attributes.value.slice(index);
-
- if (expand && !_.find(col_headers, function (hdr) {return self.isEqual(col_value, hdr.path);})) {
- return;
- }
- var row = self.find_or_create_header(row_headers, row_value, data_pt);
- var col = self.find_or_create_header(col_headers, col_value, data_pt);
-
- var cell_value = _.map(self.measures, function (m) {
- return data_pt.attributes.aggregates[m.field];
- });
- self.cells.push({
- x: Math.min(row.id, col.id),
- y: Math.max(row.id, col.id),
- values: cell_value
- });
- });
- },
-
- make_header: function (values) {
- return _.extend({
- children: [],
- domain: this.domain,
- expanded: undefined,
- id: _.uniqueId(),
- path: [],
- root: undefined,
- title: undefined
- }, values || {});
- },
-
- find_or_create_header: function (headers, path, data_pt) {
- var self = this;
- var hdr = _.find(headers, function (header) {
- return self.isEqual(path, header.path);
- });
- if (hdr) {
- return hdr;
- }
- if (!path.length) {
- hdr = this.make_header({title: _t('Total')});
- headers.push(hdr);
- return hdr;
- }
- hdr = this.make_header({
- path:path,
- domain:data_pt.model._domain,
- title: _t(_.last(path))
- });
- var parent = _.find(headers, function (header) {
- return self.isEqual(header.path, _.initial(path, 1));
- });
-
- var previous = parent.children.length ? _.last(parent.children) : parent;
- headers.splice(headers.indexOf(previous) + 1, 0, hdr);
- parent.children.push(hdr);
- return hdr;
- },
-
- perform_requests: function (group1, group2, domain) {
- var self = this,
- requests = [],
- row_gbs = _.pluck(this.rows.groupby, 'field'),
- col_gbs = _.pluck(this.cols.groupby, 'field'),
- field_list = row_gbs.concat(col_gbs, _.pluck(this.measures, 'field')),
- fields = field_list.map(function (f) { return self.raw_field(f); });
-
- group1 = group1 || row_gbs;
- group2 = group2 || col_gbs;
-
- var i,j, groupbys;
- for (i = 0; i < group1.length + 1; i++) {
- for (j = 0; j < group2.length + 1; j++) {
- groupbys = group1.slice(0,i).concat(group2.slice(0,j));
- requests.push(self.get_groups(groupbys, fields, domain || self.domain));
- }
- }
- return $.when.apply(null, requests);
- },
-
- // set the 'expanded' status of new_headers more or less like root.headers, with root as root
- set_headers: function(new_headers, root) {
- var self = this;
- if (root.headers) {
- _.each(root.headers, function (header) {
- var corresponding_header = _.find(new_headers, function (h) {
- return self.isEqual(h.path, header.path);
- });
- if (corresponding_header && header.expanded) {
- corresponding_header.expanded = true;
- _.each(corresponding_header.children, function (c) {
- c.expanded = false;
- });
- }
- if (corresponding_header && (!header.expanded)) {
- corresponding_header.expanded = false;
- corresponding_header.children = [];
- }
- });
- var updated_headers = _.filter(new_headers, function (header) {
- return (header.expanded !== undefined);
- });
- _.each(updated_headers, function (header) {
- header.root = root;
- });
- root.headers = updated_headers;
- } else {
- root.headers = new_headers;
- _.each(root.headers, function (header) {
- header.root = root;
- header.expanded = (header.children.length > 0);
- });
- }
- return new_headers;
- },
-
- get_groups: function (groupbys, fields, domain) {
- var self = this;
- return this.model.query(_.without(fields, '__count'))
- .filter(domain)
- .context(this.context)
- .lazy(false)
- .group_by(groupbys)
- .then(function (groups) {
- return groups.filter(function (group) {
- return group.attributes.length > 0;
- }).map(function (group) {
- var attrs = group.attributes,
- grouped_on = attrs.grouped_on instanceof Array ? attrs.grouped_on : [attrs.grouped_on],
- raw_grouped_on = grouped_on.map(function (f) {
- return self.raw_field(f);
- });
- if (grouped_on.length === 1) {
- attrs.value = [attrs.value];
- }
- attrs.value = _.range(grouped_on.length).map(function (i) {
- var grp = grouped_on[i],
- field = self.fields[grp];
- if (attrs.value[i] === false) {
- return _t('Undefined');
- } else if (attrs.value[i] instanceof Array) {
- return attrs.value[i][1];
- } else if (field && field.type === 'selection') {
- var selected = _.where(field.selection, {0: attrs.value[i]})[0];
- return selected ? selected[1] : attrs.value[i];
- }
- return attrs.value[i];
- });
- attrs.aggregates.__count = group.attributes.length;
- attrs.grouped_on = raw_grouped_on;
- return group;
- });
- });
- },
-
- // if field is a fieldname, returns field, if field is field_id:interval, retuns field_id
- raw_field: function (field) {
- return field.split(':')[0];
- },
-
- isEqual: function (path1, path2) {
- if (path1.length !== path2.length) { return false; }
- for (var i = 0; i < path1.length; i++) {
- if (path1[i] !== path2[i]) {
- return false;
- }
- }
- return true;
- },
-
-});
-
-})();