{
const W = 520, H = 520, R = 230;
const cx = W/2, cy = H/2;
// d3 orthographic: rotate([lambda, phi]) centers the globe on (-lambda, -phi)
// angle=0 → lambda=-75 → center on 75°E (India)
// angle=75 → lambda=0 → center on 0°E (Greenwich)
const lambda = angle - 75;
const phi = 0; // tilt: center latitude = 20°N
const proj = d3.geoOrthographic()
.scale(R)
.translate([cx, cy])
.rotate([lambda, phi, 0]);
const path = d3.geoPath(proj);
// Square frame (no border-radius)
const svg = d3.create("svg")
.attr("width", W)
.attr("height", H)
.style("background", "#020208")
.style("display", "block")
.style("margin", "auto");
// ── Ocean ──────────────────────────────────────────────────────────────────
svg.append("circle")
.attr("cx", cx).attr("cy", cy).attr("r", R)
.attr("fill", "#1a5a8a");
// ── Meridians every 15° (north-south lines, light gray) ───────────────────
// step([15, 90]): longitude lines every 15°, latitude lines at poles only
svg.append("path")
.datum(d3.geoGraticule().step([15, 90])())
.attr("fill", "none")
.attr("stroke", "rgba(200,200,200,0.18)")
.attr("stroke-width", 0.5)
.attr("d", path);
// ── Equator (slightly highlighted) ────────────────────────────────────────
const equator = {
type: "LineString",
coordinates: d3.range(-180, 181, 1).map(l => [l, 0])
};
svg.append("path").datum(equator)
.attr("fill", "none")
.attr("stroke", "rgba(255,255,180,0.25)")
.attr("stroke-width", 1)
.attr("d", path);
// ── Land ──────────────────────────────────────────────────────────────────
svg.append("path")
.datum(topojson.feature(world, world.objects.land))
.attr("fill", "#2d6a1f")
.attr("d", path);
// ── Coastlines ────────────────────────────────────────────────────────────
svg.append("path")
.datum(topojson.mesh(world, world.objects.countries))
.attr("fill", "none")
.attr("stroke", "rgba(255,255,255,0.35)")
.attr("stroke-width", 0.6)
.attr("d", path);
// ── Globe rim ─────────────────────────────────────────────────────────────
svg.append("circle")
.attr("cx", cx).attr("cy", cy).attr("r", R)
.attr("fill", "none")
.attr("stroke", "rgba(255,255,255,0.15)")
.attr("stroke-width", 1);
// ── Polar axis (orange line through N and S poles) ────────────────────────
// The poles sit on the z-axis; their projected positions don't depend on
// lambda (longitude rotation), only on phi (tilt).
const npPx = proj([0, 90]); // north pole projected pixel
const spPx = proj([0, -90]); // south pole projected pixel
// Extend the line 35px beyond each pole for a clean axis look
const dx = npPx[0] - spPx[0];
const dy = npPx[1] - spPx[1];
const len = Math.sqrt(dx*dx + dy*dy);
const ex = (dx / len) * 35;
const ey = (dy / len) * 35;
// Back half of axis (dimmer, drawn first so front half sits on top)
svg.append("line")
.attr("x1", spPx[0] - ex).attr("y1", spPx[1] - ey)
.attr("x2", cx) .attr("y2", cy)
.attr("stroke", "rgba(255,140,0,0.35)")
.attr("stroke-width", 1.5)
.attr("stroke-dasharray", "4,3");
// Front half of axis (solid)
svg.append("line")
.attr("x1", cx) .attr("y1", cy)
.attr("x2", npPx[0] + ex) .attr("y2", npPx[1] + ey)
.attr("stroke", "orange")
.attr("stroke-width", 2);
// Small arrowhead at north pole end
svg.append("circle")
.attr("cx", npPx[0] + ex).attr("cy", npPx[1] + ey)
.attr("r", 3)
.attr("fill", "orange");
// ── Visibility check ──────────────────────────────────────────────────────
function visible(lon, lat) {
const lonC = -lambda * Math.PI / 180;
const latC = -phi * Math.PI / 180;
const lonR = lon * Math.PI / 180;
const latR = lat * Math.PI / 180;
return (
Math.sin(latR) * Math.sin(latC) +
Math.cos(latR) * Math.cos(latC) * Math.cos(lonR - lonC)
) > 0;
}
// ── Reference dots ────────────────────────────────────────────────────────
const dots = [
{ lon: 75, lat: 20, color: "orange", label: "India" },
{ lon: 0, lat: 51.5, color: "white", label: "Greenwich" },
{ lon: 0, lat: 90, color: "red", label: "N pole" },
];
for (const d of dots) {
if (!visible(d.lon, d.lat)) continue;
const [px, py] = proj([d.lon, d.lat]);
svg.append("circle")
.attr("cx", px).attr("cy", py).attr("r", 6)
.attr("fill", d.color)
.attr("stroke", "white")
.attr("stroke-width", 1.5);
svg.append("text")
.attr("x", px + 10).attr("y", py + 5)
.text(d.label)
.attr("fill", d.color)
.attr("font-size", "13px")
.attr("font-family", "Arial, sans-serif");
}
return svg.node();
}