|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<style> |
|
body { |
|
font-family: "Helvetica Neue", sans-serif; |
|
margin: 0; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script src="https://d3js.org/topojson.v1.min.js"></script> |
|
<script> |
|
|
|
var width = window.innerWidth, height = window.innerHeight; |
|
|
|
var projection = d3.geoOrthographic() |
|
.scale(width / 4.1) |
|
.translate([width / 2, height / 2]) |
|
.clipAngle(90 + 1e-6) |
|
.precision(1) |
|
.rotate([0, 0]); |
|
|
|
var path = d3.geoPath() |
|
.projection(projection); |
|
|
|
var svg = d3.select("body").append("svg") |
|
.attr("width", width) |
|
.attr("height", height); |
|
|
|
var g = svg.append("g"); |
|
|
|
var graticule = d3.geoGraticule() |
|
.step([10, 10]); |
|
|
|
g.append("path") |
|
.datum(graticule) |
|
.attr("class", "graticule") |
|
.attr("d", path) |
|
.style("fill", "#fff") |
|
.style("stroke", "#ccc"); |
|
|
|
var dials = [{ |
|
name: "λ", |
|
scale: d3.scaleLinear().domain([40, height / 2 - 40]).range([-180, 180]), |
|
rscale: d3.scaleLinear().domain([-180, 180]).range([40, height / 2 - 40]) |
|
},{ |
|
name: "φ", |
|
scale: d3.scaleLinear().domain([40, height / 2 - 40]).range([90, -90]), |
|
rscale: d3.scaleLinear().domain([90, -90]).range([40, height / 2 - 40]) |
|
},{ |
|
name: "γ", |
|
scale: d3.scaleLinear().domain([40, height / 2 - 40]).range([180, -180]), |
|
rscale: d3.scaleLinear().domain([180, -180]).range([40, height / 2 - 40]) |
|
}]; |
|
|
|
svg.selectAll(".dial-rect") |
|
.data(dials) |
|
.enter().append("rect") |
|
.attr("class", "dial-rect") |
|
.attr("x", function(d,i){ return 45 * (i + 1); }) |
|
.attr("y", 40) |
|
.attr("width", 20) |
|
.attr("height", height / 2 - 80) |
|
.attr("rx", 10) |
|
.attr("ry", 10) |
|
.style("stroke", "#ccc") |
|
.style("fill", "#3a403d"); |
|
|
|
svg.selectAll(".dial-text") |
|
.data(dials) |
|
.enter().append("text") |
|
.attr("class", "dial-text") |
|
.attr("x", function(d,i) { return 45 * (i + 1); }) |
|
.attr("y", 15) |
|
.attr("dx", 10) |
|
.attr("text-anchor", "middle") |
|
.style("fill", "#3a403d") |
|
.text(function(d){ return d.name; }); |
|
|
|
svg.selectAll(".dial-circle") |
|
.data(dials) |
|
.enter().append("circle") |
|
.attr("class", function(d){ return "dial-circle dial-" + d.name; }) |
|
.attr("cx", function(d,i){ return 45 * (i + 1) + 10; }) |
|
.attr("cy", function(d){ return d.rscale(0); }) |
|
.attr("r", 20) |
|
.style("stroke", "#aaa") |
|
.style("fill", "#ccc") |
|
.style("cursor", "ns-resize") |
|
.call(d3.drag().on("drag", dragged)); |
|
|
|
svg.selectAll(".dial-circle-text") |
|
.data(dials) |
|
.enter().append("text") |
|
.attr("class", function(d) { return "dial-circle-text dial-" + d.name; }) |
|
.attr("x", function(d,i){ return 45 * (i + 1) + 10; }) |
|
.attr("y", function(d){ return d.rscale(0) + 5; }) |
|
.attr("text-anchor", "middle") |
|
.style("font-size", ".7em") |
|
.style("cursor", "ns-resize") |
|
.text("0") |
|
.call(d3.drag().on("drag", dragged)); |
|
|
|
function dragged(d){ |
|
|
|
var y = d3.mouse(this)[1]; |
|
y < 40 ? y = 40 : y = y; |
|
y > height / 2 - 40 ? y = height / 2 - 40 : y = y; |
|
|
|
d3.select(".dial-circle.dial-" + d.name) |
|
.attr("cy", y); |
|
|
|
d3.select(".dial-circle-text.dial-" + d.name) |
|
.attr("y", y + 5) |
|
.text(Math.round(d.scale(y))); |
|
|
|
projection.rotate([dials[0].scale(d3.select(".dial-λ").attr("cy")), dials[1].scale(d3.select(".dial-φ").attr("cy")), dials[2].scale(d3.select(".dial-γ").attr("cy"))]) |
|
|
|
g.selectAll("path").attr("d", path); |
|
|
|
} |
|
|
|
var c = d3.scaleOrdinal(d3.schemeCategory20); |
|
|
|
d3.json("countries.json", function(error, data){ |
|
|
|
g.selectAll(".subunit") |
|
.data(topojson.feature(data, data.objects.polygons).features) |
|
.enter().append("path") |
|
.attr("class", "subunit") |
|
.attr("d", path) |
|
.style("stroke", "#fff") |
|
.style("stroke-width", "1px") |
|
.style("fill", function(d,i){ return c(i); }) |
|
.style("opacity", ".6"); |
|
|
|
}); |
|
</script> |
|
|
|
</body> |
|
</html> |