Index: public/vendors/charts/justgage/justgage.js
===================================================================
--- public/vendors/charts/justgage/justgage.js	(revision 7304c7f4e9d471fa8974c193daec4fdd86dabe58)
+++ public/vendors/charts/justgage/justgage.js	(revision 7304c7f4e9d471fa8974c193daec4fdd86dabe58)
@@ -0,0 +1,1220 @@
+/**
+ * JustGage - animated gauges using RaphaelJS
+ * Check http://www.justgage.com for official releases
+ * Licensed under MIT.
+ * @author Bojan Djuricic (@Toorshia)
+ **/
+
+JustGage = function(config) {
+
+  var obj = this;
+
+  // Helps in case developer wants to debug it. unobtrusive
+  if (config === null || config === undefined) {
+    console.log('* justgage: Make sure to pass options to the constructor!');
+    return false;
+  }
+
+  var node;
+
+  if (config.id !== null && config.id !== undefined) {
+    node = document.getElementById(config.id);
+    if (!node) {
+      console.log('* justgage: No element with id : %s found', config.id);
+      return false;
+    }
+  } else if (config.parentNode !== null && config.parentNode !== undefined) {
+    node = config.parentNode;
+  } else {
+    console.log('* justgage: Make sure to pass the existing element id or parentNode to the constructor.');
+    return false;
+  }
+
+  var dataset = node.dataset ? node.dataset : {};
+
+  // check for defaults
+  var defaults = (config.defaults !== null && config.defaults !== undefined) ? config.defaults : false;
+  if (defaults !== false) {
+    config = extend({}, config, defaults);
+    delete config.defaults;
+  }
+
+  // configurable parameters
+  obj.config = {
+    // id : string
+    // this is container element id
+    id: config.id,
+
+    // value : float
+    // value gauge is showing
+    value: kvLookup('value', config, dataset, 0, 'float'),
+
+    // defaults : bool
+    // defaults parameter to use
+    defaults: kvLookup('defaults', config, dataset, 0, false),
+
+    // parentNode : node object
+    // this is container element
+    parentNode: kvLookup('parentNode', config, dataset, null),
+
+    // width : int
+    // gauge width
+    width: kvLookup('width', config, dataset, null),
+
+    // height : int
+    // gauge height
+    height: kvLookup('height', config, dataset, null),
+
+    // title : string
+    // gauge title
+    title: kvLookup('title', config, dataset, ""),
+
+    // titleFontColor : string
+    // color of gauge title
+    titleFontColor: kvLookup('titleFontColor', config, dataset, "#999999"),
+
+    // titleFontFamily : string
+    // color of gauge title
+    titleFontFamily: kvLookup('titleFontFamily', config, dataset, "sans-serif"),
+
+    // titlePosition : string
+    // 'above' or 'below'
+    titlePosition: kvLookup('titlePosition', config, dataset, "above"),
+
+    // valueFontColor : string
+    // color of label showing current value
+    valueFontColor: kvLookup('valueFontColor', config, dataset, "#010101"),
+
+    // valueFontFamily : string
+    // color of label showing current value
+    valueFontFamily: kvLookup('valueFontFamily', config, dataset, "Arial"),
+
+    // symbol : string
+    // special symbol to show next to value
+    symbol: kvLookup('symbol', config, dataset, ''),
+
+    // min : float
+    // min value
+    min: kvLookup('min', config, dataset, 0, 'float'),
+
+    // max : float
+    // max value
+    max: kvLookup('max', config, dataset, 100, 'float'),
+
+    // reverse : bool
+    // reverse min and max
+    reverse: kvLookup('reverse', config, dataset, false),
+
+    // humanFriendlyDecimal : int
+    // number of decimal places for our human friendly number to contain
+    humanFriendlyDecimal: kvLookup('humanFriendlyDecimal', config, dataset, 0),
+
+
+    // textRenderer: func
+    // function applied before rendering text
+    textRenderer: kvLookup('textRenderer', config, dataset, null),
+
+    // gaugeWidthScale : float
+    // width of the gauge element
+    gaugeWidthScale: kvLookup('gaugeWidthScale', config, dataset, 1.0),
+
+    // gaugeColor : string
+    // background color of gauge element
+    gaugeColor: kvLookup('gaugeColor', config, dataset, "#edebeb"),
+
+    // label : string
+    // text to show below value
+    label: kvLookup('label', config, dataset, ''),
+
+    // labelFontColor : string
+    // color of label showing label under value
+    labelFontColor: kvLookup('labelFontColor', config, dataset, "#b3b3b3"),
+
+    // shadowOpacity : int
+    // 0 ~ 1
+    shadowOpacity: kvLookup('shadowOpacity', config, dataset, 0.2),
+
+    // shadowSize: int
+    // inner shadow size
+    shadowSize: kvLookup('shadowSize', config, dataset, 5),
+
+    // shadowVerticalOffset : int
+    // how much shadow is offset from top
+    shadowVerticalOffset: kvLookup('shadowVerticalOffset', config, dataset, 3),
+
+    // levelColors : string[]
+    // colors of indicator, from lower to upper, in RGB format
+    levelColors: kvLookup('levelColors', config, dataset, ["#a9d70b", "#f9c802", "#ff0000"], 'array', ','),
+
+    // startAnimationTime : int
+    // length of initial animation
+    startAnimationTime: kvLookup('startAnimationTime', config, dataset, 700),
+
+    // startAnimationType : string
+    // type of initial animation (linear, >, <,  <>, bounce)
+    startAnimationType: kvLookup('startAnimationType', config, dataset, '>'),
+
+    // refreshAnimationTime : int
+    // length of refresh animation
+    refreshAnimationTime: kvLookup('refreshAnimationTime', config, dataset, 700),
+
+    // refreshAnimationType : string
+    // type of refresh animation (linear, >, <,  <>, bounce)
+    refreshAnimationType: kvLookup('refreshAnimationType', config, dataset, '>'),
+
+    // donutStartAngle : int
+    // angle to start from when in donut mode
+    donutStartAngle: kvLookup('donutStartAngle', config, dataset, 90),
+
+    // valueMinFontSize : int
+    // absolute minimum font size for the value
+    valueMinFontSize: kvLookup('valueMinFontSize', config, dataset, 16),
+
+    // titleMinFontSize
+    // absolute minimum font size for the title
+    titleMinFontSize: kvLookup('titleMinFontSize', config, dataset, 10),
+
+    // labelMinFontSize
+    // absolute minimum font size for the label
+    labelMinFontSize: kvLookup('labelMinFontSize', config, dataset, 10),
+
+    // minLabelMinFontSize
+    // absolute minimum font size for the minimum label
+    minLabelMinFontSize: kvLookup('minLabelMinFontSize', config, dataset, 10),
+
+    // maxLabelMinFontSize
+    // absolute minimum font size for the maximum label
+    maxLabelMinFontSize: kvLookup('maxLabelMinFontSize', config, dataset, 10),
+
+    // hideValue : bool
+    // hide value text
+    hideValue: kvLookup('hideValue', config, dataset, false),
+
+    // hideMinMax : bool
+    // hide min and max values
+    hideMinMax: kvLookup('hideMinMax', config, dataset, false),
+
+    // hideInnerShadow : bool
+    // hide inner shadow
+    hideInnerShadow: kvLookup('hideInnerShadow', config, dataset, false),
+
+    // humanFriendly : bool
+    // convert large numbers for min, max, value to human friendly (e.g. 1234567 -> 1.23M)
+    humanFriendly: kvLookup('humanFriendly', config, dataset, false),
+
+    // noGradient : bool
+    // whether to use gradual color change for value, or sector-based
+    noGradient: kvLookup('noGradient', config, dataset, false),
+
+    // donut : bool
+    // show full donut gauge
+    donut: kvLookup('donut', config, dataset, false),
+
+    // relativeGaugeSize : bool
+    // whether gauge size should follow changes in container element size
+    relativeGaugeSize: kvLookup('relativeGaugeSize', config, dataset, false),
+
+    // counter : bool
+    // animate level number change
+    counter: kvLookup('counter', config, dataset, false),
+
+    // decimals : int
+    // number of digits after floating point
+    decimals: kvLookup('decimals', config, dataset, 0),
+
+    // customSectors : [] of objects
+    // number of digits after floating point
+    customSectors: kvLookup('customSectors', config, dataset, []),
+
+    // formatNumber: boolean
+    // formats numbers with commas where appropriate
+    formatNumber: kvLookup('formatNumber', config, dataset, false),
+
+    // pointer : bool
+    // show value pointer
+    pointer: kvLookup('pointer', config, dataset, false),
+
+    // pointerOptions : object
+    // define pointer look
+    pointerOptions: kvLookup('pointerOptions', config, dataset, [])
+  };
+
+  // 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 (obj.config.value > obj.config.max) obj.config.value = obj.config.max;
+  if (obj.config.value < obj.config.min) obj.config.value = obj.config.min;
+  obj.originalValue = kvLookup('value', config, dataset, -1, 'float');
+
+  // create canvas
+  if (obj.config.id !== null && (document.getElementById(obj.config.id)) !== null) {
+    obj.canvas = Raphael(obj.config.id, "100%", "100%");
+  } else if (obj.config.parentNode !== null) {
+    obj.canvas = Raphael(obj.config.parentNode, "100%", "100%");
+  }
+
+  if (obj.config.relativeGaugeSize === true) {
+    obj.canvas.setViewBox(0, 0, 200, 150, true);
+  }
+
+  // canvas dimensions
+  if (obj.config.relativeGaugeSize === true) {
+    canvasW = 200;
+    canvasH = 150;
+  } else if (obj.config.width !== null && obj.config.height !== null) {
+    canvasW = obj.config.width;
+    canvasH = obj.config.height;
+  } else if (obj.config.parentNode !== null) {
+    obj.canvas.setViewBox(0, 0, 200, 150, true);
+    canvasW = 200;
+    canvasH = 150;
+  } else {
+    canvasW = getStyle(document.getElementById(obj.config.id), "width").slice(0, -2) * 1;
+    canvasH = getStyle(document.getElementById(obj.config.id), "height").slice(0, -2) * 1;
+  }
+
+  // widget dimensions
+  if (obj.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 (obj.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 * obj.config.gaugeWidthScale) / 2;
+    minY = labelY;
+
+    // max
+    maxFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
+    maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * obj.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;
+    if (obj.config.titlePosition === 'below') {
+      // shift whole thing down
+      dy -= (widgetH / 6.4);
+    }
+
+    // title
+    titleFontSize = ((widgetH / 8) > obj.config.titleMinFontSize) ? (widgetH / 10) : obj.config.titleMinFontSize;
+    titleX = dx + widgetW / 2;
+    titleY = dy + (obj.config.titlePosition === 'below' ? (widgetH * 1.07) : (widgetH / 6.4));
+
+    // value
+    valueFontSize = ((widgetH / 6.5) > obj.config.valueMinFontSize) ? (widgetH / 6.5) : obj.config.valueMinFontSize;
+    valueX = dx + widgetW / 2;
+    valueY = dy + widgetH / 1.275;
+
+    // label
+    labelFontSize = ((widgetH / 16) > obj.config.labelMinFontSize) ? (widgetH / 16) : obj.config.labelMinFontSize;
+    labelX = dx + widgetW / 2;
+    labelY = valueY + valueFontSize / 2 + 5;
+
+    // min
+    minFontSize = ((widgetH / 16) > obj.config.minLabelMinFontSize) ? (widgetH / 16) : obj.config.minLabelMinFontSize;
+    minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2;
+    minY = labelY;
+
+    // max
+    maxFontSize = ((widgetH / 16) > obj.config.maxLabelMinFontSize) ? (widgetH / 16) : obj.config.maxLabelMinFontSize;
+    maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2;
+    maxY = labelY;
+  }
+
+  // parameters
+  obj.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
+  obj.canvas.customAttributes.pki = function(value, min, max, w, h, dx, dy, gws, donut, reverse) {
+
+    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) - Ro * Math.sin(alpha);
+      Xi = w / 2 + dx + Ri * Math.cos(alpha);
+      Yi = h - (h - Cy) - 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) - Ro * Math.sin(alpha);
+      Xi = w / 2 + dx + Ri * Math.cos(alpha);
+      Yi = h - (h - Cy) - 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;
+  };
+
+  // ndl - custom attribute for generating needle path
+  obj.canvas.customAttributes.ndl = function(value, min, max, w, h, dx, dy, gws, donut) {
+
+    var dlt = w * 3.5 / 100;
+    var dlb = w / 15;
+    var dw = w / 100;
+
+    if (obj.config.pointerOptions.toplength != null && obj.config.pointerOptions.toplength != undefined) dlt = obj.config.pointerOptions.toplength;
+    if (obj.config.pointerOptions.bottomlength != null && obj.config.pointerOptions.bottomlength != undefined) dlb = obj.config.pointerOptions.bottomlength;
+    if (obj.config.pointerOptions.bottomwidth != null && obj.config.pointerOptions.bottomwidth != undefined) dw = obj.config.pointerOptions.bottomwidth;
+
+    var alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, Xc, Yc, Xz, Yz, Xa, Ya, Xb, Yb, 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) - Ro * Math.sin(alpha);
+      Xi = w / 2 + dx + Ri * Math.cos(alpha);
+      Yi = h - (h - Cy) - Ri * Math.sin(alpha);
+
+      Xc = Xo + dlt * Math.cos(alpha);
+      Yc = Yo - dlt * Math.sin(alpha);
+      Xz = Xi - dlb * Math.cos(alpha);
+      Yz = Yi + dlb * Math.sin(alpha);
+
+      Xa = Xz + dw * Math.sin(alpha);
+      Ya = Yz + dw * Math.cos(alpha);
+      Xb = Xz - dw * Math.sin(alpha);
+      Yb = Yz - dw * Math.cos(alpha);
+
+      path = 'M' + Xa + ',' + Ya + ' ';
+      path += 'L' + Xb + ',' + Yb + ' ';
+      path += 'L' + Xc + ',' + Yc + ' ';
+      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) - Ro * Math.sin(alpha);
+      Xi = w / 2 + dx + Ri * Math.cos(alpha);
+      Yi = h - (h - Cy) - Ri * Math.sin(alpha);
+
+      Xc = Xo + dlt * Math.cos(alpha);
+      Yc = Yo - dlt * Math.sin(alpha);
+      Xz = Xi - dlb * Math.cos(alpha);
+      Yz = Yi + dlb * Math.sin(alpha);
+
+      Xa = Xz + dw * Math.sin(alpha);
+      Ya = Yz + dw * Math.cos(alpha);
+      Xb = Xz - dw * Math.sin(alpha);
+      Yb = Yz - dw * Math.cos(alpha);
+
+      path = 'M' + Xa + ',' + Ya + ' ';
+      path += 'L' + Xb + ',' + Yb + ' ';
+      path += 'L' + Xc + ',' + Yc + ' ';
+      path += 'Z ';
+
+      return {
+        path: path
+      };
+    }
+
+    // var clear
+    alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, Xc, Yc, Xz, Yz, Xa, Ya, Xb, Yb, path = null;
+  };
+
+  // gauge
+  obj.gauge = obj.canvas.path().attr({
+    "stroke": "none",
+    "fill": obj.config.gaugeColor,
+    pki: [
+      obj.config.max,
+      obj.config.min,
+      obj.config.max,
+      obj.params.widgetW,
+      obj.params.widgetH,
+      obj.params.dx,
+      obj.params.dy,
+      obj.config.gaugeWidthScale,
+      obj.config.donut,
+      obj.config.reverse
+    ]
+  });
+
+  // level
+  obj.level = obj.canvas.path().attr({
+    "stroke": "none",
+    "fill": getColor(obj.config.value, (obj.config.value - obj.config.min) / (obj.config.max - obj.config.min), obj.config.levelColors, obj.config.noGradient, obj.config.customSectors),
+    pki: [
+      obj.config.min,
+      obj.config.min,
+      obj.config.max,
+      obj.params.widgetW,
+      obj.params.widgetH,
+      obj.params.dx,
+      obj.params.dy,
+      obj.config.gaugeWidthScale,
+      obj.config.donut,
+      obj.config.reverse
+    ]
+  });
+  if (obj.config.donut) {
+    obj.level.transform("r" + obj.config.donutStartAngle + ", " + (obj.params.widgetW / 2 + obj.params.dx) + ", " + (obj.params.widgetH / 1.95 + obj.params.dy));
+  }
+
+  if (obj.config.pointer) {
+    // needle
+    obj.needle = obj.canvas.path().attr({
+      "stroke": (obj.config.pointerOptions.stroke !== null && obj.config.pointerOptions.stroke !== undefined) ? obj.config.pointerOptions.stroke : "none",
+      "stroke-width": (obj.config.pointerOptions.stroke_width !== null && obj.config.pointerOptions.stroke_width !== undefined) ? obj.config.pointerOptions.stroke_width : 0,
+      "stroke-linecap": (obj.config.pointerOptions.stroke_linecap !== null && obj.config.pointerOptions.stroke_linecap !== undefined) ? obj.config.pointerOptions.stroke_linecap : "square",
+      "fill": (obj.config.pointerOptions.color !== null && obj.config.pointerOptions.color !== undefined) ? obj.config.pointerOptions.color : "#000000",
+      ndl: [
+        obj.config.min,
+        obj.config.min,
+        obj.config.max,
+        obj.params.widgetW,
+        obj.params.widgetH,
+        obj.params.dx,
+        obj.params.dy,
+        obj.config.gaugeWidthScale,
+        obj.config.donut
+      ]
+    });
+
+    if (obj.config.donut) {
+      obj.needle.transform("r" + obj.config.donutStartAngle + ", " + (obj.params.widgetW / 2 + obj.params.dx) + ", " + (obj.params.widgetH / 1.95 + obj.params.dy));
+    }
+
+  }
+
+  // title
+  obj.txtTitle = obj.canvas.text(obj.params.titleX, obj.params.titleY, obj.config.title);
+  obj.txtTitle.attr({
+    "font-size": obj.params.titleFontSize,
+    "font-weight": "bold",
+    "font-family": obj.config.titleFontFamily,
+    "fill": obj.config.titleFontColor,
+    "fill-opacity": "1"
+  });
+  setDy(obj.txtTitle, obj.params.titleFontSize, obj.params.titleY);
+
+  // value
+  obj.txtValue = obj.canvas.text(obj.params.valueX, obj.params.valueY, 0);
+  obj.txtValue.attr({
+    "font-size": obj.params.valueFontSize,
+    "font-weight": "bold",
+    "font-family": obj.config.valueFontFamily,
+    "fill": obj.config.valueFontColor,
+    "fill-opacity": "0"
+  });
+  setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
+
+  // label
+  obj.txtLabel = obj.canvas.text(obj.params.labelX, obj.params.labelY, obj.config.label);
+  obj.txtLabel.attr({
+    "font-size": obj.params.labelFontSize,
+    "font-weight": "normal",
+    "font-family": "Arial",
+    "fill": obj.config.labelFontColor,
+    "fill-opacity": "0"
+  });
+  setDy(obj.txtLabel, obj.params.labelFontSize, obj.params.labelY);
+
+  // min
+  var min = obj.config.min;
+  if (obj.config.reverse) {
+    min = obj.config.max;
+  }
+
+  obj.txtMinimum = min;
+  if (obj.config.humanFriendly) {
+    obj.txtMinimum = humanFriendlyNumber(min, obj.config.humanFriendlyDecimal);
+  } else if (obj.config.formatNumber) {
+    obj.txtMinimum = formatNumber(min);
+  }
+  obj.txtMin = obj.canvas.text(obj.params.minX, obj.params.minY, obj.txtMinimum);
+  obj.txtMin.attr({
+    "font-size": obj.params.minFontSize,
+    "font-weight": "normal",
+    "font-family": "Arial",
+    "fill": obj.config.labelFontColor,
+    "fill-opacity": (obj.config.hideMinMax || obj.config.donut) ? "0" : "1"
+  });
+  setDy(obj.txtMin, obj.params.minFontSize, obj.params.minY);
+
+  // max
+  var max = obj.config.max;
+  if (obj.config.reverse) {
+    max = obj.config.min;
+  }
+  obj.txtMaximum = max;
+  if (obj.config.humanFriendly) {
+    obj.txtMaximum = humanFriendlyNumber(max, obj.config.humanFriendlyDecimal);
+  } else if (obj.config.formatNumber) {
+    obj.txtMaximum = formatNumber(max);
+  }
+  obj.txtMax = obj.canvas.text(obj.params.maxX, obj.params.maxY, obj.txtMaximum);
+  obj.txtMax.attr({
+    "font-size": obj.params.maxFontSize,
+    "font-weight": "normal",
+    "font-family": "Arial",
+    "fill": obj.config.labelFontColor,
+    "fill-opacity": (obj.config.hideMinMax || obj.config.donut) ? "0" : "1"
+  });
+  setDy(obj.txtMax, obj.params.maxFontSize, obj.params.maxY);
+
+  var defs = obj.canvas.canvas.childNodes[1];
+  var svg = "http://www.w3.org/2000/svg";
+
+  if (ie !== 'undefined' && ie < 9) {
+    // VML mode - no SVG & SVG filter support
+  } else if (ie !== 'undefined') {
+    onCreateElementNsReady(function() {
+      obj.generateShadow(svg, defs);
+    });
+  } else {
+    obj.generateShadow(svg, defs);
+  }
+
+  // var clear
+  defs, svg = null;
+
+  // set value to display
+  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 if (obj.config.formatNumber) {
+    obj.originalValue = formatNumber(obj.originalValue) + obj.config.symbol;
+  } else {
+    obj.originalValue = (obj.originalValue * 1).toFixed(obj.config.decimals) + obj.config.symbol;
+  }
+
+  if (obj.config.counter === true) {
+    //on each animation frame
+    eve.on("raphael.anim.frame." + (obj.level.id), function() {
+      var currentValue = obj.level.attr("pki")[0];
+      if (obj.config.reverse) {
+        currentValue = (obj.config.max * 1) + (obj.config.min * 1) - (obj.level.attr("pki")[0] * 1);
+      }
+      if (obj.config.textRenderer) {
+        obj.txtValue.attr("text", obj.config.textRenderer(Math.floor(currentValue)));
+      } else if (obj.config.humanFriendly) {
+        obj.txtValue.attr("text", humanFriendlyNumber(Math.floor(currentValue), obj.config.humanFriendlyDecimal) + obj.config.symbol);
+      } else if (obj.config.formatNumber) {
+        obj.txtValue.attr("text", formatNumber(Math.floor(currentValue)) + obj.config.symbol);
+      } else {
+        obj.txtValue.attr("text", (currentValue * 1).toFixed(obj.config.decimals) + obj.config.symbol);
+      }
+      setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
+      currentValue = null;
+    });
+    //on animation end
+    eve.on("raphael.anim.finish." + (obj.level.id), function() {
+      obj.txtValue.attr({
+        "text": obj.originalValue
+      });
+      setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
+    });
+  } else {
+    //on animation start
+    eve.on("raphael.anim.start." + (obj.level.id), function() {
+      obj.txtValue.attr({
+        "text": obj.originalValue
+      });
+      setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
+    });
+  }
+
+  // animate gauge level, value & label
+  var rvl = obj.config.value;
+  if (obj.config.reverse) {
+    rvl = (obj.config.max * 1) + (obj.config.min * 1) - (obj.config.value * 1);
+  }
+  obj.level.animate({
+    pki: [
+      rvl,
+      obj.config.min,
+      obj.config.max,
+      obj.params.widgetW,
+      obj.params.widgetH,
+      obj.params.dx,
+      obj.params.dy,
+      obj.config.gaugeWidthScale,
+      obj.config.donut,
+      obj.config.reverse
+    ]
+  }, obj.config.startAnimationTime, obj.config.startAnimationType);
+
+  if (obj.config.pointer) {
+    obj.needle.animate({
+      ndl: [
+        rvl,
+        obj.config.min,
+        obj.config.max,
+        obj.params.widgetW,
+        obj.params.widgetH,
+        obj.params.dx,
+        obj.params.dy,
+        obj.config.gaugeWidthScale,
+        obj.config.donut
+      ]
+    }, obj.config.startAnimationTime, obj.config.startAnimationType);
+  }
+
+  obj.txtValue.animate({
+    "fill-opacity": (obj.config.hideValue) ? "0" : "1"
+  }, obj.config.startAnimationTime, obj.config.startAnimationType);
+  obj.txtLabel.animate({
+    "fill-opacity": "1"
+  }, obj.config.startAnimationTime, obj.config.startAnimationType);
+};
+
+/** Refresh gauge level */
+JustGage.prototype.refresh = function(val, max) {
+
+  var obj = this;
+  var displayVal, color, max = max || null;
+
+  // set new max
+  if (max !== null) {
+    obj.config.max = max;
+    // TODO: update customSectors
+
+    obj.txtMaximum = obj.config.max;
+    if (obj.config.humanFriendly) {
+      obj.txtMaximum = humanFriendlyNumber(obj.config.max, obj.config.humanFriendlyDecimal);
+    } else if (obj.config.formatNumber) {
+      obj.txtMaximum = formatNumber(obj.config.max);
+    }
+    if (!obj.config.reverse) {
+      obj.txtMax.attr({
+        "text": obj.txtMaximum
+      });
+      setDy(obj.txtMax, obj.params.maxFontSize, obj.params.maxY);
+    } else {
+      obj.txtMin.attr({
+        "text": obj.txtMaximum
+      });
+      setDy(obj.txtMin, obj.params.minFontSize, obj.params.minY);
+    }
+  }
+
+  // overflow values
+  displayVal = val;
+  if ((val * 1) > (obj.config.max * 1)) {
+    val = (obj.config.max * 1);
+  }
+  if ((val * 1) < (obj.config.min * 1)) {
+    val = (obj.config.min * 1);
+  }
+
+  color = getColor(val, (val - obj.config.min) / (obj.config.max - obj.config.min), obj.config.levelColors, obj.config.noGradient, obj.config.customSectors);
+
+  if (obj.config.textRenderer) {
+    displayVal = obj.config.textRenderer(displayVal);
+  } else if (obj.config.humanFriendly) {
+    displayVal = humanFriendlyNumber(displayVal, obj.config.humanFriendlyDecimal) + obj.config.symbol;
+  } else if (obj.config.formatNumber) {
+    displayVal = formatNumber((displayVal * 1).toFixed(obj.config.decimals)) + obj.config.symbol;
+  } else {
+    displayVal = (displayVal * 1).toFixed(obj.config.decimals) + obj.config.symbol;
+  }
+  obj.originalValue = displayVal;
+  obj.config.value = val * 1;
+
+  if (!obj.config.counter) {
+    obj.txtValue.attr({
+      "text": displayVal
+    });
+    setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
+  }
+
+  var rvl = obj.config.value;
+  if (obj.config.reverse) {
+    rvl = (obj.config.max * 1) + (obj.config.min * 1) - (obj.config.value * 1);
+  }
+  obj.level.animate({
+    pki: [
+      rvl,
+      obj.config.min,
+      obj.config.max,
+      obj.params.widgetW,
+      obj.params.widgetH,
+      obj.params.dx,
+      obj.params.dy,
+      obj.config.gaugeWidthScale,
+      obj.config.donut,
+      obj.config.reverse
+    ],
+    "fill": color
+  }, obj.config.refreshAnimationTime, obj.config.refreshAnimationType);
+
+  if (obj.config.pointer) {
+    obj.needle.animate({
+      ndl: [
+        rvl,
+        obj.config.min,
+        obj.config.max,
+        obj.params.widgetW,
+        obj.params.widgetH,
+        obj.params.dx,
+        obj.params.dy,
+        obj.config.gaugeWidthScale,
+        obj.config.donut
+      ]
+    }, obj.config.refreshAnimationTime, obj.config.refreshAnimationType);
+  }
+
+  // var clear
+  obj, displayVal, color, max = null;
+};
+
+/** Generate shadow */
+JustGage.prototype.generateShadow = function(svg, defs) {
+
+  var obj = this;
+  var sid = "inner-shadow-" + obj.config.id;
+  var gaussFilter, feOffset, feGaussianBlur, feComposite1, feFlood, feComposite2, feComposite3;
+
+  // FILTER
+  gaussFilter = document.createElementNS(svg, "filter");
+  gaussFilter.setAttribute("id", sid);
+  defs.appendChild(gaussFilter);
+
+  // offset
+  feOffset = document.createElementNS(svg, "feOffset");
+  feOffset.setAttribute("dx", 0);
+  feOffset.setAttribute("dy", obj.config.shadowVerticalOffset);
+  gaussFilter.appendChild(feOffset);
+
+  // blur
+  feGaussianBlur = document.createElementNS(svg, "feGaussianBlur");
+  feGaussianBlur.setAttribute("result", "offset-blur");
+  feGaussianBlur.setAttribute("stdDeviation", obj.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", obj.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 (!obj.config.hideInnerShadow) {
+    obj.canvas.canvas.childNodes[2].setAttribute("filter", "url(#" + sid + ")");
+    obj.canvas.canvas.childNodes[3].setAttribute("filter", "url(#" + sid + ")");
+  }
+
+  // var clear
+  gaussFilter, feOffset, feGaussianBlur, feComposite1, feFlood, feComposite2, feComposite3 = null;
+};
+
+//
+// tiny helper function to lookup value of a key from two hash tables
+// if none found, return defaultvalue
+//
+// key: string
+// tablea: object
+// tableb: DOMStringMap|object
+// defval: string|integer|float|null
+// datatype: return datatype
+// delimiter: delimiter to be used in conjunction with datatype formatting
+//
+function kvLookup(key, tablea, tableb, defval, datatype, delimiter) {
+  var val = defval;
+  var canConvert = false;
+  if (!(key === null || key === undefined)) {
+    if (tableb !== null && tableb !== undefined && typeof tableb === "object" && key in tableb) {
+      val = tableb[key];
+      canConvert = true;
+    } else if (tablea !== null && tablea !== undefined && typeof tablea === "object" && key in tablea) {
+      val = tablea[key];
+      canConvert = true;
+    } else {
+      val = defval;
+    }
+    if (canConvert === true) {
+      if (datatype !== null && datatype !== undefined) {
+        switch (datatype) {
+          case 'int':
+            val = parseInt(val, 10);
+            break;
+          case 'float':
+            val = parseFloat(val);
+            break;
+          default:
+            break;
+        }
+      }
+    }
+  }
+  return val;
+};
+
+/** 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 (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;
+}
+
+/** Format numbers with commas - From: http://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript */
+function formatNumber(x) {
+  var parts = x.toString().split(".");
+  parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+  return parts.join(".");
+}
+
+/**  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;
+}());
+
+// extend target object with second object
+function extend(out) {
+  out = out || {};
+
+  for (var i = 1; i < arguments.length; i++) {
+    if (!arguments[i])
+      continue;
+
+    for (var key in arguments[i]) {
+      if (arguments[i].hasOwnProperty(key))
+        out[key] = arguments[i][key];
+    }
+  }
+
+  return out;
+};
