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> |