Search code examples
javascriptsvggoogle-visualization

How can I get png(base64) with images inside of svg in Google Charts?


How can I get base64 with image inside of svg? Check this Fiddle that I got from another question. If you see the second graphic, its not generating the image that overlays the bar.

var chart = new google.visualization.ColumnChart(document.getElementById('chart_survey'));

$("[fill='#FFFFFF']").each(function( index, element ) {
    var svgimg = document.createElementNS('http://www.w3.org/2000/svg','image');
    svgimg.setAttributeNS(null,'x',element.x.baseVal.value);
    svgimg.setAttributeNS(null,'y',element.y.baseVal.value);
    svgimg.setAttributeNS(null,'width',element.width.baseVal.value);
    svgimg.setAttributeNS(null,'height',element.height.baseVal.value);
    svgimg.setAttributeNS(null,'preserveAspectRatio','none');
    svgimg.setAttributeNS('http://www.w3.org/1999/xlink','href', '${application.contextPath}/images/textura/patt.gif');
    $(element).parent().append(svgimg);
});

$('#test').val(chart.getImageURI())

Solution

  • In order to save this svg to a png, which keeps the linked <image> then you'll have to encode each <image>'s href to a dataURL first.

    Edit

    I rewrote the original snippets (which can still be found in the edit history).

    • I changed the <image> tags to <use>. This results in a much smaller memory usage.

    • I also added a fallback for IE11, which accepts to encode the external image to data URL, but still fails to convert svg to png via canvas. The fallback will replace the destination <img>tag, with the canvas. The later can be saved by user with a right click.

    • A few caveats :
      It doesn't work in Safari 7, and maybe in other outdated webkit browsers. That's a strange bug, since it does work like a charm in localhost, but won't on any other network (even on my home network, using 192.168.xxx).
      IE 9 & IE 10 will fail to convert the external images to data URL, CORS problem.

    // What to do with the result (either data URL or directly the canvas if tainted)
    var callback = function(d, isTainted) {
      if (!isTainted) {
        $('#chartImg')[0].src = d;
      } else
        $('#chartImg')[0].parentNode.replaceChild(d, $('#chartImg')[0]);
    };
    // The url of the external image (must be cross-origin compliant)
    var extURL = 'https://dl.dropboxusercontent.com/s/13dv8vzmrlcmla2/tex2.jpg';
    
    google.load('visualization', '1', {
      packages: ['corechart']
    })
    
    var encodeCall = getbase64URI.bind(this, extURL, callback);
    google.setOnLoadCallback(encodeCall);
    
    
    
    // Google Chart part
    function drawVisualizationDaily(imgUrl, callback, isTainted) {
    
      var data = google.visualization.arrayToDataTable([
        ['Daily', 'Sales'],
        ['Mon', 4],
        ['Tue', 6],
        ['Wed', 6],
        ['Thu', 5],
        ['Fri', 3],
        ['Sat', 7],
        ['Sun', 7]
      ]);
    
      var chart = new google.visualization.ColumnChart(document.getElementById('visualization'));
    
      chart.draw(data, {
        title: "Daily Sales",
        width: 500,
        height: 400,
        hAxis: {
          title: "Daily"
        }
      });
    
      // Link to chart's svg element
      var svgNode = chart.ea.querySelector('svg');
      // Create a symbol for our image
      var symbol = document.createElementNS('http://www.w3.org/2000/svg', 'symbol');
      // An svg wrapper to allow size changing with <use>
      symbol.setAttributeNS(null, 'viewBox', '0,0,10,10');
      symbol.setAttributeNS(null, 'preserveAspectRatio', 'none');
      symbol.id = 'background';
      // And the actual image, with our encoded image
      var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
      img.setAttributeNS(null, 'preserveAspectRatio', 'none');
      img.setAttributeNS(null, 'width', '100%');
      img.setAttributeNS(null, 'height', '100%');
      img.setAttributeNS('http://www.w3.org/1999/xlink', 'href', imgUrl);
    
      symbol.appendChild(img);
      svgNode.appendChild(symbol);
    
      var blueRects = $("[fill='#3366cc']");
      var max = blueRects.length - 1;
      blueRects.each(function(index, element) {
        var svgimg = document.createElementNS('http://www.w3.org/2000/svg', 'use');
        svgimg.setAttributeNS(null, 'x', element.x.baseVal.value);
        svgimg.setAttributeNS(null, 'y', element.y.baseVal.value);
        svgimg.setAttributeNS(null, 'width', element.width.baseVal.value);
        svgimg.setAttributeNS(null, 'height', element.height.baseVal.value);
        svgimg.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#background');
        svgNode.appendChild(svgimg);
    
        if (index === max && !isTainted) // no need to call it if we don't have our dataURL encoded images
        // a load event would be better but it doesn't fire in IE ...
          setTimeout(exportSVG.bind(this, svgNode, callback, isTainted), 200);
      });
    }
    
    function exportSVG(svgNode, callback, isTainted) {
    
      var svgData = (new XMLSerializer()).serializeToString(svgNode);
    
      var img = new Image();
      img.onload = function() {
        var canvas = document.createElement('canvas');
        canvas.width = svgNode.getAttribute('width');
        canvas.height = svgNode.getAttribute('height');
        canvas.getContext('2d').drawImage(this, 0, 0);
        var data, isTainted;
        try {
          data = canvas.toDataURL();
        } catch (e) {
          data = canvas;
          isTainted = true;
        }
        callback(data, isTainted);
      }
      img.src = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(svgData);
    }
    
    // A simple function to convert an images's url to base64 data URL
    function getbase64URI(url, callback) {
      var img = new Image();
      img.crossOrigin = "Anonymous";
      img.onload = function() {
        var c = document.createElement('canvas');
        c.width = this.width;
        c.height = this.height;
        c.getContext('2d').drawImage(this, 0, 0);
        var isTainted;
        try {
          c.toDataURL();
        } catch (e) {
          isTainted = true;
        }
        // if the canvas is tainted, return the url
        var output = (isTainted) ? url : c.toDataURL();
        drawVisualizationDaily(output, callback, isTainted);
      }
      img.src = url;
    }
    svg    { border: 1px solid yellow; }
    img    { border: 1px solid green;  }
    canvas { border: 1px solid red;    }
    <script src="http://www.google.com/jsapi?.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <div id="visualization"></div>
    Right-click this image to save it:
    <br>
    <img id="chartImg" />