shop.balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

jquery.flot.canvas.js (9944B)


      1 /* Flot plugin for drawing all elements of a plot on the canvas.
      2 
      3 Copyright (c) 2007-2013 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);