A minimalistic visualization of SES RP2 FABs.
Geo data from Eurocontrol Atlas.
A minimalistic visualization of SES RP2 FABs.
Geo data from Eurocontrol Atlas.
| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <style> | |
| svg { | |
| background: #00FFFF; | |
| } | |
| .graticule { | |
| fill: none; | |
| stroke: #777; | |
| stroke-width: .5px; | |
| stroke-opacity: .5; | |
| } | |
| .land { | |
| fill: #ddd; | |
| opacity: 0.4; | |
| stroke: #aaa; | |
| } | |
| .countryboundary { | |
| fill: none; | |
| stroke: #aaa; | |
| stroke-width: .5px; | |
| } | |
| .firboundary { | |
| stroke: black; | |
| fill: none; | |
| stroke-width: .5px; | |
| } | |
| .fabboundary { | |
| stroke: rgb(206,0,18); | |
| fill: none; | |
| stroke-width: .5px; | |
| } | |
| .SWFAB { | |
| fill: rgb(247,243,38); | |
| } | |
| .NEFAB { | |
| fill: rgb(13,24,73); | |
| } | |
| .FABEC { | |
| fill: rgb(238,138,27); | |
| } | |
| .FABCE { | |
| fill: rgb(169,0,25); | |
| } | |
| .UKEI { | |
| fill: rgb(125,59,148); | |
| } | |
| .BALTIC { | |
| fill: rgb(238,234,49); | |
| } | |
| .DKSE { | |
| fill: orange; | |
| } | |
| .DANUBE { | |
| fill: rgb(179,179,179); | |
| } | |
| .BLUEMED { | |
| fill: rgb(30,37,108); | |
| } | |
| .fab { | |
| opacity: 0.3; | |
| pointer-events: none; | |
| } | |
| .fir { | |
| opacity: 0.1; | |
| } | |
| #tooltip { | |
| position: absolute; | |
| width: auto; | |
| height: auto; | |
| padding: 2px 2px; | |
| -webkit-border-radius: 3px; | |
| -moz-border-radius: 3px; | |
| border-radius: 3px; | |
| -webkit-box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.4); | |
| -moz-box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.4); | |
| box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.4); | |
| pointer-events: none; | |
| background-color: #bbb; | |
| } | |
| #tooltip.hidden { | |
| display: none; | |
| } | |
| #tooltip p { | |
| margin: 0 0 0 0; | |
| padding: 2px 2px; | |
| font-family: sans-serif; | |
| font-size: 11px; | |
| } | |
| .hidden{ | |
| display: none; | |
| } | |
| </style> | |
| <body> | |
| <div id="tooltip" class="hidden"> | |
| <p id="info"></p> | |
| </div> | |
| <script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script> | |
| <script src="//d3js.org/topojson.v1.min.js"></script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/queue-async/1.0.7/queue.min.js"></script> | |
| <script> | |
| var width = 960, | |
| height = 630; | |
| var scale = 570, | |
| cLat = 52.4, | |
| cLon = 0; | |
| var color = d3.scale.category10(); | |
| var tooltip = d3.select("#tooltip").classed("hidden", true); | |
| var info = d3.select("#info"); | |
| var projection = d3.geo.albers() | |
| .center([cLon, cLat]) | |
| .rotate([4.4, 0]) | |
| .parallels([50, 60]) | |
| .scale(scale) | |
| .translate([width / 2, height / 2]); | |
| var path = d3.geo.path() | |
| .projection(projection); | |
| var graticule = d3.geo.graticule() | |
| .majorStep([5, 5]) | |
| .precision(2.5); | |
| var svg = d3.select("body").append("svg") | |
| .attr("width", width) | |
| .attr("height", height); | |
| svg.append("path") | |
| .datum(graticule) | |
| .attr("class", "graticule") | |
| .attr("d", path); | |
| svg.on("mousemove", function () { | |
| // update tooltip position | |
| tooltip.style("top", (event.pageY + 8) + "px").style("left", (event.pageX + 8) + "px"); | |
| return true; | |
| }); | |
| queue() | |
| .defer(d3.json, "rp2.json") | |
| .defer(d3.json, "world-50m.json") | |
| .await(ready); | |
| function ready(error, rp2, world) { | |
| if (error) return console.error(error); | |
| var firs = topojson.feature(rp2, rp2.objects.firs); | |
| var firborders = topojson.mesh(rp2, rp2.objects.firs, function (a, b) { return (a !== b) || (a === b); }); | |
| var fabs = topojson.feature(rp2, rp2.objects.fabs); | |
| // FAB borders (interior [a !== b] and exterior [a === b]) | |
| var fabborders = topojson.mesh(rp2, rp2.objects.fabs, function (a, b) { return (a !== b) || (a === b); }); | |
| var land = topojson.feature(world, world.objects.land); | |
| var countryborders = topojson.mesh(world, world.objects.countries, function (a, b) { return (a !== b) || (a === b); }); | |
| svg.insert("path", ".graticule") | |
| .datum(topojson.feature(world, world.objects.land)) | |
| .attr("class", "land") | |
| .attr("d", path); | |
| svg.insert("path", ".graticule") | |
| .datum(countryborders) | |
| .attr("class", "countryboundary") | |
| .attr("d", path); | |
| svg.selectAll(".fir") | |
| .data(firs.features) | |
| .enter().insert("path", ".graticule") | |
| .attr("class", function(d) { return "fir " + d.id + " " + d.properties.fab; }) | |
| .attr("d", path) | |
| .on("mouseover", function (d, i) { | |
| d3.select(this).style({'stroke-opacity': 1, 'stroke': '#F00'}); | |
| // http://stackoverflow.com/questions/17917072/#answer-17917341 | |
| tooltip.classed("hidden", false); | |
| info.text("FIR: " + d.id + ". " + d.properties.fab); | |
| }) | |
| .on("mouseout", function () { | |
| this.style.stroke = "none"; | |
| tooltip.classed("hidden", true); | |
| }); | |
| svg.insert("path", ".graticule") | |
| .datum(firborders) | |
| .attr("class", "firboundary") | |
| .attr("d", path); | |
| svg.selectAll(".fab") | |
| .data(fabs.features) | |
| .enter().insert("path", ".graticule") | |
| .attr("class", function(d) { return "fab " + d.properties.id; }) | |
| .attr("d", path); | |
| svg.insert("path", ".graticule") | |
| .datum(fabborders) | |
| .attr("class", "fabboundary") | |
| .attr("d", path); | |
| } | |
| </script> |