World tour using Wikipedia Map guidelines, more precisely the soon to come new globe locator convention. Inspired from Jason Davies world tour, merged into my localisator code.
This code is a second wave of customization.
In a 3rd wave of customization, this code could be refactored to reduce code redundancies.
The loop on data’s .countries
should in order recalculate the bounding box, run the localisator with conditional framing, print the file, before to go to i+1.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
text {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 18px;
font-weight: bold;
text-anchor: middle;
}
</style>
<body>
<script src="//code.jquery.com/jquery-2.1.0.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.13/d3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/topojson/1.1.0/topojson.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3-geo-projection/0.2.9/d3.geo.projection.min.js"></script>
<script src="../js/wikiatlas.js"></script>
<script>
/**
* Created with Wikiatlas.
* User: hugolpz
* Version: 2014.09.04 */
/* ****************************************************** */
/* MATH TOOLKIT ***************************************** */
function parallel(φ, λ0, λ1) {
if (λ0 > λ1) λ1 += 360;
var dλ = λ1 - λ0,
step = dλ / Math.ceil(dλ);
return d3.range(λ0, λ1 + 0.5 * step, step).map(function(λ) { return [normalise(λ), φ]; });
}
function normalise(x) {
return (x + 180) % 360 - 180;
}
/* ****************************************************** */
/* LOCALISATOR FN *************************************** */
var localisator = function (hookId,localisator_width, title, WNES0, WNES1, WNES2, WNES3) {
/* Init ************************************************* */
var width = 1*localisator_width,
height = 1*localisator_width;
var lon_central = function(){
var num;
if(WNES2<WNES0){ num= -(WNES0+WNES2)/2+180; }
else{ num= -(WNES0+WNES2)/2; }
return num;
};
var proj = d3.geo.orthographic()
.scale(1/2*localisator_width)
.rotate([ lon_central(), -(WNES1+WNES3)/2 +10 ]); // orthographic + 10⁰ to simulate real life globe watching.
var projection2 = proj
.translate([width / 2 , height / 2 ])
.clipAngle(90);
var path = d3.geo.path()
.projection(projection2);
/* SVG container **************************************** */
var svg = d3.select(hookId).append("svg")
.attr("id", title+"-orthographic_globe_locator_(wikiatlas_2014)")
.attr("width", width)
.attr("height", height)
.call(d3.behavior.drag()
.origin(function() {
var rotate = projection2.rotate();
return {x: 2 * rotate[0], y: -2 * rotate[1]};
})
.on("drag", function() {
projection2.rotate([d3.event.x / 2, -d3.event.y / 2, projection2.rotate()[2]]);
svg.selectAll("path").attr("d", path);
}))
.on("dblclick", function() {
projection2.rotate([ lon_central(), -(WNES1+WNES3)/2 +10 ]);
svg.selectAll("path").attr("d", path);
});
/* SVG background *************************************** */
// Blue circle
svg.append("circle")
.attr("class", "water")
.attr("cx", width/2)
.attr("cy", height/2)
.attr("r", width/2 )
.style({'fill':'#C6ECFF'})
.style({'stroke': '656565', 'stroke-width': 1.5});
// Gradiant settings
var gradient = svg.append("svg:defs")
.append("svg:linearGradient")
.attr("id", "gradient")
.attr("x1", "0%")
.attr("y1", "0%")
.attr("fx1", "30%")
.attr("fy1", "30%")
.attr("x2", "100%")
.attr("y2", "100%")
.attr("spreadMethod", "pad");
gradient.append("svg:stop") // middle step setting
.attr("offset", "50%")
.attr("stop-color", "#FFF")
.attr("stop-opacity", 0.3);
gradient.append("svg:stop") // final step setting
.attr("offset", "100%")
.attr("stop-color", "#009")
.attr("stop-opacity", 0.3);
// Gradiant-circle
var circle = svg.append('circle') // append gradient to circle
.attr('cx', width / 2)
.attr('cy', height / 2)
.attr('r', width/2 )
.attr('fill', 'url(#gradient)');
/* ****************************************************** */
/* GIS data injection *********************************** */
d3.json("world-110m-ids.json", function(error, world) {
/**/ var countries = topojson.feature(world, world.objects.countries).features,
/**/ i = -1,
/**/ n = countries.length;
var country = svg.selectAll(".country")
.data(topojson.feature(world, world.objects.countries).features)
.enter().append("path")
.attr("id", function(d){ return d.id } )
.attr("class", "country")
.style("fill", "#FDFBEA")
.attr("d", path);
var focus = d3.selectAll("#"+title)
.style("fill", "#B10000");
var boundaries = svg.append("path")
//.datum( topojson.mesh(world, world.objects.countries, function(a,b) { if (a!==b){var ret = b;}return ret;}))
.datum( topojson.mesh(world, world.objects.countries, function(a,b) { return a!==b; }))
.attr("class", "boundary")
.attr("d", path)
.style({'fill':'none','stroke': '#656565', 'stroke-width': 0.5});
var graticule = svg.append("path")
.datum(d3.geo.graticule().step([20,20]))
.attr("class", "graticule")
.attr("d", path)
.style({'fill':'none', 'stroke':'#777', 'stroke-width': 0.5, 'stroke-opacity': 0.5});
var coast = svg.append("path")
//.datum( topojson.mesh(world, world.objects.countries, function(a,b) { if (a==b){var ret = b;}return ret;}))
.datum( topojson.mesh(world, world.objects.countries, function(a,b) { return a==b; }))
.attr("class", "Coast_border")
.style({'fill': 'none', 'stroke': '#0978AB', 'stroke-linejoin': 'round'})
.style({'stroke-width': 0.5 })
.attr("d", path);
/* Red graniticule drawing
svg.append("path")
.attr("d", path(d3.geo.graticule()
.majorExtent([[WNES0, WNES3], [WNES2, WNES1]]).outline()))
.style({'fill': '#B10000', 'fill-opacity': 0.3, 'stroke': '#B10000', 'stroke-linejoin': 'round'})
.style({'stroke-width': 1 }); /**/
//* Red polygon drawing
var redwindow = svg.append("path")
.datum({type: "Polygon", coordinates: [ //LineString
[[WNES0,WNES3]]
.concat(parallel(WNES1, WNES0, WNES2))
.concat(parallel(WNES3, WNES0, WNES2).reverse())
]})
.style({'fill': '#B10000', 'fill-opacity': 0.3, 'stroke': '#B10000', 'stroke-linejoin': 'round'})
.style({'stroke-width': 1 })
.attr("d", path); /**/
var label = svg.append("text")
.attr("x", width / 2)
.attr("text-anchor","middle")
.text(title)
.attr("y", height * 57/100 );
var step = function() {
if (++i >= n) { i = 0} ;
label.text(countries[i].id);
svg.transition();
country.transition()
.style("fill", function(d, j) { return j === i ? "#B10000" : "#FDFBEA"; });
var centroid = d3.geo.path()
.projection(function(d) { return d; })
.centroid;
var area = d3.geo.path()
.projection(function(d) { return d; })
.area;
var bounds = d3.geo.path()
.projection(function(d) { return d; })
.bounds;
d3.transition()
.delay(250)
.duration(1250)
.tween("rotate", function() {
var point = centroid(countries[i]);
var surface = area(countries[i]);
var bb = bounds(countries[i]);
console.log("area: "+surface+"; bb: "+ JSON.stringify(bb) );
return function(t) {
projection2.rotate([-point[0], -point[1]+10]); // area of interest slide 10⁰ up
country.attr("d", path);
boundaries.attr("d", path);
graticule.attr("d", path);
coast.attr("d", path);
// draw polygon (red frame) bigger than bb:
if (surface <15) { // is visible
redwindow.datum({type: "Polygon", coordinates: [ //LineString
[[bb[0][0]-5,bb[0][1]-5]]
.concat(parallel(bb[1][1]+5, bb[0][0]-5, bb[1][0]+5))
.concat(parallel(bb[0][1]-5, bb[0][0]-5, bb[1][0]+5).reverse())
]})
.style({'fill': '#B10000', "opacity": 1, 'fill-opacity': 0.3})
.style({'stroke-width': 1, 'stroke-opacity': 1, 'stroke': '#B10000', 'stroke-linejoin': 'round' })
.attr("d", path); /**/
} else if (surface >15 ) { // isn't visible
redwindow.style({'opacity': 0})
}
};
})
.transition() // runs transition
.each("end", function(){ console.log(countries[i].id); return step(); } );
}
step();
});
};
</script>
<script>
var WNES = { "item":"India", "W": 67.0, "N":37.5, "E": 99.0, "S": 5.0 };
localisator("body",500, WNES.item, WNES.W, WNES.N, WNES.E, WNES.S);
</script>