Skip to content

Instantly share code, notes, and snippets.

@Fil
Last active March 7, 2017 14:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Fil/a8cfbbfd0100d38241beb48d23c9d4d1 to your computer and use it in GitHub Desktop.
Save Fil/a8cfbbfd0100d38241beb48d23c9d4d1 to your computer and use it in GitHub Desktop.
Dynamic Simplification IV zoom to feature [UNLISTED]
license: gpl-3.0
<!DOCTYPE html>
<div style="position: absolute">
<button id="resetZoom">Reset zoom</button>
<p style="display: inline-block">Selected id: <span id="selectedId"></span></p>
</div>
<canvas id="render" width="960" height="500"></canvas>
<canvas id="hidden" width="960" height="500"></canvas>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/topojson-client@2"></script>
<script src="https://unpkg.com/topojson-simplify@2"></script>
<script>
var canvas = d3.select("canvas#render"),
hidden = d3.select("canvas#hidden"),
context = canvas.node().getContext("2d"),
contextHdn = hidden.node().getContext("2d"),
width = canvas.property("width"),
height = canvas.property("height");
var selectedId = null,
selectedFeature = null,
selectedSpan = document.getElementById('selectedId'),
resetButton = d3.select("button#resetZoom");
var land,
borders;
var scale,
translate,
visibleArea, // minimum area threshold for points inside viewport
invisibleArea; // minimum area threshold for points outside viewport
// transform = d3.geoIdentity().clipExtent([[0, 0], [width, height]]),
var simplify = d3.geoTransform({
point: function(x, y, z) {
if (z < visibleArea) return
x = x * scale + translate[0]
y = y * scale + translate[1]
if (x >= -10 &&
x <= width + 10 &&
y >= -10 &&
y <= height + 10 ||
z >= invisibleArea) {
this.stream.point(x, y)
}
}
})
simplify.invert = function(u) {
return [(u[0] - translate[0])/scale, (u[1] - translate[1])/scale]
}
//*** Original zoom from Mike Bostock ***//
//*** https://bl.ocks.org/mbostock/7755778 ***//
// An arbitrary scale and center point to set the initial view.
// This projection is baked into the TopoJSON file,
// but is used here to compute the desired zoom translate.
var backed = {
projection: d3.geoMercator().translate([0, 0]).scale(4000)
}
var zoom = d3.zoom()
.on("zoom", zoomed);
// styling
context.lineJoin = "round";
context.lineCap = "round";
var styles = {
fill: "#bbb",
stroke: "#fff",
selected: {
fill: 'rgba(0, 0, 0, 0.5)',
stroke: 'red'
}
}
/*
var path = d3.geoPath()
.projection({
stream: function(s) { return simplify.stream(transform.stream(s)); }
})
.context(context);
*/
var path = d3.geoPath()
.projection(simplify)
.context(context)
d3.json("us-states.json", function(error, us) {
if (error) throw error;
topojson.presimplify(us);
land = topojson.feature(us, us.objects.states)
borders = topojson.mesh(us, us.objects.states, (a, b) => a !== b)
canvas
.call(zoom)
.on('click', onClick)
resetButton.on('click', () => {
// For exemple, it's NY latlng
zoomTo([-75.959, 38.250])
})
// Init, zoom to NY
resetButton.dispatch('click')
// d3.timer(draw)
});
function zoomed(d) {
var z = d3.event.transform
translate = [z.x, z.y]
scale = z.k
visibleArea = 1 / scale / scale
invisibleArea = 200 * visibleArea
draw()
}
function onClick () {
getHiddenData()
zoomTo(selectedFeature)
}
d3.interval(function(){
let n = land.features.length;
selectedFeature = land.features[Math.floor(n * Math.random())];
zoomTo(selectedFeature)
}, 1500)
// Retrieve the id with the color, and so, the feature. https://bl.ocks.org/Lacroute/579bc326fb547110a959c0a9ac2b30ce
function getHiddenData() {
path.context(contextHdn)
drawHidden()
let mouse = d3.mouse(canvas.node())
let data = contextHdn.getImageData(mouse[0], mouse[1], 1, 1).data
selectedId = data[0]
selectedFeature = land.features.find(f => +f.id === selectedId)
path.context(context)
}
// Zoom to bounding box of the selected area.
function zoomTo (location) {
if (location === undefined) return
let point
if (!Array.isArray(location)) {
console.log('Feature')
location = backed.projection.invert(simplify.invert(path.centroid(location)))
}
point = backed.projection(location)
console.log('location', location, 'point', point)
let sc = 0.5 // TODO compute the right scale
// Apply the new transform
canvas.transition()
.duration(750)
.call(
zoom.transform,
d3.zoomIdentity
.translate(width / 2 - point[0] * sc, height / 2 - point[1] * sc)
.scale(sc)
)
}
// Main drawing loop
function draw () {
context.clearRect(0, 0, width, height);
context.fillStyle = styles.fill;
context.strokeStyle = styles.stroke;
context.beginPath();
path(land)
context.fill();
context.beginPath();
path(borders);
context.stroke();
if (selectedFeature) drawSelected()
drawCenter()
}
// Highlight the selected feature
function drawSelected () {
selectedSpan.innerHTML = selectedId
context.fillStyle = styles.selected.fill;
context.strokeStyle = styles.selected.stroke;
context.beginPath();
path(selectedFeature)
context.fill();
context.stroke();
// Draw his centroid
context.beginPath()
let cent = path.centroid(selectedFeature)
context.arc(cent[0], cent[1], 3, 0, 2 * Math.PI)
context.fillStyle = 'green'
context.strokeStyle = 'white'
context.stroke()
context.fill()
}
// Draw a cross in the center of the canvas
function drawCenter () {
context.beginPath();
let x = width / 2
let y = height / 2
context.strokeStyle = 'green';
context.moveTo(x - 10, y - 10);
context.lineTo(x + 10, y + 10);
context.stroke();
context.moveTo(x + 10, y - 10);
context.lineTo(x - 10, y + 10);
context.stroke();
}
// Drawing loop to encode id to color
function drawHidden() {
contextHdn.clearRect(0, 0, width, height);
land.features.map(f => {
contextHdn.beginPath();
path(f);
// basic exemple because there is less than 255 counties
contextHdn.fillStyle = `rgba(${f.id}, 0, 0, 1)`
contextHdn.fill();
})
}
</script>
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment