Generating a high-resolution video from a d3 animation is tricky. LICEcap and QuickTime screen recording work in a pinch, but they aren’t scriptable and lose FPS without a beefy video card.
Noah Veltman has written about and presented different techniques for exporting d3 graphics. The best way I’ve found of exporting video come from him and uses a delightful hack: modifying time itself.
Inside of your clientside code, overwrite performance.now with a function that returns the currentTime
variable. This will let us control what time d3-timer
and d3-transition
think it is.
if (document.URL.includes('d3-video-recording')){
window.currentTime = 0
performance.now = () => currentTime
}
This code only runs if the url contains d3-video-recording
, making it easy to toggle between automatic and manual animations with a query string.
puppeteer loads the page, moving time forward slowly and taking a screenshot over and over again. Even though each screenshot takes over half a second to render, controlling the browser’s perception of time ensures no frames are dropped.
const puppeteer = require('puppeteer')
const d3 = require('d3')
;(async () => {
// open new tab and wait for data to load
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto('http://localhost:1337?d3-video-recording')
await sleep(5000)
// step through each frame:
// - increment currentTime on the page
// - save a screenshot
for (let frame of d3.range(120)){
await page.evaluate((frame) => currentTime = frame*1000/60, frame)
await sleep(50)
let path = __dirname + '/png/' + d3.format('05')(frame) + '.png'
await page.setViewport({width: 1920, height: 1080, deviceScaleFactor: 2})
const chartEl = await page.$('.chart')
await chartEl.screenshot({path})
}
browser.close()
})()
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
Finally, convert the directory of screenshots to a video:
ffmpeg -framerate 60 -pattern_type glob -i 'png/*.png' video.mp4
For quick thumbnail previews, check out gistsnap. A similar CLI tool for video could be useful, but I’m not sure passing flags to control the FPS, delay, number of frames, crop area and query string is easier than coding them directly.