25

I'm trying to follow this tutorial on converting a d3.js SVG Vis to a PNG server-side (using Node.js) http://eng.wealthfront.com/2011/12/converting-dynamic-svg-to-png-with.html

Link to full code: https://gist.github.com/1509145

However, I keep getting this error whenever I attempt to make a request to load my page

    /Users/me/Node/node_modules/jsdom/lib/jsdom.js:171
        features   = JSON.parse(JSON.stringify(window.document.implementation._fea
                                                              ^
    TypeError: Cannot read property 'implementation' of undefined
        at exports.env.exports.jsdom.env.processHTML (/Users/dereklo/Node/node_modules/jsdom/lib/jsdom.js:171:59)
        at Object.exports.env.exports.jsdom.env (/Users/dereklo/Node/node_modules/jsdom/lib/jsdom.js:262:5)
        at Server.<anonymous> (/Users/dereklo/Node/Pie/pie_serv.js:26:9)
        at Server.EventEmitter.emit (events.js:91:17)
        at HTTPParser.parser.onIncoming (http.js:1785:12)
        at HTTPParser.parserOnHeadersComplete [as onHeadersComplete] (http.js:111:23)
        at Socket.socket.ondata (http.

Does anybody know why this might be? I've installed the jsdom module fine, so I don't really know what's causing these issues...thanks in advance.

EDIT

This is the code I'm using to implement the node.js server. My latest issue is below this source...

var http = require('http'),
    url = require('url'),
    jsdom = require('jsdom'),
    child_proc = require('child_process'),
    w = 400,
    h = 400,
    __dirname = "Users/dereklo/Node/pie/"

   scripts = ["/Users/dereklo/Node/pie/d3.min.js",
               "/Users/dereklo/Node/pie/d3.layout.min.js",
               "/Users/dereklo/Node/pie/pie.js"],
      //scripts = ["./d3.v2.js",
        //         "./d3.layout.min.js",
          //       "./pie.js"]

    htmlStub = '<!DOCTYPE html><div id="pie" style="width:'+w+'px;height:'+h+'px;"></div>';

http.createServer(function (req, res) {

  res.writeHead(200, {'Content-Type': 'image/png'});
  var convert = child_proc.spawn("convert", ["svg:", "png:-"]),
      values = (url.parse(req.url, true).query['values'] || ".5,.5")
        .split(",")
        .map(function(v){return parseFloat(v)});

  convert.stdout.on('data', function (data) {
    res.write(data);
  });
  convert.on('exit', function(code) {
    res.end();
  });

  jsdom.env({features:{QuerySelector:true}, html:htmlStub, scripts:scripts, done:function(errors, window) {
    var svgsrc = window.insertPie("#pie", w, h, values).innerHTML;

  console.log("svgsrc",svgsrc);

    //jsdom's domToHTML will lowercase element names
    svgsrc = svgsrc.replace(/radialgradient/g,'radialGradient');
    convert.stdin.write(svgsrc);
    convert.stdin.end();
  }});
}).listen(8888, "127.0.0.1");

console.log('Pie SVG server running at http://127.0.0.1:8888/');
console.log('ex. http://127.0.0.1:8888/?values=.4,.3,.2,.1');

Latest Issue

    events.js:66
        throw arguments[1]; // Unhandled 'error' event
                       ^
Error: This socket is closed.
    at Socket._write (net.js:519:19)
    at Socket.write (net.js:511:15)
    at http.createServer.jsdom.env.done (/Users/dereklo/Node/Pie/pie_serv.js:38:19)
    at exports.env.exports.jsdom.env.scriptComplete (/Users/dereklo/Node/node_modules/jsdom/lib/jsdom.js:199:39)
Ian
  • 33,605
  • 26
  • 118
  • 198
Apollo
  • 8,874
  • 32
  • 104
  • 192

1 Answers1

50

This may prove to be a useful answer to your question if you take out that "using node.js" stipulation. If it doesn't help you, maybe later visitors will find it interesting.

I've been working for some time to solve this same problem (server-side d3 rasterizing), and I've found PhantomJS to be the best solution.

server.js:

var page = require('webpage').create(),
    renderElement = require('./renderElement.js'),
    Routes = require('./Routes.js'),
    app = new Routes();

page.viewportSize = {width: 1000, height: 1000};
page.open('./d3shell.html');

app.post('/', function(req, res) {
    page.evaluate(new Function(req.post.d3));
    var pic = renderElement(page, '#Viewport');
    res.send(pic);
});

app.listen(8000);

console.log('Listening on port 8000.');

Routes.js: https://gist.github.com/3061477
renderElement.js: https://gist.github.com/3176500

d3shell.html should look something like:

<!DOCTYPE HTML>
<html>
<head>
    <title>Shell</title>
</head>
<body>
    <div id="Viewport" style="display: inline-block"></div>
    <script src="http://cdnjs.cloudflare.com/ajax/libs/d3/2.8.1/d3.v2.min.js" type="text/javascript"></script>
</body>
</html>

You can then start the server with phantomjs server.js and POST d3=[d3 code that renders to #Viewport], and the server will respond with a base64-encoded png.

(Requires PhantomJS 1.7 or higher.)

Trevor Dixon
  • 23,216
  • 12
  • 72
  • 109
  • Can't use phantom because of it's BSD-2 license, but this looks like a great post that you put a lot of time into. I hope it helps others... – Apollo Jul 25 '12 at 17:45
  • 2
    Can you please post a full working example? I'm not sure how or what I should "post". – reubano Sep 06 '13 at 13:39
  • I'm also trying to use this sample but, like @reubano, I don't realize how to post as you mention. I'm new to phantomJS. Thanks – zed May 15 '14 at 00:01
  • I was able to piece something together in this [github project](https://github.com/reubano/tophubbers). It's a lot more involved as it queues all image requests and is intended for heroku which means you have to upload the generated images to s3. The relevant files are [server.coffee](https://github.com/reubano/tophubbers/blob/master/server.coffee) and [graph-view.coffee](https://github.com/reubano/tophubbers/blob/master/app/views/graph-view.coffee) – reubano May 15 '14 at 12:24
  • @Trevor Dixon Could you maybe give some details on how (what!) to post? – Tobi Apr 09 '15 at 07:25