Skip to content

Instantly share code, notes, and snippets.

Last active August 29, 2015 14:10
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
What would you like to do?
Zipline-style virtual rendering on scroll

Zipline-style virtual rendering on scroll

A toy example to demonstrate the scrolling with virtual rendering implemented in zipline.

<!DOCTYPE html>
<html lang='en'>
<meta charset='utf-8'>
<script src='' charset='utf-8'></script>
<script src=''></script>
<script src=''></script>
<link rel="stylesheet" type="text/css" href="">
<style type="text/css">
#main {
width: 800px;
height: 480px;
margin: 0 auto;
background-color: #f5f5f5;
overflow: hidden;
#nav {
width: 800px;
height: 60px;
margin: 0 auto;
background-color: #d6d6d6;
border-bottom: 5px white solid;
display: flex;
flex-direction: row;
justify-content: space-between;
#svg {
height: 420px;
overflow-x: scroll;
.button-holder {
margin-top: 8px;
button {
margin-left: 5px;
margin-right: 5px;
#axisGroup path {
fill: none;
stroke: none;
#axisGroup line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
#axisGroup text {
font-family: sans-serif;
text-transform: lowercase;
<title>Zipline scrolling</title>
<div id="main">
<div id="nav">
<div class="button-holder">
<button id="backbtn" type="button" class="btn btn-default">Back</button>
<div class="button-holder">
<button id="forwardbtn" type="button" class="btn btn-default">Forward</button>
<div id="svg"></div>
<script type="text/javascript">
// some variables
var w = 800, h = 410, axis = 40;
var barWidth = 25;
// let's make some data!
// first, dates for our x-plotting
var now = new Date();
var s = d3.time.year.floor(now), e = d3.time.year.ceil(now);
var dates = d3.time.hour.range(s, e);
// drop these in along with random numbers from 1-100 for the y-plotting
var data =, function(date) {
return {
x: date.valueOf(),
y: Math.ceil(Math.random() * 100)
// set up crossfilter for data filtering
var filterData = crossfilter(data);
var dataByDate = filterData.dimension(function(d) { return d.x; });
// we gotta make scales before even creating the SVG
// because the width will depend on the domain of the xScale
// set up scales
var xScale = d3.time.scale()
.domain([s,, 1)])
.range([0, w]);
var extent = d3.extent(data, function(d) { return d.y; });
var yScale = d3.scale.linear()
.range([h, axis]);
// change bar color depending on height, just for funsies
var funScale = d3.scale.linear()
.range(['#302e86', '#b6211d']);
// add the SVG
var svg ='#svg').append('svg')
width: xScale(e),
height: h
var mainGroup = svg.append('g')
.attr('id', 'scrollGroup');
var barsGroup = mainGroup.append('g')
.attr('id', 'barsGroup');
// initial plot with 1-day buffer on each side
plotBars(dataByDate.filter([, -1).valueOf(),, 2).valueOf()]).top(Infinity));
// factor out bar plotting into a function that binds data
// and creates or deletes bars as necessary given the current data
function plotBars(data) {
var currBars = barsGroup.selectAll('rect')
.data(data, function(d) { return d.x; });
x: function(d) {
return xScale(d.x) - barWidth/2;
y: function(d) {
return yScale(d.y);
width: barWidth,
height: function(d) {
return h - yScale(d.y);
fill: function(d) {
return funScale(d.y);
// use a dispatcher to handle and trigger refiltering data and plotting bars
var dispatcher = d3.dispatch('plotBars');
dispatcher.on('plotBars', function(date) {
// refilter data based on current position and plot
plotBars(dataByDate.filter([, -1).valueOf(),, 2).valueOf()]).top(Infinity));
// add an axis
// customize the time formatting because zero-padding bugs the stuffing out of me...
// i.e., this is not necessary ;)
var format = d3.time.format.multi([
[".%L", function(d) { return d.getMilliseconds(); }],
[":%S", function(d) { return d.getSeconds(); }],
["%-I:%M", function(d) { return d.getMinutes(); }],
["%-I %p", function(d) { return d.getHours(); }],
["%a %-d", function(d) { return d.getDay() && d.getDate() != 1; }],
["%b %-d", function(d) { return d.getDate() != 1; }],
["%b", function(d) { return true; }]
// here's the good stuff
// the xScale for the axis needs to use the entire domain
// and the entire range of our reallllly wide SVG
// which we can find the end of using the xScale
var axisScale = d3.time.scale()
.range([0, xScale(e)]);
var xAxis = d3.svg.axis()
.ticks(d3.time.hours, 6)
.attr('id', 'axisGroup')
// now we add programmatic panning by a day backwards and forwards
// to move one day per button press, we find the width that equals scrolling a day
// this is equal to the xScale domain, which is the width
var scrollWidth = w, scrollContainer ='#svg');
function panBack() {
var currentScrollLeft ='scrollLeft');
.tween('pan', function() {
var ix = d3.interpolate(currentScrollLeft, currentScrollLeft - scrollWidth);
return function(t) {'scrollLeft', ix(t));
}'#backbtn').on('click', panBack);
function panForward() {
var currentScrollLeft ='scrollLeft');
.tween('pan', function() {
var ix = d3.interpolate(currentScrollLeft, currentScrollLeft + scrollWidth);
return function(t) {'scrollLeft', ix(t));
}'#forwardbtn').on('click', panForward);
scrollContainer.on('scroll', function() {
// trigger refilter and plot of data based on current date
var date = xScale.invert('scrollLeft'));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment