Sample code to make an animated globe with d3 on canvas. Borrowing heavily from http://bl.ocks.org/mbostock/4183330
The plane flies from startCountry
to endCountry
, and you can rotate the globe around once the animation is done
Last active
December 6, 2022 15:28
-
-
Save wwymak/dcdd12937bd4643cd9b3 to your computer and use it in GitHub Desktop.
Animated 3D globe with d3js and canvas
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var width = 400, | |
height = 400; | |
//the scale corrsponds to the radius more or less so 1/2 width | |
var projection = d3.geo.orthographic() | |
.scale(180) | |
.clipAngle(90) | |
.translate([width/2,height/2]); | |
var canvas = d3.select("#globeParent").append("canvas") | |
.attr("width", width) | |
.attr("height", height) | |
.style("cursor", "move"); | |
var c = canvas.node().getContext("2d"); | |
var path = d3.geo.path() | |
.projection(projection) | |
.context(c); | |
var selectedCountryFill = "#007ea3", | |
flightPathColor = "#007ea3", | |
landFill = "#b9b5ad", | |
seaFill = "#e9e4da"; | |
var startCountry = "Australia"; | |
var endCountry = "United Kingdom"; | |
//interpolator from http://bl.ocks.org/jasondavies/4183701 | |
var d3_geo_greatArcInterpolator = function() { | |
var d3_radians = Math.PI / 180; | |
var x0, y0, cy0, sy0, kx0, ky0, | |
x1, y1, cy1, sy1, kx1, ky1, | |
d, | |
k; | |
function interpolate(t) { | |
var B = Math.sin(t *= d) * k, | |
A = Math.sin(d - t) * k, | |
x = A * kx0 + B * kx1, | |
y = A * ky0 + B * ky1, | |
z = A * sy0 + B * sy1; | |
return [ | |
Math.atan2(y, x) / d3_radians, | |
Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_radians | |
]; | |
} | |
interpolate.distance = function() { | |
if (d == null) k = 1 / Math.sin(d = Math.acos(Math.max(-1, Math.min(1, sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0))))); | |
return d; | |
}; | |
interpolate.source = function(_) { | |
var cx0 = Math.cos(x0 = _[0] * d3_radians), | |
sx0 = Math.sin(x0); | |
cy0 = Math.cos(y0 = _[1] * d3_radians); | |
sy0 = Math.sin(y0); | |
kx0 = cy0 * cx0; | |
ky0 = cy0 * sx0; | |
d = null; | |
return interpolate; | |
}; | |
interpolate.target = function(_) { | |
var cx1 = Math.cos(x1 = _[0] * d3_radians), | |
sx1 = Math.sin(x1); | |
cy1 = Math.cos(y1 = _[1] * d3_radians); | |
sy1 = Math.sin(y1); | |
kx1 = cy1 * cx1; | |
ky1 = cy1 * sx1; | |
d = null; | |
return interpolate; | |
}; | |
return interpolate; | |
} | |
function ready(error, world, names) { | |
if (error) throw error; | |
var globe = {type: "Sphere"}, | |
land = topojson.feature(world, world.objects.land), | |
countries = topojson.feature(world, world.objects.countries).features, | |
i = -1; | |
countries = countries.filter(function(d) { | |
return names.some(function(n) { | |
if (d.id == n.id) return d.name = n.name; | |
}); | |
}).sort(function(a, b) { | |
return a.name.localeCompare(b.name); | |
}); | |
var startIDObj = names.filter(function(d){ | |
return (d.name).toLowerCase() == (startCountry).toLowerCase(); | |
})[0]; | |
var endIDObj = names.filter(function(d){ | |
return (d.name).toLowerCase() == (endCountry).toLowerCase(); | |
})[0]; | |
var startGeom = countries.filter(function(d){ | |
return d.id == startIDObj.id | |
}), | |
endGeom = countries.filter(function(d){ | |
return d.id == endIDObj.id | |
}) | |
var journey = []; | |
journey[0] = startGeom[0]; | |
journey[1] = endGeom[0]; | |
var n = countries.length; | |
var startCoord = d3.geo.centroid(journey[0]), | |
endCoord = d3.geo.centroid(journey[1]) | |
var coords = [-startCoord[0], -startCoord[1]] | |
var flightPath ={} | |
flightPath.type = "LineString"; | |
flightPath.coordinates = [startCoord, endCoord]; | |
var plane = document.getElementById('plane'); | |
projection.rotate(coords); | |
redrawGlobeOnly(); | |
//redraw(flightPathDynamic) | |
customTransition(journey) | |
function redrawGlobeOnly(){ | |
c.clearRect(0, 0, width, height); | |
//base globe | |
c.shadowBlur = 0, c.shadowOffsetX = 0, c.shadowOffsetY = 0; | |
c.fillStyle = seaFill, c.beginPath(), path(globe), c.fill(); | |
c.fillStyle = landFill, c.beginPath(), path(land), c.fill(); | |
//fills for start and end countries | |
c.fillStyle = flightPathColor, c.beginPath(), path(journey[0]), c.fill(); | |
c.fillStyle = flightPathColor, c.beginPath(), path(journey[1]), c.fill(); | |
} | |
function redraw(){ | |
c.clearRect(0, 0, width, height); | |
//base globe | |
c.shadowBlur = 0, c.shadowOffsetX = 0, c.shadowOffsetY = 0; | |
c.fillStyle = seaFill, c.beginPath(), path(globe), c.fill(); | |
c.fillStyle = landFill, c.beginPath(), path(land), c.fill(); | |
//fills for start and end countries | |
c.fillStyle = selectedCountryFill, c.beginPath(), path(journey[0]), c.fill(); | |
c.fillStyle = selectedCountryFill, c.beginPath(), path(journey[1]), c.fill(); | |
//flight path | |
c.strokeStyle = selectedCountryFill, c.lineWidth = 3, c.setLineDash([10, 10]) | |
c.beginPath(), path(flightPath), | |
//c.shadowColor = "#373633", | |
//c.shadowBlur = 20, c.shadowOffsetX = 5, c.shadowOffsetY = 20, | |
c.stroke(); | |
} | |
function redraw3(flightPath, angle, planeSize){ | |
var pt = projection.rotate(); | |
var planeCartesianCoord = projection([-pt[0], -pt[1], 0]); | |
c.clearRect(0, 0, width, height); | |
c.shadowBlur = 0, c.shadowOffsetX = 0, c.shadowOffsetY = 0; | |
c.fillStyle = seaFill, c.beginPath(), path(globe), c.fill(); | |
c.fillStyle = landFill, c.beginPath(), path(land), c.fill(); | |
c.fillStyle = selectedCountryFill, c.beginPath(), path(journey[0]), c.fill(); | |
c.fillStyle = selectedCountryFill, c.beginPath(), path(journey[1]), c.fill(); | |
c.strokeStyle = flightPathColor, c.lineWidth = 3, c.setLineDash([10, 10]) | |
c.beginPath(), path(flightPath), | |
//c.shadowColor = "#373633", | |
//c.shadowBlur = 20, c.shadowOffsetX = 5, c.shadowOffsetY = 20, | |
c.stroke(); | |
drawPlane(c, plane, planeCartesianCoord[0], planeCartesianCoord[1], angle, planeSize,planeSize) | |
} | |
//letting you drag the globe around but setting it so you can't tilt the globe over | |
var dragBehaviour = d3.behavior.drag() | |
.on('drag', function(){ | |
var dx = d3.event.dx; | |
var dy = d3.event.dy; | |
var rotation = projection.rotate(); | |
var radius = projection.scale(); | |
var scale = d3.scale.linear() | |
.domain([-1 * radius, radius]) | |
.range([-90, 90]); | |
var degX = scale(dx); | |
var degY = scale(dy); | |
rotation[0] += degX; | |
rotation[1] -= degY; | |
if (rotation[1] > 90) rotation[1] = 90; | |
if (rotation[1] < -90) rotation[1] = -90; | |
if (rotation[0] >= 180) rotation[0] -= 360; | |
projection.rotate(rotation); | |
redraw(); | |
}) | |
//make the plane always align with the direction of travel | |
function calcAngle(originalRotate, newRotate){ | |
var deltaX = newRotate[0] - originalRotate[0], | |
deltaY = newRotate[1] - originalRotate[1] | |
return Math.atan2(deltaY, deltaX); | |
} | |
//this is to make the globe rotate and the plane fly along the path | |
function customTransition(journey){ | |
var rotateFunc = d3_geo_greatArcInterpolator(); | |
d3.transition() | |
.delay(250) | |
.duration(5050) | |
.tween("rotate", function() { | |
var point = d3.geo.centroid(journey[1]) | |
rotateFunc.source(projection.rotate()).target([-point[0], -point[1]]).distance(); | |
var pathInterpolate = d3.geo.interpolate(projection.rotate(), [-point[0], -point[1]]); | |
var oldPath = startCoord; | |
return function (t) { | |
projection.rotate(rotateFunc(t)); | |
var newPath = [-pathInterpolate(t)[0], -pathInterpolate(t)[1]]; | |
var planeAngle = calcAngle(projection(oldPath), projection(newPath)); | |
var flightPathDynamic = {} | |
flightPathDynamic.type = "LineString"; | |
flightPathDynamic.coordinates = [startCoord, [-pathInterpolate(t)[0], -pathInterpolate(t)[1]]]; | |
var maxPlaneSize = 0.1 * projection.scale(); | |
//this makes the plane grows and shrinks at the takeoff, landing | |
if (t <0.1){ | |
redraw3(flightPathDynamic, planeAngle, Math.pow(t/0.1, 0.5) * maxPlaneSize); | |
}else if(t > 0.9){ | |
redraw3(flightPathDynamic, planeAngle, Math.pow((1-t)/0.1, 0.5) * maxPlaneSize ); | |
}else{ | |
redraw3(flightPathDynamic, planeAngle, maxPlaneSize); | |
} | |
//redraw3(flightPathDynamic, (planeAngle)) | |
}; | |
//} | |
}).each("end", function(){ | |
//make the plane disappears after it's reached the destination | |
//also enable the drag interaction at this point | |
redraw(); | |
d3.select("#globeParent").select('canvas').call(dragBehaviour); | |
}) | |
} | |
//add the plane to the canvas and rotate it | |
function drawPlane(context, image, xPos, yPos, angleInRad, imageWidth, imageHeight){ | |
context.save(); | |
context.translate(xPos, yPos); | |
// rotate around that point, converting our | |
// angle from degrees to radians | |
context.rotate(angleInRad); | |
// draw it up and to the left by half the width | |
// and height of the image, plus add some shadow | |
//context.shadowColor = "#373633", context.shadowBlur = 20, context.shadowOffsetX = 5, context.shadowOffsetY = 10; | |
context.drawImage(image, -(imageWidth/2), -(imageHeight/2), imageWidth, imageHeight); | |
// and restore the co-ords to how they were when we began | |
context.restore(); | |
} | |
} | |
queue() | |
.defer(d3.json, "world-110m.json") | |
.defer(d3.tsv, "world-country-names.tsv") | |
.await(ready); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script> | |
<script src="//code.jquery.com/jquery-1.11.3.min.js"></script> | |
<script src="https://d3js.org/topojson.v1.min.js"></script> | |
<script src="https://d3js.org/queue.v1.min.js"></script> | |
<meta charset="UTF-8"> | |
<title></title> | |
</head> | |
<body> | |
<div class="container-fluid map-container"> | |
<div class="row"> | |
<div class="col-md-12" id="globeParent"> | |
<div style="display:none;"> | |
<img id="plane" src="plane-2_03-black.png"> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script src="globe.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
id | name | |
---|---|---|
-1 | Northern Cyprus | |
-2 | Kosovo | |
-3 | Somaliland | |
4 | Afghanistan | |
8 | Albania | |
10 | Antarctica | |
12 | Algeria | |
16 | American Samoa | |
20 | Andorra | |
24 | Angola | |
28 | Antigua and Barbuda | |
31 | Azerbaijan | |
32 | Argentina | |
36 | Australia | |
40 | Austria | |
44 | Bahamas | |
48 | Bahrain | |
50 | Bangladesh | |
51 | Armenia | |
52 | Barbados | |
56 | Belgium | |
60 | Bermuda | |
64 | Bhutan | |
68 | Bolivia, Plurinational State of | |
70 | Bosnia and Herzegovina | |
72 | Botswana | |
74 | Bouvet Island | |
76 | Brazil | |
84 | Belize | |
86 | British Indian Ocean Territory | |
90 | Solomon Islands | |
92 | Virgin Islands, British | |
96 | Brunei Darussalam | |
100 | Bulgaria | |
104 | Myanmar | |
108 | Burundi | |
112 | Belarus | |
116 | Cambodia | |
120 | Cameroon | |
124 | Canada | |
132 | Cape Verde | |
136 | Cayman Islands | |
140 | Central African Republic | |
144 | Sri Lanka | |
148 | Chad | |
152 | Chile | |
156 | China | |
158 | Taiwan, Province of China | |
162 | Christmas Island | |
166 | Cocos (Keeling) Islands | |
170 | Colombia | |
174 | Comoros | |
175 | Mayotte | |
178 | Congo | |
180 | Congo, the Democratic Republic of the | |
184 | Cook Islands | |
188 | Costa Rica | |
191 | Croatia | |
192 | Cuba | |
196 | Cyprus | |
203 | Czech Republic | |
204 | Benin | |
208 | Denmark | |
212 | Dominica | |
214 | Dominican Republic | |
218 | Ecuador | |
222 | El Salvador | |
226 | Equatorial Guinea | |
231 | Ethiopia | |
232 | Eritrea | |
233 | Estonia | |
234 | Faroe Islands | |
238 | Falkland Islands (Malvinas) | |
239 | South Georgia and the South Sandwich Islands | |
242 | Fiji | |
246 | Finland | |
248 | Åland Islands | |
250 | France | |
254 | French Guiana | |
258 | French Polynesia | |
260 | French Southern Territories | |
262 | Djibouti | |
266 | Gabon | |
268 | Georgia | |
270 | Gambia | |
275 | Palestinian Territory, Occupied | |
276 | Germany | |
288 | Ghana | |
292 | Gibraltar | |
296 | Kiribati | |
300 | Greece | |
304 | Greenland | |
308 | Grenada | |
312 | Guadeloupe | |
316 | Guam | |
320 | Guatemala | |
324 | Guinea | |
328 | Guyana | |
332 | Haiti | |
334 | Heard Island and McDonald Islands | |
336 | Holy See (Vatican City State) | |
340 | Honduras | |
344 | Hong Kong | |
348 | Hungary | |
352 | Iceland | |
356 | India | |
360 | Indonesia | |
364 | Iran, Islamic Republic of | |
368 | Iraq | |
372 | Ireland | |
376 | Israel | |
380 | Italy | |
384 | Côte d'Ivoire | |
388 | Jamaica | |
392 | Japan | |
398 | Kazakhstan | |
400 | Jordan | |
404 | Kenya | |
408 | Korea, Democratic People's Republic of | |
410 | Korea, Republic of | |
414 | Kuwait | |
417 | Kyrgyzstan | |
418 | Lao People's Democratic Republic | |
422 | Lebanon | |
426 | Lesotho | |
428 | Latvia | |
430 | Liberia | |
434 | Libya | |
438 | Liechtenstein | |
440 | Lithuania | |
442 | Luxembourg | |
446 | Macao | |
450 | Madagascar | |
454 | Malawi | |
458 | Malaysia | |
462 | Maldives | |
466 | Mali | |
470 | Malta | |
474 | Martinique | |
478 | Mauritania | |
480 | Mauritius | |
484 | Mexico | |
492 | Monaco | |
496 | Mongolia | |
498 | Moldova, Republic of | |
499 | Montenegro | |
500 | Montserrat | |
504 | Morocco | |
508 | Mozambique | |
512 | Oman | |
516 | Namibia | |
520 | Nauru | |
524 | Nepal | |
528 | Netherlands | |
531 | Curaçao | |
533 | Aruba | |
534 | Sint Maarten (Dutch part) | |
535 | Bonaire, Sint Eustatius and Saba | |
540 | New Caledonia | |
548 | Vanuatu | |
554 | New Zealand | |
558 | Nicaragua | |
562 | Niger | |
566 | Nigeria | |
570 | Niue | |
574 | Norfolk Island | |
578 | Norway | |
580 | Northern Mariana Islands | |
581 | United States Minor Outlying Islands | |
583 | Micronesia, Federated States of | |
584 | Marshall Islands | |
585 | Palau | |
586 | Pakistan | |
591 | Panama | |
598 | Papua New Guinea | |
600 | Paraguay | |
604 | Peru | |
608 | Philippines | |
612 | Pitcairn | |
616 | Poland | |
620 | Portugal | |
624 | Guinea-Bissau | |
626 | Timor-Leste | |
630 | Puerto Rico | |
634 | Qatar | |
638 | Réunion | |
642 | Romania | |
643 | Russian Federation | |
646 | Rwanda | |
652 | Saint Barthélemy | |
654 | Saint Helena, Ascension and Tristan da Cunha | |
659 | Saint Kitts and Nevis | |
660 | Anguilla | |
662 | Saint Lucia | |
663 | Saint Martin (French part) | |
666 | Saint Pierre and Miquelon | |
670 | Saint Vincent and the Grenadines | |
674 | San Marino | |
678 | Sao Tome and Principe | |
682 | Saudi Arabia | |
686 | Senegal | |
688 | Serbia | |
690 | Seychelles | |
694 | Sierra Leone | |
702 | Singapore | |
703 | Slovakia | |
704 | Viet Nam | |
705 | Slovenia | |
706 | Somalia | |
710 | South Africa | |
716 | Zimbabwe | |
724 | Spain | |
728 | South Sudan | |
729 | Sudan | |
732 | Western Sahara | |
740 | Suriname | |
744 | Svalbard and Jan Mayen | |
748 | Swaziland | |
752 | Sweden | |
756 | Switzerland | |
760 | Syrian Arab Republic | |
762 | Tajikistan | |
764 | Thailand | |
768 | Togo | |
772 | Tokelau | |
776 | Tonga | |
780 | Trinidad and Tobago | |
784 | United Arab Emirates | |
788 | Tunisia | |
792 | Turkey | |
795 | Turkmenistan | |
796 | Turks and Caicos Islands | |
798 | Tuvalu | |
800 | Uganda | |
804 | Ukraine | |
807 | Macedonia, the former Yugoslav Republic of | |
818 | Egypt | |
826 | United Kingdom | |
831 | Guernsey | |
832 | Jersey | |
833 | Isle of Man | |
834 | Tanzania, United Republic of | |
840 | United States | |
850 | Virgin Islands, U.S. | |
854 | Burkina Faso | |
858 | Uruguay | |
860 | Uzbekistan | |
862 | Venezuela, Bolivarian Republic of | |
876 | Wallis and Futuna | |
882 | Samoa | |
887 | Yemen | |
894 | Zambia |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For anyone who wants a preview, I put it in a Codepen https://codepen.io/_robin/pen/bGKzdJG?editors=1010 =)