A mashup of Map Projection Distortions and transitions using the D3.js extended geographic projections plugin.
A comparison of map projections by four different types of distortion:
Lower is better. Data transcribed from the Natural Earth Projection by @mbostock.
Read more about map projections on Wikipedia.
xxxxxxxxxx
<meta charset="utf-8">
<title>Map Projections</title>
<style>
svg {
font: 11px sans-serif;
}
.background path {
fill: none;
stroke: none;
stroke-width: 30px;
pointer-events: stroke;
}
.foreground path {
fill: none;
stroke: steelblue;
stroke-width: 2.5px;
}
.axis .title {
font-size: 11px;
font-weight: bold;
text-transform: uppercase;
}
.axis line,
.axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.label {
-webkit-transition: fill 125ms linear;
}
.active .label:not(.inactive) {
font-weight: bold;
}
.label.inactive {
fill: #888;
}
.foreground path.inactive {
stroke: #888;
stroke-opacity: .3;
stroke-width: 1.5px;
}
.stroke {
fill: none;
stroke: #000;
stroke-width: 3px;
}
.fill {
fill: #a4bac7;
}
path.foreground {
fill: none;
stroke: #333;
stroke-width: 1.5px;
}
path.graticule {
fill: none;
stroke: #aaa;
stroke-width: .5px;
}
.line:nth-child(2n) {
stroke-dasharray: 2,2;
}
.land {
fill: #d7c7ad;
stroke: #a5967e;
}
</style>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-geo-projection/0.2.9/d3.geo.projection.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js"></script>
<script>
/* Map */
var map_width = 960,
map_height = 290;
var projection = d3.geo.aitoff()
.translate([map_width / 2 - .5, map_height / 2 - .5]).scale(90);
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule();
var map_svg = d3.select("body").append("svg")
.attr("width", map_width)
.attr("height", map_height);
map_svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
d3.json("world-110m.json", function(error,world) {
if (error) throw error;
map_svg.insert("path", ".graticule")
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path);
});
/* Parallel Coordinates */
var margin = {top: 30, right: 160, bottom: 20, left: 250},
width = 960 - margin.left - margin.right,
height = 350 - margin.top - margin.bottom;
var projections = {
"Aitoff": d3.geo.aitoff().scale(90),
"Boggs Eumorphic": d3.geo.boggs().scale(90),
"Craster Parabolic (Putnins P4)": d3.geo.craster().scale(90),
"Cylindrical Equal-Area": d3.geo.cylindricalEqualArea().scale(120),
"Eckert I": d3.geo.eckert1().scale(95),
"Eckert III": d3.geo.eckert3().scale(105),
"Eckert IV": d3.geo.eckert4().scale(105),
"Eckert V": d3.geo.eckert5().scale(100),
"Equidistant Cylindrical (Plate Carrée)": d3.geo.equirectangular().scale(90),
"Fahey": d3.geo.fahey().scale(75),
"Foucaut Sinusoidal": d3.geo.foucaut().scale(80),
"Gall (Gall Stereographic)": d3.geo.cylindricalStereographic().scale(70),
"Ginzburg VIII (TsNIIGAiK 1944)": d3.geo.ginzburg8().scale(75),
"Kavraisky VII": d3.geo.kavrayskiy7().scale(90),
"Larrivée": d3.geo.larrivee().scale(55),
"McBryde-Thomas Flat-Pole Sine (No. 2)": d3.geo.mtFlatPolarSinusoidal().scale(95),
"Mercator": d3.geo.mercator().scale(50),
"Miller Cylindrical I": d3.geo.miller().scale(60),
"Mollweide": d3.geo.mollweide().scale(100),
"Natural Earth": d3.geo.naturalEarth().scale(100),
"Nell-Hammer": d3.geo.nellHammer().scale(120),
"Quartic Authalic": d3.geo.hammer().coefficient(Infinity).scale(95),
"Robinson": d3.geo.robinson().scale(90),
"Sinusoidal": d3.geo.sinusoidal().scale(90),
"van der Grinten (I)": d3.geo.vanDerGrinten().scale(50),
"Wagner VI": d3.geo.wagner6().scale(90),
"Wagner VII": d3.geo.wagner7().scale(90),
"Winkel Tripel": d3.geo.winkel3().scale(90)
};
var dimensions = [
{
name: "name",
scale: d3.scale.ordinal().rangePoints([0, height]),
type: String
},
{
name: "Acc. 40º 150%",
scale: d3.scale.linear().range([0, height]),
type: Number
},
{
name: "Scale",
scale: d3.scale.linear().range([height, 0]),
type: Number
},
{
name: "Areal",
scale: d3.scale.sqrt().range([height, 0]),
type: Number
},
{
name: "Angular",
scale: d3.scale.linear().range([height, 0]),
type: Number
}
];
var x = d3.scale.ordinal()
.domain(dimensions.map(function(d) { return d.name; }))
.rangePoints([0, width]);
var line = d3.svg.line()
.defined(function(d) { return !isNaN(d[1]); });
var yAxis = d3.svg.axis()
.orient("left");
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var dimension = svg.selectAll(".dimension")
.data(dimensions)
.enter().append("g")
.attr("class", "dimension")
.attr("transform", function(d) { return "translate(" + x(d.name) + ")"; });
d3.tsv("projections.tsv", function(data) {
data = data.filter(function(d) { return d.name in projections });
dimensions.forEach(function(dimension) {
dimension.scale.domain(dimension.type === Number
? d3.extent(data, function(d) { return +d[dimension.name]; })
: data.map(function(d) { return d[dimension.name]; }).sort());
});
svg.append("g")
.attr("class", "background")
.selectAll("path")
.data(data)
.enter().append("path")
.attr("d", draw);
svg.append("g")
.attr("class", "foreground")
.selectAll("path")
.data(data)
.enter().append("path")
.attr("d", draw);
dimension.append("g")
.attr("class", "axis")
.each(function(d) { d3.select(this).call(yAxis.scale(d.scale)); })
.append("text")
.attr("class", "title")
.attr("text-anchor", "middle")
.attr("y", -9)
.text(function(d) { return d.name; });
var lazyMouseover = debounce(mouseover, 100);
// Rebind the axis data to simplify mouseover.
svg.select(".axis").selectAll("text:not(.title)")
.attr("class", "label")
.data(data, function(d) { return d.name || d; });
var projection_line = svg.selectAll(".axis text.label,.background path,.foreground path")
.on("mouseover", lazyMouseover)
.on("mouseout", mouseout);
mouseover(data.filter(function(d) { return d.name == "Aitoff" })[0]);
function mouseover(d) {
if (!(d.name in projections)) return;
if (d.name == projection.name) return;
svg.classed("active", true);
projection_line.classed("inactive", function(p) { return p !== d; });
projection_line.filter(function(p) { return p === d; }).each(moveToFront);
// update map
var last_projection = projection;
projection = projections[d.name]
.translate([map_width / 2 - .5, map_height / 2 - .5]);
path = d3.geo.path()
.projection(projection);
map_svg.selectAll("path")
.transition()
.duration(450)
.attr("d", path)
.attrTween("d", projectionTween(last_projection, projection));
}
function mouseout(d) {
return;
}
function moveToFront() {
this.parentNode.appendChild(this);
}
});
function draw(d) {
return line(dimensions.map(function(dimension) {
return [x(dimension.name), dimension.scale(d[dimension.name])];
}));
}
d3.select(self.frameElement).style("height", (map_height+height+margin.top+margin.bottom) + "px");
function projectionTween(projection0, projection1) {
return function(d) {
var t = 0;
var projection = d3.geo.projection(project)
.scale(1)
.translate([map_width / 2, map_height / 2]);
var path = d3.geo.path()
.projection(projection);
function project(λ, φ) {
λ *= 180 / Math.PI, φ *= 180 / Math.PI;
var p0 = projection0([λ, φ]), p1 = projection1([λ, φ]);
return [(1 - t) * p0[0] + t * p1[0], (1 - t) * -p0[1] + t * -p1[1]];
}
return function(_) {
t = _;
return path(d);
};
};
}
// From underscore.js
function debounce(func, wait, immediate) {
var timeout, args, context, timestamp, result;
var later = function() {
var last = new Date().getTime() - timestamp;
if (last < wait && last >= 0) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
if (!immediate) {
result = func.apply(context, args);
if (!timeout) context = args = null;
}
}
};
return function() {
context = this;
args = arguments;
timestamp = new Date().getTime();
var callNow = immediate && !timeout;
if (!timeout) timeout = setTimeout(later, wait);
if (callNow) {
result = func.apply(context, args);
context = args = null;
}
return result;
};
};
</script>
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js
https://cdnjs.cloudflare.com/ajax/libs/d3-geo-projection/0.2.9/d3.geo.projection.min.js
https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js