Added experimental export button to display view. Not ready for deployment (e.g....
authordeclerambaul <fabian.kaelin@gmail.com>
Fri, 27 Apr 2012 19:04:08 +0000 (15:04 -0400)
committerdeclerambaul <fabian.kaelin@gmail.com>
Fri, 27 Apr 2012 19:04:08 +0000 (15:04 -0400)
lib/graph/graph-display-view.co
lib/template/graph-display.jade
static/vendor/dygraph-extra.js [new file with mode: 0755]
www/modules.yaml

index 1a73a36..8a720c2 100644 (file)
@@ -35,7 +35,7 @@ GraphDisplayView = exports.GraphDisplayView = BaseView.extend do # {{{
     events:
         # Select the whole permalink URI text when it receives focus.
         'focus      .graph-permalink input'             : 'onPermalinkFocus'     
-        #     'click    .redraw-button'                  : 'stopAndRender'
+        'click      .export-button'                     : 'exportChart'
         #     'click    .load-button'                    : 'load'
 
     data  : {}
@@ -162,7 +162,8 @@ GraphDisplayView = exports.GraphDisplayView = BaseView.extend do # {{{
                     valueFormatter     : @numberFormatterHTML
         
         # console.log "#this.render!", dataset
-        _.dump options, 'options'
+        # _.dump options, 'options'
+
         
         # Always rerender the chart to sidestep the case where we need to push defaults into
         # dygraphs to reset the current option state.
@@ -182,6 +183,24 @@ GraphDisplayView = exports.GraphDisplayView = BaseView.extend do # {{{
         #     @chart.resize size
         
         this
+
+    
+    /**
+     * Exports graph as png
+     */
+    exportChart: (evt) ->
+        # The following code is dygraph specific, thus should not 
+        # be implemented in this class. Rather in a dygraph charttype
+        # related class. The same is true for the 'renderChart' method above.
+
+        # The Dygraph.Export module is from http://cavorite.com/labs/js/dygraphs-export/
+        # Todo: We don't use the tile of the chart, which is thus missing from the png
+        console.log "#this.export!"
+        img = @$el.find '.export-image'
+        Dygraph.Export.asPNG(@chart, img);
+        window.open img.src, "toDataURL() image"
+
+    
     
     update: ->
         locals = @toTemplateLocals()
@@ -270,6 +289,8 @@ GraphDisplayView = exports.GraphDisplayView = BaseView.extend do # {{{
         # unselect the text.
         _.defer( ~> @$el.find '.graph-permalink input' .select() )
 
+
+
     # Needed because (sigh) _.debounce returns undefined
     stopAndRender: ->
         @render ...
index 17ff9b1..15651e7 100644 (file)
@@ -1,7 +1,7 @@
 include browser-helpers
 - var graph_id = view.id
 section.graph-display.graph(id=view.id)
-    
+        
     .graph-name-row.page-header.row-fluid
         h2.graph-name 
             a(id="graph-title", href="#{model.toLink()}") #{name}
@@ -10,18 +10,20 @@ section.graph-display.graph(id=view.id)
         .viewport
         .graph-legend
     
-    .graph-details-row.row-fluid        
-        .span1
-        .graph-desc.span6
-            small
+    .graph-details-row.row
+        .span7.offset1.graph-desc
                 != jade.filters.markdown(desc)
 
-    .graph-details-row.row-fluid
-        .span1 
-        .span6
-            .graph-permalink 
+    .graph-details-row.row
+        .span6.offset1.graph-permalink 
                 input.span6(value="#{model.toPermalink()}", readonly="readonly")
-            
-            .graph-notes
-                != jade.filters.markdown(notes)
-            
+        .span1.a.export-button.btn(href="#")
+            i.icon-file
+            |  Export
+
+    .graph-details-row.row
+        .span6.offset1.graph-notes
+            != jade.filters.markdown(notes)        
+        
+
+
diff --git a/static/vendor/dygraph-extra.js b/static/vendor/dygraph-extra.js
new file mode 100755 (executable)
index 0000000..61fc84f
--- /dev/null
@@ -0,0 +1,303 @@
+/*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 = {
+
+    //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",
+
+    legendHeight: 20    // Height of the legend area
+};
+
+/**
+ * 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");
+
+    //Copy the plot canvas into the context of the new image.
+    var plotCanvas = dygraph.hidden_;
+
+    var i = 0;
+    
+    ctx.drawImage(plotCanvas, 0, 0);
+
+    // Add all the inner divs to the canvas as text objects
+    for (i = 0; i < dygraph.plotter_.ylabels.length; i++) {
+        Dygraph.Export.putLabel(ctx, dygraph.plotter_.ylabels[i], options,
+                options.labelFont, options.labelFontColor);
+    }
+
+    for (i = 0; i < dygraph.plotter_.xlabels.length; i++) {
+        Dygraph.Export.putLabel(ctx, dygraph.plotter_.xlabels[i], options, 
+                options.labelFont, options.labelFontColor);
+    }
+
+    // Title and axis labels
+    Dygraph.Export.putLabel(ctx, dygraph.plotter_.chartLabels.title, options, 
+            options.titleFont, options.titleColor);
+    
+    Dygraph.Export.putLabel(ctx, dygraph.plotter_.chartLabels.xlabel, options, 
+            options.axisLabelFont, options.axisLabelColor, true);
+    
+    Dygraph.Export.putVerticalLabelY1(ctx, dygraph.plotter_.chartLabels.ylabel,
+            options, options.axisLabelFont, options.axisLabelColor, "center");
+
+    Dygraph.Export.putVerticalLabelY2(ctx, dygraph.plotter_.chartLabels.y2label,
+            options, options.axisLabelFont, options.axisLabelColor, "center");
+            
+};
+
+/**
+ * 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) {
+        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 + 9;
+
+    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;
+
+    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;
+        }
+    }
+};
+
index 0fb21e4..7433d28 100644 (file)
@@ -37,6 +37,7 @@ dev:
         - jade.runtime.min
         - moment.mod.min
         - dygraph
+        - dygraph-extra
         - d3
 
 -   suffix: .mod.js