[tor-commits] [atlas/master] Implement graph saving functionality

art at torproject.org art at torproject.org
Wed Mar 14 20:42:23 UTC 2012


commit 95078384eb36799ffc2e6ab1eaa303407f088c05
Author: Arturo Filastò <hellais at gmail.com>
Date:   Wed Mar 14 13:43:01 2012 -0700

    Implement graph saving functionality
---
 js/libs/flot/canvas2image.js |  235 ++++++++++++++++++++++++++++++++++++++++++
 js/main.js                   |    1 +
 js/views/details/main.js     |    3 +
 templates/details/main.html  |    6 +
 4 files changed, 245 insertions(+), 0 deletions(-)

diff --git a/js/libs/flot/canvas2image.js b/js/libs/flot/canvas2image.js
new file mode 100644
index 0000000..bb6aecd
--- /dev/null
+++ b/js/libs/flot/canvas2image.js
@@ -0,0 +1,235 @@
+/*
+ * Canvas2Image v0.1
+ * Copyright (c) 2008 Jacob Seidelin, jseidelin at nihilogic.dk
+ * MIT License [http://www.opensource.org/licenses/mit-license.php]
+ */
+
+var Canvas2Image = (function() {
+
+    // check if we have canvas support
+    var bHasCanvas = false;
+    var oCanvas = document.createElement("canvas");
+    if (oCanvas.getContext("2d")) {
+        bHasCanvas = true;
+    }
+
+    // no canvas, bail out.
+    if (!bHasCanvas) {
+        return {
+            saveAsBMP : function(){},
+            saveAsPNG : function(){},
+            saveAsJPEG : function(){}
+        }
+    }
+
+    var bHasImageData = !!(oCanvas.getContext("2d").getImageData);
+    var bHasDataURL = !!(oCanvas.toDataURL);
+    var bHasBase64 = !!(window.btoa);
+
+    var strDownloadMime = "image/octet-stream";
+
+    // ok, we're good
+    var readCanvasData = function(oCanvas) {
+        var iWidth = parseInt(oCanvas.width);
+        var iHeight = parseInt(oCanvas.height);
+        return oCanvas.getContext("2d").getImageData(0,0,iWidth,iHeight);
+    }
+
+    // base64 encodes either a string or an array of charcodes
+    var encodeData = function(data) {
+        var strData = "";
+        if (typeof data == "string") {
+            strData = data;
+        } else {
+            var aData = data;
+            for (var i=0;i<aData.length;i++) {
+                strData += String.fromCharCode(aData[i]);
+            }
+        }
+        return btoa(strData);
+    }
+
+    // creates a base64 encoded string containing BMP data
+    // takes an imagedata object as argument
+    var createBMP = function(oData) {
+        var aHeader = [];
+
+        var iWidth = oData.width;
+        var iHeight = oData.height;
+
+        aHeader.push(0x42); // magic 1
+        aHeader.push(0x4D);
+
+        var iFileSize = iWidth*iHeight*3 + 54; // total header size = 54 bytes
+        aHeader.push(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256);
+        aHeader.push(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256);
+        aHeader.push(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256);
+        aHeader.push(iFileSize % 256);
+
+        aHeader.push(0); // reserved
+        aHeader.push(0);
+        aHeader.push(0); // reserved
+        aHeader.push(0);
+
+        aHeader.push(54); // dataoffset
+        aHeader.push(0);
+        aHeader.push(0);
+        aHeader.push(0);
+
+        var aInfoHeader = [];
+        aInfoHeader.push(40); // info header size
+        aInfoHeader.push(0);
+        aInfoHeader.push(0);
+        aInfoHeader.push(0);
+
+        var iImageWidth = iWidth;
+        aInfoHeader.push(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256);
+        aInfoHeader.push(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256);
+        aInfoHeader.push(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256);
+        aInfoHeader.push(iImageWidth % 256);
+
+        var iImageHeight = iHeight;
+        aInfoHeader.push(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256);
+        aInfoHeader.push(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256);
+        aInfoHeader.push(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256);
+        aInfoHeader.push(iImageHeight % 256);
+
+        aInfoHeader.push(1); // num of planes
+        aInfoHeader.push(0);
+
+        aInfoHeader.push(24); // num of bits per pixel
+        aInfoHeader.push(0);
+
+        aInfoHeader.push(0); // compression = none
+        aInfoHeader.push(0);
+        aInfoHeader.push(0);
+        aInfoHeader.push(0);
+
+        var iDataSize = iWidth*iHeight*3;
+        aInfoHeader.push(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256);
+        aInfoHeader.push(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256);
+        aInfoHeader.push(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256);
+        aInfoHeader.push(iDataSize % 256);
+
+        for (var i=0;i<16;i++) {
+            aInfoHeader.push(0);    // these bytes not used
+        }
+
+        var iPadding = (4 - ((iWidth * 3) % 4)) % 4;
+
+        var aImgData = oData.data;
+
+        var strPixelData = "";
+        var y = iHeight;
+        do {
+            var iOffsetY = iWidth*(y-1)*4;
+            var strPixelRow = "";
+            for (var x=0;x<iWidth;x++) {
+                var iOffsetX = 4*x;
+
+                strPixelRow += String.fromCharCode(aImgData[iOffsetY+iOffsetX+2]);
+                strPixelRow += String.fromCharCode(aImgData[iOffsetY+iOffsetX+1]);
+                strPixelRow += String.fromCharCode(aImgData[iOffsetY+iOffsetX]);
+            }
+            for (var c=0;c<iPadding;c++) {
+                strPixelRow += String.fromCharCode(0);
+            }
+            strPixelData += strPixelRow;
+        } while (--y);
+
+        var strEncoded = encodeData(aHeader.concat(aInfoHeader)) + encodeData(strPixelData);
+
+        return strEncoded;
+    }
+
+
+    // sends the generated file to the client
+    var saveFile = function(strData) {
+        document.location.href = strData;
+    }
+
+    var makeDataURI = function(strData, strMime) {
+        return "data:" + strMime + ";base64," + strData;
+    }
+
+    // generates a <img> object containing the imagedata
+    var makeImageObject = function(strSource) {
+        var oImgElement = document.createElement("img");
+        oImgElement.src = strSource;
+        return oImgElement;
+    }
+
+    var scaleCanvas = function(oCanvas, iWidth, iHeight) {
+        if (iWidth && iHeight) {
+            var oSaveCanvas = document.createElement("canvas");
+            oSaveCanvas.width = iWidth;
+            oSaveCanvas.height = iHeight;
+            oSaveCanvas.style.width = iWidth+"px";
+            oSaveCanvas.style.height = iHeight+"px";
+
+            var oSaveCtx = oSaveCanvas.getContext("2d");
+
+            oSaveCtx.drawImage(oCanvas, 0, 0, oCanvas.width, oCanvas.height, 0, 0, iWidth, iHeight);
+            return oSaveCanvas;
+        }
+        return oCanvas;
+    }
+
+    return {
+
+        saveAsPNG : function(oCanvas, bReturnImg, iWidth, iHeight) {
+            if (!bHasDataURL) {
+                return false;
+            }
+            var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight);
+            var strData = oScaledCanvas.toDataURL("image/png");
+            if (bReturnImg) {
+                return makeImageObject(strData);
+            } else {
+                return strData;
+            }
+            return true;
+        },
+
+        saveAsJPEG : function(oCanvas, bReturnImg, iWidth, iHeight) {
+            if (!bHasDataURL) {
+                return false;
+            }
+
+            var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight);
+            var strMime = "image/jpeg";
+            var strData = oScaledCanvas.toDataURL(strMime);
+
+            // check if browser actually supports jpeg by looking for the mime type in the data uri.
+            // if not, return false
+            if (strData.indexOf(strMime) != 5) {
+                return false;
+            }
+
+            if (bReturnImg) {
+                return makeImageObject(strData);
+            } else {
+                saveFile(strData.replace(strMime, strDownloadMime));
+            }
+            return true;
+        },
+
+        saveAsBMP : function(oCanvas, bReturnImg, iWidth, iHeight) {
+            if (!(bHasImageData && bHasBase64)) {
+                return false;
+            }
+
+            var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight);
+
+            var oData = readCanvasData(oScaledCanvas);
+            var strImgData = createBMP(oData);
+            if (bReturnImg) {
+                return makeImageObject(makeDataURI(strImgData, "image/bmp"));
+            } else {
+                saveFile(makeDataURI(strImgData, strDownloadMime));
+            }
+            return true;
+        }
+    };
+
+})();
diff --git a/js/main.js b/js/main.js
index 2de75cb..0a37d0d 100644
--- a/js/main.js
+++ b/js/main.js
@@ -15,6 +15,7 @@ require.config({
     popover: 'libs/bootstrap/bootstrap-popover',
     collapse: 'libs/bootstrap/bootstrap-collapse',
     flot: 'libs/flot/jquery.flot',
+    canvas2img: 'libs/flot/canvas2image',
     templates: '../templates'
   }
 
diff --git a/js/views/details/main.js b/js/views/details/main.js
index 0fb6501..36b81d4 100644
--- a/js/views/details/main.js
+++ b/js/views/details/main.js
@@ -10,6 +10,7 @@ define([
   'tooltip',
   'popover',
   'flot',
+  'canvas2img',
   'collapse',
   'helpers'
 ], function($, _, Backbone, relayModel, graphModel, mainDetailsTemplate){
@@ -57,6 +58,8 @@ define([
                                 xaxis: {mode: 'time', tickLength: 5},
                         });
 
+                        $("#save_"+g).attr('href', Canvas2Image.saveAsPNG($("#"+g+" > canvas.base")[0], false));
+
                         var previousItem = null;
                         $("#"+g).bind("plothover", function (event, pos, item){
                             if (item) {
diff --git a/templates/details/main.html b/templates/details/main.html
index ec8fbc3..52cd1cb 100644
--- a/templates/details/main.html
+++ b/templates/details/main.html
@@ -97,6 +97,7 @@
             </div>
             <div class="caption">
               <h5>3 Days graph</h5>
+              <a id="save_days" href="">Save Graph</a>
             </div>
           </div>
         </li>
@@ -108,6 +109,7 @@
             </div>
             <div class="caption">
               <h5>1 Week graph</h5>
+              <a id="save_week" href="">Save Graph</a>
             </div>
           </div>
         </li>
@@ -123,6 +125,7 @@
             </div>
             <div class="caption">
               <h5>1 Month graph</h5>
+              <a id="save_month" href="">Save Graph</a>
             </div>
           </div>
         </li>
@@ -134,6 +137,7 @@
             </div>
             <div class="caption">
               <h5>3 Months graph</h5>
+              <a id="save_months" href="">Save Graph</a>
             </div>
           </div>
         </li>
@@ -149,6 +153,7 @@
             </div>
             <div class="caption">
               <h5>1 Year graph</h5>
+              <a id="save_year" href="">Save Graph</a>
             </div>
           </div>
         </li>
@@ -160,6 +165,7 @@
 
            <div class="caption">
               <h5>5 Years graph</h5>
+              <a id="save_years" href="">Save Graph</a>
             </div>
           </div>
         </li>



More information about the tor-commits mailing list