src/pyams_skin/resources/js/ext/jquery-plot-0.7.js
changeset 0 bb4aabe07487
equal deleted inserted replaced
-1:000000000000 0:bb4aabe07487
       
     1 /*! Javascript plotting library for jQuery, v. 0.7.
       
     2  *
       
     3  * Released under the MIT license by IOLA, December 2007.
       
     4  *
       
     5  */
       
     6 
       
     7 // first an inline dependency, jquery.colorhelpers.js, we inline it here
       
     8 // for convenience
       
     9 
       
    10 /* Plugin for jQuery for working with colors.
       
    11  * 
       
    12  * Version 1.1.
       
    13  * 
       
    14  * Inspiration from jQuery color animation plugin by John Resig.
       
    15  *
       
    16  * Released under the MIT license by Ole Laursen, October 2009.
       
    17  *
       
    18  * Examples:
       
    19  *
       
    20  *   $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
       
    21  *   var c = $.color.extract($("#mydiv"), 'background-color');
       
    22  *   console.log(c.r, c.g, c.b, c.a);
       
    23  *   $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
       
    24  *
       
    25  * Note that .scale() and .add() return the same modified object
       
    26  * instead of making a new one.
       
    27  *
       
    28  * V. 1.1: Fix error handling so e.g. parsing an empty string does
       
    29  * produce a color rather than just crashing.
       
    30  */
       
    31 (function (B) {
       
    32 	B.color = {};
       
    33 	B.color.make = function (F, E, C, D) {
       
    34 		var G = {};
       
    35 		G.r = F || 0;
       
    36 		G.g = E || 0;
       
    37 		G.b = C || 0;
       
    38 		G.a = D != null ? D : 1;
       
    39 		G.add = function (J, I) {
       
    40 			for (var H = 0; H < J.length; ++H) {
       
    41 				G[J.charAt(H)] += I
       
    42 			}
       
    43 			return G.normalize()
       
    44 		};
       
    45 		G.scale = function (J, I) {
       
    46 			for (var H = 0; H < J.length; ++H) {
       
    47 				G[J.charAt(H)] *= I
       
    48 			}
       
    49 			return G.normalize()
       
    50 		};
       
    51 		G.toString = function () {
       
    52 			if (G.a >= 1) {
       
    53 				return"rgb(" + [G.r, G.g, G.b].join(",") + ")"
       
    54 			} else {
       
    55 				return"rgba(" + [G.r, G.g, G.b, G.a].join(",") + ")"
       
    56 			}
       
    57 		};
       
    58 		G.normalize = function () {
       
    59 			function H(J, K, I) {
       
    60 				return K < J ? J : (K > I ? I : K)
       
    61 			}
       
    62 
       
    63 			G.r = H(0, parseInt(G.r), 255);
       
    64 			G.g = H(0, parseInt(G.g), 255);
       
    65 			G.b = H(0, parseInt(G.b), 255);
       
    66 			G.a = H(0, G.a, 1);
       
    67 			return G
       
    68 		};
       
    69 		G.clone = function () {
       
    70 			return B.color.make(G.r, G.b, G.g, G.a)
       
    71 		};
       
    72 		return G.normalize()
       
    73 	};
       
    74 	B.color.extract = function (D, C) {
       
    75 		var E;
       
    76 		do {
       
    77 			E = D.css(C).toLowerCase();
       
    78 			if (E != "" && E != "transparent") {
       
    79 				break
       
    80 			}
       
    81 			D = D.parent()
       
    82 		} while (!B.nodeName(D.get(0), "body"));
       
    83 		if (E == "rgba(0, 0, 0, 0)") {
       
    84 			E = "transparent"
       
    85 		}
       
    86 		return B.color.parse(E)
       
    87 	};
       
    88 	B.color.parse = function (F) {
       
    89 		var E, C = B.color.make;
       
    90 		if (E = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)) {
       
    91 			return C(parseInt(E[1], 10), parseInt(E[2], 10), parseInt(E[3], 10))
       
    92 		}
       
    93 		if (E = /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(F)) {
       
    94 			return C(parseInt(E[1], 10), parseInt(E[2], 10), parseInt(E[3], 10), parseFloat(E[4]))
       
    95 		}
       
    96 		if (E = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)) {
       
    97 			return C(parseFloat(E[1]) * 2.55, parseFloat(E[2]) * 2.55, parseFloat(E[3]) * 2.55)
       
    98 		}
       
    99 		if (E = /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(F)) {
       
   100 			return C(parseFloat(E[1]) * 2.55, parseFloat(E[2]) * 2.55, parseFloat(E[3]) * 2.55, parseFloat(E[4]))
       
   101 		}
       
   102 		if (E = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)) {
       
   103 			return C(parseInt(E[1], 16), parseInt(E[2], 16), parseInt(E[3], 16))
       
   104 		}
       
   105 		if (E = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)) {
       
   106 			return C(parseInt(E[1] + E[1], 16), parseInt(E[2] + E[2], 16), parseInt(E[3] + E[3], 16))
       
   107 		}
       
   108 		var D = B.trim(F).toLowerCase();
       
   109 		if (D == "transparent") {
       
   110 			return C(255, 255, 255, 0)
       
   111 		} else {
       
   112 			E = A[D] || [0, 0, 0];
       
   113 			return C(E[0], E[1], E[2])
       
   114 		}
       
   115 	};
       
   116 	var A = {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]}
       
   117 })(jQuery);
       
   118 
       
   119 // the actual Flot code
       
   120 (function ($) {
       
   121 	function Plot(placeholder, data_, options_, plugins) {
       
   122 		// data is on the form:
       
   123 		//   [ series1, series2 ... ]
       
   124 		// where series is either just the data as [ [x1, y1], [x2, y2], ... ]
       
   125 		// or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }
       
   126 
       
   127 		var myRetinaMdo = (window.devicePixelRatio > 1),
       
   128 			series = [],
       
   129 			options = {
       
   130 				// the color theme used for graphs
       
   131 				colors: ["#931313", "#638167", "#65596B", "#60747C", "#B09B5B"],
       
   132 				legend: {
       
   133 					show: true,
       
   134 					noColumns: 0, // number of colums in legend table
       
   135 					labelFormatter: null, // fn: string -> string
       
   136 					labelBoxBorderColor: "", // border color for the little label boxes
       
   137 					container: null, // container (as jQuery object) to put legend in, null means default on top of graph
       
   138 					position: "ne", // position of default legend container within plot
       
   139 					margin: [-5, -32], // distance from grid edge to default legend container within plot
       
   140 					backgroundColor: "", // null means auto-detect
       
   141 					backgroundOpacity: 1 // set to 0 to avoid background
       
   142 				},
       
   143 				xaxis: {
       
   144 					show: null, // null = auto-detect, true = always, false = never
       
   145 					position: "bottom", // or "top"
       
   146 					mode: null, // null or "time"
       
   147 					color: null, // base color, labels, ticks
       
   148 					tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)"
       
   149 					transform: null, // null or f: number -> number to transform axis
       
   150 					inverseTransform: null, // if transform is set, this should be the inverse function
       
   151 					min: null, // min. value to show, null means set automatically
       
   152 					max: null, // max. value to show, null means set automatically
       
   153 					autoscaleMargin: null, // margin in % to add if auto-setting min/max
       
   154 					ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
       
   155 					tickFormatter: null, // fn: number -> string
       
   156 					labelWidth: null, // size of tick labels in pixels
       
   157 					labelHeight: null,
       
   158 					reserveSpace: null, // whether to reserve space even if axis isn't shown
       
   159 					tickLength: null, // size in pixels of ticks, or "full" for whole line
       
   160 					alignTicksWithAxis: null, // axis number or null for no sync
       
   161 
       
   162 					// mode specific options
       
   163 					tickDecimals: null, // no. of decimals, null means auto
       
   164 					tickSize: null, // number or [number, "unit"]
       
   165 					minTickSize: null, // number or [number, "unit"]
       
   166 					monthNames: null, // list of names of months
       
   167 					timeformat: null, // format string to use
       
   168 					twelveHourClock: false // 12 or 24 time in time mode
       
   169 				},
       
   170 				yaxis: {
       
   171 					autoscaleMargin: 0.02,
       
   172 					position: "left" // or "right"
       
   173 				},
       
   174 				xaxes: [],
       
   175 				yaxes: [],
       
   176 				series: {
       
   177 					points: {
       
   178 						show: false,
       
   179 						radius: 3,
       
   180 						lineWidth: 2, // in pixels
       
   181 						fill: true,
       
   182 						fillColor: "#ffffff",
       
   183 						symbol: "circle" // or callback
       
   184 					},
       
   185 					lines: {
       
   186 						// we don't put in show: false so we can see
       
   187 						// whether lines were actively disabled
       
   188 						lineWidth: 2, // in pixels
       
   189 						fill: false,
       
   190 						fillColor: null,
       
   191 						steps: false
       
   192 					},
       
   193 					bars: {
       
   194 						show: false,
       
   195 						lineWidth: 1, // in pixels
       
   196 						barWidth: 1, // in units of the x axis
       
   197 						fill: true,
       
   198 						fillColor: { colors: [
       
   199 							{ opacity: 0.7 },
       
   200 							{ opacity: 1 }
       
   201 						] },
       
   202 						align: "left", // or "center"
       
   203 						horizontal: false
       
   204 					},
       
   205 					shadowSize: 0
       
   206 				},
       
   207 				grid: {
       
   208 					show: true,
       
   209 					aboveData: false,
       
   210 					color: "#545454", // primary color used for outline and labels
       
   211 					backgroundColor: null, // null for transparent, else color
       
   212 					borderColor: "#efefef", // set if different from the grid color
       
   213 					tickColor: "rgba(0,0,0,0.06)", // color for the ticks, e.g. "rgba(0,0,0,0.15)"
       
   214 					labelMargin: 10, // in pixels
       
   215 					axisMargin: 8, // in pixels
       
   216 					borderWidth: 0, // in pixels
       
   217 					minBorderMargin: 10, // in pixels, null means taken from points radius
       
   218 					markings: null, // array of ranges or fn: axes -> array of ranges
       
   219 					markingsColor: "#f4f4f4",
       
   220 					markingsLineWidth: 2,
       
   221 					// interactive stuff
       
   222 					clickable: false,
       
   223 					hoverable: false,
       
   224 					autoHighlight: true, // highlight in case mouse is near
       
   225 					mouseActiveRadius: 5 // how far the mouse can be away to activate an item
       
   226 				},
       
   227 				hooks: {}
       
   228 			},
       
   229 			canvas = null,      // the canvas for the plot itself
       
   230 			overlay = null,     // canvas for interactive stuff on top of plot
       
   231 			eventHolder = null, // jQuery object that events should be bound to
       
   232 			ctx = null, octx = null,
       
   233 			xaxes = [], yaxes = [],
       
   234 			plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
       
   235 			canvasWidth = 0, canvasHeight = 0,
       
   236 			plotWidth = 0, plotHeight = 0,
       
   237 			hooks = {
       
   238 				processOptions: [],
       
   239 				processRawData: [],
       
   240 				processDatapoints: [],
       
   241 				drawSeries: [],
       
   242 				draw: [],
       
   243 				bindEvents: [],
       
   244 				drawOverlay: [],
       
   245 				shutdown: []
       
   246 			},
       
   247 			plot = this;
       
   248 
       
   249 		// public functions
       
   250 		plot.setData = setData;
       
   251 		plot.setupGrid = setupGrid;
       
   252 		plot.draw = draw;
       
   253 		plot.getPlaceholder = function () {
       
   254 			return placeholder;
       
   255 		};
       
   256 		plot.getCanvas = function () {
       
   257 			return canvas;
       
   258 		};
       
   259 		plot.getPlotOffset = function () {
       
   260 			return plotOffset;
       
   261 		};
       
   262 		plot.width = function () {
       
   263 			return plotWidth;
       
   264 		};
       
   265 		plot.height = function () {
       
   266 			return plotHeight;
       
   267 		};
       
   268 		plot.offset = function () {
       
   269 			var o = eventHolder.offset();
       
   270 			o.left += plotOffset.left;
       
   271 			o.top += plotOffset.top;
       
   272 			return o;
       
   273 		};
       
   274 		plot.getData = function () {
       
   275 			return series;
       
   276 		};
       
   277 		plot.getAxes = function () {
       
   278 			var res = {}, i;
       
   279 			$.each(xaxes.concat(yaxes), function (_, axis) {
       
   280 				if (axis)
       
   281 					res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis;
       
   282 			});
       
   283 			return res;
       
   284 		};
       
   285 		plot.getXAxes = function () {
       
   286 			return xaxes;
       
   287 		};
       
   288 		plot.getYAxes = function () {
       
   289 			return yaxes;
       
   290 		};
       
   291 		plot.c2p = canvasToAxisCoords;
       
   292 		plot.p2c = axisToCanvasCoords;
       
   293 		plot.getOptions = function () {
       
   294 			return options;
       
   295 		};
       
   296 		plot.highlight = highlight;
       
   297 		plot.unhighlight = unhighlight;
       
   298 		plot.triggerRedrawOverlay = triggerRedrawOverlay;
       
   299 		plot.pointOffset = function (point) {
       
   300 			return {
       
   301 				left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left),
       
   302 				top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top)
       
   303 			};
       
   304 		};
       
   305 		plot.shutdown = shutdown;
       
   306 		plot.resize = function () {
       
   307 			getCanvasDimensions();
       
   308 			resizeCanvas(canvas);
       
   309 			resizeCanvas(overlay);
       
   310 		};
       
   311 
       
   312 		// public attributes
       
   313 		plot.hooks = hooks;
       
   314 
       
   315 		// initialize
       
   316 		initPlugins(plot);
       
   317 		parseOptions(options_);
       
   318 		setupCanvases();
       
   319 		setData(data_);
       
   320 		setupGrid();
       
   321 		draw();
       
   322 		bindEvents();
       
   323 
       
   324 
       
   325 		function executeHooks(hook, args) {
       
   326 			args = [plot].concat(args);
       
   327 			for (var i = 0; i < hook.length; ++i)
       
   328 				hook[i].apply(this, args);
       
   329 		}
       
   330 
       
   331 		function initPlugins() {
       
   332 			for (var i = 0; i < plugins.length; ++i) {
       
   333 				var p = plugins[i];
       
   334 				p.init(plot);
       
   335 				if (p.options)
       
   336 					$.extend(true, options, p.options);
       
   337 			}
       
   338 		}
       
   339 
       
   340 		function parseOptions(opts) {
       
   341 			var i;
       
   342 
       
   343 			$.extend(true, options, opts);
       
   344 
       
   345 			if (options.xaxis.color == null)
       
   346 				options.xaxis.color = options.grid.color;
       
   347 			if (options.yaxis.color == null)
       
   348 				options.yaxis.color = options.grid.color;
       
   349 
       
   350 			if (options.xaxis.tickColor == null) // backwards-compatibility
       
   351 				options.xaxis.tickColor = options.grid.tickColor;
       
   352 			if (options.yaxis.tickColor == null) // backwards-compatibility
       
   353 				options.yaxis.tickColor = options.grid.tickColor;
       
   354 
       
   355 			if (options.grid.borderColor == null)
       
   356 				options.grid.borderColor = options.grid.color;
       
   357 			if (options.grid.tickColor == null)
       
   358 				options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString();
       
   359 
       
   360 			// fill in defaults in axes, copy at least always the
       
   361 			// first as the rest of the code assumes it'll be there
       
   362 			for (i = 0; i < Math.max(1, options.xaxes.length); ++i)
       
   363 				options.xaxes[i] = $.extend(true, {}, options.xaxis, options.xaxes[i]);
       
   364 			for (i = 0; i < Math.max(1, options.yaxes.length); ++i)
       
   365 				options.yaxes[i] = $.extend(true, {}, options.yaxis, options.yaxes[i]);
       
   366 
       
   367 			// backwards compatibility, to be removed in future
       
   368 			if (options.xaxis.noTicks && options.xaxis.ticks == null)
       
   369 				options.xaxis.ticks = options.xaxis.noTicks;
       
   370 			if (options.yaxis.noTicks && options.yaxis.ticks == null)
       
   371 				options.yaxis.ticks = options.yaxis.noTicks;
       
   372 			if (options.x2axis) {
       
   373 				options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis);
       
   374 				options.xaxes[1].position = "top";
       
   375 			}
       
   376 			if (options.y2axis) {
       
   377 				options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis);
       
   378 				options.yaxes[1].position = "right";
       
   379 			}
       
   380 			if (options.grid.coloredAreas)
       
   381 				options.grid.markings = options.grid.coloredAreas;
       
   382 			if (options.grid.coloredAreasColor)
       
   383 				options.grid.markingsColor = options.grid.coloredAreasColor;
       
   384 			if (options.lines)
       
   385 				$.extend(true, options.series.lines, options.lines);
       
   386 			if (options.points)
       
   387 				$.extend(true, options.series.points, options.points);
       
   388 			if (options.bars)
       
   389 				$.extend(true, options.series.bars, options.bars);
       
   390 			if (options.shadowSize != null)
       
   391 				options.series.shadowSize = options.shadowSize;
       
   392 
       
   393 			// save options on axes for future reference
       
   394 			for (i = 0; i < options.xaxes.length; ++i)
       
   395 				getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i];
       
   396 			for (i = 0; i < options.yaxes.length; ++i)
       
   397 				getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i];
       
   398 
       
   399 			// add hooks from options
       
   400 			for (var n in hooks)
       
   401 				if (options.hooks[n] && options.hooks[n].length)
       
   402 					hooks[n] = hooks[n].concat(options.hooks[n]);
       
   403 
       
   404 			executeHooks(hooks.processOptions, [options]);
       
   405 		}
       
   406 
       
   407 		function setData(d) {
       
   408 			series = parseData(d);
       
   409 			fillInSeriesOptions();
       
   410 			processData();
       
   411 		}
       
   412 
       
   413 		function parseData(d) {
       
   414 			var res = [];
       
   415 			for (var i = 0; i < d.length; ++i) {
       
   416 				var s = $.extend(true, {}, options.series);
       
   417 
       
   418 				if (d[i].data != null) {
       
   419 					s.data = d[i].data; // move the data instead of deep-copy
       
   420 					delete d[i].data;
       
   421 
       
   422 					$.extend(true, s, d[i]);
       
   423 
       
   424 					d[i].data = s.data;
       
   425 				}
       
   426 				else
       
   427 					s.data = d[i];
       
   428 				res.push(s);
       
   429 			}
       
   430 
       
   431 			return res;
       
   432 		}
       
   433 
       
   434 		function axisNumber(obj, coord) {
       
   435 			var a = obj[coord + "axis"];
       
   436 			if (typeof a == "object") // if we got a real axis, extract number
       
   437 				a = a.n;
       
   438 			if (typeof a != "number")
       
   439 				a = 1; // default to first axis
       
   440 			return a;
       
   441 		}
       
   442 
       
   443 		function allAxes() {
       
   444 			// return flat array without annoying null entries
       
   445 			return $.grep(xaxes.concat(yaxes), function (a) {
       
   446 				return a;
       
   447 			});
       
   448 		}
       
   449 
       
   450 		function canvasToAxisCoords(pos) {
       
   451 			// return an object with x/y corresponding to all used axes
       
   452 			var res = {}, i, axis;
       
   453 			for (i = 0; i < xaxes.length; ++i) {
       
   454 				axis = xaxes[i];
       
   455 				if (axis && axis.used)
       
   456 					res["x" + axis.n] = axis.c2p(pos.left);
       
   457 			}
       
   458 
       
   459 			for (i = 0; i < yaxes.length; ++i) {
       
   460 				axis = yaxes[i];
       
   461 				if (axis && axis.used)
       
   462 					res["y" + axis.n] = axis.c2p(pos.top);
       
   463 			}
       
   464 
       
   465 			if (res.x1 !== undefined)
       
   466 				res.x = res.x1;
       
   467 			if (res.y1 !== undefined)
       
   468 				res.y = res.y1;
       
   469 
       
   470 			return res;
       
   471 		}
       
   472 
       
   473 		function axisToCanvasCoords(pos) {
       
   474 			// get canvas coords from the first pair of x/y found in pos
       
   475 			var res = {}, i, axis, key;
       
   476 
       
   477 			for (i = 0; i < xaxes.length; ++i) {
       
   478 				axis = xaxes[i];
       
   479 				if (axis && axis.used) {
       
   480 					key = "x" + axis.n;
       
   481 					if (pos[key] == null && axis.n == 1)
       
   482 						key = "x";
       
   483 
       
   484 					if (pos[key] != null) {
       
   485 						res.left = axis.p2c(pos[key]);
       
   486 						break;
       
   487 					}
       
   488 				}
       
   489 			}
       
   490 
       
   491 			for (i = 0; i < yaxes.length; ++i) {
       
   492 				axis = yaxes[i];
       
   493 				if (axis && axis.used) {
       
   494 					key = "y" + axis.n;
       
   495 					if (pos[key] == null && axis.n == 1)
       
   496 						key = "y";
       
   497 
       
   498 					if (pos[key] != null) {
       
   499 						res.top = axis.p2c(pos[key]);
       
   500 						break;
       
   501 					}
       
   502 				}
       
   503 			}
       
   504 
       
   505 			return res;
       
   506 		}
       
   507 
       
   508 		function getOrCreateAxis(axes, number) {
       
   509 			if (!axes[number - 1])
       
   510 				axes[number - 1] = {
       
   511 					n: number, // save the number for future reference
       
   512 					direction: axes == xaxes ? "x" : "y",
       
   513 					options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis)
       
   514 				};
       
   515 
       
   516 			return axes[number - 1];
       
   517 		}
       
   518 
       
   519 		function fillInSeriesOptions() {
       
   520 			var i;
       
   521 
       
   522 			// collect what we already got of colors
       
   523 			var neededColors = series.length,
       
   524 				usedColors = [],
       
   525 				assignedColors = [];
       
   526 			for (i = 0; i < series.length; ++i) {
       
   527 				var sc = series[i].color;
       
   528 				if (sc != null) {
       
   529 					--neededColors;
       
   530 					if (typeof sc == "number")
       
   531 						assignedColors.push(sc);
       
   532 					else
       
   533 						usedColors.push($.color.parse(series[i].color));
       
   534 				}
       
   535 			}
       
   536 
       
   537 			// we might need to generate more colors if higher indices
       
   538 			// are assigned
       
   539 			for (i = 0; i < assignedColors.length; ++i) {
       
   540 				neededColors = Math.max(neededColors, assignedColors[i] + 1);
       
   541 			}
       
   542 
       
   543 			// produce colors as needed
       
   544 			var colors = [], variation = 0;
       
   545 			i = 0;
       
   546 			while (colors.length < neededColors) {
       
   547 				var c;
       
   548 				if (options.colors.length == i) // check degenerate case
       
   549 					c = $.color.make(100, 100, 100);
       
   550 				else
       
   551 					c = $.color.parse(options.colors[i]);
       
   552 
       
   553 				// vary color if needed
       
   554 				var sign = variation % 2 == 1 ? -1 : 1;
       
   555 				c.scale('rgb', 1 + sign * Math.ceil(variation / 2) * 0.2)
       
   556 
       
   557 				// FIXME: if we're getting to close to something else,
       
   558 				// we should probably skip this one
       
   559 				colors.push(c);
       
   560 
       
   561 				++i;
       
   562 				if (i >= options.colors.length) {
       
   563 					i = 0;
       
   564 					++variation;
       
   565 				}
       
   566 			}
       
   567 
       
   568 			// fill in the options
       
   569 			var colori = 0, s;
       
   570 			for (i = 0; i < series.length; ++i) {
       
   571 				s = series[i];
       
   572 
       
   573 				// assign colors
       
   574 				if (s.color == null) {
       
   575 					s.color = colors[colori].toString();
       
   576 					++colori;
       
   577 				}
       
   578 				else if (typeof s.color == "number")
       
   579 					s.color = colors[s.color].toString();
       
   580 
       
   581 				// turn on lines automatically in case nothing is set
       
   582 				if (s.lines.show == null) {
       
   583 					var v, show = true;
       
   584 					for (v in s)
       
   585 						if (s[v] && s[v].show) {
       
   586 							show = false;
       
   587 							break;
       
   588 						}
       
   589 					if (show)
       
   590 						s.lines.show = true;
       
   591 				}
       
   592 
       
   593 				// setup axes
       
   594 				s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x"));
       
   595 				s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y"));
       
   596 			}
       
   597 		}
       
   598 
       
   599 		function processData() {
       
   600 			var topSentry = Number.POSITIVE_INFINITY,
       
   601 				bottomSentry = Number.NEGATIVE_INFINITY,
       
   602 				fakeInfinity = Number.MAX_VALUE,
       
   603 				i, j, k, m, length,
       
   604 				s, points, ps, x, y, axis, val, f, p;
       
   605 
       
   606 			function updateAxis(axis, min, max) {
       
   607 				if (min < axis.datamin && min != -fakeInfinity)
       
   608 					axis.datamin = min;
       
   609 				if (max > axis.datamax && max != fakeInfinity)
       
   610 					axis.datamax = max;
       
   611 			}
       
   612 
       
   613 			$.each(allAxes(), function (_, axis) {
       
   614 				// init axis
       
   615 				axis.datamin = topSentry;
       
   616 				axis.datamax = bottomSentry;
       
   617 				axis.used = false;
       
   618 			});
       
   619 
       
   620 			for (i = 0; i < series.length; ++i) {
       
   621 				s = series[i];
       
   622 				s.datapoints = { points: [] };
       
   623 
       
   624 				executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]);
       
   625 			}
       
   626 
       
   627 			// first pass: clean and copy data
       
   628 			for (i = 0; i < series.length; ++i) {
       
   629 				s = series[i];
       
   630 
       
   631 				var data = s.data, format = s.datapoints.format;
       
   632 
       
   633 				if (!format) {
       
   634 					format = [];
       
   635 					// find out how to copy
       
   636 					format.push({ x: true, number: true, required: true });
       
   637 					format.push({ y: true, number: true, required: true });
       
   638 
       
   639 					if (s.bars.show || (s.lines.show && s.lines.fill)) {
       
   640 						format.push({ y: true, number: true, required: false, defaultValue: 0 });
       
   641 						if (s.bars.horizontal) {
       
   642 							delete format[format.length - 1].y;
       
   643 							format[format.length - 1].x = true;
       
   644 						}
       
   645 					}
       
   646 
       
   647 					s.datapoints.format = format;
       
   648 				}
       
   649 
       
   650 				if (s.datapoints.pointsize != null)
       
   651 					continue; // already filled in
       
   652 
       
   653 				s.datapoints.pointsize = format.length;
       
   654 
       
   655 				ps = s.datapoints.pointsize;
       
   656 				points = s.datapoints.points;
       
   657 
       
   658 				insertSteps = s.lines.show && s.lines.steps;
       
   659 				s.xaxis.used = s.yaxis.used = true;
       
   660 
       
   661 				for (j = k = 0; j < data.length; ++j, k += ps) {
       
   662 					p = data[j];
       
   663 
       
   664 					var nullify = p == null;
       
   665 					if (!nullify) {
       
   666 						for (m = 0; m < ps; ++m) {
       
   667 							val = p[m];
       
   668 							f = format[m];
       
   669 
       
   670 							if (f) {
       
   671 								if (f.number && val != null) {
       
   672 									val = +val; // convert to number
       
   673 									if (isNaN(val))
       
   674 										val = null;
       
   675 									else if (val == Infinity)
       
   676 										val = fakeInfinity;
       
   677 									else if (val == -Infinity)
       
   678 										val = -fakeInfinity;
       
   679 								}
       
   680 
       
   681 								if (val == null) {
       
   682 									if (f.required)
       
   683 										nullify = true;
       
   684 
       
   685 									if (f.defaultValue != null)
       
   686 										val = f.defaultValue;
       
   687 								}
       
   688 							}
       
   689 
       
   690 							points[k + m] = val;
       
   691 						}
       
   692 					}
       
   693 
       
   694 					if (nullify) {
       
   695 						for (m = 0; m < ps; ++m) {
       
   696 							val = points[k + m];
       
   697 							if (val != null) {
       
   698 								f = format[m];
       
   699 								// extract min/max info
       
   700 								if (f.x)
       
   701 									updateAxis(s.xaxis, val, val);
       
   702 								if (f.y)
       
   703 									updateAxis(s.yaxis, val, val);
       
   704 							}
       
   705 							points[k + m] = null;
       
   706 						}
       
   707 					}
       
   708 					else {
       
   709 						// a little bit of line specific stuff that
       
   710 						// perhaps shouldn't be here, but lacking
       
   711 						// better means...
       
   712 						if (insertSteps && k > 0
       
   713 							&& points[k - ps] != null
       
   714 							&& points[k - ps] != points[k]
       
   715 							&& points[k - ps + 1] != points[k + 1]) {
       
   716 							// copy the point to make room for a middle point
       
   717 							for (m = 0; m < ps; ++m)
       
   718 								points[k + ps + m] = points[k + m];
       
   719 
       
   720 							// middle point has same y
       
   721 							points[k + 1] = points[k - ps + 1];
       
   722 
       
   723 							// we've added a point, better reflect that
       
   724 							k += ps;
       
   725 						}
       
   726 					}
       
   727 				}
       
   728 			}
       
   729 
       
   730 			// give the hooks a chance to run
       
   731 			for (i = 0; i < series.length; ++i) {
       
   732 				s = series[i];
       
   733 
       
   734 				executeHooks(hooks.processDatapoints, [ s, s.datapoints]);
       
   735 			}
       
   736 
       
   737 			// second pass: find datamax/datamin for auto-scaling
       
   738 			for (i = 0; i < series.length; ++i) {
       
   739 				s = series[i];
       
   740 				points = s.datapoints.points,
       
   741 					ps = s.datapoints.pointsize;
       
   742 
       
   743 				var xmin = topSentry, ymin = topSentry,
       
   744 					xmax = bottomSentry, ymax = bottomSentry;
       
   745 
       
   746 				for (j = 0; j < points.length; j += ps) {
       
   747 					if (points[j] == null)
       
   748 						continue;
       
   749 
       
   750 					for (m = 0; m < ps; ++m) {
       
   751 						val = points[j + m];
       
   752 						f = format[m];
       
   753 						if (!f || val == fakeInfinity || val == -fakeInfinity)
       
   754 							continue;
       
   755 
       
   756 						if (f.x) {
       
   757 							if (val < xmin)
       
   758 								xmin = val;
       
   759 							if (val > xmax)
       
   760 								xmax = val;
       
   761 						}
       
   762 						if (f.y) {
       
   763 							if (val < ymin)
       
   764 								ymin = val;
       
   765 							if (val > ymax)
       
   766 								ymax = val;
       
   767 						}
       
   768 					}
       
   769 				}
       
   770 
       
   771 				if (s.bars.show) {
       
   772 					// make sure we got room for the bar on the dancing floor
       
   773 					var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth / 2;
       
   774 					if (s.bars.horizontal) {
       
   775 						ymin += delta;
       
   776 						ymax += delta + s.bars.barWidth;
       
   777 					}
       
   778 					else {
       
   779 						xmin += delta;
       
   780 						xmax += delta + s.bars.barWidth;
       
   781 					}
       
   782 				}
       
   783 
       
   784 				updateAxis(s.xaxis, xmin, xmax);
       
   785 				updateAxis(s.yaxis, ymin, ymax);
       
   786 			}
       
   787 
       
   788 			$.each(allAxes(), function (_, axis) {
       
   789 				if (axis.datamin == topSentry)
       
   790 					axis.datamin = null;
       
   791 				if (axis.datamax == bottomSentry)
       
   792 					axis.datamax = null;
       
   793 			});
       
   794 		}
       
   795 
       
   796 		function makeCanvas(skipPositioning, cls) {
       
   797 			var c = document.createElement('canvas');
       
   798 			c.className = cls;
       
   799 			c.width = canvasWidth;
       
   800 			c.height = canvasHeight;
       
   801 
       
   802 			if (!skipPositioning)
       
   803 				$(c).css({ position: 'absolute', left: 0, top: 0 });
       
   804 
       
   805 			$(c).appendTo(placeholder);
       
   806 
       
   807 			if (myRetinaMdo) {
       
   808 				c.width = canvasWidth * 2;
       
   809 				c.height = canvasHeight * 2;
       
   810 				c.style.width = '' + canvasWidth + 'px';
       
   811 				c.style.height = '' + canvasHeight + 'px';
       
   812 			}
       
   813 
       
   814 			if (!c.getContext) // excanvas hack
       
   815 				c = window.G_vmlCanvasManager.initElement(c);
       
   816 
       
   817 			// used for resetting in case we get replotted
       
   818 			c.getContext("2d").save();
       
   819 
       
   820 			if (myRetinaMdo) {
       
   821 				c.getContext("2d").scale(2, 2);
       
   822 			}
       
   823 
       
   824 			return c;
       
   825 		}
       
   826 
       
   827 		function getCanvasDimensions() {
       
   828 			canvasWidth = placeholder.width();
       
   829 			canvasHeight = placeholder.height();
       
   830 
       
   831 			if (canvasWidth <= 0 || canvasHeight <= 0)
       
   832 				throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight;
       
   833 		}
       
   834 
       
   835 		function resizeCanvas(c) {
       
   836 			// resizing should reset the state (excanvas seems to be
       
   837 			// buggy though)
       
   838 			if (c.width != canvasWidth) {
       
   839 				c.width = canvasWidth;
       
   840 				if (myRetinaMdo) {
       
   841 					c.width = canvasWidth * 2;
       
   842 				}
       
   843 				c.style.width = '' + canvasWidth + 'px';
       
   844 
       
   845 			}
       
   846 
       
   847 			if (c.height != canvasHeight) {
       
   848 				c.height = canvasHeight;
       
   849 				if (myRetinaMdo) {
       
   850 					c.height = canvasHeight * 2;
       
   851 				}
       
   852 				c.style.height = '' + canvasHeight + 'px';
       
   853 			}
       
   854 
       
   855 			// so try to get back to the initial state (even if it's
       
   856 			// gone now, this should be safe according to the spec)
       
   857 			var cctx = c.getContext("2d");
       
   858 			cctx.restore();
       
   859 
       
   860 			// and save again
       
   861 			cctx.save();
       
   862 
       
   863 			if (myRetinaMdo) {
       
   864 				cctx.scale(2, 2);
       
   865 			}
       
   866 		}
       
   867 
       
   868 		function setupCanvases() {
       
   869 			var reused,
       
   870 				existingCanvas = placeholder.children("canvas.base"),
       
   871 				existingOverlay = placeholder.children("canvas.overlay");
       
   872 
       
   873 			if (existingCanvas.length == 0 || existingOverlay == 0) {
       
   874 				// init everything
       
   875 
       
   876 				placeholder.html(""); // make sure placeholder is clear
       
   877 
       
   878 				placeholder.css({ padding: 0 }); // padding messes up the positioning
       
   879 
       
   880 				if (placeholder.css("position") == 'static')
       
   881 					placeholder.css("position", "relative"); // for positioning labels and overlay
       
   882 
       
   883 				getCanvasDimensions();
       
   884 
       
   885 				canvas = makeCanvas(true, "base");
       
   886 				overlay = makeCanvas(false, "overlay"); // overlay canvas for interactive features
       
   887 
       
   888 				reused = false;
       
   889 			}
       
   890 			else {
       
   891 				// reuse existing elements
       
   892 
       
   893 				canvas = existingCanvas.get(0);
       
   894 				overlay = existingOverlay.get(0);
       
   895 
       
   896 				reused = true;
       
   897 			}
       
   898 
       
   899 			ctx = canvas.getContext("2d");
       
   900 			octx = overlay.getContext("2d");
       
   901 
       
   902 			// we include the canvas in the event holder too, because IE 7
       
   903 			// sometimes has trouble with the stacking order
       
   904 			eventHolder = $([overlay, canvas]);
       
   905 
       
   906 			if (reused) {
       
   907 				// run shutdown in the old plot object
       
   908 				placeholder.data("plot").shutdown();
       
   909 
       
   910 				// reset reused canvases
       
   911 				plot.resize();
       
   912 
       
   913 				// make sure overlay pixels are cleared (canvas is cleared when we redraw)
       
   914 				octx.clearRect(0, 0, canvasWidth, canvasHeight);
       
   915 
       
   916 				// then whack any remaining obvious garbage left
       
   917 				eventHolder.unbind();
       
   918 				placeholder.children().not([canvas, overlay]).remove();
       
   919 			}
       
   920 
       
   921 			// save in case we get replotted
       
   922 			placeholder.data("plot", plot);
       
   923 		}
       
   924 
       
   925 		function bindEvents() {
       
   926 			// bind events
       
   927 			if (options.grid.hoverable) {
       
   928 				eventHolder.mousemove(onMouseMove);
       
   929 				eventHolder.mouseleave(onMouseLeave);
       
   930 			}
       
   931 
       
   932 			if (options.grid.clickable)
       
   933 				eventHolder.click(onClick);
       
   934 
       
   935 			executeHooks(hooks.bindEvents, [eventHolder]);
       
   936 		}
       
   937 
       
   938 		function shutdown() {
       
   939 			if (redrawTimeout)
       
   940 				clearTimeout(redrawTimeout);
       
   941 
       
   942 			eventHolder.unbind("mousemove", onMouseMove);
       
   943 			eventHolder.unbind("mouseleave", onMouseLeave);
       
   944 			eventHolder.unbind("click", onClick);
       
   945 
       
   946 			executeHooks(hooks.shutdown, [eventHolder]);
       
   947 		}
       
   948 
       
   949 		function setTransformationHelpers(axis) {
       
   950 			// set helper functions on the axis, assumes plot area
       
   951 			// has been computed already
       
   952 
       
   953 			function identity(x) {
       
   954 				return x;
       
   955 			}
       
   956 
       
   957 			var s, m, t = axis.options.transform || identity,
       
   958 				it = axis.options.inverseTransform;
       
   959 
       
   960 			// precompute how much the axis is scaling a point
       
   961 			// in canvas space
       
   962 			if (axis.direction == "x") {
       
   963 				s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min));
       
   964 				m = Math.min(t(axis.max), t(axis.min));
       
   965 			}
       
   966 			else {
       
   967 				s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min));
       
   968 				s = -s;
       
   969 				m = Math.max(t(axis.max), t(axis.min));
       
   970 			}
       
   971 
       
   972 			// data point to canvas coordinate
       
   973 			if (t == identity) // slight optimization
       
   974 				axis.p2c = function (p) {
       
   975 					return (p - m) * s;
       
   976 				};
       
   977 			else
       
   978 				axis.p2c = function (p) {
       
   979 					return (t(p) - m) * s;
       
   980 				};
       
   981 			// canvas coordinate to data point
       
   982 			if (!it)
       
   983 				axis.c2p = function (c) {
       
   984 					return m + c / s;
       
   985 				};
       
   986 			else
       
   987 				axis.c2p = function (c) {
       
   988 					return it(m + c / s);
       
   989 				};
       
   990 		}
       
   991 
       
   992 		function measureTickLabels(axis) {
       
   993 			var opts = axis.options, i, ticks = axis.ticks || [], labels = [],
       
   994 				l, w = opts.labelWidth, h = opts.labelHeight, dummyDiv;
       
   995 
       
   996 			function makeDummyDiv(labels, width) {
       
   997 				return $('<div style="position:absolute;top:-10000px;' + width + 'font-size:smaller">' +
       
   998 							 '<div class="' + axis.direction + 'Axis ' + axis.direction + axis.n + 'Axis">'
       
   999 							 + labels.join("") + '</div></div>')
       
  1000 					.appendTo(placeholder);
       
  1001 			}
       
  1002 
       
  1003 			if (axis.direction == "x") {
       
  1004 				// to avoid measuring the widths of the labels (it's slow), we
       
  1005 				// construct fixed-size boxes and put the labels inside
       
  1006 				// them, we don't need the exact figures and the
       
  1007 				// fixed-size box content is easy to center
       
  1008 				if (w == null)
       
  1009 					w = Math.floor(canvasWidth / (ticks.length > 0 ? ticks.length : 1));
       
  1010 
       
  1011 				// measure x label heights
       
  1012 				if (h == null) {
       
  1013 					labels = [];
       
  1014 					for (i = 0; i < ticks.length; ++i) {
       
  1015 						l = ticks[i].label;
       
  1016 						if (l)
       
  1017 							labels.push('<div class="tickLabel" style="float:left;width:' + w + 'px">' + l + '</div>');
       
  1018 					}
       
  1019 
       
  1020 					if (labels.length > 0) {
       
  1021 						// stick them all in the same div and measure
       
  1022 						// collective height
       
  1023 						labels.push('<div style="clear:left"></div>');
       
  1024 						dummyDiv = makeDummyDiv(labels, "width:10000px;");
       
  1025 						h = dummyDiv.height();
       
  1026 						dummyDiv.remove();
       
  1027 					}
       
  1028 				}
       
  1029 			}
       
  1030 			else if (w == null || h == null) {
       
  1031 				// calculate y label dimensions
       
  1032 				for (i = 0; i < ticks.length; ++i) {
       
  1033 					l = ticks[i].label;
       
  1034 					if (l)
       
  1035 						labels.push('<div class="tickLabel">' + l + '</div>');
       
  1036 				}
       
  1037 
       
  1038 				if (labels.length > 0) {
       
  1039 					dummyDiv = makeDummyDiv(labels, "");
       
  1040 					if (w == null)
       
  1041 						w = dummyDiv.children().width();
       
  1042 					if (h == null)
       
  1043 						h = dummyDiv.find("div.tickLabel").height();
       
  1044 					dummyDiv.remove();
       
  1045 				}
       
  1046 			}
       
  1047 
       
  1048 			if (w == null)
       
  1049 				w = 0;
       
  1050 			if (h == null)
       
  1051 				h = 0;
       
  1052 
       
  1053 			axis.labelWidth = w;
       
  1054 			axis.labelHeight = h;
       
  1055 		}
       
  1056 
       
  1057 		function allocateAxisBoxFirstPhase(axis) {
       
  1058 			// find the bounding box of the axis by looking at label
       
  1059 			// widths/heights and ticks, make room by diminishing the
       
  1060 			// plotOffset
       
  1061 
       
  1062 			var lw = axis.labelWidth,
       
  1063 				lh = axis.labelHeight,
       
  1064 				pos = axis.options.position,
       
  1065 				tickLength = axis.options.tickLength,
       
  1066 				axismargin = options.grid.axisMargin,
       
  1067 				padding = options.grid.labelMargin,
       
  1068 				all = axis.direction == "x" ? xaxes : yaxes,
       
  1069 				index;
       
  1070 
       
  1071 			// determine axis margin
       
  1072 			var samePosition = $.grep(all, function (a) {
       
  1073 				return a && a.options.position == pos && a.reserveSpace;
       
  1074 			});
       
  1075 			if ($.inArray(axis, samePosition) == samePosition.length - 1)
       
  1076 				axismargin = 0; // outermost
       
  1077 
       
  1078 			// determine tick length - if we're innermost, we can use "full"
       
  1079 			if (tickLength == null)
       
  1080 				tickLength = "full";
       
  1081 
       
  1082 			var sameDirection = $.grep(all, function (a) {
       
  1083 				return a && a.reserveSpace;
       
  1084 			});
       
  1085 
       
  1086 			var innermost = $.inArray(axis, sameDirection) == 0;
       
  1087 			if (!innermost && tickLength == "full")
       
  1088 				tickLength = 5;
       
  1089 
       
  1090 			if (!isNaN(+tickLength))
       
  1091 				padding += +tickLength;
       
  1092 
       
  1093 			// compute box
       
  1094 			if (axis.direction == "x") {
       
  1095 				lh += padding;
       
  1096 
       
  1097 				if (pos == "bottom") {
       
  1098 					plotOffset.bottom += lh + axismargin;
       
  1099 					axis.box = { top: canvasHeight - plotOffset.bottom, height: lh };
       
  1100 				}
       
  1101 				else {
       
  1102 					axis.box = { top: plotOffset.top + axismargin, height: lh };
       
  1103 					plotOffset.top += lh + axismargin;
       
  1104 				}
       
  1105 			}
       
  1106 			else {
       
  1107 				lw += padding;
       
  1108 
       
  1109 				if (pos == "left") {
       
  1110 					axis.box = { left: plotOffset.left + axismargin, width: lw };
       
  1111 					plotOffset.left += lw + axismargin;
       
  1112 				}
       
  1113 				else {
       
  1114 					plotOffset.right += lw + axismargin;
       
  1115 					axis.box = { left: canvasWidth - plotOffset.right, width: lw };
       
  1116 				}
       
  1117 			}
       
  1118 
       
  1119 			// save for future reference
       
  1120 			axis.position = pos;
       
  1121 			axis.tickLength = tickLength;
       
  1122 			axis.box.padding = padding;
       
  1123 			axis.innermost = innermost;
       
  1124 		}
       
  1125 
       
  1126 		function allocateAxisBoxSecondPhase(axis) {
       
  1127 			// set remaining bounding box coordinates
       
  1128 			if (axis.direction == "x") {
       
  1129 				axis.box.left = plotOffset.left;
       
  1130 				axis.box.width = plotWidth;
       
  1131 			}
       
  1132 			else {
       
  1133 				axis.box.top = plotOffset.top;
       
  1134 				axis.box.height = plotHeight;
       
  1135 			}
       
  1136 		}
       
  1137 
       
  1138 		function setupGrid() {
       
  1139 			var i, axes = allAxes();
       
  1140 
       
  1141 			// first calculate the plot and axis box dimensions
       
  1142 
       
  1143 			$.each(axes, function (_, axis) {
       
  1144 				axis.show = axis.options.show;
       
  1145 				if (axis.show == null)
       
  1146 					axis.show = axis.used; // by default an axis is visible if it's got data
       
  1147 
       
  1148 				axis.reserveSpace = axis.show || axis.options.reserveSpace;
       
  1149 
       
  1150 				setRange(axis);
       
  1151 			});
       
  1152 
       
  1153 			allocatedAxes = $.grep(axes, function (axis) {
       
  1154 				return axis.reserveSpace;
       
  1155 			});
       
  1156 
       
  1157 			plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = 0;
       
  1158 			if (options.grid.show) {
       
  1159 				$.each(allocatedAxes, function (_, axis) {
       
  1160 					// make the ticks
       
  1161 					setupTickGeneration(axis);
       
  1162 					setTicks(axis);
       
  1163 					snapRangeToTicks(axis, axis.ticks);
       
  1164 
       
  1165 					// find labelWidth/Height for axis
       
  1166 					measureTickLabels(axis);
       
  1167 				});
       
  1168 
       
  1169 				// with all dimensions in house, we can compute the
       
  1170 				// axis boxes, start from the outside (reverse order)
       
  1171 				for (i = allocatedAxes.length - 1; i >= 0; --i)
       
  1172 					allocateAxisBoxFirstPhase(allocatedAxes[i]);
       
  1173 
       
  1174 				// make sure we've got enough space for things that
       
  1175 				// might stick out
       
  1176 				var minMargin = options.grid.minBorderMargin;
       
  1177 				if (minMargin == null) {
       
  1178 					minMargin = 0;
       
  1179 					for (i = 0; i < series.length; ++i)
       
  1180 						minMargin = Math.max(minMargin, series[i].points.radius + series[i].points.lineWidth / 2);
       
  1181 				}
       
  1182 
       
  1183 				for (var a in plotOffset) {
       
  1184 					plotOffset[a] += options.grid.borderWidth;
       
  1185 					plotOffset[a] = Math.max(minMargin, plotOffset[a]);
       
  1186 				}
       
  1187 			}
       
  1188 
       
  1189 			plotWidth = canvasWidth - plotOffset.left - plotOffset.right;
       
  1190 			plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top;
       
  1191 
       
  1192 			// now we got the proper plotWidth/Height, we can compute the scaling
       
  1193 			$.each(axes, function (_, axis) {
       
  1194 				setTransformationHelpers(axis);
       
  1195 			});
       
  1196 
       
  1197 			if (options.grid.show) {
       
  1198 				$.each(allocatedAxes, function (_, axis) {
       
  1199 					allocateAxisBoxSecondPhase(axis);
       
  1200 				});
       
  1201 
       
  1202 				insertAxisLabels();
       
  1203 			}
       
  1204 
       
  1205 			insertLegend();
       
  1206 		}
       
  1207 
       
  1208 		function setRange(axis) {
       
  1209 			var opts = axis.options,
       
  1210 				min = +(opts.min != null ? opts.min : axis.datamin),
       
  1211 				max = +(opts.max != null ? opts.max : axis.datamax),
       
  1212 				delta = max - min;
       
  1213 
       
  1214 			if (delta == 0.0) {
       
  1215 				// degenerate case
       
  1216 				var widen = max == 0 ? 1 : 0.01;
       
  1217 
       
  1218 				if (opts.min == null)
       
  1219 					min -= widen;
       
  1220 				// always widen max if we couldn't widen min to ensure we
       
  1221 				// don't fall into min == max which doesn't work
       
  1222 				if (opts.max == null || opts.min != null)
       
  1223 					max += widen;
       
  1224 			}
       
  1225 			else {
       
  1226 				// consider autoscaling
       
  1227 				var margin = opts.autoscaleMargin;
       
  1228 				if (margin != null) {
       
  1229 					if (opts.min == null) {
       
  1230 						min -= delta * margin;
       
  1231 						// make sure we don't go below zero if all values
       
  1232 						// are positive
       
  1233 						if (min < 0 && axis.datamin != null && axis.datamin >= 0)
       
  1234 							min = 0;
       
  1235 					}
       
  1236 					if (opts.max == null) {
       
  1237 						max += delta * margin;
       
  1238 						if (max > 0 && axis.datamax != null && axis.datamax <= 0)
       
  1239 							max = 0;
       
  1240 					}
       
  1241 				}
       
  1242 			}
       
  1243 			axis.min = min;
       
  1244 			axis.max = max;
       
  1245 		}
       
  1246 
       
  1247 		function setupTickGeneration(axis) {
       
  1248 			var opts = axis.options;
       
  1249 
       
  1250 			// estimate number of ticks
       
  1251 			var noTicks;
       
  1252 			if (typeof opts.ticks == "number" && opts.ticks > 0)
       
  1253 				noTicks = opts.ticks;
       
  1254 			else
       
  1255 			// heuristic based on the model a*sqrt(x) fitted to
       
  1256 			// some data points that seemed reasonable
       
  1257 				noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? canvasWidth : canvasHeight);
       
  1258 
       
  1259 			var delta = (axis.max - axis.min) / noTicks,
       
  1260 				size, generator, unit, formatter, i, magn, norm;
       
  1261 
       
  1262 			if (opts.mode == "time") {
       
  1263 				// pretty handling of time
       
  1264 
       
  1265 				// map of app. size of time units in milliseconds
       
  1266 				var timeUnitSize = {
       
  1267 					"second": 1000,
       
  1268 					"minute": 60 * 1000,
       
  1269 					"hour": 60 * 60 * 1000,
       
  1270 					"day": 24 * 60 * 60 * 1000,
       
  1271 					"month": 30 * 24 * 60 * 60 * 1000,
       
  1272 					"year": 365.2425 * 24 * 60 * 60 * 1000
       
  1273 				};
       
  1274 
       
  1275 
       
  1276 				// the allowed tick sizes, after 1 year we use
       
  1277 				// an integer algorithm
       
  1278 				var spec = [
       
  1279 					[1, "second"],
       
  1280 					[2, "second"],
       
  1281 					[5, "second"],
       
  1282 					[10, "second"],
       
  1283 					[30, "second"],
       
  1284 					[1, "minute"],
       
  1285 					[2, "minute"],
       
  1286 					[5, "minute"],
       
  1287 					[10, "minute"],
       
  1288 					[30, "minute"],
       
  1289 					[1, "hour"],
       
  1290 					[2, "hour"],
       
  1291 					[4, "hour"],
       
  1292 					[8, "hour"],
       
  1293 					[12, "hour"],
       
  1294 					[1, "day"],
       
  1295 					[2, "day"],
       
  1296 					[3, "day"],
       
  1297 					[0.25, "month"],
       
  1298 					[0.5, "month"],
       
  1299 					[1, "month"],
       
  1300 					[2, "month"],
       
  1301 					[3, "month"],
       
  1302 					[6, "month"],
       
  1303 					[1, "year"]
       
  1304 				];
       
  1305 
       
  1306 				var minSize = 0;
       
  1307 				if (opts.minTickSize != null) {
       
  1308 					if (typeof opts.tickSize == "number")
       
  1309 						minSize = opts.tickSize;
       
  1310 					else
       
  1311 						minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
       
  1312 				}
       
  1313 
       
  1314 				for (var i = 0; i < spec.length - 1; ++i)
       
  1315 					if (delta < (spec[i][0] * timeUnitSize[spec[i][1]]
       
  1316 						+ spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
       
  1317 						&& spec[i][0] * timeUnitSize[spec[i][1]] >= minSize)
       
  1318 						break;
       
  1319 				size = spec[i][0];
       
  1320 				unit = spec[i][1];
       
  1321 
       
  1322 				// special-case the possibility of several years
       
  1323 				if (unit == "year") {
       
  1324 					magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10));
       
  1325 					norm = (delta / timeUnitSize.year) / magn;
       
  1326 					if (norm < 1.5)
       
  1327 						size = 1;
       
  1328 					else if (norm < 3)
       
  1329 						size = 2;
       
  1330 					else if (norm < 7.5)
       
  1331 						size = 5;
       
  1332 					else
       
  1333 						size = 10;
       
  1334 
       
  1335 					size *= magn;
       
  1336 				}
       
  1337 
       
  1338 				axis.tickSize = opts.tickSize || [size, unit];
       
  1339 
       
  1340 				generator = function (axis) {
       
  1341 					var ticks = [],
       
  1342 						tickSize = axis.tickSize[0], unit = axis.tickSize[1],
       
  1343 						d = new Date(axis.min);
       
  1344 
       
  1345 					var step = tickSize * timeUnitSize[unit];
       
  1346 
       
  1347 					if (unit == "second")
       
  1348 						d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize));
       
  1349 					if (unit == "minute")
       
  1350 						d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize));
       
  1351 					if (unit == "hour")
       
  1352 						d.setUTCHours(floorInBase(d.getUTCHours(), tickSize));
       
  1353 					if (unit == "month")
       
  1354 						d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize));
       
  1355 					if (unit == "year")
       
  1356 						d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize));
       
  1357 
       
  1358 					// reset smaller components
       
  1359 					d.setUTCMilliseconds(0);
       
  1360 					if (step >= timeUnitSize.minute)
       
  1361 						d.setUTCSeconds(0);
       
  1362 					if (step >= timeUnitSize.hour)
       
  1363 						d.setUTCMinutes(0);
       
  1364 					if (step >= timeUnitSize.day)
       
  1365 						d.setUTCHours(0);
       
  1366 					if (step >= timeUnitSize.day * 4)
       
  1367 						d.setUTCDate(1);
       
  1368 					if (step >= timeUnitSize.year)
       
  1369 						d.setUTCMonth(0);
       
  1370 
       
  1371 
       
  1372 					var carry = 0, v = Number.NaN, prev;
       
  1373 					do {
       
  1374 						prev = v;
       
  1375 						v = d.getTime();
       
  1376 						ticks.push(v);
       
  1377 						if (unit == "month") {
       
  1378 							if (tickSize < 1) {
       
  1379 								// a bit complicated - we'll divide the month
       
  1380 								// up but we need to take care of fractions
       
  1381 								// so we don't end up in the middle of a day
       
  1382 								d.setUTCDate(1);
       
  1383 								var start = d.getTime();
       
  1384 								d.setUTCMonth(d.getUTCMonth() + 1);
       
  1385 								var end = d.getTime();
       
  1386 								d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
       
  1387 								carry = d.getUTCHours();
       
  1388 								d.setUTCHours(0);
       
  1389 							}
       
  1390 							else
       
  1391 								d.setUTCMonth(d.getUTCMonth() + tickSize);
       
  1392 						}
       
  1393 						else if (unit == "year") {
       
  1394 							d.setUTCFullYear(d.getUTCFullYear() + tickSize);
       
  1395 						}
       
  1396 						else
       
  1397 							d.setTime(v + step);
       
  1398 					} while (v < axis.max && v != prev);
       
  1399 
       
  1400 					return ticks;
       
  1401 				};
       
  1402 
       
  1403 				formatter = function (v, axis) {
       
  1404 					var d = new Date(v);
       
  1405 
       
  1406 					// first check global format
       
  1407 					if (opts.timeformat != null)
       
  1408 						return $.plot.formatDate(d, opts.timeformat, opts.monthNames);
       
  1409 
       
  1410 					var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
       
  1411 					var span = axis.max - axis.min;
       
  1412 					var suffix = (opts.twelveHourClock) ? " %p" : "";
       
  1413 
       
  1414 					if (t < timeUnitSize.minute)
       
  1415 						fmt = "%h:%M:%S" + suffix;
       
  1416 					else if (t < timeUnitSize.day) {
       
  1417 						if (span < 2 * timeUnitSize.day)
       
  1418 							fmt = "%h:%M" + suffix;
       
  1419 						else
       
  1420 							fmt = "%b %d %h:%M" + suffix;
       
  1421 					}
       
  1422 					else if (t < timeUnitSize.month)
       
  1423 						fmt = "%b %d";
       
  1424 					else if (t < timeUnitSize.year) {
       
  1425 						if (span < timeUnitSize.year)
       
  1426 							fmt = "%b";
       
  1427 						else
       
  1428 							fmt = "%b %y";
       
  1429 					}
       
  1430 					else
       
  1431 						fmt = "%y";
       
  1432 
       
  1433 					return $.plot.formatDate(d, fmt, opts.monthNames);
       
  1434 				};
       
  1435 			}
       
  1436 			else {
       
  1437 				// pretty rounding of base-10 numbers
       
  1438 				var maxDec = opts.tickDecimals;
       
  1439 				var dec = -Math.floor(Math.log(delta) / Math.LN10);
       
  1440 				if (maxDec != null && dec > maxDec)
       
  1441 					dec = maxDec;
       
  1442 
       
  1443 				magn = Math.pow(10, -dec);
       
  1444 				norm = delta / magn; // norm is between 1.0 and 10.0
       
  1445 
       
  1446 				if (norm < 1.5)
       
  1447 					size = 1;
       
  1448 				else if (norm < 3) {
       
  1449 					size = 2;
       
  1450 					// special case for 2.5, requires an extra decimal
       
  1451 					if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
       
  1452 						size = 2.5;
       
  1453 						++dec;
       
  1454 					}
       
  1455 				}
       
  1456 				else if (norm < 7.5)
       
  1457 					size = 5;
       
  1458 				else
       
  1459 					size = 10;
       
  1460 
       
  1461 				size *= magn;
       
  1462 
       
  1463 				if (opts.minTickSize != null && size < opts.minTickSize)
       
  1464 					size = opts.minTickSize;
       
  1465 
       
  1466 				axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
       
  1467 				axis.tickSize = opts.tickSize || size;
       
  1468 
       
  1469 				generator = function (axis) {
       
  1470 					var ticks = [];
       
  1471 
       
  1472 					// spew out all possible ticks
       
  1473 					var start = floorInBase(axis.min, axis.tickSize),
       
  1474 						i = 0, v = Number.NaN, prev;
       
  1475 					do {
       
  1476 						prev = v;
       
  1477 						v = start + i * axis.tickSize;
       
  1478 						ticks.push(v);
       
  1479 						++i;
       
  1480 					} while (v < axis.max && v != prev);
       
  1481 					return ticks;
       
  1482 				};
       
  1483 
       
  1484 				formatter = function (v, axis) {
       
  1485 					return v.toFixed(axis.tickDecimals);
       
  1486 				};
       
  1487 			}
       
  1488 
       
  1489 			if (opts.alignTicksWithAxis != null) {
       
  1490 				var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1];
       
  1491 				if (otherAxis && otherAxis.used && otherAxis != axis) {
       
  1492 					// consider snapping min/max to outermost nice ticks
       
  1493 					var niceTicks = generator(axis);
       
  1494 					if (niceTicks.length > 0) {
       
  1495 						if (opts.min == null)
       
  1496 							axis.min = Math.min(axis.min, niceTicks[0]);
       
  1497 						if (opts.max == null && niceTicks.length > 1)
       
  1498 							axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]);
       
  1499 					}
       
  1500 
       
  1501 					generator = function (axis) {
       
  1502 						// copy ticks, scaled to this axis
       
  1503 						var ticks = [], v, i;
       
  1504 						for (i = 0; i < otherAxis.ticks.length; ++i) {
       
  1505 							v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min);
       
  1506 							v = axis.min + v * (axis.max - axis.min);
       
  1507 							ticks.push(v);
       
  1508 						}
       
  1509 						return ticks;
       
  1510 					};
       
  1511 
       
  1512 					// we might need an extra decimal since forced
       
  1513 					// ticks don't necessarily fit naturally
       
  1514 					if (axis.mode != "time" && opts.tickDecimals == null) {
       
  1515 						var extraDec = Math.max(0, -Math.floor(Math.log(delta) / Math.LN10) + 1),
       
  1516 							ts = generator(axis);
       
  1517 
       
  1518 						// only proceed if the tick interval rounded
       
  1519 						// with an extra decimal doesn't give us a
       
  1520 						// zero at end
       
  1521 						if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec))))
       
  1522 							axis.tickDecimals = extraDec;
       
  1523 					}
       
  1524 				}
       
  1525 			}
       
  1526 
       
  1527 			axis.tickGenerator = generator;
       
  1528 			if ($.isFunction(opts.tickFormatter))
       
  1529 				axis.tickFormatter = function (v, axis) {
       
  1530 					return "" + opts.tickFormatter(v, axis);
       
  1531 				};
       
  1532 			else
       
  1533 				axis.tickFormatter = formatter;
       
  1534 		}
       
  1535 
       
  1536 		function setTicks(axis) {
       
  1537 			var oticks = axis.options.ticks, ticks = [];
       
  1538 			if (oticks == null || (typeof oticks == "number" && oticks > 0))
       
  1539 				ticks = axis.tickGenerator(axis);
       
  1540 			else if (oticks) {
       
  1541 				if ($.isFunction(oticks))
       
  1542 				// generate the ticks
       
  1543 					ticks = oticks({ min: axis.min, max: axis.max });
       
  1544 				else
       
  1545 					ticks = oticks;
       
  1546 			}
       
  1547 
       
  1548 			// clean up/labelify the supplied ticks, copy them over
       
  1549 			var i, v;
       
  1550 			axis.ticks = [];
       
  1551 			for (i = 0; i < ticks.length; ++i) {
       
  1552 				var label = null;
       
  1553 				var t = ticks[i];
       
  1554 				if (typeof t == "object") {
       
  1555 					v = +t[0];
       
  1556 					if (t.length > 1)
       
  1557 						label = t[1];
       
  1558 				}
       
  1559 				else
       
  1560 					v = +t;
       
  1561 				if (label == null)
       
  1562 					label = axis.tickFormatter(v, axis);
       
  1563 				if (!isNaN(v))
       
  1564 					axis.ticks.push({ v: v, label: label });
       
  1565 			}
       
  1566 		}
       
  1567 
       
  1568 		function snapRangeToTicks(axis, ticks) {
       
  1569 			if (axis.options.autoscaleMargin && ticks.length > 0) {
       
  1570 				// snap to ticks
       
  1571 				if (axis.options.min == null)
       
  1572 					axis.min = Math.min(axis.min, ticks[0].v);
       
  1573 				if (axis.options.max == null && ticks.length > 1)
       
  1574 					axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);
       
  1575 			}
       
  1576 		}
       
  1577 
       
  1578 		function draw() {
       
  1579 			ctx.clearRect(0, 0, canvasWidth, canvasHeight);
       
  1580 
       
  1581 			var grid = options.grid;
       
  1582 
       
  1583 			// draw background, if any
       
  1584 			if (grid.show && grid.backgroundColor)
       
  1585 				drawBackground();
       
  1586 
       
  1587 			if (grid.show && !grid.aboveData)
       
  1588 				drawGrid();
       
  1589 
       
  1590 			for (var i = 0; i < series.length; ++i) {
       
  1591 				executeHooks(hooks.drawSeries, [ctx, series[i]]);
       
  1592 				drawSeries(series[i]);
       
  1593 			}
       
  1594 
       
  1595 			executeHooks(hooks.draw, [ctx]);
       
  1596 
       
  1597 			if (grid.show && grid.aboveData)
       
  1598 				drawGrid();
       
  1599 		}
       
  1600 
       
  1601 		function extractRange(ranges, coord) {
       
  1602 			var axis, from, to, key, axes = allAxes();
       
  1603 
       
  1604 			for (i = 0; i < axes.length; ++i) {
       
  1605 				axis = axes[i];
       
  1606 				if (axis.direction == coord) {
       
  1607 					key = coord + axis.n + "axis";
       
  1608 					if (!ranges[key] && axis.n == 1)
       
  1609 						key = coord + "axis"; // support x1axis as xaxis
       
  1610 					if (ranges[key]) {
       
  1611 						from = ranges[key].from;
       
  1612 						to = ranges[key].to;
       
  1613 						break;
       
  1614 					}
       
  1615 				}
       
  1616 			}
       
  1617 
       
  1618 			// backwards-compat stuff - to be removed in future
       
  1619 			if (!ranges[key]) {
       
  1620 				axis = coord == "x" ? xaxes[0] : yaxes[0];
       
  1621 				from = ranges[coord + "1"];
       
  1622 				to = ranges[coord + "2"];
       
  1623 			}
       
  1624 
       
  1625 			// auto-reverse as an added bonus
       
  1626 			if (from != null && to != null && from > to) {
       
  1627 				var tmp = from;
       
  1628 				from = to;
       
  1629 				to = tmp;
       
  1630 			}
       
  1631 
       
  1632 			return { from: from, to: to, axis: axis };
       
  1633 		}
       
  1634 
       
  1635 		function drawBackground() {
       
  1636 			ctx.save();
       
  1637 			ctx.translate(plotOffset.left, plotOffset.top);
       
  1638 
       
  1639 			ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
       
  1640 			ctx.fillRect(0, 0, plotWidth, plotHeight);
       
  1641 			ctx.restore();
       
  1642 		}
       
  1643 
       
  1644 		function drawGrid() {
       
  1645 			var i;
       
  1646 
       
  1647 			ctx.save();
       
  1648 			ctx.translate(plotOffset.left, plotOffset.top);
       
  1649 
       
  1650 			// draw markings
       
  1651 			var markings = options.grid.markings;
       
  1652 			if (markings) {
       
  1653 				if ($.isFunction(markings)) {
       
  1654 					var axes = plot.getAxes();
       
  1655 					// xmin etc. is backwards compatibility, to be
       
  1656 					// removed in the future
       
  1657 					axes.xmin = axes.xaxis.min;
       
  1658 					axes.xmax = axes.xaxis.max;
       
  1659 					axes.ymin = axes.yaxis.min;
       
  1660 					axes.ymax = axes.yaxis.max;
       
  1661 
       
  1662 					markings = markings(axes);
       
  1663 				}
       
  1664 
       
  1665 				for (i = 0; i < markings.length; ++i) {
       
  1666 					var m = markings[i],
       
  1667 						xrange = extractRange(m, "x"),
       
  1668 						yrange = extractRange(m, "y");
       
  1669 
       
  1670 					// fill in missing
       
  1671 					if (xrange.from == null)
       
  1672 						xrange.from = xrange.axis.min;
       
  1673 					if (xrange.to == null)
       
  1674 						xrange.to = xrange.axis.max;
       
  1675 					if (yrange.from == null)
       
  1676 						yrange.from = yrange.axis.min;
       
  1677 					if (yrange.to == null)
       
  1678 						yrange.to = yrange.axis.max;
       
  1679 
       
  1680 					// clip
       
  1681 					if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
       
  1682 						yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)
       
  1683 						continue;
       
  1684 
       
  1685 					xrange.from = Math.max(xrange.from, xrange.axis.min);
       
  1686 					xrange.to = Math.min(xrange.to, xrange.axis.max);
       
  1687 					yrange.from = Math.max(yrange.from, yrange.axis.min);
       
  1688 					yrange.to = Math.min(yrange.to, yrange.axis.max);
       
  1689 
       
  1690 					if (xrange.from == xrange.to && yrange.from == yrange.to)
       
  1691 						continue;
       
  1692 
       
  1693 					// then draw
       
  1694 					xrange.from = xrange.axis.p2c(xrange.from);
       
  1695 					xrange.to = xrange.axis.p2c(xrange.to);
       
  1696 					yrange.from = yrange.axis.p2c(yrange.from);
       
  1697 					yrange.to = yrange.axis.p2c(yrange.to);
       
  1698 
       
  1699 					if (xrange.from == xrange.to || yrange.from == yrange.to) {
       
  1700 						// draw line
       
  1701 						ctx.beginPath();
       
  1702 						ctx.strokeStyle = m.color || options.grid.markingsColor;
       
  1703 						ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth;
       
  1704 						ctx.moveTo(xrange.from, yrange.from);
       
  1705 						ctx.lineTo(xrange.to, yrange.to);
       
  1706 						ctx.stroke();
       
  1707 					}
       
  1708 					else {
       
  1709 						// fill area
       
  1710 						ctx.fillStyle = m.color || options.grid.markingsColor;
       
  1711 						ctx.fillRect(xrange.from, yrange.to,
       
  1712 									 xrange.to - xrange.from,
       
  1713 									 yrange.from - yrange.to);
       
  1714 					}
       
  1715 				}
       
  1716 			}
       
  1717 
       
  1718 			// draw the ticks
       
  1719 			var axes = allAxes(), bw = options.grid.borderWidth;
       
  1720 
       
  1721 			for (var j = 0; j < axes.length; ++j) {
       
  1722 				var axis = axes[j], box = axis.box,
       
  1723 					t = axis.tickLength, x, y, xoff, yoff;
       
  1724 				if (!axis.show || axis.ticks.length == 0)
       
  1725 					continue
       
  1726 
       
  1727 				ctx.strokeStyle = axis.options.tickColor || $.color.parse(axis.options.color).scale('a', 0.22).toString();
       
  1728 				ctx.lineWidth = 1;
       
  1729 
       
  1730 				// find the edges
       
  1731 				if (axis.direction == "x") {
       
  1732 					x = 0;
       
  1733 					if (t == "full")
       
  1734 						y = (axis.position == "top" ? 0 : plotHeight);
       
  1735 					else
       
  1736 						y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0);
       
  1737 				}
       
  1738 				else {
       
  1739 					y = 0;
       
  1740 					if (t == "full")
       
  1741 						x = (axis.position == "left" ? 0 : plotWidth);
       
  1742 					else
       
  1743 						x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0);
       
  1744 				}
       
  1745 
       
  1746 				// draw tick bar
       
  1747 				if (!axis.innermost) {
       
  1748 					ctx.beginPath();
       
  1749 					xoff = yoff = 0;
       
  1750 					if (axis.direction == "x")
       
  1751 						xoff = plotWidth;
       
  1752 					else
       
  1753 						yoff = plotHeight;
       
  1754 
       
  1755 					if (ctx.lineWidth == 1) {
       
  1756 						x = Math.floor(x) + 0.5;
       
  1757 						y = Math.floor(y) + 0.5;
       
  1758 					}
       
  1759 
       
  1760 					ctx.moveTo(x, y);
       
  1761 					ctx.lineTo(x + xoff, y + yoff);
       
  1762 					ctx.stroke();
       
  1763 				}
       
  1764 
       
  1765 				// draw ticks
       
  1766 				ctx.beginPath();
       
  1767 				for (i = 0; i < axis.ticks.length; ++i) {
       
  1768 					var v = axis.ticks[i].v;
       
  1769 
       
  1770 					xoff = yoff = 0;
       
  1771 
       
  1772 					if (v < axis.min || v > axis.max
       
  1773 						// skip those lying on the axes if we got a border
       
  1774 						|| (t == "full" && bw > 0
       
  1775 						&& (v == axis.min || v == axis.max)))
       
  1776 						continue;
       
  1777 
       
  1778 					if (axis.direction == "x") {
       
  1779 						x = axis.p2c(v);
       
  1780 						yoff = t == "full" ? -plotHeight : t;
       
  1781 
       
  1782 						if (axis.position == "top")
       
  1783 							yoff = -yoff;
       
  1784 					}
       
  1785 					else {
       
  1786 						y = axis.p2c(v);
       
  1787 						xoff = t == "full" ? -plotWidth : t;
       
  1788 
       
  1789 						if (axis.position == "left")
       
  1790 							xoff = -xoff;
       
  1791 					}
       
  1792 
       
  1793 					if (ctx.lineWidth == 1) {
       
  1794 						if (axis.direction == "x")
       
  1795 							x = Math.floor(x) + 0.5;
       
  1796 						else
       
  1797 							y = Math.floor(y) + 0.5;
       
  1798 					}
       
  1799 
       
  1800 					ctx.moveTo(x, y);
       
  1801 					ctx.lineTo(x + xoff, y + yoff);
       
  1802 				}
       
  1803 
       
  1804 				ctx.stroke();
       
  1805 			}
       
  1806 
       
  1807 
       
  1808 			// draw border
       
  1809 			if (bw) {
       
  1810 				ctx.lineWidth = bw;
       
  1811 				ctx.strokeStyle = options.grid.borderColor;
       
  1812 				ctx.strokeRect(-bw / 2, -bw / 2, plotWidth + bw, plotHeight + bw);
       
  1813 			}
       
  1814 
       
  1815 			ctx.restore();
       
  1816 		}
       
  1817 
       
  1818 		function insertAxisLabels() {
       
  1819 			placeholder.find(".tickLabels").remove();
       
  1820 
       
  1821 			var html = ['<div class="tickLabels" style="font-size:smaller">'];
       
  1822 
       
  1823 			var axes = allAxes();
       
  1824 			for (var j = 0; j < axes.length; ++j) {
       
  1825 				var axis = axes[j], box = axis.box;
       
  1826 				if (!axis.show)
       
  1827 					continue;
       
  1828 				//debug: html.push('<div style="position:absolute;opacity:0.10;background-color:red;left:' + box.left + 'px;top:' + box.top + 'px;width:' + box.width +  'px;height:' + box.height + 'px"></div>')
       
  1829 				html.push('<div class="' + axis.direction + 'Axis ' + axis.direction + axis.n + 'Axis" style="color:' + axis.options.color + '">');
       
  1830 				for (var i = 0; i < axis.ticks.length; ++i) {
       
  1831 					var tick = axis.ticks[i];
       
  1832 					if (!tick.label || tick.v < axis.min || tick.v > axis.max)
       
  1833 						continue;
       
  1834 
       
  1835 					var pos = {}, align;
       
  1836 
       
  1837 					if (axis.direction == "x") {
       
  1838 						align = "center";
       
  1839 						pos.left = Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth / 2);
       
  1840 						if (axis.position == "bottom")
       
  1841 							pos.top = box.top + box.padding;
       
  1842 						else
       
  1843 							pos.bottom = canvasHeight - (box.top + box.height - box.padding);
       
  1844 					}
       
  1845 					else {
       
  1846 						pos.top = Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight / 2);
       
  1847 						if (axis.position == "left") {
       
  1848 							pos.right = canvasWidth - (box.left + box.width - box.padding)
       
  1849 							align = "right";
       
  1850 						}
       
  1851 						else {
       
  1852 							pos.left = box.left + box.padding;
       
  1853 							align = "left";
       
  1854 						}
       
  1855 					}
       
  1856 
       
  1857 					pos.width = axis.labelWidth;
       
  1858 
       
  1859 					var style = ["position:absolute", "text-align:" + align ];
       
  1860 					for (var a in pos)
       
  1861 						style.push(a + ":" + pos[a] + "px")
       
  1862 
       
  1863 					html.push('<div class="tickLabel" style="' + style.join(';') + '">' + tick.label + '</div>');
       
  1864 				}
       
  1865 				html.push('</div>');
       
  1866 			}
       
  1867 
       
  1868 			html.push('</div>');
       
  1869 
       
  1870 			placeholder.append(html.join(""));
       
  1871 		}
       
  1872 
       
  1873 		function drawSeries(series) {
       
  1874 			if (series.lines.show)
       
  1875 				drawSeriesLines(series);
       
  1876 			if (series.bars.show)
       
  1877 				drawSeriesBars(series);
       
  1878 			if (series.points.show)
       
  1879 				drawSeriesPoints(series);
       
  1880 		}
       
  1881 
       
  1882 		function drawSeriesLines(series) {
       
  1883 			function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
       
  1884 				var points = datapoints.points,
       
  1885 					ps = datapoints.pointsize,
       
  1886 					prevx = null, prevy = null;
       
  1887 
       
  1888 				ctx.beginPath();
       
  1889 				for (var i = ps; i < points.length; i += ps) {
       
  1890 					var x1 = points[i - ps], y1 = points[i - ps + 1],
       
  1891 						x2 = points[i], y2 = points[i + 1];
       
  1892 
       
  1893 					if (x1 == null || x2 == null)
       
  1894 						continue;
       
  1895 
       
  1896 					// clip with ymin
       
  1897 					if (y1 <= y2 && y1 < axisy.min) {
       
  1898 						if (y2 < axisy.min)
       
  1899 							continue;   // line segment is outside
       
  1900 						// compute new intersection point
       
  1901 						x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
       
  1902 						y1 = axisy.min;
       
  1903 					}
       
  1904 					else if (y2 <= y1 && y2 < axisy.min) {
       
  1905 						if (y1 < axisy.min)
       
  1906 							continue;
       
  1907 						x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
       
  1908 						y2 = axisy.min;
       
  1909 					}
       
  1910 
       
  1911 					// clip with ymax
       
  1912 					if (y1 >= y2 && y1 > axisy.max) {
       
  1913 						if (y2 > axisy.max)
       
  1914 							continue;
       
  1915 						x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
       
  1916 						y1 = axisy.max;
       
  1917 					}
       
  1918 					else if (y2 >= y1 && y2 > axisy.max) {
       
  1919 						if (y1 > axisy.max)
       
  1920 							continue;
       
  1921 						x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
       
  1922 						y2 = axisy.max;
       
  1923 					}
       
  1924 
       
  1925 					// clip with xmin
       
  1926 					if (x1 <= x2 && x1 < axisx.min) {
       
  1927 						if (x2 < axisx.min)
       
  1928 							continue;
       
  1929 						y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
       
  1930 						x1 = axisx.min;
       
  1931 					}
       
  1932 					else if (x2 <= x1 && x2 < axisx.min) {
       
  1933 						if (x1 < axisx.min)
       
  1934 							continue;
       
  1935 						y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
       
  1936 						x2 = axisx.min;
       
  1937 					}
       
  1938 
       
  1939 					// clip with xmax
       
  1940 					if (x1 >= x2 && x1 > axisx.max) {
       
  1941 						if (x2 > axisx.max)
       
  1942 							continue;
       
  1943 						y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
       
  1944 						x1 = axisx.max;
       
  1945 					}
       
  1946 					else if (x2 >= x1 && x2 > axisx.max) {
       
  1947 						if (x1 > axisx.max)
       
  1948 							continue;
       
  1949 						y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
       
  1950 						x2 = axisx.max;
       
  1951 					}
       
  1952 
       
  1953 					if (x1 != prevx || y1 != prevy)
       
  1954 						ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
       
  1955 
       
  1956 					prevx = x2;
       
  1957 					prevy = y2;
       
  1958 					ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
       
  1959 				}
       
  1960 				ctx.stroke();
       
  1961 			}
       
  1962 
       
  1963 			function plotLineArea(datapoints, axisx, axisy) {
       
  1964 				var points = datapoints.points,
       
  1965 					ps = datapoints.pointsize,
       
  1966 					bottom = Math.min(Math.max(0, axisy.min), axisy.max),
       
  1967 					i = 0, top, areaOpen = false,
       
  1968 					ypos = 1, segmentStart = 0, segmentEnd = 0;
       
  1969 
       
  1970 				// we process each segment in two turns, first forward
       
  1971 				// direction to sketch out top, then once we hit the
       
  1972 				// end we go backwards to sketch the bottom
       
  1973 				while (true) {
       
  1974 					if (ps > 0 && i > points.length + ps)
       
  1975 						break;
       
  1976 
       
  1977 					i += ps; // ps is negative if going backwards
       
  1978 
       
  1979 					var x1 = points[i - ps],
       
  1980 						y1 = points[i - ps + ypos],
       
  1981 						x2 = points[i], y2 = points[i + ypos];
       
  1982 
       
  1983 					if (areaOpen) {
       
  1984 						if (ps > 0 && x1 != null && x2 == null) {
       
  1985 							// at turning point
       
  1986 							segmentEnd = i;
       
  1987 							ps = -ps;
       
  1988 							ypos = 2;
       
  1989 							continue;
       
  1990 						}
       
  1991 
       
  1992 						if (ps < 0 && i == segmentStart + ps) {
       
  1993 							// done with the reverse sweep
       
  1994 							ctx.fill();
       
  1995 							areaOpen = false;
       
  1996 							ps = -ps;
       
  1997 							ypos = 1;
       
  1998 							i = segmentStart = segmentEnd + ps;
       
  1999 							continue;
       
  2000 						}
       
  2001 					}
       
  2002 
       
  2003 					if (x1 == null || x2 == null)
       
  2004 						continue;
       
  2005 
       
  2006 					// clip x values
       
  2007 
       
  2008 					// clip with xmin
       
  2009 					if (x1 <= x2 && x1 < axisx.min) {
       
  2010 						if (x2 < axisx.min)
       
  2011 							continue;
       
  2012 						y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
       
  2013 						x1 = axisx.min;
       
  2014 					}
       
  2015 					else if (x2 <= x1 && x2 < axisx.min) {
       
  2016 						if (x1 < axisx.min)
       
  2017 							continue;
       
  2018 						y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
       
  2019 						x2 = axisx.min;
       
  2020 					}
       
  2021 
       
  2022 					// clip with xmax
       
  2023 					if (x1 >= x2 && x1 > axisx.max) {
       
  2024 						if (x2 > axisx.max)
       
  2025 							continue;
       
  2026 						y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
       
  2027 						x1 = axisx.max;
       
  2028 					}
       
  2029 					else if (x2 >= x1 && x2 > axisx.max) {
       
  2030 						if (x1 > axisx.max)
       
  2031 							continue;
       
  2032 						y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
       
  2033 						x2 = axisx.max;
       
  2034 					}
       
  2035 
       
  2036 					if (!areaOpen) {
       
  2037 						// open area
       
  2038 						ctx.beginPath();
       
  2039 						ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
       
  2040 						areaOpen = true;
       
  2041 					}
       
  2042 
       
  2043 					// now first check the case where both is outside
       
  2044 					if (y1 >= axisy.max && y2 >= axisy.max) {
       
  2045 						ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
       
  2046 						ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
       
  2047 						continue;
       
  2048 					}
       
  2049 					else if (y1 <= axisy.min && y2 <= axisy.min) {
       
  2050 						ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
       
  2051 						ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
       
  2052 						continue;
       
  2053 					}
       
  2054 
       
  2055 					// else it's a bit more complicated, there might
       
  2056 					// be a flat maxed out rectangle first, then a
       
  2057 					// triangular cutout or reverse; to find these
       
  2058 					// keep track of the current x values
       
  2059 					var x1old = x1, x2old = x2;
       
  2060 
       
  2061 					// clip the y values, without shortcutting, we
       
  2062 					// go through all cases in turn
       
  2063 
       
  2064 					// clip with ymin
       
  2065 					if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
       
  2066 						x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
       
  2067 						y1 = axisy.min;
       
  2068 					}
       
  2069 					else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
       
  2070 						x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
       
  2071 						y2 = axisy.min;
       
  2072 					}
       
  2073 
       
  2074 					// clip with ymax
       
  2075 					if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
       
  2076 						x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
       
  2077 						y1 = axisy.max;
       
  2078 					}
       
  2079 					else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
       
  2080 						x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
       
  2081 						y2 = axisy.max;
       
  2082 					}
       
  2083 
       
  2084 					// if the x value was changed we got a rectangle
       
  2085 					// to fill
       
  2086 					if (x1 != x1old) {
       
  2087 						ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
       
  2088 						// it goes to (x1, y1), but we fill that below
       
  2089 					}
       
  2090 
       
  2091 					// fill triangular section, this sometimes result
       
  2092 					// in redundant points if (x1, y1) hasn't changed
       
  2093 					// from previous line to, but we just ignore that
       
  2094 					ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
       
  2095 					ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
       
  2096 
       
  2097 					// fill the other rectangle if it's there
       
  2098 					if (x2 != x2old) {
       
  2099 						ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
       
  2100 						ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
       
  2101 					}
       
  2102 				}
       
  2103 			}
       
  2104 
       
  2105 			ctx.save();
       
  2106 			ctx.translate(plotOffset.left, plotOffset.top);
       
  2107 			ctx.lineJoin = "round";
       
  2108 
       
  2109 			var lw = series.lines.lineWidth,
       
  2110 				sw = series.shadowSize;
       
  2111 			// FIXME: consider another form of shadow when filling is turned on
       
  2112 			if (lw > 0 && sw > 0) {
       
  2113 				// draw shadow as a thick and thin line with transparency
       
  2114 				ctx.lineWidth = sw;
       
  2115 				ctx.strokeStyle = "rgba(0,0,0,0.1)";
       
  2116 				// position shadow at angle from the mid of line
       
  2117 				var angle = Math.PI / 18;
       
  2118 				plotLine(series.datapoints, Math.sin(angle) * (lw / 2 + sw / 2), Math.cos(angle) * (lw / 2 + sw / 2), series.xaxis, series.yaxis);
       
  2119 				ctx.lineWidth = sw / 2;
       
  2120 				plotLine(series.datapoints, Math.sin(angle) * (lw / 2 + sw / 4), Math.cos(angle) * (lw / 2 + sw / 4), series.xaxis, series.yaxis);
       
  2121 			}
       
  2122 
       
  2123 			ctx.lineWidth = lw;
       
  2124 			ctx.strokeStyle = series.color;
       
  2125 			var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
       
  2126 			if (fillStyle) {
       
  2127 				ctx.fillStyle = fillStyle;
       
  2128 				plotLineArea(series.datapoints, series.xaxis, series.yaxis);
       
  2129 			}
       
  2130 
       
  2131 			if (lw > 0)
       
  2132 				plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
       
  2133 			ctx.restore();
       
  2134 		}
       
  2135 
       
  2136 		function drawSeriesPoints(series) {
       
  2137 			function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) {
       
  2138 				var points = datapoints.points, ps = datapoints.pointsize;
       
  2139 
       
  2140 				for (var i = 0; i < points.length; i += ps) {
       
  2141 					var x = points[i], y = points[i + 1];
       
  2142 					if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
       
  2143 						continue;
       
  2144 
       
  2145 					ctx.beginPath();
       
  2146 					x = axisx.p2c(x);
       
  2147 					y = axisy.p2c(y) + offset;
       
  2148 					if (symbol == "circle")
       
  2149 						ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);
       
  2150 					else
       
  2151 						symbol(ctx, x, y, radius, shadow);
       
  2152 					ctx.closePath();
       
  2153 
       
  2154 					if (fillStyle) {
       
  2155 						ctx.fillStyle = fillStyle;
       
  2156 						ctx.fill();
       
  2157 					}
       
  2158 					ctx.stroke();
       
  2159 				}
       
  2160 			}
       
  2161 
       
  2162 			ctx.save();
       
  2163 			ctx.translate(plotOffset.left, plotOffset.top);
       
  2164 
       
  2165 			var lw = series.points.lineWidth,
       
  2166 				sw = series.shadowSize,
       
  2167 				radius = series.points.radius,
       
  2168 				symbol = series.points.symbol;
       
  2169 			if (lw > 0 && sw > 0) {
       
  2170 				// draw shadow in two steps
       
  2171 				var w = sw / 2;
       
  2172 				ctx.lineWidth = w;
       
  2173 				ctx.strokeStyle = "rgba(0,0,0,0.1)";
       
  2174 				plotPoints(series.datapoints, radius, null, w + w / 2, true,
       
  2175 						   series.xaxis, series.yaxis, symbol);
       
  2176 
       
  2177 				ctx.strokeStyle = "rgba(0,0,0,0.2)";
       
  2178 				plotPoints(series.datapoints, radius, null, w / 2, true,
       
  2179 						   series.xaxis, series.yaxis, symbol);
       
  2180 			}
       
  2181 
       
  2182 			ctx.lineWidth = lw;
       
  2183 			ctx.strokeStyle = series.color;
       
  2184 			plotPoints(series.datapoints, radius,
       
  2185 					   getFillStyle(series.points, series.color), 0, false,
       
  2186 					   series.xaxis, series.yaxis, symbol);
       
  2187 			ctx.restore();
       
  2188 		}
       
  2189 
       
  2190 		function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
       
  2191 			var left, right, bottom, top,
       
  2192 				drawLeft, drawRight, drawTop, drawBottom,
       
  2193 				tmp;
       
  2194 
       
  2195 			// in horizontal mode, we start the bar from the left
       
  2196 			// instead of from the bottom so it appears to be
       
  2197 			// horizontal rather than vertical
       
  2198 			if (horizontal) {
       
  2199 				drawBottom = drawRight = drawTop = true;
       
  2200 				drawLeft = false;
       
  2201 				left = b;
       
  2202 				right = x;
       
  2203 				top = y + barLeft;
       
  2204 				bottom = y + barRight;
       
  2205 
       
  2206 				// account for negative bars
       
  2207 				if (right < left) {
       
  2208 					tmp = right;
       
  2209 					right = left;
       
  2210 					left = tmp;
       
  2211 					drawLeft = true;
       
  2212 					drawRight = false;
       
  2213 				}
       
  2214 			}
       
  2215 			else {
       
  2216 				drawLeft = drawRight = drawTop = true;
       
  2217 				drawBottom = false;
       
  2218 				left = x + barLeft;
       
  2219 				right = x + barRight;
       
  2220 				bottom = b;
       
  2221 				top = y;
       
  2222 
       
  2223 				// account for negative bars
       
  2224 				if (top < bottom) {
       
  2225 					tmp = top;
       
  2226 					top = bottom;
       
  2227 					bottom = tmp;
       
  2228 					drawBottom = true;
       
  2229 					drawTop = false;
       
  2230 				}
       
  2231 			}
       
  2232 
       
  2233 			// clip
       
  2234 			if (right < axisx.min || left > axisx.max ||
       
  2235 				top < axisy.min || bottom > axisy.max)
       
  2236 				return;
       
  2237 
       
  2238 			if (left < axisx.min) {
       
  2239 				left = axisx.min;
       
  2240 				drawLeft = false;
       
  2241 			}
       
  2242 
       
  2243 			if (right > axisx.max) {
       
  2244 				right = axisx.max;
       
  2245 				drawRight = false;
       
  2246 			}
       
  2247 
       
  2248 			if (bottom < axisy.min) {
       
  2249 				bottom = axisy.min;
       
  2250 				drawBottom = false;
       
  2251 			}
       
  2252 
       
  2253 			if (top > axisy.max) {
       
  2254 				top = axisy.max;
       
  2255 				drawTop = false;
       
  2256 			}
       
  2257 
       
  2258 			left = axisx.p2c(left);
       
  2259 			bottom = axisy.p2c(bottom);
       
  2260 			right = axisx.p2c(right);
       
  2261 			top = axisy.p2c(top);
       
  2262 
       
  2263 			// fill the bar
       
  2264 			if (fillStyleCallback) {
       
  2265 				c.beginPath();
       
  2266 				c.moveTo(left, bottom);
       
  2267 				c.lineTo(left, top);
       
  2268 				c.lineTo(right, top);
       
  2269 				c.lineTo(right, bottom);
       
  2270 				c.fillStyle = fillStyleCallback(bottom, top);
       
  2271 				c.fill();
       
  2272 			}
       
  2273 
       
  2274 			// draw outline
       
  2275 			if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
       
  2276 				c.beginPath();
       
  2277 
       
  2278 				// FIXME: inline moveTo is buggy with excanvas
       
  2279 				c.moveTo(left, bottom + offset);
       
  2280 				if (drawLeft)
       
  2281 					c.lineTo(left, top + offset);
       
  2282 				else
       
  2283 					c.moveTo(left, top + offset);
       
  2284 				if (drawTop)
       
  2285 					c.lineTo(right, top + offset);
       
  2286 				else
       
  2287 					c.moveTo(right, top + offset);
       
  2288 				if (drawRight)
       
  2289 					c.lineTo(right, bottom + offset);
       
  2290 				else
       
  2291 					c.moveTo(right, bottom + offset);
       
  2292 				if (drawBottom)
       
  2293 					c.lineTo(left, bottom + offset);
       
  2294 				else
       
  2295 					c.moveTo(left, bottom + offset);
       
  2296 				c.stroke();
       
  2297 			}
       
  2298 		}
       
  2299 
       
  2300 		function drawSeriesBars(series) {
       
  2301 			function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) {
       
  2302 				var points = datapoints.points, ps = datapoints.pointsize;
       
  2303 
       
  2304 				for (var i = 0; i < points.length; i += ps) {
       
  2305 					if (points[i] == null)
       
  2306 						continue;
       
  2307 					drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);
       
  2308 				}
       
  2309 			}
       
  2310 
       
  2311 			ctx.save();
       
  2312 			ctx.translate(plotOffset.left, plotOffset.top);
       
  2313 
       
  2314 			// FIXME: figure out a way to add shadows (for instance along the right edge)
       
  2315 			ctx.lineWidth = series.bars.lineWidth;
       
  2316 			ctx.strokeStyle = series.color;
       
  2317 			var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth / 2;
       
  2318 			var fillStyleCallback = series.bars.fill ? function (bottom, top) {
       
  2319 				return getFillStyle(series.bars, series.color, bottom, top);
       
  2320 			} : null;
       
  2321 			plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis);
       
  2322 			ctx.restore();
       
  2323 		}
       
  2324 
       
  2325 		function getFillStyle(filloptions, seriesColor, bottom, top) {
       
  2326 			var fill = filloptions.fill;
       
  2327 			if (!fill)
       
  2328 				return null;
       
  2329 
       
  2330 			if (filloptions.fillColor)
       
  2331 				return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
       
  2332 
       
  2333 			var c = $.color.parse(seriesColor);
       
  2334 			c.a = typeof fill == "number" ? fill : 0.4;
       
  2335 			c.normalize();
       
  2336 			return c.toString();
       
  2337 		}
       
  2338 
       
  2339 		function insertLegend() {
       
  2340 			placeholder.find(".legend").remove();
       
  2341 
       
  2342 			if (!options.legend.show)
       
  2343 				return;
       
  2344 
       
  2345 			var fragments = [], rowStarted = false,
       
  2346 				lf = options.legend.labelFormatter, s, label;
       
  2347 			for (var i = 0; i < series.length; ++i) {
       
  2348 				s = series[i];
       
  2349 				label = s.label;
       
  2350 				if (!label)
       
  2351 					continue;
       
  2352 
       
  2353 				if (i % options.legend.noColumns == 0) {
       
  2354 					if (rowStarted)
       
  2355 						fragments.push('</tr>');
       
  2356 					fragments.push('<tr>');
       
  2357 					rowStarted = true;
       
  2358 				}
       
  2359 
       
  2360 				if (lf)
       
  2361 					label = lf(label, s);
       
  2362 
       
  2363 				fragments.push(
       
  2364 					'<td class="legendColorBox"><div style="' + options.legend.labelBoxBorderColor + '"><div style="border:2px solid ' + s.color + ';overflow:hidden"></div></div></td>' +
       
  2365 						'<td class="legendLabel"><span>' + label + '</span></td>');
       
  2366 			}
       
  2367 			if (rowStarted)
       
  2368 				fragments.push('</tr>');
       
  2369 
       
  2370 			if (fragments.length == 0)
       
  2371 				return;
       
  2372 
       
  2373 			var table = '<table style="font-size: 11px; color:' + options.grid.color + '">' + fragments.join("") + '</table>';
       
  2374 			if (options.legend.container != null)
       
  2375 				$(options.legend.container).html(table);
       
  2376 			else {
       
  2377 				var pos = "",
       
  2378 					p = options.legend.position,
       
  2379 					m = options.legend.margin;
       
  2380 				if (m[0] == null)
       
  2381 					m = [m, m];
       
  2382 				if (p.charAt(0) == "n")
       
  2383 					pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
       
  2384 				else if (p.charAt(0) == "s")
       
  2385 					pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
       
  2386 				if (p.charAt(1) == "e")
       
  2387 					pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
       
  2388 				else if (p.charAt(1) == "w")
       
  2389 					pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
       
  2390 				var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos + ';') + '</div>').appendTo(placeholder);
       
  2391 				if (options.legend.backgroundOpacity != 0.0) {
       
  2392 					// put in the transparent background
       
  2393 					// separately to avoid blended labels and
       
  2394 					// label boxes
       
  2395 					var c = options.legend.backgroundColor;
       
  2396 					if (c == null) {
       
  2397 						c = options.grid.backgroundColor;
       
  2398 						if (c && typeof c == "string")
       
  2399 							c = $.color.parse(c);
       
  2400 						else
       
  2401 							c = $.color.extract(legend, 'background-color');
       
  2402 						c.a = 1;
       
  2403 						c = c.toString();
       
  2404 					}
       
  2405 					var div = legend.children();
       
  2406 					$('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos + 'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
       
  2407 				}
       
  2408 			}
       
  2409 		}
       
  2410 
       
  2411 
       
  2412 		// interactive features
       
  2413 
       
  2414 		var highlights = [],
       
  2415 			redrawTimeout = null;
       
  2416 
       
  2417 		// returns the data item the mouse is over, or null if none is found
       
  2418 		function findNearbyItem(mouseX, mouseY, seriesFilter) {
       
  2419 			var maxDistance = options.grid.mouseActiveRadius,
       
  2420 				smallestDistance = maxDistance * maxDistance + 1,
       
  2421 				item = null, foundPoint = false, i, j;
       
  2422 
       
  2423 			for (i = series.length - 1; i >= 0; --i) {
       
  2424 				if (!seriesFilter(series[i]))
       
  2425 					continue;
       
  2426 
       
  2427 				var s = series[i],
       
  2428 					axisx = s.xaxis,
       
  2429 					axisy = s.yaxis,
       
  2430 					points = s.datapoints.points,
       
  2431 					ps = s.datapoints.pointsize,
       
  2432 					mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
       
  2433 					my = axisy.c2p(mouseY),
       
  2434 					maxx = maxDistance / axisx.scale,
       
  2435 					maxy = maxDistance / axisy.scale;
       
  2436 
       
  2437 				// with inverse transforms, we can't use the maxx/maxy
       
  2438 				// optimization, sadly
       
  2439 				if (axisx.options.inverseTransform)
       
  2440 					maxx = Number.MAX_VALUE;
       
  2441 				if (axisy.options.inverseTransform)
       
  2442 					maxy = Number.MAX_VALUE;
       
  2443 
       
  2444 				if (s.lines.show || s.points.show) {
       
  2445 					for (j = 0; j < points.length; j += ps) {
       
  2446 						var x = points[j], y = points[j + 1];
       
  2447 						if (x == null)
       
  2448 							continue;
       
  2449 
       
  2450 						// For points and lines, the cursor must be within a
       
  2451 						// certain distance to the data point
       
  2452 						if (x - mx > maxx || x - mx < -maxx ||
       
  2453 							y - my > maxy || y - my < -maxy)
       
  2454 							continue;
       
  2455 
       
  2456 						// We have to calculate distances in pixels, not in
       
  2457 						// data units, because the scales of the axes may be different
       
  2458 						var dx = Math.abs(axisx.p2c(x) - mouseX),
       
  2459 							dy = Math.abs(axisy.p2c(y) - mouseY),
       
  2460 							dist = dx * dx + dy * dy; // we save the sqrt
       
  2461 
       
  2462 						// use <= to ensure last point takes precedence
       
  2463 						// (last generally means on top of)
       
  2464 						if (dist < smallestDistance) {
       
  2465 							smallestDistance = dist;
       
  2466 							item = [i, j / ps];
       
  2467 						}
       
  2468 					}
       
  2469 				}
       
  2470 
       
  2471 				if (s.bars.show && !item) { // no other point can be nearby
       
  2472 					var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth / 2,
       
  2473 						barRight = barLeft + s.bars.barWidth;
       
  2474 
       
  2475 					for (j = 0; j < points.length; j += ps) {
       
  2476 						var x = points[j], y = points[j + 1], b = points[j + 2];
       
  2477 						if (x == null)
       
  2478 							continue;
       
  2479 
       
  2480 						// for a bar graph, the cursor must be inside the bar
       
  2481 						if (series[i].bars.horizontal ?
       
  2482 							(mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
       
  2483 								my >= y + barLeft && my <= y + barRight) :
       
  2484 							(mx >= x + barLeft && mx <= x + barRight &&
       
  2485 								my >= Math.min(b, y) && my <= Math.max(b, y)))
       
  2486 							item = [i, j / ps];
       
  2487 					}
       
  2488 				}
       
  2489 			}
       
  2490 
       
  2491 			if (item) {
       
  2492 				i = item[0];
       
  2493 				j = item[1];
       
  2494 				ps = series[i].datapoints.pointsize;
       
  2495 
       
  2496 				return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
       
  2497 					dataIndex: j,
       
  2498 					series: series[i],
       
  2499 					seriesIndex: i };
       
  2500 			}
       
  2501 
       
  2502 			return null;
       
  2503 		}
       
  2504 
       
  2505 		function onMouseMove(e) {
       
  2506 			if (options.grid.hoverable)
       
  2507 				triggerClickHoverEvent("plothover", e,
       
  2508 									   function (s) {
       
  2509 										   return s["hoverable"] != false;
       
  2510 									   });
       
  2511 		}
       
  2512 
       
  2513 		function onMouseLeave(e) {
       
  2514 			if (options.grid.hoverable)
       
  2515 				triggerClickHoverEvent("plothover", e,
       
  2516 									   function (s) {
       
  2517 										   return false;
       
  2518 									   });
       
  2519 		}
       
  2520 
       
  2521 		function onClick(e) {
       
  2522 			triggerClickHoverEvent("plotclick", e,
       
  2523 								   function (s) {
       
  2524 									   return s["clickable"] != false;
       
  2525 								   });
       
  2526 		}
       
  2527 
       
  2528 		// trigger click or hover event (they send the same parameters
       
  2529 		// so we share their code)
       
  2530 		function triggerClickHoverEvent(eventname, event, seriesFilter) {
       
  2531 			var offset = eventHolder.offset(),
       
  2532 				canvasX = event.pageX - offset.left - plotOffset.left,
       
  2533 				canvasY = event.pageY - offset.top - plotOffset.top,
       
  2534 				pos = canvasToAxisCoords({ left: canvasX, top: canvasY });
       
  2535 
       
  2536 			pos.pageX = event.pageX;
       
  2537 			pos.pageY = event.pageY;
       
  2538 
       
  2539 			var item = findNearbyItem(canvasX, canvasY, seriesFilter);
       
  2540 
       
  2541 			if (item) {
       
  2542 				// fill in mouse pos for any listeners out there
       
  2543 				item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left);
       
  2544 				item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top);
       
  2545 			}
       
  2546 
       
  2547 			if (options.grid.autoHighlight) {
       
  2548 				// clear auto-highlights
       
  2549 				for (var i = 0; i < highlights.length; ++i) {
       
  2550 					var h = highlights[i];
       
  2551 					if (h.auto == eventname && !(item && h.series == item.series &&
       
  2552 						h.point[0] == item.datapoint[0] &&
       
  2553 						h.point[1] == item.datapoint[1]))
       
  2554 						unhighlight(h.series, h.point);
       
  2555 				}
       
  2556 
       
  2557 				if (item)
       
  2558 					highlight(item.series, item.datapoint, eventname);
       
  2559 			}
       
  2560 
       
  2561 			placeholder.trigger(eventname, [ pos, item ]);
       
  2562 		}
       
  2563 
       
  2564 		function triggerRedrawOverlay() {
       
  2565 			if (!redrawTimeout)
       
  2566 				redrawTimeout = setTimeout(drawOverlay, 30);
       
  2567 		}
       
  2568 
       
  2569 		function drawOverlay() {
       
  2570 			redrawTimeout = null;
       
  2571 
       
  2572 			// draw highlights
       
  2573 			octx.save();
       
  2574 			octx.clearRect(0, 0, canvasWidth, canvasHeight);
       
  2575 			octx.translate(plotOffset.left, plotOffset.top);
       
  2576 
       
  2577 			var i, hi;
       
  2578 			for (i = 0; i < highlights.length; ++i) {
       
  2579 				hi = highlights[i];
       
  2580 
       
  2581 				if (hi.series.bars.show)
       
  2582 					drawBarHighlight(hi.series, hi.point);
       
  2583 				else
       
  2584 					drawPointHighlight(hi.series, hi.point);
       
  2585 			}
       
  2586 			octx.restore();
       
  2587 
       
  2588 			executeHooks(hooks.drawOverlay, [octx]);
       
  2589 		}
       
  2590 
       
  2591 		function highlight(s, point, auto) {
       
  2592 			if (typeof s == "number")
       
  2593 				s = series[s];
       
  2594 
       
  2595 			if (typeof point == "number") {
       
  2596 				var ps = s.datapoints.pointsize;
       
  2597 				point = s.datapoints.points.slice(ps * point, ps * (point + 1));
       
  2598 			}
       
  2599 
       
  2600 			var i = indexOfHighlight(s, point);
       
  2601 			if (i == -1) {
       
  2602 				highlights.push({ series: s, point: point, auto: auto });
       
  2603 
       
  2604 				triggerRedrawOverlay();
       
  2605 			}
       
  2606 			else if (!auto)
       
  2607 				highlights[i].auto = false;
       
  2608 		}
       
  2609 
       
  2610 		function unhighlight(s, point) {
       
  2611 			if (s == null && point == null) {
       
  2612 				highlights = [];
       
  2613 				triggerRedrawOverlay();
       
  2614 			}
       
  2615 
       
  2616 			if (typeof s == "number")
       
  2617 				s = series[s];
       
  2618 
       
  2619 			if (typeof point == "number")
       
  2620 				point = s.data[point];
       
  2621 
       
  2622 			var i = indexOfHighlight(s, point);
       
  2623 			if (i != -1) {
       
  2624 				highlights.splice(i, 1);
       
  2625 
       
  2626 				triggerRedrawOverlay();
       
  2627 			}
       
  2628 		}
       
  2629 
       
  2630 		function indexOfHighlight(s, p) {
       
  2631 			for (var i = 0; i < highlights.length; ++i) {
       
  2632 				var h = highlights[i];
       
  2633 				if (h.series == s && h.point[0] == p[0]
       
  2634 					&& h.point[1] == p[1])
       
  2635 					return i;
       
  2636 			}
       
  2637 			return -1;
       
  2638 		}
       
  2639 
       
  2640 		function drawPointHighlight(series, point) {
       
  2641 			var x = point[0], y = point[1],
       
  2642 				axisx = series.xaxis, axisy = series.yaxis;
       
  2643 
       
  2644 			if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
       
  2645 				return;
       
  2646 
       
  2647 			var pointRadius = series.points.radius + series.points.lineWidth / 2;
       
  2648 			octx.lineWidth = pointRadius;
       
  2649 			octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
       
  2650 			var radius = 1.5 * pointRadius,
       
  2651 				x = axisx.p2c(x),
       
  2652 				y = axisy.p2c(y);
       
  2653 
       
  2654 			octx.beginPath();
       
  2655 			if (series.points.symbol == "circle")
       
  2656 				octx.arc(x, y, radius, 0, 2 * Math.PI, false);
       
  2657 			else
       
  2658 				series.points.symbol(octx, x, y, radius, false);
       
  2659 			octx.closePath();
       
  2660 			octx.stroke();
       
  2661 		}
       
  2662 
       
  2663 		function drawBarHighlight(series, point) {
       
  2664 			octx.lineWidth = series.bars.lineWidth;
       
  2665 			octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
       
  2666 			var fillStyle = $.color.parse(series.color).scale('a', 0.5).toString();
       
  2667 			var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth / 2;
       
  2668 			drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
       
  2669 					0, function () {
       
  2670 					return fillStyle;
       
  2671 				}, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);
       
  2672 		}
       
  2673 
       
  2674 		function getColorOrGradient(spec, bottom, top, defaultColor) {
       
  2675 			if (typeof spec == "string")
       
  2676 				return spec;
       
  2677 			else {
       
  2678 				// assume this is a gradient spec; IE currently only
       
  2679 				// supports a simple vertical gradient properly, so that's
       
  2680 				// what we support too
       
  2681 				var gradient = ctx.createLinearGradient(0, top, 0, bottom);
       
  2682 
       
  2683 				for (var i = 0, l = spec.colors.length; i < l; ++i) {
       
  2684 					var c = spec.colors[i];
       
  2685 					if (typeof c != "string") {
       
  2686 						var co = $.color.parse(defaultColor);
       
  2687 						if (c.brightness != null)
       
  2688 							co = co.scale('rgb', c.brightness)
       
  2689 						if (c.opacity != null)
       
  2690 							co.a *= c.opacity;
       
  2691 						c = co.toString();
       
  2692 					}
       
  2693 					gradient.addColorStop(i / (l - 1), c);
       
  2694 				}
       
  2695 
       
  2696 				return gradient;
       
  2697 			}
       
  2698 		}
       
  2699 	}
       
  2700 
       
  2701 	$.plot = function (placeholder, data, options) {
       
  2702 		//var t0 = new Date();
       
  2703 		var plot = new Plot($(placeholder), data, options, $.plot.plugins);
       
  2704 		//(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime()));
       
  2705 		return plot;
       
  2706 	};
       
  2707 
       
  2708 	$.plot.version = "0.7";
       
  2709 
       
  2710 	$.plot.plugins = [];
       
  2711 
       
  2712 	// returns a string with the date d formatted according to fmt
       
  2713 	$.plot.formatDate = function (d, fmt, monthNames) {
       
  2714 		var leftPad = function (n) {
       
  2715 			n = "" + n;
       
  2716 			return n.length == 1 ? "0" + n : n;
       
  2717 		};
       
  2718 
       
  2719 		var r = [];
       
  2720 		var escape = false, padNext = false;
       
  2721 		var hours = d.getUTCHours();
       
  2722 		var isAM = hours < 12;
       
  2723 		if (monthNames == null)
       
  2724 			monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
       
  2725 
       
  2726 		if (fmt.search(/%p|%P/) != -1) {
       
  2727 			if (hours > 12) {
       
  2728 				hours = hours - 12;
       
  2729 			} else if (hours == 0) {
       
  2730 				hours = 12;
       
  2731 			}
       
  2732 		}
       
  2733 		for (var i = 0; i < fmt.length; ++i) {
       
  2734 			var c = fmt.charAt(i);
       
  2735 
       
  2736 			if (escape) {
       
  2737 				switch (c) {
       
  2738 					case 'h':
       
  2739 						c = "" + hours;
       
  2740 						break;
       
  2741 					case 'H':
       
  2742 						c = leftPad(hours);
       
  2743 						break;
       
  2744 					case 'M':
       
  2745 						c = leftPad(d.getUTCMinutes());
       
  2746 						break;
       
  2747 					case 'S':
       
  2748 						c = leftPad(d.getUTCSeconds());
       
  2749 						break;
       
  2750 					case 'd':
       
  2751 						c = "" + d.getUTCDate();
       
  2752 						break;
       
  2753 					case 'm':
       
  2754 						c = "" + (d.getUTCMonth() + 1);
       
  2755 						break;
       
  2756 					case 'y':
       
  2757 						c = "" + d.getUTCFullYear();
       
  2758 						break;
       
  2759 					case 'b':
       
  2760 						c = "" + monthNames[d.getUTCMonth()];
       
  2761 						break;
       
  2762 					case 'p':
       
  2763 						c = (isAM) ? ("" + "am") : ("" + "pm");
       
  2764 						break;
       
  2765 					case 'P':
       
  2766 						c = (isAM) ? ("" + "AM") : ("" + "PM");
       
  2767 						break;
       
  2768 					case '0':
       
  2769 						c = "";
       
  2770 						padNext = true;
       
  2771 						break;
       
  2772 				}
       
  2773 				if (c && padNext) {
       
  2774 					c = leftPad(c);
       
  2775 					padNext = false;
       
  2776 				}
       
  2777 				r.push(c);
       
  2778 				if (!padNext)
       
  2779 					escape = false;
       
  2780 			}
       
  2781 			else {
       
  2782 				if (c == "%")
       
  2783 					escape = true;
       
  2784 				else
       
  2785 					r.push(c);
       
  2786 			}
       
  2787 		}
       
  2788 		return r.join("");
       
  2789 	};
       
  2790 
       
  2791 	// round to nearby lower multiple of base
       
  2792 	function floorInBase(n, base) {
       
  2793 		return base * Math.floor(n / base);
       
  2794 	}
       
  2795 
       
  2796 })(jQuery);