Skip to content

Instantly share code, notes, and snippets.

@jthomassie
Last active June 13, 2023 20:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jthomassie/9569067 to your computer and use it in GitHub Desktop.
Save jthomassie/9569067 to your computer and use it in GitHub Desktop.
d3 globe arcs
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.graticule {
fill: none;
stroke: #ccc;
stroke-width: 0.7;
stroke-opacity: 1.0;
}
.edge {
fill: none;
stroke: #444;
stroke-width: 0.8;
stroke-opacity: 1.0;
}
.land {
fill: #ccc;
fill-opacity: 0.6;
stroke: #999;
stroke-opacity: 1.0;
stroke-width: 1.0;
}
.countries path {
fill: #ddd;
fill-opacity: 0.4;
stroke: #444;
stroke-linejoin: round;
stroke-width: 0.4;
stroke-opacity: 0.2;
}
.countries path:hover {
fill-opacity: 0.6;
stroke-width: 1.1;
stroke-opacity: 0.5;
}
.labels {
font: 12px sans-serif;
fill: #444;
}
.point{
fill: #444;
stroke: #444;
stroke-width: 1;
stroke-opacity: 1;
}
.noclick {
pointer-events:none;
}
.arcs {
opacity:.1;
stroke: gray;
stroke-width: 3;
}
.flyers {
stroke-width:1;
opacity: .6;
stroke: darkred;
}
.arc, .flyer {
stroke-linejoin: round;
fill:none;
}
.arc { }
.flyer { }
.flyer:hover { }
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/queue.v1.min.js"></script>
<script src="http://d3js.org/topojson.v0.min.js"></script>
<script>
d3.select(window)
.on("mousemove", mousemove)
.on("mouseup", mouseup);
var width = 960,
height = 500;
var center = [50.0, -40.0];
var proj = d3.geo.orthographic()
.translate([width / 2, height / 2])
.scale(220)
.precision(0.3)
.rotate(center)
.clipAngle(90);
var sky = d3.geo.orthographic()
.translate([width / 2, height / 2])
.clipAngle(90)
.scale(280);
var path = d3.geo.path().projection(proj).pointRadius(2);
var graticule = d3.geo.graticule();
var swoosh = d3.svg.line()
.x(function(d) { return d[0]; })
.y(function(d) { return d[1]; })
.interpolate("basis");
var links = [],
arcLines = [];
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.on("mousedown", mousedown);
queue()
.defer(d3.json, "world-110m.json")
.defer(d3.json, "arc-places.json")
.await(ready);
function ready(error, world, places) {
var ocean_fill = svg.append("defs").append("radialGradient")
.attr("id", "ocean_fill")
.attr("cx", "75%")
.attr("cy", "25%");
// water sphere
svg.append("path")
.datum({type: "Sphere"})
.attr("class", "water noclick")
.attr("d", path)
.style("fill", "#fff");
// graticule
svg.append("path")
.datum(graticule)
.attr("class", "graticule noclick")
.attr("d", path);
// land shape
svg.append("path")
.datum(topojson.object(world, world.objects.land))
.attr("class", "land noclicks")
.attr("d", path);
// edge sphere
svg.append("path")
.datum({type: "Sphere"})
.attr("class", "edge noclick")
.attr("d", path);
svg.append("g")
.attr("class","points")
.selectAll("text").data(places.features)
.enter().append("path")
.attr("class", "point")
.attr("d", path);
// spawn links between cities as source/target coord pairs
places.features.forEach(function(a) {
places.features.forEach(function(b) {
if (a !== b) {
links.push({
source: a.geometry.coordinates,
target: b.geometry.coordinates
});
}
});
});
// build geoJSON features from links array
links.forEach(function(e,i,a) {
var feature = { "type": "Feature", "geometry": { "type": "LineString", "coordinates": [e.source,e.target] }};
arcLines.push(feature);
});
svg.append("g").attr("class","arcs")
.selectAll("path").data(arcLines)
.enter().append("path")
.attr("class","arc")
.attr("d",path);
svg.append("g").attr("class","flyers")
.selectAll("path").data(links)
.enter().append("path")
.attr("class","flyer")
.attr("d", function(d) {
// return swoosh(flying_arc(d));
var ddd = "M";
var pts = flying_arc(d);
for (var i = 0; i < pts.length; i++ ) {
if(i == 1) {
ddd += "C";
}
ddd += pts[i][0];
ddd += ",";
ddd += pts[i][1];
if(i < pts.length-1) {
ddd += " ";
}
}
//console.log(ddd);
return ddd;
});
proj.rotate(center);
sky.rotate(center);
refresh();
}
function flying_arc(pts) {
var source = pts.source,
target = pts.target;
var mid = location_along_arc(source, target, 0.35);
var mid2 = location_along_arc(source, target, 0.65);
var result = [
proj(source),
sky(mid),
sky(mid2),
proj(target)
];
//console.log(proj(source));
return result;
}
function refresh() {
svg.selectAll(".land").attr("d", path);
svg.selectAll(".point").attr("d", path);
svg.selectAll("path").attr("d", path);
svg.selectAll(".arc")
.attr("d", path)
.attr("opacity", function(d) {
return fade_at_edge(d);
});
svg.selectAll(".flyer")
.attr("d", function(d) {
return swoosh(flying_arc(d));
})
.attr("opacity", function(d) {
return fade_at_edge(d);
});
}
function fade_at_edge(d) {
var centerPos = proj.invert([width/2,height/2]),
arc = d3.geo.greatArc(),
start, end;
// function is called on 2 different data structures..
if (d.source) {
start = d.source,
end = d.target;
} else {
start = d.geometry.coordinates[0];
end = d.geometry.coordinates[1];
}
var start_dist = 1.57 - arc.distance({source: start, target: centerPos}),
end_dist = 1.57 - arc.distance({source: end, target: centerPos});
var fade = d3.scale.linear().domain([-0.1, 0]).range([0, 0.1]) ;
var dist = start_dist < end_dist ? start_dist : end_dist;
return fade(dist);
}
function location_along_arc(start, end, loc) {
var interpolator = d3.geo.interpolate(start,end);
return interpolator(loc);
}
// modified from http://bl.ocks.org/1392560
var m0, o0;
function mousedown() {
m0 = [d3.event.pageX, d3.event.pageY];
o0 = proj.rotate();
d3.event.preventDefault();
}
function mousemove() {
if (m0) {
var m1 = [d3.event.pageX, d3.event.pageY]
, o1 = [o0[0] + (m1[0] - m0[0]) / 6, o0[1] + (m0[1] - m1[1]) / 6];
o1[1] = o1[1] > 55 ? 55 :
o1[1] < -55 ? -55 :
o1[1];
console.log(o1);
proj.rotate(o1);
sky.rotate(o1);
refresh();
}
}
function mouseup() {
if (m0) {
mousemove();
m0 = null;
}
}
</script>
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@robin-rpr
Copy link

Great Job! I created a Demo on Codepen from it https://codepen.io/_robin/pen/OJEdyVL

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment