Skip to content

Commit 25d467b

Browse files
committed
Fixed city views
1 parent 938a6d5 commit 25d467b

4 files changed

Lines changed: 89 additions & 21 deletions

File tree

app.js

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,27 +1025,34 @@ function showLandingView() {
10251025
async function showCityView(slug) {
10261026
const city = CITIES_BY_SLUG.get(slug);
10271027
currentCity = city;
1028-
10291028
document.getElementById("view-landing").style.display = "none";
10301029
document.getElementById("view-city").style.display = "";
10311030
document.getElementById("view-city").classList.add("active");
10321031
document.getElementById("view-stats").style.display = "none";
10331032
document.getElementById("back-btn").style.display = "inline-block";
10341033
document.getElementById("header-sub").textContent = city.name;
1035-
10361034
if (window.innerWidth > 1024) {
10371035
document.getElementById("view-city").style.gridTemplateColumns = `var(--sidebar-w) 1fr 8px 1fr`;
10381036
}
1037+
1038+
// ── Reset state and tear down the previous city IMMEDIATELY (before any await).
10391039
features = []; mapFeatures = []; cartogramFeatures = null; viewMode = "map";
10401040
selectedId = null; hoveredId = null;
1041+
1042+
if (cityLeaflet) {
1043+
cityLeaflet.remove();
1044+
cityLeaflet = null;
1045+
}
1046+
if (canvas && ctx) {
1047+
ctx.clearRect(0, 0, canvas.width, canvas.height);
1048+
}
1049+
10411050
document.getElementById("view-city").classList.remove("side-closed");
10421051
citySideToggle.textContent = "‹";
1043-
// Reset filter
10441052
cdiMinSlider.value = -1; cdiMaxSlider.value = 1; updateRangeUI();
10451053
updateInfoBox(null);
10461054
updateCityStats();
10471055
syncViewToggleUI();
1048-
10491056
document.querySelectorAll(".mobile-tab").forEach((b) => b.classList.remove("active"));
10501057
document.querySelector('.mobile-tab[data-panel="map"]')?.classList.add("active");
10511058
if (window.innerWidth <= 1024) {
@@ -1054,38 +1061,81 @@ async function showCityView(slug) {
10541061
document.getElementById("city-sidebar").style.display = "none";
10551062
}
10561063

1064+
document.getElementById("info-box").innerHTML =
1065+
`<p style="color:var(--muted);font-size:.8rem">Loading ${city.name}…</p>`;
1066+
1067+
// ── Yield once so the cleared state paints, then build the empty Leaflet map
1068+
// centered on the city's known coordinates BEFORE the fetch starts.
1069+
await new Promise((r) => requestAnimationFrame(r));
1070+
initCityMap();
1071+
cityLeaflet.invalidateSize({ animate: false });
1072+
if (city.center && Number.isFinite(city.zoom)) {
1073+
cityLeaflet.setView(city.center, city.zoom, { animate: false });
1074+
}
1075+
1076+
// Show the loading overlay over the (now-centered, empty) map.
1077+
showMapLoading(true);
1078+
1079+
const loadStartedFor = slug;
10571080
const loaded = await loadCityFeatures(slug);
1081+
1082+
// Bail out if the user clicked another city while this one was loading.
1083+
if (currentCity?.slug !== loadStartedFor) {
1084+
showMapLoading(false);
1085+
return;
1086+
}
1087+
10581088
if (!loaded) {
1089+
showMapLoading(false);
10591090
document.getElementById("info-box").innerHTML =
10601091
`<p style="color:#b03a2e">Could not load data for ${city.name}.</p>`;
10611092
return;
10621093
}
1094+
// Data is here — hide the spinner now, even before rendering kicks in.
1095+
// For very large cities (Paris FUA), drawCanvas() can take seconds; we don't
1096+
// want the spinner to obscure the map while hexes are progressively painting.
1097+
showMapLoading(false);
10631098

1064-
await new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r)));
1065-
initCityMap();
10661099
if (window.innerWidth <= 1024) {
10671100
document.getElementById("map-panel").style.display = "block";
10681101
await new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r)));
1102+
cityLeaflet.invalidateSize({ animate: false });
10691103
}
1070-
cityLeaflet.invalidateSize({ animate: false });
1104+
10711105
mapFeatures = loaded;
10721106
features = mapFeatures;
10731107

10741108
// Prefetch the cartogram in the background so the toggle is instant.
1075-
// Don't await — the geographic view should render immediately.
10761109
loadCartogramFeatures(slug).then((cg) => {
1077-
if (currentCity?.slug !== slug) return; // user moved on
1078-
cartogramFeatures = cg; // array on success, false if missing/failed
1110+
if (currentCity?.slug !== slug) return;
1111+
cartogramFeatures = cg;
10791112
syncViewToggleUI();
10801113
});
10811114

1115+
// Now that data is here, refit precisely to the hex bounds.
10821116
const lngs = features.flatMap((f) => f.geometry.coordinates.flatMap((r) => r.map((p) => p[0])));
10831117
const lats = features.flatMap((f) => f.geometry.coordinates.flatMap((r) => r.map((p) => p[1])));
10841118
cityLeaflet.fitBounds([[Math.min(...lats), Math.min(...lngs)], [Math.max(...lats), Math.max(...lngs)]],
10851119
{ padding: [10, 10], animate: false });
1086-
10871120
resizeCanvas(); drawCanvas(); buildScatter(); updateCityStats();
10881121
syncViewToggleUI();
1122+
updateInfoBox(null);
1123+
1124+
}
1125+
1126+
function showMapLoading(on) {
1127+
let el = document.getElementById("map-loading");
1128+
if (on) {
1129+
if (!el) {
1130+
el = document.createElement("div");
1131+
el.id = "map-loading";
1132+
el.innerHTML = `<div class="map-loading-spinner"></div><span>${t("loading") || "Loading…"}</span>`;
1133+
document.getElementById("map-panel").appendChild(el);
1134+
}
1135+
el.style.display = "flex";
1136+
} else if (el) {
1137+
el.style.display = "none";
1138+
}
10891139
}
10901140

10911141
// ─────────────────────────────────────────────────────────────────────────────

data/cities.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ const CITIES_NAMES = [
1111
"Munich (city)",
1212
"Málaga",
1313
"Nantes",
14-
"New_york",
15-
"Paris (fua)",
14+
"New York",
15+
"Paris (FUA)",
1616
"Paris (city)",
1717
"Porto",
18+
"Rome (Metro D scenario)",
1819
"Rome",
19-
"Rome (metro d scenario)",
2020
"Seattle",
2121
"Stockholm",
2222
"Valencia",

data/index.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@
100100
},
101101
{
102102
"slug": "new_york",
103-
"name": "New_york",
103+
"name": "New York",
104104
"center": [
105105
40.663261,
106106
-73.938666
@@ -109,7 +109,7 @@
109109
},
110110
{
111111
"slug": "paris (FUA)",
112-
"name": "Paris (fua)",
112+
"name": "Paris (FUA)",
113113
"center": [
114114
48.842644,
115115
2.314062
@@ -135,17 +135,17 @@
135135
"zoom": 10
136136
},
137137
{
138-
"slug": "rome",
139-
"name": "Rome",
138+
"slug": "rome (metro D scenario)",
139+
"name": "Rome (Metro D scenario)",
140140
"center": [
141141
41.885773,
142142
12.460808
143143
],
144144
"zoom": 10
145145
},
146146
{
147-
"slug": "rome (metro D scenario)",
148-
"name": "Rome (metro d scenario)",
147+
"slug": "rome",
148+
"name": "Rome",
149149
"center": [
150150
41.885773,
151151
12.460808

style.css

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,25 @@ header h1 span { color: var(--accent); }
563563
}
564564
.view-toggle-btn[data-mode="map"] .when-cartogram { display: none; }
565565
.view-toggle-btn[data-mode="cartogram"] .when-map { display: none; }
566-
566+
#map-loading {
567+
position: absolute; inset: 0; z-index: 20;
568+
display: flex; flex-direction: column; gap: 12px;
569+
align-items: center; justify-content: center;
570+
background: rgba(255, 255, 255, .55);
571+
backdrop-filter: blur(2px);
572+
pointer-events: none;
573+
font-size: .8rem; color: var(--muted); letter-spacing: .04em;
574+
}
575+
.map-loading-spinner {
576+
width: 32px; height: 32px;
577+
border: 3px solid rgba(0, 0, 0, .12);
578+
border-top-color: var(--accent);
579+
border-radius: 50%;
580+
animation: map-spin .8s linear infinite;
581+
}
582+
@keyframes map-spin {
583+
to { transform: rotate(360deg); }
584+
}
567585
.view-mode-badge {
568586
font-size: .58rem; letter-spacing: .08em; text-transform: uppercase;
569587
background: var(--accent-soft); color: var(--accent);

0 commit comments

Comments
 (0)