index.html
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 12px sans-serif;
}
svg {
margin: 0 auto;
display: inherit;
}
.states path {
stroke-width: 1px;
stroke: white;
fill: #DBDBDB;
cursor: pointer;
}
.arcs path {
stroke-width: 1px;
opacity: .5;
stroke: tomato;
pointer-events: none;
fill: none;
}
.arcs .great-arc-end{
fill: tomato;
}
</style>
<body>
<div class="map-container" data-contains="main"></div>
<div class="map-container" data-contains="second"></div>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script>
var gfx = {
viz: {
draw: function(layer){
gfx.baseMap.bake(layer);
gfx.arcs.bake(layer);
}
},
baseMap: {
setValues: function(){
this.width = 600;
this.height = 349;
this.projection = d3.geo.albersUsa()
.scale(730.1630554896399)
.translate([this.width/2, this.height/2]);
this.path = d3.geo.path()
.projection(this.projection);
},
bake: function(layer){
this[layer] = {};
this[layer].svg = d3.select('.map-container[data-contains="'+layer+'"]').append('svg')
.attr('width', this.width)
.attr('height', this.height);
this[layer].centered;
this[layer].states = this[layer].svg.append('g')
.attr('class','states');
this[layer].states.selectAll('path')
.data(topojson.feature(data.baseMapGeometry, data.baseMapGeometry.objects.states).features)
.enter()
.append('path')
.attr('d', this.path)
.on('click', function(d,i) { gfx.baseMap.zoom(d,i,layer) });
},
zoom: function(d,i,layer){
var x, y, k;
if (d && gfx.baseMap[layer].centered !== d) {
var centroid = gfx.baseMap.path.centroid(d);
var b = gfx.baseMap.path.bounds(d);
x = centroid[0];
y = centroid[1];
k = .8 / Math.max((b[1][0] - b[0][0]) / gfx.baseMap.width, (b[1][1] - b[0][1]) / gfx.baseMap.height);
gfx.baseMap[layer].centered = d
} else {
x = gfx.baseMap.width / 2;
y = gfx.baseMap.height / 2;
k = 1;
gfx.baseMap[layer].centered = null;
}
gfx.baseMap[layer].states.selectAll("path")
.classed("highlighted",function(d) {
return d === gfx.baseMap[layer].centered;
})
.style("stroke-width", 1 / k + "px");
gfx.baseMap[layer].svg
.transition()
.duration(500)
.attr("transform","translate(" + gfx.baseMap.width / 2 + "," + gfx.baseMap.height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")");
}
},
arcs: {
bake: function(layer){
gfx.baseMap[layer].arcs = gfx.baseMap[layer].svg.append('g')
.attr('class','arcs');
var arc_group = gfx.baseMap[layer].arcs.selectAll('.great-arc-group')
.data(data.arcs).enter()
.append('g')
.classed('great-arc-group', true);
arc_group.append('path')
.attr('d', function(d) {
console.log(d)
return gfx.arcs.lngLatToArc(d, 'sourceLocation', 'targetLocation', 15);
});
arc_group.append('circle')
.attr('r', 2)
.classed('great-arc-end', true)
.attr("transform", function(d) {
return "translate(" + gfx.arcs.lngLatToPoint(d.targetLocation) + ")";
});
},
lngLatToArc: function(d, sourceName, targetName, bend){
bend = bend || 1;
var sourceLngLat = d[sourceName],
targetLngLat = d[targetName];
if (targetLngLat && sourceLngLat) {
var sourceXY = gfx.baseMap.projection( sourceLngLat ),
targetXY = gfx.baseMap.projection( targetLngLat );
if (!targetXY) console.log(d, targetLngLat, targetXY)
var sourceX = sourceXY[0],
sourceY = sourceXY[1];
var targetX = targetXY[0],
targetY = targetXY[1];
var dx = targetX - sourceX,
dy = targetY - sourceY,
dr = Math.sqrt(dx * dx + dy * dy)*bend;
var west_of_source = (targetX - sourceX) < 0;
if (west_of_source) return "M" + targetX + "," + targetY + "A" + dr + "," + dr + " 0 0,1 " + sourceX + "," + sourceY;
return "M" + sourceX + "," + sourceY + "A" + dr + "," + dr + " 0 0,1 " + targetX + "," + targetY;
} else {
return "M0,0,l0,0z";
}
},
lngLatToPoint: function(location_array){
if (location_array) {
return gfx.baseMap.projection(location_array);
} else {
return '0,0';
}
}
}
}
var onDone = {
initViz: function(){
gfx.baseMap.setValues();
gfx.viz.draw('main');
}
}
var data = {
load: {
baseMap: function(callback){
d3.json('us-states.topojson', function(error, baseMapGeometry){
if (error) return console.log(error);
data.baseMapGeometry = baseMapGeometry;
callback();
});
},
arcs: function(callback){
d3.csv('arcs.csv', function(error, arcs){
if (error) return console.log(error);
data.arcs = data.transform.locationifyArcCsv(arcs);
callback();
})
}
},
transform: {
locationifyArcCsv: function(arcs){
arcs.forEach(function(arc){
arc.sourceLocation = [+arc.source_lng, +arc.source_lat];
arc.targetLocation = [+arc.target_lng, +arc.target_lat];
});
return arcs;
}
}
}
var init = {
go: function(){
data.load.baseMap(function(){
data.load.arcs(onDone.initViz);
})
}
}
init.go();
</script>
</body>
</html>
arcs.csv
source_lng,source_lat,target_lng,target_lat
"-99.5606025","41.068178502813595","-106.503961875","33.051502817366334"
"-99.5606025","41.068178502813595","-97.27544625","34.29490081496779"
"-99.5606025","41.068178502813595","-92.793024375","34.837711658059135"
"-99.5606025","41.068178502813595","-100.3076728125","41.85852354782116"
"-99.5606025","41.068178502813595","-104.6143134375","43.18636214435451"
"-99.5606025","41.068178502813595","-106.152399375","45.57291634897"
"-99.5606025","41.068178502813595","-105.5811103125","42.3800618087319"
"-99.5606025","41.068178502813595","-74.610651328125","42.160561343227656"
"-99.5606025","41.068178502813595","-78.148248984375","40.20112201100485"
"-99.5606025","41.068178502813595","-81.795709921875","39.89836713516883"
"-99.5606025","41.068178502813595","-91.738336875","42.1320516230261"
"-99.5606025","41.068178502813595","-93.902643515625","39.89836713516886"
"-99.5606025","41.068178502813595","-146.68645699218752","62.84587613514389"
"-99.5606025","41.068178502813595","-151.03704292968752","62.3197734579205"
"-99.5606025","41.068178502813595","-150.50969917968752","68.0575087745829"
"-99.5606025","41.068178502813595","-155.58278180000002","19.896766200000002"
"-99.5606025","41.068178502813595","-155.41249371406252","19.355435189875685"
"-99.5606025","41.068178502813595","-156.22204876777346","20.77817385333129"
"-99.5606025","41.068178502813595","-156.08334637519533","20.781383752662176"
"-99.5606025","41.068178502813595","-119.41793240000001","36.77826099999999"
"-99.5606025","41.068178502813595","-111.73848904062501","34.311442605956636"
"-99.5606025","41.068178502813595","-118.62691677500001","39.80409417718468"
"-99.5606025","41.068178502813595","-115.56173122812501","44.531552843807575"
"-99.5606025","41.068178502813595","-107.13521755625001","43.90164233696157"