text: /* Javascript plotting library for jQuery, version 0.8.3.
  
  Copyright (c) 2007-2014 IOLA and Ole Laursen.
  Licensed under the MIT license.
  
  */
  
  // first an inline dependency, jquery.colorhelpers.js, we inline it here
  // for convenience
  
  /* Plugin for jQuery for working with colors.
   *
   * Version 1.1.
   *
   * Inspiration from jQuery color animation plugin by John Resig.
   *
   * Released under the MIT license by Ole Laursen, October 2009.
   *
   * Examples:
   *
   *   $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
   *   var c = $.color.extract($("#mydiv"), 'background-color');
   *   console.log(c.r, c.g, c.b, c.a);
   *   $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
   *
   * Note that .scale() and .add() return the same modified object
   * instead of making a new one.
   *
   * V. 1.1: Fix error handling so e.g. parsing an empty string does
   * produce a color rather than just crashing.
   */
  (function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i<c.length;++i)o[c.charAt(i)]+=d;return o.normalize()};o.scale=function(c,f){for(var i=0;i<c.length;++i)o[c.charAt(i)]*=f;return o.normalize()};o.toString=function(){if(o.a>=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return value<min?min:value>max?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);
  
  // the actual Flot code
  (function($) {
  
  	// Cache the prototype hasOwnProperty for faster access
  
  	var hasOwnProperty = Object.prototype.hasOwnProperty;
  
      // A shim to provide 'detach' to jQuery versions prior to 1.4.  Using a DOM
      // operation produces the same effect as detach, i.e. removing the element
      // without touching its jQuery data.
  
      // Do not merge this into Flot 0.9, since it requires jQuery 1.4.4+.
  
      if (!$.fn.detach) {
          $.fn.detach = function() {
              return this.each(function() {
                  if (this.parentNode) {
                      this.parentNode.removeChild( this );
                  }
              });
          };
      }
  
  	///////////////////////////////////////////////////////////////////////////
  	// The Canvas object is a wrapper around an HTML5 <canvas> tag.
  	//
  	// @constructor
  	// @param {string} cls List of classes to apply to the canvas.
  	// @param {element} container Element onto which to append the canvas.
  	//
  	// Requiring a container is a little iffy, but unfortunately canvas
  	// operations don't work unless the canvas is attached to the DOM.
  
  	function Canvas(cls, container) {
  
  		var element = container.children("." + cls)[0];
  
  		if (element == null) {
  
  			element = document.createElement("canvas");
  			element.className = cls;
  
  			$(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 })
  				.appendTo(container);
  
  			// If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas
  
  			if (!element.getContext) {
  				if (window.G_vmlCanvasManager) {
  					element = window.G_vmlCanvasManager.initElement(element);
  				} else {
  					throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.");
  				}
  			}
  		}
  
  		this.element = element;
  
  		var context = this.context = element.getContext("2d");
  
  		// Determine the screen's ratio of physical to device-independent
  		// pixels.  This is the ratio between the canvas width that the browser
  		// advertises and the number of pixels actually present in that space.
  
  		// The iPhone 4, for example, has a device-independent width of 320px,
  		// but its screen is actually 640px wide.  It therefore has a pixel
  		// ratio of 2, while most normal devices have a ratio of 1.
  
  		var devicePixelRatio = window.devicePixelRatio || 1,
  			backingStoreRatio =
  				context.webkitBackingStorePixelRatio ||
  				context.mozBackingStorePixelRatio ||
  				context.msBackingStorePixelRatio ||
  				context.oBackingStorePixelRatio ||
  				context.backingStorePixelRatio || 1;
  
  		this.pixelRatio = devicePixelRatio / backingStoreRatio;
  
  		// Size the canvas to match the internal dimensions of its container
  
  		this.resize(container.width(), container.height());
  
  		// Collection of HTML div layers for text overlaid onto the canvas
  
  		this.textContainer = null;
  		this.text = {};
  
  		// Cache of text fragments and metrics, so we can avoid expensively
  		// re-calculating them when the plot is re-rendered in a loop.
  
  		this._textCache = {};
  	}
  
  	// Resizes the canvas to the given dimensions.
  	//
  	// @param {number} width New width of the canvas, in pixels.
  	// @param {number} width New height of the canvas, in pixels.
  
  	Canvas.prototype.resize = function(width, height) {
  
  		if (width <= 0 || height <= 0) {
  			throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height);
  		}
  
  		var element = this.element,
  			context = this.context,
  			pixelRatio = this.pixelRatio;
  
  		// Resize the canvas, increasing its density based on the display's
  		// pixel ratio; basically giving it more pixels without increasing the
  		// size of its element, to take advantage of the fact that retina
  		// displays have that many more pixels in the same advertised space.
  
  		// Resizing should reset the state (excanvas seems to be buggy though)
  
  		if (this.width != width) {
  			element.width = width * pixelRatio;
  			element.style.width = width + "px";
  			this.width = width;
  		}
  
  		if (this.height != height) {
  			element.height = height * pixelRatio;
  			element.style.height = height + "px";
  			this.height = height;
  		}
  
  		// Save the context, so we can reset in case we get replotted.  The
  		// restore ensure that we're really back at the initial state, and
  		// should be safe even if we haven't saved the initial state yet.
  
  		context.restore();
  		context.save();
  
  		// Scale the coordinate space to match the display density; so even though we
  		// may have twice as many pixels, we still want lines and other drawing to
  		// appear at the same size; the extra pixels will just make them crisper.
  
  		context.scale(pixelRatio, pixelRatio);
  	};
  
  	// Clears the entire canvas area, not including any overlaid HTML text
  
  	Canvas.prototype.clear = function() {
  		this.context.clearRect(0, 0, this.width, this.height);
  	};
  
  	// Finishes rendering the canvas, including managing the text overlay.
  
  	Canvas.prototype.render = function() {
  
  		var cache = this._textCache;
  
  		// For each text layer, add elements marked as active that haven't
  		// already been rendered, and remove those that are no longer active.
  
  		for (var layerKey in cache) {
  			if (hasOwnProperty.call(cache, layerKey)) {
  
  				var layer = this.getTextLayer(layerKey),
  					layerCache = cache[layerKey];
  
  				layer.hide();
  
  				for (var styleKey in layerCache) {
  					if (hasOwnProperty.call(layerCache, styleKey)) {
  						var styleCache = layerCache[styleKey];
  						for (var key in styleCache) {
  							if (hasOwnProperty.call(styleCache, key)) {
  
  								var positions = styleCache[key].positions;
  
  								for (var i = 0, position; position = positions[i]; i++) {
  									if (position.active) {
  										if (!position.rendered) {
  											layer.append(position.element);
  											position.rendered = true;
  										}
  									} else {
  										positions.splice(i--, 1);
  										if (position.rendered) {
  											position.element.detach();
  										}
  									}
  								}
  
  								if (positions.length == 0) {
  									delete styleCache[key];
  								}
  							}
  						}
  					}
  				}
  
  				layer.show();
  			}
  		}
  	};
  
  	// Creates (if necessary) and returns the text overlay container.
  	//
  	// @param {string} classes String of space-separated CSS classes used to
  	//     uniquely identify the text layer.
  	// @return {object} The jQuery-wrapped text-layer div.
  
  	Canvas.prototype.getTextLayer = function(classes) {
  
  		var layer = this.text[classes];
  
  		// Create the text layer if it doesn't exist
  
  		if (layer == null) {
  
  			// Create the text layer container, if it doesn't exist
  
  			if (this.textContainer == null) {
  				this.textContainer = $("<div class='flot-text'></div>")
  					.css({
  						position: "absolute",
  						top: 0,
  						left: 0,
  						bottom: 0,
  						right: 0,
  						'font-size': "smaller",
  						color: "#545454"
  					})
  					.insertAfter(this.element);
  			}
  
  			layer = this.text[classes] = $("<div></div>")
  				.addClass(classes)
  				.css({
  					position: "absolute",
  					top: 0,
  					left: 0,
  					bottom: 0,
  					right: 0
  				})
  				.appendTo(this.textContainer);
  		}
  
  		return layer;
  	};
  
  	// Creates (if necessary) and returns a text info object.
  	//
  	// The object looks like this:
  	//
  	// {
  	//     width: Width of the text's wrapper div.
  	//     height: Height of the text's wrapper div.
  	//     element: The jQuery-wrapped HTML div containing the text.
  	//     positions: Array of positions at which this text is drawn.
  	// }
  	//
  	// The positions array contains objects that look like this:
  	//
  	// {
  	//     active: Flag indicating whether the text should be visible.
  	//     rendered: Flag indicating whether the text is currently visible.
  	//     element: The jQuery-wrapped HTML div containing the text.
  	//     x: X coordinate at which to draw the text.
  	//     y: Y coordinate at which to draw the text.
  	// }
  	//
  	// Each position after the first receives a clone of the original element.
  	//
  	// The idea is that that the width, height, and general 'identity' of the
  	// text is constant no matter where it is placed; the placements are a
  	// secondary property.
  	//
  	// Canvas maintains a cache of recently-used text info objects; getTextInfo
  	// either returns the cached element or creates a new entry.
  	//
  	// @param {string} layer A string of space-separated CSS classes uniquely
  	//     identifying the layer containing this text.
  	// @param {string} text Text string to retrieve info for.
  	// @param {(string|object)=} font Either a string of space-separated CSS
  	//     classes or a font-spec object, defining the text's font and style.
  	// @param {number=} angle Angle at which to rotate the text, in degrees.
  	//     Angle is currently unused, it will be implemented in the future.
  	// @param {number=} width Maximum width of the text before it wraps.
  	// @return {object} a text info object.
  
  	Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) {
  
  		var textStyle, layerCache, styleCache, info;
  
  		// Cast the value to a string, in case we were given a number or such
  
  		text = "" + text;
  
  		// If the font is a font-spec object, generate a CSS font definition
  
  		if (typeof font === "object") {
  			textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family;
  		} else {
  			textStyle = font;
  		}
  
  		// Retrieve (or create) the cache for the text's layer and styles
  
  		layerCache = this._textCache[layer];
  
  		if (layerCache == null) {
  			layerCache = this._textCache[layer] = {};
  		}
  
  		styleCache = layerCache[textStyle];
  
  		if (styleCache == null) {
  			styleCache = layerCache[textStyle] = {};
  		}
  
  		info = styleCache[text];
  
  		// If we can't find a matching element in our cache, create a new one
  
  		if (info == null) {
  
  			var element = $("<div></div>").html(text)
  				.css({
  					position: "absolute",
  					'max-width': width,
  					top: -9999
  				})
  				.appendTo(this.getTextLayer(layer));
  
  			if (typeof font === "object") {
  				element.css({
  					font: textStyle,
  					color: font.color
  				});
  			} else if (typeof font === "string") {
  				element.addClass(font);
  			}
  
  			info = styleCache[text] = {
  				width: element.outerWidth(true),
  				height: element.outerHeight(true),
  				element: element,
  				positions: []
  			};
  
  			element.detach();
  		}
  
  		return info;
  	};
  
  	// Adds a text string to the canvas text overlay.
  	//
  	// The text isn't drawn immediately; it is marked as rendering, which will
  	// result in its addition to the canvas on the next render pass.
  	//
  	// @param {string} layer A string of space-separated CSS classes uniquely
  	//     identifying the layer containing this text.
  	// @param {number} x X coordinate at which to draw the text.
  	// @param {number} y Y coordinate at which to draw the text.
  	// @param {string} text Text string to draw.
  	// @param {(string|object)=} font Either a string of space-separated CSS
  	//     classes or a font-spec object, defining the text's font and style.
  	// @param {number=} angle Angle at which to rotate the text, in degrees.
  	//     Angle is currently unused, it will be implemented in the future.
  	// @param {number=} width Maximum width of the text before it wraps.
  	// @param {string=} halign Horizontal alignment of the text; either "left",
  	//     "center" or "right".
  	// @param {string=} valign Vertical alignment of the text; either "top",
  	//     "middle" or "bottom".
  
  	Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) {
  
  		var info = this.getTextInfo(layer, text, font, angle, width),
  			positions = info.positions;
  
  		// Tweak the div's position to match the text's alignment
  
  		if (halign == "center") {
  			x -= info.width / 2;
  		} else if (halign == "right") {
  			x -= info.width;
  		}
  
  		if (valign == "middle") {
  			y -= info.height / 2;
  		} else if (valign == "bottom") {
  			y -= info.height;
  		}
  
  		// Determine whether this text already exists at this position.
  		// If so, mark it for inclusion in the next render pass.
  
  		for (var i = 0, position; position = positions[i]; i++) {
  			if (position.x == x && position.y == y) {
  				position.active = true;
  				return;
  			}
  		}
  
  		// If the text doesn't exist at this position, create a new entry
  
  		// For the very first position we'll re-use the original element,
  		// while for subsequent ones we'll clone it.
  
  		position = {
  			active: true,
  			rendered: false,
  			element: positions.length ? info.element.clone() : info.element,
  			x: x,
  			y: y
  		};
  
  		positions.push(position);
  
  		// Move the element to its final position within the container
  
  		position.element.css({
  			top: Math.round(y),
  			left: Math.round(x),
  			'text-align': halign	// In case the text wraps
  		});
  	};
  
  	// Removes one or more text strings from the canvas text overlay.
  	//
  	// If no parameters are given, all text within the layer is removed.
  	//
  	// Note that the text is not immediately removed; it is simply marked as
  	// inactive, which will result in its removal on the next render pass.
  	// This avoids the performance penalty for 'clear and redraw' behavior,
  	// where we potentially get rid of all text on a layer, but will likely
  	// add back most or all of it later, as when redrawing axes, for example.
  	//
  	// @param {string} layer A string of space-separated CSS classes uniquely
  	//     identifying the layer containing this text.
  	// @param {number=} x X coordinate of the text.
  	// @param {number=} y Y coordinate of the text.
  	// @param {string=} text Text string to remove.
  	// @param {(string|object)=} font Either a string of space-separated CSS
  	//     classes or a font-spec object, defining the text's font and style.
  	// @param {number=} angle Angle at which the text is rotated, in degrees.
  	//     Angle is currently unused, it will be implemented in the future.
  
  	Canvas.prototype.removeText = function(layer, x, y, text, font, angle) {
  		if (text == null) {
  			var layerCache = this._textCache[layer];
  			if (layerCache != null) {
  				for (var styleKey in layerCache) {
  					if (hasOwnProperty.call(layerCache, styleKey)) {
  						var styleCache = layerCache[styleKey];
  						for (var key in styleCache) {
  							if (hasOwnProperty.call(styleCache, key)) {
  								var positions = styleCache[key].positions;
  								for (var i = 0, position; position = positions[i]; i++) {
  									position.active = false;
  								}
  							}
  						}
  					}
  				}
  			}
  		} else {
  			var positions = this.getTextInfo(layer, text, font, angle).positions;
  			for (var i = 0, position; position = positions[i]; i++) {
  				if (position.x == x && position.y == y) {
  					position.active = false;
  				}
  			}
  		}
  	};
  
  	///////////////////////////////////////////////////////////////////////////
  	// The top-level container for the entire plot.
  
      function Plot(placeholder, data_, options_, plugins) {
          // data is on the form:
          //   [ series1, series2 ... ]
          // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
          // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }
  
          var series = [],
              options = {
                  // the color theme used for graphs
                  colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
                  legend: {
                      show: true,
                      noColumns: 1, // number of colums in legend table
                      labelFormatter: null, // fn: string -> string
                      labelBoxBorderColor: "#ccc", // border color for the little label boxes
                      container: null, // container (as jQuery object) to put legend in, null means default on top of graph
                      position: "ne", // position of default legend container within plot
                      margin: 5, // distance from grid edge to default legend container within plot
                      backgroundColor: null, // null means auto-detect
                      backgroundOpacity: 0.85, // set to 0 to avoid background
                      sorted: null    // default to no legend sorting
                  },
                  xaxis: {
                      show: null, // null = auto-detect, true = always, false = never
                      position: "bottom", // or "top"
                      mode: null, // null or "time"
                      font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" }
                      color: null, // base color, labels, ticks
                      tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)"
                      transform: null, // null or f: number -> number to transform axis
                      inverseTransform: null, // if transform is set, this should be the inverse function
                      min: null, // min. value to show, null means set automatically
                      max: null, // max. value to show, null means set automatically
                      autoscaleMargin: null, // margin in % to add if auto-setting min/max
                      ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
                      tickFormatter: null, // fn: number -> string
                      labelWidth: null, // size of tick labels in pixels
                      labelHeight: null,
                      reserveSpace: null, // whether to reserve space even if axis isn't shown
                      tickLength: null, // size in pixels of ticks, or "full" for whole line
                      alignTicksWithAxis: null, // axis number or null for no sync
                      tickDecimals: null, // no. of decimals, null means auto
                      tickSize: null, // number or [number, "unit"]
                      minTickSize: null // number or [number, "unit"]
                  },
                  yaxis: {
                      autoscaleMargin: 0.02,
                      position: "left" // or "right"
                  },
                  xaxes: [],
                  yaxes: [],
                  series: {
                      points: {
                          show: false,
                          radius: 3,
                          lineWidth: 2, // in pixels
                          fill: true,
                          fillColor: "#ffffff",
                          symbol: "circle" // or callback
                      },
                      lines: {
                          // we don't put in show: false so we can see
                          // whether lines were actively disabled
                          lineWidth: 2, // in pixels
                          fill: false,
                          fillColor: null,
                          steps: false
                          // Omit 'zero', so we can later default its value to
                          // match that of the 'fill' option.
                      },
                      bars: {
                          show: false,
                          lineWidth: 2, // in pixels
                          barWidth: 1, // in units of the x axis
                          fill: true,
                          fillColor: null,
                          align: "left", // "left", "right", or "center"
                          horizontal: false,
                          zero: true
                      },
                      shadowSize: 3,
                      highlightColor: null
                  },
                  grid: {
                      show: true,
                      aboveData: false,
                      color: "#545454", // primary color used for outline and labels
                      backgroundColor: null, // null for transparent, else color
                      borderColor: null, // set if different from the grid color
                      tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)"
                      margin: 0, // distance from the canvas edge to the grid
                      labelMargin: 5, // in pixels
                      axisMargin: 8, // in pixels
                      borderWidth: 2, // in pixels
                      minBorderMargin: null, // in pixels, null means taken from points radius
                      markings: null, // array of ranges or fn: axes -> array of ranges
                      markingsColor: "#f4f4f4",
                      markingsLineWidth: 2,
                      // interactive stuff
                      clickable: false,
                      hoverable: false,
                      autoHighlight: true, // highlight in case mouse is near
                      mouseActiveRadius: 10 // how far the mouse can be away to activate an item
                  },
                  interaction: {
                      redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow
                  },
                  hooks: {}
              },
          surface = null,     // the canvas for the plot itself
          overlay = null,     // canvas for interactive stuff on top of plot
          eventHolder = null, // jQuery object that events should be bound to
          ctx = null, octx = null,
          xaxes = [], yaxes = [],
          plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
          plotWidth = 0, plotHeight = 0,
          hooks = {
              processOptions: [],
              processRawData: [],
              processDatapoints: [],
              processOffset: [],
              drawBackground: [],
              drawSeries: [],
              draw: [],
              bindEvents: [],
              drawOverlay: [],
              shutdown: []
          },
          plot = this;
  
          // public functions
          plot.setData = setData;
          plot.setupGrid = setupGrid;
          plot.draw = draw;
          plot.getPlaceholder = function() { return placeholder; };
          plot.getCanvas = function() { return surface.element; };
          plot.getPlotOffset = function() { return plotOffset; };
          plot.width = function () { return plotWidth; };
          plot.height = function () { return plotHeight; };
          plot.offset = function () {
              var o = eventHolder.offset();
              o.left += plotOffset.left;
              o.top += plotOffset.top;
              return o;
          };
          plot.getData = function () { return series; };
          plot.getAxes = function () {
              var res = {}, i;
              $.each(xaxes.concat(yaxes), function (_, axis) {
                  if (axis)
                      res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis;
              });
              return res;
          };
          plot.getXAxes = function () { return xaxes; };
          plot.getYAxes = function () { return yaxes; };
          plot.c2p = canvasToAxisCoords;
          plot.p2c = axisToCanvasCoords;
          plot.getOptions = function () { return options; };
          plot.highlight = highlight;
          plot.unhighlight = unhighlight;
          plot.triggerRedrawOverlay = triggerRedrawOverlay;
          plot.pointOffset = function(point) {
              return {
                  left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10),
                  top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10)
              };
          };
          plot.shutdown = shutdown;
          plot.destroy = function () {
              shutdown();
              placeholder.removeData("plot").empty();
  
              series = [];
              options = null;
              surface = null;
              overlay = null;
              eventHolder = null;
              ctx = null;
              octx = null;
              xaxes = [];
              yaxes = [];
              hooks = null;
              highlights = [];
              plot = null;
          };
          plot.resize = function () {
          	var width = placeholder.width(),
          		height = placeholder.height();
              surface.resize(width, height);
              overlay.resize(width, height);
          };
  
          // public attributes
          plot.hooks = hooks;
  
          // initialize
          initPlugins(plot);
          parseOptions(options_);
          setupCanvases();
          setData(data_);
          setupGrid();
          draw();
          bindEvents();
  
  
          function executeHooks(hook, args) {
              args = [plot].concat(args);
              for (var i = 0; i < hook.length; ++i)
                  hook[i].apply(this, args);
          }
  
          function initPlugins() {
  
              // References to key classes, allowing plugins to modify them
  
              var classes = {
                  Canvas: Canvas
              };
  
              for (var i = 0; i < plugins.length; ++i) {
                  var p = plugins[i];
                  p.init(plot, classes);
                  if (p.options)
                      $.extend(true, options, p.options);
              }
          }
  
          function parseOptions(opts) {
  
              $.extend(true, options, opts);
  
              // $.extend merges arrays, rather than replacing them.  When less
              // colors are provided than the size of the default palette, we
              // end up with those colors plus the remaining defaults, which is
              // not expected behavior; avoid it by replacing them here.
  
              if (opts && opts.colors) {
              	options.colors = opts.colors;
              }
  
              if (options.xaxis.color == null)
                  options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
              if (options.yaxis.color == null)
                  options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
  
              if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility
                  options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color;
              if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility
                  options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color;
  
              if (options.grid.borderColor == null)
                  options.grid.borderColor = options.grid.color;
              if (options.grid.tickColor == null)
                  options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString();
  
              // Fill in defaults for axis options, including any unspecified
              // font-spec fields, if a font-spec was provided.
  
              // If no x/y axis options were provided, create one of each anyway,
              // since the rest of the code assumes that they exist.
  
              var i, axisOptions, axisCount,
                  fontSize = placeholder.css("font-size"),
                  fontSizeDefault = fontSize ? +fontSize.replace("px", "") : 13,
                  fontDefaults = {
                      style: placeholder.css("font-style"),
                      size: Math.round(0.8 * fontSizeDefault),
                      variant: placeholder.css("font-variant"),
                      weight: placeholder.css("font-weight"),
                      family: placeholder.css("font-family")
                  };
  
              axisCount = options.xaxes.length || 1;
              for (i = 0; i < axisCount; ++i) {
  
                  axisOptions = options.xaxes[i];
                  if (axisOptions && !axisOptions.tickColor) {
                      axisOptions.tickColor = axisOptions.color;
                  }
  
                  axisOptions = $.extend(true, {}, options.xaxis, axisOptions);
                  options.xaxes[i] = axisOptions;
  
                  if (axisOptions.font) {
                      axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
                      if (!axisOptions.font.color) {
                          axisOptions.font.color = axisOptions.color;
                      }
                      if (!axisOptions.font.lineHeight) {
                          axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15);
                      }
                  }
              }
  
              axisCount = options.yaxes.length || 1;
              for (i = 0; i < axisCount; ++i) {
  
                  axisOptions = options.yaxes[i];
                  if (axisOptions && !axisOptions.tickColor) {
                      axisOptions.tickColor = axisOptions.color;
                  }
  
                  axisOptions = $.extend(true, {}, options.yaxis, axisOptions);
                  options.yaxes[i] = axisOptions;
  
                  if (axisOptions.font) {
                      axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
                      if (!axisOptions.font.color) {
                          axisOptions.font.color = axisOptions.color;
                      }
                      if (!axisOptions.font.lineHeight) {
                          axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15);
                      }
                  }
              }
  
              // backwards compatibility, to be removed in future
              if (options.xaxis.noTicks && options.xaxis.ticks == null)
                  options.xaxis.ticks = options.xaxis.noTicks;
              if (options.yaxis.noTicks && options.yaxis.ticks == null)
                  options.yaxis.ticks = options.yaxis.noTicks;
              if (options.x2axis) {
                  options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis);
                  options.xaxes[1].position = "top";
                  // Override the inherit to allow the axis to auto-scale
                  if (options.x2axis.min == null) {
                      options.xaxes[1].min = null;
                  }
                  if (options.x2axis.max == null) {
                      options.xaxes[1].max = null;
                  }
              }
              if (options.y2axis) {
                  options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis);
                  options.yaxes[1].position = "right";
                  // Override the inherit to allow the axis to auto-scale
                  if (options.y2axis.min == null) {
                      options.yaxes[1].min = null;
                  }
                  if (options.y2axis.max == null) {
                      options.yaxes[1].max = null;
                  }
              }
              if (options.grid.coloredAreas)
                  options.grid.markings = options.grid.coloredAreas;
              if (options.grid.coloredAreasColor)
                  options.grid.markingsColor = options.grid.coloredAreasColor;
              if (options.lines)
                  $.extend(true, options.series.lines, options.lines);
              if (options.points)
                  $.extend(true, options.series.points, options.points);
              if (options.bars)
                  $.extend(true, options.series.bars, options.bars);
              if (options.shadowSize != null)
                  options.series.shadowSize = options.shadowSize;
              if (options.highlightColor != null)
                  options.series.highlightColor = options.highlightColor;
  
              // save options on axes for future reference
              for (i = 0; i < options.xaxes.length; ++i)
                  getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i];
              for (i = 0; i < options.yaxes.length; ++i)
                  getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i];
  
              // add hooks from options
              for (var n in hooks)
                  if (options.hooks[n] && options.hooks[n].length)
                      hooks[n] = hooks[n].concat(options.hooks[n]);
  
              executeHooks(hooks.processOptions, [options]);
          }
  
          function setData(d) {
              series = parseData(d);
              fillInSeriesOptions();
              processData();
          }
  
          function parseData(d) {
              var res = [];
              for (var i = 0; i < d.length; ++i) {
                  var s = $.extend(true, {}, options.series);
  
                  if (d[i].data != null) {
                      s.data = d[i].data; // move the data instead of deep-copy
                      delete d[i].data;
  
                      $.extend(true, s, d[i]);
  
                      d[i].data = s.data;
                  }
                  else
                      s.data = d[i];
                  res.push(s);
              }
  
              return res;
          }
  
          function axisNumber(obj, coord) {
              var a = obj[coord + "axis"];
              if (typeof a == "object") // if we got a real axis, extract number
                  a = a.n;
              if (typeof a != "number")
                  a = 1; // default to first axis
              return a;
          }
  
          function allAxes() {
              // return flat array without annoying null entries
              return $.grep(xaxes.concat(yaxes), function (a) { return a; });
          }
  
          function canvasToAxisCoords(pos) {
              // return an object with x/y corresponding to all used axes
              var res = {}, i, axis;
              for (i = 0; i < xaxes.length; ++i) {
                  axis = xaxes[i];
                  if (axis && axis.used)
                      res["x" + axis.n] = axis.c2p(pos.left);
              }
  
              for (i = 0; i < yaxes.length; ++i) {
                  axis = yaxes[i];
                  if (axis && axis.used)
                      res["y" + axis.n] = axis.c2p(pos.top);
              }
  
              if (res.x1 !== undefined)
                  res.x = res.x1;
              if (res.y1 !== undefined)
                  res.y = res.y1;
  
              return res;
          }
  
          function axisToCanvasCoords(pos) {
              // get canvas coords from the first pair of x/y found in pos
              var res = {}, i, axis, key;
  
              for (i = 0; i < xaxes.length; ++i) {
                  axis = xaxes[i];
                  if (axis && axis.used) {
                      key = "x" + axis.n;
                      if (pos[key] == null && axis.n == 1)
                          key = "x";
  
                      if (pos[key] != null) {
                          res.left = axis.p2c(pos[key]);
                          break;
                      }
                  }
              }
  
              for (i = 0; i < yaxes.length; ++i) {
                  axis = yaxes[i];
                  if (axis && axis.used) {
                      key = "y" + axis.n;
                      if (pos[key] == null && axis.n == 1)
                          key = "y";
  
                      if (pos[key] != null) {
                          res.top = axis.p2c(pos[key]);
                          break;
                      }
                  }
              }
  
              return res;
          }
  
          function getOrCreateAxis(axes, number) {
              if (!axes[number - 1])
                  axes[number - 1] = {
                      n: number, // save the number for future reference
                      direction: axes == xaxes ? "x" : "y",
                      options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis)
                  };
  
              return axes[number - 1];
          }
  
          function fillInSeriesOptions() {
  
              var neededColors = series.length, maxIndex = -1, i;
  
              // Subtract the number of series that already have fixed colors or
              // color indexes from the number that we still need to generate.
  
              for (i = 0; i < series.length; ++i) {
                  var sc = series[i].color;
                  if (sc != null) {
                      neededColors--;
                      if (typeof sc == "number" && sc > maxIndex) {
                          maxIndex = sc;
                      }
                  }
              }
  
              // If any of the series have fixed color indexes, then we need to
              // generate at least as many colors as the highest index.
  
              if (neededColors <= maxIndex) {
                  neededColors = maxIndex + 1;
              }
  
              // Generate all the colors, using first the option colors and then
              // variations on those colors once they're exhausted.
  
              var c, colors = [], colorPool = options.colors,
                  colorPoolSize = colorPool.length, variation = 0;
  
              for (i = 0; i < neededColors; i++) {
  
                  c = $.color.parse(colorPool[i % colorPoolSize] || "#666");
  
                  // Each time we exhaust the colors in the pool we adjust
                  // a scaling factor used to produce more variations on
                  // those colors. The factor alternates negative/positive
                  // to produce lighter/darker colors.
  
                  // Reset the variation after every few cycles, or else
                  // it will end up producing only white or black colors.
  
                  if (i % colorPoolSize == 0 && i) {
                      if (variation >= 0) {
                          if (variation < 0.5) {
                              variation = -variation - 0.2;
                          } else variation = 0;
                      } else variation = -variation;
                  }
  
                  colors[i] = c.scale('rgb', 1 + variation);
              }
  
              // Finalize the series options, filling in their colors
  
              var colori = 0, s;
              for (i = 0; i < series.length; ++i) {
                  s = series[i];
  
                  // assign colors
                  if (s.color == null) {
                      s.color = colors[colori].toString();
                      ++colori;
                  }
                  else if (typeof s.color == "number")
                      s.color = colors[s.color].toString();
  
                  // turn on lines automatically in case nothing is set
                  if (s.lines.show == null) {
                      var v, show = true;
                      for (v in s)
                          if (s[v] && s[v].show) {
                              show = false;
                              break;
                          }
                      if (show)
                          s.lines.show = true;
                  }
  
                  // If nothing was provided for lines.zero, default it to match
                  // lines.fill, since areas by default should extend to zero.
  
                  if (s.lines.zero == null) {
                      s.lines.zero = !!s.lines.fill;
                  }
  
                  // setup axes
                  s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x"));
                  s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y"));
              }
          }
  
          function processData() {
              var topSentry = Number.POSITIVE_INFINITY,
                  bottomSentry = Number.NEGATIVE_INFINITY,
                  fakeInfinity = Number.MAX_VALUE,
                  i, j, k, m, length,
                  s, points, ps, x, y, axis, val, f, p,
                  data, format;
  
              function updateAxis(axis, min, max) {
                  if (min < axis.datamin && min != -fakeInfinity)
                      axis.datamin = min;
                  if (max > axis.datamax && max != fakeInfinity)
                      axis.datamax = max;
              }
  
              $.each(allAxes(), function (_, axis) {
                  // init axis
                  axis.datamin = topSentry;
                  axis.datamax = bottomSentry;
                  axis.used = false;
              });
  
              for (i = 0; i < series.length; ++i) {
                  s = series[i];
                  s.datapoints = { points: [] };
  
                  executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]);
              }
  
              // first pass: clean and copy data
              for (i = 0; i < series.length; ++i) {
                  s = series[i];
  
                  data = s.data;
                  format = s.datapoints.format;
  
                  if (!format) {
                      format = [];
                      // find out how to copy
                      format.push({ x: true, number: true, required: true });
                      format.push({ y: true, number: true, required: true });
  
                      if (s.bars.show || (s.lines.show && s.lines.fill)) {
                          var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero));
                          format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale });
                          if (s.bars.horizontal) {
                              delete format[format.length - 1].y;
                              format[format.length - 1].x = true;
                          }
                      }
  
                      s.datapoints.format = format;
                  }
  
                  if (s.datapoints.pointsize != null)
                      continue; // already filled in
  
                  s.datapoints.pointsize = format.length;
  
                  ps = s.datapoints.pointsize;
                  points = s.datapoints.points;
  
                  var insertSteps = s.lines.show && s.lines.steps;
                  s.xaxis.used = s.yaxis.used = true;
  
                  for (j = k = 0; j < data.length; ++j, k += ps) {
                      p = data[j];
  
                      var nullify = p == null;
                      if (!nullify) {
                          for (m = 0; m < ps; ++m) {
                              val = p[m];
                              f = format[m];
  
                              if (f) {
                                  if (f.number && val != null) {
                                      val = +val; // convert to number
                                      if (isNaN(val))
                                          val = null;
                                      else if (val == Infinity)
                                          val = fakeInfinity;
                                      else if (val == -Infinity)
                                          val = -fakeInfinity;
                                  }
  
                                  if (val == null) {
                                      if (f.required)
                                          nullify = true;
  
                                      if (f.defaultValue != null)
                                          val = f.defaultValue;
                                  }
                              }
  
                              points[k + m] = val;
                          }
                      }
  
                      if (nullify) {
                          for (m = 0; m < ps; ++m) {
                              val = points[k + m];
                              if (val != null) {
                                  f = format[m];
                                  // extract min/max info
                                  if (f.autoscale !== false) {
                                      if (f.x) {
                                          updateAxis(s.xaxis, val, val);
                                      }
                                      if (f.y) {
                                          updateAxis(s.yaxis, val, val);
                                      }
                                  }
                              }
                              points[k + m] = null;
                          }
                      }
                      else {
                          // a little bit of line specific stuff that
                          // perhaps shouldn't be here, but lacking
                          // better means...
                          if (insertSteps && k > 0
                              && points[k - ps] != null
                              && points[k - ps] != points[k]
                              && points[k - ps + 1] != points[k + 1]) {
                              // copy the point to make room for a middle point
                              for (m = 0; m < ps; ++m)
                                  points[k + ps + m] = points[k + m];
  
                              // middle point has same y
                              points[k + 1] = points[k - ps + 1];
  
                              // we've added a point, better reflect that
                              k += ps;
                          }
                      }
                  }
              }
  
              // give the hooks a chance to run
              for (i = 0; i < series.length; ++i) {
                  s = series[i];
  
                  executeHooks(hooks.processDatapoints, [ s, s.datapoints]);
              }
  
              // second pass: find datamax/datamin for auto-scaling
              for (i = 0; i < series.length; ++i) {
                  s = series[i];
                  points = s.datapoints.points;
                  ps = s.datapoints.pointsize;
                  format = s.datapoints.format;
  
                  var xmin = topSentry, ymin = topSentry,
                      xmax = bottomSentry, ymax = bottomSentry;
  
                  for (j = 0; j < points.length; j += ps) {
                      if (points[j] == null)
                          continue;
  
                      for (m = 0; m < ps; ++m) {
                          val = points[j + m];
                          f = format[m];
                          if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity)
                              continue;
  
                          if (f.x) {
                              if (val < xmin)
                                  xmin = val;
                              if (val > xmax)
                                  xmax = val;
                          }
                          if (f.y) {
                              if (val < ymin)
                                  ymin = val;
                              if (val > ymax)
                                  ymax = val;
                          }
                      }
                  }
  
                  if (s.bars.show) {
                      // make sure we got room for the bar on the dancing floor
                      var delta;
  
                      switch (s.bars.align) {
                          case "left":
                              delta = 0;
                              break;
                          case "right":
                              delta = -s.bars.barWidth;
                              break;
                          default:
                              delta = -s.bars.barWidth / 2;
                      }
  
                      if (s.bars.horizontal) {
                          ymin += delta;
                          ymax += delta + s.bars.barWidth;
                      }
                      else {
                          xmin += delta;
                          xmax += delta + s.bars.barWidth;
                      }
                  }
  
                  updateAxis(s.xaxis, xmin, xmax);
                  updateAxis(s.yaxis, ymin, ymax);
              }
  
              $.each(allAxes(), function (_, axis) {
                  if (axis.datamin == topSentry)
                      axis.datamin = null;
                  if (axis.datamax == bottomSentry)
                      axis.datamax = null;
              });
          }
  
          function setupCanvases() {
  
              // Make sure the placeholder is clear of everything except canvases
              // from a previous plot in this container that we'll try to re-use.
  
              placeholder.css("padding", 0) // padding messes up the positioning
                  .children().filter(function(){
                      return !$(this).hasClass("flot-overlay") && !$(this).hasClass('flot-base');
                  }).remove();
  
              if (placeholder.css("position") == 'static')
                  placeholder.css("position", "relative"); // for positioning labels and overlay
  
              surface = new Canvas("flot-base", placeholder);
              overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features
  
              ctx = surface.context;
              octx = overlay.context;
  
              // define which element we're listening for events on
              eventHolder = $(overlay.element).unbind();
  
              // If we're re-using a plot object, shut down the old one
  
              var existing = placeholder.data("plot");
  
              if (existing) {
                  existing.shutdown();
                  overlay.clear();
              }
  
              // save in case we get replotted
              placeholder.data("plot", plot);
          }
  
          function bindEvents() {
              // bind events
              if (options.grid.hoverable) {
                  eventHolder.mousemove(onMouseMove);
  
                  // Use bind, rather than .mouseleave, because we officially
                  // still support jQuery 1.2.6, which doesn't define a shortcut
                  // for mouseenter or mouseleave.  This was a bug/oversight that
                  // was fixed somewhere around 1.3.x.  We can return to using
                  // .mouseleave when we drop support for 1.2.6.
  
                  eventHolder.bind("mouseleave", onMouseLeave);
              }
  
              if (options.grid.clickable)
                  eventHolder.click(onClick);
  
              executeHooks(hooks.bindEvents, [eventHolder]);
          }
  
          function shutdown() {
              if (redrawTimeout)
                  clearTimeout(redrawTimeout);
  
              eventHolder.unbind("mousemove", onMouseMove);
              eventHolder.unbind("mouseleave", onMouseLeave);
              eventHolder.unbind("click", onClick);
  
              executeHooks(hooks.shutdown, [eventHolder]);
          }
  
          function setTransformationHelpers(axis) {
              // set helper functions on the axis, assumes plot area
              // has been computed already
  
              function identity(x) { return x; }
  
              var s, m, t = axis.options.transform || identity,
                  it = axis.options.inverseTransform;
  
              // precompute how much the axis is scaling a point
              // in canvas space
              if (axis.direction == "x") {
                  s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min));
                  m = Math.min(t(axis.max), t(axis.min));
              }
              else {
                  s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min));
                  s = -s;
                  m = Math.max(t(axis.max), t(axis.min));
              }
  
              // data point to canvas coordinate
              if (t == identity) // slight optimization
                  axis.p2c = function (p) { return (p - m) * s; };
              else
                  axis.p2c = function (p) { return (t(p) - m) * s; };
              // canvas coordinate to data point
              if (!it)
                  axis.c2p = function (c) { return m + c / s; };
              else
                  axis.c2p = function (c) { return it(m + c / s); };
          }
  
          function measureTickLabels(axis) {
  
              var opts = axis.options,
                  ticks = axis.ticks || [],
                  labelWidth = opts.labelWidth || 0,
                  labelHeight = opts.labelHeight || 0,
                  maxWidth = labelWidth || (axis.direction == "x" ? Math.floor(surface.width / (ticks.length || 1)) : null),
                  legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
                  layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
                  font = opts.font || "flot-tick-label tickLabel";
  
              for (var i = 0; i < ticks.length; ++i) {
  
                  var t = ticks[i];
  
                  if (!t.label)
                      continue;
  
                  var info = surface.getTextInfo(layer, t.label, font, null, maxWidth);
  
                  labelWidth = Math.max(labelWidth, info.width);
                  labelHeight = Math.max(labelHeight, info.height);
              }
  
              axis.labelWidth = opts.labelWidth || labelWidth;
              axis.labelHeight = opts.labelHeight || labelHeight;
          }
  
          function allocateAxisBoxFirstPhase(axis) {
              // find the bounding box of the axis by looking at label
              // widths/heights and ticks, make room by diminishing the
              // plotOffset; this first phase only looks at one
              // dimension per axis, the other dimension depends on the
              // other axes so will have to wait
  
              var lw = axis.labelWidth,
                  lh = axis.labelHeight,
                  pos = axis.options.position,
                  isXAxis = axis.direction === "x",
                  tickLength = axis.options.tickLength,
                  axisMargin = options.grid.axisMargin,
                  padding = options.grid.labelMargin,
                  innermost = true,
                  outermost = true,
                  first = true,
                  found = false;
  
              // Determine the axis's position in its direction and on its side
  
              $.each(isXAxis ? xaxes : yaxes, function(i, a) {
                  if (a && (a.show || a.reserveSpace)) {
                      if (a === axis) {
                          found = true;
                      } else if (a.options.position === pos) {
                          if (found) {
                              outermost = false;
                          } else {
                              innermost = false;
                          }
                      }
                      if (!found) {
                          first = false;
                      }
                  }
              });
  
              // The outermost axis on each side has no margin
  
              if (outermost) {
                  axisMargin = 0;
              }
  
              // The ticks for the first axis in each direction stretch across
  
              if (tickLength == null) {
                  tickLength = first ? "full" : 5;
              }
  
              if (!isNaN(+tickLength))
                  padding += +tickLength;
  
              if (isXAxis) {
                  lh += padding;
  
                  if (pos == "bottom") {
                      plotOffset.bottom += lh + axisMargin;
                      axis.box = { top: surface.height - plotOffset.bottom, height: lh };
                  }
                  else {
                      axis.box = { top: plotOffset.top + axisMargin, height: lh };
                      plotOffset.top += lh + axisMargin;
                  }
              }
              else {
                  lw += padding;
  
                  if (pos == "left") {
                      axis.box = { left: plotOffset.left + axisMargin, width: lw };
                      plotOffset.left += lw + axisMargin;
                  }
                  else {
                      plotOffset.right += lw + axisMargin;
                      axis.box = { left: surface.width - plotOffset.right, width: lw };
                  }
              }
  
               // save for future reference
              axis.position = pos;
              axis.tickLength = tickLength;
              axis.box.padding = padding;
              axis.innermost = innermost;
          }
  
          function allocateAxisBoxSecondPhase(axis) {
              // now that all axis boxes have been placed in one
              // dimension, we can set the remaining dimension coordinates
              if (axis.direction == "x") {
                  axis.box.left = plotOffset.left - axis.labelWidth / 2;
                  axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth;
              }
              else {
                  axis.box.top = plotOffset.top - axis.labelHeight / 2;
                  axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight;
              }
          }
  
          function adjustLayoutForThingsStickingOut() {
              // possibly adjust plot offset to ensure everything stays
              // inside the canvas and isn't clipped off
  
              var minMargin = options.grid.minBorderMargin,
                  axis, i;
  
              // check stuff from the plot (FIXME: this should just read
              // a value from the series, otherwise it's impossible to
              // customize)
              if (minMargin == null) {
                  minMargin = 0;
                  for (i = 0; i < series.length; ++i)
                      minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2));
              }
  
              var margins = {
                  left: minMargin,
                  right: minMargin,
                  top: minMargin,
                  bottom: minMargin
              };
  
              // check axis labels, note we don't check the actual
              // labels but instead use the overall width/height to not
              // jump as much around with replots
              $.each(allAxes(), function (_, axis) {
                  if (axis.reserveSpace && axis.ticks && axis.ticks.length) {
                      if (axis.direction === "x") {
                          margins.left = Math.max(margins.left, axis.labelWidth / 2);
                          margins.right = Math.max(margins.right, axis.labelWidth / 2);
                      } else {
                          margins.bottom = Math.max(margins.bottom, axis.labelHeight / 2);
                          margins.top = Math.max(margins.top, axis.labelHeight / 2);
                      }
                  }
              });
  
              plotOffset.left = Math.ceil(Math.max(margins.left, plotOffset.left));
              plotOffset.right = Math.ceil(Math.max(margins.right, plotOffset.right));
              plotOffset.top = Math.ceil(Math.max(margins.top, plotOffset.top));
              plotOffset.bottom = Math.ceil(Math.max(margins.bottom, plotOffset.bottom));
          }
  
          function setupGrid() {
              var i, axes = allAxes(), showGrid = options.grid.show;
  
              // Initialize the plot's offset from the edge of the canvas
  
              for (var a in plotOffset) {
                  var margin = options.grid.margin || 0;
                  plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0;
              }
  
              executeHooks(hooks.processOffset, [plotOffset]);
  
              // If the grid is visible, add its border width to the offset
  
              for (var a in plotOffset) {
                  if(typeof(options.grid.borderWidth) == "object") {
                      plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0;
                  }
                  else {
                      plotOffset[a] += showGrid ? options.grid.borderWidth : 0;
                  }
              }
  
              $.each(axes, function (_, axis) {
                  var axisOpts = axis.options;
                  axis.show = axisOpts.show == null ? axis.used : axisOpts.show;
                  axis.reserveSpace = axisOpts.reserveSpace == null ? axis.show : axisOpts.reserveSpace;
                  setRange(axis);
              });
  
              if (showGrid) {
  
                  var allocatedAxes = $.grep(axes, function (axis) {
                      return axis.show || axis.reserveSpace;
                  });
  
                  $.each(allocatedAxes, function (_, axis) {
                      // make the ticks
                      setupTickGeneration(axis);
                      setTicks(axis);
                      snapRangeToTicks(axis, axis.ticks);
                      // find labelWidth/Height for axis
                      measureTickLabels(axis);
                  });
  
                  // with all dimensions calculated, we can compute the
                  // axis bounding boxes, start from the outside
                  // (reverse order)
                  for (i = allocatedAxes.length - 1; i >= 0; --i)
                      allocateAxisBoxFirstPhase(allocatedAxes[i]);
  
                  // make sure we've got enough space for things that
                  // might stick out
                  adjustLayoutForThingsStickingOut();
  
                  $.each(allocatedAxes, function (_, axis) {
                      allocateAxisBoxSecondPhase(axis);
                  });
              }
  
              plotWidth = surface.width - plotOffset.left - plotOffset.right;
              plotHeight = surface.height - plotOffset.bottom - plotOffset.top;
  
              // now we got the proper plot dimensions, we can compute the scaling
              $.each(axes, function (_, axis) {
                  setTransformationHelpers(axis);
              });
  
              if (showGrid) {
                  drawAxisLabels();
              }
  
              insertLegend();
          }
  
          function setRange(axis) {
              var opts = axis.options,
                  min = +(opts.min != null ? opts.min : axis.datamin),
                  max = +(opts.max != null ? opts.max : axis.datamax),
                  delta = max - min;
  
              if (delta == 0.0) {
                  // degenerate case
                  var widen = max == 0 ? 1 : 0.01;
  
                  if (opts.min == null)
                      min -= widen;
                  // always widen max if we couldn't widen min to ensure we
                  // don't fall into min == max which doesn't work
                  if (opts.max == null || opts.min != null)
                      max += widen;
              }
              else {
                  // consider autoscaling
                  var margin = opts.autoscaleMargin;
                  if (margin != null) {
                      if (opts.min == null) {
                          min -= delta * margin;
                          // make sure we don't go below zero if all values
                          // are positive
                          if (min < 0 && axis.datamin != null && axis.datamin >= 0)
                              min = 0;
                      }
                      if (opts.max == null) {
                          max += delta * margin;
                          if (max > 0 && axis.datamax != null && axis.datamax <= 0)
                              max = 0;
                      }
                  }
              }
              axis.min = min;
              axis.max = max;
          }
  
          function setupTickGeneration(axis) {
              var opts = axis.options;
  
              // estimate number of ticks
              var noTicks;
              if (typeof opts.ticks == "number" && opts.ticks > 0)
                  noTicks = opts.ticks;
              else
                  // heuristic based on the model a*sqrt(x) fitted to
                  // some data points that seemed reasonable
                  noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height);
  
              var delta = (axis.max - axis.min) / noTicks,
                  dec = -Math.floor(Math.log(delta) / Math.LN10),
                  maxDec = opts.tickDecimals;
  
              if (maxDec != null && dec > maxDec) {
                  dec = maxDec;
              }
  
              var magn = Math.pow(10, -dec),
                  norm = delta / magn, // norm is between 1.0 and 10.0
                  size;
  
              if (norm < 1.5) {
                  size = 1;
              } else if (norm < 3) {
                  size = 2;
                  // special case for 2.5, requires an extra decimal
                  if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
                      size = 2.5;
                      ++dec;
                  }
              } else if (norm < 7.5) {
                  size = 5;
              } else {
                  size = 10;
              }
  
              size *= magn;
  
              if (opts.minTickSize != null && size < opts.minTickSize) {
                  size = opts.minTickSize;
              }
  
              axis.delta = delta;
              axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
              axis.tickSize = opts.tickSize || size;
  
              // Time mode was moved to a plug-in in 0.8, and since so many people use it
              // we'll add an especially friendly reminder to make sure they included it.
  
              if (opts.mode == "time" && !axis.tickGenerator) {
                  throw new Error("Time mode requires the flot.time plugin.");
              }
  
              // Flot supports base-10 axes; any other mode else is handled by a plug-in,
              // like flot.time.js.
  
              if (!axis.tickGenerator) {
  
                  axis.tickGenerator = function (axis) {
  
                      var ticks = [],
                          start = floorInBase(axis.min, axis.tickSize),
                          i = 0,
                          v = Number.NaN,
                          prev;
  
                      do {
                          prev = v;
                          v = start + i * axis.tickSize;
                          ticks.push(v);
                          ++i;
                      } while (v < axis.max && v != prev);
                      return ticks;
                  };
  
  				axis.tickFormatter = function (value, axis) {
  
  					var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1;
  					var formatted = "" + Math.round(value * factor) / factor;
  
  					// If tickDecimals was specified, ensure that we have exactly that
  					// much precision; otherwise default to the value's own precision.
  
  					if (axis.tickDecimals != null) {
  						var decimal = formatted.indexOf(".");
  						var precision = decimal == -1 ? 0 : formatted.length - decimal - 1;
  						if (precision < axis.tickDecimals) {
  							return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision);
  						}
  					}
  
                      return formatted;
                  };
              }
  
              if ($.isFunction(opts.tickFormatter))
                  axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); };
  
              if (opts.alignTicksWithAxis != null) {
                  var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1];
                  if (otherAxis && otherAxis.used && otherAxis != axis) {
                      // consider snapping min/max to outermost nice ticks
                      var niceTicks = axis.tickGenerator(axis);
                      if (niceTicks.length > 0) {
                          if (opts.min == null)
                              axis.min = Math.min(axis.min, niceTicks[0]);
                          if (opts.max == null && niceTicks.length > 1)
                              axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]);
                      }
  
                      axis.tickGenerator = function (axis) {
                          // copy ticks, scaled to this axis
                          var ticks = [], v, i;
                          for (i = 0; i < otherAxis.ticks.length; ++i) {
                              v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min);
                              v = axis.min + v * (axis.max - axis.min);
                              ticks.push(v);
                          }
                          return ticks;
                      };
  
                      // we might need an extra decimal since forced
                      // ticks don't necessarily fit naturally
                      if (!axis.mode && opts.tickDecimals == null) {
                          var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1),
                              ts = axis.tickGenerator(axis);
  
                          // only proceed if the tick interval rounded
                          // with an extra decimal doesn't give us a
                          // zero at end
                          if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec))))
                              axis.tickDecimals = extraDec;
                      }
                  }
              }
          }
  
          function setTicks(axis) {
              var oticks = axis.options.ticks, ticks = [];
              if (oticks == null || (typeof oticks == "number" && oticks > 0))
                  ticks = axis.tickGenerator(axis);
              else if (oticks) {
                  if ($.isFunction(oticks))
                      // generate the ticks
                      ticks = oticks(axis);
                  else
                      ticks = oticks;
              }
  
              // clean up/labelify the supplied ticks, copy them over
              var i, v;
              axis.ticks = [];
              for (i = 0; i < ticks.length; ++i) {
                  var label = null;
                  var t = ticks[i];
                  if (typeof t == "object") {
                      v = +t[0];
                      if (t.length > 1)
                          label = t[1];
                  }
                  else
                      v = +t;
                  if (label == null)
                      label = axis.tickFormatter(v, axis);
                  if (!isNaN(v))
                      axis.ticks.push({ v: v, label: label });
              }
          }
  
          function snapRangeToTicks(axis, ticks) {
              if (axis.options.autoscaleMargin && ticks.length > 0) {
                  // snap to ticks
                  if (axis.options.min == null)
                      axis.min = Math.min(axis.min, ticks[0].v);
                  if (axis.options.max == null && ticks.length > 1)
                      axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);
              }
          }
  
          function draw() {
  
              surface.clear();
  
              executeHooks(hooks.drawBackground, [ctx]);
  
              var grid = options.grid;
  
              // draw background, if any
              if (grid.show && grid.backgroundColor)
                  drawBackground();
  
              if (grid.show && !grid.aboveData) {
                  drawGrid();
              }
  
              for (var i = 0; i < series.length; ++i) {
                  executeHooks(hooks.drawSeries, [ctx, series[i]]);
                  drawSeries(series[i]);
              }
  
              executeHooks(hooks.draw, [ctx]);
  
              if (grid.show && grid.aboveData) {
                  drawGrid();
              }
  
              surface.render();
  
              // A draw implies that either the axes or data have changed, so we
              // should probably update the overlay highlights as well.
  
              triggerRedrawOverlay();
          }
  
          function extractRange(ranges, coord) {
              var axis, from, to, key, axes = allAxes();
  
              for (var i = 0; i < axes.length; ++i) {
                  axis = axes[i];
                  if (axis.direction == coord) {
                      key = coord + axis.n + "axis";
                      if (!ranges[key] && axis.n == 1)
                          key = coord + "axis"; // support x1axis as xaxis
                      if (ranges[key]) {
                          from = ranges[key].from;
                          to = ranges[key].to;
                          break;
                      }
                  }
              }
  
              // backwards-compat stuff - to be removed in future
              if (!ranges[key]) {
                  axis = coord == "x" ? xaxes[0] : yaxes[0];
                  from = ranges[coord + "1"];
                  to = ranges[coord + "2"];
              }
  
              // auto-reverse as an added bonus
              if (from != null && to != null && from > to) {
                  var tmp = from;
                  from = to;
                  to = tmp;
              }
  
              return { from: from, to: to, axis: axis };
          }
  
          function drawBackground() {
              ctx.save();
              ctx.translate(plotOffset.left, plotOffset.top);
  
              ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
              ctx.fillRect(0, 0, plotWidth, plotHeight);
              ctx.restore();
          }
  
          function drawGrid() {
              var i, axes, bw, bc;
  
              ctx.save();
              ctx.translate(plotOffset.left, plotOffset.top);
  
              // draw markings
              var markings = options.grid.markings;
              if (markings) {
                  if ($.isFunction(markings)) {
                      axes = plot.getAxes();
                      // xmin etc. is backwards compatibility, to be
                      // removed in the future
                      axes.xmin = axes.xaxis.min;
                      axes.xmax = axes.xaxis.max;
                      axes.ymin = axes.yaxis.min;
                      axes.ymax = axes.yaxis.max;
  
                      markings = markings(axes);
                  }
  
                  for (i = 0; i < markings.length; ++i) {
                      var m = markings[i],
                          xrange = extractRange(m, "x"),
                          yrange = extractRange(m, "y");
  
                      // fill in missing
                      if (xrange.from == null)
                          xrange.from = xrange.axis.min;
                      if (xrange.to == null)
                          xrange.to = xrange.axis.max;
                      if (yrange.from == null)
                          yrange.from = yrange.axis.min;
                      if (yrange.to == null)
                          yrange.to = yrange.axis.max;
  
                      // clip
                      if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
                          yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)
                          continue;
  
                      xrange.from = Math.max(xrange.from, xrange.axis.min);
                      xrange.to = Math.min(xrange.to, xrange.axis.max);
                      yrange.from = Math.max(yrange.from, yrange.axis.min);
                      yrange.to = Math.min(yrange.to, yrange.axis.max);
  
                      var xequal = xrange.from === xrange.to,
                          yequal = yrange.from === yrange.to;
  
                      if (xequal && yequal) {
                          continue;
                      }
  
                      // then draw
                      xrange.from = Math.floor(xrange.axis.p2c(xrange.from));
                      xrange.to = Math.floor(xrange.axis.p2c(xrange.to));
                      yrange.from = Math.floor(yrange.axis.p2c(yrange.from));
                      yrange.to = Math.floor(yrange.axis.p2c(yrange.to));
  
                      if (xequal || yequal) {
                          var lineWidth = m.lineWidth || options.grid.markingsLineWidth,
                              subPixel = lineWidth % 2 ? 0.5 : 0;
                          ctx.beginPath();
                          ctx.strokeStyle = m.color || options.grid.markingsColor;
                          ctx.lineWidth = lineWidth;
                          if (xequal) {
                              ctx.moveTo(xrange.to + subPixel, yrange.from);
                              ctx.lineTo(xrange.to + subPixel, yrange.to);
                          } else {
                              ctx.moveTo(xrange.from, yrange.to + subPixel);
                              ctx.lineTo(xrange.to, yrange.to + subPixel);                            
                          }
                          ctx.stroke();
                      } else {
                          ctx.fillStyle = m.color || options.grid.markingsColor;
                          ctx.fillRect(xrange.from, yrange.to,
                                       xrange.to - xrange.from,
                                       yrange.from - yrange.to);
                      }
                  }
              }
  
              // draw the ticks
              axes = allAxes();
              bw = options.grid.borderWidth;
  
              for (var j = 0; j < axes.length; ++j) {
                  var axis = axes[j], box = axis.box,
                      t = axis.tickLength, x, y, xoff, yoff;
                  if (!axis.show || axis.ticks.length == 0)
                      continue;
  
                  ctx.lineWidth = 1;
  
                  // find the edges
                  if (axis.direction == "x") {
                      x = 0;
                      if (t == "full")
                          y = (axis.position == "top" ? 0 : plotHeight);
                      else
                          y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0);
                  }
                  else {
                      y = 0;
                      if (t == "full")
                          x = (axis.position == "left" ? 0 : plotWidth);
                      else
                          x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0);
                  }
  
                  // draw tick bar
                  if (!axis.innermost) {
                      ctx.strokeStyle = axis.options.color;
                      ctx.beginPath();
                      xoff = yoff = 0;
                      if (axis.direction == "x")
                          xoff = plotWidth + 1;
                      else
                          yoff = plotHeight + 1;
  
                      if (ctx.lineWidth == 1) {
                          if (axis.direction == "x") {
                              y = Math.floor(y) + 0.5;
                          } else {
                              x = Math.floor(x) + 0.5;
                          }
                      }
  
                      ctx.moveTo(x, y);
                      ctx.lineTo(x + xoff, y + yoff);
                      ctx.stroke();
                  }
  
                  // draw ticks
  
                  ctx.strokeStyle = axis.options.tickColor;
  
                  ctx.beginPath();
                  for (i = 0; i < axis.ticks.length; ++i) {
                      var v = axis.ticks[i].v;
  
                      xoff = yoff = 0;
  
                      if (isNaN(v) || v < axis.min || v > axis.max
                          // skip those lying on the axes if we got a border
                          || (t == "full"
                              && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0)
                              && (v == axis.min || v == axis.max)))
                          continue;
  
                      if (axis.direction == "x") {
                          x = axis.p2c(v);
                          yoff = t == "full" ? -plotHeight : t;
  
                          if (axis.position == "top")
                              yoff = -yoff;
                      }
                      else {
                          y = axis.p2c(v);
                          xoff = t == "full" ? -plotWidth : t;
  
                          if (axis.position == "left")
                              xoff = -xoff;
                      }
  
                      if (ctx.lineWidth == 1) {
                          if (axis.direction == "x")
                              x = Math.floor(x) + 0.5;
                          else
                              y = Math.floor(y) + 0.5;
                      }
  
                      ctx.moveTo(x, y);
                      ctx.lineTo(x + xoff, y + yoff);
                  }
  
                  ctx.stroke();
              }
  
  
              // draw border
              if (bw) {
                  // If either borderWidth or borderColor is an object, then draw the border
                  // line by line instead of as one rectangle
                  bc = options.grid.borderColor;
                  if(typeof bw == "object" || typeof bc == "object") {
                      if (typeof bw !== "object") {
                          bw = {top: bw, right: bw, bottom: bw, left: bw};
                      }
                      if (typeof bc !== "object") {
                          bc = {top: bc, right: bc, bottom: bc, left: bc};
                      }
  
                      if (bw.top > 0) {
                          ctx.strokeStyle = bc.top;
                          ctx.lineWidth = bw.top;
                          ctx.beginPath();
                          ctx.moveTo(0 - bw.left, 0 - bw.top/2);
                          ctx.lineTo(plotWidth, 0 - bw.top/2);
                          ctx.stroke();
                      }
  
                      if (bw.right > 0) {
                          ctx.strokeStyle = bc.right;
                          ctx.lineWidth = bw.right;
                          ctx.beginPath();
                          ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top);
                          ctx.lineTo(plotWidth + bw.right / 2, plotHeight);
                          ctx.stroke();
                      }
  
                      if (bw.bottom > 0) {
                          ctx.strokeStyle = bc.bottom;
                          ctx.lineWidth = bw.bottom;
                          ctx.beginPath();
                          ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2);
                          ctx.lineTo(0, plotHeight + bw.bottom / 2);
                          ctx.stroke();
                      }
  
                      if (bw.left > 0) {
                          ctx.strokeStyle = bc.left;
                          ctx.lineWidth = bw.left;
                          ctx.beginPath();
                          ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom);
                          ctx.lineTo(0- bw.left/2, 0);
                          ctx.stroke();
                      }
                  }
                  else {
                      ctx.lineWidth = bw;
                      ctx.strokeStyle = options.grid.borderColor;
                      ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
                  }
              }
  
              ctx.restore();
          }
  
          function drawAxisLabels() {
  
              $.each(allAxes(), function (_, axis) {
                  var box = axis.box,
                      legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
                      layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
                      font = axis.options.font || "flot-tick-label tickLabel",
                      tick, x, y, halign, valign;
  
                  // Remove text before checking for axis.show and ticks.length;
                  // otherwise plugins, like flot-tickrotor, that draw their own
                  // tick labels will end up with both theirs and the defaults.
  
                  surface.removeText(layer);
  
                  if (!axis.show || axis.ticks.length == 0)
                      return;
  
                  for (var i = 0; i < axis.ticks.length; ++i) {
  
                      tick = axis.ticks[i];
                      if (!tick.label || tick.v < axis.min || tick.v > axis.max)
                          continue;
  
                      if (axis.direction == "x") {
                          halign = "center";
                          x = plotOffset.left + axis.p2c(tick.v);
                          if (axis.position == "bottom") {
                              y = box.top + box.padding;
                          } else {
                              y = box.top + box.height - box.padding;
                              valign = "bottom";
                          }
                      } else {
                          valign = "middle";
                          y = plotOffset.top + axis.p2c(tick.v);
                          if (axis.position == "left") {
                              x = box.left + box.width - box.padding;
                              halign = "right";
                          } else {
                              x = box.left + box.padding;
                          }
                      }
  
                      surface.addText(layer, x, y, tick.label, font, null, null, halign, valign);
                  }
              });
          }
  
          function drawSeries(series) {
              if (series.lines.show)
                  drawSeriesLines(series);
              if (series.bars.show)
                  drawSeriesBars(series);
              if (series.points.show)
                  drawSeriesPoints(series);
          }
  
          function drawSeriesLines(series) {
              function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
                  var points = datapoints.points,
                      ps = datapoints.pointsize,
                      prevx = null, prevy = null;
  
                  ctx.beginPath();
                  for (var i = ps; i < points.length; i += ps) {
                      var x1 = points[i - ps], y1 = points[i - ps + 1],
                          x2 = points[i], y2 = points[i + 1];
  
                      if (x1 == null || x2 == null)
                          continue;
  
                      // clip with ymin
                      if (y1 <= y2 && y1 < axisy.min) {
                          if (y2 < axisy.min)
                              continue;   // line segment is outside
                          // compute new intersection point
                          x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
                          y1 = axisy.min;
                      }
                      else if (y2 <= y1 && y2 < axisy.min) {
                          if (y1 < axisy.min)
                              continue;
                          x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
                          y2 = axisy.min;
                      }
  
                      // clip with ymax
                      if (y1 >= y2 && y1 > axisy.max) {
                          if (y2 > axisy.max)
                              continue;
                          x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
                          y1 = axisy.max;
                      }
                      else if (y2 >= y1 && y2 > axisy.max) {
                          if (y1 > axisy.max)
                              continue;
                          x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
                          y2 = axisy.max;
                      }
  
                      // clip with xmin
                      if (x1 <= x2 && x1 < axisx.min) {
                          if (x2 < axisx.min)
                              continue;
                          y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
                          x1 = axisx.min;
                      }
                      else if (x2 <= x1 && x2 < axisx.min) {
                          if (x1 < axisx.min)
                              continue;
                          y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
                          x2 = axisx.min;
                      }
  
                      // clip with xmax
                      if (x1 >= x2 && x1 > axisx.max) {
                          if (x2 > axisx.max)
                              continue;
                          y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
                          x1 = axisx.max;
                      }
                      else if (x2 >= x1 && x2 > axisx.max) {
                          if (x1 > axisx.max)
                              continue;
                          y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
                          x2 = axisx.max;
                      }
  
                      if (x1 != prevx || y1 != prevy)
                          ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
  
                      prevx = x2;
                      prevy = y2;
                      ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
                  }
                  ctx.stroke();
              }
  
              function plotLineArea(datapoints, axisx, axisy) {
                  var points = datapoints.points,
                      ps = datapoints.pointsize,
                      bottom = Math.min(Math.max(0, axisy.min), axisy.max),
                      i = 0, top, areaOpen = false,
                      ypos = 1, segmentStart = 0, segmentEnd = 0;
  
                  // we process each segment in two turns, first forward
                  // direction to sketch out top, then once we hit the
                  // end we go backwards to sketch the bottom
                  while (true) {
                      if (ps > 0 && i > points.length + ps)
                          break;
  
                      i += ps; // ps is negative if going backwards
  
                      var x1 = points[i - ps],
                          y1 = points[i - ps + ypos],
                          x2 = points[i], y2 = points[i + ypos];
  
                      if (areaOpen) {
                          if (ps > 0 && x1 != null && x2 == null) {
                              // at turning point
                              segmentEnd = i;
                              ps = -ps;
                              ypos = 2;
                              continue;
                          }
  
                          if (ps < 0 && i == segmentStart + ps) {
                              // done with the reverse sweep
                              ctx.fill();
                              areaOpen = false;
                              ps = -ps;
                              ypos = 1;
                              i = segmentStart = segmentEnd + ps;
                              continue;
                          }
                      }
  
                      if (x1 == null || x2 == null)
                          continue;
  
                      // clip x values
  
                      // clip with xmin
                      if (x1 <= x2 && x1 < axisx.min) {
                          if (x2 < axisx.min)
                              continue;
                          y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
                          x1 = axisx.min;
                      }
                      else if (x2 <= x1 && x2 < axisx.min) {
                          if (x1 < axisx.min)
                              continue;
                          y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
                          x2 = axisx.min;
                      }
  
                      // clip with xmax
                      if (x1 >= x2 && x1 > axisx.max) {
                          if (x2 > axisx.max)
                              continue;
                          y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
                          x1 = axisx.max;
                      }
                      else if (x2 >= x1 && x2 > axisx.max) {
                          if (x1 > axisx.max)
                              continue;
                          y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
                          x2 = axisx.max;
                      }
  
                      if (!areaOpen) {
                          // open area
                          ctx.beginPath();
                          ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
                          areaOpen = true;
                      }
  
                      // now first check the case where both is outside
                      if (y1 >= axisy.max && y2 >= axisy.max) {
                          ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
                          ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
                          continue;
                      }
                      else if (y1 <= axisy.min && y2 <= axisy.min) {
                          ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
                          ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
                          continue;
                      }
  
                      // else it's a bit more complicated, there might
                      // be a flat maxed out rectangle first, then a
                      // triangular cutout or reverse; to find these
                      // keep track of the current x values
                      var x1old = x1, x2old = x2;
  
                      // clip the y values, without shortcutting, we
                      // go through all cases in turn
  
                      // clip with ymin
                      if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
                          x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
                          y1 = axisy.min;
                      }
                      else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
                          x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
                          y2 = axisy.min;
                      }
  
                      // clip with ymax
                      if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
                          x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
                          y1 = axisy.max;
                      }
                      else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
                          x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
                          y2 = axisy.max;
                      }
  
                      // if the x value was changed we got a rectangle
                      // to fill
                      if (x1 != x1old) {
                          ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
                          // it goes to (x1, y1), but we fill that below
                      }
  
                      // fill triangular section, this sometimes result
                      // in redundant points if (x1, y1) hasn't changed
                      // from previous line to, but we just ignore that
                      ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
                      ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
  
                      // fill the other rectangle if it's there
                      if (x2 != x2old) {
                          ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
                          ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
                      }
                  }
              }
  
              ctx.save();
              ctx.translate(plotOffset.left, plotOffset.top);
              ctx.lineJoin = "round";
  
              var lw = series.lines.lineWidth,
                  sw = series.shadowSize;
              // FIXME: consider another form of shadow when filling is turned on
              if (lw > 0 && sw > 0) {
                  // draw shadow as a thick and thin line with transparency
                  ctx.lineWidth = sw;
                  ctx.strokeStyle = "rgba(0,0,0,0.1)";
                  // position shadow at angle from the mid of line
                  var angle = Math.PI/18;
                  plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis);
                  ctx.lineWidth = sw/2;
                  plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis);
              }
  
              ctx.lineWidth = lw;
              ctx.strokeStyle = series.color;
              var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
              if (fillStyle) {
                  ctx.fillStyle = fillStyle;
                  plotLineArea(series.datapoints, series.xaxis, series.yaxis);
              }
  
              if (lw > 0)
                  plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
              ctx.restore();
          }
  
          function drawSeriesPoints(series) {
              function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) {
                  var points = datapoints.points, ps = datapoints.pointsize;
  
                  for (var i = 0; i < points.length; i += ps) {
                      var x = points[i], y = points[i + 1];
                      if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
                          continue;
  
                      ctx.beginPath();
                      x = axisx.p2c(x);
                      y = axisy.p2c(y) + offset;
                      if (symbol == "circle")
                          ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);
                      else
                          symbol(ctx, x, y, radius, shadow);
                      ctx.closePath();
  
                      if (fillStyle) {
                          ctx.fillStyle = fillStyle;
                          ctx.fill();
                      }
                      ctx.stroke();
                  }
              }
  
              ctx.save();
              ctx.translate(plotOffset.left, plotOffset.top);
  
              var lw = series.points.lineWidth,
                  sw = series.shadowSize,
                  radius = series.points.radius,
                  symbol = series.points.symbol;
  
              // If the user sets the line width to 0, we change it to a very 
              // small value. A line width of 0 seems to force the default of 1.
              // Doing the conditional here allows the shadow setting to still be 
              // optional even with a lineWidth of 0.
  
              if( lw == 0 )
                  lw = 0.0001;
  
              if (lw > 0 && sw > 0) {
                  // draw shadow in two steps
                  var w = sw / 2;
                  ctx.lineWidth = w;
                  ctx.strokeStyle = "rgba(0,0,0,0.1)";
                  plotPoints(series.datapoints, radius, null, w + w/2, true,
                             series.xaxis, series.yaxis, symbol);
  
                  ctx.strokeStyle = "rgba(0,0,0,0.2)";
                  plotPoints(series.datapoints, radius, null, w/2, true,
                             series.xaxis, series.yaxis, symbol);
              }
  
              ctx.lineWidth = lw;
              ctx.strokeStyle = series.color;
              plotPoints(series.datapoints, radius,
                         getFillStyle(series.points, series.color), 0, false,
                         series.xaxis, series.yaxis, symbol);
              ctx.restore();
          }
  
          function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
              var left, right, bottom, top,
                  drawLeft, drawRight, drawTop, drawBottom,
                  tmp;
  
              // in horizontal mode, we start the bar from the left
              // instead of from the bottom so it appears to be
              // horizontal rather than vertical
              if (horizontal) {
                  drawBottom = drawRight = drawTop = true;
                  drawLeft = false;
                  left = b;
                  right = x;
                  top = y + barLeft;
                  bottom = y + barRight;
  
                  // account for negative bars
                  if (right < left) {
                      tmp = right;
                      right = left;
                      left = tmp;
                      drawLeft = true;
                      drawRight = false;
                  }
              }
              else {
                  drawLeft = drawRight = drawTop = true;
                  drawBottom = false;
                  left = x + barLeft;
                  right = x + barRight;
                  bottom = b;
                  top = y;
  
                  // account for negative bars
                  if (top < bottom) {
                      tmp = top;
                      top = bottom;
                      bottom = tmp;
                      drawBottom = true;
                      drawTop = false;
                  }
              }
  
              // clip
              if (right < axisx.min || left > axisx.max ||
                  top < axisy.min || bottom > axisy.max)
                  return;
  
              if (left < axisx.min) {
                  left = axisx.min;
                  drawLeft = false;
              }
  
              if (right > axisx.max) {
                  right = axisx.max;
                  drawRight = false;
              }
  
              if (bottom < axisy.min) {
                  bottom = axisy.min;
                  drawBottom = false;
              }
  
              if (top > axisy.max) {
                  top = axisy.max;
                  drawTop = false;
              }
  
              left = axisx.p2c(left);
              bottom = axisy.p2c(bottom);
              right = axisx.p2c(right);
              top = axisy.p2c(top);
  
              // fill the bar
              if (fillStyleCallback) {
                  c.fillStyle = fillStyleCallback(bottom, top);
                  c.fillRect(left, top, right - left, bottom - top)
              }
  
              // draw outline
              if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
                  c.beginPath();
  
                  // FIXME: inline moveTo is buggy with excanvas
                  c.moveTo(left, bottom);
                  if (drawLeft)
                      c.lineTo(left, top);
                  else
                      c.moveTo(left, top);
                  if (drawTop)
                      c.lineTo(right, top);
                  else
                      c.moveTo(right, top);
                  if (drawRight)
                      c.lineTo(right, bottom);
                  else
                      c.moveTo(right, bottom);
                  if (drawBottom)
                      c.lineTo(left, bottom);
                  else
                      c.moveTo(left, bottom);
                  c.stroke();
              }
          }
  
          function drawSeriesBars(series) {
              function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) {
                  var points = datapoints.points, ps = datapoints.pointsize;
  
                  for (var i = 0; i < points.length; i += ps) {
                      if (points[i] == null)
                          continue;
                      drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);
                  }
              }
  
              ctx.save();
              ctx.translate(plotOffset.left, plotOffset.top);
  
              // FIXME: figure out a way to add shadows (for instance along the right edge)
              ctx.lineWidth = series.bars.lineWidth;
              ctx.strokeStyle = series.color;
  
              var barLeft;
  
              switch (series.bars.align) {
                  case "left":
                      barLeft = 0;
                      break;
                  case "right":
                      barLeft = -series.bars.barWidth;
                      break;
                  default:
                      barLeft = -series.bars.barWidth / 2;
              }
  
              var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
              plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, fillStyleCallback, series.xaxis, series.yaxis);
              ctx.restore();
          }
  
          function getFillStyle(filloptions, seriesColor, bottom, top) {
              var fill = filloptions.fill;
              if (!fill)
                  return null;
  
              if (filloptions.fillColor)
                  return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
  
              var c = $.color.parse(seriesColor);
              c.a = typeof fill == "number" ? fill : 0.4;
              c.normalize();
              return c.toString();
          }
  
          function insertLegend() {
  
              if (options.legend.container != null) {
                  $(options.legend.container).html("");
              } else {
                  placeholder.find(".legend").remove();
              }
  
              if (!options.legend.show) {
                  return;
              }
  
              var fragments = [], entries = [], rowStarted = false,
                  lf = options.legend.labelFormatter, s, label;
  
              // Build a list of legend entries, with each having a label and a color
  
              for (var i = 0; i < series.length; ++i) {
                  s = series[i];
                  if (s.label) {
                      label = lf ? lf(s.label, s) : s.label;
                      if (label) {
                          entries.push({
                              label: label,
                              color: s.color
                          });
                      }
                  }
              }
  
              // Sort the legend using either the default or a custom comparator
  
              if (options.legend.sorted) {
                  if ($.isFunction(options.legend.sorted)) {
                      entries.sort(options.legend.sorted);
                  } else if (options.legend.sorted == "reverse") {
                  	entries.reverse();
                  } else {
                      var ascending = options.legend.sorted != "descending";
                      entries.sort(function(a, b) {
                          return a.label == b.label ? 0 : (
                              (a.label < b.label) != ascending ? 1 : -1   // Logical XOR
                          );
                      });
                  }
              }
  
              // Generate markup for the list of entries, in their final order
  
              for (var i = 0; i < entries.length; ++i) {
  
                  var entry = entries[i];
  
                  if (i % options.legend.noColumns == 0) {
                      if (rowStarted)
                          fragments.push('</tr>');
                      fragments.push('<tr>');
                      rowStarted = true;
                  }
  
                  fragments.push(
                      '<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + entry.color + ';overflow:hidden"></div></div></td>' +
                      '<td class="legendLabel">' + entry.label + '</td>'
                  );
              }
  
              if (rowStarted)
                  fragments.push('</tr>');
  
              if (fragments.length == 0)
                  return;
  
              var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>';
              if (options.legend.container != null)
                  $(options.legend.container).html(table);
              else {
                  var pos = "",
                      p = options.legend.position,
                      m = options.legend.margin;
                  if (m[0] == null)
                      m = [m, m];
                  if (p.charAt(0) == "n")
                      pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
                  else if (p.charAt(0) == "s")
                      pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
                  if (p.charAt(1) == "e")
                      pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
                  else if (p.charAt(1) == "w")
                      pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
                  var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(placeholder);
                  if (options.legend.backgroundOpacity != 0.0) {
                      // put in the transparent background
                      // separately to avoid blended labels and
                      // label boxes
                      var c = options.legend.backgroundColor;
                      if (c == null) {
                          c = options.grid.backgroundColor;
                          if (c && typeof c == "string")
                              c = $.color.parse(c);
                          else
                              c = $.color.extract(legend, 'background-color');
                          c.a = 1;
                          c = c.toString();
                      }
                      var div = legend.children();
                      $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
                  }
              }
          }
  
  
          // interactive features
  
          var highlights = [],
              redrawTimeout = null;
  
          // returns the data item the mouse is over, or null if none is found
          function findNearbyItem(mouseX, mouseY, seriesFilter) {
              var maxDistance = options.grid.mouseActiveRadius,
                  smallestDistance = maxDistance * maxDistance + 1,
                  item = null, foundPoint = false, i, j, ps;
  
              for (i = series.length - 1; i >= 0; --i) {
                  if (!seriesFilter(series[i]))
                      continue;
  
                  var s = series[i],
                      axisx = s.xaxis,
                      axisy = s.yaxis,
                      points = s.datapoints.points,
                      mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
                      my = axisy.c2p(mouseY),
                      maxx = maxDistance / axisx.scale,
                      maxy = maxDistance / axisy.scale;
  
                  ps = s.datapoints.pointsize;
                  // with inverse transforms, we can't use the maxx/maxy
                  // optimization, sadly
                  if (axisx.options.inverseTransform)
                      maxx = Number.MAX_VALUE;
                  if (axisy.options.inverseTransform)
                      maxy = Number.MAX_VALUE;
  
                  if (s.lines.show || s.points.show) {
                      for (j = 0; j < points.length; j += ps) {
                          var x = points[j], y = points[j + 1];
                          if (x == null)
                              continue;
  
                          // For points and lines, the cursor must be within a
                          // certain distance to the data point
                          if (x - mx > maxx || x - mx < -maxx ||
                              y - my > maxy || y - my < -maxy)
                              continue;
  
                          // We have to calculate distances in pixels, not in
                          // data units, because the scales of the axes may be different
                          var dx = Math.abs(axisx.p2c(x) - mouseX),
                              dy = Math.abs(axisy.p2c(y) - mouseY),
                              dist = dx * dx + dy * dy; // we save the sqrt
  
                          // use <= to ensure last point takes precedence
                          // (last generally means on top of)
                          if (dist < smallestDistance) {
                              smallestDistance = dist;
                              item = [i, j / ps];
                          }
                      }
                  }
  
                  if (s.bars.show && !item) { // no other point can be nearby
  
                      var barLeft, barRight;
  
                      switch (s.bars.align) {
                          case "left":
                              barLeft = 0;
                              break;
                          case "right":
                              barLeft = -s.bars.barWidth;
                              break;
                          default:
                              barLeft = -s.bars.barWidth / 2;
                      }
  
                      barRight = barLeft + s.bars.barWidth;
  
                      for (j = 0; j < points.length; j += ps) {
                          var x = points[j], y = points[j + 1], b = points[j + 2];
                          if (x == null)
                              continue;
  
                          // for a bar graph, the cursor must be inside the bar
                          if (series[i].bars.horizontal ?
                              (mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
                               my >= y + barLeft && my <= y + barRight) :
                              (mx >= x + barLeft && mx <= x + barRight &&
                               my >= Math.min(b, y) && my <= Math.max(b, y)))
                                  item = [i, j / ps];
                      }
                  }
              }
  
              if (item) {
                  i = item[0];
                  j = item[1];
                  ps = series[i].datapoints.pointsize;
  
                  return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
                           dataIndex: j,
                           series: series[i],
                           seriesIndex: i };
              }
  
              return null;
          }
  
          function onMouseMove(e) {
              if (options.grid.hoverable)
                  triggerClickHoverEvent("plothover", e,
                                         function (s) { return s["hoverable"] != false; });
          }
  
          function onMouseLeave(e) {
              if (options.grid.hoverable)
                  triggerClickHoverEvent("plothover", e,
                                         function (s) { return false; });
          }
  
          function onClick(e) {
              triggerClickHoverEvent("plotclick", e,
                                     function (s) { return s["clickable"] != false; });
          }
  
          // trigger click or hover event (they send the same parameters
          // so we share their code)
          function triggerClickHoverEvent(eventname, event, seriesFilter) {
              var offset = eventHolder.offset(),
                  canvasX = event.pageX - offset.left - plotOffset.left,
                  canvasY = event.pageY - offset.top - plotOffset.top,
              pos = canvasToAxisCoords({ left: canvasX, top: canvasY });
  
              pos.pageX = event.pageX;
              pos.pageY = event.pageY;
  
              var item = findNearbyItem(canvasX, canvasY, seriesFilter);
  
              if (item) {
                  // fill in mouse pos for any listeners out there
                  item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10);
                  item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10);
              }
  
              if (options.grid.autoHighlight) {
                  // clear auto-highlights
                  for (var i = 0; i < highlights.length; ++i) {
                      var h = highlights[i];
                      if (h.auto == eventname &&
                          !(item && h.series == item.series &&
                            h.point[0] == item.datapoint[0] &&
                            h.point[1] == item.datapoint[1]))
                          unhighlight(h.series, h.point);
                  }
  
                  if (item)
                      highlight(item.series, item.datapoint, eventname);
              }
  
              placeholder.trigger(eventname, [ pos, item ]);
          }
  
          function triggerRedrawOverlay() {
              var t = options.interaction.redrawOverlayInterval;
              if (t == -1) {      // skip event queue
                  drawOverlay();
                  return;
              }
  
              if (!redrawTimeout)
                  redrawTimeout = setTimeout(drawOverlay, t);
          }
  
          function drawOverlay() {
              redrawTimeout = null;
  
              // draw highlights
              octx.save();
              overlay.clear();
              octx.translate(plotOffset.left, plotOffset.top);
  
              var i, hi;
              for (i = 0; i < highlights.length; ++i) {
                  hi = highlights[i];
  
                  if (hi.series.bars.show)
                      drawBarHighlight(hi.series, hi.point);
                  else
                      drawPointHighlight(hi.series, hi.point);
              }
              octx.restore();
  
              executeHooks(hooks.drawOverlay, [octx]);
          }
  
          function highlight(s, point, auto) {
              if (typeof s == "number")
                  s = series[s];
  
              if (typeof point == "number") {
                  var ps = s.datapoints.pointsize;
                  point = s.datapoints.points.slice(ps * point, ps * (point + 1));
              }
  
              var i = indexOfHighlight(s, point);
              if (i == -1) {
                  highlights.push({ series: s, point: point, auto: auto });
  
                  triggerRedrawOverlay();
              }
              else if (!auto)
                  highlights[i].auto = false;
          }
  
          function unhighlight(s, point) {
              if (s == null && point == null) {
                  highlights = [];
                  triggerRedrawOverlay();
                  return;
              }
  
              if (typeof s == "number")
                  s = series[s];
  
              if (typeof point == "number") {
                  var ps = s.datapoints.pointsize;
                  point = s.datapoints.points.slice(ps * point, ps * (point + 1));
              }
  
              var i = indexOfHighlight(s, point);
              if (i != -1) {
                  highlights.splice(i, 1);
  
                  triggerRedrawOverlay();
              }
          }
  
          function indexOfHighlight(s, p) {
              for (var i = 0; i < highlights.length; ++i) {
                  var h = highlights[i];
                  if (h.series == s && h.point[0] == p[0]
                      && h.point[1] == p[1])
                      return i;
              }
              return -1;
          }
  
          function drawPointHighlight(series, point) {
              var x = point[0], y = point[1],
                  axisx = series.xaxis, axisy = series.yaxis,
                  highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString();
  
              if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
                  return;
  
              var pointRadius = series.points.radius + series.points.lineWidth / 2;
              octx.lineWidth = pointRadius;
              octx.strokeStyle = highlightColor;
              var radius = 1.5 * pointRadius;
              x = axisx.p2c(x);
              y = axisy.p2c(y);
  
              octx.beginPath();
              if (series.points.symbol == "circle")
                  octx.arc(x, y, radius, 0, 2 * Math.PI, false);
              else
                  series.points.symbol(octx, x, y, radius, false);
              octx.closePath();
              octx.stroke();
          }
  
          function drawBarHighlight(series, point) {
              var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(),
                  fillStyle = highlightColor,
                  barLeft;
  
              switch (series.bars.align) {
                  case "left":
                      barLeft = 0;
                      break;
                  case "right":
                      barLeft = -series.bars.barWidth;
                      break;
                  default:
                      barLeft = -series.bars.barWidth / 2;
              }
  
              octx.lineWidth = series.bars.lineWidth;
              octx.strokeStyle = highlightColor;
  
              drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
                      function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);
          }
  
          function getColorOrGradient(spec, bottom, top, defaultColor) {
              if (typeof spec == "string")
                  return spec;
              else {
                  // assume this is a gradient spec; IE currently only
                  // supports a simple vertical gradient properly, so that's
                  // what we support too
                  var gradient = ctx.createLinearGradient(0, top, 0, bottom);
  
                  for (var i = 0, l = spec.colors.length; i < l; ++i) {
                      var c = spec.colors[i];
                      if (typeof c != "string") {
                          var co = $.color.parse(defaultColor);
                          if (c.brightness != null)
                              co = co.scale('rgb', c.brightness);
                          if (c.opacity != null)
                              co.a *= c.opacity;
                          c = co.toString();
                      }
                      gradient.addColorStop(i / (l - 1), c);
                  }
  
                  return gradient;
              }
          }
      }
  
      // Add the plot function to the top level of the jQuery object
  
      $.plot = function(placeholder, data, options) {
          //var t0 = new Date();
          var plot = new Plot($(placeholder), data, options, $.plot.plugins);
          //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime()));
          return plot;
      };
  
      $.plot.version = "0.8.3";
  
      $.plot.plugins = [];
  
      // Also add the plot function as a chainable property
  
      $.fn.plot = function(data, options) {
          return this.each(function() {
              $.plot(this, data, options);
          });
      };
  
      // round to nearby lower multiple of base
      function floorInBase(n, base) {
          return base * Math.floor(n / base);
      }
  
  })(jQuery);
  
  /* Pretty handling of time axes.
  
  Copyright (c) 2007-2014 IOLA and Ole Laursen.
  Licensed under the MIT license.
  
  Set axis.mode to "time" to enable. See the section "Time series data" in
  API.txt for details.
  
  */
  
  (function($) {
  
  	var options = {
  		xaxis: {
  			timezone: null,		// "browser" for local to the client or timezone for timezone-js
  			timeformat: null,	// format string to use
  			twelveHourClock: false,	// 12 or 24 time in time mode
  			monthNames: null	// list of names of months
  		}
  	};
  
  	// round to nearby lower multiple of base
  
  	function floorInBase(n, base) {
  		return base * Math.floor(n / base);
  	}
  
  	// Returns a string with the date d formatted according to fmt.
  	// A subset of the Open Group's strftime format is supported.
  
  	function formatDate(d, fmt, monthNames, dayNames) {
  
  		if (typeof d.strftime == "function") {
  			return d.strftime(fmt);
  		}
  
  		var leftPad = function(n, pad) {
  			n = "" + n;
  			pad = "" + (pad == null ? "0" : pad);
  			return n.length == 1 ? pad + n : n;
  		};
  
  		var r = [];
  		var escape = false;
  		var hours = d.getHours();
  		var isAM = hours < 12;
  
  		if (monthNames == null) {
  			monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
  		}
  
  		if (dayNames == null) {
  			dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
  		}
  
  		var hours12;
  
  		if (hours > 12) {
  			hours12 = hours - 12;
  		} else if (hours == 0) {
  			hours12 = 12;
  		} else {
  			hours12 = hours;
  		}
  
  		for (var i = 0; i < fmt.length; ++i) {
  
  			var c = fmt.charAt(i);
  
  			if (escape) {
  				switch (c) {
  					case 'a': c = "" + dayNames[d.getDay()]; break;
  					case 'b': c = "" + monthNames[d.getMonth()]; break;
  					case 'd': c = leftPad(d.getDate()); break;
  					case 'e': c = leftPad(d.getDate(), " "); break;
  					case 'h':	// For back-compat with 0.7; remove in 1.0
  					case 'H': c = leftPad(hours); break;
  					case 'I': c = leftPad(hours12); break;
  					case 'l': c = leftPad(hours12, " "); break;
  					case 'm': c = leftPad(d.getMonth() + 1); break;
  					case 'M': c = leftPad(d.getMinutes()); break;
  					// quarters not in Open Group's strftime specification
  					case 'q':
  						c = "" + (Math.floor(d.getMonth() / 3) + 1); break;
  					case 'S': c = leftPad(d.getSeconds()); break;
  					case 'y': c = leftPad(d.getFullYear() % 100); break;
  					case 'Y': c = "" + d.getFullYear(); break;
  					case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
  					case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
  					case 'w': c = "" + d.getDay(); break;
  				}
  				r.push(c);
  				escape = false;
  			} else {
  				if (c == "%") {
  					escape = true;
  				} else {
  					r.push(c);
  				}
  			}
  		}
  
  		return r.join("");
  	}
  
  	// To have a consistent view of time-based data independent of which time
  	// zone the client happens to be in we need a date-like object independent
  	// of time zones.  This is done through a wrapper that only calls the UTC
  	// versions of the accessor methods.
  
  	function makeUtcWrapper(d) {
  
  		function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) {
  			sourceObj[sourceMethod] = function() {
  				return targetObj[targetMethod].apply(targetObj, arguments);
  			};
  		};
  
  		var utc = {
  			date: d
  		};
  
  		// support strftime, if found
  
  		if (d.strftime != undefined) {
  			addProxyMethod(utc, "strftime", d, "strftime");
  		}
  
  		addProxyMethod(utc, "getTime", d, "getTime");
  		addProxyMethod(utc, "setTime", d, "setTime");
  
  		var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"];
  
  		for (var p = 0; p < props.length; p++) {
  			addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]);
  			addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]);
  		}
  
  		return utc;
  	};
  
  	// select time zone strategy.  This returns a date-like object tied to the
  	// desired timezone
  
  	function dateGenerator(ts, opts) {
  		if (opts.timezone == "browser") {
  			return new Date(ts);
  		} else if (!opts.timezone || opts.timezone == "utc") {
  			return makeUtcWrapper(new Date(ts));
  		} else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") {
  			var d = new timezoneJS.Date();
  			// timezone-js is fickle, so be sure to set the time zone before
  			// setting the time.
  			d.setTimezone(opts.timezone);
  			d.setTime(ts);
  			return d;
  		} else {
  			return makeUtcWrapper(new Date(ts));
  		}
  	}
  	
  	// map of app. size of time units in milliseconds
  
  	var timeUnitSize = {
  		"second": 1000,
  		"minute": 60 * 1000,
  		"hour": 60 * 60 * 1000,
  		"day": 24 * 60 * 60 * 1000,
  		"month": 30 * 24 * 60 * 60 * 1000,
  		"quarter": 3 * 30 * 24 * 60 * 60 * 1000,
  		"year": 365.2425 * 24 * 60 * 60 * 1000
  	};
  
  	// the allowed tick sizes, after 1 year we use
  	// an integer algorithm
  
  	var baseSpec = [
  		[1, "second"], [2, "second"], [5, "second"], [10, "second"],
  		[30, "second"], 
  		[1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
  		[30, "minute"], 
  		[1, "hour"], [2, "hour"], [4, "hour"],
  		[8, "hour"], [12, "hour"],
  		[1, "day"], [2, "day"], [3, "day"],
  		[0.25, "month"], [0.5, "month"], [1, "month"],
  		[2, "month"]
  	];
  
  	// we don't know which variant(s) we'll need yet, but generating both is
  	// cheap
  
  	var specMonths = baseSpec.concat([[3, "month"], [6, "month"],
  		[1, "year"]]);
  	var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"],
  		[1, "year"]]);
  
  	function init(plot) {
  		plot.hooks.processOptions.push(function (plot, options) {
  			$.each(plot.getAxes(), function(axisName, axis) {
  
  				var opts = axis.options;
  
  				if (opts.mode == "time") {
  					axis.tickGenerator = function(axis) {
  
  						var ticks = [];
  						var d = dateGenerator(axis.min, opts);
  						var minSize = 0;
  
  						// make quarter use a possibility if quarters are
  						// mentioned in either of these options
  
  						var spec = (opts.tickSize && opts.tickSize[1] ===
  							"quarter") ||
  							(opts.minTickSize && opts.minTickSize[1] ===
  							"quarter") ? specQuarters : specMonths;
  
  						if (opts.minTickSize != null) {
  							if (typeof opts.tickSize == "number") {
  								minSize = opts.tickSize;
  							} else {
  								minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
  							}
  						}
  
  						for (var i = 0; i < spec.length - 1; ++i) {
  							if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]]
  											  + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
  								&& spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) {
  								break;
  							}
  						}
  
  						var size = spec[i][0];
  						var unit = spec[i][1];
  
  						// special-case the possibility of several years
  
  						if (unit == "year") {
  
  							// if given a minTickSize in years, just use it,
  							// ensuring that it's an integer
  
  							if (opts.minTickSize != null && opts.minTickSize[1] == "year") {
  								size = Math.floor(opts.minTickSize[0]);
  							} else {
  
  								var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10));
  								var norm = (axis.delta / timeUnitSize.year) / magn;
  
  								if (norm < 1.5) {
  									size = 1;
  								} else if (norm < 3) {
  									size = 2;
  								} else if (norm < 7.5) {
  									size = 5;
  								} else {
  									size = 10;
  								}
  
  								size *= magn;
  							}
  
  							// minimum size for years is 1
  
  							if (size < 1) {
  								size = 1;
  							}
  						}
  
  						axis.tickSize = opts.tickSize || [size, unit];
  						var tickSize = axis.tickSize[0];
  						unit = axis.tickSize[1];
  
  						var step = tickSize * timeUnitSize[unit];
  
  						if (unit == "second") {
  							d.setSeconds(floorInBase(d.getSeconds(), tickSize));
  						} else if (unit == "minute") {
  							d.setMinutes(floorInBase(d.getMinutes(), tickSize));
  						} else if (unit == "hour") {
  							d.setHours(floorInBase(d.getHours(), tickSize));
  						} else if (unit == "month") {
  							d.setMonth(floorInBase(d.getMonth(), tickSize));
  						} else if (unit == "quarter") {
  							d.setMonth(3 * floorInBase(d.getMonth() / 3,
  								tickSize));
  						} else if (unit == "year") {
  							d.setFullYear(floorInBase(d.getFullYear(), tickSize));
  						}
  
  						// reset smaller components
  
  						d.setMilliseconds(0);
  
  						if (step >= timeUnitSize.minute) {
  							d.setSeconds(0);
  						}
  						if (step >= timeUnitSize.hour) {
  							d.setMinutes(0);
  						}
  						if (step >= timeUnitSize.day) {
  							d.setHours(0);
  						}
  						if (step >= timeUnitSize.day * 4) {
  							d.setDate(1);
  						}
  						if (step >= timeUnitSize.month * 2) {
  							d.setMonth(floorInBase(d.getMonth(), 3));
  						}
  						if (step >= timeUnitSize.quarter * 2) {
  							d.setMonth(floorInBase(d.getMonth(), 6));
  						}
  						if (step >= timeUnitSize.year) {
  							d.setMonth(0);
  						}
  
  						var carry = 0;
  						var v = Number.NaN;
  						var prev;
  
  						do {
  
  							prev = v;
  							v = d.getTime();
  							ticks.push(v);
  
  							if (unit == "month" || unit == "quarter") {
  								if (tickSize < 1) {
  
  									// a bit complicated - we'll divide the
  									// month/quarter up but we need to take
  									// care of fractions so we don't end up in
  									// the middle of a day
  
  									d.setDate(1);
  									var start = d.getTime();
  									d.setMonth(d.getMonth() +
  										(unit == "quarter" ? 3 : 1));
  									var end = d.getTime();
  									d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
  									carry = d.getHours();
  									d.setHours(0);
  								} else {
  									d.setMonth(d.getMonth() +
  										tickSize * (unit == "quarter" ? 3 : 1));
  								}
  							} else if (unit == "year") {
  								d.setFullYear(d.getFullYear() + tickSize);
  							} else {
  								d.setTime(v + step);
  							}
  						} while (v < axis.max && v != prev);
  
  						return ticks;
  					};
  
  					axis.tickFormatter = function (v, axis) {
  
  						var d = dateGenerator(v, axis.options);
  
  						// first check global format
  
  						if (opts.timeformat != null) {
  							return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames);
  						}
  
  						// possibly use quarters if quarters are mentioned in
  						// any of these places
  
  						var useQuarters = (axis.options.tickSize &&
  								axis.options.tickSize[1] == "quarter") ||
  							(axis.options.minTickSize &&
  								axis.options.minTickSize[1] == "quarter");
  
  						var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
  						var span = axis.max - axis.min;
  						var suffix = (opts.twelveHourClock) ? " %p" : "";
  						var hourCode = (opts.twelveHourClock) ? "%I" : "%H";
  						var fmt;
  
  						if (t < timeUnitSize.minute) {
  							fmt = hourCode + ":%M:%S" + suffix;
  						} else if (t < timeUnitSize.day) {
  							if (span < 2 * timeUnitSize.day) {
  								fmt = hourCode + ":%M" + suffix;
  							} else {
  								fmt = "%b %d " + hourCode + ":%M" + suffix;
  							}
  						} else if (t < timeUnitSize.month) {
  							fmt = "%b %d";
  						} else if ((useQuarters && t < timeUnitSize.quarter) ||
  							(!useQuarters && t < timeUnitSize.year)) {
  							if (span < timeUnitSize.year) {
  								fmt = "%b";
  							} else {
  								fmt = "%b %Y";
  							}
  						} else if (useQuarters && t < timeUnitSize.year) {
  							if (span < timeUnitSize.year) {
  								fmt = "Q%q";
  							} else {
  								fmt = "Q%q %Y";
  							}
  						} else {
  							fmt = "%Y";
  						}
  
  						var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames);
  
  						return rt;
  					};
  				}
  			});
  		});
  	}
  
  	$.plot.plugins.push({
  		init: init,
  		options: options,
  		name: 'time',
  		version: '1.0'
  	});
  
  	// Time-axis support used to be in Flot core, which exposed the
  	// formatDate function on the plot object.  Various plugins depend
  	// on the function, so we need to re-expose it here.
  
  	$.plot.formatDate = formatDate;
  	$.plot.dateGenerator = dateGenerator;
  
  })(jQuery);
  
  /* Flot plugin for rendering pie charts.
  
  Copyright (c) 2007-2014 IOLA and Ole Laursen.
  Licensed under the MIT license.
  
  The plugin assumes that each series has a single data value, and that each
  value is a positive integer or zero.  Negative numbers don't make sense for a
  pie chart, and have unpredictable results.  The values do NOT need to be
  passed in as percentages; the plugin will calculate the total and per-slice
  percentages internally.
  
  * Created by Brian Medendorp
  
  * Updated with contributions from btburnett3, Anthony Aragues and Xavi Ivars
  
  The plugin supports these options:
  
  	series: {
  		pie: {
  			show: true/false
  			radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto'
  			innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect
  			startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result
  			tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show)
  			offset: {
  				top: integer value to move the pie up or down
  				left: integer value to move the pie left or right, or 'auto'
  			},
  			stroke: {
  				color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF')
  				width: integer pixel width of the stroke
  			},
  			label: {
  				show: true/false, or 'auto'
  				formatter:  a user-defined function that modifies the text/style of the label text
  				radius: 0-1 for percentage of fullsize, or a specified pixel length
  				background: {
  					color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000')
  					opacity: 0-1
  				},
  				threshold: 0-1 for the percentage value at which to hide labels (if they're too small)
  			},
  			combine: {
  				threshold: 0-1 for the percentage value at which to combine slices (if they're too small)
  				color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined
  				label: any text value of what the combined slice should be labeled
  			}
  			highlight: {
  				opacity: 0-1
  			}
  		}
  	}
  
  More detail and specific examples can be found in the included HTML file.
  
  */
  
  (function($) {
  
  	// Maximum redraw attempts when fitting labels within the plot
  
  	var REDRAW_ATTEMPTS = 10;
  
  	// Factor by which to shrink the pie when fitting labels within the plot
  
  	var REDRAW_SHRINK = 0.95;
  
  	function init(plot) {
  
  		var canvas = null,
  			target = null,
  			options = null,
  			maxRadius = null,
  			centerLeft = null,
  			centerTop = null,
  			processed = false,
  			ctx = null;
  
  		// interactive variables
  
  		var highlights = [];
  
  		// add hook to determine if pie plugin in enabled, and then perform necessary operations
  
  		plot.hooks.processOptions.push(function(plot, options) {
  			if (options.series.pie.show) {
  
  				options.grid.show = false;
  
  				// set labels.show
  
  				if (options.series.pie.label.show == "auto") {
  					if (options.legend.show) {
  						options.series.pie.label.show = false;
  					} else {
  						options.series.pie.label.show = true;
  					}
  				}
  
  				// set radius
  
  				if (options.series.pie.radius == "auto") {
  					if (options.series.pie.label.show) {
  						options.series.pie.radius = 3/4;
  					} else {
  						options.series.pie.radius = 1;
  					}
  				}
  
  				// ensure sane tilt
  
  				if (options.series.pie.tilt > 1) {
  					options.series.pie.tilt = 1;
  				} else if (options.series.pie.tilt < 0) {
  					options.series.pie.tilt = 0;
  				}
  			}
  		});
  
  		plot.hooks.bindEvents.push(function(plot, eventHolder) {
  			var options = plot.getOptions();
  			if (options.series.pie.show) {
  				if (options.grid.hoverable) {
  					eventHolder.unbind("mousemove").mousemove(onMouseMove);
  				}
  				if (options.grid.clickable) {
  					eventHolder.unbind("click").click(onClick);
  				}
  			}
  		});
  
  		plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) {
  			var options = plot.getOptions();
  			if (options.series.pie.show) {
  				processDatapoints(plot, series, data, datapoints);
  			}
  		});
  
  		plot.hooks.drawOverlay.push(function(plot, octx) {
  			var options = plot.getOptions();
  			if (options.series.pie.show) {
  				drawOverlay(plot, octx);
  			}
  		});
  
  		plot.hooks.draw.push(function(plot, newCtx) {
  			var options = plot.getOptions();
  			if (options.series.pie.show) {
  				draw(plot, newCtx);
  			}
  		});
  
  		function processDatapoints(plot, series, datapoints) {
  			if (!processed)	{
  				processed = true;
  				canvas = plot.getCanvas();
  				target = $(canvas).parent();
  				options = plot.getOptions();
  				plot.setData(combine(plot.getData()));
  			}
  		}
  
  		function combine(data) {
  
  			var total = 0,
  				combined = 0,
  				numCombined = 0,
  				color = options.series.pie.combine.color,
  				newdata = [];
  
  			// Fix up the raw data from Flot, ensuring the data is numeric
  
  			for (var i = 0; i < data.length; ++i) {
  
  				var value = data[i].data;
  
  				// If the data is an array, we'll assume that it's a standard
  				// Flot x-y pair, and are concerned only with the second value.
  
  				// Note how we use the original array, rather than creating a
  				// new one; this is more efficient and preserves any extra data
  				// that the user may have stored in higher indexes.
  
  				if ($.isArray(value) && value.length == 1) {
      				value = value[0];
  				}
  
  				if ($.isArray(value)) {
  					// Equivalent to $.isNumeric() but compatible with jQuery < 1.7
  					if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) {
  						value[1] = +value[1];
  					} else {
  						value[1] = 0;
  					}
  				} else if (!isNaN(parseFloat(value)) && isFinite(value)) {
  					value = [1, +value];
  				} else {
  					value = [1, 0];
  				}
  
  				data[i].data = [value];
  			}
  
  			// Sum up all the slices, so we can calculate percentages for each
  
  			for (var i = 0; i < data.length; ++i) {
  				total += data[i].data[0][1];
  			}
  
  			// Count the number of slices with percentages below the combine
  			// threshold; if it turns out to be just one, we won't combine.
  
  			for (var i = 0; i < data.length; ++i) {
  				var value = data[i].data[0][1];
  				if (value / total <= options.series.pie.combine.threshold) {
  					combined += value;
  					numCombined++;
  					if (!color) {
  						color = data[i].color;
  					}
  				}
  			}
  
  			for (var i = 0; i < data.length; ++i) {
  				var value = data[i].data[0][1];
  				if (numCombined < 2 || value / total > options.series.pie.combine.threshold) {
  					newdata.push(
  						$.extend(data[i], {     /* extend to allow keeping all other original data values
  						                           and using them e.g. in labelFormatter. */
  							data: [[1, value]],
  							color: data[i].color,
  							label: data[i].label,
  							angle: value * Math.PI * 2 / total,
  							percent: value / (total / 100)
  						})
  					);
  				}
  			}
  
  			if (numCombined > 1) {
  				newdata.push({
  					data: [[1, combined]],
  					color: color,
  					label: options.series.pie.combine.label,
  					angle: combined * Math.PI * 2 / total,
  					percent: combined / (total / 100)
  				});
  			}
  
  			return newdata;
  		}
  
  		function draw(plot, newCtx) {
  
  			if (!target) {
  				return; // if no series were passed
  			}
  
  			var canvasWidth = plot.getPlaceholder().width(),
  				canvasHeight = plot.getPlaceholder().height(),
  				legendWidth = target.children().filter(".legend").children().width() || 0;
  
  			ctx = newCtx;
  
  			// WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE!
  
  			// When combining smaller slices into an 'other' slice, we need to
  			// add a new series.  Since Flot gives plugins no way to modify the
  			// list of series, the pie plugin uses a hack where the first call
  			// to processDatapoints results in a call to setData with the new
  			// list of series, then subsequent processDatapoints do nothing.
  
  			// The plugin-global 'processed' flag is used to control this hack;
  			// it starts out false, and is set to true after the first call to
  			// processDatapoints.
  
  			// Unfortunately this turns future setData calls into no-ops; they
  			// call processDatapoints, the flag is true, and nothing happens.
  
  			// To fix this we'll set the flag back to false here in draw, when
  			// all series have been processed, so the next sequence of calls to
  			// processDatapoints once again starts out with a slice-combine.
  			// This is really a hack; in 0.9 we need to give plugins a proper
  			// way to modify series before any processing begins.
  
  			processed = false;
  
  			// calculate maximum radius and center point
  
  			maxRadius =  Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2;
  			centerTop = canvasHeight / 2 + options.series.pie.offset.top;
  			centerLeft = canvasWidth / 2;
  
  			if (options.series.pie.offset.left == "auto") {
  				if (options.legend.position.match("w")) {
  					centerLeft += legendWidth / 2;
  				} else {
  					centerLeft -= legendWidth / 2;
  				}
  				if (centerLeft < maxRadius) {
  					centerLeft = maxRadius;
  				} else if (centerLeft > canvasWidth - maxRadius) {
  					centerLeft = canvasWidth - maxRadius;
  				}
  			} else {
  				centerLeft += options.series.pie.offset.left;
  			}
  
  			var slices = plot.getData(),
  				attempts = 0;
  
  			// Keep shrinking the pie's radius until drawPie returns true,
  			// indicating that all the labels fit, or we try too many times.
  
  			do {
  				if (attempts > 0) {
  					maxRadius *= REDRAW_SHRINK;
  				}
  				attempts += 1;
  				clear();
  				if (options.series.pie.tilt <= 0.8) {
  					drawShadow();
  				}
  			} while (!drawPie() && attempts < REDRAW_ATTEMPTS)
  
  			if (attempts >= REDRAW_ATTEMPTS) {
  				clear();
  				target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>");
  			}
  
  			if (plot.setSeries && plot.insertLegend) {
  				plot.setSeries(slices);
  				plot.insertLegend();
  			}
  
  			// we're actually done at this point, just defining internal functions at this point
  
  			function clear() {
  				ctx.clearRect(0, 0, canvasWidth, canvasHeight);
  				target.children().filter(".pieLabel, .pieLabelBackground").remove();
  			}
  
  			function drawShadow() {
  
  				var shadowLeft = options.series.pie.shadow.left;
  				var shadowTop = options.series.pie.shadow.top;
  				var edge = 10;
  				var alpha = options.series.pie.shadow.alpha;
  				var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
  
  				if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) {
  					return;	// shadow would be outside canvas, so don't draw it
  				}
  
  				ctx.save();
  				ctx.translate(shadowLeft,shadowTop);
  				ctx.globalAlpha = alpha;
  				ctx.fillStyle = "#000";
  
  				// center and rotate to starting position
  
  				ctx.translate(centerLeft,centerTop);
  				ctx.scale(1, options.series.pie.tilt);
  
  				//radius -= edge;
  
  				for (var i = 1; i <= edge; i++) {
  					ctx.beginPath();
  					ctx.arc(0, 0, radius, 0, Math.PI * 2, false);
  					ctx.fill();
  					radius -= i;
  				}
  
  				ctx.restore();
  			}
  
  			function drawPie() {
  
  				var startAngle = Math.PI * options.series.pie.startAngle;
  				var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
  
  				// center and rotate to starting position
  
  				ctx.save();
  				ctx.translate(centerLeft,centerTop);
  				ctx.scale(1, options.series.pie.tilt);
  				//ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera
  
  				// draw slices
  
  				ctx.save();
  				var currentAngle = startAngle;
  				for (var i = 0; i < slices.length; ++i) {
  					slices[i].startAngle = currentAngle;
  					drawSlice(slices[i].angle, slices[i].color, true);
  				}
  				ctx.restore();
  
  				// draw slice outlines
  
  				if (options.series.pie.stroke.width > 0) {
  					ctx.save();
  					ctx.lineWidth = options.series.pie.stroke.width;
  					currentAngle = startAngle;
  					for (var i = 0; i < slices.length; ++i) {
  						drawSlice(slices[i].angle, options.series.pie.stroke.color, false);
  					}
  					ctx.restore();
  				}
  
  				// draw donut hole
  
  				drawDonutHole(ctx);
  
  				ctx.restore();
  
  				// Draw the labels, returning true if they fit within the plot
  
  				if (options.series.pie.label.show) {
  					return drawLabels();
  				} else return true;
  
  				function drawSlice(angle, color, fill) {
  
  					if (angle <= 0 || isNaN(angle)) {
  						return;
  					}
  
  					if (fill) {
  						ctx.fillStyle = color;
  					} else {
  						ctx.strokeStyle = color;
  						ctx.lineJoin = "round";
  					}
  
  					ctx.beginPath();
  					if (Math.abs(angle - Math.PI * 2) > 0.000000001) {
  						ctx.moveTo(0, 0); // Center of the pie
  					}
  
  					//ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera
  					ctx.arc(0, 0, radius,currentAngle, currentAngle + angle / 2, false);
  					ctx.arc(0, 0, radius,currentAngle + angle / 2, currentAngle + angle, false);
  					ctx.closePath();
  					//ctx.rotate(angle); // This doesn't work properly in Opera
  					currentAngle += angle;
  
  					if (fill) {
  						ctx.fill();
  					} else {
  						ctx.stroke();
  					}
  				}
  
  				function drawLabels() {
  
  					var currentAngle = startAngle;
  					var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius;
  
  					for (var i = 0; i < slices.length; ++i) {
  						if (slices[i].percent >= options.series.pie.label.threshold * 100) {
  							if (!drawLabel(slices[i], currentAngle, i)) {
  								return false;
  							}
  						}
  						currentAngle += slices[i].angle;
  					}
  
  					return true;
  
  					function drawLabel(slice, startAngle, index) {
  
  						if (slice.data[0][1] == 0) {
  							return true;
  						}
  
  						// format label text
  
  						var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter;
  
  						if (lf) {
  							text = lf(slice.label, slice);
  						} else {
  							text = slice.label;
  						}
  
  						if (plf) {
  							text = plf(text, slice);
  						}
  
  						var halfAngle = ((startAngle + slice.angle) + startAngle) / 2;
  						var x = centerLeft + Math.round(Math.cos(halfAngle) * radius);
  						var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt;
  
  						var html = "<span class='pieLabel' id='pieLabel" + index + "' style='position:absolute;top:" + y + "px;left:" + x + "px;'>" + text + "</span>";
  						target.append(html);
  
  						var label = target.children("#pieLabel" + index);
  						var labelTop = (y - label.height() / 2);
  						var labelLeft = (x - label.width() / 2);
  
  						label.css("top", labelTop);
  						label.css("left", labelLeft);
  
  						// check to make sure that the label is not outside the canvas
  
  						if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) {
  							return false;
  						}
  
  						if (options.series.pie.label.background.opacity != 0) {
  
  							// put in the transparent background separately to avoid blended labels and label boxes
  
  							var c = options.series.pie.label.background.color;
  
  							if (c == null) {
  								c = slice.color;
  							}
  
  							var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;";
  							$("<div class='pieLabelBackground' style='position:absolute;width:" + label.width() + "px;height:" + label.height() + "px;" + pos + "background-color:" + c + ";'></div>")
  								.css("opacity", options.series.pie.label.background.opacity)
  								.insertBefore(label);
  						}
  
  						return true;
  					} // end individual label function
  				} // end drawLabels function
  			} // end drawPie function
  		} // end draw function
  
  		// Placed here because it needs to be accessed from multiple locations
  
  		function drawDonutHole(layer) {
  			if (options.series.pie.innerRadius > 0) {
  
  				// subtract the center
  
  				layer.save();
  				var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius;
  				layer.globalCompositeOperation = "destination-out"; // this does not work with excanvas, but it will fall back to using the stroke color
  				layer.beginPath();
  				layer.fillStyle = options.series.pie.stroke.color;
  				layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
  				layer.fill();
  				layer.closePath();
  				layer.restore();
  
  				// add inner stroke
  
  				layer.save();
  				layer.beginPath();
  				layer.strokeStyle = options.series.pie.stroke.color;
  				layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
  				layer.stroke();
  				layer.closePath();
  				layer.restore();
  
  				// TODO: add extra shadow inside hole (with a mask) if the pie is tilted.
  			}
  		}
  
  		//-- Additional Interactive related functions --
  
  		function isPointInPoly(poly, pt) {
  			for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
  				((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1]))
  				&& (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0])
  				&& (c = !c);
  			return c;
  		}
  
  		function findNearbySlice(mouseX, mouseY) {
  
  			var slices = plot.getData(),
  				options = plot.getOptions(),
  				radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius,
  				x, y;
  
  			for (var i = 0; i < slices.length; ++i) {
  
  				var s = slices[i];
  
  				if (s.pie.show) {
  
  					ctx.save();
  					ctx.beginPath();
  					ctx.moveTo(0, 0); // Center of the pie
  					//ctx.scale(1, options.series.pie.tilt);	// this actually seems to break everything when here.
  					ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false);
  					ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false);
  					ctx.closePath();
  					x = mouseX - centerLeft;
  					y = mouseY - centerTop;
  
  					if (ctx.isPointInPath) {
  						if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) {
  							ctx.restore();
  							return {
  								datapoint: [s.percent, s.data],
  								dataIndex: 0,
  								series: s,
  								seriesIndex: i
  							};
  						}
  					} else {
  
  						// excanvas for IE doesn;t support isPointInPath, this is a workaround.
  
  						var p1X = radius * Math.cos(s.startAngle),
  							p1Y = radius * Math.sin(s.startAngle),
  							p2X = radius * Math.cos(s.startAngle + s.angle / 4),
  							p2Y = radius * Math.sin(s.startAngle + s.angle / 4),
  							p3X = radius * Math.cos(s.startAngle + s.angle / 2),
  							p3Y = radius * Math.sin(s.startAngle + s.angle / 2),
  							p4X = radius * Math.cos(s.startAngle + s.angle / 1.5),
  							p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5),
  							p5X = radius * Math.cos(s.startAngle + s.angle),
  							p5Y = radius * Math.sin(s.startAngle + s.angle),
  							arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]],
  							arrPoint = [x, y];
  
  						// TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt?
  
  						if (isPointInPoly(arrPoly, arrPoint)) {
  							ctx.restore();
  							return {
  								datapoint: [s.percent, s.data],
  								dataIndex: 0,
  								series: s,
  								seriesIndex: i
  							};
  						}
  					}
  
  					ctx.restore();
  				}
  			}
  
  			return null;
  		}
  
  		function onMouseMove(e) {
  			triggerClickHoverEvent("plothover", e);
  		}
  
  		function onClick(e) {
  			triggerClickHoverEvent("plotclick", e);
  		}
  
  		// trigger click or hover event (they send the same parameters so we share their code)
  
  		function triggerClickHoverEvent(eventname, e) {
  
  			var offset = plot.offset();
  			var canvasX = parseInt(e.pageX - offset.left);
  			var canvasY =  parseInt(e.pageY - offset.top);
  			var item = findNearbySlice(canvasX, canvasY);
  
  			if (options.grid.autoHighlight) {
  
  				// clear auto-highlights
  
  				for (var i = 0; i < highlights.length; ++i) {
  					var h = highlights[i];
  					if (h.auto == eventname && !(item && h.series == item.series)) {
  						unhighlight(h.series);
  					}
  				}
  			}
  
  			// highlight the slice
  
  			if (item) {
  				highlight(item.series, eventname);
  			}
  
  			// trigger any hover bind events
  
  			var pos = { pageX: e.pageX, pageY: e.pageY };
  			target.trigger(eventname, [pos, item]);
  		}
  
  		function highlight(s, auto) {
  			//if (typeof s == "number") {
  			//	s = series[s];
  			//}
  
  			var i = indexOfHighlight(s);
  
  			if (i == -1) {
  				highlights.push({ series: s, auto: auto });
  				plot.triggerRedrawOverlay();
  			} else if (!auto) {
  				highlights[i].auto = false;
  			}
  		}
  
  		function unhighlight(s) {
  			if (s == null) {
  				highlights = [];
  				plot.triggerRedrawOverlay();
  			}
  
  			//if (typeof s == "number") {
  			//	s = series[s];
  			//}
  
  			var i = indexOfHighlight(s);
  
  			if (i != -1) {
  				highlights.splice(i, 1);
  				plot.triggerRedrawOverlay();
  			}
  		}
  
  		function indexOfHighlight(s) {
  			for (var i = 0; i < highlights.length; ++i) {
  				var h = highlights[i];
  				if (h.series == s)
  					return i;
  			}
  			return -1;
  		}
  
  		function drawOverlay(plot, octx) {
  
  			var options = plot.getOptions();
  
  			var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
  
  			octx.save();
  			octx.translate(centerLeft, centerTop);
  			octx.scale(1, options.series.pie.tilt);
  
  			for (var i = 0; i < highlights.length; ++i) {
  				drawHighlight(highlights[i].series);
  			}
  
  			drawDonutHole(octx);
  
  			octx.restore();
  
  			function drawHighlight(series) {
  
  				if (series.angle <= 0 || isNaN(series.angle)) {
  					return;
  				}
  
  				//octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString();
  				octx.fillStyle = "rgba(255, 255, 255, " + options.series.pie.highlight.opacity + ")"; // this is temporary until we have access to parseColor
  				octx.beginPath();
  				if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) {
  					octx.moveTo(0, 0); // Center of the pie
  				}
  				octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false);
  				octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false);
  				octx.closePath();
  				octx.fill();
  			}
  		}
  	} // end init (plugin body)
  
  	// define pie specific options and their default values
  
  	var options = {
  		series: {
  			pie: {
  				show: false,
  				radius: "auto",	// actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)
  				innerRadius: 0, /* for donut */
  				startAngle: 3/2,
  				tilt: 1,
  				shadow: {
  					left: 5,	// shadow left offset
  					top: 15,	// shadow top offset
  					alpha: 0.02	// shadow alpha
  				},
  				offset: {
  					top: 0,
  					left: "auto"
  				},
  				stroke: {
  					color: "#fff",
  					width: 1
  				},
  				label: {
  					show: "auto",
  					formatter: function(label, slice) {
  						return "<div style='font-size:x-small;text-align:center;padding:2px;color:" + slice.color + ";'>" + label + "<br/>" + Math.round(slice.percent) + "%</div>";
  					},	// formatter function
  					radius: 1,	// radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value)
  					background: {
  						color: null,
  						opacity: 0
  					},
  					threshold: 0	// percentage at which to hide the label (i.e. the slice is too narrow)
  				},
  				combine: {
  					threshold: -1,	// percentage at which to combine little slices into one larger slice
  					color: null,	// color to give the new slice (auto-generated if null)
  					label: "Other"	// label to give the new slice
  				},
  				highlight: {
  					//color: "#fff",		// will add this functionality once parseColor is available
  					opacity: 0.5
  				}
  			}
  		}
  	};
  
  	$.plot.plugins.push({
  		init: init,
  		options: options,
  		name: "pie",
  		version: "1.1"
  	});
  
  })(jQuery);
  
  /* Flot plugin for stacking data sets rather than overlyaing them.
  
  Copyright (c) 2007-2014 IOLA and Ole Laursen.
  Licensed under the MIT license.
  
  The plugin assumes the data is sorted on x (or y if stacking horizontally).
  For line charts, it is assumed that if a line has an undefined gap (from a
  null point), then the line above it should have the same gap - insert zeros
  instead of "null" if you want another behaviour. This also holds for the start
  and end of the chart. Note that stacking a mix of positive and negative values
  in most instances doesn't make sense (so it looks weird).
  
  Two or more series are stacked when their "stack" attribute is set to the same
  key (which can be any number or string or just "true"). To specify the default
  stack, you can set the stack option like this:
  
  	series: {
  		stack: null/false, true, or a key (number/string)
  	}
  
  You can also specify it for a single series, like this:
  
  	$.plot( $("#placeholder"), [{
  		data: [ ... ],
  		stack: true
  	}])
  
  The stacking order is determined by the order of the data series in the array
  (later series end up on top of the previous).
  
  Internally, the plugin modifies the datapoints in each series, adding an
  offset to the y value. For line series, extra data points are inserted through
  interpolation. If there's a second y value, it's also adjusted (e.g for bar
  charts or filled areas).
  
  */
  
  (function ($) {
      var options = {
          series: { stack: null } // or number/string
      };
      
      function init(plot) {
          function findMatchingSeries(s, allseries) {
              var res = null;
              for (var i = 0; i < allseries.length; ++i) {
                  if (s == allseries[i])
                      break;
                  
                  if (allseries[i].stack == s.stack)
                      res = allseries[i];
              }
              
              return res;
          }
          
          function stackData(plot, s, datapoints) {
              if (s.stack == null || s.stack === false)
                  return;
  
              var other = findMatchingSeries(s, plot.getData());
              if (!other)
                  return;
  
              var ps = datapoints.pointsize,
                  points = datapoints.points,
                  otherps = other.datapoints.pointsize,
                  otherpoints = other.datapoints.points,
                  newpoints = [],
                  px, py, intery, qx, qy, bottom,
                  withlines = s.lines.show,
                  horizontal = s.bars.horizontal,
                  withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y),
                  withsteps = withlines && s.lines.steps,
                  fromgap = true,
                  keyOffset = horizontal ? 1 : 0,
                  accumulateOffset = horizontal ? 0 : 1,
                  i = 0, j = 0, l, m;
  
              while (true) {
                  if (i >= points.length)
                      break;
  
                  l = newpoints.length;
  
                  if (points[i] == null) {
                      // copy gaps
                      for (m = 0; m < ps; ++m)
                          newpoints.push(points[i + m]);
                      i += ps;
                  }
                  else if (j >= otherpoints.length) {
                      // for lines, we can't use the rest of the points
                      if (!withlines) {
                          for (m = 0; m < ps; ++m)
                              newpoints.push(points[i + m]);
                      }
                      i += ps;
                  }
                  else if (otherpoints[j] == null) {
                      // oops, got a gap
                      for (m = 0; m < ps; ++m)
                          newpoints.push(null);
                      fromgap = true;
                      j += otherps;
                  }
                  else {
                      // cases where we actually got two points
                      px = points[i + keyOffset];
                      py = points[i + accumulateOffset];
                      qx = otherpoints[j + keyOffset];
                      qy = otherpoints[j + accumulateOffset];
                      bottom = 0;
  
                      if (px == qx) {
                          for (m = 0; m < ps; ++m)
                              newpoints.push(points[i + m]);
  
                          newpoints[l + accumulateOffset] += qy;
                          bottom = qy;
                          
                          i += ps;
                          j += otherps;
                      }
                      else if (px > qx) {
                          // we got past point below, might need to
                          // insert interpolated extra point
                          if (withlines && i > 0 && points[i - ps] != null) {
                              intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px);
                              newpoints.push(qx);
                              newpoints.push(intery + qy);
                              for (m = 2; m < ps; ++m)
                                  newpoints.push(points[i + m]);
                              bottom = qy; 
                          }
  
                          j += otherps;
                      }
                      else { // px < qx
                          if (fromgap && withlines) {
                              // if we come from a gap, we just skip this point
                              i += ps;
                              continue;
                          }
                              
                          for (m = 0; m < ps; ++m)
                              newpoints.push(points[i + m]);
                          
                          // we might be able to interpolate a point below,
                          // this can give us a better y
                          if (withlines && j > 0 && otherpoints[j - otherps] != null)
                              bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx);
  
                          newpoints[l + accumulateOffset] += bottom;
                          
                          i += ps;
                      }
  
                      fromgap = false;
                      
                      if (l != newpoints.length && withbottom)
                          newpoints[l + 2] += bottom;
                  }
  
                  // maintain the line steps invariant
                  if (withsteps && l != newpoints.length && l > 0
                      && newpoints[l] != null
                      && newpoints[l] != newpoints[l - ps]
                      && newpoints[l + 1] != newpoints[l - ps + 1]) {
                      for (m = 0; m < ps; ++m)
                          newpoints[l + ps + m] = newpoints[l + m];
                      newpoints[l + 1] = newpoints[l - ps + 1];
                  }
              }
  
              datapoints.points = newpoints;
          }
          
          plot.hooks.processDatapoints.push(stackData);
      }
      
      $.plot.plugins.push({
          init: init,
          options: options,
          name: 'stack',
          version: '1.2'
      });
  })(jQuery);
  
  /* Flot plugin for automatically redrawing plots as the placeholder resizes.
  
  Copyright (c) 2007-2014 IOLA and Ole Laursen.
  Licensed under the MIT license.
  
  It works by listening for changes on the placeholder div (through the jQuery
  resize event plugin) - if the size changes, it will redraw the plot.
  
  There are no options. If you need to disable the plugin for some plots, you
  can just fix the size of their placeholders.
  
  */
  
  /* Inline dependency:
   * jQuery resize event - v1.1 - 3/14/2010
   * http://benalman.com/projects/jquery-resize-plugin/
   *
   * Copyright (c) 2010 "Cowboy" Ben Alman
   * Dual licensed under the MIT and GPL licenses.
   * http://benalman.com/about/license/
   */
  (function($,e,t){"$:nomunge";var i=[],n=$.resize=$.extend($.resize,{}),a,r=false,s="setTimeout",u="resize",m=u+"-special-event",o="pendingDelay",l="activeDelay",f="throttleWindow";n[o]=200;n[l]=20;n[f]=true;$.event.special[u]={setup:function(){if(!n[f]&&this[s]){return false}var e=$(this);i.push(this);e.data(m,{w:e.width(),h:e.height()});if(i.length===1){a=t;h()}},teardown:function(){if(!n[f]&&this[s]){return false}var e=$(this);for(var t=i.length-1;t>=0;t--){if(i[t]==this){i.splice(t,1);break}}e.removeData(m);if(!i.length){if(r){cancelAnimationFrame(a)}else{clearTimeout(a)}a=null}},add:function(e){if(!n[f]&&this[s]){return false}var i;function a(e,n,a){var r=$(this),s=r.data(m)||{};s.w=n!==t?n:r.width();s.h=a!==t?a:r.height();i.apply(this,arguments)}if($.isFunction(e)){i=e;return a}else{i=e.handler;e.handler=a}}};function h(t){if(r===true){r=t||1}for(var s=i.length-1;s>=0;s--){var l=$(i[s]);if(l[0]==e||l.is(":visible")){var f=l.width(),c=l.height(),d=l.data(m);if(d&&(f!==d.w||c!==d.h)){l.trigger(u,[d.w=f,d.h=c]);r=t||true}}else{d=l.data(m);d.w=0;d.h=0}}if(a!==null){if(r&&(t==null||t-r<1e3)){a=e.requestAnimationFrame(h)}else{a=setTimeout(h,n[o]);r=false}}}if(!e.requestAnimationFrame){e.requestAnimationFrame=function(){return e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(t,i){return e.setTimeout(function(){t((new Date).getTime())},n[l])}}()}if(!e.cancelAnimationFrame){e.cancelAnimationFrame=function(){return e.webkitCancelRequestAnimationFrame||e.mozCancelRequestAnimationFrame||e.oCancelRequestAnimationFrame||e.msCancelRequestAnimationFrame||clearTimeout}()}})(jQuery,this);
  
  (function ($) {
      var options = { }; // no options
  
      function init(plot) {
          function onResize() {
              var placeholder = plot.getPlaceholder();
  
              // somebody might have hidden us and we can't plot
              // when we don't have the dimensions
              if (placeholder.width() == 0 || placeholder.height() == 0)
                  return;
  
              plot.resize();
              plot.setupGrid();
              plot.draw();
          }
          
          function bindEvents(plot, eventHolder) {
              plot.getPlaceholder().resize(onResize);
          }
  
          function shutdown(plot, eventHolder) {
              plot.getPlaceholder().unbind("resize", onResize);
          }
          
          plot.hooks.bindEvents.push(bindEvents);
          plot.hooks.shutdown.push(shutdown);
      }
      
      $.plot.plugins.push({
          init: init,
          options: options,
          name: 'resize',
          version: '1.0'
      });
  })(jQuery);
  
  /*
   * Flot plugin to order bars side by side.
   *
   * Released under the MIT license by Benjamin BUFFET, 20-Sep-2010.
   * Modifications made by Steven Hall <github.com/emmerich>, 01-May-2013.
   *
   * This plugin is an alpha version.
   *
   * To activate the plugin you must specify the parameter "order" for the specific serie :
   *
   *  $.plot($("#placeholder"), [{ data: [ ... ], bars :{ order = null or integer }])
   *
   * If 2 series have the same order param, they are ordered by the position in the array;
   *
   * The plugin adjust the point by adding a value depanding of the barwidth
   * Exemple for 3 series (barwidth : 0.1) :
   *
   *          first bar décalage : -0.15
   *          second bar décalage : -0.05
   *          third bar décalage : 0.05
   *
   */
  
  // INFO: decalage/decallage is French for gap. It's used to denote the spacing applied to each
  // bar.
  (function($){
      function init(plot){
          var orderedBarSeries;
          var nbOfBarsToOrder;
          var borderWidth;
          var borderWidthInXabsWidth;
          var pixelInXWidthEquivalent = 1;
          var isHorizontal = false;
  
          // A mapping of order integers to decallage.
          var decallageByOrder = {};
  
          /*
           * This method add shift to x values
           */
          function reOrderBars(plot, serie, datapoints){
              var shiftedPoints = null;
  
              if(serieNeedToBeReordered(serie)){
                  checkIfGraphIsHorizontal(serie);
                  calculPixel2XWidthConvert(plot);
                  retrieveBarSeries(plot);
                  calculBorderAndBarWidth(serie);
  
                  if(nbOfBarsToOrder >= 2){
                      var position = findPosition(serie);
                      var decallage = 0;
  
                      var centerBarShift = calculCenterBarShift();
  
                      // If we haven't already calculated the decallage for this order value, do it.
                      if(typeof decallageByOrder[serie.bars.order] === 'undefined') {
                          if (isBarAtLeftOfCenter(position)){
                              decallageByOrder[serie.bars.order] = -1*(sumWidth(orderedBarSeries,position-1,Math.floor(nbOfBarsToOrder / 2)-1)) - centerBarShift;
                          }else{
                              decallageByOrder[serie.bars.order] = sumWidth(orderedBarSeries,Math.ceil(nbOfBarsToOrder / 2),position-2) + centerBarShift + borderWidthInXabsWidth*2;
                          }
                      }
  
                      // Lookup the decallage based on the series' order value.
                      decallage = decallageByOrder[serie.bars.order];
  
                      shiftedPoints = shiftPoints(datapoints,serie,decallage);
                      datapoints.points = shiftedPoints;
                  }
              }
              return shiftedPoints;
          }
  
          function serieNeedToBeReordered(serie){
              return serie.bars != null
                  && serie.bars.show
                  && serie.bars.order != null;
          }
  
          function calculPixel2XWidthConvert(plot){
              var gridDimSize = isHorizontal ? plot.getPlaceholder().innerHeight() : plot.getPlaceholder().innerWidth();
              var minMaxValues = isHorizontal ? getAxeMinMaxValues(plot.getData(),1) : getAxeMinMaxValues(plot.getData(),0);
              var AxeSize = minMaxValues[1] - minMaxValues[0];
              pixelInXWidthEquivalent = AxeSize / gridDimSize;
          }
  
          function getAxeMinMaxValues(series,AxeIdx){
              var minMaxValues = new Array();
              for(var i = 0; i < series.length; i++){
                  minMaxValues[0] = (series[i].data[0]) ? series[i].data[0][AxeIdx]: null;
                  minMaxValues[1] = (series[i].data[series[i].data.length - 1]) ? series[i].data[series[i].data.length - 1][AxeIdx]: null;
              }
              return minMaxValues;
          }
  
          function retrieveBarSeries(plot){
              orderedBarSeries = findOthersBarsToReOrders(plot.getData());
              nbOfBarsToOrder = orderedBarSeries.length;
          }
  
          function findOthersBarsToReOrders(series){
              var retSeries = new Array();
              var orderValuesSeen = [];
  
              for(var i = 0; i < series.length; i++){
                  if(series[i].bars.order != null && series[i].bars.show &&
                      orderValuesSeen.indexOf(series[i].bars.order) < 0){
  
                      orderValuesSeen.push(series[i].bars.order);
                      retSeries.push(series[i]);
                  }
              }
              return retSeries.sort(sortByOrder);
          }
  
          function sortByOrder(serie1,serie2){
              var x = serie1.bars.order;
              var y = serie2.bars.order;
              return ((x < y) ? -1 : ((x > y) ? 1 : 0));
          }
  
          function  calculBorderAndBarWidth(serie){
              borderWidth = typeof serie.bars.lineWidth !== 'undefined' ? serie.bars.lineWidth  : 2;
              borderWidthInXabsWidth = borderWidth * pixelInXWidthEquivalent;
          }
  
          function checkIfGraphIsHorizontal(serie){
              if(serie.bars.horizontal){
                  isHorizontal = true;
              }
          }
  
          function findPosition(serie){
              var pos = 0
              for (var i = 0; i < orderedBarSeries.length; ++i) {
                  if (serie == orderedBarSeries[i]){
                      pos = i;
                      break;
                  }
              }
  
              return pos+1;
          }
  
          function calculCenterBarShift(){
              var width = 0;
  
              if(nbOfBarsToOrder%2 != 0)
                  width = (orderedBarSeries[Math.ceil(nbOfBarsToOrder / 2)].bars.barWidth)/2;
  
              return width;
          }
  
          function isBarAtLeftOfCenter(position){
              return position <= Math.ceil(nbOfBarsToOrder / 2);
          }
  
          function sumWidth(series,start,end){
              var totalWidth = 0;
  
              for(var i = start; i <= end; i++){
                  totalWidth += series[i].bars.barWidth+borderWidthInXabsWidth*2;
              }
  
              return totalWidth;
          }
  
          function shiftPoints(datapoints,serie,dx){
              var ps = datapoints.pointsize;
              var points = datapoints.points;
              var j = 0;
              for(var i = isHorizontal ? 1 : 0;i < points.length; i += ps){
                  points[i] += dx;
                  //Adding the new x value in the serie to be abble to display the right tooltip value,
                  //using the index 3 to not overide the third index.
                  serie.data[j][3] = points[i];
                  j++;
              }
  
              return points;
          }
  
          plot.hooks.processDatapoints.push(reOrderBars);
  
      }
  
      var options = {
          series : {
              bars: {order: null} // or number/string
          }
      };
  
      $.plot.plugins.push({
          init: init,
          options: options,
          name: "orderBars",
          version: "0.2"
      });
  
  })(jQuery);
  /** 
   * @overview datejs
   * @version 1.0.0-rc3
   * @author Gregory Wild-Smith <gregory@wild-smith.com>
   * @copyright 2014 Gregory Wild-Smith
   * @license MIT
   * @homepage https://github.com/abritinthebay/datejs
   */(function () {
  	var $D = Date;
  	var lang = Date.CultureStrings ? Date.CultureStrings.lang : null;
  	var loggedKeys = {}; // for debug purposes.
  	var getText = {
  		getFromKey: function (key, countryCode) {
  			var output;
  			if (Date.CultureStrings && Date.CultureStrings[countryCode] && Date.CultureStrings[countryCode][key]) {
  				output = Date.CultureStrings[countryCode][key];
  			} else {
  				output = getText.buildFromDefault(key);
  			}
  			if (key.charAt(0) === "/") { // Assume it's a regex
  				output = getText.buildFromRegex(key, countryCode);
  			}
  			return output;
  		},
  		getFromObjectValues: function (obj, countryCode) {
  			var key, output = {};
  			for(key in obj) {
  				if (obj.hasOwnProperty(key)) {
  					output[key] = getText.getFromKey(obj[key], countryCode);
  				}
  			}
  			return output;
  		},
  		getFromObjectKeys: function (obj, countryCode) {
  			var key, output = {};
  			for(key in obj) {
  				if (obj.hasOwnProperty(key)) {
  					output[getText.getFromKey(key, countryCode)] = obj[key];
  				}
  			}
  			return output;
  		},
  		getFromArray: function (arr, countryCode) {
  			var output = [];
  			for (var i=0; i < arr.length; i++){
  				if (i in arr) {
  					output[i] = getText.getFromKey(arr[i], countryCode);
  				}
  			}
  			return output;
  		},
  		buildFromDefault: function (key) {
  			var output, length, split, last;
  			switch(key) {
  				case "name":
  					output = "en-US";
  					break;
  				case "englishName":
  					output = "English (United States)";
  					break;
  				case "nativeName":
  					output = "English (United States)";
  					break;
  				case "twoDigitYearMax":
  					output = 2049;
  					break;
  				case "firstDayOfWeek":
  					output = 0;
  					break;
  				default:
  					output = key;
  					split = key.split("_");
  					length = split.length;
  					if (length > 1 && key.charAt(0) !== "/") {
  						// if the key isn't a regex and it has a split.
  						last = split[(length - 1)].toLowerCase();
  						if (last === "initial" || last === "abbr") {
  							output = split[0];
  						}
  					}
  					break;
  			}
  			return output;
  		},
  		buildFromRegex: function (key, countryCode) {
  			var output;
  			if (Date.CultureStrings && Date.CultureStrings[countryCode] && Date.CultureStrings[countryCode][key]) {
  				output = new RegExp(Date.CultureStrings[countryCode][key], "i");
  			} else {
  				output = new RegExp(key.replace(new RegExp("/", "g"),""), "i");
  			}
  			return output;
  		}
  	};
  
  	var shallowMerge = function (obj1, obj2) {
  		for (var attrname in obj2) {
  			if (obj2.hasOwnProperty(attrname)) {
  				obj1[attrname] = obj2[attrname];
  			}
  		}
  	};
  
  	var __ = function (key, language) {
  		var countryCode = (language) ? language : lang;
  		loggedKeys[key] = key;
  		if (typeof key === "object") {
  			if (key instanceof Array) {
  				return getText.getFromArray(key, countryCode);
  			} else {
  				return getText.getFromObjectKeys(key, countryCode);
  			}
  		} else {
  			return getText.getFromKey(key, countryCode);
  		}
  	};
  	
  	var loadI18nScript = function (code) {
  		// paatterned after jQuery's getScript.
  		var url = Date.Config.i18n + code + ".js";
  		var head = document.getElementsByTagName("head")[0] || document.documentElement;
  		var script = document.createElement("script");
  		script.src = url;
  
  		var completed = false;
  		var events = {
  			done: function (){} // placeholder function
  		};
  		// Attach handlers for all browsers
  		script.onload = script.onreadystatechange = function() {
  			if ( !completed && (!this.readyState || this.readyState === "loaded" || this.readyState === "complete") ) {
  				events.done();
  				head.removeChild(script);
  			}
  		};
  
  		setTimeout(function() {
  			head.insertBefore(script, head.firstChild);
  		}, 0); // allows return to execute first
  		
  		return {
  			done: function (cb) {
  				events.done = function() {
  					if (cb) {
  						setTimeout(cb,0);
  					}
  				};
  			}
  		};
  	};
  
  	var buildInfo = {
  		buildFromMethodHash: function (obj) {
  			var key;
  			for(key in obj) {
  				if (obj.hasOwnProperty(key)) {
  					obj[key] = buildInfo[obj[key]]();
  				}
  			}
  			return obj;
  		},
  		timeZoneDST: function () {
  			var DST = {
  				"CHADT": "+1345",
  				"NZDT": "+1300",
  				"AEDT": "+1100",
  				"ACDT": "+1030",
  				"AZST": "+0500",
  				"IRDT": "+0430",
  				"EEST": "+0300",
  				"CEST": "+0200",
  				"BST": "+0100",
  				"PMDT": "-0200",
  				"ADT": "-0300",
  				"NDT": "-0230",
  				"EDT": "-0400",
  				"CDT": "-0500",
  				"MDT": "-0600",
  				"PDT": "-0700",
  				"AKDT": "-0800",
  				"HADT": "-0900"
  			};
  			return __(DST);
  		},
  		timeZoneStandard: function () {
  			var standard = {
  				"LINT": "+1400",
  				"TOT": "+1300",
  				"CHAST": "+1245",
  				"NZST": "+1200",
  				"NFT": "+1130",
  				"SBT": "+1100",
  				"AEST": "+1000",
  				"ACST": "+0930",
  				"JST": "+0900",
  				"CWST": "+0845",
  				"CT": "+0800",
  				"ICT": "+0700",
  				"MMT": "+0630",
  				"BST": "+0600",
  				"NPT": "+0545",
  				"IST": "+0530",
  				"PKT": "+0500",
  				"AFT": "+0430",
  				"MSK": "+0400",
  				"IRST": "+0330",
  				"FET": "+0300",
  				"EET": "+0200",
  				"CET": "+0100",
  				"GMT": "+0000",
  				"UTC": "+0000",
  				"CVT": "-0100",
  				"GST": "-0200",
  				"BRT": "-0300",
  				"NST": "-0330",
  				"AST": "-0400",
  				"EST": "-0500",
  				"CST": "-0600",
  				"MST": "-0700",
  				"PST": "-0800",
  				"AKST": "-0900",
  				"MIT": "-0930",
  				"HST": "-1000",
  				"SST": "-1100",
  				"BIT": "-1200"
  			};
  			return __(standard);
  		},
  		timeZones: function (data) {
  			var zone;
  			data.timezones = [];
  			for (zone in data.abbreviatedTimeZoneStandard) {
  				if (data.abbreviatedTimeZoneStandard.hasOwnProperty(zone)) {
  					data.timezones.push({ name: zone, offset: data.abbreviatedTimeZoneStandard[zone]});
  				}
  			}
  			for (zone in data.abbreviatedTimeZoneDST) {
  				if (data.abbreviatedTimeZoneDST.hasOwnProperty(zone)) {
  					data.timezones.push({ name: zone, offset: data.abbreviatedTimeZoneDST[zone], dst: true});
  				}
  			}
  			return data.timezones;
  		},
  		days: function () {
  			return __(["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]);
  		},
  		dayAbbr: function () {
  			return __(["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]);
  		},
  		dayShortNames: function () {
  			return __(["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]);
  		},
  		dayFirstLetters: function () {
  			return __(["S_Sun_Initial", "M_Mon_Initial", "T_Tues_Initial", "W_Wed_Initial", "T_Thu_Initial", "F_Fri_Initial", "S_Sat_Initial"]);
  		},
  		months: function () {
  			return __(["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]);
  		},
  		monthAbbr: function () {
  			return __(["Jan_Abbr", "Feb_Abbr", "Mar_Abbr", "Apr_Abbr", "May_Abbr", "Jun_Abbr", "Jul_Abbr", "Aug_Abbr", "Sep_Abbr", "Oct_Abbr", "Nov_Abbr", "Dec_Abbr"]);
  		},
  		formatPatterns: function () {
  			return getText.getFromObjectValues({
  				shortDate: "M/d/yyyy",
  				longDate: "dddd, MMMM dd, yyyy",
  				shortTime: "h:mm tt",
  				longTime: "h:mm:ss tt",
  				fullDateTime: "dddd, MMMM dd, yyyy h:mm:ss tt",
  				sortableDateTime: "yyyy-MM-ddTHH:mm:ss",
  				universalSortableDateTime: "yyyy-MM-dd HH:mm:ssZ",
  				rfc1123: "ddd, dd MMM yyyy HH:mm:ss",
  				monthDay: "MMMM dd",
  				yearMonth: "MMMM, yyyy"
  			}, Date.i18n.currentLanguage());
  		},
  		regex: function () {
  			return getText.getFromObjectValues({
  				inTheMorning: "/( in the )(morn(ing)?)\\b/",
  				thisMorning: "/(this )(morn(ing)?)\\b/",
  				amThisMorning: "/(\b\\d(am)? )(this )(morn(ing)?)/",
  				inTheEvening: "/( in the )(even(ing)?)\\b/",
  				thisEvening: "/(this )(even(ing)?)\\b/",
  				pmThisEvening: "/(\b\\d(pm)? )(this )(even(ing)?)/",
  				jan: "/jan(uary)?/",
  				feb: "/feb(ruary)?/",
  				mar: "/mar(ch)?/",
  				apr: "/apr(il)?/",
  				may: "/may/",
  				jun: "/jun(e)?/",
  				jul: "/jul(y)?/",
  				aug: "/aug(ust)?/",
  				sep: "/sep(t(ember)?)?/",
  				oct: "/oct(ober)?/",
  				nov: "/nov(ember)?/",
  				dec: "/dec(ember)?/",
  				sun: "/^su(n(day)?)?/",
  				mon: "/^mo(n(day)?)?/",
  				tue: "/^tu(e(s(day)?)?)?/",
  				wed: "/^we(d(nesday)?)?/",
  				thu: "/^th(u(r(s(day)?)?)?)?/",
  				fri: "/fr(i(day)?)?/",
  				sat: "/^sa(t(urday)?)?/",
  				future: "/^next/",
  				past: "/^last|past|prev(ious)?/",
  				add: "/^(\\+|aft(er)?|from|hence)/",
  				subtract: "/^(\\-|bef(ore)?|ago)/",
  				yesterday: "/^yes(terday)?/",
  				today: "/^t(od(ay)?)?/",
  				tomorrow: "/^tom(orrow)?/",
  				now: "/^n(ow)?/",
  				millisecond: "/^ms|milli(second)?s?/",
  				second: "/^sec(ond)?s?/",
  				minute: "/^mn|min(ute)?s?/",
  				hour: "/^h(our)?s?/",
  				week: "/^w(eek)?s?/",
  				month: "/^m(onth)?s?/",
  				day: "/^d(ay)?s?/",
  				year: "/^y(ear)?s?/",
  				shortMeridian: "/^(a|p)/",
  				longMeridian: "/^(a\\.?m?\\.?|p\\.?m?\\.?)/",
  				timezone: "/^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\\s*(\\+|\\-)\\s*\\d\\d\\d\\d?)|gmt|utc)/",
  				ordinalSuffix: "/^\\s*(st|nd|rd|th)/",
  				timeContext: "/^\\s*(\\:|a(?!u|p)|p)/"
  			}, Date.i18n.currentLanguage());
  		}
  	};
  
  	var CultureInfo = function () {
  		var info = getText.getFromObjectValues({
  			name: "name",
  			englishName: "englishName",
  			nativeName: "nativeName",
  			amDesignator: "AM",
  			pmDesignator: "PM",
  			firstDayOfWeek: "firstDayOfWeek",
  			twoDigitYearMax: "twoDigitYearMax",
  			dateElementOrder: "mdy"
  		}, Date.i18n.currentLanguage());
  
  		var constructedInfo = buildInfo.buildFromMethodHash({
  			dayNames: "days",
  			abbreviatedDayNames: "dayAbbr",
  			shortestDayNames: "dayShortNames",
  			firstLetterDayNames: "dayFirstLetters",
  			monthNames: "months",
  			abbreviatedMonthNames: "monthAbbr",
  			formatPatterns: "formatPatterns",
  			regexPatterns: "regex",
  			abbreviatedTimeZoneDST: "timeZoneDST",
  			abbreviatedTimeZoneStandard: "timeZoneStandard"
  		});
  
  		shallowMerge(info, constructedInfo);
  		buildInfo.timeZones(info);
  		return info;
  	};
  
  	$D.i18n = {
  		__: function (key, lang) {
  			return __(key, lang);
  		},
  		currentLanguage: function () {
  			return lang || "en-US";
  		},
  		setLanguage: function (code, force, cb) {
  			var async = false;
  			if (force || code === "en-US" || (!!Date.CultureStrings && !!Date.CultureStrings[code])) {
  				lang = code;
  				Date.CultureStrings = Date.CultureStrings || {};
  				Date.CultureStrings.lang = code;
  				Date.CultureInfo = new CultureInfo();
  			} else {
  				if (!(!!Date.CultureStrings && !!Date.CultureStrings[code])) {
  					if (typeof exports !== "undefined" && this.exports !== exports) {
  						// we're in a Node enviroment, load it using require
  						try {
  							require("../i18n/" + code + ".js");
  							lang = code;
  							Date.CultureStrings.lang = code;
  							Date.CultureInfo = new CultureInfo();
  						} catch (e) {
  							// var str = "The language for '" + code + "' could not be loaded by Node. It likely does not exist.";
  							throw new Error("The DateJS IETF language tag '" + code + "' could not be loaded by Node. It likely does not exist.");
  						}
  					} else if (Date.Config && Date.Config.i18n) {
  						// we know the location of the files, so lets load them					
  						async = true;
  						loadI18nScript(code).done(function(){
  							lang = code;
  							Date.CultureStrings = Date.CultureStrings || {};
  							Date.CultureStrings.lang = code;
  							Date.CultureInfo = new CultureInfo();
  							$D.Parsing.Normalizer.buildReplaceData(); // because this is async
  							if ($D.Grammar) {
  								$D.Grammar.buildGrammarFormats(); // so we can parse those strings...
  							}
  							if (cb) {
  								setTimeout(cb,0);
  							}
  						});
  					} else {
  						Date.console.error("The DateJS IETF language tag '" + code + "' is not available and has not been loaded.");
  						return false;
  					}
  				}
  			}
  			$D.Parsing.Normalizer.buildReplaceData(); // rebuild normalizer strings
  			if ($D.Grammar) {
  				$D.Grammar.buildGrammarFormats(); // so we can parse those strings...
  			}
  			if (!async && cb) {
  				setTimeout(cb,0);
  			}
  		},
  		getLoggedKeys: function () {
  			return loggedKeys;
  		},
  		updateCultureInfo: function () {
  			Date.CultureInfo = new CultureInfo();
  		}
  	};
  	$D.i18n.updateCultureInfo(); // run automatically
  }());
  (function () {
  	var $D = Date,
  		$P = $D.prototype,
  		p = function (s, l) {
  			if (!l) {
  				l = 2;
  			}
  			return ("000" + s).slice(l * -1);
  		};
  	
  	if (typeof window !== "undefined" && typeof window.console !== "undefined" && typeof window.console.log !== "undefined") {
  		$D.console = console; // used only to raise non-critical errors if available
  	} else {
  		// set mock so we don't give errors.
  		$D.console = {
  			log: function(){},
  			error: function(){}
  		};
  	}
  	$D.Config = $D.Config || {};
  
  	$D.initOverloads = function() {
  		/** 
  		 * Overload of Date.now. Allows an alternate call for Date.now where it returns the 
  		 * current Date as an object rather than just milliseconds since the Unix Epoch.
  		 *
  		 * Also provides an implementation of now() for browsers (IE<9) that don't have it.
  		 * 
  		 * Backwards compatible so with work with either:
  		 *  Date.now() [returns ms]
  		 * or
  		 *  Date.now(true) [returns Date]
  		 */
  		if (!$D.now) {
  			$D._now = function now() {
  				return new Date().getTime();
  			};
  		} else if (!$D._now) {
  			$D._now = $D.now;
  		}
  
  		$D.now = function (returnObj) {
  			if (returnObj) {
  				return $D.present();
  			} else {
  				return $D._now();
  			}
  		};
  
  		if ( !$P.toISOString ) {
  			$P.toISOString = function() {
  				return this.getUTCFullYear() +
  				"-" + p(this.getUTCMonth() + 1) +
  				"-" + p(this.getUTCDate()) +
  				"T" + p(this.getUTCHours()) +
  				":" + p(this.getUTCMinutes()) +
  				":" + p(this.getUTCSeconds()) +
  				"." + String( (this.getUTCMilliseconds()/1000).toFixed(3)).slice(2, 5) +
  				"Z";
  			};
  		}
  		
  		// private
  		if ( $P._toString === undefined ){
  			$P._toString = $P.toString;
  		}
  
  	};
  	$D.initOverloads();
  
  
  	/** 
  	 * Gets a date that is set to the current date. The time is set to the start of the day (00:00 or 12:00 AM).
  	 * @return {Date}    The current date.
  	 */
  	$D.today = function () {
  		return new Date().clearTime();
  	};
  
  	/** 
  	 * Gets a date that is set to the current date and time (same as new Date, but chainable)
  	 * @return {Date}    The current date.
  	 */
  	$D.present = function () {
  		return new Date();
  	};
  
  	/**
  	 * Compares the first date to the second date and returns an number indication of their relative values.  
  	 * @param {Date}     First Date object to compare [Required].
  	 * @param {Date}     Second Date object to compare to [Required].
  	 * @return {Number}  -1 = date1 is lessthan date2. 0 = values are equal. 1 = date1 is greaterthan date2.
  	 */
  	$D.compare = function (date1, date2) {
  		if (isNaN(date1) || isNaN(date2)) {
  			throw new Error(date1 + " - " + date2);
  		} else if (date1 instanceof Date && date2 instanceof Date) {
  			return (date1 < date2) ? -1 : (date1 > date2) ? 1 : 0;
  		} else {
  			throw new TypeError(date1 + " - " + date2);
  		}
  	};
  	
  	/**
  	 * Compares the first Date object to the second Date object and returns true if they are equal.  
  	 * @param {Date}     First Date object to compare [Required]
  	 * @param {Date}     Second Date object to compare to [Required]
  	 * @return {Boolean} true if dates are equal. false if they are not equal.
  	 */
  	$D.equals = function (date1, date2) {
  		return (date1.compareTo(date2) === 0);
  	};
  
  	/**
  	 * Gets the language appropriate day name when given the day number(0-6)
  	 * eg - 0 == Sunday
  	 * @return {String}  The day name
  	 */
  	$D.getDayName = function (n) {
  		return Date.CultureInfo.dayNames[n];
  	};
  
  	/**
  	 * Gets the day number (0-6) if given a CultureInfo specific string which is a valid dayName, abbreviatedDayName or shortestDayName (two char).
  	 * @param {String}   The name of the day (eg. "Monday, "Mon", "tuesday", "tue", "We", "we").
  	 * @return {Number}  The day number
  	 */
  	$D.getDayNumberFromName = function (name) {
  		var n = Date.CultureInfo.dayNames, m = Date.CultureInfo.abbreviatedDayNames, o = Date.CultureInfo.shortestDayNames, s = name.toLowerCase();
  		for (var i = 0; i < n.length; i++) {
  			if (n[i].toLowerCase() === s || m[i].toLowerCase() === s || o[i].toLowerCase() === s) {
  				return i;
  			}
  		}
  		return -1;
  	};
  	
  	/**
  	 * Gets the month number (0-11) if given a Culture Info specific string which is a valid monthName or abbreviatedMonthName.
  	 * @param {String}   The name of the month (eg. "February, "Feb", "october", "oct").
  	 * @return {Number}  The day number
  	 */
  	$D.getMonthNumberFromName = function (name) {
  		var n = Date.CultureInfo.monthNames, m = Date.CultureInfo.abbreviatedMonthNames, s = name.toLowerCase();
  		for (var i = 0; i < n.length; i++) {
  			if (n[i].toLowerCase() === s || m[i].toLowerCase() === s) {
  				return i;
  			}
  		}
  		return -1;
  	};
  
  	/**
  	 * Gets the language appropriate month name when given the month number(0-11)
  	 * eg - 0 == January
  	 * @return {String}  The month name
  	 */
  	$D.getMonthName = function (n) {
  		return Date.CultureInfo.monthNames[n];
  	};
  
  	/**
  	 * Determines if the current date instance is within a LeapYear.
  	 * @param {Number}   The year.
  	 * @return {Boolean} true if date is within a LeapYear, otherwise false.
  	 */
  	$D.isLeapYear = function (year) {
  		return ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0);
  	};
  
  	/**
  	 * Gets the number of days in the month, given a year and month value. Automatically corrects for LeapYear.
  	 * @param {Number}   The year.
  	 * @param {Number}   The month (0-11).
  	 * @return {Number}  The number of days in the month.
  	 */
  	$D.getDaysInMonth = function (year, month) {
  		if (!month && $D.validateMonth(year)) {
  				month = year;
  				year = Date.today().getFullYear();
  		}
  		return [31, ($D.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
  	};
  
  	$P.getDaysInMonth = function () {
  		return $D.getDaysInMonth(this.getFullYear(), this.getMonth());
  	};
   
  	$D.getTimezoneAbbreviation = function (offset, dst) {
  		var p, n = (dst || false) ? Date.CultureInfo.abbreviatedTimeZoneDST : Date.CultureInfo.abbreviatedTimeZoneStandard;
  		for (p in n) {
  			if (n.hasOwnProperty(p)) {
  				if (n[p] === offset) {
  					return p;
  				}
  			}
  		}
  		return null;
  	};
  	
  	$D.getTimezoneOffset = function (name, dst) {
  		var i, a =[], z = Date.CultureInfo.timezones;
  		if (!name) { name = (new Date()).getTimezone();}
  		for (i = 0; i < z.length; i++) {
  			if (z[i].name === name.toUpperCase()) {
  				a.push(i);
  			}
  		}
  		if (!z[a[0]]) {
  			return null;
  		}
  		if (a.length === 1 || !dst) {
  			return z[a[0]].offset;
  		} else {
  			for (i=0; i < a.length; i++) {
  				if (z[a[i]].dst) {
  					return z[a[i]].offset;
  				}
  			}
  		}
  	};
  
  	$D.getQuarter = function (d) {
  		d = d || new Date(); // If no date supplied, use today
  		var q = [1,2,3,4];
  		return q[Math.floor(d.getMonth() / 3)]; // ~~~ is a bitwise op. Faster than Math.floor
  	};
  
  	$D.getDaysLeftInQuarter = function (d) {
  		d = d || new Date();
  		var qEnd = new Date(d);
  		qEnd.setMonth(qEnd.getMonth() + 3 - qEnd.getMonth() % 3, 0);
  		return Math.floor((qEnd - d) / 8.64e7);
  	};
  
  	// private
  	var validate = function (n, min, max, name) {
  		name = name ? name : "Object";
  		if (typeof n === "undefined") {
  			return false;
  		} else if (typeof n !== "number") {
  			throw new TypeError(n + " is not a Number.");
  		} else if (n < min || n > max) {
  			// As failing validation is *not* an exceptional circumstance 
  			// lets not throw a RangeError Exception here. 
  			// It's semantically correct but it's not sensible.
  			return false;
  		}
  		return true;
  	};
  
  	/**
  	 * Validates the number is within an acceptable range for milliseconds [0-999].
  	 * @param {Number}   The number to check if within range.
  	 * @return {Boolean} true if within range, otherwise false.
  	 */
  	$D.validateMillisecond = function (value) {
  		return validate(value, 0, 999, "millisecond");
  	};
  
  	/**
  	 * Validates the number is within an acceptable range for seconds [0-59].
  	 * @param {Number}   The number to check if within range.
  	 * @return {Boolean} true if within range, otherwise false.
  	 */
  	$D.validateSecond = function (value) {
  		return validate(value, 0, 59, "second");
  	};
  
  	/**
  	 * Validates the number is within an acceptable range for minutes [0-59].
  	 * @param {Number}   The number to check if within range.
  	 * @return {Boolean} true if within range, otherwise false.
  	 */
  	$D.validateMinute = function (value) {
  		return validate(value, 0, 59, "minute");
  	};
  
  	/**
  	 * Validates the number is within an acceptable range for hours [0-23].
  	 * @param {Number}   The number to check if within range.
  	 * @return {Boolean} true if within range, otherwise false.
  	 */
  	$D.validateHour = function (value) {
  		return validate(value, 0, 23, "hour");
  	};
  
  	/**
  	 * Validates the number is within an acceptable range for the days in a month [0-MaxDaysInMonth].
  	 * @param {Number}   The number to check if within range.
  	 * @return {Boolean} true if within range, otherwise false.
  	 */
  	$D.validateDay = function (value, year, month) {
  		if (year === undefined || year === null || month === undefined || month === null) { return false;}
  		return validate(value, 1, $D.getDaysInMonth(year, month), "day");
  	};
  
  	/**
  	 * Validates the number is within an acceptable range for months [0-11].
  	 * @param {Number}   The number to check if within range.
  	 * @return {Boolean} true if within range, otherwise false.
  	 */
  	$D.validateWeek = function (value) {
  		return validate(value, 0, 53, "week");
  	};
  
  	/**
  	 * Validates the number is within an acceptable range for months [0-11].
  	 * @param {Number}   The number to check if within range.
  	 * @return {Boolean} true if within range, otherwise false.
  	 */
  	$D.validateMonth = function (value) {
  		return validate(value, 0, 11, "month");
  	};
  
  	/**
  	 * Validates the number is within an acceptable range for years.
  	 * @param {Number}   The number to check if within range.
  	 * @return {Boolean} true if within range, otherwise false.
  	 */
  	$D.validateYear = function (value) {
  		/**
  		 * Per ECMAScript spec the range of times supported by Date objects is 
  		 * exactly -100,000,000 days to +100,000,000 days measured relative to 
  		 * midnight at the beginning of 01 January, 1970 UTC. 
  		 * This gives a range of 8,640,000,000,000,000 milliseconds to either 
  		 * side of 01 January, 1970 UTC.
  		 *
  		 * Earliest possible date: Tue, 20 Apr 271,822 B.C. 00:00:00 UTC
  		 * Latest possible date: Sat, 13 Sep 275,760 00:00:00 UTC
  		 */
  		return validate(value, -271822, 275760, "year");
  	};
  	$D.validateTimezone = function(value) {
  		var timezones = {"ACDT":1,"ACST":1,"ACT":1,"ADT":1,"AEDT":1,"AEST":1,"AFT":1,"AKDT":1,"AKST":1,"AMST":1,"AMT":1,"ART":1,"AST":1,"AWDT":1,"AWST":1,"AZOST":1,"AZT":1,"BDT":1,"BIOT":1,"BIT":1,"BOT":1,"BRT":1,"BST":1,"BTT":1,"CAT":1,"CCT":1,"CDT":1,"CEDT":1,"CEST":1,"CET":1,"CHADT":1,"CHAST":1,"CHOT":1,"ChST":1,"CHUT":1,"CIST":1,"CIT":1,"CKT":1,"CLST":1,"CLT":1,"COST":1,"COT":1,"CST":1,"CT":1,"CVT":1,"CWST":1,"CXT":1,"DAVT":1,"DDUT":1,"DFT":1,"EASST":1,"EAST":1,"EAT":1,"ECT":1,"EDT":1,"EEDT":1,"EEST":1,"EET":1,"EGST":1,"EGT":1,"EIT":1,"EST":1,"FET":1,"FJT":1,"FKST":1,"FKT":1,"FNT":1,"GALT":1,"GAMT":1,"GET":1,"GFT":1,"GILT":1,"GIT":1,"GMT":1,"GST":1,"GYT":1,"HADT":1,"HAEC":1,"HAST":1,"HKT":1,"HMT":1,"HOVT":1,"HST":1,"ICT":1,"IDT":1,"IOT":1,"IRDT":1,"IRKT":1,"IRST":1,"IST":1,"JST":1,"KGT":1,"KOST":1,"KRAT":1,"KST":1,"LHST":1,"LINT":1,"MAGT":1,"MART":1,"MAWT":1,"MDT":1,"MET":1,"MEST":1,"MHT":1,"MIST":1,"MIT":1,"MMT":1,"MSK":1,"MST":1,"MUT":1,"MVT":1,"MYT":1,"NCT":1,"NDT":1,"NFT":1,"NPT":1,"NST":1,"NT":1,"NUT":1,"NZDT":1,"NZST":1,"OMST":1,"ORAT":1,"PDT":1,"PET":1,"PETT":1,"PGT":1,"PHOT":1,"PHT":1,"PKT":1,"PMDT":1,"PMST":1,"PONT":1,"PST":1,"PYST":1,"PYT":1,"RET":1,"ROTT":1,"SAKT":1,"SAMT":1,"SAST":1,"SBT":1,"SCT":1,"SGT":1,"SLST":1,"SRT":1,"SST":1,"SYOT":1,"TAHT":1,"THA":1,"TFT":1,"TJT":1,"TKT":1,"TLT":1,"TMT":1,"TOT":1,"TVT":1,"UCT":1,"ULAT":1,"UTC":1,"UYST":1,"UYT":1,"UZT":1,"VET":1,"VLAT":1,"VOLT":1,"VOST":1,"VUT":1,"WAKT":1,"WAST":1,"WAT":1,"WEDT":1,"WEST":1,"WET":1,"WST":1,"YAKT":1,"YEKT":1,"Z":1};
  		return (timezones[value] === 1);
  	};
  	$D.validateTimezoneOffset= function(value) {
  		// timezones go from +14hrs to -12hrs, the +X hours are negative offsets.
  		return (value > -841 && value < 721);
  	};
  
  }());
  
  (function () {
  	var $D = Date,
  		$P = $D.prototype,
  		p = function (s, l) {
  			if (!l) {
  				l = 2;
  			}
  			return ("000" + s).slice(l * -1);
  		};
  
  	var validateConfigObject = function (obj) {
  		var result = {}, self = this, prop, testFunc;
  		testFunc = function (prop, func, value) {
  			if (prop === "day") {
  				var month = (obj.month !== undefined) ? obj.month : self.getMonth();
  				var year = (obj.year !== undefined) ? obj.year : self.getFullYear();
  				return $D[func](value, year, month);
  			} else {
  				return $D[func](value);
  			}
  		};
  		for (prop in obj) {
  			if (hasOwnProperty.call(obj, prop)) {
  				var func = "validate" + prop.charAt(0).toUpperCase() + prop.slice(1);
  
  				if ($D[func] && obj[prop] !== null && testFunc(prop, func, obj[prop])) {
  					result[prop] = obj[prop];
  				}
  			}
  		}
  		return result;
  	};
  	/**
  	 * Resets the time of this Date object to 12:00 AM (00:00), which is the start of the day.
  	 * @param {Boolean}  .clone() this date instance before clearing Time
  	 * @return {Date}    this
  	 */
  	$P.clearTime = function () {
  		this.setHours(0);
  		this.setMinutes(0);
  		this.setSeconds(0);
  		this.setMilliseconds(0);
  		return this;
  	};
  
  	/**
  	 * Resets the time of this Date object to the current time ('now').
  	 * @return {Date}    this
  	 */
  	$P.setTimeToNow = function () {
  		var n = new Date();
  		this.setHours(n.getHours());
  		this.setMinutes(n.getMinutes());
  		this.setSeconds(n.getSeconds());
  		this.setMilliseconds(n.getMilliseconds());
  		return this;
  	};
  	/**
  	 * Returns a new Date object that is an exact date and time copy of the original instance.
  	 * @return {Date}    A new Date instance
  	 */
  	$P.clone = function () {
  		return new Date(this.getTime());
  	};
  
  	/**
  	 * Compares this instance to a Date object and returns an number indication of their relative values.  
  	 * @param {Date}     Date object to compare [Required]
  	 * @return {Number}  -1 = this is lessthan date. 0 = values are equal. 1 = this is greaterthan date.
  	 */
  	$P.compareTo = function (date) {
  		return Date.compare(this, date);
  	};
  
  	/**
  	 * Compares this instance to another Date object and returns true if they are equal.  
  	 * @param {Date}     Date object to compare. If no date to compare, new Date() [now] is used.
  	 * @return {Boolean} true if dates are equal. false if they are not equal.
  	 */
  	$P.equals = function (date) {
  		return Date.equals(this, (date !== undefined ? date : new Date()));
  	};
  
  	/**
  	 * Determines if this instance is between a range of two dates or equal to either the start or end dates.
  	 * @param {Date}     Start of range [Required]
  	 * @param {Date}     End of range [Required]
  	 * @return {Boolean} true is this is between or equal to the start and end dates, else false
  	 */
  	$P.between = function (start, end) {
  		return this.getTime() >= start.getTime() && this.getTime() <= end.getTime();
  	};
  
  	/**
  	 * Determines if this date occurs after the date to compare to.
  	 * @param {Date}     Date object to compare. If no date to compare, new Date() ("now") is used.
  	 * @return {Boolean} true if this date instance is greater than the date to compare to (or "now"), otherwise false.
  	 */
  	$P.isAfter = function (date) {
  		return this.compareTo(date || new Date()) === 1;
  	};
  
  	/**
  	 * Determines if this date occurs before the date to compare to.
  	 * @param {Date}     Date object to compare. If no date to compare, new Date() ("now") is used.
  	 * @return {Boolean} true if this date instance is less than the date to compare to (or "now").
  	 */
  	$P.isBefore = function (date) {
  		return (this.compareTo(date || new Date()) === -1);
  	};
  
  	/**
  	 * Determines if the current Date instance occurs today.
  	 * @return {Boolean} true if this date instance is 'today', otherwise false.
  	 */
  	
  	/**
  	 * Determines if the current Date instance occurs on the same Date as the supplied 'date'. 
  	 * If no 'date' to compare to is provided, the current Date instance is compared to 'today'. 
  	 * @param {date}     Date object to compare. If no date to compare, the current Date ("now") is used.
  	 * @return {Boolean} true if this Date instance occurs on the same Day as the supplied 'date'.
  	 */
  	$P.isToday = $P.isSameDay = function (date) {
  		return this.clone().clearTime().equals((date || new Date()).clone().clearTime());
  	};
  	
  	/**
  	 * Adds the specified number of milliseconds to this instance. 
  	 * @param {Number}   The number of milliseconds to add. The number can be positive or negative [Required]
  	 * @return {Date}    this
  	 */
  	$P.addMilliseconds = function (value) {
  		if (!value) { return this; }
  		this.setTime(this.getTime() + value * 1);
  		return this;
  	};
  
  	/**
  	 * Adds the specified number of seconds to this instance. 
  	 * @param {Number}   The number of seconds to add. The number can be positive or negative [Required]
  	 * @return {Date}    this
  	 */
  	$P.addSeconds = function (value) {
  		if (!value) { return this; }
  		return this.addMilliseconds(value * 1000);
  	};
  
  	/**
  	 * Adds the specified number of seconds to this instance. 
  	 * @param {Number}   The number of seconds to add. The number can be positive or negative [Required]
  	 * @return {Date}    this
  	 */
  	$P.addMinutes = function (value) {
  		if (!value) { return this; }
  		return this.addMilliseconds(value * 60000); // 60*1000
  	};
  
  	/**
  	 * Adds the specified number of hours to this instance. 
  	 * @param {Number}   The number of hours to add. The number can be positive or negative [Required]
  	 * @return {Date}    this
  	 */
  	$P.addHours = function (value) {
  		if (!value) { return this; }
  		return this.addMilliseconds(value * 3600000); // 60*60*1000
  	};
  
  	/**
  	 * Adds the specified number of days to this instance. 
  	 * @param {Number}   The number of days to add. The number can be positive or negative [Required]
  	 * @return {Date}    this
  	 */
  	$P.addDays = function (value) {
  		if (!value) { return this; }
  		this.setDate(this.getDate() + value * 1);
  		return this;
  	};
  
  	/**
  	 * Adds the specified number of weekdays (ie - not sat or sun) to this instance. 
  	 * @param {Number}   The number of days to add. The number can be positive or negative [Required]
  	 * @return {Date}    this
  	 */
  	$P.addWeekdays = function (value) {
  		if (!value) { return this; }
  		var day = this.getDay();
  		var weeks = (Math.ceil(Math.abs(value)/7));
  		if (day === 0 || day === 6) {
  			if (value > 0) {
  				this.next().monday();
  				this.addDays(-1);
  				day = this.getDay();
  			}
  		}
  
  		if (value < 0) {
  			while (value < 0) {
  				this.addDays(-1);
  				day = this.getDay();
  				if (day !== 0 && day !== 6) {
  					value++;
  				}
  			}
  			return this;
  		} else if (value > 5 || (6-day) <= value) {
  			value = value + (weeks * 2);
  		}
  
  		return this.addDays(value);
  	};
  
  	/**
  	 * Adds the specified number of weeks to this instance. 
  	 * @param {Number}   The number of weeks to add. The number can be positive or negative [Required]
  	 * @return {Date}    this
  	 */
  	$P.addWeeks = function (value) {
  		if (!value) { return this; }
  		return this.addDays(value * 7);
  	};
  
  
  	/**
  	 * Adds the specified number of months to this instance. 
  	 * @param {Number}   The number of months to add. The number can be positive or negative [Required]
  	 * @return {Date}    this
  	 */
  	$P.addMonths = function (value) {
  		if (!value) { return this; }
  		var n = this.getDate();
  		this.setDate(1);
  		this.setMonth(this.getMonth() + value * 1);
  		this.setDate(Math.min(n, $D.getDaysInMonth(this.getFullYear(), this.getMonth())));
  		return this;
  	};
  
  	$P.addQuarters = function (value) {
  		if (!value) { return this; }
  		// note this will take you to the same point in the quarter as you are now.
  		// i.e. - if you are 15 days into the quarter you'll be 15 days into the resulting one.
  		// bonus: this allows adding fractional quarters
  		return this.addMonths(value * 3);
  	};
  
  	/**
  	 * Adds the specified number of years to this instance. 
  	 * @param {Number}   The number of years to add. The number can be positive or negative [Required]
  	 * @return {Date}    this
  	 */
  	$P.addYears = function (value) {
  		if (!value) { return this; }
  		return this.addMonths(value * 12);
  	};
  
  	/**
  	 * Adds (or subtracts) to the value of the years, months, weeks, days, hours, minutes, seconds, milliseconds of the date instance using given configuration object. Positive and Negative values allowed.
  	 * Example
  	<pre><code>
  	Date.today().add( { days: 1, months: 1 } )
  	 
  	new Date().add( { years: -1 } )
  	</code></pre> 
  	 * @param {Object}   Configuration object containing attributes (months, days, etc.)
  	 * @return {Date}    this
  	 */
  	$P.add = function (config) {
  		if (typeof config === "number") {
  			this._orient = config;
  			return this;
  		}
  		
  		var x = config;
  
  		if (x.day) {
  			// If we should be a different date than today (eg: for 'tomorrow -1d', etc).
  			// Should only effect parsing, not direct usage (eg, Finish and FinishExact)
  			if ((x.day - this.getDate()) !== 0) {
  				this.setDate(x.day);
  			}
  		}
  		if (x.milliseconds) {
  			this.addMilliseconds(x.milliseconds);
  		}
  		if (x.seconds) {
  			this.addSeconds(x.seconds);
  		}
  		if (x.minutes) {
  			this.addMinutes(x.minutes);
  		}
  		if (x.hours) {
  			this.addHours(x.hours);
  		}
  		if (x.weeks) {
  			this.addWeeks(x.weeks);
  		}
  		if (x.months) {
  			this.addMonths(x.months);
  		}
  		if (x.years) {
  			this.addYears(x.years);
  		}
  		if (x.days) {
  			this.addDays(x.days);
  		}
  		return this;
  	};
  	
  	/**
  	 * Get the week number. Week one (1) is the week which contains the first Thursday of the year. Monday is considered the first day of the week.
  	 * The .getWeek() function does NOT convert the date to UTC. The local datetime is used. 
  	 * Please use .getISOWeek() to get the week of the UTC converted date.
  	 * @return {Number}  1 to 53
  	 */
  	$P.getWeek = function (utc) {
  		// Create a copy of this date object  
  		var self, target = new Date(this.valueOf());
  		if (utc) {
  			target.addMinutes(target.getTimezoneOffset());
  			self = target.clone();
  		} else {
  			self = this;
  		}
  		// ISO week date weeks start on monday  
  		// so correct the day number  
  		var dayNr = (self.getDay() + 6) % 7;
  		// ISO 8601 states that week 1 is the week  
  		// with the first thursday of that year.  
  		// Set the target date to the thursday in the target week  
  		target.setDate(target.getDate() - dayNr + 3);
  		// Store the millisecond value of the target date  
  		var firstThursday = target.valueOf();
  		// Set the target to the first thursday of the year  
  		// First set the target to january first  
  		target.setMonth(0, 1);
  		// Not a thursday? Correct the date to the next thursday  
  		if (target.getDay() !== 4) {
  			target.setMonth(0, 1 + ((4 - target.getDay()) + 7) % 7);
  		}
  		// The weeknumber is the number of weeks between the   
  		// first thursday of the year and the thursday in the target week  
  		return 1 + Math.ceil((firstThursday - target) / 604800000); // 604800000 = 7 * 24 * 3600 * 1000  
  	};
  	
  	/**
  	 * Get the ISO 8601 week number. Week one ("01") is the week which contains the first Thursday of the year. Monday is considered the first day of the week.
  	 * The .getISOWeek() function does convert the date to it's UTC value. Please use .getWeek() to get the week of the local date.
  	 * @return {String}  "01" to "53"
  	 */
  	$P.getISOWeek = function () {
  		return p(this.getWeek(true));
  	};
  
  	/**
  	 * Moves the date to Monday of the week set. Week one (1) is the week which contains the first Thursday of the year.
  	 * @param {Number}   A Number (1 to 53) that represents the week of the year.
  	 * @return {Date}    this
  	 */
  	$P.setWeek = function (n) {
  		if ((n - this.getWeek()) === 0) {
  			if (this.getDay() !== 1) {
  				return this.moveToDayOfWeek(1, (this.getDay() > 1 ? -1 : 1));
  			} else {
  				return this;
  			}
  		} else {
  			return this.moveToDayOfWeek(1, (this.getDay() > 1 ? -1 : 1)).addWeeks(n - this.getWeek());
  		}
  	};
  
  	$P.setQuarter = function (qtr) {
  		var month = Math.abs(((qtr-1) * 3) + 1);
  		return this.setMonth(month, 1);
  	};
  
  	$P.getQuarter = function () {
  		return Date.getQuarter(this);
  	};
  
  	$P.getDaysLeftInQuarter = function () {
  		return Date.getDaysLeftInQuarter(this);
  	};
  
  	/**
  	 * Moves the date to the next n'th occurrence of the dayOfWeek starting from the beginning of the month. The number (-1) is a magic number and will return the last occurrence of the dayOfWeek in the month.
  	 * @param {Number}   The dayOfWeek to move to
  	 * @param {Number}   The n'th occurrence to move to. Use (-1) to return the last occurrence in the month
  	 * @return {Date}    this
  	 */
  	$P.moveToNthOccurrence = function (dayOfWeek, occurrence) {
  		if (dayOfWeek === "Weekday") {
  			if (occurrence > 0) {
  				this.moveToFirstDayOfMonth();
  				if (this.is().weekday()) {
  					occurrence -= 1;
  				}
  			} else if (occurrence < 0) {
  				this.moveToLastDayOfMonth();
  				if (this.is().weekday()) {
  					occurrence += 1;
  				}
  			} else {
  				return this;
  			}
  			return this.addWeekdays(occurrence);
  		}
  		var shift = 0;
  		if (occurrence > 0) {
  			shift = occurrence - 1;
  		}
  		else if (occurrence === -1) {
  			this.moveToLastDayOfMonth();
  			if (this.getDay() !== dayOfWeek) {
  				this.moveToDayOfWeek(dayOfWeek, -1);
  			}
  			return this;
  		}
  		return this.moveToFirstDayOfMonth().addDays(-1).moveToDayOfWeek(dayOfWeek, +1).addWeeks(shift);
  	};
  
  
  	var moveToN = function (getFunc, addFunc, nVal) {
  		return function (value, orient) {
  			var diff = (value - this[getFunc]() + nVal * (orient || +1)) % nVal;
  			return this[addFunc]((diff === 0) ? diff += nVal * (orient || +1) : diff);
  		};
  	};
  	/**
  	 * Move to the next or last dayOfWeek based on the orient value.
  	 * @param {Number}   The dayOfWeek to move to
  	 * @param {Number}   Forward (+1) or Back (-1). Defaults to +1. [Optional]
  	 * @return {Date}    this
  	 */
  	$P.moveToDayOfWeek = moveToN("getDay", "addDays", 7);
  	/**
  	 * Move to the next or last month based on the orient value.
  	 * @param {Number}   The month to move to. 0 = January, 11 = December
  	 * @param {Number}   Forward (+1) or Back (-1). Defaults to +1. [Optional]
  	 * @return {Date}    this
  	 */
  	$P.moveToMonth = moveToN("getMonth", "addMonths", 12);
  	/**
  	 * Get the Ordinate of the current day ("th", "st", "rd").
  	 * @return {String} 
  	 */
  	$P.getOrdinate = function () {
  		var num = this.getDate();
  		return ord(num);
  	};
  	/**
  	 * Get the Ordinal day (numeric day number) of the year, adjusted for leap year.
  	 * @return {Number} 1 through 365 (366 in leap years)
  	 */
  	$P.getOrdinalNumber = function () {
  		return Math.ceil((this.clone().clearTime() - new Date(this.getFullYear(), 0, 1)) / 86400000) + 1;
  	};
  
  	/**
  	 * Get the time zone abbreviation of the current date.
  	 * @return {String} The abbreviated time zone name (e.g. "EST")
  	 */
  	$P.getTimezone = function () {
  		return $D.getTimezoneAbbreviation(this.getUTCOffset(), this.isDaylightSavingTime());
  	};
  
  	$P.setTimezoneOffset = function (offset) {
  		var here = this.getTimezoneOffset(), there = Number(offset) * -6 / 10;
  		return (there || there === 0) ? this.addMinutes(there - here) : this;
  	};
  
  	$P.setTimezone = function (offset) {
  		return this.setTimezoneOffset($D.getTimezoneOffset(offset));
  	};
  
  	/**
  	 * Indicates whether Daylight Saving Time is observed in the current time zone.
  	 * @return {Boolean} true|false
  	 */
  	$P.hasDaylightSavingTime = function () {
  		return (Date.today().set({month: 0, day: 1}).getTimezoneOffset() !== Date.today().set({month: 6, day: 1}).getTimezoneOffset());
  	};
  	
  	/**
  	 * Indicates whether this Date instance is within the Daylight Saving Time range for the current time zone.
  	 * @return {Boolean} true|false
  	 */
  	$P.isDaylightSavingTime = function () {
  		return Date.today().set({month: 0, day: 1}).getTimezoneOffset() !== this.getTimezoneOffset();
  	};
  
  	/**
  	 * Get the offset from UTC of the current date.
  	 * @return {String} The 4-character offset string prefixed with + or - (e.g. "-0500")
  	 */
  	$P.getUTCOffset = function (offset) {
  		var n = (offset || this.getTimezoneOffset()) * -10 / 6, r;
  		if (n < 0) {
  			r = (n - 10000).toString();
  			return r.charAt(0) + r.substr(2);
  		} else {
  			r = (n + 10000).toString();
  			return "+" + r.substr(1);
  		}
  	};
  
  	/**
  	 * Returns the number of milliseconds between this date and date.
  	 * @param {Date} Defaults to now
  	 * @return {Number} The diff in milliseconds
  	 */
  	$P.getElapsed = function (date) {
  		return (date || new Date()) - this;
  	};
  
  	/**
  	 * Set the value of year, month, day, hour, minute, second, millisecond of date instance using given configuration object.
  	 * Example
  	<pre><code>
  	Date.today().set( { day: 20, month: 1 } )
  
  	new Date().set( { millisecond: 0 } )
  	</code></pre>
  	 * 
  	 * @param {Object}   Configuration object containing attributes (month, day, etc.)
  	 * @return {Date}    this
  	 */
  	$P.set = function (config) {
  		config = validateConfigObject.call(this, config);
  		var key;
  		for (key in config) {
  			if (hasOwnProperty.call(config, key)) {
  				var name = key.charAt(0).toUpperCase() + key.slice(1);
  				var addFunc, getFunc;
  				if (key !== "week" && key !== "month" && key !== "timezone" && key !== "timezoneOffset") {
  					name += "s";
  				}
  				addFunc = "add" + name;
  				getFunc = "get" + name;
  				if (key === "month") {
  					addFunc = addFunc + "s";
  				} else if (key === "year"){
  					getFunc = "getFullYear";
  				}
  				if (key !== "day" && key !== "timezone" && key !== "timezoneOffset"  && key !== "week" &&  key !== "hour") {
  						this[addFunc](config[key] - this[getFunc]());
  				} else if ( key === "timezone"|| key === "timezoneOffset" || key === "week" || key === "hour") {
  					this["set"+name](config[key]);
  				}
  			}
  		}
  		// day has to go last because you can't validate the day without first knowing the month
  		if (config.day) {
  			this.addDays(config.day - this.getDate());
  		}
  		
  		return this;
  	};
  
  	/**
  	 * Moves the date to the first day of the month.
  	 * @return {Date}    this
  	 */
  	$P.moveToFirstDayOfMonth = function () {
  		return this.set({ day: 1 });
  	};
  
  	/**
  	 * Moves the date to the last day of the month.
  	 * @return {Date}    this
  	 */
  	$P.moveToLastDayOfMonth = function () {
  		return this.set({ day: $D.getDaysInMonth(this.getFullYear(), this.getMonth())});
  	};
  
  
  	/**
  	 * Converts the value of the current Date object to its equivalent string representation.
  	 * Format Specifiers
  	 * CUSTOM DATE AND TIME FORMAT STRINGS
  	 * Format  Description                                                                  Example
  	 * ------  ---------------------------------------------------------------------------  -----------------------
  	 * s      The seconds of the minute between 0-59.                                      "0" to "59"
  	 * ss     The seconds of the minute with leading zero if required.                     "00" to "59"
  	 * 
  	 * m      The minute of the hour between 0-59.                                         "0"  or "59"
  	 * mm     The minute of the hour with leading zero if required.                        "00" or "59"
  	 * 
  	 * h      The hour of the day between 1-12.                                            "1"  to "12"
  	 * hh     The hour of the day with leading zero if required.                           "01" to "12"
  	 * 
  	 * H      The hour of the day between 0-23.                                            "0"  to "23"
  	 * HH     The hour of the day with leading zero if required.                           "00" to "23"
  	 * 
  	 * d      The day of the month between 1 and 31.                                       "1"  to "31"
  	 * dd     The day of the month with leading zero if required.                          "01" to "31"
  	 * ddd    Abbreviated day name. Date.CultureInfo.abbreviatedDayNames.                                "Mon" to "Sun" 
  	 * dddd   The full day name. Date.CultureInfo.dayNames.                                              "Monday" to "Sunday"
  	 * 
  	 * M      The month of the year between 1-12.                                          "1" to "12"
  	 * MM     The month of the year with leading zero if required.                         "01" to "12"
  	 * MMM    Abbreviated month name. Date.CultureInfo.abbreviatedMonthNames.                            "Jan" to "Dec"
  	 * MMMM   The full month name. Date.CultureInfo.monthNames.                                          "January" to "December"
  	 *
  	 * yy     The year as a two-digit number.                                              "99" or "08"
  	 * yyyy   The full four digit year.                                                    "1999" or "2008"
  	 * 
  	 * t      Displays the first character of the A.M./P.M. designator.                    "A" or "P"
  	 *		Date.CultureInfo.amDesignator or Date.CultureInfo.pmDesignator
  	 * tt     Displays the A.M./P.M. designator.                                           "AM" or "PM"
  	 *		Date.CultureInfo.amDesignator or Date.CultureInfo.pmDesignator
  	 * 
  	 * S      The ordinal suffix ("st, "nd", "rd" or "th") of the current day.            "st, "nd", "rd" or "th"
  	 *
  	 * STANDARD DATE AND TIME FORMAT STRINGS
  	 * Format  Description                                                                  Example
  	 *------  ---------------------------------------------------------------------------  -----------------------
  	 * d      The CultureInfo shortDate Format Pattern                                     "M/d/yyyy"
  	 * D      The CultureInfo longDate Format Pattern                                      "dddd, MMMM dd, yyyy"
  	 * F      The CultureInfo fullDateTime Format Pattern                                  "dddd, MMMM dd, yyyy h:mm:ss tt"
  	 * m      The CultureInfo monthDay Format Pattern                                      "MMMM dd"
  	 * r      The CultureInfo rfc1123 Format Pattern                                       "ddd, dd MMM yyyy HH:mm:ss GMT"
  	 * s      The CultureInfo sortableDateTime Format Pattern                              "yyyy-MM-ddTHH:mm:ss"
  	 * t      The CultureInfo shortTime Format Pattern                                     "h:mm tt"
  	 * T      The CultureInfo longTime Format Pattern                                      "h:mm:ss tt"
  	 * u      The CultureInfo universalSortableDateTime Format Pattern                     "yyyy-MM-dd HH:mm:ssZ"
  	 * y      The CultureInfo yearMonth Format Pattern                                     "MMMM, yyyy"
  	 *
  	 * @param {String}   A format string consisting of one or more format spcifiers [Optional].
  	 * @return {String}  A string representation of the current Date object.
  	 */
  	
  	var ord = function (n) {
  		switch (n * 1) {
  		case 1:
  		case 21:
  		case 31:
  			return "st";
  		case 2:
  		case 22:
  			return "nd";
  		case 3:
  		case 23:
  			return "rd";
  		default:
  			return "th";
  		}
  	};
  	var parseStandardFormats = function (format) {
  		var y, c = Date.CultureInfo.formatPatterns;
  		switch (format) {
  			case "d":
  				return this.toString(c.shortDate);
  			case "D":
  				return this.toString(c.longDate);
  			case "F":
  				return this.toString(c.fullDateTime);
  			case "m":
  				return this.toString(c.monthDay);
  			case "r":
  			case "R":
  				y = this.clone().addMinutes(this.getTimezoneOffset());
  				return y.toString(c.rfc1123) + " GMT";
  			case "s":
  				return this.toString(c.sortableDateTime);
  			case "t":
  				return this.toString(c.shortTime);
  			case "T":
  				return this.toString(c.longTime);
  			case "u":
  				y = this.clone().addMinutes(this.getTimezoneOffset());
  				return y.toString(c.universalSortableDateTime);
  			case "y":
  				return this.toString(c.yearMonth);
  			default:
  				return false;
  		}
  	};
  	var parseFormatStringsClosure = function (context) {
  		return function (m) {
  			if (m.charAt(0) === "\\") {
  				return m.replace("\\", "");
  			}
  			switch (m) {
  				case "hh":
  					return p(context.getHours() < 13 ? (context.getHours() === 0 ? 12 : context.getHours()) : (context.getHours() - 12));
  				case "h":
  					return context.getHours() < 13 ? (context.getHours() === 0 ? 12 : context.getHours()) : (context.getHours() - 12);
  				case "HH":
  					return p(context.getHours());
  				case "H":
  					return context.getHours();
  				case "mm":
  					return p(context.getMinutes());
  				case "m":
  					return context.getMinutes();
  				case "ss":
  					return p(context.getSeconds());
  				case "s":
  					return context.getSeconds();
  				case "yyyy":
  					return p(context.getFullYear(), 4);
  				case "yy":
  					return p(context.getFullYear());
  				case "y":
  					return context.getFullYear();
  				case "E":
  				case "dddd":
  					return Date.CultureInfo.dayNames[context.getDay()];
  				case "ddd":
  					return Date.CultureInfo.abbreviatedDayNames[context.getDay()];
  				case "dd":
  					return p(context.getDate());
  				case "d":
  					return context.getDate();
  				case "MMMM":
  					return Date.CultureInfo.monthNames[context.getMonth()];
  				case "MMM":
  					return Date.CultureInfo.abbreviatedMonthNames[context.getMonth()];
  				case "MM":
  					return p((context.getMonth() + 1));
  				case "M":
  					return context.getMonth() + 1;
  				case "t":
  					return context.getHours() < 12 ? Date.CultureInfo.amDesignator.substring(0, 1) : Date.CultureInfo.pmDesignator.substring(0, 1);
  				case "tt":
  					return context.getHours() < 12 ? Date.CultureInfo.amDesignator : Date.CultureInfo.pmDesignator;
  				case "S":
  					return ord(context.getDate());
  				case "W":
  					return context.getWeek();
  				case "WW":
  					return context.getISOWeek();
  				case "Q":
  					return "Q" + context.getQuarter();
  				case "q":
  					return String(context.getQuarter());
  				case "z":
  					return context.getTimezone();
  				case "Z":
  				case "X":
  					return Date.getTimezoneOffset(context.getTimezone());
  				case "ZZ": // Timezone offset in seconds
  					return context.getTimezoneOffset() * -60;
  				case "u":
  					return context.getDay();
  				case "L":
  					return ($D.isLeapYear(context.getFullYear())) ? 1 : 0;
  				case "B":
  					// Swatch Internet Time (.beats)
  					return "@"+((context.getUTCSeconds() + (context.getUTCMinutes()*60) + ((context.getUTCHours()+1)*3600))/86.4);
  				default:
  					return m;
  			}
  		};
  	};
  	$P.toString = function (format, ignoreStandards) {
  		
  		// Standard Date and Time Format Strings. Formats pulled from CultureInfo file and
  		// may vary by culture. 
  		if (!ignoreStandards && format && format.length === 1) {
  			output = parseStandardFormats.call(this, format);
  			if (output) {
  				return output;
  			}
  		}
  		var parseFormatStrings = parseFormatStringsClosure(this);
  		return format ? format.replace(/((\\)?(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|S|q|Q|WW?W?W?)(?![^\[]*\]))/g, parseFormatStrings).replace(/\[|\]/g, "") : this._toString();
  	};
  
  }());
  /*************************************************************
   * SugarPak - Domain Specific Language -  Syntactical Sugar  *
   *************************************************************/
   
  (function () {
  	var $D = Date, $P = $D.prototype, $N = Number.prototype;
  
  	// private
  	$P._orient = +1;
  
  	// private
  	$P._nth = null;
  
  	// private
  	$P._is = false;
  
  	// private
  	$P._same = false;
  	
  	// private
  	$P._isSecond = false;
  
  	// private
  	$N._dateElement = "days";
  
  	/** 
  	 * Moves the date to the next instance of a date as specified by the subsequent date element function (eg. .day(), .month()), month name function (eg. .january(), .jan()) or day name function (eg. .friday(), fri()).
  	 * Example
  	<pre><code>
  	Date.today().next().friday();
  	Date.today().next().fri();
  	Date.today().next().march();
  	Date.today().next().mar();
  	Date.today().next().week();
  	</code></pre>
  	 * 
  	 * @return {Date}    date
  	 */
  	$P.next = function () {
  		this._move = true;
  		this._orient = +1;
  		return this;
  	};
  
  	/** 
  	 * Creates a new Date (Date.today()) and moves the date to the next instance of the date as specified by the subsequent date element function (eg. .day(), .month()), month name function (eg. .january(), .jan()) or day name function (eg. .friday(), fri()).
  	 * Example
  	<pre><code>
  	Date.next().friday();
  	Date.next().fri();
  	Date.next().march();
  	Date.next().mar();
  	Date.next().week();
  	</code></pre>
  	 * 
  	 * @return {Date}    date
  	 */
  	$D.next = function () {
  		return $D.today().next();
  	};
  
  	/** 
  	 * Moves the date to the previous instance of a date as specified by the subsequent date element function (eg. .day(), .month()), month name function (eg. .january(), .jan()) or day name function (eg. .friday(), fri()).
  	 * Example
  	<pre><code>
  	Date.today().last().friday();
  	Date.today().last().fri();
  	Date.today().last().march();
  	Date.today().last().mar();
  	Date.today().last().week();
  	</code></pre>
  	 *  
  	 * @return {Date}    date
  	 */
  	$P.last = $P.prev = $P.previous = function () {
  		this._move = true;
  		this._orient = -1;
  		return this;
  	};
  
  	/** 
  	 * Creates a new Date (Date.today()) and moves the date to the previous instance of the date as specified by the subsequent date element function (eg. .day(), .month()), month name function (eg. .january(), .jan()) or day name function (eg. .friday(), fri()).
  	 * Example
  	<pre><code>
  	Date.last().friday();
  	Date.last().fri();
  	Date.previous().march();
  	Date.prev().mar();
  	Date.last().week();
  	</code></pre>
  	 *  
  	 * @return {Date}    date
  	 */
  	$D.last = $D.prev = $D.previous = function () {
  		return $D.today().last();
  	};
  
  	/** 
  	 * Performs a equality check when followed by either a month name, day name or .weekday() function.
  	 * Example
  	<pre><code>
  	Date.today().is().friday(); // true|false
  	Date.today().is().fri();
  	Date.today().is().march();
  	Date.today().is().mar();
  	</code></pre>
  	 *  
  	 * @return {Boolean}    true|false
  	 */
  	$P.is = function () {
  		this._is = true;
  		return this;
  	};
  
  	/** 
  	 * Determines if two date objects occur on/in exactly the same instance of the subsequent date part function.
  	 * The function .same() must be followed by a date part function (example: .day(), .month(), .year(), etc).
  	 *
  	 * An optional Date can be passed in the date part function. If now date is passed as a parameter, 'Now' is used. 
  	 *
  	 * The following example demonstrates how to determine if two dates fall on the exact same day.
  	 *
  	 * Example
  	<pre><code>
  	var d1 = Date.today(); // today at 00:00
  	var d2 = new Date();   // exactly now.
  
  	// Do they occur on the same day?
  	d1.same().day(d2); // true
  	
  	// Do they occur on the same hour?
  	d1.same().hour(d2); // false, unless d2 hour is '00' (midnight).
  	
  	// What if it's the same day, but one year apart?
  	var nextYear = Date.today().add(1).year();
  
  	d1.same().day(nextYear); // false, because the dates must occur on the exact same day. 
  	</code></pre>
  	 *
  	 * Scenario: Determine if a given date occurs during some week period 2 months from now. 
  	 *
  	 * Example
  	<pre><code>
  	var future = Date.today().add(2).months();
  	return someDate.same().week(future); // true|false;
  	</code></pre>
  	 *  
  	 * @return {Boolean}    true|false
  	 */
  	$P.same = function () {
  		this._same = true;
  		this._isSecond = false;
  		return this;
  	};
  
  	/** 
  	 * Determines if the current date/time occurs during Today. Must be preceded by the .is() function.
  	 * Example
  	<pre><code>
  	someDate.is().today();    // true|false
  	new Date().is().today();  // true
  	Date.today().is().today();// true
  	Date.today().add(-1).day().is().today(); // false
  	</code></pre>
  	 *  
  	 * @return {Boolean}    true|false
  	 */
  	$P.today = function () {
  		return this.same().day();
  	};
  
  	/** 
  	 * Determines if the current date is a weekday. This function must be preceded by the .is() function.
  	 * Example
  	<pre><code>
  	Date.today().is().weekday(); // true|false
  	</code></pre>
  	 *  
  	 * @return {Boolean}    true|false
  	 */
  	$P.weekday = function () {
  		if (this._nth) {
  			return df("Weekday").call(this);
  		}
  		if (this._move) {
  			return this.addWeekdays(this._orient);
  		}
  		if (this._is) {
  			this._is = false;
  			return (!this.is().sat() && !this.is().sun());
  		}
  		return false;
  	};
  	/** 
  	 * Determines if the current date is on the weekend. This function must be preceded by the .is() function.
  	 * Example
  	<pre><code>
  	Date.today().is().weekend(); // true|false
  	</code></pre>
  	 *  
  	 * @return {Boolean}    true|false
  	 */
  	$P.weekend = function () {
  		if (this._is) {
  			this._is = false;
  			return (this.is().sat() || this.is().sun());
  		}
  		return false;
  	};
  
  	/** 
  	 * Sets the Time of the current Date instance. A string "6:15 pm" or config object {hour:18, minute:15} are accepted.
  	 * Example
  	<pre><code>
  	// Set time to 6:15pm with a String
  	Date.today().at("6:15pm");
  
  	// Set time to 6:15pm with a config object
  	Date.today().at({hour:18, minute:15});
  	</code></pre>
  	 *  
  	 * @return {Date}    date
  	 */
  	$P.at = function (time) {
  		return (typeof time === "string") ? $D.parse(this.toString("d") + " " + time) : this.set(time);
  	};
  		
  	/** 
  	 * Creates a new Date() and adds this (Number) to the date based on the preceding date element function (eg. second|minute|hour|day|month|year).
  	 * Example
  	<pre><code>
  	// Undeclared Numbers must be wrapped with parentheses. Requirment of JavaScript.
  	(3).days().fromNow();
  	(6).months().fromNow();
  
  	// Declared Number variables do not require parentheses. 
  	var n = 6;
  	n.months().fromNow();
  	</code></pre>
  	 *  
  	 * @return {Date}    A new Date instance
  	 */
  	$N.fromNow = $N.after = function (date) {
  		var c = {};
  		c[this._dateElement] = this;
  		return ((!date) ? new Date() : date.clone()).add(c);
  	};
  
  	/** 
  	 * Creates a new Date() and subtract this (Number) from the date based on the preceding date element function (eg. second|minute|hour|day|month|year).
  	 * Example
  	<pre><code>
  	// Undeclared Numbers must be wrapped with parentheses. Requirment of JavaScript.
  	(3).days().ago();
  	(6).months().ago();
  
  	// Declared Number variables do not require parentheses. 
  	var n = 6;
  	n.months().ago();
  	</code></pre>
  	 *  
  	 * @return {Date}    A new Date instance
  	 */
  	$N.ago = $N.before = function (date) {
  		var c = {},
  		s = (this._dateElement[this._dateElement.length-1] !== "s") ? this._dateElement + "s" : this._dateElement;
  		c[s] = this * -1;
  		return ((!date) ? new Date() : date.clone()).add(c);
  	};
  
  	// Do NOT modify the following string tokens. These tokens are used to build dynamic functions.
  	// All culture-specific strings can be found in the CultureInfo files.
  	var dx = ("sunday monday tuesday wednesday thursday friday saturday").split(/\s/),
  		mx = ("january february march april may june july august september october november december").split(/\s/),
  		px = ("Millisecond Second Minute Hour Day Week Month Year Quarter Weekday").split(/\s/),
  		pxf = ("Milliseconds Seconds Minutes Hours Date Week Month FullYear Quarter").split(/\s/),
  		nth = ("final first second third fourth fifth").split(/\s/),
  		de;
  
     /** 
  	 * Returns an object literal of all the date parts.
  	 * Example
  	<pre><code>
  	var o = new Date().toObject();
  	
  	// { year: 2008, month: 4, week: 20, day: 13, hour: 18, minute: 9, second: 32, millisecond: 812 }
  	
  	// The object properties can be referenced directly from the object.
  	
  	alert(o.day);  // alerts "13"
  	alert(o.year); // alerts "2008"
  	</code></pre>
  	 *  
  	 * @return {Date}    An object literal representing the original date object.
  	 */
  	$P.toObject = function () {
  		var o = {};
  		for (var i = 0; i < px.length; i++) {
  			if (this["get" + pxf[i]]) {
  				o[px[i].toLowerCase()] = this["get" + pxf[i]]();
  			}
  		}
  		return o;
  	};
     
     /** 
  	 * Returns a date created from an object literal. Ignores the .week property if set in the config. 
  	 * Example
  	<pre><code>
  	var o = new Date().toObject();
  	
  	return Date.fromObject(o); // will return the same date. 
  
  	var o2 = {month: 1, day: 20, hour: 18}; // birthday party!
  	Date.fromObject(o2);
  	</code></pre>
  	 *  
  	 * @return {Date}    An object literal representing the original date object.
  	 */
  	$D.fromObject = function(config) {
  		config.week = null;
  		return Date.today().set(config);
  	};
  		
  	// Create day name functions and abbreviated day name functions (eg. monday(), friday(), fri()).
  	
  	var df = function (n) {
  		return function () {
  			if (this._is) {
  				this._is = false;
  				return this.getDay() === n;
  			}
  			if (this._move) { this._move = null; }
  			if (this._nth !== null) {
  				// If the .second() function was called earlier, remove the _orient 
  				// from the date, and then continue.
  				// This is required because 'second' can be used in two different context.
  				// 
  				// Example
  				//
  				//   Date.today().add(1).second();
  				//   Date.march().second().monday();
  				// 
  				// Things get crazy with the following...
  				//   Date.march().add(1).second().second().monday(); // but it works!!
  				//  
  				if (this._isSecond) {
  					this.addSeconds(this._orient * -1);
  				}
  				// make sure we reset _isSecond
  				this._isSecond = false;
  
  				var ntemp = this._nth;
  				this._nth = null;
  				var temp = this.clone().moveToLastDayOfMonth();
  				this.moveToNthOccurrence(n, ntemp);
  				if (this > temp) {
  					throw new RangeError($D.getDayName(n) + " does not occur " + ntemp + " times in the month of " + $D.getMonthName(temp.getMonth()) + " " + temp.getFullYear() + ".");
  				}
  				return this;
  			}
  			return this.moveToDayOfWeek(n, this._orient);
  		};
  	};
  	
  	var sdf = function (n) {
  		return function () {
  			var t = $D.today(), shift = n - t.getDay();
  			if (n === 0 && Date.CultureInfo.firstDayOfWeek === 1 && t.getDay() !== 0) {
  				shift = shift + 7;
  			}
  			return t.addDays(shift);
  		};
  	};
  	
  
  	
  	// Create month name functions and abbreviated month name functions (eg. january(), march(), mar()).
  	var month_instance_functions = function (n) {
  		return function () {
  			if (this._is) {
  				this._is = false;
  				return this.getMonth() === n;
  			}
  			return this.moveToMonth(n, this._orient);
  		};
  	};
  	
  	var month_static_functions = function (n) {
  		return function () {
  			return $D.today().set({ month: n, day: 1 });
  		};
  	};
  	
  	var processTerms = function (names, staticFunc, instanceFunc) {
  		for (var i = 0; i < names.length; i++) {
  			// Create constant static Name variables.
  			$D[names[i].toUpperCase()] = $D[names[i].toUpperCase().substring(0, 3)] = i;
  			// Create Name functions.
  			$D[names[i]] = $D[names[i].substring(0, 3)] = staticFunc(i);
  			// Create Name instance functions.
  			$P[names[i]] = $P[names[i].substring(0, 3)] = instanceFunc(i);
  		}
  
  	};
  
  	processTerms(dx, sdf, df);
  	processTerms(mx, month_static_functions, month_instance_functions);
  	
  	// Create date element functions and plural date element functions used with Date (eg. day(), days(), months()).
  	var ef = function (j) {
  		return function () {
  			// if the .second() function was called earlier, the _orient 
  			// has alread been added. Just return this and reset _isSecond.
  			if (this._isSecond) {
  				this._isSecond = false;
  				return this;
  			}
  
  			if (this._same) {
  				this._same = this._is = false;
  				var o1 = this.toObject(),
  					o2 = (arguments[0] || new Date()).toObject(),
  					v = "",
  					k = j.toLowerCase();
  
  				// the substr trick with -1 doesn't work in IE8 or less
  				k = (k[k.length-1] === "s") ? k.substring(0,k.length-1) : k;
  					
  				for (var m = (px.length - 1); m > -1; m--) {
  					v = px[m].toLowerCase();
  					if (o1[v] !== o2[v]) {
  						return false;
  					}
  					if (k === v) {
  						break;
  					}
  				}
  				return true;
  			}
  			
  			if (j.substring(j.length - 1) !== "s") {
  				j += "s";
  			}
  			if (this._move) { this._move = null; }
  			return this["add" + j](this._orient);
  		};
  	};
  	
  	
  	var nf = function (n) {
  		return function () {
  			this._dateElement = n;
  			return this;
  		};
  	};
     
  	for (var k = 0; k < px.length; k++) {
  		de = px[k].toLowerCase();
  		if(de !== "weekday") {
  			// Create date element functions and plural date element functions used with Date (eg. day(), days(), months()).
  			$P[de] = $P[de + "s"] = ef(px[k]);
  			
  			// Create date element functions and plural date element functions used with Number (eg. day(), days(), months()).
  			$N[de] = $N[de + "s"] = nf(de + "s");
  		}
  	}
  	
  	$P._ss = ef("Second");
  	
  	var nthfn = function (n) {
  		return function (dayOfWeek) {
  			if (this._same) {
  				return this._ss(arguments[0]);
  			}
  			if (dayOfWeek || dayOfWeek === 0) {
  				return this.moveToNthOccurrence(dayOfWeek, n);
  			}
  			this._nth = n;
  
  			// if the operator is 'second' add the _orient, then deal with it later...
  			if (n === 2 && (dayOfWeek === undefined || dayOfWeek === null)) {
  				this._isSecond = true;
  				return this.addSeconds(this._orient);
  			}
  			return this;
  		};
  	};
  
  	for (var l = 0; l < nth.length; l++) {
  		$P[nth[l]] = (l === 0) ? nthfn(-1) : nthfn(l);
  	}
  }());
  
  (function () {
  	"use strict";
  	Date.Parsing = {
  		Exception: function (s) {
  			this.message = "Parse error at '" + s.substring(0, 10) + " ...'";
  		}
  	};
  	var $P = Date.Parsing;
  	var dayOffsets = {
  		standard: [0,31,59,90,120,151,181,212,243,273,304,334],
  		leap: [0,31,60,91,121,152,182,213,244,274,305,335]
  	};
  
  	$P.isLeapYear = function(year) {
  		return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
  	};
  
  	var utils = {
  		multiReplace : function (str, hash ) {
  			var key;
  			for (key in hash) {
  				if (Object.prototype.hasOwnProperty.call(hash, key)) {
  					var regex;
  					if (typeof hash[key] === "function") {
  
  					} else {
  						regex = (hash[key] instanceof RegExp) ? hash[key] : new RegExp(hash[key], "g");
  					}
  					str = str.replace(regex, key);
  				}
  			}
  			return str;
  		},
  		getDayOfYearFromWeek : function (obj) {
  			var d, jan4, offset;
  			obj.weekDay = (!obj.weekDay && obj.weekDay !== 0) ? 1 : obj.weekDay;
  			d = new Date(obj.year, 0, 4);
  			jan4 = d.getDay() === 0 ? 7 : d.getDay(); // JS is 0 indexed on Sunday.
  			offset = jan4+3;
  			obj.dayOfYear = ((obj.week * 7) + (obj.weekDay === 0 ? 7 : obj.weekDay))-offset;
  			return obj;
  		},
  		getDayOfYear : function (obj, dayOffset) {
  			if (!obj.dayOfYear) {
  				obj = utils.getDayOfYearFromWeek(obj);
  			}
  			for (var i=0;i <= dayOffset.length;i++) {
  				if (obj.dayOfYear < dayOffset[i] || i === dayOffset.length) {
  					obj.day = obj.day ? obj.day : (obj.dayOfYear - dayOffset[i-1]);
  					break;
  				} else {
  					obj.month = i;
  				}
  			}
  			return obj;
  		},
  		adjustForTimeZone : function (obj, date) {
  			var offset;
  			if (obj.zone.toUpperCase() === "Z" || (obj.zone_hours === 0 && obj.zone_minutes === 0)) {
  				// it's UTC/GML so work out the current timeszone offset
  				offset = -date.getTimezoneOffset();
  			} else {
  				offset = (obj.zone_hours*60) + (obj.zone_minutes || 0);
  				if (obj.zone_sign === "+") {
  					offset *= -1;
  				}
  				offset -= date.getTimezoneOffset();
  			}
  			date.setMinutes(date.getMinutes()+offset);
  			return date;
  		},
  		setDefaults : function (obj) {
  			obj.year = obj.year || Date.today().getFullYear();
  			obj.hours = obj.hours || 0;
  			obj.minutes = obj.minutes || 0;
  			obj.seconds = obj.seconds || 0;
  			obj.milliseconds = obj.milliseconds || 0;
  			if (!(!obj.month && (obj.week || obj.dayOfYear))) {
  				// if we have a month, or if we don't but don't have the day calculation data
  				obj.month = obj.month || 0;
  				obj.day = obj.day || 1;
  			}
  			return obj;
  		},
  		dataNum: function (data, mod, explict, postProcess) {
  			var dataNum = data*1;
  			if (mod) {
  				if (postProcess) {
  					return data ? mod(data)*1 : data;
  				} else {
  					return data ? mod(dataNum) : data;
  				}
  			} else if (!explict){
  				return data ? dataNum : data;
  			} else {
  				return (data && typeof data !== "undefined") ? dataNum : data;
  			}
  		},
  		timeDataProcess: function (obj) {
  			var timeObj = {};
  			for (var x in obj.data) {
  				if (obj.data.hasOwnProperty(x)) {
  					timeObj[x] = obj.ignore[x] ? obj.data[x] : utils.dataNum(obj.data[x], obj.mods[x], obj.explict[x], obj.postProcess[x]);
  				}
  			}
  			if (obj.data.secmins) {
  				obj.data.secmins = obj.data.secmins.replace(",", ".") * 60;
  				if (!timeObj.minutes) {
  					timeObj.minutes = obj.data.secmins;
  				} else if (!timeObj.seconds) {
  					timeObj.seconds = obj.data.secmins;
  				}
  				delete obj.secmins;
  			}
  			return timeObj;
  		},
  		buildTimeObjectFromData: function (data) {
  			var time = utils.timeDataProcess({
  				data: {
  					year : data[1],
  					month : data[5],
  					day : data[7],
  					week : data[8],
  					dayOfYear : data[10],
  					hours : data[15],
  					zone_hours : data[23],
  					zone_minutes : data[24],
  					zone : data[21],
  					zone_sign : data[22],
  					weekDay : data[9],
  					minutes: data[16],
  					seconds: data[19],
  					milliseconds: data[20],
  					secmins: data[18]
  				},
  				mods: {
  					month: function(data) {
  						return data-1;
  					},
  					weekDay: function (data) {
  						data = Math.abs(data);
  						return (data === 7 ? 0 : data);
  					},
  					minutes: function (data) {
  						return data.replace(":","");
  					},
  					seconds: function (data) {
  						return Math.floor( (data.replace(":","").replace(",","."))*1 );
  					},
  					milliseconds: function (data) {
  						return (data.replace(",",".")*1000);
  					}
  				},
  				postProcess: {
  					minutes: true,
  					seconds: true,
  					milliseconds: true
  				},
  				explict: {
  					zone_hours: true,
  					zone_minutes: true
  				},
  				ignore: {
  					zone: true,
  					zone_sign: true,
  					secmins: true
  				}
  			});
  			return time;
  		},
  		addToHash: function (hash, keys, data) {
  			keys = keys;
  			data = data;
  			var len = keys.length;
  			for (var i = 0; i < len; i++) {
  			  hash[keys[i]] = data[i];
  			}
  			return hash;
  		},
  		combineRegex: function (r1, r2) {
  			return new RegExp("(("+r1.source+")\\s("+r2.source+"))");
  		},
  		getDateNthString: function(add, last, inc){
  			if (add) {
  				return Date.today().addDays(inc).toString("d");
  			} else if (last) {
  				return Date.today().last()[inc]().toString("d");
  			}
  
  		},
  		buildRegexData: function (array) {
  			var arr = [];
  			var len = array.length;
  			for (var i=0; i < len; i++) {
  				if (Object.prototype.toString.call(array[i]) === '[object Array]') { // oldIE compat version of Array.isArray
  					arr.push(this.combineRegex(array[i][0], array[i][1]));
  				} else {
  					arr.push(array[i]);
  				}
  			}
  			return arr;
  		}
  	};
  
  	$P.processTimeObject = function (obj) {
  		var date, dayOffset;
  
  		utils.setDefaults(obj);
  		dayOffset = ($P.isLeapYear(obj.year)) ? dayOffsets.leap : dayOffsets.standard;
  
  		if (!obj.month && (obj.week || obj.dayOfYear)) {
  			utils.getDayOfYear(obj, dayOffset);
  		} else {
  			obj.dayOfYear = dayOffset[obj.month] + obj.day;
  		}
  
  		date = new Date(obj.year, obj.month, obj.day, obj.hours, obj.minutes, obj.seconds, obj.milliseconds);
  
  		if (obj.zone) {
  			utils.adjustForTimeZone(obj, date); // adjust (and calculate) for timezone
  		}
  		return date;
  	};
  
  	$P.ISO = {
  		regex : /^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-3])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-4])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?\s?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/,
  		parse : function (s) {
  			var time, data = s.match(this.regex);
  			if (!data || !data.length) {
  				return null;
  			}
  
  			time = utils.buildTimeObjectFromData(data);
  
  			if (!time.year || (!time.year && (!time.month && !time.day) && (!time.week && !time.dayOfYear)) ) {
  				return null;
  			}
  			return $P.processTimeObject(time);
  		}
  	};
  
  	$P.Numeric = {
  		isNumeric: function (e){return!isNaN(parseFloat(e))&&isFinite(e);},
  		regex: /\b([0-1]?[0-9])([0-3]?[0-9])([0-2]?[0-9]?[0-9][0-9])\b/i,
  		parse: function (s) {
  			var data, i,
  				time = {},
  				order = Date.CultureInfo.dateElementOrder.split("");
  			if (!(this.isNumeric(s)) || // if it's non-numeric OR
  				(s[0] === "+" && s[0] === "-")) {			// It's an arithmatic string (eg +/-1000)
  				return null;
  			}
  			if (s.length < 5 && s.indexOf(".") < 0 && s.indexOf("/") < 0) { // assume it's just a year.
  				time.year = s;
  				return $P.processTimeObject(time);
  			}
  			data = s.match(this.regex);
  			if (!data || !data.length) {
  				return null;
  			}
  			for (i=0; i < order.length; i++) {
  				switch(order[i]) {
  					case "d":
  						time.day = data[i+1];
  						break;
  					case "m":
  						time.month = (data[i+1]-1);
  						break;
  					case "y":
  						time.year = data[i+1];
  						break;
  				}
  			}
  			return $P.processTimeObject(time);
  		}
  	};
  
  	$P.Normalizer = {
  		regexData: function () {
  			var $R = Date.CultureInfo.regexPatterns;
  			return utils.buildRegexData([
  				$R.tomorrow,
  				$R.yesterday,
  				[$R.past, $R.mon],
  				[$R.past, $R.tue],
  				[$R.past, $R.wed],
  				[$R.past, $R.thu],
  				[$R.past, $R.fri],
  				[$R.past, $R.sat],
  				[$R.past, $R.sun]
  			]);
  		},
  		basicReplaceHash : function() {
  			var $R = Date.CultureInfo.regexPatterns;
  			return {
  				"January": $R.jan.source,
  				"February": $R.feb,
  				"March": $R.mar,
  				"April": $R.apr,
  				"May": $R.may,
  				"June": $R.jun,
  				"July": $R.jul,
  				"August": $R.aug,
  				"September": $R.sep,
  				"October": $R.oct,
  				"November": $R.nov,
  				"December": $R.dec,
  				"": /\bat\b/gi,
  				" ": /\s{2,}/,
  				"am": $R.inTheMorning,
  				"9am": $R.thisMorning,
  				"pm": $R.inTheEvening,
  				"7pm":$R.thisEvening
  			};
  		},
  		keys : function(){
  			return [
  				utils.getDateNthString(true, false, 1),				// tomorrow
  				utils.getDateNthString(true, false, -1),			// yesterday
  				utils.getDateNthString(false, true, "monday"),		//last mon
  				utils.getDateNthString(false, true, "tuesday"),		//last tues
  				utils.getDateNthString(false, true, "wednesday"),	//last wed
  				utils.getDateNthString(false, true, "thursday"),	//last thurs
  				utils.getDateNthString(false, true, "friday"),		//last fri
  				utils.getDateNthString(false, true, "saturday"),	//last sat
  				utils.getDateNthString(false, true, "sunday")		//last sun
  			];
  		},
  		buildRegexFunctions: function () {
  			var $R = Date.CultureInfo.regexPatterns;
  			var __ = Date.i18n.__;
  			var tomorrowRE = new RegExp("(\\b\\d\\d?("+__("AM")+"|"+__("PM")+")? )("+$R.tomorrow.source.slice(1)+")", "i"); // adapted tomorrow regex for AM PM relative dates
  			var todayRE = new RegExp($R.today.source + "(?!\\s*([+-]))\\b"); // today, but excludes the math operators (eg "today + 2h")
  
  			this.replaceFuncs = [
  				[todayRE, function (full) {
  					return (full.length > 1) ? Date.today().toString("d") : full;
  				}],
  				[tomorrowRE,
  				function(full, m1) {
  					var t = Date.today().addDays(1).toString("d");
  					return (t + " " + m1);
  				}],
  				[$R.amThisMorning, function(str, am){return am;}],
  				[$R.pmThisEvening, function(str, pm){return pm;}]
  			];
  
  		},
  		buildReplaceData: function () {
  			this.buildRegexFunctions();
  			this.replaceHash = utils.addToHash(this.basicReplaceHash(), this.keys(), this.regexData());
  		},
  		stringReplaceFuncs: function (s) {
  			for (var i=0; i < this.replaceFuncs.length; i++) {
  				s = s.replace(this.replaceFuncs[i][0], this.replaceFuncs[i][1]);
  			}
  			return s;
  		},
  		parse: function (s) {
  			s = this.stringReplaceFuncs(s);
  			s = utils.multiReplace(s, this.replaceHash);
  
  			try {
  				var n = s.split(/([\s\-\.\,\/\x27]+)/);
  				if (n.length === 3 &&
  					$P.Numeric.isNumeric(n[0]) &&
  					$P.Numeric.isNumeric(n[2]) &&
  					(n[2].length >= 4)) {
  						// ok, so we're dealing with x/year. But that's not a full date.
  						// This fixes wonky dateElementOrder parsing when set to dmy order.
  						if (Date.CultureInfo.dateElementOrder[0] === "d") {
  							s = "1/" + n[0] + "/" + n[2]; // set to 1st of month and normalize the seperator
  						}
  				}
  			} catch (e) {}
  
  			return s;
  		}
  	};
  	$P.Normalizer.buildReplaceData();
  }());
  (function () {
  	var $P = Date.Parsing;
  	var _ = $P.Operators = {
  		//
  		// Tokenizers
  		//
  		rtoken: function (r) { // regex token
  			return function (s) {
  				var mx = s.match(r);
  				if (mx) {
  					return ([ mx[0], s.substring(mx[0].length) ]);
  				} else {
  					throw new $P.Exception(s);
  				}
  			};
  		},
  		token: function () { // whitespace-eating token
  			return function (s) {
  				return _.rtoken(new RegExp("^\\s*" + s + "\\s*"))(s);
  			};
  		},
  		stoken: function (s) { // string token
  			return _.rtoken(new RegExp("^" + s));
  		},
  
  		// Atomic Operators
  
  		until: function (p) {
  			return function (s) {
  				var qx = [], rx = null;
  				while (s.length) {
  					try {
  						rx = p.call(this, s);
  					} catch (e) {
  						qx.push(rx[0]);
  						s = rx[1];
  						continue;
  					}
  					break;
  				}
  				return [ qx, s ];
  			};
  		},
  		many: function (p) {
  			return function (s) {
  				var rx = [], r = null;
  				while (s.length) {
  					try {
  						r = p.call(this, s);
  					} catch (e) {
  						return [ rx, s ];
  					}
  					rx.push(r[0]);
  					s = r[1];
  				}
  				return [ rx, s ];
  			};
  		},
  
  		// generator operators -- see below
  		optional: function (p) {
  			return function (s) {
  				var r = null;
  				try {
  					r = p.call(this, s);
  				} catch (e) {
  					return [ null, s ];
  				}
  				return [ r[0], r[1] ];
  			};
  		},
  		not: function (p) {
  			return function (s) {
  				try {
  					p.call(this, s);
  				} catch (e) {
  					return [null, s];
  				}
  				throw new $P.Exception(s);
  			};
  		},
  		ignore: function (p) {
  			return p ?
  			function (s) {
  				var r = null;
  				r = p.call(this, s);
  				return [null, r[1]];
  			} : null;
  		},
  		product: function () {
  			var px = arguments[0],
  			qx = Array.prototype.slice.call(arguments, 1), rx = [];
  			for (var i = 0 ; i < px.length ; i++) {
  				rx.push(_.each(px[i], qx));
  			}
  			return rx;
  		},
  		cache: function (rule) {
  			var cache = {}, cache_length = 0, cache_keys = [], CACHE_MAX = Date.Config.CACHE_MAX || 100000, r = null;
  			var cacheCheck = function () {
  				if (cache_length === CACHE_MAX) {
  					// kill several keys, don't want to have to do this all the time...
  					for (var i=0; i < 10; i++) {
  						var key = cache_keys.shift();
  						if (key) {
  							delete cache[key];
  							cache_length--;
  						}
  					}
  				}
  			};
  			return function (s) {
  				cacheCheck();
  				try {
  					r = cache[s] = (cache[s] || rule.call(this, s));
  				} catch (e) {
  					r = cache[s] = e;
  				}
  				cache_length++;
  				cache_keys.push(s);
  				if (r instanceof $P.Exception) {
  					throw r;
  				} else {
  					return r;
  				}
  			};
  		},
  
  		// vector operators -- see below
  		any: function () {
  			var px = arguments;
  			return function (s) {
  				var r = null;
  				for (var i = 0; i < px.length; i++) {
  					if (px[i] == null) {
  						continue;
  					}
  					try {
  						r = (px[i].call(this, s));
  					} catch (e) {
  						r = null;
  					}
  					if (r) {
  						return r;
  					}
  				}
  				throw new $P.Exception(s);
  			};
  		},
  		each: function () {
  			var px = arguments;
  			return function (s) {
  				var rx = [], r = null;
  				for (var i = 0; i < px.length ; i++) {
  					if (px[i] == null) {
  						continue;
  					}
  					try {
  						r = (px[i].call(this, s));
  					} catch (e) {
  						throw new $P.Exception(s);
  					}
  					rx.push(r[0]);
  					s = r[1];
  				}
  				return [ rx, s];
  			};
  		},
  		all: function () {
  			var px = arguments, _ = _;
  			return _.each(_.optional(px));
  		},
  
  		// delimited operators
  		sequence: function (px, d, c) {
  			d = d || _.rtoken(/^\s*/);
  			c = c || null;
  			
  			if (px.length === 1) {
  				return px[0];
  			}
  			return function (s) {
  				var r = null, q = null;
  				var rx = [];
  				for (var i = 0; i < px.length ; i++) {
  					try {
  						r = px[i].call(this, s);
  					} catch (e) {
  						break;
  					}
  					rx.push(r[0]);
  					try {
  						q = d.call(this, r[1]);
  					} catch (ex) {
  						q = null;
  						break;
  					}
  					s = q[1];
  				}
  				if (!r) {
  					throw new $P.Exception(s);
  				}
  				if (q) {
  					throw new $P.Exception(q[1]);
  				}
  				if (c) {
  					try {
  						r = c.call(this, r[1]);
  					} catch (ey) {
  						throw new $P.Exception(r[1]);
  					}
  				}
  				return [ rx, (r?r[1]:s) ];
  			};
  		},
  
  		//
  		// Composite Operators
  		//
  
  		between: function (d1, p, d2) {
  			d2 = d2 || d1;
  			var _fn = _.each(_.ignore(d1), p, _.ignore(d2));
  			return function (s) {
  				var rx = _fn.call(this, s);
  				return [[rx[0][0], r[0][2]], rx[1]];
  			};
  		},
  		list: function (p, d, c) {
  			d = d || _.rtoken(/^\s*/);
  			c = c || null;
  			return (p instanceof Array ?
  				_.each(_.product(p.slice(0, -1), _.ignore(d)), p.slice(-1), _.ignore(c)) :
  				_.each(_.many(_.each(p, _.ignore(d))), px, _.ignore(c)));
  		},
  		set: function (px, d, c) {
  			d = d || _.rtoken(/^\s*/);
  			c = c || null;
  			return function (s) {
  				// r is the current match, best the current 'best' match
  				// which means it parsed the most amount of input
  				var r = null, p = null, q = null, rx = null, best = [[], s], last = false;
  				// go through the rules in the given set
  				for (var i = 0; i < px.length ; i++) {
  
  					// last is a flag indicating whether this must be the last element
  					// if there is only 1 element, then it MUST be the last one
  					q = null;
  					p = null;
  					r = null;
  					last = (px.length === 1);
  					// first, we try simply to match the current pattern
  					// if not, try the next pattern
  					try {
  						r = px[i].call(this, s);
  					} catch (e) {
  						continue;
  					}
  					// since we are matching against a set of elements, the first
  					// thing to do is to add r[0] to matched elements
  					rx = [[r[0]], r[1]];
  					// if we matched and there is still input to parse and 
  					// we don't already know this is the last element,
  					// we're going to next check for the delimiter ...
  					// if there's none, or if there's no input left to parse
  					// than this must be the last element after all ...
  					if (r[1].length > 0 && ! last) {
  						try {
  							q = d.call(this, r[1]);
  						} catch (ex) {
  							last = true;
  						}
  					} else {
  						last = true;
  					}
  
  					// if we parsed the delimiter and now there's no more input,
  					// that means we shouldn't have parsed the delimiter at all
  					// so don't update r and mark this as the last element ...
  					if (!last && q[1].length === 0) {
  						last = true;
  					}
  
  
  					// so, if this isn't the last element, we're going to see if
  					// we can get any more matches from the remaining (unmatched)
  					// elements ...
  					if (!last) {
  						// build a list of the remaining rules we can match against,
  						// i.e., all but the one we just matched against
  						var qx = [];
  						for (var j = 0; j < px.length ; j++) {
  							if (i !== j) {
  								qx.push(px[j]);
  							}
  						}
  
  						// now invoke recursively set with the remaining input
  						// note that we don't include the closing delimiter ...
  						// we'll check for that ourselves at the end
  						p = _.set(qx, d).call(this, q[1]);
  
  						// if we got a non-empty set as a result ...
  						// (otw rx already contains everything we want to match)
  						if (p[0].length > 0) {
  							// update current result, which is stored in rx ...
  							// basically, pick up the remaining text from p[1]
  							// and concat the result from p[0] so that we don't
  							// get endless nesting ...
  							rx[0] = rx[0].concat(p[0]);
  							rx[1] = p[1];
  						}
  					}
  
  					// at this point, rx either contains the last matched element
  					// or the entire matched set that starts with this element.
  
  					// now we just check to see if this variation is better than
  					// our best so far, in terms of how much of the input is parsed
  					if (rx[1].length < best[1].length) {
  						best = rx;
  					}
  
  					// if we've parsed all the input, then we're finished
  					if (best[1].length === 0) {
  						break;
  					}
  				}
  
  				// so now we've either gone through all the patterns trying them
  				// as the initial match; or we found one that parsed the entire
  				// input string ...
  
  				// if best has no matches, just return empty set ...
  				if (best[0].length === 0) {
  					return best;
  				}
  
  				// if a closing delimiter is provided, then we have to check it also
  				if (c) {
  					// we try this even if there is no remaining input because the pattern
  					// may well be optional or match empty input ...
  					try {
  						q = c.call(this, best[1]);
  					} catch (ey) {
  						throw new $P.Exception(best[1]);
  					}
  
  					// it parsed ... be sure to update the best match remaining input
  					best[1] = q[1];
  				}
  				// if we're here, either there was no closing delimiter or we parsed it
  				// so now we have the best match; just return it!
  				return best;
  			};
  		},
  		forward: function (gr, fname) {
  			return function (s) {
  				return gr[fname].call(this, s);
  			};
  		},
  
  		//
  		// Translation Operators
  		//
  		replace: function (rule, repl) {
  			return function (s) {
  				var r = rule.call(this, s);
  				return [repl, r[1]];
  			};
  		},
  		process: function (rule, fn) {
  			return function (s) {
  				var r = rule.call(this, s);
  				return [fn.call(this, r[0]), r[1]];
  			};
  		},
  		min: function (min, rule) {
  			return function (s) {
  				var rx = rule.call(this, s);
  				if (rx[0].length < min) {
  					throw new $P.Exception(s);
  				}
  				return rx;
  			};
  		}
  	};
  	
  
  	// Generator Operators And Vector Operators
  
  	// Generators are operators that have a signature of F(R) => R,
  	// taking a given rule and returning another rule, such as 
  	// ignore, which parses a given rule and throws away the result.
  
  	// Vector operators are those that have a signature of F(R1,R2,...) => R,
  	// take a list of rules and returning a new rule, such as each.
  
  	// Generator operators are converted (via the following _generator
  	// function) into functions that can also take a list or array of rules
  	// and return an array of new rules as though the function had been
  	// called on each rule in turn (which is what actually happens).
  
  	// This allows generators to be used with vector operators more easily.
  	// Example:
  	// each(ignore(foo, bar)) instead of each(ignore(foo), ignore(bar))
  
  	// This also turns generators into vector operators, which allows
  	// constructs like:
  	// not(cache(foo, bar))
  	
  	var _generator = function (op) {
  		function gen() {
  			var args = null, rx = [], px, i;
  			if (arguments.length > 1) {
  				args = Array.prototype.slice.call(arguments);
  			} else if (arguments[0] instanceof Array) {
  				args = arguments[0];
  			}
  			if (args) {
  				px = args.shift();
  				if (px.length > 0) {
  					args.unshift(px[i]);
  					rx.push(op.apply(null, args));
  					args.shift();
  					return rx;
  				}
  			} else {
  				return op.apply(null, arguments);
  			}
  		}
  
  		return gen;
  	};
  	
  	var gx = "optional not ignore cache".split(/\s/);
  	
  	for (var i = 0 ; i < gx.length ; i++) {
  		_[gx[i]] = _generator(_[gx[i]]);
  	}
  
  	var _vector = function (op) {
  		return function () {
  			if (arguments[0] instanceof Array) {
  				return op.apply(null, arguments[0]);
  			} else {
  				return op.apply(null, arguments);
  			}
  		};
  	};
  	
  	var vx = "each any all".split(/\s/);
  	
  	for (var j = 0 ; j < vx.length ; j++) {
  		_[vx[j]] = _vector(_[vx[j]]);
  	}
  	
  }());
  (function () {
  	var $D = Date;
  
  	var flattenAndCompact = function (ax) {
  		var rx = [];
  		for (var i = 0; i < ax.length; i++) {
  			if (ax[i] instanceof Array) {
  				rx = rx.concat(flattenAndCompact(ax[i]));
  			} else {
  				if (ax[i]) {
  					rx.push(ax[i]);
  				}
  			}
  		}
  		return rx;
  	};
  
  	var parseMeridian = function () {
  		if (this.meridian && (this.hour || this.hour === 0)) {
  			if (this.meridian === "a" && this.hour > 11 && Date.Config.strict24hr){
  				throw "Invalid hour and meridian combination";
  			} else if (this.meridian === "p" && this.hour < 12 && Date.Config.strict24hr){
  				throw "Invalid hour and meridian combination";
  			} else if (this.meridian === "p" && this.hour < 12) {
  				this.hour = this.hour + 12;
  			} else if (this.meridian === "a" && this.hour === 12) {
  				this.hour = 0;
  			}
  		}
  	};
  
  	var setDefaults = function () {
  		var now = new Date();
  		if ((this.hour || this.minute) && (!this.month && !this.year && !this.day)) {
  			this.day = now.getDate();
  		}
  
  		if (!this.year) {
  			this.year = now.getFullYear();
  		}
  		
  		if (!this.month && this.month !== 0) {
  			this.month = now.getMonth();
  		}
  		
  		if (!this.day) {
  			this.day = 1;
  		}
  		
  		if (!this.hour) {
  			this.hour = 0;
  		}
  		
  		if (!this.minute) {
  			this.minute = 0;
  		}
  
  		if (!this.second) {
  			this.second = 0;
  		}
  		if (!this.millisecond) {
  			this.millisecond = 0;
  		}
  	};
  
  	var finishUtils = {
  		getToday: function () {
  			 if (this.now || "hour minute second".indexOf(this.unit) !== -1) {
  				return new Date();
  			} else {
  				return $D.today();
  			}
  		},
  		setDaysFromWeekday: function (today, orient){
  			var gap;
  			orient = orient || 1;
  			this.unit = "day";
  			gap = ($D.getDayNumberFromName(this.weekday) - today.getDay());
  			this.days = gap ? ((gap + (orient * 7)) % 7) : (orient * 7);
  			return this;
  		},
  		setMonthsFromMonth: function (today, orient) {
  			var gap;
  			orient = orient || 1;
  			this.unit = "month";
  			gap = (this.month - today.getMonth());
  			this.months = gap ? ((gap + (orient * 12)) % 12) : (orient * 12);
  			this.month = null;
  			return this;
  		},
  		setDMYFromWeekday: function () {
  			var d = Date[this.weekday]();
  			this.day = d.getDate();
  			if (!this.month) {
  				this.month = d.getMonth();
  			}
  			this.year = d.getFullYear();
  			return this;
  		},
  		setUnitValue: function (orient) {
  			if (!this.value && this.operator && this.operator !== null && this[this.unit + "s"] && this[this.unit + "s"] !== null) {
  				this[this.unit + "s"] = this[this.unit + "s"] + ((this.operator === "add") ? 1 : -1) + (this.value||0) * orient;
  			} else if (this[this.unit + "s"] == null || this.operator != null) {
  				if (!this.value) {
  					this.value = 1;
  				}
  				this[this.unit + "s"] = this.value * orient;
  			}
  		},
  		generateDateFromWeeks: function () {
  			var weekday = (this.weekday !== undefined) ? this.weekday : "today";
  			var d = Date[weekday]().addWeeks(this.weeks);
  			if (this.now) {
  				d.setTimeToNow();
  			}
  			return d;
  		}
  	};
  
  	$D.Translator = {
  		hour: function (s) {
  			return function () {
  				this.hour = Number(s);
  			};
  		},
  		minute: function (s) {
  			return function () {
  				this.minute = Number(s);
  			};
  		},
  		second: function (s) {
  			return function () {
  				this.second = Number(s);
  			};
  		},
  		/* for ss.s format */
  		secondAndMillisecond: function (s) {
  			return function () {
  				var mx = s.match(/^([0-5][0-9])\.([0-9]{1,3})/);
  				this.second = Number(mx[1]);
  				this.millisecond = Number(mx[2]);
  			};
  		},
  		meridian: function (s) {
  			return function () {
  				this.meridian = s.slice(0, 1).toLowerCase();
  			};
  		},
  		timezone: function (s) {
  			return function () {
  				var n = s.replace(/[^\d\+\-]/g, "");
  				if (n.length) {
  					this.timezoneOffset = Number(n);
  				} else {
  					this.timezone = s.toLowerCase();
  				}
  			};
  		},
  		day: function (x) {
  			var s = x[0];
  			return function () {
  				this.day = Number(s.match(/\d+/)[0]);
  				if (this.day < 1) {
  					throw "invalid day";
  				}
  			};
  		},
  		month: function (s) {
  			return function () {
  				this.month = (s.length === 3) ? "jan feb mar apr may jun jul aug sep oct nov dec".indexOf(s)/4 : Number(s) - 1;
  				if (this.month < 0) {
  					throw "invalid month";
  				}
  			};
  		},
  		year: function (s) {
  			return function () {
  				var n = Number(s);
  				this.year = ((s.length > 2) ? n :
  					(n + (((n + 2000) < Date.CultureInfo.twoDigitYearMax) ? 2000 : 1900)));
  			};
  		},
  		rday: function (s) {
  			return function () {
  				switch (s) {
  					case "yesterday":
  						this.days = -1;
  						break;
  					case "tomorrow":
  						this.days = 1;
  						break;
  					case "today":
  						this.days = 0;
  						break;
  					case "now":
  						this.days = 0;
  						this.now = true;
  						break;
  				}
  			};
  		},
  		finishExact: function (x) {
  			var d;
  			x = (x instanceof Array) ? x : [x];
  
  			for (var i = 0 ; i < x.length ; i++) {
  				if (x[i]) {
  					x[i].call(this);
  				}
  			}
  			
  			setDefaults.call(this);
  			parseMeridian.call(this);
  
  			if (this.day > $D.getDaysInMonth(this.year, this.month)) {
  				throw new RangeError(this.day + " is not a valid value for days.");
  			}
  
  			d = new Date(this.year, this.month, this.day, this.hour, this.minute, this.second, this.millisecond);
  			if (this.year < 100) {
  				d.setFullYear(this.year); // means years less that 100 are process correctly. JS will parse it otherwise as 1900-1999.
  			}
  			if (this.timezone) {
  				d.set({ timezone: this.timezone });
  			} else if (this.timezoneOffset) {
  				d.set({ timezoneOffset: this.timezoneOffset });
  			}
  			
  			return d;
  		},
  		finish: function (x) {
  			var today, expression, orient, temp;
  
  			x = (x instanceof Array) ? flattenAndCompact(x) : [ x ];
  
  			if (x.length === 0) {
  				return null;
  			}
  
  			for (var i = 0 ; i < x.length ; i++) {
  				if (typeof x[i] === "function") {
  					x[i].call(this);
  				}
  			}
  			if (this.now && !this.unit && !this.operator) {
  				return new Date();
  			} else {
  				today = finishUtils.getToday.call(this);
  			}
  			
  			expression = !!(this.days && this.days !== null || this.orient || this.operator);
  			orient = ((this.orient === "past" || this.operator === "subtract") ? -1 : 1);
  
  			if (this.month && this.unit === "week") {
  				this.value = this.month + 1;
  				delete this.month;
  				delete this.day;
  			}
  
  			if ((this.month || this.month === 0) && "year day hour minute second".indexOf(this.unit) !== -1) {
  				if (!this.value) {
  					this.value = this.month + 1;
  				}
  				this.month = null;
  				expression = true;
  			}
  
  			if (!expression && this.weekday && !this.day && !this.days) {
  				finishUtils.setDMYFromWeekday.call(this);
  			}
  
  			if (expression && this.weekday && this.unit !== "month" && this.unit !== "week") {
  				finishUtils.setDaysFromWeekday.call(this, today, orient);
  			}
  
  			if (this.weekday && this.unit !== "week" && !this.day && !this.days) {
  				temp = Date[this.weekday]();
  				this.day = temp.getDate();
  				if (temp.getMonth() !== today.getMonth()) {
  					this.month = temp.getMonth();
  				}
  			}
  
  			if (this.month && this.unit === "day" && this.operator) {
  				if (!this.value) {
  					this.value = (this.month + 1);
  				}
  				this.month = null;
  			}
  
  			if (this.value != null && this.month != null && this.year != null) {
  				this.day = this.value * 1;
  			}
  
  			if (this.month && !this.day && this.value) {
  				today.set({ day: this.value * 1 });
  				if (!expression) {
  					this.day = this.value * 1;
  				}
  			}
  
  			if (!this.month && this.value && this.unit === "month" && !this.now) {
  				this.month = this.value;
  				expression = true;
  			}
  
  			if (expression && (this.month || this.month === 0) && this.unit !== "year") {
  				finishUtils.setMonthsFromMonth.call(this, today, orient);
  			}
  
  			if (!this.unit) {
  				this.unit = "day";
  			}
  
  			finishUtils.setUnitValue.call(this, orient);
  			parseMeridian.call(this);
  			
  			if ((this.month || this.month === 0) && !this.day) {
  				this.day = 1;
  			}
  
  			if (!this.orient && !this.operator && this.unit === "week" && this.value && !this.day && !this.month) {
  				return Date.today().setWeek(this.value);
  			}
  
  			if (this.unit === "week" && this.weeks && !this.day && !this.month) {
  				return finishUtils.generateDateFromWeeks.call(this);
  			}
  
  			if (expression && this.timezone && this.day && this.days) {
  				this.day = this.days;
  			}
  
  			if (expression){
  				today.add(this);
  			} else {
  				today.set(this);
  			}
  			
  			if (this.timezone) {
  				this.timezone = this.timezone.toUpperCase();
  				var offset = $D.getTimezoneOffset(this.timezone);
  				var timezone;
  				if (today.hasDaylightSavingTime()) {
  					// lets check that we're being sane with timezone setting
  					timezone = $D.getTimezoneAbbreviation(offset, today.isDaylightSavingTime());
  					if (timezone !== this.timezone) {
  						// bugger, we're in a place where things like EST vs EDT matters.
  						if (today.isDaylightSavingTime()) {
  							today.addHours(-1);
  						} else {
  							today.addHours(1);
  						}
  					}
  				}
  				today.setTimezoneOffset(offset);
  			}
  
  			return today;
  		}
  	};
  }());
  (function () {
  	var $D = Date;
  	$D.Grammar = {};
  	var _ = $D.Parsing.Operators, g = $D.Grammar, t = $D.Translator, _fn;
  	// Allow rolling up into general purpose rules
  	_fn = function () {
  		return _.each(_.any.apply(null, arguments), _.not(g.ctoken2("timeContext")));
  	};
  	
  	g.datePartDelimiter = _.rtoken(/^([\s\-\.\,\/\x27]+)/);
  	g.timePartDelimiter = _.stoken(":");
  	g.whiteSpace = _.rtoken(/^\s*/);
  	g.generalDelimiter = _.rtoken(/^(([\s\,]|at|@|on)+)/);
    
  	var _C = {};
  	g.ctoken = function (keys) {
  		var fn = _C[keys];
  		if (! fn) {
  			var c = Date.CultureInfo.regexPatterns;
  			var kx = keys.split(/\s+/), px = [];
  			for (var i = 0; i < kx.length ; i++) {
  				px.push(_.replace(_.rtoken(c[kx[i]]), kx[i]));
  			}
  			fn = _C[keys] = _.any.apply(null, px);
  		}
  		return fn;
  	};
  	g.ctoken2 = function (key) {
  		return _.rtoken(Date.CultureInfo.regexPatterns[key]);
  	};
  	var cacheProcessRtoken = function (key, token, type, eachToken) {
  		if (eachToken) {
  			g[key] = _.cache(_.process(_.each(_.rtoken(token),_.optional(g.ctoken2(eachToken))), type));
  		} else {
  			g[key] = _.cache(_.process(_.rtoken(token), type));
  		}
  	};
  	var cacheProcessCtoken = function (token, type) {
  		return _.cache(_.process(g.ctoken2(token), type));
  	};
  	var _F = {}; //function cache
  
  	var _get = function (f) {
  		_F[f] = (_F[f] || g.format(f)[0]);
  		return _F[f];
  	};
  
  	g.allformats = function (fx) {
  		var rx = [];
  		if (fx instanceof Array) {
  			for (var i = 0; i < fx.length; i++) {
  				rx.push(_get(fx[i]));
  			}
  		} else {
  			rx.push(_get(fx));
  		}
  		return rx;
  	};
    
  	g.formats = function (fx) {
  		if (fx instanceof Array) {
  			var rx = [];
  			for (var i = 0 ; i < fx.length ; i++) {
  				rx.push(_get(fx[i]));
  			}
  			return _.any.apply(null, rx);
  		} else {
  			return _get(fx);
  		}
  	};
  
  	var grammarFormats = {
  		 timeFormats: function(){
  			var i,
  			RTokenKeys = [
  				"h",
  				"hh",
  				"H",
  				"HH",
  				"m",
  				"mm",
  				"s",
  				"ss",
  				"ss.s",
  				"z",
  				"zz"
  			],
  			RToken = [
  				/^(0[0-9]|1[0-2]|[1-9])/,
  				/^(0[0-9]|1[0-2])/,
  				/^([0-1][0-9]|2[0-3]|[0-9])/,
  				/^([0-1][0-9]|2[0-3])/,
  				/^([0-5][0-9]|[0-9])/,
  				/^[0-5][0-9]/,
  				/^([0-5][0-9]|[0-9])/,
  				/^[0-5][0-9]/,
  				/^[0-5][0-9]\.[0-9]{1,3}/,
  				/^((\+|\-)\s*\d\d\d\d)|((\+|\-)\d\d\:?\d\d)/,
  				/^((\+|\-)\s*\d\d\d\d)|((\+|\-)\d\d\:?\d\d)/
  			],
  			tokens = [
  				t.hour,
  				t.hour,
  				t.hour,
  				t.minute,
  				t.minute,
  				t.second,
  				t.second,
  				t.secondAndMillisecond,
  				t.timezone,
  				t.timezone,
  				t.timezone
  			];
  
  			for (i=0; i < RTokenKeys.length; i++) {
  				cacheProcessRtoken(RTokenKeys[i], RToken[i], tokens[i]);
  			}
  
  			g.hms = _.cache(_.sequence([g.H, g.m, g.s], g.timePartDelimiter));
  
  			g.t = cacheProcessCtoken("shortMeridian", t.meridian);
  			g.tt = cacheProcessCtoken("longMeridian", t.meridian);
  			g.zzz = cacheProcessCtoken("timezone", t.timezone);
  
  			g.timeSuffix = _.each(_.ignore(g.whiteSpace), _.set([ g.tt, g.zzz ]));
  			g.time = _.each(_.optional(_.ignore(_.stoken("T"))), g.hms, g.timeSuffix);
  		 },
  		 dateFormats: function () {
  			// pre-loaded rules for different date part order preferences
  			var _setfn = function () {
  				return  _.set(arguments, g.datePartDelimiter);
  			};
  			var i,
  			RTokenKeys = [
  				"d",
  				"dd",
  				"M",
  				"MM",
  				"y",
  				"yy",
  				"yyy",
  				"yyyy"
  			],
  			RToken = [
  				/^([0-2]\d|3[0-1]|\d)/,
  				/^([0-2]\d|3[0-1])/,
  				/^(1[0-2]|0\d|\d)/,
  				/^(1[0-2]|0\d)/,
  				/^(\d+)/,
  				/^(\d\d)/,
  				/^(\d\d?\d?\d?)/,
  				/^(\d\d\d\d)/
  			],
  			tokens = [
  				t.day,
  				t.day,
  				t.month,
  				t.month,
  				t.year,
  				t.year,
  				t.year,
  				t.year
  			],
  			eachToken = [
  				"ordinalSuffix",
  				"ordinalSuffix"
  			];
  			for (i=0; i < RTokenKeys.length; i++) {
  				cacheProcessRtoken(RTokenKeys[i], RToken[i], tokens[i], eachToken[i]);
  			}
  
  			g.MMM = g.MMMM = _.cache(_.process(g.ctoken("jan feb mar apr may jun jul aug sep oct nov dec"), t.month));
  			g.ddd = g.dddd = _.cache(_.process(g.ctoken("sun mon tue wed thu fri sat"),
  				function (s) {
  					return function () {
  						this.weekday = s;
  					};
  				}
  			));
  
  			g.day = _fn(g.d, g.dd);
  			g.month = _fn(g.M, g.MMM);
  			g.year = _fn(g.yyyy, g.yy);
  
  			g.mdy = _setfn(g.ddd, g.month, g.day, g.year);
  			g.ymd = _setfn(g.ddd, g.year, g.month, g.day);
  			g.dmy = _setfn(g.ddd, g.day, g.month, g.year);
  						
  			g.date = function (s) {
  				return ((g[Date.CultureInfo.dateElementOrder] || g.mdy).call(this, s));
  			};
  		 },
  		 relative: function () {
  			// relative date / time expressions
  			g.orientation = _.process(g.ctoken("past future"),
  				function (s) {
  					return function () {
  						this.orient = s;
  					};
  				}
  			);
  
  			g.operator = _.process(g.ctoken("add subtract"),
  				function (s) {
  					return function () {
  						this.operator = s;
  					};
  				}
  			);
  			g.rday = _.process(g.ctoken("yesterday tomorrow today now"), t.rday);
  			g.unit = _.process(g.ctoken("second minute hour day week month year"),
  				function (s) {
  					return function () {
  						this.unit = s;
  					};
  				}
  			);
  		 }
  	};
  
  	g.buildGrammarFormats = function () {
  		// these need to be rebuilt every time the language changes.
  		_C = {};
  
  		grammarFormats.timeFormats();
  		grammarFormats.dateFormats();
  		grammarFormats.relative();
  
  		
  		g.value = _.process(_.rtoken(/^([-+]?\d+)?(st|nd|rd|th)?/),
  			function (s) {
  				return function () {
  					this.value = s.replace(/\D/g, "");
  				};
  			}
  		);
  		g.expression = _.set([g.rday, g.operator, g.value, g.unit, g.orientation, g.ddd, g.MMM ]);
  
  		g.format = _.process(_.many(
  			_.any(
  				// translate format specifiers into grammar rules
  				_.process(
  					_.rtoken(/^(dd?d?d?(?!e)|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?)/),
  						function (fmt) {
  							if (g[fmt]) {
  								return g[fmt];
  							} else {
  								throw $D.Parsing.Exception(fmt);
  							}
  						}
  					),
  					// translate separator tokens into token rules
  					_.process(_.rtoken(/^[^dMyhHmstz]+/), // all legal separators 
  						function (s) {
  							return _.ignore(_.stoken(s));
  						}
  					)
  				)
  			),
  			// construct the parser ...
  			function (rules) {
  				return _.process(_.each.apply(null, rules), t.finishExact);
  			}
  		);
  
  		// starting rule for general purpose grammar
  		g._start = _.process(_.set([ g.date, g.time, g.expression ],
  		g.generalDelimiter, g.whiteSpace), t.finish);
  	};
  
  	g.buildGrammarFormats();
  	// parsing date format specifiers - ex: "h:m:s tt" 
  	// this little guy will generate a custom parser based
  	// on the format string, ex: g.format("h:m:s tt")
  	// check for these formats first
  	g._formats = g.formats([
  		"\"yyyy-MM-ddTHH:mm:ssZ\"",
  		"yyyy-MM-ddTHH:mm:ss.sz",
  		"yyyy-MM-ddTHH:mm:ssZ",
  		"yyyy-MM-ddTHH:mm:ssz",
  		"yyyy-MM-ddTHH:mm:ss",
  		"yyyy-MM-ddTHH:mmZ",
  		"yyyy-MM-ddTHH:mmz",
  		"yyyy-MM-ddTHH:mm",
  		"ddd, MMM dd, yyyy H:mm:ss tt",
  		"ddd MMM d yyyy HH:mm:ss zzz",
  		"MMddyyyy",
  		"ddMMyyyy",
  		"Mddyyyy",
  		"ddMyyyy",
  		"Mdyyyy",
  		"dMyyyy",
  		"yyyy",
  		"Mdyy",
  		"dMyy",
  		"d"
  	]);
  	
  	// real starting rule: tries selected formats first, 
  	// then general purpose rule
  	g.start = function (s) {
  		try {
  			var r = g._formats.call({}, s);
  			if (r[1].length === 0) {
  				return r;
  			}
  		} catch (e) {}
  		return g._start.call({}, s);
  	};
  }());
  (function () {
  	var $D = Date;
  
  	/**
  	 * @desc Converts the specified string value into its JavaScript Date equivalent using CultureInfo specific format information.
  	 * 
  	 * Example
  	<pre><code>
  	///////////
  	// Dates //
  	///////////
  
  	// 15-Oct-2004
  	var d1 = Date.parse("10/15/2004");
  
  	// 15-Oct-2004
  	var d1 = Date.parse("15-Oct-2004");
  
  	// 15-Oct-2004
  	var d1 = Date.parse("2004.10.15");
  
  	//Fri Oct 15, 2004
  	var d1 = Date.parse("Fri Oct 15, 2004");
  
  	///////////
  	// Times //
  	///////////
  
  	// Today at 10 PM.
  	var d1 = Date.parse("10 PM");
  
  	// Today at 10:30 PM.
  	var d1 = Date.parse("10:30 P.M.");
  
  	// Today at 6 AM.
  	var d1 = Date.parse("06am");
  
  	/////////////////////
  	// Dates and Times //
  	/////////////////////
  
  	// 8-July-2004 @ 10:30 PM
  	var d1 = Date.parse("July 8th, 2004, 10:30 PM");
  
  	// 1-July-2004 @ 10:30 PM
  	var d1 = Date.parse("2004-07-01T22:30:00");
  
  	////////////////////
  	// Relative Dates //
  	////////////////////
  
  	// Returns today's date. The string "today" is culture specific.
  	var d1 = Date.parse("today");
  
  	// Returns yesterday's date. The string "yesterday" is culture specific.
  	var d1 = Date.parse("yesterday");
  
  	// Returns the date of the next thursday.
  	var d1 = Date.parse("Next thursday");
  
  	// Returns the date of the most previous monday.
  	var d1 = Date.parse("last monday");
  
  	// Returns today's day + one year.
  	var d1 = Date.parse("next year");
  
  	///////////////
  	// Date Math //
  	///////////////
  
  	// Today + 2 days
  	var d1 = Date.parse("t+2");
  
  	// Today + 2 days
  	var d1 = Date.parse("today + 2 days");
  
  	// Today + 3 months
  	var d1 = Date.parse("t+3m");
  
  	// Today - 1 year
  	var d1 = Date.parse("today - 1 year");
  
  	// Today - 1 year
  	var d1 = Date.parse("t-1y"); 
  
  
  	/////////////////////////////
  	// Partial Dates and Times //
  	/////////////////////////////
  
  	// July 15th of this year.
  	var d1 = Date.parse("July 15");
  
  	// 15th day of current day and year.
  	var d1 = Date.parse("15");
  
  	// July 1st of current year at 10pm.
  	var d1 = Date.parse("7/1 10pm");
  	</code></pre>
  	 *
  	 * @param {String}   The string value to convert into a Date object [Required]
  	 * @return {Date}    A Date object or null if the string cannot be converted into a Date.
  	 */
  	var parseUtils = {
  		removeOrds: function (s) {
  			ords = s.match(/\b(\d+)(?:st|nd|rd|th)\b/); // find ordinal matches
  			s = ((ords && ords.length === 2) ? s.replace(ords[0], ords[1]) : s);
  			return s;
  		},
  		grammarParser: function (s) {
  			var r = null;
  			try {
  				r = $D.Grammar.start.call({}, s.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1"));
  			} catch (e) {
  				return null;
  			}
  			
  			return ((r[1].length === 0) ? r[0] : null);
  		},
  		nativeFallback: function(s) {
  			var t;
  			try {
  				// ok we haven't parsed it, last ditch attempt with the built-in parser.
  				t = Date._parse(s);
  				return (t || t === 0) ? new Date(t) : null;
  			} catch (e) {
  				return null;
  			}
  		}
  	};
  	function parse (s) {
  		var d;
  		if (!s) {
  			return null;
  		}
  		if (s instanceof Date) {
  			return s.clone();
  		}
  		if (s.length >= 4 && s.charAt(0) !== "0" && s.charAt(0) !== "+"&& s.charAt(0) !== "-") { // ie: 2004 will pass, 0800 won't.
  			//  Start with specific formats
  			d = $D.Parsing.ISO.parse(s) || $D.Parsing.Numeric.parse(s);
  		}
  		if (d instanceof Date && !isNaN(d.getTime())) {
  			return d;
  		} else {
  			// find ordinal dates (1st, 3rd, 8th, etc and remove them as they cause parsing issues)
  			s = $D.Parsing.Normalizer.parse(parseUtils.removeOrds(s));
  			d = parseUtils.grammarParser(s);
  			if (d !== null) {
  				return d;
  			} else {
  				return parseUtils.nativeFallback(s);
  			}
  		}
  	}
  
  	if (!$D._parse) {
  		$D._parse = $D.parse;
  	}
  	$D.parse = parse;
  
  	Date.getParseFunction = function (fx) {
  		var fns = Date.Grammar.allformats(fx);
  		return function (s) {
  			var r = null;
  			for (var i = 0; i < fns.length; i++) {
  				try {
  					r = fns[i].call({}, s);
  				} catch (e) {
  					continue;
  				}
  				if (r[1].length === 0) {
  					return r[0];
  				}
  			}
  			return null;
  		};
  	};
  	
  	/**
  	 * Converts the specified string value into its JavaScript Date equivalent using the specified format {String} or formats {Array} and the CultureInfo specific format information.
  	 * The format of the string value must match one of the supplied formats exactly.
  	 * 
  	 * Example
  	<pre><code>
  	// 15-Oct-2004
  	var d1 = Date.parseExact("10/15/2004", "M/d/yyyy");
  
  	// 15-Oct-2004
  	var d1 = Date.parse("15-Oct-2004", "M-ddd-yyyy");
  
  	// 15-Oct-2004
  	var d1 = Date.parse("2004.10.15", "yyyy.MM.dd");
  
  	// Multiple formats
  	var d1 = Date.parseExact("10/15/2004", ["M/d/yyyy", "MMMM d, yyyy"]);
  	</code></pre>
  	 *
  	 * @param {String}   The string value to convert into a Date object [Required].
  	 * @param {Object}   The expected format {String} or an array of expected formats {Array} of the date string [Required].
  	 * @return {Date}    A Date object or null if the string cannot be converted into a Date.
  	 */
  	$D.parseExact = function (s, fx) {
  		return $D.getParseFunction(fx)(s);
  	};
  }());
  
  (function () {
  	var $D = Date,
  		$P = $D.prototype,
  		// $C = $D.CultureInfo, // not used atm
  		p = function (s, l) {
  			if (!l) {
  				l = 2;
  			}
  			return ("000" + s).slice(l * -1);
  		};
  	/**
  	 * Converts a PHP format string to Java/.NET format string.
  	 * A PHP format string can be used with ._format or .format.
  	 * A Java/.NET format string can be used with .toString().
  	 * The .parseExact function will only accept a Java/.NET format string
  	 *
  	 * Example
  	 * var f1 = "%m/%d/%y"
  	 * var f2 = Date.normalizeFormat(f1);	// "MM/dd/yy"
  	 *
  	 * new Date().format(f1);	// "04/13/08"
  	 * new Date()._format(f1);	// "04/13/08"
  	 * new Date().toString(f2);	// "04/13/08"
  	 *
  	 * var date = Date.parseExact("04/13/08", f2); // Sun Apr 13 2008
  	 *
  	 * @param {String}   A PHP format string consisting of one or more format spcifiers.
  	 * @return {String}  The PHP format converted to a Java/.NET format string.
  	 */
  	 var normalizerSubstitutions = {
  		"d" : "dd",
  		"%d": "dd",
  		"D" : "ddd",
  		"%a": "ddd",
  		"j" : "dddd",
  		"l" : "dddd",
  		"%A": "dddd",
  		"S" : "S",
  		"F" : "MMMM",
  		"%B": "MMMM",
  		"m" : "MM",
  		"%m": "MM",
  		"M" : "MMM",
  		"%b": "MMM",
  		"%h": "MMM",
  		"n" : "M",
  		"Y" : "yyyy",
  		"%Y": "yyyy",
  		"y" : "yy",
  		"%y": "yy",
  		"g" : "h",
  		"%I": "h",
  		"G" : "H",
  		"h" : "hh",
  		"H" : "HH",
  		"%H": "HH",
  		"i" : "mm",
  		"%M": "mm",
  		"s" : "ss",
  		"%S": "ss",
  		"%r": "hh:mm tt",
  		"%R": "H:mm",
  		"%T": "H:mm:ss",
  		"%X": "t",
  		"%x": "d",
  		"%e": "d",
  		"%D": "MM/dd/yy",
  		"%n": "\\n",
  		"%t": "\\t",
  		"e" : "z",
  		"T" : "z",
  		"%z": "z",
  		"%Z": "z",
  		"Z" : "ZZ",
  		"N" : "u",
  		"w" : "u",
  		"%w": "u",
  		"W" : "W",
  		"%V": "W"
  	};
  	var normalizer = {
  		substitutes: function (m) {
  			return normalizerSubstitutions[m];
  		},
  		interpreted: function (m, x) {
  			var y;
  			switch (m) {
  				case "%u":
  					return x.getDay() + 1;
  				case "z":
  					return x.getOrdinalNumber();
  				case "%j":
  					return p(x.getOrdinalNumber(), 3);
  				case "%U":
  					var d1 = x.clone().set({month: 0, day: 1}).addDays(-1).moveToDayOfWeek(0),
  						d2 = x.clone().addDays(1).moveToDayOfWeek(0, -1);
  					return (d2 < d1) ? "00" : p((d2.getOrdinalNumber() - d1.getOrdinalNumber()) / 7 + 1);
  
  				case "%W":
  					return p(x.getWeek());
  				case "t":
  					return $D.getDaysInMonth(x.getFullYear(), x.getMonth());
  				case "o":
  				case "%G":
  					return x.setWeek(x.getISOWeek()).toString("yyyy");
  				case "%g":
  					return x._format("%G").slice(-2);
  				case "a":
  				case "%p":
  					return t("tt").toLowerCase();
  				case "A":
  					return t("tt").toUpperCase();
  				case "u":
  					return p(x.getMilliseconds(), 3);
  				case "I":
  					return (x.isDaylightSavingTime()) ? 1 : 0;
  				case "O":
  					return x.getUTCOffset();
  				case "P":
  					y = x.getUTCOffset();
  					return y.substring(0, y.length - 2) + ":" + y.substring(y.length - 2);
  				case "B":
  					var now = new Date();
  					return Math.floor(((now.getHours() * 3600) + (now.getMinutes() * 60) + now.getSeconds() + (now.getTimezoneOffset() + 60) * 60) / 86.4);
  				case "c":
  					return x.toISOString().replace(/\"/g, "");
  				case "U":
  					return $D.strtotime("now");
  				case "%c":
  					return t("d") + " " + t("t");
  				case "%C":
  					return Math.floor(x.getFullYear() / 100 + 1);
  			}
  		},
  		shouldOverrideDefaults: function (m) {
  			switch (m) {
  				case "%e":
  					return true;
  				default:
  					return false;
  			}
  		},
  		parse: function (m, context) {
  			var formatString, c = context || new Date();
  			formatString = normalizer.substitutes(m);
  			if (formatString) {
  				return formatString;
  			}
  			formatString = normalizer.interpreted(m, c);
  
  			if (formatString) {
  				return formatString;
  			} else {
  				return m;
  			}
  		}
  	};
  
  	$D.normalizeFormat = function (format, context) {
  		return format.replace(/(%|\\)?.|%%/g, function(t){
  				return normalizer.parse(t, context);
  		});
  	};
  	/**
  	 * Format a local Unix timestamp according to locale settings
  	 *
  	 * Example:
  	 * Date.strftime("%m/%d/%y", new Date());		// "04/13/08"
  	 * Date.strftime("c", "2008-04-13T17:52:03Z");	// "04/13/08"
  	 *
  	 * @param {String}   A format string consisting of one or more format spcifiers [Optional].
  	 * @param {Number|String}   The number representing the number of seconds that have elapsed since January 1, 1970 (local time).
  	 * @return {String}  A string representation of the current Date object.
  	 */
  	$D.strftime = function (format, time) {
  		var d = Date.parse(time);
  		return d._format(format);
  	};
  	/**
  	 * Parse any textual datetime description into a Unix timestamp.
  	 * A Unix timestamp is the number of seconds that have elapsed since January 1, 1970 (midnight UTC/GMT).
  	 *
  	 * Example:
  	 * Date.strtotime("04/13/08");				// 1208044800
  	 * Date.strtotime("1970-01-01T00:00:00Z");	// 0
  	 *
  	 * @param {String}   A format string consisting of one or more format spcifiers [Optional].
  	 * @param {Object}   A string or date object.
  	 * @return {String}  A string representation of the current Date object.
  	 */
  	$D.strtotime = function (time) {
  		var d = $D.parse(time);
  		return Math.round($D.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds()) / 1000);
  	};
  	/**
  	 * Converts the value of the current Date object to its equivalent string representation using a PHP/Unix style of date format specifiers.
  	 * Format Specifiers
  	 * Format  Description																	Example
  	 * ------  ---------------------------------------------------------------------------	-----------------------
  	 * %a		abbreviated weekday name according to the current localed					"Mon" through "Sun"
  	 * %A		full weekday name according to the current localed							"Sunday" through "Saturday"
  	 * %b		abbreviated month name according to the current localed						"Jan" through "Dec"
  	 * %B		full month name according to the current locale								"January" through "December"
  	 * %c		preferred date and time representation for the current locale				"4/13/2008 12:33 PM"
  	 * %C		century number (the year divided by 100 and truncated to an integer)		"00" to "99"
  	 * %d		day of the month as a decimal number										"01" to "31"
  	 * %D		same as %m/%d/%y															"04/13/08"
  	 * %e		day of the month as a decimal number, a single digit is preceded by a space	"1" to "31"
  	 * %g		like %G, but without the century											"08"
  	 * %G		The 4-digit year corresponding to the ISO week number (see %V).				"2008"
  	 *		This has the same format and value as %Y, except that if the ISO week number
  	 *		belongs to the previous or next year, that year is used instead.
  	 * %h		same as %b																	"Jan" through "Dec"
  	 * %H		hour as a decimal number using a 24-hour clock.								"00" to "23"
  	 * %I		hour as a decimal number using a 12-hour clock.								"01" to "12"
  	 * %j		day of the year as a decimal number.										"001" to "366"
  	 * %m		month as a decimal number.													"01" to "12"
  	 * %M		minute as a decimal number.													"00" to "59"
  	 * %n		newline character		"\n"
  	 * %p		either "am" or "pm" according to the given time value, or the				"am" or "pm"
  	 *		corresponding strings for the current locale.
  	 * %r		time in a.m. and p.m. notation												"8:44 PM"
  	 * %R		time in 24 hour notation													"20:44"
  	 * %S		second as a decimal number													"00" to "59"
  	 * %t		tab character																"\t"
  	 * %T		current time, equal to %H:%M:%S												"12:49:11"
  	 * %u		weekday as a decimal number ["1", "7"], with "1" representing Monday		"1" to "7"
  	 * %U		week number of the current year as a decimal number, starting with the		"0" to ("52" or "53")
  	 *		first Sunday as the first day of the first week
  	 * %V		The ISO 8601:1988 week number of the current year as a decimal number,		"00" to ("52" or "53")
  	 *		range 01 to 53, where week 1 is the first week that has at least 4 days
  	 *		in the current year, and with Monday as the first day of the week.
  	 *		(Use %G or %g for the year component that corresponds to the week number
  	 *		for the specified timestamp.)
  	 * %W		week number of the current year as a decimal number, starting with the		"00" to ("52" or "53")
  	 *		first Monday as the first day of the first week
  	 * %w		day of the week as a decimal, Sunday being "0"								"0" to "6"
  	 * %x		preferred date representation for the current locale without the time		"4/13/2008"
  	 * %X		preferred time representation for the current locale without the date		"12:53:05"
  	 * %y		year as a decimal number without a century									"00" "99"
  	 * %Y		year as a decimal number including the century								"2008"
  	 * %Z		time zone or name or abbreviation											"UTC", "EST", "PST"
  	 * %z		same as %Z
  	 * %%		a literal "%" characters													"%"
  	 * d		Day of the month, 2 digits with leading zeros								"01" to "31"
  	 * D		A textual representation of a day, three letters							"Mon" through "Sun"
  	 * j		Day of the month without leading zeros										"1" to "31"
  	 * l		A full textual representation of the day of the week (lowercase "L")		"Sunday" through "Saturday"
  	 * N		ISO-8601 numeric representation of the day of the week (added in PHP 5.1.0)	"1" (for Monday) through "7" (for Sunday)
  	 * S		English ordinal suffix for the day of the month, 2 characters				"st", "nd", "rd" or "th". Works well with j
  	 * w		Numeric representation of the day of the week								"0" (for Sunday) through "6" (for Saturday)
  	 * z		The day of the year (starting from "0")										"0" through "365"
  	 * W		ISO-8601 week number of year, weeks starting on Monday						"00" to ("52" or "53")
  	 * F		A full textual representation of a month, such as January or March			"January" through "December"
  	 * m		Numeric representation of a month, with leading zeros						"01" through "12"
  	 * M		A short textual representation of a month, three letters					"Jan" through "Dec"
  	 * n		Numeric representation of a month, without leading zeros					"1" through "12"
  	 * t		Number of days in the given month											"28" through "31"
  	 * L		Whether it's a leap year													"1" if it is a leap year, "0" otherwise
  	 * o		ISO-8601 year number. This has the same value as Y, except that if the		"2008"
  	 *		ISO week number (W) belongs to the previous or next year, that year
  	 *		is used instead.
  	 * Y		A full numeric representation of a year, 4 digits							"2008"
  	 * y		A two digit representation of a year										"08"
  	 * a		Lowercase Ante meridiem and Post meridiem									"am" or "pm"
  	 * A		Uppercase Ante meridiem and Post meridiem									"AM" or "PM"
  	 * B		Swatch Internet time														"000" through "999"
  	 * g		12-hour format of an hour without leading zeros								"1" through "12"
  	 * G		24-hour format of an hour without leading zeros								"0" through "23"
  	 * h		12-hour format of an hour with leading zeros								"01" through "12"
  	 * H		24-hour format of an hour with leading zeros								"00" through "23"
  	 * i		Minutes with leading zeros													"00" to "59"
  	 * s		Seconds, with leading zeros													"00" through "59"
  	 * u		Milliseconds																"54321"
  	 * e		Timezone identifier															"UTC", "EST", "PST"
  	 * I		Whether or not the date is in daylight saving time (uppercase i)			"1" if Daylight Saving Time, "0" otherwise
  	 * O		Difference to Greenwich time (GMT) in hours									"+0200", "-0600"
  	 * P		Difference to Greenwich time (GMT) with colon between hours and minutes		"+02:00", "-06:00"
  	 * T		Timezone abbreviation														"UTC", "EST", "PST"
  	 * Z		Timezone offset in seconds. The offset for timezones west of UTC is			"-43200" through "50400"
  	 *			always negative, and for those east of UTC is always positive.
  	 * c		ISO 8601 date																"2004-02-12T15:19:21+00:00"
  	 * r		RFC 2822 formatted date														"Thu, 21 Dec 2000 16:01:07 +0200"
  	 * U		Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)					"0"
  	 * @param {String}   A format string consisting of one or more format spcifiers [Optional].
  	 * @return {String}  A string representation of the current Date object.
  	 */
  	var formatReplace = function (context) {
  		return function (m) {
  			var formatString, override = false;
  			if (m.charAt(0) === "\\" || m.substring(0, 2) === "%%") {
  				return m.replace("\\", "").replace("%%", "%");
  			}
  
  			override = normalizer.shouldOverrideDefaults(m);
  			formatString = $D.normalizeFormat(m, context);
  			if (formatString) {
  				return context.toString(formatString, override);
  			}
  		};
  	};
  	$P._format = function (format) {
  		var formatter = formatReplace(this);
  		if (!format) {
  			return this._toString();
  		} else {
  			return format.replace(/(%|\\)?.|%%/g, formatter);
  		}
  	};
  
  	if (!$P.format) {
  		$P.format = $P._format;
  	}
  }());
  (function () {
  	"use strict";
  	var gFn = function (attr) {
  		return function () {
  			return this[attr];
  		};
  	};
  	
  	var sFn = function (attr) {
  		return function (val) {
  			this[attr] = val;
  			return this;
  		};
  	};
  	var attrs = ["years", "months", "days", "hours", "minutes", "seconds", "milliseconds"];
  	var addSetFuncs = function (context, attrs) {
  		for (var i = 0; i < attrs.length ; i++) {
  			var $a = attrs[i], $b = $a.slice(0, 1).toUpperCase() + $a.slice(1);
  			context.prototype[$a] = 0;
  			context.prototype["get" + $b] = gFn($a);
  			context.prototype["set" + $b] = sFn($a);
  		}
  	};
  	/**
  	 * new TimeSpan(milliseconds);
  	 * new TimeSpan(days, hours, minutes, seconds);
  	 * new TimeSpan(days, hours, minutes, seconds, milliseconds);
  	 */
  	var TimeSpan = function (days, hours, minutes, seconds, milliseconds) {
  		if (arguments.length === 1 && typeof days === "number") {
  			var orient = (days < 0) ? -1 : +1;
  			var millsLeft = Math.abs(days);
  			this.setDays(Math.floor(millsLeft / 86400000) * orient);
  			millsLeft = millsLeft % 86400000;
  			this.setHours(Math.floor(millsLeft / 3600000) * orient);
  			millsLeft = millsLeft % 3600000;
  			this.setMinutes(Math.floor(millsLeft / 60000) * orient);
  			millsLeft = millsLeft % 60000;
  			this.setSeconds(Math.floor(millsLeft / 1000) * orient);
  			millsLeft = millsLeft % 1000;
  			this.setMilliseconds(millsLeft * orient);
  		} else {
  			this.set(days, hours, minutes, seconds, milliseconds);
  		}
  
  		this.getTotalMilliseconds = function () {
  			return	(this.getDays() * 86400000) +
  					(this.getHours() * 3600000) +
  					(this.getMinutes() * 60000) +
  					(this.getSeconds() * 1000);
  		};
  		
  		this.compareTo = function (time) {
  			var t1 = new Date(1970, 1, 1, this.getHours(), this.getMinutes(), this.getSeconds()), t2;
  			if (time === null) {
  				t2 = new Date(1970, 1, 1, 0, 0, 0);
  			}
  			else {
  				t2 = new Date(1970, 1, 1, time.getHours(), time.getMinutes(), time.getSeconds());
  			}
  			return (t1 < t2) ? -1 : (t1 > t2) ? 1 : 0;
  		};
  
  		this.equals = function (time) {
  			return (this.compareTo(time) === 0);
  		};
  
  		this.add = function (time) {
  			return (time === null) ? this : this.addSeconds(time.getTotalMilliseconds() / 1000);
  		};
  
  		this.subtract = function (time) {
  			return (time === null) ? this : this.addSeconds(-time.getTotalMilliseconds() / 1000);
  		};
  
  		this.addDays = function (n) {
  			return new TimeSpan(this.getTotalMilliseconds() + (n * 86400000));
  		};
  
  		this.addHours = function (n) {
  			return new TimeSpan(this.getTotalMilliseconds() + (n * 3600000));
  		};
  
  		this.addMinutes = function (n) {
  			return new TimeSpan(this.getTotalMilliseconds() + (n * 60000));
  		};
  
  		this.addSeconds = function (n) {
  			return new TimeSpan(this.getTotalMilliseconds() + (n * 1000));
  		};
  
  		this.addMilliseconds = function (n) {
  			return new TimeSpan(this.getTotalMilliseconds() + n);
  		};
  
  		this.get12HourHour = function () {
  			return (this.getHours() > 12) ? this.getHours() - 12 : (this.getHours() === 0) ? 12 : this.getHours();
  		};
  
  		this.getDesignator = function () {
  			return (this.getHours() < 12) ? Date.CultureInfo.amDesignator : Date.CultureInfo.pmDesignator;
  		};
  
  		this.toString = function (format) {
  			this._toString = function () {
  				if (this.getDays() !== null && this.getDays() > 0) {
  					return this.getDays() + "." + this.getHours() + ":" + this.p(this.getMinutes()) + ":" + this.p(this.getSeconds());
  				} else {
  					return this.getHours() + ":" + this.p(this.getMinutes()) + ":" + this.p(this.getSeconds());
  				}
  			};
  			
  			this.p = function (s) {
  				return (s.toString().length < 2) ? "0" + s : s;
  			};
  			
  			var me = this;
  			
  			return format ? format.replace(/dd?|HH?|hh?|mm?|ss?|tt?/g,
  			function (format) {
  				switch (format) {
  				case "d":
  					return me.getDays();
  				case "dd":
  					return me.p(me.getDays());
  				case "H":
  					return me.getHours();
  				case "HH":
  					return me.p(me.getHours());
  				case "h":
  					return me.get12HourHour();
  				case "hh":
  					return me.p(me.get12HourHour());
  				case "m":
  					return me.getMinutes();
  				case "mm":
  					return me.p(me.getMinutes());
  				case "s":
  					return me.getSeconds();
  				case "ss":
  					return me.p(me.getSeconds());
  				case "t":
  					return ((me.getHours() < 12) ? Date.CultureInfo.amDesignator : Date.CultureInfo.pmDesignator).substring(0, 1);
  				case "tt":
  					return (me.getHours() < 12) ? Date.CultureInfo.amDesignator : Date.CultureInfo.pmDesignator;
  				}
  			}
  			) : this._toString();
  		};
  		return this;
  	};
  	addSetFuncs(TimeSpan, attrs.slice(2));
  	TimeSpan.prototype.set = function (days, hours, minutes, seconds, milliseconds){
  		this.setDays(days || this.getDays());
  		this.setHours(hours || this.getHours());
  		this.setMinutes(minutes || this.getMinutes());
  		this.setSeconds(seconds || this.getSeconds());
  		this.setMilliseconds(milliseconds || this.getMilliseconds());
  	};
  
  
  	/**
  	 * Gets the time of day for this date instances. 
  	 * @return {TimeSpan} TimeSpan
  	 */
  	Date.prototype.getTimeOfDay = function () {
  		return new TimeSpan(0, this.getHours(), this.getMinutes(), this.getSeconds(), this.getMilliseconds());
  	};
  
  	Date.TimeSpan = TimeSpan;
  
  	if (typeof window !== "undefined" ) {
  		// keeping API compatible for v1.x 
  		window.TimeSpan = TimeSpan;
  	}
  }());
  (function () {
  	"use strict";
  	var attrs = ["years", "months", "days", "hours", "minutes", "seconds", "milliseconds"];
  	var gFn = function (attr) {
  		return function () {
  			return this[attr];
  		};
  	};
  	
  	var sFn = function (attr) {
  		return function (val) {
  			this[attr] = val;
  			return this;
  		};
  	};
  	var addSetFuncs = function (context, attrs) {
  		for (var i = 0; i < attrs.length ; i++) {
  			var $a = attrs[i], $b = $a.slice(0, 1).toUpperCase() + $a.slice(1);
  			context.prototype[$a] = 0;
  			context.prototype["get" + $b] = gFn($a);
  			context.prototype["set" + $b] = sFn($a);
  		}
  	};
  
  	var setMonthsAndYears = function (orient, d1, d2, context) {
  		function inc() {
  			d1.addMonths(-orient);
  			context.months++;
  			if (context.months === 12) {
  				context.years++;
  				context.months = 0;
  			}
  		}
  		if (orient === +1) {
  			while (d1 > d2) {
  				inc();
  			}
  		} else {
  			while (d1 < d2) {
  				inc();
  			}
  		}
  		context.months--;
  		context.months *= orient;
  		context.years *= orient;
  	};
  
  	var adjustForDST = function(orient, startDate, endDate) {
  		var hasDSTMismatch = (false === (startDate.isDaylightSavingTime() === endDate.isDaylightSavingTime()));
  		if (hasDSTMismatch && orient === 1) {
  			startDate.addHours(-1);
  		} else if (hasDSTMismatch) {
  			startDate.addHours(1);
  		}
  	};
  	/**
  	 * TimePeriod(startDate, endDate);
  	 * TimePeriod(years, months, days, hours, minutes, seconds, milliseconds);
  	 */
  	var TimePeriod = function (years, months, days, hours, minutes, seconds, milliseconds) {
  		if (arguments.length === 7) {
  			this.set(years, months, days, hours, minutes, seconds, milliseconds);
  		} else if (arguments.length === 2 && arguments[0] instanceof Date && arguments[1] instanceof Date) {
  			var startDate = arguments[0].clone();
  			var endDate = arguments[1].clone();
  			var orient = (startDate > endDate) ? +1 : -1;
  			this.dates = {
  				start: arguments[0].clone(),
  				end: arguments[1].clone()
  			};
  
  			setMonthsAndYears(orient, startDate, endDate, this);
  			adjustForDST(orient, startDate, endDate);
  			// // TODO - adjust for DST
  			var diff = endDate - startDate;
  			if (diff !== 0) {
  				var ts = new TimeSpan(diff);
  				this.set(this.years, this.months, ts.getDays(), ts.getHours(), ts.getMinutes(), ts.getSeconds(), ts.getMilliseconds());
  			}
  		}
  		return this;
  	};
  	// create all the set functions.
  	addSetFuncs(TimePeriod, attrs);
  	TimePeriod.prototype.set = function (years, months, days, hours, minutes, seconds, milliseconds){
  		this.setYears(years || this.getYears());
  		this.setMonths(months || this.getMonths());
  		this.setDays(days || this.getDays());
  		this.setHours(hours || this.getHours());
  		this.setMinutes(minutes || this.getMinutes());
  		this.setSeconds(seconds || this.getSeconds());
  		this.setMilliseconds(milliseconds || this.getMilliseconds());
  	};
  
  	Date.TimePeriod = TimePeriod;
  
  	if (typeof window !== "undefined") {
  		// keeping API compatible for v1.x 
  		window.TimePeriod = TimePeriod;
  	}
  }());
  /* The MIT License
  
   Copyright (c) 2011 by Michael Zinsmaier and nergal.dev
   Copyright (c) 2012 by Thomas Ritou
  
   Permission is hereby granted, free of charge, to any person obtaining a copy
   of this software and associated documentation files (the "Software"), to deal
   in the Software without restriction, including without limitation the rights
   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   copies of the Software, and to permit persons to whom the Software is
   furnished to do so, subject to the following conditions:
  
   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.
  
   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   THE SOFTWARE.
  */
  
  /*
  ____________________________________________________
  
   what it is:
   ____________________________________________________
  
   curvedLines is a plugin for flot, that tries to display lines in a smoother way.
   This is achieved through adding of more data points. The plugin is a data processor and can thus be used
   in combination with standard line / point rendering options.
  
   => 1) with large data sets you may get trouble
   => 2) if you want to display the points too, you have to plot them as 2nd data series over the lines
   => 3) consecutive x data points are not allowed to have the same value
  
   Feel free to further improve the code
  
   ____________________________________________________
  
   how to use it:
   ____________________________________________________
  
   var d1 = [[5,5],[7,3],[9,12]];
  
   var options = { series: { curvedLines: {  active: true }}};
  
   $.plot($("#placeholder"), [{data: d1, lines: { show: true}, curvedLines: {apply: true}}], options);
  
   _____________________________________________________
  
   options:
   _____________________________________________________
  
   active:           bool true => plugin can be used
   apply:            bool true => series will be drawn as curved line
   monotonicFit:	   bool true => uses monotone cubic interpolation (preserve monotonicity)
   tension:          int          defines the tension parameter of the hermite spline interpolation (no effect if monotonicFit is set)
   nrSplinePoints:   int 			defines the number of sample points (of the spline) in between two consecutive points
  
   deprecated options from flot prior to 1.0.0:
   ------------------------------------------------
   legacyOverride	   bool true => use old default
      OR
   legacyOverride    optionArray
   {
   	fit: 	             bool true => forces the max,mins of the curve to be on the datapoints
   	curvePointFactor	 int  		  defines how many "virtual" points are used per "real" data point to
   									  emulate the curvedLines (points total = real points * curvePointFactor)
   	fitPointDist: 	     int  		  defines the x axis distance of the additional two points that are used
   }						   		   	  to enforce the min max condition.
   */
  
  /*
   *  v0.1   initial commit
   *  v0.15  negative values should work now (outcommented a negative -> 0 hook hope it does no harm)
   *  v0.2   added fill option (thanks to monemihir) and multi axis support (thanks to soewono effendi)
   *  v0.3   improved saddle handling and added basic handling of Dates
   *  v0.4   rewritten fill option (thomas ritou) mostly from original flot code (now fill between points rather than to graph bottom), corrected fill Opacity bug
   *  v0.5   rewritten instead of implementing a own draw function CurvedLines is now based on the processDatapoints flot hook (credits go to thomas ritou).
   * 		   This change breakes existing code however CurvedLines are now just many tiny straight lines to flot and therefore all flot lines options (like gradient fill,
   * 	       shadow) are now supported out of the box
   *  v0.6   flot 0.8 compatibility and some bug fixes
   *  v0.6.x changed versioning schema
   *
   *  v1.0.0 API Break marked existing implementation/options as deprecated
   *  v1.1.0 added the new curved line calculations based on hermite splines
   *  v1.1.1 added a rough parameter check to make sure the new options are used
   */
  
  (function($) {
  
  	var options = {
  		series : {
  			curvedLines : {
  				active : false,
  				apply : false,
  				monotonicFit : false,
  				tension : 0.5,
  				nrSplinePoints : 20,
  				legacyOverride : undefined
  			}
  		}
  	};
  
  	function init(plot) {
  
  		plot.hooks.processOptions.push(processOptions);
  
  		//if the plugin is active register processDatapoints method
  		function processOptions(plot, options) {
  			if (options.series.curvedLines.active) {
  				plot.hooks.processDatapoints.unshift(processDatapoints);
  			}
  		}
  
  		//only if the plugin is active
  		function processDatapoints(plot, series, datapoints) {
  			var nrPoints = datapoints.points.length / datapoints.pointsize;
  			var EPSILON = 0.005;
  
  			//detects missplaced legacy parameters (prior v1.x.x) in the options object
  			//this can happen if somebody upgrades to v1.x.x without adjusting the parameters or uses old examples
              var invalidLegacyOptions = hasInvalidParameters(series.curvedLines);
  
  			if (!invalidLegacyOptions && series.curvedLines.apply == true && series.originSeries === undefined && nrPoints > (1 + EPSILON)) {
  				if (series.lines.fill) {
  
  					var pointsTop = calculateCurvePoints(datapoints, series.curvedLines, 1);
  					var pointsBottom = calculateCurvePoints(datapoints, series.curvedLines, 2);
  					//flot makes sure for us that we've got a second y point if fill is true !
  
  					//Merge top and bottom curve
  					datapoints.pointsize = 3;
  					datapoints.points = [];
  					var j = 0;
  					var k = 0;
  					var i = 0;
  					var ps = 2;
  					while (i < pointsTop.length || j < pointsBottom.length) {
  						if (pointsTop[i] == pointsBottom[j]) {
  							datapoints.points[k] = pointsTop[i];
  							datapoints.points[k + 1] = pointsTop[i + 1];
  							datapoints.points[k + 2] = pointsBottom[j + 1];
  							j += ps;
  							i += ps;
  
  						} else if (pointsTop[i] < pointsBottom[j]) {
  							datapoints.points[k] = pointsTop[i];
  							datapoints.points[k + 1] = pointsTop[i + 1];
  							datapoints.points[k + 2] = k > 0 ? datapoints.points[k - 1] : null;
  							i += ps;
  						} else {
  							datapoints.points[k] = pointsBottom[j];
  							datapoints.points[k + 1] = k > 1 ? datapoints.points[k - 2] : null;
  							datapoints.points[k + 2] = pointsBottom[j + 1];
  							j += ps;
  						}
  						k += 3;
  					}
  				} else if (series.lines.lineWidth > 0) {
  					datapoints.points = calculateCurvePoints(datapoints, series.curvedLines, 1);
  					datapoints.pointsize = 2;
  				}
  			}
  		}
  
  		function calculateCurvePoints(datapoints, curvedLinesOptions, yPos) {
  			if ( typeof curvedLinesOptions.legacyOverride != 'undefined' && curvedLinesOptions.legacyOverride != false) {
  				var defaultOptions = {
  					fit : false,
  					curvePointFactor : 20,
  					fitPointDist : undefined
  				};
  				var legacyOptions = jQuery.extend(defaultOptions, curvedLinesOptions.legacyOverride);
  				return calculateLegacyCurvePoints(datapoints, legacyOptions, yPos);
  			}
  
  			return calculateSplineCurvePoints(datapoints, curvedLinesOptions, yPos);
  		}
  
  		function calculateSplineCurvePoints(datapoints, curvedLinesOptions, yPos) {
  			var points = datapoints.points;
  			var ps = datapoints.pointsize;
  			
  			//create interpolant fuction
  			var splines = createHermiteSplines(datapoints, curvedLinesOptions, yPos);
  			var result = [];
  
  			//sample the function
  			// (the result is intependent from the input data =>
  			//	it is ok to alter the input after this method)
  			var j = 0;
  			for (var i = 0; i < points.length - ps; i += ps) {
  				var curX = i;
  				var curY = i + yPos;	
  				
  				var xStart = points[curX];
  				var xEnd = points[curX + ps];
  				var xStep = (xEnd - xStart) / Number(curvedLinesOptions.nrSplinePoints);
  
  				//add point
  				result.push(points[curX]);
  				result.push(points[curY]);
  
  				//add curve point
  				for (var x = (xStart += xStep); x < xEnd; x += xStep) {
  					result.push(x);
  					result.push(splines[j](x));
  				}
  				
  				j++;
  			}
  
  			//add last point
  			result.push(points[points.length - ps]);
  			result.push(points[points.length - ps + yPos]);
  
  			return result;
  		}
  
  
  
  		// Creates an array of splines, one for each segment of the original curve. Algorithm based on the wikipedia articles: 
  		//
  		// http://de.wikipedia.org/w/index.php?title=Kubisch_Hermitescher_Spline&oldid=130168003 and 
  		// http://en.wikipedia.org/w/index.php?title=Monotone_cubic_interpolation&oldid=622341725 and the description of Fritsch-Carlson from
  		// http://math.stackexchange.com/questions/45218/implementation-of-monotone-cubic-interpolation
  		// for a detailed description see https://github.com/MichaelZinsmaier/CurvedLines/docu
  		function createHermiteSplines(datapoints, curvedLinesOptions, yPos) {
  			var points = datapoints.points;
  			var ps = datapoints.pointsize;
  			
  			// preparation get length (x_{k+1} - x_k) and slope s=(p_{k+1} - p_k) / (x_{k+1} - x_k) of the segments
  			var segmentLengths = [];
  			var segmentSlopes = [];
  
  			for (var i = 0; i < points.length - ps; i += ps) {
  				var curX = i;
  				var curY = i + yPos;			
  				var dx = points[curX + ps] - points[curX];
  				var dy = points[curY + ps] - points[curY];
  							
  				segmentLengths.push(dx);
  				segmentSlopes.push(dy / dx);
  			}
  
  			//get the values for the desired gradients  m_k for all points k
  			//depending on the used method the formula is different
  			var gradients = [segmentSlopes[0]];	
  			if (curvedLinesOptions.monotonicFit) {
  				// Fritsch Carlson
  				for (var i = 1; i < segmentLengths.length; i++) {
  					var slope = segmentSlopes[i];
  					var prev_slope = segmentSlopes[i - 1];
  					if (slope * prev_slope <= 0) { // sign(prev_slope) != sign(slpe)
  						gradients.push(0);
  					} else {
  						var length = segmentLengths[i];
  						var prev_length = segmentLengths[i - 1];
  						var common = length + prev_length;
  						//m = 3 (prev_length + length) / ((2 length + prev_length) / prev_slope + (length + 2 prev_length) / slope)
  						gradients.push(3 * common / ((common + length) / prev_slope + (common + prev_length) / slope));
  					}
  				}
  			} else {
  				// Cardinal spline with t € [0,1]
  				// Catmull-Rom for t = 0
  				for (var i = ps; i < points.length - ps; i += ps) {
  					var curX = i;
  					var curY = i + yPos;	
  					gradients.push(Number(curvedLinesOptions.tension) * (points[curY + ps] - points[curY - ps]) / (points[curX + ps] - points[curX - ps]));
  				}
  			}
  			gradients.push(segmentSlopes[segmentSlopes.length - 1]);
  
  			//get the two major coefficients (c'_{oef1} and c'_{oef2}) for each segment spline
  			var coefs1 = [];
  			var coefs2 = [];
  			for (i = 0; i < segmentLengths.length; i++) {
  				var m_k = gradients[i];
  				var m_k_plus = gradients[i + 1];
  				var slope = segmentSlopes[i];
  				var invLength = 1 / segmentLengths[i];
  				var common = m_k + m_k_plus - slope - slope;
  				
  				coefs1.push(common * invLength * invLength);
  				coefs2.push((slope - common - m_k) * invLength);
  			}
  
  			//create functions with from the coefficients and capture the parameters
  			var ret = [];
  			for (var i = 0; i < segmentLengths.length; i ++) {
  				var spline = function (x_k, coef1, coef2, coef3, coef4) {
  					// spline for a segment
  					return function (x) {									
  						var diff = x - x_k;
  						var diffSq = diff * diff;
  						return coef1 * diff * diffSq + coef2 * diffSq + coef3 * diff + coef4;
  					};
  				};			
  		
  				ret.push(spline(points[i * ps], coefs1[i], coefs2[i], gradients[i], points[i * ps + yPos]));
  			}
  			
  			return ret;
  		};
  
  		//no real idea whats going on here code mainly from https://code.google.com/p/flot/issues/detail?id=226
  		//if fit option is selected additional datapoints get inserted before the curve calculations in nergal.dev s code.
  		function calculateLegacyCurvePoints(datapoints, curvedLinesOptions, yPos) {
  
  			var points = datapoints.points;
  			var ps = datapoints.pointsize;
  			var num = Number(curvedLinesOptions.curvePointFactor) * (points.length / ps);
  
  			var xdata = new Array;
  			var ydata = new Array;
  
  			var curX = -1;
  			var curY = -1;
  			var j = 0;
  
  			if (curvedLinesOptions.fit) {
  				//insert a point before and after the "real" data point to force the line
  				//to have a max,min at the data point.
  
  				var fpDist;
  				if ( typeof curvedLinesOptions.fitPointDist == 'undefined') {
  					//estimate it
  					var minX = points[0];
  					var maxX = points[points.length - ps];
  					fpDist = (maxX - minX) / (500 * 100);
  					//x range / (estimated pixel length of placeholder * factor)
  				} else {
  					//use user defined value
  					fpDist = Number(curvedLinesOptions.fitPointDist);
  				}
  
  				for (var i = 0; i < points.length; i += ps) {
  
  					var frontX;
  					var backX;
  					curX = i;
  					curY = i + yPos;
  
  					//add point X s
  					frontX = points[curX] - fpDist;
  					backX = points[curX] + fpDist;
  
  					var factor = 2;
  					while (frontX == points[curX] || backX == points[curX]) {
  						//inside the ulp
  						frontX = points[curX] - (fpDist * factor);
  						backX = points[curX] + (fpDist * factor);
  						factor++;
  					}
  
  					//add curve points
  					xdata[j] = frontX;
  					ydata[j] = points[curY];
  					j++;
  
  					xdata[j] = points[curX];
  					ydata[j] = points[curY];
  					j++;
  
  					xdata[j] = backX;
  					ydata[j] = points[curY];
  					j++;
  				}
  			} else {
  				//just use the datapoints
  				for (var i = 0; i < points.length; i += ps) {
  					curX = i;
  					curY = i + yPos;
  
  					xdata[j] = points[curX];
  					ydata[j] = points[curY];
  					j++;
  				}
  			}
  
  			var n = xdata.length;
  
  			var y2 = new Array();
  			var delta = new Array();
  			y2[0] = 0;
  			y2[n - 1] = 0;
  			delta[0] = 0;
  
  			for (var i = 1; i < n - 1; ++i) {
  				var d = (xdata[i + 1] - xdata[i - 1]);
  				if (d == 0) {
  					//point before current point and after current point need some space in between
  					return [];
  				}
  
  				var s = (xdata[i] - xdata[i - 1]) / d;
  				var p = s * y2[i - 1] + 2;
  				y2[i] = (s - 1) / p;
  				delta[i] = (ydata[i + 1] - ydata[i]) / (xdata[i + 1] - xdata[i]) - (ydata[i] - ydata[i - 1]) / (xdata[i] - xdata[i - 1]);
  				delta[i] = (6 * delta[i] / (xdata[i + 1] - xdata[i - 1]) - s * delta[i - 1]) / p;
  			}
  
  			for (var j = n - 2; j >= 0; --j) {
  				y2[j] = y2[j] * y2[j + 1] + delta[j];
  			}
  
  			//   xmax  - xmin  / #points
  			var step = (xdata[n - 1] - xdata[0]) / (num - 1);
  
  			var xnew = new Array;
  			var ynew = new Array;
  			var result = new Array;
  
  			xnew[0] = xdata[0];
  			ynew[0] = ydata[0];
  
  			result.push(xnew[0]);
  			result.push(ynew[0]);
  
  			for ( j = 1; j < num; ++j) {
  				//new x point (sampling point for the created curve)
  				xnew[j] = xnew[0] + j * step;
  
  				var max = n - 1;
  				var min = 0;
  
  				while (max - min > 1) {
  					var k = Math.round((max + min) / 2);
  					if (xdata[k] > xnew[j]) {
  						max = k;
  					} else {
  						min = k;
  					}
  				}
  
  				//found point one to the left and one to the right of generated new point
  				var h = (xdata[max] - xdata[min]);
  
  				if (h == 0) {
  					//similar to above two points from original x data need some space between them
  					return [];
  				}
  
  				var a = (xdata[max] - xnew[j]) / h;
  				var b = (xnew[j] - xdata[min]) / h;
  
  				ynew[j] = a * ydata[min] + b * ydata[max] + ((a * a * a - a) * y2[min] + (b * b * b - b) * y2[max]) * (h * h) / 6;
  
  				result.push(xnew[j]);
  				result.push(ynew[j]);
  			}
  
  			return result;
  		}
  		
  		function hasInvalidParameters(curvedLinesOptions) {
  			if (typeof curvedLinesOptions.fit != 'undefined' ||
  			    typeof curvedLinesOptions.curvePointFactor != 'undefined' ||
  			    typeof curvedLinesOptions.fitPointDist != 'undefined') {
  			    	throw new Error("CurvedLines detected illegal parameters. The CurvedLines API changed with version 1.0.0 please check the options object.");
  			    	return true;
  			    }
  			return false;
  		}
  		
  
  	}//end init
  
  
  	$.plot.plugins.push({
  		init : init,
  		options : options,
  		name : 'curvedLines',
  		version : '1.1.1'
  	});
  
  })(jQuery);
  
  
  !function(a){"use strict";function b(a,b,c,d,e,f,g){var j,k,l,m,n,o,p,q,h=Math.pow,i=Math.sqrt;return j=i(h(c-a,2)+h(d-b,2)),k=i(h(e-c,2)+h(f-d,2)),l=g*j/(j+k),m=g-l,n=c+l*(a-e),o=d+l*(b-f),p=c-m*(a-e),q=d-m*(b-f),[n,o,p,q]}function d(b,c,d,e,f){var g=a.color.parse(f);g.a="number"==typeof e?e:.3,g.normalize(),g=g.toString(),c.beginPath(),c.moveTo(b[0][0],b[0][1]);for(var h=b.length,i=0;h>i;i++)c[b[i][3]].apply(c,b[i][2]);c.stroke(),c.lineWidth=0,c.lineTo(b[h-1][0],d),c.lineTo(b[0][0],d),c.closePath(),e!==!1&&(c.fillStyle=g,c.fill())}function e(a,b,d,e){(void 0===b||"bezier"!==b&&"quadratic"!==b)&&(b="quadratic"),b+="CurveTo",0==c.length?c.push([d[0],d[1],e.concat(d.slice(2)),b]):"quadraticCurveTo"==b&&2==d.length?(e=e.slice(0,2).concat(d),c.push([d[0],d[1],e,b])):c.push([d[2],d[3],e.concat(d.slice(2)),b])}function f(f,g,h){if(h.splines.show===!0){var k,l,m,i=[],j=h.splines.tension||.5,n=h.datapoints.points,o=h.datapoints.pointsize,p=f.getPlotOffset(),q=n.length,r=[];if(c=[],4>q/o)return a.extend(h.lines,h.splines),void 0;for(k=0;q>k;k+=o)l=n[k],m=n[k+1],null==l||l<h.xaxis.min||l>h.xaxis.max||m<h.yaxis.min||m>h.yaxis.max||r.push(h.xaxis.p2c(l)+p.left,h.yaxis.p2c(m)+p.top);for(q=r.length,k=0;q-2>k;k+=2)i=i.concat(b.apply(this,r.slice(k,k+6).concat([j])));for(g.save(),g.strokeStyle=h.color,g.lineWidth=h.splines.lineWidth,e(g,"quadratic",r.slice(0,4),i.slice(0,2)),k=2;q-3>k;k+=2)e(g,"bezier",r.slice(k,k+4),i.slice(2*k-2,2*k+2));e(g,"quadratic",r.slice(q-2,q),[i[2*q-10],i[2*q-9],r[q-4],r[q-3]]),d(c,g,f.height()+10,h.splines.fill,h.color),g.restore()}}var c=[];a.plot.plugins.push({init:function(a){a.hooks.drawSeries.push(f)},options:{series:{splines:{show:!1,lineWidth:2,tension:.5,fill:!1}}},name:"spline",version:"0.8.2"})}(jQuery);
  //! moment.js
  //! version : 2.9.0
  //! authors : Tim Wood, Iskren Chernev, Moment.js contributors
  //! license : MIT
  //! momentjs.com
  (function(a){function b(a,b,c){switch(arguments.length){case 2:return null!=a?a:b;case 3:return null!=a?a:null!=b?b:c;default:throw new Error("Implement me")}}function c(a,b){return Bb.call(a,b)}function d(){return{empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function e(a){vb.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+a)}function f(a,b){var c=!0;return o(function(){return c&&(e(a),c=!1),b.apply(this,arguments)},b)}function g(a,b){sc[a]||(e(b),sc[a]=!0)}function h(a,b){return function(c){return r(a.call(this,c),b)}}function i(a,b){return function(c){return this.localeData().ordinal(a.call(this,c),b)}}function j(a,b){var c,d,e=12*(b.year()-a.year())+(b.month()-a.month()),f=a.clone().add(e,"months");return 0>b-f?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)}function k(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&12>b&&(b+=12),d||12!==b||(b=0),b):b}function l(){}function m(a,b){b!==!1&&H(a),p(this,a),this._d=new Date(+a._d),uc===!1&&(uc=!0,vb.updateOffset(this),uc=!1)}function n(a){var b=A(a),c=b.year||0,d=b.quarter||0,e=b.month||0,f=b.week||0,g=b.day||0,h=b.hour||0,i=b.minute||0,j=b.second||0,k=b.millisecond||0;this._milliseconds=+k+1e3*j+6e4*i+36e5*h,this._days=+g+7*f,this._months=+e+3*d+12*c,this._data={},this._locale=vb.localeData(),this._bubble()}function o(a,b){for(var d in b)c(b,d)&&(a[d]=b[d]);return c(b,"toString")&&(a.toString=b.toString),c(b,"valueOf")&&(a.valueOf=b.valueOf),a}function p(a,b){var c,d,e;if("undefined"!=typeof b._isAMomentObject&&(a._isAMomentObject=b._isAMomentObject),"undefined"!=typeof b._i&&(a._i=b._i),"undefined"!=typeof b._f&&(a._f=b._f),"undefined"!=typeof b._l&&(a._l=b._l),"undefined"!=typeof b._strict&&(a._strict=b._strict),"undefined"!=typeof b._tzm&&(a._tzm=b._tzm),"undefined"!=typeof b._isUTC&&(a._isUTC=b._isUTC),"undefined"!=typeof b._offset&&(a._offset=b._offset),"undefined"!=typeof b._pf&&(a._pf=b._pf),"undefined"!=typeof b._locale&&(a._locale=b._locale),Kb.length>0)for(c in Kb)d=Kb[c],e=b[d],"undefined"!=typeof e&&(a[d]=e);return a}function q(a){return 0>a?Math.ceil(a):Math.floor(a)}function r(a,b,c){for(var d=""+Math.abs(a),e=a>=0;d.length<b;)d="0"+d;return(e?c?"+":"":"-")+d}function s(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function t(a,b){var c;return b=M(b,a),a.isBefore(b)?c=s(a,b):(c=s(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c}function u(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||(g(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period)."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=vb.duration(c,d),v(this,e,a),this}}function v(a,b,c,d){var e=b._milliseconds,f=b._days,g=b._months;d=null==d?!0:d,e&&a._d.setTime(+a._d+e*c),f&&pb(a,"Date",ob(a,"Date")+f*c),g&&nb(a,ob(a,"Month")+g*c),d&&vb.updateOffset(a,f||g)}function w(a){return"[object Array]"===Object.prototype.toString.call(a)}function x(a){return"[object Date]"===Object.prototype.toString.call(a)||a instanceof Date}function y(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;e>d;d++)(c&&a[d]!==b[d]||!c&&C(a[d])!==C(b[d]))&&g++;return g+f}function z(a){if(a){var b=a.toLowerCase().replace(/(.)s$/,"$1");a=lc[a]||mc[b]||b}return a}function A(a){var b,d,e={};for(d in a)c(a,d)&&(b=z(d),b&&(e[b]=a[d]));return e}function B(b){var c,d;if(0===b.indexOf("week"))c=7,d="day";else{if(0!==b.indexOf("month"))return;c=12,d="month"}vb[b]=function(e,f){var g,h,i=vb._locale[b],j=[];if("number"==typeof e&&(f=e,e=a),h=function(a){var b=vb().utc().set(d,a);return i.call(vb._locale,b,e||"")},null!=f)return h(f);for(g=0;c>g;g++)j.push(h(g));return j}}function C(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=b>=0?Math.floor(b):Math.ceil(b)),c}function D(a,b){return new Date(Date.UTC(a,b+1,0)).getUTCDate()}function E(a,b,c){return jb(vb([a,11,31+b-c]),b,c).week}function F(a){return G(a)?366:365}function G(a){return a%4===0&&a%100!==0||a%400===0}function H(a){var b;a._a&&-2===a._pf.overflow&&(b=a._a[Db]<0||a._a[Db]>11?Db:a._a[Eb]<1||a._a[Eb]>D(a._a[Cb],a._a[Db])?Eb:a._a[Fb]<0||a._a[Fb]>24||24===a._a[Fb]&&(0!==a._a[Gb]||0!==a._a[Hb]||0!==a._a[Ib])?Fb:a._a[Gb]<0||a._a[Gb]>59?Gb:a._a[Hb]<0||a._a[Hb]>59?Hb:a._a[Ib]<0||a._a[Ib]>999?Ib:-1,a._pf._overflowDayOfYear&&(Cb>b||b>Eb)&&(b=Eb),a._pf.overflow=b)}function I(b){return null==b._isValid&&(b._isValid=!isNaN(b._d.getTime())&&b._pf.overflow<0&&!b._pf.empty&&!b._pf.invalidMonth&&!b._pf.nullInput&&!b._pf.invalidFormat&&!b._pf.userInvalidated,b._strict&&(b._isValid=b._isValid&&0===b._pf.charsLeftOver&&0===b._pf.unusedTokens.length&&b._pf.bigHour===a)),b._isValid}function J(a){return a?a.toLowerCase().replace("_","-"):a}function K(a){for(var b,c,d,e,f=0;f<a.length;){for(e=J(a[f]).split("-"),b=e.length,c=J(a[f+1]),c=c?c.split("-"):null;b>0;){if(d=L(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&y(e,c,!0)>=b-1)break;b--}f++}return null}function L(a){var b=null;if(!Jb[a]&&Lb)try{b=vb.locale(),require("./locale/"+a),vb.locale(b)}catch(c){}return Jb[a]}function M(a,b){var c,d;return b._isUTC?(c=b.clone(),d=(vb.isMoment(a)||x(a)?+a:+vb(a))-+c,c._d.setTime(+c._d+d),vb.updateOffset(c,!1),c):vb(a).local()}function N(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function O(a){var b,c,d=a.match(Pb);for(b=0,c=d.length;c>b;b++)d[b]=rc[d[b]]?rc[d[b]]:N(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function P(a,b){return a.isValid()?(b=Q(b,a.localeData()),nc[b]||(nc[b]=O(b)),nc[b](a)):a.localeData().invalidDate()}function Q(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(Qb.lastIndex=0;d>=0&&Qb.test(a);)a=a.replace(Qb,c),Qb.lastIndex=0,d-=1;return a}function R(a,b){var c,d=b._strict;switch(a){case"Q":return _b;case"DDDD":return bc;case"YYYY":case"GGGG":case"gggg":return d?cc:Tb;case"Y":case"G":case"g":return ec;case"YYYYYY":case"YYYYY":case"GGGGG":case"ggggg":return d?dc:Ub;case"S":if(d)return _b;case"SS":if(d)return ac;case"SSS":if(d)return bc;case"DDD":return Sb;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":return Wb;case"a":case"A":return b._locale._meridiemParse;case"x":return Zb;case"X":return $b;case"Z":case"ZZ":return Xb;case"T":return Yb;case"SSSS":return Vb;case"MM":case"DD":case"YY":case"GG":case"gg":case"HH":case"hh":case"mm":case"ss":case"ww":case"WW":return d?ac:Rb;case"M":case"D":case"d":case"H":case"h":case"m":case"s":case"w":case"W":case"e":case"E":return Rb;case"Do":return d?b._locale._ordinalParse:b._locale._ordinalParseLenient;default:return c=new RegExp($(Z(a.replace("\\","")),"i"))}}function S(a){a=a||"";var b=a.match(Xb)||[],c=b[b.length-1]||[],d=(c+"").match(jc)||["-",0,0],e=+(60*d[1])+C(d[2]);return"+"===d[0]?e:-e}function T(a,b,c){var d,e=c._a;switch(a){case"Q":null!=b&&(e[Db]=3*(C(b)-1));break;case"M":case"MM":null!=b&&(e[Db]=C(b)-1);break;case"MMM":case"MMMM":d=c._locale.monthsParse(b,a,c._strict),null!=d?e[Db]=d:c._pf.invalidMonth=b;break;case"D":case"DD":null!=b&&(e[Eb]=C(b));break;case"Do":null!=b&&(e[Eb]=C(parseInt(b.match(/\d{1,2}/)[0],10)));break;case"DDD":case"DDDD":null!=b&&(c._dayOfYear=C(b));break;case"YY":e[Cb]=vb.parseTwoDigitYear(b);break;case"YYYY":case"YYYYY":case"YYYYYY":e[Cb]=C(b);break;case"a":case"A":c._meridiem=b;break;case"h":case"hh":c._pf.bigHour=!0;case"H":case"HH":e[Fb]=C(b);break;case"m":case"mm":e[Gb]=C(b);break;case"s":case"ss":e[Hb]=C(b);break;case"S":case"SS":case"SSS":case"SSSS":e[Ib]=C(1e3*("0."+b));break;case"x":c._d=new Date(C(b));break;case"X":c._d=new Date(1e3*parseFloat(b));break;case"Z":case"ZZ":c._useUTC=!0,c._tzm=S(b);break;case"dd":case"ddd":case"dddd":d=c._locale.weekdaysParse(b),null!=d?(c._w=c._w||{},c._w.d=d):c._pf.invalidWeekday=b;break;case"w":case"ww":case"W":case"WW":case"d":case"e":case"E":a=a.substr(0,1);case"gggg":case"GGGG":case"GGGGG":a=a.substr(0,2),b&&(c._w=c._w||{},c._w[a]=C(b));break;case"gg":case"GG":c._w=c._w||{},c._w[a]=vb.parseTwoDigitYear(b)}}function U(a){var c,d,e,f,g,h,i;c=a._w,null!=c.GG||null!=c.W||null!=c.E?(g=1,h=4,d=b(c.GG,a._a[Cb],jb(vb(),1,4).year),e=b(c.W,1),f=b(c.E,1)):(g=a._locale._week.dow,h=a._locale._week.doy,d=b(c.gg,a._a[Cb],jb(vb(),g,h).year),e=b(c.w,1),null!=c.d?(f=c.d,g>f&&++e):f=null!=c.e?c.e+g:g),i=kb(d,e,f,h,g),a._a[Cb]=i.year,a._dayOfYear=i.dayOfYear}function V(a){var c,d,e,f,g=[];if(!a._d){for(e=X(a),a._w&&null==a._a[Eb]&&null==a._a[Db]&&U(a),a._dayOfYear&&(f=b(a._a[Cb],e[Cb]),a._dayOfYear>F(f)&&(a._pf._overflowDayOfYear=!0),d=fb(f,0,a._dayOfYear),a._a[Db]=d.getUTCMonth(),a._a[Eb]=d.getUTCDate()),c=0;3>c&&null==a._a[c];++c)a._a[c]=g[c]=e[c];for(;7>c;c++)a._a[c]=g[c]=null==a._a[c]?2===c?1:0:a._a[c];24===a._a[Fb]&&0===a._a[Gb]&&0===a._a[Hb]&&0===a._a[Ib]&&(a._nextDay=!0,a._a[Fb]=0),a._d=(a._useUTC?fb:eb).apply(null,g),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[Fb]=24)}}function W(a){var b;a._d||(b=A(a._i),a._a=[b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],V(a))}function X(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function Y(b){if(b._f===vb.ISO_8601)return void ab(b);b._a=[],b._pf.empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,j=0;for(e=Q(b._f,b._locale).match(Pb)||[],c=0;c<e.length;c++)f=e[c],d=(h.match(R(f,b))||[])[0],d&&(g=h.substr(0,h.indexOf(d)),g.length>0&&b._pf.unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),j+=d.length),rc[f]?(d?b._pf.empty=!1:b._pf.unusedTokens.push(f),T(f,d,b)):b._strict&&!d&&b._pf.unusedTokens.push(f);b._pf.charsLeftOver=i-j,h.length>0&&b._pf.unusedInput.push(h),b._pf.bigHour===!0&&b._a[Fb]<=12&&(b._pf.bigHour=a),b._a[Fb]=k(b._locale,b._a[Fb],b._meridiem),V(b),H(b)}function Z(a){return a.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e})}function $(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function _(a){var b,c,e,f,g;if(0===a._f.length)return a._pf.invalidFormat=!0,void(a._d=new Date(0/0));for(f=0;f<a._f.length;f++)g=0,b=p({},a),null!=a._useUTC&&(b._useUTC=a._useUTC),b._pf=d(),b._f=a._f[f],Y(b),I(b)&&(g+=b._pf.charsLeftOver,g+=10*b._pf.unusedTokens.length,b._pf.score=g,(null==e||e>g)&&(e=g,c=b));o(a,c||b)}function ab(a){var b,c,d=a._i,e=fc.exec(d);if(e){for(a._pf.iso=!0,b=0,c=hc.length;c>b;b++)if(hc[b][1].exec(d)){a._f=hc[b][0]+(e[6]||" ");break}for(b=0,c=ic.length;c>b;b++)if(ic[b][1].exec(d)){a._f+=ic[b][0];break}d.match(Xb)&&(a._f+="Z"),Y(a)}else a._isValid=!1}function bb(a){ab(a),a._isValid===!1&&(delete a._isValid,vb.createFromInputFallback(a))}function cb(a,b){var c,d=[];for(c=0;c<a.length;++c)d.push(b(a[c],c));return d}function db(b){var c,d=b._i;d===a?b._d=new Date:x(d)?b._d=new Date(+d):null!==(c=Mb.exec(d))?b._d=new Date(+c[1]):"string"==typeof d?bb(b):w(d)?(b._a=cb(d.slice(0),function(a){return parseInt(a,10)}),V(b)):"object"==typeof d?W(b):"number"==typeof d?b._d=new Date(d):vb.createFromInputFallback(b)}function eb(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function fb(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function gb(a,b){if("string"==typeof a)if(isNaN(a)){if(a=b.weekdaysParse(a),"number"!=typeof a)return null}else a=parseInt(a,10);return a}function hb(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function ib(a,b,c){var d=vb.duration(a).abs(),e=Ab(d.as("s")),f=Ab(d.as("m")),g=Ab(d.as("h")),h=Ab(d.as("d")),i=Ab(d.as("M")),j=Ab(d.as("y")),k=e<oc.s&&["s",e]||1===f&&["m"]||f<oc.m&&["mm",f]||1===g&&["h"]||g<oc.h&&["hh",g]||1===h&&["d"]||h<oc.d&&["dd",h]||1===i&&["M"]||i<oc.M&&["MM",i]||1===j&&["y"]||["yy",j];return k[2]=b,k[3]=+a>0,k[4]=c,hb.apply({},k)}function jb(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=vb(a).add(f,"d"),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function kb(a,b,c,d,e){var f,g,h=fb(a,0,1).getUTCDay();return h=0===h?7:h,c=null!=c?c:e,f=e-h+(h>d?7:0)-(e>h?7:0),g=7*(b-1)+(c-e)+f+1,{year:g>0?a:a-1,dayOfYear:g>0?g:F(a-1)+g}}function lb(b){var c,d=b._i,e=b._f;return b._locale=b._locale||vb.localeData(b._l),null===d||e===a&&""===d?vb.invalid({nullInput:!0}):("string"==typeof d&&(b._i=d=b._locale.preparse(d)),vb.isMoment(d)?new m(d,!0):(e?w(e)?_(b):Y(b):db(b),c=new m(b),c._nextDay&&(c.add(1,"d"),c._nextDay=a),c))}function mb(a,b){var c,d;if(1===b.length&&w(b[0])&&(b=b[0]),!b.length)return vb();for(c=b[0],d=1;d<b.length;++d)b[d][a](c)&&(c=b[d]);return c}function nb(a,b){var c;return"string"==typeof b&&(b=a.localeData().monthsParse(b),"number"!=typeof b)?a:(c=Math.min(a.date(),D(a.year(),b)),a._d["set"+(a._isUTC?"UTC":"")+"Month"](b,c),a)}function ob(a,b){return a._d["get"+(a._isUTC?"UTC":"")+b]()}function pb(a,b,c){return"Month"===b?nb(a,c):a._d["set"+(a._isUTC?"UTC":"")+b](c)}function qb(a,b){return function(c){return null!=c?(pb(this,a,c),vb.updateOffset(this,b),this):ob(this,a)}}function rb(a){return 400*a/146097}function sb(a){return 146097*a/400}function tb(a){vb.duration.fn[a]=function(){return this._data[a]}}function ub(a){"undefined"==typeof ender&&(wb=zb.moment,zb.moment=a?f("Accessing Moment through the global scope is deprecated, and will be removed in an upcoming release.",vb):vb)}for(var vb,wb,xb,yb="2.9.0",zb="undefined"==typeof global||"undefined"!=typeof window&&window!==global.window?this:global,Ab=Math.round,Bb=Object.prototype.hasOwnProperty,Cb=0,Db=1,Eb=2,Fb=3,Gb=4,Hb=5,Ib=6,Jb={},Kb=[],Lb="undefined"!=typeof module&&module&&module.exports,Mb=/^\/?Date\((\-?\d+)/i,Nb=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,Ob=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,Pb=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|x|X|zz?|ZZ?|.)/g,Qb=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,Rb=/\d\d?/,Sb=/\d{1,3}/,Tb=/\d{1,4}/,Ub=/[+\-]?\d{1,6}/,Vb=/\d+/,Wb=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Xb=/Z|[\+\-]\d\d:?\d\d/gi,Yb=/T/i,Zb=/[\+\-]?\d+/,$b=/[\+\-]?\d+(\.\d{1,3})?/,_b=/\d/,ac=/\d\d/,bc=/\d{3}/,cc=/\d{4}/,dc=/[+-]?\d{6}/,ec=/[+-]?\d+/,fc=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,gc="YYYY-MM-DDTHH:mm:ssZ",hc=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],ic=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],jc=/([\+\-]|\d\d)/gi,kc=("Date|Hours|Minutes|Seconds|Milliseconds".split("|"),{Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6}),lc={ms:"millisecond",s:"second",m:"minute",h:"hour",d:"day",D:"date",w:"week",W:"isoWeek",M:"month",Q:"quarter",y:"year",DDD:"dayOfYear",e:"weekday",E:"isoWeekday",gg:"weekYear",GG:"isoWeekYear"},mc={dayofyear:"dayOfYear",isoweekday:"isoWeekday",isoweek:"isoWeek",weekyear:"weekYear",isoweekyear:"isoWeekYear"},nc={},oc={s:45,m:45,h:22,d:26,M:11},pc="DDD w W M D d".split(" "),qc="M D H h m s w W".split(" "),rc={M:function(){return this.month()+1},MMM:function(a){return this.localeData().monthsShort(this,a)},MMMM:function(a){return this.localeData().months(this,a)},D:function(){return this.date()},DDD:function(){return this.dayOfYear()},d:function(){return this.day()},dd:function(a){return this.localeData().weekdaysMin(this,a)},ddd:function(a){return this.localeData().weekdaysShort(this,a)},dddd:function(a){return this.localeData().weekdays(this,a)},w:function(){return this.week()},W:function(){return this.isoWeek()},YY:function(){return r(this.year()%100,2)},YYYY:function(){return r(this.year(),4)},YYYYY:function(){return r(this.year(),5)},YYYYYY:function(){var a=this.year(),b=a>=0?"+":"-";return b+r(Math.abs(a),6)},gg:function(){return r(this.weekYear()%100,2)},gggg:function(){return r(this.weekYear(),4)},ggggg:function(){return r(this.weekYear(),5)},GG:function(){return r(this.isoWeekYear()%100,2)},GGGG:function(){return r(this.isoWeekYear(),4)},GGGGG:function(){return r(this.isoWeekYear(),5)},e:function(){return this.weekday()},E:function(){return this.isoWeekday()},a:function(){return this.localeData().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.localeData().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return C(this.milliseconds()/100)},SS:function(){return r(C(this.milliseconds()/10),2)},SSS:function(){return r(this.milliseconds(),3)},SSSS:function(){return r(this.milliseconds(),3)},Z:function(){var a=this.utcOffset(),b="+";return 0>a&&(a=-a,b="-"),b+r(C(a/60),2)+":"+r(C(a)%60,2)},ZZ:function(){var a=this.utcOffset(),b="+";return 0>a&&(a=-a,b="-"),b+r(C(a/60),2)+r(C(a)%60,2)},z:function(){return this.zoneAbbr()},zz:function(){return this.zoneName()},x:function(){return this.valueOf()},X:function(){return this.unix()},Q:function(){return this.quarter()}},sc={},tc=["months","monthsShort","weekdays","weekdaysShort","weekdaysMin"],uc=!1;pc.length;)xb=pc.pop(),rc[xb+"o"]=i(rc[xb],xb);for(;qc.length;)xb=qc.pop(),rc[xb+xb]=h(rc[xb],2);rc.DDDD=h(rc.DDD,3),o(l.prototype,{set:function(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(a){return this._months[a.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(a){return this._monthsShort[a.month()]},monthsParse:function(a,b,c){var d,e,f;for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),d=0;12>d;d++){if(e=vb.utc([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(a){return this._weekdays[a.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(a){return this._weekdaysShort[a.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(a){return this._weekdaysMin[a.day()]},weekdaysParse:function(a){var b,c,d;for(this._weekdaysParse||(this._weekdaysParse=[]),b=0;7>b;b++)if(this._weekdaysParse[b]||(c=vb([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b},_longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY LT",LLLL:"dddd, MMMM D, YYYY LT"},longDateFormat:function(a){var b=this._longDateFormat[a];return!b&&this._longDateFormat[a.toUpperCase()]&&(b=this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a]=b),b},isPM:function(a){return"p"===(a+"").toLowerCase().charAt(0)},_meridiemParse:/[ap]\.?m?\.?/i,meridiem:function(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},calendar:function(a,b,c){var d=this._calendar[a];return"function"==typeof d?d.apply(b,[c]):d},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)},pastFuture:function(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)},ordinal:function(a){return this._ordinal.replace("%d",a)},_ordinal:"%d",_ordinalParse:/\d{1,2}/,preparse:function(a){return a},postformat:function(a){return a},week:function(a){return jb(a,this._week.dow,this._week.doy).week},_week:{dow:0,doy:6},firstDayOfWeek:function(){return this._week.dow},firstDayOfYear:function(){return this._week.doy},_invalidDate:"Invalid date",invalidDate:function(){return this._invalidDate}}),vb=function(b,c,e,f){var g;return"boolean"==typeof e&&(f=e,e=a),g={},g._isAMomentObject=!0,g._i=b,g._f=c,g._l=e,g._strict=f,g._isUTC=!1,g._pf=d(),lb(g)},vb.suppressDeprecationWarnings=!1,vb.createFromInputFallback=f("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),vb.min=function(){var a=[].slice.call(arguments,0);return mb("isBefore",a)},vb.max=function(){var a=[].slice.call(arguments,0);return mb("isAfter",a)},vb.utc=function(b,c,e,f){var g;return"boolean"==typeof e&&(f=e,e=a),g={},g._isAMomentObject=!0,g._useUTC=!0,g._isUTC=!0,g._l=e,g._i=b,g._f=c,g._strict=f,g._pf=d(),lb(g).utc()},vb.unix=function(a){return vb(1e3*a)},vb.duration=function(a,b){var d,e,f,g,h=a,i=null;return vb.isDuration(a)?h={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(h={},b?h[b]=a:h.milliseconds=a):(i=Nb.exec(a))?(d="-"===i[1]?-1:1,h={y:0,d:C(i[Eb])*d,h:C(i[Fb])*d,m:C(i[Gb])*d,s:C(i[Hb])*d,ms:C(i[Ib])*d}):(i=Ob.exec(a))?(d="-"===i[1]?-1:1,f=function(a){var b=a&&parseFloat(a.replace(",","."));return(isNaN(b)?0:b)*d},h={y:f(i[2]),M:f(i[3]),d:f(i[4]),h:f(i[5]),m:f(i[6]),s:f(i[7]),w:f(i[8])}):null==h?h={}:"object"==typeof h&&("from"in h||"to"in h)&&(g=t(vb(h.from),vb(h.to)),h={},h.ms=g.milliseconds,h.M=g.months),e=new n(h),vb.isDuration(a)&&c(a,"_locale")&&(e._locale=a._locale),e},vb.version=yb,vb.defaultFormat=gc,vb.ISO_8601=function(){},vb.momentProperties=Kb,vb.updateOffset=function(){},vb.relativeTimeThreshold=function(b,c){return oc[b]===a?!1:c===a?oc[b]:(oc[b]=c,!0)},vb.lang=f("moment.lang is deprecated. Use moment.locale instead.",function(a,b){return vb.locale(a,b)}),vb.locale=function(a,b){var c;return a&&(c="undefined"!=typeof b?vb.defineLocale(a,b):vb.localeData(a),c&&(vb.duration._locale=vb._locale=c)),vb._locale._abbr},vb.defineLocale=function(a,b){return null!==b?(b.abbr=a,Jb[a]||(Jb[a]=new l),Jb[a].set(b),vb.locale(a),Jb[a]):(delete Jb[a],null)},vb.langData=f("moment.langData is deprecated. Use moment.localeData instead.",function(a){return vb.localeData(a)}),vb.localeData=function(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return vb._locale;if(!w(a)){if(b=L(a))return b;a=[a]}return K(a)},vb.isMoment=function(a){return a instanceof m||null!=a&&c(a,"_isAMomentObject")},vb.isDuration=function(a){return a instanceof n};for(xb=tc.length-1;xb>=0;--xb)B(tc[xb]);vb.normalizeUnits=function(a){return z(a)},vb.invalid=function(a){var b=vb.utc(0/0);return null!=a?o(b._pf,a):b._pf.userInvalidated=!0,b},vb.parseZone=function(){return vb.apply(null,arguments).parseZone()},vb.parseTwoDigitYear=function(a){return C(a)+(C(a)>68?1900:2e3)},vb.isDate=x,o(vb.fn=m.prototype,{clone:function(){return vb(this)},valueOf:function(){return+this._d-6e4*(this._offset||0)},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){var a=vb(this).utc();return 0<a.year()&&a.year()<=9999?"function"==typeof Date.prototype.toISOString?this.toDate().toISOString():P(a,"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]"):P(a,"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]")},toArray:function(){var a=this;return[a.year(),a.month(),a.date(),a.hours(),a.minutes(),a.seconds(),a.milliseconds()]},isValid:function(){return I(this)},isDSTShifted:function(){return this._a?this.isValid()&&y(this._a,(this._isUTC?vb.utc(this._a):vb(this._a)).toArray())>0:!1},parsingFlags:function(){return o({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(a){return this.utcOffset(0,a)},local:function(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(this._dateUtcOffset(),"m")),this},format:function(a){var b=P(this,a||vb.defaultFormat);return this.localeData().postformat(b)},add:u(1,"add"),subtract:u(-1,"subtract"),diff:function(a,b,c){var d,e,f=M(a,this),g=6e4*(f.utcOffset()-this.utcOffset());return b=z(b),"year"===b||"month"===b||"quarter"===b?(e=j(this,f),"quarter"===b?e/=3:"year"===b&&(e/=12)):(d=this-f,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-g)/864e5:"week"===b?(d-g)/6048e5:d),c?e:q(e)},from:function(a,b){return vb.duration({to:this,from:a}).locale(this.locale()).humanize(!b)},fromNow:function(a){return this.from(vb(),a)},calendar:function(a){var b=a||vb(),c=M(b,this).startOf("day"),d=this.diff(c,"days",!0),e=-6>d?"sameElse":-1>d?"lastWeek":0>d?"lastDay":1>d?"sameDay":2>d?"nextDay":7>d?"nextWeek":"sameElse";return this.format(this.localeData().calendar(e,this,vb(b)))},isLeapYear:function(){return G(this.year())},isDST:function(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},day:function(a){var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=gb(a,this.localeData()),this.add(a-b,"d")):b},month:qb("Month",!0),startOf:function(a){switch(a=z(a)){case"year":this.month(0);case"quarter":case"month":this.date(1);case"week":case"isoWeek":case"day":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}return"week"===a?this.weekday(0):"isoWeek"===a&&this.isoWeekday(1),"quarter"===a&&this.month(3*Math.floor(this.month()/3)),this},endOf:function(b){return b=z(b),b===a||"millisecond"===b?this:this.startOf(b).add(1,"isoWeek"===b?"week":b).subtract(1,"ms")},isAfter:function(a,b){var c;return b=z("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=vb.isMoment(a)?a:vb(a),+this>+a):(c=vb.isMoment(a)?+a:+vb(a),c<+this.clone().startOf(b))},isBefore:function(a,b){var c;return b=z("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=vb.isMoment(a)?a:vb(a),+a>+this):(c=vb.isMoment(a)?+a:+vb(a),+this.clone().endOf(b)<c)},isBetween:function(a,b,c){return this.isAfter(a,c)&&this.isBefore(b,c)},isSame:function(a,b){var c;return b=z(b||"millisecond"),"millisecond"===b?(a=vb.isMoment(a)?a:vb(a),+this===+a):(c=+vb(a),+this.clone().startOf(b)<=c&&c<=+this.clone().endOf(b))},min:f("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(a){return a=vb.apply(null,arguments),this>a?this:a}),max:f("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(a){return a=vb.apply(null,arguments),a>this?this:a}),zone:f("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",function(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}),utcOffset:function(a,b){var c,d=this._offset||0;return null!=a?("string"==typeof a&&(a=S(a)),Math.abs(a)<16&&(a=60*a),!this._isUTC&&b&&(c=this._dateUtcOffset()),this._offset=a,this._isUTC=!0,null!=c&&this.add(c,"m"),d!==a&&(!b||this._changeInProgress?v(this,vb.duration(a-d,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,vb.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?d:this._dateUtcOffset()},isLocal:function(){return!this._isUTC},isUtcOffset:function(){return this._isUTC},isUtc:function(){return this._isUTC&&0===this._offset},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(S(this._i)),this},hasAlignedHourOffset:function(a){return a=a?vb(a).utcOffset():0,(this.utcOffset()-a)%60===0},daysInMonth:function(){return D(this.year(),this.month())},dayOfYear:function(a){var b=Ab((vb(this).startOf("day")-vb(this).startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")},quarter:function(a){return null==a?Math.ceil((this.month()+1)/3):this.month(3*(a-1)+this.month()%3)},weekYear:function(a){var b=jb(this,this.localeData()._week.dow,this.localeData()._week.doy).year;return null==a?b:this.add(a-b,"y")},isoWeekYear:function(a){var b=jb(this,1,4).year;return null==a?b:this.add(a-b,"y")},week:function(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")},isoWeek:function(a){var b=jb(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")},weekday:function(a){var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")},isoWeekday:function(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)},isoWeeksInYear:function(){return E(this.year(),1,4)},weeksInYear:function(){var a=this.localeData()._week;return E(this.year(),a.dow,a.doy)},get:function(a){return a=z(a),this[a]()},set:function(a,b){var c;if("object"==typeof a)for(c in a)this.set(c,a[c]);else a=z(a),"function"==typeof this[a]&&this[a](b);return this},locale:function(b){var c;return b===a?this._locale._abbr:(c=vb.localeData(b),null!=c&&(this._locale=c),this)},lang:f("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(b){return b===a?this.localeData():this.locale(b)}),localeData:function(){return this._locale},_dateUtcOffset:function(){return 15*-Math.round(this._d.getTimezoneOffset()/15)}}),vb.fn.millisecond=vb.fn.milliseconds=qb("Milliseconds",!1),vb.fn.second=vb.fn.seconds=qb("Seconds",!1),vb.fn.minute=vb.fn.minutes=qb("Minutes",!1),vb.fn.hour=vb.fn.hours=qb("Hours",!0),vb.fn.date=qb("Date",!0),vb.fn.dates=f("dates accessor is deprecated. Use date instead.",qb("Date",!0)),vb.fn.year=qb("FullYear",!0),vb.fn.years=f("years accessor is deprecated. Use year instead.",qb("FullYear",!0)),vb.fn.days=vb.fn.day,vb.fn.months=vb.fn.month,vb.fn.weeks=vb.fn.week,vb.fn.isoWeeks=vb.fn.isoWeek,vb.fn.quarters=vb.fn.quarter,vb.fn.toJSON=vb.fn.toISOString,vb.fn.isUTC=vb.fn.isUtc,o(vb.duration.fn=n.prototype,{_bubble:function(){var a,b,c,d=this._milliseconds,e=this._days,f=this._months,g=this._data,h=0;g.milliseconds=d%1e3,a=q(d/1e3),g.seconds=a%60,b=q(a/60),g.minutes=b%60,c=q(b/60),g.hours=c%24,e+=q(c/24),h=q(rb(e)),e-=q(sb(h)),f+=q(e/30),e%=30,h+=q(f/12),f%=12,g.days=e,g.months=f,g.years=h},abs:function(){return this._milliseconds=Math.abs(this._milliseconds),this._days=Math.abs(this._days),this._months=Math.abs(this._months),this._data.milliseconds=Math.abs(this._data.milliseconds),this._data.seconds=Math.abs(this._data.seconds),this._data.minutes=Math.abs(this._data.minutes),this._data.hours=Math.abs(this._data.hours),this._data.months=Math.abs(this._data.months),this._data.years=Math.abs(this._data.years),this},weeks:function(){return q(this.days()/7)},valueOf:function(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*C(this._months/12)
  },humanize:function(a){var b=ib(this,!a,this.localeData());return a&&(b=this.localeData().pastFuture(+this,b)),this.localeData().postformat(b)},add:function(a,b){var c=vb.duration(a,b);return this._milliseconds+=c._milliseconds,this._days+=c._days,this._months+=c._months,this._bubble(),this},subtract:function(a,b){var c=vb.duration(a,b);return this._milliseconds-=c._milliseconds,this._days-=c._days,this._months-=c._months,this._bubble(),this},get:function(a){return a=z(a),this[a.toLowerCase()+"s"]()},as:function(a){var b,c;if(a=z(a),"month"===a||"year"===a)return b=this._days+this._milliseconds/864e5,c=this._months+12*rb(b),"month"===a?c:c/12;switch(b=this._days+Math.round(sb(this._months/12)),a){case"week":return b/7+this._milliseconds/6048e5;case"day":return b+this._milliseconds/864e5;case"hour":return 24*b+this._milliseconds/36e5;case"minute":return 24*b*60+this._milliseconds/6e4;case"second":return 24*b*60*60+this._milliseconds/1e3;case"millisecond":return Math.floor(24*b*60*60*1e3)+this._milliseconds;default:throw new Error("Unknown unit "+a)}},lang:vb.fn.lang,locale:vb.fn.locale,toIsoString:f("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",function(){return this.toISOString()}),toISOString:function(){var a=Math.abs(this.years()),b=Math.abs(this.months()),c=Math.abs(this.days()),d=Math.abs(this.hours()),e=Math.abs(this.minutes()),f=Math.abs(this.seconds()+this.milliseconds()/1e3);return this.asSeconds()?(this.asSeconds()<0?"-":"")+"P"+(a?a+"Y":"")+(b?b+"M":"")+(c?c+"D":"")+(d||e||f?"T":"")+(d?d+"H":"")+(e?e+"M":"")+(f?f+"S":""):"P0D"},localeData:function(){return this._locale},toJSON:function(){return this.toISOString()}}),vb.duration.fn.toString=vb.duration.fn.toISOString;for(xb in kc)c(kc,xb)&&tb(xb.toLowerCase());vb.duration.fn.asMilliseconds=function(){return this.as("ms")},vb.duration.fn.asSeconds=function(){return this.as("s")},vb.duration.fn.asMinutes=function(){return this.as("m")},vb.duration.fn.asHours=function(){return this.as("h")},vb.duration.fn.asDays=function(){return this.as("d")},vb.duration.fn.asWeeks=function(){return this.as("weeks")},vb.duration.fn.asMonths=function(){return this.as("M")},vb.duration.fn.asYears=function(){return this.as("y")},vb.locale("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===C(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),Lb?module.exports=vb:"function"==typeof define&&define.amd?(define(function(a,b,c){return c.config&&c.config()&&c.config().noGlobal===!0&&(zb.moment=wb),vb}),ub(!0)):ub()}).call(this);
  /**
  * @version: 2.1.24
  * @author: Dan Grossman http://www.dangrossman.info/
  * @copyright: Copyright (c) 2012-2016 Dan Grossman. All rights reserved.
  * @license: Licensed under the MIT license. See http://www.opensource.org/licenses/mit-license.php
  * @website: https://www.improvely.com/
  */
  // Follow the UMD template https://github.com/umdjs/umd/blob/master/templates/returnExportsGlobal.js
  (function (root, factory) {
      if (typeof define === 'function' && define.amd) {
          // AMD. Make globaly available as well
          define(['moment', 'jquery'], function (moment, jquery) {
              return (root.daterangepicker = factory(moment, jquery));
          });
      } else if (typeof module === 'object' && module.exports) {
          // Node / Browserify
          //isomorphic issue
          var jQuery = (typeof window != 'undefined') ? window.jQuery : undefined;
          if (!jQuery) {
              jQuery = require('jquery');
              if (!jQuery.fn) jQuery.fn = {};
          }
          module.exports = factory(require('moment'), jQuery);
      } else {
          // Browser globals
          root.daterangepicker = factory(root.moment, root.jQuery);
      }
  }(this, function(moment, $) {
      var DateRangePicker = function(element, options, cb) {
  
          //default settings for options
          this.parentEl = 'body';
          this.element = $(element);
          this.startDate = moment().startOf('day');
          this.endDate = moment().endOf('day');
          this.minDate = false;
          this.maxDate = false;
          this.dateLimit = false;
          this.autoApply = false;
          this.singleDatePicker = false;
          this.showDropdowns = false;
          this.showWeekNumbers = false;
          this.showISOWeekNumbers = false;
          this.showCustomRangeLabel = true;
          this.timePicker = false;
          this.timePicker24Hour = false;
          this.timePickerIncrement = 1;
          this.timePickerSeconds = false;
          this.linkedCalendars = true;
          this.autoUpdateInput = true;
          this.alwaysShowCalendars = false;
          this.ranges = {};
  
          this.opens = 'right';
          if (this.element.hasClass('pull-right'))
              this.opens = 'left';
  
          this.drops = 'down';
          if (this.element.hasClass('dropup'))
              this.drops = 'up';
  
          this.buttonClasses = 'btn btn-sm';
          this.applyClass = 'btn-success';
          this.cancelClass = 'btn-default';
  
          this.locale = {
              direction: 'ltr',
              format: moment.localeData().longDateFormat('L'),
              separator: ' - ',
              applyLabel: 'Apply',
              cancelLabel: 'Cancel',
              weekLabel: 'W',
              customRangeLabel: 'Custom Range',
              daysOfWeek: moment.weekdaysMin(),
              monthNames: moment.monthsShort(),
              firstDay: moment.localeData().firstDayOfWeek()
          };
  
          this.callback = function() { };
  
          //some state information
          this.isShowing = false;
          this.leftCalendar = {};
          this.rightCalendar = {};
  
          //custom options from user
          if (typeof options !== 'object' || options === null)
              options = {};
  
          //allow setting options with data attributes
          //data-api options will be overwritten with custom javascript options
          options = $.extend(this.element.data(), options);
  
          //html template for the picker UI
          if (typeof options.template !== 'string' && !(options.template instanceof $))
              options.template = '<div class="daterangepicker dropdown-menu">' +
                  '<div class="calendar left">' +
                      '<div class="daterangepicker_input">' +
                        '<input class="input-mini form-control" type="text" name="daterangepicker_start" value="" />' +
                        '<i class="fa fa-calendar glyphicon glyphicon-calendar"></i>' +
                        '<div class="calendar-time">' +
                          '<div></div>' +
                          '<i class="fa fa-clock-o glyphicon glyphicon-time"></i>' +
                        '</div>' +
                      '</div>' +
                      '<div class="calendar-table"></div>' +
                  '</div>' +
                  '<div class="calendar right">' +
                      '<div class="daterangepicker_input">' +
                        '<input class="input-mini form-control" type="text" name="daterangepicker_end" value="" />' +
                        '<i class="fa fa-calendar glyphicon glyphicon-calendar"></i>' +
                        '<div class="calendar-time">' +
                          '<div></div>' +
                          '<i class="fa fa-clock-o glyphicon glyphicon-time"></i>' +
                        '</div>' +
                      '</div>' +
                      '<div class="calendar-table"></div>' +
                  '</div>' +
                  '<div class="ranges">' +
                      '<div class="range_inputs">' +
                          '<button class="applyBtn" disabled="disabled" type="button"></button> ' +
                          '<button class="cancelBtn" type="button"></button>' +
                      '</div>' +
                  '</div>' +
              '</div>';
  
          this.parentEl = (options.parentEl && $(options.parentEl).length) ? $(options.parentEl) : $(this.parentEl);
          this.container = $(options.template).appendTo(this.parentEl);
  
          //
          // handle all the possible options overriding defaults
          //
  
          if (typeof options.locale === 'object') {
  
              if (typeof options.locale.direction === 'string')
                  this.locale.direction = options.locale.direction;
  
              if (typeof options.locale.format === 'string')
                  this.locale.format = options.locale.format;
  
              if (typeof options.locale.separator === 'string')
                  this.locale.separator = options.locale.separator;
  
              if (typeof options.locale.daysOfWeek === 'object')
                  this.locale.daysOfWeek = options.locale.daysOfWeek.slice();
  
              if (typeof options.locale.monthNames === 'object')
                this.locale.monthNames = options.locale.monthNames.slice();
  
              if (typeof options.locale.firstDay === 'number')
                this.locale.firstDay = options.locale.firstDay;
  
              if (typeof options.locale.applyLabel === 'string')
                this.locale.applyLabel = options.locale.applyLabel;
  
              if (typeof options.locale.cancelLabel === 'string')
                this.locale.cancelLabel = options.locale.cancelLabel;
  
              if (typeof options.locale.weekLabel === 'string')
                this.locale.weekLabel = options.locale.weekLabel;
  
              if (typeof options.locale.customRangeLabel === 'string')
                this.locale.customRangeLabel = options.locale.customRangeLabel;
  
          }
          this.container.addClass(this.locale.direction);
  
          if (typeof options.startDate === 'string')
              this.startDate = moment(options.startDate, this.locale.format);
  
          if (typeof options.endDate === 'string')
              this.endDate = moment(options.endDate, this.locale.format);
  
          if (typeof options.minDate === 'string')
              this.minDate = moment(options.minDate, this.locale.format);
  
          if (typeof options.maxDate === 'string')
              this.maxDate = moment(options.maxDate, this.locale.format);
  
          if (typeof options.startDate === 'object')
              this.startDate = moment(options.startDate);
  
          if (typeof options.endDate === 'object')
              this.endDate = moment(options.endDate);
  
          if (typeof options.minDate === 'object')
              this.minDate = moment(options.minDate);
  
          if (typeof options.maxDate === 'object')
              this.maxDate = moment(options.maxDate);
  
          // sanity check for bad options
          if (this.minDate && this.startDate.isBefore(this.minDate))
              this.startDate = this.minDate.clone();
  
          // sanity check for bad options
          if (this.maxDate && this.endDate.isAfter(this.maxDate))
              this.endDate = this.maxDate.clone();
  
          if (typeof options.applyClass === 'string')
              this.applyClass = options.applyClass;
  
          if (typeof options.cancelClass === 'string')
              this.cancelClass = options.cancelClass;
  
          if (typeof options.dateLimit === 'object')
              this.dateLimit = options.dateLimit;
  
          if (typeof options.opens === 'string')
              this.opens = options.opens;
  
          if (typeof options.drops === 'string')
              this.drops = options.drops;
  
          if (typeof options.showWeekNumbers === 'boolean')
              this.showWeekNumbers = options.showWeekNumbers;
  
          if (typeof options.showISOWeekNumbers === 'boolean')
              this.showISOWeekNumbers = options.showISOWeekNumbers;
  
          if (typeof options.buttonClasses === 'string')
              this.buttonClasses = options.buttonClasses;
  
          if (typeof options.buttonClasses === 'object')
              this.buttonClasses = options.buttonClasses.join(' ');
  
          if (typeof options.singleClasses === 'string')
              this.singleClasses = options.singleClasses;
  
          if (typeof options.singleClasses === 'object')
              this.singleClasses = options.singleClasses.join(' ');
  
  
          if (typeof options.showDropdowns === 'boolean')
              this.showDropdowns = options.showDropdowns;
  
          if (typeof options.showCustomRangeLabel === 'boolean')
              this.showCustomRangeLabel = options.showCustomRangeLabel;
  
          if (typeof options.singleDatePicker === 'boolean') {
              this.singleDatePicker = options.singleDatePicker;
              if (this.singleDatePicker)
                  this.endDate = this.startDate.clone();
          }
  
          if (typeof options.timePicker === 'boolean')
              this.timePicker = options.timePicker;
  
          if (typeof options.timePickerSeconds === 'boolean')
              this.timePickerSeconds = options.timePickerSeconds;
  
          if (typeof options.timePickerIncrement === 'number')
              this.timePickerIncrement = options.timePickerIncrement;
  
          if (typeof options.timePicker24Hour === 'boolean')
              this.timePicker24Hour = options.timePicker24Hour;
  
          if (typeof options.autoApply === 'boolean')
              this.autoApply = options.autoApply;
  
          if (typeof options.autoUpdateInput === 'boolean')
              this.autoUpdateInput = options.autoUpdateInput;
  
          if (typeof options.linkedCalendars === 'boolean')
              this.linkedCalendars = options.linkedCalendars;
  
          if (typeof options.isInvalidDate === 'function')
              this.isInvalidDate = options.isInvalidDate;
  
          if (typeof options.isCustomDate === 'function')
              this.isCustomDate = options.isCustomDate;
  
          if (typeof options.alwaysShowCalendars === 'boolean')
              this.alwaysShowCalendars = options.alwaysShowCalendars;
  
          // update day names order to firstDay
          if (this.locale.firstDay != 0) {
              var iterator = this.locale.firstDay;
              while (iterator > 0) {
                  this.locale.daysOfWeek.push(this.locale.daysOfWeek.shift());
                  iterator--;
              }
          }
  
          var start, end, range;
  
          //if no start/end dates set, check if an input element contains initial values
          if (typeof options.startDate === 'undefined' && typeof options.endDate === 'undefined') {
              if ($(this.element).is('input[type=text]')) {
                  var val = $(this.element).val(),
                      split = val.split(this.locale.separator);
  
                  start = end = null;
  
                  if (split.length == 2) {
                      start = moment(split[0], this.locale.format);
                      end = moment(split[1], this.locale.format);
                  } else if (this.singleDatePicker && val !== "") {
                      start = moment(val, this.locale.format);
                      end = moment(val, this.locale.format);
                  }
                  if (start !== null && end !== null) {
                      this.setStartDate(start);
                      this.setEndDate(end);
                  }
              }
          }
  
          if (typeof options.ranges === 'object') {
              for (range in options.ranges) {
  
                  if (typeof options.ranges[range][0] === 'string')
                      start = moment(options.ranges[range][0], this.locale.format);
                  else
                      start = moment(options.ranges[range][0]);
  
                  if (typeof options.ranges[range][1] === 'string')
                      end = moment(options.ranges[range][1], this.locale.format);
                  else
                      end = moment(options.ranges[range][1]);
  
                  // If the start or end date exceed those allowed by the minDate or dateLimit
                  // options, shorten the range to the allowable period.
                  if (this.minDate && start.isBefore(this.minDate))
                      start = this.minDate.clone();
  
                  var maxDate = this.maxDate;
                  if (this.dateLimit && maxDate && start.clone().add(this.dateLimit).isAfter(maxDate))
                      maxDate = start.clone().add(this.dateLimit);
                  if (maxDate && end.isAfter(maxDate))
                      end = maxDate.clone();
  
                  // If the end of the range is before the minimum or the start of the range is
                  // after the maximum, don't display this range option at all.
                  if ((this.minDate && end.isBefore(this.minDate, this.timepicker ? 'minute' : 'day')) 
                    || (maxDate && start.isAfter(maxDate, this.timepicker ? 'minute' : 'day')))
                      continue;
  
                  //Support unicode chars in the range names.
                  var elem = document.createElement('textarea');
                  elem.innerHTML = range;
                  var rangeHtml = elem.value;
  
                  this.ranges[rangeHtml] = [start, end];
              }
  
              var list = '<ul>';
              for (range in this.ranges) {
                  list += '<li data-range-key="' + range + '">' + range + '</li>';
              }
              if (this.showCustomRangeLabel) {
                  list += '<li data-range-key="' + this.locale.customRangeLabel + '">' + this.locale.customRangeLabel + '</li>';
              }
              list += '</ul>';
              this.container.find('.ranges').prepend(list);
          }
  
          if (typeof cb === 'function') {
              this.callback = cb;
          }
  
          if (!this.timePicker) {
              this.startDate = this.startDate.startOf('day');
              this.endDate = this.endDate.endOf('day');
              this.container.find('.calendar-time').hide();
          }
  
          //can't be used together for now
          if (this.timePicker && this.autoApply)
              this.autoApply = false;
  
          if (this.autoApply && typeof options.ranges !== 'object') {
              this.container.find('.ranges').hide();
          } else if (this.autoApply) {
              this.container.find('.applyBtn, .cancelBtn').addClass('hide');
          }
  
          if (this.singleDatePicker) {
              this.container.addClass('single');
              if (this.singleClasses) {
                  this.container.addClass(this.singleClasses);
              }
              this.container.find('.calendar.left').addClass('single');
              this.container.find('.calendar.left').show();
              this.container.find('.calendar.right').hide();
              this.container.find('.daterangepicker_input input, .daterangepicker_input > i').hide();
              if (this.timePicker) {
                  this.container.find('.ranges ul').hide();
              } else {
                  this.container.find('.ranges').hide();
              }
          }
  
          if ((typeof options.ranges === 'undefined' && !this.singleDatePicker) || this.alwaysShowCalendars) {
              this.container.addClass('show-calendar');
          }
  
          this.container.addClass('opens' + this.opens);
  
          //swap the position of the predefined ranges if opens right
          if (typeof options.ranges !== 'undefined' && this.opens == 'right') {
              this.container.find('.ranges').prependTo( this.container.find('.calendar.left').parent() );
          }
  
          //apply CSS classes and labels to buttons
          this.container.find('.applyBtn, .cancelBtn').addClass(this.buttonClasses);
          if (this.applyClass.length)
              this.container.find('.applyBtn').addClass(this.applyClass);
          if (this.cancelClass.length)
              this.container.find('.cancelBtn').addClass(this.cancelClass);
          this.container.find('.applyBtn').html(this.locale.applyLabel);
          this.container.find('.cancelBtn').html(this.locale.cancelLabel);
  
          //
          // event listeners
          //
  
          this.container.find('.calendar')
              .on('click.daterangepicker', '.prev', $.proxy(this.clickPrev, this))
              .on('click.daterangepicker', '.next', $.proxy(this.clickNext, this))
              .on('mousedown.daterangepicker', 'td.available', $.proxy(this.clickDate, this))
              .on('mouseenter.daterangepicker', 'td.available', $.proxy(this.hoverDate, this))
              .on('mouseleave.daterangepicker', 'td.available', $.proxy(this.updateFormInputs, this))
              .on('change.daterangepicker', 'select.yearselect', $.proxy(this.monthOrYearChanged, this))
              .on('change.daterangepicker', 'select.monthselect', $.proxy(this.monthOrYearChanged, this))
              .on('change.daterangepicker', 'select.hourselect,select.minuteselect,select.secondselect,select.ampmselect', $.proxy(this.timeChanged, this))
              .on('click.daterangepicker', '.daterangepicker_input input', $.proxy(this.showCalendars, this))
              .on('focus.daterangepicker', '.daterangepicker_input input', $.proxy(this.formInputsFocused, this))
              .on('blur.daterangepicker', '.daterangepicker_input input', $.proxy(this.formInputsBlurred, this))
              .on('change.daterangepicker', '.daterangepicker_input input', $.proxy(this.formInputsChanged, this));
  
          this.container.find('.ranges')
              .on('click.daterangepicker', 'button.applyBtn', $.proxy(this.clickApply, this))
              .on('click.daterangepicker', 'button.cancelBtn', $.proxy(this.clickCancel, this))
              .on('click.daterangepicker', 'li', $.proxy(this.clickRange, this))
              .on('mouseenter.daterangepicker', 'li', $.proxy(this.hoverRange, this))
              .on('mouseleave.daterangepicker', 'li', $.proxy(this.updateFormInputs, this));
  
          if (this.element.is('input') || this.element.is('button')) {
              this.element.on({
                  'click.daterangepicker': $.proxy(this.show, this),
                  'focus.daterangepicker': $.proxy(this.show, this),
                  'keyup.daterangepicker': $.proxy(this.elementChanged, this),
                  'keydown.daterangepicker': $.proxy(this.keydown, this)
              });
          } else {
              this.element.on('click.daterangepicker', $.proxy(this.toggle, this));
          }
  
          //
          // if attached to a text input, set the initial value
          //
  
          if (this.element.is('input') && !this.singleDatePicker && this.autoUpdateInput) {
              this.element.val(this.startDate.format(this.locale.format) + this.locale.separator + this.endDate.format(this.locale.format));
              this.element.trigger('change');
          } else if (this.element.is('input') && this.autoUpdateInput) {
              this.element.val(this.startDate.format(this.locale.format));
              this.element.trigger('change');
          }
  
      };
  
      DateRangePicker.prototype = {
  
          constructor: DateRangePicker,
  
          setStartDate: function(startDate) {
              if (typeof startDate === 'string')
                  this.startDate = moment(startDate, this.locale.format);
  
              if (typeof startDate === 'object')
                  this.startDate = moment(startDate);
  
              if (!this.timePicker)
                  this.startDate = this.startDate.startOf('day');
  
              if (this.timePicker && this.timePickerIncrement)
                  this.startDate.minute(Math.round(this.startDate.minute() / this.timePickerIncrement) * this.timePickerIncrement);
  
              if (this.minDate && this.startDate.isBefore(this.minDate)) {
                  this.startDate = this.minDate.clone();
                  if (this.timePicker && this.timePickerIncrement)
                      this.startDate.minute(Math.round(this.startDate.minute() / this.timePickerIncrement) * this.timePickerIncrement);
              }
  
              if (this.maxDate && this.startDate.isAfter(this.maxDate)) {
                  this.startDate = this.maxDate.clone();
                  if (this.timePicker && this.timePickerIncrement)
                      this.startDate.minute(Math.floor(this.startDate.minute() / this.timePickerIncrement) * this.timePickerIncrement);
              }
  
              if (!this.isShowing)
                  this.updateElement();
  
              this.updateMonthsInView();
          },
  
          setEndDate: function(endDate) {
              if (typeof endDate === 'string')
                  this.endDate = moment(endDate, this.locale.format);
  
              if (typeof endDate === 'object')
                  this.endDate = moment(endDate);
  
              if (!this.timePicker)
                  this.endDate = this.endDate.endOf('day');
  
              if (this.timePicker && this.timePickerIncrement)
                  this.endDate.minute(Math.round(this.endDate.minute() / this.timePickerIncrement) * this.timePickerIncrement);
  
              if (this.endDate.isBefore(this.startDate))
                  this.endDate = this.startDate.clone();
  
              if (this.maxDate && this.endDate.isAfter(this.maxDate))
                  this.endDate = this.maxDate.clone();
  
              if (this.dateLimit && this.startDate.clone().add(this.dateLimit).isBefore(this.endDate))
                  this.endDate = this.startDate.clone().add(this.dateLimit);
  
              this.previousRightTime = this.endDate.clone();
  
              if (!this.isShowing)
                  this.updateElement();
  
              this.updateMonthsInView();
          },
  
          isInvalidDate: function() {
              return false;
          },
  
          isCustomDate: function() {
              return false;
          },
  
          updateView: function() {
              if (this.timePicker) {
                  this.renderTimePicker('left');
                  this.renderTimePicker('right');
                  if (!this.endDate) {
                      this.container.find('.right .calendar-time select').attr('disabled', 'disabled').addClass('disabled');
                  } else {
                      this.container.find('.right .calendar-time select').removeAttr('disabled').removeClass('disabled');
                  }
              }
              if (this.endDate) {
                  this.container.find('input[name="daterangepicker_end"]').removeClass('active');
                  this.container.find('input[name="daterangepicker_start"]').addClass('active');
              } else {
                  this.container.find('input[name="daterangepicker_end"]').addClass('active');
                  this.container.find('input[name="daterangepicker_start"]').removeClass('active');
              }
              this.updateMonthsInView();
              this.updateCalendars();
              this.updateFormInputs();
          },
  
          updateMonthsInView: function() {
              if (this.endDate) {
  
                  //if both dates are visible already, do nothing
                  if (!this.singleDatePicker && this.leftCalendar.month && this.rightCalendar.month &&
                      (this.startDate.format('YYYY-MM') == this.leftCalendar.month.format('YYYY-MM') || this.startDate.format('YYYY-MM') == this.rightCalendar.month.format('YYYY-MM'))
                      &&
                      (this.endDate.format('YYYY-MM') == this.leftCalendar.month.format('YYYY-MM') || this.endDate.format('YYYY-MM') == this.rightCalendar.month.format('YYYY-MM'))
                      ) {
                      return;
                  }
  
                  this.leftCalendar.month = this.startDate.clone().date(2);
                  if (!this.linkedCalendars && (this.endDate.month() != this.startDate.month() || this.endDate.year() != this.startDate.year())) {
                      this.rightCalendar.month = this.endDate.clone().date(2);
                  } else {
                      this.rightCalendar.month = this.startDate.clone().date(2).add(1, 'month');
                  }
  
              } else {
                  if (this.leftCalendar.month.format('YYYY-MM') != this.startDate.format('YYYY-MM') && this.rightCalendar.month.format('YYYY-MM') != this.startDate.format('YYYY-MM')) {
                      this.leftCalendar.month = this.startDate.clone().date(2);
                      this.rightCalendar.month = this.startDate.clone().date(2).add(1, 'month');
                  }
              }
              if (this.maxDate && this.linkedCalendars && !this.singleDatePicker && this.rightCalendar.month > this.maxDate) {
                this.rightCalendar.month = this.maxDate.clone().date(2);
                this.leftCalendar.month = this.maxDate.clone().date(2).subtract(1, 'month');
              }
          },
  
          updateCalendars: function() {
  
              if (this.timePicker) {
                  var hour, minute, second;
                  if (this.endDate) {
                      hour = parseInt(this.container.find('.left .hourselect').val(), 10);
                      minute = parseInt(this.container.find('.left .minuteselect').val(), 10);
                      second = this.timePickerSeconds ? parseInt(this.container.find('.left .secondselect').val(), 10) : 0;
                      if (!this.timePicker24Hour) {
                          var ampm = this.container.find('.left .ampmselect').val();
                          if (ampm === 'PM' && hour < 12)
                              hour += 12;
                          if (ampm === 'AM' && hour === 12)
                              hour = 0;
                      }
                  } else {
                      hour = parseInt(this.container.find('.right .hourselect').val(), 10);
                      minute = parseInt(this.container.find('.right .minuteselect').val(), 10);
                      second = this.timePickerSeconds ? parseInt(this.container.find('.right .secondselect').val(), 10) : 0;
                      if (!this.timePicker24Hour) {
                          var ampm = this.container.find('.right .ampmselect').val();
                          if (ampm === 'PM' && hour < 12)
                              hour += 12;
                          if (ampm === 'AM' && hour === 12)
                              hour = 0;
                      }
                  }
                  this.leftCalendar.month.hour(hour).minute(minute).second(second);
                  this.rightCalendar.month.hour(hour).minute(minute).second(second);
              }
  
              this.renderCalendar('left');
              this.renderCalendar('right');
  
              //highlight any predefined range matching the current start and end dates
              this.container.find('.ranges li').removeClass('active');
              if (this.endDate == null) return;
  
              this.calculateChosenLabel();
          },
  
          renderCalendar: function(side) {
  
              //
              // Build the matrix of dates that will populate the calendar
              //
  
              var calendar = side == 'left' ? this.leftCalendar : this.rightCalendar;
              var month = calendar.month.month();
              var year = calendar.month.year();
              var hour = calendar.month.hour();
              var minute = calendar.month.minute();
              var second = calendar.month.second();
              var daysInMonth = moment([year, month]).daysInMonth();
              var firstDay = moment([year, month, 1]);
              var lastDay = moment([year, month, daysInMonth]);
              var lastMonth = moment(firstDay).subtract(1, 'month').month();
              var lastYear = moment(firstDay).subtract(1, 'month').year();
              var daysInLastMonth = moment([lastYear, lastMonth]).daysInMonth();
              var dayOfWeek = firstDay.day();
  
              //initialize a 6 rows x 7 columns array for the calendar
              var calendar = [];
              calendar.firstDay = firstDay;
              calendar.lastDay = lastDay;
  
              for (var i = 0; i < 6; i++) {
                  calendar[i] = [];
              }
  
              //populate the calendar with date objects
              var startDay = daysInLastMonth - dayOfWeek + this.locale.firstDay + 1;
              if (startDay > daysInLastMonth)
                  startDay -= 7;
  
              if (dayOfWeek == this.locale.firstDay)
                  startDay = daysInLastMonth - 6;
  
              var curDate = moment([lastYear, lastMonth, startDay, 12, minute, second]);
  
              var col, row;
              for (var i = 0, col = 0, row = 0; i < 42; i++, col++, curDate = moment(curDate).add(24, 'hour')) {
                  if (i > 0 && col % 7 === 0) {
                      col = 0;
                      row++;
                  }
                  calendar[row][col] = curDate.clone().hour(hour).minute(minute).second(second);
                  curDate.hour(12);
  
                  if (this.minDate && calendar[row][col].format('YYYY-MM-DD') == this.minDate.format('YYYY-MM-DD') && calendar[row][col].isBefore(this.minDate) && side == 'left') {
                      calendar[row][col] = this.minDate.clone();
                  }
  
                  if (this.maxDate && calendar[row][col].format('YYYY-MM-DD') == this.maxDate.format('YYYY-MM-DD') && calendar[row][col].isAfter(this.maxDate) && side == 'right') {
                      calendar[row][col] = this.maxDate.clone();
                  }
  
              }
  
              //make the calendar object available to hoverDate/clickDate
              if (side == 'left') {
                  this.leftCalendar.calendar = calendar;
              } else {
                  this.rightCalendar.calendar = calendar;
              }
  
              //
              // Display the calendar
              //
  
              var minDate = side == 'left' ? this.minDate : this.startDate;
              var maxDate = this.maxDate;
              var selected = side == 'left' ? this.startDate : this.endDate;
              var arrow = this.locale.direction == 'ltr' ? {left: 'chevron-left', right: 'chevron-right'} : {left: 'chevron-right', right: 'chevron-left'};
  
              var html = '<table class="table-condensed">';
              html += '<thead>';
              html += '<tr>';
  
              // add empty cell for week number
              if (this.showWeekNumbers || this.showISOWeekNumbers)
                  html += '<th></th>';
  
              if ((!minDate || minDate.isBefore(calendar.firstDay)) && (!this.linkedCalendars || side == 'left')) {
                  html += '<th class="prev available"><i class="fa fa-' + arrow.left + ' glyphicon glyphicon-' + arrow.left + '"></i></th>';
              } else {
                  html += '<th></th>';
              }
  
              var dateHtml = this.locale.monthNames[calendar[1][1].month()] + calendar[1][1].format(" YYYY");
  
              if (this.showDropdowns) {
                  var currentMonth = calendar[1][1].month();
                  var currentYear = calendar[1][1].year();
                  var maxYear = (maxDate && maxDate.year()) || (currentYear + 5);
                  var minYear = (minDate && minDate.year()) || (currentYear - 50);
                  var inMinYear = currentYear == minYear;
                  var inMaxYear = currentYear == maxYear;
  
                  var monthHtml = '<select class="monthselect">';
                  for (var m = 0; m < 12; m++) {
                      if ((!inMinYear || m >= minDate.month()) && (!inMaxYear || m <= maxDate.month())) {
                          monthHtml += "<option value='" + m + "'" +
                              (m === currentMonth ? " selected='selected'" : "") +
                              ">" + this.locale.monthNames[m] + "</option>";
                      } else {
                          monthHtml += "<option value='" + m + "'" +
                              (m === currentMonth ? " selected='selected'" : "") +
                              " disabled='disabled'>" + this.locale.monthNames[m] + "</option>";
                      }
                  }
                  monthHtml += "</select>";
  
                  var yearHtml = '<select class="yearselect">';
                  for (var y = minYear; y <= maxYear; y++) {
                      yearHtml += '<option value="' + y + '"' +
                          (y === currentYear ? ' selected="selected"' : '') +
                          '>' + y + '</option>';
                  }
                  yearHtml += '</select>';
  
                  dateHtml = monthHtml + yearHtml;
              }
  
              html += '<th colspan="5" class="month">' + dateHtml + '</th>';
              if ((!maxDate || maxDate.isAfter(calendar.lastDay)) && (!this.linkedCalendars || side == 'right' || this.singleDatePicker)) {
                  html += '<th class="next available"><i class="fa fa-' + arrow.right + ' glyphicon glyphicon-' + arrow.right + '"></i></th>';
              } else {
                  html += '<th></th>';
              }
  
              html += '</tr>';
              html += '<tr>';
  
              // add week number label
              if (this.showWeekNumbers || this.showISOWeekNumbers)
                  html += '<th class="week">' + this.locale.weekLabel + '</th>';
  
              $.each(this.locale.daysOfWeek, function(index, dayOfWeek) {
                  html += '<th>' + dayOfWeek + '</th>';
              });
  
              html += '</tr>';
              html += '</thead>';
              html += '<tbody>';
  
              //adjust maxDate to reflect the dateLimit setting in order to
              //grey out end dates beyond the dateLimit
              if (this.endDate == null && this.dateLimit) {
                  var maxLimit = this.startDate.clone().add(this.dateLimit).endOf('day');
                  if (!maxDate || maxLimit.isBefore(maxDate)) {
                      maxDate = maxLimit;
                  }
              }
  
              for (var row = 0; row < 6; row++) {
                  html += '<tr>';
  
                  // add week number
                  if (this.showWeekNumbers)
                      html += '<td class="week">' + calendar[row][0].week() + '</td>';
                  else if (this.showISOWeekNumbers)
                      html += '<td class="week">' + calendar[row][0].isoWeek() + '</td>';
  
                  for (var col = 0; col < 7; col++) {
  
                      var classes = [];
  
                      //highlight today's date
                      if (calendar[row][col].isSame(new Date(), "day"))
                          classes.push('today');
  
                      //highlight weekends
                      if (calendar[row][col].isoWeekday() > 5)
                          classes.push('weekend');
  
                      //grey out the dates in other months displayed at beginning and end of this calendar
                      if (calendar[row][col].month() != calendar[1][1].month())
                          classes.push('off');
  
                      //don't allow selection of dates before the minimum date
                      if (this.minDate && calendar[row][col].isBefore(this.minDate, 'day'))
                          classes.push('off', 'disabled');
  
                      //don't allow selection of dates after the maximum date
                      if (maxDate && calendar[row][col].isAfter(maxDate, 'day'))
                          classes.push('off', 'disabled');
  
                      //don't allow selection of date if a custom function decides it's invalid
                      if (this.isInvalidDate(calendar[row][col]))
                          classes.push('off', 'disabled');
  
                      //highlight the currently selected start date
                      if (calendar[row][col].format('YYYY-MM-DD') == this.startDate.format('YYYY-MM-DD'))
                          classes.push('active', 'start-date');
  
                      //highlight the currently selected end date
                      if (this.endDate != null && calendar[row][col].format('YYYY-MM-DD') == this.endDate.format('YYYY-MM-DD'))
                          classes.push('active', 'end-date');
  
                      //highlight dates in-between the selected dates
                      if (this.endDate != null && calendar[row][col] > this.startDate && calendar[row][col] < this.endDate)
                          classes.push('in-range');
  
                      //apply custom classes for this date
                      var isCustom = this.isCustomDate(calendar[row][col]);
                      if (isCustom !== false) {
                          if (typeof isCustom === 'string')
                              classes.push(isCustom);
                          else
                              Array.prototype.push.apply(classes, isCustom);
                      }
  
                      var cname = '', disabled = false;
                      for (var i = 0; i < classes.length; i++) {
                          cname += classes[i] + ' ';
                          if (classes[i] == 'disabled')
                              disabled = true;
                      }
                      if (!disabled)
                          cname += 'available';
  
                      html += '<td class="' + cname.replace(/^\s+|\s+$/g, '') + '" data-title="' + 'r' + row + 'c' + col + '">' + calendar[row][col].date() + '</td>';
  
                  }
                  html += '</tr>';
              }
  
              html += '</tbody>';
              html += '</table>';
  
              this.container.find('.calendar.' + side + ' .calendar-table').html(html);
  
          },
  
          renderTimePicker: function(side) {
  
              // Don't bother updating the time picker if it's currently disabled
              // because an end date hasn't been clicked yet
              if (side == 'right' && !this.endDate) return;
  
              var html, selected, minDate, maxDate = this.maxDate;
  
              if (this.dateLimit && (!this.maxDate || this.startDate.clone().add(this.dateLimit).isAfter(this.maxDate)))
                  maxDate = this.startDate.clone().add(this.dateLimit);
  
              if (side == 'left') {
                  selected = this.startDate.clone();
                  minDate = this.minDate;
              } else if (side == 'right') {
                  selected = this.endDate.clone();
                  minDate = this.startDate;
  
                  //Preserve the time already selected
                  var timeSelector = this.container.find('.calendar.right .calendar-time div');
                  if (!this.endDate && timeSelector.html() != '') {
  
                      selected.hour(timeSelector.find('.hourselect option:selected').val() || selected.hour());
                      selected.minute(timeSelector.find('.minuteselect option:selected').val() || selected.minute());
                      selected.second(timeSelector.find('.secondselect option:selected').val() || selected.second());
  
                      if (!this.timePicker24Hour) {
                          var ampm = timeSelector.find('.ampmselect option:selected').val();
                          if (ampm === 'PM' && selected.hour() < 12)
                              selected.hour(selected.hour() + 12);
                          if (ampm === 'AM' && selected.hour() === 12)
                              selected.hour(0);
                      }
  
                  }
  
                  if (selected.isBefore(this.startDate))
                      selected = this.startDate.clone();
  
                  if (maxDate && selected.isAfter(maxDate))
                      selected = maxDate.clone();
  
              }
  
              //
              // hours
              //
  
              html = '<select class="hourselect">';
  
              var start = this.timePicker24Hour ? 0 : 1;
              var end = this.timePicker24Hour ? 23 : 12;
  
              for (var i = start; i <= end; i++) {
                  var i_in_24 = i;
                  if (!this.timePicker24Hour)
                      i_in_24 = selected.hour() >= 12 ? (i == 12 ? 12 : i + 12) : (i == 12 ? 0 : i);
  
                  var time = selected.clone().hour(i_in_24);
                  var disabled = false;
                  if (minDate && time.minute(59).isBefore(minDate))
                      disabled = true;
                  if (maxDate && time.minute(0).isAfter(maxDate))
                      disabled = true;
  
                  if (i_in_24 == selected.hour() && !disabled) {
                      html += '<option value="' + i + '" selected="selected">' + i + '</option>';
                  } else if (disabled) {
                      html += '<option value="' + i + '" disabled="disabled" class="disabled">' + i + '</option>';
                  } else {
                      html += '<option value="' + i + '">' + i + '</option>';
                  }
              }
  
              html += '</select> ';
  
              //
              // minutes
              //
  
              html += ': <select class="minuteselect">';
  
              for (var i = 0; i < 60; i += this.timePickerIncrement) {
                  var padded = i < 10 ? '0' + i : i;
                  var time = selected.clone().minute(i);
  
                  var disabled = false;
                  if (minDate && time.second(59).isBefore(minDate))
                      disabled = true;
                  if (maxDate && time.second(0).isAfter(maxDate))
                      disabled = true;
  
                  if (selected.minute() == i && !disabled) {
                      html += '<option value="' + i + '" selected="selected">' + padded + '</option>';
                  } else if (disabled) {
                      html += '<option value="' + i + '" disabled="disabled" class="disabled">' + padded + '</option>';
                  } else {
                      html += '<option value="' + i + '">' + padded + '</option>';
                  }
              }
  
              html += '</select> ';
  
              //
              // seconds
              //
  
              if (this.timePickerSeconds) {
                  html += ': <select class="secondselect">';
  
                  for (var i = 0; i < 60; i++) {
                      var padded = i < 10 ? '0' + i : i;
                      var time = selected.clone().second(i);
  
                      var disabled = false;
                      if (minDate && time.isBefore(minDate))
                          disabled = true;
                      if (maxDate && time.isAfter(maxDate))
                          disabled = true;
  
                      if (selected.second() == i && !disabled) {
                          html += '<option value="' + i + '" selected="selected">' + padded + '</option>';
                      } else if (disabled) {
                          html += '<option value="' + i + '" disabled="disabled" class="disabled">' + padded + '</option>';
                      } else {
                          html += '<option value="' + i + '">' + padded + '</option>';
                      }
                  }
  
                  html += '</select> ';
              }
  
              //
              // AM/PM
              //
  
              if (!this.timePicker24Hour) {
                  html += '<select class="ampmselect">';
  
                  var am_html = '';
                  var pm_html = '';
  
                  if (minDate && selected.clone().hour(12).minute(0).second(0).isBefore(minDate))
                      am_html = ' disabled="disabled" class="disabled"';
  
                  if (maxDate && selected.clone().hour(0).minute(0).second(0).isAfter(maxDate))
                      pm_html = ' disabled="disabled" class="disabled"';
  
                  if (selected.hour() >= 12) {
                      html += '<option value="AM"' + am_html + '>AM</option><option value="PM" selected="selected"' + pm_html + '>PM</option>';
                  } else {
                      html += '<option value="AM" selected="selected"' + am_html + '>AM</option><option value="PM"' + pm_html + '>PM</option>';
                  }
  
                  html += '</select>';
              }
  
              this.container.find('.calendar.' + side + ' .calendar-time div').html(html);
  
          },
  
          updateFormInputs: function() {
  
              //ignore mouse movements while an above-calendar text input has focus
              if (this.container.find('input[name=daterangepicker_start]').is(":focus") || this.container.find('input[name=daterangepicker_end]').is(":focus"))
                  return;
  
              this.container.find('input[name=daterangepicker_start]').val(this.startDate.format(this.locale.format));
              if (this.endDate)
                  this.container.find('input[name=daterangepicker_end]').val(this.endDate.format(this.locale.format));
  
              if (this.singleDatePicker || (this.endDate && (this.startDate.isBefore(this.endDate) || this.startDate.isSame(this.endDate)))) {
                  this.container.find('button.applyBtn').removeAttr('disabled');
              } else {
                  this.container.find('button.applyBtn').attr('disabled', 'disabled');
              }
  
          },
  
          move: function() {
              var parentOffset = { top: 0, left: 0 },
                  containerTop;
              var parentRightEdge = $(window).width();
              if (!this.parentEl.is('body')) {
                  parentOffset = {
                      top: this.parentEl.offset().top - this.parentEl.scrollTop(),
                      left: this.parentEl.offset().left - this.parentEl.scrollLeft()
                  };
                  parentRightEdge = this.parentEl[0].clientWidth + this.parentEl.offset().left;
              }
  
              if (this.drops == 'up')
                  containerTop = this.element.offset().top - this.container.outerHeight() - parentOffset.top;
              else
                  containerTop = this.element.offset().top + this.element.outerHeight() - parentOffset.top;
              this.container[this.drops == 'up' ? 'addClass' : 'removeClass']('dropup');
  
              if (this.opens == 'left') {
                  this.container.css({
                      top: containerTop,
                      right: parentRightEdge - this.element.offset().left - this.element.outerWidth(),
                      left: 'auto'
                  });
                  if (this.container.offset().left < 0) {
                      this.container.css({
                          right: 'auto',
                          left: 9
                      });
                  }
              } else if (this.opens == 'center') {
                  this.container.css({
                      top: containerTop,
                      left: this.element.offset().left - parentOffset.left + this.element.outerWidth() / 2
                              - this.container.outerWidth() / 2,
                      right: 'auto'
                  });
                  if (this.container.offset().left < 0) {
                      this.container.css({
                          right: 'auto',
                          left: 9
                      });
                  }
              } else {
                  this.container.css({
                      top: containerTop,
                      left: this.element.offset().left - parentOffset.left,
                      right: 'auto'
                  });
                  if (this.container.offset().left + this.container.outerWidth() > $(window).width()) {
                      this.container.css({
                          left: 'auto',
                          right: 0
                      });
                  }
              }
          },
  
          show: function(e) {
              if (this.isShowing) return;
  
              // Create a click proxy that is private to this instance of datepicker, for unbinding
              this._outsideClickProxy = $.proxy(function(e) { this.outsideClick(e); }, this);
  
              // Bind global datepicker mousedown for hiding and
              $(document)
                .on('mousedown.daterangepicker', this._outsideClickProxy)
                // also support mobile devices
                .on('touchend.daterangepicker', this._outsideClickProxy)
                // also explicitly play nice with Bootstrap dropdowns, which stopPropagation when clicking them
                .on('click.daterangepicker', '[data-toggle=dropdown]', this._outsideClickProxy)
                // and also close when focus changes to outside the picker (eg. tabbing between controls)
                .on('focusin.daterangepicker', this._outsideClickProxy);
  
              // Reposition the picker if the window is resized while it's open
              $(window).on('resize.daterangepicker', $.proxy(function(e) { this.move(e); }, this));
  
              this.oldStartDate = this.startDate.clone();
              this.oldEndDate = this.endDate.clone();
              this.previousRightTime = this.endDate.clone();
  
              this.updateView();
              this.container.show();
              this.move();
              this.element.trigger('show.daterangepicker', this);
              this.isShowing = true;
          },
  
          hide: function(e) {
              if (!this.isShowing) return;
  
              //incomplete date selection, revert to last values
              if (!this.endDate) {
                  this.startDate = this.oldStartDate.clone();
                  this.endDate = this.oldEndDate.clone();
              }
  
              //if a new date range was selected, invoke the user callback function
              if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate))
                  this.callback(this.startDate, this.endDate, this.chosenLabel);
  
              //if picker is attached to a text input, update it
              this.updateElement();
  
              $(document).off('.daterangepicker');
              $(window).off('.daterangepicker');
              this.container.hide();
              this.element.trigger('hide.daterangepicker', this);
              this.isShowing = false;
          },
  
          toggle: function(e) {
              if (this.isShowing) {
                  this.hide();
              } else {
                  this.show();
              }
          },
  
          outsideClick: function(e) {
              var target = $(e.target);
              // if the page is clicked anywhere except within the daterangerpicker/button
              // itself then call this.hide()
              if (
                  // ie modal dialog fix
                  e.type == "focusin" ||
                  target.closest(this.element).length ||
                  target.closest(this.container).length ||
                  target.closest('.calendar-table').length
                  ) return;
              this.hide();
              this.element.trigger('outsideClick.daterangepicker', this);
          },
  
          showCalendars: function() {
              this.container.addClass('show-calendar');
              this.move();
              this.element.trigger('showCalendar.daterangepicker', this);
          },
  
          hideCalendars: function() {
              this.container.removeClass('show-calendar');
              this.element.trigger('hideCalendar.daterangepicker', this);
          },
  
          hoverRange: function(e) {
  
              //ignore mouse movements while an above-calendar text input has focus
              if (this.container.find('input[name=daterangepicker_start]').is(":focus") || this.container.find('input[name=daterangepicker_end]').is(":focus"))
                  return;
  
              var label = e.target.getAttribute('data-range-key');
  
              if (label == this.locale.customRangeLabel) {
                  this.updateView();
              } else {
                  var dates = this.ranges[label];
                  this.container.find('input[name=daterangepicker_start]').val(dates[0].format(this.locale.format));
                  this.container.find('input[name=daterangepicker_end]').val(dates[1].format(this.locale.format));
              }
  
          },
  
          clickRange: function(e) {
              var label = e.target.getAttribute('data-range-key');
              this.chosenLabel = label;
              if (label == this.locale.customRangeLabel) {
                  this.showCalendars();
              } else {
                  var dates = this.ranges[label];
                  this.startDate = dates[0];
                  this.endDate = dates[1];
  
                  if (!this.timePicker) {
                      this.startDate.startOf('day');
                      this.endDate.endOf('day');
                  }
  
                  if (!this.alwaysShowCalendars)
                      this.hideCalendars();
                  this.clickApply();
              }
          },
  
          clickPrev: function(e) {
              var cal = $(e.target).parents('.calendar');
              if (cal.hasClass('left')) {
                  this.leftCalendar.month.subtract(1, 'month');
                  if (this.linkedCalendars)
                      this.rightCalendar.month.subtract(1, 'month');
              } else {
                  this.rightCalendar.month.subtract(1, 'month');
              }
              this.updateCalendars();
          },
  
          clickNext: function(e) {
              var cal = $(e.target).parents('.calendar');
              if (cal.hasClass('left')) {
                  this.leftCalendar.month.add(1, 'month');
              } else {
                  this.rightCalendar.month.add(1, 'month');
                  if (this.linkedCalendars)
                      this.leftCalendar.month.add(1, 'month');
              }
              this.updateCalendars();
          },
  
          hoverDate: function(e) {
  
              //ignore mouse movements while an above-calendar text input has focus
              //if (this.container.find('input[name=daterangepicker_start]').is(":focus") || this.container.find('input[name=daterangepicker_end]').is(":focus"))
              //    return;
  
              //ignore dates that can't be selected
              if (!$(e.target).hasClass('available')) return;
  
              //have the text inputs above calendars reflect the date being hovered over
              var title = $(e.target).attr('data-title');
              var row = title.substr(1, 1);
              var col = title.substr(3, 1);
              var cal = $(e.target).parents('.calendar');
              var date = cal.hasClass('left') ? this.leftCalendar.calendar[row][col] : this.rightCalendar.calendar[row][col];
  
              if (this.endDate && !this.container.find('input[name=daterangepicker_start]').is(":focus")) {
                  this.container.find('input[name=daterangepicker_start]').val(date.format(this.locale.format));
              } else if (!this.endDate && !this.container.find('input[name=daterangepicker_end]').is(":focus")) {
                  this.container.find('input[name=daterangepicker_end]').val(date.format(this.locale.format));
              }
  
              //highlight the dates between the start date and the date being hovered as a potential end date
              var leftCalendar = this.leftCalendar;
              var rightCalendar = this.rightCalendar;
              var startDate = this.startDate;
              if (!this.endDate) {
                  this.container.find('.calendar td').each(function(index, el) {
  
                      //skip week numbers, only look at dates
                      if ($(el).hasClass('week')) return;
  
                      var title = $(el).attr('data-title');
                      var row = title.substr(1, 1);
                      var col = title.substr(3, 1);
                      var cal = $(el).parents('.calendar');
                      var dt = cal.hasClass('left') ? leftCalendar.calendar[row][col] : rightCalendar.calendar[row][col];
  
                      if ((dt.isAfter(startDate) && dt.isBefore(date)) || dt.isSame(date, 'day')) {
                          $(el).addClass('in-range');
                      } else {
                          $(el).removeClass('in-range');
                      }
  
                  });
              }
  
          },
  
          clickDate: function(e) {
  
              if (!$(e.target).hasClass('available')) return;
  
              var title = $(e.target).attr('data-title');
              var row = title.substr(1, 1);
              var col = title.substr(3, 1);
              var cal = $(e.target).parents('.calendar');
              var date = cal.hasClass('left') ? this.leftCalendar.calendar[row][col] : this.rightCalendar.calendar[row][col];
  
              //
              // this function needs to do a few things:
              // * alternate between selecting a start and end date for the range,
              // * if the time picker is enabled, apply the hour/minute/second from the select boxes to the clicked date
              // * if autoapply is enabled, and an end date was chosen, apply the selection
              // * if single date picker mode, and time picker isn't enabled, apply the selection immediately
              // * if one of the inputs above the calendars was focused, cancel that manual input
              //
  
              if (this.endDate || date.isBefore(this.startDate, 'day')) { //picking start
                  if (this.timePicker) {
                      var hour = parseInt(this.container.find('.left .hourselect').val(), 10);
                      if (!this.timePicker24Hour) {
                          var ampm = this.container.find('.left .ampmselect').val();
                          if (ampm === 'PM' && hour < 12)
                              hour += 12;
                          if (ampm === 'AM' && hour === 12)
                              hour = 0;
                      }
                      var minute = parseInt(this.container.find('.left .minuteselect').val(), 10);
                      var second = this.timePickerSeconds ? parseInt(this.container.find('.left .secondselect').val(), 10) : 0;
                      date = date.clone().hour(hour).minute(minute).second(second);
                  }
                  this.endDate = null;
                  this.setStartDate(date.clone());
              } else if (!this.endDate && date.isBefore(this.startDate)) {
                  //special case: clicking the same date for start/end,
                  //but the time of the end date is before the start date
                  this.setEndDate(this.startDate.clone());
              } else { // picking end
                  if (this.timePicker) {
                      var hour = parseInt(this.container.find('.right .hourselect').val(), 10);
                      if (!this.timePicker24Hour) {
                          var ampm = this.container.find('.right .ampmselect').val();
                          if (ampm === 'PM' && hour < 12)
                              hour += 12;
                          if (ampm === 'AM' && hour === 12)
                              hour = 0;
                      }
                      var minute = parseInt(this.container.find('.right .minuteselect').val(), 10);
                      var second = this.timePickerSeconds ? parseInt(this.container.find('.right .secondselect').val(), 10) : 0;
                      date = date.clone().hour(hour).minute(minute).second(second);
                  }
                  this.setEndDate(date.clone());
                  if (this.autoApply) {
                    this.calculateChosenLabel();
                    this.clickApply();
                  }
              }
  
              if (this.singleDatePicker) {
                  this.setEndDate(this.startDate);
                  if (!this.timePicker)
                      this.clickApply();
              }
  
              this.updateView();
  
              //This is to cancel the blur event handler if the mouse was in one of the inputs
              e.stopPropagation();
  
          },
  
          calculateChosenLabel: function () {
              var customRange = true;
              var i = 0;
              for (var range in this.ranges) {
                  if (this.timePicker) {
                      if (this.startDate.isSame(this.ranges[range][0]) && this.endDate.isSame(this.ranges[range][1])) {
                          customRange = false;
                          this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')').addClass('active').html();
                          break;
                      }
                  } else {
                      //ignore times when comparing dates if time picker is not enabled
                      if (this.startDate.format('YYYY-MM-DD') == this.ranges[range][0].format('YYYY-MM-DD') && this.endDate.format('YYYY-MM-DD') == this.ranges[range][1].format('YYYY-MM-DD')) {
                          customRange = false;
                          this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')').addClass('active').html();
                          break;
                      }
                  }
                  i++;
              }
              if (customRange) {
                  if (this.showCustomRangeLabel) {
                      this.chosenLabel = this.container.find('.ranges li:last').addClass('active').html();
                  } else {
                      this.chosenLabel = null;
                  }
                  this.showCalendars();
              }
          },
  
          clickApply: function(e) {
              this.hide();
              this.element.trigger('apply.daterangepicker', this);
          },
  
          clickCancel: function(e) {
              this.startDate = this.oldStartDate;
              this.endDate = this.oldEndDate;
              this.hide();
              this.element.trigger('cancel.daterangepicker', this);
          },
  
          monthOrYearChanged: function(e) {
              var isLeft = $(e.target).closest('.calendar').hasClass('left'),
                  leftOrRight = isLeft ? 'left' : 'right',
                  cal = this.container.find('.calendar.'+leftOrRight);
  
              // Month must be Number for new moment versions
              var month = parseInt(cal.find('.monthselect').val(), 10);
              var year = cal.find('.yearselect').val();
  
              if (!isLeft) {
                  if (year < this.startDate.year() || (year == this.startDate.year() && month < this.startDate.month())) {
                      month = this.startDate.month();
                      year = this.startDate.year();
                  }
              }
  
              if (this.minDate) {
                  if (year < this.minDate.year() || (year == this.minDate.year() && month < this.minDate.month())) {
                      month = this.minDate.month();
                      year = this.minDate.year();
                  }
              }
  
              if (this.maxDate) {
                  if (year > this.maxDate.year() || (year == this.maxDate.year() && month > this.maxDate.month())) {
                      month = this.maxDate.month();
                      year = this.maxDate.year();
                  }
              }
  
              if (isLeft) {
                  this.leftCalendar.month.month(month).year(year);
                  if (this.linkedCalendars)
                      this.rightCalendar.month = this.leftCalendar.month.clone().add(1, 'month');
              } else {
                  this.rightCalendar.month.month(month).year(year);
                  if (this.linkedCalendars)
                      this.leftCalendar.month = this.rightCalendar.month.clone().subtract(1, 'month');
              }
              this.updateCalendars();
          },
  
          timeChanged: function(e) {
  
              var cal = $(e.target).closest('.calendar'),
                  isLeft = cal.hasClass('left');
  
              var hour = parseInt(cal.find('.hourselect').val(), 10);
              var minute = parseInt(cal.find('.minuteselect').val(), 10);
              var second = this.timePickerSeconds ? parseInt(cal.find('.secondselect').val(), 10) : 0;
  
              if (!this.timePicker24Hour) {
                  var ampm = cal.find('.ampmselect').val();
                  if (ampm === 'PM' && hour < 12)
                      hour += 12;
                  if (ampm === 'AM' && hour === 12)
                      hour = 0;
              }
  
              if (isLeft) {
                  var start = this.startDate.clone();
                  start.hour(hour);
                  start.minute(minute);
                  start.second(second);
                  this.setStartDate(start);
                  if (this.singleDatePicker) {
                      this.endDate = this.startDate.clone();
                  } else if (this.endDate && this.endDate.format('YYYY-MM-DD') == start.format('YYYY-MM-DD') && this.endDate.isBefore(start)) {
                      this.setEndDate(start.clone());
                  }
              } else if (this.endDate) {
                  var end = this.endDate.clone();
                  end.hour(hour);
                  end.minute(minute);
                  end.second(second);
                  this.setEndDate(end);
              }
  
              //update the calendars so all clickable dates reflect the new time component
              this.updateCalendars();
  
              //update the form inputs above the calendars with the new time
              this.updateFormInputs();
  
              //re-render the time pickers because changing one selection can affect what's enabled in another
              this.renderTimePicker('left');
              this.renderTimePicker('right');
  
          },
  
          formInputsChanged: function(e) {
              var isRight = $(e.target).closest('.calendar').hasClass('right');
              var start = moment(this.container.find('input[name="daterangepicker_start"]').val(), this.locale.format);
              var end = moment(this.container.find('input[name="daterangepicker_end"]').val(), this.locale.format);
  
              if (start.isValid() && end.isValid()) {
  
                  if (isRight && end.isBefore(start))
                      start = end.clone();
  
                  this.setStartDate(start);
                  this.setEndDate(end);
  
                  if (isRight) {
                      this.container.find('input[name="daterangepicker_start"]').val(this.startDate.format(this.locale.format));
                  } else {
                      this.container.find('input[name="daterangepicker_end"]').val(this.endDate.format(this.locale.format));
                  }
  
              }
  
              this.updateView();
          },
  
          formInputsFocused: function(e) {
  
              // Highlight the focused input
              this.container.find('input[name="daterangepicker_start"], input[name="daterangepicker_end"]').removeClass('active');
              $(e.target).addClass('active');
  
              // Set the state such that if the user goes back to using a mouse, 
              // the calendars are aware we're selecting the end of the range, not
              // the start. This allows someone to edit the end of a date range without
              // re-selecting the beginning, by clicking on the end date input then
              // using the calendar.
              var isRight = $(e.target).closest('.calendar').hasClass('right');
              if (isRight) {
                  this.endDate = null;
                  this.setStartDate(this.startDate.clone());
                  this.updateView();
              }
  
          },
  
          formInputsBlurred: function(e) {
  
              // this function has one purpose right now: if you tab from the first
              // text input to the second in the UI, the endDate is nulled so that
              // you can click another, but if you tab out without clicking anything
              // or changing the input value, the old endDate should be retained
  
              if (!this.endDate) {
                  var val = this.container.find('input[name="daterangepicker_end"]').val();
                  var end = moment(val, this.locale.format);
                  if (end.isValid()) {
                      this.setEndDate(end);
                      this.updateView();
                  }
              }
  
          },
  
          elementChanged: function() {
              if (!this.element.is('input')) return;
              if (!this.element.val().length) return;
              if (this.element.val().length < this.locale.format.length) return;
  
              var dateString = this.element.val().split(this.locale.separator),
                  start = null,
                  end = null;
  
              if (dateString.length === 2) {
                  start = moment(dateString[0], this.locale.format);
                  end = moment(dateString[1], this.locale.format);
              }
  
              if (this.singleDatePicker || start === null || end === null) {
                  start = moment(this.element.val(), this.locale.format);
                  end = start;
              }
  
              if (!start.isValid() || !end.isValid()) return;
  
              this.setStartDate(start);
              this.setEndDate(end);
              this.updateView();
          },
  
          keydown: function(e) {
              //hide on tab or enter
              if ((e.keyCode === 9) || (e.keyCode === 13)) {
                  this.hide();
              }
          },
  
          updateElement: function() {
              if (this.element.is('input') && !this.singleDatePicker && this.autoUpdateInput) {
                  this.element.val(this.startDate.format(this.locale.format) + this.locale.separator + this.endDate.format(this.locale.format));
                  this.element.trigger('change');
              } else if (this.element.is('input') && this.autoUpdateInput) {
                  this.element.val(this.startDate.format(this.locale.format));
                  this.element.trigger('change');
              }
          },
  
          remove: function() {
              this.container.remove();
              this.element.off('.daterangepicker');
              this.element.removeData();
          }
  
      };
  
      $.fn.daterangepicker = function(options, callback) {
          this.each(function() {
              var el = $(this);
              if (el.data('daterangepicker'))
                  el.data('daterangepicker').remove();
              el.data('daterangepicker', new DateRangePicker(el, options, callback));
          });
          return this;
      };
  
      return DateRangePicker;
  
  }));
  
  /*!
   * Chart.js
   * http://chartjs.org/
   * Version: 2.1.4
   *
   * Copyright 2016 Nick Downie
   * Released under the MIT license
   * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md
   */
  (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
  
  },{}],2:[function(require,module,exports){
  /* MIT license */
  var colorNames = require('color-name');
  
  module.exports = {
     getRgba: getRgba,
     getHsla: getHsla,
     getRgb: getRgb,
     getHsl: getHsl,
     getHwb: getHwb,
     getAlpha: getAlpha,
  
     hexString: hexString,
     rgbString: rgbString,
     rgbaString: rgbaString,
     percentString: percentString,
     percentaString: percentaString,
     hslString: hslString,
     hslaString: hslaString,
     hwbString: hwbString,
     keyword: keyword
  }
  
  function getRgba(string) {
     if (!string) {
        return;
     }
     var abbr =  /^#([a-fA-F0-9]{3})$/,
         hex =  /^#([a-fA-F0-9]{6})$/,
         rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/,
         per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/,
         keyword = /(\w+)/;
  
     var rgb = [0, 0, 0],
         a = 1,
         match = string.match(abbr);
     if (match) {
        match = match[1];
        for (var i = 0; i < rgb.length; i++) {
           rgb[i] = parseInt(match[i] + match[i], 16);
        }
     }
     else if (match = string.match(hex)) {
        match = match[1];
        for (var i = 0; i < rgb.length; i++) {
           rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16);
        }
     }
     else if (match = string.match(rgba)) {
        for (var i = 0; i < rgb.length; i++) {
           rgb[i] = parseInt(match[i + 1]);
        }
        a = parseFloat(match[4]);
     }
     else if (match = string.match(per)) {
        for (var i = 0; i < rgb.length; i++) {
           rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55);
        }
        a = parseFloat(match[4]);
     }
     else if (match = string.match(keyword)) {
        if (match[1] == "transparent") {
           return [0, 0, 0, 0];
        }
        rgb = colorNames[match[1]];
        if (!rgb) {
           return;
        }
     }
  
     for (var i = 0; i < rgb.length; i++) {
        rgb[i] = scale(rgb[i], 0, 255);
     }
     if (!a && a != 0) {
        a = 1;
     }
     else {
        a = scale(a, 0, 1);
     }
     rgb[3] = a;
     return rgb;
  }
  
  function getHsla(string) {
     if (!string) {
        return;
     }
     var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
     var match = string.match(hsl);
     if (match) {
        var alpha = parseFloat(match[4]);
        var h = scale(parseInt(match[1]), 0, 360),
            s = scale(parseFloat(match[2]), 0, 100),
            l = scale(parseFloat(match[3]), 0, 100),
            a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
        return [h, s, l, a];
     }
  }
  
  function getHwb(string) {
     if (!string) {
        return;
     }
     var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
     var match = string.match(hwb);
     if (match) {
      var alpha = parseFloat(match[4]);
        var h = scale(parseInt(match[1]), 0, 360),
            w = scale(parseFloat(match[2]), 0, 100),
            b = scale(parseFloat(match[3]), 0, 100),
            a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
        return [h, w, b, a];
     }
  }
  
  function getRgb(string) {
     var rgba = getRgba(string);
     return rgba && rgba.slice(0, 3);
  }
  
  function getHsl(string) {
    var hsla = getHsla(string);
    return hsla && hsla.slice(0, 3);
  }
  
  function getAlpha(string) {
     var vals = getRgba(string);
     if (vals) {
        return vals[3];
     }
     else if (vals = getHsla(string)) {
        return vals[3];
     }
     else if (vals = getHwb(string)) {
        return vals[3];
     }
  }
  
  // generators
  function hexString(rgb) {
     return "#" + hexDouble(rgb[0]) + hexDouble(rgb[1])
                + hexDouble(rgb[2]);
  }
  
  function rgbString(rgba, alpha) {
     if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
        return rgbaString(rgba, alpha);
     }
     return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")";
  }
  
  function rgbaString(rgba, alpha) {
     if (alpha === undefined) {
        alpha = (rgba[3] !== undefined ? rgba[3] : 1);
     }
     return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2]
             + ", " + alpha + ")";
  }
  
  function percentString(rgba, alpha) {
     if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
        return percentaString(rgba, alpha);
     }
     var r = Math.round(rgba[0]/255 * 100),
         g = Math.round(rgba[1]/255 * 100),
         b = Math.round(rgba[2]/255 * 100);
  
     return "rgb(" + r + "%, " + g + "%, " + b + "%)";
  }
  
  function percentaString(rgba, alpha) {
     var r = Math.round(rgba[0]/255 * 100),
         g = Math.round(rgba[1]/255 * 100),
         b = Math.round(rgba[2]/255 * 100);
     return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")";
  }
  
  function hslString(hsla, alpha) {
     if (alpha < 1 || (hsla[3] && hsla[3] < 1)) {
        return hslaString(hsla, alpha);
     }
     return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)";
  }
  
  function hslaString(hsla, alpha) {
     if (alpha === undefined) {
        alpha = (hsla[3] !== undefined ? hsla[3] : 1);
     }
     return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, "
             + alpha + ")";
  }
  
  // hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax
  // (hwb have alpha optional & 1 is default value)
  function hwbString(hwb, alpha) {
     if (alpha === undefined) {
        alpha = (hwb[3] !== undefined ? hwb[3] : 1);
     }
     return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%"
             + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")";
  }
  
  function keyword(rgb) {
    return reverseNames[rgb.slice(0, 3)];
  }
  
  // helpers
  function scale(num, min, max) {
     return Math.min(Math.max(min, num), max);
  }
  
  function hexDouble(num) {
    var str = num.toString(16).toUpperCase();
    return (str.length < 2) ? "0" + str : str;
  }
  
  
  //create a list of reverse color names
  var reverseNames = {};
  for (var name in colorNames) {
     reverseNames[colorNames[name]] = name;
  }
  
  },{"color-name":6}],3:[function(require,module,exports){
  /* MIT license */
  var convert = require('color-convert');
  var string = require('chartjs-color-string');
  
  var Color = function (obj) {
  	if (obj instanceof Color) {
  		return obj;
  	}
  	if (!(this instanceof Color)) {
  		return new Color(obj);
  	}
  
  	this.values = {
  		rgb: [0, 0, 0],
  		hsl: [0, 0, 0],
  		hsv: [0, 0, 0],
  		hwb: [0, 0, 0],
  		cmyk: [0, 0, 0, 0],
  		alpha: 1
  	};
  
  	// parse Color() argument
  	var vals;
  	if (typeof obj === 'string') {
  		vals = string.getRgba(obj);
  		if (vals) {
  			this.setValues('rgb', vals);
  		} else if (vals = string.getHsla(obj)) {
  			this.setValues('hsl', vals);
  		} else if (vals = string.getHwb(obj)) {
  			this.setValues('hwb', vals);
  		} else {
  			throw new Error('Unable to parse color from string "' + obj + '"');
  		}
  	} else if (typeof obj === 'object') {
  		vals = obj;
  		if (vals.r !== undefined || vals.red !== undefined) {
  			this.setValues('rgb', vals);
  		} else if (vals.l !== undefined || vals.lightness !== undefined) {
  			this.setValues('hsl', vals);
  		} else if (vals.v !== undefined || vals.value !== undefined) {
  			this.setValues('hsv', vals);
  		} else if (vals.w !== undefined || vals.whiteness !== undefined) {
  			this.setValues('hwb', vals);
  		} else if (vals.c !== undefined || vals.cyan !== undefined) {
  			this.setValues('cmyk', vals);
  		} else {
  			throw new Error('Unable to parse color from object ' + JSON.stringify(obj));
  		}
  	}
  };
  
  Color.prototype = {
  	rgb: function () {
  		return this.setSpace('rgb', arguments);
  	},
  	hsl: function () {
  		return this.setSpace('hsl', arguments);
  	},
  	hsv: function () {
  		return this.setSpace('hsv', arguments);
  	},
  	hwb: function () {
  		return this.setSpace('hwb', arguments);
  	},
  	cmyk: function () {
  		return this.setSpace('cmyk', arguments);
  	},
  
  	rgbArray: function () {
  		return this.values.rgb;
  	},
  	hslArray: function () {
  		return this.values.hsl;
  	},
  	hsvArray: function () {
  		return this.values.hsv;
  	},
  	hwbArray: function () {
  		var values = this.values;
  		if (values.alpha !== 1) {
  			return values.hwb.concat([values.alpha]);
  		}
  		return values.hwb;
  	},
  	cmykArray: function () {
  		return this.values.cmyk;
  	},
  	rgbaArray: function () {
  		var values = this.values;
  		return values.rgb.concat([values.alpha]);
  	},
  	hslaArray: function () {
  		var values = this.values;
  		return values.hsl.concat([values.alpha]);
  	},
  	alpha: function (val) {
  		if (val === undefined) {
  			return this.values.alpha;
  		}
  		this.setValues('alpha', val);
  		return this;
  	},
  
  	red: function (val) {
  		return this.setChannel('rgb', 0, val);
  	},
  	green: function (val) {
  		return this.setChannel('rgb', 1, val);
  	},
  	blue: function (val) {
  		return this.setChannel('rgb', 2, val);
  	},
  	hue: function (val) {
  		if (val) {
  			val %= 360;
  			val = val < 0 ? 360 + val : val;
  		}
  		return this.setChannel('hsl', 0, val);
  	},
  	saturation: function (val) {
  		return this.setChannel('hsl', 1, val);
  	},
  	lightness: function (val) {
  		return this.setChannel('hsl', 2, val);
  	},
  	saturationv: function (val) {
  		return this.setChannel('hsv', 1, val);
  	},
  	whiteness: function (val) {
  		return this.setChannel('hwb', 1, val);
  	},
  	blackness: function (val) {
  		return this.setChannel('hwb', 2, val);
  	},
  	value: function (val) {
  		return this.setChannel('hsv', 2, val);
  	},
  	cyan: function (val) {
  		return this.setChannel('cmyk', 0, val);
  	},
  	magenta: function (val) {
  		return this.setChannel('cmyk', 1, val);
  	},
  	yellow: function (val) {
  		return this.setChannel('cmyk', 2, val);
  	},
  	black: function (val) {
  		return this.setChannel('cmyk', 3, val);
  	},
  
  	hexString: function () {
  		return string.hexString(this.values.rgb);
  	},
  	rgbString: function () {
  		return string.rgbString(this.values.rgb, this.values.alpha);
  	},
  	rgbaString: function () {
  		return string.rgbaString(this.values.rgb, this.values.alpha);
  	},
  	percentString: function () {
  		return string.percentString(this.values.rgb, this.values.alpha);
  	},
  	hslString: function () {
  		return string.hslString(this.values.hsl, this.values.alpha);
  	},
  	hslaString: function () {
  		return string.hslaString(this.values.hsl, this.values.alpha);
  	},
  	hwbString: function () {
  		return string.hwbString(this.values.hwb, this.values.alpha);
  	},
  	keyword: function () {
  		return string.keyword(this.values.rgb, this.values.alpha);
  	},
  
  	rgbNumber: function () {
  		var rgb = this.values.rgb;
  		return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
  	},
  
  	luminosity: function () {
  		// http://www.w3.org/TR/WCAG20/#relativeluminancedef
  		var rgb = this.values.rgb;
  		var lum = [];
  		for (var i = 0; i < rgb.length; i++) {
  			var chan = rgb[i] / 255;
  			lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4);
  		}
  		return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
  	},
  
  	contrast: function (color2) {
  		// http://www.w3.org/TR/WCAG20/#contrast-ratiodef
  		var lum1 = this.luminosity();
  		var lum2 = color2.luminosity();
  		if (lum1 > lum2) {
  			return (lum1 + 0.05) / (lum2 + 0.05);
  		}
  		return (lum2 + 0.05) / (lum1 + 0.05);
  	},
  
  	level: function (color2) {
  		var contrastRatio = this.contrast(color2);
  		if (contrastRatio >= 7.1) {
  			return 'AAA';
  		}
  
  		return (contrastRatio >= 4.5) ? 'AA' : '';
  	},
  
  	dark: function () {
  		// YIQ equation from http://24ways.org/2010/calculating-color-contrast
  		var rgb = this.values.rgb;
  		var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
  		return yiq < 128;
  	},
  
  	light: function () {
  		return !this.dark();
  	},
  
  	negate: function () {
  		var rgb = [];
  		for (var i = 0; i < 3; i++) {
  			rgb[i] = 255 - this.values.rgb[i];
  		}
  		this.setValues('rgb', rgb);
  		return this;
  	},
  
  	lighten: function (ratio) {
  		var hsl = this.values.hsl;
  		hsl[2] += hsl[2] * ratio;
  		this.setValues('hsl', hsl);
  		return this;
  	},
  
  	darken: function (ratio) {
  		var hsl = this.values.hsl;
  		hsl[2] -= hsl[2] * ratio;
  		this.setValues('hsl', hsl);
  		return this;
  	},
  
  	saturate: function (ratio) {
  		var hsl = this.values.hsl;
  		hsl[1] += hsl[1] * ratio;
  		this.setValues('hsl', hsl);
  		return this;
  	},
  
  	desaturate: function (ratio) {
  		var hsl = this.values.hsl;
  		hsl[1] -= hsl[1] * ratio;
  		this.setValues('hsl', hsl);
  		return this;
  	},
  
  	whiten: function (ratio) {
  		var hwb = this.values.hwb;
  		hwb[1] += hwb[1] * ratio;
  		this.setValues('hwb', hwb);
  		return this;
  	},
  
  	blacken: function (ratio) {
  		var hwb = this.values.hwb;
  		hwb[2] += hwb[2] * ratio;
  		this.setValues('hwb', hwb);
  		return this;
  	},
  
  	greyscale: function () {
  		var rgb = this.values.rgb;
  		// http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
  		var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;
  		this.setValues('rgb', [val, val, val]);
  		return this;
  	},
  
  	clearer: function (ratio) {
  		var alpha = this.values.alpha;
  		this.setValues('alpha', alpha - (alpha * ratio));
  		return this;
  	},
  
  	opaquer: function (ratio) {
  		var alpha = this.values.alpha;
  		this.setValues('alpha', alpha + (alpha * ratio));
  		return this;
  	},
  
  	rotate: function (degrees) {
  		var hsl = this.values.hsl;
  		var hue = (hsl[0] + degrees) % 360;
  		hsl[0] = hue < 0 ? 360 + hue : hue;
  		this.setValues('hsl', hsl);
  		return this;
  	},
  
  	/**
  	 * Ported from sass implementation in C
  	 * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209
  	 */
  	mix: function (mixinColor, weight) {
  		var color1 = this;
  		var color2 = mixinColor;
  		var p = weight === undefined ? 0.5 : weight;
  
  		var w = 2 * p - 1;
  		var a = color1.alpha() - color2.alpha();
  
  		var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
  		var w2 = 1 - w1;
  
  		return this
  			.rgb(
  				w1 * color1.red() + w2 * color2.red(),
  				w1 * color1.green() + w2 * color2.green(),
  				w1 * color1.blue() + w2 * color2.blue()
  			)
  			.alpha(color1.alpha() * p + color2.alpha() * (1 - p));
  	},
  
  	toJSON: function () {
  		return this.rgb();
  	},
  
  	clone: function () {
  		// NOTE(SB): using node-clone creates a dependency to Buffer when using browserify,
  		// making the final build way to big to embed in Chart.js. So let's do it manually,
  		// assuming that values to clone are 1 dimension arrays containing only numbers,
  		// except 'alpha' which is a number.
  		var result = new Color();
  		var source = this.values;
  		var target = result.values;
  		var value, type;
  
  		for (var prop in source) {
  			if (source.hasOwnProperty(prop)) {
  				value = source[prop];
  				type = ({}).toString.call(value);
  				if (type === '[object Array]') {
  					target[prop] = value.slice(0);
  				} else if (type === '[object Number]') {
  					target[prop] = value;
  				} else {
  					console.error('unexpected color value:', value);
  				}
  			}
  		}
  
  		return result;
  	}
  };
  
  Color.prototype.spaces = {
  	rgb: ['red', 'green', 'blue'],
  	hsl: ['hue', 'saturation', 'lightness'],
  	hsv: ['hue', 'saturation', 'value'],
  	hwb: ['hue', 'whiteness', 'blackness'],
  	cmyk: ['cyan', 'magenta', 'yellow', 'black']
  };
  
  Color.prototype.maxes = {
  	rgb: [255, 255, 255],
  	hsl: [360, 100, 100],
  	hsv: [360, 100, 100],
  	hwb: [360, 100, 100],
  	cmyk: [100, 100, 100, 100]
  };
  
  Color.prototype.getValues = function (space) {
  	var values = this.values;
  	var vals = {};
  
  	for (var i = 0; i < space.length; i++) {
  		vals[space.charAt(i)] = values[space][i];
  	}
  
  	if (values.alpha !== 1) {
  		vals.a = values.alpha;
  	}
  
  	// {r: 255, g: 255, b: 255, a: 0.4}
  	return vals;
  };
  
  Color.prototype.setValues = function (space, vals) {
  	var values = this.values;
  	var spaces = this.spaces;
  	var maxes = this.maxes;
  	var alpha = 1;
  	var i;
  
  	if (space === 'alpha') {
  		alpha = vals;
  	} else if (vals.length) {
  		// [10, 10, 10]
  		values[space] = vals.slice(0, space.length);
  		alpha = vals[space.length];
  	} else if (vals[space.charAt(0)] !== undefined) {
  		// {r: 10, g: 10, b: 10}
  		for (i = 0; i < space.length; i++) {
  			values[space][i] = vals[space.charAt(i)];
  		}
  
  		alpha = vals.a;
  	} else if (vals[spaces[space][0]] !== undefined) {
  		// {red: 10, green: 10, blue: 10}
  		var chans = spaces[space];
  
  		for (i = 0; i < space.length; i++) {
  			values[space][i] = vals[chans[i]];
  		}
  
  		alpha = vals.alpha;
  	}
  
  	values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha)));
  
  	if (space === 'alpha') {
  		return false;
  	}
  
  	var capped;
  
  	// cap values of the space prior converting all values
  	for (i = 0; i < space.length; i++) {
  		capped = Math.max(0, Math.min(maxes[space][i], values[space][i]));
  		values[space][i] = Math.round(capped);
  	}
  
  	// convert to all the other color spaces
  	for (var sname in spaces) {
  		if (sname !== space) {
  			values[sname] = convert[space][sname](values[space]);
  		}
  	}
  
  	return true;
  };
  
  Color.prototype.setSpace = function (space, args) {
  	var vals = args[0];
  
  	if (vals === undefined) {
  		// color.rgb()
  		return this.getValues(space);
  	}
  
  	// color.rgb(10, 10, 10)
  	if (typeof vals === 'number') {
  		vals = Array.prototype.slice.call(args);
  	}
  
  	this.setValues(space, vals);
  	return this;
  };
  
  Color.prototype.setChannel = function (space, index, val) {
  	var svalues = this.values[space];
  	if (val === undefined) {
  		// color.red()
  		return svalues[index];
  	} else if (val === svalues[index]) {
  		// color.red(color.red())
  		return this;
  	}
  
  	// color.red(100)
  	svalues[index] = val;
  	this.setValues(space, svalues);
  
  	return this;
  };
  
  if (typeof window !== 'undefined') {
  	window.Color = Color;
  }
  
  module.exports = Color;
  
  },{"chartjs-color-string":2,"color-convert":5}],4:[function(require,module,exports){
  /* MIT license */
  
  module.exports = {
    rgb2hsl: rgb2hsl,
    rgb2hsv: rgb2hsv,
    rgb2hwb: rgb2hwb,
    rgb2cmyk: rgb2cmyk,
    rgb2keyword: rgb2keyword,
    rgb2xyz: rgb2xyz,
    rgb2lab: rgb2lab,
    rgb2lch: rgb2lch,
  
    hsl2rgb: hsl2rgb,
    hsl2hsv: hsl2hsv,
    hsl2hwb: hsl2hwb,
    hsl2cmyk: hsl2cmyk,
    hsl2keyword: hsl2keyword,
  
    hsv2rgb: hsv2rgb,
    hsv2hsl: hsv2hsl,
    hsv2hwb: hsv2hwb,
    hsv2cmyk: hsv2cmyk,
    hsv2keyword: hsv2keyword,
  
    hwb2rgb: hwb2rgb,
    hwb2hsl: hwb2hsl,
    hwb2hsv: hwb2hsv,
    hwb2cmyk: hwb2cmyk,
    hwb2keyword: hwb2keyword,
  
    cmyk2rgb: cmyk2rgb,
    cmyk2hsl: cmyk2hsl,
    cmyk2hsv: cmyk2hsv,
    cmyk2hwb: cmyk2hwb,
    cmyk2keyword: cmyk2keyword,
  
    keyword2rgb: keyword2rgb,
    keyword2hsl: keyword2hsl,
    keyword2hsv: keyword2hsv,
    keyword2hwb: keyword2hwb,
    keyword2cmyk: keyword2cmyk,
    keyword2lab: keyword2lab,
    keyword2xyz: keyword2xyz,
  
    xyz2rgb: xyz2rgb,
    xyz2lab: xyz2lab,
    xyz2lch: xyz2lch,
  
    lab2xyz: lab2xyz,
    lab2rgb: lab2rgb,
    lab2lch: lab2lch,
  
    lch2lab: lch2lab,
    lch2xyz: lch2xyz,
    lch2rgb: lch2rgb
  }
  
  
  function rgb2hsl(rgb) {
    var r = rgb[0]/255,
        g = rgb[1]/255,
        b = rgb[2]/255,
        min = Math.min(r, g, b),
        max = Math.max(r, g, b),
        delta = max - min,
        h, s, l;
  
    if (max == min)
      h = 0;
    else if (r == max)
      h = (g - b) / delta;
    else if (g == max)
      h = 2 + (b - r) / delta;
    else if (b == max)
      h = 4 + (r - g)/ delta;
  
    h = Math.min(h * 60, 360);
  
    if (h < 0)
      h += 360;
  
    l = (min + max) / 2;
  
    if (max == min)
      s = 0;
    else if (l <= 0.5)
      s = delta / (max + min);
    else
      s = delta / (2 - max - min);
  
    return [h, s * 100, l * 100];
  }
  
  function rgb2hsv(rgb) {
    var r = rgb[0],
        g = rgb[1],
        b = rgb[2],
        min = Math.min(r, g, b),
        max = Math.max(r, g, b),
        delta = max - min,
        h, s, v;
  
    if (max == 0)
      s = 0;
    else
      s = (delta/max * 1000)/10;
  
    if (max == min)
      h = 0;
    else if (r == max)
      h = (g - b) / delta;
    else if (g == max)
      h = 2 + (b - r) / delta;
    else if (b == max)
      h = 4 + (r - g) / delta;
  
    h = Math.min(h * 60, 360);
  
    if (h < 0)
      h += 360;
  
    v = ((max / 255) * 1000) / 10;
  
    return [h, s, v];
  }
  
  function rgb2hwb(rgb) {
    var r = rgb[0],
        g = rgb[1],
        b = rgb[2],
        h = rgb2hsl(rgb)[0],
        w = 1/255 * Math.min(r, Math.min(g, b)),
        b = 1 - 1/255 * Math.max(r, Math.max(g, b));
  
    return [h, w * 100, b * 100];
  }
  
  function rgb2cmyk(rgb) {
    var r = rgb[0] / 255,
        g = rgb[1] / 255,
        b = rgb[2] / 255,
        c, m, y, k;
  
    k = Math.min(1 - r, 1 - g, 1 - b);
    c = (1 - r - k) / (1 - k) || 0;
    m = (1 - g - k) / (1 - k) || 0;
    y = (1 - b - k) / (1 - k) || 0;
    return [c * 100, m * 100, y * 100, k * 100];
  }
  
  function rgb2keyword(rgb) {
    return reverseKeywords[JSON.stringify(rgb)];
  }
  
  function rgb2xyz(rgb) {
    var r = rgb[0] / 255,
        g = rgb[1] / 255,
        b = rgb[2] / 255;
  
    // assume sRGB
    r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
    g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
    b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);
  
    var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
    var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
    var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
  
    return [x * 100, y *100, z * 100];
  }
  
  function rgb2lab(rgb) {
    var xyz = rgb2xyz(rgb),
          x = xyz[0],
          y = xyz[1],
          z = xyz[2],
          l, a, b;
  
    x /= 95.047;
    y /= 100;
    z /= 108.883;
  
    x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
    y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
    z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
  
    l = (116 * y) - 16;
    a = 500 * (x - y);
    b = 200 * (y - z);
  
    return [l, a, b];
  }
  
  function rgb2lch(args) {
    return lab2lch(rgb2lab(args));
  }
  
  function hsl2rgb(hsl) {
    var h = hsl[0] / 360,
        s = hsl[1] / 100,
        l = hsl[2] / 100,
        t1, t2, t3, rgb, val;
  
    if (s == 0) {
      val = l * 255;
      return [val, val, val];
    }
  
    if (l < 0.5)
      t2 = l * (1 + s);
    else
      t2 = l + s - l * s;
    t1 = 2 * l - t2;
  
    rgb = [0, 0, 0];
    for (var i = 0; i < 3; i++) {
      t3 = h + 1 / 3 * - (i - 1);
      t3 < 0 && t3++;
      t3 > 1 && t3--;
  
      if (6 * t3 < 1)
        val = t1 + (t2 - t1) * 6 * t3;
      else if (2 * t3 < 1)
        val = t2;
      else if (3 * t3 < 2)
        val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
      else
        val = t1;
  
      rgb[i] = val * 255;
    }
  
    return rgb;
  }
  
  function hsl2hsv(hsl) {
    var h = hsl[0],
        s = hsl[1] / 100,
        l = hsl[2] / 100,
        sv, v;
  
    if(l === 0) {
        // no need to do calc on black
        // also avoids divide by 0 error
        return [0, 0, 0];
    }
  
    l *= 2;
    s *= (l <= 1) ? l : 2 - l;
    v = (l + s) / 2;
    sv = (2 * s) / (l + s);
    return [h, sv * 100, v * 100];
  }
  
  function hsl2hwb(args) {
    return rgb2hwb(hsl2rgb(args));
  }
  
  function hsl2cmyk(args) {
    return rgb2cmyk(hsl2rgb(args));
  }
  
  function hsl2keyword(args) {
    return rgb2keyword(hsl2rgb(args));
  }
  
  
  function hsv2rgb(hsv) {
    var h = hsv[0] / 60,
        s = hsv[1] / 100,
        v = hsv[2] / 100,
        hi = Math.floor(h) % 6;
  
    var f = h - Math.floor(h),
        p = 255 * v * (1 - s),
        q = 255 * v * (1 - (s * f)),
        t = 255 * v * (1 - (s * (1 - f))),
        v = 255 * v;
  
    switch(hi) {
      case 0:
        return [v, t, p];
      case 1:
        return [q, v, p];
      case 2:
        return [p, v, t];
      case 3:
        return [p, q, v];
      case 4:
        return [t, p, v];
      case 5:
        return [v, p, q];
    }
  }
  
  function hsv2hsl(hsv) {
    var h = hsv[0],
        s = hsv[1] / 100,
        v = hsv[2] / 100,
        sl, l;
  
    l = (2 - s) * v;
    sl = s * v;
    sl /= (l <= 1) ? l : 2 - l;
    sl = sl || 0;
    l /= 2;
    return [h, sl * 100, l * 100];
  }
  
  function hsv2hwb(args) {
    return rgb2hwb(hsv2rgb(args))
  }
  
  function hsv2cmyk(args) {
    return rgb2cmyk(hsv2rgb(args));
  }
  
  function hsv2keyword(args) {
    return rgb2keyword(hsv2rgb(args));
  }
  
  // http://dev.w3.org/csswg/css-color/#hwb-to-rgb
  function hwb2rgb(hwb) {
    var h = hwb[0] / 360,
        wh = hwb[1] / 100,
        bl = hwb[2] / 100,
        ratio = wh + bl,
        i, v, f, n;
  
    // wh + bl cant be > 1
    if (ratio > 1) {
      wh /= ratio;
      bl /= ratio;
    }
  
    i = Math.floor(6 * h);
    v = 1 - bl;
    f = 6 * h - i;
    if ((i & 0x01) != 0) {
      f = 1 - f;
    }
    n = wh + f * (v - wh);  // linear interpolation
  
    switch (i) {
      default:
      case 6:
      case 0: r = v; g = n; b = wh; break;
      case 1: r = n; g = v; b = wh; break;
      case 2: r = wh; g = v; b = n; break;
      case 3: r = wh; g = n; b = v; break;
      case 4: r = n; g = wh; b = v; break;
      case 5: r = v; g = wh; b = n; break;
    }
  
    return [r * 255, g * 255, b * 255];
  }
  
  function hwb2hsl(args) {
    return rgb2hsl(hwb2rgb(args));
  }
  
  function hwb2hsv(args) {
    return rgb2hsv(hwb2rgb(args));
  }
  
  function hwb2cmyk(args) {
    return rgb2cmyk(hwb2rgb(args));
  }
  
  function hwb2keyword(args) {
    return rgb2keyword(hwb2rgb(args));
  }
  
  function cmyk2rgb(cmyk) {
    var c = cmyk[0] / 100,
        m = cmyk[1] / 100,
        y = cmyk[2] / 100,
        k = cmyk[3] / 100,
        r, g, b;
  
    r = 1 - Math.min(1, c * (1 - k) + k);
    g = 1 - Math.min(1, m * (1 - k) + k);
    b = 1 - Math.min(1, y * (1 - k) + k);
    return [r * 255, g * 255, b * 255];
  }
  
  function cmyk2hsl(args) {
    return rgb2hsl(cmyk2rgb(args));
  }
  
  function cmyk2hsv(args) {
    return rgb2hsv(cmyk2rgb(args));
  }
  
  function cmyk2hwb(args) {
    return rgb2hwb(cmyk2rgb(args));
  }
  
  function cmyk2keyword(args) {
    return rgb2keyword(cmyk2rgb(args));
  }
  
  
  function xyz2rgb(xyz) {
    var x = xyz[0] / 100,
        y = xyz[1] / 100,
        z = xyz[2] / 100,
        r, g, b;
  
    r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
    g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
    b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
  
    // assume sRGB
    r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)
      : r = (r * 12.92);
  
    g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)
      : g = (g * 12.92);
  
    b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)
      : b = (b * 12.92);
  
    r = Math.min(Math.max(0, r), 1);
    g = Math.min(Math.max(0, g), 1);
    b = Math.min(Math.max(0, b), 1);
  
    return [r * 255, g * 255, b * 255];
  }
  
  function xyz2lab(xyz) {
    var x = xyz[0],
        y = xyz[1],
        z = xyz[2],
        l, a, b;
  
    x /= 95.047;
    y /= 100;
    z /= 108.883;
  
    x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
    y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
    z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
  
    l = (116 * y) - 16;
    a = 500 * (x - y);
    b = 200 * (y - z);
  
    return [l, a, b];
  }
  
  function xyz2lch(args) {
    return lab2lch(xyz2lab(args));
  }
  
  function lab2xyz(lab) {
    var l = lab[0],
        a = lab[1],
        b = lab[2],
        x, y, z, y2;
  
    if (l <= 8) {
      y = (l * 100) / 903.3;
      y2 = (7.787 * (y / 100)) + (16 / 116);
    } else {
      y = 100 * Math.pow((l + 16) / 116, 3);
      y2 = Math.pow(y / 100, 1/3);
    }
  
    x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3);
  
    z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3);
  
    return [x, y, z];
  }
  
  function lab2lch(lab) {
    var l = lab[0],
        a = lab[1],
        b = lab[2],
        hr, h, c;
  
    hr = Math.atan2(b, a);
    h = hr * 360 / 2 / Math.PI;
    if (h < 0) {
      h += 360;
    }
    c = Math.sqrt(a * a + b * b);
    return [l, c, h];
  }
  
  function lab2rgb(args) {
    return xyz2rgb(lab2xyz(args));
  }
  
  function lch2lab(lch) {
    var l = lch[0],
        c = lch[1],
        h = lch[2],
        a, b, hr;
  
    hr = h / 360 * 2 * Math.PI;
    a = c * Math.cos(hr);
    b = c * Math.sin(hr);
    return [l, a, b];
  }
  
  function lch2xyz(args) {
    return lab2xyz(lch2lab(args));
  }
  
  function lch2rgb(args) {
    return lab2rgb(lch2lab(args));
  }
  
  function keyword2rgb(keyword) {
    return cssKeywords[keyword];
  }
  
  function keyword2hsl(args) {
    return rgb2hsl(keyword2rgb(args));
  }
  
  function keyword2hsv(args) {
    return rgb2hsv(keyword2rgb(args));
  }
  
  function keyword2hwb(args) {
    return rgb2hwb(keyword2rgb(args));
  }
  
  function keyword2cmyk(args) {
    return rgb2cmyk(keyword2rgb(args));
  }
  
  function keyword2lab(args) {
    return rgb2lab(keyword2rgb(args));
  }
  
  function keyword2xyz(args) {
    return rgb2xyz(keyword2rgb(args));
  }
  
  var cssKeywords = {
    aliceblue:  [240,248,255],
    antiquewhite: [250,235,215],
    aqua: [0,255,255],
    aquamarine: [127,255,212],
    azure:  [240,255,255],
    beige:  [245,245,220],
    bisque: [255,228,196],
    black:  [0,0,0],
    blanchedalmond: [255,235,205],
    blue: [0,0,255],
    blueviolet: [138,43,226],
    brown:  [165,42,42],
    burlywood:  [222,184,135],
    cadetblue:  [95,158,160],
    chartreuse: [127,255,0],
    chocolate:  [210,105,30],
    coral:  [255,127,80],
    cornflowerblue: [100,149,237],
    cornsilk: [255,248,220],
    crimson:  [220,20,60],
    cyan: [0,255,255],
    darkblue: [0,0,139],
    darkcyan: [0,139,139],
    darkgoldenrod:  [184,134,11],
    darkgray: [169,169,169],
    darkgreen:  [0,100,0],
    darkgrey: [169,169,169],
    darkkhaki:  [189,183,107],
    darkmagenta:  [139,0,139],
    darkolivegreen: [85,107,47],
    darkorange: [255,140,0],
    darkorchid: [153,50,204],
    darkred:  [139,0,0],
    darksalmon: [233,150,122],
    darkseagreen: [143,188,143],
    darkslateblue:  [72,61,139],
    darkslategray:  [47,79,79],
    darkslategrey:  [47,79,79],
    darkturquoise:  [0,206,209],
    darkviolet: [148,0,211],
    deeppink: [255,20,147],
    deepskyblue:  [0,191,255],
    dimgray:  [105,105,105],
    dimgrey:  [105,105,105],
    dodgerblue: [30,144,255],
    firebrick:  [178,34,34],
    floralwhite:  [255,250,240],
    forestgreen:  [34,139,34],
    fuchsia:  [255,0,255],
    gainsboro:  [220,220,220],
    ghostwhite: [248,248,255],
    gold: [255,215,0],
    goldenrod:  [218,165,32],
    gray: [128,128,128],
    green:  [0,128,0],
    greenyellow:  [173,255,47],
    grey: [128,128,128],
    honeydew: [240,255,240],
    hotpink:  [255,105,180],
    indianred:  [205,92,92],
    indigo: [75,0,130],
    ivory:  [255,255,240],
    khaki:  [240,230,140],
    lavender: [230,230,250],
    lavenderblush:  [255,240,245],
    lawngreen:  [124,252,0],
    lemonchiffon: [255,250,205],
    lightblue:  [173,216,230],
    lightcoral: [240,128,128],
    lightcyan:  [224,255,255],
    lightgoldenrodyellow: [250,250,210],
    lightgray:  [211,211,211],
    lightgreen: [144,238,144],
    lightgrey:  [211,211,211],
    lightpink:  [255,182,193],
    lightsalmon:  [255,160,122],
    lightseagreen:  [32,178,170],
    lightskyblue: [135,206,250],
    lightslategray: [119,136,153],
    lightslategrey: [119,136,153],
    lightsteelblue: [176,196,222],
    lightyellow:  [255,255,224],
    lime: [0,255,0],
    limegreen:  [50,205,50],
    linen:  [250,240,230],
    magenta:  [255,0,255],
    maroon: [128,0,0],
    mediumaquamarine: [102,205,170],
    mediumblue: [0,0,205],
    mediumorchid: [186,85,211],
    mediumpurple: [147,112,219],
    mediumseagreen: [60,179,113],
    mediumslateblue:  [123,104,238],
    mediumspringgreen:  [0,250,154],
    mediumturquoise:  [72,209,204],
    mediumvioletred:  [199,21,133],
    midnightblue: [25,25,112],
    mintcream:  [245,255,250],
    mistyrose:  [255,228,225],
    moccasin: [255,228,181],
    navajowhite:  [255,222,173],
    navy: [0,0,128],
    oldlace:  [253,245,230],
    olive:  [128,128,0],
    olivedrab:  [107,142,35],
    orange: [255,165,0],
    orangered:  [255,69,0],
    orchid: [218,112,214],
    palegoldenrod:  [238,232,170],
    palegreen:  [152,251,152],
    paleturquoise:  [175,238,238],
    palevioletred:  [219,112,147],
    papayawhip: [255,239,213],
    peachpuff:  [255,218,185],
    peru: [205,133,63],
    pink: [255,192,203],
    plum: [221,160,221],
    powderblue: [176,224,230],
    purple: [128,0,128],
    rebeccapurple: [102, 51, 153],
    red:  [255,0,0],
    rosybrown:  [188,143,143],
    royalblue:  [65,105,225],
    saddlebrown:  [139,69,19],
    salmon: [250,128,114],
    sandybrown: [244,164,96],
    seagreen: [46,139,87],
    seashell: [255,245,238],
    sienna: [160,82,45],
    silver: [192,192,192],
    skyblue:  [135,206,235],
    slateblue:  [106,90,205],
    slategray:  [112,128,144],
    slategrey:  [112,128,144],
    snow: [255,250,250],
    springgreen:  [0,255,127],
    steelblue:  [70,130,180],
    tan:  [210,180,140],
    teal: [0,128,128],
    thistle:  [216,191,216],
    tomato: [255,99,71],
    turquoise:  [64,224,208],
    violet: [238,130,238],
    wheat:  [245,222,179],
    white:  [255,255,255],
    whitesmoke: [245,245,245],
    yellow: [255,255,0],
    yellowgreen:  [154,205,50]
  };
  
  var reverseKeywords = {};
  for (var key in cssKeywords) {
    reverseKeywords[JSON.stringify(cssKeywords[key])] = key;
  }
  
  },{}],5:[function(require,module,exports){
  var conversions = require("./conversions");
  
  var convert = function() {
     return new Converter();
  }
  
  for (var func in conversions) {
    // export Raw versions
    convert[func + "Raw"] =  (function(func) {
      // accept array or plain args
      return function(arg) {
        if (typeof arg == "number")
          arg = Array.prototype.slice.call(arguments);
        return conversions[func](arg);
      }
    })(func);
  
    var pair = /(\w+)2(\w+)/.exec(func),
        from = pair[1],
        to = pair[2];
  
    // export rgb2hsl and ["rgb"]["hsl"]
    convert[from] = convert[from] || {};
  
    convert[from][to] = convert[func] = (function(func) { 
      return function(arg) {
        if (typeof arg == "number")
          arg = Array.prototype.slice.call(arguments);
        
        var val = conversions[func](arg);
        if (typeof val == "string" || val === undefined)
          return val; // keyword
  
        for (var i = 0; i < val.length; i++)
          val[i] = Math.round(val[i]);
        return val;
      }
    })(func);
  }
  
  
  /* Converter does lazy conversion and caching */
  var Converter = function() {
     this.convs = {};
  };
  
  /* Either get the values for a space or
    set the values for a space, depending on args */
  Converter.prototype.routeSpace = function(space, args) {
     var values = args[0];
     if (values === undefined) {
        // color.rgb()
        return this.getValues(space);
     }
     // color.rgb(10, 10, 10)
     if (typeof values == "number") {
        values = Array.prototype.slice.call(args);        
     }
  
     return this.setValues(space, values);
  };
    
  /* Set the values for a space, invalidating cache */
  Converter.prototype.setValues = function(space, values) {
     this.space = space;
     this.convs = {};
     this.convs[space] = values;
     return this;
  };
  
  /* Get the values for a space. If there's already
    a conversion for the space, fetch it, otherwise
    compute it */
  Converter.prototype.getValues = function(space) {
     var vals = this.convs[space];
     if (!vals) {
        var fspace = this.space,
            from = this.convs[fspace];
        vals = convert[fspace][space](from);
  
        this.convs[space] = vals;
     }
    return vals;
  };
  
  ["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) {
     Converter.prototype[space] = function(vals) {
        return this.routeSpace(space, arguments);
     }
  });
  
  module.exports = convert;
  },{"./conversions":4}],6:[function(require,module,exports){
  module.exports = {
  	"aliceblue": [240, 248, 255],
  	"antiquewhite": [250, 235, 215],
  	"aqua": [0, 255, 255],
  	"aquamarine": [127, 255, 212],
  	"azure": [240, 255, 255],
  	"beige": [245, 245, 220],
  	"bisque": [255, 228, 196],
  	"black": [0, 0, 0],
  	"blanchedalmond": [255, 235, 205],
  	"blue": [0, 0, 255],
  	"blueviolet": [138, 43, 226],
  	"brown": [165, 42, 42],
  	"burlywood": [222, 184, 135],
  	"cadetblue": [95, 158, 160],
  	"chartreuse": [127, 255, 0],
  	"chocolate": [210, 105, 30],
  	"coral": [255, 127, 80],
  	"cornflowerblue": [100, 149, 237],
  	"cornsilk": [255, 248, 220],
  	"crimson": [220, 20, 60],
  	"cyan": [0, 255, 255],
  	"darkblue": [0, 0, 139],
  	"darkcyan": [0, 139, 139],
  	"darkgoldenrod": [184, 134, 11],
  	"darkgray": [169, 169, 169],
  	"darkgreen": [0, 100, 0],
  	"darkgrey": [169, 169, 169],
  	"darkkhaki": [189, 183, 107],
  	"darkmagenta": [139, 0, 139],
  	"darkolivegreen": [85, 107, 47],
  	"darkorange": [255, 140, 0],
  	"darkorchid": [153, 50, 204],
  	"darkred": [139, 0, 0],
  	"darksalmon": [233, 150, 122],
  	"darkseagreen": [143, 188, 143],
  	"darkslateblue": [72, 61, 139],
  	"darkslategray": [47, 79, 79],
  	"darkslategrey": [47, 79, 79],
  	"darkturquoise": [0, 206, 209],
  	"darkviolet": [148, 0, 211],
  	"deeppink": [255, 20, 147],
  	"deepskyblue": [0, 191, 255],
  	"dimgray": [105, 105, 105],
  	"dimgrey": [105, 105, 105],
  	"dodgerblue": [30, 144, 255],
  	"firebrick": [178, 34, 34],
  	"floralwhite": [255, 250, 240],
  	"forestgreen": [34, 139, 34],
  	"fuchsia": [255, 0, 255],
  	"gainsboro": [220, 220, 220],
  	"ghostwhite": [248, 248, 255],
  	"gold": [255, 215, 0],
  	"goldenrod": [218, 165, 32],
  	"gray": [128, 128, 128],
  	"green": [0, 128, 0],
  	"greenyellow": [173, 255, 47],
  	"grey": [128, 128, 128],
  	"honeydew": [240, 255, 240],
  	"hotpink": [255, 105, 180],
  	"indianred": [205, 92, 92],
  	"indigo": [75, 0, 130],
  	"ivory": [255, 255, 240],
  	"khaki": [240, 230, 140],
  	"lavender": [230, 230, 250],
  	"lavenderblush": [255, 240, 245],
  	"lawngreen": [124, 252, 0],
  	"lemonchiffon": [255, 250, 205],
  	"lightblue": [173, 216, 230],
  	"lightcoral": [240, 128, 128],
  	"lightcyan": [224, 255, 255],
  	"lightgoldenrodyellow": [250, 250, 210],
  	"lightgray": [211, 211, 211],
  	"lightgreen": [144, 238, 144],
  	"lightgrey": [211, 211, 211],
  	"lightpink": [255, 182, 193],
  	"lightsalmon": [255, 160, 122],
  	"lightseagreen": [32, 178, 170],
  	"lightskyblue": [135, 206, 250],
  	"lightslategray": [119, 136, 153],
  	"lightslategrey": [119, 136, 153],
  	"lightsteelblue": [176, 196, 222],
  	"lightyellow": [255, 255, 224],
  	"lime": [0, 255, 0],
  	"limegreen": [50, 205, 50],
  	"linen": [250, 240, 230],
  	"magenta": [255, 0, 255],
  	"maroon": [128, 0, 0],
  	"mediumaquamarine": [102, 205, 170],
  	"mediumblue": [0, 0, 205],
  	"mediumorchid": [186, 85, 211],
  	"mediumpurple": [147, 112, 219],
  	"mediumseagreen": [60, 179, 113],
  	"mediumslateblue": [123, 104, 238],
  	"mediumspringgreen": [0, 250, 154],
  	"mediumturquoise": [72, 209, 204],
  	"mediumvioletred": [199, 21, 133],
  	"midnightblue": [25, 25, 112],
  	"mintcream": [245, 255, 250],
  	"mistyrose": [255, 228, 225],
  	"moccasin": [255, 228, 181],
  	"navajowhite": [255, 222, 173],
  	"navy": [0, 0, 128],
  	"oldlace": [253, 245, 230],
  	"olive": [128, 128, 0],
  	"olivedrab": [107, 142, 35],
  	"orange": [255, 165, 0],
  	"orangered": [255, 69, 0],
  	"orchid": [218, 112, 214],
  	"palegoldenrod": [238, 232, 170],
  	"palegreen": [152, 251, 152],
  	"paleturquoise": [175, 238, 238],
  	"palevioletred": [219, 112, 147],
  	"papayawhip": [255, 239, 213],
  	"peachpuff": [255, 218, 185],
  	"peru": [205, 133, 63],
  	"pink": [255, 192, 203],
  	"plum": [221, 160, 221],
  	"powderblue": [176, 224, 230],
  	"purple": [128, 0, 128],
  	"rebeccapurple": [102, 51, 153],
  	"red": [255, 0, 0],
  	"rosybrown": [188, 143, 143],
  	"royalblue": [65, 105, 225],
  	"saddlebrown": [139, 69, 19],
  	"salmon": [250, 128, 114],
  	"sandybrown": [244, 164, 96],
  	"seagreen": [46, 139, 87],
  	"seashell": [255, 245, 238],
  	"sienna": [160, 82, 45],
  	"silver": [192, 192, 192],
  	"skyblue": [135, 206, 235],
  	"slateblue": [106, 90, 205],
  	"slategray": [112, 128, 144],
  	"slategrey": [112, 128, 144],
  	"snow": [255, 250, 250],
  	"springgreen": [0, 255, 127],
  	"steelblue": [70, 130, 180],
  	"tan": [210, 180, 140],
  	"teal": [0, 128, 128],
  	"thistle": [216, 191, 216],
  	"tomato": [255, 99, 71],
  	"turquoise": [64, 224, 208],
  	"violet": [238, 130, 238],
  	"wheat": [245, 222, 179],
  	"white": [255, 255, 255],
  	"whitesmoke": [245, 245, 245],
  	"yellow": [255, 255, 0],
  	"yellowgreen": [154, 205, 50]
  };
  },{}],7:[function(require,module,exports){
  var Chart = require('./core/core.js')();
  
  require('./core/core.helpers')(Chart);
  require('./core/core.element')(Chart);
  require('./core/core.animation')(Chart);
  require('./core/core.controller')(Chart);
  require('./core/core.datasetController')(Chart);
  require('./core/core.layoutService')(Chart);
  require('./core/core.legend')(Chart);
  require('./core/core.plugin.js')(Chart);
  require('./core/core.scale')(Chart);
  require('./core/core.scaleService')(Chart);
  require('./core/core.title')(Chart);
  require('./core/core.tooltip')(Chart);
  
  require('./elements/element.arc')(Chart);
  require('./elements/element.line')(Chart);
  require('./elements/element.point')(Chart);
  require('./elements/element.rectangle')(Chart);
  
  require('./scales/scale.category')(Chart);
  require('./scales/scale.linear')(Chart);
  require('./scales/scale.logarithmic')(Chart);
  require('./scales/scale.radialLinear')(Chart);
  require('./scales/scale.time')(Chart);
  
  // Controllers must be loaded after elements
  // See Chart.core.datasetController.dataElementType
  require('./controllers/controller.bar')(Chart);
  require('./controllers/controller.bubble')(Chart);
  require('./controllers/controller.doughnut')(Chart);
  require('./controllers/controller.line')(Chart);
  require('./controllers/controller.polarArea')(Chart);
  require('./controllers/controller.radar')(Chart);
  
  require('./charts/Chart.Bar')(Chart);
  require('./charts/Chart.Bubble')(Chart);
  require('./charts/Chart.Doughnut')(Chart);
  require('./charts/Chart.Line')(Chart);
  require('./charts/Chart.PolarArea')(Chart);
  require('./charts/Chart.Radar')(Chart);
  require('./charts/Chart.Scatter')(Chart);
  
  window.Chart = module.exports = Chart;
  
  },{"./charts/Chart.Bar":8,"./charts/Chart.Bubble":9,"./charts/Chart.Doughnut":10,"./charts/Chart.Line":11,"./charts/Chart.PolarArea":12,"./charts/Chart.Radar":13,"./charts/Chart.Scatter":14,"./controllers/controller.bar":15,"./controllers/controller.bubble":16,"./controllers/controller.doughnut":17,"./controllers/controller.line":18,"./controllers/controller.polarArea":19,"./controllers/controller.radar":20,"./core/core.animation":21,"./core/core.controller":22,"./core/core.datasetController":23,"./core/core.element":24,"./core/core.helpers":25,"./core/core.js":26,"./core/core.layoutService":27,"./core/core.legend":28,"./core/core.plugin.js":29,"./core/core.scale":30,"./core/core.scaleService":31,"./core/core.title":32,"./core/core.tooltip":33,"./elements/element.arc":34,"./elements/element.line":35,"./elements/element.point":36,"./elements/element.rectangle":37,"./scales/scale.category":38,"./scales/scale.linear":39,"./scales/scale.logarithmic":40,"./scales/scale.radialLinear":41,"./scales/scale.time":42}],8:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	Chart.Bar = function(context, config) {
  		config.type = 'bar';
  
  		return new Chart(context, config);
  	};
  
  };
  },{}],9:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	Chart.Bubble = function(context, config) {
  		config.type = 'bubble';
  		return new Chart(context, config);
  	};
  
  };
  },{}],10:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	Chart.Doughnut = function(context, config) {
  		config.type = 'doughnut';
  
  		return new Chart(context, config);
  	};
  
  };
  },{}],11:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	Chart.Line = function(context, config) {
  		config.type = 'line';
  
  		return new Chart(context, config);
  	};
  
  };
  },{}],12:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	Chart.PolarArea = function(context, config) {
  		config.type = 'polarArea';
  
  		return new Chart(context, config);
  	};
  
  };
  },{}],13:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  	
  	Chart.Radar = function(context, config) {
  		config.options = Chart.helpers.configMerge({ aspectRatio: 1 }, config.options);
  		config.type = 'radar';
  
  		return new Chart(context, config);
  	};
  
  };
  
  },{}],14:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var defaultConfig = {
  		hover: {
  			mode: 'single'
  		},
  
  		scales: {
  			xAxes: [{
  				type: "linear", // scatter should not use a category axis
  				position: "bottom",
  				id: "x-axis-1" // need an ID so datasets can reference the scale
  			}],
  			yAxes: [{
  				type: "linear",
  				position: "left",
  				id: "y-axis-1"
  			}]
  		},
  
  		tooltips: {
  			callbacks: {
  				title: function(tooltipItems, data) {
  					// Title doesn't make sense for scatter since we format the data as a point
  					return '';
  				},
  				label: function(tooltipItem, data) {
  					return '(' + tooltipItem.xLabel + ', ' + tooltipItem.yLabel + ')';
  				}
  			}
  		}
  	};
  
  	// Register the default config for this type
  	Chart.defaults.scatter = defaultConfig;
  
  	// Scatter charts use line controllers
  	Chart.controllers.scatter = Chart.controllers.line;
  
  	Chart.Scatter = function(context, config) {
  		config.type = 'scatter';
  		return new Chart(context, config);
  	};
  
  };
  },{}],15:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers;
  
  	Chart.defaults.bar = {
  		hover: {
  			mode: "label"
  		},
  
  		scales: {
  			xAxes: [{
  				type: "category",
  
  				// Specific to Bar Controller
  				categoryPercentage: 0.8,
  				barPercentage: 0.9,
  
  				// grid line settings
  				gridLines: {
  					offsetGridLines: true
  				}
  			}],
  			yAxes: [{
  				type: "linear"
  			}]
  		}
  	};
  
  	Chart.controllers.bar = Chart.DatasetController.extend({
  
  		dataElementType: Chart.elements.Rectangle,
  
  		initialize: function(chart, datasetIndex) {
  			Chart.DatasetController.prototype.initialize.call(this, chart, datasetIndex);
  
  			// Use this to indicate that this is a bar dataset.
  			this.getMeta().bar = true;
  		},
  
  		// Get the number of datasets that display bars. We use this to correctly calculate the bar width
  		getBarCount: function getBarCount() {
  			var barCount = 0;
  			helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) {
  				var meta = this.chart.getDatasetMeta(datasetIndex);
  				if (meta.bar && this.chart.isDatasetVisible(datasetIndex)) {
  					++barCount;
  				}
  			}, this);
  			return barCount;
  		},
  
  		update: function update(reset) {
  			helpers.each(this.getMeta().data, function(rectangle, index) {
  				this.updateElement(rectangle, index, reset);
  			}, this);
  		},
  
  		updateElement: function updateElement(rectangle, index, reset) {
  			var meta = this.getMeta();
  			var xScale = this.getScaleForId(meta.xAxisID);
  			var yScale = this.getScaleForId(meta.yAxisID);
  			var scaleBase = yScale.getBasePixel();
  			var rectangleElementOptions = this.chart.options.elements.rectangle;
  			var custom = rectangle.custom || {};
  			var dataset = this.getDataset();
  
  			helpers.extend(rectangle, {
  				// Utility
  				_xScale: xScale,
  				_yScale: yScale,
  				_datasetIndex: this.index,
  				_index: index,
  
  				// Desired view properties
  				_model: {
  					x: this.calculateBarX(index, this.index),
  					y: reset ? scaleBase : this.calculateBarY(index, this.index),
  
  					// Tooltip
  					label: this.chart.data.labels[index],
  					datasetLabel: dataset.label,
  
  					// Appearance
  					base: reset ? scaleBase : this.calculateBarBase(this.index, index),
  					width: this.calculateBarWidth(index),
  					backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor),
  					borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped,
  					borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor),
  					borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth)
  				}
  			});
  			rectangle.pivot();
  		},
  
  		calculateBarBase: function(datasetIndex, index) {
  			var meta = this.getMeta();
  			var yScale = this.getScaleForId(meta.yAxisID);
  			var base = 0;
  
  			if (yScale.options.stacked) {
  				var chart = this.chart;
  				var datasets = chart.data.datasets;
  				var value = datasets[datasetIndex].data[index];
  
  				if (value < 0) {
  					for (var i = 0; i < datasetIndex; i++) {
  						var negDS = datasets[i];
  						var negDSMeta = chart.getDatasetMeta(i);
  						if (negDSMeta.bar && negDSMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
  							base += negDS.data[index] < 0 ? negDS.data[index] : 0;
  						}
  					}
  				} else {
  					for (var j = 0; j < datasetIndex; j++) {
  						var posDS = datasets[j];
  						var posDSMeta = chart.getDatasetMeta(j);
  						if (posDSMeta.bar && posDSMeta.yAxisID === yScale.id && chart.isDatasetVisible(j)) {
  							base += posDS.data[index] > 0 ? posDS.data[index] : 0;
  						}
  					}
  				}
  
  				return yScale.getPixelForValue(base);
  			}
  
  			return yScale.getBasePixel();
  		},
  
  		getRuler: function(index) {
  			var meta = this.getMeta();
  			var xScale = this.getScaleForId(meta.xAxisID);
  			var datasetCount = this.getBarCount();
  
  			var tickWidth;
  
  			if (xScale.options.type === 'category') {
  				tickWidth = xScale.getPixelForTick(index + 1) - xScale.getPixelForTick(index);
  			} else {
  				// Average width
  				tickWidth = xScale.width / xScale.ticks.length;
  			}
  			var categoryWidth = tickWidth * xScale.options.categoryPercentage;
  			var categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2;
  			var fullBarWidth = categoryWidth / datasetCount;
  
  			if (xScale.ticks.length !== this.chart.data.labels.length) {
  			    var perc = xScale.ticks.length / this.chart.data.labels.length;
  			    fullBarWidth = fullBarWidth * perc;
  			}
  
  			var barWidth = fullBarWidth * xScale.options.barPercentage;
  			var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage);
  
  			return {
  				datasetCount: datasetCount,
  				tickWidth: tickWidth,
  				categoryWidth: categoryWidth,
  				categorySpacing: categorySpacing,
  				fullBarWidth: fullBarWidth,
  				barWidth: barWidth,
  				barSpacing: barSpacing
  			};
  		},
  
  		calculateBarWidth: function(index) {
  			var xScale = this.getScaleForId(this.getMeta().xAxisID);
  			var ruler = this.getRuler(index);
  			return xScale.options.stacked ? ruler.categoryWidth : ruler.barWidth;
  		},
  
  		// Get bar index from the given dataset index accounting for the fact that not all bars are visible
  		getBarIndex: function(datasetIndex) {
  			var barIndex = 0;
  			var meta, j;
  
  			for (j = 0; j < datasetIndex; ++j) {
  				meta = this.chart.getDatasetMeta(j);
  				if (meta.bar && this.chart.isDatasetVisible(j)) {
  					++barIndex;
  				}
  			}
  
  			return barIndex;
  		},
  
  		calculateBarX: function(index, datasetIndex) {
  			var meta = this.getMeta();
  			var xScale = this.getScaleForId(meta.xAxisID);
  			var barIndex = this.getBarIndex(datasetIndex);
  
  			var ruler = this.getRuler(index);
  			var leftTick = xScale.getPixelForValue(null, index, datasetIndex, this.chart.isCombo);
  			leftTick -= this.chart.isCombo ? (ruler.tickWidth / 2) : 0;
  
  			if (xScale.options.stacked) {
  				return leftTick + (ruler.categoryWidth / 2) + ruler.categorySpacing;
  			}
  
  			return leftTick +
  				(ruler.barWidth / 2) +
  				ruler.categorySpacing +
  				(ruler.barWidth * barIndex) +
  				(ruler.barSpacing / 2) +
  				(ruler.barSpacing * barIndex);
  		},
  
  		calculateBarY: function(index, datasetIndex) {
  			var meta = this.getMeta();
  			var yScale = this.getScaleForId(meta.yAxisID);
  			var value = this.getDataset().data[index];
  
  			if (yScale.options.stacked) {
  
  				var sumPos = 0,
  					sumNeg = 0;
  
  				for (var i = 0; i < datasetIndex; i++) {
  					var ds = this.chart.data.datasets[i];
  					var dsMeta = this.chart.getDatasetMeta(i);
  					if (dsMeta.bar && dsMeta.yAxisID === yScale.id && this.chart.isDatasetVisible(i)) {
  						if (ds.data[index] < 0) {
  							sumNeg += ds.data[index] || 0;
  						} else {
  							sumPos += ds.data[index] || 0;
  						}
  					}
  				}
  
  				if (value < 0) {
  					return yScale.getPixelForValue(sumNeg + value);
  				} else {
  					return yScale.getPixelForValue(sumPos + value);
  				}
  			}
  
  			return yScale.getPixelForValue(value);
  		},
  
  		draw: function(ease) {
  			var easingDecimal = ease || 1;
  			helpers.each(this.getMeta().data, function(rectangle, index) {
  				var d = this.getDataset().data[index];
  				if (d !== null && d !== undefined && !isNaN(d)) {
  					rectangle.transition(easingDecimal).draw();
  				}
  			}, this);
  		},
  
  		setHoverStyle: function(rectangle) {
  			var dataset = this.chart.data.datasets[rectangle._datasetIndex];
  			var index = rectangle._index;
  
  			var custom = rectangle.custom || {};
  			var model = rectangle._model;
  			model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
  			model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor));
  			model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
  		},
  
  		removeHoverStyle: function(rectangle) {
  			var dataset = this.chart.data.datasets[rectangle._datasetIndex];
  			var index = rectangle._index;
  			var custom = rectangle.custom || {};
  			var model = rectangle._model;
  			var rectangleElementOptions = this.chart.options.elements.rectangle;
  
  			model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor);
  			model.borderColor = custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor);
  			model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth);
  		}
  
  	});
  
  
  	// including horizontalBar in the bar file, instead of a file of its own
  	// it extends bar (like pie extends doughnut)
  	Chart.defaults.horizontalBar = {
  		hover: {
  			mode: "label"
  		},
  
  		scales: {
  			xAxes: [{
  				type: "linear",
  				position: "bottom"
  			}],
  			yAxes: [{
  				position: "left",
  				type: "category",
  
  				// Specific to Horizontal Bar Controller
  				categoryPercentage: 0.8,
  				barPercentage: 0.9,
  
  				// grid line settings
  				gridLines: {
  					offsetGridLines: true
  				}
  			}]
  		},
  		elements: {
  			rectangle: {
  				borderSkipped: 'left'
  			}
  		},
  		tooltips: {
  			callbacks: {
  				title: function(tooltipItems, data) {
  					// Pick first xLabel for now
  					var title = '';
  
  					if (tooltipItems.length > 0) {
  						if (tooltipItems[0].yLabel) {
  							title = tooltipItems[0].yLabel;
  						} else if (data.labels.length > 0 && tooltipItems[0].index < data.labels.length) {
  							title = data.labels[tooltipItems[0].index];
  						}
  					}
  
  					return title;
  				},
  				label: function(tooltipItem, data) {
  					var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || '';
  				return datasetLabel + ': ' + tooltipItem.xLabel;
  				}
  			}
  		}
  	};
  
  	Chart.controllers.horizontalBar = Chart.controllers.bar.extend({
  		updateElement: function updateElement(rectangle, index, reset, numBars) {
  			var meta = this.getMeta();
  			var xScale = this.getScaleForId(meta.xAxisID);
  			var yScale = this.getScaleForId(meta.yAxisID);
  			var scaleBase = xScale.getBasePixel();
  			var custom = rectangle.custom || {};
  			var dataset = this.getDataset();
  			var rectangleElementOptions = this.chart.options.elements.rectangle;
  
  			helpers.extend(rectangle, {
  				// Utility
  				_xScale: xScale,
  				_yScale: yScale,
  				_datasetIndex: this.index,
  				_index: index,
  
  				// Desired view properties
  				_model: {
  					x: reset ? scaleBase : this.calculateBarX(index, this.index),
  					y: this.calculateBarY(index, this.index),
  
  					// Tooltip
  					label: this.chart.data.labels[index],
  					datasetLabel: dataset.label,
  
  					// Appearance
  					base: reset ? scaleBase : this.calculateBarBase(this.index, index),
  					height: this.calculateBarHeight(index),
  					backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor),
  					borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped,
  					borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor),
  					borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth)
  				},
  
  				draw: function () {
  
  					var ctx = this._chart.ctx;
  					var vm = this._view;
  
  					var halfHeight = vm.height / 2,
  						topY = vm.y - halfHeight,
  						bottomY = vm.y + halfHeight,
  						right = vm.base - (vm.base - vm.x),
  						halfStroke = vm.borderWidth / 2;
  
  					// Canvas doesn't allow us to stroke inside the width so we can
  					// adjust the sizes to fit if we're setting a stroke on the line
  					if (vm.borderWidth) {
  						topY += halfStroke;
  						bottomY -= halfStroke;
  						right += halfStroke;
  					}
  
  					ctx.beginPath();
  
  					ctx.fillStyle = vm.backgroundColor;
  					ctx.strokeStyle = vm.borderColor;
  					ctx.lineWidth = vm.borderWidth;
  
  					// Corner points, from bottom-left to bottom-right clockwise
  					// | 1 2 |
  					// | 0 3 |
  					var corners = [
  						[vm.base, bottomY],
  						[vm.base, topY],
  						[right, topY],
  						[right, bottomY]
  					];
  
  					// Find first (starting) corner with fallback to 'bottom'
  					var borders = ['bottom', 'left', 'top', 'right'];
  					var startCorner = borders.indexOf(vm.borderSkipped, 0);
  					if (startCorner === -1)
  						startCorner = 0;
  
  					function cornerAt(index) {
  						return corners[(startCorner + index) % 4];
  					}
  
  					// Draw rectangle from 'startCorner'
  					ctx.moveTo.apply(ctx, cornerAt(0));
  					for (var i = 1; i < 4; i++)
  						ctx.lineTo.apply(ctx, cornerAt(i));
  
  					ctx.fill();
  					if (vm.borderWidth) {
  						ctx.stroke();
  					}
  				},
  
  				inRange: function (mouseX, mouseY) {
  					var vm = this._view;
  					var inRange = false;
  
  					if (vm) {
  						if (vm.x < vm.base) {
  							inRange = (mouseY >= vm.y - vm.height / 2 && mouseY <= vm.y + vm.height / 2) && (mouseX >= vm.x && mouseX <= vm.base);
  						} else {
  							inRange = (mouseY >= vm.y - vm.height / 2 && mouseY <= vm.y + vm.height / 2) && (mouseX >= vm.base && mouseX <= vm.x);
  						}
  					}
  
  					return inRange;
  				}
  			});
  
  			rectangle.pivot();
  		},
  
  		calculateBarBase: function (datasetIndex, index) {
  			var meta = this.getMeta();
  			var xScale = this.getScaleForId(meta.xAxisID);
  			var base = 0;
  
  			if (xScale.options.stacked) {
  
  				var value = this.chart.data.datasets[datasetIndex].data[index];
  
  				if (value < 0) {
  					for (var i = 0; i < datasetIndex; i++) {
  						var negDS = this.chart.data.datasets[i];
  						var negDSMeta = this.chart.getDatasetMeta(i);
  						if (negDSMeta.bar && negDSMeta.xAxisID === xScale.id && this.chart.isDatasetVisible(i)) {
  							base += negDS.data[index] < 0 ? negDS.data[index] : 0;
  						}
  					}
  				} else {
  					for (var j = 0; j < datasetIndex; j++) {
  						var posDS = this.chart.data.datasets[j];
  						var posDSMeta = this.chart.getDatasetMeta(j);
  						if (posDSMeta.bar && posDSMeta.xAxisID === xScale.id && this.chart.isDatasetVisible(j)) {
  							base += posDS.data[index] > 0 ? posDS.data[index] : 0;
  						}
  					}
  				}
  
  				return xScale.getPixelForValue(base);
  			}
  
  			return xScale.getBasePixel();
  		},
  
  		getRuler: function (index) {
  			var meta = this.getMeta();
  			var yScale = this.getScaleForId(meta.yAxisID);
  			var datasetCount = this.getBarCount();
  
  			var tickHeight;
  			if (yScale.options.type === 'category') {
  				tickHeight = yScale.getPixelForTick(index + 1) - yScale.getPixelForTick(index);
  			} else {
  				// Average width
  				tickHeight = yScale.width / yScale.ticks.length;
  			}
  			var categoryHeight = tickHeight * yScale.options.categoryPercentage;
  			var categorySpacing = (tickHeight - (tickHeight * yScale.options.categoryPercentage)) / 2;
  			var fullBarHeight = categoryHeight / datasetCount;
  
  			if (yScale.ticks.length !== this.chart.data.labels.length) {
  				var perc = yScale.ticks.length / this.chart.data.labels.length;
  				fullBarHeight = fullBarHeight * perc;
  			}
  
  			var barHeight = fullBarHeight * yScale.options.barPercentage;
  			var barSpacing = fullBarHeight - (fullBarHeight * yScale.options.barPercentage);
  
  			return {
  				datasetCount: datasetCount,
  				tickHeight: tickHeight,
  				categoryHeight: categoryHeight,
  				categorySpacing: categorySpacing,
  				fullBarHeight: fullBarHeight,
  				barHeight: barHeight,
  				barSpacing: barSpacing,
  			};
  		},
  
  		calculateBarHeight: function (index) {
  			var yScale = this.getScaleForId(this.getMeta().yAxisID);
  			var ruler = this.getRuler(index);
  			return yScale.options.stacked ? ruler.categoryHeight : ruler.barHeight;
  		},
  
  		calculateBarX: function (index, datasetIndex) {
  			var meta = this.getMeta();
  			var xScale = this.getScaleForId(meta.xAxisID);
  			var value = this.getDataset().data[index];
  
  			if (xScale.options.stacked) {
  
  				var sumPos = 0,
  					sumNeg = 0;
  
  				for (var i = 0; i < datasetIndex; i++) {
  					var ds = this.chart.data.datasets[i];
  					var dsMeta = this.chart.getDatasetMeta(i);
  					if (dsMeta.bar && dsMeta.xAxisID === xScale.id && this.chart.isDatasetVisible(i)) {
  						if (ds.data[index] < 0) {
  							sumNeg += ds.data[index] || 0;
  						} else {
  							sumPos += ds.data[index] || 0;
  						}
  					}
  				}
  
  				if (value < 0) {
  					return xScale.getPixelForValue(sumNeg + value);
  				} else {
  					return xScale.getPixelForValue(sumPos + value);
  				}
  			}
  
  			return xScale.getPixelForValue(value);
  		},
  
  		calculateBarY: function (index, datasetIndex) {
  			var meta = this.getMeta();
  			var yScale = this.getScaleForId(meta.yAxisID);
  			var barIndex = this.getBarIndex(datasetIndex);
  
  			var ruler = this.getRuler(index);
  			var topTick = yScale.getPixelForValue(null, index, datasetIndex, this.chart.isCombo);
  			topTick -= this.chart.isCombo ? (ruler.tickHeight / 2) : 0;
  
  			if (yScale.options.stacked) {
  				return topTick + (ruler.categoryHeight / 2) + ruler.categorySpacing;
  			}
  
  			return topTick +
  				(ruler.barHeight / 2) +
  				ruler.categorySpacing +
  				(ruler.barHeight * barIndex) +
  				(ruler.barSpacing / 2) +
  				(ruler.barSpacing * barIndex);
  		}
  	});
  };
  
  },{}],16:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers;
  
  	Chart.defaults.bubble = {
  		hover: {
  			mode: "single"
  		},
  
  		scales: {
  			xAxes: [{
  				type: "linear", // bubble should probably use a linear scale by default
  				position: "bottom",
  				id: "x-axis-0" // need an ID so datasets can reference the scale
  			}],
  			yAxes: [{
  				type: "linear",
  				position: "left",
  				id: "y-axis-0"
  			}]
  		},
  
  		tooltips: {
  			callbacks: {
  				title: function(tooltipItems, data) {
  					// Title doesn't make sense for scatter since we format the data as a point
  					return '';
  				},
  				label: function(tooltipItem, data) {
  					var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || '';
  					var dataPoint = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
  					return datasetLabel + ': (' + dataPoint.x + ', ' + dataPoint.y + ', ' + dataPoint.r + ')';
  				}
  			}
  		}
  	};
  
  	Chart.controllers.bubble = Chart.DatasetController.extend({
  
  		dataElementType: Chart.elements.Point,
  
  		update: function update(reset) {
  			var meta = this.getMeta();
  			var points = meta.data;
  
  			// Update Points
  			helpers.each(points, function(point, index) {
  				this.updateElement(point, index, reset);
  			}, this);
  		},
  
  		updateElement: function(point, index, reset) {
  			var meta = this.getMeta();
  			var xScale = this.getScaleForId(meta.xAxisID);
  			var yScale = this.getScaleForId(meta.yAxisID);
  
  			var custom = point.custom || {};
  			var dataset = this.getDataset();
  			var data = dataset.data[index];
  			var pointElementOptions = this.chart.options.elements.point;
  
  			helpers.extend(point, {
  				// Utility
  				_xScale: xScale,
  				_yScale: yScale,
  				_datasetIndex: this.index,
  				_index: index,
  
  				// Desired view properties
  				_model: {
  					x: reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(data, index, this.index, this.chart.isCombo),
  					y: reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, this.index),
  					// Appearance
  					radius: reset ? 0 : custom.radius ? custom.radius : this.getRadius(data),
  					backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, pointElementOptions.backgroundColor),
  					borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, pointElementOptions.borderColor),
  					borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, pointElementOptions.borderWidth),
  
  					// Tooltip
  					hitRadius: custom.hitRadius ? custom.hitRadius : helpers.getValueAtIndexOrDefault(dataset.hitRadius, index, pointElementOptions.hitRadius)
  				}
  			});
  
  			var model = point._model;
  			model.skip = custom.skip ? custom.skip : (isNaN(model.x) || isNaN(model.y));
  
  			point.pivot();
  		},
  
  		getRadius: function(value) {
  			return value.r || this.chart.options.elements.point.radius;
  		},
  
  		setHoverStyle: function(point) {
  			// Point
  			var dataset = this.chart.data.datasets[point._datasetIndex];
  			var index = point._index;
  			var custom = point.custom || {};
  			var model = point._model;
  
  			model.radius = custom.hoverRadius ? custom.hoverRadius : (helpers.getValueAtIndexOrDefault(dataset.hoverRadius, index, this.chart.options.elements.point.hoverRadius)) + this.getRadius(this.getDataset().data[point._index]);
  			model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
  			model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor));
  			model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
  		},
  
  		removeHoverStyle: function(point) {
  			var dataset = this.chart.data.datasets[point._datasetIndex];
  			var index = point._index;
  			var custom = point.custom || {};
  			var model = point._model;
  			var pointElementOptions = this.chart.options.elements.point;
  
  			model.radius = custom.radius ? custom.radius : this.getRadius(dataset.data[point._index]);
  			model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, pointElementOptions.backgroundColor);
  			model.borderColor = custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, pointElementOptions.borderColor);
  			model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, pointElementOptions.borderWidth);
  		}
  	});
  };
  
  },{}],17:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers,
  		defaults = Chart.defaults;
  
  	defaults.doughnut = {
  		animation: {
  			//Boolean - Whether we animate the rotation of the Doughnut
  			animateRotate: true,
  			//Boolean - Whether we animate scaling the Doughnut from the centre
  			animateScale: false
  		},
  		aspectRatio: 1,
  		hover: {
  			mode: 'single'
  		},
  		legendCallback: function(chart) {
  			var text = [];
  			text.push('<ul class="' + chart.id + '-legend">');
  
  			var data = chart.data;
  			var datasets = data.datasets;
  			var labels = data.labels;
  
  			if (datasets.length) {
  				for (var i = 0; i < datasets[0].data.length; ++i) {
  					text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
  					if (labels[i]) {
  						text.push(labels[i]);
  					}
  					text.push('</li>');
  				}
  			}
  
  			text.push('</ul>');
  			return text.join("");
  		},
  		legend: {
  			labels: {
  				generateLabels: function(chart) {
  					var data = chart.data;
  					if (data.labels.length && data.datasets.length) {
  						return data.labels.map(function(label, i) {
  							var meta = chart.getDatasetMeta(0);
  							var ds = data.datasets[0];
  							var arc = meta.data[i];
  							var custom = arc.custom || {};
  							var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;
  							var arcOpts = chart.options.elements.arc;
  							var fill = custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
  							var stroke = custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
  							var bw = custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
  
  							return {
  								text: label,
  								fillStyle: fill,
  								strokeStyle: stroke,
  								lineWidth: bw,
  								hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
  
  								// Extra data used for toggling the correct item
  								index: i
  							};
  						});
  					} else {
  						return [];
  					}
  				}
  			},
  
  			onClick: function(e, legendItem) {
  				var index = legendItem.index;
  				var chart = this.chart;
  				var i, ilen, meta;
  
  				for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
  					meta = chart.getDatasetMeta(i);
  					meta.data[index].hidden = !meta.data[index].hidden;
  				}
  
  				chart.update();
  			}
  		},
  
  		//The percentage of the chart that we cut out of the middle.
  		cutoutPercentage: 50,
  
  		//The rotation of the chart, where the first data arc begins.
  		rotation: Math.PI * -0.5,
  
  		//The total circumference of the chart.
  		circumference: Math.PI * 2.0,
  
  		// Need to override these to give a nice default
  		tooltips: {
  			callbacks: {
  				title: function() {
  					return '';
  				},
  				label: function(tooltipItem, data) {
  					return data.labels[tooltipItem.index] + ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
  				}
  			}
  		}
  	};
  
  	defaults.pie = helpers.clone(defaults.doughnut);
  	helpers.extend(defaults.pie, {
  		cutoutPercentage: 0
  	});
  
  
  	Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({
  
  		dataElementType: Chart.elements.Arc,
  
  		linkScales: helpers.noop,
  
  		// Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
  		getRingIndex: function getRingIndex(datasetIndex) {
  			var ringIndex = 0;
  
  			for (var j = 0; j < datasetIndex; ++j) {
  				if (this.chart.isDatasetVisible(j)) {
  					++ringIndex;
  				}
  			}
  
  			return ringIndex;
  		},
  
  		update: function update(reset) {
  			var _this = this;
  			var chart = _this.chart,
  				chartArea = chart.chartArea,
  				opts = chart.options,
  				arcOpts = opts.elements.arc,
  				availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth,
  				availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth,
  				minSize = Math.min(availableWidth, availableHeight),
  				offset = {
  					x: 0,
  					y: 0
  				},
  				meta = _this.getMeta(),
  				cutoutPercentage = opts.cutoutPercentage,
  				circumference = opts.circumference;
  
  			// If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc
  			if (circumference < Math.PI * 2.0) {
  				var startAngle = opts.rotation % (Math.PI * 2.0);
  				startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0);
  				var endAngle = startAngle + circumference;
  				var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)};
  				var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)};
  				var contains0 = (startAngle <= 0 && 0 <= endAngle) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle);
  				var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle);
  				var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle);
  				var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle);
  				var cutout = cutoutPercentage / 100.0;
  				var min = {x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout))};
  				var max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))};
  				var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5};
  				minSize = Math.min(availableWidth / size.width, availableHeight / size.height);
  				offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5};
  			}
  
  			chart.outerRadius = Math.max(minSize / 2, 0);
  			chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 1, 0);
  			chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
  			chart.offsetX = offset.x * chart.outerRadius;
  			chart.offsetY = offset.y * chart.outerRadius;
  
  			meta.total = _this.calculateTotal();
  
  			_this.outerRadius = chart.outerRadius - (chart.radiusLength * _this.getRingIndex(_this.index));
  			_this.innerRadius = _this.outerRadius - chart.radiusLength;
  
  			helpers.each(meta.data, function(arc, index) {
  				_this.updateElement(arc, index, reset);
  			});
  		},
  
  		updateElement: function(arc, index, reset) {
  			var _this = this;
  			var chart = _this.chart,
  				chartArea = chart.chartArea,
  				opts = chart.options,
  				animationOpts = opts.animation,
  				arcOpts = opts.elements.arc,
  				centerX = (chartArea.left + chartArea.right) / 2,
  				centerY = (chartArea.top + chartArea.bottom) / 2,
  				startAngle = opts.rotation, // non reset case handled later
  				endAngle = opts.rotation, // non reset case handled later
  				dataset = _this.getDataset(),
  				circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : _this.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI)),
  				innerRadius = reset && animationOpts.animateScale ? 0 : _this.innerRadius,
  				outerRadius = reset && animationOpts.animateScale ? 0 : _this.outerRadius,
  				custom = arc.custom || {},
  				valueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;
  
  			helpers.extend(arc, {
  				// Utility
  				_datasetIndex: _this.index,
  				_index: index,
  
  				// Desired view properties
  				_model: {
  					x: centerX + chart.offsetX,
  					y: centerY + chart.offsetY,
  					startAngle: startAngle,
  					endAngle: endAngle,
  					circumference: circumference,
  					outerRadius: outerRadius,
  					innerRadius: innerRadius,
  					label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])
  				}
  			});
  
  			var model = arc._model;
  			model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(dataset.backgroundColor, index, arcOpts.backgroundColor);
  			model.hoverBackgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueAtIndexOrDefault(dataset.hoverBackgroundColor, index, arcOpts.hoverBackgroundColor);
  			model.borderWidth = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(dataset.borderWidth, index, arcOpts.borderWidth);
  			model.borderColor = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(dataset.borderColor, index, arcOpts.borderColor);
  
  			// Set correct angles if not resetting
  			if (!reset || !animationOpts.animateRotate) {
  				if (index === 0) {
  					model.startAngle = opts.rotation;
  				} else {
  					model.startAngle = _this.getMeta().data[index - 1]._model.endAngle;
  				}
  
  				model.endAngle = model.startAngle + model.circumference;
  			}
  
  			arc.pivot();
  		},
  
  		removeHoverStyle: function(arc) {
  			Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
  		},
  
  		calculateTotal: function() {
  			var dataset = this.getDataset();
  			var meta = this.getMeta();
  			var total = 0;
  			var value;
  
  			helpers.each(meta.data, function(element, index) {
  				value = dataset.data[index];
  				if (!isNaN(value) && !element.hidden) {
  					total += Math.abs(value);
  				}
  			});
  
  			return total;
  		},
  
  		calculateCircumference: function(value) {
  			var total = this.getMeta().total;
  			if (total > 0 && !isNaN(value)) {
  				return (Math.PI * 2.0) * (value / total);
  			} else {
  				return 0;
  			}
  		}
  	});
  };
  
  },{}],18:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers;
  
  	Chart.defaults.line = {
  		showLines: true,
  
  		hover: {
  			mode: "label"
  		},
  
  		scales: {
  			xAxes: [{
  				type: "category",
  				id: 'x-axis-0'
  			}],
  			yAxes: [{
  				type: "linear",
  				id: 'y-axis-0'
  			}]
  		}
  	};
  
  	Chart.controllers.line = Chart.DatasetController.extend({
  
  		datasetElementType: Chart.elements.Line,
  
  		dataElementType: Chart.elements.Point,
  
  		addElementAndReset: function(index) {
  			var me = this;
  			var options = me.chart.options;
  
  			Chart.DatasetController.prototype.addElementAndReset.call(me, index);
  
  			// Make sure bezier control points are updated
  			if (options.showLines && options.elements.line.tension !== 0) {
  				me.updateBezierControlPoints();
  			}
  		},
  
  		update: function update(reset) {
  			var me = this;
  			var meta = me.getMeta();
  			var line = meta.dataset;
  			var points = meta.data || [];
  			var options = me.chart.options;
  			var lineElementOptions = options.elements.line;
  			var scale = me.getScaleForId(meta.yAxisID);
  			var i, ilen, dataset, custom;
  
  			// Update Line
  			if (options.showLines) {
  				dataset = me.getDataset();
  				custom = line.custom || {};
  
  				// Compatibility: If the properties are defined with only the old name, use those values
  				if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
  					dataset.lineTension = dataset.tension;
  				}
  
  				// Utility
  				line._scale = scale;
  				line._datasetIndex = me.index;
  				// Data
  				line._children = points;
  				// Model
  				line._model = {
  					// Appearance
  					tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, lineElementOptions.tension),
  					backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
  					borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
  					borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
  					borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
  					borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
  					borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
  					borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
  					fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
  					// Scale
  					scaleTop: scale.top,
  					scaleBottom: scale.bottom,
  					scaleZero: scale.getBasePixel()
  				};
  
  				line.pivot();
  			}
  
  			// Update Points
  			for (i=0, ilen=points.length; i<ilen; ++i) {
  				me.updateElement(points[i], i, reset);
  			}
  
  			if (options.showLines && lineElementOptions.tension !== 0) {
  				me.updateBezierControlPoints();
  			}
  		},
  
  		getPointBackgroundColor: function(point, index) {
  			var backgroundColor = this.chart.options.elements.point.backgroundColor;
  			var dataset = this.getDataset();
  			var custom = point.custom || {};
  
  			if (custom.backgroundColor) {
  				backgroundColor = custom.backgroundColor;
  			} else if (dataset.pointBackgroundColor) {
  				backgroundColor = helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor);
  			} else if (dataset.backgroundColor) {
  				backgroundColor = dataset.backgroundColor;
  			}
  
  			return backgroundColor;
  		},
  
  		getPointBorderColor: function(point, index) {
  			var borderColor = this.chart.options.elements.point.borderColor;
  			var dataset = this.getDataset();
  			var custom = point.custom || {};
  
  			if (custom.borderColor) {
  				borderColor = custom.borderColor;
  			} else if (dataset.pointBorderColor) {
  				borderColor = helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor);
  			} else if (dataset.borderColor) {
  				borderColor = dataset.borderColor;
  			}
  
  			return borderColor;
  		},
  
  		getPointBorderWidth: function(point, index) {
  			var borderWidth = this.chart.options.elements.point.borderWidth;
  			var dataset = this.getDataset();
  			var custom = point.custom || {};
  
  			if (custom.borderWidth) {
  				borderWidth = custom.borderWidth;
  			} else if (dataset.pointBorderWidth) {
  				borderWidth = helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth);
  			} else if (dataset.borderWidth) {
  				borderWidth = dataset.borderWidth;
  			}
  
  			return borderWidth;
  		},
  
  		updateElement: function(point, index, reset) {
  			var me = this;
  			var meta = me.getMeta();
  			var custom = point.custom || {};
  			var dataset = me.getDataset();
  			var datasetIndex = me.index;
  			var value = dataset.data[index];
  			var yScale = me.getScaleForId(meta.yAxisID);
  			var xScale = me.getScaleForId(meta.xAxisID);
  			var pointOptions = me.chart.options.elements.point;
  			var x, y;
  
  			// Compatibility: If the properties are defined with only the old name, use those values
  			if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
  				dataset.pointRadius = dataset.radius;
  			}
  			if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
  				dataset.pointHitRadius = dataset.hitRadius;
  			}
  
  			x = xScale.getPixelForValue(value, index, datasetIndex, me.chart.isCombo);
  			y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex, me.chart.isCombo);
  
  			// Utility
  			point._xScale = xScale;
  			point._yScale = yScale;
  			point._datasetIndex = datasetIndex;
  			point._index = index;
  
  			// Desired view properties
  			point._model = {
  				x: x,
  				y: y,
  				skip: custom.skip || isNaN(x) || isNaN(y),
  				// Appearance
  				radius: custom.radius || helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius),
  				pointStyle: custom.pointStyle || helpers.getValueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle),
  				backgroundColor: me.getPointBackgroundColor(point, index),
  				borderColor: me.getPointBorderColor(point, index),
  				borderWidth: me.getPointBorderWidth(point, index),
  				tension: meta.dataset._model ? meta.dataset._model.tension : 0,
  				// Tooltip
  				hitRadius: custom.hitRadius || helpers.getValueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius)
  			};
  		},
  
  		calculatePointY: function(value, index, datasetIndex, isCombo) {
  			var me = this;
  			var chart = me.chart;
  			var meta = me.getMeta();
  			var yScale = me.getScaleForId(meta.yAxisID);
  			var sumPos = 0;
  			var sumNeg = 0;
  			var i, ds, dsMeta;
  
  			if (yScale.options.stacked) {
  				for (i = 0; i < datasetIndex; i++) {
  					ds = chart.data.datasets[i];
  					dsMeta = chart.getDatasetMeta(i);
  					if (dsMeta.type === 'line' && chart.isDatasetVisible(i)) {
  						if (ds.data[index] < 0) {
  							sumNeg += ds.data[index] || 0;
  						} else {
  							sumPos += ds.data[index] || 0;
  						}
  					}
  				}
  
  				if (value < 0) {
  					return yScale.getPixelForValue(sumNeg + value);
  				} else {
  					return yScale.getPixelForValue(sumPos + value);
  				}
  			}
  
  			return yScale.getPixelForValue(value);
  		},
  
  		updateBezierControlPoints: function() {
  			var meta = this.getMeta();
  			var area = this.chart.chartArea;
  			var points = meta.data || [];
  			var i, ilen, point, model, controlPoints;
  
  			for (i=0, ilen=points.length; i<ilen; ++i) {
  				point = points[i];
  				model = point._model;
  				controlPoints = helpers.splineCurve(
  					helpers.previousItem(points, i)._model,
  					model,
  					helpers.nextItem(points, i)._model,
  					meta.dataset._model.tension
  				);
  
  				// Prevent the bezier going outside of the bounds of the graph
  				model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, area.right), area.left);
  				model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, area.bottom), area.top);
  				model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, area.right), area.left);
  				model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, area.bottom), area.top);
  
  				// Now pivot the point for animation
  				point.pivot();
  			}
  		},
  
  		draw: function(ease) {
  			var meta = this.getMeta();
  			var points = meta.data || [];
  			var easingDecimal = ease || 1;
  			var i, ilen;
  
  			// Transition Point Locations
  			for (i=0, ilen=points.length; i<ilen; ++i) {
  				points[i].transition(easingDecimal);
  			}
  
  			// Transition and Draw the line
  			if (this.chart.options.showLines) {
  				meta.dataset.transition(easingDecimal).draw();
  			}
  
  			// Draw the points
  			for (i=0, ilen=points.length; i<ilen; ++i) {
  				points[i].draw();
  			}
  		},
  
  		setHoverStyle: function(point) {
  			// Point
  			var dataset = this.chart.data.datasets[point._datasetIndex];
  			var index = point._index;
  			var custom = point.custom || {};
  			var model = point._model;
  
  			model.radius = custom.hoverRadius || helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
  			model.backgroundColor = custom.hoverBackgroundColor || helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
  			model.borderColor = custom.hoverBorderColor || helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
  			model.borderWidth = custom.hoverBorderWidth || helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
  		},
  
  		removeHoverStyle: function(point) {
  			var me = this;
  			var dataset = me.chart.data.datasets[point._datasetIndex];
  			var index = point._index;
  			var custom = point.custom || {};
  			var model = point._model;
  
  			// Compatibility: If the properties are defined with only the old name, use those values
  			if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
  				dataset.pointRadius = dataset.radius;
  			}
  
  			model.radius = custom.radius || helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, me.chart.options.elements.point.radius);
  			model.backgroundColor = me.getPointBackgroundColor(point, index);
  			model.borderColor = me.getPointBorderColor(point, index);
  			model.borderWidth = me.getPointBorderWidth(point, index);
  		}
  	});
  };
  
  },{}],19:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers;
  
  	Chart.defaults.polarArea = {
  
  		scale: {
  			type: "radialLinear",
  			lineArc: true // so that lines are circular
  		},
  
  		//Boolean - Whether to animate the rotation of the chart
  		animation: {
  			animateRotate: true,
  			animateScale: true
  		},
  
  		aspectRatio: 1,
  		legendCallback: function(chart) {
  			var text = [];
  			text.push('<ul class="' + chart.id + '-legend">');
  
  			var data = chart.data;
  			var datasets = data.datasets;
  			var labels = data.labels;
  
  			if (datasets.length) {
  				for (var i = 0; i < datasets[0].data.length; ++i) {
  					text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '">');
  					if (labels[i]) {
  						text.push(labels[i]);
  					}
  					text.push('</span></li>');
  				}
  			}
  
  			text.push('</ul>');
  			return text.join("");
  		},
  		legend: {
  			labels: {
  				generateLabels: function(chart) {
  					var data = chart.data;
  					if (data.labels.length && data.datasets.length) {
  						return data.labels.map(function(label, i) {
  							var meta = chart.getDatasetMeta(0);
  							var ds = data.datasets[0];
  							var arc = meta.data[i];
  							var custom = arc.custom || {};
  							var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;
  							var arcOpts = chart.options.elements.arc;
  							var fill = custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
  							var stroke = custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
  							var bw = custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
  
  							return {
  								text: label,
  								fillStyle: fill,
  								strokeStyle: stroke,
  								lineWidth: bw,
  								hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
  
  								// Extra data used for toggling the correct item
  								index: i
  							};
  						});
  					} else {
  						return [];
  					}
  				}
  			},
  
  			onClick: function(e, legendItem) {
  				var index = legendItem.index;
  				var chart = this.chart;
  				var i, ilen, meta;
  
  				for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
  					meta = chart.getDatasetMeta(i);
  					meta.data[index].hidden = !meta.data[index].hidden;
  				}
  
  				chart.update();
  			}
  		},
  
  		// Need to override these to give a nice default
  		tooltips: {
  			callbacks: {
  				title: function() {
  					return '';
  				},
  				label: function(tooltipItem, data) {
  					return data.labels[tooltipItem.index] + ': ' + tooltipItem.yLabel;
  				}
  			}
  		}
  	};
  
  	Chart.controllers.polarArea = Chart.DatasetController.extend({
  
  		dataElementType: Chart.elements.Arc,
  
  		linkScales: helpers.noop,
  
  		update: function update(reset) {
  			var _this = this;
  			var chart = _this.chart;
  			var chartArea = chart.chartArea;
  			var meta = this.getMeta();
  			var opts = chart.options;
  			var arcOpts = opts.elements.arc;
  			var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
  			chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0);
  			chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
  			chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
  
  			_this.outerRadius = chart.outerRadius - (chart.radiusLength * _this.index);
  			_this.innerRadius = _this.outerRadius - chart.radiusLength;
  
  			meta.count = _this.countVisibleElements();
  
  			helpers.each(meta.data, function(arc, index) {
  				_this.updateElement(arc, index, reset);
  			});
  		},
  
  		updateElement: function(arc, index, reset) {
  			var _this = this;
  			var chart = _this.chart;
  			var chartArea = chart.chartArea;
  			var dataset = _this.getDataset();
  			var opts = chart.options;
  			var animationOpts = opts.animation;
  			var arcOpts = opts.elements.arc;
  			var custom = arc.custom || {};
  			var scale = chart.scale;
  			var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;
  			var labels = chart.data.labels;
  
  			var circumference = _this.calculateCircumference(dataset.data[index]);
  			var centerX = (chartArea.left + chartArea.right) / 2;
  			var centerY = (chartArea.top + chartArea.bottom) / 2;
  
  			// If there is NaN data before us, we need to calculate the starting angle correctly.
  			// We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data
  			var visibleCount = 0;
  			var meta = _this.getMeta();
  			for (var i = 0; i < index; ++i) {
  				if (!isNaN(dataset.data[i]) && !meta.data[i].hidden) {
  					++visibleCount;
  				}
  			}
  
  			var distance = arc.hidden? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
  			var startAngle = (-0.5 * Math.PI) + (circumference * visibleCount);
  			var endAngle = startAngle + (arc.hidden? 0 : circumference);
  
  			var resetModel = {
  				x: centerX,
  				y: centerY,
  				innerRadius: 0,
  				outerRadius: animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]),
  				startAngle: animationOpts.animateRotate ? Math.PI * -0.5 : startAngle,
  				endAngle: animationOpts.animateRotate ? Math.PI * -0.5 : endAngle,
  
  				backgroundColor: custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(dataset.backgroundColor, index, arcOpts.backgroundColor),
  				borderWidth: custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(dataset.borderWidth, index, arcOpts.borderWidth),
  				borderColor: custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(dataset.borderColor, index, arcOpts.borderColor),
  
  				label: getValueAtIndexOrDefault(labels, index, labels[index])
  			};
  
  			helpers.extend(arc, {
  				// Utility
  				_datasetIndex: _this.index,
  				_index: index,
  				_scale: scale,
  
  				// Desired view properties
  				_model: reset ? resetModel : {
  					x: centerX,
  					y: centerY,
  					innerRadius: 0,
  					outerRadius: distance,
  					startAngle: startAngle,
  					endAngle: endAngle,
  
  					backgroundColor: custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(dataset.backgroundColor, index, arcOpts.backgroundColor),
  					borderWidth: custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(dataset.borderWidth, index, arcOpts.borderWidth),
  					borderColor: custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(dataset.borderColor, index, arcOpts.borderColor),
  
  					label: getValueAtIndexOrDefault(labels, index, labels[index])
  				}
  			});
  
  			arc.pivot();
  		},
  
  		removeHoverStyle: function(arc) {
  			Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
  		},
  
  		countVisibleElements: function() {
  			var dataset = this.getDataset();
  			var meta = this.getMeta();
  			var count = 0;
  
  			helpers.each(meta.data, function(element, index) {
  				if (!isNaN(dataset.data[index]) && !element.hidden) {
  					count++;
  				}
  			});
  
  			return count;
  		},
  
  		calculateCircumference: function(value) {
  			var count = this.getMeta().count;
  			if (count > 0 && !isNaN(value)) {
  				return (2 * Math.PI) / count;
  			} else {
  				return 0;
  			}
  		}
  	});
  };
  
  },{}],20:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers;
  
  	Chart.defaults.radar = {
  		scale: {
  			type: "radialLinear"
  		},
  		elements: {
  			line: {
  				tension: 0 // no bezier in radar
  			}
  		}
  	};
  
  	Chart.controllers.radar = Chart.DatasetController.extend({
  
  		datasetElementType: Chart.elements.Line,
  
  		dataElementType: Chart.elements.Point,
  
  		linkScales: helpers.noop,
  
  		addElementAndReset: function(index) {
  			Chart.DatasetController.prototype.addElementAndReset.call(this, index);
  
  			// Make sure bezier control points are updated
  			this.updateBezierControlPoints();
  		},
  
  		update: function update(reset) {
  			var meta = this.getMeta();
  			var line = meta.dataset;
  			var points = meta.data;
  			var custom = line.custom || {};
  			var dataset = this.getDataset();
  			var lineElementOptions = this.chart.options.elements.line;
  			var scale = this.chart.scale;
  
  			// Compatibility: If the properties are defined with only the old name, use those values
  			if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
  				dataset.lineTension = dataset.tension;
  			}
  
  			helpers.extend(meta.dataset, {
  				// Utility
  				_datasetIndex: this.index,
  				// Data
  				_children: points,
  				_loop: true,
  				// Model
  				_model: {
  					// Appearance
  					tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, lineElementOptions.tension),
  					backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
  					borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
  					borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
  					fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
  					borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
  					borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
  					borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
  					borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
  
  					// Scale
  					scaleTop: scale.top,
  					scaleBottom: scale.bottom,
  					scaleZero: scale.getBasePosition()
  				}
  			});
  
  			meta.dataset.pivot();
  
  			// Update Points
  			helpers.each(points, function(point, index) {
  				this.updateElement(point, index, reset);
  			}, this);
  
  
  			// Update bezier control points
  			this.updateBezierControlPoints();
  		},
  		updateElement: function(point, index, reset) {
  			var custom = point.custom || {};
  			var dataset = this.getDataset();
  			var scale = this.chart.scale;
  			var pointElementOptions = this.chart.options.elements.point;
  			var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]);
  
  			helpers.extend(point, {
  				// Utility
  				_datasetIndex: this.index,
  				_index: index,
  				_scale: scale,
  
  				// Desired view properties
  				_model: {
  					x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales
  					y: reset ? scale.yCenter : pointPosition.y,
  
  					// Appearance
  					tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.tension, this.chart.options.elements.line.tension),
  					radius: custom.radius ? custom.radius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius),
  					backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor),
  					borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor),
  					borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth),
  					pointStyle: custom.pointStyle ? custom.pointStyle : helpers.getValueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle),
  
  					// Tooltip
  					hitRadius: custom.hitRadius ? custom.hitRadius : helpers.getValueAtIndexOrDefault(dataset.hitRadius, index, pointElementOptions.hitRadius)
  				}
  			});
  
  			point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
  		},
  		updateBezierControlPoints: function() {
  			var chartArea = this.chart.chartArea;
  			var meta = this.getMeta();
  
  			helpers.each(meta.data, function(point, index) {
  				var model = point._model;
  				var controlPoints = helpers.splineCurve(
  					helpers.previousItem(meta.data, index, true)._model,
  					model,
  					helpers.nextItem(meta.data, index, true)._model,
  					model.tension
  				);
  
  				// Prevent the bezier going outside of the bounds of the graph
  				model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left);
  				model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top);
  
  				model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left);
  				model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top);
  
  				// Now pivot the point for animation
  				point.pivot();
  			}, this);
  		},
  
  		draw: function(ease) {
  			var meta = this.getMeta();
  			var easingDecimal = ease || 1;
  
  			// Transition Point Locations
  			helpers.each(meta.data, function(point, index) {
  				point.transition(easingDecimal);
  			});
  
  			// Transition and Draw the line
  			meta.dataset.transition(easingDecimal).draw();
  
  			// Draw the points
  			helpers.each(meta.data, function(point) {
  				point.draw();
  			});
  		},
  
  		setHoverStyle: function(point) {
  			// Point
  			var dataset = this.chart.data.datasets[point._datasetIndex];
  			var custom = point.custom || {};
  			var index = point._index;
  			var model = point._model;
  
  			model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
  			model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
  			model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
  			model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
  		},
  
  		removeHoverStyle: function(point) {
  			var dataset = this.chart.data.datasets[point._datasetIndex];
  			var custom = point.custom || {};
  			var index = point._index;
  			var model = point._model;
  			var pointElementOptions = this.chart.options.elements.point;
  
  			model.radius = custom.radius ? custom.radius : helpers.getValueAtIndexOrDefault(dataset.radius, index, pointElementOptions.radius);
  			model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor);
  			model.borderColor = custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor);
  			model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth);
  		}
  	});
  };
  
  },{}],21:[function(require,module,exports){
  /*global window: false */
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers;
  
  	Chart.defaults.global.animation = {
  		duration: 1000,
  		easing: "easeOutQuart",
  		onProgress: helpers.noop,
  		onComplete: helpers.noop
  	};
  
  	Chart.Animation = Chart.Element.extend({
  		currentStep: null, // the current animation step
  		numSteps: 60, // default number of steps
  		easing: "", // the easing to use for this animation
  		render: null, // render function used by the animation service
  
  		onAnimationProgress: null, // user specified callback to fire on each step of the animation
  		onAnimationComplete: null // user specified callback to fire when the animation finishes
  	});
  
  	Chart.animationService = {
  		frameDuration: 17,
  		animations: [],
  		dropFrames: 0,
  		request: null,
  		addAnimation: function(chartInstance, animationObject, duration, lazy) {
  
  			if (!lazy) {
  				chartInstance.animating = true;
  			}
  
  			for (var index = 0; index < this.animations.length; ++index) {
  				if (this.animations[index].chartInstance === chartInstance) {
  					// replacing an in progress animation
  					this.animations[index].animationObject = animationObject;
  					return;
  				}
  			}
  
  			this.animations.push({
  				chartInstance: chartInstance,
  				animationObject: animationObject
  			});
  
  			// If there are no animations queued, manually kickstart a digest, for lack of a better word
  			if (this.animations.length === 1) {
  				this.requestAnimationFrame();
  			}
  		},
  		// Cancel the animation for a given chart instance
  		cancelAnimation: function(chartInstance) {
  			var index = helpers.findIndex(this.animations, function(animationWrapper) {
  				return animationWrapper.chartInstance === chartInstance;
  			});
  
  			if (index !== -1) {
  				this.animations.splice(index, 1);
  				chartInstance.animating = false;
  			}
  		},
  		requestAnimationFrame: function() {
  			var me = this;
  			if (me.request === null) {
  				// Skip animation frame requests until the active one is executed.
  				// This can happen when processing mouse events, e.g. 'mousemove'
  				// and 'mouseout' events will trigger multiple renders.
  				me.request = helpers.requestAnimFrame.call(window, function() {
  					me.request = null;
  					me.startDigest();
  				});
  			}
  		},
  		startDigest: function() {
  
  			var startTime = Date.now();
  			var framesToDrop = 0;
  
  			if (this.dropFrames > 1) {
  				framesToDrop = Math.floor(this.dropFrames);
  				this.dropFrames = this.dropFrames % 1;
  			}
  
  			var i = 0;
  			while (i < this.animations.length) {
  				if (this.animations[i].animationObject.currentStep === null) {
  					this.animations[i].animationObject.currentStep = 0;
  				}
  
  				this.animations[i].animationObject.currentStep += 1 + framesToDrop;
  
  				if (this.animations[i].animationObject.currentStep > this.animations[i].animationObject.numSteps) {
  					this.animations[i].animationObject.currentStep = this.animations[i].animationObject.numSteps;
  				}
  
  				this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject);
  				if (this.animations[i].animationObject.onAnimationProgress && this.animations[i].animationObject.onAnimationProgress.call) {
  					this.animations[i].animationObject.onAnimationProgress.call(this.animations[i].chartInstance, this.animations[i]);
  				}
  
  				if (this.animations[i].animationObject.currentStep === this.animations[i].animationObject.numSteps) {
  					if (this.animations[i].animationObject.onAnimationComplete && this.animations[i].animationObject.onAnimationComplete.call) {
  						this.animations[i].animationObject.onAnimationComplete.call(this.animations[i].chartInstance, this.animations[i]);
  					}
  
  					// executed the last frame. Remove the animation.
  					this.animations[i].chartInstance.animating = false;
  
  					this.animations.splice(i, 1);
  				} else {
  					++i;
  				}
  			}
  
  			var endTime = Date.now();
  			var dropFrames = (endTime - startTime) / this.frameDuration;
  
  			this.dropFrames += dropFrames;
  
  			// Do we have more stuff to animate?
  			if (this.animations.length > 0) {
  				this.requestAnimationFrame();
  			}
  		}
  	};
  };
  },{}],22:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers;
  	//Create a dictionary of chart types, to allow for extension of existing types
  	Chart.types = {};
  
  	//Store a reference to each instance - allowing us to globally resize chart instances on window resize.
  	//Destroy method on the chart will remove the instance of the chart from this reference.
  	Chart.instances = {};
  
  	// Controllers available for dataset visualization eg. bar, line, slice, etc.
  	Chart.controllers = {};
  
  	// The main controller of a chart
  	Chart.Controller = function(instance) {
  
  		this.chart = instance;
  		this.config = instance.config;
  		this.options = this.config.options = helpers.configMerge(Chart.defaults.global, Chart.defaults[this.config.type], this.config.options || {});
  		this.id = helpers.uid();
  
  		Object.defineProperty(this, 'data', {
  			get: function() {
  				return this.config.data;
  			}
  		});
  
  		//Add the chart instance to the global namespace
  		Chart.instances[this.id] = this;
  
  		if (this.options.responsive) {
  			// Silent resize before chart draws
  			this.resize(true);
  		}
  
  		this.initialize();
  
  		return this;
  	};
  
  	helpers.extend(Chart.Controller.prototype, {
  
  		initialize: function initialize() {
  			// Before init plugin notification
  			Chart.pluginService.notifyPlugins('beforeInit', [this]);
  
  			this.bindEvents();
  
  			// Make sure controllers are built first so that each dataset is bound to an axis before the scales
  			// are built
  			this.ensureScalesHaveIDs();
  			this.buildOrUpdateControllers();
  			this.buildScales();
  			this.buildSurroundingItems();
  			this.updateLayout();
  			this.resetElements();
  			this.initToolTip();
  			this.update();
  
  			// After init plugin notification
  			Chart.pluginService.notifyPlugins('afterInit', [this]);
  
  			return this;
  		},
  
  		clear: function clear() {
  			helpers.clear(this.chart);
  			return this;
  		},
  
  		stop: function stop() {
  			// Stops any current animation loop occuring
  			Chart.animationService.cancelAnimation(this);
  			return this;
  		},
  
  		resize: function resize(silent) {
  			var canvas = this.chart.canvas;
  			var newWidth = helpers.getMaximumWidth(this.chart.canvas);
  			var newHeight = (this.options.maintainAspectRatio && isNaN(this.chart.aspectRatio) === false && isFinite(this.chart.aspectRatio) && this.chart.aspectRatio !== 0) ? newWidth / this.chart.aspectRatio : helpers.getMaximumHeight(this.chart.canvas);
  
  			var sizeChanged = this.chart.width !== newWidth || this.chart.height !== newHeight;
  
  			if (!sizeChanged)
  				return this;
  
  			canvas.width = this.chart.width = newWidth;
  			canvas.height = this.chart.height = newHeight;
  
  			helpers.retinaScale(this.chart);
  
  			if (!silent) {
  				this.stop();
  				this.update(this.options.responsiveAnimationDuration);
  			}
  
  			return this;
  		},
  
  		ensureScalesHaveIDs: function ensureScalesHaveIDs() {
  			var options = this.options;
  			var scalesOptions = options.scales || {};
  			var scaleOptions = options.scale;
  
  			helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) {
  				xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index);
  			});
  
  			helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) {
  				yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index);
  			});
  
  			if (scaleOptions) {
  				scaleOptions.id = scaleOptions.id || 'scale';
  			}
  		},
  
  		/**
  		 * Builds a map of scale ID to scale object for future lookup.
  		 */
  		buildScales: function buildScales() {
  			var me = this;
  			var options = me.options;
  			var scales = me.scales = {};
  			var items = [];
  
  			if (options.scales) {
  				items = items.concat(
  					(options.scales.xAxes || []).map(function(xAxisOptions) {
  						return { options: xAxisOptions, dtype: 'category' }; }),
  					(options.scales.yAxes || []).map(function(yAxisOptions) {
  						return { options: yAxisOptions, dtype: 'linear' }; }));
  			}
  
  			if (options.scale) {
  				items.push({ options: options.scale, dtype: 'radialLinear', isDefault: true });
  			}
  
  			helpers.each(items, function(item, index) {
  				var scaleOptions = item.options;
  				var scaleType = helpers.getValueOrDefault(scaleOptions.type, item.dtype);
  				var scaleClass = Chart.scaleService.getScaleConstructor(scaleType);
  				if (!scaleClass) {
  					return;
  				}
  
  				var scale = new scaleClass({
  					id: scaleOptions.id,
  					options: scaleOptions,
  					ctx: me.chart.ctx,
  					chart: me
  				});
  
  				scales[scale.id] = scale;
  
  				// TODO(SB): I think we should be able to remove this custom case (options.scale)
  				// and consider it as a regular scale part of the "scales"" map only! This would
  				// make the logic easier and remove some useless? custom code.
  				if (item.isDefault) {
  					me.scale = scale;
  				}
  			});
  
  			Chart.scaleService.addScalesToLayout(this);
  		},
  
  		buildSurroundingItems: function() {
  			if (this.options.title) {
  				this.titleBlock = new Chart.Title({
  					ctx: this.chart.ctx,
  					options: this.options.title,
  					chart: this
  				});
  
  				Chart.layoutService.addBox(this, this.titleBlock);
  			}
  
  			if (this.options.legend) {
  				this.legend = new Chart.Legend({
  					ctx: this.chart.ctx,
  					options: this.options.legend,
  					chart: this
  				});
  
  				Chart.layoutService.addBox(this, this.legend);
  			}
  		},
  
  		updateLayout: function() {
  			Chart.layoutService.update(this, this.chart.width, this.chart.height);
  		},
  
  		buildOrUpdateControllers: function buildOrUpdateControllers() {
  			var types = [];
  			var newControllers = [];
  
  			helpers.each(this.data.datasets, function(dataset, datasetIndex) {
  				var meta = this.getDatasetMeta(datasetIndex);
  				if (!meta.type) {
  					meta.type = dataset.type || this.config.type;
  				}
  
  				types.push(meta.type);
  
  				if (meta.controller) {
  					meta.controller.updateIndex(datasetIndex);
  				} else {
  					meta.controller = new Chart.controllers[meta.type](this, datasetIndex);
  					newControllers.push(meta.controller);
  				}
  			}, this);
  
  			if (types.length > 1) {
  				for (var i = 1; i < types.length; i++) {
  					if (types[i] !== types[i - 1]) {
  						this.isCombo = true;
  						break;
  					}
  				}
  			}
  
  			return newControllers;
  		},
  
  		resetElements: function resetElements() {
  			helpers.each(this.data.datasets, function(dataset, datasetIndex) {
  				this.getDatasetMeta(datasetIndex).controller.reset();
  			}, this);
  		},
  
  		update: function update(animationDuration, lazy) {
  			Chart.pluginService.notifyPlugins('beforeUpdate', [this]);
  
  			// In case the entire data object changed
  			this.tooltip._data = this.data;
  
  			// Make sure dataset controllers are updated and new controllers are reset
  			var newControllers = this.buildOrUpdateControllers();
  
  			// Make sure all dataset controllers have correct meta data counts
  			helpers.each(this.data.datasets, function(dataset, datasetIndex) {
  				this.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements();
  			}, this);
  
  			Chart.layoutService.update(this, this.chart.width, this.chart.height);
  
  			// Apply changes to the dataets that require the scales to have been calculated i.e BorderColor chages
  			Chart.pluginService.notifyPlugins('afterScaleUpdate', [this]);
  
  			// Can only reset the new controllers after the scales have been updated
  			helpers.each(newControllers, function(controller) {
  				controller.reset();
  			});
  
  			// This will loop through any data and do the appropriate element update for the type
  			helpers.each(this.data.datasets, function(dataset, datasetIndex) {
  				this.getDatasetMeta(datasetIndex).controller.update();
  			}, this);
  
  			// Do this before render so that any plugins that need final scale updates can use it
  			Chart.pluginService.notifyPlugins('afterUpdate', [this]);
  
  			this.render(animationDuration, lazy);
  		},
  
  		render: function render(duration, lazy) {
  			Chart.pluginService.notifyPlugins('beforeRender', [this]);
  
  			var animationOptions = this.options.animation;
  			if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) {
  				var animation = new Chart.Animation();
  				animation.numSteps = (duration || animationOptions.duration) / 16.66; //60 fps
  				animation.easing = animationOptions.easing;
  
  				// render function
  				animation.render = function(chartInstance, animationObject) {
  					var easingFunction = helpers.easingEffects[animationObject.easing];
  					var stepDecimal = animationObject.currentStep / animationObject.numSteps;
  					var easeDecimal = easingFunction(stepDecimal);
  
  					chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep);
  				};
  
  				// user events
  				animation.onAnimationProgress = animationOptions.onProgress;
  				animation.onAnimationComplete = animationOptions.onComplete;
  
  				Chart.animationService.addAnimation(this, animation, duration, lazy);
  			} else {
  				this.draw();
  				if (animationOptions && animationOptions.onComplete && animationOptions.onComplete.call) {
  					animationOptions.onComplete.call(this);
  				}
  			}
  			return this;
  		},
  
  		draw: function(ease) {
  			var easingDecimal = ease || 1;
  			this.clear();
  
  			Chart.pluginService.notifyPlugins('beforeDraw', [this, easingDecimal]);
  
  			// Draw all the scales
  			helpers.each(this.boxes, function(box) {
  				box.draw(this.chartArea);
  			}, this);
  			if (this.scale) {
  				this.scale.draw();
  			}
  
  			// Clip out the chart area so that anything outside does not draw. This is necessary for zoom and pan to function
  			var context = this.chart.ctx;
  			context.save();
  			context.beginPath();
  			context.rect(this.chartArea.left, this.chartArea.top, this.chartArea.right - this.chartArea.left, this.chartArea.bottom - this.chartArea.top);
  			context.clip();
  
  			// Draw each dataset via its respective controller (reversed to support proper line stacking)
  			helpers.each(this.data.datasets, function(dataset, datasetIndex) {
  				if (this.isDatasetVisible(datasetIndex)) {
  					this.getDatasetMeta(datasetIndex).controller.draw(ease);
  				}
  			}, this, true);
  
  			// Restore from the clipping operation
  			context.restore();
  
  			// Finally draw the tooltip
  			this.tooltip.transition(easingDecimal).draw();
  
  			Chart.pluginService.notifyPlugins('afterDraw', [this, easingDecimal]);
  		},
  
  		// Get the single element that was clicked on
  		// @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
  		getElementAtEvent: function(e) {
  			var eventPosition = helpers.getRelativePosition(e, this.chart);
  			var elementsArray = [];
  
  			helpers.each(this.data.datasets, function(dataset, datasetIndex) {
  				if (this.isDatasetVisible(datasetIndex)) {
  					var meta = this.getDatasetMeta(datasetIndex);
  					helpers.each(meta.data, function(element, index) {
  						if (element.inRange(eventPosition.x, eventPosition.y)) {
  							elementsArray.push(element);
  							return elementsArray;
  						}
  					});
  				}
  			}, this);
  
  			return elementsArray;
  		},
  
  		getElementsAtEvent: function(e) {
  			var eventPosition = helpers.getRelativePosition(e, this.chart);
  			var elementsArray = [];
  
  			var found = (function() {
  				if (this.data.datasets) {
  					for (var i = 0; i < this.data.datasets.length; i++) {
  						var meta = this.getDatasetMeta(i);
  						if (this.isDatasetVisible(i)) {
  							for (var j = 0; j < meta.data.length; j++) {
  								if (meta.data[j].inRange(eventPosition.x, eventPosition.y)) {
  									return meta.data[j];
  								}
  							}
  						}
  					}
  				}
  			}).call(this);
  
  			if (!found) {
  				return elementsArray;
  			}
  
  			helpers.each(this.data.datasets, function(dataset, datasetIndex) {
  				if (this.isDatasetVisible(datasetIndex)) {
  					var meta = this.getDatasetMeta(datasetIndex);
  					elementsArray.push(meta.data[found._index]);
  				}
  			}, this);
  
  			return elementsArray;
  		},
  
  		getElementsAtEventForMode: function(e, mode) {
  			var me = this;
  			switch (mode) {
  			case 'single':
  				return me.getElementAtEvent(e);
  			case 'label':
  				return me.getElementsAtEvent(e);
  			case 'dataset':
  				return me.getDatasetAtEvent(e);
  			default:
  				return e;
  			}
  		},
  
  		getDatasetAtEvent: function(e) {
  			var elementsArray = this.getElementAtEvent(e);
  
  			if (elementsArray.length > 0) {
  				elementsArray = this.getDatasetMeta(elementsArray[0]._datasetIndex).data;
  			}
  
  			return elementsArray;
  		},
  
  		getDatasetMeta: function(datasetIndex) {
  			var dataset = this.data.datasets[datasetIndex];
  			if (!dataset._meta) {
  				dataset._meta = {};
  			}
  
  			var meta = dataset._meta[this.id];
  			if (!meta) {
  				meta = dataset._meta[this.id] = {
  				type: null,
  				data: [],
  				dataset: null,
  				controller: null,
  				hidden: null,			// See isDatasetVisible() comment
  				xAxisID: null,
  				yAxisID: null
  			};
  			}
  
  			return meta;
  		},
  
  		getVisibleDatasetCount: function() {
  			var count = 0;
  			for (var i = 0, ilen = this.data.datasets.length; i<ilen; ++i) {
  				 if (this.isDatasetVisible(i)) {
  					count++;
  				}
  			}
  			return count;
  		},
  
  		isDatasetVisible: function(datasetIndex) {
  			var meta = this.getDatasetMeta(datasetIndex);
  
  			// meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false,
  			// the dataset.hidden value is ignored, else if null, the dataset hidden state is returned.
  			return typeof meta.hidden === 'boolean'? !meta.hidden : !this.data.datasets[datasetIndex].hidden;
  		},
  
  		generateLegend: function generateLegend() {
  			return this.options.legendCallback(this);
  		},
  
  		destroy: function destroy() {
  			this.clear();
  			helpers.unbindEvents(this, this.events);
  			helpers.removeResizeListener(this.chart.canvas.parentNode);
  
  			// Reset canvas height/width attributes
  			var canvas = this.chart.canvas;
  			canvas.width = this.chart.width;
  			canvas.height = this.chart.height;
  
  			// if we scaled the canvas in response to a devicePixelRatio !== 1, we need to undo that transform here
  			if (this.chart.originalDevicePixelRatio !== undefined) {
  				this.chart.ctx.scale(1 / this.chart.originalDevicePixelRatio, 1 / this.chart.originalDevicePixelRatio);
  			}
  
  			// Reset to the old style since it may have been changed by the device pixel ratio changes
  			canvas.style.width = this.chart.originalCanvasStyleWidth;
  			canvas.style.height = this.chart.originalCanvasStyleHeight;
  
  			Chart.pluginService.notifyPlugins('destroy', [this]);
  
  			delete Chart.instances[this.id];
  		},
  
  		toBase64Image: function toBase64Image() {
  			return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
  		},
  
  		initToolTip: function initToolTip() {
  			this.tooltip = new Chart.Tooltip({
  				_chart: this.chart,
  				_chartInstance: this,
  				_data: this.data,
  				_options: this.options
  			}, this);
  		},
  
  		bindEvents: function bindEvents() {
  			helpers.bindEvents(this, this.options.events, function(evt) {
  				this.eventHandler(evt);
  			});
  		},
  
  		updateHoverStyle: function(elements, mode, enabled) {
  			var method = enabled? 'setHoverStyle' : 'removeHoverStyle';
  			var element, i, ilen;
  
  			switch (mode) {
  			case 'single':
  				elements = [ elements[0] ];
  				break;
  			case 'label':
  			case 'dataset':
  				// elements = elements;
  				break;
  			default:
  				// unsupported mode
  				return;
  			}
  
  			for (i=0, ilen=elements.length; i<ilen; ++i) {
  				element = elements[i];
  				if (element) {
  					this.getDatasetMeta(element._datasetIndex).controller[method](element);
  				}
  			}
  		},
  
  		eventHandler: function eventHandler(e) {
  			var me = this;
  			var tooltip = me.tooltip;
  			var options = me.options || {};
  			var hoverOptions = options.hover;
  			var tooltipsOptions = options.tooltips;
  
  			me.lastActive = me.lastActive || [];
  			me.lastTooltipActive = me.lastTooltipActive || [];
  
  			// Find Active Elements for hover and tooltips
  			if (e.type === 'mouseout') {
  				me.active = [];
  				me.tooltipActive = [];
  			} else {
  				me.active = me.getElementsAtEventForMode(e, hoverOptions.mode);
  				me.tooltipActive =  me.getElementsAtEventForMode(e, tooltipsOptions.mode);
  			}
  
  			// On Hover hook
  			if (hoverOptions.onHover) {
  				hoverOptions.onHover.call(me, me.active);
  			}
  
  			if (e.type === 'mouseup' || e.type === 'click') {
  				if (options.onClick) {
  					options.onClick.call(me, e, me.active);
  				}
  				if (me.legend && me.legend.handleEvent) {
  					me.legend.handleEvent(e);
  				}
  			}
  
  			// Remove styling for last active (even if it may still be active)
  			if (me.lastActive.length) {
  				me.updateHoverStyle(me.lastActive, hoverOptions.mode, false);
  			}
  
  			// Built in hover styling
  			if (me.active.length && hoverOptions.mode) {
  				me.updateHoverStyle(me.active, hoverOptions.mode, true);
  			}
  
  			// Built in Tooltips
  			if (tooltipsOptions.enabled || tooltipsOptions.custom) {
  				tooltip.initialize();
  				tooltip._active = me.tooltipActive;
  				tooltip.update(true);
  			}
  
  			// Hover animations
  			tooltip.pivot();
  
  			if (!me.animating) {
  				// If entering, leaving, or changing elements, animate the change via pivot
  				if (!helpers.arrayEquals(me.active, me.lastActive) ||
  					!helpers.arrayEquals(me.tooltipActive, me.lastTooltipActive)) {
  
  					me.stop();
  
  					if (tooltipsOptions.enabled || tooltipsOptions.custom) {
  						tooltip.update(true);
  					}
  
  					// We only need to render at this point. Updating will cause scales to be
  					// recomputed generating flicker & using more memory than necessary.
  					me.render(hoverOptions.animationDuration, true);
  				}
  			}
  
  			// Remember Last Actives
  			me.lastActive = me.active;
  			me.lastTooltipActive = me.tooltipActive;
  			return me;
  		}
  	});
  };
  
  },{}],23:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers;
  	var noop = helpers.noop;
  
  	// Base class for all dataset controllers (line, bar, etc)
  	Chart.DatasetController = function(chart, datasetIndex) {
  		this.initialize.call(this, chart, datasetIndex);
  	};
  
  	helpers.extend(Chart.DatasetController.prototype, {
  
  		/**
  		 * Element type used to generate a meta dataset (e.g. Chart.element.Line).
  		 * @type {Chart.core.element}
  		 */
  		datasetElementType: null,
  
  		/**
  		 * Element type used to generate a meta data (e.g. Chart.element.Point).
  		 * @type {Chart.core.element}
  		 */
  		dataElementType: null,
  
  		initialize: function(chart, datasetIndex) {
  			this.chart = chart;
  			this.index = datasetIndex;
  			this.linkScales();
  			this.addElements();
  		},
  
  		updateIndex: function(datasetIndex) {
  			this.index = datasetIndex;
  		},
  
  		linkScales: function() {
  			var meta = this.getMeta();
  			var dataset = this.getDataset();
  
  			if (meta.xAxisID === null) {
  				meta.xAxisID = dataset.xAxisID || this.chart.options.scales.xAxes[0].id;
  			}
  			if (meta.yAxisID === null) {
  				meta.yAxisID = dataset.yAxisID || this.chart.options.scales.yAxes[0].id;
  			}
  		},
  
  		getDataset: function() {
  			return this.chart.data.datasets[this.index];
  		},
  
  		getMeta: function() {
  			return this.chart.getDatasetMeta(this.index);
  		},
  
  		getScaleForId: function(scaleID) {
  			return this.chart.scales[scaleID];
  		},
  
  		reset: function() {
  			this.update(true);
  		},
  
  		createMetaDataset: function() {
  			var me = this;
  			var type = me.datasetElementType;
  			return type && new type({
  				_chart: me.chart.chart,
  				_datasetIndex: me.index
  			});
  		},
  
  		createMetaData: function(index) {
  			var me = this;
  			var type = me.dataElementType;
  			return type && new type({
  				_chart: me.chart.chart,
  				_datasetIndex: me.index,
  				_index: index
  			});
  		},
  
  		addElements: function() {
  			var me = this;
  			var meta = me.getMeta();
  			var data = me.getDataset().data || [];
  			var metaData = meta.data;
  			var i, ilen;
  
  			for (i=0, ilen=data.length; i<ilen; ++i) {
  				metaData[i] = metaData[i] || me.createMetaData(meta, i);
  			}
  
  			meta.dataset = meta.dataset || me.createMetaDataset();
  		},
  
  		addElementAndReset: function(index) {
  			var me = this;
  			var element = me.createMetaData(index);
  			me.getMeta().data.splice(index, 0, element);
  			me.updateElement(element, index, true);
  		},
  
  		buildOrUpdateElements: function buildOrUpdateElements() {
  			// Handle the number of data points changing
  			var meta = this.getMeta(),
  				md = meta.data,
  				numData = this.getDataset().data.length,
  				numMetaData = md.length;
  
  			// Make sure that we handle number of datapoints changing
  			if (numData < numMetaData) {
  				// Remove excess bars for data points that have been removed
  				md.splice(numData, numMetaData - numData);
  			} else if (numData > numMetaData) {
  				// Add new elements
  				for (var index = numMetaData; index < numData; ++index) {
  					this.addElementAndReset(index);
  				}
  			}
  		},
  
  		update: noop,
  
  		draw: function(ease) {
  			var easingDecimal = ease || 1;
  			helpers.each(this.getMeta().data, function(element, index) {
  				element.transition(easingDecimal).draw();
  			});
  		},
  
  		removeHoverStyle: function(element, elementOpts) {
  			var dataset = this.chart.data.datasets[element._datasetIndex],
  				index = element._index,
  				custom = element.custom || {},
  				valueOrDefault = helpers.getValueAtIndexOrDefault,
  				color = helpers.color,
  				model = element._model;
  
  			model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor);
  			model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor);
  			model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth);
  		},
  
  		setHoverStyle: function(element) {
  			var dataset = this.chart.data.datasets[element._datasetIndex],
  				index = element._index,
  				custom = element.custom || {},
  				valueOrDefault = helpers.getValueAtIndexOrDefault,
  				color = helpers.color,
  				getHoverColor = helpers.getHoverColor,
  				model = element._model;
  
  			model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor));
  			model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor));
  			model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
  		}
  	});
  
  	Chart.DatasetController.extend = helpers.inherits;
  };
  },{}],24:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
    var helpers = Chart.helpers;
  
    Chart.elements = {};
  
    Chart.Element = function(configuration) {
      helpers.extend(this, configuration);
      this.initialize.apply(this, arguments);
    };
    helpers.extend(Chart.Element.prototype, {
      initialize: function() {
        this.hidden = false;
      },
      pivot: function() {
        if (!this._view) {
          this._view = helpers.clone(this._model);
        }
        this._start = helpers.clone(this._view);
        return this;
      },
      transition: function(ease) {
        if (!this._view) {
          this._view = helpers.clone(this._model);
        }
  
        // No animation -> No Transition
        if (ease === 1) {
          this._view = this._model;
          this._start = null;
          return this;
        }
  
        if (!this._start) {
          this.pivot();
        }
  
        helpers.each(this._model, function(value, key) {
  
          if (key[0] === '_') {
            // Only non-underscored properties
          }
  
          // Init if doesn't exist
          else if (!this._view.hasOwnProperty(key)) {
            if (typeof value === 'number' && !isNaN(this._view[key])) {
              this._view[key] = value * ease;
            } else {
              this._view[key] = value;
            }
          }
  
          // No unnecessary computations
          else if (value === this._view[key]) {
            // It's the same! Woohoo!
          }
  
          // Color transitions if possible
          else if (typeof value === 'string') {
            try {
              var color = helpers.color(this._model[key]).mix(helpers.color(this._start[key]), ease);
              this._view[key] = color.rgbString();
            } catch (err) {
              this._view[key] = value;
            }
          }
          // Number transitions
          else if (typeof value === 'number') {
            var startVal = this._start[key] !== undefined && isNaN(this._start[key]) === false ? this._start[key] : 0;
            this._view[key] = ((this._model[key] - startVal) * ease) + startVal;
          }
          // Everything else
          else {
            this._view[key] = value;
          }
        }, this);
  
        return this;
      },
      tooltipPosition: function() {
        return {
          x: this._model.x,
          y: this._model.y
        };
      },
      hasValue: function() {
        return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y);
      }
    });
  
    Chart.Element.extend = helpers.inherits;
  
  };
  
  },{}],25:[function(require,module,exports){
  /*global window: false */
  /*global document: false */
  "use strict";
  
  var color = require('chartjs-color');
  
  module.exports = function(Chart) {
  	//Global Chart helpers object for utility methods and classes
  	var helpers = Chart.helpers = {};
  
  	//-- Basic js utility methods
  	helpers.each = function(loopable, callback, self, reverse) {
  		// Check to see if null or undefined firstly.
  		var i, len;
  		if (helpers.isArray(loopable)) {
  			len = loopable.length;
  			if (reverse) {
  				for (i = len - 1; i >= 0; i--) {
  					callback.call(self, loopable[i], i);
  				}
  			} else {
  				for (i = 0; i < len; i++) {
  					callback.call(self, loopable[i], i);
  				}
  			}
  		} else if (typeof loopable === 'object') {
  			var keys = Object.keys(loopable);
  			len = keys.length;
  			for (i = 0; i < len; i++) {
  				callback.call(self, loopable[keys[i]], keys[i]);
  			}
  		}
  	};
  	helpers.clone = function(obj) {
  		var objClone = {};
  		helpers.each(obj, function(value, key) {
  			if (helpers.isArray(value)) {
  				objClone[key] = value.slice(0);
  			} else if (typeof value === 'object' && value !== null) {
  				objClone[key] = helpers.clone(value);
  			} else {
  				objClone[key] = value;
  			}
  		});
  		return objClone;
  	};
  	helpers.extend = function(base) {
  		var len = arguments.length;
  		var additionalArgs = [];
  		for (var i = 1; i < len; i++) {
  			additionalArgs.push(arguments[i]);
  		}
  		helpers.each(additionalArgs, function(extensionObject) {
  			helpers.each(extensionObject, function(value, key) {
  				base[key] = value;
  			});
  		});
  		return base;
  	};
  	// Need a special merge function to chart configs since they are now grouped
  	helpers.configMerge = function(_base) {
  		var base = helpers.clone(_base);
  		helpers.each(Array.prototype.slice.call(arguments, 1), function(extension) {
  			helpers.each(extension, function(value, key) {
  				if (key === 'scales') {
  					// Scale config merging is complex. Add out own function here for that
  					base[key] = helpers.scaleMerge(base.hasOwnProperty(key) ? base[key] : {}, value);
  
  				} else if (key === 'scale') {
  					// Used in polar area & radar charts since there is only one scale
  					base[key] = helpers.configMerge(base.hasOwnProperty(key) ? base[key] : {}, Chart.scaleService.getScaleDefaults(value.type), value);
  				} else if (base.hasOwnProperty(key) && helpers.isArray(base[key]) && helpers.isArray(value)) {
  					// In this case we have an array of objects replacing another array. Rather than doing a strict replace,
  					// merge. This allows easy scale option merging
  					var baseArray = base[key];
  
  					helpers.each(value, function(valueObj, index) {
  
  						if (index < baseArray.length) {
  							if (typeof baseArray[index] === 'object' && baseArray[index] !== null && typeof valueObj === 'object' && valueObj !== null) {
  								// Two objects are coming together. Do a merge of them.
  								baseArray[index] = helpers.configMerge(baseArray[index], valueObj);
  							} else {
  								// Just overwrite in this case since there is nothing to merge
  								baseArray[index] = valueObj;
  							}
  						} else {
  							baseArray.push(valueObj); // nothing to merge
  						}
  					});
  
  				} else if (base.hasOwnProperty(key) && typeof base[key] === "object" && base[key] !== null && typeof value === "object") {
  					// If we are overwriting an object with an object, do a merge of the properties.
  					base[key] = helpers.configMerge(base[key], value);
  
  				} else {
  					// can just overwrite the value in this case
  					base[key] = value;
  				}
  			});
  		});
  
  		return base;
  	};
  	helpers.extendDeep = function(_base) {
  		return _extendDeep.apply(this, arguments);
  
  		function _extendDeep(dst) {
  			helpers.each(arguments, function(obj) {
  				if (obj !== dst) {
  					helpers.each(obj, function(value, key) {
  						if (dst[key] && dst[key].constructor && dst[key].constructor === Object) {
  							_extendDeep(dst[key], value);
  						} else {
  							dst[key] = value;
  						}
  					});
  				}
  			});
  			return dst;
  		}
  	};
  	helpers.scaleMerge = function(_base, extension) {
  		var base = helpers.clone(_base);
  
  		helpers.each(extension, function(value, key) {
  			if (key === 'xAxes' || key === 'yAxes') {
  				// These properties are arrays of items
  				if (base.hasOwnProperty(key)) {
  					helpers.each(value, function(valueObj, index) {
  						var axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear');
  						var axisDefaults = Chart.scaleService.getScaleDefaults(axisType);
  						if (index >= base[key].length || !base[key][index].type) {
  							base[key].push(helpers.configMerge(axisDefaults, valueObj));
  						} else if (valueObj.type && valueObj.type !== base[key][index].type) {
  							// Type changed. Bring in the new defaults before we bring in valueObj so that valueObj can override the correct scale defaults
  							base[key][index] = helpers.configMerge(base[key][index], axisDefaults, valueObj);
  						} else {
  							// Type is the same
  							base[key][index] = helpers.configMerge(base[key][index], valueObj);
  						}
  					});
  				} else {
  					base[key] = [];
  					helpers.each(value, function(valueObj) {
  						var axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear');
  						base[key].push(helpers.configMerge(Chart.scaleService.getScaleDefaults(axisType), valueObj));
  					});
  				}
  			} else if (base.hasOwnProperty(key) && typeof base[key] === "object" && base[key] !== null && typeof value === "object") {
  				// If we are overwriting an object with an object, do a merge of the properties.
  				base[key] = helpers.configMerge(base[key], value);
  
  			} else {
  				// can just overwrite the value in this case
  				base[key] = value;
  			}
  		});
  
  		return base;
  	};
  	helpers.getValueAtIndexOrDefault = function(value, index, defaultValue) {
  		if (value === undefined || value === null) {
  			return defaultValue;
  		}
  
  		if (helpers.isArray(value)) {
  			return index < value.length ? value[index] : defaultValue;
  		}
  
  		return value;
  	};
  	helpers.getValueOrDefault = function(value, defaultValue) {
  		return value === undefined ? defaultValue : value;
  	};
  	helpers.indexOf = function(arrayToSearch, item) {
  		if (Array.prototype.indexOf) {
  			return arrayToSearch.indexOf(item);
  		} else {
  			for (var i = 0; i < arrayToSearch.length; i++) {
  				if (arrayToSearch[i] === item)
  					return i;
  			}
  			return -1;
  		}
  	};
  	helpers.where = function(collection, filterCallback) {
  		if (helpers.isArray(collection) && Array.prototype.filter) {
  			return collection.filter(filterCallback);
  		} else {
  			var filtered = [];
  
  			helpers.each(collection, function(item) {
  				if (filterCallback(item)) {
  					filtered.push(item);
  				}
  			});
  
  			return filtered;
  		}
  	};
  	helpers.findIndex = function(arrayToSearch, callback, thisArg) {
  		var index = -1;
  		if (Array.prototype.findIndex) {
  			index = arrayToSearch.findIndex(callback, thisArg);
  		} else {
  			for (var i = 0; i < arrayToSearch.length; ++i) {
  				thisArg = thisArg !== undefined ? thisArg : arrayToSearch;
  
  				if (callback.call(thisArg, arrayToSearch[i], i, arrayToSearch)) {
  					index = i;
  					break;
  				}
  			}
  		}
  
  		return index;
  	};
  	helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
  		// Default to start of the array
  		if (startIndex === undefined || startIndex === null) {
  			startIndex = -1;
  		}
  		for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
  			var currentItem = arrayToSearch[i];
  			if (filterCallback(currentItem)) {
  				return currentItem;
  			}
  		}
  	};
  	helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
  		// Default to end of the array
  		if (startIndex === undefined || startIndex === null) {
  			startIndex = arrayToSearch.length;
  		}
  		for (var i = startIndex - 1; i >= 0; i--) {
  			var currentItem = arrayToSearch[i];
  			if (filterCallback(currentItem)) {
  				return currentItem;
  			}
  		}
  	};
  	helpers.inherits = function(extensions) {
  		//Basic javascript inheritance based on the model created in Backbone.js
  		var parent = this;
  		var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function() {
  			return parent.apply(this, arguments);
  		};
  
  		var Surrogate = function() {
  			this.constructor = ChartElement;
  		};
  		Surrogate.prototype = parent.prototype;
  		ChartElement.prototype = new Surrogate();
  
  		ChartElement.extend = helpers.inherits;
  
  		if (extensions) {
  			helpers.extend(ChartElement.prototype, extensions);
  		}
  
  		ChartElement.__super__ = parent.prototype;
  
  		return ChartElement;
  	};
  	helpers.noop = function() {};
  	helpers.uid = (function() {
  		var id = 0;
  		return function() {
  			return id++;
  		};
  	})();
  	helpers.warn = function(str) {
  		//Method for warning of errors
  		if (console && typeof console.warn === "function") {
  			console.warn(str);
  		}
  	};
  	//-- Math methods
  	helpers.isNumber = function(n) {
  		return !isNaN(parseFloat(n)) && isFinite(n);
  	};
  	helpers.almostEquals = function(x, y, epsilon) {
  		return Math.abs(x - y) < epsilon;
  	};
  	helpers.max = function(array) {
  		return array.reduce(function(max, value) {
  			if (!isNaN(value)) {
  				return Math.max(max, value);
  			} else {
  				return max;
  			}
  		}, Number.NEGATIVE_INFINITY);
  	};
  	helpers.min = function(array) {
  		return array.reduce(function(min, value) {
  			if (!isNaN(value)) {
  				return Math.min(min, value);
  			} else {
  				return min;
  			}
  		}, Number.POSITIVE_INFINITY);
  	};
  	helpers.sign = function(x) {
  		if (Math.sign) {
  			return Math.sign(x);
  		} else {
  			x = +x; // convert to a number
  			if (x === 0 || isNaN(x)) {
  				return x;
  			}
  			return x > 0 ? 1 : -1;
  		}
  	};
  	helpers.log10 = function(x) {
  		if (Math.log10) {
  			return Math.log10(x);
  		} else {
  			return Math.log(x) / Math.LN10;
  		}
  	};
  	helpers.toRadians = function(degrees) {
  		return degrees * (Math.PI / 180);
  	};
  	helpers.toDegrees = function(radians) {
  		return radians * (180 / Math.PI);
  	};
  	// Gets the angle from vertical upright to the point about a centre.
  	helpers.getAngleFromPoint = function(centrePoint, anglePoint) {
  		var distanceFromXCenter = anglePoint.x - centrePoint.x,
  			distanceFromYCenter = anglePoint.y - centrePoint.y,
  			radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
  
  		var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
  
  		if (angle < (-0.5 * Math.PI)) {
  			angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
  		}
  
  		return {
  			angle: angle,
  			distance: radialDistanceFromCenter
  		};
  	};
  	helpers.aliasPixel = function(pixelWidth) {
  		return (pixelWidth % 2 === 0) ? 0 : 0.5;
  	};
  	helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) {
  		//Props to Rob Spencer at scaled innovation for his post on splining between points
  		//http://scaledinnovation.com/analytics/splines/aboutSplines.html
  
  		// This function must also respect "skipped" points
  
  		var previous = firstPoint.skip ? middlePoint : firstPoint,
  			current = middlePoint,
  			next = afterPoint.skip ? middlePoint : afterPoint;
  
  		var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2));
  		var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));
  
  		var s01 = d01 / (d01 + d12);
  		var s12 = d12 / (d01 + d12);
  
  		// If all points are the same, s01 & s02 will be inf
  		s01 = isNaN(s01) ? 0 : s01;
  		s12 = isNaN(s12) ? 0 : s12;
  
  		var fa = t * s01; // scaling factor for triangle Ta
  		var fb = t * s12;
  
  		return {
  			previous: {
  				x: current.x - fa * (next.x - previous.x),
  				y: current.y - fa * (next.y - previous.y)
  			},
  			next: {
  				x: current.x + fb * (next.x - previous.x),
  				y: current.y + fb * (next.y - previous.y)
  			}
  		};
  	};
  	helpers.nextItem = function(collection, index, loop) {
  		if (loop) {
  			return index >= collection.length - 1 ? collection[0] : collection[index + 1];
  		}
  
  		return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1];
  	};
  	helpers.previousItem = function(collection, index, loop) {
  		if (loop) {
  			return index <= 0 ? collection[collection.length - 1] : collection[index - 1];
  		}
  		return index <= 0 ? collection[0] : collection[index - 1];
  	};
  	// Implementation of the nice number algorithm used in determining where axis labels will go
  	helpers.niceNum = function(range, round) {
  		var exponent = Math.floor(helpers.log10(range));
  		var fraction = range / Math.pow(10, exponent);
  		var niceFraction;
  
  		if (round) {
  			if (fraction < 1.5) {
  				niceFraction = 1;
  			} else if (fraction < 3) {
  				niceFraction = 2;
  			} else if (fraction < 7) {
  				niceFraction = 5;
  			} else {
  				niceFraction = 10;
  			}
  		} else {
  			if (fraction <= 1.0) {
  				niceFraction = 1;
  			} else if (fraction <= 2) {
  				niceFraction = 2;
  			} else if (fraction <= 5) {
  				niceFraction = 5;
  			} else {
  				niceFraction = 10;
  			}
  		}
  
  		return niceFraction * Math.pow(10, exponent);
  	};
  	//Easing functions adapted from Robert Penner's easing equations
  	//http://www.robertpenner.com/easing/
  	var easingEffects = helpers.easingEffects = {
  		linear: function(t) {
  			return t;
  		},
  		easeInQuad: function(t) {
  			return t * t;
  		},
  		easeOutQuad: function(t) {
  			return -1 * t * (t - 2);
  		},
  		easeInOutQuad: function(t) {
  			if ((t /= 1 / 2) < 1) {
  				return 1 / 2 * t * t;
  			}
  			return -1 / 2 * ((--t) * (t - 2) - 1);
  		},
  		easeInCubic: function(t) {
  			return t * t * t;
  		},
  		easeOutCubic: function(t) {
  			return 1 * ((t = t / 1 - 1) * t * t + 1);
  		},
  		easeInOutCubic: function(t) {
  			if ((t /= 1 / 2) < 1) {
  				return 1 / 2 * t * t * t;
  			}
  			return 1 / 2 * ((t -= 2) * t * t + 2);
  		},
  		easeInQuart: function(t) {
  			return t * t * t * t;
  		},
  		easeOutQuart: function(t) {
  			return -1 * ((t = t / 1 - 1) * t * t * t - 1);
  		},
  		easeInOutQuart: function(t) {
  			if ((t /= 1 / 2) < 1) {
  				return 1 / 2 * t * t * t * t;
  			}
  			return -1 / 2 * ((t -= 2) * t * t * t - 2);
  		},
  		easeInQuint: function(t) {
  			return 1 * (t /= 1) * t * t * t * t;
  		},
  		easeOutQuint: function(t) {
  			return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
  		},
  		easeInOutQuint: function(t) {
  			if ((t /= 1 / 2) < 1) {
  				return 1 / 2 * t * t * t * t * t;
  			}
  			return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
  		},
  		easeInSine: function(t) {
  			return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
  		},
  		easeOutSine: function(t) {
  			return 1 * Math.sin(t / 1 * (Math.PI / 2));
  		},
  		easeInOutSine: function(t) {
  			return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
  		},
  		easeInExpo: function(t) {
  			return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
  		},
  		easeOutExpo: function(t) {
  			return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
  		},
  		easeInOutExpo: function(t) {
  			if (t === 0) {
  				return 0;
  			}
  			if (t === 1) {
  				return 1;
  			}
  			if ((t /= 1 / 2) < 1) {
  				return 1 / 2 * Math.pow(2, 10 * (t - 1));
  			}
  			return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
  		},
  		easeInCirc: function(t) {
  			if (t >= 1) {
  				return t;
  			}
  			return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
  		},
  		easeOutCirc: function(t) {
  			return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
  		},
  		easeInOutCirc: function(t) {
  			if ((t /= 1 / 2) < 1) {
  				return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
  			}
  			return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
  		},
  		easeInElastic: function(t) {
  			var s = 1.70158;
  			var p = 0;
  			var a = 1;
  			if (t === 0) {
  				return 0;
  			}
  			if ((t /= 1) === 1) {
  				return 1;
  			}
  			if (!p) {
  				p = 1 * 0.3;
  			}
  			if (a < Math.abs(1)) {
  				a = 1;
  				s = p / 4;
  			} else {
  				s = p / (2 * Math.PI) * Math.asin(1 / a);
  			}
  			return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
  		},
  		easeOutElastic: function(t) {
  			var s = 1.70158;
  			var p = 0;
  			var a = 1;
  			if (t === 0) {
  				return 0;
  			}
  			if ((t /= 1) === 1) {
  				return 1;
  			}
  			if (!p) {
  				p = 1 * 0.3;
  			}
  			if (a < Math.abs(1)) {
  				a = 1;
  				s = p / 4;
  			} else {
  				s = p / (2 * Math.PI) * Math.asin(1 / a);
  			}
  			return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
  		},
  		easeInOutElastic: function(t) {
  			var s = 1.70158;
  			var p = 0;
  			var a = 1;
  			if (t === 0) {
  				return 0;
  			}
  			if ((t /= 1 / 2) === 2) {
  				return 1;
  			}
  			if (!p) {
  				p = 1 * (0.3 * 1.5);
  			}
  			if (a < Math.abs(1)) {
  				a = 1;
  				s = p / 4;
  			} else {
  				s = p / (2 * Math.PI) * Math.asin(1 / a);
  			}
  			if (t < 1) {
  				return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
  			}
  			return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
  		},
  		easeInBack: function(t) {
  			var s = 1.70158;
  			return 1 * (t /= 1) * t * ((s + 1) * t - s);
  		},
  		easeOutBack: function(t) {
  			var s = 1.70158;
  			return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
  		},
  		easeInOutBack: function(t) {
  			var s = 1.70158;
  			if ((t /= 1 / 2) < 1) {
  				return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
  			}
  			return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
  		},
  		easeInBounce: function(t) {
  			return 1 - easingEffects.easeOutBounce(1 - t);
  		},
  		easeOutBounce: function(t) {
  			if ((t /= 1) < (1 / 2.75)) {
  				return 1 * (7.5625 * t * t);
  			} else if (t < (2 / 2.75)) {
  				return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
  			} else if (t < (2.5 / 2.75)) {
  				return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
  			} else {
  				return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
  			}
  		},
  		easeInOutBounce: function(t) {
  			if (t < 1 / 2) {
  				return easingEffects.easeInBounce(t * 2) * 0.5;
  			}
  			return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
  		}
  	};
  	//Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
  	helpers.requestAnimFrame = (function() {
  		return window.requestAnimationFrame ||
  			window.webkitRequestAnimationFrame ||
  			window.mozRequestAnimationFrame ||
  			window.oRequestAnimationFrame ||
  			window.msRequestAnimationFrame ||
  			function(callback) {
  				return window.setTimeout(callback, 1000 / 60);
  			};
  	})();
  	helpers.cancelAnimFrame = (function() {
  		return window.cancelAnimationFrame ||
  			window.webkitCancelAnimationFrame ||
  			window.mozCancelAnimationFrame ||
  			window.oCancelAnimationFrame ||
  			window.msCancelAnimationFrame ||
  			function(callback) {
  				return window.clearTimeout(callback, 1000 / 60);
  			};
  	})();
  	//-- DOM methods
  	helpers.getRelativePosition = function(evt, chart) {
  		var mouseX, mouseY;
  		var e = evt.originalEvent || evt,
  			canvas = evt.currentTarget || evt.srcElement,
  			boundingRect = canvas.getBoundingClientRect();
  
  		var touches = e.touches;
  		if (touches && touches.length > 0) {
  			mouseX = touches[0].clientX;
  			mouseY = touches[0].clientY;
  
  		} else {
  			mouseX = e.clientX;
  			mouseY = e.clientY;
  		}
  
  		// Scale mouse coordinates into canvas coordinates
  		// by following the pattern laid out by 'jerryj' in the comments of
  		// http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
  		var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left'));
  		var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top'));
  		var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right'));
  		var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom'));
  		var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight;
  		var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom;
  
  		// We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However
  		// the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here
  		mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio);
  		mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio);
  
  		return {
  			x: mouseX,
  			y: mouseY
  		};
  
  	};
  	helpers.addEvent = function(node, eventType, method) {
  		if (node.addEventListener) {
  			node.addEventListener(eventType, method);
  		} else if (node.attachEvent) {
  			node.attachEvent("on" + eventType, method);
  		} else {
  			node["on" + eventType] = method;
  		}
  	};
  	helpers.removeEvent = function(node, eventType, handler) {
  		if (node.removeEventListener) {
  			node.removeEventListener(eventType, handler, false);
  		} else if (node.detachEvent) {
  			node.detachEvent("on" + eventType, handler);
  		} else {
  			node["on" + eventType] = helpers.noop;
  		}
  	};
  	helpers.bindEvents = function(chartInstance, arrayOfEvents, handler) {
  		// Create the events object if it's not already present
  		var events = chartInstance.events = chartInstance.events || {};
  
  		helpers.each(arrayOfEvents, function(eventName) {
  			events[eventName] = function() {
  				handler.apply(chartInstance, arguments);
  			};
  			helpers.addEvent(chartInstance.chart.canvas, eventName, events[eventName]);
  		});
  	};
  	helpers.unbindEvents = function(chartInstance, arrayOfEvents) {
  		var canvas = chartInstance.chart.canvas;
  		helpers.each(arrayOfEvents, function(handler, eventName) {
  			helpers.removeEvent(canvas, eventName, handler);
  		});
  	};
  
  	// Private helper function to convert max-width/max-height values that may be percentages into a number
  	function parseMaxStyle(styleValue, node, parentProperty) {
  		var valueInPixels;
  		if (typeof(styleValue) === 'string') {
  			valueInPixels = parseInt(styleValue, 10);
  
  			if (styleValue.indexOf('%') != -1) {
  				// percentage * size in dimension
  				valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
  			}
  		} else {
  			valueInPixels = styleValue;
  		}
  
  		return valueInPixels;
  	}
  
  	/**
  	 * Returns if the given value contains an effective constraint.
  	 * @private
  	 */
  	function isConstrainedValue(value) {
  		return value !== undefined &&  value !== null && value !== 'none';
  	}
  
  	// Private helper to get a constraint dimension
  	// @param domNode : the node to check the constraint on
  	// @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight)
  	// @param percentageProperty : property of parent to use when calculating width as a percentage
  	// @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser
  	function getConstraintDimension(domNode, maxStyle, percentageProperty) {
  		var view = document.defaultView;
  		var parentNode = domNode.parentNode;
  		var constrainedNode = view.getComputedStyle(domNode)[maxStyle];
  		var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle];
  		var hasCNode = isConstrainedValue(constrainedNode);
  		var hasCContainer = isConstrainedValue(constrainedContainer);
  		var infinity = Number.POSITIVE_INFINITY;
  
  		if (hasCNode || hasCContainer) {
  			return Math.min(
  				hasCNode? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity,
  				hasCContainer? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity);
  		}
  
  		return 'none';
  	}
  	// returns Number or undefined if no constraint
  	helpers.getConstraintWidth = function(domNode) {
  		return getConstraintDimension(domNode, 'max-width', 'clientWidth');
  	};
  	// returns Number or undefined if no constraint
  	helpers.getConstraintHeight = function(domNode) {
  		return getConstraintDimension(domNode, 'max-height', 'clientHeight');
  	};
  	helpers.getMaximumWidth = function(domNode) {
  		var container = domNode.parentNode;
  		var padding = parseInt(helpers.getStyle(container, 'padding-left')) + parseInt(helpers.getStyle(container, 'padding-right'));
  		var w = container.clientWidth - padding;
  		var cw = helpers.getConstraintWidth(domNode);
  		return isNaN(cw)? w : Math.min(w, cw);
  	};
  	helpers.getMaximumHeight = function(domNode) {
  		var container = domNode.parentNode;
  		var padding = parseInt(helpers.getStyle(container, 'padding-top')) + parseInt(helpers.getStyle(container, 'padding-bottom'));
  		var h = container.clientHeight - padding;
  		var ch = helpers.getConstraintHeight(domNode);
  		return isNaN(ch)? h : Math.min(h, ch);
  	};
  	helpers.getStyle = function(el, property) {
  		return el.currentStyle ?
  			el.currentStyle[property] :
  			document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
  	};
  	helpers.retinaScale = function(chart) {
  		var ctx = chart.ctx;
  		var canvas = chart.canvas;
  		var width = canvas.width;
  		var height = canvas.height;
  		var pixelRatio = chart.currentDevicePixelRatio = window.devicePixelRatio || 1;
  
  		if (pixelRatio !== 1) {
  			canvas.height = height * pixelRatio;
  			canvas.width = width * pixelRatio;
  			ctx.scale(pixelRatio, pixelRatio);
  
  			// Store the device pixel ratio so that we can go backwards in `destroy`.
  			// The devicePixelRatio changes with zoom, so there are no guarantees that it is the same
  			// when destroy is called
  			chart.originalDevicePixelRatio = chart.originalDevicePixelRatio || pixelRatio;
  		}
  
  		canvas.style.width = width + 'px';
  		canvas.style.height = height + 'px';
  	};
  	//-- Canvas methods
  	helpers.clear = function(chart) {
  		chart.ctx.clearRect(0, 0, chart.width, chart.height);
  	};
  	helpers.fontString = function(pixelSize, fontStyle, fontFamily) {
  		return fontStyle + " " + pixelSize + "px " + fontFamily;
  	};
  	helpers.longestText = function(ctx, font, arrayOfStrings, cache) {
  		cache = cache || {};
  		var data = cache.data = cache.data || {};
  		var gc = cache.garbageCollect = cache.garbageCollect || [];
  
  		if (cache.font !== font) {
  			data = cache.data = {};
  			gc = cache.garbageCollect = [];
  			cache.font = font;
  		}
  
  		ctx.font = font;
  		var longest = 0;
  		helpers.each(arrayOfStrings, function(string) {
  			// Undefined strings should not be measured
  			if (string !== undefined && string !== null) {
  				var textWidth = data[string];
  				if (!textWidth) {
  					textWidth = data[string] = ctx.measureText(string).width;
  					gc.push(string);
  				}
  
  				if (textWidth > longest) {
  					longest = textWidth;
  				}
  			}
  		});
  
  		var gcLen = gc.length / 2;
  		if (gcLen > arrayOfStrings.length) {
  			for (var i = 0; i < gcLen; i++) {
  				delete data[gc[i]];
  			}
  			gc.splice(0, gcLen);
  		}
  
  		return longest;
  	};
  	helpers.drawRoundedRectangle = function(ctx, x, y, width, height, radius) {
  		ctx.beginPath();
  		ctx.moveTo(x + radius, y);
  		ctx.lineTo(x + width - radius, y);
  		ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
  		ctx.lineTo(x + width, y + height - radius);
  		ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
  		ctx.lineTo(x + radius, y + height);
  		ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
  		ctx.lineTo(x, y + radius);
  		ctx.quadraticCurveTo(x, y, x + radius, y);
  		ctx.closePath();
  	};
  	helpers.color = function(c) {
  		if (!color) {
  			console.log('Color.js not found!');
  			return c;
  		}
  
  		/* global CanvasGradient */
  		if (c instanceof CanvasGradient) {
  			return color(Chart.defaults.global.defaultColor);
  		}
  
  		return color(c);
  	};
  	helpers.addResizeListener = function(node, callback) {
  		// Hide an iframe before the node
  		var hiddenIframe = document.createElement('iframe');
  		var hiddenIframeClass = 'chartjs-hidden-iframe';
  
  		if (hiddenIframe.classlist) {
  			// can use classlist
  			hiddenIframe.classlist.add(hiddenIframeClass);
  		} else {
  			hiddenIframe.setAttribute('class', hiddenIframeClass);
  		}
  
  		// Set the style
  		var style = hiddenIframe.style;
  		style.width = '100%';
  		style.display = 'block';
  		style.border = 0;
  		style.height = 0;
  		style.margin = 0;
  		style.position = 'absolute';
  		style.left = 0;
  		style.right = 0;
  		style.top = 0;
  		style.bottom = 0;
  
  		// Insert the iframe so that contentWindow is available
  		node.insertBefore(hiddenIframe, node.firstChild);
  
  		(hiddenIframe.contentWindow || hiddenIframe).onresize = function() {
  			if (callback) {
  				callback();
  			}
  		};
  	};
  	helpers.removeResizeListener = function(node) {
  		var hiddenIframe = node.querySelector('.chartjs-hidden-iframe');
  
  		// Remove the resize detect iframe
  		if (hiddenIframe) {
  			hiddenIframe.parentNode.removeChild(hiddenIframe);
  		}
  	};
  	helpers.isArray = function(obj) {
  		if (!Array.isArray) {
  			return Object.prototype.toString.call(obj) === '[object Array]';
  		}
  		return Array.isArray(obj);
  	};
  	//! @see http://stackoverflow.com/a/14853974
  	helpers.arrayEquals = function(a0, a1) {
  		var i, ilen, v0, v1;
  
  		if (!a0 || !a1 || a0.length != a1.length) {
  			return false;
  		}
  
  		for (i = 0, ilen=a0.length; i < ilen; ++i) {
  			v0 = a0[i];
  			v1 = a1[i];
  
  			if (v0 instanceof Array && v1 instanceof Array) {
  				if (!helpers.arrayEquals(v0, v1)) {
  					return false;
  				}
  			} else if (v0 != v1) {
  				// NOTE: two different object instances will never be equal: {x:20} != {x:20}
  				return false;
  			}
  		}
  
  		return true;
  	};
  	helpers.pushAllIfDefined = function(element, array) {
  		if (typeof element === "undefined") {
  			return;
  		}
  
  		if (helpers.isArray(element)) {
  			array.push.apply(array, element);
  		} else {
  			array.push(element);
  		}
  	};
  	helpers.callCallback = function(fn, args, _tArg) {
  		if (fn && typeof fn.call === 'function') {
  			fn.apply(_tArg, args);
  		}
  	};
  	helpers.getHoverColor = function(color) {
  		/* global CanvasPattern */
  		return (color instanceof CanvasPattern) ?
  			color :
  			helpers.color(color).saturate(0.5).darken(0.1).rgbString();
  	};
  };
  
  },{"chartjs-color":3}],26:[function(require,module,exports){
  "use strict";
  
  module.exports = function() {
  
  	//Occupy the global variable of Chart, and create a simple base class
  	var Chart = function(context, config) {
  		this.config = config;
  
  		// Support a jQuery'd canvas element
  		if (context.length && context[0].getContext) {
  			context = context[0];
  		}
  
  		// Support a canvas domnode
  		if (context.getContext) {
  			context = context.getContext("2d");
  		}
  
  		this.ctx = context;
  		this.canvas = context.canvas;
  
  		// Figure out what the size of the chart will be.
  		// If the canvas has a specified width and height, we use those else
  		// we look to see if the canvas node has a CSS width and height.
  		// If there is still no height, fill the parent container
  		this.width = context.canvas.width || parseInt(Chart.helpers.getStyle(context.canvas, 'width')) || Chart.helpers.getMaximumWidth(context.canvas);
  		this.height = context.canvas.height || parseInt(Chart.helpers.getStyle(context.canvas, 'height')) || Chart.helpers.getMaximumHeight(context.canvas);
  
  		this.aspectRatio = this.width / this.height;
  
  		if (isNaN(this.aspectRatio) || isFinite(this.aspectRatio) === false) {
  			// If the canvas has no size, try and figure out what the aspect ratio will be.
  			// Some charts prefer square canvases (pie, radar, etc). If that is specified, use that
  			// else use the canvas default ratio of 2
  			this.aspectRatio = config.aspectRatio !== undefined ? config.aspectRatio : 2;
  		}
  
  		// Store the original style of the element so we can set it back
  		this.originalCanvasStyleWidth = context.canvas.style.width;
  		this.originalCanvasStyleHeight = context.canvas.style.height;
  
  		// High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
  		Chart.helpers.retinaScale(this);
  
  		if (config) {
  			this.controller = new Chart.Controller(this);
  		}
  
  		// Always bind this so that if the responsive state changes we still work
  		var _this = this;
  		Chart.helpers.addResizeListener(context.canvas.parentNode, function() {
  			if (_this.controller && _this.controller.config.options.responsive) {
  				_this.controller.resize();
  			}
  		});
  
  		return this.controller ? this.controller : this;
  
  	};
  
  	//Globally expose the defaults to allow for user updating/changing
  	Chart.defaults = {
  		global: {
  			responsive: true,
  			responsiveAnimationDuration: 0,
  			maintainAspectRatio: true,
  			events: ["mousemove", "mouseout", "click", "touchstart", "touchmove"],
  			hover: {
  				onHover: null,
  				mode: 'single',
  				animationDuration: 400
  			},
  			onClick: null,
  			defaultColor: 'rgba(0,0,0,0.1)',
  			defaultFontColor: '#666',
  			defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
  			defaultFontSize: 12,
  			defaultFontStyle: 'normal',
  			showLines: true,
  
  			// Element defaults defined in element extensions
  			elements: {},
  
  			// Legend callback string
  			legendCallback: function(chart) {
  				var text = [];
  				text.push('<ul class="' + chart.id + '-legend">');
  				for (var i = 0; i < chart.data.datasets.length; i++) {
  					text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '"></span>');
  					if (chart.data.datasets[i].label) {
  						text.push(chart.data.datasets[i].label);
  					}
  					text.push('</li>');
  				}
  				text.push('</ul>');
  
  				return text.join("");
  			}
  		}
  	};
  
  	return Chart;
  
  };
  
  },{}],27:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers;
  
  	// The layout service is very self explanatory.  It's responsible for the layout within a chart.
  	// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
  	// It is this service's responsibility of carrying out that layout.
  	Chart.layoutService = {
  		defaults: {},
  
  		// Register a box to a chartInstance. A box is simply a reference to an object that requires layout. eg. Scales, Legend, Plugins.
  		addBox: function(chartInstance, box) {
  			if (!chartInstance.boxes) {
  				chartInstance.boxes = [];
  			}
  			chartInstance.boxes.push(box);
  		},
  
  		removeBox: function(chartInstance, box) {
  			if (!chartInstance.boxes) {
  				return;
  			}
  			chartInstance.boxes.splice(chartInstance.boxes.indexOf(box), 1);
  		},
  
  		// The most important function
  		update: function(chartInstance, width, height) {
  
  			if (!chartInstance) {
  				return;
  			}
  
  			var xPadding = 0;
  			var yPadding = 0;
  
  			var leftBoxes = helpers.where(chartInstance.boxes, function(box) {
  				return box.options.position === "left";
  			});
  			var rightBoxes = helpers.where(chartInstance.boxes, function(box) {
  				return box.options.position === "right";
  			});
  			var topBoxes = helpers.where(chartInstance.boxes, function(box) {
  				return box.options.position === "top";
  			});
  			var bottomBoxes = helpers.where(chartInstance.boxes, function(box) {
  				return box.options.position === "bottom";
  			});
  
  			// Boxes that overlay the chartarea such as the radialLinear scale
  			var chartAreaBoxes = helpers.where(chartInstance.boxes, function(box) {
  				return box.options.position === "chartArea";
  			});
  
  			// Ensure that full width boxes are at the very top / bottom
  			topBoxes.sort(function(a, b) {
  				return (b.options.fullWidth ? 1 : 0) - (a.options.fullWidth ? 1 : 0);
  			});
  			bottomBoxes.sort(function(a, b) {
  				return (a.options.fullWidth ? 1 : 0) - (b.options.fullWidth ? 1 : 0);
  			});
  
  			// Essentially we now have any number of boxes on each of the 4 sides.
  			// Our canvas looks like the following.
  			// The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
  			// B1 is the bottom axis
  			// There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
  			// These locations are single-box locations only, when trying to register a chartArea location that is already taken,
  			// an error will be thrown.
  			//
  			// |----------------------------------------------------|
  			// |                  T1 (Full Width)                   |
  			// |----------------------------------------------------|
  			// |    |    |                 T2                  |    |
  			// |    |----|-------------------------------------|----|
  			// |    |    | C1 |                           | C2 |    |
  			// |    |    |----|                           |----|    |
  			// |    |    |                                     |    |
  			// | L1 | L2 |           ChartArea (C0)            | R1 |
  			// |    |    |                                     |    |
  			// |    |    |----|                           |----|    |
  			// |    |    | C3 |                           | C4 |    |
  			// |    |----|-------------------------------------|----|
  			// |    |    |                 B1                  |    |
  			// |----------------------------------------------------|
  			// |                  B2 (Full Width)                   |
  			// |----------------------------------------------------|
  			//
  			// What we do to find the best sizing, we do the following
  			// 1. Determine the minimum size of the chart area.
  			// 2. Split the remaining width equally between each vertical axis
  			// 3. Split the remaining height equally between each horizontal axis
  			// 4. Give each layout the maximum size it can be. The layout will return it's minimum size
  			// 5. Adjust the sizes of each axis based on it's minimum reported size.
  			// 6. Refit each axis
  			// 7. Position each axis in the final location
  			// 8. Tell the chart the final location of the chart area
  			// 9. Tell any axes that overlay the chart area the positions of the chart area
  
  			// Step 1
  			var chartWidth = width - (2 * xPadding);
  			var chartHeight = height - (2 * yPadding);
  			var chartAreaWidth = chartWidth / 2; // min 50%
  			var chartAreaHeight = chartHeight / 2; // min 50%
  
  			// Step 2
  			var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length);
  
  			// Step 3
  			var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length);
  
  			// Step 4
  			var maxChartAreaWidth = chartWidth;
  			var maxChartAreaHeight = chartHeight;
  			var minBoxSizes = [];
  
  			helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize);
  
  			function getMinimumBoxSize(box) {
  				var minSize;
  				var isHorizontal = box.isHorizontal();
  
  				if (isHorizontal) {
  					minSize = box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight);
  					maxChartAreaHeight -= minSize.height;
  				} else {
  					minSize = box.update(verticalBoxWidth, chartAreaHeight);
  					maxChartAreaWidth -= minSize.width;
  				}
  
  				minBoxSizes.push({
  					horizontal: isHorizontal,
  					minSize: minSize,
  					box: box
  				});
  			}
  
  			// At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could
  			// be if the axes are drawn at their minimum sizes.
  
  			// Steps 5 & 6
  			var totalLeftBoxesWidth = xPadding;
  			var totalRightBoxesWidth = xPadding;
  			var totalTopBoxesHeight = yPadding;
  			var totalBottomBoxesHeight = yPadding;
  
  			// Update, and calculate the left and right margins for the horizontal boxes
  			helpers.each(leftBoxes.concat(rightBoxes), fitBox);
  
  			helpers.each(leftBoxes, function(box) {
  				totalLeftBoxesWidth += box.width;
  			});
  
  			helpers.each(rightBoxes, function(box) {
  				totalRightBoxesWidth += box.width;
  			});
  
  			// Set the Left and Right margins for the horizontal boxes
  			helpers.each(topBoxes.concat(bottomBoxes), fitBox);
  
  			// Function to fit a box
  			function fitBox(box) {
  				var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) {
  					return minBoxSize.box === box;
  				});
  
  				if (minBoxSize) {
  					if (box.isHorizontal()) {
  						var scaleMargin = {
  							left: totalLeftBoxesWidth,
  							right: totalRightBoxesWidth,
  							top: 0,
  							bottom: 0
  						};
  
  						// Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends
  						// on the margin. Sometimes they need to increase in size slightly
  						box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin);
  					} else {
  						box.update(minBoxSize.minSize.width, maxChartAreaHeight);
  					}
  				}
  			}
  
  			// Figure out how much margin is on the top and bottom of the vertical boxes
  			helpers.each(topBoxes, function(box) {
  				totalTopBoxesHeight += box.height;
  			});
  
  			helpers.each(bottomBoxes, function(box) {
  				totalBottomBoxesHeight += box.height;
  			});
  
  			// Let the left layout know the final margin
  			helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox);
  
  			function finalFitVerticalBox(box) {
  				var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) {
  					return minBoxSize.box === box;
  				});
  
  				var scaleMargin = {
  					left: 0,
  					right: 0,
  					top: totalTopBoxesHeight,
  					bottom: totalBottomBoxesHeight
  				};
  
  				if (minBoxSize) {
  					box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin);
  				}
  			}
  
  			// Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
  			totalLeftBoxesWidth = xPadding;
  			totalRightBoxesWidth = xPadding;
  			totalTopBoxesHeight = yPadding;
  			totalBottomBoxesHeight = yPadding;
  
  			helpers.each(leftBoxes, function(box) {
  				totalLeftBoxesWidth += box.width;
  			});
  
  			helpers.each(rightBoxes, function(box) {
  				totalRightBoxesWidth += box.width;
  			});
  
  			helpers.each(topBoxes, function(box) {
  				totalTopBoxesHeight += box.height;
  			});
  			helpers.each(bottomBoxes, function(box) {
  				totalBottomBoxesHeight += box.height;
  			});
  
  			// Figure out if our chart area changed. This would occur if the dataset layout label rotation
  			// changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
  			// without calling `fit` again
  			var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight;
  			var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth;
  
  			if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) {
  				helpers.each(leftBoxes, function(box) {
  					box.height = newMaxChartAreaHeight;
  				});
  
  				helpers.each(rightBoxes, function(box) {
  					box.height = newMaxChartAreaHeight;
  				});
  
  				helpers.each(topBoxes, function(box) {
  					if (!box.options.fullWidth) {
  						box.width = newMaxChartAreaWidth;
  					}
  				});
  
  				helpers.each(bottomBoxes, function(box) {
  					if (!box.options.fullWidth) {
  						box.width = newMaxChartAreaWidth;
  					}
  				});
  
  				maxChartAreaHeight = newMaxChartAreaHeight;
  				maxChartAreaWidth = newMaxChartAreaWidth;
  			}
  
  			// Step 7 - Position the boxes
  			var left = xPadding;
  			var top = yPadding;
  			var right = 0;
  			var bottom = 0;
  
  			helpers.each(leftBoxes.concat(topBoxes), placeBox);
  
  			// Account for chart width and height
  			left += maxChartAreaWidth;
  			top += maxChartAreaHeight;
  
  			helpers.each(rightBoxes, placeBox);
  			helpers.each(bottomBoxes, placeBox);
  
  			function placeBox(box) {
  				if (box.isHorizontal()) {
  					box.left = box.options.fullWidth ? xPadding : totalLeftBoxesWidth;
  					box.right = box.options.fullWidth ? width - xPadding : totalLeftBoxesWidth + maxChartAreaWidth;
  					box.top = top;
  					box.bottom = top + box.height;
  
  					// Move to next point
  					top = box.bottom;
  
  				} else {
  
  					box.left = left;
  					box.right = left + box.width;
  					box.top = totalTopBoxesHeight;
  					box.bottom = totalTopBoxesHeight + maxChartAreaHeight;
  
  					// Move to next point
  					left = box.right;
  				}
  			}
  
  			// Step 8
  			chartInstance.chartArea = {
  				left: totalLeftBoxesWidth,
  				top: totalTopBoxesHeight,
  				right: totalLeftBoxesWidth + maxChartAreaWidth,
  				bottom: totalTopBoxesHeight + maxChartAreaHeight
  			};
  
  			// Step 9
  			helpers.each(chartAreaBoxes, function(box) {
  				box.left = chartInstance.chartArea.left;
  				box.top = chartInstance.chartArea.top;
  				box.right = chartInstance.chartArea.right;
  				box.bottom = chartInstance.chartArea.bottom;
  
  				box.update(maxChartAreaWidth, maxChartAreaHeight);
  			});
  		}
  	};
  };
  
  },{}],28:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers;
  	var noop = helpers.noop;
  
  	Chart.defaults.global.legend = {
  
  		display: true,
  		position: 'top',
  		fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes)
  		reverse: false,
  
  		// a callback that will handle
  		onClick: function(e, legendItem) {
  			var index = legendItem.datasetIndex;
  			var ci = this.chart;
  			var meta = ci.getDatasetMeta(index);
  
  			// See controller.isDatasetVisible comment
  			meta.hidden = meta.hidden === null? !ci.data.datasets[index].hidden : null;
  
  			// We hid a dataset ... rerender the chart
  			ci.update();
  		},
  
  		labels: {
  			boxWidth: 40,
  			padding: 10,
  			// Generates labels shown in the legend
  			// Valid properties to return:
  			// text : text to display
  			// fillStyle : fill of coloured box
  			// strokeStyle: stroke of coloured box
  			// hidden : if this legend item refers to a hidden item
  			// lineCap : cap style for line
  			// lineDash
  			// lineDashOffset :
  			// lineJoin :
  			// lineWidth :
  			generateLabels: function(chart) {
  				var data = chart.data;
  				return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) {
  					return {
  						text: dataset.label,
  						fillStyle: dataset.backgroundColor,
  						hidden: !chart.isDatasetVisible(i),
  						lineCap: dataset.borderCapStyle,
  						lineDash: dataset.borderDash,
  						lineDashOffset: dataset.borderDashOffset,
  						lineJoin: dataset.borderJoinStyle,
  						lineWidth: dataset.borderWidth,
  						strokeStyle: dataset.borderColor,
  
  						// Below is extra data used for toggling the datasets
  						datasetIndex: i
  					};
  				}, this) : [];
  			}
  		}
  	};
  
  	Chart.Legend = Chart.Element.extend({
  
  		initialize: function(config) {
  			helpers.extend(this, config);
  
  			// Contains hit boxes for each dataset (in dataset order)
  			this.legendHitBoxes = [];
  
  			// Are we in doughnut mode which has a different data type
  			this.doughnutMode = false;
  		},
  
  		// These methods are ordered by lifecyle. Utilities then follow.
  		// Any function defined here is inherited by all legend types.
  		// Any function can be extended by the legend type
  
  		beforeUpdate: noop,
  		update: function(maxWidth, maxHeight, margins) {
  
  			// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
  			this.beforeUpdate();
  
  			// Absorb the master measurements
  			this.maxWidth = maxWidth;
  			this.maxHeight = maxHeight;
  			this.margins = margins;
  
  			// Dimensions
  			this.beforeSetDimensions();
  			this.setDimensions();
  			this.afterSetDimensions();
  			// Labels
  			this.beforeBuildLabels();
  			this.buildLabels();
  			this.afterBuildLabels();
  
  			// Fit
  			this.beforeFit();
  			this.fit();
  			this.afterFit();
  			//
  			this.afterUpdate();
  
  			return this.minSize;
  		},
  		afterUpdate: noop,
  
  		//
  
  		beforeSetDimensions: noop,
  		setDimensions: function() {
  			// Set the unconstrained dimension before label rotation
  			if (this.isHorizontal()) {
  				// Reset position before calculating rotation
  				this.width = this.maxWidth;
  				this.left = 0;
  				this.right = this.width;
  			} else {
  				this.height = this.maxHeight;
  
  				// Reset position before calculating rotation
  				this.top = 0;
  				this.bottom = this.height;
  			}
  
  			// Reset padding
  			this.paddingLeft = 0;
  			this.paddingTop = 0;
  			this.paddingRight = 0;
  			this.paddingBottom = 0;
  
  			// Reset minSize
  			this.minSize = {
  				width: 0,
  				height: 0
  			};
  		},
  		afterSetDimensions: noop,
  
  		//
  
  		beforeBuildLabels: noop,
  		buildLabels: function() {
  			this.legendItems = this.options.labels.generateLabels.call(this, this.chart);
  			if(this.options.reverse){
  				this.legendItems.reverse();
  			}
  		},
  		afterBuildLabels: noop,
  
  		//
  
  		beforeFit: noop,
  		fit: function() {
  			var opts = this.options;
  			var labelOpts = opts.labels;
  			var display = opts.display;
  
  			var ctx = this.ctx;
  
  			var globalDefault = Chart.defaults.global,
  				itemOrDefault = helpers.getValueOrDefault,
  				fontSize = itemOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize),
  				fontStyle = itemOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle),
  				fontFamily = itemOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily),
  				labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
  
  			// Reset hit boxes
  			var hitboxes = this.legendHitBoxes = [];
  
  			var minSize = this.minSize;
  			var isHorizontal = this.isHorizontal();
  
  			if (isHorizontal) {
  				minSize.width = this.maxWidth; // fill all the width
  				minSize.height = display ? 10 : 0;
  			} else {
  				minSize.width = display ? 10 : 0;
  				minSize.height = this.maxHeight; // fill all the height
  			}
  
  			// Increase sizes here
  			if (display) {
  				if (isHorizontal) {
  					// Labels
  
  					// Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
  					var lineWidths = this.lineWidths = [0];
  					var totalHeight = this.legendItems.length ? fontSize + (labelOpts.padding) : 0;
  
  					ctx.textAlign = "left";
  					ctx.textBaseline = 'top';
  					ctx.font = labelFont;
  
  					helpers.each(this.legendItems, function(legendItem, i) {
  						var width = labelOpts.boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
  						if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= this.width) {
  							totalHeight += fontSize + (labelOpts.padding);
  							lineWidths[lineWidths.length] = this.left;
  						}
  
  						// Store the hitbox width and height here. Final position will be updated in `draw`
  						hitboxes[i] = {
  							left: 0,
  							top: 0,
  							width: width,
  							height: fontSize
  						};
  
  						lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
  					}, this);
  
  					minSize.height += totalHeight;
  
  				} else {
  					// TODO vertical
  				}
  			}
  
  			this.width = minSize.width;
  			this.height = minSize.height;
  		},
  		afterFit: noop,
  
  		// Shared Methods
  		isHorizontal: function() {
  			return this.options.position === "top" || this.options.position === "bottom";
  		},
  
  		// Actualy draw the legend on the canvas
  		draw: function() {
  			var opts = this.options;
  			var labelOpts = opts.labels;
  			var globalDefault = Chart.defaults.global,
  				lineDefault = globalDefault.elements.line,
  				legendWidth = this.width,
  				lineWidths = this.lineWidths;
  
  			if (opts.display) {
  				var ctx = this.ctx,
  					cursor = {
  						x: this.left + ((legendWidth - lineWidths[0]) / 2),
  						y: this.top + labelOpts.padding,
  						line: 0
  					},
  					itemOrDefault = helpers.getValueOrDefault,
  					fontColor = itemOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor),
  					fontSize = itemOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize),
  					fontStyle = itemOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle),
  					fontFamily = itemOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily),
  					labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
  
  				// Horizontal
  				if (this.isHorizontal()) {
  					// Labels
  					ctx.textAlign = "left";
  					ctx.textBaseline = 'top';
  					ctx.lineWidth = 0.5;
  					ctx.strokeStyle = fontColor; // for strikethrough effect
  					ctx.fillStyle = fontColor; // render in correct colour
  					ctx.font = labelFont;
  
  					var boxWidth = labelOpts.boxWidth,
  						hitboxes = this.legendHitBoxes;
  
  					helpers.each(this.legendItems, function(legendItem, i) {
  						var textWidth = ctx.measureText(legendItem.text).width,
  							width = boxWidth + (fontSize / 2) + textWidth,
  							x = cursor.x,
  							y = cursor.y;
  
  						if (x + width >= legendWidth) {
  							y = cursor.y += fontSize + (labelOpts.padding);
  							cursor.line++;
  							x = cursor.x = this.left + ((legendWidth - lineWidths[cursor.line]) / 2);
  						}
  
  						// Set the ctx for the box
  						ctx.save();
  
  						ctx.fillStyle = itemOrDefault(legendItem.fillStyle, globalDefault.defaultColor);
  						ctx.lineCap = itemOrDefault(legendItem.lineCap, lineDefault.borderCapStyle);
  						ctx.lineDashOffset = itemOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset);
  						ctx.lineJoin = itemOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle);
  						ctx.lineWidth = itemOrDefault(legendItem.lineWidth, lineDefault.borderWidth);
  						ctx.strokeStyle = itemOrDefault(legendItem.strokeStyle, globalDefault.defaultColor);
  
  						if (ctx.setLineDash) {
  							// IE 9 and 10 do not support line dash
  							ctx.setLineDash(itemOrDefault(legendItem.lineDash, lineDefault.borderDash));
  						}
  
  						// Draw the box
  						ctx.strokeRect(x, y, boxWidth, fontSize);
  						ctx.fillRect(x, y, boxWidth, fontSize);
  
  						ctx.restore();
  
  						hitboxes[i].left = x;
  						hitboxes[i].top = y;
  
  						// Fill the actual label
  						ctx.fillText(legendItem.text, boxWidth + (fontSize / 2) + x, y);
  
  						if (legendItem.hidden) {
  							// Strikethrough the text if hidden
  							ctx.beginPath();
  							ctx.lineWidth = 2;
  							ctx.moveTo(boxWidth + (fontSize / 2) + x, y + (fontSize / 2));
  							ctx.lineTo(boxWidth + (fontSize / 2) + x + textWidth, y + (fontSize / 2));
  							ctx.stroke();
  						}
  
  						cursor.x += width + (labelOpts.padding);
  					}, this);
  				} else {
  
  				}
  			}
  		},
  
  		// Handle an event
  		handleEvent: function(e) {
  			var position = helpers.getRelativePosition(e, this.chart.chart),
  				x = position.x,
  				y = position.y,
  				opts = this.options;
  
  			if (x >= this.left && x <= this.right && y >= this.top && y <= this.bottom) {
  				// See if we are touching one of the dataset boxes
  				var lh = this.legendHitBoxes;
  				for (var i = 0; i < lh.length; ++i) {
  					var hitBox = lh[i];
  
  					if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
  						// Touching an element
  						if (opts.onClick) {
  							opts.onClick.call(this, e, this.legendItems[i]);
  						}
  						break;
  					}
  				}
  			}
  		}
  	});
  
  };
  
  },{}],29:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  	var helpers = Chart.helpers;
  
  	// Plugins are stored here
  	Chart.plugins = [];
  	Chart.pluginService = {
  		// Register a new plugin
  		register: function(plugin) {
  			var p = Chart.plugins;
  			if (p.indexOf(plugin) === -1) {
  				p.push(plugin);
  			}
  		},
  
  		// Remove a registered plugin
  		remove: function(plugin) {
  			var p = Chart.plugins;
  			var idx = p.indexOf(plugin);
  			if (idx !== -1) {
  				p.splice(idx, 1);
  			}
  		},
  
  		// Iterate over all plugins
  		notifyPlugins: function(method, args, scope) {
  			helpers.each(Chart.plugins, function(plugin) {
  				if (plugin[method] && typeof plugin[method] === 'function') {
  					plugin[method].apply(scope, args);
  				}
  			}, scope);
  		}
  	};
  
  	var noop = helpers.noop;
  	Chart.PluginBase = Chart.Element.extend({
  		// Plugin methods. All functions are passed the chart instance
  
  		// Called at start of chart init
  		beforeInit: noop,
  
  		// Called at end of chart init
  		afterInit: noop,
  
  		// Called at start of update
  		beforeUpdate: noop,
  
  		// Called at end of update
  		afterUpdate: noop,
  
  		// Called at start of draw
  		beforeDraw: noop,
  
  		// Called at end of draw
  		afterDraw: noop,
  
  		// Called during destroy
  		destroy: noop
  	});
  };
  
  },{}],30:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers;
  
  	Chart.defaults.scale = {
  		display: true,
  		position: "left",
  
  		// grid line settings
  		gridLines: {
  			display: true,
  			color: "rgba(0, 0, 0, 0.1)",
  			lineWidth: 1,
  			drawBorder: true,
  			drawOnChartArea: true,
  			drawTicks: true,
  			tickMarkLength: 10,
  			zeroLineWidth: 1,
  			zeroLineColor: "rgba(0,0,0,0.25)",
  			offsetGridLines: false
  		},
  
  		// scale label
  		scaleLabel: {
  			// actual label
  			labelString: '',
  
  			// display property
  			display: false
  		},
  
  		// label settings
  		ticks: {
  			beginAtZero: false,
  			minRotation: 0,
  			maxRotation: 50,
  			mirror: false,
  			padding: 10,
  			reverse: false,
  			display: true,
  			autoSkip: true,
  			autoSkipPadding: 0,
  			labelOffset: 0,
  			callback: function(value) {
  				return '' + value;
  			}
  		}
  	};
  
  	Chart.Scale = Chart.Element.extend({
  
  		// These methods are ordered by lifecyle. Utilities then follow.
  		// Any function defined here is inherited by all scale types.
  		// Any function can be extended by the scale type
  
  		beforeUpdate: function() {
  			helpers.callCallback(this.options.beforeUpdate, [this]);
  		},
  		update: function(maxWidth, maxHeight, margins) {
  
  			// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
  			this.beforeUpdate();
  
  			// Absorb the master measurements
  			this.maxWidth = maxWidth;
  			this.maxHeight = maxHeight;
  			this.margins = helpers.extend({
  				left: 0,
  				right: 0,
  				top: 0,
  				bottom: 0
  			}, margins);
  
  			// Dimensions
  			this.beforeSetDimensions();
  			this.setDimensions();
  			this.afterSetDimensions();
  
  			// Data min/max
  			this.beforeDataLimits();
  			this.determineDataLimits();
  			this.afterDataLimits();
  
  			// Ticks
  			this.beforeBuildTicks();
  			this.buildTicks();
  			this.afterBuildTicks();
  
  			this.beforeTickToLabelConversion();
  			this.convertTicksToLabels();
  			this.afterTickToLabelConversion();
  
  			// Tick Rotation
  			this.beforeCalculateTickRotation();
  			this.calculateTickRotation();
  			this.afterCalculateTickRotation();
  			// Fit
  			this.beforeFit();
  			this.fit();
  			this.afterFit();
  			//
  			this.afterUpdate();
  
  			return this.minSize;
  
  		},
  		afterUpdate: function() {
  			helpers.callCallback(this.options.afterUpdate, [this]);
  		},
  
  		//
  
  		beforeSetDimensions: function() {
  			helpers.callCallback(this.options.beforeSetDimensions, [this]);
  		},
  		setDimensions: function() {
  			// Set the unconstrained dimension before label rotation
  			if (this.isHorizontal()) {
  				// Reset position before calculating rotation
  				this.width = this.maxWidth;
  				this.left = 0;
  				this.right = this.width;
  			} else {
  				this.height = this.maxHeight;
  
  				// Reset position before calculating rotation
  				this.top = 0;
  				this.bottom = this.height;
  			}
  
  			// Reset padding
  			this.paddingLeft = 0;
  			this.paddingTop = 0;
  			this.paddingRight = 0;
  			this.paddingBottom = 0;
  		},
  		afterSetDimensions: function() {
  			helpers.callCallback(this.options.afterSetDimensions, [this]);
  		},
  
  		// Data limits
  		beforeDataLimits: function() {
  			helpers.callCallback(this.options.beforeDataLimits, [this]);
  		},
  		determineDataLimits: helpers.noop,
  		afterDataLimits: function() {
  			helpers.callCallback(this.options.afterDataLimits, [this]);
  		},
  
  		//
  		beforeBuildTicks: function() {
  			helpers.callCallback(this.options.beforeBuildTicks, [this]);
  		},
  		buildTicks: helpers.noop,
  		afterBuildTicks: function() {
  			helpers.callCallback(this.options.afterBuildTicks, [this]);
  		},
  
  		beforeTickToLabelConversion: function() {
  			helpers.callCallback(this.options.beforeTickToLabelConversion, [this]);
  		},
  		convertTicksToLabels: function() {
  			// Convert ticks to strings
  			this.ticks = this.ticks.map(function(numericalTick, index, ticks) {
  					if (this.options.ticks.userCallback) {
  						return this.options.ticks.userCallback(numericalTick, index, ticks);
  					}
  					return this.options.ticks.callback(numericalTick, index, ticks);
  				},
  				this);
  		},
  		afterTickToLabelConversion: function() {
  			helpers.callCallback(this.options.afterTickToLabelConversion, [this]);
  		},
  
  		//
  
  		beforeCalculateTickRotation: function() {
  			helpers.callCallback(this.options.beforeCalculateTickRotation, [this]);
  		},
  		calculateTickRotation: function() {
  			var context = this.ctx;
  			var globalDefaults = Chart.defaults.global;
  			var optionTicks = this.options.ticks;
  
  			//Get the width of each grid by calculating the difference
  			//between x offsets between 0 and 1.
  			var tickFontSize = helpers.getValueOrDefault(optionTicks.fontSize, globalDefaults.defaultFontSize);
  			var tickFontStyle = helpers.getValueOrDefault(optionTicks.fontStyle, globalDefaults.defaultFontStyle);
  			var tickFontFamily = helpers.getValueOrDefault(optionTicks.fontFamily, globalDefaults.defaultFontFamily);
  			var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
  			context.font = tickLabelFont;
  
  			var firstWidth = context.measureText(this.ticks[0]).width;
  			var lastWidth = context.measureText(this.ticks[this.ticks.length - 1]).width;
  			var firstRotated;
  
  			this.labelRotation = optionTicks.minRotation || 0;
  			this.paddingRight = 0;
  			this.paddingLeft = 0;
  
  			if (this.options.display) {
  				if (this.isHorizontal()) {
  					this.paddingRight = lastWidth / 2 + 3;
  					this.paddingLeft = firstWidth / 2 + 3;
  
  					if (!this.longestTextCache) {
  						this.longestTextCache = {};
  					}
  					var originalLabelWidth = helpers.longestText(context, tickLabelFont, this.ticks, this.longestTextCache);
  					var labelWidth = originalLabelWidth;
  					var cosRotation;
  					var sinRotation;
  
  					// Allow 3 pixels x2 padding either side for label readability
  					// only the index matters for a dataset scale, but we want a consistent interface between scales
  					var tickWidth = this.getPixelForTick(1) - this.getPixelForTick(0) - 6;
  
  					//Max label rotation can be set or default to 90 - also act as a loop counter
  					while (labelWidth > tickWidth && this.labelRotation < optionTicks.maxRotation) {
  						cosRotation = Math.cos(helpers.toRadians(this.labelRotation));
  						sinRotation = Math.sin(helpers.toRadians(this.labelRotation));
  
  						firstRotated = cosRotation * firstWidth;
  
  						// We're right aligning the text now.
  						if (firstRotated + tickFontSize / 2 > this.yLabelWidth) {
  							this.paddingLeft = firstRotated + tickFontSize / 2;
  						}
  
  						this.paddingRight = tickFontSize / 2;
  
  						if (sinRotation * originalLabelWidth > this.maxHeight) {
  							// go back one step
  							this.labelRotation--;
  							break;
  						}
  
  						this.labelRotation++;
  						labelWidth = cosRotation * originalLabelWidth;
  					}
  				}
  			}
  
  			if (this.margins) {
  				this.paddingLeft = Math.max(this.paddingLeft - this.margins.left, 0);
  				this.paddingRight = Math.max(this.paddingRight - this.margins.right, 0);
  			}
  		},
  		afterCalculateTickRotation: function() {
  			helpers.callCallback(this.options.afterCalculateTickRotation, [this]);
  		},
  
  		//
  
  		beforeFit: function() {
  			helpers.callCallback(this.options.beforeFit, [this]);
  		},
  		fit: function() {
  			// Reset
  			var minSize = this.minSize = {
  				width: 0,
  				height: 0
  			};
  
  			var opts = this.options;
  			var globalDefaults = Chart.defaults.global;
  			var tickOpts = opts.ticks;
  			var scaleLabelOpts = opts.scaleLabel;
  			var display = opts.display;
  			var isHorizontal = this.isHorizontal();
  
  			var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
  			var tickFontStyle = helpers.getValueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle);
  			var tickFontFamily = helpers.getValueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily);
  			var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
  
  			var scaleLabelFontSize = helpers.getValueOrDefault(scaleLabelOpts.fontSize, globalDefaults.defaultFontSize);
  			var scaleLabelFontStyle = helpers.getValueOrDefault(scaleLabelOpts.fontStyle, globalDefaults.defaultFontStyle);
  			var scaleLabelFontFamily = helpers.getValueOrDefault(scaleLabelOpts.fontFamily, globalDefaults.defaultFontFamily);
  			var scaleLabelFont = helpers.fontString(scaleLabelFontSize, scaleLabelFontStyle, scaleLabelFontFamily);
  
  			var tickMarkLength = opts.gridLines.tickMarkLength;
  
  			// Width
  			if (isHorizontal) {
  				// subtract the margins to line up with the chartArea if we are a full width scale
  				minSize.width = this.isFullWidth() ? this.maxWidth - this.margins.left - this.margins.right : this.maxWidth;
  			} else {
  				minSize.width = display ? tickMarkLength : 0;
  			}
  
  			// height
  			if (isHorizontal) {
  				minSize.height = display ? tickMarkLength : 0;
  			} else {
  				minSize.height = this.maxHeight; // fill all the height
  			}
  
  			// Are we showing a title for the scale?
  			if (scaleLabelOpts.display && display) {
  				if (isHorizontal) {
  					minSize.height += (scaleLabelFontSize * 1.5);
  				} else {
  					minSize.width += (scaleLabelFontSize * 1.5);
  				}
  			}
  
  			if (tickOpts.display && display) {
  				// Don't bother fitting the ticks if we are not showing them
  				if (!this.longestTextCache) {
  					this.longestTextCache = {};
  				}
  
  				var largestTextWidth = helpers.longestText(this.ctx, tickLabelFont, this.ticks, this.longestTextCache);
  
  				if (isHorizontal) {
  					// A horizontal axis is more constrained by the height.
  					this.longestLabelWidth = largestTextWidth;
  
  					// TODO - improve this calculation
  					var labelHeight = (Math.sin(helpers.toRadians(this.labelRotation)) * this.longestLabelWidth) + 1.5 * tickFontSize;
  
  					minSize.height = Math.min(this.maxHeight, minSize.height + labelHeight);
  					this.ctx.font = tickLabelFont;
  
  					var firstLabelWidth = this.ctx.measureText(this.ticks[0]).width;
  					var lastLabelWidth = this.ctx.measureText(this.ticks[this.ticks.length - 1]).width;
  
  					// Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned which means that the right padding is dominated
  					// by the font height
  					var cosRotation = Math.cos(helpers.toRadians(this.labelRotation));
  					var sinRotation = Math.sin(helpers.toRadians(this.labelRotation));
  					this.paddingLeft = this.labelRotation !== 0 ? (cosRotation * firstLabelWidth) + 3 : firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges
  					this.paddingRight = this.labelRotation !== 0 ? (sinRotation * (tickFontSize / 2)) + 3 : lastLabelWidth / 2 + 3; // when rotated
  				} else {
  					// A vertical axis is more constrained by the width. Labels are the dominant factor here, so get that length first
  					var maxLabelWidth = this.maxWidth - minSize.width;
  
  					// Account for padding
  					var mirror = tickOpts.mirror;
  					if (!mirror) {
  						largestTextWidth += this.options.ticks.padding;
  					} else {
  						// If mirrored text is on the inside so don't expand
  						largestTextWidth = 0;
  					}
  
  					if (largestTextWidth < maxLabelWidth) {
  						// We don't need all the room
  						minSize.width += largestTextWidth;
  					} else {
  						// Expand to max size
  						minSize.width = this.maxWidth;
  					}
  
  					this.paddingTop = tickFontSize / 2;
  					this.paddingBottom = tickFontSize / 2;
  				}
  			}
  
  			if (this.margins) {
  				this.paddingLeft = Math.max(this.paddingLeft - this.margins.left, 0);
  				this.paddingTop = Math.max(this.paddingTop - this.margins.top, 0);
  				this.paddingRight = Math.max(this.paddingRight - this.margins.right, 0);
  				this.paddingBottom = Math.max(this.paddingBottom - this.margins.bottom, 0);
  			}
  
  			this.width = minSize.width;
  			this.height = minSize.height;
  
  		},
  		afterFit: function() {
  			helpers.callCallback(this.options.afterFit, [this]);
  		},
  
  		// Shared Methods
  		isHorizontal: function() {
  			return this.options.position === "top" || this.options.position === "bottom";
  		},
  		isFullWidth: function() {
  			return (this.options.fullWidth);
  		},
  
  		// Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not
  		getRightValue: function getRightValue(rawValue) {
  			// Null and undefined values first
  			if (rawValue === null || typeof(rawValue) === 'undefined') {
  				return NaN;
  			}
  			// isNaN(object) returns true, so make sure NaN is checking for a number
  			if (typeof(rawValue) === 'number' && isNaN(rawValue)) {
  				return NaN;
  			}
  			// If it is in fact an object, dive in one more level
  			if (typeof(rawValue) === "object") {
  				if ((rawValue instanceof Date) || (rawValue.isValid)) {
  					return rawValue;
  				} else {
  					return getRightValue(this.isHorizontal() ? rawValue.x : rawValue.y);
  				}
  			}
  
  			// Value is good, return it
  			return rawValue;
  		},
  
  		// Used to get the value to display in the tooltip for the data at the given index
  		// function getLabelForIndex(index, datasetIndex)
  		getLabelForIndex: helpers.noop,
  
  		// Used to get data value locations.  Value can either be an index or a numerical value
  		getPixelForValue: helpers.noop,
  
  		// Used to get the data value from a given pixel. This is the inverse of getPixelForValue
  		getValueForPixel: helpers.noop,
  
  		// Used for tick location, should
  		getPixelForTick: function(index, includeOffset) {
  			if (this.isHorizontal()) {
  				var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
  				var tickWidth = innerWidth / Math.max((this.ticks.length - ((this.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
  				var pixel = (tickWidth * index) + this.paddingLeft;
  
  				if (includeOffset) {
  					pixel += tickWidth / 2;
  				}
  
  				var finalVal = this.left + Math.round(pixel);
  				finalVal += this.isFullWidth() ? this.margins.left : 0;
  				return finalVal;
  			} else {
  				var innerHeight = this.height - (this.paddingTop + this.paddingBottom);
  				return this.top + (index * (innerHeight / (this.ticks.length - 1)));
  			}
  		},
  
  		// Utility for getting the pixel location of a percentage of scale
  		getPixelForDecimal: function(decimal /*, includeOffset*/ ) {
  			if (this.isHorizontal()) {
  				var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
  				var valueOffset = (innerWidth * decimal) + this.paddingLeft;
  
  				var finalVal = this.left + Math.round(valueOffset);
  				finalVal += this.isFullWidth() ? this.margins.left : 0;
  				return finalVal;
  			} else {
  				return this.top + (decimal * this.height);
  			}
  		},
  
  		getBasePixel: function() {
  			var me = this;
  			var min = me.min;
  			var max = me.max;
  
  			return me.getPixelForValue(
  				me.beginAtZero? 0:
  				min < 0 && max < 0? max :
  				min > 0 && max > 0? min :
  				0);
  		},
  
  		// Actualy draw the scale on the canvas
  		// @param {rectangle} chartArea : the area of the chart to draw full grid lines on
  		draw: function(chartArea) {
  			var options = this.options;
  			if (!options.display) {
  				return;
  			}
  
  			var context = this.ctx;
  			var globalDefaults = Chart.defaults.global;
  			var optionTicks = options.ticks;
  			var gridLines = options.gridLines;
  			var scaleLabel = options.scaleLabel;
  
  			var setContextLineSettings;
  			var isRotated = this.labelRotation !== 0;
  			var skipRatio;
  			var scaleLabelX;
  			var scaleLabelY;
  			var useAutoskipper = optionTicks.autoSkip;
  
  			// figure out the maximum number of gridlines to show
  			var maxTicks;
  			if (optionTicks.maxTicksLimit) {
  				maxTicks = optionTicks.maxTicksLimit;
  			}
  
  			var tickFontColor = helpers.getValueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor);
  			var tickFontSize = helpers.getValueOrDefault(optionTicks.fontSize, globalDefaults.defaultFontSize);
  			var tickFontStyle = helpers.getValueOrDefault(optionTicks.fontStyle, globalDefaults.defaultFontStyle);
  			var tickFontFamily = helpers.getValueOrDefault(optionTicks.fontFamily, globalDefaults.defaultFontFamily);
  			var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
  			var tl = gridLines.tickMarkLength;
  
  			var scaleLabelFontColor = helpers.getValueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor);
  			var scaleLabelFontSize = helpers.getValueOrDefault(scaleLabel.fontSize, globalDefaults.defaultFontSize);
  			var scaleLabelFontStyle = helpers.getValueOrDefault(scaleLabel.fontStyle, globalDefaults.defaultFontStyle);
  			var scaleLabelFontFamily = helpers.getValueOrDefault(scaleLabel.fontFamily, globalDefaults.defaultFontFamily);
  			var scaleLabelFont = helpers.fontString(scaleLabelFontSize, scaleLabelFontStyle, scaleLabelFontFamily);
  
  			var labelRotationRadians = helpers.toRadians(this.labelRotation);
  			var cosRotation = Math.cos(labelRotationRadians);
  			var sinRotation = Math.sin(labelRotationRadians);
  			var longestRotatedLabel = this.longestLabelWidth * cosRotation;
  			var rotatedLabelHeight = tickFontSize * sinRotation;
  
  			// Make sure we draw text in the correct color and font
  			context.fillStyle = tickFontColor;
  
  			if (this.isHorizontal()) {
  				setContextLineSettings = true;
  				var yTickStart = options.position === "bottom" ? this.top : this.bottom - tl;
  				var yTickEnd = options.position === "bottom" ? this.top + tl : this.bottom;
  				skipRatio = false;
  
                  // Only calculate the skip ratio with the half width of longestRotateLabel if we got an actual rotation
                  // See #2584
                  if (isRotated) {
                      longestRotatedLabel /= 2;
                  }
  
  				if ((longestRotatedLabel + optionTicks.autoSkipPadding) * this.ticks.length > (this.width - (this.paddingLeft + this.paddingRight))) {
  					skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * this.ticks.length) / (this.width - (this.paddingLeft + this.paddingRight)));
  				}
  
  				// if they defined a max number of optionTicks,
  				// increase skipRatio until that number is met
  				if (maxTicks && this.ticks.length > maxTicks) {
  					while (!skipRatio || this.ticks.length / (skipRatio || 1) > maxTicks) {
  						if (!skipRatio) {
  							skipRatio = 1;
  						}
  						skipRatio += 1;
  					}
  				}
  
  				if (!useAutoskipper) {
  					skipRatio = false;
  				}
  
  				helpers.each(this.ticks, function (label, index) {
  					// Blank optionTicks
  					var isLastTick = this.ticks.length === index + 1;
  
  					// Since we always show the last tick,we need may need to hide the last shown one before
  					var shouldSkip = (skipRatio > 1 && index % skipRatio > 0) || (index % skipRatio === 0 && index + skipRatio >= this.ticks.length);
  					if (shouldSkip && !isLastTick || (label === undefined || label === null)) {
  						return;
  					}
  					var xLineValue = this.getPixelForTick(index); // xvalues for grid lines
  					var xLabelValue = this.getPixelForTick(index, gridLines.offsetGridLines); // x values for optionTicks (need to consider offsetLabel option)
  
  					if (gridLines.display) {
  						if (index === (typeof this.zeroLineIndex !== 'undefined' ? this.zeroLineIndex : 0)) {
  							// Draw the first index specially
  							context.lineWidth = gridLines.zeroLineWidth;
  							context.strokeStyle = gridLines.zeroLineColor;
  							setContextLineSettings = true; // reset next time
  						} else if (setContextLineSettings) {
  							context.lineWidth = gridLines.lineWidth;
  							context.strokeStyle = gridLines.color;
  							setContextLineSettings = false;
  						}
  
  						xLineValue += helpers.aliasPixel(context.lineWidth);
  
  						// Draw the label area
  						context.beginPath();
  
  						if (gridLines.drawTicks) {
  							context.moveTo(xLineValue, yTickStart);
  							context.lineTo(xLineValue, yTickEnd);
  						}
  
  						// Draw the chart area
  						if (gridLines.drawOnChartArea) {
  							context.moveTo(xLineValue, chartArea.top);
  							context.lineTo(xLineValue, chartArea.bottom);
  						}
  
  						// Need to stroke in the loop because we are potentially changing line widths & colours
  						context.stroke();
  					}
  
  					if (optionTicks.display) {
  						context.save();
  						context.translate(xLabelValue + optionTicks.labelOffset, (isRotated) ? this.top + 12 : options.position === "top" ? this.bottom - tl : this.top + tl);
  						context.rotate(labelRotationRadians * -1);
  						context.font = tickLabelFont;
  						context.textAlign = (isRotated) ? "right" : "center";
  						context.textBaseline = (isRotated) ? "middle" : options.position === "top" ? "bottom" : "top";
  						context.fillText(label, 0, 0);
  						context.restore();
  					}
  				}, this);
  
  				if (scaleLabel.display) {
  					// Draw the scale label
  					context.textAlign = "center";
  					context.textBaseline = 'middle';
  					context.fillStyle = scaleLabelFontColor; // render in correct colour
  					context.font = scaleLabelFont;
  
  					scaleLabelX = this.left + ((this.right - this.left) / 2); // midpoint of the width
  					scaleLabelY = options.position === 'bottom' ? this.bottom - (scaleLabelFontSize / 2) : this.top + (scaleLabelFontSize / 2);
  
  					context.fillText(scaleLabel.labelString, scaleLabelX, scaleLabelY);
  				}
  
  			} else {
  				setContextLineSettings = true;
  				var xTickStart = options.position === "right" ? this.left : this.right - 5;
  				var xTickEnd = options.position === "right" ? this.left + 5 : this.right;
  
  				helpers.each(this.ticks, function (label, index) {
  					// If the callback returned a null or undefined value, do not draw this line
  					if (label === undefined || label === null) {
  						return;
  					}
  
  					var yLineValue = this.getPixelForTick(index); // xvalues for grid lines
  
  					if (gridLines.display) {
  						if (index === (typeof this.zeroLineIndex !== 'undefined' ? this.zeroLineIndex : 0)) {
  							// Draw the first index specially
  							context.lineWidth = gridLines.zeroLineWidth;
  							context.strokeStyle = gridLines.zeroLineColor;
  							setContextLineSettings = true; // reset next time
  						} else if (setContextLineSettings) {
  							context.lineWidth = gridLines.lineWidth;
  							context.strokeStyle = gridLines.color;
  							setContextLineSettings = false;
  						}
  
  						yLineValue += helpers.aliasPixel(context.lineWidth);
  
  						// Draw the label area
  						context.beginPath();
  
  						if (gridLines.drawTicks) {
  							context.moveTo(xTickStart, yLineValue);
  							context.lineTo(xTickEnd, yLineValue);
  						}
  
  						// Draw the chart area
  						if (gridLines.drawOnChartArea) {
  							context.moveTo(chartArea.left, yLineValue);
  							context.lineTo(chartArea.right, yLineValue);
  						}
  
  						// Need to stroke in the loop because we are potentially changing line widths & colours
  						context.stroke();
  					}
  
  					if (optionTicks.display) {
  						var xLabelValue;
  						var yLabelValue = this.getPixelForTick(index, gridLines.offsetGridLines); // x values for optionTicks (need to consider offsetLabel option)
  
  						context.save();
  
  						if (options.position === "left") {
  							if (optionTicks.mirror) {
  								xLabelValue = this.right + optionTicks.padding;
  								context.textAlign = "left";
  							} else {
  								xLabelValue = this.right - optionTicks.padding;
  								context.textAlign = "right";
  							}
  						} else {
  							// right side
  							if (optionTicks.mirror) {
  								xLabelValue = this.left - optionTicks.padding;
  								context.textAlign = "right";
  							} else {
  								xLabelValue = this.left + optionTicks.padding;
  								context.textAlign = "left";
  							}
  						}
  
  						context.translate(xLabelValue, yLabelValue + optionTicks.labelOffset);
  						context.rotate(labelRotationRadians * -1);
  						context.font = tickLabelFont;
  						context.textBaseline = "middle";
  						context.fillText(label, 0, 0);
  						context.restore();
  					}
  				}, this);
  
  				if (scaleLabel.display) {
  					// Draw the scale label
  					scaleLabelX = options.position === 'left' ? this.left + (scaleLabelFontSize / 2) : this.right - (scaleLabelFontSize / 2);
  					scaleLabelY = this.top + ((this.bottom - this.top) / 2);
  					var rotation = options.position === 'left' ? -0.5 * Math.PI : 0.5 * Math.PI;
  
  					context.save();
  					context.translate(scaleLabelX, scaleLabelY);
  					context.rotate(rotation);
  					context.textAlign = "center";
  					context.fillStyle = scaleLabelFontColor; // render in correct colour
  					context.font = scaleLabelFont;
  					context.textBaseline = 'middle';
  					context.fillText(scaleLabel.labelString, 0, 0);
  					context.restore();
  				}
  			}
  
  			if (gridLines.drawBorder) {
  				// Draw the line at the edge of the axis
  				context.lineWidth = gridLines.lineWidth;
  				context.strokeStyle = gridLines.color;
  				var x1 = this.left,
  					x2 = this.right,
  					y1 = this.top,
  					y2 = this.bottom;
  
  				var aliasPixel = helpers.aliasPixel(context.lineWidth);
  				if (this.isHorizontal()) {
  					y1 = y2 = options.position === 'top' ? this.bottom : this.top;
  					y1 += aliasPixel;
  					y2 += aliasPixel;
  				} else {
  					x1 = x2 = options.position === 'left' ? this.right : this.left;
  					x1 += aliasPixel;
  					x2 += aliasPixel;
  				}
  
  				context.beginPath();
  				context.moveTo(x1, y1);
  				context.lineTo(x2, y2);
  				context.stroke();
  			}
  		}
  	});
  };
  
  },{}],31:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers;
  
  	Chart.scaleService = {
  		// Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
  		// use the new chart options to grab the correct scale
  		constructors: {},
  		// Use a registration function so that we can move to an ES6 map when we no longer need to support
  		// old browsers
  
  		// Scale config defaults
  		defaults: {},
  		registerScaleType: function(type, scaleConstructor, defaults) {
  			this.constructors[type] = scaleConstructor;
  			this.defaults[type] = helpers.clone(defaults);
  		},
  		getScaleConstructor: function(type) {
  			return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;
  		},
  		getScaleDefaults: function(type) {
  			// Return the scale defaults merged with the global settings so that we always use the latest ones
  			return this.defaults.hasOwnProperty(type) ? helpers.scaleMerge(Chart.defaults.scale, this.defaults[type]) : {};
  		},
  		updateScaleDefaults: function(type, additions) {
  			var defaults = this.defaults;
  			if (defaults.hasOwnProperty(type)) {
  				defaults[type] = helpers.extend(defaults[type], additions);
  			}
  		},
  		addScalesToLayout: function(chartInstance) {
  			// Adds each scale to the chart.boxes array to be sized accordingly
  			helpers.each(chartInstance.scales, function(scale) {
  				Chart.layoutService.addBox(chartInstance, scale);
  			});
  		}
  	};
  };
  },{}],32:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers;
  
  	Chart.defaults.global.title = {
  		display: false,
  		position: 'top',
  		fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes)
  
  		fontStyle: 'bold',
  		padding: 10,
  
  		// actual title
  		text: ''
  	};
  
  	var noop = helpers.noop;
  	Chart.Title = Chart.Element.extend({
  
  		initialize: function(config) {
  			helpers.extend(this, config);
  			this.options = helpers.configMerge(Chart.defaults.global.title, config.options);
  
  			// Contains hit boxes for each dataset (in dataset order)
  			this.legendHitBoxes = [];
  		},
  
  		// These methods are ordered by lifecyle. Utilities then follow.
  
  		beforeUpdate: noop,
  		update: function(maxWidth, maxHeight, margins) {
  
  			// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
  			this.beforeUpdate();
  
  			// Absorb the master measurements
  			this.maxWidth = maxWidth;
  			this.maxHeight = maxHeight;
  			this.margins = margins;
  
  			// Dimensions
  			this.beforeSetDimensions();
  			this.setDimensions();
  			this.afterSetDimensions();
  			// Labels
  			this.beforeBuildLabels();
  			this.buildLabels();
  			this.afterBuildLabels();
  
  			// Fit
  			this.beforeFit();
  			this.fit();
  			this.afterFit();
  			//
  			this.afterUpdate();
  
  			return this.minSize;
  
  		},
  		afterUpdate: noop,
  
  		//
  
  		beforeSetDimensions: noop,
  		setDimensions: function() {
  			// Set the unconstrained dimension before label rotation
  			if (this.isHorizontal()) {
  				// Reset position before calculating rotation
  				this.width = this.maxWidth;
  				this.left = 0;
  				this.right = this.width;
  			} else {
  				this.height = this.maxHeight;
  
  				// Reset position before calculating rotation
  				this.top = 0;
  				this.bottom = this.height;
  			}
  
  			// Reset padding
  			this.paddingLeft = 0;
  			this.paddingTop = 0;
  			this.paddingRight = 0;
  			this.paddingBottom = 0;
  
  			// Reset minSize
  			this.minSize = {
  				width: 0,
  				height: 0
  			};
  		},
  		afterSetDimensions: noop,
  
  		//
  
  		beforeBuildLabels: noop,
  		buildLabels: noop,
  		afterBuildLabels: noop,
  
  		//
  
  		beforeFit: noop,
  		fit: function() {
  
  			var _this = this,
  				ctx = _this.ctx,
  				valueOrDefault = helpers.getValueOrDefault,
  				opts = _this.options,
  				globalDefaults = Chart.defaults.global,
  				display = opts.display,
  				fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize),
  				minSize = _this.minSize;
  
  			if (_this.isHorizontal()) {
  				minSize.width = _this.maxWidth; // fill all the width
  				minSize.height = display ? fontSize + (opts.padding * 2) : 0;
  			} else {
  				minSize.width = display ? fontSize + (opts.padding * 2) : 0;
  				minSize.height = _this.maxHeight; // fill all the height
  			}
  
  			_this.width = minSize.width;
  			_this.height = minSize.height;
  
  		},
  		afterFit: noop,
  
  		// Shared Methods
  		isHorizontal: function() {
  			var pos = this.options.position;
  			return pos === "top" || pos === "bottom";
  		},
  
  		// Actualy draw the title block on the canvas
  		draw: function() {
  			var _this = this,
  				ctx = _this.ctx,
  				valueOrDefault = helpers.getValueOrDefault,
  				opts = _this.options,
  				globalDefaults = Chart.defaults.global;
  
  			if (opts.display) {
  				var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize),
  					fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle),
  					fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily),
  					titleFont = helpers.fontString(fontSize, fontStyle, fontFamily),
  					rotation = 0,
  					titleX, 
  					titleY,
  					top = _this.top,
  					left = _this.left,
  					bottom = _this.bottom,
  					right = _this.right;
  
  				ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour
  				ctx.font = titleFont;
  
  				// Horizontal
  				if (_this.isHorizontal()) {
  					titleX = left + ((right - left) / 2); // midpoint of the width
  					titleY = top + ((bottom - top) / 2); // midpoint of the height
  				} else {
  					titleX = opts.position === 'left' ? left + (fontSize / 2) : right - (fontSize / 2);
  					titleY = top + ((bottom - top) / 2);
  					rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
  				}
  
  				ctx.save();
  				ctx.translate(titleX, titleY);
  				ctx.rotate(rotation);
  				ctx.textAlign = 'center';
  				ctx.textBaseline = 'middle';
  				ctx.fillText(opts.text, 0, 0);
  				ctx.restore();
  			}
  		}
  	});
  };
  },{}],33:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers;
  
  	Chart.defaults.global.tooltips = {
  		enabled: true,
  		custom: null,
  		mode: 'single',
  		backgroundColor: "rgba(0,0,0,0.8)",
  		titleFontStyle: "bold",
  		titleSpacing: 2,
  		titleMarginBottom: 6,
  		titleColor: "#fff",
  		titleAlign: "left",
  		bodySpacing: 2,
  		bodyColor: "#fff",
  		bodyAlign: "left",
  		footerFontStyle: "bold",
  		footerSpacing: 2,
  		footerMarginTop: 6,
  		footerColor: "#fff",
  		footerAlign: "left",
  		yPadding: 6,
  		xPadding: 6,
  		yAlign : 'center',
  		xAlign : 'center',
  		caretSize: 5,
  		cornerRadius: 6,
  		multiKeyBackground: '#fff',
  		callbacks: {
  			// Args are: (tooltipItems, data)
  			beforeTitle: helpers.noop,
  			title: function(tooltipItems, data) {
  				// Pick first xLabel for now
  				var title = '';
  
  				if (tooltipItems.length > 0) {
  					if (tooltipItems[0].xLabel) {
  						title = tooltipItems[0].xLabel;
  					} else if (data.labels.length > 0 && tooltipItems[0].index < data.labels.length) {
  						title = data.labels[tooltipItems[0].index];
  					}
  				}
  
  				return title;
  			},
  			afterTitle: helpers.noop,
  
  			// Args are: (tooltipItems, data)
  			beforeBody: helpers.noop,
  
  			// Args are: (tooltipItem, data)
  			beforeLabel: helpers.noop,
  			label: function(tooltipItem, data) {
  				var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || '';
  				return datasetLabel + ': ' + tooltipItem.yLabel;
  			},
  			afterLabel: helpers.noop,
  
  			// Args are: (tooltipItems, data)
  			afterBody: helpers.noop,
  
  			// Args are: (tooltipItems, data)
  			beforeFooter: helpers.noop,
  			footer: helpers.noop,
  			afterFooter: helpers.noop
  		}
  	};
  
  	// Helper to push or concat based on if the 2nd parameter is an array or not
  	function pushOrConcat(base, toPush) {
  		if (toPush) {
  			if (helpers.isArray(toPush)) {
  				base = base.concat(toPush);
  			} else {
  				base.push(toPush);
  			}
  		}
  
  		return base;
  	}
  
  	Chart.Tooltip = Chart.Element.extend({
  		initialize: function() {
  			var globalDefaults = Chart.defaults.global;
  			var options = this._options;
  			var tooltips = options.tooltips;
  
  			helpers.extend(this, {
  				_model: {
  					// Positioning
  					xPadding: tooltips.xPadding,
  					yPadding: tooltips.yPadding,
  					xAlign : tooltips.yAlign,
  					yAlign : tooltips.xAlign,
  
  					// Body
  					bodyColor: tooltips.bodyColor,
  					_bodyFontFamily: helpers.getValueOrDefault(tooltips.bodyFontFamily, globalDefaults.defaultFontFamily),
  					_bodyFontStyle: helpers.getValueOrDefault(tooltips.bodyFontStyle, globalDefaults.defaultFontStyle),
  					_bodyAlign: tooltips.bodyAlign,
  					bodyFontSize: helpers.getValueOrDefault(tooltips.bodyFontSize, globalDefaults.defaultFontSize),
  					bodySpacing: tooltips.bodySpacing,
  
  					// Title
  					titleColor: tooltips.titleColor,
  					_titleFontFamily: helpers.getValueOrDefault(tooltips.titleFontFamily, globalDefaults.defaultFontFamily),
  					_titleFontStyle: helpers.getValueOrDefault(tooltips.titleFontStyle, globalDefaults.defaultFontStyle),
  					titleFontSize: helpers.getValueOrDefault(tooltips.titleFontSize, globalDefaults.defaultFontSize),
  					_titleAlign: tooltips.titleAlign,
  					titleSpacing: tooltips.titleSpacing,
  					titleMarginBottom: tooltips.titleMarginBottom,
  
  					// Footer
  					footerColor: tooltips.footerColor,
  					_footerFontFamily: helpers.getValueOrDefault(tooltips.footerFontFamily, globalDefaults.defaultFontFamily),
  					_footerFontStyle: helpers.getValueOrDefault(tooltips.footerFontStyle, globalDefaults.defaultFontStyle),
  					footerFontSize: helpers.getValueOrDefault(tooltips.footerFontSize, globalDefaults.defaultFontSize),
  					_footerAlign: tooltips.footerAlign,
  					footerSpacing: tooltips.footerSpacing,
  					footerMarginTop: tooltips.footerMarginTop,
  
  					// Appearance
  					caretSize: tooltips.caretSize,
  					cornerRadius: tooltips.cornerRadius,
  					backgroundColor: tooltips.backgroundColor,
  					opacity: 0,
  					legendColorBackground: tooltips.multiKeyBackground
  				}
  			});
  		},
  
  		// Get the title
  		// Args are: (tooltipItem, data)
  		getTitle: function() {
  			var beforeTitle = this._options.tooltips.callbacks.beforeTitle.apply(this, arguments),
  				title = this._options.tooltips.callbacks.title.apply(this, arguments),
  				afterTitle = this._options.tooltips.callbacks.afterTitle.apply(this, arguments);
  
  			var lines = [];
  			lines = pushOrConcat(lines, beforeTitle);
  			lines = pushOrConcat(lines, title);
  			lines = pushOrConcat(lines, afterTitle);
  
  			return lines;
  		},
  
  		// Args are: (tooltipItem, data)
  		getBeforeBody: function() {
  			var lines = this._options.tooltips.callbacks.beforeBody.apply(this, arguments);
  			return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
  		},
  
  		// Args are: (tooltipItem, data)
  		getBody: function(tooltipItems, data) {
  			var lines = [];
  
  			helpers.each(tooltipItems, function(bodyItem) {
  				helpers.pushAllIfDefined(this._options.tooltips.callbacks.beforeLabel.call(this, bodyItem, data), lines);
  				helpers.pushAllIfDefined(this._options.tooltips.callbacks.label.call(this, bodyItem, data), lines);
  				helpers.pushAllIfDefined(this._options.tooltips.callbacks.afterLabel.call(this, bodyItem, data), lines);
  			}, this);
  
  			return lines;
  		},
  
  		// Args are: (tooltipItem, data)
  		getAfterBody: function() {
  			var lines = this._options.tooltips.callbacks.afterBody.apply(this, arguments);
  			return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
  		},
  
  		// Get the footer and beforeFooter and afterFooter lines
  		// Args are: (tooltipItem, data)
  		getFooter: function() {
  			var beforeFooter = this._options.tooltips.callbacks.beforeFooter.apply(this, arguments);
  			var footer = this._options.tooltips.callbacks.footer.apply(this, arguments);
  			var afterFooter = this._options.tooltips.callbacks.afterFooter.apply(this, arguments);
  
  			var lines = [];
  			lines = pushOrConcat(lines, beforeFooter);
  			lines = pushOrConcat(lines, footer);
  			lines = pushOrConcat(lines, afterFooter);
  
  			return lines;
  		},
  
  		getAveragePosition: function(elements) {
  
  			if (!elements.length) {
  				return false;
  			}
  
  			var xPositions = [];
  			var yPositions = [];
  
  			helpers.each(elements, function(el) {
  				if (el && el.hasValue()){
  					var pos = el.tooltipPosition();
  					xPositions.push(pos.x);
  					yPositions.push(pos.y);
  				}
  			});
  
  			var x = 0,
  				y = 0;
  			for (var i = 0; i < xPositions.length; i++) {
  				x += xPositions[i];
  				y += yPositions[i];
  			}
  
  			return {
  				x: Math.round(x / xPositions.length),
  				y: Math.round(y / xPositions.length)
  			};
  
  		},
  
  		update: function(changed) {
  			if (this._active.length) {
  				this._model.opacity = 1;
  
  				var element = this._active[0],
  					labelColors = [],
  					tooltipPosition;
  
  				var tooltipItems = [];
  
  				if (this._options.tooltips.mode === 'single') {
  					var yScale = element._yScale || element._scale; // handle radar || polarArea charts
  					tooltipItems.push({
  						xLabel: element._xScale ? element._xScale.getLabelForIndex(element._index, element._datasetIndex) : '',
  						yLabel: yScale ? yScale.getLabelForIndex(element._index, element._datasetIndex) : '',
  						index: element._index,
  						datasetIndex: element._datasetIndex
  					});
  					tooltipPosition = this.getAveragePosition(this._active);
  				} else {
  					helpers.each(this._data.datasets, function(dataset, datasetIndex) {
  						if (!this._chartInstance.isDatasetVisible(datasetIndex)) {
  							return;
  						}
  
  						var meta = this._chartInstance.getDatasetMeta(datasetIndex);
  						var currentElement = meta.data[element._index];
  						if (currentElement) {
  							var yScale = element._yScale || element._scale; // handle radar || polarArea charts
  
  							tooltipItems.push({
  								xLabel: currentElement._xScale ? currentElement._xScale.getLabelForIndex(currentElement._index, currentElement._datasetIndex) : '',
  								yLabel: yScale ? yScale.getLabelForIndex(currentElement._index, currentElement._datasetIndex) : '',
  								index: element._index,
  								datasetIndex: datasetIndex
  							});
  						}
  					}, this);
  
  					helpers.each(this._active, function(active) {
  						if (active) {
  							labelColors.push({
  								borderColor: active._view.borderColor,
  								backgroundColor: active._view.backgroundColor
  							});
  						}
  					}, null);
  
  					tooltipPosition = this.getAveragePosition(this._active);
  				}
  
  				// Build the Text Lines
  				helpers.extend(this._model, {
  					title: this.getTitle(tooltipItems, this._data),
  					beforeBody: this.getBeforeBody(tooltipItems, this._data),
  					body: this.getBody(tooltipItems, this._data),
  					afterBody: this.getAfterBody(tooltipItems, this._data),
  					footer: this.getFooter(tooltipItems, this._data)
  				});
  
  				helpers.extend(this._model, {
  					x: Math.round(tooltipPosition.x),
  					y: Math.round(tooltipPosition.y),
  					caretPadding: helpers.getValueOrDefault(tooltipPosition.padding, 2),
  					labelColors: labelColors
  				});
  
  				// We need to determine alignment of
  				var tooltipSize = this.getTooltipSize(this._model);
  				this.determineAlignment(tooltipSize); // Smart Tooltip placement to stay on the canvas
  
  				helpers.extend(this._model, this.getBackgroundPoint(this._model, tooltipSize));
  			} else {
  				this._model.opacity = 0;
  			}
  
  			if (changed && this._options.tooltips.custom) {
  				this._options.tooltips.custom.call(this, this._model);
  			}
  
  			return this;
  		},
  		getTooltipSize: function getTooltipSize(vm) {
  			var ctx = this._chart.ctx;
  
  			var size = {
  				height: vm.yPadding * 2, // Tooltip Padding
  				width: 0
  			};
  			var combinedBodyLength = vm.body.length + vm.beforeBody.length + vm.afterBody.length;
  
  			size.height += vm.title.length * vm.titleFontSize; // Title Lines
  			size.height += (vm.title.length - 1) * vm.titleSpacing; // Title Line Spacing
  			size.height += vm.title.length ? vm.titleMarginBottom : 0; // Title's bottom Margin
  			size.height += combinedBodyLength * vm.bodyFontSize; // Body Lines
  			size.height += combinedBodyLength ? (combinedBodyLength - 1) * vm.bodySpacing : 0; // Body Line Spacing
  			size.height += vm.footer.length ? vm.footerMarginTop : 0; // Footer Margin
  			size.height += vm.footer.length * (vm.footerFontSize); // Footer Lines
  			size.height += vm.footer.length ? (vm.footer.length - 1) * vm.footerSpacing : 0; // Footer Line Spacing
  
  			// Width
  			ctx.font = helpers.fontString(vm.titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
  			helpers.each(vm.title, function(line) {
  				size.width = Math.max(size.width, ctx.measureText(line).width);
  			});
  
  			ctx.font = helpers.fontString(vm.bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
  			helpers.each(vm.beforeBody.concat(vm.afterBody), function(line) {
  				size.width = Math.max(size.width, ctx.measureText(line).width);
  			});
  			helpers.each(vm.body, function(line) {
  				size.width = Math.max(size.width, ctx.measureText(line).width + (this._options.tooltips.mode !== 'single' ? (vm.bodyFontSize + 2) : 0));
  			}, this);
  
  			ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
  			helpers.each(vm.footer, function(line) {
  				size.width = Math.max(size.width, ctx.measureText(line).width);
  			});
  			size.width += 2 * vm.xPadding;
  
  			return size;
  		},
  		determineAlignment: function determineAlignment(size) {
  			if (this._model.y < size.height) {
  				this._model.yAlign = 'top';
  			} else if (this._model.y > (this._chart.height - size.height)) {
  				this._model.yAlign = 'bottom';
  			}
  
  			var lf, rf; // functions to determine left, right alignment
  			var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart
  			var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges
  			var _this = this;
  			var midX = (this._chartInstance.chartArea.left + this._chartInstance.chartArea.right) / 2;
  			var midY = (this._chartInstance.chartArea.top + this._chartInstance.chartArea.bottom) / 2;
  
  			if (this._model.yAlign === 'center') {
  				lf = function(x) {
  					return x <= midX;
  				};
  				rf = function(x) {
  					return x > midX;
  				};
  			} else {
  				lf = function(x) {
  					return x <= (size.width / 2);
  				};
  				rf = function(x) {
  					return x >= (_this._chart.width - (size.width / 2));
  				};
  			}
  
  			olf = function(x) {
  				return x + size.width > _this._chart.width;
  			};
  			orf = function(x) {
  				return x - size.width < 0;
  			};
  			yf = function(y) {
  				return y <= midY ? 'top' : 'bottom';
  			};
  
  			if (lf(this._model.x)) {
  				this._model.xAlign = 'left';
  
  				// Is tooltip too wide and goes over the right side of the chart.?
  				if (olf(this._model.x)) {
  					this._model.xAlign = 'center';
  					this._model.yAlign = yf(this._model.y);
  				}
  			} else if (rf(this._model.x)) {
  				this._model.xAlign = 'right';
  
  				// Is tooltip too wide and goes outside left edge of canvas?
  				if (orf(this._model.x)) {
  					this._model.xAlign = 'center';
  					this._model.yAlign = yf(this._model.y);
  				}
  			}
  		},
  		getBackgroundPoint: function getBackgroundPoint(vm, size) {
  			// Background Position
  			var pt = {
  				x: vm.x,
  				y: vm.y
  			};
  
  			if (vm.xAlign === 'right') {
  				pt.x -= size.width;
  			} else if (vm.xAlign === 'center') {
  				pt.x -= (size.width / 2);
  			}
  
  			if (vm.yAlign === 'top') {
  				pt.y += vm.caretPadding + vm.caretSize;
  			} else if (vm.yAlign === 'bottom') {
  				pt.y -= size.height + vm.caretPadding + vm.caretSize;
  			} else {
  				pt.y -= (size.height / 2);
  			}
  
  			if (vm.yAlign === 'center') {
  				if (vm.xAlign === 'left') {
  					pt.x += vm.caretPadding + vm.caretSize;
  				} else if (vm.xAlign === 'right') {
  					pt.x -= vm.caretPadding + vm.caretSize;
  				}
  			} else {
  				if (vm.xAlign === 'left') {
  					pt.x -= vm.cornerRadius + vm.caretPadding;
  				} else if (vm.xAlign === 'right') {
  					pt.x += vm.cornerRadius + vm.caretPadding;
  				}
  			}
  
  			return pt;
  		},
  		drawCaret: function drawCaret(tooltipPoint, size, opacity, caretPadding) {
  			var vm = this._view;
  			var ctx = this._chart.ctx;
  			var x1, x2, x3;
  			var y1, y2, y3;
  
  			if (vm.yAlign === 'center') {
  				// Left or right side
  				if (vm.xAlign === 'left') {
  					x1 = tooltipPoint.x;
  					x2 = x1 - vm.caretSize;
  					x3 = x1;
  				} else {
  					x1 = tooltipPoint.x + size.width;
  					x2 = x1 + vm.caretSize;
  					x3 = x1;
  				}
  
  				y2 = tooltipPoint.y + (size.height / 2);
  				y1 = y2 - vm.caretSize;
  				y3 = y2 + vm.caretSize;
  			} else {
  				if (vm.xAlign === 'left') {
  					x1 = tooltipPoint.x + vm.cornerRadius;
  					x2 = x1 + vm.caretSize;
  					x3 = x2 + vm.caretSize;
  				} else if (vm.xAlign === 'right') {
  					x1 = tooltipPoint.x + size.width - vm.cornerRadius;
  					x2 = x1 - vm.caretSize;
  					x3 = x2 - vm.caretSize;
  				} else {
  					x2 = tooltipPoint.x + (size.width / 2);
  					x1 = x2 - vm.caretSize;
  					x3 = x2 + vm.caretSize;
  				}
  
  				if (vm.yAlign === 'top') {
  					y1 = tooltipPoint.y;
  					y2 = y1 - vm.caretSize;
  					y3 = y1;
  				} else {
  					y1 = tooltipPoint.y + size.height;
  					y2 = y1 + vm.caretSize;
  					y3 = y1;
  				}
  			}
  
  			var bgColor = helpers.color(vm.backgroundColor);
  			ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString();
  			ctx.beginPath();
  			ctx.moveTo(x1, y1);
  			ctx.lineTo(x2, y2);
  			ctx.lineTo(x3, y3);
  			ctx.closePath();
  			ctx.fill();
  		},
  		drawTitle: function drawTitle(pt, vm, ctx, opacity) {
  			if (vm.title.length) {
  				ctx.textAlign = vm._titleAlign;
  				ctx.textBaseline = "top";
  
  				var titleColor = helpers.color(vm.titleColor);
  				ctx.fillStyle = titleColor.alpha(opacity * titleColor.alpha()).rgbString();
  				ctx.font = helpers.fontString(vm.titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
  
  				helpers.each(vm.title, function(title, i) {
  					ctx.fillText(title, pt.x, pt.y);
  					pt.y += vm.titleFontSize + vm.titleSpacing; // Line Height and spacing
  
  					if (i + 1 === vm.title.length) {
  						pt.y += vm.titleMarginBottom - vm.titleSpacing; // If Last, add margin, remove spacing
  					}
  				});
  			}
  		},
  		drawBody: function drawBody(pt, vm, ctx, opacity) {
  			ctx.textAlign = vm._bodyAlign;
  			ctx.textBaseline = "top";
  
  			var bodyColor = helpers.color(vm.bodyColor);
  			ctx.fillStyle = bodyColor.alpha(opacity * bodyColor.alpha()).rgbString();
  			ctx.font = helpers.fontString(vm.bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
  
  			// Before Body
  			helpers.each(vm.beforeBody, function(beforeBody) {
  				ctx.fillText(beforeBody, pt.x, pt.y);
  				pt.y += vm.bodyFontSize + vm.bodySpacing;
  			});
  
  			helpers.each(vm.body, function(body, i) {
  				// Draw Legend-like boxes if needed
  				if (this._options.tooltips.mode !== 'single') {
  					// Fill a white rect so that colours merge nicely if the opacity is < 1
  					ctx.fillStyle = helpers.color(vm.legendColorBackground).alpha(opacity).rgbaString();
  					ctx.fillRect(pt.x, pt.y, vm.bodyFontSize, vm.bodyFontSize);
  
  					// Border
  					ctx.strokeStyle = helpers.color(vm.labelColors[i].borderColor).alpha(opacity).rgbaString();
  					ctx.strokeRect(pt.x, pt.y, vm.bodyFontSize, vm.bodyFontSize);
  
  					// Inner square
  					ctx.fillStyle = helpers.color(vm.labelColors[i].backgroundColor).alpha(opacity).rgbaString();
  					ctx.fillRect(pt.x + 1, pt.y + 1, vm.bodyFontSize - 2, vm.bodyFontSize - 2);
  
  					ctx.fillStyle = helpers.color(vm.bodyColor).alpha(opacity).rgbaString(); // Return fill style for text
  				}
  
  				// Body Line
  				ctx.fillText(body, pt.x + (this._options.tooltips.mode !== 'single' ? (vm.bodyFontSize + 2) : 0), pt.y);
  
  				pt.y += vm.bodyFontSize + vm.bodySpacing;
  			}, this);
  
  			// After Body
  			helpers.each(vm.afterBody, function(afterBody) {
  				ctx.fillText(afterBody, pt.x, pt.y);
  				pt.y += vm.bodyFontSize;
  			});
  
  			pt.y -= vm.bodySpacing; // Remove last body spacing
  		},
  		drawFooter: function drawFooter(pt, vm, ctx, opacity) {
  			if (vm.footer.length) {
  				pt.y += vm.footerMarginTop;
  
  				ctx.textAlign = vm._footerAlign;
  				ctx.textBaseline = "top";
  
  				var footerColor = helpers.color(vm.footerColor);
  				ctx.fillStyle = footerColor.alpha(opacity * footerColor.alpha()).rgbString();
  				ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
  
  				helpers.each(vm.footer, function(footer) {
  					ctx.fillText(footer, pt.x, pt.y);
  					pt.y += vm.footerFontSize + vm.footerSpacing;
  				});
  			}
  		},
  		draw: function draw() {
  			var ctx = this._chart.ctx;
  			var vm = this._view;
  
  			if (vm.opacity === 0) {
  				return;
  			}
  
  			var caretPadding = vm.caretPadding;
  			var tooltipSize = this.getTooltipSize(vm);
  			var pt = {
  				x: vm.x,
  				y: vm.y
  			};
  
  			// IE11/Edge does not like very small opacities, so snap to 0
  			var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
  
  			if (this._options.tooltips.enabled) {
  				// Draw Background
  				var bgColor = helpers.color(vm.backgroundColor);
  				ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString();
  				helpers.drawRoundedRectangle(ctx, pt.x, pt.y, tooltipSize.width, tooltipSize.height, vm.cornerRadius);
  				ctx.fill();
  
  				// Draw Caret
  				this.drawCaret(pt, tooltipSize, opacity, caretPadding);
  
  				// Draw Title, Body, and Footer
  				pt.x += vm.xPadding;
  				pt.y += vm.yPadding;
  
  				// Titles
  				this.drawTitle(pt, vm, ctx, opacity);
  
  				// Body
  				this.drawBody(pt, vm, ctx, opacity);
  
  				// Footer
  				this.drawFooter(pt, vm, ctx, opacity);
  			}
  		}
  	});
  };
  
  },{}],34:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart, moment) {
  
    var helpers = Chart.helpers,
      globalOpts = Chart.defaults.global;
  
    globalOpts.elements.arc = {
      backgroundColor: globalOpts.defaultColor,
      borderColor: "#fff",
      borderWidth: 2
    };
  
    Chart.elements.Arc = Chart.Element.extend({
      inLabelRange: function(mouseX) {
        var vm = this._view;
  
        if (vm) {
          return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
        } else {
          return false;
        }
      },
      inRange: function(chartX, chartY) {
        var vm = this._view;
  
        if (vm) {
          var pointRelativePosition = helpers.getAngleFromPoint(vm, {
              x: chartX,
              y: chartY
            }),
            angle = pointRelativePosition.angle,
            distance = pointRelativePosition.distance;
  
          //Sanitise angle range
          var startAngle = vm.startAngle;
          var endAngle = vm.endAngle;
          while (endAngle < startAngle) {
            endAngle += 2.0 * Math.PI;
          }
          while (angle > endAngle) {
            angle -= 2.0 * Math.PI;
          }
          while (angle < startAngle) {
            angle += 2.0 * Math.PI;
          }
  
          //Check if within the range of the open/close angle
          var betweenAngles = (angle >= startAngle && angle <= endAngle),
            withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius);
  
          return (betweenAngles && withinRadius);
        } else {
          return false;
        }
      },
      tooltipPosition: function() {
        var vm = this._view;
  
        var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2),
          rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
        return {
          x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
          y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
        };
      },
      draw: function() {
  
        var ctx = this._chart.ctx,
          vm = this._view,
          sA = vm.startAngle,
          eA = vm.endAngle;
  
        ctx.beginPath();
  
        ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA);
        ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true);
  
        ctx.closePath();
        ctx.strokeStyle = vm.borderColor;
        ctx.lineWidth = vm.borderWidth;
  
        ctx.fillStyle = vm.backgroundColor;
  
        ctx.fill();
        ctx.lineJoin = 'bevel';
  
        if (vm.borderWidth) {
          ctx.stroke();
        }
      }
    });
  };
  
  },{}],35:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers;
  	var globalDefaults = Chart.defaults.global;
  
  	Chart.defaults.global.elements.line = {
  		tension: 0.4,
  		backgroundColor: globalDefaults.defaultColor,
  		borderWidth: 3,
  		borderColor: globalDefaults.defaultColor,
  		borderCapStyle: 'butt',
  		borderDash: [],
  		borderDashOffset: 0.0,
  		borderJoinStyle: 'miter',
  		fill: true // do we fill in the area between the line and its base axis
  	};
  
  	Chart.elements.Line = Chart.Element.extend({
  		lineToNextPoint: function(previousPoint, point, nextPoint, skipHandler, previousSkipHandler) {
  			var ctx = this._chart.ctx;
  
  			if (point._view.skip) {
  				skipHandler.call(this, previousPoint, point, nextPoint);
  			} else if (previousPoint._view.skip) {
  				previousSkipHandler.call(this, previousPoint, point, nextPoint);
  			} else if (point._view.tension === 0) {
  				ctx.lineTo(point._view.x, point._view.y);
  			} else {
  				// Line between points
  				ctx.bezierCurveTo(
  					previousPoint._view.controlPointNextX,
  					previousPoint._view.controlPointNextY,
  					point._view.controlPointPreviousX,
  					point._view.controlPointPreviousY,
  					point._view.x,
  					point._view.y
  				);
  			}
  		},
  
  		draw: function() {
  			var _this = this;
  
  			var vm = this._view;
  			var ctx = this._chart.ctx;
  			var first = this._children[0];
  			var last = this._children[this._children.length - 1];
  
  			function loopBackToStart(drawLineToCenter) {
  				if (!first._view.skip && !last._view.skip) {
  					// Draw a bezier line from last to first
  					ctx.bezierCurveTo(
  						last._view.controlPointNextX,
  						last._view.controlPointNextY,
  						first._view.controlPointPreviousX,
  						first._view.controlPointPreviousY,
  						first._view.x,
  						first._view.y
  					);
  				} else if (drawLineToCenter) {
  					// Go to center
  					ctx.lineTo(_this._view.scaleZero.x, _this._view.scaleZero.y);
  				}
  			}
  
  			ctx.save();
  
  			// If we had points and want to fill this line, do so.
  			if (this._children.length > 0 && vm.fill) {
  				// Draw the background first (so the border is always on top)
  				ctx.beginPath();
  
  				helpers.each(this._children, function(point, index) {
  					var previous = helpers.previousItem(this._children, index);
  					var next = helpers.nextItem(this._children, index);
  
  					// First point moves to it's starting position no matter what
  					if (index === 0) {
  						if (this._loop) {
  							ctx.moveTo(vm.scaleZero.x, vm.scaleZero.y);
  						} else {
  							ctx.moveTo(point._view.x, vm.scaleZero);
  						}
  
  						if (point._view.skip) {
  							if (!this._loop) {
  								ctx.moveTo(next._view.x, this._view.scaleZero);
  							}
  						} else {
  							ctx.lineTo(point._view.x, point._view.y);
  						}
  					} else {
  						this.lineToNextPoint(previous, point, next, function(previousPoint, point, nextPoint) {
  							if (this._loop) {
  								// Go to center
  								ctx.lineTo(this._view.scaleZero.x, this._view.scaleZero.y);
  							} else {
  								ctx.lineTo(previousPoint._view.x, this._view.scaleZero);
  								ctx.moveTo(nextPoint._view.x, this._view.scaleZero);
  							}
  						}, function(previousPoint, point) {
  							// If we skipped the last point, draw a line to ourselves so that the fill is nice
  							ctx.lineTo(point._view.x, point._view.y);
  						});
  					}
  				}, this);
  
  				// For radial scales, loop back around to the first point
  				if (this._loop) {
  					loopBackToStart(true);
  				} else {
  					//Round off the line by going to the base of the chart, back to the start, then fill.
  					ctx.lineTo(this._children[this._children.length - 1]._view.x, vm.scaleZero);
  					ctx.lineTo(this._children[0]._view.x, vm.scaleZero);
  				}
  
  				ctx.fillStyle = vm.backgroundColor || globalDefaults.defaultColor;
  				ctx.closePath();
  				ctx.fill();
  			}
  
  			var globalOptionLineElements = globalDefaults.elements.line;
  			// Now draw the line between all the points with any borders
  			ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle;
  
  			// IE 9 and 10 do not support line dash
  			if (ctx.setLineDash) {
  				ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash);
  			}
  
  			ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset;
  			ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle;
  			ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth;
  			ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor;
  			ctx.beginPath();
  
  			helpers.each(this._children, function(point, index) {
  				var previous = helpers.previousItem(this._children, index);
  				var next = helpers.nextItem(this._children, index);
  
  				if (index === 0) {
  					ctx.moveTo(point._view.x, point._view.y);
  				} else {
  					this.lineToNextPoint(previous, point, next, function(previousPoint, point, nextPoint) {
  						ctx.moveTo(nextPoint._view.x, nextPoint._view.y);
  					}, function(previousPoint, point) {
  						// If we skipped the last point, move up to our point preventing a line from being drawn
  						ctx.moveTo(point._view.x, point._view.y);
  					});
  				}
  			}, this);
  
  			if (this._loop && this._children.length > 0) {
  				loopBackToStart();
  			}
  
  			ctx.stroke();
  			ctx.restore();
  		}
  	});
  };
  },{}],36:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers,
  		globalOpts = Chart.defaults.global,
  		defaultColor = globalOpts.defaultColor;
  
  	globalOpts.elements.point = {
  		radius: 3,
  		pointStyle: 'circle',
  		backgroundColor: defaultColor,
  		borderWidth: 1,
  		borderColor: defaultColor,
  		// Hover
  		hitRadius: 1,
  		hoverRadius: 4,
  		hoverBorderWidth: 1
  	};
  
  	Chart.elements.Point = Chart.Element.extend({
  		inRange: function(mouseX, mouseY) {
  			var vm = this._view;
  			return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false;
  		},
  		inLabelRange: function(mouseX) {
  			var vm = this._view;
  			return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;
  		},
  		tooltipPosition: function() {
  			var vm = this._view;
  			return {
  				x: vm.x,
  				y: vm.y,
  				padding: vm.radius + vm.borderWidth
  			};
  		},
  		draw: function() {
  			var vm = this._view;
  			var ctx = this._chart.ctx;
  			var pointStyle = vm.pointStyle;
  			var radius = vm.radius;
  			var x = vm.x;
  			var y = vm.y;
  			var type, edgeLength, xOffset, yOffset, height, size;
  
  			if (vm.skip) {
  				return;
  			}
  
  			if (typeof pointStyle === 'object') {
  				type = pointStyle.toString();
  				if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
  					ctx.drawImage(pointStyle, x - pointStyle.width / 2, y - pointStyle.height / 2);
  					return;
  				}
  			}
  
  			if (isNaN(radius) || radius <= 0) {
  				return;
  			}
  
  			ctx.strokeStyle = vm.borderColor || defaultColor;
  			ctx.lineWidth = helpers.getValueOrDefault(vm.borderWidth, globalOpts.elements.point.borderWidth);
  			ctx.fillStyle = vm.backgroundColor || defaultColor;
  
  			switch (pointStyle) {
  			// Default includes circle
  			default:
  				ctx.beginPath();
  				ctx.arc(x, y, radius, 0, Math.PI * 2);
  				ctx.closePath();
  				ctx.fill();
  				break;
  			case 'triangle':
  				ctx.beginPath();
  				edgeLength = 3 * radius / Math.sqrt(3);
  				height = edgeLength * Math.sqrt(3) / 2;
  				ctx.moveTo(x - edgeLength / 2, y + height / 3);
  				ctx.lineTo(x + edgeLength / 2, y + height / 3);
  				ctx.lineTo(x, y - 2 * height / 3);
  				ctx.closePath();
  				ctx.fill();
  				break;
  			case 'rect':
  				size = 1 / Math.SQRT2 * radius;
  				ctx.fillRect(x - size, y - size, 2 * size,  2 * size);
  				ctx.strokeRect(x - size, y - size, 2 * size, 2 * size);
  				break;
  			case 'rectRot':
  				ctx.translate(x, y);
  				ctx.rotate(Math.PI / 4);
  				size = 1 / Math.SQRT2 * radius;
  				ctx.fillRect(-size, -size, 2 * size, 2 * size);
  				ctx.strokeRect(-size, -size, 2 * size, 2 * size);
  				ctx.setTransform(1, 0, 0, 1, 0, 0);
  				break;
  			case 'cross':
  				ctx.beginPath();
  				ctx.moveTo(x, y + radius);
  				ctx.lineTo(x, y - radius);
  				ctx.moveTo(x - radius, y);
  				ctx.lineTo(x + radius, y);
  				ctx.closePath();
  				break;
  			case 'crossRot':
  				ctx.beginPath();
  				xOffset = Math.cos(Math.PI / 4) * radius;
  				yOffset = Math.sin(Math.PI / 4) * radius;
  				ctx.moveTo(x - xOffset, y - yOffset);
  				ctx.lineTo(x + xOffset, y + yOffset);
  				ctx.moveTo(x - xOffset, y + yOffset);
  				ctx.lineTo(x + xOffset, y - yOffset);
  				ctx.closePath();
  				break;
  			case 'star':
  				ctx.beginPath();
  				ctx.moveTo(x, y + radius);
  				ctx.lineTo(x, y - radius);
  				ctx.moveTo(x - radius, y);
  				ctx.lineTo(x + radius, y);
  				xOffset = Math.cos(Math.PI / 4) * radius;
  				yOffset = Math.sin(Math.PI / 4) * radius;
  				ctx.moveTo(x - xOffset, y - yOffset);
  				ctx.lineTo(x + xOffset, y + yOffset);
  				ctx.moveTo(x - xOffset, y + yOffset);
  				ctx.lineTo(x + xOffset, y - yOffset);
  				ctx.closePath();
  				break;
  			case 'line':
  				ctx.beginPath();
  				ctx.moveTo(x - radius, y);
  				ctx.lineTo(x + radius, y);
  				ctx.closePath();
  				break;
  			case 'dash':
  				ctx.beginPath();
  				ctx.moveTo(x, y);
  				ctx.lineTo(x + radius, y);
  				ctx.closePath();
  				break;
  			}
  
  			ctx.stroke();
  		}
  	});
  };
  
  },{}],37:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers,
  		globalOpts = Chart.defaults.global;
  
  	globalOpts.elements.rectangle = {
  		backgroundColor: globalOpts.defaultColor,
  		borderWidth: 0,
  		borderColor: globalOpts.defaultColor,
  		borderSkipped: 'bottom'
  	};
  
  	Chart.elements.Rectangle = Chart.Element.extend({
  		draw: function() {
  			var ctx = this._chart.ctx;
  			var vm = this._view;
  
  			var halfWidth = vm.width / 2,
  				leftX = vm.x - halfWidth,
  				rightX = vm.x + halfWidth,
  				top = vm.base - (vm.base - vm.y),
  				halfStroke = vm.borderWidth / 2;
  
  			// Canvas doesn't allow us to stroke inside the width so we can
  			// adjust the sizes to fit if we're setting a stroke on the line
  			if (vm.borderWidth) {
  				leftX += halfStroke;
  				rightX -= halfStroke;
  				top += halfStroke;
  			}
  
  			ctx.beginPath();
  			ctx.fillStyle = vm.backgroundColor;
  			ctx.strokeStyle = vm.borderColor;
  			ctx.lineWidth = vm.borderWidth;
  
  			// Corner points, from bottom-left to bottom-right clockwise
  			// | 1 2 |
  			// | 0 3 |
  			var corners = [
  				[leftX, vm.base],
  				[leftX, top],
  				[rightX, top],
  				[rightX, vm.base]
  			];
  
  			// Find first (starting) corner with fallback to 'bottom' 
  			var borders = ['bottom', 'left', 'top', 'right'];
  			var startCorner = borders.indexOf(vm.borderSkipped, 0);
  			if (startCorner === -1)
  				startCorner = 0;
  
  			function cornerAt(index) {
  				return corners[(startCorner + index) % 4];
  			}
  
  			// Draw rectangle from 'startCorner'
  			ctx.moveTo.apply(ctx, cornerAt(0));
  			for (var i = 1; i < 4; i++)
  				ctx.lineTo.apply(ctx, cornerAt(i));
  
  			ctx.fill();
  			if (vm.borderWidth) {
  				ctx.stroke();
  			}
  		},
  		height: function() {
  			var vm = this._view;
  			return vm.base - vm.y;
  		},
  		inRange: function(mouseX, mouseY) {
  			var vm = this._view;
  			return vm ? 
  					(vm.y < vm.base ? 
  						(mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base) :
  						(mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y)) :
  					false;
  		},
  		inLabelRange: function(mouseX) {
  			var vm = this._view;
  			return vm ? (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) : false;
  		},
  		tooltipPosition: function() {
  			var vm = this._view;
  			return {
  				x: vm.x,
  				y: vm.y
  			};
  		}
  	});
  
  };
  },{}],38:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers;
  	// Default config for a category scale
  	var defaultConfig = {
  		position: "bottom"
  	};
  
  	var DatasetScale = Chart.Scale.extend({
  		// Implement this so that 
  		determineDataLimits: function() {
  			this.minIndex = 0;
  			this.maxIndex = this.chart.data.labels.length - 1;
  			var findIndex;
  
  			if (this.options.ticks.min !== undefined) {
  				// user specified min value
  				findIndex = helpers.indexOf(this.chart.data.labels, this.options.ticks.min);
  				this.minIndex = findIndex !== -1 ? findIndex : this.minIndex;
  			}
  
  			if (this.options.ticks.max !== undefined) {
  				// user specified max value
  				findIndex = helpers.indexOf(this.chart.data.labels, this.options.ticks.max);
  				this.maxIndex = findIndex !== -1 ? findIndex : this.maxIndex;
  			}
  
  			this.min = this.chart.data.labels[this.minIndex];
  			this.max = this.chart.data.labels[this.maxIndex];
  		},
  
  		buildTicks: function(index) {
  			// If we are viewing some subset of labels, slice the original array
  			this.ticks = (this.minIndex === 0 && this.maxIndex === this.chart.data.labels.length - 1) ? this.chart.data.labels : this.chart.data.labels.slice(this.minIndex, this.maxIndex + 1);
  		},
  
  		getLabelForIndex: function(index, datasetIndex) {
  			return this.ticks[index];
  		},
  
  		// Used to get data value locations.  Value can either be an index or a numerical value
  		getPixelForValue: function(value, index, datasetIndex, includeOffset) {
  			// 1 is added because we need the length but we have the indexes
  			var offsetAmt = Math.max((this.maxIndex + 1 - this.minIndex - ((this.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
  
  			if (this.isHorizontal()) {
  				var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
  				var valueWidth = innerWidth / offsetAmt;
  				var widthOffset = (valueWidth * (index - this.minIndex)) + this.paddingLeft;
  
  				if (this.options.gridLines.offsetGridLines && includeOffset) {
  					widthOffset += (valueWidth / 2);
  				}
  
  				return this.left + Math.round(widthOffset);
  			} else {
  				var innerHeight = this.height - (this.paddingTop + this.paddingBottom);
  				var valueHeight = innerHeight / offsetAmt;
  				var heightOffset = (valueHeight * (index - this.minIndex)) + this.paddingTop;
  
  				if (this.options.gridLines.offsetGridLines && includeOffset) {
  					heightOffset += (valueHeight / 2);
  				}
  
  				return this.top + Math.round(heightOffset);
  			}
  		},
  		getPixelForTick: function(index, includeOffset) {
  			return this.getPixelForValue(this.ticks[index], index + this.minIndex, null, includeOffset);
  		},
  		getValueForPixel: function(pixel) {
  			var value
  ;			var offsetAmt = Math.max((this.ticks.length - ((this.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
  			var horz = this.isHorizontal();
  			var innerDimension = horz ? this.width - (this.paddingLeft + this.paddingRight) : this.height - (this.paddingTop + this.paddingBottom);
  			var valueDimension = innerDimension / offsetAmt;
  
  			if (this.options.gridLines.offsetGridLines) {
  				pixel -= (valueDimension / 2);
  			}
  			pixel -= horz ? this.paddingLeft : this.paddingTop;
  
  			if (pixel <= 0) {
  				value = 0;
  			} else {
  				value = Math.round(pixel / valueDimension);
  			}
  
  			return value;
  		}
  	});
  
  	Chart.scaleService.registerScaleType("category", DatasetScale, defaultConfig);
  
  };
  },{}],39:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers;
  
  	var defaultConfig = {
  		position: "left",
  		ticks: {
  			callback: function(tickValue, index, ticks) {
  				// If we have lots of ticks, don't use the ones
  				var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0];
  
  				// If we have a number like 2.5 as the delta, figure out how many decimal places we need
  				if (Math.abs(delta) > 1) {
  					if (tickValue !== Math.floor(tickValue)) {
  						// not an integer
  						delta = tickValue - Math.floor(tickValue);
  					}
  				}
  
  				var logDelta = helpers.log10(Math.abs(delta));
  				var tickString = '';
  
  				if (tickValue !== 0) {
  					var numDecimal = -1 * Math.floor(logDelta);
  					numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places
  					tickString = tickValue.toFixed(numDecimal);
  				} else {
  					tickString = '0'; // never show decimal places for 0
  				}
  
  				return tickString;
  			}
  		}
  	};
  
  	var LinearScale = Chart.Scale.extend({
  		determineDataLimits: function() {
  			var _this = this;
  			var opts = _this.options;
  			var tickOpts = opts.ticks;
  			var chart = _this.chart;
  			var data = chart.data;
  			var datasets = data.datasets;
  			var isHorizontal = _this.isHorizontal();
  
  			function IDMatches(meta) {
  				return isHorizontal ? meta.xAxisID === _this.id : meta.yAxisID === _this.id;
  			}
  
  			// First Calculate the range
  			_this.min = null;
  			_this.max = null;
  
  			if (opts.stacked) {
  				var valuesPerType = {};
  				var hasPositiveValues = false;
  				var hasNegativeValues = false;
  
  				helpers.each(datasets, function(dataset, datasetIndex) {
  					var meta = chart.getDatasetMeta(datasetIndex);
  					if (valuesPerType[meta.type] === undefined) {
  						valuesPerType[meta.type] = {
  							positiveValues: [],
  							negativeValues: []
  						};
  					}
  
  					// Store these per type
  					var positiveValues = valuesPerType[meta.type].positiveValues;
  					var negativeValues = valuesPerType[meta.type].negativeValues;
  
  					if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
  						helpers.each(dataset.data, function(rawValue, index) {
  							var value = +_this.getRightValue(rawValue);
  							if (isNaN(value) || meta.data[index].hidden) {
  								return;
  							}
  
  							positiveValues[index] = positiveValues[index] || 0;
  							negativeValues[index] = negativeValues[index] || 0;
  
  							if (opts.relativePoints) {
  								positiveValues[index] = 100;
  							} else {
  								if (value < 0) {
  									hasNegativeValues = true;
  									negativeValues[index] += value;
  								} else {
  									hasPositiveValues = true;
  									positiveValues[index] += value;
  								}
  							}
  						});
  					}
  				});
  
  				helpers.each(valuesPerType, function(valuesForType) {
  					var values = valuesForType.positiveValues.concat(valuesForType.negativeValues);
  					var minVal = helpers.min(values);
  					var maxVal = helpers.max(values);
  					_this.min = _this.min === null ? minVal : Math.min(_this.min, minVal);
  					_this.max = _this.max === null ? maxVal : Math.max(_this.max, maxVal);
  				});
  
  			} else {
  				helpers.each(datasets, function(dataset, datasetIndex) {
  					var meta = chart.getDatasetMeta(datasetIndex);
  					if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
  						helpers.each(dataset.data, function(rawValue, index) {
  							var value = +_this.getRightValue(rawValue);
  							if (isNaN(value) || meta.data[index].hidden) {
  								return;
  							}
  
  							if (_this.min === null) {
  								_this.min = value;
  							} else if (value < _this.min) {
  								_this.min = value;
  							}
  
  							if (_this.max === null) {
  								_this.max = value;
  							} else if (value > _this.max) {
  								_this.max = value;
  							}
  						});
  					}
  				});
  			}
  
  			// If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
  			// do nothing since that would make the chart weird. If the user really wants a weird chart
  			// axis, they can manually override it
  			if (tickOpts.beginAtZero) {
  				var minSign = helpers.sign(_this.min);
  				var maxSign = helpers.sign(_this.max);
  
  				if (minSign < 0 && maxSign < 0) {
  					// move the top up to 0
  					_this.max = 0;
  				} else if (minSign > 0 && maxSign > 0) {
  					// move the botttom down to 0
  					_this.min = 0;
  				}
  			}
  
  			if (tickOpts.min !== undefined) {
  				_this.min = tickOpts.min;
  			} else if (tickOpts.suggestedMin !== undefined) {
  				_this.min = Math.min(_this.min, tickOpts.suggestedMin);
  			}
  
  			if (tickOpts.max !== undefined) {
  				_this.max = tickOpts.max;
  			} else if (tickOpts.suggestedMax !== undefined) {
  				_this.max = Math.max(_this.max, tickOpts.suggestedMax);
  			}
  
  			if (_this.min === _this.max) {
  				_this.max++;
  
  				if (!tickOpts.beginAtZero) {
  					_this.min--;
  				}
  			}
  		},
  		buildTicks: function() {
  			var _this = this;
  			var opts = _this.options;
  			var tickOpts = opts.ticks;
  			var getValueOrDefault = helpers.getValueOrDefault;
  			var isHorizontal = _this.isHorizontal();
  
  			var ticks = _this.ticks = [];
  
  			// Figure out what the max number of ticks we can support it is based on the size of
  			// the axis area. For now, we say that the minimum tick spacing in pixels must be 50
  			// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
  			// the graph
  
  			var maxTicks;
  
  			if (isHorizontal) {
  				maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(_this.width / 50));
  			} else {
  				// The factor of 2 used to scale the font size has been experimentally determined.
  				var tickFontSize = getValueOrDefault(tickOpts.fontSize, Chart.defaults.global.defaultFontSize);
  				maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(_this.height / (2 * tickFontSize)));
  			}
  
  			// Make sure we always have at least 2 ticks
  			maxTicks = Math.max(2, maxTicks);
  
  			// To get a "nice" value for the tick spacing, we will use the appropriately named
  			// "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
  			// for details.
  
  			var spacing;
  			var fixedStepSizeSet = (tickOpts.fixedStepSize && tickOpts.fixedStepSize > 0) || (tickOpts.stepSize && tickOpts.stepSize > 0);
  			if (fixedStepSizeSet) {
  				spacing = getValueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize);
  			} else {
  				var niceRange = helpers.niceNum(_this.max - _this.min, false);
  				spacing = helpers.niceNum(niceRange / (maxTicks - 1), true);
  			}
  			var niceMin = Math.floor(_this.min / spacing) * spacing;
  			var niceMax = Math.ceil(_this.max / spacing) * spacing;
  			var numSpaces = (niceMax - niceMin) / spacing;
  
  			// If very close to our rounded value, use it.
  			if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
  				numSpaces = Math.round(numSpaces);
  			} else {
  				numSpaces = Math.ceil(numSpaces);
  			}
  
  			// Put the values into the ticks array
  			ticks.push(tickOpts.min !== undefined ? tickOpts.min : niceMin);
  			for (var j = 1; j < numSpaces; ++j) {
  				ticks.push(niceMin + (j * spacing));
  			}
  			ticks.push(tickOpts.max !== undefined ? tickOpts.max : niceMax);
  
  			if (!isHorizontal) {
  				// We are in a vertical orientation. The top value is the highest. So reverse the array
  				ticks.reverse();
  			}
  
  			// At this point, we need to update our max and min given the tick values since we have expanded the
  			// range of the scale
  			_this.max = helpers.max(ticks);
  			_this.min = helpers.min(ticks);
  
  			if (tickOpts.reverse) {
  				ticks.reverse();
  
  				_this.start = _this.max;
  				_this.end = _this.min;
  			} else {
  				_this.start = _this.min;
  				_this.end = _this.max;
  			}
  		},
  		getLabelForIndex: function(index, datasetIndex) {
  			return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
  		},
  		convertTicksToLabels: function() {
  			var _this = this;
  			_this.ticksAsNumbers = _this.ticks.slice();
  			_this.zeroLineIndex = _this.ticks.indexOf(0);
  
  			Chart.Scale.prototype.convertTicksToLabels.call(_this);
  		},
  		// Utils
  		getPixelForValue: function(value, index, datasetIndex, includeOffset) {
  			// This must be called after fit has been run so that
  			//      this.left, this.top, this.right, and this.bottom have been defined
  			var _this = this;
  			var paddingLeft = _this.paddingLeft;
  			var paddingBottom = _this.paddingBottom;
  			var start = _this.start;
  
  			var rightValue = +_this.getRightValue(value);
  			var pixel;
  			var innerDimension;
  			var range = _this.end - start;
  
  			if (_this.isHorizontal()) {
  				innerDimension = _this.width - (paddingLeft + _this.paddingRight);
  				pixel = _this.left + (innerDimension / range * (rightValue - start));
  				return Math.round(pixel + paddingLeft);
  			} else {
  				innerDimension = _this.height - (_this.paddingTop + paddingBottom);
  				pixel = (_this.bottom - paddingBottom) - (innerDimension / range * (rightValue - start));
  				return Math.round(pixel);
  			}
  		},
  		getValueForPixel: function(pixel) {
  			var _this = this;
  			var isHorizontal = _this.isHorizontal();
  			var paddingLeft = _this.paddingLeft;
  			var paddingBottom = _this.paddingBottom;
  			var innerDimension = isHorizontal ? _this.width - (paddingLeft + _this.paddingRight) : _this.height - (_this.paddingTop + paddingBottom);
  			var offset = (isHorizontal ? pixel - _this.left - paddingLeft : _this.bottom - paddingBottom - pixel) / innerDimension;
  			return _this.start + ((_this.end - _this.start) * offset);
  		},
  		getPixelForTick: function(index, includeOffset) {
  			return this.getPixelForValue(this.ticksAsNumbers[index], null, null, includeOffset);
  		}
  	});
  	Chart.scaleService.registerScaleType("linear", LinearScale, defaultConfig);
  
  };
  },{}],40:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers;
  
  	var defaultConfig = {
  		position: "left",
  
  		// label settings
  		ticks: {
  			callback: function(value, index, arr) {
  				var remain = value / (Math.pow(10, Math.floor(helpers.log10(value))));
  
  				if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === arr.length - 1) {
  					return value.toExponential();
  				} else {
  					return '';
  				}
  			}
  		}
  	};
  
  	var LogarithmicScale = Chart.Scale.extend({
  		determineDataLimits: function() {
  			var _this = this;
  			var opts = _this.options;
  			var tickOpts = opts.ticks;
  			var chart = _this.chart;
  			var data = chart.data;
  			var datasets = data.datasets;
  			var getValueOrDefault = helpers.getValueOrDefault;
  			var isHorizontal = _this.isHorizontal();
  			function IDMatches(meta) {
  				return isHorizontal ? meta.xAxisID === _this.id : meta.yAxisID === _this.id;
  			}
  
  			// Calculate Range
  			_this.min = null;
  			_this.max = null;
  
  			if (opts.stacked) {
  				var valuesPerType = {};
  
  				helpers.each(datasets, function(dataset, datasetIndex) {
  					var meta = chart.getDatasetMeta(datasetIndex);
  					if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
  						if (valuesPerType[meta.type] === undefined) {
  							valuesPerType[meta.type] = [];
  						}
  
  						helpers.each(dataset.data, function(rawValue, index) {
  							var values = valuesPerType[meta.type];
  							var value = +_this.getRightValue(rawValue);
  							if (isNaN(value) || meta.data[index].hidden) {
  								return;
  							}
  
  							values[index] = values[index] || 0;
  
  							if (opts.relativePoints) {
  								values[index] = 100;
  							} else {
  								// Don't need to split positive and negative since the log scale can't handle a 0 crossing
  								values[index] += value;
  							}
  						});
  					}
  				});
  
  				helpers.each(valuesPerType, function(valuesForType) {
  					var minVal = helpers.min(valuesForType);
  					var maxVal = helpers.max(valuesForType);
  					_this.min = _this.min === null ? minVal : Math.min(_this.min, minVal);
  					_this.max = _this.max === null ? maxVal : Math.max(_this.max, maxVal);
  				});
  
  			} else {
  				helpers.each(datasets, function(dataset, datasetIndex) {
  					var meta = chart.getDatasetMeta(datasetIndex);
  					if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
  						helpers.each(dataset.data, function(rawValue, index) {
  							var value = +_this.getRightValue(rawValue);
  							if (isNaN(value) || meta.data[index].hidden) {
  								return;
  							}
  
  							if (_this.min === null) {
  								_this.min = value;
  							} else if (value < _this.min) {
  								_this.min = value;
  							}
  
  							if (_this.max === null) {
  								_this.max = value;
  							} else if (value > _this.max) {
  								_this.max = value;
  							}
  						});
  					}
  				});
  			}
  
  			_this.min = getValueOrDefault(tickOpts.min, _this.min);
  			_this.max = getValueOrDefault(tickOpts.max, _this.max);
  
  			if (_this.min === _this.max) {
  				if (_this.min !== 0 && _this.min !== null) {
  					_this.min = Math.pow(10, Math.floor(helpers.log10(_this.min)) - 1);
  					_this.max = Math.pow(10, Math.floor(helpers.log10(_this.max)) + 1);
  				} else {
  					_this.min = 1;
  					_this.max = 10;
  				}
  			}
  		},
  		buildTicks: function() {
  			var _this = this;
  			var opts = _this.options;
  			var tickOpts = opts.ticks;
  			var getValueOrDefault = helpers.getValueOrDefault;
  
  			// Reset the ticks array. Later on, we will draw a grid line at these positions
  			// The array simply contains the numerical value of the spots where ticks will be
  			var ticks = _this.ticks = [];
  
  			// Figure out what the max number of ticks we can support it is based on the size of
  			// the axis area. For now, we say that the minimum tick spacing in pixels must be 50
  			// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
  			// the graph
  
  			var tickVal = getValueOrDefault(tickOpts.min, Math.pow(10, Math.floor(helpers.log10(_this.min))));
  
  			while (tickVal < _this.max) {
  				ticks.push(tickVal);
  
  				var exp = Math.floor(helpers.log10(tickVal));
  				var significand = Math.floor(tickVal / Math.pow(10, exp)) + 1;
  
  				if (significand === 10) {
  					significand = 1;
  					++exp;
  				}
  
  				tickVal = significand * Math.pow(10, exp);
  			}
  
  			var lastTick = getValueOrDefault(tickOpts.max, tickVal);
  			ticks.push(lastTick);
  
  			if (!_this.isHorizontal()) {
  				// We are in a vertical orientation. The top value is the highest. So reverse the array
  				ticks.reverse();
  			}
  
  			// At this point, we need to update our max and min given the tick values since we have expanded the
  			// range of the scale
  			_this.max = helpers.max(ticks);
  			_this.min = helpers.min(ticks);
  
  			if (tickOpts.reverse) {
  				ticks.reverse();
  
  				_this.start = _this.max;
  				_this.end = _this.min;
  			} else {
  				_this.start = _this.min;
  				_this.end = _this.max;
  			}
  		},
  		convertTicksToLabels: function() {
  			this.tickValues = this.ticks.slice();
  
  			Chart.Scale.prototype.convertTicksToLabels.call(this);
  		},
  		// Get the correct tooltip label
  		getLabelForIndex: function(index, datasetIndex) {
  			return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
  		},
  		getPixelForTick: function(index, includeOffset) {
  			return this.getPixelForValue(this.tickValues[index], null, null, includeOffset);
  		},
  		getPixelForValue: function(value, index, datasetIndex, includeOffset) {
  			var _this = this;
  			var innerDimension;
  			var pixel;
  
  			var start = _this.start;
  			var newVal = +_this.getRightValue(value);
  			var range = helpers.log10(_this.end) - helpers.log10(start);
  			var paddingTop = _this.paddingTop;
  			var paddingBottom = _this.paddingBottom;
  			var paddingLeft = _this.paddingLeft;
  
  			if (_this.isHorizontal()) {
  
  				if (newVal === 0) {
  					pixel = _this.left + paddingLeft;
  				} else {
  					innerDimension = _this.width - (paddingLeft + _this.paddingRight);
  					pixel = _this.left + (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
  					pixel += paddingLeft;
  				}
  			} else {
  				// Bottom - top since pixels increase downard on a screen
  				if (newVal === 0) {
  					pixel = _this.top + paddingTop;
  				} else {
  					innerDimension = _this.height - (paddingTop + paddingBottom);
  					pixel = (_this.bottom - paddingBottom) - (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
  				}
  			}
  
  			return pixel;
  		},
  		getValueForPixel: function(pixel) {
  			var _this = this;
  			var offset;
  			var range = helpers.log10(_this.end) - helpers.log10(_this.start);
  			var value;
  			var innerDimension;
  
  			if (_this.isHorizontal()) {
  				innerDimension = _this.width - (_this.paddingLeft + _this.paddingRight);
  				value = _this.start * Math.pow(10, (pixel - _this.left - _this.paddingLeft) * range / innerDimension);
  			} else {
  				innerDimension = _this.height - (_this.paddingTop + _this.paddingBottom);
  				value = Math.pow(10, (_this.bottom - _this.paddingBottom - pixel) * range / innerDimension) / _this.start;
  			}
  
  			return value;
  		}
  	});
  	Chart.scaleService.registerScaleType("logarithmic", LogarithmicScale, defaultConfig);
  
  };
  },{}],41:[function(require,module,exports){
  "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers;
  	var globalDefaults = Chart.defaults.global;
  
  	var defaultConfig = {
  		display: true,
  
  		//Boolean - Whether to animate scaling the chart from the centre
  		animate: true,
  		lineArc: false,
  		position: "chartArea",
  
  		angleLines: {
  			display: true,
  			color: "rgba(0, 0, 0, 0.1)",
  			lineWidth: 1
  		},
  
  		// label settings
  		ticks: {
  			//Boolean - Show a backdrop to the scale label
  			showLabelBackdrop: true,
  
  			//String - The colour of the label backdrop
  			backdropColor: "rgba(255,255,255,0.75)",
  
  			//Number - The backdrop padding above & below the label in pixels
  			backdropPaddingY: 2,
  
  			//Number - The backdrop padding to the side of the label in pixels
  			backdropPaddingX: 2
  		},
  
  		pointLabels: {
  			//Number - Point label font size in pixels
  			fontSize: 10,
  
  			//Function - Used to convert point labels
  			callback: function(label) {
  				return label;
  			}
  		}
  	};
  
  	var LinearRadialScale = Chart.Scale.extend({
  		getValueCount: function() {
  			return this.chart.data.labels.length;
  		},
  		setDimensions: function() {
  			var options = this.options;
  			// Set the unconstrained dimension before label rotation
  			this.width = this.maxWidth;
  			this.height = this.maxHeight;
  			this.xCenter = Math.round(this.width / 2);
  			this.yCenter = Math.round(this.height / 2);
  
  			var minSize = helpers.min([this.height, this.width]);
  			var tickFontSize = helpers.getValueOrDefault(options.ticks.fontSize, globalDefaults.defaultFontSize);
  			this.drawingArea = (options.display) ? (minSize / 2) - (tickFontSize / 2 + options.ticks.backdropPaddingY) : (minSize / 2);
  		},
  		determineDataLimits: function() {
  			this.min = null;
  			this.max = null;
  
  			helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) {
  				if (this.chart.isDatasetVisible(datasetIndex)) {
  					var meta = this.chart.getDatasetMeta(datasetIndex);
  					helpers.each(dataset.data, function(rawValue, index) {
  						var value = +this.getRightValue(rawValue);
  						if (isNaN(value) || meta.data[index].hidden) {
  							return;
  						}
  
  						if (this.min === null) {
  							this.min = value;
  						} else if (value < this.min) {
  							this.min = value;
  						}
  
  						if (this.max === null) {
  							this.max = value;
  						} else if (value > this.max) {
  							this.max = value;
  						}
  					}, this);
  				}
  			}, this);
  
  			// If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
  			// do nothing since that would make the chart weird. If the user really wants a weird chart
  			// axis, they can manually override it
  			if (this.options.ticks.beginAtZero) {
  				var minSign = helpers.sign(this.min);
  				var maxSign = helpers.sign(this.max);
  
  				if (minSign < 0 && maxSign < 0) {
  					// move the top up to 0
  					this.max = 0;
  				} else if (minSign > 0 && maxSign > 0) {
  					// move the botttom down to 0
  					this.min = 0;
  				}
  			}
  
  			if (this.options.ticks.min !== undefined) {
  				this.min = this.options.ticks.min;
  			} else if (this.options.ticks.suggestedMin !== undefined) {
  				this.min = Math.min(this.min, this.options.ticks.suggestedMin);
  			}
  
  			if (this.options.ticks.max !== undefined) {
  				this.max = this.options.ticks.max;
  			} else if (this.options.ticks.suggestedMax !== undefined) {
  				this.max = Math.max(this.max, this.options.ticks.suggestedMax);
  			}
  
  			if (this.min === this.max) {
  				this.min--;
  				this.max++;
  			}
  		},
  		buildTicks: function() {
  
  
  			this.ticks = [];
  
  			// Figure out what the max number of ticks we can support it is based on the size of
  			// the axis area. For now, we say that the minimum tick spacing in pixels must be 50
  			// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
  			// the graph
  			var tickFontSize = helpers.getValueOrDefault(this.options.ticks.fontSize, globalDefaults.defaultFontSize);
  			var maxTicks = Math.min(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize)));
  			maxTicks = Math.max(2, maxTicks); // Make sure we always have at least 2 ticks
  
  			// To get a "nice" value for the tick spacing, we will use the appropriately named
  			// "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
  			// for details.
  
  			var niceRange = helpers.niceNum(this.max - this.min, false);
  			var spacing = helpers.niceNum(niceRange / (maxTicks - 1), true);
  			var niceMin = Math.floor(this.min / spacing) * spacing;
  			var niceMax = Math.ceil(this.max / spacing) * spacing;
  
  			var numSpaces = Math.ceil((niceMax - niceMin) / spacing);
  
  			// Put the values into the ticks array
  			this.ticks.push(this.options.ticks.min !== undefined ? this.options.ticks.min : niceMin);
  			for (var j = 1; j < numSpaces; ++j) {
  				this.ticks.push(niceMin + (j * spacing));
  			}
  			this.ticks.push(this.options.ticks.max !== undefined ? this.options.ticks.max : niceMax);
  
  			// At this point, we need to update our max and min given the tick values since we have expanded the
  			// range of the scale
  			this.max = helpers.max(this.ticks);
  			this.min = helpers.min(this.ticks);
  
  			if (this.options.ticks.reverse) {
  				this.ticks.reverse();
  
  				this.start = this.max;
  				this.end = this.min;
  			} else {
  				this.start = this.min;
  				this.end = this.max;
  			}
  
  			this.zeroLineIndex = this.ticks.indexOf(0);
  		},
  		convertTicksToLabels: function() {
  			Chart.Scale.prototype.convertTicksToLabels.call(this);
  
  			// Point labels
  			this.pointLabels = this.chart.data.labels.map(this.options.pointLabels.callback, this);
  		},
  		getLabelForIndex: function(index, datasetIndex) {
  			return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
  		},
  		fit: function() {
  			/*
  			 * Right, this is really confusing and there is a lot of maths going on here
  			 * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
  			 *
  			 * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
  			 *
  			 * Solution:
  			 *
  			 * We assume the radius of the polygon is half the size of the canvas at first
  			 * at each index we check if the text overlaps.
  			 *
  			 * Where it does, we store that angle and that index.
  			 *
  			 * After finding the largest index and angle we calculate how much we need to remove
  			 * from the shape radius to move the point inwards by that x.
  			 *
  			 * We average the left and right distances to get the maximum shape radius that can fit in the box
  			 * along with labels.
  			 *
  			 * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
  			 * on each side, removing that from the size, halving it and adding the left x protrusion width.
  			 *
  			 * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
  			 * and position it in the most space efficient manner
  			 *
  			 * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
  			 */
  
  			var pointLabels = this.options.pointLabels;
  			var pointLabelFontSize = helpers.getValueOrDefault(pointLabels.fontSize, globalDefaults.defaultFontSize);
  			var pointLabeFontStyle = helpers.getValueOrDefault(pointLabels.fontStyle, globalDefaults.defaultFontStyle);
  			var pointLabeFontFamily = helpers.getValueOrDefault(pointLabels.fontFamily, globalDefaults.defaultFontFamily);
  			var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily);
  
  			// Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
  			// Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
  			var largestPossibleRadius = helpers.min([(this.height / 2 - pointLabelFontSize - 5), this.width / 2]),
  				pointPosition,
  				i,
  				textWidth,
  				halfTextWidth,
  				furthestRight = this.width,
  				furthestRightIndex,
  				furthestRightAngle,
  				furthestLeft = 0,
  				furthestLeftIndex,
  				furthestLeftAngle,
  				xProtrusionLeft,
  				xProtrusionRight,
  				radiusReductionRight,
  				radiusReductionLeft,
  				maxWidthRadius;
  			this.ctx.font = pointLabeFont;
  
  			for (i = 0; i < this.getValueCount(); i++) {
  				// 5px to space the text slightly out - similar to what we do in the draw function.
  				pointPosition = this.getPointPosition(i, largestPossibleRadius);
  				textWidth = this.ctx.measureText(this.pointLabels[i] ? this.pointLabels[i] : '').width + 5;
  				if (i === 0 || i === this.getValueCount() / 2) {
  					// If we're at index zero, or exactly the middle, we're at exactly the top/bottom
  					// of the radar chart, so text will be aligned centrally, so we'll half it and compare
  					// w/left and right text sizes
  					halfTextWidth = textWidth / 2;
  					if (pointPosition.x + halfTextWidth > furthestRight) {
  						furthestRight = pointPosition.x + halfTextWidth;
  						furthestRightIndex = i;
  					}
  					if (pointPosition.x - halfTextWidth < furthestLeft) {
  						furthestLeft = pointPosition.x - halfTextWidth;
  						furthestLeftIndex = i;
  					}
  				} else if (i < this.getValueCount() / 2) {
  					// Less than half the values means we'll left align the text
  					if (pointPosition.x + textWidth > furthestRight) {
  						furthestRight = pointPosition.x + textWidth;
  						furthestRightIndex = i;
  					}
  				} else if (i > this.getValueCount() / 2) {
  					// More than half the values means we'll right align the text
  					if (pointPosition.x - textWidth < furthestLeft) {
  						furthestLeft = pointPosition.x - textWidth;
  						furthestLeftIndex = i;
  					}
  				}
  			}
  
  			xProtrusionLeft = furthestLeft;
  			xProtrusionRight = Math.ceil(furthestRight - this.width);
  
  			furthestRightAngle = this.getIndexAngle(furthestRightIndex);
  			furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
  
  			radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2);
  			radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2);
  
  			// Ensure we actually need to reduce the size of the chart
  			radiusReductionRight = (helpers.isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
  			radiusReductionLeft = (helpers.isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
  
  			this.drawingArea = Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2);
  			this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
  		},
  		setCenterPoint: function(leftMovement, rightMovement) {
  
  			var maxRight = this.width - rightMovement - this.drawingArea,
  				maxLeft = leftMovement + this.drawingArea;
  
  			this.xCenter = Math.round(((maxLeft + maxRight) / 2) + this.left);
  			// Always vertically in the centre as the text height doesn't change
  			this.yCenter = Math.round((this.height / 2) + this.top);
  		},
  
  		getIndexAngle: function(index) {
  			var angleMultiplier = (Math.PI * 2) / this.getValueCount();
  			// Start from the top instead of right, so remove a quarter of the circle
  
  			return index * angleMultiplier - (Math.PI / 2);
  		},
  		getDistanceFromCenterForValue: function(value) {
  			if (value === null) {
  				return 0; // null always in center
  			}
  
  			// Take into account half font size + the yPadding of the top value
  			var scalingFactor = this.drawingArea / (this.max - this.min);
  			if (this.options.reverse) {
  				return (this.max - value) * scalingFactor;
  			} else {
  				return (value - this.min) * scalingFactor;
  			}
  		},
  		getPointPosition: function(index, distanceFromCenter) {
  			var thisAngle = this.getIndexAngle(index);
  			return {
  				x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
  				y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
  			};
  		},
  		getPointPositionForValue: function(index, value) {
  			return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
  		},
  
  		getBasePosition: function() {
  			var me = this;
  			var min = me.min;
  			var max = me.max;
  
  			return me.getPointPositionForValue(0,
  				me.beginAtZero? 0:
  				min < 0 && max < 0? max :
  				min > 0 && max > 0? min :
  				0);
  		},
  
  		draw: function() {
  			if (this.options.display) {
  				var ctx = this.ctx;
  				helpers.each(this.ticks, function(label, index) {
  					// Don't draw a centre value (if it is minimum)
  					if (index > 0 || this.options.reverse) {
  						var yCenterOffset = this.getDistanceFromCenterForValue(this.ticks[index]);
  						var yHeight = this.yCenter - yCenterOffset;
  
  						// Draw circular lines around the scale
  						if (this.options.gridLines.display) {
  							ctx.strokeStyle = this.options.gridLines.color;
  							ctx.lineWidth = this.options.gridLines.lineWidth;
  
  							if (this.options.lineArc) {
  								// Draw circular arcs between the points
  								ctx.beginPath();
  								ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI * 2);
  								ctx.closePath();
  								ctx.stroke();
  							} else {
  								// Draw straight lines connecting each index
  								ctx.beginPath();
  								for (var i = 0; i < this.getValueCount(); i++) {
  									var pointPosition = this.getPointPosition(i, this.getDistanceFromCenterForValue(this.ticks[index]));
  									if (i === 0) {
  										ctx.moveTo(pointPosition.x, pointPosition.y);
  									} else {
  										ctx.lineTo(pointPosition.x, pointPosition.y);
  									}
  								}
  								ctx.closePath();
  								ctx.stroke();
  							}
  						}
  
  						if (this.options.ticks.display) {
  							var tickFontColor = helpers.getValueOrDefault(this.options.ticks.fontColor, globalDefaults.defaultFontColor);
  							var tickFontSize = helpers.getValueOrDefault(this.options.ticks.fontSize, globalDefaults.defaultFontSize);
  							var tickFontStyle = helpers.getValueOrDefault(this.options.ticks.fontStyle, globalDefaults.defaultFontStyle);
  							var tickFontFamily = helpers.getValueOrDefault(this.options.ticks.fontFamily, globalDefaults.defaultFontFamily);
  							var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
  							ctx.font = tickLabelFont;
  
  							if (this.options.ticks.showLabelBackdrop) {
  								var labelWidth = ctx.measureText(label).width;
  								ctx.fillStyle = this.options.ticks.backdropColor;
  								ctx.fillRect(
  									this.xCenter - labelWidth / 2 - this.options.ticks.backdropPaddingX,
  									yHeight - tickFontSize / 2 - this.options.ticks.backdropPaddingY,
  									labelWidth + this.options.ticks.backdropPaddingX * 2,
  									tickFontSize + this.options.ticks.backdropPaddingY * 2
  								);
  							}
  
  							ctx.textAlign = 'center';
  							ctx.textBaseline = "middle";
  							ctx.fillStyle = tickFontColor;
  							ctx.fillText(label, this.xCenter, yHeight);
  						}
  					}
  				}, this);
  
  				if (!this.options.lineArc) {
  					ctx.lineWidth = this.options.angleLines.lineWidth;
  					ctx.strokeStyle = this.options.angleLines.color;
  
  					for (var i = this.getValueCount() - 1; i >= 0; i--) {
  						if (this.options.angleLines.display) {
  							var outerPosition = this.getPointPosition(i, this.getDistanceFromCenterForValue(this.options.reverse ? this.min : this.max));
  							ctx.beginPath();
  							ctx.moveTo(this.xCenter, this.yCenter);
  							ctx.lineTo(outerPosition.x, outerPosition.y);
  							ctx.stroke();
  							ctx.closePath();
  						}
  						// Extra 3px out for some label spacing
  						var pointLabelPosition = this.getPointPosition(i, this.getDistanceFromCenterForValue(this.options.reverse ? this.min : this.max) + 5);
  
  						var pointLabelFontColor = helpers.getValueOrDefault(this.options.pointLabels.fontColor, globalDefaults.defaultFontColor);
  						var pointLabelFontSize = helpers.getValueOrDefault(this.options.pointLabels.fontSize, globalDefaults.defaultFontSize);
  						var pointLabeFontStyle = helpers.getValueOrDefault(this.options.pointLabels.fontStyle, globalDefaults.defaultFontStyle);
  						var pointLabeFontFamily = helpers.getValueOrDefault(this.options.pointLabels.fontFamily, globalDefaults.defaultFontFamily);
  						var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily);
  
  						ctx.font = pointLabeFont;
  						ctx.fillStyle = pointLabelFontColor;
  
  						var labelsCount = this.pointLabels.length,
  							halfLabelsCount = this.pointLabels.length / 2,
  							quarterLabelsCount = halfLabelsCount / 2,
  							upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
  							exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
  						if (i === 0) {
  							ctx.textAlign = 'center';
  						} else if (i === halfLabelsCount) {
  							ctx.textAlign = 'center';
  						} else if (i < halfLabelsCount) {
  							ctx.textAlign = 'left';
  						} else {
  							ctx.textAlign = 'right';
  						}
  
  						// Set the correct text baseline based on outer positioning
  						if (exactQuarter) {
  							ctx.textBaseline = 'middle';
  						} else if (upperHalf) {
  							ctx.textBaseline = 'bottom';
  						} else {
  							ctx.textBaseline = 'top';
  						}
  
  						ctx.fillText(this.pointLabels[i] ? this.pointLabels[i] : '', pointLabelPosition.x, pointLabelPosition.y);
  					}
  				}
  			}
  		}
  	});
  	Chart.scaleService.registerScaleType("radialLinear", LinearRadialScale, defaultConfig);
  
  };
  },{}],42:[function(require,module,exports){
  /*global window: false */
  "use strict";
  
  var moment = require('moment');
  moment = typeof(moment) === 'function' ? moment : window.moment;
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers;
  	var time = {
  		units: [{
  			name: 'millisecond',
  			steps: [1, 2, 5, 10, 20, 50, 100, 250, 500]
  		}, {
  			name: 'second',
  			steps: [1, 2, 5, 10, 30]
  		}, {
  			name: 'minute',
  			steps: [1, 2, 5, 10, 30]
  		}, {
  			name: 'hour',
  			steps: [1, 2, 3, 6, 12]
  		}, {
  			name: 'day',
  			steps: [1, 2, 5]
  		}, {
  			name: 'week',
  			maxStep: 4
  		}, {
  			name: 'month',
  			maxStep: 3
  		}, {
  			name: 'quarter',
  			maxStep: 4
  		}, {
  			name: 'year',
  			maxStep: false
  		}]
  	};
  
  	var defaultConfig = {
  		position: "bottom",
  
  		time: {
  			parser: false, // false == a pattern string from http://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment
  			format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/
  			unit: false, // false == automatic or override with week, month, year, etc.
  			round: false, // none, or override with week, month, year, etc.
  			displayFormat: false, // DEPRECATED
  			isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/
  
  			// defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/
  			displayFormats: {
  				'millisecond': 'h:mm:ss.SSS a', // 11:20:01.123 AM,
  				'second': 'h:mm:ss a', // 11:20:01 AM
  				'minute': 'h:mm:ss a', // 11:20:01 AM
  				'hour': 'MMM D, hA', // Sept 4, 5PM
  				'day': 'll', // Sep 4 2015
  				'week': 'll', // Week 46, or maybe "[W]WW - YYYY" ?
  				'month': 'MMM YYYY', // Sept 2015
  				'quarter': '[Q]Q - YYYY', // Q3
  				'year': 'YYYY' // 2015
  			}
  		},
  		ticks: {
  			autoSkip: false
  		}
  	};
  
  	var TimeScale = Chart.Scale.extend({
  		initialize: function() {
  			if (!moment) {
  				throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com');
  			}
  
  			Chart.Scale.prototype.initialize.call(this);
  		},
  		getLabelMoment: function(datasetIndex, index) {
  			return this.labelMoments[datasetIndex][index];
  		},
  		getMomentStartOf: function(tick) {
  			if (this.options.time.unit === 'week' && this.options.time.isoWeekday !== false) {
  				return tick.clone().startOf('isoWeek').isoWeekday(this.options.time.isoWeekday);
  			} else {
  				return tick.clone().startOf(this.tickUnit);
  			}
  		},
  		determineDataLimits: function() {
  			this.labelMoments = [];
  
  			// Only parse these once. If the dataset does not have data as x,y pairs, we will use
  			// these
  			var scaleLabelMoments = [];
  			if (this.chart.data.labels && this.chart.data.labels.length > 0) {
  				helpers.each(this.chart.data.labels, function(label, index) {
  					var labelMoment = this.parseTime(label);
  
  					if (labelMoment.isValid()) {
  						if (this.options.time.round) {
  							labelMoment.startOf(this.options.time.round);
  						}
  						scaleLabelMoments.push(labelMoment);
  					}
  				}, this);
  
  				this.firstTick = moment.min.call(this, scaleLabelMoments);
  				this.lastTick = moment.max.call(this, scaleLabelMoments);
  			} else {
  				this.firstTick = null;
  				this.lastTick = null;
  			}
  
  			helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) {
  				var momentsForDataset = [];
  				var datasetVisible = this.chart.isDatasetVisible(datasetIndex);
  
  				if (typeof dataset.data[0] === 'object' && dataset.data[0] !== null) {
  					helpers.each(dataset.data, function(value, index) {
  						var labelMoment = this.parseTime(this.getRightValue(value));
  
  						if (labelMoment.isValid()) {
  							if (this.options.time.round) {
  								labelMoment.startOf(this.options.time.round);
  							}
  							momentsForDataset.push(labelMoment);
  
  							if (datasetVisible) {
  								// May have gone outside the scale ranges, make sure we keep the first and last ticks updated
  								this.firstTick = this.firstTick !== null ? moment.min(this.firstTick, labelMoment) : labelMoment;
  								this.lastTick = this.lastTick !== null ? moment.max(this.lastTick, labelMoment) : labelMoment;
  							}
  						}
  					}, this);
  				} else {
  					// We have no labels. Use the ones from the scale
  					momentsForDataset = scaleLabelMoments;
  				}
  
  				this.labelMoments.push(momentsForDataset);
  			}, this);
  
  			// Set these after we've done all the data
  			if (this.options.time.min) {
  				this.firstTick = this.parseTime(this.options.time.min);
  			}
  
  			if (this.options.time.max) {
  				this.lastTick = this.parseTime(this.options.time.max);
  			}
  
  			// We will modify these, so clone for later
  			this.firstTick = (this.firstTick || moment()).clone();
  			this.lastTick = (this.lastTick || moment()).clone();
  		},
  		buildTicks: function(index) {
  
  			this.ctx.save();
  			var tickFontSize = helpers.getValueOrDefault(this.options.ticks.fontSize, Chart.defaults.global.defaultFontSize);
  			var tickFontStyle = helpers.getValueOrDefault(this.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle);
  			var tickFontFamily = helpers.getValueOrDefault(this.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily);
  			var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
  			this.ctx.font = tickLabelFont;
  
  			this.ticks = [];
  			this.unitScale = 1; // How much we scale the unit by, ie 2 means 2x unit per step
  			this.scaleSizeInUnits = 0; // How large the scale is in the base unit (seconds, minutes, etc)
  
  			// Set unit override if applicable
  			if (this.options.time.unit) {
  				this.tickUnit = this.options.time.unit || 'day';
  				this.displayFormat = this.options.time.displayFormats[this.tickUnit];
  				this.scaleSizeInUnits = this.lastTick.diff(this.firstTick, this.tickUnit, true);
  				this.unitScale = helpers.getValueOrDefault(this.options.time.unitStepSize, 1);
  			} else {
  				// Determine the smallest needed unit of the time
  				var innerWidth = this.isHorizontal() ? this.width - (this.paddingLeft + this.paddingRight) : this.height - (this.paddingTop + this.paddingBottom);
  
  				// Crude approximation of what the label length might be
  				var tempFirstLabel = this.tickFormatFunction(this.firstTick, 0, []);
  				var tickLabelWidth = this.ctx.measureText(tempFirstLabel).width;
  				var cosRotation = Math.cos(helpers.toRadians(this.options.ticks.maxRotation));
  				var sinRotation = Math.sin(helpers.toRadians(this.options.ticks.maxRotation));
  				tickLabelWidth = (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation);
  				var labelCapacity = innerWidth / (tickLabelWidth);
  
  				// Start as small as possible
  				this.tickUnit = 'millisecond';
  				this.scaleSizeInUnits = this.lastTick.diff(this.firstTick, this.tickUnit, true);
  				this.displayFormat = this.options.time.displayFormats[this.tickUnit];
  
  				var unitDefinitionIndex = 0;
  				var unitDefinition = time.units[unitDefinitionIndex];
  
  				// While we aren't ideal and we don't have units left
  				while (unitDefinitionIndex < time.units.length) {
  					// Can we scale this unit. If `false` we can scale infinitely
  					this.unitScale = 1;
  
  					if (helpers.isArray(unitDefinition.steps) && Math.ceil(this.scaleSizeInUnits / labelCapacity) < helpers.max(unitDefinition.steps)) {
  						// Use one of the prefedined steps
  						for (var idx = 0; idx < unitDefinition.steps.length; ++idx) {
  							if (unitDefinition.steps[idx] >= Math.ceil(this.scaleSizeInUnits / labelCapacity)) {
  								this.unitScale = helpers.getValueOrDefault(this.options.time.unitStepSize, unitDefinition.steps[idx]);
  								break;
  							}
  						}
  
  						break;
  					} else if ((unitDefinition.maxStep === false) || (Math.ceil(this.scaleSizeInUnits / labelCapacity) < unitDefinition.maxStep)) {
  						// We have a max step. Scale this unit
  						this.unitScale = helpers.getValueOrDefault(this.options.time.unitStepSize, Math.ceil(this.scaleSizeInUnits / labelCapacity));
  						break;
  					} else {
  						// Move to the next unit up
  						++unitDefinitionIndex;
  						unitDefinition = time.units[unitDefinitionIndex];
  
  						this.tickUnit = unitDefinition.name;
  						var leadingUnitBuffer = this.firstTick.diff(this.getMomentStartOf(this.firstTick), this.tickUnit, true);
  						var trailingUnitBuffer = this.getMomentStartOf(this.lastTick.clone().add(1, this.tickUnit)).diff(this.lastTick, this.tickUnit, true);
  						this.scaleSizeInUnits = this.lastTick.diff(this.firstTick, this.tickUnit, true) + leadingUnitBuffer + trailingUnitBuffer;
  						this.displayFormat = this.options.time.displayFormats[unitDefinition.name];
  					}
  				}
  			}
  
  			var roundedStart;
  
  			// Only round the first tick if we have no hard minimum
  			if (!this.options.time.min) {
  				this.firstTick = this.getMomentStartOf(this.firstTick);
  				roundedStart = this.firstTick;
  			} else {
  				roundedStart = this.getMomentStartOf(this.firstTick);
  			}
  
  			// Only round the last tick if we have no hard maximum
  			if (!this.options.time.max) {
  				var roundedEnd = this.getMomentStartOf(this.lastTick);
  				if (roundedEnd.diff(this.lastTick, this.tickUnit, true) !== 0) {
  					// Do not use end of because we need this to be in the next time unit
  					this.lastTick = this.getMomentStartOf(this.lastTick.add(1, this.tickUnit));
  				}
  			}
  
  			this.smallestLabelSeparation = this.width;
  
  			helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) {
  				for (var i = 1; i < this.labelMoments[datasetIndex].length; i++) {
  					this.smallestLabelSeparation = Math.min(this.smallestLabelSeparation, this.labelMoments[datasetIndex][i].diff(this.labelMoments[datasetIndex][i - 1], this.tickUnit, true));
  				}
  			}, this);
  
  			// Tick displayFormat override
  			if (this.options.time.displayFormat) {
  				this.displayFormat = this.options.time.displayFormat;
  			}
  
  			// first tick. will have been rounded correctly if options.time.min is not specified
  			this.ticks.push(this.firstTick.clone());
  
  			// For every unit in between the first and last moment, create a moment and add it to the ticks tick
  			for (var i = 1; i <= this.scaleSizeInUnits; ++i) {
  				var newTick = roundedStart.clone().add(i, this.tickUnit);
  
  				// Are we greater than the max time
  				if (this.options.time.max && newTick.diff(this.lastTick, this.tickUnit, true) >= 0) {
  					break;
  				}
  
  				if (i % this.unitScale === 0) {
  					this.ticks.push(newTick);
  				}
  			}
  
  			// Always show the right tick
  			var diff = this.ticks[this.ticks.length - 1].diff(this.lastTick, this.tickUnit);
  			if (diff !== 0 || this.scaleSizeInUnits === 0) {
  				// this is a weird case. If the <max> option is the same as the end option, we can't just diff the times because the tick was created from the roundedStart
  				// but the last tick was not rounded.
  				if (this.options.time.max) {
  					this.ticks.push(this.lastTick.clone());
  					this.scaleSizeInUnits = this.lastTick.diff(this.ticks[0], this.tickUnit, true);
  				} else {
  					this.ticks.push(this.lastTick.clone());
  					this.scaleSizeInUnits = this.lastTick.diff(this.firstTick, this.tickUnit, true);
  				}
  			}
  
  			this.ctx.restore();
  		},
  		// Get tooltip label
  		getLabelForIndex: function(index, datasetIndex) {
  			var label = this.chart.data.labels && index < this.chart.data.labels.length ? this.chart.data.labels[index] : '';
  
  			if (typeof this.chart.data.datasets[datasetIndex].data[0] === 'object') {
  				label = this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
  			}
  
  			// Format nicely
  			if (this.options.time.tooltipFormat) {
  				label = this.parseTime(label).format(this.options.time.tooltipFormat);
  			}
  
  			return label;
  		},
  		// Function to format an individual tick mark
  		tickFormatFunction: function tickFormatFunction(tick, index, ticks) {
  			var formattedTick = tick.format(this.displayFormat);
  			var tickOpts = this.options.ticks;
  			var callback = helpers.getValueOrDefault(tickOpts.callback, tickOpts.userCallback);
  
  			if (callback) {
  				return callback(formattedTick, index, ticks);
  			} else {
  				return formattedTick;
  			}
  		},
  		convertTicksToLabels: function() {
  			this.tickMoments = this.ticks;
  			this.ticks = this.ticks.map(this.tickFormatFunction, this);
  		},
  		getPixelForValue: function(value, index, datasetIndex, includeOffset) {
  			var labelMoment = value && value.isValid && value.isValid() ? value : this.getLabelMoment(datasetIndex, index);
  
  			if (labelMoment) {
  				var offset = labelMoment.diff(this.firstTick, this.tickUnit, true);
  
  				var decimal = offset / this.scaleSizeInUnits;
  
  				if (this.isHorizontal()) {
  					var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
  					var valueWidth = innerWidth / Math.max(this.ticks.length - 1, 1);
  					var valueOffset = (innerWidth * decimal) + this.paddingLeft;
  
  					return this.left + Math.round(valueOffset);
  				} else {
  					var innerHeight = this.height - (this.paddingTop + this.paddingBottom);
  					var valueHeight = innerHeight / Math.max(this.ticks.length - 1, 1);
  					var heightOffset = (innerHeight * decimal) + this.paddingTop;
  
  					return this.top + Math.round(heightOffset);
  				}
  			}
  		},
  		getPixelForTick: function(index, includeOffset) {
  			return this.getPixelForValue(this.tickMoments[index], null, null, includeOffset);
  		},
  		getValueForPixel: function(pixel) {
  			var innerDimension = this.isHorizontal() ? this.width - (this.paddingLeft + this.paddingRight) : this.height - (this.paddingTop + this.paddingBottom);
  			var offset = (pixel - (this.isHorizontal() ? this.left + this.paddingLeft : this.top + this.paddingTop)) / innerDimension;
  			offset *= this.scaleSizeInUnits;
  			return this.firstTick.clone().add(moment.duration(offset, this.tickUnit).asSeconds(), 'seconds');
  		},
  		parseTime: function(label) {
  			if (typeof this.options.time.parser === 'string') {
  				return moment(label, this.options.time.parser);
  			}
  			if (typeof this.options.time.parser === 'function') {
  				return this.options.time.parser(label);
  			}
  			// Date objects
  			if (typeof label.getMonth === 'function' || typeof label === 'number') {
  				return moment(label);
  			}
  			// Moment support
  			if (label.isValid && label.isValid()) {
  				return label;
  			}
  			// Custom parsing (return an instance of moment)
  			if (typeof this.options.time.format !== 'string' && this.options.time.format.call) {
  				console.warn("options.time.format is deprecated and replaced by options.time.parser. See http://nnnick.github.io/Chart.js/docs-v2/#scales-time-scale");
  				return this.options.time.format(label);
  			}
  			// Moment format parsing
  			return moment(label, this.options.time.format);
  		}
  	});
  	Chart.scaleService.registerScaleType("time", TimeScale, defaultConfig);
  
  };
  
  },{"moment":1}]},{},[7]);
  
  /*! jCarousel - v0.3.7 - 2018-02-17
  * http://sorgalla.com/jcarousel/
  * Copyright (c) 2006-2018 Jan Sorgalla; Licensed MIT */
  
  !function(t){"use strict";var i=t.jCarousel={};i.version="0.3.7";var s=/^([+\-]=)?(.+)$/;i.parseTarget=function(t){var i=!1,e="object"!=typeof t?s.exec(t):null;return e?(t=parseInt(e[2],10)||0,e[1]&&(i=!0,"-="===e[1]&&(t*=-1))):"object"!=typeof t&&(t=parseInt(t,10)||0),{target:t,relative:i}},i.detectCarousel=function(t){for(var i;t.length>0;){if((i=t.filter("[data-jcarousel]")).length>0)return i;if((i=t.find("[data-jcarousel]")).length>0)return i;t=t.parent()}return null},i.base=function(s){return{version:i.version,_options:{},_element:null,_carousel:null,_init:t.noop,_create:t.noop,_destroy:t.noop,_reload:t.noop,create:function(){return this._element.attr("data-"+s.toLowerCase(),!0).data(s,this),!1===this._trigger("create")?this:(this._create(),this._trigger("createend"),this)},destroy:function(){return!1===this._trigger("destroy")?this:(this._destroy(),this._trigger("destroyend"),this._element.removeData(s).removeAttr("data-"+s.toLowerCase()),this)},reload:function(t){return!1===this._trigger("reload")?this:(t&&this.options(t),this._reload(),this._trigger("reloadend"),this)},element:function(){return this._element},options:function(i,s){if(0===arguments.length)return t.extend({},this._options);if("string"==typeof i){if(void 0===s)return void 0===this._options[i]?null:this._options[i];this._options[i]=s}else this._options=t.extend({},this._options,i);return this},carousel:function(){return this._carousel||(this._carousel=i.detectCarousel(this.options("carousel")||this._element),this._carousel||t.error('Could not detect carousel for plugin "'+s+'"')),this._carousel},_trigger:function(i,e,r){var n,o=!1;return r=[this].concat(r||[]),(e||this._element).each(function(){n=t.Event((s+":"+i).toLowerCase()),t(this).trigger(n,r),n.isDefaultPrevented()&&(o=!0)}),!o}}},i.plugin=function(s,e){var r=t[s]=function(i,s){this._element=t(i),this.options(s),this._init(),this.create()};return r.fn=r.prototype=t.extend({},i.base(s),e),t.fn[s]=function(i){var e=Array.prototype.slice.call(arguments,1),n=this;return"string"==typeof i?this.each(function(){var r=t(this).data(s);if(!r)return t.error("Cannot call methods on "+s+' prior to initialization; attempted to call method "'+i+'"');if(!t.isFunction(r[i])||"_"===i.charAt(0))return t.error('No such method "'+i+'" for '+s+" instance");var o=r[i].apply(r,e);return o!==r&&void 0!==o?(n=o,!1):void 0}):this.each(function(){var e=t(this).data(s);e instanceof r?e.reload(i):new r(this,i)}),n},r}}(jQuery),function(t,i){"use strict";var s=t(i),e=function(t){return parseFloat(t)||0};t.jCarousel.plugin("jcarousel",{animating:!1,tail:0,inTail:!1,resizeState:null,resizeTimer:null,lt:null,vertical:!1,rtl:!1,circular:!1,underflow:!1,relative:!1,_options:{list:function(){return this.element().children().eq(0)},items:function(){return this.list().children()},animation:400,transitions:!1,wrap:null,vertical:null,rtl:null,center:!1},_list:null,_items:null,_target:t(),_first:t(),_last:t(),_visible:t(),_fullyvisible:t(),_init:function(){var t=this;return t.resizeState=s.width()+"x"+s.height(),this.onWindowResize=function(){t.resizeTimer&&clearTimeout(t.resizeTimer),t.resizeTimer=setTimeout(function(){var i=s.width()+"x"+s.height();i!==t.resizeState&&(t.resizeState=i,t.reload())},100)},this},_create:function(){this._reload(),s.on("resize.jcarousel",this.onWindowResize)},_destroy:function(){s.off("resize.jcarousel",this.onWindowResize)},_reload:function(){this.vertical=this.options("vertical"),null==this.vertical&&(this.vertical=e(this.list().height())>e(this.list().width())),this.rtl=this.options("rtl"),null==this.rtl&&(this.rtl=function(i){if("rtl"===(""+i.attr("dir")).toLowerCase())return!0;var s=!1;return i.parents("[dir]").each(function(){if(/rtl/i.test(t(this).attr("dir")))return s=!0,!1}),s}(this._element)),this.lt=this.vertical?"top":"left",this.relative="relative"===this.list().css("position"),this._list=null,this._items=null;var i=this.index(this._target)>=0?this._target:this.closest();this.circular="circular"===this.options("wrap"),this.underflow=!1;var s={left:0,top:0};return i.length>0&&(this._prepare(i),this.list().find("[data-jcarousel-clone]").remove(),this._items=null,this.underflow=this._fullyvisible.length>=this.items().length,this.circular=this.circular&&!this.underflow,s[this.lt]=this._position(i)+"px"),this.move(s),this},list:function(){if(null===this._list){var i=this.options("list");this._list=t.isFunction(i)?i.call(this):this._element.find(i)}return this._list},items:function(){if(null===this._items){var i=this.options("items");this._items=(t.isFunction(i)?i.call(this):this.list().find(i)).not("[data-jcarousel-clone]")}return this._items},index:function(t){return this.items().index(t)},closest:function(){var i,s=this,r=this.list().position()[this.lt],n=t(),o=!1,l=this.vertical?"bottom":this.rtl&&!this.relative?"left":"right";return this.rtl&&this.relative&&!this.vertical&&(r+=e(this.list().width())-this.clipping()),this.items().each(function(){if(n=t(this),o)return!1;var a=s.dimension(n);if((r+=a)>=0){if(i=a-e(n.css("margin-"+l)),!(Math.abs(r)-a+i/2<=0))return!1;o=!0}}),n},target:function(){return this._target},first:function(){return this._first},last:function(){return this._last},visible:function(){return this._visible},fullyvisible:function(){return this._fullyvisible},hasNext:function(){if(!1===this._trigger("hasnext"))return!0;var t=this.options("wrap"),i=this.items().length-1,s=this.options("center")?this._target:this._last;return!!(i>=0&&!this.underflow&&(t&&"first"!==t||this.index(s)<i||this.tail&&!this.inTail))},hasPrev:function(){if(!1===this._trigger("hasprev"))return!0;var t=this.options("wrap");return!!(this.items().length>0&&!this.underflow&&(t&&"last"!==t||this.index(this._first)>0||this.tail&&this.inTail))},clipping:function(){return e(this._element["inner"+(this.vertical?"Height":"Width")]())},dimension:function(t){return e(t["outer"+(this.vertical?"Height":"Width")](!0))},scroll:function(i,s,e){if(this.animating)return this;if(!1===this._trigger("scroll",null,[i,s]))return this;t.isFunction(s)&&(e=s,s=!0);var r=t.jCarousel.parseTarget(i);if(r.relative){var n,o,l,a,h,u,c,f,d=this.items().length-1,_=Math.abs(r.target),p=this.options("wrap");if(r.target>0){var g=this.index(this._last);if(g>=d&&this.tail)this.inTail?"both"===p||"last"===p?this._scroll(0,s,e):t.isFunction(e)&&e.call(this,!1):this._scrollTail(s,e);else if(n=this.index(this._target),this.underflow&&n===d&&("circular"===p||"both"===p||"last"===p)||!this.underflow&&g===d&&("both"===p||"last"===p))this._scroll(0,s,e);else if(l=n+_,this.circular&&l>d){for(f=d,h=this.items().get(-1);f++<l;)h=this.items().eq(0),(u=this._visible.index(h)>=0)&&h.after(h.clone(!0).attr("data-jcarousel-clone",!0)),this.list().append(h),u||((c={})[this.lt]=this.dimension(h),this.moveBy(c)),this._items=null;this._scroll(h,s,e)}else this._scroll(Math.min(l,d),s,e)}else if(this.inTail)this._scroll(Math.max(this.index(this._first)-_+1,0),s,e);else if(o=this.index(this._first),n=this.index(this._target),l=(a=this.underflow?n:o)-_,a<=0&&(this.underflow&&"circular"===p||"both"===p||"first"===p))this._scroll(d,s,e);else if(this.circular&&l<0){for(f=l,h=this.items().get(0);f++<0;){h=this.items().eq(-1),(u=this._visible.index(h)>=0)&&h.after(h.clone(!0).attr("data-jcarousel-clone",!0)),this.list().prepend(h),this._items=null;var v=this.dimension(h);(c={})[this.lt]=-v,this.moveBy(c)}this._scroll(h,s,e)}else this._scroll(Math.max(l,0),s,e)}else this._scroll(r.target,s,e);return this._trigger("scrollend"),this},moveBy:function(t,i){var s=this.list().position(),r=1,n=0;return this.rtl&&!this.vertical&&(r=-1,this.relative&&(n=e(this.list().width())-this.clipping())),t.left&&(t.left=e(s.left)+n+e(t.left)*r+"px"),t.top&&(t.top=e(s.top)+n+e(t.top)*r+"px"),this.move(t,i)},move:function(i,s){s=s||{};var e=this.options("transitions"),r=!!e,n=!!e.transforms,o=!!e.transforms3d,l=s.duration||0,a=this.list();if(!r&&l>0)a.animate(i,s);else{var h=s.complete||t.noop,u={};if(r){var c={transitionDuration:a.css("transitionDuration"),transitionTimingFunction:a.css("transitionTimingFunction"),transitionProperty:a.css("transitionProperty")},f=h;h=function(){t(this).css(c),f.call(this)},u={transitionDuration:(l>0?l/1e3:0)+"s",transitionTimingFunction:e.easing||s.easing,transitionProperty:l>0?n||o?"all":i.left?"left":"top":"none",transform:"none"}}o?u.transform="translate3d("+(i.left||0)+","+(i.top||0)+",0)":n?u.transform="translate("+(i.left||0)+","+(i.top||0)+")":t.extend(u,i),r&&l>0&&a.one("transitionend webkitTransitionEnd oTransitionEnd otransitionend MSTransitionEnd",h),a.css(u),l<=0&&a.each(function(){h.call(this)})}},_scroll:function(i,s,r){if(this.animating)return t.isFunction(r)&&r.call(this,!1),this;if("object"!=typeof i?i=this.items().eq(i):void 0===i.jquery&&(i=t(i)),0===i.length)return t.isFunction(r)&&r.call(this,!1),this;this.inTail=!1,this._prepare(i);var n=this._position(i);if(n===e(this.list().position()[this.lt]))return t.isFunction(r)&&r.call(this,!1),this;var o={};return o[this.lt]=n+"px",this._animate(o,s,r),this},_scrollTail:function(i,s){if(this.animating||!this.tail)return t.isFunction(s)&&s.call(this,!1),this;var r=this.list().position()[this.lt];this.rtl&&this.relative&&!this.vertical&&(r+=e(this.list().width())-this.clipping()),this.rtl&&!this.vertical?r+=this.tail:r-=this.tail,this.inTail=!0;var n={};return n[this.lt]=r+"px",this._update({target:this._target.next(),fullyvisible:this._fullyvisible.slice(1).add(this._visible.last())}),this._animate(n,i,s),this},_animate:function(i,s,e){if(e=e||t.noop,!1===this._trigger("animate"))return e.call(this,!1),this;this.animating=!0;var r=this.options("animation"),n=t.proxy(function(){this.animating=!1;var t=this.list().find("[data-jcarousel-clone]");t.length>0&&(t.remove(),this._reload()),this._trigger("animateend"),e.call(this,!0)},this),o="object"==typeof r?t.extend({},r):{duration:r},l=o.complete||t.noop;return!1===s?o.duration=0:void 0!==t.fx.speeds[o.duration]&&(o.duration=t.fx.speeds[o.duration]),o.complete=function(){n(),l.call(this)},this.move(i,o),this},_prepare:function(i){var s,r,n,o=this.index(i),l=o,a=this.dimension(i),h=this.clipping(),u=this.vertical?"bottom":this.rtl?"left":"right",c=this.options("center"),f={target:i,first:i,last:i,visible:i,fullyvisible:a<=h?i:t()};if(c&&(a/=2,h/=2),a<h)for(;;){if(0===(s=this.items().eq(++l)).length){if(!this.circular)break;if(s=this.items().eq(0),i.get(0)===s.get(0))break;if((r=this._visible.index(s)>=0)&&s.after(s.clone(!0).attr("data-jcarousel-clone",!0)),this.list().append(s),!r){var d={};d[this.lt]=this.dimension(s),this.moveBy(d)}this._items=null}if(0===(n=this.dimension(s)))break;if(a+=n,f.last=s,f.visible=f.visible.add(s),a-e(s.css("margin-"+u))<=h&&(f.fullyvisible=f.fullyvisible.add(s)),a>=h)break}if(!this.circular&&!c&&a<h)for(l=o;!(--l<0||0===(s=this.items().eq(l)).length||0===(n=this.dimension(s))||(a+=n,f.first=s,f.visible=f.visible.add(s),a-e(s.css("margin-"+u))<=h&&(f.fullyvisible=f.fullyvisible.add(s)),a>=h)););return this._update(f),this.tail=0,c||"circular"===this.options("wrap")||"custom"===this.options("wrap")||this.index(f.last)!==this.items().length-1||(a-=e(f.last.css("margin-"+u)))>h&&(this.tail=a-h),this},_position:function(t){var i=this._first,s=e(i.position()[this.lt]),r=this.options("center"),n=r?this.clipping()/2-this.dimension(i)/2:0;return this.rtl&&!this.vertical?(this.relative?s-=e(this.list().width())-this.dimension(i):s-=this.clipping()-this.dimension(i),s+=n):s-=n,!r&&(this.index(t)>this.index(i)||this.inTail)&&this.tail?(s=this.rtl&&!this.vertical?s-this.tail:s+this.tail,this.inTail=!0):this.inTail=!1,-s},_update:function(i){var s,e=this,r={target:this._target,first:this._first,last:this._last,visible:this._visible,fullyvisible:this._fullyvisible},n=this.index(i.first||r.first)<this.index(r.first),o=function(s){var o=[],l=[];i[s].each(function(){r[s].index(this)<0&&o.push(this)}),r[s].each(function(){i[s].index(this)<0&&l.push(this)}),n?o=o.reverse():l=l.reverse(),e._trigger(s+"in",t(o)),e._trigger(s+"out",t(l)),e["_"+s]=i[s]};for(s in i)o(s);return this}})}(jQuery,window),function(t){"use strict";t.jcarousel.fn.scrollIntoView=function(i,s,e){var r,n=t.jCarousel.parseTarget(i),o=this.index(this._fullyvisible.first()),l=this.index(this._fullyvisible.last());if((r=n.relative?n.target<0?Math.max(0,o+n.target):l+n.target:"object"!=typeof n.target?n.target:this.index(n.target))<o)return this.scroll(r,s,e);if(r>=o&&r<=l)return t.isFunction(e)&&e.call(this,!1),this;for(var a,h=this.items(),u=this.clipping(),c=this.vertical?"bottom":this.rtl?"left":"right",f=0;0!==(a=h.eq(r)).length;){if((f+=this.dimension(a))>=u){f-(parseFloat(a.css("margin-"+c))||0)!==u&&r++;break}if(r<=0)break;r--}return this.scroll(r,s,e)}}(jQuery),function(t){"use strict";t.jCarousel.plugin("jcarouselControl",{_options:{target:"+=1",event:"click",method:"scroll"},_active:null,_init:function(){this.onDestroy=t.proxy(function(){this._destroy(),this.carousel().one("jcarousel:createend",t.proxy(this._create,this))},this),this.onReload=t.proxy(this._reload,this),this.onEvent=t.proxy(function(i){i.preventDefault();var s=this.options("method");t.isFunction(s)?s.call(this):this.carousel().jcarousel(this.options("method"),this.options("target"))},this)},_create:function(){this.carousel().one("jcarousel:destroy",this.onDestroy).on("jcarousel:reloadend jcarousel:scrollend",this.onReload),this._element.on(this.options("event")+".jcarouselcontrol",this.onEvent),this._reload()},_destroy:function(){this._element.off(".jcarouselcontrol",this.onEvent),this.carousel().off("jcarousel:destroy",this.onDestroy).off("jcarousel:reloadend jcarousel:scrollend",this.onReload)},_reload:function(){var i,s=t.jCarousel.parseTarget(this.options("target")),e=this.carousel();if(s.relative)i=e.jcarousel(s.target>0?"hasNext":"hasPrev");else{var r="object"!=typeof s.target?e.jcarousel("items").eq(s.target):s.target;i=e.jcarousel("target").index(r)>=0}return this._active!==i&&(this._trigger(i?"active":"inactive"),this._active=i),this}})}(jQuery),function(t){"use strict";t.jCarousel.plugin("jcarouselPagination",{_options:{perPage:null,item:function(t){return'<a href="#'+t+'">'+t+"</a>"},event:"click",method:"scroll"},_carouselItems:null,_pages:{},_items:{},_currentPage:null,_init:function(){this.onDestroy=t.proxy(function(){this._destroy(),this.carousel().one("jcarousel:createend",t.proxy(this._create,this))},this),this.onReload=t.proxy(this._reload,this),this.onScroll=t.proxy(this._update,this)},_create:function(){this.carousel().one("jcarousel:destroy",this.onDestroy).on("jcarousel:reloadend",this.onReload).on("jcarousel:scrollend",this.onScroll),this._reload()},_destroy:function(){this._clear(),this.carousel().off("jcarousel:destroy",this.onDestroy).off("jcarousel:reloadend",this.onReload).off("jcarousel:scrollend",this.onScroll),this._carouselItems=null},_reload:function(){var i=this.options("perPage");if(this._pages={},this._items={},t.isFunction(i)&&(i=i.call(this)),null==i)this._pages=this._calculatePages();else for(var s,e=parseInt(i,10)||0,r=this._getCarouselItems(),n=1,o=0;0!==(s=r.eq(o++)).length;)this._pages[n]?this._pages[n]=this._pages[n].add(s):this._pages[n]=s,o%e==0&&n++;this._clear();var l=this,a=this.carousel().data("jcarousel"),h=this._element,u=this.options("item"),c=this._getCarouselItems().length;t.each(this._pages,function(i,s){var e=l._items[i]=t(u.call(l,i,s));e.on(l.options("event")+".jcarouselpagination",t.proxy(function(){var t=s.eq(0);if(a.circular){var e=a.index(a.target()),r=a.index(t);parseFloat(i)>parseFloat(l._currentPage)?r<e&&(t="+="+(c-e+r)):r>e&&(t="-="+(e+(c-r)))}a[this.options("method")](t)},l)),h.append(e)}),this._update()},_update:function(){var i,s=this.carousel().jcarousel("target");t.each(this._pages,function(t,e){if(e.each(function(){if(s.is(this))return i=t,!1}),i)return!1}),this._currentPage!==i&&(this._trigger("inactive",this._items[this._currentPage]),this._trigger("active",this._items[i])),this._currentPage=i},items:function(){return this._items},reloadCarouselItems:function(){return this._carouselItems=null,this},_clear:function(){this._element.empty(),this._currentPage=null},_calculatePages:function(){for(var t,i,s=this.carousel().data("jcarousel"),e=this._getCarouselItems(),r=s.clipping(),n=0,o=0,l=1,a={};0!==(t=e.eq(o++)).length;)n+(i=s.dimension(t))>r&&(l++,n=0),n+=i,a[l]?a[l]=a[l].add(t):a[l]=t;return a},_getCarouselItems:function(){return this._carouselItems||(this._carouselItems=this.carousel().jcarousel("items")),this._carouselItems}})}(jQuery),function(t,i){"use strict";var s,e;t.each({hidden:"visibilitychange",mozHidden:"mozvisibilitychange",msHidden:"msvisibilitychange",webkitHidden:"webkitvisibilitychange"},function(t,r){if(void 0!==i[t])return s=t,e=r,!1}),t.jCarousel.plugin("jcarouselAutoscroll",{_options:{target:"+=1",interval:3e3,autostart:!0},_timer:null,_started:!1,_init:function(){this.onDestroy=t.proxy(function(){this._destroy(),this.carousel().one("jcarousel:createend",t.proxy(this._create,this))},this),this.onAnimateEnd=t.proxy(this._start,this),this.onVisibilityChange=t.proxy(function(){i[s]?this._stop():this._start()},this)},_create:function(){this.carousel().one("jcarousel:destroy",this.onDestroy),t(i).on(e,this.onVisibilityChange),this.options("autostart")&&this.start()},_destroy:function(){this._stop(),this.carousel().off("jcarousel:destroy",this.onDestroy),t(i).off(e,this.onVisibilityChange)},_start:function(){if(this._stop(),this._started)return this.carousel().one("jcarousel:animateend",this.onAnimateEnd),this._timer=setTimeout(t.proxy(function(){this.carousel().jcarousel("scroll",this.options("target"))},this),this.options("interval")),this},_stop:function(){return this._timer&&(this._timer=clearTimeout(this._timer)),this.carousel().off("jcarousel:animateend",this.onAnimateEnd),this},start:function(){return this._started=!0,this._start(),this},stop:function(){return this._started=!1,this._stop(),this}})}(jQuery,document);
  (function ($) {
  
      //log chart
      var logActivity = {
          options: {
              date: {
                  startDate: moment().subtract(6, 'days'),
                  endDate: moment(),
                  minDate: moment().subtract(60, 'days'),
                  maxDate: moment(),
                  dateLimit: {
                      days: 60
                  },
                  showDropdowns: true,
                  showWeekNumbers: true,
                  timePicker: false,
                  timePickerIncrement: 1,
                  timePicker12Hour: true,
                  ranges: {
                      'Last 7 Days': [moment().subtract(6, 'days'), moment()],
                      'Last 30 Days': [moment().subtract(29, 'days'), moment()],
                      'This Month': [moment().startOf('month'), moment().endOf('month')],
                      'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
                  },
                  opens: 'left',
                  buttonClasses: ['btn btn-default'],
                  applyClass: 'btn-small btn-primary',
                  cancelClass: 'btn-small',
                  format: 'MM/DD/YYYY',
                  separator: ' to ',
                  locale: {
                      applyLabel: 'Submit',
                      cancelLabel: 'Clear',
                      fromLabel: 'From',
                      toLabel: 'To',
                      customRangeLabel: 'Custom',
                      daysOfWeek: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
                      monthNames: [
                          'January',
                          'February',
                          'March',
                          'April',
                          'May',
                          'June',
                          'July',
                          'August',
                          'September',
                          'October',
                          'November',
                          'December'
                      ],
                      firstDay: 1
                  }
              },
              chart: {
                  series: {
                      lines: {
                          show: false,
                          fill: true
                      },
                      splines: {
                          show: true,
                          tension: 0.4,
                          lineWidth: 1,
                          fill: 0.4
                      },
                      points: {
                          radius: 0,
                          show: true
                      },
                      shadowSize: 2
                  },
                  grid: {
                      verticalLines: true,
                      hoverable: true,
                      clickable: true,
                      tickColor: "#d5d5d5",
                      borderWidth: 1,
                      color: '#fff'
                  },
                  colors: ["#B71C1C", "#D32F2F", '#F44336', '#FF5722', '#FF9100', '#4CAF50', '#1976D2', '#90CAF9'],
                  xaxis: {
                      tickColor: "rgba(51, 51, 51, 0.06)",
                      mode: "time",
                      tickSize: [1, "day"],
                      //tickLength: 10,
                      axisLabel: "Date",
                      axisLabelUseCanvas: true,
                      axisLabelFontSizePixels: 12,
                      axisLabelFontFamily: 'Verdana, Arial',
                      axisLabelPadding: 10
                  },
                  yaxis: {
                      ticks: 8,
                      tickColor: "rgba(51, 51, 51, 0.06)",
                  },
                  tooltip: false
              }
          },
          gteChartData: function ($el, start, end) {
              var self = this;
  
              $.ajax({
                  url: 'admin/dashboard/log-chart',
                  data: {start: start, end: end},
                  success: function (response) {
                      var data = {};
                      var progress = {all: 0};
  
                      $.each(response, function (k, v) {
                          data[k] = [];
                          progress[k] = 0;
                          $.each(v, function (date, value) {
                              data[k].push([new Date(date).getTime(), value]);
                              progress.all += value;
                              progress[k] += value;
                          });
                      });
  
                      $.plot($el,
                          [data.emergency, data.alert, data.critical, data.error, data.warning, data.notice, data.info, data.debug],
                          self.options.chart);
  
  
                      $.each(progress, function (k, v) {
                          var $progress = $('.progress-bar.log-' + k);
                          if ($progress.length) {
                              $progress.attr('data-transitiongoal', 100 / progress.all * v).progressbar();
                          }
                      });
                  }
              });
          },
          init: function ($el) {
              var self = this;
  
              $el = $($el);
  
              var $dateEl = $el.find('.date_piker');
              var $chartEl = $el.find('.chart');
  
              $dateEl.daterangepicker(this.options.date, function (start, end) {
                  $dateEl.find('.date_piker_label').html(start.format('MMMM D, YYYY') + ' - ' + end.format('MMMM D, YYYY'));
              });
  
              $dateEl.on('apply.daterangepicker', function (ev, picker) {
                  self.gteChartData($chartEl, picker.startDate.format('YYYY-MM-DD'), picker.endDate.format('YYYY-MM-DD'));
              });
  
              self.gteChartData($chartEl, this.options.date.startDate.format('YYYY-MM-DD'), this.options.date.endDate.format('YYYY-MM-DD'));
          }
      };
  
      logActivity.init($('#log_activity'));
  
  
      var registrationUsage = {
          _defaults: {
              type: 'doughnut',
              tooltipFillColor: "rgba(51, 51, 51, 0.55)",
              data: {
                  labels: [],
                  datasets: [{
                      data: [],
                      backgroundColor: [
                          "#3498DB",
                          "#3498DB",
                          "#9B59B6",
                          "#E74C3C",
                      ],
                      hoverBackgroundColor: [
                          "#36CAAB",
                          "#49A9EA",
                          "#B370CF",
                          "#E95E4F",
                      ]
                  }]
              },
              options: {
                  legend: false,
                  responsive: false
              }
          },
          init: function ($el) {
              var self = this;
              $el = $($el);
  
              $.ajax({
                  url: 'admin/dashboard/registration-chart',
                  success: function (response) {
                      $.each($el.find('.tile_label'), function () {
                          self._defaults.data.labels.push($(this).text());
                      });
  
                      var count = 0;
  
                      $.each(response, function () {
                          count += parseInt(this);
                      });
  
                      $('#registration_usage_from').text(100 / count * parseInt(response.registration_form));
                      $('#registration_usage_google').text(100 / count * parseInt(response.google));
                      $('#registration_usage_facebook').text(100 / count * parseInt(response.facebook));
                      $('#registration_usage_twitter').text(100 / count * parseInt(response.twitter));
  
                      self._defaults.data.datasets[0].data = [response.registration_form, response.google, response.facebook, response.twitter];
  
                      new Chart($el.find('.canvasChart'), self._defaults);
                  }
              });
          }
      };
  
      registrationUsage.init($('#registration_usage'));
  
      //jcarousel
      var jcarousel = $('.jcarousel').jcarousel();
  
      $('.jcarousel-control-prev')
          .on('jcarouselcontrol:active', function () {
              $(this).removeClass('inactive');
          })
          .on('jcarouselcontrol:inactive', function () {
              $(this).addClass('inactive');
          })
          .jcarouselControl({
              target: '-=1'
          });
  
      $('.jcarousel-control-next')
          .on('jcarouselcontrol:active', function () {
              $(this).removeClass('inactive');
          })
          .on('jcarouselcontrol:inactive', function () {
              $(this).addClass('inactive');
          })
          .jcarouselControl({
              target: '+=1'
          });
  
      var url = 'https://photolancer.zone/api/photos',
          param = {
              page: 1,
              perPage: 10,
              sort: [{by: 'rating', type: 'desc'}]
          };
  
      $.ajax({
          url: url + '?' + $.param(param),
          method: 'GET',
          success: function (response) {
              var html = '<ul>';
  
              var href = 'https://photolancer.zone/photos';
  
              $.each(response, function () {
                  html += '<li><a href="' + href + '/' + this.slug + '/detail" target="_blank"><img src="' + this.thumbnails.file.photos.small + '" alt="' + this.name + '"/></a></li>';
              });
  
              html += '</ul>';
  
              // Append items
              jcarousel
                  .html(html);
  
              // Reload carousel
              jcarousel
                  .jcarousel('reload');
          }
      });
  
  
  })(jQuery);
  