(function() {
	
	//Our input colors is Colors, an array of strings.
	//Out output is an array of colors with at least Count colors in it.
	var GetColorSet = function(Colors, Count) {
		if(Count <= Colors.length)
			return Colors;
		
		var ret = [];
		var Length = Colors.length-1; //If there are three colors, Position will range from 0 to 2
		var Position = 0;
		for(var i = 0; i < Count; i++) {
			if(Math.round(Position) == Position) {
				var c = new RGBColor(Colors[Position]);
				ret.push(c.toHex());
			}
			else {
				var c1 = new RGBColor(Colors[Math.floor(Position)]);
				var c2 = new RGBColor(Colors[Math.ceil(Position)]);
				var c2part = Position - Math.floor(Position);
				
				c1.r = Math.round(c1.r*(1-c2part) + c2.r*c2part);
				c1.g = Math.round(c1.g*(1-c2part) + c2.g*c2part);
				c1.b = Math.round(c1.b*(1-c2part) + c2.b*c2part);
				
				ret.push(c1.toHex());
			}
			Position += Length/(Count-1);
		}
		return ret;
	}
	
	/*
	(x,y) = point to check, relative to center of ellipse.
	(xr,yr) = x- and y-radius of ellipse centered at (0,0).
	[a1,a2] = range of angles enclosing the slice.
	*/
	var InPieSlice = function(x, y, xr, yr, a1, a2) {
		//First, we change the problem so we're just checking if a point is in a slice of a unit circle.
		x /= xr;
		y /= yr;
		
		//Distance check: If it's farther than 1 from the origin, it's outside the circle altogether.
		var DistSq = x*x+y*y;
		if(DistSq > 1)
			return false;
		
		//Angle check:
		var a = Math.atan2(y,x);
		while(a < 0)
			a += Math.PI*2;
		return a >= a1 && a <= a2;
	}
	
	
	var MouseX = function(evt) {
		if (evt.pageX) return evt.pageX;
		else if (evt.clientX)
		   return evt.clientX + (document.documentElement.scrollLeft ?
		   document.documentElement.scrollLeft :
		   document.body.scrollLeft);
		else return null;
	}
	var MouseY = function(evt) {
		if (evt.pageY) return evt.pageY;
		else if (evt.clientY)
		   return evt.clientY + (document.documentElement.scrollTop ?
		   document.documentElement.scrollTop :
		   document.body.scrollTop);
		else return null;
	}

		
	var Pie3DGraph = function(Canvas, Data, Labels, Colors, Title) {
		this.Canvas = Canvas;
		this.Data = Data;
		this.Labels = Labels;
		this.Colors = GetColorSet(Colors, Data.length);
		this.Title = Title;
		this.Dirty = true;
		var graph = this;
		
		//First, wrap the canvas in a relative-positioned div of the same size.  This will allow easier text positioning.
		$(this.Canvas).wrap("<div class='clientgraph' style='position:relative;width:"+$(this.Canvas).attr('width')+"px;height:"+$(this.Canvas).attr('height')+"px;'></div>");
		this.CanvasDiv = this.Canvas.parentNode;
		$(this.CanvasDiv).css({overflow:'hidden'});
		
		this.PlaceTitle();
		this.CreateLabels();
		this.CalculatePieRect();
		
		this.SetupContext();
		
		//Set up the swoop-out initial animation
		if(is_ie)
			this.Scale = 1; //Animation is really choppy in IE, even on fast machines.
		else
			this.Scale = 0;
	
		
		//Set up the slice-callout animation on mouseover.
		this.CurrentSliceCallout = null;
		this.CurrentSliceCalloutDistance = 0; //Ratio from 0 to 1 of maximum distance
		this.TargetSliceCallout = null;
		
		$(this.Canvas).mousemove(function(e) {
			var offset = $(this).offset();
			var mx = MouseX(e) - offset.left - (graph.PieX+graph.PieWidth/2);
			var my = MouseY(e) - offset.top - (graph.PieY+graph.PieHeight/2);
			
			//Figure out which, if any, slice it's over.
			var OldAngle = 0;
			var HoverSlice = null;
			for(var i = 0; i < graph.Data.length; i++) {
				var Angle = OldAngle + graph.Data[i]/100 * Math.PI*2;
				
				if(InPieSlice(mx, my, graph.PieWidth*.6, graph.PieHeight*.6, OldAngle, Angle)) {
					HoverSlice = i;
					break;
				}
				
				OldAngle = Angle;
			}

			graph.SetSliceCallout(HoverSlice);
		});
		
		$(this.Canvas).mouseout(function(e) {
			if (!e) var e = window.event;
			var reltg = (e.relatedTarget) ? e.relatedTarget : e.toElement;
			while (reltg.tagName != 'BODY'){
			if (reltg.id == this.id){return;}
			reltg = reltg.parentNode;
			} 
			
			graph.SetSliceCallout(null);
		});
	}

	Pie3DGraph.prototype.SetSliceCallout = function(i) {
		this.TargetSliceCallout = i;
		this.Dirty = true;
	}
	
	Pie3DGraph.prototype.UpdateSliceCallout = function() {
		var graph = this;
		
		if(is_ie) {
			var Speed = 1;
			var FadeTime = 0;
		}
		else {
			var Speed = 0.2;
			var FadeTime = 750;
		}
		
		if(is_chrome)
			FadeTime = 0; //Looks funny in Chrome.
		
		if(this.CurrentSliceCallout == null) {
			if(this.TargetSliceCallout == null) {
				//Not calling out, don't want to, so don't do anything.
			}
			else {
				//Not calling out, but we want to.  Start going to the target.
				this.CurrentSliceCallout = this.TargetSliceCallout;
				this.CurrentSliceCalloutDistance = 0;
				this.Dirty = true;
			}
		}
		else {
			if(this.TargetSliceCallout == null) {
				//Calling out, don't want to, so reduce the distance to 0.
				if(this.CalloutPercentDiv) {
					if(FadeTime > 0) {
						this.CalloutPercentDiv.fadeOut(FadeTime, function() {
							$(this).remove();
						});
					}
					else
						this.CalloutPercentDiv.remove();
					this.CalloutPercentDiv = null;
				}

				if(this.CurrentSliceCalloutDistance > 0) {
					this.CurrentSliceCalloutDistance = Math.max(0, this.CurrentSliceCalloutDistance - Speed);
					this.Dirty = true;
				}
				else
					this.CurrentSliceCallout = null; //Done!
			}
			else {
				//Calling out, and we want to.
				if(this.CurrentSliceCallout == this.TargetSliceCallout) {
					//We're going towards the right one.
					if(this.CurrentSliceCalloutDistance < 1) {
						this.CurrentSliceCalloutDistance = Math.min(1,this.CurrentSliceCalloutDistance+Speed);
						this.Dirty = true;
					}
					else {
						//We've finished calling out the slice.  Now place the label.
						if(!this.CalloutPercentDiv) {
							var Angles = this.GetSliceAngles(this.CurrentSliceCallout);
							var x = (this.PieX+this.PieWidth/2) + this.PieWidth*.35*Math.cos((Angles[0]+Angles[1])/2);
							var y = (this.PieY+this.PieHeight/2) + this.PieHeight*.35*Math.sin((Angles[0]+Angles[1])/2);
							
							this.CalloutPercentDiv = this.PlaceText(-100,-100, null, Math.round(this.Data[this.CurrentSliceCallout])+'%', 'center');
							
							var FontSizeScaler = Math.min((Angles[1]-Angles[0])/5,.2);
							
							this.CalloutPercentDiv.css({
								fontSize: (this.PieHeight*FontSizeScaler)+'px'
							}).addClass('piepercentcallout');
							
							//Put a high-contrast label color on there.
							var Color = new RGBColor(this.Colors[this.CurrentSliceCallout]);
							if(Color.Total() >= 255)
								this.CalloutPercentDiv.addClass('dark');
							else
								this.CalloutPercentDiv.addClass('light');
				
							//Finalize the label's position.
							x -= this.CalloutPercentDiv.width()/2;
							y -= this.CalloutPercentDiv.height()/2;
							this.CalloutPercentDiv.css({
								left:x+'px',
								top:y+'px'
							});
							
							//Fade in the label.
							if(FadeTime > 0)
								this.CalloutPercentDiv.hide().fadeIn(FadeTime);
						}
					}
				}
				else {
					//We're going towards (or are at) the wrong one.  Reduce it to 0, then set the new target as current.
					if(this.CalloutPercentDiv) {
						if(FadeTime > 0) {
							this.CalloutPercentDiv.fadeOut(FadeTime, function() {
								$(this).remove();
							});
						}
						else
							this.CalloutPercentDiv.remove();
						this.CalloutPercentDiv = null;
					}
						
					if(this.CurrentSliceCalloutDistance > 0) {
						this.CurrentSliceCalloutDistance = Math.max(0, this.CurrentSliceCalloutDistance - Speed);
						this.Dirty = true;
					}
					else {
						this.CurrentSliceCallout = this.TargetSliceCallout; //Done!
						this.Dirty = true;
					}
				}
			}
		}
	}
	
	Pie3DGraph.prototype.GetSliceAngles = function(slice) {
		var OldAngle = 0;
		var Angle = 0;
		for(var i = 0; i <= slice; i++) {
			OldAngle = Angle;
			Angle = OldAngle + this.Data[i]/100 * Math.PI*2;
		}
		return [OldAngle,Angle];
	}
	
	//If there's a title, place it at the top center, and reduce our available dimensions by its actual size.
	Pie3DGraph.prototype.PlaceTitle = function() {
		if(this.Title != null && this.Title != '') {
			this.TitleDiv = this.PlaceText(0, 0, $(this.Canvas).attr('width'), this.Title.replace('|', '<br/>'), 'center').addClass('title');
		}
	}
	
	Pie3DGraph.prototype.PlaceText = function(x, y, w, t, TextAlign) {
		var width = w==null ? '' : 'width:'+w+'px;';
		$(this.CanvasDiv).append('<div style="position:absolute;left:'+x+'px;top:'+y+'px;'+width+'text-align:'+TextAlign+';">'+t+'</div>');
		return $(this.CanvasDiv).find('div:last');
	}
	
	//Create all the labels right off the bat, so we can figure out how wide the biggest one is, then leave that much
	//space available on both sides of the graph.  Do the same for height.
	Pie3DGraph.prototype.CreateLabels = function() {
		this.MaxLabelHeight = 0;
		this.MaxLabelWidth = 0;
		this.LabelDivs = [];
		for(var i = 0; i < this.Labels.length; i++) {
			var LabelDiv = this.PlaceText(0, 0, null, this.Labels[i], 'left').addClass('pielabel');
			this.MaxLabelHeight = Math.max(this.MaxLabelHeight, LabelDiv.height());
			this.MaxLabelWidth = Math.max(this.MaxLabelWidth, LabelDiv.width());
			this.LabelDivs.push(LabelDiv);
		}
	}
	
	Pie3DGraph.prototype.CalculatePieRect = function() {
		//The eventual maximum bounding box for the actual pie in the graph.
		this.PieX = 0;
		this.PieY = 0;
		this.PieWidth = $(this.Canvas).attr('width');
		this.PieHeight = $(this.Canvas).attr('height');
	
		//Make space for the title.
		if(this.TitleDiv) {
			this.PieY += this.TitleDiv.height();
			this.PieHeight -= this.TitleDiv.height();
		}
		
		//Make enough space around it for the biggest label.
		this.PieX += this.MaxLabelWidth;
		this.PieWidth -= this.MaxLabelWidth*2;
		this.PieY += this.MaxLabelHeight/2;
		this.PieHeight -= this.MaxLabelHeight;
		
		//We have to fit a Ratio:1 aspect ratio into the space as well as possible.
		var Ratio = 2.0;
		var OldWidth = this.PieWidth;
		var OldHeight = this.PieHeight;
		var Margin = 4;//At least a LITTLE whitespace around the edges.
		/*
		w = h*Ratio
		w <= OldWidth - w*0.2 - Margin
		h <= OldHeight - h*0.2 - Margin
		
		h*Ratio <= OldWidth - h*Ratio*0.2 - Margin
		h <= OldHeight - h*0.2 - Margin
		
		h*(Ratio*1.2) <= OldWidth - Margin
		h*(1.2) <= OldHeight - Margin
		
		h <= (OldWidth-Margin)/(Ratio*1.2)
		h <= (OldHeight-Margin)/1.2
		
		h = min((OldWidth - Margin)/(Ratio*1.2), (OldHeight - Margin)/1.2)
		*/
		this.PieHeight = Math.min((OldWidth - Margin)/(Ratio*1.2), (OldHeight - Margin)/1.2)
		this.PieWidth = this.PieHeight*Ratio;
		this.PieX += (OldWidth-this.PieWidth)/2;
		this.PieY += (OldHeight-this.PieHeight)/2;	
	}
	
	Pie3DGraph.prototype.SetupContext = function() {
		//Set up the drawing context with some extensions:
		this.Context = this.Canvas.getContext('2d');
		this.Context.StrokeAndFill = function(f) {
			this.beginPath();
			f.call(this);
			this.fill();
			
			this.beginPath();
			f.call(this);
			this.stroke();
		};
		
		this.Context.Ellipse = function(x,y,w,h,s,e,pie,depth) {
			if(s == null) s = 0;
			if(e == null) e = Math.PI*2;
			if(pie == null) pie = true;
			
			var xr = w/2;
			var yr = h/2;
			var k = 0.5522847498;
			
			var PointAt = function(t) {
				//Tangent to an ellipse: (x(t),y(t))t = (w(cosα - tsinα),h(sinα + tcosα))
				//Note that if t=0, (x,y) = (w(cosα),h(sinα)), the point on the circle.
				//And if t=k, (x,y) = (w(cosα - ksinα),h(sinα + kcosα)), which is the control point we want.
				return {
					t:t,
					x:x+xr + Math.cos(t)*xr,
					y:y+yr + Math.sin(t)*yr,
					xt:function(k) { return x+xr + xr*(Math.cos(t) - k*Math.sin(t)); }, //In direction of positive t
					yt:function(k) { return y+yr + yr*(Math.sin(t) + k*Math.cos(t)); },
					xt2:function(k) { return x+xr + xr*(Math.cos(t) - -k*Math.sin(t)); }, //In direction of negative t
					yt2:function(k) { return y+yr + yr*(Math.sin(t) + -k*Math.cos(t)); }
				};
			}
			
			
			var points = [];
			
			if(depth != null) {
				//Draw the depth if they asked for it and part of the slice is in the bottom half.
				if(s < Math.PI) {
					for(var t = s; t < Math.min(e,Math.PI); t += Math.PI*0.5)
						points.push(PointAt(t));
					points.push(PointAt(Math.min(e,Math.PI)));
				
					this.moveTo(points[0].x, points[0].y);
					for(var i = 0; i < points.length-1; i++) {
						var kval = k * (points[i+1].t-points[i].t)/(Math.PI/2);
						this.bezierCurveTo(points[i].xt(kval), points[i].yt(kval), points[i+1].xt2(kval), points[i+1].yt2(kval), points[i+1].x, points[i+1].y);
					}
					
					points.reverse();
					this.lineTo(points[0].x, points[0].y+depth);
					for(var i = 0; i < points.length-1; i++) {
						var kval = k * (points[i+1].t-points[i].t)/(Math.PI/2);
						this.bezierCurveTo(points[i].xt(kval), points[i].yt(kval)+depth, points[i+1].xt2(kval), points[i+1].yt2(kval)+depth, points[i+1].x, points[i+1].y+depth);
					}
					
					points.reverse();
					this.lineTo(points[0].x, points[0].y);
				}
			}
			else {
				//Now draw the actual ellipse/pie slice
				for(var t = s; t < e; t += Math.PI*0.5)
					points.push(PointAt(t));
				points.push(PointAt(e));
				
				if(pie) {
					this.moveTo(x+xr, y+yr);
					this.lineTo(points[0].x, points[0].y);
				}
				else {
					this.moveTo(points[0].x, points[0].y);
				}
				
				for(var i = 0; i < points.length-1; i++) {
					var kval = k * (points[i+1].t-points[i].t)/(Math.PI/2);
					this.bezierCurveTo(points[i].xt(kval), points[i].yt(kval), points[i+1].xt2(kval), points[i+1].yt2(kval), points[i+1].x, points[i+1].y);
				}
				
				if(pie)
					this.lineTo(x+xr, y+yr);
			}
		}
	}
	
	Pie3DGraph.prototype.Render = function() {
		if($(this.Canvas).height() == 0) //Not visible yet, just wait.
			return;
		
		var graph = this;
		if(this.Scale == null)
			this.Scale = 1;
		
		var x = this.PieX;
		var y = this.PieY;
		var w = this.PieWidth;
		var h = this.PieHeight;
		
		var depth = h*0.13;
		this.Context.lineWidth = h*0.005;
		
		this.Context.fillStyle = '#ffffff';
		this.Context.fillRect(0, 0, $(this.Canvas).width(), $(this.Canvas).height());
		
		var RenderData = [];
		for(var i = 0; i < this.Data.length; i++)
			RenderData[i] = this.Data[i]*this.Scale;
		
		var OldAngle = 0;
		for(var i = 0; i < RenderData.length; i++) {
			//Draw the actual pie slice
			var Color = new RGBColor(this.Colors[i%this.Colors.length]);
			var GivenColor = Color.toHex();
			var OldR = Color.r;
			var OldG = Color.g;
			var OldB = Color.b
			
			Color.r = Math.round(OldR * .6);
			Color.g = Math.round(OldG * .6);
			Color.b = Math.round(OldB * .6);
			var DarkColor = Color.toHex();
			
			Color.r = Math.round(Math.min(255,OldR * 1.2));
			Color.g = Math.round(Math.min(255,OldG * 1.2));
			Color.b = Math.round(Math.min(255,OldB * 1.2));
			var LightColor = Color.toHex();
			
			var Angle = OldAngle + RenderData[i]/100 * Math.PI*2;
			
			var CalloutOffsetX = 0;
			var CalloutOffsetY = 0;
			if(i == this.CurrentSliceCallout) {
				CalloutOffsetX = this.CurrentSliceCalloutDistance/10 * w * Math.cos((Angle+OldAngle)/2);
				CalloutOffsetY = this.CurrentSliceCalloutDistance/10 * h * Math.sin((Angle+OldAngle)/2);
			}
			
			this.Context.fillStyle = GivenColor;
			this.Context.strokeStyle = GivenColor;
			this.Context.StrokeAndFill(function() {
				graph.Context.Ellipse(x+CalloutOffsetX,y+CalloutOffsetY,w,h, OldAngle, Angle, true);
			});	
			
			if(is_safari)
				this.Context.fillStyle = DarkColor;
			else {
				//Create the linear gradient for the depth.
				var gradient = this.Context.createLinearGradient(x,y,x+w,y);
				gradient.addColorStop(0.0, DarkColor);
				gradient.addColorStop(0.15, GivenColor);
				gradient.addColorStop(0.30, LightColor);
				gradient.addColorStop(0.45, GivenColor);
				gradient.addColorStop(1.0, DarkColor);
				
				this.Context.fillStyle = gradient;
			}
			this.Context.strokeStyle = DarkColor;
			this.Context.StrokeAndFill(function() {
				graph.Context.Ellipse(x+CalloutOffsetX,y+CalloutOffsetY,w,h, OldAngle, Angle, true, depth);
			});	
			
			if(this.Scale < 1) {
				//Hide the labels
				$(this.Canvas).parent().find('div').css({color:'white'});
			}
			else {
				//Show the labels.
				$(this.Canvas).parent().find('div').css({color:''});
				
				//Draw the line from the slice to the label
				var LabelAngle = (Angle+OldAngle)/2;
				this.Context.lineWidth = .5;
				this.Context.strokeStyle = '#000000';
				this.Context.beginPath();
				var p1 = {x:x+w/2 + (w*.5)*Math.cos(LabelAngle), y:y+h/2 + (h*.5)*Math.sin(LabelAngle)};
				var p2 = {x:x+w/2 + (w*.55)*Math.cos(LabelAngle), y:y+h/2 + (h*.55)*Math.sin(LabelAngle)};
				this.Context.moveTo(p1.x, p1.y);
				this.Context.lineTo(p2.x, p2.y);
				this.Context.stroke();
				
				/*
				And place the label.
				*/
				var HorizontalLength = w*0.05;
				if(p2.x > p1.x) {
					this.Context.beginPath();
					this.Context.moveTo(p2.x, p2.y);
					this.Context.lineTo(x+w*1.1, p2.y);
					this.Context.stroke();
				
					this.LabelDivs[i].css({
						left:(x+w*1.1 + 2)+'px',
						top:(p2.y - this.LabelDivs[i].height()/2)+'px'
					});
				}
				else {
					this.Context.beginPath();
					this.Context.moveTo(p2.x, p2.y);
					this.Context.lineTo(x-w*0.1, p2.y);
					this.Context.stroke();
				
					this.LabelDivs[i].css({
						left:(x-w*0.1 - this.LabelDivs[i].width() - 2)+'px',
						top:(p2.y - this.LabelDivs[i].height()/2)+'px'
					});
				}
			}
			
			OldAngle = Angle;
		}
		
		if(Angle < Math.PI*2) {
			//Draw the actual pie slice
			var Color = new RGBColor('white');
			var GivenColor = Color.toHex();
			var OldR = Color.r;
			var OldG = Color.g;
			var OldB = Color.b
			
			Color.r = Math.round(OldR * .6);
			Color.g = Math.round(OldG * .6);
			Color.b = Math.round(OldB * .6);
			var DarkColor = Color.toHex();
			
			Color.r = Math.round(Math.min(255,OldR * 1.2));
			Color.g = Math.round(Math.min(255,OldG * 1.2));
			Color.b = Math.round(Math.min(255,OldB * 1.2));
			var LightColor = Color.toHex();
			
			var Angle = Math.PI*2;
			
			this.Context.fillStyle = GivenColor;
			this.Context.strokeStyle = DarkColor;
			this.Context.StrokeAndFill(function() {
				graph.Context.Ellipse(x,y,w,h, OldAngle, Angle, true);
			});	
			
			if(is_safari)
				this.Context.fillStyle = DarkColor;
			else {
				//Create the linear gradient for the depth.
				var gradient = this.Context.createLinearGradient(x,y,x+w,y);
				gradient.addColorStop(0.0, DarkColor);
				gradient.addColorStop(0.15, GivenColor);
				gradient.addColorStop(0.30, LightColor);
				gradient.addColorStop(0.45, GivenColor);
				gradient.addColorStop(1.0, DarkColor);
				
				this.Context.fillStyle = gradient;
			}
			this.Context.strokeStyle = DarkColor;
			this.Context.StrokeAndFill(function() {
				graph.Context.Ellipse(x,y,w,h, OldAngle, Angle, true, depth);
			});	
		}
		
		this.Dirty = false;
		
		/*
		For the various possible animations, keep Dirty=true.
		*/
		
		//Initial swoop-out animation (Non-IE)
		if(this.Scale < 1) {
			this.Scale = Math.min(1,(this.Scale*10+1.05)/11);
			this.Dirty = true;
		}
		
		this.UpdateSliceCallout();
	}

	
	var RGBColor = function(color_string) {
	    this.ok = false;
	
	    // strip any leading #
	    if (color_string.charAt(0) == '#') { // remove # if any
	        color_string = color_string.substr(1,6);
	    }
	
	    color_string = color_string.replace(/ /g,'');
	    color_string = color_string.toLowerCase();
	
	    // before getting into regexps, try simple matches
	    // and overwrite the input
	    var simple_colors = {
	        aliceblue: 'f0f8ff',
	        antiquewhite: 'faebd7',
	        aqua: '00ffff',
	        aquamarine: '7fffd4',
	        azure: 'f0ffff',
	        beige: 'f5f5dc',
	        bisque: 'ffe4c4',
	        black: '000000',
	        blanchedalmond: 'ffebcd',
	        blue: '0000ff',
	        blueviolet: '8a2be2',
	        brown: 'a52a2a',
	        burlywood: 'deb887',
	        cadetblue: '5f9ea0',
	        chartreuse: '7fff00',
	        chocolate: 'd2691e',
	        coral: 'ff7f50',
	        cornflowerblue: '6495ed',
	        cornsilk: 'fff8dc',
	        crimson: 'dc143c',
	        cyan: '00ffff',
	        darkblue: '00008b',
	        darkcyan: '008b8b',
	        darkgoldenrod: 'b8860b',
	        darkgray: 'a9a9a9',
	        darkgreen: '006400',
	        darkkhaki: 'bdb76b',
	        darkmagenta: '8b008b',
	        darkolivegreen: '556b2f',
	        darkorange: 'ff8c00',
	        darkorchid: '9932cc',
	        darkred: '8b0000',
	        darksalmon: 'e9967a',
	        darkseagreen: '8fbc8f',
	        darkslateblue: '483d8b',
	        darkslategray: '2f4f4f',
	        darkturquoise: '00ced1',
	        darkviolet: '9400d3',
	        deeppink: 'ff1493',
	        deepskyblue: '00bfff',
	        dimgray: '696969',
	        dodgerblue: '1e90ff',
	        feldspar: 'd19275',
	        firebrick: 'b22222',
	        floralwhite: 'fffaf0',
	        forestgreen: '228b22',
	        fuchsia: 'ff00ff',
	        gainsboro: 'dcdcdc',
	        ghostwhite: 'f8f8ff',
	        gold: 'ffd700',
	        goldenrod: 'daa520',
	        gray: '808080',
	        green: '008000',
	        greenyellow: 'adff2f',
	        honeydew: 'f0fff0',
	        hotpink: 'ff69b4',
	        indianred : 'cd5c5c',
	        indigo : '4b0082',
	        ivory: 'fffff0',
	        khaki: 'f0e68c',
	        lavender: 'e6e6fa',
	        lavenderblush: 'fff0f5',
	        lawngreen: '7cfc00',
	        lemonchiffon: 'fffacd',
	        lightblue: 'add8e6',
	        lightcoral: 'f08080',
	        lightcyan: 'e0ffff',
	        lightgoldenrodyellow: 'fafad2',
	        lightgrey: 'd3d3d3',
	        lightgreen: '90ee90',
	        lightpink: 'ffb6c1',
	        lightsalmon: 'ffa07a',
	        lightseagreen: '20b2aa',
	        lightskyblue: '87cefa',
	        lightslateblue: '8470ff',
	        lightslategray: '778899',
	        lightsteelblue: 'b0c4de',
	        lightyellow: 'ffffe0',
	        lime: '00ff00',
	        limegreen: '32cd32',
	        linen: 'faf0e6',
	        magenta: 'ff00ff',
	        maroon: '800000',
	        mediumaquamarine: '66cdaa',
	        mediumblue: '0000cd',
	        mediumorchid: 'ba55d3',
	        mediumpurple: '9370d8',
	        mediumseagreen: '3cb371',
	        mediumslateblue: '7b68ee',
	        mediumspringgreen: '00fa9a',
	        mediumturquoise: '48d1cc',
	        mediumvioletred: 'c71585',
	        midnightblue: '191970',
	        mintcream: 'f5fffa',
	        mistyrose: 'ffe4e1',
	        moccasin: 'ffe4b5',
	        navajowhite: 'ffdead',
	        navy: '000080',
	        oldlace: 'fdf5e6',
	        olive: '808000',
	        olivedrab: '6b8e23',
	        orange: 'ffa500',
	        orangered: 'ff4500',
	        orchid: 'da70d6',
	        palegoldenrod: 'eee8aa',
	        palegreen: '98fb98',
	        paleturquoise: 'afeeee',
	        palevioletred: 'd87093',
	        papayawhip: 'ffefd5',
	        peachpuff: 'ffdab9',
	        peru: 'cd853f',
	        pink: 'ffc0cb',
	        plum: 'dda0dd',
	        powderblue: 'b0e0e6',
	        purple: '800080',
	        red: 'ff0000',
	        rosybrown: 'bc8f8f',
	        royalblue: '4169e1',
	        saddlebrown: '8b4513',
	        salmon: 'fa8072',
	        sandybrown: 'f4a460',
	        seagreen: '2e8b57',
	        seashell: 'fff5ee',
	        sienna: 'a0522d',
	        silver: 'c0c0c0',
	        skyblue: '87ceeb',
	        slateblue: '6a5acd',
	        slategray: '708090',
	        snow: 'fffafa',
	        springgreen: '00ff7f',
	        steelblue: '4682b4',
	        tan: 'd2b48c',
	        teal: '008080',
	        thistle: 'd8bfd8',
	        tomato: 'ff6347',
	        turquoise: '40e0d0',
	        violet: 'ee82ee',
	        violetred: 'd02090',
	        wheat: 'f5deb3',
	        white: 'ffffff',
	        whitesmoke: 'f5f5f5',
	        yellow: 'ffff00',
	        yellowgreen: '9acd32'
	    };
	    for (var key in simple_colors) {
	        if (color_string == key) {
	            color_string = simple_colors[key];
	        }
	    }
	    // emd of simple type-in colors
	
	    // array of color definition objects
	    var color_defs = [
	        {
	            re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,
	            example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],
	            process: function (bits){
	                return [
	                    parseInt(bits[1]),
	                    parseInt(bits[2]),
	                    parseInt(bits[3])
	                ];
	            }
	        },
	        {
	            re: /^(\w{2})(\w{2})(\w{2})$/,
	            example: ['#00ff00', '336699'],
	            process: function (bits){
	                return [
	                    parseInt(bits[1], 16),
	                    parseInt(bits[2], 16),
	                    parseInt(bits[3], 16)
	                ];
	            }
	        },
	        {
	            re: /^(\w{1})(\w{1})(\w{1})$/,
	            example: ['#fb0', 'f0f'],
	            process: function (bits){
	                return [
	                    parseInt(bits[1] + bits[1], 16),
	                    parseInt(bits[2] + bits[2], 16),
	                    parseInt(bits[3] + bits[3], 16)
	                ];
	            }
	        }
	    ];
	
	    // search through the definitions to find a match
	    for (var i = 0; i < color_defs.length; i++) {
	        var re = color_defs[i].re;
	        var processor = color_defs[i].process;
	        var bits = re.exec(color_string);
	        if (bits) {
	            channels = processor(bits);
	            this.r = channels[0];
	            this.g = channels[1];
	            this.b = channels[2];
	            this.ok = true;
	        }
	
	    }
	
	    // validate/cleanup values
	    this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r);
	    this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g);
	    this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b);
	
	    // some getters
	    this.toRGB = function () {
	        return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')';
	    }
	    this.toHex = function () {
	        var r = this.r.toString(16);
	        var g = this.g.toString(16);
	        var b = this.b.toString(16);
	        if (r.length == 1) r = '0' + r;
	        if (g.length == 1) g = '0' + g;
	        if (b.length == 1) b = '0' + b;
	        return '#' + r + g + b;
	    }
		
		this.Total = function() {
			return this.r+this.g+this.b;
		}
		
	}
	
		
	$(function() {
		var graphs = [];
		$('canvas.clientgraph').each(function() {
			$(this).removeClass('clientgraph');
			if($(this).attr('graphtype') == 'pie3d') {
				var graph = new Pie3DGraph(this, $(this).attr('data').split('|'), $(this).attr('labels').split('|'), $(this).attr('colors').split('|'), $(this).attr('title'));
				graphs.push(graph);
			}
			$(this).attr('title','');
		});
		
		setInterval(function() {
			for(var i = 0; i < graphs.length; i++) {
				if(graphs[i].Dirty) {
					graphs[i].Render();
				}
			}
		}, 33);
	});
})();

