Subversion Repositories Integrator Subversion

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 espaco 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);