Skip to content

Instantly share code, notes, and snippets.

@maddo
Created October 3, 2012 23:00
Show Gist options
  • Save maddo/3830441 to your computer and use it in GitHub Desktop.
Save maddo/3830441 to your computer and use it in GitHub Desktop.
<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>
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment