101

I currently have a website using D3 and I'd like the user to have the option to save the SVG as an SVG file. I'm using crowbar.js to do this, but it only works on chrome. Nothing happens of safari and IE gives an access denied on the click() method used in crowbar.js to download the file.

var e = document.createElement('script'); 

if (window.location.protocol === 'https:') { 
    e.setAttribute('src', 'https://raw.github.com/NYTimes/svg-crowbar/gh-pages/svg-crowbar.js'); 
} else { 
    e.setAttribute('src', 'http://nytimes.github.com/svg-crowbar/svg-crowbar.js'); 
}

e.setAttribute('class', 'svg-crowbar'); 
document.body.appendChild(e);

How do I download an SVG file based on the SVG element on my website in safari, IE and chrome?

Hooked
  • 84,485
  • 43
  • 192
  • 261
Sina Sohi
  • 2,719
  • 9
  • 33
  • 50

9 Answers9

99

There are 5 steps. I often use this method to output inline svg.

  1. get inline svg element to output.
  2. get svg source by XMLSerializer.
  3. add name spaces of svg and xlink.
  4. construct url data scheme of svg by encodeURIComponent method.
  5. set this url to href attribute of some "a" element, and right click this link to download svg file.

//get svg element.
var svg = document.getElementById("svg");

//get svg source.
var serializer = new XMLSerializer();
var source = serializer.serializeToString(svg);

//add name spaces.
if(!source.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)){
    source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
}
if(!source.match(/^<svg[^>]+"http\:\/\/www\.w3\.org\/1999\/xlink"/)){
    source = source.replace(/^<svg/, '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
}

//add xml declaration
source = '<?xml version="1.0" standalone="no"?>\r\n' + source;

//convert svg source to URI data scheme.
var url = "data:image/svg+xml;charset=utf-8,"+encodeURIComponent(source);

//set url value to a element's href attribute.
document.getElementById("link").href = url;
//you can download svg file by right click menu.
defghi1977
  • 5,081
  • 1
  • 30
  • 29
  • 1
    Thank you for the answer! This downloads an svg for me, althought everything goes black and coloring is very wierd. Why is this? You can see what I mean on my website, I've applied your suggested code: http://servers.binf.ku.dk/hemaexplorerbeta/ - Just click "submit" and then "export graph". Thank you very much – Sina Sohi Apr 22 '14 at 12:20
  • 2
    This sample is simple case. If you use css style by outer css files by link element, the link of svg and style sheet was broken. So, this problem will be solved by appending style data to inline svg. – defghi1977 Apr 22 '14 at 12:32
  • 1
    "So, this problem will be solved by appending style data to inline svg." Can you explain this to me in another way? I don't quite understand. – Sina Sohi Apr 22 '14 at 13:59
  • 5
    See http://www.w3.org/TR/SVG11/styling.html#ReferencingExternalStyleSheets So, add and convert, but in this case the svg file will not be stand-alone. – defghi1977 Apr 22 '14 at 14:41
  • 9
    Note: This no longer works in Chrome or Firefox as they both now block data URIs for SVGs when used on the top level. – Undistraction Mar 17 '18 at 19:32
  • @Undistraction any solutions? – SumNeuron Mar 20 '18 at 19:11
  • @SumNeuron I'm using https://github.com/eligrey/FileSaver.js/ which saves Blobs. Only tested in Chrome, Safari and FFox on Mac so far but works great in all. – Undistraction Mar 20 '18 at 20:37
90

I know this has already been answered, and that answer works well most of the time. However I found that it failed on Chrome (but not Firefox) if the svg image was large-ish (about 1MB). It works if you go back to using a Blob construct, as described here and here. The only difference is the type argument. In my code I wanted a single button press to download the svg for the user, which I accomplished with:

var svgData = $("#figureSvg")[0].outerHTML;
var svgBlob = new Blob([svgData], {type:"image/svg+xml;charset=utf-8"});
var svgUrl = URL.createObjectURL(svgBlob);
var downloadLink = document.createElement("a");
downloadLink.href = svgUrl;
downloadLink.download = "newesttree.svg";
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);

October 2019 edit: Comments have indicated that this code will work even without appending downloadLink to document.body and subsequently removing it after click(). I believe that used to work on Firefox, but as of now it no longer does (Firefox requires that you append and then remove downloadLink). The code works on Chrome either way.

DaveTheScientist
  • 3,299
  • 25
  • 19
  • 1
    This worked really well in Chrome! I'd give +100 for this simple answer! – kashiraja May 12 '17 at 19:55
  • 3
    Looks like it works without appending and removing `downloadLink` in `document.body` – piotr_cz Aug 22 '17 at 10:26
  • 1
    `.outerHTML` not does not work in internet explorer. But you can use an `XMLSerializer()` as [defghi1977](https://stackoverflow.com/a/23218877/3620572). Everything else keeps the same. – roland Oct 23 '17 at 13:06
  • Good alternative to XMLSerializer()! – V. Rubinetti May 14 '18 at 04:03
  • This worked best! Great answer and easy to use snippet. – Kay Angevare Sep 17 '20 at 07:08
  • You will need to add the svg name spaces for browsers to properly render the generated svg: ```if(!svgData.match(/^]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)){ svgData = svgData.replace(/^]+"http\:\/\/www\.w3\.org\/1999\/xlink"/)){ svgData = svgData.replace(/^ – AlienKevin Dec 27 '20 at 13:06
  • @DaveTheScientist If svg tag contains some images they do not load. – Vadim Sheremetov Feb 02 '21 at 15:44
  • @VadimSheremetov if the img element is pointing to an external image file, I'm sure those links would break. You'd have to find a way to include the image data in the svg itself I imagine. – DaveTheScientist Feb 03 '21 at 16:39
51

Combining Dave's and defghi1977 answers. Here is a reusable function:

function saveSvg(svgEl, name) {
    svgEl.setAttribute("xmlns", "http://www.w3.org/2000/svg");
    var svgData = svgEl.outerHTML;
    var preface = '<?xml version="1.0" standalone="no"?>\r\n';
    var svgBlob = new Blob([preface, svgData], {type:"image/svg+xml;charset=utf-8"});
    var svgUrl = URL.createObjectURL(svgBlob);
    var downloadLink = document.createElement("a");
    downloadLink.href = svgUrl;
    downloadLink.download = name;
    document.body.appendChild(downloadLink);
    downloadLink.click();
    document.body.removeChild(downloadLink);
}

Invocation example:

saveSvg(svg, 'test.svg')
senz
  • 1,980
  • 1
  • 16
  • 14
  • 1
    `svgEl.outerHTML` won't work in IE and Edge. They don't support `outerHTML` for SVG elements. [source](https://stackoverflow.com/a/45120072/9069356) – Dominus.Vobiscum Jul 23 '19 at 19:42
  • I want to save SVG silently in a folder on server and I don't want to trigger the download for user how it can be done please assist me. Thanks! – Rehan Nov 27 '20 at 08:49
  • @senz, What if svg contains images. For some reason this does not work. Check my example https://jsfiddle.net/10Ljgrv7/3/ . – Vadim Sheremetov Feb 02 '21 at 15:49
3

For this snippet to work you need FileSaver.js.

function save_as_svg(){


        var svg_data = document.getElementById("svg").innerHTML //put id of your svg element here

        var head = '<svg title="graph" version="1.1" xmlns="http://www.w3.org/2000/svg">'

        //if you have some additional styling like graph edges put them inside <style> tag

        var style = '<style>circle {cursor: pointer;stroke-width: 1.5px;}text {font: 10px arial;}path {stroke: DimGrey;stroke-width: 1.5px;}</style>'

        var full_svg = head +  style + svg_data + "</svg>"
        var blob = new Blob([full_svg], {type: "image/svg+xml"});  
        saveAs(blob, "graph.svg");


};
Vasyl Vaskivskyi
  • 917
  • 12
  • 15
3

I tryed every solution here and nothing worked for me. My picture was always smaller than my d3.js canvas.

I had to set the canvas width, height then do a clearRect on the context to make it works. Here is my working version

Export function:

var svgHtml = document.getElementById("d3-canvas"),
    svgData = new XMLSerializer().serializeToString(svgHtml),
    svgBlob = new Blob([svgData], {type:"image/svg+xml;charset=utf-8"}),
    bounding = svgHtml.getBoundingClientRect(),
    width = bounding.width * 2,
    height = bounding.height * 2,
    canvas = document.createElement("canvas"),
    context = canvas.getContext("2d"),
    exportFileName = 'd3-graph-image.png';

//Set the canvas width and height before loading the new Image
canvas.width = width;
canvas.height = height;

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

    //Create blob and save if with FileSaver.js
    canvas.toBlob(function(blob) {
        saveAs(blob, exportFileName);
    });     
};
var svgUrl = URL.createObjectURL(svgBlob);
image.src = svgUrl;

It use FileSaver.js to save the file.

This is my canvas creation, note that i solve the namespace issue here

d3.js canvas creation:

var canvas = d3.select("body")
    .insert("svg")
    .attr('id', 'd3-canvas')
    //Solve the namespace issue (xmlns and xlink)
    .attr("xmlns", "http://www.w3.org/2000/svg")
    .attr("xmlns:xlink", "http://www.w3.org/1999/xlink")
    .attr("width", width)
    .attr("height", height);
Ludovic Feltz
  • 11,416
  • 4
  • 47
  • 63
3

While this question has been answered, I created a small library called SaveSVG which can help save D3.js generated SVG which use external stylesheets or external definition files (using <use> and def) tags.

Coola
  • 2,934
  • 2
  • 19
  • 43
2

Based on @vasyl-vaskivskyi 's answer.

<script src="../../assets/FileSaver.js"></script>
<script>
function save_as_svg(){
    fetch('path/../assets/chart.css')
    .then(response => response.text())
    .then(text => {
        var svg_data = document.getElementById("svg").innerHTML
        var head = '<svg title="graph" version="1.1" xmlns="http://www.w3.org/2000/svg">'
        var style = "<style>" + text + "</style>"
        var full_svg = head +  style + svg_data + "</svg>"
        var blob = new Blob([full_svg], {type: "image/svg+xml"});  
        saveAs(blob, "graph.svg");
    })
};
save_as_svg();
</script>

The above code read your chart.css and then embed the css code to your svg file.

Feng Jiang
  • 1,776
  • 19
  • 25
1

I try this and worked for me.

function downloadSvg() {

var svg = document.getElementById("svg");
var serializer = new XMLSerializer();
var source = serializer.serializeToString(svg);
source = source.replace(/(\w+)?:?xlink=/g, 'xmlns:xlink='); // Fix root xlink without namespace

source = source.replace(/ns\d+:href/g, 'xlink:href'); // Safari NS namespace fix.


if (!source.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)) {
    source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
}
if (!source.match(/^<svg[^>]+"http\:\/\/www\.w3\.org\/1999\/xlink"/)) {
    source = source.replace(/^<svg/, '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
}


var preface = '<?xml version="1.0" standalone="no"?>\r\n';
var svgBlob = new Blob([preface, source], { type: "image/svg+xml;charset=utf-8" });
var svgUrl = URL.createObjectURL(svgBlob);
var downloadLink = document.createElement("a");
downloadLink.href = svgUrl;
downloadLink.download = name;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);

}

Salar Afshar
  • 229
  • 1
  • 2
  • 13
  • I was struggling with downloading SVG and tried as many ways as possible and this is the solution that worked for me. Thank you very much Afshar. – Jeremy Gillbert Feb 21 '23 at 09:17
0

In my scenario, I had to use some svg images created using D3.js in other projects. So I opened dev tools and inspected those svg and copied their content. Then created a new blank svg file and pasted the copied content there. Then I used those new svg files in other areas.

And if you want to do it programmatically, then we can use document.getElementById('svgId')

I know this is a basic approach but in case anyone find it useful.

kushal
  • 1
  • 1