Pour @Lacroute
This map dynamically simplifies the geometry in response to zooming, so that the smallest displayed detail is approximately one square pixel. Use the mousewheel, or pinch on touch devices, to zoom.
There is a click handler that highlight a specific feature, and zoom to it.
forked from Lacroute's block: Dynamic Simplification IV with canvas selection
forked from mbostock's block: Dynamic Simplification IV
forked from Lacroute's block: Dynamic Simplification IV zoom to feature
xxxxxxxxxx
<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>
https://d3js.org/d3.v4.min.js
https://unpkg.com/topojson-client@2
https://unpkg.com/topojson-simplify@2