/* 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); 
 |