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