Skip to content

Instantly share code, notes, and snippets.

@vincentpham1991
Forked from lunarmoon26/README.md
Created August 17, 2018 00:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vincentpham1991/f838b0c291ed646123739f19eca197c6 to your computer and use it in GitHub Desktop.
Save vincentpham1991/f838b0c291ed646123739f19eca197c6 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
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment