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