Skip to content

Instantly share code, notes, and snippets.

@aerrity
Forked from gabrielflorit/Makefile
Last active March 8, 2017 14:27
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 aerrity/3baaad32227b302b8662fb2316e4dd4c to your computer and use it in GitHub Desktop.
Save aerrity/3baaad32227b302b8662fb2316e4dd4c to your computer and use it in GitHub Desktop.
Equidistant map perimeter circles
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.
.buttons {
margin: 0 auto;
text-align: center;
}
.map svg {
display: block;
margin: 0 auto;
}
.map svg circle {
fill: #008000;
fill-opacity: 0.25;
stroke: #008000;
}
var margin={top:10,right:10,bottom:10,left:10};d3.json("./boundary.topojson",function(t){var e=topojson.feature(t,t.objects.boundary),n=(d3.geoBounds(e),d3.geoCentroid(e),d3.geoAlbers()),r=d3.geoPath().projection(n),a=r.bounds(e),o=(a[1][0]-a[0][0])/(a[1][1]-a[0][1]),i=460,c=i*o,u=c-margin.left-margin.right,l=i-margin.top-margin.bottom,d=d3.select(".map svg").attrs({width:c,height:i}).append("g").attr("transform","rotate(45) translate("+100+", "+-170+")");n.fitSize([u,l],e);var s=_(e.features).map("geometry").flatten().map("coordinates").flatten().sortBy("length").map(function(t){return turf.lineString(t)}).value(),f=_(s).map(function(t){return turf.lineDistance(t)}).sum(),m=1e3,g=m,p=_(s).map(function(t){var e=turf.lineDistance(t),n=Math.ceil(e*m/f),r=Math.min(g,n);g-=r;var a=e/r,o=d3.range(r).map(function(e){return turf.along(t,e*a)});return o}).flatten().map(function(t){return n(t.geometry.coordinates)}).value(),v=function(){var t=d.selectAll("circle").data(p);t.enter().append("circle").attrs({cx:u/2,cy:l/2,r:0}).transition("enter").duration(1e3).delay(function(t,e){return 10*e}).attrs({cx:function(t){return t[0]},cy:function(t){return t[1]},r:1})},y=function(){var t=d.selectAll("circle").data(p);t.transition("exit").duration(250).delay(function(t,e){return 2*e}).attrs({cx:u/2,cy:l/2,r:0}).remove()};v(),document.querySelector("button.enter").addEventListener("click",v),document.querySelector("button.exit").addEventListener("click",y)});
//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInNjcmlwdC5qcyJdLCJuYW1lcyI6WyJjb25zdCIsIm1hcmdpbiIsInRvcCIsInJpZ2h0IiwiYm90dG9tIiwibGVmdCIsImQzIiwianNvbiIsImZlYXR1cmUiLCJ0b3BvanNvbiIsIm9iamVjdHMiLCJib3VuZGFyeSIsInByb2plY3Rpb24iLCJnZW9Cb3VuZHMiLCJnZW9DZW50cm9pZCIsImdlb0FsYmVycyIsInBhdGgiLCJnZW9QYXRoIiwiYiIsImJvdW5kcyIsImFzcGVjdCIsIm91dGVySGVpZ2h0Iiwib3V0ZXJXaWR0aCIsIndpZHRoIiwiaGVpZ2h0IiwiZyIsInNlbGVjdCIsImF0dHJzIiwiYXBwZW5kIiwiYXR0ciIsImZpdFNpemUiLCJsaW5lU3RyaW5ncyIsIl8iLCJmZWF0dXJlcyIsIm1hcCIsImZsYXR0ZW4iLCJzb3J0QnkiLCJkIiwidHVyZiIsImxpbmVTdHJpbmciLCJ2YWx1ZSIsInRvdGFsTGVuZ3RoIiwibGluZURpc3RhbmNlIiwic3VtIiwicG9pbnRzQ291bnQiLCJwb2ludHNSZW1haW5pbmciLCJwb2ludHMiLCJsaW5lIiwibGluZUxlbmd0aCIsInVwcGVyQ291bnQiLCJNYXRoIiwiY2VpbCIsImxpbmVQb2ludHNDb3VudCIsIm1pbiIsInN0ZXAiLCJsaW5lUG9pbnRzIiwicmFuZ2UiLCJhbG9uZyIsImdlb21ldHJ5IiwiY29vcmRpbmF0ZXMiLCJlbnRlciIsImNpcmNsZXMiLCJzZWxlY3RBbGwiLCJkYXRhIiwiY3giLCJjeSIsInIiLCJ0cmFuc2l0aW9uIiwiZHVyYXRpb24iLCJkZWxheSIsImkiLCJleGl0IiwicmVtb3ZlIiwiZG9jdW1lbnQiLCJxdWVyeVNlbGVjdG9yIiwiYWRkRXZlbnRMaXN0ZW5lciJdLCJtYXBwaW5ncyI6IkFBQ0FBLEdBQU1DLFNBQVdDLElBQU8sR0FBRUMsTUFBUyxHQUFFQyxPQUFVLEdBQUVDLEtBQVEsR0FHekRDLElBQUdDLEtBQUssc0JBQXVCLFNBQUFBLEdBRTlCUCxHQUFNUSxHQUFVQyxTQUFTRCxRQUFRRCxFQUFNQSxFQUFLRyxRQUFRQyxVQVc5Q0MsR0FSV04sR0FBQ08sVUFBVUwsR0FDVEYsR0FBQ1EsWUFBWU4sR0FPWEYsR0FBQ1MsYUFHaEJDLEVBQVNWLEdBQUNXLFVBQVVMLFdBQVdBLEdBRzlCTSxFQUFHRixFQUFLRyxPQUFPWCxHQUdoQlksR0FBWUYsRUFBRSxHQUFHLEdBQUtBLEVBQUUsR0FBRyxLQUFPQSxFQUFFLEdBQUcsR0FBS0EsRUFBRSxHQUFHLElBRWpERyxFQUFjLElBQ2RDLEVBQWFELEVBQWNELEVBRTNCRyxFQUFRRCxFQUFhckIsT0FBT0ksS0FBT0osT0FBT0UsTUFDMUNxQixFQUFTSCxFQUFjcEIsT0FBT0MsSUFBTUQsT0FBT0csT0FHMUNxQixFQUFLbkIsR0FBQ29CLE9BQU8sWUFDakJDLE9BQVFKLE1BQU9ELEVBQVlFLE9BQVFILElBQ3BDTyxPQUFPLEtBQ05DLEtBQUssWUFBYSxhQUFXNUIsT0FBUyxLQUFBLEtBQUlBLE9BQUcsSUFBQSxJQUdoRFcsR0FBV2tCLFNBQVNQLEVBQU9DLEdBQVNoQixFQUdwQ1IsSUFBTStCLEdBQWdCQyxFQUFBeEIsRUFBUXlCLFVBQzVCQyxJQUFJLFlBQ0pDLFVBQ0FELElBQUksZUFDSkMsVUFDQUMsT0FBTyxVQUNQRixJQUFJLFNBQUFHLEdBQUEsTUFBQUMsTUFBQUMsV0FBRUYsS0FDTkcsUUFHSUMsRUFBZ0JULEVBQUFELEdBQ3BCRyxJQUFJLFNBQUFHLEdBQUEsTUFBQUMsTUFBQUksYUFBS0wsS0FDVE0sTUFHSUMsRUFBYyxJQUNoQkMsRUFBa0JELEVBRWhCRSxFQUFXZCxFQUFBRCxHQUNmRyxJQUFJLFNBQUFhLEdBS0ovQyxHQUFNZ0QsR0FBYVYsS0FBS0ksYUFBYUssR0FHL0JFLEVBQWFDLEtBQUtDLEtBQUtILEVBQWFKLEVBQWNILEdBR2xEVyxFQUFrQkYsS0FBS0csSUFBSVIsRUFBaUJJLEVBR2xESixJQUFtQk8sQ0FJbkJwRCxJQUFNc0QsR0FBT04sRUFBYUksRUFFcEJHLEVBQWVqRCxHQUFDa0QsTUFBTUosR0FDMUJsQixJQUFJLFNBQUFHLEdBQUEsTUFBQUMsTUFBQW1CLE1BQUVWLEVBQUdWLEVBQUlpQixJQUVmLE9BQU9DLEtBR1BwQixVQUNBRCxJQUFJLFNBQUFHLEdBQUEsTUFBQXpCLEdBQUV5QixFQUFBcUIsU0FBR0MsZUFDVG5CLFFBR0lvQixFQUFRLFdBR2I1RCxHQUFNNkQsR0FBWXBDLEVBQUFxQyxVQUFVLFVBQ3pCQyxLQUFLakIsRUFHUmUsR0FBUUQsUUFBUWhDLE9BQU8sVUFDcEJELE9BQ0FxQyxHQUFJekMsRUFBTSxFQUNWMEMsR0FBSXpDLEVBQU8sRUFDWDBDLEVBQUcsSUFFSkMsV0FBVyxTQUNWQyxTQUFTLEtBQ1RDLE1BQU0sU0FBQWhDLEVBQUFpQyxHQUFBLE1BQUssSUFBSkEsSUFDUDNDLE9BQ0FxQyxHQUFJLFNBQUEzQixHQUFBLE1BQUFBLEdBQUEsSUFDSjRCLEdBQUksU0FBQTVCLEdBQUEsTUFBQUEsR0FBQSxJQUNKNkIsRUFBRyxLQU1ESyxFQUFPLFdBR1p2RSxHQUFNNkQsR0FBWXBDLEVBQUFxQyxVQUFVLFVBQ3pCQyxLQUFLakIsRUFHUmUsR0FDRU0sV0FBVyxRQUNWQyxTQUFTLEtBQ1RDLE1BQU0sU0FBQWhDLEVBQUFpQyxHQUFBLE1BQUssR0FBSkEsSUFDUDNDLE9BQ0FxQyxHQUFJekMsRUFBTSxFQUNWMEMsR0FBSXpDLEVBQU8sRUFDWDBDLEVBQUcsSUFFSk0sU0FLSFosS0FHQWEsU0FBU0MsY0FBYyxnQkFBZ0JDLGlCQUFpQixRQUFTZixHQUNqRWEsU0FBU0MsY0FBYyxlQUFlQyxpQkFBaUIsUUFBU0oiLCJmaWxlIjoic2NyaXB0LmpzIiwic291cmNlc0NvbnRlbnQiOlsiLy8gU2V0dXAgY2hhcnQgZGltZW5zaW9ucy5cbmNvbnN0IG1hcmdpbiA9IHsgdG9wOiAxMCwgcmlnaHQ6IDEwLCBib3R0b206IDEwLCBsZWZ0OiAxMCB9XG5cbi8vIEdldCBHZW9KU09OLlxuZDMuanNvbignLi9ib3VuZGFyeS50b3BvanNvbicsIGpzb24gPT4ge1xuXG5cdGNvbnN0IGZlYXR1cmUgPSB0b3BvanNvbi5mZWF0dXJlKGpzb24sIGpzb24ub2JqZWN0cy5ib3VuZGFyeSlcblxuXHQvLyBHZXQgZmVhdHVyZSdzIGJvdW5kcyBhbmQgY2VudHJvaWQuXG5cdGNvbnN0IGJvdW5kcyA9IGQzLmdlb0JvdW5kcyhmZWF0dXJlKVxuXHRjb25zdCBjZW50cm9pZCA9IGQzLmdlb0NlbnRyb2lkKGZlYXR1cmUpXG5cblx0Ly8gY29uc3QgcHJvamVjdGlvbiA9IGQzLmdlb0NvbmljQ29uZm9ybWFsKClcblx0Ly8gXHQucGFyYWxsZWxzKFtib3VuZHNbMF1bMV0sIGJvdW5kc1sxXVsxXV0pXG5cdC8vIFx0LnJvdGF0ZShbLWNlbnRyb2lkWzBdLCAwXSlcblx0Ly8gXHQuY2VudGVyKFswLCAtY2VudHJvaWRbMV1dKVxuXG5cdGNvbnN0IHByb2plY3Rpb24gPSBkMy5nZW9BbGJlcnMoKVxuXG5cdC8vIEdldCB0aGUgcGF0aC5cblx0Y29uc3QgcGF0aCA9IGQzLmdlb1BhdGgoKS5wcm9qZWN0aW9uKHByb2plY3Rpb24pXG5cblx0Ly8gR2V0IHRoZSBwYXRoJ3MgYm91bmRzIChpLmUuLCBpbiBwaXhlbHMpLlxuXHRjb25zdCBiID0gcGF0aC5ib3VuZHMoZmVhdHVyZSlcblxuXHQvLyBHZXQgYXNwZWN0IHJhdGlvLlxuXHRjb25zdCBhc3BlY3QgPSAoYlsxXVswXSAtIGJbMF1bMF0pIC8gKGJbMV1bMV0gLSBiWzBdWzFdKVxuXG5cdGNvbnN0IG91dGVySGVpZ2h0ID0gNDYwXG5cdGNvbnN0IG91dGVyV2lkdGggPSBvdXRlckhlaWdodCAqIGFzcGVjdFxuXG5cdGNvbnN0IHdpZHRoID0gb3V0ZXJXaWR0aCAtIG1hcmdpbi5sZWZ0IC0gbWFyZ2luLnJpZ2h0XG5cdGNvbnN0IGhlaWdodCA9IG91dGVySGVpZ2h0IC0gbWFyZ2luLnRvcCAtIG1hcmdpbi5ib3R0b21cblxuXHQvLyBQcmVwYXJlIHN2Zy5cblx0Y29uc3QgZyA9IGQzLnNlbGVjdCgnLm1hcCBzdmcnKVxuXHRcdFx0LmF0dHJzKHsgd2lkdGg6IG91dGVyV2lkdGgsIGhlaWdodDogb3V0ZXJIZWlnaHQgfSlcblx0XHQuYXBwZW5kKCdnJylcblx0XHRcdC5hdHRyKCd0cmFuc2Zvcm0nLCBgdHJhbnNsYXRlKCR7bWFyZ2luLmxlZnR9LCAke21hcmdpbi50b3B9KWApXG5cblx0Ly8gRml0IHRoZSBmZWF0dXJlIHRvIHRoZSBjb250YWluZXIncyB3aWR0aC5cblx0cHJvamVjdGlvbi5maXRTaXplKFt3aWR0aCwgaGVpZ2h0XSwgZmVhdHVyZSlcblxuXHQvLyBHZXQgdGhlIGluZGl2aWR1YWwgbGluZSBzdHJpbmdzLlxuXHRjb25zdCBsaW5lU3RyaW5ncyA9IF8oZmVhdHVyZS5mZWF0dXJlcylcblx0XHQubWFwKCdnZW9tZXRyeScpXG5cdFx0LmZsYXR0ZW4oKVxuXHRcdC5tYXAoJ2Nvb3JkaW5hdGVzJylcblx0XHQuZmxhdHRlbigpXG5cdFx0LnNvcnRCeSgnbGVuZ3RoJylcblx0XHQubWFwKGQgPT4gdHVyZi5saW5lU3RyaW5nKGQpKVxuXHRcdC52YWx1ZSgpXG5cblx0Ly8gQ2FsY3VsYXRlIHRoZSBvdmVyYWxsIGxpbmUgc3RyaW5nIGxlbmd0aC5cblx0Y29uc3QgdG90YWxMZW5ndGggPSBfKGxpbmVTdHJpbmdzKVxuXHRcdC5tYXAoZCA9PiB0dXJmLmxpbmVEaXN0YW5jZShkKSlcblx0XHQuc3VtKClcblxuXHQvLyBEZXNpcmVkIG51bWJlciBvZiB0b3RhbCBwb2ludHMuXG5cdGNvbnN0IHBvaW50c0NvdW50ID0gMTAwMFxuXHRsZXQgcG9pbnRzUmVtYWluaW5nID0gcG9pbnRzQ291bnRcblxuXHRjb25zdCBwb2ludHMgPSBfKGxpbmVTdHJpbmdzKVxuXHRcdC5tYXAobGluZSA9PiB7XG5cblx0XHRcdC8vIEhvdyBtYW55IHBvaW50cyB3aWxsIHRoaXMgbGluZSBnZXQ/XG5cblx0XHRcdC8vIEZpcnN0LCBjYWxjdWxhdGUgdGhpcyBsaW5lJ3MgbGVuZ3RoLlxuXHRcdFx0Y29uc3QgbGluZUxlbmd0aCA9IHR1cmYubGluZURpc3RhbmNlKGxpbmUpXG5cblx0XHRcdC8vIE5leHQsIGdldCB0aGlzIGxpbmUncyBwb2ludHMgcHJvcG9ydGlvbiwgcm91bmRlZCB1cC5cblx0XHRcdGNvbnN0IHVwcGVyQ291bnQgPSBNYXRoLmNlaWwobGluZUxlbmd0aCAqIHBvaW50c0NvdW50IC8gdG90YWxMZW5ndGgpXG5cblx0XHRcdC8vIERvbid0IGdldCBtb3JlIHBvaW50cyB0aGF0IGFyZSBhdmFpbGFibGUuXG5cdFx0XHRjb25zdCBsaW5lUG9pbnRzQ291bnQgPSBNYXRoLm1pbihwb2ludHNSZW1haW5pbmcsIHVwcGVyQ291bnQpXG5cblx0XHRcdC8vIE1ha2Ugc3VyZSB0byB1cGRhdGUgcG9pbnRzIHJlbWFpbmluZy5cblx0XHRcdHBvaW50c1JlbWFpbmluZyAtPSBsaW5lUG9pbnRzQ291bnRcblxuXHRcdFx0Ly8gTm93IHRoYXQgd2Uga25vdyBob3cgbWFueSBwb2ludHMgdGhpcyBsaW5lIHdpbGwgZ2V0LFxuXHRcdFx0Ly8gY2FsY3VsYXRlIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIHBvaW50cyAtIHRoZSBzdGVwOlxuXHRcdFx0Y29uc3Qgc3RlcCA9IGxpbmVMZW5ndGggLyBsaW5lUG9pbnRzQ291bnRcblxuXHRcdFx0Y29uc3QgbGluZVBvaW50cyA9IGQzLnJhbmdlKGxpbmVQb2ludHNDb3VudClcblx0XHRcdFx0Lm1hcChkID0+IHR1cmYuYWxvbmcobGluZSwgZCAqIHN0ZXApKVxuXG5cdFx0XHRyZXR1cm4gbGluZVBvaW50c1xuXG5cdFx0fSlcblx0XHQuZmxhdHRlbigpXG5cdFx0Lm1hcChkID0+IHByb2plY3Rpb24oZC5nZW9tZXRyeS5jb29yZGluYXRlcykpXG5cdFx0LnZhbHVlKClcblxuXHQvLyBUaGlzIGZ1bmN0aW9uIGFkZHMgdGhlIGNpcmNsZXMuXG5cdGNvbnN0IGVudGVyID0gKCkgPT4ge1xuXG5cdFx0Ly8gSk9JTiBuZXcgZGF0YSB3aXRoIG9sZCBlbGVtZW50cy5cblx0XHRjb25zdCBjaXJjbGVzID0gZy5zZWxlY3RBbGwoJ2NpcmNsZScpXG5cdFx0XHRcdC5kYXRhKHBvaW50cylcblxuXHRcdC8vIEVOVEVSIG5ldyBlbGVtZW50cyBwcmVzZW50IGluIG5ldyBkYXRhLlxuXHRcdGNpcmNsZXMuZW50ZXIoKS5hcHBlbmQoJ2NpcmNsZScpXG5cdFx0XHRcdC5hdHRycyh7XG5cdFx0XHRcdFx0Y3g6IHdpZHRoLzIsXG5cdFx0XHRcdFx0Y3k6IGhlaWdodC8yLFxuXHRcdFx0XHRcdHI6IDAsXG5cdFx0XHRcdH0pXG5cdFx0XHQudHJhbnNpdGlvbignZW50ZXInKVxuXHRcdFx0XHQuZHVyYXRpb24oMTAwMClcblx0XHRcdFx0LmRlbGF5KChkLCBpKSA9PiBpICogMTApXG5cdFx0XHRcdC5hdHRycyh7XG5cdFx0XHRcdFx0Y3g6IGQgPT4gZFswXSxcblx0XHRcdFx0XHRjeTogZCA9PiBkWzFdLFxuXHRcdFx0XHRcdHI6IDEsXG5cdFx0XHRcdH0pXG5cblx0fVxuXG5cdC8vIFRoaXMgZnVuY3Rpb24gcmVtb3ZlcyB0aGUgY2lyY2xlcy5cblx0Y29uc3QgZXhpdCA9ICgpID0+IHtcblxuXHRcdC8vIEpPSU4gbmV3IGRhdGEgd2l0aCBvbGQgZWxlbWVudHMuXG5cdFx0Y29uc3QgY2lyY2xlcyA9IGcuc2VsZWN0QWxsKCdjaXJjbGUnKVxuXHRcdFx0XHQuZGF0YShwb2ludHMpXG5cblx0XHQvLyBVUERBVEUgb2xkIGVsZW1lbnRzIHByZXNlbnQgaW4gbmV3IGRhdGEuXG5cdFx0Y2lyY2xlc1xuXHRcdFx0LnRyYW5zaXRpb24oJ2V4aXQnKVxuXHRcdFx0XHQuZHVyYXRpb24oMjUwKVxuXHRcdFx0XHQuZGVsYXkoKGQsIGkpID0+IGkgKiAyKVxuXHRcdFx0XHQuYXR0cnMoe1xuXHRcdFx0XHRcdGN4OiB3aWR0aC8yLFxuXHRcdFx0XHRcdGN5OiBoZWlnaHQvMixcblx0XHRcdFx0XHRyOiAwLFxuXHRcdFx0XHR9KVxuXHRcdFx0LnJlbW92ZSgpXG5cblx0fVxuXG5cdC8vIEZpcmUgdGhlIGVudGVyIGZ1bmN0aW9uIG9uIHBhZ2UgbG9hZC5cblx0ZW50ZXIoKVxuXG5cdC8vIExpc3RlbiB0byBidXR0b24gY2xpY2tzLlxuXHRkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdidXR0b24uZW50ZXInKS5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsIGVudGVyKVxuXHRkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdidXR0b24uZXhpdCcpLmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgZXhpdClcblxufSlcbiJdfQ==
<!DOCTYPE html>
<title>Equidistant outline circles</title>
<link href='dist.css' rel='stylesheet' />
<body>
<div class='map'>
<svg></svg>
</div>
<div class='buttons'>
<button class='enter'>Enter</button>
<button class='exit'>Exit</button>
</div>
<script src='https://d3js.org/d3.v4.min.js'></script>
<script src='https://d3js.org/d3-selection-multi.v1.min.js'></script>
<script src='https://d3js.org/topojson.v2.min.js'></script>
<script src='https://npmcdn.com/@turf/turf@3.10.2/turf.min.js'></script>
<script src='https://cdn.jsdelivr.net/lodash/4.17.4/lodash.min.js'></script>
<script src='dist.js'></script>
</body>
# Not used for Irish example. http://mapshaper.org/ used instead.
#all:
#
# rm boundary.topojson;
# mapshaper -i ~/Downloads/cb_2015_us_nation_5m/cb_2015_us_nation_5m.shp name=boundary -clip bbox=-126,23,-65,50 -filter-slivers min-area=700000000 -lines -simplify dp 5% -o format=topojson boundary.topojson;
// Setup chart dimensions.
const margin = { top: 10, right: 10, bottom: 10, left: 10 }
// Get GeoJSON.
d3.json('./boundary.topojson', json => {
const feature = topojson.feature(json, json.objects.boundary)
// Get feature's bounds and centroid.
const bounds = d3.geoBounds(feature)
const centroid = d3.geoCentroid(feature)
// const projection = d3.geoConicConformal()
// .parallels([bounds[0][1], bounds[1][1]])
// .rotate([-centroid[0], 0])
// .center([0, -centroid[1]])
const projection = d3.geoAlbers()
const rotate = [0,0,-45]
// Get the path.
const path = d3.geoPath().projection(projection).rotate(rotate)
// Get the path's bounds (i.e., in pixels).
const b = path.bounds(feature)
// Get aspect ratio.
const aspect = (b[1][0] - b[0][0]) / (b[1][1] - b[0][1])
const outerHeight = 460
const outerWidth = outerHeight * aspect
const width = outerWidth - margin.left - margin.right
const height = outerHeight - margin.top - margin.bottom
// Prepare svg.
const g = d3.select('.map svg')
.attrs({ width: outerWidth, height: outerHeight })
.append('g')
.attr('transform', `rotate(45) translate("+100+", "+-170+"))`)
// Fit the feature to the container's width.
projection.fitSize([width, height], feature)
// Get the individual line strings.
const lineStrings = _(feature.features)
.map('geometry')
.flatten()
.map('coordinates')
.flatten()
.sortBy('length')
.map(d => turf.lineString(d))
.value()
// Calculate the overall line string length.
const totalLength = _(lineStrings)
.map(d => turf.lineDistance(d))
.sum()
// Desired number of total points.
const pointsCount = 1000
let pointsRemaining = pointsCount
const points = _(lineStrings)
.map(line => {
// How many points will this line get?
// First, calculate this line's length.
const lineLength = turf.lineDistance(line)
// Next, get this line's points proportion, rounded up.
const upperCount = Math.ceil(lineLength * pointsCount / totalLength)
// Don't get more points that are available.
const linePointsCount = Math.min(pointsRemaining, upperCount)
// Make sure to update points remaining.
pointsRemaining -= linePointsCount
// Now that we know how many points this line will get,
// calculate the distance between points - the step:
const step = lineLength / linePointsCount
const linePoints = d3.range(linePointsCount)
.map(d => turf.along(line, d * step))
return linePoints
})
.flatten()
.map(d => projection(d.geometry.coordinates))
.value()
// This function adds the circles.
const enter = () => {
// JOIN new data with old elements.
const circles = g.selectAll('circle')
.data(points)
// ENTER new elements present in new data.
circles.enter().append('circle')
.attrs({
cx: width/2,
cy: height/2,
r: 0,
})
.transition('enter')
.duration(1000)
.delay((d, i) => i * 10)
.attrs({
cx: d => d[0],
cy: d => d[1],
r: 1,
})
}
// This function removes the circles.
const exit = () => {
// JOIN new data with old elements.
const circles = g.selectAll('circle')
.data(points)
// UPDATE old elements present in new data.
circles
.transition('exit')
.duration(250)
.delay((d, i) => i * 2)
.attrs({
cx: width/2,
cy: height/2,
r: 0,
})
.remove()
}
// Fire the enter function on page load.
enter()
// Listen to button clicks.
document.querySelector('button.enter').addEventListener('click', enter)
document.querySelector('button.exit').addEventListener('click', exit)
})
$green = #008000
.buttons
margin 0 auto
text-align center
.map
svg
display block
margin 0 auto
circle
fill $green
fill-opacity 0.25
stroke $green
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment