Public
Edited
Aug 1, 2023
115 forks
Importers
139 stars
Topographic MappingBubble mapChoroplethAccess to Family planningMD Counties Total Cases MapTendance de la production des déchets en Union EuropéenneAndy's Walgreens COVID-19 Tracker TrackerElection Maps for Incomplete ResultsA better U.S. house election results map?1983 Mayoral Election, Dot density mapsMastodon 🐘Cheat sheet bertinBertin.js: regular squaresWaterlinesNeumorphism Contour Density MapCartographic DoodlesStars and constellationsPlot: Grid choroplethHello Polygon MorphingMapping with pie chartsU.S. Geographic DataHow big are countries... like really!AttitudeB&W ChoroplethWeb Mercator Tile VisibilityMARTINI: Real-Time RTIN Terrain Mesh"Magnifying-Glass" projectionsTissot's indicatrixAntipodal mapMapping gridded data with a Voronoi diagramA Map of Every BuildingUrbano Monti’s Planisphere (1587)Bivariate choroplethDIY HillshadeMapbox Map MakerWorld tourHillshaderSimplified Earth with curved shapesHexbin mapInner glowNicolosi vs. StereographicData-driven projections: Darwin's worldSatellite ground track visualizerDirection to shoreHello, OpenLayers!U.S. airports VoronoiHello, NYC Geosearch API!Mapbox Fly-ToSpilhaus shoreline mapWalmart’s growthHow well does population density predict U.S. voting outcomes?Drawing maps from geodata with D3 & ObservableHexgrid maps with d3-hexgridTissot's indicatrixWorld airports VoronoiSwiss Elevation Line GraphsVector tiles
Versor dragging
OrthographicSolar TerminatorStreaming ShapefilesFake GlobesPeirce Quincuncial🍃 LeafletU.S.G.S. World Earthquake MapUsing Mapbox GL JSUsing Google Maps
Also listed in…
Gallery
d3-drag
d3-geo
Insert cell
Insert cell
Insert cell
chart = {
const context = DOM.context2d(width, height);
const path = d3.geoPath(projection, context);

function render(land) {
context.clearRect(0, 0, width, height);
context.beginPath(), path(sphere), context.fillStyle = "#fff", context.fill();
context.beginPath(), path(land), context.fillStyle = "#000", context.fill();
context.beginPath(), path(sphere), context.stroke();
}

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, a0, l;

function pointer(event, that) {
const t = d3.pointers(event, that);

if (t.length !== l) {
l = t.length;
if (l > 1) a0 = Math.atan2(t[1][1] - t[0][1], t[1][0] - t[0][0]);
dragstarted.apply(that, [event, that]);
}

// For multitouch, average positions and compute rotation.
if (l > 1) {
const x = d3.mean(t, p => p[0]);
const y = d3.mean(t, p => p[1]);
const a = Math.atan2(t[1][1] - t[0][1], t[1][0] - t[0][0]);
return [x, y, a];
}

return t[0];
}

function dragstarted({x, y}) {
v0 = versor.cartesian(projection.invert([x, y]));
q0 = versor(r0 = projection.rotate());
}

function dragged(event) {
const v1 = versor.cartesian(projection.rotate(r0).invert([event.x, event.y]));
const delta = versor.delta(v0, v1);
let q1 = versor.multiply(q0, delta);

// For multitouch, compose with a rotation around the axis.
const p = pointer(event, this);
if (p[2]) {
const d = (p[2] - a0) / 2;
const s = -Math.sin(d);
const c = Math.sign(Math.cos(d));
q1 = versor.multiply([Math.sqrt(1 - s * s), 0, 0, c * s], q1);
}

projection.rotate(versor.rotation(q1));

// In vicinity of the antipode (unstable) of q0, restart.
if (delta[0] < 0.7) dragstarted.apply(this, [event, this]);
}

return d3.drag()
.on("start", dragstarted)
.on("drag", dragged);
}
Insert cell
height = {
const [[x0, y0], [x1, y1]] = d3.geoPath(projection.fitWidth(width, sphere)).bounds(sphere);
const dy = Math.ceil(y1 - y0), l = Math.min(Math.ceil(x1 - x0), dy);
projection.scale(projection.scale() * (l - 1) / l).precision(0.2);
return dy;
}
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
versor = require("versor@0.2/dist/versor.min.js")
Insert cell
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