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)
: []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).