I am trying to create spinning globe with bars like in this example. You can see my example here. And everything goes fine until bars go over horizon. I have no idea how to cut bars from the bottom when they on other side of planet. Anybody can suggest me how to do it?
/*
* Original code source
* http://codepen.io/teetteet/pen/Dgvfw
*/
var width = 400;
var height = 400;
var scrollSpeed = 50;
var current = 180;
var longitudeScale = d3.scale.linear()
.domain([0, width])
.range([-180, 180]);
var planetProjection = d3.geo.orthographic()
.scale(200)
.rotate([longitudeScale(current), 0])
.translate([width / 2, height / 2])
.clipAngle(90);
var barProjection = d3.geo.orthographic()
.scale(200)
.rotate([longitudeScale(current), 0])
.translate([width / 2, height / 2])
.clipAngle(90);
var path = d3.geo.path()
.projection(planetProjection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("https://dl.dropboxusercontent.com/s/4hp49mvf7pa2cg2/world-110m.json?dl=1", function(error, world) {
if (error) throw error;
var planet = svg.append("path")
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path);
d3.csv("https://dl.dropboxusercontent.com/s/v4kn2hrnjlgx1np/data.csv?dl=1", function(error, data) {
if (error) throw error;
var max = d3.max(data, function(d) {
return parseInt(d.Value);
})
var lengthScale = d3.scale.linear()
.domain([0, max])
.range([200, 250])
var bars = svg.selectAll(".bar")
.data(data)
.enter()
.append("line")
.attr("class", "bar")
.attr("stroke", "red")
.attr("stroke-width", "2");
function bgscroll() {
current += 1;
planetProjection.rotate([longitudeScale(current), 0]);
barProjection.rotate([longitudeScale(current), 0]);
planet.attr("d", path);
bars.attr("x1", function(d) {
return planetProjection([d.Longitude, d.Latitude])[0];
}).attr("y1", function(d) {
return planetProjection([d.Longitude, d.Latitude])[1];
}).attr("x2", function(d) {
barProjection.scale(lengthScale(d.Value));
return barProjection([d.Longitude, d.Latitude])[0];
}).attr("y2", function(d) {
barProjection.scale(lengthScale(d.Value));
return barProjection([d.Longitude, d.Latitude])[1];
});
}
// bgscroll();
setInterval(bgscroll, scrollSpeed);
})
})
To clip off the bars at the horizon, we add a mask centered at the globe 2D center and with it's radius. Then we apply this mask only if the bottom edge crosses the horizon (by tracking the longitude).
Creating the mask
// get the center of the circle
var center = planetProjection.translate();
// edge point
var edge = planetProjection([-90, 90])
// radius
var r = Math.pow(Math.pow(center[0] - edge[0], 2) + Math.pow(center[1] - edge[1], 2), 0.5);
svg.append("defs")
.append("clipPath")
.append("circle")
.attr("id", "edgeCircle")
.attr("cx", center[0])
.attr("cy", center[1])
.attr("r", r)
var mask = svg.append("mask").attr("id", "edge")
mask.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "white");
mask.append("use")
.attr("xlink:href", "#edgeCircle")
.attr("fill", "black");
Applying the mask
.... bars ....
.attr("mask", function (d) {
// make the range from 0 to 360, so that it's easier to compare
var longitude = Number(d.Longitude) + 180;
// +270 => -90 => the position of the left edge when the center is at 0
// -value because a rotation to the right => left edge longitude is reducing
// 360 because we want the range from 0 to 360
var startLongitude = 360 - ((longitudeScale(current) + 270) % 360);
// the right edge is start edge + 180
var endLongitude = (startLongitude + 180) % 360;
if ((startLongitude < endLongitude && longitude > startLongitude && longitude < endLongitude) ||
// wrap around
(startLongitude > endLongitude && (longitude > startLongitude || longitude < endLongitude)))
return null;
else
return "url(#edge)";
});
We could also do this by measuring the distance.
Fiddle - http://jsfiddle.net/gp3wvm8o/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With