[IMP] crm,sale_crm: kanban widget
authorChristophe Matthieu <chm@openerp.com>
Fri, 3 May 2013 15:46:52 +0000 (17:46 +0200)
committerChristophe Matthieu <chm@openerp.com>
Fri, 3 May 2013 15:46:52 +0000 (17:46 +0200)
bzr revid: chm@openerp.com-20130503154652-r9mv3zovs8lnpj3e

addons/crm/__openerp__.py
addons/crm/crm_case_section_view.xml
addons/crm/static/lib/justgage.js [deleted file]
addons/crm/static/src/css/crm.css
addons/crm/static/src/js/crm_case_section.js
addons/sale_crm/__openerp__.py
addons/sale_crm/sale_crm_view.xml
addons/sale_crm/static/lib/justgage.js [new file with mode: 0644]
addons/sale_crm/static/src/js/sale_crm.js [new file with mode: 0644]

index 8bd87db..937e079 100644 (file)
@@ -122,7 +122,6 @@ Dashboard for CRM will include:
     'js': [
         'static/src/js/crm_case_section.js',
         'static/lib/sparkline/jquery.sparkline.min.js',
-        'static/lib/justgage.js',
     ],
     'installable': True,
     'application': True,
index 79a0abe..f9591ff 100644 (file)
                                     <div class="oe_items_list">
                                         <div>
                                             <a t-if="record.use_leads.raw_value" name="%(crm_case_form_view_salesteams_lead)d" type="action">Leads</a>
-                                            <a name="%(action_report_crm_lead)d" type="action" class="oe_sparkline_bar" t-att-data-value="record.open_lead_per_duration.raw_value">Number of opening leads per duration.<br/>(last one is <t t-esc="record.target_duration_txt.value"/>).<br/>Click to acces the Leads Analysis</a>
+                                            <a name="%(action_report_crm_lead)d" type="action" class="oe_sparkline_bar_link"><field name="open_lead_per_duration" widget="sparkline_bar">Number of opening leads per duration.<br/>(last one is <t t-esc="record.target_duration_txt.value"/>).<br/>Click to acces the Leads Analysis</field></a>
                                         </div>
                                         <div>
                                             <a name="%(crm_case_form_view_salesteams_opportunity)d" type="action">Opportunities</a>
-                                            <a name="%(action_report_crm_opportunity)d" type="action" class="oe_sparkline_bar" t-att-data-value="record.won_opportunity_per_duration.raw_value">Revenue of won opportunities per duration.<br/>(last one is <t t-esc="record.target_duration_txt.value"/>).<br/>Click to acces the Opportunities Analysis</a>
+                                            <a name="%(action_report_crm_opportunity)d" type="action"><field name="won_opportunity_per_duration" widget="sparkline_bar">Revenue of won opportunities per duration.<br/>(last one is <t t-esc="record.target_duration_txt.value"/>).<br/>Click to acces the Opportunities Analysis</field></a>
                                         </div>
                                     </div>
                                 </div>
diff --git a/addons/crm/static/lib/justgage.js b/addons/crm/static/lib/justgage.js
deleted file mode 100644 (file)
index 52b5c6e..0000000
+++ /dev/null
@@ -1,883 +0,0 @@
-/**
- * JustGage - this is work-in-progress, unreleased, unofficial code, so it might not work top-notch :)
- * Check http://www.justgage.com for official releases
- * Licensed under MIT.
- * @author Bojan Djuricic  (@Toorshia)
- *
- * LATEST UPDATES
-
- * -----------------------------
- * April 01, 2013.
- * -----------------------------
-     * fix - https://github.com/toorshia/justgage/issues/46
-
- * -----------------------------
- * March 26, 2013.
- * -----------------------------
-     * customSectors - define specific color for value range (0-10 : red, 10-30 : blue etc.)
-
- * -----------------------------
- * March 23, 2013.
- * -----------------------------
-     * counter - option to animate value  in counting fashion
-     * fix - https://github.com/toorshia/justgage/issues/45
-
- * -----------------------------
- * March 13, 2013.
- * -----------------------------
-     * refresh method - added optional 'max' parameter to use when you need to update max value
-
- * -----------------------------
- * February 26, 2013.
- * -----------------------------
-     * decimals - option to define/limit number of decimals when not using humanFriendly or customRenderer to display value
-     * fixed a missing parameters bug when calling generateShadow()  for IE < 9
-
- * -----------------------------
- * December 31, 2012.
- * -----------------------------
-     * fixed text y-position for hidden divs - workaround for Raphael <tspan> 'dy' bug - https://github.com/DmitryBaranovskiy/raphael/issues/491
-     * 'show' parameters, like showMinMax are now 'hide' because I am lame developer - please update these in your setups
-     * Min and Max labels are now auto-off when in donut mode
-     * Start angle in donut mode is now 90
-     * donutStartAngle - option to define start angle for donut
-
- * -----------------------------
- * November 25, 2012.
- * -----------------------------
-     * Option to define custom rendering function for displayed value
-
- * -----------------------------
- * November 19, 2012.
- * -----------------------------
-     * Config.value is now updated after gauge refresh
-
- * -----------------------------
- * November 13, 2012.
- * -----------------------------
-     * Donut display mode added
-     * Option to hide value label
-     * Option to enable responsive gauge size
-     * Removed default title attribute
-     * Option to accept min and max defined as string values
-     * Option to configure value symbol
-     * Fixed bad aspect ratio calculations
-     * Option to configure minimum font size for all texts
-     * Option to show shorthand big numbers (human friendly)
-     */
-
- JustGage = function(config) {
-
-  if (!config.id) {alert("Missing id parameter for gauge!"); return false;}
-  if (!config.node) {
-    if (!document.getElementById(config.id)) {alert("No element with id: \""+config.id+"\" found!"); return false;}
-    config.node = document.getElementById(config.id);
-  }
-
-  var obj = this;
-
-  // configurable parameters
-  obj.config =
-  {
-    // id : string
-    // this is container element id
-    id : config.id,
-
-    // node : string
-    // the node to use instead of DOM
-    node : config.node,
-
-    // title : string
-    // gauge title
-    title : (config.title) ? config.title : "",
-
-    // titleFontColor : string
-    // color of gauge title
-    titleFontColor : (config.titleFontColor) ? config.titleFontColor : "#999999",
-
-    // value : int
-    // value gauge is showing
-    value : (config.value) ? config.value : 0,
-
-
-    // valueFontColor : string
-    // color of label showing current value
-    valueFontColor : (config.valueFontColor) ? config.valueFontColor : "#010101",
-
-    // symbol : string
-    // special symbol to show next to value
-    symbol : (config.symbol) ? config.symbol : "",
-
-    // min : int
-    // min value
-    min : (config.min) ? parseFloat(config.min) : 0,
-
-    // max : int
-    // max value
-    max : (config.max) ? parseFloat(config.max) : 100,
-
-    // humanFriendlyDecimal : int
-    // number of decimal places for our human friendly number to contain
-    humanFriendlyDecimal : (config.humanFriendlyDecimal) ? config.humanFriendlyDecimal : 0,
-
-    // textRenderer: func
-    // function applied before rendering text
-    textRenderer  : (config.textRenderer) ? config.textRenderer : null,
-
-    // gaugeWidthScale : float
-    // width of the gauge element
-    gaugeWidthScale : (config.gaugeWidthScale) ? config.gaugeWidthScale : 1.0,
-
-    // gaugeColor : string
-    // background color of gauge element
-    gaugeColor : (config.gaugeColor) ? config.gaugeColor : "#edebeb",
-
-    // label : string
-    // text to show below value
-    label : (config.label) ? config.label : "",
-
-    // labelFontColor : string
-    // color of label showing label under value
-    labelFontColor : (config.labelFontColor) ? config.labelFontColor : "#b3b3b3",
-
-    // shadowOpacity : int
-    // 0 ~ 1
-    shadowOpacity : (config.shadowOpacity) ? config.shadowOpacity : 0.2,
-
-    // shadowSize: int
-    // inner shadow size
-    shadowSize : (config.shadowSize) ? config.shadowSize : 5,
-
-    // shadowVerticalOffset : int
-    // how much shadow is offset from top
-    shadowVerticalOffset : (config.shadowVerticalOffset) ? config.shadowVerticalOffset : 3,
-
-    // levelColors : string[]
-    // colors of indicator, from lower to upper, in RGB format
-    levelColors : (config.levelColors) ? config.levelColors : [
-    "#a9d70b",
-    "#f9c802",
-    "#ff0000"
-    ],
-
-    // startAnimationTime : int
-    // length of initial animation
-    startAnimationTime : (config.startAnimationTime) ? config.startAnimationTime : 700,
-
-    // startAnimationType : string
-    // type of initial animation (linear, >, <,  <>, bounce)
-    startAnimationType : (config.startAnimationType) ? config.startAnimationType : ">",
-
-    // refreshAnimationTime : int
-    // length of refresh animation
-    refreshAnimationTime : (config.refreshAnimationTime) ? config.refreshAnimationTime : 700,
-
-    // refreshAnimationType : string
-    // type of refresh animation (linear, >, <,  <>, bounce)
-    refreshAnimationType : (config.refreshAnimationType) ? config.refreshAnimationType : ">",
-
-    // donutStartAngle : int
-    // angle to start from when in donut mode
-    donutStartAngle : (config.donutStartAngle) ? config.donutStartAngle : 90,
-
-    // valueMinFontSize : int
-    // absolute minimum font size for the value
-    valueMinFontSize : config.valueMinFontSize || 16,
-
-    // titleMinFontSize
-    // absolute minimum font size for the title
-    titleMinFontSize : config.titleMinFontSize || 10,
-
-    // labelMinFontSize
-    // absolute minimum font size for the label
-    labelMinFontSize : config.labelMinFontSize || 10,
-
-    // minLabelMinFontSize
-    // absolute minimum font size for the minimum label
-    minLabelMinFontSize : config.minLabelMinFontSize || 10,
-
-    // maxLabelMinFontSize
-    // absolute minimum font size for the maximum label
-    maxLabelMinFontSize : config.maxLabelMinFontSize || 10,
-
-    // hideValue : bool
-    // hide value text
-    hideValue : (config.hideValue) ? config.hideValue : false,
-
-    // hideMinMax : bool
-    // hide min and max values
-    hideMinMax : (config.hideMinMax) ? config.hideMinMax : false,
-
-    // hideInnerShadow : bool
-    // hide inner shadow
-    hideInnerShadow : (config.hideInnerShadow) ? config.hideInnerShadow : false,
-
-    // humanFriendly : bool
-    // convert large numbers for min, max, value to human friendly (e.g. 1234567 -> 1.23M)
-    humanFriendly : (config.humanFriendly) ? config.humanFriendly : false,
-
-    // noGradient : bool
-    // whether to use gradual color change for value, or sector-based
-    noGradient : (config.noGradient) ? config.noGradient : false,
-
-    // donut : bool
-    // show full donut gauge
-    donut : (config.donut) ? config.donut : false,
-
-    // relativeGaugeSize : bool
-    // whether gauge size should follow changes in container element size
-    relativeGaugeSize : (config.relativeGaugeSize) ? config.relativeGaugeSize : false,
-
-    // counter : bool
-    // animate level number change
-    counter : (config.counter) ? config.counter : false,
-
-    // decimals : int
-    // number of digits after floating point
-    decimals : (config.decimals) ? config.decimals : 0,
-
-    // customSectors : [] of objects
-    // number of digits after floating point
-    customSectors : (config.customSectors) ? config.customSectors : []
-  };
-
-  // variables
-  var
-  canvasW,
-  canvasH,
-  widgetW,
-  widgetH,
-  aspect,
-  dx,
-  dy,
-  titleFontSize,
-  titleX,
-  titleY,
-  valueFontSize,
-  valueX,
-  valueY,
-  labelFontSize,
-  labelX,
-  labelY,
-  minFontSize,
-  minX,
-  minY,
-  maxFontSize,
-  maxX,
-  maxY;
-
-  // overflow values
-  if (this.config.value > this.config.max) this.config.value = this.config.max;
-  if (this.config.value < this.config.min) this.config.value = this.config.min;
-  this.originalValue = config.value;
-
-  // canvas
-  this.canvas = Raphael(this.config.node, "100%", "100%");
-  if (this.config.relativeGaugeSize === true) {
-    this.canvas.setViewBox(0, 0, 200, 150, true);
-  }
-
-  // canvas dimensions
-  if (this.config.relativeGaugeSize === true) {
-    canvasW = 200;
-    canvasH = 150;
-  } else {
-    canvasW = getStyle(this.config.node, "width").slice(0, -2) * 1;
-    canvasH = getStyle(this.config.node, "height").slice(0, -2) * 1;
-  }
-
-  // widget dimensions
-  if (this.config.donut === true) {
-
-    // DONUT *******************************
-
-    // width more than height
-    if(canvasW > canvasH) {
-      widgetH = canvasH;
-      widgetW = widgetH;
-    // width less than height
-  } else if (canvasW < canvasH) {
-    widgetW = canvasW;
-    widgetH = widgetW;
-      // if height don't fit, rescale both
-      if(widgetH > canvasH) {
-        aspect = widgetH / canvasH;
-        widgetH = widgetH / aspect;
-        widgetW = widgetH / aspect;
-      }
-    // equal
-  } else {
-    widgetW = canvasW;
-    widgetH = widgetW;
-  }
-
-    // delta
-    dx = (canvasW - widgetW)/2;
-    dy = (canvasH - widgetH)/2;
-
-    // title
-    titleFontSize = ((widgetH / 8) > 10) ? (widgetH / 10) : 10;
-    titleX = dx + widgetW / 2;
-    titleY = dy + widgetH / 11;
-
-    // value
-    valueFontSize = ((widgetH / 6.4) > 16) ? (widgetH / 5.4) : 18;
-    valueX = dx + widgetW / 2;
-    if(this.config.label !== '') {
-      valueY = dy + widgetH / 1.85;
-    } else {
-      valueY = dy + widgetH / 1.7;
-    }
-
-    // label
-    labelFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
-    labelX = dx + widgetW / 2;
-    labelY = valueY + labelFontSize;
-
-    // min
-    minFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
-    minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2 ;
-    minY = labelY;
-
-    // max
-    maxFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
-    maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2 ;
-    maxY = labelY;
-
-  } else {
-    // HALF *******************************
-
-    // width more than height
-    if(canvasW > canvasH) {
-      widgetH = canvasH;
-      widgetW = widgetH * 1.25;
-      //if width doesn't fit, rescale both
-      if(widgetW > canvasW) {
-        aspect = widgetW / canvasW;
-        widgetW = widgetW / aspect;
-        widgetH = widgetH / aspect;
-      }
-    // width less than height
-  } else if (canvasW < canvasH) {
-    widgetW = canvasW;
-    widgetH = widgetW / 1.25;
-      // if height don't fit, rescale both
-      if(widgetH > canvasH) {
-        aspect = widgetH / canvasH;
-        widgetH = widgetH / aspect;
-        widgetW = widgetH / aspect;
-      }
-    // equal
-  } else {
-    widgetW = canvasW;
-    widgetH = widgetW * 0.75;
-  }
-
-    // delta
-    dx = (canvasW - widgetW)/2;
-    dy = (canvasH - widgetH)/2;
-
-    // title
-    titleFontSize = ((widgetH / 8) > this.config.titleMinFontSize) ? (widgetH / 10) : this.config.titleMinFontSize;
-    titleX = dx + widgetW / 2;
-    titleY = dy + widgetH / 6.4;
-
-    // value
-    valueFontSize = ((widgetH / 6.5) > this.config.valueMinFontSize) ? (widgetH / 6.5) : this.config.valueMinFontSize;
-    valueX = dx + widgetW / 2;
-    valueY = dy + widgetH / 1.275;
-
-    // label
-    labelFontSize = ((widgetH / 16) > this.config.labelMinFontSize) ? (widgetH / 16) : this.config.labelMinFontSize;
-    labelX = dx + widgetW / 2;
-    labelY = valueY + valueFontSize / 2 + 5;
-
-    // min
-    minFontSize = ((widgetH / 16) > this.config.minLabelMinFontSize) ? (widgetH / 16) : this.config.minLabelMinFontSize;
-    minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2 ;
-    minY = labelY;
-
-    // max
-    maxFontSize = ((widgetH / 16) > this.config.maxLabelMinFontSize) ? (widgetH / 16) : this.config.maxLabelMinFontSize;
-    maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2 ;
-    maxY = labelY;
-  }
-
-  // parameters
-  this.params  = {
-    canvasW : canvasW,
-    canvasH : canvasH,
-    widgetW : widgetW,
-    widgetH : widgetH,
-    dx : dx,
-    dy : dy,
-    titleFontSize : titleFontSize,
-    titleX : titleX,
-    titleY : titleY,
-    valueFontSize : valueFontSize,
-    valueX : valueX,
-    valueY : valueY,
-    labelFontSize : labelFontSize,
-    labelX : labelX,
-    labelY : labelY,
-    minFontSize : minFontSize,
-    minX : minX,
-    minY : minY,
-    maxFontSize : maxFontSize,
-    maxX : maxX,
-    maxY : maxY
-  };
-
-  // var clear
-  canvasW, canvasH, widgetW, widgetH, aspect, dx, dy, titleFontSize, titleX, titleY, valueFontSize, valueX, valueY, labelFontSize, labelX, labelY, minFontSize, minX, minY, maxFontSize, maxX, maxY = null
-
-  // pki - custom attribute for generating gauge paths
-  this.canvas.customAttributes.pki = function (value, min, max, w, h, dx, dy, gws, donut) {
-
-    var alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, path;
-
-    if (donut) {
-      alpha = (1 - 2 * (value - min) / (max - min)) * Math.PI;
-      Ro = w / 2 - w / 7;
-      Ri = Ro - w / 6.666666666666667 * gws;
-
-      Cx = w / 2 + dx;
-      Cy = h / 1.95 + dy;
-
-      Xo = w / 2 + dx + Ro * Math.cos(alpha);
-      Yo = h - (h - Cy) + 0 - Ro * Math.sin(alpha);
-      Xi = w / 2 + dx + Ri * Math.cos(alpha);
-      Yi = h - (h - Cy) + 0 - Ri * Math.sin(alpha);
-
-      path += "M" + (Cx - Ri) + "," + Cy + " ";
-      path += "L" + (Cx - Ro) + "," + Cy + " ";
-      if (value > ((max - min) / 2)) {
-        path += "A" + Ro + "," + Ro + " 0 0 1 " + (Cx + Ro) + "," + Cy + " ";
-      }
-      path += "A" + Ro + "," + Ro + " 0 0 1 " + Xo + "," + Yo + " ";
-      path += "L" + Xi + "," + Yi + " ";
-      if (value > ((max - min) / 2)) {
-        path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx + Ri) + "," + Cy + " ";
-      }
-      path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx - Ri) + "," + Cy + " ";
-      path += "Z ";
-
-      return { path: path };
-
-    } else {
-      alpha = (1 - (value - min) / (max - min)) * Math.PI;
-      Ro = w / 2 - w / 10;
-      Ri = Ro - w / 6.666666666666667 * gws;
-
-      Cx = w / 2 + dx;
-      Cy = h / 1.25 + dy;
-
-      Xo = w / 2 + dx + Ro * Math.cos(alpha);
-      Yo = h - (h - Cy) + 0 - Ro * Math.sin(alpha);
-      Xi = w / 2 + dx + Ri * Math.cos(alpha);
-      Yi = h - (h - Cy) + 0 - Ri * Math.sin(alpha);
-
-      path += "M" + (Cx - Ri) + "," + Cy + " ";
-      path += "L" + (Cx - Ro) + "," + Cy + " ";
-      path += "A" + Ro + "," + Ro + " 0 0 1 " + Xo + "," + Yo + " ";
-      path += "L" + Xi + "," + Yi + " ";
-      path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx - Ri) + "," + Cy + " ";
-      path += "Z ";
-
-      return { path: path };
-    }
-
-    // var clear
-    alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, path = null;
-  };
-
-  // gauge
-  this.gauge = this.canvas.path().attr({
-    "stroke": "none",
-    "fill": this.config.gaugeColor,
-    pki: [this.config.max, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH,  this.params.dx, this.params.dy, this.config.gaugeWidthScale, this.config.donut]
-  });
-  this.gauge.id = this.config.id+"-gauge";
-
-  // level
-  this.level = this.canvas.path().attr({
-    "stroke": "none",
-    "fill": getColor(this.config.value, (this.config.value - this.config.min) / (this.config.max - this.config.min), this.config.levelColors, this.config.noGradient, this.config.customSectors),
-    pki: [this.config.min, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH,  this.params.dx, this.params.dy, this.config.gaugeWidthScale, this.config.donut]
-  });
-  if(this.config.donut) {
-    this.level.transform("r" + this.config.donutStartAngle + ", " + (this.params.widgetW/2 + this.params.dx) + ", " + (this.params.widgetH/1.95 + this.params.dy));
-  }
-  this.level.id = this.config.id+"-level";
-
-  // title
-  this.txtTitle = this.canvas.text(this.params.titleX, this.params.titleY, this.config.title);
-  this.txtTitle.attr({
-    "font-size":this.params.titleFontSize,
-    "font-weight":"bold",
-    "font-family":"Arial",
-    "fill":this.config.titleFontColor,
-    "fill-opacity":"1"
-  });
-  setDy(this.txtTitle, this.params.titleFontSize, this.params.titleY);
-  this.txtTitle.id = this.config.id+"-txttitle";
-
-  // value
-  this.txtValue = this.canvas.text(this.params.valueX, this.params.valueY, 0);
-  this.txtValue.attr({
-    "font-size":this.params.valueFontSize,
-    "font-weight":"bold",
-    "font-family":"Arial",
-    "fill":this.config.valueFontColor,
-    "fill-opacity":"0"
-  });
-  setDy(this.txtValue, this.params.valueFontSize, this.params.valueY);
-  this.txtValue.id = this.config.id+"-txtvalue";
-
-  // label
-  this.txtLabel = this.canvas.text(this.params.labelX, this.params.labelY, this.config.label);
-  this.txtLabel.attr({
-    "font-size":this.params.labelFontSize,
-    "font-weight":"normal",
-    "font-family":"Arial",
-    "fill":this.config.labelFontColor,
-    "fill-opacity":"0"
-  });
-  setDy(this.txtLabel, this.params.labelFontSize, this.params.labelY);
-  this.txtLabel.id = this.config.id+"-txtlabel";
-
-  // min
-  this.txtMinimum = this.config.min;
-  if( this.config.humanFriendly ) this.txtMinimum = humanFriendlyNumber( this.config.min, this.config.humanFriendlyDecimal );
-  this.txtMin = this.canvas.text(this.params.minX, this.params.minY, this.txtMinimum);
-  this.txtMin.attr({
-    "font-size":this.params.minFontSize,
-    "font-weight":"normal",
-    "font-family":"Arial",
-    "fill":this.config.labelFontColor,
-    "fill-opacity": (this.config.hideMinMax || this.config.donut)? "0" : "1"
-  });
-  setDy(this.txtMin, this.params.minFontSize, this.params.minY);
-  this.txtMin.id = this.config.id+"-txtmin";
-
-  // max
-  this.txtMaximum = this.config.max;
-  if( this.config.humanFriendly ) this.txtMaximum = humanFriendlyNumber( this.config.max, this.config.humanFriendlyDecimal );
-  this.txtMax = this.canvas.text(this.params.maxX, this.params.maxY, this.txtMaximum);
-  this.txtMax.attr({
-    "font-size":this.params.maxFontSize,
-    "font-weight":"normal",
-    "font-family":"Arial",
-    "fill":this.config.labelFontColor,
-    "fill-opacity": (this.config.hideMinMax || this.config.donut)? "0" : "1"
-  });
-  setDy(this.txtMax, this.params.maxFontSize, this.params.maxY);
-  this.txtMax.id = this.config.id+"-txtmax";
-
-  var defs = this.canvas.canvas.childNodes[1];
-  var svg = "http://www.w3.org/2000/svg";
-
-  if (ie < 9) {
-    onCreateElementNsReady(function() {
-      this.generateShadow(svg, defs);
-    });
-  } else {
-    this.generateShadow(svg, defs);
-  }
-
-  // var clear
-  defs, svg = null;
-
-  // execute on each animation frame
-  function onAnimate() {
-    if (obj.config.counter) {
-      var currentValue = obj.level.attr("pki");
-
-      if(obj.config.textRenderer) {
-        // this.originalValue = this.config.textRenderer(this.originalValue);
-        obj.txtValue.attr("text", obj.config.textRenderer(Math.floor(currentValue[0])));
-      } else if(obj.config.humanFriendly) {
-        // this.originalValue = humanFriendlyNumber( this.originalValue, this.config.humanFriendlyDecimal ) + this.config.symbol;
-        obj.txtValue.attr("text", humanFriendlyNumber( Math.floor(currentValue[0]), obj.config.humanFriendlyDecimal ) + obj.config.symbol);
-      } else {
-        // this.originalValue += this.config.symbol;
-        obj.txtValue.attr("text", (currentValue[0] * 1).toFixed(obj.config.decimals) + obj.config.symbol);
-      }
-
-      setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
-      currentValue = null;
-    }
-  }
-
-  if(!obj.config.counter) {
-    if(obj.config.textRenderer) {
-      obj.originalValue = obj.config.textRenderer(obj.originalValue);
-    } else if(obj.config.humanFriendly) {
-      obj.originalValue = humanFriendlyNumber( obj.originalValue, obj.config.humanFriendlyDecimal ) + obj.config.symbol;
-    } else {
-      obj.originalValue = (obj.originalValue * 1).toFixed(obj.config.decimals) + obj.config.symbol;
-    }
-
-    obj.txtValue.attr("text", obj.originalValue);
-    setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
-  }
-
-  //event fired on each animation frame
-  eve.on("raphael.anim.frame.*", onAnimate);
-
-  // animate gauge level
-  this.level.animate({pki: [this.config.value, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH,  this.params.dx, this.params.dy, this.config.gaugeWidthScale, this.config.donut]},  this.config.startAnimationTime, this.config.startAnimationType);
-
-  // animate value
-  this.txtValue.animate({"fill-opacity":"1"}, this.config.startAnimationTime, this.config.startAnimationType);
-
-  // animate label
-  this.txtLabel.animate({"fill-opacity":"1"}, this.config.startAnimationTime, this.config.startAnimationType);
-};
-
-/** Refresh gauge level */
-JustGage.prototype.refresh = function(val, max) {
-
-  var originalVal, displayVal, color, max = max || null;
-
-  // set new max
-  if(max !== null) {
-    this.config.max = max;
-
-    this.txtMaximum = this.config.max;
-    if( this.config.humanFriendly ) this.txtMaximum = humanFriendlyNumber( this.config.max, this.config.humanFriendlyDecimal );
-    this.txtMax.attr({"text" : this.config.max});
-    setDy(this.txtMax, this.params.maxFontSize, this.params.maxY);
-  }
-
-  // overflow values
-  originalVal = val;
-  displayVal = val;
-  if ((val * 1) > (this.config.max * 1)) {val = (this.config.max * 1);}
-  if ((val * 1) < (this.config.min * 1)) {val = (this.config.min * 1);}
-
-  color = getColor(val, (val - this.config.min) / (this.config.max - this.config.min), this.config.levelColors, this.config.noGradient, this.config.customSectors);
-
-  if(this.config.textRenderer) {
-    displayVal = this.config.textRenderer(displayVal);
-  } else if( this.config.humanFriendly ) {
-    displayVal = humanFriendlyNumber( displayVal, this.config.humanFriendlyDecimal ) + this.config.symbol;
-  } else {
-    displayVal = (displayVal * 1).toFixed(this.config.decimals) + this.config.symbol;
-  }
-
-  if(!this.config.counter) {
-    this.canvas.getById(this.config.id+"-txtvalue").attr({"text":displayVal});
-    setDy(this.canvas.getById(this.config.id+"-txtvalue"), this.params.valueFontSize, this.params.valueY);
-  }
-
-  this.canvas.getById(this.config.id+"-level").animate({pki: [val, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH,  this.params.dx, this.params.dy, this.config.gaugeWidthScale, this.config.donut], "fill":color},  this.config.refreshAnimationTime, this.config.refreshAnimationType);
-  this.config.value = val * 1;
-
-  // var clear
-  originalVal, displayVal, color, max = null;
-};
-
-/** Generate shadow */
-JustGage.prototype.generateShadow = function(svg, defs) {
-
-  var gaussFilter, feOffset, feGaussianBlur, feComposite1, feFlood, feComposite2, feComposite3;
-
-  // FILTER
-  gaussFilter=document.createElementNS(svg,"filter");
-  gaussFilter.setAttribute("id", this.config.id + "-inner-shadow");
-  defs.appendChild(gaussFilter);
-
-  // offset
-  feOffset = document.createElementNS(svg,"feOffset");
-  feOffset.setAttribute("dx", 0);
-  feOffset.setAttribute("dy", this.config.shadowVerticalOffset);
-  gaussFilter.appendChild(feOffset);
-
-  // blur
-  feGaussianBlur = document.createElementNS(svg,"feGaussianBlur");
-  feGaussianBlur.setAttribute("result","offset-blur");
-  feGaussianBlur.setAttribute("stdDeviation", this.config.shadowSize);
-  gaussFilter.appendChild(feGaussianBlur);
-
-  // composite 1
-  feComposite1 = document.createElementNS(svg,"feComposite");
-  feComposite1.setAttribute("operator","out");
-  feComposite1.setAttribute("in", "SourceGraphic");
-  feComposite1.setAttribute("in2","offset-blur");
-  feComposite1.setAttribute("result","inverse");
-  gaussFilter.appendChild(feComposite1);
-
-  // flood
-  feFlood = document.createElementNS(svg,"feFlood");
-  feFlood.setAttribute("flood-color","black");
-  feFlood.setAttribute("flood-opacity", this.config.shadowOpacity);
-  feFlood.setAttribute("result","color");
-  gaussFilter.appendChild(feFlood);
-
-  // composite 2
-  feComposite2 = document.createElementNS(svg,"feComposite");
-  feComposite2.setAttribute("operator","in");
-  feComposite2.setAttribute("in", "color");
-  feComposite2.setAttribute("in2","inverse");
-  feComposite2.setAttribute("result","shadow");
-  gaussFilter.appendChild(feComposite2);
-
-  // composite 3
-  feComposite3 = document.createElementNS(svg,"feComposite");
-  feComposite3.setAttribute("operator","over");
-  feComposite3.setAttribute("in", "shadow");
-  feComposite3.setAttribute("in2","SourceGraphic");
-  gaussFilter.appendChild(feComposite3);
-
-  // set shadow
-  if (!this.config.hideInnerShadow) {
-    this.canvas.canvas.childNodes[2].setAttribute("filter", "url(#" + this.config.id + "-inner-shadow)");
-    this.canvas.canvas.childNodes[3].setAttribute("filter", "url(#" + this.config.id + "-inner-shadow)");
-  }
-
-  // var clear
-  gaussFilter, feOffset, feGaussianBlur, feComposite1, feFlood, feComposite2, feComposite3 = null;
-
-};
-
-/** Get color for value */
-function getColor(val, pct, col, noGradient, custSec) {
-
-  var no, inc, colors, percentage, rval, gval, bval, lower, upper, range, rangePct, pctLower, pctUpper, color;
-  var noGradient = noGradient || custSec.length > 0;
-
-  if(custSec.length > 0) {
-    for(var i = 0; i < custSec.length; i++) {
-      if(val > custSec[i].lo && val <= custSec[i].hi) {
-        return custSec[i].color;
-      }
-    }
-  }
-
-  no = col.length;
-  if (no === 1) return col[0];
-  inc = (noGradient) ? (1 / no) : (1 / (no - 1));
-  colors = [];
-  for (var i = 0; i < col.length; i++) {
-    percentage = (noGradient) ? (inc * (i + 1)) : (inc * i);
-    rval = parseInt((cutHex(col[i])).substring(0,2),16);
-    gval = parseInt((cutHex(col[i])).substring(2,4),16);
-    bval = parseInt((cutHex(col[i])).substring(4,6),16);
-    colors[i] = { pct: percentage, color: { r: rval, g: gval, b: bval  } };
-  }
-
-  if(pct === 0) {
-    return 'rgb(' + [colors[0].color.r, colors[0].color.g, colors[0].color.b].join(',') + ')';
-  }
-
-  for (var j = 0; j < colors.length; j++) {
-    if (pct <= colors[j].pct) {
-      if (noGradient) {
-        return 'rgb(' + [colors[j].color.r, colors[j].color.g, colors[j].color.b].join(',') + ')';
-      } else {
-        lower = colors[j - 1];
-        upper = colors[j];
-        range = upper.pct - lower.pct;
-        rangePct = (pct - lower.pct) / range;
-        pctLower = 1 - rangePct;
-        pctUpper = rangePct;
-        color = {
-          r: Math.floor(lower.color.r * pctLower + upper.color.r * pctUpper),
-          g: Math.floor(lower.color.g * pctLower + upper.color.g * pctUpper),
-          b: Math.floor(lower.color.b * pctLower + upper.color.b * pctUpper)
-        };
-        return 'rgb(' + [color.r, color.g, color.b].join(',') + ')';
-      }
-    }
-  }
-
-}
-
-/** Fix Raphael display:none tspan dy attribute bug */
-function setDy(elem, fontSize, txtYpos) {
-  if ((!ie || ie > 9) && (elem.node.firstChild.attributes.dy)) {
-    elem.node.firstChild.attributes.dy.value = 0;
-  }
-}
-
-/** Random integer  */
-function getRandomInt (min, max) {
-  return Math.floor(Math.random() * (max - min + 1)) + min;
-}
-
-/**  Cut hex  */
-function cutHex(str) {
-  return (str.charAt(0)=="#") ? str.substring(1,7):str;
-}
-
-/**  Human friendly number suffix - From: http://stackoverflow.com/questions/2692323/code-golf-friendly-number-abbreviator */
-function humanFriendlyNumber( n, d ) {
-  var p, d2, i, s;
-
-  p = Math.pow;
-  d2 = p(10, d);
-  i = 7;
-  while( i ) {
-    s = p(10,i--*3);
-    if( s <= n ) {
-     n = Math.round(n*d2/s)/d2+"KMGTPE"[i];
-   }
- }
- return n;
-}
-
-/**  Get style  */
-function getStyle(oElm, strCssRule){
-  var strValue = "";
-  if(document.defaultView && document.defaultView.getComputedStyle){
-    strValue = document.defaultView.getComputedStyle(oElm).getPropertyValue(strCssRule);
-  }
-  else if(oElm.currentStyle){
-    strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){
-      return p1.toUpperCase();
-    });
-    strValue = oElm.currentStyle[strCssRule];
-  }
-  return strValue;
-}
-
-/**  Create Element NS Ready  */
-function onCreateElementNsReady(func) {
-  if (document.createElementNS !== undefined) {
-    func();
-  } else {
-    setTimeout(function() { onCreateElementNsReady(func); }, 100);
-  }
-}
-
-/**  Get IE version  */
-// ----------------------------------------------------------
-// A short snippet for detecting versions of IE in JavaScript
-// without resorting to user-agent sniffing
-// ----------------------------------------------------------
-// If you're not in IE (or IE version is less than 5) then:
-// ie === undefined
-// If you're in IE (>=5) then you can determine which version:
-// ie === 7; // IE7
-// Thus, to detect IE:
-// if (ie) {}
-// And to detect the version:
-// ie === 6 // IE6
-// ie > 7 // IE8, IE9 ...
-// ie < 9 // Anything less than IE9
-// ----------------------------------------------------------
-// UPDATE: Now using Live NodeList idea from @jdalton
-var ie = (function(){
-
-  var undef,
-  v = 3,
-  div = document.createElement('div'),
-  all = div.getElementsByTagName('i');
-
-  while (
-    div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',
-    all[0]
-    );
-    return v > 4 ? v : undef;
-}());
\ No newline at end of file
index ae5062b..b1fadda 100644 (file)
     margin: 10px;
 }
 .openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list div {
-    width: 120px;
+    width: 160px;
     height: 22px;
     margin: 0;
-}
-.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list div:nth-child(2n) {
-    position: absolute;
-    right: 0px;
-}
-.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list div:nth-child(2n) a {
     position: relative;
-    top: -20px;
-}
-.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_kanban_content > div {
-    clear: both;
-}
-.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list div a:first-child {
-    width: 80px;
+    display: inline-block;
 }
 .openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list a:hover {
     text-decoration: underline !important;
 }
+.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list div a:nth-child(2n) {
+    position: absolute;
+    left: 90px;
+    top: 0;
+}
+.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list div:nth-child(2n) a:nth-child(2n) {
+    left: 110px;
+}
 .openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_center {
     text-align: center;
     margin: 3px 0;
@@ -63,6 +59,6 @@
 }
 
 .openerp .oe_kanban_view .oe_sparkline_bar {
-    float: right;
     height: 20px;
+    width: 36px;
 }
\ No newline at end of file
index 22d042c..656e368 100644 (file)
@@ -1,93 +1,5 @@
 openerp.crm = function(openerp) {
     openerp.web_kanban.KanbanRecord.include({
-        renderElement: function () {
-            var rendering = this._super();
-
-            if (this.view.dataset.model === 'crm.case.section') {
-                var self = this;
-                $.when(rendering).done(function() {
-                    self.$(".oe_justgage").each(function () {
-                        var $el = $(this);
-                        var title = $el.html();
-                        var unique_id = _.uniqueId("JustGage");
-                        $el.empty().css("position", "relative").attr('id', unique_id);
-
-                        new JustGage({
-                            id: unique_id,
-                            node: this,
-                            title: title,
-                            value: +$el.data('value'),
-                            min: 0,
-                            max: +$el.data('max'),
-                            relativeGaugeSize: true,
-                            humanFriendly: true,
-                            titleFontColor: '#333333',
-                            valueFontColor: '#333333',
-                            labelFontColor: '#000',
-                            label: $el.data('label'),
-                            levelColors: [
-                                "#ff0000",
-                                "#f9c802",
-                                "#a9d70b"
-                            ],
-                        });
-
-                        var flag_open = false;
-                        if ($el.data('action')) {
-                            $el.click(function (event) {
-                                event.stopPropagation();
-                                flag_open = false;
-                                if (!self.view.is_action_enabled('edit')) {
-                                    return;
-                                }
-                                if (!$el.find(".oe_justgage_edit").size()) {
-                                    var $svg = $el.find('svg');
-                                    $div = $('<div class="oe_justgage_edit" style="text-align: center; z-index: 1; position: absolute; width: ' + $svg.outerWidth() + 'px; top: ' + ($svg.outerHeight()/2-5) + 'px;"/>');
-                                    $input = $('<input style="text-align: center; width: ' + ($svg.outerWidth()-40) + 'px; margin: auto;"/>').val($el.data('value'));
-                                    $div.append($input);
-                                    $el.prepend($div)
-                                    $input.focus()
-                                        .keydown(function (event) {
-                                            event.stopPropagation();
-                                            if (event.keyCode == 13 || event.keyCode == 9) {
-                                                if ($input.val() != $el.data('value')) {
-                                                    self.view.dataset.call($el.data('action'), [self.id, $input.val()]).then(function () {
-                                                        self.do_reload();
-                                                    });
-                                                } else {
-                                                    $div.remove();
-                                                }
-                                            }
-                                        })
-                                        .click(function (event) {
-                                            event.stopPropagation();
-                                            flag_open = false;
-                                        })
-                                        .blur(function (event) {
-                                            if(!flag_open) {
-                                                $el.find(".oe_justgage_edit").remove();
-                                            } else {
-                                                flag_open = false;
-                                                setTimeout(function () {$input.focus();}, 0);
-                                            }
-                                        });
-                                }
-                            }).mousedown(function () {
-                                flag_open = true;
-                            });
-                        }
-                    });
-                    setTimeout(function () {
-                        self.$(".oe_sparkline_bar").each(function () {
-                            var $el = $(this);
-                            $el.data("title", $el.html());
-                            $el.sparkline($el.data("value").split(','), {type: 'bar', barWidth: 5} );
-                            $el.tipsy({'delayIn': 0, 'html': true, 'title': function(){return $(this).data("title")}, 'gravity': 'n'});
-                        });
-                    }, 0);
-                });
-            }
-        },
         on_card_clicked: function() {
             if (this.view.dataset.model === 'crm.case.section') {
                 this.$('.oe_kanban_crm_salesteams_list a').first().click();
@@ -97,4 +9,17 @@ openerp.crm = function(openerp) {
         },
     });
 
+    openerp.crm.SparklineBarWidget = openerp.web_kanban.AbstractField.extend({
+        className: "oe_sparkline_bar",
+        start: function() {
+            var self = this;
+            var title = this.$node.html();
+            setTimeout(function () {
+                self.$el.sparkline(self.field.value, {type: 'bar', barWidth: 5} );
+                self.$el.tipsy({'delayIn': 0, 'html': true, 'title': function(){return title}, 'gravity': 'n'});
+            }, 0);
+        },
+    });
+    openerp.web_kanban.fields_registry.add("sparkline_bar", "openerp.crm.SparklineBarWidget");
+
 };
index 7bc1dc2..8cadff5 100644 (file)
@@ -47,6 +47,10 @@ modules.
         'security/ir.model.access.csv',
         'report/sale_crm_account_invoice_report_view.xml',
     ],
+    'js': [
+        'static/src/js/sale_crm.js',
+        'static/lib/justgage.js',
+    ],
     'demo': ['sale_crm_demo.xml'],
     'test': ['test/sale_crm.yml'],
     'installable': True,
index fe29c72..d169bf4 100644 (file)
                 </xpath>
                 <xpath expr="//div[@class='oe_items_list']" position="inside">
                     <div>
-                        <a name="%(action_quotations_salesteams)d" type="action">Quotations</a>
-                        <a name="%(sale.action_order_report_all)d" type="action" class="oe_sparkline_bar" t-att-data-value="record.created_quotation_per_duration.raw_value">Revenue of created quotation per <t t-esc="record.target_duration_txt.value"/>.<br/>Click to acces the Sales Analysis</a>
+                        <a name="%(action_quotations_salesteams)d" type="action" class="oe_sparkline_bar_link">Quotations</a>
+                        <a name="%(sale.action_order_report_all)d" type="action" class="oe_sparkline_bar_link"><field name="created_quotation_per_duration" widget="sparkline_bar">Revenue of created quotation per <t t-esc="record.target_duration_txt.value"/>.<br/>Click to acces the Sales Analysis</field></a>
                     </div>
                     <div>
                         <a name="%(action_orders_salesteams)d" type="action">Sales Orders</a>
-                        <a name="%(sale.action_order_report_all)d" type="action" class="oe_sparkline_bar" t-att-data-value="record.validate_saleorder_per_duration.raw_value">Revenue of confirmed sales orders per <t t-esc="record.target_duration_txt.value"/>).<br/>Click the acces to Sales Analysis</a>
+                        <a name="%(sale.action_order_report_all)d" type="action" class="oe_sparkline_bar_link"><field name="validate_saleorder_per_duration" widget="sparkline_bar">Revenue of confirmed sales orders per <t t-esc="record.target_duration_txt.value"/>).<br/>Click the acces to Sales Analysis</field></a>
                     </div>
                     <div>
                         <a name="%(action_invoice_salesteams)d" type="action" groups="account.group_account_invoice">Invoices</a>
-                        <a name="%(account.action_account_invoice_report_all)d" type="action" class="oe_sparkline_bar" t-att-data-value="record.sent_invoice_per_duration.raw_value">Revenue of sent invoices per <t t-esc="record.target_duration_txt.value"/>.<br/>Click to acces the Invoices Analysis</a>
+                        <a name="%(account.action_account_invoice_report_all)d" type="action" class="oe_sparkline_bar_link"><field name="sent_invoice_per_duration" widget="sparkline_bar">Revenue of sent invoices per <t t-esc="record.target_duration_txt.value"/>.<br/>Click to acces the Invoices Analysis</field></a>
                     </div>
                 </xpath>
                 <xpath expr="//div[@class='oe_items_list']" position="after">
                     <div class="oe_center" t-if="record.target_invoice.raw_value">
-                        <div class="oe_justgage" style="width:160px; height: 120px;"
-                            t-att-data-value="record.sent_invoice_per_duration.raw_value.pop()"
-                            t-att-data-max="record.target_invoice.raw_value"
-                            t-att-data-label="_.str.sprintf(_t('this %%s'), record.target_duration_txt.value)">Invoiced</div>
-                        <div class="oe_justgage" style="width:160px; height: 120px;"
-                            t-att-data-value="record.forecast.raw_value"
-                            t-att-data-max="record.target_invoice.raw_value"
-                            t-att-data-label="_.str.sprintf(_t('this %%s'), record.target_duration_txt.value)"
-                            data-action="action_forecast">Forecast</div>
+                        <field name="sent_invoice_per_duration" widget="gage" style="width:160px; height: 120px;" options="{'max_field': 'target_invoice', 'label_field': 'target_duration_txt'}">Invoiced</field>
+                        <field name="forecast" widget="gage" style="width:160px; height: 120px;" options="{'max_field': 'target_invoice', 'label_field': 'target_duration_txt'}">Forecast</field>
                     </div>
                     <div class="oe_center" style="color:#bbbbbb;" t-if="!record.target_invoice.raw_value">
                         <br/>Not target invoicing defined
diff --git a/addons/sale_crm/static/lib/justgage.js b/addons/sale_crm/static/lib/justgage.js
new file mode 100644 (file)
index 0000000..52b5c6e
--- /dev/null
@@ -0,0 +1,883 @@
+/**
+ * JustGage - this is work-in-progress, unreleased, unofficial code, so it might not work top-notch :)
+ * Check http://www.justgage.com for official releases
+ * Licensed under MIT.
+ * @author Bojan Djuricic  (@Toorshia)
+ *
+ * LATEST UPDATES
+
+ * -----------------------------
+ * April 01, 2013.
+ * -----------------------------
+     * fix - https://github.com/toorshia/justgage/issues/46
+
+ * -----------------------------
+ * March 26, 2013.
+ * -----------------------------
+     * customSectors - define specific color for value range (0-10 : red, 10-30 : blue etc.)
+
+ * -----------------------------
+ * March 23, 2013.
+ * -----------------------------
+     * counter - option to animate value  in counting fashion
+     * fix - https://github.com/toorshia/justgage/issues/45
+
+ * -----------------------------
+ * March 13, 2013.
+ * -----------------------------
+     * refresh method - added optional 'max' parameter to use when you need to update max value
+
+ * -----------------------------
+ * February 26, 2013.
+ * -----------------------------
+     * decimals - option to define/limit number of decimals when not using humanFriendly or customRenderer to display value
+     * fixed a missing parameters bug when calling generateShadow()  for IE < 9
+
+ * -----------------------------
+ * December 31, 2012.
+ * -----------------------------
+     * fixed text y-position for hidden divs - workaround for Raphael <tspan> 'dy' bug - https://github.com/DmitryBaranovskiy/raphael/issues/491
+     * 'show' parameters, like showMinMax are now 'hide' because I am lame developer - please update these in your setups
+     * Min and Max labels are now auto-off when in donut mode
+     * Start angle in donut mode is now 90
+     * donutStartAngle - option to define start angle for donut
+
+ * -----------------------------
+ * November 25, 2012.
+ * -----------------------------
+     * Option to define custom rendering function for displayed value
+
+ * -----------------------------
+ * November 19, 2012.
+ * -----------------------------
+     * Config.value is now updated after gauge refresh
+
+ * -----------------------------
+ * November 13, 2012.
+ * -----------------------------
+     * Donut display mode added
+     * Option to hide value label
+     * Option to enable responsive gauge size
+     * Removed default title attribute
+     * Option to accept min and max defined as string values
+     * Option to configure value symbol
+     * Fixed bad aspect ratio calculations
+     * Option to configure minimum font size for all texts
+     * Option to show shorthand big numbers (human friendly)
+     */
+
+ JustGage = function(config) {
+
+  if (!config.id) {alert("Missing id parameter for gauge!"); return false;}
+  if (!config.node) {
+    if (!document.getElementById(config.id)) {alert("No element with id: \""+config.id+"\" found!"); return false;}
+    config.node = document.getElementById(config.id);
+  }
+
+  var obj = this;
+
+  // configurable parameters
+  obj.config =
+  {
+    // id : string
+    // this is container element id
+    id : config.id,
+
+    // node : string
+    // the node to use instead of DOM
+    node : config.node,
+
+    // title : string
+    // gauge title
+    title : (config.title) ? config.title : "",
+
+    // titleFontColor : string
+    // color of gauge title
+    titleFontColor : (config.titleFontColor) ? config.titleFontColor : "#999999",
+
+    // value : int
+    // value gauge is showing
+    value : (config.value) ? config.value : 0,
+
+
+    // valueFontColor : string
+    // color of label showing current value
+    valueFontColor : (config.valueFontColor) ? config.valueFontColor : "#010101",
+
+    // symbol : string
+    // special symbol to show next to value
+    symbol : (config.symbol) ? config.symbol : "",
+
+    // min : int
+    // min value
+    min : (config.min) ? parseFloat(config.min) : 0,
+
+    // max : int
+    // max value
+    max : (config.max) ? parseFloat(config.max) : 100,
+
+    // humanFriendlyDecimal : int
+    // number of decimal places for our human friendly number to contain
+    humanFriendlyDecimal : (config.humanFriendlyDecimal) ? config.humanFriendlyDecimal : 0,
+
+    // textRenderer: func
+    // function applied before rendering text
+    textRenderer  : (config.textRenderer) ? config.textRenderer : null,
+
+    // gaugeWidthScale : float
+    // width of the gauge element
+    gaugeWidthScale : (config.gaugeWidthScale) ? config.gaugeWidthScale : 1.0,
+
+    // gaugeColor : string
+    // background color of gauge element
+    gaugeColor : (config.gaugeColor) ? config.gaugeColor : "#edebeb",
+
+    // label : string
+    // text to show below value
+    label : (config.label) ? config.label : "",
+
+    // labelFontColor : string
+    // color of label showing label under value
+    labelFontColor : (config.labelFontColor) ? config.labelFontColor : "#b3b3b3",
+
+    // shadowOpacity : int
+    // 0 ~ 1
+    shadowOpacity : (config.shadowOpacity) ? config.shadowOpacity : 0.2,
+
+    // shadowSize: int
+    // inner shadow size
+    shadowSize : (config.shadowSize) ? config.shadowSize : 5,
+
+    // shadowVerticalOffset : int
+    // how much shadow is offset from top
+    shadowVerticalOffset : (config.shadowVerticalOffset) ? config.shadowVerticalOffset : 3,
+
+    // levelColors : string[]
+    // colors of indicator, from lower to upper, in RGB format
+    levelColors : (config.levelColors) ? config.levelColors : [
+    "#a9d70b",
+    "#f9c802",
+    "#ff0000"
+    ],
+
+    // startAnimationTime : int
+    // length of initial animation
+    startAnimationTime : (config.startAnimationTime) ? config.startAnimationTime : 700,
+
+    // startAnimationType : string
+    // type of initial animation (linear, >, <,  <>, bounce)
+    startAnimationType : (config.startAnimationType) ? config.startAnimationType : ">",
+
+    // refreshAnimationTime : int
+    // length of refresh animation
+    refreshAnimationTime : (config.refreshAnimationTime) ? config.refreshAnimationTime : 700,
+
+    // refreshAnimationType : string
+    // type of refresh animation (linear, >, <,  <>, bounce)
+    refreshAnimationType : (config.refreshAnimationType) ? config.refreshAnimationType : ">",
+
+    // donutStartAngle : int
+    // angle to start from when in donut mode
+    donutStartAngle : (config.donutStartAngle) ? config.donutStartAngle : 90,
+
+    // valueMinFontSize : int
+    // absolute minimum font size for the value
+    valueMinFontSize : config.valueMinFontSize || 16,
+
+    // titleMinFontSize
+    // absolute minimum font size for the title
+    titleMinFontSize : config.titleMinFontSize || 10,
+
+    // labelMinFontSize
+    // absolute minimum font size for the label
+    labelMinFontSize : config.labelMinFontSize || 10,
+
+    // minLabelMinFontSize
+    // absolute minimum font size for the minimum label
+    minLabelMinFontSize : config.minLabelMinFontSize || 10,
+
+    // maxLabelMinFontSize
+    // absolute minimum font size for the maximum label
+    maxLabelMinFontSize : config.maxLabelMinFontSize || 10,
+
+    // hideValue : bool
+    // hide value text
+    hideValue : (config.hideValue) ? config.hideValue : false,
+
+    // hideMinMax : bool
+    // hide min and max values
+    hideMinMax : (config.hideMinMax) ? config.hideMinMax : false,
+
+    // hideInnerShadow : bool
+    // hide inner shadow
+    hideInnerShadow : (config.hideInnerShadow) ? config.hideInnerShadow : false,
+
+    // humanFriendly : bool
+    // convert large numbers for min, max, value to human friendly (e.g. 1234567 -> 1.23M)
+    humanFriendly : (config.humanFriendly) ? config.humanFriendly : false,
+
+    // noGradient : bool
+    // whether to use gradual color change for value, or sector-based
+    noGradient : (config.noGradient) ? config.noGradient : false,
+
+    // donut : bool
+    // show full donut gauge
+    donut : (config.donut) ? config.donut : false,
+
+    // relativeGaugeSize : bool
+    // whether gauge size should follow changes in container element size
+    relativeGaugeSize : (config.relativeGaugeSize) ? config.relativeGaugeSize : false,
+
+    // counter : bool
+    // animate level number change
+    counter : (config.counter) ? config.counter : false,
+
+    // decimals : int
+    // number of digits after floating point
+    decimals : (config.decimals) ? config.decimals : 0,
+
+    // customSectors : [] of objects
+    // number of digits after floating point
+    customSectors : (config.customSectors) ? config.customSectors : []
+  };
+
+  // variables
+  var
+  canvasW,
+  canvasH,
+  widgetW,
+  widgetH,
+  aspect,
+  dx,
+  dy,
+  titleFontSize,
+  titleX,
+  titleY,
+  valueFontSize,
+  valueX,
+  valueY,
+  labelFontSize,
+  labelX,
+  labelY,
+  minFontSize,
+  minX,
+  minY,
+  maxFontSize,
+  maxX,
+  maxY;
+
+  // overflow values
+  if (this.config.value > this.config.max) this.config.value = this.config.max;
+  if (this.config.value < this.config.min) this.config.value = this.config.min;
+  this.originalValue = config.value;
+
+  // canvas
+  this.canvas = Raphael(this.config.node, "100%", "100%");
+  if (this.config.relativeGaugeSize === true) {
+    this.canvas.setViewBox(0, 0, 200, 150, true);
+  }
+
+  // canvas dimensions
+  if (this.config.relativeGaugeSize === true) {
+    canvasW = 200;
+    canvasH = 150;
+  } else {
+    canvasW = getStyle(this.config.node, "width").slice(0, -2) * 1;
+    canvasH = getStyle(this.config.node, "height").slice(0, -2) * 1;
+  }
+
+  // widget dimensions
+  if (this.config.donut === true) {
+
+    // DONUT *******************************
+
+    // width more than height
+    if(canvasW > canvasH) {
+      widgetH = canvasH;
+      widgetW = widgetH;
+    // width less than height
+  } else if (canvasW < canvasH) {
+    widgetW = canvasW;
+    widgetH = widgetW;
+      // if height don't fit, rescale both
+      if(widgetH > canvasH) {
+        aspect = widgetH / canvasH;
+        widgetH = widgetH / aspect;
+        widgetW = widgetH / aspect;
+      }
+    // equal
+  } else {
+    widgetW = canvasW;
+    widgetH = widgetW;
+  }
+
+    // delta
+    dx = (canvasW - widgetW)/2;
+    dy = (canvasH - widgetH)/2;
+
+    // title
+    titleFontSize = ((widgetH / 8) > 10) ? (widgetH / 10) : 10;
+    titleX = dx + widgetW / 2;
+    titleY = dy + widgetH / 11;
+
+    // value
+    valueFontSize = ((widgetH / 6.4) > 16) ? (widgetH / 5.4) : 18;
+    valueX = dx + widgetW / 2;
+    if(this.config.label !== '') {
+      valueY = dy + widgetH / 1.85;
+    } else {
+      valueY = dy + widgetH / 1.7;
+    }
+
+    // label
+    labelFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
+    labelX = dx + widgetW / 2;
+    labelY = valueY + labelFontSize;
+
+    // min
+    minFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
+    minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2 ;
+    minY = labelY;
+
+    // max
+    maxFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
+    maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2 ;
+    maxY = labelY;
+
+  } else {
+    // HALF *******************************
+
+    // width more than height
+    if(canvasW > canvasH) {
+      widgetH = canvasH;
+      widgetW = widgetH * 1.25;
+      //if width doesn't fit, rescale both
+      if(widgetW > canvasW) {
+        aspect = widgetW / canvasW;
+        widgetW = widgetW / aspect;
+        widgetH = widgetH / aspect;
+      }
+    // width less than height
+  } else if (canvasW < canvasH) {
+    widgetW = canvasW;
+    widgetH = widgetW / 1.25;
+      // if height don't fit, rescale both
+      if(widgetH > canvasH) {
+        aspect = widgetH / canvasH;
+        widgetH = widgetH / aspect;
+        widgetW = widgetH / aspect;
+      }
+    // equal
+  } else {
+    widgetW = canvasW;
+    widgetH = widgetW * 0.75;
+  }
+
+    // delta
+    dx = (canvasW - widgetW)/2;
+    dy = (canvasH - widgetH)/2;
+
+    // title
+    titleFontSize = ((widgetH / 8) > this.config.titleMinFontSize) ? (widgetH / 10) : this.config.titleMinFontSize;
+    titleX = dx + widgetW / 2;
+    titleY = dy + widgetH / 6.4;
+
+    // value
+    valueFontSize = ((widgetH / 6.5) > this.config.valueMinFontSize) ? (widgetH / 6.5) : this.config.valueMinFontSize;
+    valueX = dx + widgetW / 2;
+    valueY = dy + widgetH / 1.275;
+
+    // label
+    labelFontSize = ((widgetH / 16) > this.config.labelMinFontSize) ? (widgetH / 16) : this.config.labelMinFontSize;
+    labelX = dx + widgetW / 2;
+    labelY = valueY + valueFontSize / 2 + 5;
+
+    // min
+    minFontSize = ((widgetH / 16) > this.config.minLabelMinFontSize) ? (widgetH / 16) : this.config.minLabelMinFontSize;
+    minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2 ;
+    minY = labelY;
+
+    // max
+    maxFontSize = ((widgetH / 16) > this.config.maxLabelMinFontSize) ? (widgetH / 16) : this.config.maxLabelMinFontSize;
+    maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2 ;
+    maxY = labelY;
+  }
+
+  // parameters
+  this.params  = {
+    canvasW : canvasW,
+    canvasH : canvasH,
+    widgetW : widgetW,
+    widgetH : widgetH,
+    dx : dx,
+    dy : dy,
+    titleFontSize : titleFontSize,
+    titleX : titleX,
+    titleY : titleY,
+    valueFontSize : valueFontSize,
+    valueX : valueX,
+    valueY : valueY,
+    labelFontSize : labelFontSize,
+    labelX : labelX,
+    labelY : labelY,
+    minFontSize : minFontSize,
+    minX : minX,
+    minY : minY,
+    maxFontSize : maxFontSize,
+    maxX : maxX,
+    maxY : maxY
+  };
+
+  // var clear
+  canvasW, canvasH, widgetW, widgetH, aspect, dx, dy, titleFontSize, titleX, titleY, valueFontSize, valueX, valueY, labelFontSize, labelX, labelY, minFontSize, minX, minY, maxFontSize, maxX, maxY = null
+
+  // pki - custom attribute for generating gauge paths
+  this.canvas.customAttributes.pki = function (value, min, max, w, h, dx, dy, gws, donut) {
+
+    var alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, path;
+
+    if (donut) {
+      alpha = (1 - 2 * (value - min) / (max - min)) * Math.PI;
+      Ro = w / 2 - w / 7;
+      Ri = Ro - w / 6.666666666666667 * gws;
+
+      Cx = w / 2 + dx;
+      Cy = h / 1.95 + dy;
+
+      Xo = w / 2 + dx + Ro * Math.cos(alpha);
+      Yo = h - (h - Cy) + 0 - Ro * Math.sin(alpha);
+      Xi = w / 2 + dx + Ri * Math.cos(alpha);
+      Yi = h - (h - Cy) + 0 - Ri * Math.sin(alpha);
+
+      path += "M" + (Cx - Ri) + "," + Cy + " ";
+      path += "L" + (Cx - Ro) + "," + Cy + " ";
+      if (value > ((max - min) / 2)) {
+        path += "A" + Ro + "," + Ro + " 0 0 1 " + (Cx + Ro) + "," + Cy + " ";
+      }
+      path += "A" + Ro + "," + Ro + " 0 0 1 " + Xo + "," + Yo + " ";
+      path += "L" + Xi + "," + Yi + " ";
+      if (value > ((max - min) / 2)) {
+        path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx + Ri) + "," + Cy + " ";
+      }
+      path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx - Ri) + "," + Cy + " ";
+      path += "Z ";
+
+      return { path: path };
+
+    } else {
+      alpha = (1 - (value - min) / (max - min)) * Math.PI;
+      Ro = w / 2 - w / 10;
+      Ri = Ro - w / 6.666666666666667 * gws;
+
+      Cx = w / 2 + dx;
+      Cy = h / 1.25 + dy;
+
+      Xo = w / 2 + dx + Ro * Math.cos(alpha);
+      Yo = h - (h - Cy) + 0 - Ro * Math.sin(alpha);
+      Xi = w / 2 + dx + Ri * Math.cos(alpha);
+      Yi = h - (h - Cy) + 0 - Ri * Math.sin(alpha);
+
+      path += "M" + (Cx - Ri) + "," + Cy + " ";
+      path += "L" + (Cx - Ro) + "," + Cy + " ";
+      path += "A" + Ro + "," + Ro + " 0 0 1 " + Xo + "," + Yo + " ";
+      path += "L" + Xi + "," + Yi + " ";
+      path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx - Ri) + "," + Cy + " ";
+      path += "Z ";
+
+      return { path: path };
+    }
+
+    // var clear
+    alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, path = null;
+  };
+
+  // gauge
+  this.gauge = this.canvas.path().attr({
+    "stroke": "none",
+    "fill": this.config.gaugeColor,
+    pki: [this.config.max, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH,  this.params.dx, this.params.dy, this.config.gaugeWidthScale, this.config.donut]
+  });
+  this.gauge.id = this.config.id+"-gauge";
+
+  // level
+  this.level = this.canvas.path().attr({
+    "stroke": "none",
+    "fill": getColor(this.config.value, (this.config.value - this.config.min) / (this.config.max - this.config.min), this.config.levelColors, this.config.noGradient, this.config.customSectors),
+    pki: [this.config.min, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH,  this.params.dx, this.params.dy, this.config.gaugeWidthScale, this.config.donut]
+  });
+  if(this.config.donut) {
+    this.level.transform("r" + this.config.donutStartAngle + ", " + (this.params.widgetW/2 + this.params.dx) + ", " + (this.params.widgetH/1.95 + this.params.dy));
+  }
+  this.level.id = this.config.id+"-level";
+
+  // title
+  this.txtTitle = this.canvas.text(this.params.titleX, this.params.titleY, this.config.title);
+  this.txtTitle.attr({
+    "font-size":this.params.titleFontSize,
+    "font-weight":"bold",
+    "font-family":"Arial",
+    "fill":this.config.titleFontColor,
+    "fill-opacity":"1"
+  });
+  setDy(this.txtTitle, this.params.titleFontSize, this.params.titleY);
+  this.txtTitle.id = this.config.id+"-txttitle";
+
+  // value
+  this.txtValue = this.canvas.text(this.params.valueX, this.params.valueY, 0);
+  this.txtValue.attr({
+    "font-size":this.params.valueFontSize,
+    "font-weight":"bold",
+    "font-family":"Arial",
+    "fill":this.config.valueFontColor,
+    "fill-opacity":"0"
+  });
+  setDy(this.txtValue, this.params.valueFontSize, this.params.valueY);
+  this.txtValue.id = this.config.id+"-txtvalue";
+
+  // label
+  this.txtLabel = this.canvas.text(this.params.labelX, this.params.labelY, this.config.label);
+  this.txtLabel.attr({
+    "font-size":this.params.labelFontSize,
+    "font-weight":"normal",
+    "font-family":"Arial",
+    "fill":this.config.labelFontColor,
+    "fill-opacity":"0"
+  });
+  setDy(this.txtLabel, this.params.labelFontSize, this.params.labelY);
+  this.txtLabel.id = this.config.id+"-txtlabel";
+
+  // min
+  this.txtMinimum = this.config.min;
+  if( this.config.humanFriendly ) this.txtMinimum = humanFriendlyNumber( this.config.min, this.config.humanFriendlyDecimal );
+  this.txtMin = this.canvas.text(this.params.minX, this.params.minY, this.txtMinimum);
+  this.txtMin.attr({
+    "font-size":this.params.minFontSize,
+    "font-weight":"normal",
+    "font-family":"Arial",
+    "fill":this.config.labelFontColor,
+    "fill-opacity": (this.config.hideMinMax || this.config.donut)? "0" : "1"
+  });
+  setDy(this.txtMin, this.params.minFontSize, this.params.minY);
+  this.txtMin.id = this.config.id+"-txtmin";
+
+  // max
+  this.txtMaximum = this.config.max;
+  if( this.config.humanFriendly ) this.txtMaximum = humanFriendlyNumber( this.config.max, this.config.humanFriendlyDecimal );
+  this.txtMax = this.canvas.text(this.params.maxX, this.params.maxY, this.txtMaximum);
+  this.txtMax.attr({
+    "font-size":this.params.maxFontSize,
+    "font-weight":"normal",
+    "font-family":"Arial",
+    "fill":this.config.labelFontColor,
+    "fill-opacity": (this.config.hideMinMax || this.config.donut)? "0" : "1"
+  });
+  setDy(this.txtMax, this.params.maxFontSize, this.params.maxY);
+  this.txtMax.id = this.config.id+"-txtmax";
+
+  var defs = this.canvas.canvas.childNodes[1];
+  var svg = "http://www.w3.org/2000/svg";
+
+  if (ie < 9) {
+    onCreateElementNsReady(function() {
+      this.generateShadow(svg, defs);
+    });
+  } else {
+    this.generateShadow(svg, defs);
+  }
+
+  // var clear
+  defs, svg = null;
+
+  // execute on each animation frame
+  function onAnimate() {
+    if (obj.config.counter) {
+      var currentValue = obj.level.attr("pki");
+
+      if(obj.config.textRenderer) {
+        // this.originalValue = this.config.textRenderer(this.originalValue);
+        obj.txtValue.attr("text", obj.config.textRenderer(Math.floor(currentValue[0])));
+      } else if(obj.config.humanFriendly) {
+        // this.originalValue = humanFriendlyNumber( this.originalValue, this.config.humanFriendlyDecimal ) + this.config.symbol;
+        obj.txtValue.attr("text", humanFriendlyNumber( Math.floor(currentValue[0]), obj.config.humanFriendlyDecimal ) + obj.config.symbol);
+      } else {
+        // this.originalValue += this.config.symbol;
+        obj.txtValue.attr("text", (currentValue[0] * 1).toFixed(obj.config.decimals) + obj.config.symbol);
+      }
+
+      setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
+      currentValue = null;
+    }
+  }
+
+  if(!obj.config.counter) {
+    if(obj.config.textRenderer) {
+      obj.originalValue = obj.config.textRenderer(obj.originalValue);
+    } else if(obj.config.humanFriendly) {
+      obj.originalValue = humanFriendlyNumber( obj.originalValue, obj.config.humanFriendlyDecimal ) + obj.config.symbol;
+    } else {
+      obj.originalValue = (obj.originalValue * 1).toFixed(obj.config.decimals) + obj.config.symbol;
+    }
+
+    obj.txtValue.attr("text", obj.originalValue);
+    setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
+  }
+
+  //event fired on each animation frame
+  eve.on("raphael.anim.frame.*", onAnimate);
+
+  // animate gauge level
+  this.level.animate({pki: [this.config.value, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH,  this.params.dx, this.params.dy, this.config.gaugeWidthScale, this.config.donut]},  this.config.startAnimationTime, this.config.startAnimationType);
+
+  // animate value
+  this.txtValue.animate({"fill-opacity":"1"}, this.config.startAnimationTime, this.config.startAnimationType);
+
+  // animate label
+  this.txtLabel.animate({"fill-opacity":"1"}, this.config.startAnimationTime, this.config.startAnimationType);
+};
+
+/** Refresh gauge level */
+JustGage.prototype.refresh = function(val, max) {
+
+  var originalVal, displayVal, color, max = max || null;
+
+  // set new max
+  if(max !== null) {
+    this.config.max = max;
+
+    this.txtMaximum = this.config.max;
+    if( this.config.humanFriendly ) this.txtMaximum = humanFriendlyNumber( this.config.max, this.config.humanFriendlyDecimal );
+    this.txtMax.attr({"text" : this.config.max});
+    setDy(this.txtMax, this.params.maxFontSize, this.params.maxY);
+  }
+
+  // overflow values
+  originalVal = val;
+  displayVal = val;
+  if ((val * 1) > (this.config.max * 1)) {val = (this.config.max * 1);}
+  if ((val * 1) < (this.config.min * 1)) {val = (this.config.min * 1);}
+
+  color = getColor(val, (val - this.config.min) / (this.config.max - this.config.min), this.config.levelColors, this.config.noGradient, this.config.customSectors);
+
+  if(this.config.textRenderer) {
+    displayVal = this.config.textRenderer(displayVal);
+  } else if( this.config.humanFriendly ) {
+    displayVal = humanFriendlyNumber( displayVal, this.config.humanFriendlyDecimal ) + this.config.symbol;
+  } else {
+    displayVal = (displayVal * 1).toFixed(this.config.decimals) + this.config.symbol;
+  }
+
+  if(!this.config.counter) {
+    this.canvas.getById(this.config.id+"-txtvalue").attr({"text":displayVal});
+    setDy(this.canvas.getById(this.config.id+"-txtvalue"), this.params.valueFontSize, this.params.valueY);
+  }
+
+  this.canvas.getById(this.config.id+"-level").animate({pki: [val, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH,  this.params.dx, this.params.dy, this.config.gaugeWidthScale, this.config.donut], "fill":color},  this.config.refreshAnimationTime, this.config.refreshAnimationType);
+  this.config.value = val * 1;
+
+  // var clear
+  originalVal, displayVal, color, max = null;
+};
+
+/** Generate shadow */
+JustGage.prototype.generateShadow = function(svg, defs) {
+
+  var gaussFilter, feOffset, feGaussianBlur, feComposite1, feFlood, feComposite2, feComposite3;
+
+  // FILTER
+  gaussFilter=document.createElementNS(svg,"filter");
+  gaussFilter.setAttribute("id", this.config.id + "-inner-shadow");
+  defs.appendChild(gaussFilter);
+
+  // offset
+  feOffset = document.createElementNS(svg,"feOffset");
+  feOffset.setAttribute("dx", 0);
+  feOffset.setAttribute("dy", this.config.shadowVerticalOffset);
+  gaussFilter.appendChild(feOffset);
+
+  // blur
+  feGaussianBlur = document.createElementNS(svg,"feGaussianBlur");
+  feGaussianBlur.setAttribute("result","offset-blur");
+  feGaussianBlur.setAttribute("stdDeviation", this.config.shadowSize);
+  gaussFilter.appendChild(feGaussianBlur);
+
+  // composite 1
+  feComposite1 = document.createElementNS(svg,"feComposite");
+  feComposite1.setAttribute("operator","out");
+  feComposite1.setAttribute("in", "SourceGraphic");
+  feComposite1.setAttribute("in2","offset-blur");
+  feComposite1.setAttribute("result","inverse");
+  gaussFilter.appendChild(feComposite1);
+
+  // flood
+  feFlood = document.createElementNS(svg,"feFlood");
+  feFlood.setAttribute("flood-color","black");
+  feFlood.setAttribute("flood-opacity", this.config.shadowOpacity);
+  feFlood.setAttribute("result","color");
+  gaussFilter.appendChild(feFlood);
+
+  // composite 2
+  feComposite2 = document.createElementNS(svg,"feComposite");
+  feComposite2.setAttribute("operator","in");
+  feComposite2.setAttribute("in", "color");
+  feComposite2.setAttribute("in2","inverse");
+  feComposite2.setAttribute("result","shadow");
+  gaussFilter.appendChild(feComposite2);
+
+  // composite 3
+  feComposite3 = document.createElementNS(svg,"feComposite");
+  feComposite3.setAttribute("operator","over");
+  feComposite3.setAttribute("in", "shadow");
+  feComposite3.setAttribute("in2","SourceGraphic");
+  gaussFilter.appendChild(feComposite3);
+
+  // set shadow
+  if (!this.config.hideInnerShadow) {
+    this.canvas.canvas.childNodes[2].setAttribute("filter", "url(#" + this.config.id + "-inner-shadow)");
+    this.canvas.canvas.childNodes[3].setAttribute("filter", "url(#" + this.config.id + "-inner-shadow)");
+  }
+
+  // var clear
+  gaussFilter, feOffset, feGaussianBlur, feComposite1, feFlood, feComposite2, feComposite3 = null;
+
+};
+
+/** Get color for value */
+function getColor(val, pct, col, noGradient, custSec) {
+
+  var no, inc, colors, percentage, rval, gval, bval, lower, upper, range, rangePct, pctLower, pctUpper, color;
+  var noGradient = noGradient || custSec.length > 0;
+
+  if(custSec.length > 0) {
+    for(var i = 0; i < custSec.length; i++) {
+      if(val > custSec[i].lo && val <= custSec[i].hi) {
+        return custSec[i].color;
+      }
+    }
+  }
+
+  no = col.length;
+  if (no === 1) return col[0];
+  inc = (noGradient) ? (1 / no) : (1 / (no - 1));
+  colors = [];
+  for (var i = 0; i < col.length; i++) {
+    percentage = (noGradient) ? (inc * (i + 1)) : (inc * i);
+    rval = parseInt((cutHex(col[i])).substring(0,2),16);
+    gval = parseInt((cutHex(col[i])).substring(2,4),16);
+    bval = parseInt((cutHex(col[i])).substring(4,6),16);
+    colors[i] = { pct: percentage, color: { r: rval, g: gval, b: bval  } };
+  }
+
+  if(pct === 0) {
+    return 'rgb(' + [colors[0].color.r, colors[0].color.g, colors[0].color.b].join(',') + ')';
+  }
+
+  for (var j = 0; j < colors.length; j++) {
+    if (pct <= colors[j].pct) {
+      if (noGradient) {
+        return 'rgb(' + [colors[j].color.r, colors[j].color.g, colors[j].color.b].join(',') + ')';
+      } else {
+        lower = colors[j - 1];
+        upper = colors[j];
+        range = upper.pct - lower.pct;
+        rangePct = (pct - lower.pct) / range;
+        pctLower = 1 - rangePct;
+        pctUpper = rangePct;
+        color = {
+          r: Math.floor(lower.color.r * pctLower + upper.color.r * pctUpper),
+          g: Math.floor(lower.color.g * pctLower + upper.color.g * pctUpper),
+          b: Math.floor(lower.color.b * pctLower + upper.color.b * pctUpper)
+        };
+        return 'rgb(' + [color.r, color.g, color.b].join(',') + ')';
+      }
+    }
+  }
+
+}
+
+/** Fix Raphael display:none tspan dy attribute bug */
+function setDy(elem, fontSize, txtYpos) {
+  if ((!ie || ie > 9) && (elem.node.firstChild.attributes.dy)) {
+    elem.node.firstChild.attributes.dy.value = 0;
+  }
+}
+
+/** Random integer  */
+function getRandomInt (min, max) {
+  return Math.floor(Math.random() * (max - min + 1)) + min;
+}
+
+/**  Cut hex  */
+function cutHex(str) {
+  return (str.charAt(0)=="#") ? str.substring(1,7):str;
+}
+
+/**  Human friendly number suffix - From: http://stackoverflow.com/questions/2692323/code-golf-friendly-number-abbreviator */
+function humanFriendlyNumber( n, d ) {
+  var p, d2, i, s;
+
+  p = Math.pow;
+  d2 = p(10, d);
+  i = 7;
+  while( i ) {
+    s = p(10,i--*3);
+    if( s <= n ) {
+     n = Math.round(n*d2/s)/d2+"KMGTPE"[i];
+   }
+ }
+ return n;
+}
+
+/**  Get style  */
+function getStyle(oElm, strCssRule){
+  var strValue = "";
+  if(document.defaultView && document.defaultView.getComputedStyle){
+    strValue = document.defaultView.getComputedStyle(oElm).getPropertyValue(strCssRule);
+  }
+  else if(oElm.currentStyle){
+    strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){
+      return p1.toUpperCase();
+    });
+    strValue = oElm.currentStyle[strCssRule];
+  }
+  return strValue;
+}
+
+/**  Create Element NS Ready  */
+function onCreateElementNsReady(func) {
+  if (document.createElementNS !== undefined) {
+    func();
+  } else {
+    setTimeout(function() { onCreateElementNsReady(func); }, 100);
+  }
+}
+
+/**  Get IE version  */
+// ----------------------------------------------------------
+// A short snippet for detecting versions of IE in JavaScript
+// without resorting to user-agent sniffing
+// ----------------------------------------------------------
+// If you're not in IE (or IE version is less than 5) then:
+// ie === undefined
+// If you're in IE (>=5) then you can determine which version:
+// ie === 7; // IE7
+// Thus, to detect IE:
+// if (ie) {}
+// And to detect the version:
+// ie === 6 // IE6
+// ie > 7 // IE8, IE9 ...
+// ie < 9 // Anything less than IE9
+// ----------------------------------------------------------
+// UPDATE: Now using Live NodeList idea from @jdalton
+var ie = (function(){
+
+  var undef,
+  v = 3,
+  div = document.createElement('div'),
+  all = div.getElementsByTagName('i');
+
+  while (
+    div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',
+    all[0]
+    );
+    return v > 4 ? v : undef;
+}());
\ No newline at end of file
diff --git a/addons/sale_crm/static/src/js/sale_crm.js b/addons/sale_crm/static/src/js/sale_crm.js
new file mode 100644 (file)
index 0000000..066ec06
--- /dev/null
@@ -0,0 +1,44 @@
+openerp.sale_crm = function(openerp) {
+
+openerp.sale_crm.GaugeWidget = openerp.web_kanban.AbstractField.extend({
+    className: "oe_gage",
+    start: function() {
+        var max = 100;
+        if (this.options.max_field) {
+            max = this.getParent().record[this.options.max_field].raw_value;
+        }
+        var label = "";
+        if (this.options.label_field) {
+            label = this.getParent().record[this.options.label_field].raw_value;
+        }
+        var title = this.$node.html();
+        var value = _.isArray(this.field.value) ? this.field.value.pop() : this.field.value;
+        var unique_id = _.uniqueId("JustGage");
+        
+        this.$el.empty()
+            .attr('style', this.$node.attr('style') + ';position:relative; display:inline-block;')
+            .attr('id', unique_id);
+        this.gage = new JustGage({
+            id: unique_id,
+            node: this.$el[0],
+            title: title,
+            value: value,
+            min: 0,
+            max: max,
+            relativeGaugeSize: true,
+            humanFriendly: true,
+            titleFontColor: '#333333',
+            valueFontColor: '#333333',
+            labelFontColor: '#000',
+            label: label,
+            levelColors: [
+                "#ff0000",
+                "#f9c802",
+                "#a9d70b"
+            ],
+        });
+    },
+});
+openerp.web_kanban.fields_registry.add("gage", "openerp.sale_crm.GaugeWidget");
+
+};