Faux-3d SVG globe using d3.geo.orthographic and a few radial gradients. Labels offset or hidden based on radians from current map center to enhance the effect.
Uncomment svg.append("g").attr("class","countries") for hover-able country outlines.
Faux-3d SVG globe using d3.geo.orthographic and a few radial gradients. Labels offset or hidden based on radians from current map center to enhance the effect.
Uncomment svg.append("g").attr("class","countries") for hover-able country outlines.
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
.land { | |
fill: rgb(117, 87, 57); | |
stroke-opacity: 1; | |
} | |
.countries path { | |
stroke: rgb(80, 64, 39); | |
stroke-linejoin: round; | |
stroke-width:.5; | |
fill: rgb(117, 87, 57); | |
opacity: .1; | |
} | |
.countries path:hover { | |
fill-opacity:.1; | |
stroke-width:1; | |
opacity: 1; | |
} | |
.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; | |
} | |
</style> | |
<body> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script src="http://d3js.org/queue.v1.min.js"></script> | |
<script src="http://d3js.org/topojson.v0.min.js"></script> | |
<script> | |
// Lots of code from: | |
// http://bl.ocks.org/3757125 | |
// http://bl.ocks.org/3795040 | |
d3.select(window) | |
.on("mousemove", mousemove) | |
.on("mouseup", mouseup); | |
var width = 960, | |
height = 500; | |
var proj = d3.geo.orthographic() | |
.scale(220) | |
.translate([width / 2, height / 2]) | |
.clipAngle(90); | |
var path = d3.geo.path().projection(proj).pointRadius(1.5); | |
var graticule = d3.geo.graticule(); | |
var svg = d3.select("body").append("svg") | |
.attr("width", width) | |
.attr("height", height) | |
.on("mousedown", mousedown); | |
queue() | |
.defer(d3.json, "world-110m.json") | |
.defer(d3.json, "places.json") | |
.await(ready); | |
function ready(error, world, places) { | |
var ocean_fill = svg.append("defs").append("radialGradient") | |
.attr("id", "ocean_fill") | |
.attr("cx", "75%") | |
.attr("cy", "25%"); | |
ocean_fill.append("stop").attr("offset", "5%").attr("stop-color", "#ddf"); | |
ocean_fill.append("stop").attr("offset", "100%").attr("stop-color", "#9ab"); | |
var globe_highlight = svg.append("defs").append("radialGradient") | |
.attr("id", "globe_highlight") | |
.attr("cx", "75%") | |
.attr("cy", "25%"); | |
globe_highlight.append("stop") | |
.attr("offset", "5%").attr("stop-color", "#ffd") | |
.attr("stop-opacity","0.6"); | |
globe_highlight.append("stop") | |
.attr("offset", "100%").attr("stop-color", "#ba9") | |
.attr("stop-opacity","0.2"); | |
var globe_shading = svg.append("defs").append("radialGradient") | |
.attr("id", "globe_shading") | |
.attr("cx", "50%") | |
.attr("cy", "40%"); | |
globe_shading.append("stop") | |
.attr("offset","50%").attr("stop-color", "#9ab") | |
.attr("stop-opacity","0") | |
globe_shading.append("stop") | |
.attr("offset","100%").attr("stop-color", "#3e6184") | |
.attr("stop-opacity","0.3") | |
var drop_shadow = svg.append("defs").append("radialGradient") | |
.attr("id", "drop_shadow") | |
.attr("cx", "50%") | |
.attr("cy", "50%"); | |
drop_shadow.append("stop") | |
.attr("offset","20%").attr("stop-color", "#000") | |
.attr("stop-opacity",".5") | |
drop_shadow.append("stop") | |
.attr("offset","100%").attr("stop-color", "#000") | |
.attr("stop-opacity","0") | |
svg.append("ellipse") | |
.attr("cx", 440).attr("cy", 450) | |
.attr("rx", proj.scale()*.90) | |
.attr("ry", proj.scale()*.25) | |
.attr("class", "noclicks") | |
.style("fill", "url(#drop_shadow)"); | |
svg.append("circle") | |
.attr("cx", width / 2).attr("cy", height / 2) | |
.attr("r", proj.scale()) | |
.attr("class", "noclicks") | |
.style("fill", "url(#ocean_fill)"); | |
svg.append("path") | |
.datum(topojson.object(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", width / 2).attr("cy", height / 2) | |
.attr("r", proj.scale()) | |
.attr("class","noclicks") | |
.style("fill", "url(#globe_highlight)"); | |
svg.append("circle") | |
.attr("cx", width / 2).attr("cy", height / 2) | |
.attr("r", proj.scale()) | |
.attr("class","noclicks") | |
.style("fill", "url(#globe_shading)"); | |
svg.append("g").attr("class","points") | |
.selectAll("text").data(places.features) | |
.enter().append("path") | |
.attr("class", "point") | |
.attr("d", path); | |
svg.append("g").attr("class","labels") | |
.selectAll("text").data(places.features) | |
.enter().append("text") | |
.attr("class", "label") | |
.text(function(d) { return d.properties.name }) | |
// uncomment for hover-able country outlines | |
// svg.append("g").attr("class","countries") | |
// .selectAll("path") | |
// .data(topojson.object(world, world.objects.countries).geometries) | |
// .enter().append("path") | |
// .attr("d", path); | |
position_labels(); | |
} | |
function position_labels() { | |
var centerPos = proj.invert([width/2,height/2]); | |
var arc = d3.geo.greatArc(); | |
svg.selectAll(".label") | |
.attr("text-anchor",function(d) { | |
var x = proj(d.geometry.coordinates)[0]; | |
return x < width/2-20 ? "end" : | |
x < width/2+20 ? "middle" : | |
"start" | |
}) | |
.attr("transform", function(d) { | |
var loc = proj(d.geometry.coordinates), | |
x = loc[0], | |
y = loc[1]; | |
var offset = x < width/2 ? -5 : 5; | |
return "translate(" + (x+offset) + "," + (y-2) + ")" | |
}) | |
.style("display",function(d) { | |
var d = arc.distance({source: d.geometry.coordinates, target: centerPos}); | |
return (d > 1.57) ? 'none' : 'inline'; | |
}) | |
} | |
// modified from http://bl.ocks.org/1392560 | |
var m0, o0; | |
function mousedown() { | |
m0 = [d3.event.pageX, d3.event.pageY]; | |
o0 = proj.rotate(); | |
d3.event.preventDefault(); | |
} | |
function mousemove() { | |
if (m0) { | |
var m1 = [d3.event.pageX, d3.event.pageY] | |
, o1 = [o0[0] + (m1[0] - m0[0]) / 6, o0[1] + (m0[1] - m1[1]) / 6]; | |
o1[1] = o1[1] > 30 ? 30 : | |
o1[1] < -30 ? -30 : | |
o1[1]; | |
proj.rotate(o1); | |
refresh(); | |
} | |
} | |
function mouseup() { | |
if (m0) { | |
mousemove(); | |
m0 = null; | |
} | |
} | |
function refresh() { | |
svg.selectAll(".land").attr("d", path); | |
svg.selectAll(".countries path").attr("d", path); | |
svg.selectAll(".graticule").attr("d", path); | |
svg.selectAll(".point").attr("d", path); | |
position_labels(); | |
} | |
</script> |