|
|
|
// move elements to front |
|
// http://tributary.io/tributary/3922684 |
|
d3.selection.prototype.moveToFront = function() { |
|
return this.each(function(){ |
|
this.parentNode.appendChild(this); |
|
}); |
|
}; |
|
|
|
var width = 960, |
|
height = 500, |
|
active = d3.select(null); //zoom |
|
|
|
// set projection and map position |
|
// tweek these settings for a nice display |
|
var projection = d3.geo.albers() // popular alternative is geo.mercator() |
|
.center([0, 40.0]) //[30, 45.0] |
|
.scale(900) // 275 |
|
.rotate([97.8717, 0]) |
|
.translate([width / 2, height / 2]) |
|
.precision(.1); |
|
|
|
var path = d3.geo.path() |
|
.projection(projection); |
|
|
|
var svg = d3.select("body").append("svg") |
|
.attr("width", width) |
|
.attr("height", height) |
|
.attr("class", "chart"); |
|
|
|
var graticule = d3.geo.graticule(); |
|
|
|
var dataset; |
|
|
|
var destination = [-75.1667 , 39.9500]; // Philly lat lon |
|
|
|
var map = svg.append("g") |
|
.attr("class", "map"); |
|
|
|
var |
|
vizGroup = svg.append("g").attr("class", "viz"), |
|
countryGroup = map.append("g"), |
|
stateGroup = map.append("g") |
|
; |
|
|
|
// plot world, states, and people data |
|
queue() |
|
.defer(d3.json, "world-50m.json") |
|
.defer(d3.json, "us.json") |
|
.defer(d3.csv, "people.csv") |
|
.await(visualize); |
|
|
|
function visualize(error, world, us, people){ |
|
if (error) return console.log(error); |
|
|
|
dataset = people; |
|
|
|
// draw backgound to for reset function |
|
map.append("rect") |
|
.attr("class", "background") |
|
.attr("width", width) |
|
.attr("height", height) |
|
.on("click", reset); |
|
|
|
// draw graticules |
|
map.append("path") |
|
.datum(graticule) |
|
.attr("class", "graticule") |
|
.attr("d", path); |
|
|
|
// draw world |
|
countryGroup |
|
.selectAll("path") |
|
.data(topojson.feature(world, world.objects.countries).features) |
|
.enter().insert("path", ".graticule") |
|
.attr("class", "boundary") |
|
.attr("d", path); |
|
|
|
// draw states |
|
stateGroup |
|
.selectAll("path") |
|
.data(topojson.feature(us, us.objects.states).features) |
|
.enter().append("path") |
|
// .attr("d", path) |
|
.attr("class", "state-boundary") |
|
.attr("d", path); |
|
|
|
// create a group for each person record |
|
var groups = vizGroup.selectAll("g") |
|
.data(dataset) |
|
.enter() |
|
.append("g") |
|
.attr("class", function (d) { |
|
return d.name; |
|
}) |
|
.on("mouseover", function() { |
|
d3.select(this).attr("class", "highlight"); |
|
// move to front |
|
var sel = d3.select(this); |
|
sel.moveToFront(); |
|
}) |
|
.on("mouseout", function (d) { |
|
d3.select(this).attr("class", function () { |
|
return d.name;}); |
|
}) |
|
; |
|
// draw arcs from each record origin to destination |
|
groups.append("path") |
|
.attr("class", "arc") |
|
.attr("d", function (d) { |
|
var coordDepart = [ d.longitude, d.latitude ]; |
|
var coordArrive = destination; |
|
return path({ |
|
type: "LineString", |
|
coordinates: [coordDepart,coordArrive] |
|
}); |
|
}) |
|
.call(d3.helper.tooltip(function(d, i){ |
|
return "<IMG SRC=" + "'" +d.avatar+ "'" + ">"+ |
|
"<div class='name'>" + d.name + "</div>"+ |
|
"<div class = 'org'>" + d.org + "</div>"+ |
|
"<div class = 'place'>" + d.place + "</div>" |
|
} |
|
)); |
|
// draw circles for each record origin |
|
groups.append("circle") |
|
.attr("cx", function(d) { |
|
return projection([d.longitude, d.latitude])[0]; |
|
}) |
|
.attr("cy", function(d) { |
|
return projection([d.longitude, d.latitude])[1]; |
|
}) |
|
.attr("r", function(d) { return 4 + (d.count * 2); }) |
|
.attr("class", "origin") |
|
.call(d3.helper.tooltip(function(d, i){ |
|
return "<IMG SRC=" + "'" +d.avatar+ "'" + ">"+ |
|
"<div class='name'>" + d.name + "</div>"+ |
|
"<div class = 'org'>" + d.org + "</div>"+ |
|
"<div class = 'place'>" + d.place + "</div>" |
|
} |
|
)) |
|
.on("click", clicked); // zoom |
|
|
|
function clicked(d) { |
|
if (active.node() === this) return reset(); |
|
active.classed("active", false); |
|
active = d3.select(this).classed("active", true); |
|
|
|
var |
|
cx = projection([d.longitude, d.latitude])[0], |
|
cy = projection([d.longitude, d.latitude])[1], |
|
scale = 3.75, |
|
translate = [width / 2 - scale * cx, height / 2 - scale * cy]; |
|
|
|
// TODO how to link this to main.css? |
|
d3.selectAll(".chart circle").transition() |
|
.duration(1000) //750 |
|
.style("stroke-width", 1 / scale *1.5 + "px") // *1.5 is just a fudge |
|
.attr("r", 6 / scale *1.5 ) |
|
.attr("transform", "translate(" + translate + ")scale(" + scale + ")"); |
|
|
|
d3.selectAll(".chart path").transition() |
|
.duration(1000) //750 |
|
.style("stroke-width", 2 / scale *1.5 + "px") // *1.5 is just a fudge |
|
.attr("transform", "translate(" + translate + ")scale(" + scale + ")"); |
|
} |
|
|
|
// TODO how to link this to main.css? |
|
function reset() { |
|
active.classed("active", false); |
|
active = d3.select(null); |
|
|
|
d3.selectAll(".chart path, circle").transition() |
|
.duration(1000) //750 |
|
.style("stroke-width", "1.5px") // this is a fudge |
|
.attr("r", 6 ) |
|
.attr("transform", ""); |
|
} |
|
|
|
// backgound for sidebar |
|
groups.append("rect") |
|
.attr("class", "side-menu") |
|
.attr("y", function(d, i) { return (0 + (20 * i)); }) |
|
.attr("x", "0") |
|
.attr("width", "150") |
|
.attr("height", "20") |
|
.call(d3.helper.tooltip(function(d, i){ |
|
return "<IMG SRC=" + "'" +d.avatar+ "'" + ">" |
|
+"<div class='name'>" + d.name + "</div>" |
|
+"<div class = 'org'>" + d.org + "</div>" |
|
} |
|
)) |
|
.on("click", clicked); |
|
; |
|
|
|
// add sidebar list of names for each person record |
|
groups.append("text") |
|
.attr("class", "speaker-list") |
|
.attr("y", function(d, i) { return (15 + (20 * i)); }) |
|
.attr("x", "10") |
|
.text(function(d) {return d.name;}) |
|
.call(d3.helper.tooltip(function(d, i){ |
|
return "<IMG SRC=" + "'" +d.avatar+ "'" + ">"+ |
|
"<div class='name'>" + d.name + "</div>"+ |
|
"<div class = 'org'>" + d.org + "</div>"+ |
|
"<div class = 'place'>" + d.place + "</div>" |
|
} |
|
)) |
|
.on("click", clicked); |
|
; |
|
|
|
}; |
|
|
|
d3.select(self.frameElement).style("height", height + "px"); |
|
|
|
// universal zoom and pan listener, disabled |
|
// http://bl.ocks.org/d3noob/5189284 |
|
// var zoom = d3.behavior.zoom() |
|
// .on("zoom",function() { |
|
// d3.selectAll(".chart path, circle").attr("transform","translate("+ |
|
// d3.event.translate.join(",")+")scale("+d3.event.scale+")"); |
|
// d3.selectAll(".chart path, circle").selectAll("path") |
|
// .attr("d", path.projection(projection)); |
|
// }); |
|
// svg.call(zoom) |