0

I'm quite new to D3 (so my question might be a bit noobish) and working on a visualisation which I want to export to png. I'm doing so following: http://bl.ocks.org/Rokotyan/0556f8facbaf344507cdc45dc3622177

I had some problems with the styling etc but the in-line styling solved this.

The problem I encounter now is that the png image I'm using in my SVG, does not appear in the canvas which I export to the png.

I've been experimenting quite a bit, without the desired outcome.

The code below puts the png's onto the SVG:

svg.selectAll("svg")
     .data(inputdata)
     .enter()
     .append("svg:image")
     .attr("xlink:href", "image_1.png")
     .attr("x", function(d) {
        return (d.X_Coordinate);})
     .attr("y", function(d) {
        return (d.Y_Coordinate);})
    .attr("width", image_width)
    .attr("height", image_height)
     ;

d3.selectAll("svg").attr({'xmlns': 'http://www.w3.org/2000/svg','xmlns:xmlns:xlink': 'http://www.w3.org/1999/xlink'});
Joppe
  • 1
  • Maybe I wasnt quite clear. I used the example as in the link, in which I put my code for the svg. Apart from the images i want to place, I also placed other things like textboxes etc. Most of the elements are included in the export except for the included png's. These are defined by the above – Joppe Nov 19 '17 at 15:27

1 Answers1

0

Your would need to encode the images as base64. So, borrowing some code from this excellent answer and putting it together with your linked example:

<!DOCTYPE html>
<html lang="en">

<head>
  <title>How to properly export SVG D3 visualization to PNG or JPEG</title>
  <meta charset="utf-8">
  <script src="https://cdn.rawgit.com/eligrey/canvas-toBlob.js/f1a01896135ab378aa5c0118eadd81da55e698d8/canvas-toBlob.js"></script>
  <script src="https://cdn.rawgit.com/eligrey/FileSaver.js/e9d941381475b5df8b7d7691013401e171014e89/FileSaver.min.js"></script>
  <script src="https://d3js.org/d3.v3.min.js"></script>
  <style type="text/css">
    .blendCircle {
      mix-blend-mode: multiply;
    }
  </style>
</head>

<body>
  <div>
    <button id='saveButton'>Export my D3 visualization to PNG</button>
  </div>
  <script type="text/javascript">
    var width = 200,
      height = 200;

    var svg = d3.select('body').append('svg')
      .attr('width', width)
      .attr('height', height);

    svg
      .append("svg:image")
      .each(function() {
        toDataURL(
          'https://i.imgur.com/1kqZEq2.jpg',
          (dataUrl) => {
            d3.select(this).attr("xlink:href", dataUrl);
          }
        )
      })
      .attr("width", width)
      .attr("height", height);

    function toDataURL(src, callback, outputFormat) {
      var img = new Image();
      img.crossOrigin = 'Anonymous';
      img.onload = function() {
        var canvas = document.createElement('CANVAS');
        var ctx = canvas.getContext('2d');
        var dataURL;
        canvas.height = this.naturalHeight;
        canvas.width = this.naturalWidth;
        ctx.drawImage(this, 0, 0);
        dataURL = canvas.toDataURL(outputFormat);
        callback(dataURL);
      };
      img.src = src;
      if (img.complete || img.complete === undefined) {
        img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
        img.src = src;
      }
    }

    // Set-up the export button
    d3.select('#saveButton').on('click', function() {
      var svgString = getSVGString(svg.node());
      svgString2Image(svgString, 2 * width, 2 * height, 'png', save); // passes Blob and filesize String to the callback

      function save(dataBlob, filesize) {
        saveAs(dataBlob, 'D3 vis exported to PNG.png'); // FileSaver.js function
      }
    });

    // Below are the functions that handle actual exporting:
    // getSVGString ( svgNode ) and svgString2Image( svgString, width, height, format, callback )
    function getSVGString(svgNode) {
      svgNode.setAttribute('xlink', 'http://www.w3.org/1999/xlink');
      var cssStyleText = getCSSStyles(svgNode);
      appendCSS(cssStyleText, svgNode);

      var serializer = new XMLSerializer();
      var svgString = serializer.serializeToString(svgNode);
      svgString = svgString.replace(/(\w+)?:?xlink=/g, 'xmlns:xlink='); // Fix root xlink without namespace
      svgString = svgString.replace(/NS\d+:href/g, 'xlink:href'); // Safari NS namespace fix

      return svgString;

      function getCSSStyles(parentElement) {
        var selectorTextArr = [];

        // Add Parent element Id and Classes to the list
        selectorTextArr.push('#' + parentElement.id);
        for (var c = 0; c < parentElement.classList.length; c++)
          if (!contains('.' + parentElement.classList[c], selectorTextArr))
            selectorTextArr.push('.' + parentElement.classList[c]);

        // Add Children element Ids and Classes to the list
        var nodes = parentElement.getElementsByTagName("*");
        for (var i = 0; i < nodes.length; i++) {
          var id = nodes[i].id;
          if (!contains('#' + id, selectorTextArr))
            selectorTextArr.push('#' + id);

          var classes = nodes[i].classList;
          for (var c = 0; c < classes.length; c++)
            if (!contains('.' + classes[c], selectorTextArr))
              selectorTextArr.push('.' + classes[c]);
        }

        // Extract CSS Rules
        var extractedCSSText = "";
        for (var i = 0; i < document.styleSheets.length; i++) {
          var s = document.styleSheets[i];

          try {
            if (!s.cssRules) continue;
          } catch (e) {
            if (e.name !== 'SecurityError') throw e; // for Firefox
            continue;
          }

          var cssRules = s.cssRules;
          for (var r = 0; r < cssRules.length; r++) {
            if (contains(cssRules[r].selectorText, selectorTextArr))
              extractedCSSText += cssRules[r].cssText;
          }
        }


        return extractedCSSText;

        function contains(str, arr) {
          return arr.indexOf(str) === -1 ? false : true;
        }

      }

      function appendCSS(cssText, element) {
        var styleElement = document.createElement("style");
        styleElement.setAttribute("type", "text/css");
        styleElement.innerHTML = cssText;
        var refNode = element.hasChildNodes() ? element.children[0] : null;
        element.insertBefore(styleElement, refNode);
      }
    }


    function svgString2Image(svgString, width, height, format, callback) {
      var format = format ? format : 'png';

      var imgsrc = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgString))); // Convert SVG string to data URL

      var canvas = document.createElement("canvas");
      var context = canvas.getContext("2d");

      canvas.width = width;
      canvas.height = height;

      var image = new Image();
      image.onload = function() {
        context.clearRect(0, 0, width, height);
        context.drawImage(image, 0, 0, width, height);

        canvas.toBlob(function(blob) {
          var filesize = Math.round(blob.length / 1024) + ' KB';
          if (callback) callback(blob, filesize);
        });


      };

      image.src = imgsrc;
    }
  </script>
</body>

</html>
Mark
  • 106,305
  • 20
  • 172
  • 230