Using d3-tile and an SVG transform in combination. Many thanks for Mike Bostock and Noah Veltman respectively.
Tiles copyright OpenStreetMap contributors and Stamen design.
license: gpl-3.0 |
Using d3-tile and an SVG transform in combination. Many thanks for Mike Bostock and Noah Veltman respectively.
Tiles copyright OpenStreetMap contributors and Stamen design.
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
body { | |
margin: 0; | |
} | |
path { | |
fill: none; | |
stroke: red; | |
stroke-linejoin: round; | |
stroke-width: 1.5px; | |
} | |
</style> | |
<svg> | |
<defs> | |
<filter id="duotone" color-interpolation-filters="sRGB"> | |
<feColorMatrix type="saturate" values="0" /> | |
<feColorMatrix type="matrix" /> | |
</filter> | |
</defs> | |
</svg> | |
<script src="//d3js.org/d3.v4.0.0-rc.1.min.js"></script> | |
<script src="//d3js.org/d3-tile.v0.0.min.js"></script> | |
<script src="//d3js.org/topojson.v1.min.js"></script> | |
<script> | |
var pi = Math.PI, | |
tau = 2 * pi; | |
var width = Math.max(960, window.innerWidth), | |
height = Math.max(500, window.innerHeight); | |
// Initialize the projection to fit the world in a 1×1 square centered at the origin. | |
var projection = d3.geoMercator() | |
.scale(1 / tau) | |
.translate([0, 0]); | |
var path = d3.geoPath() | |
.projection(projection); | |
var tile = d3.tile() | |
.size([width, height]); | |
var zoom = d3.zoom() | |
.scaleExtent([1 << 11, 1 << 14]) | |
.on("zoom", zoomed); | |
var svg = d3.select("svg") | |
.attr("width", width) | |
.attr("height", height); | |
var raster = svg.append("g"); | |
// Compute the projected initial center. | |
var center = projection([-77.03687,38.8919]); | |
// Apply a zoom transform equivalent to projection.{scale,translate,center}. | |
svg | |
.call(zoom) | |
.call(zoom.transform, d3.zoomIdentity | |
.translate(width / 2, height / 2) | |
.scale(1 << 22) | |
.translate(-center[0], -center[1])); | |
function zoomed() { | |
var transform = d3.event.transform; | |
var tiles = tile | |
.scale(transform.k) | |
.translate([transform.x, transform.y]) | |
(); | |
projection | |
.scale(transform.k / tau) | |
.translate([transform.x, transform.y]); | |
var image = raster | |
.attr("transform", stringify(tiles.scale, tiles.translate)) | |
.selectAll("image") | |
.data(tiles, function(d) { return d; }); | |
image.exit().remove(); | |
image.enter().append("image") | |
.attr("xlink:href", function(d) { return "http://" + "abc"[d[1] % 3] + ".tile.stamen.com/toner/" + d[2] + "/" + d[0] + "/" + d[1] + ".png"; }) | |
.attr("x", function(d) { return d[0] * 256; }) | |
.attr("y", function(d) { return d[1] * 256; }) | |
.attr("width", 256) | |
.attr("height", 256) | |
.attr("filter","url(#duotone)"); | |
} | |
function type(d) { | |
return { | |
type: "Feature", | |
properties: {name: d.description, state: d.name}, | |
geometry: {type: "Point", coordinates: [+d.longitude, +d.latitude]} | |
}; | |
} | |
function stringify(scale, translate) { | |
var k = scale / 256, r = scale % 1 ? Number : Math.round; | |
return "translate(" + r(translate[0] * scale) + "," + r(translate[1] * scale) + ") scale(" + k + ")"; | |
} | |
var filter = d3.select("feColorMatrix:last-child"), | |
matrix = [ | |
[ 0, 0, 0, 0, 0 ], | |
[ 0, 0, 0, 0, 0 ], | |
[ 0, 0, 0, 0, 0 ], | |
[ 0, 0, 0, 1, 0 ], | |
]; | |
d3.timer(function(t){ | |
var bg, | |
fg; | |
t = t % 7500 / 7500; | |
if (t > 0.5) t = 1 - t; | |
fg = d3.rgb(d3.interpolateWarm(t)).darker(0.75); | |
bg = d3.rgb(d3.interpolateCool(1 - t)).brighter(0.1); | |
["r", "g", "b"].forEach(function(d, i){ | |
matrix[i][i] = (bg[d] - fg[d]) / 256; | |
matrix[i][4] = fg[d] / 256; | |
}); | |
filter.attr("values", matrix.join(" ")); | |
}); | |
</script> |