Published
Edited
Apr 20, 2019
3 forks
8 stars
Insert cell
Insert cell
map = {
let container = d3.select(DOM.element("div"));
let context = this ? this.getContext("2d") : DOM.context2d(width, width);
container.append(() => context.canvas);
let tooltip = container.append("div").attr("id","tooltip");
let height = context.canvas.height;
let originalScale = height / 2.1;
let scale = originalScale;
let previousScaleFactor = 1;
let rotation;
let sphere = {type: 'Sphere'};
let path = d3.geoPath(projection, context);
let tooltipQuadtree = getQuadtree();
let tooltipPositions = [];
let tooltipPositionsIndex = {};
let twitterUserFeatures = twitterUsers.features;
/**
* After each zoom/pan projection recalculation, force the svg(really canvas) paths to update
*/
function drawWorld() {
context.clearRect(0, 0, width, height);
context.beginPath(), path(graticule), context.lineWidth = .4; context.strokeStyle = "#ccc", context.stroke();
context.beginPath(), path(land), context.fillStyle = '#888', context.fill(); context.strokeStyle = "#fff"; context.lineWidth = .3; context.stroke();
context.beginPath(), path(sphere),context.strokeStyle = "#333", context.stroke();
//set up the points
path.pointRadius(3.5);
twitterUserFeatures.forEach(d => {
context.beginPath(), context.fillStyle = 'blue'; path(d); context.fill()
});
//reset the tooltip caches
tooltipPositions = []; tooltipPositionsIndex = {}; tooltipQuadtree = getQuadtree();
//update tooltip caches
for(let i=0, len=twitterUserFeatures.length; i<len; i++){
let pixelCoords = projection(twitterUserFeatures[i].geometry.coordinates);
tooltipPositions.push(pixelCoords);
tooltipPositionsIndex[pixelCoords.join(",")] = twitterUserFeatures[i].properties.screenName;
}
//update the quadtree
tooltipQuadtree.addAll(tooltipPositions);
//hide the tooltip if the map is being zoomed/panned
d3.select('#tooltip').style('opacity', 0);
}
/**
* Every time the globe is zoomed or panned, recalculate the correct projection parameters
* and then request that the map data be redrawn/updated
*/
d3.geoZoom()
.projection(projection)
.northUp(true)
.onMove(drawWorld)
(d3.select(context.canvas).node());
//initiate the first globe draw
drawWorld();
//bind the tooltips
d3.select(context.canvas).on("mousemove click",function(){
let mouse = d3.mouse(this);
let closest = tooltipQuadtree.find(mouse[0], mouse[1], 10);
if(closest){
d3.select('#tooltip')
.style('opacity', 0.8)
.style('top', mouse[1] + 5 + 'px')
.style('left', mouse[0] + 5 + 'px')
.html("@"+tooltipPositionsIndex[closest.join(",")]);
} else {
d3.select('#tooltip')
.style('opacity', 0);
}
});
return container.node();
}
Insert cell
Insert cell
d3 = require("d3-fetch@1", "d3-geo@1", "d3-quadtree@1", "d3@5", 'd3-geo-zoom')
Insert cell
topojson = require("topojson-client@3")
Insert cell
world = d3.json("https://unpkg.com/world-atlas@1/world/110m.json")
Insert cell
land = topojson.feature(world, world.objects.countries)
Insert cell
graticule = d3.geoGraticule10()
Insert cell
//width = width //default to built in width instead - https://observablehq.com/@tmcw/responsive-notebook-design-protips
Insert cell
longitude = 50
Insert cell
//Use longitude and -20 latitude to set the initial rotation and tilt of the globe
projection = d3.geoOrthographic()
.rotate([longitude, -20])
.translate([width / 2, width / 2])
.fitExtent([[5, 5], [width - 5, width - 5]], {type: "Sphere"})
.precision(0.1)
Insert cell
function getQuadtree(){ return d3.quadtree().extent([[0, 0],[width, width]]); }
Insert cell
Insert cell
Insert cell
/**
* Every time the globe is zoomed or panned, recalculate the correct projection parameters
* and then request that the map data be redrawn/updated
*
* Note: This was originally how we were supporting zoom/pan updates, but then i found d3-geo-zoom lib
* which takes care of all of this -
*/
// function zoomed() {
// var dx = d3.event.sourceEvent.movementX;
// var dy = d3.event.sourceEvent.movementY;
// var event = d3.event.sourceEvent.type;
// if (event === 'wheel') {
// let scaleFactor = d3.event.transform.k;
// let scaleChange = scaleFactor - previousScaleFactor;
// scale = scale + scaleChange * originalScale;
// projection.scale(scale);
// previousScaleFactor = scaleFactor;
// } else {
// var r = projection.rotate();
// rotation = [r[0] + dx * 0.4, r[1] - dy * 0.5, r[2]];
// projection.rotate(rotation);
// }
// requestAnimationFrame(drawWorld);
// };
//
// //bind mouse interactions with the canvas to the custom zoom function
// d3.select(context.canvas).call(d3.zoom().scaleExtent([0, 16]).on("zoom", zoomed));
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