text: /**
   * JustGage - a handy JavaScript plugin for generating and animating nice & clean dashboard gauges.
   * Copyright (c) 2012 Bojan Djuricic - pindjur(at)gmail(dot)com | http://www.madcog.com
   * Licensed under MIT.
   * Date: 31/07/2012
   * @author Bojan Djuricic  (@Toorshia)
   * @version 1.0
   *
   * http://www.justgage.com
   */
  
  JustGage = function(config)
  {
  
  	if(!config.id)
  	{
  		alert("Missing id parameter for gauge!");
  		return false;
  	}
  	if(!document.getElementById(config.id))
  	{
  		alert("No element with id: \"" + config.id + "\" found!");
  		return false;
  	}
  
  	// configurable parameters
  	this.config = {
  		// id : string
  		// this is container element id
  		id                  : config.id,
  
  		// 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",
  
  		// min : int
  		// min value
  		min                 : (config.min) ? config.min : 0,
  
  		// max : int
  		// max value
  		max                 : (config.max) ? config.max : 100,
  
  		// showMinMax : bool
  		// hide or display min and max values
  		showMinMax          : (config.showMinMax != null) ? config.showMinMax : true,
  
  		// 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 : "",
  
  		// showInnerShadow : bool
  		// give gauge element small amount of inner shadow
  		showInnerShadow     : (config.showInnerShadow != null) ? config.showInnerShadow : true,
  
  		// 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 : percentColors,
  
  		// levelColorsGradient : bool
  		// whether to use gradual color change for value, or sector-based
  		levelColorsGradient : (config.levelColorsGradient != null) ? config.levelColorsGradient : true,
  
  		// labelFontColor : string
  		// color of label showing label under value
  		labelFontColor      : (config.labelFontColor) ? config.labelFontColor : "#b3b3b3",
  
  		// 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 : ">"
  	};
  
  	// overflow values
  	if(config.value > this.config.max) this.config.value = this.config.max;
  	if(config.value < this.config.min) this.config.value = this.config.min;
  	this.originalValue = config.value;
  
  	// canvas
  	this.canvas = Raphael(this.config.id, "100%", "100%");
  
  	// canvas dimensions
  	//var canvasW = document.getElementById(this.config.id).clientWidth;
  	//var canvasH = document.getElementById(this.config.id).clientHeight;
  	var canvasW = getStyle(document.getElementById(this.config.id), "width").slice(0, -2) * 1;
  	var canvasH = getStyle(document.getElementById(this.config.id), "height").slice(0, -2) * 1;
  
  	// widget dimensions
  	var widgetW, widgetH;
  	if((canvasW / canvasH) > 1.25)
  	{
  		widgetW = 1.25 * canvasH;
  		widgetH = canvasH;
  	}
  	else
  	{
  		widgetW = canvasW;
  		widgetH = canvasW / 1.25;
  	}
  
  	// delta
  	var dx = (canvasW - widgetW) / 2;
  	var dy = (canvasH - widgetH) / 2;
  
  	// value
  	var valueFontSize = ((widgetH / 6.4) > 16) ? (widgetH / 6.4) : 16;
  	var valueX = dx + widgetW / 2;
  	var valueY = dy + widgetH / 1.4;
  
  	// label
  	var labelFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
  	var labelX = dx + widgetW / 2;
  	//var labelY = dy + widgetH / 1.126760563380282;
  	var labelY = valueY + valueFontSize / 2 + 6;
  
  	// min
  	var minFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
  	var minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2;
  	var minY = dy + widgetH / 1.126760563380282;
  
  	// max
  	var maxFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
  	var maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2;
  	var maxY = dy + widgetH / 1.126760563380282;
  
  	// parameters
  	this.params = {
  		canvasW      : canvasW,
  		canvasH      : canvasH,
  		widgetW      : widgetW,
  		widgetH      : widgetH,
  		dx           : dx,
  		dy           : dy,
  		valueFontSize: valueFontSize,
  		valueX       : valueX,
  		valueY       : valueY,
  		labelFontSize: labelFontSize,
  		labelX       : labelX,
  		labelY       : labelY,
  		minFontSize  : minFontSize,
  		minX         : minX,
  		minY         : minY,
  		maxFontSize  : maxFontSize,
  		maxX         : maxX,
  		maxY         : maxY
  	};
  
  	// pki - custom attribute for generating gauge paths
  	this.canvas.customAttributes.pki = function(value, min, max, w, h, dx, dy, gws)
  	{
  
  		var 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) + dy - Ro * Math.sin(alpha), Xi = w / 2 + dx + Ri * Math.cos(alpha), Yi = h - (h - Cy) + dy - Ri * Math.sin(alpha), path;
  
  		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 };
  	}
  
  	// 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.gauge.id = this.config.id + "-gauge";
  
  	// level
  	this.level = this.canvas.path().attr({
  		"stroke": "none",
  		"fill"  : getColorForPercentage((this.config.value - this.config.min) / (this.config.max - this.config.min), this.config.levelColors, this.config.levelColorsGradient),
  		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.level.id = this.config.id + "-level";
  
  	// value
  	this.txtValue = this.canvas.text(this.params.valueX, this.params.valueY, this.originalValue);
  	this.txtValue.attr({
  		"font-size"   : this.params.valueFontSize,
  		"font-weight" : "bold",
  		"font-family" : "Arial",
  		"fill"        : this.config.valueFontColor,
  		"fill-opacity": "0"
  	});
  	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"
  	});
  	this.txtLabel.id = this.config.id + "-txtlabel";
  
  	// min
  	this.txtMin = this.canvas.text(this.params.minX, this.params.minY, this.config.min);
  	this.txtMin.attr({
  		"font-size"   : this.params.minFontSize,
  		"font-weight" : "normal",
  		"font-family" : "Arial",
  		"fill"        : this.config.labelFontColor,
  		"fill-opacity": (this.config.showMinMax == true) ? "1" : "0"
  	});
  	this.txtMin.id = this.config.id + "-txtmin";
  
  	// max
  	this.txtMax = this.canvas.text(this.params.maxX, this.params.maxY, this.config.max);
  	this.txtMax.attr({
  		"font-size"   : this.params.maxFontSize,
  		"font-weight" : "normal",
  		"font-family" : "Arial",
  		"fill"        : this.config.labelFontColor,
  		"fill-opacity": (this.config.showMinMax == true) ? "1" : "0"
  	});
  	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();
  		});
  	}
  	else
  	{
  		this.generateShadow(svg, defs);
  	}
  
  	// animate
  	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.startAnimationTime, this.config.startAnimationType);
  
  	this.txtValue.animate({"fill-opacity": "1"}, this.config.startAnimationTime, this.config.startAnimationType);
  	this.txtLabel.animate({"fill-opacity": "1"}, this.config.startAnimationTime, this.config.startAnimationType);
  };
  
  // refresh gauge level
  JustGage.prototype.refresh = function(val)
  {
  	// overflow values
  	originalVal = val;
  	if(val > this.config.max)
  	{
  		val = this.config.max;
  	}
  	if(val < this.config.min)
  	{
  		val = this.config.min;
  	}
  
  	var color = getColorForPercentage((val - this.config.min) / (this.config.max - this.config.min), this.config.levelColors, this.config.levelColorsGradient);
  	this.canvas.getById(this.config.id + "-txtvalue").attr({"text": originalVal});
  	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
  	], "fill"                                                                                         : color}, this.config.refreshAnimationTime, this.config.refreshAnimationType);
  };
  
  var percentColors = [
  	"#a9d70b", "#f9c802", "#ff0000"
  ]
  
  JustGage.prototype.generateShadow = function(svg, defs)
  {
  	// FILTER
  	var gaussFilter = document.createElementNS(svg, "filter");
  	gaussFilter.setAttribute("id", this.config.id + "-inner-shadow");
  	defs.appendChild(gaussFilter);
  
  	// offset
  	var feOffset = document.createElementNS(svg, "feOffset");
  	feOffset.setAttribute("dx", 0);
  	feOffset.setAttribute("dy", this.config.shadowVerticalOffset);
  	gaussFilter.appendChild(feOffset);
  
  	// blur
  	var feGaussianBlur = document.createElementNS(svg, "feGaussianBlur");
  	feGaussianBlur.setAttribute("result", "offset-blur");
  	feGaussianBlur.setAttribute("stdDeviation", this.config.shadowSize);
  	gaussFilter.appendChild(feGaussianBlur);
  
  	// composite 1
  	var 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
  	var 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
  	var 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
  	var 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.showInnerShadow == true)
  	{
  		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 getColorForPercentage = function(pct, col, grad)
  {
  
  	var no = col.length;
  	if(no === 1) return col[0];
  	var inc = (grad) ? (1 / (no - 1)) : (1 / no);
  	var colors = new Array();
  	for(var i = 0; i < col.length; i++)
  	{
  		var percentage = (grad) ? (inc * i) : (inc * (i + 1));
  		var rval = parseInt((cutHex(col[i])).substring(0, 2), 16);
  		var gval = parseInt((cutHex(col[i])).substring(2, 4), 16);
  		var 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 i = 0; i < colors.length; i++)
  	{
  		if(pct <= colors[i].pct)
  		{
  			if(grad == true)
  			{
  				var lower = colors[i - 1];
  				var upper = colors[i];
  				var range = upper.pct - lower.pct;
  				var rangePct = (pct - lower.pct) / range;
  				var pctLower = 1 - rangePct;
  				var pctUpper = rangePct;
  				var 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(',') + ')';
  			}
  			else
  			{
  				return 'rgb(' + [colors[i].color.r, colors[i].color.g, colors[i].color.b].join(',') + ')';
  			}
  		}
  	}
  }
  
  function getRandomInt(min, max)
  {
  	return Math.floor(Math.random() * (max - min + 1)) + min;
  }
  
  function cutHex(str)
  {
  	return (str.charAt(0) == "#") ? str.substring(1, 7) : str
  }
  
  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;
  }
  
  function onCreateElementNsReady(func)
  {
  	if(document.createElementNS != undefined)
  	{
  		func();
  	}
  	else
  	{
  		setTimeout(function()
  		{
  			onCreateElementNsReady(func);
  		}, 100);
  	}
  }
  
  // ----------------------------------------------------------
  // 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;
  
  }());