Upload 2 files
Browse files- depth_interactive_map_alpha.R +148 -66
- styles.css +69 -13
depth_interactive_map_alpha.R
CHANGED
|
@@ -1185,23 +1185,38 @@ WEB_SPEC <- list(
|
|
| 1185 |
list(id = "hazards", label = "Hazards", open = TRUE, items = list(
|
| 1186 |
list(kind = "layer", value = "crime-choro",
|
| 1187 |
label = "Criminal incidence by municipality",
|
| 1188 |
-
tooltip =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1189 |
legend = list(type = "ramp", pal = "crime", unit = " /100k",
|
| 1190 |
labels = "qualitative"),
|
| 1191 |
checked = TRUE),
|
| 1192 |
list(kind = "radios", group = "crime"),
|
| 1193 |
list(kind = "layer", value = "migrants-pts",
|
| 1194 |
label = "Deaths during migration",
|
| 1195 |
-
tooltip =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1196 |
legend = list(type = "dot", color = COL_YEL, label = "Death location")),
|
| 1197 |
list(kind = "layer", value = "desap-choro",
|
| 1198 |
label = "Missing and no located persons per 100k",
|
| 1199 |
-
tooltip =
|
| 1200 |
-
|
|
|
|
|
|
|
|
|
|
| 1201 |
legend = list(type = "ramp", pal = "desap", unit = " /100k")),
|
| 1202 |
list(kind = "layer", value = "fosas-choro",
|
| 1203 |
label = "Clandestine graves by municipality",
|
| 1204 |
-
tooltip =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1205 |
legend = list(type = "ramp", pal = "fosas", unit = " pits/bodies",
|
| 1206 |
labels = "qualitative")),
|
| 1207 |
list(kind = "radios", group = "fosas"),
|
|
@@ -1232,7 +1247,11 @@ WEB_SPEC <- list(
|
|
| 1232 |
legend = list(type = "ramp", pal = "pam", unit = " events")),
|
| 1233 |
list(kind = "layer", value = "remesas-choro",
|
| 1234 |
label = "Degree of Migration Intensity MEX-USA (GIIM)",
|
| 1235 |
-
tooltip =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1236 |
legend = list(type = "cats", pal = "giim", filterable = "remesas")),
|
| 1237 |
list(kind = "layer", value = "routes-lines",
|
| 1238 |
label = "Main migration routes through Mexico",
|
|
@@ -1256,7 +1275,11 @@ WEB_SPEC <- list(
|
|
| 1256 |
legend = list(type = "cats", pal = "infra_cats", filterable = "infra")),
|
| 1257 |
list(kind = "layer", value = "patp-choro",
|
| 1258 |
label = "Territorial analysis of poverty",
|
| 1259 |
-
tooltip =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1260 |
legend = list(type = "ramp", pal = "patp", unit = "",
|
| 1261 |
labels = "qualitative"),
|
| 1262 |
# Variant legends shown when the matching categorical chip is active.
|
|
@@ -1327,9 +1350,9 @@ WEB_SPEC <- list(
|
|
| 1327 |
),
|
| 1328 |
charts = list(
|
| 1329 |
list(id = "migrants_treemap_cause", label = "Cause of death",
|
| 1330 |
-
height = "
|
| 1331 |
list(id = "migrants_treemap_demo", label = "Demographics (children / female / male)",
|
| 1332 |
-
height = "
|
| 1333 |
)),
|
| 1334 |
list(title = "Missing persons and not located (RNPDNO)", kind = "stat",
|
| 1335 |
stats = list(list(id = "n_desap", label = "Disappearances & no located", color = COL_RISK))),
|
|
@@ -1387,7 +1410,24 @@ LUCIDE_JS <- tags$head(
|
|
| 1387 |
setTimeout(initViewport, 800);
|
| 1388 |
}, 400);
|
| 1389 |
|
| 1390 |
-
// Tooltip: positioned via JS to escape overflow:hidden containers
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1391 |
document.addEventListener('mouseover', function(e) {
|
| 1392 |
var el = e.target.closest('[data-tooltip]');
|
| 1393 |
if (!el) { hideTooltip(); return; }
|
|
@@ -1398,11 +1438,36 @@ LUCIDE_JS <- tags$head(
|
|
| 1398 |
tip.className = 'depth-tooltip';
|
| 1399 |
document.body.appendChild(tip);
|
| 1400 |
}
|
| 1401 |
-
|
| 1402 |
-
var
|
| 1403 |
-
|
| 1404 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1405 |
tip.style.display = 'block';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1406 |
});
|
| 1407 |
document.addEventListener('mouseout', function(e) {
|
| 1408 |
if (e.target.closest('[data-tooltip]')) hideTooltip();
|
|
@@ -1470,7 +1535,9 @@ LUCIDE_JS <- tags$head(
|
|
| 1470 |
|
| 1471 |
function hideTooltip() {
|
| 1472 |
var tip = document.getElementById('depth-tooltip');
|
| 1473 |
-
if (tip)
|
|
|
|
|
|
|
| 1474 |
}
|
| 1475 |
|
| 1476 |
// -- Guided spotlight tour (Driver.js) --------------------------------------
|
|
@@ -2380,14 +2447,25 @@ acc_layer <- function(value, label, tooltip = NULL, legend = NULL, checked = FAL
|
|
| 2380 |
),
|
| 2381 |
span(class = "acc-toggle-text", label)
|
| 2382 |
),
|
| 2383 |
-
if (!is.null(tooltip))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2384 |
tags$span(class = "acc-info-wrap",
|
| 2385 |
-
`data-tooltip`
|
| 2386 |
-
|
| 2387 |
-
|
| 2388 |
-
|
|
|
|
|
|
|
|
|
|
| 2389 |
HTML('<i data-lucide="info" style="width:12px;height:12px;stroke-width:2.5;color:var(--c-text-mid)"></i>')
|
| 2390 |
)
|
|
|
|
| 2391 |
),
|
| 2392 |
if (!is.null(legend)) legend,
|
| 2393 |
if (!is.null(legend_extras)) legend_extras
|
|
@@ -4626,58 +4704,62 @@ server <- function(input, output, session) {
|
|
| 4626 |
Female = "#7E4A86", # mauve
|
| 4627 |
Male = "#3A4B5C") # slate
|
| 4628 |
|
| 4629 |
-
|
| 4630 |
-
|
|
|
|
|
|
|
| 4631 |
df <- df[!is.na(df$value) & df$value > 0, , drop = FALSE]
|
| 4632 |
if (nrow(df) == 0) return(NULL)
|
| 4633 |
df <- df[order(-df$value), , drop = FALSE]
|
| 4634 |
total <- sum(df$value)
|
| 4635 |
-
|
| 4636 |
-
|
| 4637 |
-
|
| 4638 |
-
|
| 4639 |
-
|
| 4640 |
-
|
| 4641 |
-
if (!is.null(rest) && nrow(rest) > 0) {
|
| 4642 |
-
rest_share <- rest$value / sum(rest$value)
|
| 4643 |
-
rest$xmax <- cumsum(rest_share)
|
| 4644 |
-
rest$xmin <- c(0, head(rest$xmax, -1))
|
| 4645 |
-
rest$ymin <- 0; rest$ymax <- 1 - leader_share
|
| 4646 |
-
rbind(leader, rest)
|
| 4647 |
-
} else {
|
| 4648 |
-
leader$ymin <- 0; leader$ymax <- 1
|
| 4649 |
-
leader
|
| 4650 |
-
}
|
| 4651 |
}
|
| 4652 |
|
| 4653 |
-
#
|
| 4654 |
-
#
|
| 4655 |
-
#
|
| 4656 |
-
#
|
| 4657 |
-
|
| 4658 |
-
|
|
|
|
| 4659 |
if (is.null(laid)) return(NULL)
|
| 4660 |
-
|
| 4661 |
-
laid$
|
| 4662 |
-
laid$cx
|
| 4663 |
-
laid$
|
| 4664 |
-
|
| 4665 |
-
#
|
| 4666 |
-
|
| 4667 |
-
|
| 4668 |
-
|
| 4669 |
-
|
| 4670 |
-
|
| 4671 |
-
laid$
|
|
|
|
|
|
|
|
|
|
| 4672 |
ggplot(laid, aes(xmin = xmin, xmax = xmax,
|
| 4673 |
-
ymin = ymin, ymax = ymax,
|
| 4674 |
-
|
| 4675 |
-
|
| 4676 |
-
|
| 4677 |
-
|
| 4678 |
-
|
| 4679 |
-
|
| 4680 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4681 |
scale_fill_manual(values = pal, na.value = "#334155") +
|
| 4682 |
coord_cartesian(expand = FALSE, xlim = c(0, 1), ylim = c(0, 1)) +
|
| 4683 |
theme_void() +
|
|
@@ -4717,7 +4799,7 @@ server <- function(input, output, session) {
|
|
| 4717 |
value = agg$n_dead,
|
| 4718 |
color = unname(CAUSE_PAL[agg$cause_cat]),
|
| 4719 |
stringsAsFactors = FALSE)
|
| 4720 |
-
|
| 4721 |
}, bg = "transparent", res = 110)
|
| 4722 |
|
| 4723 |
output$migrants_treemap_demo <- renderPlot({
|
|
@@ -4731,7 +4813,7 @@ server <- function(input, output, session) {
|
|
| 4731 |
color = unname(DEMO_PAL),
|
| 4732 |
stringsAsFactors = FALSE
|
| 4733 |
)
|
| 4734 |
-
|
| 4735 |
}, bg = "transparent", res = 110)
|
| 4736 |
|
| 4737 |
# Shared helper: temporal bar chart by year for point event tables
|
|
|
|
| 1185 |
list(id = "hazards", label = "Hazards", open = TRUE, items = list(
|
| 1186 |
list(kind = "layer", value = "crime-choro",
|
| 1187 |
label = "Criminal incidence by municipality",
|
| 1188 |
+
tooltip = list(
|
| 1189 |
+
title = "Criminal incidence",
|
| 1190 |
+
body = "Rate of crimes per 100,000 people in each municipality. Pick a crime type with the chips below the layer to switch the painted column.",
|
| 1191 |
+
meta = "Source: Registro Nacional de Incidencia Delictiva (RNID)"
|
| 1192 |
+
),
|
| 1193 |
legend = list(type = "ramp", pal = "crime", unit = " /100k",
|
| 1194 |
labels = "qualitative"),
|
| 1195 |
checked = TRUE),
|
| 1196 |
list(kind = "radios", group = "crime"),
|
| 1197 |
list(kind = "layer", value = "migrants-pts",
|
| 1198 |
label = "Deaths during migration",
|
| 1199 |
+
tooltip = list(
|
| 1200 |
+
title = "Migrant deaths",
|
| 1201 |
+
body = "Each dot marks a location where migrants died on their journey. The temporal strip and the cards both report SUM(n_dead), not raw incident counts.",
|
| 1202 |
+
meta = "Source: IOM Missing Migrants Project"
|
| 1203 |
+
),
|
| 1204 |
legend = list(type = "dot", color = COL_YEL, label = "Death location")),
|
| 1205 |
list(kind = "layer", value = "desap-choro",
|
| 1206 |
label = "Missing and no located persons per 100k",
|
| 1207 |
+
tooltip = list(
|
| 1208 |
+
title = "Disappearances per 100k",
|
| 1209 |
+
body = "People officially reported as disappeared and not located in each municipality, normalised per 100,000 residents (2010-2024). Colour ramp uses per-muni percentile breaks.",
|
| 1210 |
+
meta = "Source: RNPDNO"
|
| 1211 |
+
),
|
| 1212 |
legend = list(type = "ramp", pal = "desap", unit = " /100k")),
|
| 1213 |
list(kind = "layer", value = "fosas-choro",
|
| 1214 |
label = "Clandestine graves by municipality",
|
| 1215 |
+
tooltip = list(
|
| 1216 |
+
title = "Clandestine graves",
|
| 1217 |
+
body = "Number of clandestine pits and recovered bodies per municipality. Annual data -- use the temporal strip to scope to a year range.",
|
| 1218 |
+
meta = "Source: Plataforma Ciudadana de Fosas (2006-2024)"
|
| 1219 |
+
),
|
| 1220 |
legend = list(type = "ramp", pal = "fosas", unit = " pits/bodies",
|
| 1221 |
labels = "qualitative")),
|
| 1222 |
list(kind = "radios", group = "fosas"),
|
|
|
|
| 1247 |
legend = list(type = "ramp", pal = "pam", unit = " events")),
|
| 1248 |
list(kind = "layer", value = "remesas-choro",
|
| 1249 |
label = "Degree of Migration Intensity MEX-USA (GIIM)",
|
| 1250 |
+
tooltip = list(
|
| 1251 |
+
title = "Migration intensity (GIIM)",
|
| 1252 |
+
body = "Composite index from CONAPO: emigration, return, circular and remittance-receiving households per muni. Click a swatch to hide that GIIM bracket.",
|
| 1253 |
+
meta = "Source: CONAPO Degree of Migration Intensity"
|
| 1254 |
+
),
|
| 1255 |
legend = list(type = "cats", pal = "giim", filterable = "remesas")),
|
| 1256 |
list(kind = "layer", value = "routes-lines",
|
| 1257 |
label = "Main migration routes through Mexico",
|
|
|
|
| 1275 |
legend = list(type = "cats", pal = "infra_cats", filterable = "infra")),
|
| 1276 |
list(kind = "layer", value = "patp-choro",
|
| 1277 |
label = "Territorial analysis of poverty",
|
| 1278 |
+
tooltip = list(
|
| 1279 |
+
title = "PATP - territorial poverty",
|
| 1280 |
+
body = "CONEVAL territorial analysis: poverty share, extreme poverty, economic participation, occupation, remittances, road connectivity, predominant rural problem and crime focus. Pick a chip to switch the painted column.",
|
| 1281 |
+
meta = "Source: CONEVAL PATP 2020 + Poverty Census 2020"
|
| 1282 |
+
),
|
| 1283 |
legend = list(type = "ramp", pal = "patp", unit = "",
|
| 1284 |
labels = "qualitative"),
|
| 1285 |
# Variant legends shown when the matching categorical chip is active.
|
|
|
|
| 1350 |
),
|
| 1351 |
charts = list(
|
| 1352 |
list(id = "migrants_treemap_cause", label = "Cause of death",
|
| 1353 |
+
height = "40px"),
|
| 1354 |
list(id = "migrants_treemap_demo", label = "Demographics (children / female / male)",
|
| 1355 |
+
height = "40px")
|
| 1356 |
)),
|
| 1357 |
list(title = "Missing persons and not located (RNPDNO)", kind = "stat",
|
| 1358 |
stats = list(list(id = "n_desap", label = "Disappearances & no located", color = COL_RISK))),
|
|
|
|
| 1410 |
setTimeout(initViewport, 800);
|
| 1411 |
}, 400);
|
| 1412 |
|
| 1413 |
+
// Tooltip: positioned via JS to escape overflow:hidden containers.
|
| 1414 |
+
// Supports a rich form with header / body / meta when the trigger sets
|
| 1415 |
+
// data-tooltip-title and/or data-tooltip-meta in addition to data-tooltip.
|
| 1416 |
+
// Build the rich tooltip via DOM methods (textContent auto-escapes, no
|
| 1417 |
+
// quoting issues inside the surrounding R-quoted block).
|
| 1418 |
+
function _setRichTooltip(tip, title, body, meta) {
|
| 1419 |
+
while (tip.firstChild) tip.removeChild(tip.firstChild);
|
| 1420 |
+
function add(cls, text) {
|
| 1421 |
+
if (text == null || text === '') return;
|
| 1422 |
+
var d = document.createElement('div');
|
| 1423 |
+
d.className = cls;
|
| 1424 |
+
d.textContent = text;
|
| 1425 |
+
tip.appendChild(d);
|
| 1426 |
+
}
|
| 1427 |
+
add('depth-tooltip-title', title);
|
| 1428 |
+
add('depth-tooltip-body', body);
|
| 1429 |
+
add('depth-tooltip-meta', meta);
|
| 1430 |
+
}
|
| 1431 |
document.addEventListener('mouseover', function(e) {
|
| 1432 |
var el = e.target.closest('[data-tooltip]');
|
| 1433 |
if (!el) { hideTooltip(); return; }
|
|
|
|
| 1438 |
tip.className = 'depth-tooltip';
|
| 1439 |
document.body.appendChild(tip);
|
| 1440 |
}
|
| 1441 |
+
var title = el.dataset.tooltipTitle;
|
| 1442 |
+
var body = el.dataset.tooltip;
|
| 1443 |
+
var meta = el.dataset.tooltipMeta;
|
| 1444 |
+
if (title || meta) {
|
| 1445 |
+
tip.classList.add('is-rich');
|
| 1446 |
+
_setRichTooltip(tip, title, body, meta);
|
| 1447 |
+
} else {
|
| 1448 |
+
tip.classList.remove('is-rich');
|
| 1449 |
+
tip.textContent = body || '';
|
| 1450 |
+
}
|
| 1451 |
tip.style.display = 'block';
|
| 1452 |
+
// Position to the right of the trigger by default; if there's not
|
| 1453 |
+
// enough room on the right (e.g. element in a left-pinned panel
|
| 1454 |
+
// whose right edge sits near the viewport edge), flip to the left.
|
| 1455 |
+
var r = el.getBoundingClientRect();
|
| 1456 |
+
var tw = tip.offsetWidth;
|
| 1457 |
+
var th = tip.offsetHeight;
|
| 1458 |
+
var vw = window.innerWidth;
|
| 1459 |
+
var pad = 8;
|
| 1460 |
+
var left = r.right + pad;
|
| 1461 |
+
if (left + tw > vw - pad) left = Math.max(pad, r.left - tw - pad);
|
| 1462 |
+
var top = r.top + r.height / 2;
|
| 1463 |
+
// Clamp vertically too so the (translateY -50%) tooltip stays onscreen.
|
| 1464 |
+
var half = th / 2;
|
| 1465 |
+
if (top - half < pad) top = pad + half;
|
| 1466 |
+
if (top + half > window.innerHeight - pad)
|
| 1467 |
+
top = window.innerHeight - pad - half;
|
| 1468 |
+
tip.style.left = left + 'px';
|
| 1469 |
+
tip.style.top = top + 'px';
|
| 1470 |
+
requestAnimationFrame(function() { tip.classList.add('is-visible'); });
|
| 1471 |
});
|
| 1472 |
document.addEventListener('mouseout', function(e) {
|
| 1473 |
if (e.target.closest('[data-tooltip]')) hideTooltip();
|
|
|
|
| 1535 |
|
| 1536 |
function hideTooltip() {
|
| 1537 |
var tip = document.getElementById('depth-tooltip');
|
| 1538 |
+
if (!tip) return;
|
| 1539 |
+
tip.classList.remove('is-visible');
|
| 1540 |
+
tip.style.display = 'none';
|
| 1541 |
}
|
| 1542 |
|
| 1543 |
// -- Guided spotlight tour (Driver.js) --------------------------------------
|
|
|
|
| 2447 |
),
|
| 2448 |
span(class = "acc-toggle-text", label)
|
| 2449 |
),
|
| 2450 |
+
if (!is.null(tooltip)) {
|
| 2451 |
+
# `tooltip` may be a plain string (legacy form) or a list with
|
| 2452 |
+
# title / body / meta (rich form). Rich-form keys are passed through
|
| 2453 |
+
# as data-* attributes; the JS reads them and renders the structured
|
| 2454 |
+
# layout (.depth-tooltip.is-rich).
|
| 2455 |
+
tip_body <- if (is.list(tooltip)) tooltip$body else tooltip
|
| 2456 |
+
tip_title <- if (is.list(tooltip)) tooltip$title else NULL
|
| 2457 |
+
tip_meta <- if (is.list(tooltip)) tooltip$meta else NULL
|
| 2458 |
tags$span(class = "acc-info-wrap",
|
| 2459 |
+
`data-tooltip` = tip_body,
|
| 2460 |
+
`data-tooltip-title` = tip_title,
|
| 2461 |
+
`data-tooltip-meta` = tip_meta,
|
| 2462 |
+
role = "button",
|
| 2463 |
+
tabindex = "0",
|
| 2464 |
+
`aria-label` = paste0("Info: ",
|
| 2465 |
+
tip_title %||% substr(tip_body, 1, 60)),
|
| 2466 |
HTML('<i data-lucide="info" style="width:12px;height:12px;stroke-width:2.5;color:var(--c-text-mid)"></i>')
|
| 2467 |
)
|
| 2468 |
+
}
|
| 2469 |
),
|
| 2470 |
if (!is.null(legend)) legend,
|
| 2471 |
if (!is.null(legend_extras)) legend_extras
|
|
|
|
| 4704 |
Female = "#7E4A86", # mauve
|
| 4705 |
Male = "#3A4B5C") # slate
|
| 4706 |
|
| 4707 |
+
# Stacked-bar layout: one horizontal row of segments, widths proportional
|
| 4708 |
+
# to value. Returns the same shape as the treemap layout did (xmin/xmax/
|
| 4709 |
+
# ymin/ymax/value) so downstream code stays small.
|
| 4710 |
+
stacked_bar_layout <- function(df) {
|
| 4711 |
df <- df[!is.na(df$value) & df$value > 0, , drop = FALSE]
|
| 4712 |
if (nrow(df) == 0) return(NULL)
|
| 4713 |
df <- df[order(-df$value), , drop = FALSE]
|
| 4714 |
total <- sum(df$value)
|
| 4715 |
+
df$pct <- df$value / total
|
| 4716 |
+
df$xmax <- cumsum(df$pct)
|
| 4717 |
+
df$xmin <- c(0, head(df$xmax, -1))
|
| 4718 |
+
df$ymin <- 0
|
| 4719 |
+
df$ymax <- 1
|
| 4720 |
+
df
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4721 |
}
|
| 4722 |
|
| 4723 |
+
# Single-row stacked bar: each segment a category, width = its share. Labels
|
| 4724 |
+
# sit centred in the segment; percentages drop to a second line. Narrow
|
| 4725 |
+
# segments fall back to just the percentage. geom_text size is in mm:
|
| 4726 |
+
# 2.4mm ~ 6.8pt label, 2.0mm ~ 5.7pt percentage -- tuned for the ~60px tall
|
| 4727 |
+
# card slot the migrants card gives each chart.
|
| 4728 |
+
stacked_bar_plot <- function(df, pal) {
|
| 4729 |
+
laid <- stacked_bar_layout(df)
|
| 4730 |
if (is.null(laid)) return(NULL)
|
| 4731 |
+
laid$pct_int <- round(100 * laid$pct)
|
| 4732 |
+
laid$pct_lbl <- paste0(laid$pct_int, "%")
|
| 4733 |
+
laid$cx <- (laid$xmin + laid$xmax) / 2
|
| 4734 |
+
laid$w <- laid$xmax - laid$xmin
|
| 4735 |
+
# Truncate the label to roughly what each segment can fit. Narrower than
|
| 4736 |
+
# ~6% of the bar (~16 px) gets just the percentage; below that, nothing.
|
| 4737 |
+
max_chars <- pmax(0L, as.integer(laid$w * 28))
|
| 4738 |
+
laid$short <- ifelse(nchar(laid$label) > max_chars,
|
| 4739 |
+
paste0(substr(laid$label, 1L, pmax(1L, max_chars - 1L)), "."),
|
| 4740 |
+
laid$label)
|
| 4741 |
+
laid$show_label <- laid$w >= 0.10 # ~ 33 px segments and up
|
| 4742 |
+
laid$show_pct <- laid$w >= 0.06
|
| 4743 |
+
label_df <- laid[laid$show_label, , drop = FALSE]
|
| 4744 |
+
pct_df <- laid[laid$show_pct, , drop = FALSE]
|
| 4745 |
+
pct_only <- !pct_df$show_label
|
| 4746 |
ggplot(laid, aes(xmin = xmin, xmax = xmax,
|
| 4747 |
+
ymin = ymin, ymax = ymax, fill = label)) +
|
| 4748 |
+
# Soft 1px separator: white at low alpha reads on every palette colour
|
| 4749 |
+
# without dominating either segment (#ffffff99 = 60% opacity in R hex).
|
| 4750 |
+
geom_rect(color = "#ffffff99", linewidth = 0.4) +
|
| 4751 |
+
# Label centred high; percentage drops below it.
|
| 4752 |
+
{ if (nrow(label_df) > 0)
|
| 4753 |
+
geom_text(data = label_df,
|
| 4754 |
+
aes(x = cx, y = 0.62, label = short),
|
| 4755 |
+
size = 2.4, color = "white", family = PLOT_FONT,
|
| 4756 |
+
fontface = "bold") } +
|
| 4757 |
+
{ if (nrow(pct_df) > 0)
|
| 4758 |
+
geom_text(data = pct_df,
|
| 4759 |
+
aes(x = cx, y = ifelse(pct_only, 0.5, 0.32),
|
| 4760 |
+
label = pct_lbl),
|
| 4761 |
+
size = 2.0, color = "white", family = PLOT_FONT,
|
| 4762 |
+
alpha = 0.9) } +
|
| 4763 |
scale_fill_manual(values = pal, na.value = "#334155") +
|
| 4764 |
coord_cartesian(expand = FALSE, xlim = c(0, 1), ylim = c(0, 1)) +
|
| 4765 |
theme_void() +
|
|
|
|
| 4799 |
value = agg$n_dead,
|
| 4800 |
color = unname(CAUSE_PAL[agg$cause_cat]),
|
| 4801 |
stringsAsFactors = FALSE)
|
| 4802 |
+
stacked_bar_plot(summary_df, CAUSE_PAL)
|
| 4803 |
}, bg = "transparent", res = 110)
|
| 4804 |
|
| 4805 |
output$migrants_treemap_demo <- renderPlot({
|
|
|
|
| 4813 |
color = unname(DEMO_PAL),
|
| 4814 |
stringsAsFactors = FALSE
|
| 4815 |
)
|
| 4816 |
+
stacked_bar_plot(summary_df, DEMO_PAL)
|
| 4817 |
}, bg = "transparent", res = 110)
|
| 4818 |
|
| 4819 |
# Shared helper: temporal bar chart by year for point event tables
|
styles.css
CHANGED
|
@@ -878,11 +878,14 @@
|
|
| 878 |
}
|
| 879 |
|
| 880 |
.acc-toggle-text {
|
| 881 |
-
|
|
|
|
|
|
|
|
|
|
| 882 |
color: var(--c-text-mid);
|
| 883 |
font-family: var(--font-ui);
|
| 884 |
transition: color var(--t-fast) var(--ease-out);
|
| 885 |
-
line-height: 1.
|
| 886 |
overflow: hidden;
|
| 887 |
text-overflow: ellipsis;
|
| 888 |
}
|
|
@@ -921,8 +924,13 @@
|
|
| 921 |
transition: opacity var(--t-fast) var(--ease-out);
|
| 922 |
}
|
| 923 |
|
| 924 |
-
.acc-info-wrap:hover
|
| 925 |
-
.acc-info-wrap:focus-visible {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 926 |
|
| 927 |
/* -- Layer legends ---------------------------------------------------------- */
|
| 928 |
.acc-legend {
|
|
@@ -1201,25 +1209,72 @@
|
|
| 1201 |
/* -- JS-positioned tooltip (inverted glass) -------------------------------- */
|
| 1202 |
.depth-tooltip {
|
| 1203 |
position: fixed;
|
| 1204 |
-
background: oklch(15% 0.012 250 / 0.
|
| 1205 |
-
backdrop-filter: blur(
|
| 1206 |
-
-webkit-backdrop-filter: blur(
|
| 1207 |
color: oklch(98% 0.003 250);
|
| 1208 |
-
font-size:
|
| 1209 |
font-family: var(--font-ui);
|
| 1210 |
font-weight: 400;
|
| 1211 |
-
line-height: 1.
|
| 1212 |
-
|
|
|
|
|
|
|
| 1213 |
border-radius: var(--radius-md);
|
| 1214 |
-
border: 1px solid oklch(100% 0 0 / 0.
|
| 1215 |
-
max-width:
|
| 1216 |
z-index: 99999;
|
| 1217 |
pointer-events: none;
|
| 1218 |
transform: translateY(-50%);
|
| 1219 |
box-shadow: var(--depth-3);
|
| 1220 |
word-break: break-word;
|
| 1221 |
display: none;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1222 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1223 |
|
| 1224 |
/* -- Hide ALL MapLibre Draw native buttons -------------------------------- */
|
| 1225 |
/* Custom Lucide-icon buttons in the nav-bar proxy-click these natives via */
|
|
@@ -1980,8 +2035,9 @@ button.mapbox-gl-draw_uncombine { display: none !important; }
|
|
| 1980 |
border-radius: var(--radius-md) !important;
|
| 1981 |
box-shadow: var(--depth-3) !important;
|
| 1982 |
font-family: var(--font-ui) !important;
|
| 1983 |
-
font-size:
|
| 1984 |
line-height: 1.55 !important;
|
|
|
|
| 1985 |
font-variant-numeric: tabular-nums;
|
| 1986 |
padding: 10px 14px !important;
|
| 1987 |
max-width: 280px;
|
|
|
|
| 878 |
}
|
| 879 |
|
| 880 |
.acc-toggle-text {
|
| 881 |
+
/* 12.5 px is the smallest size that stays comfortably legible at glass-card
|
| 882 |
+
contrast (c-text-mid on c-glass-1 is ~7.4:1, but the eye reads thinner
|
| 883 |
+
strokes slower than that ratio suggests). */
|
| 884 |
+
font-size: 12.5px;
|
| 885 |
color: var(--c-text-mid);
|
| 886 |
font-family: var(--font-ui);
|
| 887 |
transition: color var(--t-fast) var(--ease-out);
|
| 888 |
+
line-height: 1.45;
|
| 889 |
overflow: hidden;
|
| 890 |
text-overflow: ellipsis;
|
| 891 |
}
|
|
|
|
| 924 |
transition: opacity var(--t-fast) var(--ease-out);
|
| 925 |
}
|
| 926 |
|
| 927 |
+
.acc-info-wrap:hover { opacity: 1; }
|
| 928 |
+
.acc-info-wrap:focus-visible {
|
| 929 |
+
opacity: 1;
|
| 930 |
+
outline: 2px solid var(--c-focus);
|
| 931 |
+
outline-offset: 2px;
|
| 932 |
+
border-radius: var(--radius-sm);
|
| 933 |
+
}
|
| 934 |
|
| 935 |
/* -- Layer legends ---------------------------------------------------------- */
|
| 936 |
.acc-legend {
|
|
|
|
| 1209 |
/* -- JS-positioned tooltip (inverted glass) -------------------------------- */
|
| 1210 |
.depth-tooltip {
|
| 1211 |
position: fixed;
|
| 1212 |
+
background: oklch(15% 0.012 250 / 0.94);
|
| 1213 |
+
backdrop-filter: blur(14px) saturate(150%);
|
| 1214 |
+
-webkit-backdrop-filter: blur(14px) saturate(150%);
|
| 1215 |
color: oklch(98% 0.003 250);
|
| 1216 |
+
font-size: 12.5px;
|
| 1217 |
font-family: var(--font-ui);
|
| 1218 |
font-weight: 400;
|
| 1219 |
+
line-height: 1.55;
|
| 1220 |
+
letter-spacing: 0.005em;
|
| 1221 |
+
font-variant-numeric: tabular-nums;
|
| 1222 |
+
padding: 9px 13px;
|
| 1223 |
border-radius: var(--radius-md);
|
| 1224 |
+
border: 1px solid oklch(100% 0 0 / 0.10);
|
| 1225 |
+
max-width: 280px;
|
| 1226 |
z-index: 99999;
|
| 1227 |
pointer-events: none;
|
| 1228 |
transform: translateY(-50%);
|
| 1229 |
box-shadow: var(--depth-3);
|
| 1230 |
word-break: break-word;
|
| 1231 |
display: none;
|
| 1232 |
+
/* Fade in via transition rather than springing the size; the JS pops the
|
| 1233 |
+
element by toggling display, then the next frame the opacity lifts. */
|
| 1234 |
+
opacity: 0;
|
| 1235 |
+
transition: opacity var(--t-fast) var(--ease-out);
|
| 1236 |
+
}
|
| 1237 |
+
.depth-tooltip.is-visible { opacity: 1; }
|
| 1238 |
+
|
| 1239 |
+
/* ---- Rich tooltip variant -------------------------------------------------
|
| 1240 |
+
When a trigger has data-tooltip-title and/or data-tooltip-meta in addition
|
| 1241 |
+
to data-tooltip, JS adds .is-rich and emits structured slots. The layout
|
| 1242 |
+
reads as a tiny card: tinted uppercase title, paragraph body, dim meta
|
| 1243 |
+
footer separated by a hairline divider. */
|
| 1244 |
+
.depth-tooltip.is-rich {
|
| 1245 |
+
padding: 0;
|
| 1246 |
+
max-width: 320px;
|
| 1247 |
+
}
|
| 1248 |
+
.depth-tooltip-title {
|
| 1249 |
+
padding: 9px 14px 6px;
|
| 1250 |
+
font-family: var(--font-ui);
|
| 1251 |
+
font-size: 10.5px;
|
| 1252 |
+
font-weight: 600;
|
| 1253 |
+
letter-spacing: 0.08em;
|
| 1254 |
+
text-transform: uppercase;
|
| 1255 |
+
color: oklch(82% 0.07 218); /* soft teal-on-dark; readable, low chroma */
|
| 1256 |
+
border-bottom: 1px solid oklch(100% 0 0 / 0.08);
|
| 1257 |
+
}
|
| 1258 |
+
.depth-tooltip-body {
|
| 1259 |
+
padding: 8px 14px;
|
| 1260 |
+
font-size: 12.5px;
|
| 1261 |
+
line-height: 1.6;
|
| 1262 |
+
color: oklch(95% 0.003 250);
|
| 1263 |
+
}
|
| 1264 |
+
.depth-tooltip-meta {
|
| 1265 |
+
padding: 6px 14px 9px;
|
| 1266 |
+
border-top: 1px solid oklch(100% 0 0 / 0.06);
|
| 1267 |
+
font-family: var(--font-ui);
|
| 1268 |
+
font-size: 10.5px;
|
| 1269 |
+
letter-spacing: 0.04em;
|
| 1270 |
+
color: oklch(70% 0 0);
|
| 1271 |
+
font-variant-numeric: tabular-nums;
|
| 1272 |
}
|
| 1273 |
+
/* When only title+body or only body+meta are provided, suppress the empty
|
| 1274 |
+
border that would otherwise hang under the missing slot. */
|
| 1275 |
+
.depth-tooltip-title:last-child,
|
| 1276 |
+
.depth-tooltip-body:last-child { border-bottom: 0; padding-bottom: 9px; }
|
| 1277 |
+
.depth-tooltip-meta:first-child { border-top: 0; padding-top: 9px; }
|
| 1278 |
|
| 1279 |
/* -- Hide ALL MapLibre Draw native buttons -------------------------------- */
|
| 1280 |
/* Custom Lucide-icon buttons in the nav-bar proxy-click these natives via */
|
|
|
|
| 2035 |
border-radius: var(--radius-md) !important;
|
| 2036 |
box-shadow: var(--depth-3) !important;
|
| 2037 |
font-family: var(--font-ui) !important;
|
| 2038 |
+
font-size: 12.5px !important;
|
| 2039 |
line-height: 1.55 !important;
|
| 2040 |
+
letter-spacing: 0.005em;
|
| 2041 |
font-variant-numeric: tabular-nums;
|
| 2042 |
padding: 10px 14px !important;
|
| 2043 |
max-width: 280px;
|