[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [atlas/master] Implement graph saving functionality
commit 95078384eb36799ffc2e6ab1eaa303407f088c05
Author: Arturo Filastò <hellais@xxxxxxxxx>
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@xxxxxxxxxxxx
+ * 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>
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits