Subversion-Projekte lars-tiefland.ci

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
776 lars 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);