Using d3-brush to create an interactive minimap.
forked from tlfrd's block: Brush Minimap
license: mit |
Using d3-brush to create an interactive minimap.
forked from tlfrd's block: Brush Minimap
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script src="https://d3js.org/topojson.v2.min.js"></script> | |
<style> | |
body { margin: 0; position: fixed; top: 0; right: 0; bottom: 0; left: 0; } | |
.minimap { | |
fill: white; | |
stroke: black; | |
} | |
.land { | |
fill: green; | |
fill-opacity: 0.5; | |
} | |
.minimap-land { | |
fill: white; | |
stroke: black; | |
stroke-width: 0.5; | |
} | |
.exterior { | |
fill: none; | |
stroke: black; | |
stroke-width: 0.5; | |
} | |
.interior { | |
fill: none; | |
stroke: black; | |
stroke-opacity: 0.5; | |
stroke-width: 0.5; | |
} | |
.border { | |
fill: none; | |
stroke: black; | |
} | |
g.brush > .handle { | |
display: none; | |
} | |
.overlay { | |
pointer-events: none; | |
} | |
</style> | |
</head> | |
<body> | |
<script> | |
var margin = {top: 0, right: 0, bottom: 0, left: 0}; | |
var width = 960 - margin.left - margin.right, | |
height = 500 - margin.top - margin.bottom; | |
var minimapMargin = {right: 20, bottom: 20}; | |
var minimapWidth = width / 4, | |
minimapHeight = height / 4; | |
var svg = d3.select("body").append("svg") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom) | |
.append("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
var minimap = svg.append("g") | |
.attr("transform", "translate(" + (width - minimapWidth - minimapMargin.right) + "," + (height - minimapHeight- minimapMargin.bottom) + ")"); | |
minimap.append("rect") | |
.attr("class", "minimap") | |
.attr("width", minimapWidth) | |
.attr("height", minimapHeight); | |
var brush = d3.brush() | |
.extent([[0, 0], [minimapWidth, minimapHeight]]) | |
.on("start brush", brushed); | |
var projection = d3.geoEquirectangular(); | |
var path = d3.geoPath().projection(projection); | |
var miniProjection = d3.geoEquirectangular(); | |
var miniPath = d3.geoPath().projection(miniProjection); | |
var mapSource = "https://unpkg.com/world-atlas@1/world/110m.json" | |
d3.json(mapSource, function(error, world) { | |
if (error) throw error; | |
var land = topojson.feature(world, world.objects.land); | |
var exteriors = topojson.mesh(world, world.objects.countries, function(a, b) { return a == b; }); | |
var interiors = topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }); | |
projection.fitSize([width, height], land); | |
miniProjection.fitSize([minimapWidth, minimapHeight], land); | |
var landMap = svg.append("g") | |
.append("path") | |
.datum(land) | |
.attr("class", "land") | |
.attr("d", path); | |
var exteriors = svg.append("g") | |
.append("path") | |
.datum(exteriors) | |
.attr("class", "exterior") | |
.attr("d", path); | |
var interiors = svg.append("g") | |
.append("path") | |
.datum(interiors) | |
.attr("class", "interior") | |
.attr("d", path); | |
minimap.raise(); | |
minimap.append("g") | |
.append("path") | |
.datum(land) | |
.attr("class", "minimap-land") | |
.attr("d", miniPath); | |
minimap.append("g") | |
.attr("class", "brush") | |
.call(brush) | |
.call(brush.move, [[0, 0], [minimapWidth / 2, minimapHeight / 2]]); | |
}); | |
function brushed() { | |
var s = d3.event.selection, | |
c0 = s[0], | |
c1 = s[1]; | |
// convert to lat long | |
var mpc0 = miniProjection.invert(c0); | |
var mpc1 = miniProjection.invert(c1); | |
// convert to larger pixels | |
var pc0 = projection(mpc0); | |
var pc1 = projection(mpc1); | |
projection.center(mpc1); | |
svg.select(".land").attr("d", path); | |
svg.select(".interior").attr("d", path); | |
svg.select(".exterior").attr("d", path); | |
svg.select(".land").attr("transform", "scale(2)") | |
svg.select(".interior").attr("transform", "scale(2)") | |
svg.select(".exterior").attr("transform", "scale(2)") | |
} | |
</script> | |
</body> |