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 --> |