hunk.code_window: # -*- coding: utf-8 -*-
   from openerp import SUPERUSER_ID
   from openerp.osv import orm, fields
  
hunk.labels: add keep keep keep keep
hunk.after_edit: import logging
  
hunk.file_path: addons/website_sale/models/payment.py
hunk.type: add
file: odoo.define('web.GraphWidget', function (require) {
  "use strict";
  
  var config = require('web.config');
  var core = require('web.core');
  var Model = require('web.DataModel');
  var Widget = require('web.Widget');
  
  var _t = core._t;
  var QWeb = core.qweb;
  
  // hide top legend when too many item for device size
  var MAX_LEGEND_LENGTH = 25 * (1 + config.device.size_class);
  
  return Widget.extend({
      init: function (parent, model, options) {
          this._super(parent);
          this.context = options.context;
          this.fields = options.fields;
          this.fields.__count__ = {string: _t("Quantity"), type: "integer"};
          this.model = new Model(model, {group_by_no_leaf: true});
  
          this.domain = options.domain || [];
          this.groupbys = options.groupbys || [];
          this.mode = options.mode || "bar";
          this.measure = options.measure || "__count__";
      },
      start: function () {
          return this.load_data().then(this.proxy('display_graph'));
      },
      update_data: function (domain, groupbys) {
          this.domain = domain;
          this.groupbys = groupbys;
          return this.load_data().then(this.proxy('display_graph'));
      },
      set_mode: function (mode) {
          this.mode = mode;
          this.display_graph();
      },
      set_measure: function (measure) {
          this.measure = measure;
          return this.load_data().then(this.proxy('display_graph'));        
      },
      load_data: function () {
          var fields = this.groupbys.slice(0);
          if (this.measure !== '__count__'.slice(0))
              fields = fields.concat(this.measure);
          return this.model
                      .query(fields)
                      .filter(this.domain)
                      .context(this.context)
                      .lazy(false)
                      .group_by(this.groupbys.slice(0,2))
                      .then(this.proxy('prepare_data'));
      },
      prepare_data: function () {
          var raw_data = arguments[0],
              is_count = this.measure === '__count__';
          var data_pt, j, values, value;
  
          this.data = [];
          for (var i = 0; i < raw_data.length; i++) {
              data_pt = raw_data[i].attributes;
              values = [];
              if (this.groupbys.length === 1) data_pt.value = [data_pt.value];
              for (j = 0; j < data_pt.value.length; j++) {
                  values[j] = this.sanitize_value(data_pt.value[j], data_pt.grouped_on[j]);
              }
              value = is_count ? data_pt.length : data_pt.aggregates[this.measure];
              this.data.push({
                  labels: values,
                  value: value
              });
          }
      },
      sanitize_value: function (value, field) {
          if (value === false) return _t("Undefined");
          if (value instanceof Array) return value[1];
          if (field && this.fields[field] && (this.fields[field].type === 'selection')) {
              var selected = _.where(this.fields[field].selection, {0: value})[0];
              return selected ? selected[1] : value;
          }
          return value;
      },
      display_graph: function () {
          if (this.to_remove) {
              nv.utils.offWindowResize(this.to_remove);
          }
          this.$el.empty();
          if (!this.data.length) {
              this.$el.append(QWeb.render('GraphView.error', {
                  title: _t("No data to display"),
                  description: _t("No data available for this chart. " +
                      "Try to add some records, or make sure that " +
                      "there is no active filter in the search bar."),
              }));
          } else {
              this['display_' + this.mode]();
          }
      },
      display_bar: function () {
          // prepare data for bar chart
          var data, values,
              measure = this.fields[this.measure].string;
  
          // zero groupbys
          if (this.groupbys.length === 0) {
              data = [{
                  values: [{
                      x: measure,
                      y: this.data[0].value}],
                  key: measure
              }];
          } 
          // one groupby
          if (this.groupbys.length === 1) {
              values = this.data.map(function (datapt) {
                  return {x: datapt.labels, y: datapt.value};
              });
              data = [
                  {
                      values: values,
                      key: measure,
                  }
              ];
          }
          if (this.groupbys.length > 1) {
              var xlabels = [],
                  series = [],
                  label, serie, value;
              values = {};
              for (var i = 0; i < this.data.length; i++) {
                  label = this.data[i].labels[0];
                  serie = this.data[i].labels[1];
                  value = this.data[i].value;
                  if ((!xlabels.length) || (xlabels[xlabels.length-1] !== label)) {
                      xlabels.push(label);
                  }
                  series.push(this.data[i].labels[1]);
                  if (!(serie in values)) {values[serie] = {};}
                  values[serie][label] = this.data[i].value;
              }
              series = _.uniq(series);
              data = [];
              var current_serie, j;
              for (i = 0; i < series.length; i++) {
                  current_serie = {values: [], key: series[i]};
                  for (j = 0; j < xlabels.length; j++) {
                      current_serie.values.push({
                          x: xlabels[j],
                          y: values[series[i]][xlabels[j]] || 0,
                      });
                  }
                  data.push(current_serie);
              }
          }
          var svg = d3.select(this.$el[0]).append('svg');
          svg.datum(data);
  
          svg.transition().duration(0);
  
          var chart = nv.models.multiBarChart();
          chart.options({
            delay: 250,
            transition: 10,
            showLegend: _.size(data) <= MAX_LEGEND_LENGTH,
            showXAxis: true,
            showYAxis: true,
            rightAlignYAxis: false,
            stacked: true,
            reduceXTicks: false,
            // rotateLabels: 40,
            showControls: (this.groupbys.length > 1)
          });
          chart.yAxis.tickFormat(function(d) { return openerp.web.format_value(d, { type : 'float' });});
  
          chart(svg);
          this.to_remove = chart.update;
          nv.utils.onWindowResize(chart.update);
      },
      display_pie: function () {
          var data = [],
              all_negative = true,
              some_negative = false,
              all_zero = true;
          
          this.data.forEach(function (datapt) {
              all_negative = all_negative && (datapt.value < 0);
              some_negative = some_negative || (datapt.value < 0);
              all_zero = all_zero && (datapt.value === 0);
          });
          if (some_negative && !all_negative) {
              return this.$el.append(QWeb.render('GraphView.error', {
                  title: _t("Invalid data"),
                  description: _t("Pie chart cannot mix positive and negative numbers. " +
                      "Try to change your domain to only display positive results"),
              }));
          }
          if (all_zero) {
              return this.$el.append(QWeb.render('GraphView.error', {
                  title: _t("Invalid data"),
                  description: _t("Pie chart cannot display all zero numbers.. " +
                      "Try to change your domain to display positive results"),
              }));
          }
          if (this.groupbys.length) {
              data = this.data.map(function (datapt) {
                  return {x:datapt.labels.join("/"), y: datapt.value};
              });
          }
          var svg = d3.select(this.$el[0]).append('svg');
          svg.datum(data);
  
          svg.transition().duration(100);
  
          var legend_right = config.device.size_class > config.device.SIZES.XS;
  
          var chart = nv.models.pieChart();
          chart.options({
            delay: 250,
            showLegend: legend_right || _.size(data) <= MAX_LEGEND_LENGTH,
            legendPosition: legend_right ? 'right' : 'top',
            transition: 100,
            color: d3.scale.category10().range(),
          });
  
          chart(svg);
          this.to_remove = chart.update;
          nv.utils.onWindowResize(chart.update);
      },
      display_line: function () {
          if (this.data.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."
              }));
              return;
          }
          var self = this,
              data = [],
              tickValues,
              tickFormat,
              measure = this.fields[this.measure].string;
          if (this.groupbys.length === 1) {
              var values = this.data.map(function (datapt, index) {
                  return {x: index, y: datapt.value};
              });
              data = [
                  {
                      values: values,
                      key: measure,
                  }
              ];
              tickValues = this.data.map(function (d, i) { return i;});
              tickFormat = function (d) {return self.data[d].labels;};
          }
          if (this.groupbys.length > 1) {
              data = [];
              var data_dict = {},
                  tick = 0,
                  tickLabels = [],
                  serie, tickLabel,
                  identity = function (p) {return p;};
              tickValues = [];
              for (var i = 0; i < this.data.length; i++) {
                  if (this.data[i].labels[0] !== tickLabel) {
                      tickLabel = this.data[i].labels[0];
                      tickValues.push(tick);
                      tickLabels.push(tickLabel);
                      tick++;
                  }
                  serie = this.data[i].labels[1];
                  if (!data_dict[serie]) {
                      data_dict[serie] = {
                          values: [],
                          key: serie,
                      };
                  }
                  data_dict[serie].values.push({
                      x: tick, y: this.data[i].value,
                  });
                  data = _.map(data_dict, identity);
              }
              tickFormat = function (d) {return tickLabels[d];};
          }
  
          var svg = d3.select(this.$el[0]).append('svg');
          svg.datum(data);
  
          svg.transition().duration(0);
  
          var chart = nv.models.lineChart();
          chart.options({
            margin: {left: 50, right: 50},
            useInteractiveGuideline: true,
            showLegend: _.size(data) <= MAX_LEGEND_LENGTH,
            showXAxis: true,
            showYAxis: true,
          });
          chart.xAxis.tickValues(tickValues)
              .tickFormat(tickFormat);
  
          chart(svg);
          this.to_remove = chart.update;
          nv.utils.onWindowResize(chart.update);  
      },
      destroy: function () {
          nv.utils.offWindowResize(this.to_remove);
          return this._super();
      }
  });
  
  });
  