Last active
May 25, 2017 15:31
-
-
Save willgriffiths/d85ef13cd284d3f7313c16819dcb44d2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
body { | |
margin: 0; | |
} | |
svg { | |
position: absolute; | |
} | |
.tooltip text { | |
font: 14px Helvetica, Arial, sans-serif; | |
} | |
.tooltip rect { | |
fill: rgba(200, 200, 200, 0.8); | |
} | |
</style> | |
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.1.0/d3.min.js"></script> | |
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.0/topojson.min.js"></script> | |
<script type="text/javascript" src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script> | |
<script> | |
function init() { | |
var meteoriteDataURL = 'https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/meteorite-strike-data.json'; | |
var mapURL = 'https://raw.githubusercontent.com/mbostock/topojson/master/examples/world-110m.json'; | |
function getRadius(mass) { | |
if (mass < 1000) { | |
return 0.5; | |
} | |
if (mass < 100000) { | |
return 1; | |
} | |
if (mass < 1000000) { | |
return 2; | |
} | |
if (mass < 10000000) { | |
return 4; | |
} | |
return 8; | |
}; | |
var width = 960, | |
height = 500, | |
originalProjectionScale = height / 4; | |
var tooltipWidth = 170, | |
tooltipHeight = 74; | |
var formatTime = d3.timeFormat("%B %d, %Y"); | |
var colors = d3.scaleSequential(d3.interpolateCool); | |
var projection = d3.geoOrthographic().scale(originalProjectionScale).translate([ | |
width / 2, | |
height / 2 | |
]).rotate([0, 0]).clipAngle(90).precision(1); | |
var canvas = d3.select("body") | |
.append("canvas") | |
.attr('width', width) | |
.attr('height', height), | |
hiddenCanvas = d3.select("body") | |
.append("canvas") | |
.attr('width', width) | |
.attr('height', height) | |
.style('display','none'), | |
path = d3.geoPath().projection(projection), | |
circle = d3.geoCircle(), | |
ctx, | |
hiddenCtx; | |
var cachedHiddenCanvas = false; | |
var svg = d3.select('body') | |
.append('svg') | |
.attr('width', 200) | |
.attr('height', 150) | |
.style('top', -200) | |
.style('left', -200); | |
d3.json(mapURL, function(error, world) { | |
if (error) | |
throw(error); | |
hiddenCtx = hiddenCanvas.node().getContext('2d'); | |
ctx = canvas.node().getContext('2d'); | |
var land = topojson.feature(world, world.objects.land), | |
globe = { | |
type: 'Sphere' | |
}; | |
d3.json(meteoriteDataURL, function(error, meteoriteData) { | |
if (error) | |
throw(error); | |
var impacts = [], | |
impactsScaleable = [], | |
impactScale = 1; | |
var nextColor = 1, | |
colorKey = {}; | |
//Remove meteors without mass or coordinates | |
//Convert mass to integers and year to time | |
//Sort decending by mass so we can draw them from largest to smallest | |
var meteorites = meteoriteData.features.filter(function(d) { | |
return d.geometry && d.properties.mass; | |
}).map(function(d) { | |
d.properties.mass = parseFloat(d.properties.mass,10); | |
d.properties.year = d3.isoParse(d.properties.year); | |
return d; | |
}).sort(function(a,b) { | |
return b.properties.mass - a.properties.mass; | |
}); | |
//Colour scale for coloring meteorite impacts | |
colors.domain([d3.max(meteorites, function(d) { | |
return Math.log10(d.properties.mass); | |
}),d3.min(meteorites, function(d) { | |
return Math.log10(d.properties.mass); | |
})]); | |
meteorites = meteorites.map(function(d) { | |
d.properties.color = colors(Math.log10(d.properties.mass)); | |
return d; | |
}); | |
meteorites.forEach(function(d,i) { | |
var color = d3.hsl(d.properties.color), | |
hiddenColor = getColor() | |
radius = getRadius(d.properties.mass); | |
color.opacity = 0.75; | |
color = color + ''; | |
impacts.push({ | |
circle: circle.center(d.geometry.coordinates).radius(getRadius(d.properties.mass)).precision(20)(), | |
color: color, | |
mass: d.properties.mass, | |
hiddenColor: hiddenColor | |
}); | |
}); | |
meteorites.forEach(function(d,i) { | |
//Create a d3 color so we can change the opacity of it | |
var color = d3.hsl(d.properties.color), | |
hiddenColor = getColor(); | |
color.opacity = 0.75; | |
color + ''; | |
colorKey[hiddenColor] = d; | |
impactsScaleable.push({ | |
circle: circle.precision(20), | |
color: color, | |
mass: d.properties.mass, | |
coordinates: d.geometry.coordinates, | |
hiddenColor: hiddenColor | |
}); | |
}); | |
drawCanvas(ctx, impactsScaleable, impactScale, false); | |
var zoomD3 = d3.zoom() | |
.scaleExtent([0, 32]) | |
.on("zoom", zoomed); | |
var dragD3 = d3.drag() | |
.on("drag", dragged) | |
.on("start", dragstarted) | |
.on("end", dragended); | |
canvas.on('mousemove', mousemove); | |
canvas.call(dragD3); | |
canvas.call(zoomD3); | |
function mousemove() { | |
var pickedColor, | |
rgb, | |
target; | |
if (!cachedHiddenCanvas) { | |
drawCanvas(hiddenCtx, impactsScaleable, impactScale, true); | |
cachedHiddenCanvas = true; | |
} | |
pickedColor = hiddenCtx.getImageData(d3.event.offsetX, d3.event.offsetY, 1, 1).data; | |
rgb = 'rgb(' + pickedColor.slice(0,-1).join(',') + ')'; | |
target = colorKey[rgb] || ''; | |
if(target) { | |
showTooltip(ctx, target, d3.event.offsetX, d3.event.offsetY); | |
} else { | |
hideTooltip(); | |
} | |
} | |
function zoomed() { | |
impactScale = (d3.event.transform.k/3 < 1) ? 1 : d3.event.transform.k/3; | |
currentK = d3.event.transform.k; | |
projection.scale((d3.event.transform.k * originalProjectionScale)); | |
drawCanvas(ctx, impactsScaleable, impactScale, false); | |
cachedHiddenCanvas = false; | |
} | |
function dragstarted(d) { | |
d3.event.sourceEvent.stopPropagation(); | |
d3.select(this).classed("dragging", true); | |
} | |
function dragged(d) { | |
var scale = projection.scale(), | |
currentRotation = projection.rotate(), | |
rotationScale = scale/50, | |
newLambda, | |
newPhi; | |
newLambda = currentRotation[0] + (d3.event.dx/rotationScale); | |
newPhi = currentRotation[1] + (-d3.event.dy/rotationScale); | |
projection.rotate([newLambda,newPhi]); | |
drawCanvas(ctx, impactsScaleable, impactScale, false); | |
cachedHiddenCanvas = false; | |
} | |
function dragended(d) { | |
d3.select(this).classed("dragging", false); | |
} | |
function drawCanvas(canvas, impacts, impactScale, hidden) { | |
canvas.save(); | |
canvas.clearRect(0, 0, width, height); | |
//Draw land | |
canvas.beginPath(); path.context(canvas)(land); | |
canvas.fillStyle='black'; canvas.fill(); canvas.closePath(); | |
//Draw sphere outline | |
canvas.beginPath(); path(globe); canvas.stroke(); canvas.closePath(); | |
//Draw impacts on canvas normally if shown | |
if (!hidden) { | |
impacts.forEach(function(d) { | |
canvas.beginPath(); | |
path.context(canvas)(d.circle.center(d.coordinates).radius(getRadius(d.mass)/impactScale)()); | |
canvas.fillStyle= d.color; | |
canvas.fill(); | |
canvas.closePath; | |
}); | |
} else { | |
impacts.forEach(function(d) { | |
canvas.beginPath(); | |
path.context(canvas)(d.circle.center(d.coordinates).radius(getRadius(d.mass)/impactScale)()); | |
canvas.fillStyle= d.hiddenColor; | |
canvas.fill(); | |
canvas.closePath; | |
}); | |
} | |
canvas.restore(); | |
} | |
function getColor() { | |
var rgb = []; | |
rgb.push(nextColor % 256); | |
rgb.push(parseInt((nextColor / 256) % 256,10)); | |
rgb.push(parseInt((nextColor / 65536) % 256,10)); | |
nextColor++; | |
return 'rgb(' + rgb.join(',') + ')'; | |
} | |
function showTooltip(canvas, meteor, mouseX, mouseY) { | |
var tooltip; | |
svg.selectAll('g').remove(); | |
tooltip = svg.selectAll('g').data([meteor]).enter().append('g').attr('class', 'tooltip'); | |
svg.style('top', mouseY + 5) | |
.style('left', mouseX + 5); | |
tooltip.append('rect') | |
.attr('width', tooltipWidth) | |
.attr('height', tooltipHeight); | |
tooltip.append('text') | |
.attr('transform', 'translate(' + (tooltipWidth/2) + ',21)') | |
.attr('text-anchor', 'middle') | |
.text(function(d) { | |
return 'Name: ' + d.properties.name; | |
}); | |
tooltip.append('text') | |
.attr('transform', 'translate(' + (tooltipWidth/2) + ',42)') | |
.attr('text-anchor', 'middle') | |
.text(function(d) { | |
return 'Mass: ' + (d.properties.mass / 1000) + ' kg'; | |
}); | |
tooltip.append('text') | |
.attr('transform', 'translate(' + (tooltipWidth/2) + ',63)') | |
.attr('text-anchor', 'middle') | |
.text(function(d) { | |
return 'Year: ' + formatTime(d.properties.year); | |
}); | |
} | |
function hideTooltip() { | |
svg.selectAll('g').remove(); | |
svg.style('top', -200) | |
.style('left', -200); | |
} | |
}); | |
}); | |
}; | |
document.addEventListener("DOMContentLoaded", init, false); | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment