Skip to content

Instantly share code, notes, and snippets.

@gabrielflorit
Last active February 20, 2017 19:41
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 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
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