Skip to content

Instantly share code, notes, and snippets.

@noahgibbs
Last active Aug 29, 2015
Embed
What would you like to do?
<h1>D3 Shaded Globe</h1>
<!-- Add scripts for topoJson, jQuery -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="http://datamaps.github.io/scripts/datamaps.world.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js" ></script>
<div class="world-map"
style="position: relative; height: 500px">
<svg></svg>
</div>
<script>
function is_clipped (proj, lat, long) {
var clip_test_path = d3.geo.path().projection(proj);
return typeof(clip_test_path({ type: "MultiPoint", coordinates: [[ long, lat ]] })) == "undefined";
}
var dark_ocean_color = '#0020CC';
var medium_ocean_color = "#2040D0";
var light_ocean_color = "#80B0DD";
var extra_dark_ocean_color = '#000080';
var dark_land_color = '#222222';
var medium_land_color = "#444444";
var light_land_color = "#BBBBBB";
var sunlight_fadein_duration = 2000;
var sunlight_fadeout_duration = 5000;
var t0 = Date.now();
var elt = $(".world-map")[0]
var width = $(elt).width();
var height = $(elt).height();
var projection = d3.geo.orthographic()
.scale(height / 2.1)
.translate([width / 2, height / 2])
.clipAngle(90)
.precision(.5);
var path = d3.geo.path()
.projection(projection)
.pointRadius(3.0);
var svg = d3.select(".world-map svg")
.attr("width", width)
.attr("height", height);
var topo = Datamap.prototype.worldTopo;
var land = topojson.feature(topo, topo.objects.world).features;
var globe_group = svg.append("g");
var land_group = svg.append("g");
var svg_defs = svg.append("defs");
var ocean_gradient = svg_defs.append("radialGradient").attr("id", "ocean_gradient").attr("cx", 0).attr("cy", 0).attr("r", 0).attr("gradientUnits", "userSpaceOnUse");
var ocean_stop1 = ocean_gradient.append("stop").attr("offset", "0%").style("stop-color", light_ocean_color);
var ocean_stop2 = ocean_gradient.append("stop").attr("offset", "50%").style("stop-color", medium_ocean_color);
var ocean_stop3 = ocean_gradient.append("stop").attr("offset", "100%").style("stop-color", dark_ocean_color);
var land_gradient = svg_defs.append("radialGradient").attr("id", "land_gradient") .attr("cx", 0).attr("cy", 0).attr("r", 0).attr("gradientUnits", "userSpaceOnUse");
var land_stop1 = land_gradient.append("stop").attr("offset", "0%").style("stop-color", light_land_color);
var land_stop2 = land_gradient.append("stop").attr("offset", "70%").style("stop-color", medium_land_color);
var land_stop3 = land_gradient.append("stop").attr("offset", "100%").style("stop-color", dark_land_color);
// Globe origin and velocity
var origin = [0.0, 0.0];
var velocity = [0.03, 0.0];
var last_iter_lightness = "";
console.debug("Changed[6]");
d3.timer(function() {
width = $(elt).width();
height = $(elt).height();
d3.select(elt).select("svg").attr("width", width); // Resize svg on window resize
// Rotation occurs independent of display mode, unless paused
dt = Date.now() - t0;
projection.rotate([velocity[0] * dt + origin[0], velocity[1] * dt + origin[1]])
.scale(height / 2.1)
.translate([width / 2, height / 2]);
gr = gradients_for_brightest_and_darkest(path, projection, new Date() - 0, width, height);
// Single big globe circle, with oceans
globe_group.selectAll("circle").data([1]).enter()
.append("circle")
.attr("r", height / 2.1)
.attr("cx", "50%")
.attr("cy", "50%")
.attr("stroke", "black")
.attr("fill", "url(#ocean_gradient)");
// Country boundaries and land
land_sel = land_group.selectAll("path")
.data(land, function (obj) { return obj.id; });
land_sel.enter()
.append("path")
.attr("d", path)
.attr("stroke", "darkgray")
.attr("fill", "url(#land_gradient)");
land_sel.attr("d", path); // Update when needed
return null; // Don't cancel timer
});
function gradients_for_brightest_and_darkest(path, proj, current_t, width, height) {
// Calculate brightest and darkest longitudes
var day_in_millis = 24 * 60 * 60 * 1000;
var half_day = day_in_millis / 2;
var offset_from_day_start = current_t % day_in_millis;
var offset_from_noon = offset_from_day_start - half_day;
// The sun moves one degree of longitude every 240 seconds
var degrees_from_noon = parseInt(offset_from_noon / 240000.0);
if(degrees_from_noon >= 180.0)
degrees_from_noon -= 360.0;
lightest_longitude = -degrees_from_noon;
// If the darkest is clipped, the lightest isn't and vice-versa...
// At least with an Albers projection. If we change the projection,
// we'll need a different fill gradient.
this_iter_lightness = "dark"
if(!is_clipped(proj, 30.0, lightest_longitude)) {
// Lightest spot visible, use gradient fill
this_iter_lightness = "light";
projected_lightest = proj([lightest_longitude, 30.0]);
ocean_gradient.attr("cx", "50%").attr("cy", "50%").attr("r", height / 2.1)
.attr("fx", projected_lightest[0]).attr("fy", projected_lightest[1]);
land_gradient.attr("cx", width / 2.0).attr("cy", height / 2.0).attr("r", height / 2.1)
.attr("fx", projected_lightest[0]).attr("fy", projected_lightest[1]);
}
// First iteration, assume we *didn't* just transition
if(last_iter_lightness == "") last_iter_lightness = this_iter_lightness;
if(last_iter_lightness != this_iter_lightness) {
if(this_iter_lightness == "light") {
// Transition to light gradients
ocean_stop1.transition().duration(sunlight_fadein_duration).style("stop-color", light_ocean_color);
ocean_stop2.transition().duration(sunlight_fadein_duration).style("stop-color", medium_ocean_color);
ocean_stop3.transition().duration(sunlight_fadein_duration).style("stop-color", dark_ocean_color);
land_stop1.transition().duration(sunlight_fadein_duration).style("stop-color", light_land_color);
land_stop2.transition().duration(sunlight_fadein_duration).style("stop-color", medium_land_color);
land_stop3.transition().duration(sunlight_fadein_duration).style("stop-color", dark_land_color);
} else {
ocean_stop1.transition().duration(sunlight_fadeout_duration).style("stop-color", dark_ocean_color);
ocean_stop2.transition().duration(sunlight_fadeout_duration).style("stop-color", dark_ocean_color);
ocean_stop3.transition().duration(sunlight_fadeout_duration).style("stop-color", extra_dark_ocean_color);
land_stop1.transition().duration(sunlight_fadeout_duration).style("stop-color", dark_land_color);
land_stop2.transition().duration(sunlight_fadeout_duration).style("stop-color", dark_land_color);
land_stop3.transition().duration(sunlight_fadeout_duration).style("stop-color", dark_land_color);
}
last_iter_lightness = this_iter_lightness;
}
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment