diff options
Diffstat (limited to 'misc/flot/jquery.flot.canvas.js')
-rw-r--r-- | misc/flot/jquery.flot.canvas.js | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/misc/flot/jquery.flot.canvas.js b/misc/flot/jquery.flot.canvas.js new file mode 100644 index 0000000..29328d5 --- /dev/null +++ b/misc/flot/jquery.flot.canvas.js @@ -0,0 +1,345 @@ +/* Flot plugin for drawing all elements of a plot on the canvas. + +Copyright (c) 2007-2014 IOLA and Ole Laursen. +Licensed under the MIT license. + +Flot normally produces certain elements, like axis labels and the legend, using +HTML elements. This permits greater interactivity and customization, and often +looks better, due to cross-browser canvas text inconsistencies and limitations. + +It can also be desirable to render the plot entirely in canvas, particularly +if the goal is to save it as an image, or if Flot is being used in a context +where the HTML DOM does not exist, as is the case within Node.js. This plugin +switches out Flot's standard drawing operations for canvas-only replacements. + +Currently the plugin supports only axis labels, but it will eventually allow +every element of the plot to be rendered directly to canvas. + +The plugin supports these options: + +{ + canvas: boolean +} + +The "canvas" option controls whether full canvas drawing is enabled, making it +possible to toggle on and off. This is useful when a plot uses HTML text in the +browser, but needs to redraw with canvas text when exporting as an image. + +*/ + +(function($) { + + var options = { + canvas: true + }; + + var render, getTextInfo, addText; + + // Cache the prototype hasOwnProperty for faster access + + var hasOwnProperty = Object.prototype.hasOwnProperty; + + function init(plot, classes) { + + var Canvas = classes.Canvas; + + // We only want to replace the functions once; the second time around + // we would just get our new function back. This whole replacing of + // prototype functions is a disaster, and needs to be changed ASAP. + + if (render == null) { + getTextInfo = Canvas.prototype.getTextInfo, + addText = Canvas.prototype.addText, + render = Canvas.prototype.render; + } + + // Finishes rendering the canvas, including overlaid text + + Canvas.prototype.render = function() { + + if (!plot.getOptions().canvas) { + return render.call(this); + } + + var context = this.context, + cache = this._textCache; + + // For each text layer, render elements marked as active + + context.save(); + context.textBaseline = "middle"; + + for (var layerKey in cache) { + if (hasOwnProperty.call(cache, layerKey)) { + var layerCache = cache[layerKey]; + for (var styleKey in layerCache) { + if (hasOwnProperty.call(layerCache, styleKey)) { + var styleCache = layerCache[styleKey], + updateStyles = true; + for (var key in styleCache) { + if (hasOwnProperty.call(styleCache, key)) { + + var info = styleCache[key], + positions = info.positions, + lines = info.lines; + + // Since every element at this level of the cache have the + // same font and fill styles, we can just change them once + // using the values from the first element. + + if (updateStyles) { + context.fillStyle = info.font.color; + context.font = info.font.definition; + updateStyles = false; + } + + for (var i = 0, position; position = positions[i]; i++) { + if (position.active) { + for (var j = 0, line; line = position.lines[j]; j++) { + context.fillText(lines[j].text, line[0], line[1]); + } + } else { + positions.splice(i--, 1); + } + } + + if (positions.length == 0) { + delete styleCache[key]; + } + } + } + } + } + } + } + + context.restore(); + }; + + // Creates (if necessary) and returns a text info object. + // + // When the canvas option is set, the object looks like this: + // + // { + // width: Width of the text's bounding box. + // height: Height of the text's bounding box. + // positions: Array of positions at which this text is drawn. + // lines: [{ + // height: Height of this line. + // widths: Width of this line. + // text: Text on this line. + // }], + // font: { + // definition: Canvas font property string. + // color: Color of the text. + // }, + // } + // + // The positions array contains objects that look like this: + // + // { + // active: Flag indicating whether the text should be visible. + // lines: Array of [x, y] coordinates at which to draw the line. + // x: X coordinate at which to draw the text. + // y: Y coordinate at which to draw the text. + // } + + Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) { + + if (!plot.getOptions().canvas) { + return getTextInfo.call(this, layer, text, font, angle, width); + } + + var textStyle, layerCache, styleCache, info; + + // Cast the value to a string, in case we were given a number + + text = "" + text; + + // If the font is a font-spec object, generate a CSS definition + + if (typeof font === "object") { + textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px " + font.family; + } else { + textStyle = font; + } + + // Retrieve (or create) the cache for the text's layer and styles + + layerCache = this._textCache[layer]; + + if (layerCache == null) { + layerCache = this._textCache[layer] = {}; + } + + styleCache = layerCache[textStyle]; + + if (styleCache == null) { + styleCache = layerCache[textStyle] = {}; + } + + info = styleCache[text]; + + if (info == null) { + + var context = this.context; + + // If the font was provided as CSS, create a div with those + // classes and examine it to generate a canvas font spec. + + if (typeof font !== "object") { + + var element = $("<div> </div>") + .css("position", "absolute") + .addClass(typeof font === "string" ? font : null) + .appendTo(this.getTextLayer(layer)); + + font = { + lineHeight: element.height(), + style: element.css("font-style"), + variant: element.css("font-variant"), + weight: element.css("font-weight"), + family: element.css("font-family"), + color: element.css("color") + }; + + // Setting line-height to 1, without units, sets it equal + // to the font-size, even if the font-size is abstract, + // like 'smaller'. This enables us to read the real size + // via the element's height, working around browsers that + // return the literal 'smaller' value. + + font.size = element.css("line-height", 1).height(); + + element.remove(); + } + + textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px " + font.family; + + // Create a new info object, initializing the dimensions to + // zero so we can count them up line-by-line. + + info = styleCache[text] = { + width: 0, + height: 0, + positions: [], + lines: [], + font: { + definition: textStyle, + color: font.color + } + }; + + context.save(); + context.font = textStyle; + + // Canvas can't handle multi-line strings; break on various + // newlines, including HTML brs, to build a list of lines. + // Note that we could split directly on regexps, but IE < 9 is + // broken; revisit when we drop IE 7/8 support. + + var lines = (text + "").replace(/<br ?\/?>|\r\n|\r/g, "\n").split("\n"); + + for (var i = 0; i < lines.length; ++i) { + + var lineText = lines[i], + measured = context.measureText(lineText); + + info.width = Math.max(measured.width, info.width); + info.height += font.lineHeight; + + info.lines.push({ + text: lineText, + width: measured.width, + height: font.lineHeight + }); + } + + context.restore(); + } + + return info; + }; + + // Adds a text string to the canvas text overlay. + + Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) { + + if (!plot.getOptions().canvas) { + return addText.call(this, layer, x, y, text, font, angle, width, halign, valign); + } + + var info = this.getTextInfo(layer, text, font, angle, width), + positions = info.positions, + lines = info.lines; + + // Text is drawn with baseline 'middle', which we need to account + // for by adding half a line's height to the y position. + + y += info.height / lines.length / 2; + + // Tweak the initial y-position to match vertical alignment + + if (valign == "middle") { + y = Math.round(y - info.height / 2); + } else if (valign == "bottom") { + y = Math.round(y - info.height); + } else { + y = Math.round(y); + } + + // FIXME: LEGACY BROWSER FIX + // AFFECTS: Opera < 12.00 + + // Offset the y coordinate, since Opera is off pretty + // consistently compared to the other browsers. + + if (!!(window.opera && window.opera.version().split(".")[0] < 12)) { + y -= 2; + } + + // Determine whether this text already exists at this position. + // If so, mark it for inclusion in the next render pass. + + for (var i = 0, position; position = positions[i]; i++) { + if (position.x == x && position.y == y) { + position.active = true; + return; + } + } + + // If the text doesn't exist at this position, create a new entry + + position = { + active: true, + lines: [], + x: x, + y: y + }; + + positions.push(position); + + // Fill in the x & y positions of each line, adjusting them + // individually for horizontal alignment. + + for (var i = 0, line; line = lines[i]; i++) { + if (halign == "center") { + position.lines.push([Math.round(x - line.width / 2), y]); + } else if (halign == "right") { + position.lines.push([Math.round(x - line.width), y]); + } else { + position.lines.push([Math.round(x), y]); + } + y += line.height; + } + }; + } + + $.plot.plugins.push({ + init: init, + options: options, + name: "canvas", + version: "1.0" + }); + +})(jQuery); |