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

4

3

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())
Hawsepipe answered 30/4, 2015 at 18:30 Comment(8)
fc03.deviantart.net/fs27/i/2011/193/9/f/… appear to be image at bars at top chart ? Is requirement to apply same image to lower chart ? Not appear to be attempt to retrieve base64 at jsfiddle jsfiddle.net/R8A8P/51 ?Levinson
Yes, the image is appeared to be top chart. and Yes, its requirement, because instead of colors, I want images, so I put in at the top. But when I retrieve base64, the image is not there.Hawsepipe
Cannot utilize image that already populates bars ? What would be difference between base64 and existing image link ?Levinson
I want to send to the server and put it into a pdf.Hawsepipe
And I dont know what you mean in your questionHawsepipe
See post. After re-viewing Question , believe that requirement is to create png image from svg element ? Tried , though did not retain linked image for bars colors . Created data URI of svg which retained linked image at bars when loaded in in browser.Levinson
See php.net/imagickLevinson
See also bugzilla.mozilla.org/show_bug.cgi?id=672013 , bugs.webkit.org/show_bug.cgi?id=129172Levinson
S
2

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" />
Slenderize answered 9/5, 2015 at 3:3 Comment(11)
Be careful however, this can be really consumptive : it first gets all the images from the server and then encode them before reencoding the final image to a last dataURL. For production, you should really consider using some server side solution like the one pointed by @Guest271314Slenderize
What do you mean consumptive? Heavy load? Taking too long to load entire png? More ram? Because I notice that I see the png keeping adding the images inside.Hawsepipe
Yes, a little bit of the three I guess. But this is mainly due to the big size of the image you use. The first snippet can also be data consumptive, in the meaning of a lot of network requests can be done if you've got different images linked in the svg. But what do you mean by "the png keeping adding the images inside"?Slenderize
For the first example, when I refresh the page, I can see the <img> updating itself for adding the tex.jpg because after the graphic is generate. But the second, not.Hawsepipe
Do you mean the svg <image> tag ? I don't see it on my computer, but I may have the cache disabled right now. But anyyway, both examples should make the same use of browser cache.Slenderize
Not <image>, its the <img>, and just to test how was done the final form. And Yes, both examples works, but id prefer the second one.Hawsepipe
Yes for your case, definitely choose this one.Slenderize
@Hawsepipe I edited the answer. Have a look since it's not the one you accepted anymore. If it doesn't fit your needs, I could rollback, but IMO, it's a way better approach to achieve this.Slenderize
Im only having trouble with multiple images(var images = ["..png", "...png"]) instead of one image. Is it possible to add base64 into array? since array is extendable(increase whenever it needs). So I tried modify on the function getbase64URI to loop it and push it to an array.Hawsepipe
Yes it should be possible. Check the new code, it may be easier to edit. I'll give you some help tomorrow if you didn't find. (I think you should make a loop which calls getbase64URI, and then the callback to drawVisual...)Slenderize
Yes, I will try to ajust.Hawsepipe
G
0

This example creates an svg container populated by an image. In my example the image is an svg image but you should be able to put in any type of image (jpg, png, gif). The container is created first then the image is created inside the container.

 // create svg
                var svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
                svg.setAttribute('class','shadowed handle_icon_sensors');
                svg.setAttribute('height','25');
                svg.setAttribute('width','25');
                svg.setAttribute('id',idAttr);
                svg.setAttribute('z-index','21000');
                document.getElementById("zones_container").appendChild(svg);

            // create svg image
            var svgimg = document.createElementNS('http://www.w3.org/2000/svg','image');
            svgimg.setAttribute('height','25');
            svgimg.setAttribute('width','25');
            svgimg.setAttributeNS('http://www.w3.org/1999/xlink','href','svg/icon_sensorYellow.svg');
            svgimg.setAttribute('x','0');
            svgimg.setAttribute('y','0');
            document.getElementById(idAttr).appendChild(svgimg);
Geniagenial answered 30/4, 2015 at 20:3 Comment(3)
I didnt get it quite right, you mean, I have the graphic and your var svg = ... will transform to svg again(my graphic) to a new one and then I get the image I want?Hawsepipe
The key is you have to create the svg container (// create svg), then create the image within it (// create svg image). I was referring to my image "'svg/icon_sensorYellow.svg'" being an svg image. But any other web image should work as well. "idAttr" is created by my code to create multiple instances in a for loop, but any id you select will work. Just set the variable equal to your id.Geniagenial
Can you help me using the fiddle? I tried to use the url that gives me svg but is not generating the image.Hawsepipe
L
0

Note, When converted to src of img element retained blue background-color .

Try , after .each()

// set namespace attributes
var svg = $("svg").attr({
    "xmlns": "http://www.w3.org/2000/svg",
    "xmlns:xlink": "http://www.w3.org/1999/xlink"
})[0];

// create `data URI` of `svg`
var dataURI = "data:image/svg+xml;charset=utf-8;base64," + btoa(svg.outerHTML.trim());    

// post `svg` as `data URI` to server
$.post("/path/to/server/", {
    html: dataURI
}, "html")
    .then(function (data) {
    // do stuff
    // `svg` `data URI`
    console.log(data);

}, function (jqxhr, textStatus, errorThrown) {
    console.log(textStatus, errorThrown);
});

jsfiddle http://jsfiddle.net/R8A8P/58/

Levinson answered 9/5, 2015 at 0:16 Comment(13)
The SVG didnt rendered properly, This page contains the following errors: error on line 1 at column 1: Encoding error.Hawsepipe
@Hawsepipe Interesting, did not receive error notification here; neither at opened window or data URI at console ? Browser tried at ?Levinson
@Hawsepipe Tried js above at chrome, chromium, nightly without any error notifications. If could include linked image as data URI before loading "visualization" at page , image at "bars" may not be considered "cross origin" , or "cross domain" . Attempting to create png at canvas from cross origin , perhaps, challenging. One method to actually create a png image of the svg created above is to utilize firefox web console to create a screenshot of of the svg. This does not resolve building the bars with linked cross origin image. See https://mcmap.net/q/1482621/-tainted-canvas-due-to-cors-and-svg/2801559Levinson
@Levinson , to do so he actually needs to convert the external images to base64 dataURL and embed them first. After that it will work, at least on non-IE browsers. See my answer which does it, in an horrible way. But actually, as you stated in your comment on OP, the server side solution is the best one, tried with Imagick and it does work with external images too. I don't know about inkscape but it may worth the try too.Slenderize
@Slenderize Yes. Attempted to suggest utilizing data URI of background "bars" inline , instead of linked to external resource. Did not attempt , here, to do so; unaware of any possible licensing , etc. attached to that specific image ? Also considered suggesting creating linear-gradient as substitute for external link . Tried to open svg , with data-uri as src of "bars" at https://mcmap.net/q/1477376/-how-can-i-get-png-base64-with-images-inside-of-svg-in-google-charts; svg appear not to have namespace of svg, xlink included at svg ? Which image at https://mcmap.net/q/1477376/-how-can-i-get-png-base64-with-images-inside-of-svg-in-google-charts renders resultant svg as png ?Levinson
Hum didn't get the whole thing in that comment. If you want a walkthrough my answer, in the first snippet, the svg loads the images with OP's code, then it searches for <images> tags and convert each external resource it found (only new ones). Then it changes the external urls to the newly created dataURL, so we've got a crossorigin compliant svg, with no external resources. Finally, the svg is passed to an `<img>' tag's src as dataURI and drawn to a canvas to get the dataURL of the full svg.Slenderize
The second snippet is quite simpler as it directly converts the external png to dataURL and then applies it to the svg at its creation, saving one request to external resource. Then it uses the same technique to export the full svg as png dataURLSlenderize
@Slenderize How to open png of "visualization" at new tab ?Levinson
In first snippet replace $('#chartImg')[0].src = d; in encodeSVGImages with window.open(d), and in the second one, the callback is set in the .each loop (which is not a great idea by the way…)Slenderize
@Slenderize Tried replacing $('#chartImg')[0].src = d; with window.open(d) at first stacksnippets ; no window is opened ? How to accomplish at second stacksnippets ?Levinson
jsfiddle.net/f2Lrvdzo for first snippet and jsfiddle.net/f2Lrvdzo/1 for second oneSlenderize
Let us continue this discussion in chat.Levinson
@Slenderize Tried , new window not opening at jsfiddle ?Levinson
G
0

this html worked in react

 <a
    href={chart.getImageURI()}
    download="chart.png">
    getImage
  </a>
Gilliland answered 4/5, 2021 at 21:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.