23

Given a D3js code, such as:

var square= function() {
        var svg = window.d3.select("body")
            .append("svg")
            .attr("width", 100)
            .attr("height", 100);
        svg.append("rect")
            .attr("x", 10)
            .attr("y", 10)
            .attr("width", 80)
            .attr("height", 80)
            .style("fill", "orange");
    }
square();
svg { border: 1px solid grey;} /* just to visualized the svg file's area */
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<body></body>

How to generate a correct stand alone *.svg file with my D3js code & NodeJS ?

Hugolpz
  • 17,296
  • 26
  • 100
  • 187
  • See also #PhantomJS direction with [Exporting D3.js graphs to static SVG files, programmatically](http://stackoverflow.com/questions/18240391/exporting-d3-js-graphs-to-static-svg-files-programmatically) – Hugolpz Oct 24 '13 at 21:21
  • May works prettily together with [code to convert (png?) images files into `data:image`](http://stackoverflow.com/questions/24939562/), to embed in SVG :) – Hugolpz Jul 24 '14 at 17:08

5 Answers5

26

Github repository svgcreator.node.js to try out.


D3 doesn't care at all what actually generate your SVG. The main problem with creating only SVG is that you can't have Javascript then, which of course means that you can't use D3. Apart from this fundamental no-no, there's nothing stopping you :)

Proof of concept: Inspired by the other answer, here's some proof-of-concept code using jsdom.

1. Install NodeJS (1).

curl http://npmjs.org/install.sh | sh       #this should work (not tested)

2. Install jsdom using the Node Packages Manager (2):

$npm install jsdom

3. Wrap your D3js code within some jsdom code, paste into a jsdom.node.js file :

var jsdom = require('jsdom');

jsdom.env(
  "<html><body></body></html>",
  [ 'http://d3js.org/d3.v3.min.js' ],
  function (err, window) {
    var svg = window.d3.select("body")
        .append("svg")
        .attr("width", 100).attr("height", 100);

    svg.append("rect")
        .attr("x", 10)
        .attr("y", 10)
        .attr("width", 80)
        .attr("height", 80)
        .style("fill", "orange");
// PRINT OUT:
    console.log(window.d3.select("body").html());
//  fs.writeFileSync('out.svg', window.d3.select("body").html()); // or this
  }
);

4. Run in terminal

$node jsdom.node.js > test.svg

The stdout output is the SVG, which is then injected into a test.svg file. Job done.

As Gilly points out in the comments, you may need version 3 of jsdom for this to work.

Lars Kotthoff
  • 107,425
  • 16
  • 204
  • 204
  • Hello Lars. I'am begginer to JS, I don't get it. Are you thinking about a nodejs solution ? put my D3js code within a pure .js , run in node, and then grasp the output ? – Hugolpz Sep 29 '13 at 17:29
  • Well, most of the examples do generate pure SVGs, they're just embedded in HTML. You're right that using something like node.js you can run JS without HTML. My point is it really doesn't matter what you generate. Do you have a concrete application in mind? – Lars Kotthoff Sep 29 '13 at 17:38
  • [This](http://bl.ocks.org/hugolpz/raw/6310180/). I would like to output hundreds or thousands of such files using D3js. Would be better if I directly output SVGs. – Hugolpz Sep 29 '13 at 18:45
  • So what's the problem with generating those SVGs? – Lars Kotthoff Sep 29 '13 at 18:48
  • I want the .svg files programmatically. Generate a html where I can click to download the file is not a suitable workflow when we wish to generate hundreds if not thousands files. Highest estimation is 20,000 svg to generate. – Hugolpz Sep 29 '13 at 21:27
  • sorry to comment here out of the blue, but the download button on your example doesn't work on safari 6.0.5 for mac os – tomtomtom Sep 29 '13 at 21:36
  • I'm not sure if D3 is the way to go in this case -- sounds more like a job for Python/Ruby. – Lars Kotthoff Sep 29 '13 at 21:55
  • Python will have difficulties to manage my GIS files. On the other hand I already have a workflow to generate the topojson, and a D3js code to generate the SVG... within a browser. This last step -being locked within the web-browser- is now a big limitation. It forbidden automation. – Hugolpz Sep 30 '13 at 22:26
  • I've added a proof-of-concept example. – Lars Kotthoff Oct 01 '13 at 19:05
  • Hello Lars, for some reason I missed your new try. I understand it. Doing this in shell + NodeJS, then some shel `> title.svg` and that could make it. That's your approach right ? (I haven't yet used NodeJS+D3js) – Hugolpz Oct 24 '13 at 15:07
  • It's more than a proof of concept, it was a working solution. Just implemented, it works, it's great. – Hugolpz Oct 29 '13 at 23:09
  • I think this won't work in every case because of limitations in the implementation of jsdom. But it's probably enough for most cases. – Lars Kotthoff Oct 30 '13 at 09:39
  • Given the few support this answer get (just us two), I think we r alone to gets how cool this is ! :D – Hugolpz Oct 31 '13 at 00:08
  • Please note any newcomers that you now require `$ npm install jsdom@3`, the newer versions will not work with node. @Lars or @Hugo, maybe you could update the answer? – Gilly Sep 09 '15 at 12:33
  • In order to get this working I had to define `` as `
    ` and then `console.log(window.d3.select("body>div").html());` . Otherwise the
    – rubenafo Mar 03 '16 at 19:37
  • Is this still working or not? I kept getting `error on line 2 at column 1: Extra content at the end of the document` – Haifeng Zhang Jan 28 '19 at 23:47
4

Here's a very short node script that will output an svg to stdout generated with d3.js.

#!/usr/bin/env node

var d3 = require("d3"),
    jsdom = require("jsdom").jsdom;

var body = d3.select(jsdom().documentElement).select("body");

var svg = body.append("svg");
process.stdout.write(body.node().innerHTML);

Link to snippet on bl.ocks.org

Shan Carter
  • 2,425
  • 2
  • 14
  • 10
3

I recently wanted to do just that and asked a question here. I was pointed to phantomJS. Using PhantomJS, I created a JS -

svggen.js:

var page = require('webpage').create(),
    url = 'http://www.example.com/wordcloud.html';

page.open(url, function (status) {
    if (status !== 'success') {
        console.log('Unable to access network');
    } else {
        var svgData = page.evaluate(function(s){
                var serializer = new XMLSerializer();
                var element = document.getElementById("svg1");
                return serializer.serializeToString(element);
        });
        console.log("<?xml version=\"1.0\"?>"+svgData);
    }
    phantom.exit();
});

wordcloud.html:

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="d3.min.js"></script>
<script src="d3.layout.cloud.js"></script>
<script>
  var fill = d3.scale.category20();

  d3.layout.cloud().size([500, 800])
      .words([
        "Hello", "world", "normally", "you", "want", "more", "words",
        "than", "this"].map(function(d) {
        return {text: d, size: 10 + Math.random() * 90};
      }))
      .padding(5)
      .rotate(function() { return ~~(Math.random() * 2) * 90; })
      .font("Impact")
      .fontSize(function(d) { return d.size; })
      .on("end", draw)
      .start();

  function draw(words) {
    d3.select("body").append("svg")
        .attr("width", 500)
        .attr("height", 800)
        .attr("id","svg1")
        .attr("xmlns","http://www.w3.org/2000/svg")
        .attr("xmlns:xlink","http://www.w3.org/1999/xlink")
      .append("g")
        .attr("transform", "translate(150,150)")
      .selectAll("text")
        .data(words)
      .enter().append("text")
        .style("font-size", function(d) { return d.size + "px"; })
        .style("font-family", "Impact")
        .style("fill", function(d, i) { return fill(i); })
        .attr("text-anchor", "middle")
        .attr("transform", function(d) {
          return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
        })
        .text(function(d) { return d.text; });
  }
</script>
</body></html>

Then I run

phantomjs svggen.js > svgFile.svg

The resulting svgFile.svg is a standalone SVG File. For d3cloud check this.

Community
  • 1
  • 1
Lord Loh.
  • 2,437
  • 7
  • 39
  • 64
2

node.js is the way to go. You can install d3 directly with npm. (It will also add jsdom as a dependency to provide a "fake" DOM.) After the d3 code generates the SVG, just grab its contents and write to a file.

Stephen Thomas
  • 13,843
  • 2
  • 32
  • 53
  • 4
    Insert the graph inside an html `
    ` and then use $('whatever').html() to get its contents. Write the contents to a file using plain old nodejs file system (fs). (If you want someone to actually write the code for you, send me a private message. I charge $90/hour.)
    – Stephen Thomas Sep 30 '13 at 18:46
  • I designed the project and proposed it, it haven't funding yet. But thanks for the notice. – Hugolpz Sep 30 '13 at 21:14
1

if you donot want to use node.js , then use phantomJs , here you can find a demo https://github.com/subramanya2107/d3js-phantomjs-demo.git

subbu
  • 254
  • 2
  • 7