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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more