|
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 |
|
); |
|
} |