Skip to content
Snippets Groups Projects
dygraph-extra.js 10.3 KiB
Newer Older
Laura Cappelli's avatar
Laura Cappelli committed
/*jslint vars: true, nomen: true, plusplus: true, maxerr: 500, indent: 4 */

/**
 * @license
 * Copyright 2011 Juan Manuel Caicedo Carvajal (juan@cavorite.com)
 * MIT-licensed (http://opensource.org/licenses/MIT)
 */

/**
 * @fileoverview This file contains additional features for dygraphs, which
 * are not required but can be useful for certain use cases. Examples include
 * exporting a dygraph as a PNG image.
 */

/**
 * Demo code for exporting a Dygraph object as an image.
 *
 * See: http://cavorite.com/labs/js/dygraphs-export/
 */

Dygraph.Export = {};

Dygraph.Export.DEFAULT_ATTRS = {

    backgroundColor: "transparent",

    //Texts displayed below the chart's x-axis and to the left of the y-axis 
    titleFont: "bold 18px serif",
    titleFontColor: "black",

    //Texts displayed below the chart's x-axis and to the left of the y-axis 
    axisLabelFont: "bold 14px serif",
    axisLabelFontColor: "black",

    // Texts for the axis ticks
    labelFont: "normal 12px serif",
    labelFontColor: "black",

    // Text for the chart legend
    legendFont: "bold 12px serif",
    legendFontColor: "black",

    // Default position for vertical labels
    vLabelLeft: 20,

    legendHeight: 20,    // Height of the legend area
    legendMargin: 20,
    lineHeight: 30,
    maxlabelsWidth: 0,
    labelTopMargin: 35,
    magicNumbertop: 8
    
};

/**
 * Tests whether the browser supports the canvas API and its methods for 
 * drawing text and exporting it as a data URL.
 */
Dygraph.Export.isSupported = function () {
    "use strict";
    try {
        var canvas = document.createElement("canvas");
        var context = canvas.getContext("2d");
        return (!!canvas.toDataURL && !!context.fillText);
    } catch (e) {
        // Silent exception.
    }
    return false;
};

/**
 * Exports a dygraph object as a PNG image.
 *
 *  dygraph: A Dygraph object
 *  img: An IMG DOM node
 *  userOptions: An object with the user specified options.
 *
 */
Dygraph.Export.asPNG = function (dygraph, img, userOptions) {
    "use strict";
    var canvas = Dygraph.Export.asCanvas(dygraph, userOptions);
    img.src = canvas.toDataURL();
};

/**
 * Exports a dygraph into a single canvas object.
 *
 * Returns a canvas object that can be exported as a PNG.
 *
 *  dygraph: A Dygraph object
 *  userOptions: An object with the user specified options.
 *
 */
Dygraph.Export.asCanvas = function (dygraph, userOptions) {
    "use strict";
    var options = {}, 
        canvas = Dygraph.createCanvas();
    
    Dygraph.update(options, Dygraph.Export.DEFAULT_ATTRS);
    Dygraph.update(options, userOptions);

    canvas.width = dygraph.width_;
    canvas.height = dygraph.height_ + options.legendHeight;

    Dygraph.Export.drawPlot(canvas, dygraph, options);    
    Dygraph.Export.drawLegend(canvas, dygraph, options);
    
    return canvas;
};

/**
 * Adds the plot and the axes to a canvas context.
 */
Dygraph.Export.drawPlot = function (canvas, dygraph, options) {
    "use strict";
    var ctx = canvas.getContext("2d");

    // Add user defined background
    ctx.fillStyle = options.backgroundColor;
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // Copy the plot canvas into the context of the new image.
    var plotCanvas = dygraph.hidden_;
    
    var i = 0;
    
    ctx.drawImage(plotCanvas, 0, 0);


    // Add the x and y axes
    var axesPluginDict = Dygraph.Export.getPlugin(dygraph, 'Axes Plugin');
    if (axesPluginDict) {
        var axesPlugin = axesPluginDict.plugin;
        
        for (i = 0; i < axesPlugin.ylabels_.length; i++) {
            Dygraph.Export.putLabel(ctx, axesPlugin.ylabels_[i], options,
                options.labelFont, options.labelFontColor);
        }
        
        for (i = 0; i < axesPlugin.xlabels_.length; i++) {
            Dygraph.Export.putLabel(ctx, axesPlugin.xlabels_[i], options,
                options.labelFont, options.labelFontColor);
        }
    }

    // Title and axis labels

    var labelsPluginDict = Dygraph.Export.getPlugin(dygraph, 'ChartLabels Plugin');
    if (labelsPluginDict) {
        var labelsPlugin = labelsPluginDict.plugin;

        Dygraph.Export.putLabel(ctx, labelsPlugin.title_div_, options, 
            options.titleFont, options.titleFontColor);

        Dygraph.Export.putLabel(ctx, labelsPlugin.xlabel_div_, options, 
            options.axisLabelFont, options.axisLabelFontColor);

        Dygraph.Export.putVerticalLabelY1(ctx, labelsPlugin.ylabel_div_, options, 
            options.axisLabelFont, options.axisLabelFontColor, "center");

        Dygraph.Export.putVerticalLabelY2(ctx, labelsPlugin.y2label_div_, options, 
            options.axisLabelFont, options.axisLabelFontColor, "center");
    }


    for (i = 0; i < dygraph.layout_.annotations.length; i++) {
        Dygraph.Export.putLabelAnn(ctx, dygraph.layout_.annotations[i], options, 
                options.labelFont, options.labelColor);
    }
    
};

/**
 * Draws a label (axis label or graph title) at the same position 
 * where the div containing the text is located.
 */
Dygraph.Export.putLabel = function (ctx, divLabel, options, font, color) {
    "use strict";

    if (!divLabel || !divLabel.style) {
        return;
    }

    var top = parseInt(divLabel.style.top, 10);
    var left = parseInt(divLabel.style.left, 10);
    
    if (!divLabel.style.top.length) {
        var bottom = parseInt(divLabel.style.bottom, 10);
        var height = parseInt(divLabel.style.height, 10);

        top = ctx.canvas.height - options.legendHeight - bottom - height;
    }

    // FIXME: Remove this 'magic' number needed to get the line-height. 
    top = top + options.magicNumbertop;

    var width = parseInt(divLabel.style.width, 10);

    switch (divLabel.style.textAlign) {
    case "center":
        left = left + Math.ceil(width / 2);
        break;
    case "right":
        left = left + width;
        break;
    }

    Dygraph.Export.putText(ctx, left, top, divLabel, font, color);
};
 
/**
 * Draws a label Y1 rotated 90 degrees counterclockwise.
 */
Dygraph.Export.putVerticalLabelY1 = function (ctx, divLabel, options, font, color, textAlign) {
    "use strict";
    if (!divLabel) {
        return;
    }

    var top = parseInt(divLabel.style.top, 10);
    var left = parseInt(divLabel.style.left, 10) + parseInt(divLabel.style.width, 10) / 2;
    var text = divLabel.innerText || divLabel.textContent;


    // FIXME: The value of the 'left' property is frequently 0, used the option.
    if (!left)
        left = options.vLabelLeft;

    if (textAlign == "center") {
        var textDim = ctx.measureText(text);
        top = Math.ceil((ctx.canvas.height - textDim.width) / 2 + textDim.width);
    }

    ctx.save();
    ctx.translate(0, ctx.canvas.height);
    ctx.rotate(-Math.PI / 2);

    ctx.fillStyle = color;
    ctx.font = font;
    ctx.textAlign = textAlign;
    ctx.fillText(text, top, left);
    
    ctx.restore();
};

/**
 * Draws a label Y2 rotated 90 degrees clockwise.
 */
Dygraph.Export.putVerticalLabelY2 = function (ctx, divLabel, options, font, color, textAlign) {
    "use strict";
    if (!divLabel) {
        return;
    }
        
    var top = parseInt(divLabel.style.top, 10);
    var right = parseInt(divLabel.style.right, 10) + parseInt(divLabel.style.width, 10) * 2;
    var text = divLabel.innerText || divLabel.textContent;

    if (textAlign == "center") {
        top = Math.ceil(ctx.canvas.height / 2);
    }
    
    ctx.save();
    ctx.translate(parseInt(divLabel.style.width, 10), 0);
    ctx.rotate(Math.PI / 2);

    ctx.fillStyle = color;
    ctx.font = font;
    ctx.textAlign = textAlign;
    ctx.fillText(text, top, right - ctx.canvas.width);
    
    ctx.restore();
};

/**
 * Draws the text contained in 'divLabel' at the specified position.
 */
Dygraph.Export.putText = function (ctx, left, top, divLabel, font, color) {
    "use strict";
    var textAlign = divLabel.style.textAlign || "left";    
    var text = divLabel.innerText || divLabel.textContent;

    ctx.fillStyle = color;
    ctx.font = font;
    ctx.textAlign = textAlign;
    ctx.textBaseline = "middle";
    ctx.fillText(text, left, top);
};

/**
 * Draws the legend of a dygraph
 *
 */
Dygraph.Export.drawLegend = function (canvas, dygraph, options) {
    "use strict";
    var ctx = canvas.getContext("2d");

    // Margin from the plot
    var labelTopMargin = 10;

    // Margin between labels
    var labelMargin = 5;
    
    var colors = dygraph.getColors();
    // Drop the first element, which is the label for the time dimension
    var labels = dygraph.attr_("labels").slice(1);
    
    // 1. Compute the width of the labels:
    var labelsWidth = 0;
    
    var i;
    for (i = 0; i < labels.length; i++) {
        labelsWidth = labelsWidth + ctx.measureText("- " + labels[i]).width + labelMargin;
    }

    var labelsX = Math.floor((canvas.width - labelsWidth) / 2);
    var labelsY = canvas.height - options.legendHeight + labelTopMargin;


    var labelVisibility=dygraph.attr_("visibility");

    ctx.font = options.legendFont;
    ctx.textAlign = "left";
    ctx.textBaseline = "middle";

    var usedColorCount=0;
    for (i = 0; i < labels.length; i++) {
        if (labelVisibility[i]) {
            //TODO Replace the minus sign by a proper dash, although there is a
            //     problem when the page encoding is different than the encoding 
            //     of this file (UTF-8).
            var txt = "- " + labels[i];
            ctx.fillStyle = colors[usedColorCount];
            usedColorCount++
            ctx.fillText(txt, labelsX, labelsY);
            labelsX = labelsX + ctx.measureText(txt).width + labelMargin;
        }
    }
};

/**
 * Finds a plugin by the value returned by its toString method..
 *
 * Returns the the dictionary corresponding to the plugin for the argument.
 * If the plugin is not found, it returns null.
 */
Dygraph.Export.getPlugin = function(dygraph, name) {
    for (i = 0; i < dygraph.plugins_.length; i++) {
        if (dygraph.plugins_[i].plugin.toString() == name) {
            return dygraph.plugins_[i];
        }
    }
    return null;
}