Search code examples
javascriptcsvsvgjszip

How to export a PNG image generated from an SVG element and a csv file into a downloadable ZIP file


I am exporting an SVG to a PNG image.Now i want to download this PNG image and a CSV file into a ZIP folder.I am using JSZIP for the purpose of zipping two files into a ZIP folder.Now when i click on the download button the ZIP downloads.The downloaded ZIP contains the CSV file as desired but consists a blank image. How to get the required image downloaded in the required ZIP folder?

My current code is:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <link href="../build/nv.d3.css" rel="stylesheet" type="text/css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.2/d3.min.js" charset="utf-8"></script>
    <script src="../build/nv.d3.js"></script>
    <script type="text/javascript" src="FileSaver.js"></script>  
    <script type="text/javascript" src="jszip.js"></script>

    <style>
        text {
            font: 12px sans-serif;
        }
        svg {
            display: block;
        }
        html, body, #chart1, svg {
            margin: 0px;
            padding: 0px;
            height: 100%;
            width: 100%;
        }

        .dashed {
            stroke-dasharray: 5,5;
        }
    </style>
     <script type="text/javascript">

    function download()
    {


img = new Image(),
        serializer = new XMLSerializer(),
        svgStr = serializer.serializeToString(document.getElementById('svg'));

    img.src = 'data:image/svg+xml;base64,'+window.btoa(svgStr);

    // You could also use the actual string without base64 encoding it:
    //img.src = "data:image/svg+xml;utf8," + svgStr;

    var canvas = document.createElement("canvas");

    var w=3000;
    var h=3000;

    canvas.width = w;
    canvas.height = h;
    canvas.getContext("2d").drawImage(img,0,0,w,h);

    var imgURL = canvas.toDataURL("image/png");


var CSV="ab";

var zip = new JSZip();

 zip.file("abc.csv", CSV);
 zip.file("img.png", imgURL);

content = zip.generate();
    location.href="data:application/zip;base64," + content;
    }

    </script>
</head>
<body >

<div id="chart1" width="100%" height='100%'></div>
<button onclick="download()">Download</button>

<script>


var data = [{"color":"#a215af","key":"products","values":[
    {"y":0,"x":0},
    {"y":0,"x":1},
    {"y":1,"x":2},
    {"y":6,"x":3},
    {"y":2,"x":4},
    {"y":0,"x":5},
    {"y":13,"x":6}]}] 

nv.addGraph(function() {
        chart = nv.models.lineChart()
            .options({
                transitionDuration: 300,
                useInteractiveGuideline: true
            })
        ;;

  var days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];

  chart.xAxis
    .rotateLabels(-45)
    .tickValues([0, 1, 2, 3, 4, 5, 6])
    .tickFormat(function(d){
      return days[d]
    });


 chart.yAxis
            .axisLabel('Voltage (v)')
            .tickFormat(function(d) {
                if (d == null) {
                    return 'N/A';
                }
                return d3.format(',.2f')(d);
            });


 d3.select('#chart1').append('svg')
            .datum(data)
            .attr("id","svg")
            .attr("height","1000")
            .attr("width","1000")
            .call(chart);

  nv.utils.windowResize(chart.update);

  return chart;
});



</script>
</body>
</html>

Solution

  • In your case, you tell JSZip that the content of the image is the string data:image/png;base64,iVBORw0K.... The easiest solution is to remove the data:image/png;base64, part and use base64: true:

    zip.file("img.png", imgURL.slice("data:image/png;base64,".length), {base64:true});
    

    But creating/parsing base64 strings is not efficient (I also get a 3000x3000 grey image with your code but that's unrelated).

    You can also use Blob to avoid base64 manipulation (and use JSZip v3 to support it): canvas.toBlob to get a png file as a blob, URL.createObjectURL to generate the url of the intermediate image.

    Example of this solution (I couldn't get a perfect image but your issue, putting an image in a zip file, is fixed):

    var canvas = document.createElement("canvas");
    canvas.width = 1000;
    canvas.height = 1000;
    var ctx = canvas.getContext("2d");
    
    var serializer = new XMLSerializer(),
        svgString = serializer.serializeToString(document.getElementById('svg'));
    
    var svg = new Blob([svgString], {type: "image/svg+xml;charset=utf-8"});
    var url = URL.createObjectURL(svg); // <-- create a blob url for the svg image
    var img = new Image();
    img.onload = function() {
      // draw the svg to a canvas
      ctx.drawImage(img, 0, 0);
      URL.revokeObjectURL(url);
      canvas.toBlob(function (blob) { // <-- convert the canvas to a png (in a blob)
    
        var zip = new JSZip();
        zip.file("abc.csv", CSV);
        zip.file("img.png", blob); // <-- JSZip v3 accepts blob
    
        content = zip.generateAsync({type:"blob"}).then(function (blob) {
          saveAs(blob, "result.zip"); // <-- trigger the download
        }, function (e) {
          console.error(e)
        });
      }, "image/png");
    };
    img.src = url; // <-- load the blob url