Published
Edited
Jan 27, 2020
Importers
26 stars
Insert cell
Insert cell
chart = {
const context = DOM.context2d(width, height);
const path = d3.geoPath(projection, context);

swoosh.context(context);
function render(land) {
context.clearRect(0, 0, width, height);
// Background
context.beginPath();
path(sphere);
context.fillStyle = "#fff";
context.fill();
// Land
context.beginPath();
path(land);
context.fillStyle = "#ccc";
context.fill();
// Border around the globe
context.beginPath();
path(sphere);
context.strokeStyle = '#ccc';
context.stroke();
// Shadow for the lofted arcs
links.forEach((link) => {
context.beginPath();
path(link.feature);
context.globalAlpha = fadeOnEdge(link) * 0.25;
context.shadowColor = '#333';
context.shadowBlur = 5;
context.lineWidth = 0.5;
context.lineWidth = 2;
context.strokeStyle = '#ccc';
context.stroke();
});
context.shadowBlur = 0;

// Lofted arcs and the locations
links.forEach((link) => {
context.globalAlpha = fadeOnEdge(link);
context.beginPath();
swoosh(flyingArc(link));
context.strokeStyle = 'tomato';
context.lineWidth = 2;
context.stroke();
context.beginPath();
const [sourceX, sourceY] = projection(link.source);
context.arc(sourceX, sourceY, cityMarkerRadius, 0, 2 * Math.PI);
context.fillStyle = '#666';
context.fill();
context.beginPath();
const [targetX, targetY] = projection(link.target);
context.arc(targetX, targetY, cityMarkerRadius, 0, 2 * Math.PI);
context.fillStyle = '#666';
context.fill();
});
context.lineWidth = 1;
context.globalAlpha = 1;
}
return d3.select(context.canvas)
.call(drag(projection)
.on("drag.render", () => render(land110))
.on("end.render", () => render(land50)))
.call(() => render(land50))
.node();
}
Insert cell
function drag(projection) {
let v0, q0, r0;
function dragstarted() {
v0 = versor.cartesian(projection.invert([d3.event.x, d3.event.y]));
q0 = versor(r0 = projection.rotate());
}
function dragged() {
const v1 = versor.cartesian(projection.rotate(r0).invert([d3.event.x, d3.event.y]));
const q1 = versor.multiply(q0, versor.delta(v0, v1));
projection.rotate(versor.rotation(q1));
loftedProjection.rotate(versor.rotation(q1));
}
return d3.drag()
.on("start", dragstarted)
.on("drag", dragged);
}
Insert cell
projection = d3.geoOrthographic().precision(0.1)
Insert cell
loftedProjection = d3.geoOrthographic().precision(0.1);
Insert cell
Insert cell
cityMarkerRadius = 3
Insert cell
swoosh = d3.line()
.curve(d3.curveNatural)
.defined(function(d) { return projection.invert(d); });
Insert cell
function flyingArc(link) {
var source = link.source,
target = link.target,
middle = locationAlongArc(source, target, 0.5);
return [
projection(source),
loftedProjection(middle),
projection(target)
];
}
Insert cell
function locationAlongArc(start, end, theta) {
return d3.geoInterpolate(start, end)(theta);
}
Insert cell
function fadeOnEdge(link) {
const center = projection.invert([width / 2, width / 2]);
const source = link.source;
const target = link.target;
const startDistance = 1.57 - d3.geoDistance(source, center);
const endDistance = 1.57 - d3.geoDistance(target, center);
const fade = d3.scaleLinear()
.domain([-0.1, 0])
.range([0, 0.1]);
const distance = startDistance < endDistance ? startDistance : endDistance;
const clamper = d3.scaleLinear()
.domain([0, 1])
.range([0, 1])
.clamp(true);
return clamper(fade(distance));
}
Insert cell
Insert cell
Insert cell
height = {
projection.fitExtent(extent, sphere)
loftedProjection.fitExtent(extent, sphere);
const [[x0, y0], [x1, y1]] = d3.geoPath(projection).bounds(sphere);
const dy = Math.ceil(y1 - y0);
const l = Math.min(Math.ceil(x1 - x0), dy);
const scale = projection.scale() * (l - 1) / l
projection.scale(scale).precision(0.2);
loftedProjection.scale(scale * loftedness).precision(0.2);
return dy + 2 * margin;
}
Insert cell
sphere = ({type: "Sphere"})
Insert cell
land50 = FileAttachment("land-50m.json").json().then(world => topojson.feature(world, world.objects.land))
Insert cell
land110 = FileAttachment("land-110m.json").json().then(world => topojson.feature(world, world.objects.land))
Insert cell
Insert cell
Insert cell
versor = require("versor@0.0.3")
Insert cell
topojson = require("topojson-client@3")
Insert cell
d3 = require("d3@5")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more