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';
this.projection = projection
.geoSatellite()
.distance(1.75)
.fitExtent(
[
[this.padding, this.padding],
[this.size - this.padding, this.size - this.padding]
],
{ type: 'Sphere' }
);
this.path = d3.geoPath().context(this.context);
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;
});
}
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]]);
});
}
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;
}
}
}
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));
});
}
render(x = 0, y = 0, highlight = false) {
y = y < -90 ? -90 : y > 90 ? 90 : y;
x = x % 360;
this.x = x;
this.y = y;
this.projection.rotate([x, y, 0]);
this.path.projection(this.projection);
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
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();
this.context.fillStyle = '#ffFFff';
this.context.beginPath();
this.path({ type: 'FeatureCollection', features: this.geojson.features });
this.context.fill();
this.context.stroke();
if (highlight) {
this.context.fillStyle = '#000000';
this.context.beginPath();
this.path(highlight);
this.context.fill();
this.context.stroke();
}
}
}
return new CanvasWorld(canvas);
}