Skip to content

Instantly share code, notes, and snippets.

@gabrielflorit
Last active February 20, 2017 19:41
Show Gist options
  • Save gabrielflorit/7c35da2e705c159fb11d53791c882298 to your computer and use it in GitHub Desktop.
Save gabrielflorit/7c35da2e705c159fb11d53791c882298 to your computer and use it in GitHub Desktop.
Circles on an exploded mesh

Circles on an exploded mesh

Made with blockup.

.buttons{margin:0 auto;text-align:center}.map svg{display:block;margin:0 auto}.map svg circle{fill:#8f092a}.map svg path{fill:none;stroke:#8f092a}
var margin={top:10,right:10,bottom:10,left:10},outerWidth=360,outerHeight=outerWidth,width=outerWidth-margin.left-margin.right,height=outerHeight-margin.top-margin.bottom,g=d3.select(".map svg").attrs({width:outerWidth,height:outerHeight}).append("g").attr("transform","translate("+margin.left+", "+margin.top+")");d3.json("./MA.topojson",function(t){var e=topojson.mesh(t),r=d3.geoBounds(e),n=d3.geoCentroid(e),o=d3.geoConicConformal().parallels([r[0][1],r[1][1]]).rotate([-n[0],0]).center([0,-n[1]]),i=d3.geoPath().projection(o);i.bounds(e);o.fitSize([width,height],e);var a=_(e.coordinates).flatten().map(function(t){return{xy:o(t)}}).value(),c=function(){var t=g.selectAll("circle").data(a);t.enter().append("circle").attrs({cx:width/2,cy:height,r:0}).transition("enter").duration(1e3).delay(function(t,e){return 5*e}).attrs({cx:function(t){return t.xy[0]},cy:function(t){return t.xy[1]},r:1})},d=function(){var t=g.selectAll("circle").data(a);t.transition("exit").duration(250).delay(function(t,e){return 2*e}).attrs({cx:width/2,cy:height,r:0}).remove()};c(),document.querySelector("button.enter").addEventListener("click",c),document.querySelector("button.exit").addEventListener("click",d)});
//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInNjcmlwdC5qcyJdLCJuYW1lcyI6WyJjb25zdCIsIm1hcmdpbiIsInRvcCIsInJpZ2h0IiwiYm90dG9tIiwibGVmdCIsIm91dGVyV2lkdGgiLCJvdXRlckhlaWdodCIsIndpZHRoIiwiaGVpZ2h0IiwiZyIsImQzIiwic2VsZWN0IiwiYXR0cnMiLCJhcHBlbmQiLCJhdHRyIiwianNvbiIsImZlYXR1cmUiLCJ0b3BvanNvbiIsIm1lc2giLCJib3VuZHMiLCJnZW9Cb3VuZHMiLCJjZW50cm9pZCIsImdlb0NlbnRyb2lkIiwicHJvamVjdGlvbiIsImdlb0NvbmljQ29uZm9ybWFsIiwicGFyYWxsZWxzIiwicm90YXRlIiwiY2VudGVyIiwicGF0aCIsImdlb1BhdGgiLCJmaXRTaXplIiwicG9pbnRzIiwiXyIsImNvb3JkaW5hdGVzIiwiZmxhdHRlbiIsIm1hcCIsInYiLCJ4eSIsInZhbHVlIiwiZW50ZXIiLCJjaXJjbGVzIiwic2VsZWN0QWxsIiwiZGF0YSIsImN4IiwiY3kiLCJyIiwidHJhbnNpdGlvbiIsImR1cmF0aW9uIiwiZGVsYXkiLCJkIiwiaSIsImV4aXQiLCJyZW1vdmUiLCJkb2N1bWVudCIsInF1ZXJ5U2VsZWN0b3IiLCJhZGRFdmVudExpc3RlbmVyIl0sIm1hcHBpbmdzIjoiQUFDQUEsR0FBTUMsU0FBV0MsSUFBTyxHQUFFQyxNQUFTLEdBQUVDLE9BQVUsR0FBRUMsS0FBUSxJQUVuREMsV0FBYSxJQUNiQyxZQUFjRCxXQUVkRSxNQUFRRixXQUFhTCxPQUFPSSxLQUFPSixPQUFPRSxNQUMxQ00sT0FBU0YsWUFBY04sT0FBT0MsSUFBTUQsT0FBT0csT0FHMUNNLEVBQUtDLEdBQUNDLE9BQU8sWUFDakJDLE9BQVFMLE1BQU9GLFdBQVlHLE9BQVFGLGNBQ3BDTyxPQUFPLEtBQ05DLEtBQUssWUFBYSxhQUFXZCxPQUFTLEtBQUEsS0FBSUEsT0FBRyxJQUFBLElBR2hEVSxJQUFHSyxLQUFLLGdCQUFpQixTQUFBQSxHQUV4QmhCLEdBQU1pQixHQUFVQyxTQUFTQyxLQUFLSCxHQUd4QkksRUFBV1QsR0FBQ1UsVUFBVUosR0FDdEJLLEVBQWFYLEdBQUNZLFlBQVlOLEdBRzFCTyxFQUFlYixHQUFDYyxvQkFDcEJDLFdBQVdOLEVBQU8sR0FBRyxHQUFJQSxFQUFPLEdBQUcsS0FDbkNPLFNBQVNMLEVBQVMsR0FBSSxJQUN0Qk0sUUFBUSxHQUFJTixFQUFTLEtBR2pCTyxFQUFTbEIsR0FBQ21CLFVBQVVOLFdBQVdBLEVBRzNCSyxHQUFLVCxPQUFPSCxFQUd0Qk8sR0FBV08sU0FBU3ZCLE1BQU9DLFFBQVNRLEVBRXBDakIsSUFBTWdDLEdBQVdDLEVBQUFoQixFQUFRaUIsYUFDdkJDLFVBQ0FDLElBQUksU0FBQUMsR0FBQSxPQUNKQyxHQUFJZCxFQUFXYSxNQUVmRSxRQVFJQyxFQUFRLFdBR2J4QyxHQUFNeUMsR0FBWS9CLEVBQUFnQyxVQUFVLFVBQ3pCQyxLQUFLWCxFQUdSUyxHQUFRRCxRQUFRMUIsT0FBTyxVQUNwQkQsT0FDQStCLEdBQUlwQyxNQUFNLEVBQ1ZxQyxHQUFJcEMsT0FDSnFDLEVBQUcsSUFFSkMsV0FBVyxTQUNWQyxTQUFTLEtBQ1RDLE1BQU0sU0FBQUMsRUFBQUMsR0FBQSxNQUFLLEdBQUpBLElBQ1B0QyxPQUNBK0IsR0FBSSxTQUFBTSxHQUFBLE1BQUFBLEdBQUFaLEdBQUEsSUFDSk8sR0FBSSxTQUFBSyxHQUFBLE1BQUFBLEdBQUFaLEdBQUEsSUFDSlEsRUFBRyxLQU1ETSxFQUFPLFdBR1pwRCxHQUFNeUMsR0FBWS9CLEVBQUFnQyxVQUFVLFVBQ3pCQyxLQUFLWCxFQUdSUyxHQUNFTSxXQUFXLFFBQ1ZDLFNBQVMsS0FDVEMsTUFBTSxTQUFBQyxFQUFBQyxHQUFBLE1BQUssR0FBSkEsSUFDUHRDLE9BQ0ErQixHQUFJcEMsTUFBTSxFQUNWcUMsR0FBSXBDLE9BQ0pxQyxFQUFHLElBRUpPLFNBS0hiLEtBR0FjLFNBQVNDLGNBQWMsZ0JBQWdCQyxpQkFBaUIsUUFBU2hCLEdBQ2pFYyxTQUFTQyxjQUFjLGVBQWVDLGlCQUFpQixRQUFTSiIsImZpbGUiOiJzY3JpcHQuanMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBTZXR1cCBjaGFydCBkaW1lbnNpb25zLlxuY29uc3QgbWFyZ2luID0geyB0b3A6IDEwLCByaWdodDogMTAsIGJvdHRvbTogMTAsIGxlZnQ6IDEwIH1cblxuY29uc3Qgb3V0ZXJXaWR0aCA9IDM2MFxuY29uc3Qgb3V0ZXJIZWlnaHQgPSBvdXRlcldpZHRoXG5cbmNvbnN0IHdpZHRoID0gb3V0ZXJXaWR0aCAtIG1hcmdpbi5sZWZ0IC0gbWFyZ2luLnJpZ2h0XG5jb25zdCBoZWlnaHQgPSBvdXRlckhlaWdodCAtIG1hcmdpbi50b3AgLSBtYXJnaW4uYm90dG9tXG5cbi8vIFByZXBhcmUgc3ZnLlxuY29uc3QgZyA9IGQzLnNlbGVjdCgnLm1hcCBzdmcnKVxuXHRcdC5hdHRycyh7IHdpZHRoOiBvdXRlcldpZHRoLCBoZWlnaHQ6IG91dGVySGVpZ2h0IH0pXG5cdC5hcHBlbmQoJ2cnKVxuXHRcdC5hdHRyKCd0cmFuc2Zvcm0nLCBgdHJhbnNsYXRlKCR7bWFyZ2luLmxlZnR9LCAke21hcmdpbi50b3B9KWApXG5cbi8vIEdldCBHZW9KU09OLlxuZDMuanNvbignLi9NQS50b3BvanNvbicsIGpzb24gPT4ge1xuXG5cdGNvbnN0IGZlYXR1cmUgPSB0b3BvanNvbi5tZXNoKGpzb24pXG5cblx0Ly8gR2V0IGZlYXR1cmUncyBib3VuZHMgYW5kIGNlbnRyb2lkLlxuXHRjb25zdCBib3VuZHMgPSBkMy5nZW9Cb3VuZHMoZmVhdHVyZSlcblx0Y29uc3QgY2VudHJvaWQgPSBkMy5nZW9DZW50cm9pZChmZWF0dXJlKVxuXG5cdC8vIFVzZSB0aGVtIHRvIGNyZWF0ZSB0aGUgcHJvamVjdGlvbi5cblx0Y29uc3QgcHJvamVjdGlvbiA9IGQzLmdlb0NvbmljQ29uZm9ybWFsKClcblx0XHQucGFyYWxsZWxzKFtib3VuZHNbMF1bMV0sIGJvdW5kc1sxXVsxXV0pXG5cdFx0LnJvdGF0ZShbLWNlbnRyb2lkWzBdLCAwXSlcblx0XHQuY2VudGVyKFswLCAtY2VudHJvaWRbMV1dKVxuXG5cdC8vIEdldCB0aGUgcGF0aC5cblx0Y29uc3QgcGF0aCA9IGQzLmdlb1BhdGgoKS5wcm9qZWN0aW9uKHByb2plY3Rpb24pXG5cblx0Ly8gR2V0IHRoZSBwYXRoJ3MgYm91bmRzIChpLmUuLCBpbiBwaXhlbHMpLlxuXHRjb25zdCBiID0gcGF0aC5ib3VuZHMoZmVhdHVyZSlcblxuXHQvLyBGaXQgdGhlIGZlYXR1cmUgdG8gdGhlIGNvbnRhaW5lcidzIHdpZHRoLlxuXHRwcm9qZWN0aW9uLmZpdFNpemUoW3dpZHRoLCBoZWlnaHRdLCBmZWF0dXJlKVxuXG5cdGNvbnN0IHBvaW50cyA9IF8oZmVhdHVyZS5jb29yZGluYXRlcylcblx0XHQuZmxhdHRlbigpXG5cdFx0Lm1hcCh2ID0+ICh7XG5cdFx0XHR4eTogcHJvamVjdGlvbih2KSxcblx0XHR9KSlcblx0XHQudmFsdWUoKVxuXG5cdC8vIGcuc2VsZWN0QWxsKCdwYXRoJylcblx0Ly8gXHRcdC5kYXRhKFtmZWF0dXJlXSlcblx0Ly8gXHQuZW50ZXIoKS5hcHBlbmQoJ3BhdGgnKVxuXHQvLyBcdFx0LmF0dHIoJ2QnLCBwYXRoKVxuXG5cdC8vIFRoaXMgZnVuY3Rpb24gYWRkcyB0aGUgY2lyY2xlcy5cblx0Y29uc3QgZW50ZXIgPSAoKSA9PiB7XG5cblx0XHQvLyBKT0lOIG5ldyBkYXRhIHdpdGggb2xkIGVsZW1lbnRzLlxuXHRcdGNvbnN0IGNpcmNsZXMgPSBnLnNlbGVjdEFsbCgnY2lyY2xlJylcblx0XHRcdFx0LmRhdGEocG9pbnRzKVxuXG5cdFx0Ly8gRU5URVIgbmV3IGVsZW1lbnRzIHByZXNlbnQgaW4gbmV3IGRhdGEuXG5cdFx0Y2lyY2xlcy5lbnRlcigpLmFwcGVuZCgnY2lyY2xlJylcblx0XHRcdFx0LmF0dHJzKHtcblx0XHRcdFx0XHRjeDogd2lkdGgvMixcblx0XHRcdFx0XHRjeTogaGVpZ2h0LFxuXHRcdFx0XHRcdHI6IDAsXG5cdFx0XHRcdH0pXG5cdFx0XHQudHJhbnNpdGlvbignZW50ZXInKVxuXHRcdFx0XHQuZHVyYXRpb24oMTAwMClcblx0XHRcdFx0LmRlbGF5KChkLCBpKSA9PiBpICogNSlcblx0XHRcdFx0LmF0dHJzKHtcblx0XHRcdFx0XHRjeDogZCA9PiBkLnh5WzBdLFxuXHRcdFx0XHRcdGN5OiBkID0+IGQueHlbMV0sXG5cdFx0XHRcdFx0cjogMSxcblx0XHRcdFx0fSlcblxuXHR9XG5cblx0Ly8gVGhpcyBmdW5jdGlvbiByZW1vdmVzIHRoZSBjaXJjbGVzLlxuXHRjb25zdCBleGl0ID0gKCkgPT4ge1xuXG5cdFx0Ly8gSk9JTiBuZXcgZGF0YSB3aXRoIG9sZCBlbGVtZW50cy5cblx0XHRjb25zdCBjaXJjbGVzID0gZy5zZWxlY3RBbGwoJ2NpcmNsZScpXG5cdFx0XHRcdC5kYXRhKHBvaW50cylcblxuXHRcdC8vIFVQREFURSBvbGQgZWxlbWVudHMgcHJlc2VudCBpbiBuZXcgZGF0YS5cblx0XHRjaXJjbGVzXG5cdFx0XHQudHJhbnNpdGlvbignZXhpdCcpXG5cdFx0XHRcdC5kdXJhdGlvbigyNTApXG5cdFx0XHRcdC5kZWxheSgoZCwgaSkgPT4gaSAqIDIpXG5cdFx0XHRcdC5hdHRycyh7XG5cdFx0XHRcdFx0Y3g6IHdpZHRoLzIsXG5cdFx0XHRcdFx0Y3k6IGhlaWdodCxcblx0XHRcdFx0XHRyOiAwLFxuXHRcdFx0XHR9KVxuXHRcdFx0LnJlbW92ZSgpXG5cblx0fVxuXG5cdC8vIEZpcmUgdGhlIGVudGVyIGZ1bmN0aW9uIG9uIHBhZ2UgbG9hZC5cblx0ZW50ZXIoKVxuXG5cdC8vIExpc3RlbiB0byBidXR0b24gY2xpY2tzLlxuXHRkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdidXR0b24uZW50ZXInKS5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsIGVudGVyKVxuXHRkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdidXR0b24uZXhpdCcpLmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgZXhpdClcblxufSlcbiJdfQ==
<!DOCTYPE html>
<title>Circles on an exploded mesh</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>
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
// Setup chart dimensions.
const margin = { top: 10, right: 10, bottom: 10, left: 10 }
const outerWidth = 360
const outerHeight = outerWidth
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})`)
// Get GeoJSON.
d3.json('./MA.topojson', json => {
const feature = topojson.mesh(json)
// Get feature's bounds and centroid.
const bounds = d3.geoBounds(feature)
const centroid = d3.geoCentroid(feature)
// Use them to create the projection.
const projection = d3.geoConicConformal()
.parallels([bounds[0][1], bounds[1][1]])
.rotate([-centroid[0], 0])
.center([0, -centroid[1]])
// Get the path.
const path = d3.geoPath().projection(projection)
// Get the path's bounds (i.e., in pixels).
const b = path.bounds(feature)
// Fit the feature to the container's width.
projection.fitSize([width, height], feature)
const points = _(feature.coordinates)
.flatten()
.map(v => ({
xy: projection(v),
}))
.value()
// g.selectAll('path')
// .data([feature])
// .enter().append('path')
// .attr('d', path)
// 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,
r: 0,
})
.transition('enter')
.duration(1000)
.delay((d, i) => i * 5)
.attrs({
cx: d => d.xy[0],
cy: d => d.xy[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,
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)
})
.buttons
margin 0 auto
text-align center
.map
svg
display block
margin 0 auto
circle
fill #8f092a
// fill-opacity 0.125
// stroke #8f092a
path
fill none
stroke #8f092a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment