Lat/lon histogram
Made with blockup.
license: mit | |
height: 720 | |
border: no |
.buttons{margin:0 auto;text-align:center}.map svg{display:block;margin:0 auto}.map svg circle{fill:#8f092a;fill-opacity:.25;stroke:#8f092a} |
var margin={top:10,right:10,bottom:10,left:10};d3.json("./boundary.topojson",function(t){var n=topojson.feature(t,t.objects.boundary),e=(d3.geoBounds(n),d3.geoCentroid(n),d3.geoAlbers()),r=d3.geoPath().projection(e),o=r.bounds(n),a=(o[1][0]-o[0][0])/(o[1][1]-o[0][1]),i=460,u=i*a,c=u-margin.left-margin.right,f=i-margin.top-margin.bottom,l=d3.select(".map svg").attrs({width:u,height:i}).append("g").attr("transform","translate("+margin.left+", "+margin.top+")");e.fitSize([c,f],n);var d=_(n.features).map("geometry").flatten().map("coordinates").flatten().sortBy("length").map(function(t){return turf.lineString(t)}).value(),s=_(d).map(function(t){return turf.lineDistance(t)}).sum(),m=1e3,g=m,p=_(d).map(function(t){var n=turf.lineDistance(t),e=Math.ceil(n*m/s),r=Math.min(g,e);g-=r;var o=n/r,a=d3.range(r).map(function(n){return turf.along(t,n*o)});return a}).flatten().map(function(t){return e(t.geometry.coordinates)}).map(function(t,n){return{index:n,x2:t[0],y2:t[1]}}).value(),y=5,x=_(p).groupBy(function(t){return Math.round(t.y2/y)*y}).map(function(t,n){return _(t).sortBy("x2").map(function(e,r){return{index:e.index,x1:c/2+4*r-4*t.length/2,y1:+n,x2:e.x2,y2:e.y2}}).value()}).flatten().value(),v=function(){var t=l.selectAll("circle").data(x,function(t){return t.index});t.enter().append("circle").attrs({cx:function(t){return t.x1},cy:function(t){return t.y1},r:1}).merge(t).transition("enter").duration(1e3).delay(function(t,n){return 10*n}).attrs({cx:function(t){return t.x2},cy:function(t){return t.y2},r:1})},h=function(){var t=l.selectAll("circle").data(x,function(t){return t.index});t.transition("exit").duration(250).delay(function(t,n){return 2*n}).attrs({cx:function(t){return t.x1},cy:function(t){return t.y1},r:1})};v(),document.querySelector("button.enter").addEventListener("click",v),document.querySelector("button.exit").addEventListener("click",h)}); | |
//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInNjcmlwdC5qcyJdLCJuYW1lcyI6WyJjb25zdCIsIm1hcmdpbiIsInRvcCIsInJpZ2h0IiwiYm90dG9tIiwibGVmdCIsImQzIiwianNvbiIsImZlYXR1cmUiLCJ0b3BvanNvbiIsIm9iamVjdHMiLCJib3VuZGFyeSIsInByb2plY3Rpb24iLCJnZW9Cb3VuZHMiLCJnZW9DZW50cm9pZCIsImdlb0FsYmVycyIsInBhdGgiLCJnZW9QYXRoIiwiYiIsImJvdW5kcyIsImFzcGVjdCIsIm91dGVySGVpZ2h0Iiwib3V0ZXJXaWR0aCIsIndpZHRoIiwiaGVpZ2h0IiwiZyIsInNlbGVjdCIsImF0dHJzIiwiYXBwZW5kIiwiYXR0ciIsImZpdFNpemUiLCJsaW5lU3RyaW5ncyIsIl8iLCJmZWF0dXJlcyIsIm1hcCIsImZsYXR0ZW4iLCJzb3J0QnkiLCJkIiwidHVyZiIsImxpbmVTdHJpbmciLCJ2YWx1ZSIsInRvdGFsTGVuZ3RoIiwibGluZURpc3RhbmNlIiwic3VtIiwicG9pbnRzQ291bnQiLCJwb2ludHNSZW1haW5pbmciLCJwb2ludHMiLCJsaW5lIiwibGluZUxlbmd0aCIsInVwcGVyQ291bnQiLCJNYXRoIiwiY2VpbCIsImxpbmVQb2ludHNDb3VudCIsIm1pbiIsInN0ZXAiLCJsaW5lUG9pbnRzIiwicmFuZ2UiLCJhbG9uZyIsImdlb21ldHJ5IiwiY29vcmRpbmF0ZXMiLCJpbmRleCIsIngyIiwieTIiLCJ5U3RlcCIsImhpc3RvZ3JhbSIsImdyb3VwQnkiLCJyb3VuZCIsImtleSIsImkiLCJ4MSIsImxlbmd0aCIsInkxIiwiZW50ZXIiLCJjaXJjbGVzIiwic2VsZWN0QWxsIiwiZGF0YSIsImN4IiwiY3kiLCJyIiwibWVyZ2UiLCJ0cmFuc2l0aW9uIiwiZHVyYXRpb24iLCJkZWxheSIsImV4aXQiLCJkb2N1bWVudCIsInF1ZXJ5U2VsZWN0b3IiLCJhZGRFdmVudExpc3RlbmVyIl0sIm1hcHBpbmdzIjoiQUFDQUEsR0FBTUMsU0FBV0MsSUFBTyxHQUFFQyxNQUFTLEdB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LEdBQUEsTUFBQUEsR0FBQXdCLElBQ0pnQixHQUFJLFNBQUF4QyxHQUFBLE1BQUFBLEdBQUF5QixJQUNKZ0IsRUFBQyxLQUtDSyxFQUFPLFdBR1puRixHQUNHeUUsR0FBS2hELEVBQVNpRCxVQUFFLFVBQWhCQyxLQUFLWCxFQUFXLFNBQUEzQixHQUFFLE1BQUdBLEdBQUV1QixPQUcxQmEsR0FDRU8sV0FDVSxRQUNUQyxTQUFNLEtBQ05DLE1BQU0sU0FBQTdDLEVBQUErQixHQUFBLE1BQUEsR0FBQUEsSUFDTnpDLE9BQ0FpRCxHQUFJLFNBQUF2QyxHQUFBLE1BQUFBLEdBQUFnQyxJQUNKUSxHQUFJLFNBQUF4QyxHQUFBLE1BQUFBLEdBQUFrQyxJQUNKTyxFQUFDLElBS0xOLEtBSUFZLFNBQVNDLGNBQWMsZ0JBQWVDLGlCQUFpQixRQUFTZCxHQUFoRVksU0FBU0MsY0FBYyxlQUFlQyxpQkFBaUIsUUFBU0giLCJmaWxlIjoic2NyaXB0LmpzIiwic291cmNlc0NvbnRlbnQiOlsiLy8gU2V0dXAgY2hhcnQgZGltZW5zaW9ucy5cbmNvbnN0IG1hcmdpbiA9IHsgdG9wOiAxMCwgcmlnaHQ6IDEwLCBib3R0b206IDEwLCBsZWZ0OiAxMCB9XG5cbi8vIEdldCBHZW9KU09OLlxuZDMuanNvbignLi9ib3VuZGFyeS50b3BvanNvbicsIGpzb24gPT4ge1xuXG5cdGNvbnN0IGZlYXR1cmUgPSB0b3BvanNvbi5mZWF0dXJlKGpzb24sIGpzb24ub2JqZWN0cy5ib3VuZGFyeSlcblxuXHQvLyBHZXQgZmVhdHVyZSdzIGJvdW5kcywgY2VudHJvaWQsIGFuZCBwcm9qZWN0aW9uLlxuXHRjb25zdCBib3VuZHMgPSBkMy5nZW9Cb3VuZHMoZmVhdHVyZSlcblx0Y29uc3QgY2VudHJvaWQgPSBkMy5nZW9DZW50cm9pZChmZWF0dXJlKVxuXHRjb25zdCBwcm9qZWN0aW9uID0gZDMuZ2VvQWxiZXJzKClcblxuXHQvLyBHZXQgdGhlIHBhdGguXG5cdGNvbnN0IHBhdGggPSBkMy5nZW9QYXRoKCkucHJvamVjdGlvbihwcm9qZWN0aW9uKVxuXG5cdC8vIEdldCB0aGUgcGF0aCdzIGJvdW5kcyAoaS5lLiwgaW4gcGl4ZWxzKS5cblx0Y29uc3QgYiA9IHBhdGguYm91bmRzKGZlYXR1cmUpXG5cblx0Ly8gR2V0IGFzcGVjdCByYXRpby5cblx0Y29uc3QgYXNwZWN0ID0gKGJbMV1bMF0gLSBiWzBdWzBdKSAvIChiWzFdWzFdIC0gYlswXVsxXSlcblxuXHRjb25zdCBvdXRlckhlaWdodCA9IDQ2MFxuXHRjb25zdCBvdXRlcldpZHRoID0gb3V0ZXJIZWlnaHQgKiBhc3BlY3RcblxuXHRjb25zdCB3aWR0aCA9IG91dGVyV2lkdGggLSBtYXJnaW4ubGVmdCAtIG1hcmdpbi5yaWdodFxuXHRjb25zdCBoZWlnaHQgPSBvdXRlckhlaWdodCAtIG1hcmdpbi50b3AgLSBtYXJnaW4uYm90dG9tXG5cblx0Ly8gUHJlcGFyZSBzdmcuXG5cdGNvbnN0IGcgPSBkMy5zZWxlY3QoJy5tYXAgc3ZnJylcblx0XHRcdC5hdHRycyh7IHdpZHRoOiBvdXRlcldpZHRoLCBoZWlnaHQ6IG91dGVySGVpZ2h0IH0pXG5cdFx0LmFwcGVuZCgnZycpXG5cdFx0XHQuYXR0cigndHJhbnNmb3JtJywgYHRyYW5zbGF0ZSgke21hcmdpbi5sZWZ0fSwgJHttYXJnaW4udG9wfSlgKVxuXG5cdC8vIEZpdCB0aGUgZmVhdHVyZSB0byB0aGUgY29udGFpbmVyJ3Mgd2lkdGguXG5cdHByb2plY3Rpb24uZml0U2l6ZShbd2lkdGgsIGhlaWdodF0sIGZlYXR1cmUpXG5cblx0Ly8gR2V0IHRoZSBpbmRpdmlkdWFsIGxpbmUgc3RyaW5ncy5cblx0Y29uc3QgbGluZVN0cmluZ3MgPSBfKGZlYXR1cmUuZmVhdHVyZXMpXG5cdFx0Lm1hcCgnZ2VvbWV0cnknKVxuXHRcdC5mbGF0dGVuKClcblx0XHQubWFwKCdjb29yZGluYXRlcycpXG5cdFx0LmZsYXR0ZW4oKVxuXHRcdC5zb3J0QnkoJ2xlbmd0aCcpXG5cdFx0Lm1hcChkID0+IHR1cmYubGluZVN0cmluZyhkKSlcblx0XHQudmFsdWUoKVxuXG5cdC8vIENhbGN1bGF0ZSB0aGUgb3ZlcmFsbCBsaW5lIHN0cmluZyBsZW5ndGguXG5cdGNvbnN0IHRvdGFsTGVuZ3RoID0gXyhsaW5lU3RyaW5ncylcblx0XHQubWFwKGQgPT4gdHVyZi5saW5lRGlzdGFuY2UoZCkpXG5cdFx0LnN1bSgpXG5cblx0Ly8gRGVzaXJlZCBudW1iZXIgb2YgdG90YWwgcG9pbnRzLlxuXHRjb25zdCBwb2ludHNDb3VudCA9IDEwMDBcblx0bGV0IHBvaW50c1JlbWFpbmluZyA9IHBvaW50c0NvdW50XG5cblx0Y29uc3QgcG9pbnRzID0gXyhsaW5lU3RyaW5ncylcblx0XHQubWFwKGxpbmUgPT4ge1xuXG5cdFx0XHQvLyBIb3cgbWFueSBwb2ludHMgd2lsbCB0aGlzIGxpbmUgZ2V0P1xuXG5cdFx0XHQvLyBGaXJzdCwgY2FsY3VsYXRlIHRoaXMgbGluZSdzIGxlbmd0aC5cblx0XHRcdGNvbnN0IGxpbmVMZW5ndGggPSB0dXJmLmxpbmVEaXN0YW5jZShsaW5lKVxuXG5cdFx0XHQvLyBOZXh0LCBnZXQgdGhpcyBsaW5lJ3MgcG9pbnRzIHByb3BvcnRpb24sIHJvdW5kZWQgdXAuXG5cdFx0XHRjb25zdCB1cHBlckNvdW50ID0gTWF0aC5jZWlsKGxpbmVMZW5ndGggKiBwb2ludHNDb3VudCAvIHRvdGFsTGVuZ3RoKVxuXG5cdFx0XHQvLyBEb24ndCBnZXQgbW9yZSBwb2ludHMgdGhhdCBhcmUgYXZhaWxhYmxlLlxuXHRcdFx0Y29uc3QgbGluZVBvaW50c0NvdW50ID0gTWF0aC5taW4ocG9pbnRzUmVtYWluaW5nLCB1cHBlckNvdW50KVxuXG5cdFx0XHQvLyBNYWtlIHN1cmUgdG8gdXBkYXRlIHBvaW50cyByZW1haW5pbmcuXG5cdFx0XHRwb2ludHNSZW1haW5pbmcgLT0gbGluZVBvaW50c0NvdW50XG5cblx0XHRcdC8vIE5vdyB0aGF0IHdlIGtub3cgaG93IG1hbnkgcG9pbnRzIHRoaXMgbGluZSB3aWxsIGdldCxcblx0XHRcdC8vIGNhbGN1bGF0ZSB0aGUgZGlzdGFuY2UgYmV0d2VlbiBwb2ludHMgLSB0aGUgc3RlcDpcblx0XHRcdGNvbnN0IHN0ZXAgPSBsaW5lTGVuZ3RoIC8gbGluZVBvaW50c0NvdW50XG5cblx0XHRcdGNvbnN0IGxpbmVQb2ludHMgPSBkMy5yYW5nZShsaW5lUG9pbnRzQ291bnQpXG5cdFx0XHRcdC5tYXAoZCA9PiB0dXJmLmFsb25nKGxpbmUsIGQgKiBzdGVwKSlcblxuXHRcdFx0cmV0dXJuIGxpbmVQb2ludHNcblxuXHRcdH0pXG5cdFx0LmZsYXR0ZW4oKVxuXHRcdC5tYXAoZCA9PiBwcm9qZWN0aW9uKGQuZ2VvbWV0cnkuY29vcmRpbmF0ZXMpKVxuXHRcdC5tYXAoKGQsIGluZGV4KSA9PiAoe1xuXHRcdFx0aW5kZXgsXG5cdFx0XHR4MjogZFswXSxcblx0XHRcdHkyOiBkWzFdLFxuXHRcdH0pKVxuXHRcdC52YWx1ZSgpXG5cblx0Y29uc3QgeVN0ZXAgPSA1XG5cblx0Y29uc3QgaGlzdG9ncmFtID0gXyhwb2ludHMpXG5cdFx0Lmdyb3VwQnkoZCA9PiBNYXRoLnJvdW5kKGQueTIgLyB5U3RlcCkgKiB5U3RlcClcblx0XHQubWFwKCh2YWx1ZSwga2V5KSA9PlxuXHRcdFx0Xyh2YWx1ZSlcblx0XHRcdFx0LnNvcnRCeSgneDInKVxuXHRcdFx0XHQubWFwKChkLCBpKSA9PiAoe1xuXHRcdFx0XHRcdGluZGV4OiBkLmluZGV4LFxuXHRcdFx0XHRcdHgxOiAoKHdpZHRoIC8gMikgKyAoaSAqIDQpKSAtICh2YWx1ZS5sZW5ndGggKiA0KS8yLFxuXHRcdFx0XHRcdHkxOiAra2V5LFxuXHRcdFx0XHRcdHgyOiBkLngyLFxuXHRcdFx0XHRcdHkyOiBkLnkyLFxuXHRcdFx0XHR9KSlcblx0XHRcdFx0LnZhbHVlKCkpXG5cdFx0LmZsYXR0ZW4oKVxuXHRcdC52YWx1ZSgpXG5cblx0Ly8gVGhpcyBmdW5jdGlvbiBhZGRzIHRoZSBjaXJjbGVzLlxuXHRjb25zdCBlbnRlciA9ICgpID0+IHtcblxuXHRcdC8vIEpPSU4gbmV3IGRhdGEgd2l0aCBvbGQgZWxlbWVudHMuXG5cdFx0Y29uc3QgY2lyY2xlcyA9IGcuc2VsZWN0QWxsKCdjaXJjbGUnKVxuXHRcdFx0LmRhdGEoaGlzdG9ncmFtLCBkID0+IGQuaW5kZXgpXG5cblx0XHQvLyBFTlRFUiBuZXcgZWxlbWVudHMgcHJlc2VudCBpbiBuZXcgZGF0YS5cblx0XHRjaXJjbGVzLmVudGVyKCkuYXBwZW5kKCdjaXJjbGUnKVxuXHRcdFx0XHQuYXR0cnMoe1xuXHRcdFx0XHRcdGN4OiBkID0+IGQueDEsXG5cdFx0XHRcdFx0Y3k6IGQgPT4gZC55MSxcblx0XHRcdFx0XHRyOiAxLFxuXHRcdFx0XHR9KVxuXHRcdFx0Lm1lcmdlKGNpcmNsZXMpXG5cdFx0XHQudHJhbnNpdGlvbignZW50ZXInKVxuXHRcdFx0XHQuZHVyYXRpb24oMTAwMClcblx0XHRcdFx0LmRlbGF5KChkLCBpKSA9PiBpICogMTApXG5cdFx0XHRcdC5hdHRycyh7XG5cdFx0XHRcdFx0Y3g6IGQgPT4gZC54Mixcblx0XHRcdFx0XHRjeTogZCA9PiBkLnkyLFxuXHRcdFx0XHRcdHI6IDEsXG5cdFx0XHRcdH0pXG5cblx0fVxuXG5cdC8vIFRoaXMgZnVuY3Rpb24gcmVtb3ZlcyB0aGUgY2lyY2xlcy5cblx0Y29uc3QgZXhpdCA9ICgpID0+IHtcblxuXHRcdC8vIEpPSU4gbmV3IGRhdGEgd2l0aCBvbGQgZWxlbWVudHMuXG5cdFx0Y29uc3QgY2lyY2xlcyA9IGcuc2VsZWN0QWxsKCdjaXJjbGUnKVxuXHRcdFx0XHQuZGF0YShoaXN0b2dyYW0sIGQgPT4gZC5pbmRleClcblxuXHRcdC8vIFVQREFURSBvbGQgZWxlbWVudHMgcHJlc2VudCBpbiBuZXcgZGF0YS5cblx0XHRjaXJjbGVzXG5cdFx0XHQudHJhbnNpdGlvbignZXhpdCcpXG5cdFx0XHRcdC5kdXJhdGlvbigyNTApXG5cdFx0XHRcdC5kZWxheSgoZCwgaSkgPT4gaSAqIDIpXG5cdFx0XHRcdC5hdHRycyh7XG5cdFx0XHRcdFx0Y3g6IGQgPT4gZC54MSxcblx0XHRcdFx0XHRjeTogZCA9PiBkLnkxLFxuXHRcdFx0XHRcdHI6IDEsXG5cdFx0XHRcdH0pXG5cblx0fVxuXG5cdC8vIEZpcmUgdGhlIGVudGVyIGZ1bmN0aW9uIG9uIHBhZ2UgbG9hZC5cblx0ZW50ZXIoKVxuXG5cdC8vIExpc3RlbiB0byBidXR0b24gY2xpY2tzLlxuXHRkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdidXR0b24uZW50ZXInKS5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsIGVudGVyKVxuXHRkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdidXR0b24uZXhpdCcpLmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgZXhpdClcblxufSlcbiJdfQ== |
<!DOCTYPE html> | |
<title>Lat/lon histogram</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> |
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, centroid, and projection. | |
const bounds = d3.geoBounds(feature) | |
const centroid = d3.geoCentroid(feature) | |
const projection = d3.geoAlbers() | |
// Get the path. | |
const path = d3.geoPath().projection(projection) | |
// 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', `translate(${margin.left}, ${margin.top})`) | |
// 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)) | |
.map((d, index) => ({ | |
index, | |
x2: d[0], | |
y2: d[1], | |
})) | |
.value() | |
const yStep = 5 | |
const histogram = _(points) | |
.groupBy(d => Math.round(d.y2 / yStep) * yStep) | |
.map((value, key) => | |
_(value) | |
.sortBy('x2') | |
.map((d, i) => ({ | |
index: d.index, | |
x1: ((width / 2) + (i * 4)) - (value.length * 4)/2, | |
y1: +key, | |
x2: d.x2, | |
y2: d.y2, | |
})) | |
.value()) | |
.flatten() | |
.value() | |
// This function adds the circles. | |
const enter = () => { | |
// JOIN new data with old elements. | |
const circles = g.selectAll('circle') | |
.data(histogram, d => d.index) | |
// ENTER new elements present in new data. | |
circles.enter().append('circle') | |
.attrs({ | |
cx: d => d.x1, | |
cy: d => d.y1, | |
r: 1, | |
}) | |
.merge(circles) | |
.transition('enter') | |
.duration(1000) | |
.delay((d, i) => i * 10) | |
.attrs({ | |
cx: d => d.x2, | |
cy: d => d.y2, | |
r: 1, | |
}) | |
} | |
// This function removes the circles. | |
const exit = () => { | |
// JOIN new data with old elements. | |
const circles = g.selectAll('circle') | |
.data(histogram, d => d.index) | |
// UPDATE old elements present in new data. | |
circles | |
.transition('exit') | |
.duration(250) | |
.delay((d, i) => i * 2) | |
.attrs({ | |
cx: d => d.x1, | |
cy: d => d.y1, | |
r: 1, | |
}) | |
} | |
// 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) | |
}) |
$red = #8f092a | |
.buttons | |
margin 0 auto | |
text-align center | |
.map | |
svg | |
display block | |
margin 0 auto | |
circle | |
fill $red | |
fill-opacity 0.25 | |
stroke $red |