Using d3 v4 geo pipeline to skew and tweak a projection.
Uising dat.gui for easy parameters modifications.
Based on @mbostock's following blocks:
license: gpl-3.0 |
Using d3 v4 geo pipeline to skew and tweak a projection.
Uising dat.gui for easy parameters modifications.
Based on @mbostock's following blocks:
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<body> | |
<script src="//d3js.org/d3.v4.min.js"></script> | |
<script src="//d3js.org/d3-geo-projection.v1.min.js"></script><script src="//d3js.org/topojson.v1.min.js"></script> | |
<script> | |
var width = 960, | |
height = 500, | |
opts = { | |
scale: 250, | |
translate: { x: width/2 - 110, y: height/2 }, | |
focusY: 4.4, | |
angle: 8, | |
scaleY: 0.55, | |
fillColor: 'rgba(78, 216, 158, 0.99)' | |
}; | |
function draw(land, places){ | |
// We use a scale 1 Robinson projection as base | |
// then tweak it (with different rescales on X and Y) | |
var baseprojection = d3.geoRobinson() | |
.rotate([-10.5, 0]) | |
.translate([0, 0]) | |
.scale(1) | |
.precision(0.001), | |
// maps the base-projected point to the screen coords | |
screenproject = function (p) { | |
var x = p[0] * (p[1] + opts.focusY)/opts.angle, | |
y = p[1] * opts.scaleY; | |
return [opts.scale * x + opts.translate.x, opts.scale * y + opts.translate.y]; | |
}, | |
// creates a geoTransform from the previous function | |
transform = d3.geoTransform({ | |
point: function (x, y) { | |
var q = screenproject([x, y]); | |
if (q) this.stream.point(q[0], q[1]); | |
} | |
}), | |
// this so we can use the projection as a function([lon,lat]) | |
projection = function (p) { | |
return screenproject(baseprojection(p)); | |
}; | |
// Here is where we compose the functions. | |
// it's `transform o baseprojection`, but coded the other way, | |
// as it works with callbacks (bleh!) | |
projection.stream = function (s) { | |
return baseprojection.stream(transform.stream(s)); | |
}; | |
var context = canvas.node().getContext("2d"); | |
var path = d3.geoPath() | |
.projection(projection) | |
.context(context); | |
context.clearRect(0, 0, width, height); | |
context.beginPath(); | |
context.strokeStyle ="#eee"; | |
context.fillStyle ="#9ab"; | |
context.lineWidth = 0.5; | |
path(land); | |
context.fill(); | |
context.stroke(); | |
context.beginPath(); | |
path(places); | |
context.fillStyle = opts.fillColor; | |
context.lineWidth = 1.5; | |
context.strokeStyle = d3.rgb(opts.fillColor).darker(); | |
context.fill(); | |
context.stroke(); | |
} | |
var canvas = d3.select("body").append("canvas") | |
.attr("width", width) | |
.attr("height", height); | |
d3.json("countries.topo.json", function(error, world) { | |
if (error) throw error; | |
var land = topojson.feature(world, world.objects.countries); | |
var places = {type: "MultiPoint", | |
coordinates: land.features | |
.filter(function(c){ | |
return c.properties.pop_est > 50000000 | |
}) | |
.map(function(c){ | |
return d3.geoCentroid(c) ; | |
}) | |
}; | |
function redraw(){ | |
draw(land, places); | |
} | |
redraw(); | |
if (typeof gui == 'function') | |
gui(opts, redraw, { | |
listen: true, | |
options: { width: 300 }, | |
}); | |
}); | |
</script> | |
<script src="https://unpkg.com/dat.gui/build/dat.gui.min.js"></script> | |
<link rel="stylesheet" href="https://raw.githack.com/liabru/dat-gui-light-theme/master/dat-gui-light-theme.css"/> | |
<script> | |
dat.GUI.TEXT_CLOSED = '🏇'; | |
dat.GUI.TEXT_OPEN = '💡'; | |
function gui(opts, redraw, config) { | |
var gui = new dat.GUI(config.options || {}); | |
gui.close(); | |
for (var i in opts) { | |
add(gui, opts, i); | |
} | |
function add(src, o, t) { | |
if (typeof o[t] == 'object') { | |
var group = src.addFolder(t); | |
for (var j in o[t]) { | |
add(group, o[t], j); | |
} | |
} else { | |
var control = (t.match(/color/i)) | |
? src.addColor(o, t) | |
: src.add(o, t); | |
if (config.listen) control.listen(); | |
if (redraw) control.onChange(redraw); | |
} | |
} | |
} | |
</script> | |
<!-- /dat.gui --> |