Skip to content

Commit 0b7498e

Browse files
committed
update live map error handling and edge cases
1 parent 8b29c6b commit 0b7498e

1 file changed

Lines changed: 118 additions & 62 deletions

File tree

_includes/livemap.html

Lines changed: 118 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
<p id="map-text">
2-
Loading...
3-
</p>
1+
<p id="map-text">Loading...</p>
42
<div id="map-container"></div>
53
<div id="map-tooltip" class="tooltip"></div>
64
<p>Join the Discord to add yourself to the map</p>
@@ -23,63 +21,121 @@
2321
// grab the data
2422
Promise.all([
2523
d3.json("{{ site.live_map_api }}/locations.geojson"),
26-
d3.json("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson")
27-
]).then(([pointData, worldData]) => {
28-
// update map text
29-
text.innerText = `${pointData.features.length} hackers online!`;
30-
31-
// get the bounds of locations (min & max coordinates)
32-
const bounds = d3.geoBounds(pointData);
33-
const [[x0, y0], [x1, y1]] = bounds;
34-
35-
const dx = x1 - x0;
36-
const dy = y1 - y0;
37-
const x = ((x0 + x1) / 2) || 0;
38-
const y = ((y0 + y1) / 2) || 0;
39-
40-
// calculate scale and translation based on the bounds
41-
const scale = Math.max(10, Math.min(dx / width, dy / height)) * 100;
42-
const translate = [width / 2 - scale * x, height / 2 - scale * y];
43-
const padding = 100;
44-
45-
// create a new projection, centered at the middle of the locations
46-
let projection = d3.geoNaturalEarth1()
47-
.center([x, y])
48-
.translate([width / 2, height / 2]);
49-
50-
// if there's more than 1 location, fit the map to show all locations nicely
51-
if (pointData.features.length > 1) {
52-
projection = projection.fitExtent([[0 + padding, 0 + padding], [width - padding, height - padding]], pointData);
53-
}
54-
55-
// clamp the map scaling to 2000 so countries can be seen still
56-
projection = projection.scale(Math.min(projection.scale(), 2000));
57-
58-
const pathGenerator = d3.geoPath(projection).pointRadius(4);
59-
60-
// draw countries
61-
svg.append("path")
62-
.datum(worldData)
63-
.attr("d", pathGenerator)
64-
.attr("fill", "#e6e7e8")
65-
.attr("stroke", "white");
66-
67-
// draw points for each hacker location
68-
svg.selectAll("circle")
69-
.data(pointData.features)
70-
.enter()
71-
.append("circle")
72-
.attr("cx", (d) => projection(d.geometry.coordinates)[0])
73-
.attr("cy", (d) => projection(d.geometry.coordinates)[1])
74-
.attr("r", 4)
75-
.attr("fill", "blue")
76-
.on("mouseover", (event, d) => {
77-
tooltip.style("display", "block")
78-
.html(`<b>${escape(d.properties.name || "Anonymous")}</b><br>${escape(d.properties.locationName)}`)
79-
.style("left", (event.pageX + 10) + "px")
80-
.style("top", (event.pageY - 10) + "px");
81-
}).on("mouseout", () => {
82-
tooltip.style("display", "none");
83-
});
84-
});
24+
d3.json(
25+
"https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson",
26+
),
27+
])
28+
.then(([pointData, worldData]) => {
29+
// handle empty or invalid data
30+
if (
31+
!pointData ||
32+
!pointData.features ||
33+
pointData.features.length === 0
34+
) {
35+
text.innerText = "No hackers online yet!";
36+
return;
37+
}
38+
39+
const nameList = pointData.features
40+
.map((f) => escape(f.properties.name))
41+
.filter((name) => name !== null);
42+
43+
// update map text
44+
text.innerText = `${pointData.features.length} hackers online!`;
45+
46+
// get the bounds of locations (min & max coordinates)
47+
const bounds = d3.geoBounds(pointData);
48+
const [[x0, y0], [x1, y1]] = bounds;
49+
50+
const dx = x1 - x0;
51+
const dy = y1 - y0;
52+
53+
// check for degenerate case: all points at same or very close locations
54+
const isDegenerate = (dx === 0 && dy === 0) || isNaN(dx) || isNaN(dy);
55+
56+
let projection;
57+
58+
if (isDegenerate) {
59+
// Use the first point's coordinates as center
60+
const center = pointData.features[0].geometry.coordinates;
61+
projection = d3
62+
.geoNaturalEarth1()
63+
.center([center[0], center[1]])
64+
.scale(2000)
65+
.translate([width / 2, height / 2]);
66+
} else {
67+
const x = (x0 + x1) / 2;
68+
const y = (y0 + y1) / 2;
69+
const padding = 100;
70+
71+
// create a new projection, centered at the middle of the locations
72+
projection = d3
73+
.geoNaturalEarth1()
74+
.center([x, y])
75+
.translate([width / 2, height / 2]);
76+
77+
// if there's more than 1 location, fit the map to show all locations nicely
78+
if (pointData.features.length > 1) {
79+
projection = projection.fitExtent(
80+
[
81+
[padding, padding],
82+
[width - padding, height - padding],
83+
],
84+
pointData,
85+
);
86+
}
87+
}
88+
89+
// clamp the map scaling to 2000 so countries can be seen still
90+
projection = projection.scale(Math.min(projection.scale(), 2000));
91+
92+
const pathGenerator = d3.geoPath(projection).pointRadius(4);
93+
94+
// draw countries
95+
svg
96+
.append("path")
97+
.datum(worldData)
98+
.attr("d", pathGenerator)
99+
.attr("fill", "#e6e7e8")
100+
.attr("stroke", "white");
101+
102+
// draw points for each hacker location
103+
svg
104+
.selectAll("circle")
105+
.data(pointData.features)
106+
.enter()
107+
.append("circle")
108+
.attr("cx", (d) => {
109+
const coords = d.geometry.coordinates;
110+
if (!coords || coords[0] == null || coords[1] == null) return 0;
111+
const p = projection(coords);
112+
if (!p || isNaN(p[0])) return 0;
113+
return p[0];
114+
})
115+
.attr("cy", (d) => {
116+
const coords = d.geometry.coordinates;
117+
if (!coords || coords[0] == null || coords[1] == null) return 0;
118+
const p = projection(coords);
119+
if (!p || isNaN(p[1])) return 0;
120+
return p[1];
121+
})
122+
.attr("r", 4)
123+
.attr("fill", "blue")
124+
.on("mouseover", (event, d) => {
125+
tooltip
126+
.style("display", "block")
127+
.html(
128+
`<b>${escape(d.properties.name || "Anonymous")}</b><br>${escape(d.properties.locationName)}`,
129+
)
130+
.style("left", event.pageX + 10 + "px")
131+
.style("top", event.pageY - 10 + "px");
132+
})
133+
.on("mouseout", () => {
134+
tooltip.style("display", "none");
135+
});
136+
})
137+
.catch((err) => {
138+
console.error("Error loading map data:", err);
139+
text.innerText = "Unable to load map";
140+
});
85141
</script>

0 commit comments

Comments
 (0)