1

I need to make my D3-created SVG charts downloadable. I found one answer to this question that suggested encoding SVG's XML to base64, but then I found this post that said plain text should be OK too, so I came up with something like this:

<!DOCTYPE html>
<html>
<head>
  <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
  <script>
    var data = [22, 33, 11, 55, 44];
    var svg = d3.select('body').append('svg')
      .attr('id', 'chart')
      .attr('version', '1.1')
      .attr('xmlns', 'http://www.w3.org/2000/svg')
      .attr('width', 600)
      .attr('height', 400)
      .style('background-color', 'powderblue')
      .selectAll('circle')
        .data(data)
        .enter().append('circle')
          .attr('cx', function(d) { return 8*d; })
          .attr('cy', function(d) { return 4*d; })
          .attr('r', function(d) { return d; })
          .style('fill', function(d) { return d3.rgb(2*d, 4*d, 3*d); })
    ;
    d3.select('body').append('a')
      .attr('href-lang', 'image/svg+xml')
      .attr('href', 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg">' + unescape(svg.node().parentNode.innerHTML) + '</svg>')
      .text('Download')
    ;
  </script>
</body>
</html>

The above code works partially, because the download link can be opened in the browser or downloaded to a file, but in each case only the circles are visible and all else (ie. width, height, bg color) is lost, so the SVG is only properly displayed in the original HTML page. What is the right way to get the full code of SVG created with D3? I tried svg.html() and d3.select(svg).html(), but it returns null.

Community
  • 1
  • 1
mac13k
  • 2,423
  • 23
  • 34

1 Answers1

4

You could try saving the parent's outerHTML.
.attr('href', 'data:image/svg+xml;utf8,' + unescape(svg.node().parentNode.outerHTML))

<!DOCTYPE html>
<html>
<head>
  <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
  <script>
    var data = [22, 33, 11, 55, 44];
    var svg = d3.select('body').append('svg')
      .attr('id', 'chart')
      .attr('version', '1.1')
      .attr('xmlns', 'http://www.w3.org/2000/svg')
      .attr('width', 600)
      .attr('height', 400)
      .style('background-color', 'powderblue')
      .selectAll('circle')
        .data(data)
        .enter().append('circle')
          .attr('cx', function(d) { return 8*d; })
          .attr('cy', function(d) { return 4*d; })
          .attr('r', function(d) { return d; })
          .style('fill', function(d) { return d3.rgb(2*d, 4*d, 3*d); })
    ;
    d3.select('body').append('a')
      .attr('href-lang', 'image/svg+xml')
      .attr('href', 'data:image/svg+xml; charset=utf8, ' + unescape(svg.node().parentNode.outerHTML))
      .text('Download')
    ;
  </script>
</body>
</html>

Ps: You may also want to add the download attribute on your link, for recent browsers users can directly download it as a file.

PPs: To force the width height of the new file, you should set the viewbox attribute of your svg

PPPs: To have a fully valid svg file (i.e with DOCTYPE set etc.) I think you will have to add those four more lines :

var svgDocType = document.implementation.createDocumentType('svg',  "-//W3C//DTD SVG 1.1//EN", "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd");
var svgDoc= document.implementation.createDocument ('http://www.w3.org/2000/svg', 'svg', svgDocType);
svgDoc.replaceChild(svg.node().parentNode.cloneNode(true), svgDoc.documentElement);
svgData = (new XMLSerializer()).serializeToString(svgDoc);

var data = [22, 33, 11, 55, 44];
    var svg = d3.select('body').append('svg')
      .attr('id', 'chart')
      .attr('version', '1.1')
      .attr('xmlns', 'http://www.w3.org/2000/svg')
      .attr('width', 600)
      .attr('height', 400)
      .style('background-color', 'powderblue')
      .selectAll('circle')
        .data(data)
        .enter().append('circle')
          .attr('cx', function(d) { return 8*d; })
          .attr('cy', function(d) { return 4*d; })
          .attr('r', function(d) { return d; })
          .style('fill', function(d) { return d3.rgb(2*d, 4*d, 3*d); })
    ;
    var svgDocType = document.implementation.createDocumentType('svg',  "-//W3C//DTD SVG 1.1//EN", "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd");
    var svgDoc= document.implementation.createDocument ('http://www.w3.org/2000/svg', 'svg', svgDocType);
    svgDoc.replaceChild(svg.node().parentNode.cloneNode(true), svgDoc.documentElement);
    svgData = (new XMLSerializer()).serializeToString(svgDoc);
    d3.select('body').append('a')
      .attr('href-lang', 'image/svg+xml')
      .attr('href', 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(svgData))
      .text('Download')
    ;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • This is a bit better, because now the attributes are there inside of the tag, but the image is still displayed 'naked' when opened in the browser or saved to file from the download link. Any idea what still can be missing? – mac13k Apr 11 '15 at 14:49
  • I didn't remove tags from the href. This works: `.attr('href', 'data:image/svg+xml;utf8,' + unescape(svg.node().parentNode.outerHTML))`. – mac13k Apr 11 '15 at 14:56