Reputation: 1044
I am trying to implement a version of Mike Bostock's spinny globe and to overlay country labels. This has been achieved successfully here: http://bl.ocks.org/dwtkns/4686432
Unfortunately I am not able to hide the labels that should fall 'behind the globe'. The method I have tried is to calculate the distance between path centroids and the center position of the globe. But for some reason the code seems to be inconsistent and hiding the wrong labels...
This is the bit of the code that hides the labels:
function position_labels() {
var centerPos = projection.invert([width / 2, height / 2]);
var arc = d3.geo.greatArc();
// update: update path projection before applying it to labels position
path.projection(projection);
svg.selectAll(".label")
.attr("transform", function(d) {
return "translate(" + path.centroid(d) + ")";
})
.style("display", function(d) {
var d = arc.distance({
source: path.centroid(d),
target: centerPos
});
return (d > 1.57) ? 'inline' : 'none';
})
}
I have saved my work in the following jsfiddle -please let me know what I am doing wrong: http://jsfiddle.net/Guill84/kh3kegsy/
UPDATE:
The below is a snapshot of the issue whereby country names still seem to float around the map. Please see my comment below for further explanation:
Upvotes: 3
Views: 615
Reputation: 38201
I noted a potential hurdle with using coordinates to determine label visibility in my comment above. However, there is another way to determine if a label should be visible: determining if a country's svg path has an attribute 'd' that is not null.
First, id's will need to be set properly, currently d.properties.name includes spaces, so setting ids with:
.attr("id", function(d) { return d.properties.name.split(' ').join('_'); })
will allow for better identification of places such as the United Kingdom and United States.
By selecting each path based on id, we can see if it has any data defining its shape. If it is not shown, the 'd' property of the path will be null.
if (d3.select("#"+d.properties.name.split(' ').join('_')).attr('d') != null) {
return 'inline';
}
else {
return 'none';
}
Updated fiddle: here
Using a different text anchor might center your text better as well:
.style("text-anchor","middle");
Fiddle with text anchor of middle: here
--- Update --- While the code above will hide labels for countries not drawn (completely behind the globe), if any portion of the country is drawn then a label will be created. A lot of issues around this can be addressed by not drawing labels that are a certain distance from the center: fiddle, as mentioned in comment below.
However, for some large countries such as Russia, the geographic centroid (and thus label location) can fall within the specified radius and will be drawn if a tiny sliver of the country is rendered. When this happens the geographic centroid, rather than the svg centroid, will be used to label the country. In these cases, the geographic centroid is on the far side of the earth, but rendered as though it were on the visible side.
Also, in the case of France, the svg centroid and geographic centroid will be problematic:
A couple methods could be used to aid in toggling of labels, such as computing and recording the geographic centroids in the geographic data and using the angular distance from the map projection center to show/hide the labels. In the case of France, splitting Guiana off would be useful.
Without modifying the data, another criterion can be used to select which labels should be visible: seeing if the center of the bounding box of the country as drawn is near the edge of the map. It adds an extra layer of computation, but if each criterion is only checked if needed, it should be ok: http://jsfiddle.net/0ptcy7f3/1/
var country = d3.select("#"+d.properties.name.split(' ').join('_'));
// Exclude labels this far from center:
var r = 350;
// Check to see if the country has a path visible as before
if (country.attr('d') != null) {
// Then check to see if the bounding box
// for the country as drawn is far from center
var BB = country.node().getBBox();
var x = (BB.x + (BB.x + BB.width)) / 2;
var y = (BB.y + (BB.y + BB.height)) / 2;
if ( ( (width-x)*(width-x) + (height-y)*(height-y) ) < (r * r) ) {
// And ensure the geographic centroid is also not on the edge:
center = path.centroid(d);
x = center[0];
y = center[1];
if ( ( (width-x)*(width-x) + (height-y)*(height-y) ) < (r * r) ) {
return 'inline';
}
else {
return 'none';
}
}
else { return 'none'; }
else { return 'none'; }
The case of France is not addressed, as it's geographic path includes Guiana. A special case could be built in to the code to force the label to a particular place and to evaluate that location as a valid location. Otherwise, the data needs to be edited. This update should take care of Russia or other large/long countries.
A tweak that could be made is changing the exclusion radius as the map is zoomed in (or not doing that check at all if the map fills the whole frame).
Upvotes: 2