Last active
January 21, 2022 04:32
-
-
Save chriskoiak/d3de95e270a4cc186046 to your computer and use it in GitHub Desktop.
d3 Virtual Horizontal Scrolling Plugin (based on http://bl.ocks.org/billdwhite/36d15bc6126e6f6365d0)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8" /> | |
<title>Virtual Horizontal Scrolling Demo</title> | |
<style> | |
html, body { | |
width: 100%; | |
height: 100%; | |
margin: 0; | |
font-family: sans-serif; | |
font-size: 12px; | |
} | |
.viewport { | |
position: absolute; | |
top: 15px; | |
left: 15px; | |
overflow-y: auto; | |
width: 280px; | |
height: 400px; | |
background-color: #e8e8e8; | |
border: 1px solid #AAAAAA; | |
border-radius: 4px; | |
box-shadow: inset 1px 1px 6px 2px rgba(0,0,0, .25); | |
} | |
.scroll-svg { | |
} | |
.longscroll .row { | |
font-family: Arial; | |
font-size: 11px; | |
height: 19px; | |
padding: 0 8px; | |
border-bottom: solid #eee 1px; | |
} | |
.information { | |
position: absolute; | |
top: 15px; | |
left: 300px; | |
width: 350px; | |
height: 400px; | |
} | |
.info-svg { | |
fill: #2968AA; | |
overflow: visible; | |
} | |
.brace { | |
stroke: #2968AA; | |
stroke-width: 2px; | |
fill: none; | |
} | |
.infotext { | |
font-size: 20px; | |
} | |
</style> | |
<script src="http://d3js.org/d3.v3.min.js" type="text/javascript"></script> | |
<script src="virtualscroller.js"></script> | |
</head> | |
<body> | |
<div class="viewport"></div> | |
<div class="information"></div> | |
<script type="text/javascript"> | |
d3.json("states.json", function (states) { | |
var colorScale = d3.scale.category20(); | |
var scrollSVG = d3.select(".viewport").append("svg") | |
.attr("class", "scroll-svg"); | |
var chartGroup = scrollSVG.append("g") | |
.attr("class", "chartGroup"); | |
//.attr("filter", "url(#dropShadow1)"); // sometimes causes issues in chrome | |
chartGroup.append("rect") | |
.attr("fill", "#FFFFFF"); | |
var rowEnter = function(rowSelection) { | |
rowSelection.append("rect") | |
.attr("rx", 3) | |
.attr("ry", 3) | |
.attr("width", "24") | |
.attr("height", "250") | |
.attr("fill-opacity", 0.25) | |
.attr("stroke", "#999999") | |
.attr("stroke-width", "2px"); | |
rowSelection.append("text") | |
.attr("transform", "translate(10,15)rotate(90)"); | |
}; | |
var rowUpdate = function(rowSelection) { | |
rowSelection.select("rect") | |
.attr("fill", function(d) { | |
return colorScale(d.id); | |
}); | |
rowSelection.select("text") | |
.text(function (d) { | |
return (d.index + 1) + ". " + d.label; | |
}); | |
}; | |
var rowExit = function(rowSelection) { | |
}; | |
var virtualScroller = d3.VirtualScroller() | |
.columnWidth(60) | |
.enter(rowEnter) | |
.update(rowUpdate) | |
.exit(rowExit) | |
.svg(scrollSVG) | |
.totalColumns(50) | |
.viewport(d3.select(".viewport")); | |
// tack on index to each data item for easy to read display | |
states.items.forEach(function(nextState, i) { | |
nextState.index = i; | |
}); | |
virtualScroller.data(states.items, function(d) { return d.id; }); | |
chartGroup.call(virtualScroller); | |
}); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{"identifier":"id", | |
"label": "label", | |
"items": [ | |
{"name":"Alabama", "label":"Alabama","id":"AL"}, | |
{"name":"Alaska", "label":"Alaska","id":"AK"}, | |
{"name":"Arizona", "label":"Arizona","id":"AZ"}, | |
{"name":"Arkansas", "label":"Arkansas","id":"AR"}, | |
{"name":"California", "label":"California","id":"CA"}, | |
{"name":"Colorado", "label":"Colorado","id":"CO"}, | |
{"name":"Connecticut", "label":"Connecticut","id":"CT"}, | |
{"name":"Delaware", "label":"Delaware","id":"DE"}, | |
{"name":"Florida", "label":"Florida","id":"FL"}, | |
{"name":"Georgia", "label":"Georgia","id":"GA"}, | |
{"name":"Hawaii", "label":"Hawaii","id":"HI"}, | |
{"name":"Idaho", "label":"Idaho","id":"ID"}, | |
{"name":"Illinois", "label":"Illinois","id":"IL"}, | |
{"name":"Indiana", "label":"Indiana","id":"IN"}, | |
{"name":"Iowa", "label":"Iowa","id":"IA"}, | |
{"name":"Kansas", "label":"Kansas","id":"KS"}, | |
{"name":"Kentucky", "label":"Kentucky","id":"KY"}, | |
{"name":"Louisiana", "label":"Louisiana","id":"LA"}, | |
{"name":"Maine", "label":"Maine","id":"ME"}, | |
{"name":"Maryland", "label":"Maryland","id":"MD"}, | |
{"name":"Massachusetts", "label":"Massachusetts","id":"MA"}, | |
{"name":"Michigan", "label":"Michigan","id":"MI"}, | |
{"name":"Minnesota", "label":"Minnesota","id":"MN"}, | |
{"name":"Mississippi", "label":"Mississippi","id":"MS"}, | |
{"name":"Missouri", "label":"Missouri","id":"MO"}, | |
{"name":"Montana", "label":"Montana","id":"MT"}, | |
{"name":"Nebraska", "label":"Nebraska","id":"NE"}, | |
{"name":"Nevada", "label":"Nevada","id":"NV"}, | |
{"name":"New Hampshire", "label":"New Hampshire","id":"NH"}, | |
{"name":"New Jersey", "label":"New Jersey","id":"NJ"}, | |
{"name":"New Mexico", "label":"New Mexico","id":"NM"}, | |
{"name":"New York", "label":"New York","id":"NY"}, | |
{"name":"North Carolina", "label":"North Carolina","id":"NC"}, | |
{"name":"North Dakota", "label":"North Dakota","id":"ND"}, | |
{"name":"Ohio", "label":"Ohio","id":"OH"}, | |
{"name":"Oklahoma", "label":"Oklahoma","id":"OK"}, | |
{"name":"Oregon", "label":"Oregon","id":"OR"}, | |
{"name":"Pennsylvania", "label":"Pennsylvania","id":"PA"}, | |
{"name":"Rhode Island", "label":"Rhode Island","id":"RI"}, | |
{"name":"South Carolina", "label":"South Carolina","id":"SC"}, | |
{"name":"South Dakota", "label":"South Dakota","id":"SD"}, | |
{"name":"Tennessee", "label":"Tennessee","id":"TN"}, | |
{"name":"Texas", "label":"Texas","id":"TX"}, | |
{"name":"Utah", "label":"Utah","id":"UT"}, | |
{"name":"Vermont", "label":"Vermont","id":"VT"}, | |
{"name":"Virginia", "label":"Virginia","id":"VA"}, | |
{"name":"Washington", "label":"Washington","id":"WA"}, | |
{"name":"West Virginia", "label":"West Virginia","id":"WV"}, | |
{"name":"Wisconsin", "label":"Wisconsin","id":"WI"}, | |
{"name":"Wyoming", "label":"Wyoming","id":"WY"} | |
]} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
d3.VirtualScroller = function () { | |
var enter = null, | |
update = null, | |
exit = null, | |
data = [], | |
dataid = null, | |
svg = null, | |
viewport = null, | |
totalColumns = 0, | |
position = 0, | |
columnWidth = 24, | |
totalWidth = 0, | |
minWidth = 0, | |
viewportWidth = 0, | |
visibleColumns = 0, | |
delta = 0, | |
dispatch = d3.dispatch("pageDown", "pageUp"); | |
function virtualscroller(container) { | |
function render(resize) { | |
if (resize) { // re-calculate height of viewport and # of visible row | |
viewportWidth = parseInt(viewport.style("width")); | |
visibleColumns = Math.ceil(viewportWidth / columnWidth) + 1; // add 1 more row for extra overlap; avoids visible add/remove at top/bottom | |
} | |
var scrollTop = viewport.node().scrollLeft; | |
totalWidth = Math.max(minWidth, (totalColumns * columnWidth)); | |
svg.style("width", totalWidth + "px") // both style and attr height values seem to be respected | |
.attr("width", totalWidth) | |
.attr("height", "300px"); | |
var lastPosition = position; | |
position = Math.floor(scrollTop / columnWidth); | |
delta = position - lastPosition; | |
scrollRenderFrame(position); | |
} | |
function scrollRenderFrame(scrollPosition) { | |
container.attr("transform", "translate(" + (scrollPosition * columnWidth) + ",0)"); // position viewport to stay visible | |
var position0 = Math.max(0, Math.min(scrollPosition, totalColumns - visibleColumns + 1)), // calculate positioning (use + 1 to offset 0 position vs totalRow count diff) | |
position1 = position0 + visibleColumns; | |
container.each(function () { // slice out visible rows from data and display | |
var rowSelection = container.selectAll(".column") | |
.data(data.slice(position0, Math.min(position1, totalColumns)), dataid); | |
rowSelection.exit().call(exit).remove(); | |
rowSelection.enter().append("g") | |
.attr("class", "column") | |
.call(enter); | |
rowSelection.order(); | |
var rowUpdateSelection = container.selectAll(".column:not(.transitioning)"); // do not position .transitioning elements | |
rowUpdateSelection.call(update); | |
rowUpdateSelection.each(function (d, i) { | |
d3.select(this).attr("transform", function (d) { | |
return "translate(" + ((i * columnWidth)) + ",0)"; | |
}); | |
}); | |
}); | |
if (position1 > (data.length - visibleColumns)) { // dispatch events | |
dispatch.pageDown({ | |
delta: delta | |
}); | |
} else if (position0 < visibleColumns) { | |
dispatch.pageUp({ | |
delta: delta | |
}); | |
} | |
} | |
virtualscroller.render = render; // make render function publicly visible | |
viewport.on("scroll.virtualscroller", render); // call render on scrolling event | |
render(true); // call render() to start | |
} | |
virtualscroller.render = function (resize) { // placeholder function that is overridden at runtime | |
}; | |
virtualscroller.data = function (_, __) { | |
if (!arguments.length) return data; | |
data = _; | |
dataid = __; | |
return virtualscroller; | |
}; | |
virtualscroller.dataid = function (_) { | |
if (!arguments.length) return dataid; | |
dataid = _; | |
return virtualscroller; | |
}; | |
virtualscroller.enter = function (_) { | |
if (!arguments.length) return enter; | |
enter = _; | |
return virtualscroller; | |
}; | |
virtualscroller.update = function (_) { | |
if (!arguments.length) return update; | |
update = _; | |
return virtualscroller; | |
}; | |
virtualscroller.exit = function (_) { | |
if (!arguments.length) return exit; | |
exit = _; | |
return virtualscroller; | |
}; | |
virtualscroller.totalColumns = function (_) { | |
if (!arguments.length) return totalColumns; | |
totalColumns = _; | |
return virtualscroller; | |
}; | |
virtualscroller.columnWidth = function (_) { | |
if (!arguments.length) return columnWidth; | |
columnWidth = +_; | |
return virtualscroller; | |
}; | |
virtualscroller.totalWidth = function (_) { | |
if (!arguments.length) return totalWidth; | |
totalWidth = +_; | |
return virtualscroller; | |
}; | |
virtualscroller.minWidth = function (_) { | |
if (!arguments.length) return minWidth; | |
minWidth = +_; | |
return virtualscroller; | |
}; | |
virtualscroller.position = function (_) { | |
if (!arguments.length) return position; | |
position = +_; | |
if (viewport) { | |
viewport.node().scrollTop = position; | |
} | |
return virtualscroller; | |
}; | |
virtualscroller.svg = function (_) { | |
if (!arguments.length) return svg; | |
svg = _; | |
return virtualscroller; | |
}; | |
virtualscroller.viewport = function (_) { | |
if (!arguments.length) return viewport; | |
viewport = _; | |
return virtualscroller; | |
}; | |
virtualscroller.delta = function () { | |
return delta; | |
}; | |
d3.rebind(virtualscroller, dispatch, "on"); | |
return virtualscroller; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here's an example of the rendered page