|
<!DOCTYPE html> |
|
<style> |
|
.states :hover { |
|
fill: red; |
|
} |
|
.state-borders { |
|
fill: none; |
|
stroke: #fff; |
|
stroke-width: 0.5px; |
|
stroke-linejoin: round; |
|
stroke-linecap: round; |
|
pointer-events: none; |
|
} |
|
</style> |
|
<svg width="960" height="600"></svg> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script src="https://d3js.org/topojson.v2.min.js"></script> |
|
<script> |
|
|
|
//Width and height of map |
|
var width = 960; |
|
var height = 500; |
|
|
|
var svg = d3.select("svg"); |
|
|
|
|
|
var projection = d3.geoAlbersUsa() |
|
.translate([width/2, height/2]) // translate to center of screen |
|
.scale([1000]); // scale things down so see entire US |
|
|
|
var path = d3.geoPath(projection); |
|
|
|
function uniqueId() { |
|
return Math.random().toString(36).substr(2, 36) + new Date().getUTCMilliseconds() |
|
} |
|
|
|
//initializing with some sample points |
|
var origin = [-122.336422, 47.610902]; |
|
var destination = [-73.986740, 40.662688]; |
|
|
|
var origin1 = [-87.981494, 41.745663]; |
|
var destination1= [-74.591878, 40.061957]; |
|
|
|
var routePairs = [ |
|
{id: uniqueId(), type: "LineString", coordinates: [origin, destination]}, |
|
{id: uniqueId(), type: "LineString", coordinates: [origin1, destination1]} |
|
]; |
|
|
|
//keeping track of routes that done animating to remove them |
|
var animatedRoutes = {}; |
|
|
|
//the size of the routePairs array to keep it from growing indefinitely |
|
var maxRoutes = 50; |
|
|
|
//limites the size of the routePairs array so routes done animating are removed |
|
function trim(routePairs, animatedRoutes, maxRoutes) { |
|
while(routePairs.length > maxRoutes) { |
|
if (!(routePairs[0].id in animatedRoutes)) { |
|
break; |
|
} |
|
delete animatedRoutes[routePairs[0].id]; |
|
routePairs.shift(); |
|
} |
|
} |
|
|
|
function drawStates(us) { |
|
|
|
svg.append("g") |
|
.attr("class", "states") |
|
.selectAll("path") |
|
.data(topojson.feature(us, us.objects.states).features) |
|
.enter().append("path") |
|
.attr("d", path); |
|
|
|
svg.append("path") |
|
.attr("class", "state-borders") |
|
.attr("d", path(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))); |
|
} |
|
|
|
function animateRoutes(routePairs) { |
|
|
|
var routes = svg.selectAll(".route") |
|
.data(routePairs, function(d) { return d.id; }) |
|
|
|
routes.exit().remove(); |
|
|
|
routes.enter() |
|
.append("path") |
|
.attr("class", "route") |
|
.attr("id", function(d) { return d.id; }) |
|
.attr("d", path) |
|
.attr("stroke", "#d3d3d3") |
|
.attr("fill", "none") |
|
.attr("stroke-dasharray", function(){ |
|
var t = this.getTotalLength(); |
|
this.__data__["len"] = t; |
|
return t + "," + t; |
|
}) |
|
.attr("stroke-dashoffset", function(){return this.__data__.len; }) |
|
.transition() |
|
.duration(300) |
|
.ease(d3.easeCubic) |
|
.attr("stroke-dashoffset", function(){return this.__data__.len * 2; }) |
|
.transition() |
|
.duration(300) |
|
.attr("stroke-dashoffset", function(){return this.__data__.len * 3; }) |
|
.on("end", function(d) {animatedRoutes[d.id] = null; } ); |
|
} |
|
|
|
//randomly select zipcodes to simulate a route between them |
|
function simulateNewData(routePairs, zipcodesLatLng) { |
|
var zipcodes = Object.keys(zipcodesLatLng); |
|
var zipcode1 = zipcodes[Math.floor(Math.random() * zipcodes.length)]; |
|
var zipcode2 = zipcodes[Math.floor(Math.random() * zipcodes.length)]; |
|
|
|
|
|
coordinates = [zipcodesLatLng[zipcode1], zipcodesLatLng[zipcode2]]; |
|
|
|
routePairs.push({id: uniqueId(), type: "LineString", |
|
coordinates: coordinates |
|
}); |
|
|
|
} |
|
|
|
var zipcodesLatLng = d3.json("zipcodes.json", function(error, zipcodesLatLng){ |
|
if (error) throw error; |
|
|
|
d3.interval(function(elapsed) { |
|
simulateNewData(routePairs, zipcodesLatLng); |
|
simulateNewData(routePairs, zipcodesLatLng); |
|
|
|
animateRoutes(routePairs); |
|
|
|
trim(routePairs, animatedRoutes, maxRoutes); |
|
}, 200); |
|
}); |
|
|
|
var us = d3.json("us-states.json", function(error, us) { |
|
if (error) throw error; |
|
drawStates(us); |
|
animateRoutes(routePairs); |
|
}); |
|
|
|
</script> |