Skip to content

Instantly share code, notes, and snippets.

@lunarmoon26
Last active July 29, 2023 22:01
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save lunarmoon26/09d4d0ef25fd32ed663db969f5bc79fe to your computer and use it in GitHub Desktop.
Save lunarmoon26/09d4d0ef25fd32ed663db969f5bc79fe to your computer and use it in GitHub Desktop.
D3 V5 - Faux-3d Shaded Globe With Zoom, Places and Arcs

A D3 V5 implementation of a shaded globe mimic the 3d effect. Drag to rotate and middle wheel to zoom. The flyer arcs are interpolated from 2 control points.

You can modify the dataset (Topojson format) to change landmarks.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3 V5 - Faux-3d Shaded Globe With Zoom, Places and Arcs</title>
<style>
.land {
fill: rgb(117, 87, 57);
stroke-opacity: 1;
}
.countries path {
stroke: rgba(0, 0, 0, .1);
stroke-linejoin: round;
stroke-width: .5;
fill: transparent;
}
.countries path:hover {
stroke: rgba(0, 0, 0, .6);
fill-opacity: .3;
fill: white;
}
.graticule {
fill: none;
stroke: black;
stroke-width: .5;
opacity: .2;
}
.labels {
font: 8px sans-serif;
fill: black;
opacity: .5;
}
.noclicks {
pointer-events: none;
}
.point {
opacity: .6;
}
.arcs {
opacity: .1;
stroke: gray;
stroke-width: 3;
}
.flyers {
stroke-width: 1;
opacity: .6;
stroke: darkred;
}
.arc,
.flyer {
stroke-linejoin: round;
fill: none;
}
.arc {}
.flyer {}
.flyer:hover {}
</style>
</head>
<body>
<svg>
<defs>
<radialGradient cx="75%" cy="25%" id="ocean_fill">
<stop offset="5%" stop-color="#ddf" />
<stop offset="100%" stop-color="#9ab" />
</radialGradient>
<radialGradient cx="75%" cy="25%" id="globe_highlight">
<stop offset="5%" stop-color="#ffd" stop-opacity="0.6" />
<stop offset="100%" stop-color="#ba9" stop-opacity="0.2" />
</radialGradient>
<radialGradient cx="50%" cy="40%" id="globe_shading">
<stop offset="50%" stop-color="#9ab" stop-opacity="0" />
<stop offset="100%" stop-color="#3e6184" stop-opacity="0.3" />
</radialGradient>
<radialGradient cx="50%" cy="50%" id="drop_shadow">
<stop offset="20%" stop-color="#000" stop-opacity="0.5" />
<stop offset="100%" stop-color="#000" stop-opacity="0" />
</radialGradient>
</defs>
</svg>
<script src="http://d3js.org/d3.v5.min.js"></script>
<!-- Use d3-fetch instead of d3-request in ES6 -->
<script src="https://d3js.org/d3-request.v1.min.js"></script>
<script src="https://d3js.org/d3-queue.v3.min.js"></script>
<script src="http://d3js.org/topojson.v3.min.js"></script>
<script>
// References:
// http://bl.ocks.org/dwtkns/4686432
// http://bl.ocks.org/dwtkns/4973620
// http://bl.ocks.org/KoGor/5994804
// https://medium.com/@xiaoyangzhao/drawing-curves-on-webgl-globe-using-three-js-and-d3-draft-7e782ffd7ab
// https://raw.githubusercontent.com/d3/d3.github.com/master/world-110m.v1.json
var width = 960,
height = 500,
radius = 220,
sensitivity = 0.25,
offsetX = width / 2,
offsetY = height / 2,
maxElevation = 45,
initRotation = [0, -30],
scaleExtent = [1, 8],
flyerAltitude = 80;
var projection = d3
.geoOrthographic()
.scale(radius)
.rotate(initRotation)
.translate([offsetX, offsetY])
.clipAngle(90);
var skyProjection = d3
.geoOrthographic()
.scale(radius + flyerAltitude)
.rotate(initRotation)
.translate([offsetX, offsetY])
.clipAngle(90);
var path = d3
.geoPath()
.projection(projection)
.pointRadius(1.5);
var swoosh = d3.line()
.x(function (d) { return d[0] })
.y(function (d) { return d[1] })
.curve(d3.curveBasis);
var graticule = d3.geoGraticule();
var svg = d3
.select("svg")
.attr("width", width)
.attr("height", height)
.attr("transform-origin", offsetX + "px " + offsetY + "px")
.call(
d3
.drag()
.subject(function () {
var r = projection.rotate();
return { x: r[0] / sensitivity, y: -r[1] / sensitivity };
})
.on("drag", dragged)
)
.call(
d3
.zoom()
.scaleExtent(scaleExtent)
.on("zoom", zoomed)
)
.on("dblclick.zoom", null);
d3.queue()
.defer(d3.json, "https://raw.githubusercontent.com/d3/d3.github.com/master/world-110m.v1.json")
.defer(d3.json, "places.json")
.defer(d3.json, "links.json")
.await(ready);
function ready(error, world, places, links) {
svg
.append("ellipse")
.attr("cx", offsetX - 40)
.attr("cy", offsetY + radius - 20)
.attr("rx", projection.scale() * 0.9)
.attr("ry", projection.scale() * 0.25)
.attr("class", "noclicks")
.style("fill", "url(#drop_shadow)");
svg
.append("circle")
.attr("cx", offsetX)
.attr("cy", offsetY)
.attr("r", projection.scale())
.attr("class", "noclicks")
.style("fill", "url(#ocean_fill)");
svg
.append("path")
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path);
svg
.append("path")
.datum(graticule)
.attr("class", "graticule noclicks")
.attr("d", path);
svg
.append("circle")
.attr("cx", offsetX)
.attr("cy", offsetY)
.attr("r", projection.scale())
.attr("class", "noclicks")
.style("fill", "url(#globe_highlight)");
svg
.append("circle")
.attr("cx", offsetX)
.attr("cy", offsetY)
.attr("r", projection.scale())
.attr("class", "noclicks")
.style("fill", "url(#globe_shading)");
svg
.append("g")
.attr("class", "points")
.selectAll(".point")
.data(places.features)
.enter()
.append("path")
.attr("class", "point")
.attr("d", path);
svg
.append("g")
.attr("class", "labels")
.selectAll(".label")
.data(places.features)
.enter()
.append("text")
.attr("class", "label")
.text(function (d) {
return d.properties.name;
});
svg
.append("g")
.attr("class", "countries")
.selectAll("path")
.data(topojson.feature(world, world.objects.countries).features)
.enter()
.append("path")
.attr("d", path);
position_labels();
svg.append("g").attr("class", "arcs")
.selectAll("path").data(links.features)
.enter().append("path")
.attr("class", "arc")
.attr("d", path)
.attr("opacity", function (d) {
return fade_at_edge(d)
});
svg.append("g").attr("class", "flyers")
.selectAll("path").data(links.features)
.enter().append("path")
.attr("class", "flyer")
.attr("d", function (d) { return swoosh(flying_arc(d)) })
.attr("opacity", function (d) {
return fade_at_edge(d)
});
}
function position_labels() {
var centerPos = projection.invert([offsetX, offsetY]);
svg
.selectAll(".label")
.attr("text-anchor", function (d) {
var x = projection(d.geometry.coordinates)[0];
return x < offsetX - 20 ? "end" : x < offsetX + 20 ? "middle" : "start";
})
.attr("transform", function (d) {
var loc = projection(d.geometry.coordinates),
x = loc[0],
y = loc[1];
var offset = x < offsetX ? -5 : 5;
return "translate(" + (x + offset) + "," + (y - 2) + ")";
})
.style("display", function (d) {
var d = d3.geoDistance(d.geometry.coordinates, centerPos);
return d > 1.57 ? "none" : "inline";
});
}
function flying_arc(pts) {
var source = pts.geometry.coordinates[0],
target = pts.geometry.coordinates[1];
var mid1 = location_along_arc(source, target, .333);
var mid2 = location_along_arc(source, target, .667);
var result = [projection(source),
skyProjection(mid1),
skyProjection(mid2),
projection(target)]
// console.log(result);
return result;
}
function fade_at_edge(d) {
var centerPos = projection.invert([offsetX, offsetY]);
start = d.geometry.coordinates[0];
end = d.geometry.coordinates[1];
var start_dist = 1.57 - d3.geoDistance(start, centerPos),
end_dist = 1.57 - d3.geoDistance(end, centerPos);
var fade = d3.scaleLinear().domain([-.1, 0]).range([0, .1])
var dist = start_dist < end_dist ? start_dist : end_dist;
return fade(dist)
}
function location_along_arc(start, end, loc) {
var interpolator = d3.geoInterpolate(start, end);
return interpolator(loc)
}
function dragged() {
var o1 = [d3.event.x * sensitivity, -d3.event.y * sensitivity];
o1[1] =
o1[1] > maxElevation
? maxElevation
: o1[1] < -maxElevation
? -maxElevation
: o1[1];
projection.rotate(o1);
skyProjection.rotate(o1);
refresh();
}
function zoomed() {
if (d3.event) {
svg.attr("transform", "scale(" + d3.event.transform.k + ")");
}
}
function refresh() {
svg.selectAll(".land").attr("d", path);
svg.selectAll(".countries path").attr("d", path);
svg.selectAll(".graticule").attr("d", path);
refreshLandmarks();
refreshFlyers();
}
function refreshLandmarks() {
svg.selectAll(".point").attr("d", path);
position_labels();
}
function refreshFlyers() {
svg.selectAll(".arc").attr("d", path)
.attr("opacity", function (d) {
return fade_at_edge(d)
});
svg.selectAll(".flyer")
.attr("d", function (d) { return swoosh(flying_arc(d)) })
.attr("opacity", function (d) {
return fade_at_edge(d)
});
}
</script>
</body>
</html>
Display the source blob
Display the rendered blob
Raw
{"type": "FeatureCollection","features": [
{ "type": "Feature", "properties": { "scalerank": 0, "labelrank": 1, "featurecla": "Populated place", "name": "Los Angeles", "nameascii": "Los Angeles", "adm0name": "United States of America", "adm0_a3": "USA", "adm1name": "California", "iso_a2": "US", "note": null, "latitude": 33.989978250199997, "longitude": -118.179980511, "changed": 0.0, "namediff": 0, "diffnote": null, "pop_max": 12500000, "pop_min": 3694820, "pop_other": 142265, "rank_max": 14, "rank_min": 12, "geonameid": 5368361.0, "meganame": "Los Angeles-Long Beach-Santa Ana", "ls_name": "Los Angeles1", "ls_match": 1, "checkme": 0 }, "geometry": { "type": "Point", "coordinates": [ -118.181926369940413, 33.991924108765431 ] } },
{ "type": "Feature", "properties": { "scalerank": 0, "labelrank": 1, "featurecla": "Admin-0 capital", "name": "Washington, D.C.", "nameascii": "Washington, D.C.", "adm0name": "United States of America", "adm0_a3": "USA", "adm1name": "District of Columbia", "iso_a2": "US", "note": null, "latitude": 38.899549376499998, "longitude": -77.009418580800002, "changed": 0.0, "namediff": 0, "diffnote": null, "pop_max": 4338000, "pop_min": 552433, "pop_other": 2175991, "rank_max": 12, "rank_min": 11, "geonameid": 4140963.0, "meganame": "Washington, D.C.", "ls_name": "Washington, D.C.", "ls_match": 1, "checkme": 5 }, "geometry": { "type": "Point", "coordinates": [ -77.011364439437159, 38.901495235087054 ] } },
{ "type": "Feature", "properties": { "scalerank": 0, "labelrank": 1, "featurecla": "Populated place", "name": "New York", "nameascii": "New York", "adm0name": "United States of America", "adm0_a3": "USA", "adm1name": "New York", "iso_a2": "US", "note": null, "latitude": 40.749979064, "longitude": -73.980016928799998, "changed": 0.0, "namediff": 0, "diffnote": null, "pop_max": 19040000, "pop_min": 8008278, "pop_other": 9292603, "rank_max": 14, "rank_min": 13, "geonameid": 5128581.0, "meganame": "New York-Newark", "ls_name": "New York", "ls_match": 1, "checkme": 0 }, "geometry": { "type": "Point", "coordinates": [ -73.981962787406815, 40.75192492259464 ] } },
{ "type": "Feature", "properties": { "scalerank": 0, "labelrank": 2, "featurecla": "Admin-0 capital", "name": "Moscow", "nameascii": "Moscow", "adm0name": "Russia", "adm0_a3": "RUS", "adm1name": "Moskva", "iso_a2": "RU", "note": null, "latitude": 55.7521641226, "longitude": 37.615522825900001, "changed": 0.0, "namediff": 0, "diffnote": null, "pop_max": 10452000, "pop_min": 10452000, "pop_other": 10585385, "rank_max": 14, "rank_min": 14, "geonameid": 524901.0, "meganame": "Moskva", "ls_name": "Moscow", "ls_match": 1, "checkme": 0 }, "geometry": { "type": "Point", "coordinates": [ 37.613576967271399, 55.754109981248178 ] } },
{ "type": "Feature", "properties": { "scalerank": 0, "labelrank": 2, "featurecla": "Admin-0 capital", "name": "Mexico City", "nameascii": "Mexico City", "adm0name": "Mexico", "adm0_a3": "MEX", "adm1name": "Distrito Federal", "iso_a2": "MX", "note": null, "latitude": 19.442442442800001, "longitude": -99.130988201700006, "changed": 0.0, "namediff": 0, "diffnote": null, "pop_max": 19028000, "pop_min": 10811002, "pop_other": 10018444, "rank_max": 14, "rank_min": 14, "geonameid": 3530597.0, "meganame": "Ciudad de México", "ls_name": "Mexico City", "ls_match": 1, "checkme": 0 }, "geometry": { "type": "Point", "coordinates": [ -99.132934060293906, 19.444388301415472 ] } },
{ "type": "Feature", "properties": { "scalerank": 0, "labelrank": 2, "featurecla": "Admin-0 capital alt", "name": "Lagos", "nameascii": "Lagos", "adm0name": "Nigeria", "adm0_a3": "NGA", "adm1name": "Lagos", "iso_a2": "NG", "note": null, "latitude": 6.44326165348, "longitude": 3.39153107121, "changed": 4.0, "namediff": 0, "diffnote": "Location adjusted. Changed scale rank.", "pop_max": 9466000, "pop_min": 1536, "pop_other": 6567892, "rank_max": 13, "rank_min": 3, "geonameid": 735497.0, "meganame": "Lagos", "ls_name": "Lagos", "ls_match": 1, "checkme": 0 }, "geometry": { "type": "Point", "coordinates": [ 3.389585212598433, 6.445207512093191 ] } },
{ "type": "Feature", "properties": { "scalerank": 0, "labelrank": 1, "featurecla": "Admin-0 capital", "name": "Beijing", "nameascii": "Beijing", "adm0name": "China", "adm0_a3": "CHN", "adm1name": "Beijing", "iso_a2": "CN", "note": null, "latitude": 39.928892231299997, "longitude": 116.388285684, "changed": 0.0, "namediff": 0, "diffnote": null, "pop_max": 11106000, "pop_min": 7480601, "pop_other": 9033231, "rank_max": 14, "rank_min": 13, "geonameid": 1816670.0, "meganame": "Beijing", "ls_name": "Beijing", "ls_match": 1, "checkme": 0 }, "geometry": { "type": "Point", "coordinates": [ 116.386339825659434, 39.930838089909059 ] } },
{ "type": "Feature", "properties": { "scalerank": 0, "labelrank": 1, "featurecla": "Admin-0 capital", "name": "Jakarta", "nameascii": "Jakarta", "adm0name": "Indonesia", "adm0_a3": "IDN", "adm1name": "Jakarta Raya", "iso_a2": "ID", "note": null, "latitude": -6.17441770541, "longitude": 106.829437621, "changed": 0.0, "namediff": 0, "diffnote": null, "pop_max": 9125000, "pop_min": 8540121, "pop_other": 9129613, "rank_max": 13, "rank_min": 13, "geonameid": 1642911.0, "meganame": "Jakarta", "ls_name": "Jakarta", "ls_match": 1, "checkme": 0 }, "geometry": { "type": "Point", "coordinates": [ 106.827491762470117, -6.172471846798885 ] } },
{ "type": "Feature", "properties": { "scalerank": 0, "labelrank": 1, "featurecla": "Admin-1 capital", "name": "Shanghai", "nameascii": "Shanghai", "adm0name": "China", "adm0_a3": "CHN", "adm1name": "Shanghai", "iso_a2": "CN", "note": null, "latitude": 31.216452452599999, "longitude": 121.436504678, "changed": 0.0, "namediff": 0, "diffnote": null, "pop_max": 14987000, "pop_min": 14608512, "pop_other": 16803572, "rank_max": 14, "rank_min": 14, "geonameid": 1796236.0, "meganame": "Shanghai", "ls_name": "Shanghai", "ls_match": 1, "checkme": 0 }, "geometry": { "type": "Point", "coordinates": [ 121.434558819820154, 31.218398311228327 ] } },
{ "type": "Feature", "properties": { "scalerank": 0, "labelrank": 2, "featurecla": "Admin-0 capital", "name": "Tokyo", "nameascii": "Tokyo", "adm0name": "Japan", "adm0_a3": "JPN", "adm1name": "Tokyo", "iso_a2": "JP", "note": null, "latitude": 35.685016905799998, "longitude": 139.751407429000011, "changed": 0.0, "namediff": 0, "diffnote": null, "pop_max": 35676000, "pop_min": 8336599, "pop_other": 12945252, "rank_max": 14, "rank_min": 13, "geonameid": 1850147.0, "meganame": "Tokyo", "ls_name": "Tokyo", "ls_match": 1, "checkme": 0 }, "geometry": { "type": "Point", "coordinates": [ 139.749461570544668, 35.686962764371174 ] } },
{ "type": "Feature", "properties": { "scalerank": 0, "labelrank": 1, "featurecla": "Admin-1 capital", "name": "Mumbai", "nameascii": "Mumbai", "adm0name": "India", "adm0_a3": "IND", "adm1name": "Maharashtra", "iso_a2": "IN", "note": null, "latitude": 19.016990375700001, "longitude": 72.856989297400006, "changed": 0.0, "namediff": 0, "diffnote": null, "pop_max": 18978000, "pop_min": 12691836, "pop_other": 12426085, "rank_max": 14, "rank_min": 14, "geonameid": 1275339.0, "meganame": "Mumbai", "ls_name": "Mumbai", "ls_match": 1, "checkme": 0 }, "geometry": { "type": "Point", "coordinates": [ 72.855043438766472, 19.018936234356602 ] } },
{ "type": "Feature", "properties": { "scalerank": 0, "labelrank": 1, "featurecla": "Admin-1 capital", "name": "Kolkata", "nameascii": "Kolkata", "adm0name": "India", "adm0_a3": "IND", "adm1name": "West Bengal", "iso_a2": "IN", "note": null, "latitude": 22.494969298299999, "longitude": 88.324675658100006, "changed": 4.0, "namediff": 1, "diffnote": "Name changed. Changed scale rank.", "pop_max": 14787000, "pop_min": 4631392, "pop_other": 7783716, "rank_max": 14, "rank_min": 12, "geonameid": 1275004.0, "meganame": "Kolkata", "ls_name": "Calcutta", "ls_match": 1, "checkme": 0 }, "geometry": { "type": "Point", "coordinates": [ 88.32272979950551, 22.496915156896421 ] } },
{ "type": "Feature", "properties": { "scalerank": 0, "labelrank": 1, "featurecla": "Populated place", "name": "Rio de Janeiro", "nameascii": "Rio de Janeiro", "adm0name": "Brazil", "adm0_a3": "BRA", "adm1name": "Rio de Janeiro", "iso_a2": "BR", "note": null, "latitude": -22.9250231742, "longitude": -43.225020794199999, "changed": 0.0, "namediff": 0, "diffnote": null, "pop_max": 11748000, "pop_min": 2010175, "pop_other": 1821489, "rank_max": 14, "rank_min": 12, "geonameid": 3451190.0, "meganame": "Rio de Janeiro", "ls_name": "Rio de Janeiro", "ls_match": 1, "checkme": 0 }, "geometry": { "type": "Point", "coordinates": [ -43.226966652843657, -22.923077315615956 ] } },
{ "type": "Feature", "properties": { "scalerank": 0, "labelrank": 1, "featurecla": "Admin-1 capital", "name": "Sao Paulo", "nameascii": "Sao Paulo", "adm0name": "Brazil", "adm0_a3": "BRA", "adm1name": "São Paulo", "iso_a2": "BR", "note": null, "latitude": -23.558679587, "longitude": -46.625019980399998, "changed": 0.0, "namediff": 0, "diffnote": null, "pop_max": 18845000, "pop_min": 10021295, "pop_other": 11522944, "rank_max": 14, "rank_min": 14, "geonameid": 3448439.0, "meganame": "São Paulo", "ls_name": "Sao Paolo", "ls_match": 1, "checkme": 0 }, "geometry": { "type": "Point", "coordinates": [ -46.626965839055231, -23.556733728378958 ] } },
{ "type": "Feature", "properties": { "scalerank": 0, "labelrank": 0, "featurecla": "Admin-0 capital", "name": "Singapore", "nameascii": "Singapore", "adm0name": "Singapore", "adm0_a3": "SGP", "adm1name": null, "iso_a2": "SG", "note": null, "latitude": 1.29303346649, "longitude": 103.855820678, "changed": 0.0, "namediff": 0, "diffnote": null, "pop_max": 5183700, "pop_min": 3289529, "pop_other": 3314179, "rank_max": 13, "rank_min": 12, "geonameid": 1880252.0, "meganame": "Singapore", "ls_name": "Singapore", "ls_match": 1, "checkme": 5 }, "geometry": { "type": "Point", "coordinates": [ 103.853874819099019, 1.294979325105942 ] } },
{ "type": "Feature", "properties": { "scalerank": 0, "labelrank": 0, "featurecla": "Admin-0 region capital", "name": "Hong Kong", "nameascii": "Hong Kong", "adm0name": "Hong Kong S.A.R.", "adm0_a3": "HKG", "adm1name": null, "iso_a2": "HK", "note": null, "latitude": 22.304980895, "longitude": 114.185009317, "changed": 0.0, "namediff": 0, "diffnote": null, "pop_max": 7206000, "pop_min": 4551579, "pop_other": 4549026, "rank_max": 13, "rank_min": 12, "geonameid": 1819729.0, "meganame": "Hong Kong", "ls_name": "Hong Kong", "ls_match": 1, "checkme": 0 }, "geometry": { "type": "Point", "coordinates": [ 114.183063458463039, 22.30692675357551 ] } }
]}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment