Published
Edited
Oct 14, 2019
Insert cell
Insert cell
Insert cell
world = {
class CanvasWorld {
constructor(el) {
this.size = 600;
this.padding = 0;
this.x = 0;
this.y = 0;
this.drag = false;

this.canvas = d3
.select(el)
.attr('height', this.size)
.attr('width', this.size)
.node();

this.context = this.canvas.getContext('2d');
this.context.lineWidth = 0.25;
this.context.fillStyle = '#ffffee';
this.context.strokeStyle = '#000';

// Set up the geo projection
this.projection = projection
.geoSatellite()
.distance(1.75)
.fitExtent(
[
[this.padding, this.padding],
[this.size - this.padding, this.size - this.padding]
],
{ type: 'Sphere' }
);

// Setup the Path generator for canvas
this.path = d3.geoPath().context(this.context);

// Handle dragging
d3.select(this.canvas).on('mousedown', () => {
this.drag = true;
});
d3.select(window).on('mousemove', () => {
if (!this.drag) return;
const target = [
0.25 * d3.event.movementX + this.x,
-.25 * d3.event.movementY + this.y
];
this.render(...target);
});
d3.select(window).on('mouseup mouseleave', () => {
this.drag = false;
});
}

// Initiate the map using the provided geojson
createWorldElements(data) {
this.geojson = topojson.feature(data, data.objects.countries);
this.render();

d3.select(this.canvas).on('mousemove', () => {
const feature = this.findFeature();
if (!feature) return;
this.render(this.x, this.y, feature);
});

d3.select(this.canvas).on('click', () => {
const feature = this.findFeature();
if (!feature) return;
const center = this.projection.invert(this.path.centroid(feature));

this.panTo([-center[0], -center[1]]);
});
}

// Find which country is being targeted
findFeature(event) {
const x = d3.event.layerX;
const y = d3.event.layerY;

const r = this.size / 2;
const distanceFromCenter = Math.sqrt(
Math.pow(Math.abs(x - r), 2) + Math.pow(Math.abs(y - r), 2)
);

if (distanceFromCenter > r) return;

const coordinates = this.projection.invert([x, y]);
const point = turf.point(coordinates);
const l = this.geojson.features.length;

for (let i = 0; i < l; i++) {
const feature = this.geojson.features[i];
if (turf.booleanPointInPolygon(point, feature)) {
return feature;
}
}
}

// Animate to a new location
panTo(target) {
const interpolator = d3.interpolateArray([this.x, this.y], target);
const ease = d3.easeBackOut.overshoot(3);
const transition = d3
.transition()
.duration(1000)
.ease(ease);

transition.tween("render", () => t => {
this.render(...interpolator(t));
});
}

// When rendering the map we can retrieve rotation values (x and y)
render(x = 0, y = 0, highlight = false) {
y = y < -90 ? -90 : y > 90 ? 90 : y;
x = x % 360;

this.x = x;
this.y = y;

// Set the rotation to the projection
this.projection.rotate([x, y, 0]);

// Set the path generator with the new projection
this.path.projection(this.projection);

this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);

// Draw globe background
this.context.fillStyle = '#eeeeff';
this.context.beginPath();
this.context.arc(
this.size / 2,
this.size / 2,
this.size / 2 - this.padding,
0,
2 * Math.PI
);
this.context.fill();

// Draw countries
this.context.fillStyle = '#ffFFff';
this.context.beginPath();
this.path({ type: 'FeatureCollection', features: this.geojson.features });
this.context.fill();
this.context.stroke();

// Draw highlighted country
if (highlight) {
this.context.fillStyle = '#000000';
this.context.beginPath();
this.path(highlight);
this.context.fill();
this.context.stroke();
}
}
}

// Return our world instance
return new CanvasWorld(canvas);
}
Insert cell
// Fetch world geography and call the createWorldElements method
{
const url = 'https://cdn.jsdelivr.net/npm/world-atlas@2.0.2/countries-110m.json'
d3.json(url).then((data,error) => {
if(error) throw error
world.createWorldElements(data)
})
}
Insert cell
Insert cell
Insert cell
Insert cell
projection = require('d3-geo-projection')
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