Skip to content

Instantly share code, notes, and snippets.

@jamieeeeeeeee
Last active November 17, 2017 20:35
Show Gist options
  • Select an option

  • Save jamieeeeeeeee/b01fe3ce35cd8f3f0396b54b772cb166 to your computer and use it in GitHub Desktop.

Select an option

Save jamieeeeeeeee/b01fe3ce35cd8f3f0396b54b772cb166 to your computer and use it in GitHub Desktop.
Rotatable globe showing the world's most populous cities, made with D3
// Copyright (c) 2013, Jason Davies, http://www.jasondavies.com
// See LICENSE.txt for details.
(function() {
var radians = Math.PI / 180,
degrees = 180 / Math.PI;
// TODO make incremental rotate optional
d3.geo.zoom = function() {
var projection,
zoomPoint,
event = d3.dispatch("zoomstart", "zoom", "zoomend"),
zoom = d3.behavior.zoom()
.on("zoomstart", function() {
var mouse0 = d3.mouse(this),
rotate = quaternionFromEuler(projection.rotate()),
point = position(projection, mouse0);
if (point) zoomPoint = point;
zoomOn.call(zoom, "zoom", function() {
projection.scale(d3.event.scale);
var mouse1 = d3.mouse(this),
between = rotateBetween(zoomPoint, position(projection, mouse1));
projection.rotate(eulerFromQuaternion(rotate = between
? multiply(rotate, between)
: multiply(bank(projection, mouse0, mouse1), rotate)));
mouse0 = mouse1;
event.zoom.apply(this, arguments);
});
event.zoomstart.apply(this, arguments);
})
.on("zoomend", function() {
zoomOn.call(zoom, "zoom", null);
event.zoomend.apply(this, arguments);
}),
zoomOn = zoom.on;
zoom.projection = function(_) {
return arguments.length ? zoom.scale((projection = _).scale()) : projection;
};
return d3.rebind(zoom, event, "on");
};
function bank(projection, p0, p1) {
var t = projection.translate(),
angle = Math.atan2(p0[1] - t[1], p0[0] - t[0]) - Math.atan2(p1[1] - t[1], p1[0] - t[0]);
return [Math.cos(angle / 2), 0, 0, Math.sin(angle / 2)];
}
function position(projection, point) {
var t = projection.translate(),
spherical = projection.invert(point);
return spherical && isFinite(spherical[0]) && isFinite(spherical[1]) && cartesian(spherical);
}
function quaternionFromEuler(euler) {
var λ = .5 * euler[0] * radians,
φ = .5 * euler[1] * radians,
γ = .5 * euler[2] * radians,
sinλ = Math.sin(λ), cosλ = Math.cos(λ),
sinφ = Math.sin(φ), cosφ = Math.cos(φ),
sinγ = Math.sin(γ), cosγ = Math.cos(γ);
return [
cosλ * cosφ * cosγ + sinλ * sinφ * sinγ,
sinλ * cosφ * cosγ - cosλ * sinφ * sinγ,
cosλ * sinφ * cosγ + sinλ * cosφ * sinγ,
cosλ * cosφ * sinγ - sinλ * sinφ * cosγ
];
}
function multiply(a, b) {
var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3],
b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
return [
a0 * b0 - a1 * b1 - a2 * b2 - a3 * b3,
a0 * b1 + a1 * b0 + a2 * b3 - a3 * b2,
a0 * b2 - a1 * b3 + a2 * b0 + a3 * b1,
a0 * b3 + a1 * b2 - a2 * b1 + a3 * b0
];
}
function rotateBetween(a, b) {
if (!a || !b) return;
var axis = cross(a, b),
norm = Math.sqrt(dot(axis, axis)),
halfγ = .5 * Math.acos(Math.max(-1, Math.min(1, dot(a, b)))),
k = Math.sin(halfγ) / norm;
return norm && [Math.cos(halfγ), axis[2] * k, -axis[1] * k, axis[0] * k];
}
function eulerFromQuaternion(q) {
return [
Math.atan2(2 * (q[0] * q[1] + q[2] * q[3]), 1 - 2 * (q[1] * q[1] + q[2] * q[2])) * degrees,
Math.asin(Math.max(-1, Math.min(1, 2 * (q[0] * q[2] - q[3] * q[1])))) * degrees,
Math.atan2(2 * (q[0] * q[3] + q[1] * q[2]), 1 - 2 * (q[2] * q[2] + q[3] * q[3])) * degrees
];
}
function cartesian(spherical) {
var λ = spherical[0] * radians,
φ = spherical[1] * radians,
cosφ = Math.cos(φ);
return [
cosφ * Math.cos(λ),
cosφ * Math.sin(λ),
Math.sin(φ)
];
}
function dot(a, b) {
for (var i = 0, n = a.length, s = 0; i < n; ++i) s += a[i] * b[i];
return s;
}
function cross(a, b) {
return [
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0]
];
}
})();
<!DOCTYPE html>
<meta charset="utf-8">
<style>
#globe{
background: #fcfcfa;
width: 900px;
height: 500px;
margin-left: 50px;
}
.stroke {
fill: none;
stroke: #000;
stroke-width: 3px;
}
.fill {
fill: #fff;
}
.graticule {
fill: none;
stroke: #777;
stroke-width: .5px;
stroke-opacity: .5;
}
.land {
fill: #344729;
}
.boundary {
fill: none;
stroke: #fff;
stroke-width: .5px;
}
.overlay {
fill: none;
pointer-events: all;
}
.city{
fill: #D17846;
}
h1{
font-size: 3em;
font-family: 'Playfair Display', serif;
color: #252525;
padding-left: 10px;
margin: 5px;
}
h2 {
font-size: 1.5em;
font-family: 'Droid Sans Mono', Arial, serif;
font-weight: 400;
color: #525252;
padding-left: 20px;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="d3.geo.zoom.js"></script>
<script src="//d3js.org/queue.v1.min.js"></script>
<link href='https://fonts.googleapis.com/css?family=Playfair+Display:400,900' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Droid+Sans+Mono' rel='stylesheet' type='text/css'>
<div id="header">
<h1>The Most Populous Cities in the World<h1>
<h2> Data from Natural Earth</h2>
</div>
<div id="globe"></div>
<script>
var width = 680,
height = 680;
// Map configuration
var rScale = d3.scale.sqrt();
var peoplePerPixel = 50000;
var max_population = [];
var projection = d3.geo.orthographic()
.scale(270)
.translate([width / 2, height / 2])
.clipAngle(90)
.precision(.1);
var zoom = d3.behavior.zoom()
.scaleExtent([1,6])
.on("zoom",zoomed);
var zoomEnhanced = d3.geo.zoom().projection(projection)
.on("zoom",zoomedEnhanced);
var drag = d3.behavior.drag()
.origin(function() { var r = projection.rotate(); return {x: r[0], y: -r[1]}; })
.on("drag", dragged)
.on("dragstart", dragstarted)
.on("dragend", dragended);
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule();
var svg = d3.select("#globe").append("svg")
.attr("width", width)
.attr("height", height);
var pathG = svg.append("g");
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.call(zoomEnhanced)
pathG.append("defs").append("path")
.datum({type: "Sphere"})
.attr("id", "sphere")
.attr("d", path);
pathG.append("use")
.attr("class", "stroke")
.attr("xlink:href", "#sphere");
pathG.append("use")
.attr("class", "fill")
.attr("xlink:href", "#sphere");
pathG.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
d3.json("worldTopo-with-places-simplified.json", function(error, world) {
// to render meridians/graticules on top of lands, use insert which adds new path before graticule in the selection
pathG.insert("path", ".graticule")
.datum(topojson.feature(world, world.objects.world_subunits))
.attr("class", "land")
.attr("d", path)
//this line of code draw countries on to the map
pathG.insert("path", ".graticule")
.datum(topojson.mesh(world, world.objects.world_subunits, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
});
//FUCKING WORKING
d3.json("world-places.json", function(error, world_places) {
if (error) return console.error(error);
data = world_places.features;
// setting the circle size (not radius!) according to the number of inhabitants per city
population_array = [];
for (i = 0; i < world_places.features.length; i++) {
if (world_places.features[i].properties ) {
population_array.push(world_places.features[i].properties.POP_MAX);
} else {
population_array.push(1);
}
}
max_population = population_array.sort(d3.descending)[0]
var rMin = 0;
var rMax = Math.sqrt(max_population / (peoplePerPixel * Math.PI));
rScale.domain([0, max_population]);
rScale.range([rMin, rMax]);
path.pointRadius(function(d) {
return d.properties ? rScale(d.properties.POP_MAX) : 1;
//console.log(rScale(d.properties.POP_MAX));
});
console.log("geo");
console.log(data[0].properties.POP_MAX);
pathG.selectAll("path.city")
.data(data)
.enter().append("path")
.attr("class", "city")
.attr("d", path)
.attr("fill-opacity", 0.7);
});
// apply transformations to map and all elements on it
function zoomed()
{
pathG.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
//grids.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
//geofeatures.select("path.graticule").style("stroke-width", 0.5 / d3.event.scale);
pathG.selectAll("path.boundary").style("stroke-width", 0.5 / d3.event.scale);
}
function zoomedEnhanced()
{
pathG.selectAll("path").attr("d",path);
//pathG.selectAll("circle").attr("cx");
//pathG.selectAll("circle").attr("cy");
}
function dragstarted(d)
{
//stopPropagation prevents dragging to "bubble up" which triggers same event for all elements below this object
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged() {
projection.rotate([d3.event.x, -d3.event.y]);
pathG.selectAll("path").attr("d", path);
//pathG.selectAll("circle").attr("cx");
//pathG.selectAll("circle").attr("cy");
}
function dragended(d)
{
d3.select(this).classed("dragging", false);
}
d3.select(self.frameElement).style("height", height + "px");
</script>
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View raw

(Sorry about that, but we can’t show files that are this big right now.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment