Created
October 3, 2012 23:00
-
-
Save maddo/3830441 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.v2.js"></script> | |
<style type="text/css"> | |
svg { | |
border:1px solid gray; | |
} | |
#countries path { | |
fill: green; | |
stroke: black; | |
fill-opacity: .3; | |
} | |
#arcs path { | |
fill: none; | |
stroke-opacity: .9; | |
} | |
</style> | |
<div id="body"> | |
<div id="chart"></div> | |
<div id="footer"> | |
d3.geo.azimuthal | |
<div class="hint">drag to rotate the origin</div> | |
<div> | |
<select> | |
<option value="equalarea">equalarea</option> | |
<option value="equidistant">equidistant</option> | |
<option value="gnomonic">gnomonic</option> | |
<option value="orthographic" selected>orthographic</option> | |
<option value="stereographic">stereographic</option> | |
</select> | |
</div> | |
</div> | |
</div> | |
<script type="text/javascript"> | |
var w = 1200, | |
h = 800; | |
var useGreatCircles = true; | |
d3.loadData = function() { | |
var loadedCallback = null; | |
var toload = {}; | |
var data = {}; | |
var loaded = function(name, d) { | |
delete toload[name]; | |
data[name] = d; | |
return notifyIfAll(); | |
}; | |
var notifyIfAll = function() { | |
if((loadedCallback != null) && d3.keys(toload).length === 0) { | |
loadedCallback(data); | |
} | |
}; | |
var loader = { | |
json: function(name, url) { | |
toload[name] = url; | |
d3.json(url, function(d) { | |
return loaded(name, d); | |
}); | |
return loader; | |
}, | |
csv: function(name, url) { | |
toload[name] = url; | |
d3.csv(url, function(d) { | |
return loaded(name, d); | |
}); | |
return loader; | |
}, | |
onload: function(callback) { | |
loadedCallback = callback; | |
notifyIfAll(); | |
} | |
}; | |
return loader; | |
}; | |
var feature; | |
var projection = d3.geo.azimuthal() | |
.scale(380) | |
.origin([-97, 30]) | |
.mode("orthographic") | |
.translate([640, 400]); | |
var path = d3.geo.path() .projection(projection); | |
var circle = d3.geo.greatCircle() | |
.origin(projection.origin()); | |
// TODO fix d3.geo.azimuthal to be consistent with scale | |
var scale = { | |
orthographic: 380, | |
stereographic: 380, | |
gnomonic: 380, | |
equidistant: 380 / Math.PI * 2, | |
equalarea: 380 / Math.SQRT2 | |
}; | |
var arc = d3.geo.greatArc().precision(3); | |
var svg = d3.select("body").append("svg:svg") | |
.attr("width", w) | |
.attr("height", h) | |
; | |
var countries = svg.append("g").attr("id", "countries"); | |
var centroids = svg.append("g").attr("id", "centroids"); | |
var arcs = svg.append("g").attr("id", "arcs"); | |
d3.loadData() | |
.json('countries', 'world-countries.json') | |
.onload(function(data) { | |
svg.on("mousedown", mousedown); | |
var nodeDataByCode = {}, links = []; | |
// console.debug(countries); | |
var nodes = [ | |
[-97, 30], | |
[-71.03,42.37], | |
[-61.872666,-53.169127] | |
]; | |
var maxMagnitude = 100; | |
var magnitudeFormat = d3.format(",.0f"); | |
var arcWidth = d3.scale.linear().domain([1, maxMagnitude]).range([.1, 7]); | |
var minColor = '#f0f0f0', maxColor = 'rgb(8, 48, 107)'; | |
var arcColor = d3.scale.log().domain([1, maxMagnitude]).range([minColor, maxColor]); | |
var arcOpacity = d3.scale.log().domain([1, maxMagnitude]).range([0.3, 1]); | |
var feature = countries.selectAll("path") | |
.data(data.countries.features) | |
.enter() | |
.append("path") | |
.attr("d", clip); | |
function nodeCoords(node) { | |
var lon = parseFloat(node[0]), lat = parseFloat(node[1]); | |
if (isNaN(lon) || isNaN(lat)) return null; | |
return [lon, lat]; | |
} | |
nodes.forEach(function(node) { | |
node.coords = nodeCoords(node); | |
node.projection = node.coords ? projection(node.coords) : undefined; | |
nodeDataByCode[node.Code] = node; | |
}); | |
links.push({ | |
source: nodeCoords(nodes[0]), | |
target: nodeCoords(nodes[1]), | |
magnitude: 100, | |
origin: {Code:'ABC'}, | |
dest: {Code:'DEF'}, | |
originp: projection(nodeCoords(nodes[0])), | |
destp: projection(nodeCoords(nodes[1])) | |
}); | |
links.push({ | |
source: nodeCoords(nodes[1]), | |
target: nodeCoords(nodes[2]), | |
magnitude: 100, | |
origin: {Code:'DEF'}, | |
dest: {Code:'GHI'}, | |
originp: projection(nodeCoords(nodes[1])), | |
destp: projection(nodeCoords(nodes[2])) | |
}); | |
var gradientNameFun = function(d) { | |
return "grd"+d.origin.Code+d.dest.Code; | |
}; | |
var gradientRefNameFun = function(d) { | |
return "url(#"+gradientNameFun(d)+")"; | |
}; | |
var drawDots = function() { | |
nodes.forEach(function(node) { | |
node.coords = nodeCoords(node); | |
node.projection = node.coords ? projection(node.coords) : undefined; | |
nodeDataByCode[node.Code] = node; | |
}); | |
centroids.selectAll("circle") | |
.data(nodes.filter(function(node) { | |
return node.projection ? true : false | |
})) | |
.enter() | |
.append("circle") | |
.attr("cx", function(d) { return d.projection[0] } ) | |
.attr("cy", function(d) { return d.projection[1] } ) | |
.attr("r", 5) | |
.attr("fill", "blue") | |
.attr("opacity", 1) | |
; | |
}; | |
var redrawDots = function(){ | |
centroids.selectAll("circle") | |
.remove("circle"); | |
drawDots(); | |
} | |
var drawLines = function(){ | |
var arcNodes = arcs.selectAll("path") | |
.data(links) | |
.enter().append("path") | |
//.attr("visibility", function(d) { return d.magnitude > 500 ? "visible" : "hidden"}) | |
.attr("stroke", gradientRefNameFun) | |
//.attr("stroke", "red") | |
//.attr("opacity", function(d) { return arcOpacity(d.magnitude); }) | |
//.attr("stroke", strokeFun) | |
.attr("stroke-linecap", "round") | |
.attr("stroke-width", function(d) { return arcWidth(d.magnitude); }) | |
.attr("d", function(d) { | |
if (useGreatCircles) return splitPath(path(arc(d))); | |
else return path({ | |
type: "LineString", | |
coordinates: [d.source, d.target] | |
}); | |
}); | |
} | |
var redrawLines = function(){ | |
var arcNodes = arcs.selectAll("path") | |
.remove("path"); | |
drawLines(); | |
} | |
drawDots(); | |
drawLines(); | |
var strokeFun = function(d) { return arcColor(d.magnitude); }; | |
function splitPath(path) { | |
var avgd = 0, i, d; | |
var c, pc, dx, dy; | |
var points = path.split("L"); | |
if (points.length < 2) return path; | |
var newpath = [ points[0] ]; | |
var coords = points.map(function(d, i) { | |
return d.substr(i > 0 ? 0 : 1).split(","); // remove M and split | |
}); | |
// calc avg dist between points | |
for (i = 1; i < coords.length; i++) { | |
pc = coords[i-1]; c = coords[i]; | |
dx = c[0] - pc[0]; dy = c[1] - pc[1]; | |
d = Math.sqrt(dx*dx + dy*dy); | |
c.push(d); // push dist as last elem of c | |
avgd += d; | |
} | |
avgd /= coords.length - 1; | |
// for points with long dist from prev use M instead of L | |
for (i = 1; i < coords.length; i++) { | |
c = coords[i]; | |
newpath.push((c[2] > 5 * avgd ? "M" : "L") + points[i]); | |
} | |
return newpath.join(); | |
} | |
var defs = svg.append("svg:defs"); | |
defs.append("marker") | |
.attr("id", "arrowHead") | |
.attr("viewBox", "0 0 10 10") | |
.attr("refX", 10) | |
.attr("refY", 5) | |
.attr("orient", "auto") | |
//.attr("markerUnits", "strokeWidth") | |
.attr("markerUnits", "userSpaceOnUse") | |
.attr("markerWidth", 4*2) | |
.attr("markerHeight", 3*2) | |
.append("polyline") | |
.attr("points", "0,0 10,5 0,10 1,5") | |
.attr("fill", maxColor) | |
//.attr("opacity", 0.5) | |
; | |
var gradient = defs.selectAll("linearGradient") | |
.data(links) | |
.enter() | |
.append("svg:linearGradient") | |
.attr("id", gradientNameFun) | |
.attr("gradientUnits", "userSpaceOnUse") | |
.attr("x1", function(d) { return d.originp[0]; }) | |
.attr("y1", function(d) { return d.originp[1]; }) | |
.attr("x2", function(d) { return d.destp[0]; }) | |
.attr("y2", function(d) { return d.destp[1]; }) | |
; | |
gradient.append("svg:stop") | |
.attr("offset", "0%") | |
.attr("stop-color", minColor) | |
.attr("stop-opacity", .0); | |
gradient.append("svg:stop") | |
.attr("offset", "80%") | |
.attr("stop-color", strokeFun) | |
.attr("stop-opacity", 1.0); | |
gradient.append("svg:stop") | |
.attr("offset", "100%") | |
.attr("stop-color", strokeFun) | |
.attr("stop-opacity", 1.0); | |
// arcNodes.on("mouseover", function(d) { | |
// d3.select(this) | |
// .attr("stroke", "green") | |
// .attr("marker-end", "url(#arrowHead)"); | |
// }); | |
// arcNodes.on("mouseout", function(d) { | |
// d3.select(this) | |
// .attr("marker-end", "none") | |
// .attr("stroke", gradientRefNameFun); | |
// }) | |
// ; | |
function clip(d) { | |
return path(circle.clip(d)); | |
} | |
d3.select("select").on("change", function() { | |
projection.mode(this.value).scale(scale[this.value]); | |
refresh(750); | |
}); | |
d3.select(window) | |
.on("mousemove", mousemove) | |
.on("mouseup", mouseup); | |
var m0, o0; | |
function mousedown() { | |
m0 = [d3.event.pageX, d3.event.pageY]; | |
o0 = projection.origin(); | |
// console.debug(m0,o0); | |
d3.event.preventDefault(); | |
} | |
function mousemove() { | |
if (m0) { | |
var m1 = [d3.event.pageX, d3.event.pageY], | |
o1 = [o0[0] + (m0[0] - m1[0]) / 8, o0[1] + (m1[1] - m0[1]) / 8]; | |
projection.origin(o1); | |
circle.origin(o1) | |
// console.debug(o1); | |
refresh(); | |
} | |
} | |
function mouseup() { | |
if (m0) { | |
mousemove(); | |
m0 = null; | |
} | |
} | |
function refresh(duration) { | |
(duration ? feature.transition().duration(duration) : feature).attr("d", clip); | |
redrawDots(); | |
redrawLines(); | |
} | |
}); | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment