OJS Dashboard
  • Home

OJS Dashboard

d3 = require("d3@7")
L = require("leaflet@1.9.4")

// Add Leaflet CSS
html`<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />`
species_data = d3.csv("data/species_polygons.csv")
polygons_geojson = d3.json("data/polygons.geojson")
// Get unique species for dropdown
unique_species = [...new Set(species_data.map(d => d.species))].sort()

Species Dashboard

Select Species

viewof selected_species = Inputs.text(
  {
    label: "Species:",
    placeholder: "Type to search species...",
    value: unique_species[0],
    datalist: unique_species
  }
)

Filtered Polygons

// Validate that selected species exists in the list
validated_species = unique_species.includes(selected_species) 
  ? selected_species 
  : unique_species[0]
filtered_data = species_data.filter(d => d.species === validated_species)
filtered_polygon_ids = filtered_data.map(d => d.polygons_id)

Showing \({filtered_polygon_ids.length}** polygon(s) for **\){validated_species}

Map - Click on a polygon to view plot

// Create map container
map_container = html`<div id="map" style="height: 500px; width: 100%;"></div>`
// Initialize map - map_container is the div element itself
// Create map centered on Belgium (where coordinates are)
map = {
  const m = L.map(map_container).setView([51.15, 4.15], 11);
  
  // Add base tile layer
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap contributors'
  }).addTo(m);
  
  return m;
}
// Store layer group for polygons
polygonLayer = L.layerGroup().addTo(map)
function slugify(str) {
  const slug = str
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '')   // strip combining diacritics
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '_')
    .replace(/^_|_$/g, '');
  return slug.length > 0 ? slug : 'unknown';
}

// Sanitise a polygon ID so it can be used safely in a file path.
// Keeps only alphanumeric chars, hyphens and underscores.
function sanitizeId(id) {
  return String(id).replace(/[^a-zA-Z0-9_-]/g, '_');
}

// Function to create popup content with a pre-rendered PNG plot.
// Displays the image at assets/plots/<species_slug>__<polygon_id>.png.
// Shows "No plot available" when the image cannot be loaded.
function createPopupContent(polygon_id, species_name) {
  const popupDiv = document.createElement('div');
  popupDiv.style.minWidth = '300px';

  // Title
  const title = document.createElement('h4');
  title.textContent = `${species_name} - ${polygon_id}`;
  title.style.marginTop = '0';
  popupDiv.appendChild(title);

  // PNG image: assets/plots/<species_slug>__<sanitized_polygon_id>.png
  const img = document.createElement('img');
  img.src = `assets/plots/${slugify(species_name)}__${sanitizeId(polygon_id)}.png`;
  img.alt = `Plot for ${species_name} in ${polygon_id}`;
  img.style.maxWidth = '300px';
  img.style.height = 'auto';
  img.style.display = 'block';

  img.onerror = () => {
    img.replaceWith(Object.assign(document.createElement('p'), {
      textContent: 'No plot available for this selection.'
    }));
  };

  popupDiv.appendChild(img);
  return popupDiv;
}
// Reactive cell: Update polygons when filtered_polygon_ids changes
{
  // Clear existing polygons
  polygonLayer.clearLayers();
  
  // Add all polygons with appropriate styling
  polygons_geojson.features.forEach(feature => {
    const polygon_id = feature.properties.polygon_id;
    const is_filtered = filtered_polygon_ids.includes(polygon_id);
    
    // Find all species for this polygon
    const polygon_records = species_data.filter(d => d.polygons_id === polygon_id);
    const all_species = polygon_records.map(d => d.species);
    const species_count = all_species.length;
    
    // Style based on whether it's in filtered set
    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: style,
      onEachFeature: (feature, layer) => {
        // Add tooltip showing all species in this polygon
        const tooltip_text = all_species.length > 0 
          ? `${polygon_id}: ${all_species.join(', ')} (${all_species.length} species)`
          : `${polygon_id}: No species data`;
        
        layer.bindTooltip(tooltip_text, {
          permanent: false,
          direction: 'center'
        });
        
        // Add click handler with popup
        if (is_filtered) {
          layer.on('click', () => {
            // Use the currently validated species for the popup
            const popupContent = createPopupContent(polygon_id, validated_species);
            L.popup()
              .setLatLng(layer.getBounds().getCenter())
              .setContent(popupContent)
              .openOn(map);
          });
          
          // Change cursor on hover
          layer.on('mouseover', () => {
            layer.setStyle({ fillOpacity: 0.8 });
          });
          layer.on('mouseout', () => {
            layer.setStyle({ fillOpacity: 0.6 });
          });
        }
      }
    });
    
    layer.addTo(polygonLayer);
  });
  
  // Fit map to filtered polygons if any
  if (filtered_polygon_ids.length > 0) {
    const bounds = polygonLayer.getBounds();
    if (bounds.isValid()) {
      map.fitBounds(bounds, { padding: [50, 50] });
    }
  }
}

Instructions: 1. Type a species name in the text input on the left (autocomplete suggestions will appear) 2. The map will highlight polygons containing that species in blue 3. Click on any highlighted polygon to view its plot in a popup