var ε = 1e-6, π = Math.PI, radians = π / 180, degrees = 180 / π, θ = Math.atan(Math.SQRT1_2) * degrees; // map in a cube. (function() { var vertices = [ [0, θ], [90, θ], [180, θ], [-90, θ], [0, -θ], [90, -θ], [180, -θ], [-90, -θ] ]; var polyhedron = [ [0, 3, 2, 1], // N [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7], [4, 5, 6, 7] // S ].map(function(face) { return face.map(function(i) { return vertices[i]; }); }); var faces = polyhedron.map(function(face, i) { var centroid = d3.geo.centroid({type: "MultiPoint", coordinates: face}); if (Math.abs(Math.abs(centroid[1]) - 90) < ε) centroid[0] = 0; var ring = face.map(function(d) { return [((d[0] + 180) % 360 - 180) * radians, d[1] * radians]; }); face.centroid = centroid; return { face: face, polygon: [ring], project: d3.geo.gnomonic().scale(1).translate([0, 0]).rotate([-centroid[0], -centroid[1]]) }; }); // Connect each face to a parent face. [-1, 4, 5, 2, 0, 1]; [-1, 0, 0, 0, 0, 4] //f3 .forEach(function (d, i) { var node = faces[d]; node && (node.children || (node.children = [])).push(faces[i]); }); var width = 900, height = 600, rotate = [35, 10, 0], rotation = d3.geo.rotation(rotate); var projection = d3.geo.polyhedron(faces[0], face, - π/4) // 1 * π / 2) .translate([ Math.atan(Math.SQRT1_2) * width / 2 + 45, height / 2]) .translate([ width / 2, height/2]) .rotate(rotate) .center([10, 69]) .scale(90); var path = d3.geo.path().projection(projection); var svg = d3.select("#map").append("svg") .attr("width", width) .attr("height", height); var movable = svg.append('g'); movable.append("path") .datum({type: "Sphere"}) .attr("class", "background") .attr("d", path); movable.append("path") .datum(d3.geo.graticule()) .attr("class", "graticule") .attr("d", path); svg.append("path") .datum({type: "MultiLineString", coordinates: projection.mesh.map(function(segment) { return segment.map(rotation.invert); })}) .attr("class", "face") .attr("d", path); svg.append("path") .datum({type: "Sphere"}) .attr("class", "outline") .attr("d", path); d3.json("world-110m.json", function(error, world) { var land = topojson.feature(world, world.objects.land); movable.insert("path", ".graticule") .datum(land) .attr("class", "land") .attr("d", path); }); function face(λ, φ) { var point = [λ, φ], face = null, inside = 0; for (var i = 0, n = faces.length; i < n; ++i) { face = faces[i]; if (d3.geo.pointInPolygon(point, face.polygon)) return face; } } d3.select(self.frameElement).style("height", height + "px"); svg.call( d3.behavior.drag() .on("dragstart", dragstarted) .on("drag", dragged) //.on("dragend", dragended) ); var render = function() { movable.selectAll('path').attr('d', path); }, v0, // Mouse position in Cartesian coordinates at start of drag gesture. r0, // Projection rotation as Euler angles at start. q0; // Projection rotation as versor at start. function dragstarted() { var mouse = d3.event.sourceEvent; v0 = versor.cartesian(projection.invert([mouse.x, mouse.y])); r0 = projection.rotate(); q0 = versor(r0); } function dragged() { var mouse = d3.event.sourceEvent; var inv = projection.rotate(r0).invert([mouse.x, mouse.y]); if (!inv || isNaN(inv[0])) return; var v1 = versor.cartesian(inv), q1 = versor.multiply(q0, versor.delta(v0, v1)), r1 = versor.rotation(q1); projection.rotate(r1); render(); } })();