var maxBlockWidth = 80; var panelWidth = maxBlockWidth * 4; var height = 200; var width = 960; var iOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent); var visibleRange = [panelWidth, 2 * panelWidth]; var visibleLow, visibleHigh; function pluck(array, prop) { var i; var result = []; for(i = 0; i < array.length; i++) { result.push(array[i][prop]); } return result; } function constant(c) { return function() { return c; }; } function start() { var previousLow, previousHigh, targetLow, targetHigh, kineticInProgress; function startTrackX(x) { kineticInProgress = false; trackX(x); } function trackX(x) { if(kineticInProgress) return; previousLow = visibleLow; previousHigh = visibleHigh; var low = visibleRange[0] - x; var high = visibleRange[1] - x; visibleLow = low; visibleHigh = high; renderSlippy(); } function startKineticX(x) { kineticInProgress = true; previousLow = visibleLow; previousHigh = visibleHigh; var low = visibleRange[0] - x; var high = visibleRange[1] - x; targetLow = low; targetHigh = high; visibleLow = targetLow; visibleHigh = targetHigh; renderSlippy(); } function endKineticX() { if(!kineticInProgress) { return; } kineticInProgress = false; visibleLow = targetLow; visibleHigh = targetHigh; renderSlippy(); } var tserSet = new TserSet(100, 160); var svgWidth = d3.max(pluck(tserSet.value, 'xWidth')); var xScale = d3.scale.linear() .domain(extentUnion(pluck(tserSet.value, 'xDomain'))) .range([0, svgWidth]); var yScale = d3.scale.linear() .domain(extentUnion(pluck(tserSet.value, 'yDomain'))) .range([height, 0]); var body = d3.selectAll('body'); var container = d3u.bind(body, 'div', constant([{key:0}]), ['container']); container.entered .style('width', width + 'px') .style('height', height + 'px') .style('overflow', 'hidden'); var stationary = d3u.bind(container, 'div', constant([{key:0, drag:{}}]), ['stationary']); stationary.entered .style('width', width + 'px') .style('height', height + 'px') .style('overflow', 'hidden'); var standingSvg = d3u.bind(stationary, 'svg', d3u.repeat, ['standing-svg']); standingSvg.entered .attr('width', svgWidth) .attr('height', height) .style('position', 'absolute') .style('top', '0px'); var visibleBoundaries = d3u.bind(standingSvg, 'line', constant(visibleRange), ['border']); visibleBoundaries.entered .attr('x1', function(d, i) {return i ? d : d + maxBlockWidth;}) .attr('x2', function(d, i) {return i ? d : d + maxBlockWidth;}) .attr('y1', yScale.range()[0]) .attr('y2', yScale.range()[1]); var slipping = d3u.bind(stationary, 'div', constant([{key:0, drag:{}}]), ['slipping']); slipping.entered .style('position', 'absolute') .style('top', '0px'); var movingSvg = d3u.bind(slipping, 'svg', d3u.repeat, ['moving-svg']); movingSvg .attr('width', svgWidth) .attr('height', height); var infiniteCanvas = new InfiniteCanvas({ maxPanelWidth: maxBlockWidth, canvasWidth: panelWidth, height: height, visibleRange: visibleRange, root: slipping.node() }); var movingBackgroundGroup = d3u.bind(movingSvg, 'g', constant([{key: 0}]), ['movingBackgroundGroup']); var movingBackground = d3u.bind(movingBackgroundGroup, 'rect', d3u.repeat, ['movingBackground']); movingBackground.entered .attr('width', svgWidth) .attr('height', height); var xAxisLayer = d3u.bind(movingSvg, 'g', d3u.repeat, ['xAxisLayer']); var ticksPerUnit = 0.01; var xAxis = d3.svg.axis().scale(xScale).orient('top').ticks(ticksPerUnit * svgWidth); renderAxisX(xAxisLayer); function renderAxisX(layer) { layer.entered .attr('transform', d3u.translateSVG(constant(0), constant(height))) .call(xAxis); } function renderSlippy() { // Render children renderPanels(); } var fastScaleX = d3u.scaleMaker(xScale.domain()[0], xScale.domain()[1], xScale.range()[0], xScale.range()[1]); var fastScaleY = d3u.scaleMaker(yScale.domain()[0], yScale.domain()[1], yScale.range()[0], yScale.range()[1]); var pipeline = []; function buildPanelCache(tserSet) { var block, innerValue, value = tserSet.value, result = {}, key; for(var i = 0; i < value.length; i++) { innerValue = value[i].value; for(var j = 0; j < innerValue.length; j++) { block = innerValue[j]; key = panelWidth * Math.floor(block.from / panelWidth) if(!result[key]) result[key] = []; result[key].push(block); } } return result; } var k = 0; var prevKey = null; var panelCache = buildPanelCache(tserSet); function renderPanels() { var key; if(previousLow < visibleLow) { key = panelWidth * Math.floor(visibleHigh / panelWidth); } else // if (previousHigh > visibleHigh) { key = panelWidth * Math.floor(visibleLow / panelWidth); } if(prevKey === key) return; prevKey = key; pipeline = panelCache[key] || []; k = pipeline.length; infiniteCanvas.updateCanvasPositions(visibleLow, visibleHigh); requestAnimationFrame(renderSnippets); } var contexts = infiniteCanvas.getContexts(); contexts.map(function(context) { context.beginPath(); context.strokeStyle = 'black'; context.globalAlpha = 0.1; }); function draw(context, offset, tser) { var j; context.moveTo(fastScaleX(offset), fastScaleY(tser[0])); for (j = 1; j < tser.length; j++) { context.lineTo(fastScaleX(offset + j), fastScaleY(tser[j])); } } function renderSnippets(timestamp) { var d, key, tser, offset, renderedSomething = false; if(k) { do { d = pipeline[--k]; offset = d.from; tser = d.value; key = d.key; var canvas = infiniteCanvas.getCanvas(offset); offset = offset % panelWidth; var cache = infiniteCanvas.cache(canvas); if(!cache[key]) { draw(infiniteCanvas.context(canvas), offset, tser); renderedSomething = true; cache[key] = true; } else {} } while (!(iOS && renderedSomething) && performance.now() - timestamp < 10 && k); } contexts.forEach(function(context) { context.stroke(); context.beginPath(); }); if(k) { requestAnimationFrame(renderSnippets); } } slipping.call( panBehavior, startTrackX, trackX, startKineticX, endKineticX, trackX ); }