This shows why line simplification between projection and adaptive resampling is not a good idea.
xxxxxxxxxx
<meta charset="utf-8">
<style>
.graticule {
fill: none;
stroke: #777;
stroke-width: 1px;
}
.graticule:nth-child(1) {
fill: none;
stroke-opacity: 0.6;
stroke: #000;
stroke-width: 6px;
}
.graticule:nth-child(2) {
fill: none;
stroke-opacity: 0.6;
stroke: #00f;
stroke-width: 6px;
}
.graticule:nth-child(3) {
fill: none;
stroke-opacity: 0.6;
stroke: #f00;
stroke-width: 6px;
}
.graticule:nth-child(4) {
fill: none;
stroke-opacity: 0.6;
stroke: #f70;
stroke-width: 6px;
}
.spiral {
fill: none;
stroke: black;
stroke-width: 1px;
}
</style>
<body></body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://d3js.org/d3.geo.projection.v0.min.js"></script>
<script src="simplify.js"></script>
<script>
var width = 960,
height = 500;
// Graticule to indicate time of period
var graticule = d3.geo.graticule()
.majorStep([90,90]) // Major step indicates 6-hour intervals
.minorStep([15,0]) // Minor step indicates 1-hour intervals
.majorExtent([[-180,-70], [180,65]])
.minorExtent([[-180,-60], [180,55]])
// Spiral function derived from https://www.jasondavies.com/maps/spiral/
var n = 1e4, dy = 3, rot=50;
var deadArea = 0.2
var spiral = d3.range(0+deadArea, 1 + 1 / n - deadArea, 1 / n).map(function(t) {
return [(360 * rot * t) % 360 - 180, -90 + dy
- Math.random()
+ (Math.cos(100 * Math.PI * t) - 1) / 2
+ (Math.cos(3 * Math.PI * t) - 1) / 2
+ (90 - dy) * 2 * t];
});
var spiralBase = d3.range(1-deadArea, 0+ deadArea, -1 / n).map(function(t) {
return [(360 * rot * t) % 360 - 180, -90 + (90 - dy ) * 2 * t];
});
// Interpolating projection from Michael Bostock's https://bl.ocks.org/mbostock/5731632
function interpolatedProjection(a, b) {
var projection = d3.geo.projection(raw).scale(1),
translate = projection.translate,
α;
function raw(λ, φ) {
var pa = a([λ *= 180 / Math.PI, φ *= 180 / Math.PI]), pb = b([λ, φ]);
return [(1 - α) * pa[0] + α * pb[0], (α - 1) * pa[1] - α * pb[1]];
}
projection.alpha = function(_) {
if (!arguments.length) return α;
α = +_;
var ta = a.translate(), tb = b.translate();
translate([(1 - α) * ta[0] + α * tb[0], (1 - α) * ta[1] + α * tb[1]]);
return projection;
};
delete projection.scale;
delete projection.translate;
return projection.alpha(0);
}
// Projection transforms
function lineSimplification(proj, directionForward, features, path) {
return function(tweenArg) {
simplificationFactor=0.7-0.7*Math.pow(2*(Math.abs(tweenArg-0.5)), 4);
features.attr("d", function(d) {
return path({
type: d.type,
coordinates: d.type==='MultiLineString'
? d.coordinates.map(function(c,i) { return simplify(c, simplificationFactor, true); })
: d.coordinates
});
});
};
}
function conicCartesianToSpiral(proj, directionForward, features, path) {
return function(tweenArg) {
var _ = directionForward?tweenArg:(1-tweenArg);
proj.parallels([_*89.99, _*89.99]);
proj.scale((1-_)*(1-_)*110+40);
proj.translate([width / 2 - .5, height / 2 + Math.sqrt(_)*87])
features.attr("d", path);
};
}
function interpolationSpiralToSphere(proj, directionForward, features, path) {
return function(tweenArg) {
var _ = directionForward?tweenArg:(1-tweenArg);
proj.alpha(_);
features.attr("d", path);
};
}
function ortographicSpin(proj, directionForward, features, path) {
var originalRotation = proj.rotate();
return function(tweenArg) {
var _ = directionForward?tweenArg:(1-tweenArg);
proj.clipAngle(95);
proj.rotate([_*360, _*-90, _*-90]);
features.attr("d", path);
};
}
// Showreel with projection/transform pairs
// Items are out of order so one projection can refer to another (e.g. next one)
showReel = []
showReel[0] = {
projection: d3.geo.conicConformal().parallels([0,0]).scale(150).translate([width / 2, height / 2 ]),
transform: lineSimplification
};
showReel[1] = {
projection: showReel[0].projection,
transform: conicCartesianToSpiral
};
showReel[3] = {
projection: d3.geo.orthographic().rotate([0,0,0]).scale(250).translate([width / 2 , height / 2 ]),
transform: ortographicSpin
};
showReel[2] = {
projection: interpolatedProjection(showReel[1].projection, showReel[3].projection),
transform: interpolationSpiralToSphere
};
showReel[4] = {
projection: showReel[3].projection,
transform: lineSimplification
};
// Animation and initial output
function animate(index, directionForward) {
var delta = directionForward?1:-1,
nextIndex = Math.min(Math.max(index+delta,0), showReel.length-1),
nextDirectionForward = nextIndex===index+delta?directionForward:!directionForward,
keyframe = showReel[index],
path = d3.geo.path().projection(keyframe.projection),
features = render(path);
svg.transition()
.duration(2000)
.tween("projection", function() {
return keyframe.transform(keyframe.projection, directionForward, features, path);
})
.transition()
.duration(0)
.each('end', animate.bind(this, nextIndex, nextDirectionForward));
}
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height),
simplificationFactor = 0;
function render(path) {
svg.selectAll(".graticule")
.data(graticule.lines)
.enter().append("path")
.attr("class", "graticule")
.attr("d", path);
svg.selectAll(".spiral")
.data([{type: "MultiLineString", coordinates: [spiral]}])
.enter().append("path")
.attr("class", "spiral")
.attr("d", path);
return svg.selectAll("path");
}
animate(0, true);
</script>
Modified http://d3js.org/d3.v3.min.js to a secure url
Modified http://d3js.org/d3.geo.projection.v0.min.js to a secure url
https://d3js.org/d3.v3.min.js
https://d3js.org/d3.geo.projection.v0.min.js