lme_geojson = d3.json("data/lme_polygons.geojson")
// Load species data from emtrends repository
appearing_species_data = d3.csv("https://raw.githubusercontent.com/guardias-eu/emtrends/main/data/output/appearing_species.csv")
reappearing_species_data = d3.csv("https://raw.githubusercontent.com/guardias-eu/emtrends/main/data/output/reappearing_species.csv")
emerging_species_data = d3.csv("https://raw.githubusercontent.com/guardias-eu/emtrends/main/data/output/emerging_trends_ranking_list.csv")// 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>`;
}
}// Modal overlay for species plots (shared by appearing and reappearing tables)
modal_overlay = {
const overlay = document.createElement("div");
overlay.id = "species-plot-modal";
overlay.style.cssText = `
display:none; position:fixed; top:0; left:0; width:100%; height:100%;
background:rgba(0,0,0,0.7); z-index:10000; justify-content:center;
align-items:center; overflow:auto;
`;
overlay.addEventListener("click", e => {
if (e.target === overlay) overlay.style.display = "none";
});
const box = document.createElement("div");
box.id = "species-plot-modal-box";
box.style.cssText = `
background:#222; border-radius:8px; padding:24px; max-width:620px;
width:95%; max-height:90vh; overflow:auto; position:relative;
`;
overlay.appendChild(box);
document.body.appendChild(overlay);
return overlay;
}// Show species plot modal for appearing/reappearing species
function showSpeciesPlotModal(speciesKey, speciesName, lmeName, prefix) {
const overlay = document.getElementById("species-plot-modal");
const box = document.getElementById("species-plot-modal-box");
box.innerHTML = "";
const title = document.createElement("h4");
title.textContent = `${speciesName} – ${lmeName}`;
title.style.marginTop = "0";
box.appendChild(title);
// Plot containers
const plotDiv1 = document.createElement("div");
plotDiv1.style.cssText = "width:100%;height:320px;";
box.appendChild(plotDiv1);
const plotDiv2 = document.createElement("div");
plotDiv2.style.cssText = "width:100%;height:320px;margin-top:8px;";
box.appendChild(plotDiv2);
overlay.style.display = "flex";
// Build JSON URLs using the prefix (appearing/reappearing) naming
const baseName = prefix ? `${prefix}_lme_${lmeName}_species_${speciesKey}` : `lme_${lmeName}_species_${speciesKey}`;
const url1 = PLOTS_JSON_BASE + encodeURIComponent(`${baseName}_1.json`);
const url2 = PLOTS_JSON_BASE + encodeURIComponent(`${baseName}_2.json`);
renderPlotlyChart(plotDiv1, url1, "No occurrences plot available.");
renderPlotlyChart(plotDiv2, url2, "No grid-cells plot available.");
}// Add LME polygons to the map
add_lme_polygons = {
lme_layer.clearLayers();
lme_geojson.features.forEach(feature => {
const lme_id = feature.properties.lme_id;
const lme_name = feature.properties.lme_name;
const style = {
fillColor: selected_lme_id === lme_id ? "#3388ff" : "#cccccc",
weight: 2,
opacity: 1,
color: selected_lme_id === lme_id ? "#0066cc" : "#999999",
fillOpacity: selected_lme_id === lme_id ? 0.8 : 0.3
};
const layer = L.geoJSON(feature, {
style,
onEachFeature: (feat, lyr) => {
// Tooltip showing LME name
lyr.bindTooltip(lme_name, { permanent: false, direction: "center" });
// Click handler to select LME
lyr.on("click", () => {
mutable selected_lme_id = lme_id;
// Update styles for all layers
lme_layer.eachLayer(l => {
const fid = l.feature.properties.lme_id;
l.setStyle({
fillColor: fid === lme_id ? "#3388ff" : "#cccccc",
color: fid === lme_id ? "#0066cc" : "#999999",
fillOpacity: fid === lme_id ? 0.8 : 0.3
});
});
});
// Hover effects
lyr.on("mouseover", () => {
if (selected_lme_id !== lme_id) {
lyr.setStyle({ fillOpacity: 0.5 });
}
});
lyr.on("mouseout", () => {
if (selected_lme_id !== lme_id) {
lyr.setStyle({ fillOpacity: 0.3 });
}
});
}
});
layer.addTo(lme_layer);
});
}filtered_appearing = selected_lme_id !== null
? appearing_species_data.filter(d => +d.lme_id === selected_lme_id)
: []
filtered_reappearing = selected_lme_id !== null
? reappearing_species_data.filter(d => +d.lme_id === selected_lme_id)
: []
filtered_emerging = selected_lme_id !== null
? emerging_species_data.filter(d => +d.lme_id === selected_lme_id)
: []
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.
Appearing Species
// Display appearing species table
{
if (selected_lme_id !== null && filtered_appearing.length > 0) {
const table = Inputs.table(filtered_appearing, {
columns: ["specieskey", "species", "first_year"],
header: {
specieskey: "speciesKey",
species: "Species",
first_year: "First Year"
},
width: {
specieskey: 150,
species: 350,
first_year: 150
},
format: {
specieskey: x => htl.html`<a href="https://www.gbif.org/species/${x}" target="_blank">${x}</a>`,
species: (value, idx) => {
const row = filtered_appearing[idx];
const link = document.createElement("a");
link.href = "#";
link.textContent = value;
link.style.cssText = "cursor:pointer;text-decoration:underline;";
link.onclick = e => {
e.preventDefault();
showSpeciesPlotModal(row.specieskey, value, selected_lme_name, "appearing_species_plots_ggplot2");
};
return link;
}
},
sort: "first_year",
reverse: true
});
// Wrap in a plain container so Observable does not treat it as an input with checkboxes
const wrapper = document.createElement("div");
wrapper.appendChild(table);
yield wrapper;
} else if (selected_lme_id !== null) {
yield html`<p style="text-align: center; color: #999; padding: 20px;">No appearing species data available for this LME</p>`;
}
}Reappearing Species
// Display reappearing species table
{
if (selected_lme_id !== null && filtered_reappearing.length > 0) {
const table = Inputs.table(filtered_reappearing, {
columns: ["specieskey", "species", "reappearance_year", "years_without_occurrences"],
header: {
specieskey: "speciesKey",
species: "Species",
reappearance_year: "Reappearance Year",
years_without_occurrences: "Years Without Occurrences"
},
width: {
specieskey: 150,
species: 250,
reappearance_year: 150,
years_without_occurrences: 200
},
format: {
specieskey: x => htl.html`<a href="https://www.gbif.org/species/${x}" target="_blank">${x}</a>`,
species: (value, idx) => {
const row = filtered_reappearing[idx];
const link = document.createElement("a");
link.href = "#";
link.textContent = value;
link.style.cssText = "cursor:pointer;text-decoration:underline;";
link.onclick = e => {
e.preventDefault();
showSpeciesPlotModal(row.specieskey, value, selected_lme_name, "reappearing_species_plots_ggplot2");
};
return link;
}
},
sort: "reappearance_year",
reverse: true
});
const wrapper = document.createElement("div");
wrapper.appendChild(table);
yield wrapper;
} else if (selected_lme_id !== null) {
yield html`<p style="text-align: center; color: #999; padding: 20px;">No reappearing species data available for this LME</p>`;
}
}Emerging Species
// Display emerging species table
{
if (selected_lme_id !== null && filtered_emerging.length > 0) {
const table = Inputs.table(filtered_emerging, {
columns: ["specieskey", "species_name", "weighted_em_status", "model", "growth"],
header: {
specieskey: "speciesKey",
species_name: "Species",
weighted_em_status: "Emergence Score",
model: "Model",
growth: "Growth"
},
width: {
specieskey: 120,
species_name: 250,
weighted_em_status: 140,
model: 100,
growth: 120
},
format: {
specieskey: x => htl.html`<a href="https://www.gbif.org/species/${x}" target="_blank">${x}</a>`,
species_name: (value, idx) => {
const row = filtered_emerging[idx];
const link = document.createElement("a");
link.href = "#";
link.textContent = value;
link.style.cssText = "cursor:pointer;text-decoration:underline;";
link.onclick = e => {
e.preventDefault();
showSpeciesPlotModal(row.specieskey, value, selected_lme_name, null);
};
return link;
},
growth: x => x ? (+x).toFixed(4) : "—"
}
});
const wrapper = document.createElement("div");
wrapper.appendChild(table);
yield wrapper;
} else if (selected_lme_id !== null) {
yield html`<p style="text-align: center; color: #999; padding: 20px;">No emerging species data available for this LME</p>`;
}
}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).