Rendering your D3 viz outside of your browser
Noah Veltman
@veltman
Portability
Performance
Code once, generate many
Work in JS instead of animation software
an SVG
a <canvas>
HTML
images (SVG, PNG, etc.)
GIFs
videos
something else
CSS
browser APIs
webfonts
one of them
lots of them
Easy, doesn't scale
Relatively easy, still labor-intensive
Scaleable, no browser features
Hybrid approach
Manual Capture
Easy
Zero code modification
Doesn't scale
Imprecise crop/timing
VIDEOS
GIFS
IMAGES
SVG
HTML
QuickTime
LICECap
Screenshot
SVG Crowbar
Copy/paste
SVG vs. Canvas
Vector
Manipulate elements
Style with CSS
Have to rasterize
Raster
Draw pixels
Style explicitly
CSS defined outside of your <svg> won't be included
Use SVG Crowbar
Copy stylesheets into SVG element
  var allStyles = Array.from(document.styleSheets)
    .map(sheet =>
      Array.from(sheet.cssRules)
        .map(rule => rule.cssText)
        .join(" ")
    ).join(" ");
  d3.select("svg").append("style")
    .text(allStyles);Add helper code
Repeatable
Precise
Still manual
No d3.transition()
VIDEOS
GIFS
IMAGES
MediaRecorder API
GIF.js
toDataUrl
Animation frames
  // Set stuff up
  draw(t) {
    // Draw everything based on t
    requestAnimationFrame(draw);
  }
  requestAnimationFrame(draw);
  d3.select("circle")
    .transition()
    .duration(5000)
    .attr("r", 5)
    .attr("fill", "red");Specify every detail
Can render any frame
Lots of hidden magic
Chainable
Events
Can't frame-seek
  var circle = d3.select("circle");
      color = circle.attr("fill"),
      radius = circle.attr("r"),
      interpolateColor = d3.interpolate(color, "red"),
      interpolateRadius = d3.interpolate(radius, 5);
  var timer = d3.timer(function(t){
    var easedT = d3.easeCubicInOut(t);
    circle.attr("fill", interpolateColor(easedT))
      .attr("r", interpolateRadius(easedT);
    if (t >= 5000) {
      timer.stop();
    }
  });No background color
Draw a rectangle behind it
  var gif = new GIF();
  for (var i = 0; i < numFrames; i++) {
    drawFrame(i * duration / frames);
    gif.addFrame(myCanvas, {
      delay: duration / frames,
    });
  }
  gif.render();Globe to GIF
SVG to GIF
Globe to WebM
Server side
Repeatable
No d3.transition()
No browser APIs
Font nightmares
VIDEOS
GIFS
IMAGES
SVG
HTML
node-canvas -> FFmpeg
node-canvas -> FFmpeg or GIFEncoder
node-canvas or svg2png
jsdom
jsdom
  var Canvas = require("canvas");
  var myCanvas = new Canvas(600, 600);
  var context = myCanvas.getContext("2d");
  context.fillStyle = "papayawhip";
  context.fillRect(100, 100, 400, 400);
  fs.writeFile("whipit.png", canvas.toBuffer(), etc);
  var d3 = require("d3"),
    jsdom = require("jsdom");
  var body = new jsdom.JSDOM().window.document.body;
  var svg = d3
    .select(body)
    .append("svg")
    .attr("width", 100)
    .attr("height", 100);
  // Add stuff here
  fs.writeFile("mychart.svg", body.innerHTML);
Globe to GIF
Globe to video
SVG minimaps
Coordinate precision increases file size
Round coordinates
  var svgpath = require("svgpath");
  function roundedPath(d){
    return svgpath(d)
      .round(2).toString();
  }Fonts
Embed fonts in graphics software
Embed font-face in <svg>
Headless browser
Repeatable
Browser APIs
Still no d3.transition()
Finicky and complex
Slow
PUPPETEER
NIGHTMAREJS
SELENIUM
PHANTOMJS
Globe to GIF
Lie to your computer
  var fakeTime = 0;
  Date.now = function() {
    return fakeTime;
  };
  window.requestAnimationFrame = function(cb) {
    saveFrame();
    fakeTime += 17;
    setTimeout(cb,0);
  };d3.transition()
Use existing code
Is completely insane
Clock hacking
github.com/veltman/d3-unconf
github.com/veltman/gifs
github.com/GoogleChrome/puppeteer
github.com/antimatter15/whammy
github.com/fivethirtyeight/d3-pre