diff --git a/addons/web/static/lib/underscore/underscore.string.js b/addons/web/static/lib/underscore/underscore.string.js index 4da2b26ec1c..d15c1f1f7ce 100644 --- a/addons/web/static/lib/underscore/underscore.string.js +++ b/addons/web/static/lib/underscore/underscore.string.js @@ -4,301 +4,436 @@ // Documentation: https://github.com/edtsech/underscore.string // Some code is borrowed from MooTools and Alexandru Marasteanu. -// Version 1.1.4 +// Version 1.1.6 -(function(){ - // ------------------------- Baseline setup --------------------------------- - // Establish the root object, "window" in the browser, or "global" on the server. - var root = this; +(function(root){ + 'use strict'; - var nativeTrim = String.prototype.trim; + if (typeof _ != 'undefined') { + var _reverse = _().reverse, + _include = _.include; + } - function str_repeat(i, m) { - for (var o = []; m > 0; o[--m] = i); - return o.join(''); + // Defining helper functions. + + var nativeTrim = String.prototype.trim; + + var parseNumber = function(source) { return source * 1 || 0; }; + + var strRepeat = function(i, m) { + for (var o = []; m > 0; o[--m] = i); + return o.join(''); + }; + + var slice = function(a){ + return Array.prototype.slice.call(a); + }; + + var defaultToWhiteSpace = function(characters){ + if (characters) { + return _s.escapeRegExp(characters); + } + return '\\s'; + }; + + var sArgs = function(method){ + return function(){ + var args = slice(arguments); + for(var i=0; i + // All rights reserved. + + var sprintf = (function() { + function get_type(variable) { + return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase(); } - function defaultToWhiteSpace(characters){ - if (characters) { - return _s.escapeRegExp(characters); + var str_repeat = strRepeat; + + var str_format = function() { + if (!str_format.cache.hasOwnProperty(arguments[0])) { + str_format.cache[arguments[0]] = str_format.parse(arguments[0]); + } + return str_format.format.call(null, str_format.cache[arguments[0]], arguments); + }; + + str_format.format = function(parse_tree, argv) { + var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length; + for (i = 0; i < tree_length; i++) { + node_type = get_type(parse_tree[i]); + if (node_type === 'string') { + output.push(parse_tree[i]); } - return '\\s'; - } - - var _s = { - - isBlank: function(str){ - return !!str.match(/^\s*$/); - }, - - capitalize : function(str) { - return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase(); - }, - - chop: function(str, step){ - step = step || str.length; - var arr = []; - for (var i = 0; i < str.length;) { - arr.push(str.slice(i,i + step)); - i = i + step; + else if (node_type === 'array') { + match = parse_tree[i]; // convenience purposes only + if (match[2]) { // keyword argument + arg = argv[cursor]; + for (k = 0; k < match[2].length; k++) { + if (!arg.hasOwnProperty(match[2][k])) { + throw(sprintf('[_.sprintf] property "%s" does not exist', match[2][k])); + } + arg = arg[match[2][k]]; } - return arr; - }, + } else if (match[1]) { // positional argument (explicit) + arg = argv[match[1]]; + } + else { // positional argument (implicit) + arg = argv[cursor++]; + } - clean: function(str){ - return _s.strip(str.replace(/\s+/g, ' ')); - }, + if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) { + throw(sprintf('[_.sprintf] expecting number but found %s', get_type(arg))); + } + switch (match[8]) { + case 'b': arg = arg.toString(2); break; + case 'c': arg = String.fromCharCode(arg); break; + case 'd': arg = parseInt(arg, 10); break; + case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break; + case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break; + case 'o': arg = arg.toString(8); break; + case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break; + case 'u': arg = Math.abs(arg); break; + case 'x': arg = arg.toString(16); break; + case 'X': arg = arg.toString(16).toUpperCase(); break; + } + arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg); + pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' '; + pad_length = match[6] - String(arg).length; + pad = match[6] ? str_repeat(pad_character, pad_length) : ''; + output.push(match[5] ? arg + pad : pad + arg); + } + } + return output.join(''); + }; - count: function(str, substr){ - var count = 0, index; - for (var i=0; i < str.length;) { - index = str.indexOf(substr, i); - index >= 0 && count++; - i = i + (index >= 0 ? index : 0) + substr.length; - } - return count; - }, + str_format.cache = {}; - chars: function(str) { - return str.split(''); - }, - - escapeHTML: function(str) { - return String(str||'').replace(/&/g,'&').replace(//g,'>') - .replace(/"/g, '"').replace(/'/g, "'"); - }, - - unescapeHTML: function(str) { - return String(str||'').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') - .replace(/"/g, '"').replace(/'/g, "'"); - }, - - escapeRegExp: function(str){ - // From MooTools core 1.2.4 - return String(str||'').replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1'); - }, - - insert: function(str, i, substr){ - var arr = str.split(''); - arr.splice(i, 0, substr); - return arr.join(''); - }, - - includes: function(str, needle){ - return str.indexOf(needle) !== -1; - }, - - join: function(sep) { - // TODO: Could this be faster by converting - // arguments to Array and using array.join(sep)? - sep = String(sep); - var str = ""; - for (var i=1; i < arguments.length; i += 1) { - str += String(arguments[i]); - if ( i !== arguments.length-1 ) { - str += sep; + str_format.parse = function(fmt) { + var _fmt = fmt, match = [], parse_tree = [], arg_names = 0; + while (_fmt) { + if ((match = /^[^\x25]+/.exec(_fmt)) !== null) { + parse_tree.push(match[0]); + } + else if ((match = /^\x25{2}/.exec(_fmt)) !== null) { + parse_tree.push('%'); + } + else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) { + if (match[2]) { + arg_names |= 1; + var field_list = [], replacement_field = match[2], field_match = []; + if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); + while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') { + if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); } - } - return str; - }, - - lines: function(str) { - return str.split("\n"); - }, - -// reverse: function(str){ -// return Array.prototype.reverse.apply(str.split('')).join(''); -// }, - - splice: function(str, i, howmany, substr){ - var arr = str.split(''); - arr.splice(i, howmany, substr); - return arr.join(''); - }, - - startsWith: function(str, starts){ - return str.length >= starts.length && str.substring(0, starts.length) === starts; - }, - - endsWith: function(str, ends){ - return str.length >= ends.length && str.substring(str.length - ends.length) === ends; - }, - - succ: function(str){ - var arr = str.split(''); - arr.splice(str.length-1, 1, String.fromCharCode(str.charCodeAt(str.length-1) + 1)); - return arr.join(''); - }, - - titleize: function(str){ - var arr = str.split(' '), - word; - for (var i=0; i < arr.length; i++) { - word = arr[i].split(''); - if(typeof word[0] !== 'undefined') word[0] = word[0].toUpperCase(); - i+1 === arr.length ? arr[i] = word.join('') : arr[i] = word.join('') + ' '; - } - return arr.join(''); - }, - - camelize: function(str){ - return _s.trim(str).replace(/(\-|_|\s)+(.)?/g, function(match, separator, chr) { - return chr ? chr.toUpperCase() : ''; - }); - }, - - underscored: function(str){ - return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1_$2').replace(/\-|\s+/g, '_').toLowerCase(); - }, - - dasherize: function(str){ - return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1-$2').replace(/^([A-Z]+)/, '-$1').replace(/\_|\s+/g, '-').toLowerCase(); - }, - - trim: function(str, characters){ - if (!characters && nativeTrim) { - return nativeTrim.call(str); - } - characters = defaultToWhiteSpace(characters); - return str.replace(new RegExp('\^[' + characters + ']+|[' + characters + ']+$', 'g'), ''); - }, - - ltrim: function(str, characters){ - characters = defaultToWhiteSpace(characters); - return str.replace(new RegExp('\^[' + characters + ']+', 'g'), ''); - }, - - rtrim: function(str, characters){ - characters = defaultToWhiteSpace(characters); - return str.replace(new RegExp('[' + characters + ']+$', 'g'), ''); - }, - - truncate: function(str, length, truncateStr){ - truncateStr = truncateStr || '...'; - return str.slice(0,length) + truncateStr; - }, - - words: function(str, delimiter) { - delimiter = delimiter || " "; - return str.split(delimiter); - }, - - - pad: function(str, length, padStr, type) { - - var padding = ''; - var padlen = 0; - - if (!padStr) { padStr = ' '; } - else if (padStr.length > 1) { padStr = padStr[0]; } - switch(type) { - case "right": - padlen = (length - str.length); - padding = str_repeat(padStr, padlen); - str = str+padding; - break; - case "both": - padlen = (length - str.length); - padding = { - 'left' : str_repeat(padStr, Math.ceil(padlen/2)), - 'right': str_repeat(padStr, Math.floor(padlen/2)) - }; - str = padding.left+str+padding.right; - break; - default: // "left" - padlen = (length - str.length); - padding = str_repeat(padStr, padlen);; - str = padding+str; - } - return str; - }, - - lpad: function(str, length, padStr) { - return _s.pad(str, length, padStr); - }, - - rpad: function(str, length, padStr) { - return _s.pad(str, length, padStr, 'right'); - }, - - lrpad: function(str, length, padStr) { - return _s.pad(str, length, padStr, 'both'); - }, - - - /** - * Credits for this function goes to - * http://www.diveintojavascript.com/projects/sprintf-for-javascript - * - * Copyright (c) Alexandru Marasteanu - * All rights reserved. - * */ - sprintf: function(){ - - var i = 0, a, f = arguments[i++], o = [], m, p, c, x, s = ''; - while (f) { - if (m = /^[^\x25]+/.exec(f)) { - o.push(m[0]); - } - else if (m = /^\x25{2}/.exec(f)) { - o.push('%'); - } - else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) { - if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) { - throw('Too few arguments.'); - } - if (/[^s]/.test(m[7]) && (typeof(a) != 'number')) { - throw('Expecting number but found ' + typeof(a)); - } - switch (m[7]) { - case 'b': a = a.toString(2); break; - case 'c': a = String.fromCharCode(a); break; - case 'd': a = parseInt(a); break; - case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break; - case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break; - case 'o': a = a.toString(8); break; - case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break; - case 'u': a = Math.abs(a); break; - case 'x': a = a.toString(16); break; - case 'X': a = a.toString(16).toUpperCase(); break; - } - a = (/[def]/.test(m[7]) && m[2] && a >= 0 ? '+'+ a : a); - c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' '; - x = m[5] - String(a).length - s.length; - p = m[5] ? str_repeat(c, x) : ''; - o.push(s + (m[4] ? a + p : p + a)); + else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); } else { - throw('Huh ?!'); + throw('[_.sprintf] huh?'); } - f = f.substring(m[0].length); + } } - return o.join(''); + else { + throw('[_.sprintf] huh?'); + } + match[2] = field_list; + } + else { + arg_names |= 2; + } + if (arg_names === 3) { + throw('[_.sprintf] mixing positional and named placeholders is not (yet) supported'); + } + parse_tree.push(match); } - } + else { + throw('[_.sprintf] huh?'); + } + _fmt = _fmt.substring(match[0].length); + } + return parse_tree; + }; - // Aliases + return str_format; + })(); - _s.strip = _s.trim; - _s.lstrip = _s.ltrim; - _s.rstrip = _s.rtrim; - _s.center = _s.lrpad - _s.ljust = _s.lpad - _s.rjust = _s.rpad - // CommonJS module is defined - if (typeof window === 'undefined' && typeof module !== 'undefined') { - // Export module - module.exports = _s; - // Integrate with Underscore.js - } else if (typeof root._ !== 'undefined') { - root._.mixin(_s); + // Defining underscore.string - // Or define it - } else { - root._ = _s; - } + var _s = { -}()); + isBlank: sArgs(function(str){ + return (/^\s*$/).test(str); + }), + + stripTags: sArgs(function(str){ + return str.replace(/<\/?[^>]+>/ig, ''); + }), + + capitalize : sArgs(function(str) { + return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase(); + }), + + chop: sArgs(function(str, step){ + step = parseNumber(step) || str.length; + var arr = []; + for (var i = 0; i < str.length;) { + arr.push(str.slice(i,i + step)); + i = i + step; + } + return arr; + }), + + clean: sArgs(function(str){ + return _s.strip(str.replace(/\s+/g, ' ')); + }), + + count: sArgs(function(str, substr){ + var count = 0, index; + for (var i=0; i < str.length;) { + index = str.indexOf(substr, i); + index >= 0 && count++; + i = i + (index >= 0 ? index : 0) + substr.length; + } + return count; + }), + + chars: sArgs(function(str) { + return str.split(''); + }), + + escapeHTML: sArgs(function(str) { + return str.replace(/&/g,'&').replace(//g,'>') + .replace(/"/g, '"').replace(/'/g, "'"); + }), + + unescapeHTML: sArgs(function(str) { + return str.replace(/</g, '<').replace(/>/g, '>') + .replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, '&'); + }), + + escapeRegExp: sArgs(function(str){ + // From MooTools core 1.2.4 + return str.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1'); + }), + + insert: sArgs(function(str, i, substr){ + var arr = str.split(''); + arr.splice(parseNumber(i), 0, substr); + return arr.join(''); + }), + + includes: sArgs(function(str, needle){ + return str.indexOf(needle) !== -1; + }), + + include: function(obj, needle) { + if (!_include || (/string|number/).test(typeof obj)) { + return this.includes(obj, needle); + } else { + return _include(obj, needle); + } + }, + + join: sArgs(function(sep) { + var args = slice(arguments); + return args.join(args.shift()); + }), + + lines: sArgs(function(str) { + return str.split("\n"); + }), + + reverse: function(obj){ + if (!_reverse || (/string|number/).test(typeof obj)) { + return Array.prototype.reverse.apply(String(obj).split('')).join(''); + } else { + return _reverse.call(_(obj)); + } + }, + + splice: sArgs(function(str, i, howmany, substr){ + var arr = str.split(''); + arr.splice(parseNumber(i), parseNumber(howmany), substr); + return arr.join(''); + }), + + startsWith: sArgs(function(str, starts){ + return str.length >= starts.length && str.substring(0, starts.length) === starts; + }), + + endsWith: sArgs(function(str, ends){ + return str.length >= ends.length && str.substring(str.length - ends.length) === ends; + }), + + succ: sArgs(function(str){ + var arr = str.split(''); + arr.splice(str.length-1, 1, String.fromCharCode(str.charCodeAt(str.length-1) + 1)); + return arr.join(''); + }), + + titleize: sArgs(function(str){ + var arr = str.split(' '), + word; + for (var i=0; i < arr.length; i++) { + word = arr[i].split(''); + if(typeof word[0] !== 'undefined') word[0] = word[0].toUpperCase(); + i+1 === arr.length ? arr[i] = word.join('') : arr[i] = word.join('') + ' '; + } + return arr.join(''); + }), + + camelize: sArgs(function(str){ + return _s.trim(str).replace(/(\-|_|\s)+(.)?/g, function(match, separator, chr) { + return chr ? chr.toUpperCase() : ''; + }); + }), + + underscored: function(str){ + return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1_$2').replace(/\-|\s+/g, '_').toLowerCase(); + }, + + dasherize: function(str){ + return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1-$2').replace(/^([A-Z]+)/, '-$1').replace(/\_|\s+/g, '-').toLowerCase(); + }, + + trim: sArgs(function(str, characters){ + if (!characters && nativeTrim) { + return nativeTrim.call(str); + } + characters = defaultToWhiteSpace(characters); + return str.replace(new RegExp('\^[' + characters + ']+|[' + characters + ']+$', 'g'), ''); + }), + + ltrim: sArgs(function(str, characters){ + characters = defaultToWhiteSpace(characters); + return str.replace(new RegExp('\^[' + characters + ']+', 'g'), ''); + }), + + rtrim: sArgs(function(str, characters){ + characters = defaultToWhiteSpace(characters); + return str.replace(new RegExp('[' + characters + ']+$', 'g'), ''); + }), + + truncate: sArgs(function(str, length, truncateStr){ + truncateStr = truncateStr || '...'; + length = parseNumber(length); + return str.length > length ? str.slice(0,length) + truncateStr : str; + }), + + words: function(str, delimiter) { + return String(str).split(delimiter || " "); + }, + + pad: sArgs(function(str, length, padStr, type) { + var padding = '', + padlen = 0; + + length = parseNumber(length); + + if (!padStr) { padStr = ' '; } + else if (padStr.length > 1) { padStr = padStr.charAt(0); } + switch(type) { + case 'right': + padlen = (length - str.length); + padding = strRepeat(padStr, padlen); + str = str+padding; + break; + case 'both': + padlen = (length - str.length); + padding = { + 'left' : strRepeat(padStr, Math.ceil(padlen/2)), + 'right': strRepeat(padStr, Math.floor(padlen/2)) + }; + str = padding.left+str+padding.right; + break; + default: // 'left' + padlen = (length - str.length); + padding = strRepeat(padStr, padlen);; + str = padding+str; + } + return str; + }), + + lpad: function(str, length, padStr) { + return _s.pad(str, length, padStr); + }, + + rpad: function(str, length, padStr) { + return _s.pad(str, length, padStr, 'right'); + }, + + lrpad: function(str, length, padStr) { + return _s.pad(str, length, padStr, 'both'); + }, + + sprintf: sprintf, + + vsprintf: function(fmt, argv){ + argv.unshift(fmt); + return sprintf.apply(null, argv); + }, + + toNumber: function(str, decimals) { + var num = parseNumber(parseNumber(str).toFixed(parseNumber(decimals))); + return (!(num === 0 && (str !== "0" && str !== 0))) ? num : Number.NaN; + }, + + strRight: sArgs(function(sourceStr, sep){ + var pos = (!sep) ? -1 : sourceStr.indexOf(sep); + return (pos != -1) ? sourceStr.slice(pos+sep.length, sourceStr.length) : sourceStr; + }), + + strRightBack: sArgs(function(sourceStr, sep){ + var pos = (!sep) ? -1 : sourceStr.lastIndexOf(sep); + return (pos != -1) ? sourceStr.slice(pos+sep.length, sourceStr.length) : sourceStr; + }), + + strLeft: sArgs(function(sourceStr, sep){ + var pos = (!sep) ? -1 : sourceStr.indexOf(sep); + return (pos != -1) ? sourceStr.slice(0, pos) : sourceStr; + }), + + strLeftBack: sArgs(function(sourceStr, sep){ + var pos = sourceStr.lastIndexOf(sep); + return (pos != -1) ? sourceStr.slice(0, pos) : sourceStr; + }) + + }; + + // Aliases + + _s.strip = _s.trim; + _s.lstrip = _s.ltrim; + _s.rstrip = _s.rtrim; + _s.center = _s.lrpad; + _s.ljust = _s.lpad; + _s.rjust = _s.rpad; + + // CommonJS module is defined + if (typeof module !== 'undefined' && module.exports) { + // Export module + module.exports = _s; + + // Integrate with Underscore.js + } else if (typeof root._ !== 'undefined') { + root._.mixin(_s); + + // Or define it + } else { + root._ = _s; + } + +}(this || window)); diff --git a/addons/web_graph/static/src/js/graph.js b/addons/web_graph/static/src/js/graph.js index 59bc2c29dcf..df661fd65e6 100644 --- a/addons/web_graph/static/src/js/graph.js +++ b/addons/web_graph/static/src/js/graph.js @@ -19,11 +19,14 @@ openerp.web_graph.GraphView = openerp.web.View.extend({ init: function(parent, dataset, view_id) { this._super(parent); - this.view_manager = parent; this.dataset = dataset; - this.dataset_index = 0; - this.model = this.dataset.model; this.view_id = view_id; + + this.first_field = null; + this.abscissa = null; + this.ordinate = null; + this.columns = []; + this.group_field = null; }, do_show: function () { // TODO: re-trigger search @@ -36,355 +39,262 @@ openerp.web_graph.GraphView = openerp.web.View.extend({ var self = this; this._super(); return $.when( - new openerp.web.DataSet(this, this.model).call('fields_get', []), + this.dataset.call('fields_get', []), this.rpc('/web/view/load', { - model: this.model, + model: this.dataset.model, view_id: this.view_id, view_type: 'graph' })).then(function (fields_result, view_result) { - self.on_loaded({ - all_fields: fields_result[0], - fields_view: view_result[0] - }); + self.fields = fields_result[0]; + self.fields_view = view_result[0]; + self.on_loaded(); }); }, - on_loaded: function(data) { - this.all_fields = data.all_fields; - this.fields_view = data.fields_view; - this.name = this.fields_view.name || this.fields_view.arch.attrs.string; - this.view_id = this.fields_view.view_id; + /** + * Returns all object fields involved in the graph view + */ + list_fields: function () { + var fs = [this.abscissa]; + fs.push.apply(fs, _(this.columns).pluck('name')); + if (this.group_field) { + fs.push(this.group_field); + } + return fs; + }, + on_loaded: function() { this.chart = this.fields_view.arch.attrs.type || 'pie'; - this.fields = this.fields_view.fields; - this.chart_info_fields = []; - this.operator_field = ''; - this.operator_field_one = ''; - this.operator = []; - this.group_field = []; - this.orientation = this.fields_view.arch.attrs.orientation || ''; + this.orientation = this.fields_view.arch.attrs.orientation || 'vertical'; + _.each(this.fields_view.arch.children, function (field) { - if (field.attrs.operator) { - this.operator.push(field.attrs.name); - } - else if (field.attrs.group) { - this.group_field.push(field.attrs.name); - } - else { - this.chart_info_fields.push(field.attrs.name); - } - }, this); - - this.operator_field = this.operator[0]; - if(this.operator.length > 1){ - this.operator_field_one = this.operator[1]; - } - if(this.operator == ''){ - this.operator_field = this.chart_info_fields[1]; - } - this.chart_info = this.chart_info_fields[0]; - this.x_title = this.fields[this.chart_info_fields[0]]['string']; - this.y_title = this.fields[this.operator_field]['string']; - this.load_chart(); - }, - - load_chart: function(data) { - var self = this; - var domain = false; - if(data){ - this.x_title = this.all_fields[this.chart_info_fields]['string']; - this.y_title = this.all_fields[this.operator_field]['string']; - self.schedule_chart(data); - }else{ - if(! _.isEmpty(this.view_manager.dataset.domain)){ - domain = this.view_manager.dataset.domain; - }else if(! _.isEmpty(this.view_manager.action.domain)){ - domain = this.view_manager.action.domain; - } - this.dataset.domain = domain; - this.dataset.context = this.view_manager.dataset.context; - this.dataset.read_slice(_(this.fields).keys(),{}, function(res) { - self.schedule_chart(res); - }); - } - }, - - schedule_chart: function(results) { - this.$element.html(QWeb.render("GraphView", {"fields_view": this.fields_view, "chart": this.chart,'element_id': this.element_id})); - - _.each(results, function (result) { - _.each(result, function (field_value, field_name) { - if (typeof field_value == 'object') { - result[field_name] = field_value[field_value.length - 1]; - } - if (typeof field_value == 'string') { - var choices = this.all_fields[field_name]['selection']; - _.each(choices, function (choice) { - if (field_value == choice[0]) { - result[field_name] = choice; - } - }); - } - }, this); - }, this); - - var graph_data = {}; - _.each(results, function (result) { - var group_key = []; - if(this.group_field.length){ - _.each(this.group_field, function (res) { - result[res] = (typeof result[res] == 'object') ? result[res][1] : result[res]; - group_key.push(result[res]); - }); - }else{ - group_key.push(result[this.group_field]); - } - var column_key = result[this.chart_info_fields] + "_" + group_key; - var column_descriptor = {}; - if (graph_data[column_key] == undefined) { - column_descriptor[this.operator_field] = result[this.operator_field]; - if (this.operator.length > 1) { - column_descriptor[this.operator_field_one] = result[this.operator_field_one]; - } - column_descriptor[this.chart_info_fields] = result[this.chart_info_fields]; - if(this.group_field.length){ - _.each(this.group_field, function (res) { - column_descriptor[res] = (typeof result[res] == 'object') ? result[res][1] : result[res]; - }); - } + var attrs = field.attrs; + if (attrs.group) { + this.group_field = attrs.name; + } else if(!this.abscissa) { + this.first_field = this.abscissa = attrs.name; } else { - column_descriptor = graph_data[column_key]; - column_descriptor[this.operator_field] += result[this.operator_field]; - if (this.operator.length > 1) { - column_descriptor[this.operator_field_one] += result[this.operator_field_one]; - } + this.columns.push({ + name: attrs.name, + operator: attrs.operator || '+' + }); } - graph_data[column_key] = column_descriptor; }, this); + this.ordinate = this.columns[0].name; + + this.dataset.read_slice( + this.list_fields(), {}, $.proxy(this, 'schedule_chart')); + }, + schedule_chart: function(results) { + var self = this; + this.$element.html(QWeb.render("GraphView", { + "fields_view": this.fields_view, + "chart": this.chart, + 'element_id': this.element_id + })); + + var fields = _(this.columns).pluck('name').concat([this.first_field]); + if (this.group_field) { fields.push(this.group_field); } + // transform search result into usable records (convert from OpenERP + // value shapes to usable atomic types + var records = _(results).map(function (result) { + var point = {}; + _(result).each(function (value, field) { + if (!_(fields).contains(field)) { return; } + if (value === false) { point[field] = false; return; } + switch (self.fields[field].type) { + case 'selection': + point[field] = _(self.fields[field].selection).detect(function (choice) { + return choice[0] === value; + })[1]; + break; + case 'many2one': + point[field] = value[1]; + break; + case 'integer': case 'float': case 'char': + case 'date': case 'datetime': + point[field] = value; + break; + default: + throw new Error( + "Unknown field type " + self.fields[field].type + + "for field " + field + " (" + value + ")"); + } + }); + return point; + }); + // aggregate data, because dhtmlx is crap. Aggregate on abscissa field, + // leave split on group field => max m*n records where m is the # of + // values for the abscissa and n is the # of values for the group field + var graph_data = []; + _(records).each(function (record) { + var abscissa = record[self.abscissa], + group = record[self.group_field]; + var r = _(graph_data).detect(function (potential) { + return potential[self.abscissa] === abscissa + && (!self.group_field + || potential[self.group_field] === group); + }); + var datapoint = r || {}; + + datapoint[self.abscissa] = abscissa; + if (self.group_field) { datapoint[self.group_field] = group; } + _(self.columns).each(function (column) { + var val = record[column.name], + aggregate = datapoint[column.name]; + switch(column.operator) { + case '+': + datapoint[column.name] = (aggregate || 0) + val; + return; + case '*': + datapoint[column.name] = (aggregate || 1) * val; + return; + case 'min': + datapoint[column.name] = (aggregate || Infinity) > val + ? val + : aggregate; + return; + case 'max': + datapoint[column.name] = (aggregate || -Infinity) < val + ? val + : aggregate; + } + }); + + if (!r) { graph_data.push(datapoint); } + }); + graph_data = _(graph_data).sortBy(function (point) { + return point[self.abscissa] + '[[--]]' + point[self.group_field]; + }); if (this.chart == 'bar') { - return this.schedule_bar(_.values(graph_data)); + return this.schedule_bar(graph_data); } else if (this.chart == "pie") { - return this.schedule_pie(_.values(graph_data)); + return this.schedule_pie(graph_data); } }, schedule_bar: function(results) { var self = this; - var view_chart = ''; - var group_list = []; - var legend_list = []; - var newkey = '', newkey_one; - var string_legend = ''; - if((self.group_field.length) && (this.operator.length <= 1)){ - view_chart = self.orientation == 'horizontal'? 'stackedBarH' : 'stackedBar'; - }else{ - view_chart = self.orientation == 'horizontal'? 'barH' : 'bar'; - } - - _.each(results, function (result) { - if ((self.group_field.length) && (this.operator.length <= 1)) { - var legend_key = ''; - _.each(self.group_field, function (res) { - result[res] = (typeof result[res] == 'object') ? result[res][1] : result[res]; - legend_key += result[res]; - }); - newkey = legend_key.replace(/\s+/g,'_').replace(/[^a-zA-Z 0-9]+/g,'_'); - string_legend = legend_key; - } else { - newkey = string_legend = "val"; - } - - if (_.contains(group_list, newkey) && _.contains(legend_list, string_legend)) { - return; - } - group_list.push(newkey); - legend_list.push(string_legend); - - if (this.operator.length > 1) { - newkey_one = "val1"; - group_list.push(newkey_one); - legend_list.push(newkey_one); - } - }, this); - - if (group_list.length <=1){ - group_list = []; - legend_list = []; - newkey = string_legend = "val"; - group_list.push(newkey); - legend_list.push(string_legend); - } - - var abscissa_data = {}; - _.each(results, function (result) { - var label = result[self.chart_info_fields], - section = {}; - if ((self.group_field.length) && (group_list.length > 1) && (self.operator.length <= 1)){ - var legend_key_two = ''; - _.each(self.group_field, function (res) { - result[res] = (typeof result[res] == 'object') ? result[res][1] : result[res]; - legend_key_two += result[res]; - }); - newkey = legend_key_two.replace(/\s+/g,'_').replace(/[^a-zA-Z 0-9]+/g,'_'); - }else{ - newkey = "val"; - } - if (abscissa_data[label] == undefined){ - section[self.chart_info_fields] = label; - _.each(group_list, function (group) { - section[group] = 0; - }); - } else { - section = abscissa_data[label]; - } - section[newkey] = result[self.operator_field]; - if (self.operator.length > 1){ - section[newkey_one] = result[self.operator_field_one]; - } - abscissa_data[label] = section; - }); - - //for legend color - var grp_color = _.map(legend_list, function (group_legend, index) { - var legend = {color: COLOR_PALETTE[index]}; - if (group_legend == "val"){ - legend['text'] = self.fields[self.operator_field]['string'] - }else if(group_legend == "val1"){ - legend['text'] = self.fields[self.operator_field_one]['string'] - }else{ - legend['text'] = group_legend; - } - return legend; - }); - - //for axis's value and title - var max,min,step; - var maximum,minimum; - if(_.isEmpty(abscissa_data)){ - max = 9; - min = 0; - step=1; - }else{ - var max_min = []; - _.each(abscissa_data, function (abscissa_datas) { - _.each(group_list, function(res){ - max_min.push(abscissa_datas[res]); - }); - }); - maximum = Math.max.apply(Math,max_min); - minimum = Math.min.apply(Math,max_min); - if (maximum == minimum){ - if (maximum == 0){ - max = 9; - min = 0; - step=1; - }else if(maximum > 0){ - max = maximum + (10 - maximum % 10); - min = 0; - step = Math.round(max/10); - }else{ - max = 0; - min = minimum - (10 + minimum % 10); - step = Math.round(Math.abs(min)/10); + var group_list, view_chart; + if (!this.group_field) { + view_chart = (this.orientation === 'horizontal') ? 'barH' : 'bar'; + group_list = _(this.columns).map(function (column, index) { + return { + group: column.name, + text: self.fields[column.name].string, + color: COLOR_PALETTE[index % (COLOR_PALETTE.length)] } + }); + } else { + // dhtmlx handles clustered bar charts (> 1 column per abscissa + // value) and stacked bar charts (basically the same but with the + // columns on top of one another instead of side by side), but it + // does not handle clustered stacked bar charts + if (this.columns.length > 1) { + this.$element.text( + 'OpenERP Web does not support combining grouping and ' + + 'multiple columns in graph at this time.'); + throw new Error( + 'dhtmlx can not handle columns counts of that magnitude'); } + // transform series for clustered charts into series for stacked + // charts + view_chart = (this.orientation === 'horizontal') + ? 'stackedBarH' : 'stackedBar'; + group_list = _(results).chain() + .pluck(this.group_field) + .uniq() + .map(function (value, index) { + return { + group: self.ordinate + '_' + + value.toLowerCase().replace(/\s/g, '_'), + text: value, + color: COLOR_PALETTE[index % COLOR_PALETTE.length] + }; + }).value(); + + results = _(results).chain() + .groupBy(function (record) { return record[self.abscissa]; }) + .map(function (records) { + var r = {}; + // second argument is coerced to a str, no good for boolean + r[self.abscissa] = records[0][self.abscissa]; + _(records).each(function (record) { + var key = _.sprintf('%s_%s', + self.ordinate, + record[self.group_field].toLowerCase().replace(/\s/g, '_')); + r[key] = record[self.ordinate]; + }); + return r; + }) + .value(); } + var abscissa_description = { - template: self.chart_info_fields, - title: ""+self.x_title+"" + title: "" + this.fields[this.abscissa].string + "", + template: function (obj) { + return obj[self.abscissa] || 'Undefined'; + } }; var ordinate_description = { lines: true, - title: ""+self.y_title+"", - start: min, - step: step, - end: max + title: "" + this.fields[this.ordinate].string + "" }; - var x_axis, y_axis, tooltip; - if (self.orientation == 'horizontal'){ - x_axis = ordinate_description; - y_axis = abscissa_description; - }else{ - x_axis = abscissa_description; - y_axis = ordinate_description; + var x_axis, y_axis; + if (self.orientation == 'horizontal') { + x_axis = ordinate_description; + y_axis = abscissa_description; + } else { + x_axis = abscissa_description; + y_axis = ordinate_description; } - tooltip = self.chart_info_fields; - + var bar_chart = new dhtmlXChart({ view: view_chart, - container: self.element_id+"-barchart", - value:"#"+group_list[0]+"#", + container: this.element_id+"-barchart", + value:"#"+group_list[0].group+"#", gradient: "3d", border: false, width: 1024, tooltip:{ - template:"#"+tooltip+"#"+","+grp_color[0]['text']+"="+"#"+group_list[0]+"#" + template: _.sprintf("#%s#, %s=#%s#", + this.abscissa, group_list[0].text, group_list[0].group) }, radius: 0, - color:grp_color[0]['color'], + color:group_list[0].color, origin:0, - xAxis:{ - template:function(obj){ - if(x_axis['template']){ - var val = obj[x_axis['template']]; - val = (typeof val == 'object')?val[1]:(!val?'Undefined':val); - if(val.length > 12){ - val = val.substring(0,12); - } - return val; - }else{ - return obj; - } - }, - title:x_axis['title'], - lines:x_axis['lines'] - }, - yAxis:{ - template:function(obj){ - if(y_axis['template']){ - var vals = obj[y_axis['template']]; - vals = (typeof vals == 'object')?vals[1]:(!vals?'Undefined':vals); - if(vals.length > 12){ - vals = vals.substring(0,12); - } - return vals; - }else{ - return obj; - } - }, - title:y_axis['title'], - lines: y_axis['lines'], - start:y_axis['start'], - step:y_axis['step'], - end:y_axis['end'] - }, + xAxis: x_axis, + yAxis: y_axis, padding: { left: 75 }, legend: { - values: grp_color, + values: group_list, align:"left", valign:"top", layout: "x", - marker:{ + marker: { type:"round", width:12 } } }); for (var m = 1; m