src/pyams_skin/resources/js/ext/flot/jquery.flot.canvas.js
changeset 474 7bb070e90138
equal deleted inserted replaced
-1:000000000000 474:7bb070e90138
       
     1 /* Flot plugin for drawing all elements of a plot on the canvas.
       
     2 
       
     3 Copyright (c) 2007-2014 IOLA and Ole Laursen.
       
     4 Licensed under the MIT license.
       
     5 
       
     6 Flot normally produces certain elements, like axis labels and the legend, using
       
     7 HTML elements. This permits greater interactivity and customization, and often
       
     8 looks better, due to cross-browser canvas text inconsistencies and limitations.
       
     9 
       
    10 It can also be desirable to render the plot entirely in canvas, particularly
       
    11 if the goal is to save it as an image, or if Flot is being used in a context
       
    12 where the HTML DOM does not exist, as is the case within Node.js. This plugin
       
    13 switches out Flot's standard drawing operations for canvas-only replacements.
       
    14 
       
    15 Currently the plugin supports only axis labels, but it will eventually allow
       
    16 every element of the plot to be rendered directly to canvas.
       
    17 
       
    18 The plugin supports these options:
       
    19 
       
    20 {
       
    21     canvas: boolean
       
    22 }
       
    23 
       
    24 The "canvas" option controls whether full canvas drawing is enabled, making it
       
    25 possible to toggle on and off. This is useful when a plot uses HTML text in the
       
    26 browser, but needs to redraw with canvas text when exporting as an image.
       
    27 
       
    28 */
       
    29 
       
    30 (function($) {
       
    31 
       
    32 	var options = {
       
    33 		canvas: true
       
    34 	};
       
    35 
       
    36 	var render, getTextInfo, addText;
       
    37 
       
    38 	// Cache the prototype hasOwnProperty for faster access
       
    39 
       
    40 	var hasOwnProperty = Object.prototype.hasOwnProperty;
       
    41 
       
    42 	function init(plot, classes) {
       
    43 
       
    44 		var Canvas = classes.Canvas;
       
    45 
       
    46 		// We only want to replace the functions once; the second time around
       
    47 		// we would just get our new function back.  This whole replacing of
       
    48 		// prototype functions is a disaster, and needs to be changed ASAP.
       
    49 
       
    50 		if (render == null) {
       
    51 			getTextInfo = Canvas.prototype.getTextInfo,
       
    52 			addText = Canvas.prototype.addText,
       
    53 			render = Canvas.prototype.render;
       
    54 		}
       
    55 
       
    56 		// Finishes rendering the canvas, including overlaid text
       
    57 
       
    58 		Canvas.prototype.render = function() {
       
    59 
       
    60 			if (!plot.getOptions().canvas) {
       
    61 				return render.call(this);
       
    62 			}
       
    63 
       
    64 			var context = this.context,
       
    65 				cache = this._textCache;
       
    66 
       
    67 			// For each text layer, render elements marked as active
       
    68 
       
    69 			context.save();
       
    70 			context.textBaseline = "middle";
       
    71 
       
    72 			for (var layerKey in cache) {
       
    73 				if (hasOwnProperty.call(cache, layerKey)) {
       
    74 					var layerCache = cache[layerKey];
       
    75 					for (var styleKey in layerCache) {
       
    76 						if (hasOwnProperty.call(layerCache, styleKey)) {
       
    77 							var styleCache = layerCache[styleKey],
       
    78 								updateStyles = true;
       
    79 							for (var key in styleCache) {
       
    80 								if (hasOwnProperty.call(styleCache, key)) {
       
    81 
       
    82 									var info = styleCache[key],
       
    83 										positions = info.positions,
       
    84 										lines = info.lines;
       
    85 
       
    86 									// Since every element at this level of the cache have the
       
    87 									// same font and fill styles, we can just change them once
       
    88 									// using the values from the first element.
       
    89 
       
    90 									if (updateStyles) {
       
    91 										context.fillStyle = info.font.color;
       
    92 										context.font = info.font.definition;
       
    93 										updateStyles = false;
       
    94 									}
       
    95 
       
    96 									for (var i = 0, position; position = positions[i]; i++) {
       
    97 										if (position.active) {
       
    98 											for (var j = 0, line; line = position.lines[j]; j++) {
       
    99 												context.fillText(lines[j].text, line[0], line[1]);
       
   100 											}
       
   101 										} else {
       
   102 											positions.splice(i--, 1);
       
   103 										}
       
   104 									}
       
   105 
       
   106 									if (positions.length == 0) {
       
   107 										delete styleCache[key];
       
   108 									}
       
   109 								}
       
   110 							}
       
   111 						}
       
   112 					}
       
   113 				}
       
   114 			}
       
   115 
       
   116 			context.restore();
       
   117 		};
       
   118 
       
   119 		// Creates (if necessary) and returns a text info object.
       
   120 		//
       
   121 		// When the canvas option is set, the object looks like this:
       
   122 		//
       
   123 		// {
       
   124 		//     width: Width of the text's bounding box.
       
   125 		//     height: Height of the text's bounding box.
       
   126 		//     positions: Array of positions at which this text is drawn.
       
   127 		//     lines: [{
       
   128 		//         height: Height of this line.
       
   129 		//         widths: Width of this line.
       
   130 		//         text: Text on this line.
       
   131 		//     }],
       
   132 		//     font: {
       
   133 		//         definition: Canvas font property string.
       
   134 		//         color: Color of the text.
       
   135 		//     },
       
   136 		// }
       
   137 		//
       
   138 		// The positions array contains objects that look like this:
       
   139 		//
       
   140 		// {
       
   141 		//     active: Flag indicating whether the text should be visible.
       
   142 		//     lines: Array of [x, y] coordinates at which to draw the line.
       
   143 		//     x: X coordinate at which to draw the text.
       
   144 		//     y: Y coordinate at which to draw the text.
       
   145 		// }
       
   146 
       
   147 		Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) {
       
   148 
       
   149 			if (!plot.getOptions().canvas) {
       
   150 				return getTextInfo.call(this, layer, text, font, angle, width);
       
   151 			}
       
   152 
       
   153 			var textStyle, layerCache, styleCache, info;
       
   154 
       
   155 			// Cast the value to a string, in case we were given a number
       
   156 
       
   157 			text = "" + text;
       
   158 
       
   159 			// If the font is a font-spec object, generate a CSS definition
       
   160 
       
   161 			if (typeof font === "object") {
       
   162 				textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px " + font.family;
       
   163 			} else {
       
   164 				textStyle = font;
       
   165 			}
       
   166 
       
   167 			// Retrieve (or create) the cache for the text's layer and styles
       
   168 
       
   169 			layerCache = this._textCache[layer];
       
   170 
       
   171 			if (layerCache == null) {
       
   172 				layerCache = this._textCache[layer] = {};
       
   173 			}
       
   174 
       
   175 			styleCache = layerCache[textStyle];
       
   176 
       
   177 			if (styleCache == null) {
       
   178 				styleCache = layerCache[textStyle] = {};
       
   179 			}
       
   180 
       
   181 			info = styleCache[text];
       
   182 
       
   183 			if (info == null) {
       
   184 
       
   185 				var context = this.context;
       
   186 
       
   187 				// If the font was provided as CSS, create a div with those
       
   188 				// classes and examine it to generate a canvas font spec.
       
   189 
       
   190 				if (typeof font !== "object") {
       
   191 
       
   192 					var element = $("<div>&nbsp;</div>")
       
   193 						.css("position", "absolute")
       
   194 						.addClass(typeof font === "string" ? font : null)
       
   195 						.appendTo(this.getTextLayer(layer));
       
   196 
       
   197 					font = {
       
   198 						lineHeight: element.height(),
       
   199 						style: element.css("font-style"),
       
   200 						variant: element.css("font-variant"),
       
   201 						weight: element.css("font-weight"),
       
   202 						family: element.css("font-family"),
       
   203 						color: element.css("color")
       
   204 					};
       
   205 
       
   206 					// Setting line-height to 1, without units, sets it equal
       
   207 					// to the font-size, even if the font-size is abstract,
       
   208 					// like 'smaller'.  This enables us to read the real size
       
   209 					// via the element's height, working around browsers that
       
   210 					// return the literal 'smaller' value.
       
   211 
       
   212 					font.size = element.css("line-height", 1).height();
       
   213 
       
   214 					element.remove();
       
   215 				}
       
   216 
       
   217 				textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px " + font.family;
       
   218 
       
   219 				// Create a new info object, initializing the dimensions to
       
   220 				// zero so we can count them up line-by-line.
       
   221 
       
   222 				info = styleCache[text] = {
       
   223 					width: 0,
       
   224 					height: 0,
       
   225 					positions: [],
       
   226 					lines: [],
       
   227 					font: {
       
   228 						definition: textStyle,
       
   229 						color: font.color
       
   230 					}
       
   231 				};
       
   232 
       
   233 				context.save();
       
   234 				context.font = textStyle;
       
   235 
       
   236 				// Canvas can't handle multi-line strings; break on various
       
   237 				// newlines, including HTML brs, to build a list of lines.
       
   238 				// Note that we could split directly on regexps, but IE < 9 is
       
   239 				// broken; revisit when we drop IE 7/8 support.
       
   240 
       
   241 				var lines = (text + "").replace(/<br ?\/?>|\r\n|\r/g, "\n").split("\n");
       
   242 
       
   243 				for (var i = 0; i < lines.length; ++i) {
       
   244 
       
   245 					var lineText = lines[i],
       
   246 						measured = context.measureText(lineText);
       
   247 
       
   248 					info.width = Math.max(measured.width, info.width);
       
   249 					info.height += font.lineHeight;
       
   250 
       
   251 					info.lines.push({
       
   252 						text: lineText,
       
   253 						width: measured.width,
       
   254 						height: font.lineHeight
       
   255 					});
       
   256 				}
       
   257 
       
   258 				context.restore();
       
   259 			}
       
   260 
       
   261 			return info;
       
   262 		};
       
   263 
       
   264 		// Adds a text string to the canvas text overlay.
       
   265 
       
   266 		Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) {
       
   267 
       
   268 			if (!plot.getOptions().canvas) {
       
   269 				return addText.call(this, layer, x, y, text, font, angle, width, halign, valign);
       
   270 			}
       
   271 
       
   272 			var info = this.getTextInfo(layer, text, font, angle, width),
       
   273 				positions = info.positions,
       
   274 				lines = info.lines;
       
   275 
       
   276 			// Text is drawn with baseline 'middle', which we need to account
       
   277 			// for by adding half a line's height to the y position.
       
   278 
       
   279 			y += info.height / lines.length / 2;
       
   280 
       
   281 			// Tweak the initial y-position to match vertical alignment
       
   282 
       
   283 			if (valign == "middle") {
       
   284 				y = Math.round(y - info.height / 2);
       
   285 			} else if (valign == "bottom") {
       
   286 				y = Math.round(y - info.height);
       
   287 			} else {
       
   288 				y = Math.round(y);
       
   289 			}
       
   290 
       
   291 			// FIXME: LEGACY BROWSER FIX
       
   292 			// AFFECTS: Opera < 12.00
       
   293 
       
   294 			// Offset the y coordinate, since Opera is off pretty
       
   295 			// consistently compared to the other browsers.
       
   296 
       
   297 			if (!!(window.opera && window.opera.version().split(".")[0] < 12)) {
       
   298 				y -= 2;
       
   299 			}
       
   300 
       
   301 			// Determine whether this text already exists at this position.
       
   302 			// If so, mark it for inclusion in the next render pass.
       
   303 
       
   304 			for (var i = 0, position; position = positions[i]; i++) {
       
   305 				if (position.x == x && position.y == y) {
       
   306 					position.active = true;
       
   307 					return;
       
   308 				}
       
   309 			}
       
   310 
       
   311 			// If the text doesn't exist at this position, create a new entry
       
   312 
       
   313 			position = {
       
   314 				active: true,
       
   315 				lines: [],
       
   316 				x: x,
       
   317 				y: y
       
   318 			};
       
   319 
       
   320 			positions.push(position);
       
   321 
       
   322 			// Fill in the x & y positions of each line, adjusting them
       
   323 			// individually for horizontal alignment.
       
   324 
       
   325 			for (var i = 0, line; line = lines[i]; i++) {
       
   326 				if (halign == "center") {
       
   327 					position.lines.push([Math.round(x - line.width / 2), y]);
       
   328 				} else if (halign == "right") {
       
   329 					position.lines.push([Math.round(x - line.width), y]);
       
   330 				} else {
       
   331 					position.lines.push([Math.round(x), y]);
       
   332 				}
       
   333 				y += line.height;
       
   334 			}
       
   335 		};
       
   336 	}
       
   337 
       
   338 	$.plot.plugins.push({
       
   339 		init: init,
       
   340 		options: options,
       
   341 		name: "canvas",
       
   342 		version: "1.0"
       
   343 	});
       
   344 
       
   345 })(jQuery);