// Merge all species categories into a common schema used by this page.
species_data = [
...emerging_species_data.map(function(d) {
return {
species_key: d["specieskey"] || d["species_key"] || "",
species_name: d["species_name"] || "",
lme_id: d["lme_id"] || "",
lme_name: d["lme_name"] || "",
plot_prefix: "",
first_year: null,
reappearance_year: null,
years_without_occurrences: null,
weighted_em_status: d["weighted_em_status"] || "",
model_used: d["model"] || ""
};
}),
...appearing_species_data.map(function(d) {
return {
species_key: d["specieskey"] || d["species_key"] || "",
species_name: d["species"] || "",
lme_id: d["lme_id"] || "",
lme_name: d["lme_name"] || "",
plot_prefix: "appearing_species_plots_ggplot2",
first_year: +d["first_year"],
reappearance_year: null,
years_without_occurrences: null,
weighted_em_status: null,
model_used: null
};
}),
...reappearing_species_data.map(function(d) {
return {
species_key: d["specieskey"] || d["species_key"] || "",
species_name: d["species"] || "",
lme_id: d["lme_id"] || "",
lme_name: d["lme_name"] || "",
plot_prefix: "reappearing_species_plots_ggplot2",
first_year: null,
reappearance_year: +d["reappearance_year"],
years_without_occurrences: +d["years_without_occurrences"],
weighted_em_status: null,
model_used: null
};
})
]// The data row for the currently selected species + LME.
// Falls back to filtered_rows[0] so a row is always available if data exists.
current_row = {
const rows = filtered_rows;
if (rows.length === 0) return null;
if (selected_lme_id !== null) {
const match = rows.find(d => +d.lme_id === selected_lme_id);
if (match) return match;
}
return rows[0];
}// Build and render a descriptive sentence for the validated species and selected LME
{
const row = current_row;
if (row && row.species_key) {
const speciesLink = `<a href="https://www.gbif.org/species/${row.species_key}">${validated_species}</a>`;
const lmeName = row.lme_name || "";
let htmlStr;
if (row.plot_prefix === "appearing_species_plots_ggplot2") {
htmlStr = `The species ${speciesLink} in the <strong>${lmeName}</strong> is appearing in <strong>${row.first_year}</strong>.`;
} else if (row.plot_prefix === "reappearing_species_plots_ggplot2") {
htmlStr = `The species ${speciesLink} in the <strong>${lmeName}</strong> is reappearing in <strong>${row.reappearance_year}</strong> after <strong>${row.years_without_occurrences}</strong> years.`;
} else {
const score = row.weighted_em_status;
const modelName = row.model_used;
htmlStr = `The species ${speciesLink} in the <strong>${lmeName}</strong> has a total emerging score of <strong>${score}</strong>. Model used: <strong>${modelName || "unknown"}</strong>.`;
}
const p = document.createElement("p");
p.style.cssText = "text-align:center;font-size:1rem;";
p.innerHTML = htmlStr;
yield p;
} else {
yield html``;
}
}// Show inline plots for the selected LME and species
{
const row = current_row;
if (row) {
const container = document.createElement("div");
const title = document.createElement("h4");
title.textContent = `${validated_species} – ${row.lme_name}`;
title.style.marginTop = "12px";
container.appendChild(title);
const plotDiv1 = document.createElement("div");
plotDiv1.style.cssText = "width:100%;height:320px;";
container.appendChild(plotDiv1);
const plotDiv2 = document.createElement("div");
plotDiv2.style.cssText = "width:100%;height:320px;margin-top:8px;";
container.appendChild(plotDiv2);
renderPlotlyChart(plotDiv1, plotJsonUrl(row.lme_name, row.species_key, 1, row.plot_prefix),
"No occurrences plot available.");
renderPlotlyChart(plotDiv2, plotJsonUrl(row.lme_name, row.species_key, 2, row.plot_prefix),
"No grid-cells plot available.");
yield container;
} else if (filtered_lme_ids.length > 0) {
yield html`<p style="color:#999;padding:20px;text-align:center;">Click on a highlighted region on the map to view plots.</p>`;
} else {
yield html`<p style="color:#999;padding:20px;text-align:center;">No LME data available for this species.</p>`;
}
}// Build the JSON URL for a given plot.
function plotJsonUrl(lme_name, species_key, index, plot_prefix = "") {
const baseName = plot_prefix
? `${plot_prefix}_lme_${lme_name}_species_${species_key}`
: `lme_${lme_name}_species_${species_key}`;
const filename = `${baseName}_${index}.json`;
return PLOTS_JSON_BASE + encodeURIComponent(filename);
}// Render a single Plotly chart into a container div.
async function renderPlotlyChart(container, jsonUrl, fallbackText) {
try {
const resp = await fetch(jsonUrl);
if (!resp.ok) throw new Error("not found");
const plotData = await resp.json();
await Plotly.newPlot(container, plotData.data || [], plotData.layout || {}, {
responsive: true,
displayModeBar: false
});
} catch (e) {
container.innerHTML = `<p style="color:#999;text-align:center;">${fallbackText}</p>`;
}
}// Reactive cell: rebuild LME polygons whenever the selected species changes
update_map = {
lme_layer.clearLayers();
lme_geojson.features.forEach(feature => {
const lme_id = feature.properties.lme_id;
const lme_name = feature.properties.lme_name;
// Records for this LME (all species present here)
const lme_records = species_data.filter(d => +d.lme_id === lme_id);
const all_species = [...new Set(lme_records.map(d => d.species_name))];
const is_filtered = filtered_lme_ids.includes(lme_id);
const style = {
fillColor: is_filtered ? "#3388ff" : "#cccccc",
weight: 2,
opacity: 1,
color: is_filtered ? "#0066cc" : "#999999",
fillOpacity: is_filtered ? 0.6 : 0.3
};
const layer = L.geoJSON(feature, {
style,
onEachFeature: (feat, lyr) => {
// Tooltip: show the first 5 species present in this LME (with count)
const TOOLTIP_MAX = 5;
const shown = all_species.slice(0, TOOLTIP_MAX).join(", ");
const extra = all_species.length > TOOLTIP_MAX
? ` … (+${all_species.length - TOOLTIP_MAX} more)`
: "";
const tooltip_text = all_species.length > 0
? `${lme_name}: ${shown}${extra} (${all_species.length} species)`
: `${lme_name}: No species data`;
lyr.bindTooltip(tooltip_text, { permanent: false, direction: "center" });
// Click: select LME to show plots inline
if (is_filtered) {
lyr.on("click", () => {
mutable selected_lme_id = lme_id;
});
lyr.on("mouseover", () => lyr.setStyle({ fillOpacity: 0.8 }));
lyr.on("mouseout", () => lyr.setStyle({ fillOpacity: 0.6 }));
}
}
});
layer.addTo(lme_layer);
});
// Zoom to highlighted regions when any are selected
if (filtered_lme_ids.length > 0) {
const bounds = lme_layer.getBounds();
if (bounds.isValid()) map.fitBounds(bounds, { padding: [50, 50] });
}
}// Reactive cell: draw a blue bounding box around the selected LME
highlight_selected = {
highlight_layer.clearLayers();
if (selected_lme_id !== null) {
const feature = lme_geojson.features.find(f => f.properties.lme_id === selected_lme_id);
if (feature) {
L.geoJSON(feature, {
style: {
color: "#0066cc",
weight: 4,
opacity: 1,
fillOpacity: 0,
dashArray: null
},
interactive: false
}).addTo(highlight_layer);
}
}
}Funding: This dashboard is being developed in the framework of the GuardIAS project. GuardIAS receives funding from the European Union’s Horizon Europe Research and Innovation Programme (ID No 101181413).