// 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``;
}
}
Warning
Marine occurrence data are substantially less well represented in open biodiversity databases than terrestrial data. This means that species listed as newly appearing or emerging may in reality could have been present and spreading for considerably longer, with earlier records remaining unpublished or scattered across grey literature and personal datasets. Because of these data limitations, the current results should be interpreted with caution.
// 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).