score:5

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).

``````// get the center of the circle
var center = planetProjection.translate();
// edge point
var edge = planetProjection([-90, 90])
var r = Math.pow(Math.pow(center - edge, 2) + Math.pow(center - edge, 2), 0.5);

svg.append("defs")
.append("clipPath")
.append("circle")
.attr("id", "edgeCircle")
.attr("cx", center)
.attr("cy", center)
.attr("r", r)

.attr("x", 0)
.attr("y", 0)
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "white");
.attr("fill", "black");
``````

``````.... 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/ score:0

Just keep track of the range of visible longitudes and hide the bars if they are not in that range

``````.attr("display", 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 "block";
else
return "none";
})
``````

Fiddle - http://jsfiddle.net/b12ryhda/

score:0

A simpler method that works with canvas would be to:

1. Draw all the bars without clipping
2. Draw the map
3. Draw only the foreground bars (i.e., use clipping)

This clipping does not have to be down manually but can leverage the `path.centroid` method which respects the clipping set on the projections by `clipAngle`. Pseudo code might look like:

``````let projection = d3.geoOrthographic()
.clipAngle(90)
...
let barProjection = d3.geoOrthographic()
.clipAngle(90)
...

let path = d3.geoPath()
.projection(projection)
.context(canvasCtx)
let barPath = d3.geoPath()
.projection(barProjection)

let renderBar = function(isBgLayer = false) {
let barLengthAsScale = ...
barProjection.scale(barLengthAsScale)
let barStart, barEnd
if (isBgLayer) {
barStart = projection([ lon, lat ])
barEnd = barProjection([ lon, lat ])
} else {
let geoJs = { type: 'Point', coordinates: [ lon, lat ] }
barStart = path.centroid(geoJs)
barEnd = barPath.centroid(geoJs)
}
// draw line from start to end using canvasCtx
};

let renderMap = function(topology) {
// normal map drawing to canvas
};

// then to render a frame
renderBar(true)
renderMap(topoJsonTopology)
renderBar()
``````

Some bars will be drawn twice, but with I've found that canvas is fast enough to keep up with the drawing and keep animations smooth with at least 200+ bars.

For an example, check out this code on GitHub and the live page.