Skip to content

Instantly share code, notes, and snippets.

@Andrew-Reid
Last active November 28, 2017 18:57
Show Gist options
  • Save Andrew-Reid/11d6939bbb3b7b63fc494b5107769112 to your computer and use it in GitHub Desktop.
Save Andrew-Reid/11d6939bbb3b7b63fc494b5107769112 to your computer and use it in GitHub Desktop.
Orthographic Pie Chart

This bl.ock follows in the footsteps of this block with orthographic labels.

It uses a quick demo of a module that takes a path or selection with paths, overlays it on a projection, converts the path's coordinates to geographic coordinates and then into geojson.

It uses a few methods:

geoCentroid([longitude,latitude])

The geographic centroid to apply to the non-geographic path. Aligns with 0,0 in the non-geographic coordinate space.

geoScale(value)

Sets the scale of the geographic projection which the non-geographic path is overlain ontop of before getting the geographic coordinates. Lower values result in larger features.

pathScale(value)

Sets the scaling value of the non-geographic path. Smaller values result in smaller features.

selection(selection)

Takes a d3 selection, extracts their paths and creates geojson from the paths.

path(path)

Takes a path and creates geojson from the path.

features()

Returns features generated from the selection/path

featureCollection()

Returns a featureCollection generated from the selection/path

d3.orthoLabel = function() {
var falseSVG = d3.select(document.createElementNS(d3.namespaces.svg, 'svg'));
// projection variables:
var centroid = [0,0];
var scale = 1000;
var projection = d3.geoAzimuthalEquidistant()
.translate([0,0]);
// path variables
var pathScale = 1;
var distance = 1;
var features = [];
function orthoLabel() {
}
//
// Set Geographic Centroid
//
orthoLabel.geoCentroid = function(c) {
if (c) {
centroid[0] = -c[0];
centroid[1] = -c[1];
projection.rotate(centroid);
return orthoLabel;
}
else {
return centroid;
}
}
//
// Set Geographic Zoom
//
orthoLabel.geoScale = function(z) {
if (z) {
scale = z;
projection.scale(scale);
return orthoLabel;
}
else {
return centroid;
}
}
///////
//
// Set Path Scale
//
orthoLabel.pathScale = function(s) {
if (s) {
pathScale = s;
return orthoLabel;
}
else {
return pathScale;
}
}
//
// Set distance between sampled points
//
orthoLabel.distance = function(i) {
if (i) {
distance = i;
return orthoLabel;
}
else {
return distance;
}
}
//
// Take a selection and get geojson representing path:
//
orthoLabel.selection = function(selection) {
features=[];
selection.each(function(d) {
var points = [];
var d = distance;
var i = 0;
var node = d3.select(this).node();
var l = node.getTotalLength();
var n = l / d;
var p0 = node.getPointAtLength(i * d);
p0 = projection.invert([p0.x*pathScale,p0.y*pathScale])
while (i < n) {
var p = node.getPointAtLength(i * d);
points.push(projection.invert([p.x*pathScale,p.y*pathScale]));
i++;
}
points.push(p0);
// Check to see if the geojson follows the right hand rule. Given context assuming the antipode is not intended to be in the feature.
var feature = {"type":"Feature","geometry":{"type":"Polygon","coordinates":[points]},"properties":null};
if(d3.geoContains(feature,centroid)) {
points.reverse();
feature = {"type":"Feature","geometry":{"type":"Polygon","coordinates":[points]},"properties":null};
}
// add to features array
features.push(feature);
})
return orthoLabel;
}
//
// Get a path(s) (.attr("d")) and turn it to geojson.
//
orthoLabel.path = function(path) {
features=[];
path = falseSVG.append("path")
.attr("d",path);
var points = [];
var d = distance;
var i = 0;
var node = path.node();
var l = node.getTotalLength();
var n = l / d;
var t = function(a) { return a };
var p0 = node.getPointAtLength(i * d);
p0 = projection.invert([p0.x*pathScale,p0.y*pathScale])
while (i < n) {
var p = node.getPointAtLength(i * d);
points.push(projection.invert([p.x*pathScale,p.y*pathScale]));
i++;
}
points.push(p0);
// Check to see if the geojson follows the right hand rule. Given context assuming the antipode is not intended to be in the feature.
var feature = {"type":"Feature","geometry":{"type":"Polygon","coordinates":[points]},"properties":null};
if(d3.geoContains(feature,centroid)) {
points.reverse();
feature = {"type":"Feature","geometry":{"type":"Polygon","coordinates":[points]},"properties":null};
}
// add to features array
features.push(feature);
return orthoLabel;
}
orthoLabel.features = function() {
return features;
}
orthoLabel.featureCollection = function() {
return {"type":"FeatureCollection","features":features}
}
return orthoLabel;
}
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<script src="d3orthoPath.js"></script>
<script>
var width = 800;
var height = 800;
var svg = d3.select("body").append("svg")
.attr("width",width)
.attr("height",height);
var projection = d3.geoOrthographic();
var pie = d3.pie().value(function(d) { return d; });
var arc = d3.arc()
.outerRadius(20)
.innerRadius(10);
var color = ["orange","steelblue","crimson","lightgreen"]
var data = [10,20,30,40];
var path = d3.geoPath().projection(projection);
d3.json("https://unpkg.com/world-atlas@1/world/50m.json", function(error, world) {
if (error) throw error;
svg.insert("path", ".graticule")
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path);
svg.insert("path", ".graticule")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
var ortho = d3.orthoLabel()
.geoCentroid([-40,40])
.geoScale(100)
.pathScale(1)
.distance(1.5);
var geojson = [];
var symbols = svg.selectAll()
.data(pie(data))
.enter()
.append("path")
.attr("fill",function(d,i) { return color[i]; })
.attr("opacity",0.7)
.attr("d",function(d,i) { geojson.push(ortho.path(arc(d)).featureCollection()); return geojson[i]; });
symbols.data(geojson);
var speed = 1e-2;
var start = Date.now();
d3.timer(function() {
projection.rotate([speed * (Date.now() - start), -15]);
d3.selectAll("path").attr("d",path);
});
})
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment