Insert cell
Insert cell
viewof layout = html`
<div style="
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
padding: 20px;
box-sizing: border-box;
background-color: #ffff; /* Main background color */
">
${viewof title_layout}

<!-- FAO Region Map Container -->
<div style="
margin-top: 20px;
width: 100%;
max-width: 1200px; /* Max width for larger screens */
padding: 15px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: #fff; /* White background */
border: 2px solid #1E6091; /* Deep blue border */
box-sizing: border-box;
">
<div style="
margin-bottom: 10px;
font-weight: bold;
font-size: 1.2em;
color: #1E6091; /* Deep blue text color */
">
FAO Region Map
</div>

<div style="
margin-top: 20px;
width: 95%; /* Ensure this div is 95% of the parent container */
margin-left: auto;
margin-right: auto;
padding: 15px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: #fff; /* White background */
border: 2px solid #1E6091; /* Deep blue border */
box-sizing: border-box;
">
<div style="
margin-bottom: 10px;
font-weight: bold;
font-size: 1.2em;
color: #1E6091; /* Deep blue color */
">
Map Projection Settings
</div>
${viewof projection}
</div>

<!-- Move Nuclide Selection Here -->
<div style="
margin-top: 20px;
width: 95%;
margin-left: auto;
margin-right: auto;
padding: 15px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: #fff;
border: 2px solid #1E6091;
box-sizing: border-box;
">
<div style="
margin-bottom: 10px;
font-weight: bold;
font-size: 1.2em;
color: #1E6091;
">
Nuclide Selection
</div>
${viewof nuclide_selected}
</div>

<div style="
margin-top: 20px;
width: 95%;
margin-left: auto;
margin-right: auto;
padding: 15px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: #fff;
border: 2px solid #1E6091;
box-sizing: border-box;
">
<div style="
margin-bottom: 10px;
font-weight: bold;
font-size: 1.2em;
color: #1E6091;
">
Select a year to view
</div>
<div style="width: 100%; padding: 0 15px; box-sizing: border-box; text-align: center;">
${viewof year}
</div>
</div>

<div style="
margin-top: 20px;
width: 95%;
margin-left: auto;
margin-right: auto;
padding: 15px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: #fff;
border: 2px solid #1E6091;
box-sizing: border-box;
">
<div style="
margin-bottom: 10px;
font-weight: bold;
font-size: 1.2em;
color: #1E6091;
">
Map
</div>
<div style="text-align: left;">${viewof yearnuclideElement}</div>
<div style="text-align: center;">${viewof scaleLegend}</div>
<div id="globeDiv" style="text-align: center;">${globeMap}</div>
<div style="text-align: center;">${viewof faoTable}</div>
</div>
</div>

<!-- FAO Explorer Map Container -->
<div style="
margin-top: 20px;
width: 100%;
max-width: 1200px; /* Max width for larger screens */
padding: 15px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: #fff; /* White background */
border: 2px solid #1E6091; /* Deep blue border */
box-sizing: border-box;
">
<div style="
margin-bottom: 10px;
font-weight: bold;
font-size: 1.2em;
color: #1E6091; /* Deep blue text color */
">
FAO Detailed Exploration
</div>

<!-- Map Type Selection -->
<div style="
margin-top: 20px;
width: 95%;
margin-left: auto;
margin-right: auto;
padding: 15px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: #fff;
border: 2px solid #1E6091;
box-sizing: border-box;
">
<div style="
margin-bottom: 10px;
font-weight: bold;
font-size: 1.2em;
color: #1E6091;
">
Map selection
</div>
${viewof mapType}
${viewof logScale}
</div>

<div style="
margin-top: 20px;
width: 95%;
margin-left: auto;
margin-right: auto;
padding: 15px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: #fff;
border: 2px solid #1E6091;
box-sizing: border-box;
">
<div style="
margin-bottom: 10px;
font-weight: bold;
font-size: 1.2em;
color: #1E6091;
">
</div>
<div style="text-align: center;">${viewof combinedMapWithHexBinsAndPoints}</div>
<div style="text-align: center;">${viewof scatterPlot3D}</div>



</div>
</div>

</div>
`;

Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
params_example = ({
sampleTypeId: 1, // Sample type in MARIS database (1: Seawater, 2: Biota, 3: Sediment, 4: Suspended matter)
nuclideId: "33", // The nuclide ID. See lookup table 'lutArray' for nuclides (e.g., 137Cs equals 33)
samplingDateFrom: "2020-01-01", // The start date to query the API
samplingDateTo: "2020-02-01" // The end date to query the API
})
Insert cell
Insert cell
measurementDataExample=getMeasurementData(params_example)
Insert cell
Insert cell
measurementDataExample
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
Insert cell
Insert cell
sampleTypes=getSampleTypes()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
marisNuclides=nuclides.map(item => item["nusymbol"]).sort((a, b) => a.localeCompare(b))
Insert cell
Insert cell
world_fao_zones
Insert cell
Insert cell
Insert cell
Insert cell
viewof title_layout = html`
<!-- Main container -->
<div style="
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
padding: 20px;
box-sizing: border-box;
background-color: #F0F8FF; /* Light background for the main container */
">
<h1 style="
font-size: 2em;
color: #1E6091; /* Deep blue text color */
margin-bottom: 20px;
">
MARIS Sea Water Data Explorer
</h1>
</div>
`

Insert cell
Insert cell
viewof nuclide_selected = Inputs.select(
marisNuclides // Use the array of nuclide strings
.sort((a, b) => {
// Sort alphabetically by the entire string
return a.localeCompare(b);
}),
{
unique: true,
value: 'Cs-137', // Set default value
}
);

Insert cell
Insert cell
Insert cell
getNuclideId(nuclide_selected)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof year = Inputs.range([1950, new Date().getFullYear()], {step: 1,label: "Year", width: '100%'})
Insert cell
Insert cell
viewof yearnuclideElement = html`
<h2 style="
color: #1E6091; /* Deep blue, MARIS primary color */
font-size: 1.5em; /* Larger text size for emphasis */
font-weight: bold; /* Bold text for emphasis */
background-color: #FFFF; /* White background */
padding: 10px; /* Padding to space out the text */
border-radius: 8px; /* Rounded corners for smooth look */
text-align: center; /* Centered text */
">
Year: ${year} | Nuclide: ${nuclide_selected}
</h2>
`;

Insert cell
Insert cell
Insert cell
viewof projection = projectionInput({value: new URLSearchParams(location.search).get("projection") || "Mercator"})
Insert cell
Insert cell
Insert cell
marisMeasurementData=getMeasurementData(params)
Insert cell
Insert cell
Insert cell
Insert cell
marisMeasurementDataReducedCOI = filterByColumns (marisMeasurementData, seawaterColumnsOfInterest)
Insert cell
Insert cell
Insert cell
Insert cell
processedMarisMeasurementData = processData(marisMeasurementDataReducedCOI)
Insert cell
Insert cell
Insert cell
Insert cell
processedDataWithFaoRegion=addFaoRegionToMeasurements(processedMarisMeasurementData ,world_fao_zones)
Insert cell
Insert cell
Insert cell
Insert cell
summaryMeasurementData=summarizeMeasurementData(processedDataWithFaoRegion)
Insert cell
Insert cell
Insert cell
minSummaryActivity_by_year= d3.min(summaryMeasurementData , d => d.averageActivity);

Insert cell
maxSummaryActivity_by_year = d3.max(summaryMeasurementData, d => d.averageActivity);

Insert cell
colorScale = d3.scaleSequential(d3.interpolateViridis).domain([minSummaryActivity_by_year , maxSummaryActivity_by_year ]);
Insert cell
mutable selectedZone = null
Insert cell
function createGlobe(year) {
const globeDiv = document.getElementById('globeDiv');
const height = 675;
const width = globeDiv ? (globeDiv.clientWidth || containerWidth) : containerWidth;
const sensitivity = 75;

const colors = {
water: "#888888",
land: "#ffffff",
borders: "#000000",
graticules: "#d0d0d0",
defaultColor: '#acfaee',
clickedColor: '#ff5722',
hoverColor: '#FFA500'
};

const path = d3.geoPath().projection(projection);

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height);

const initialScale = projection.scale();
projection.rotate([-1, -1]); // Corrects an issue with the mercator projection where it renders the inverse.
svg.append("circle")
.attr("fill", colors.land)
.attr("stroke", "none")
.attr("stroke-width", "0")
.attr("cx", width / 2)
.attr("cy", height / 2)
.attr("r", initialScale);

const graticule = d3.geoGraticule();
svg.append("path")
.datum(graticule())
.attr("class", "graticule")
.attr("d", path)
.attr('fill', 'none')
.attr('stroke', colors.graticules)
.attr('stroke-width', '0.5px');

function updateMap(year) {
const filteredData = summaryMeasurementData;

const minActivity = d3.min(filteredData, d => d.averageActivity);
const maxActivity = d3.max(filteredData, d => d.averageActivity);


const colorMap = new Map(filteredData.map(d => [d.faoRegion, colorScale(d.averageActivity)]));

const faoFeatures = world_fao_zones.features;

svg.selectAll(".fao-region").remove();

svg.selectAll(".fao-region")
.data(faoFeatures)
.enter().append("path")
.attr("class", "fao-region")
.attr("d", d => path(d))
.style("fill", d => colorMap.get(d.properties.zone) || colors.defaultColor)
.on("mouseover", (event, d) => {
d3.select(event.target).style("fill", colors.hoverColor);
})
.on("mouseout", (event, d) => {
d3.select(event.target).style("fill", colorMap.get(d.properties.zone) || colors.defaultColor);
})
.on("click", (event, d) => {
d3.selectAll(".fao-region").style("fill", d => colorMap.get(d.properties.zone) || colors.defaultColor);
d3.select(event.target).style("fill", colors.clickedColor);

// Update the mutable cell with the clicked zone
mutable selectedZone = d.properties.zone;
});

svg.append("path")
.datum(topojson.mesh(world_fao_zones, (a, b) => a !== b))
.attr("d", path)
.attr("class", "borders")
.style("fill", "none")
.style("stroke", colors.borders)
.style("stroke-width", "0.3px");
svg.call(d3.drag().on('drag', (event) => {
const rotate = projection.rotate();
const k = sensitivity / projection.scale();
projection.rotate([
rotate[0] + event.dx * k,
rotate[1] - event.dy * k
]);
path.projection(projection);
svg.selectAll("path").attr("d", path);
}))
.call(d3.zoom().on('zoom', (event) => {
if (event.transform.k > 0.3) {
projection.scale(initialScale * event.transform.k);
path.projection(projection);
svg.selectAll("path").attr("d", path);
svg.select("circle").attr("r", projection.scale());
} else {
event.transform.k = 0.3;
}
}));
// After data is rendered, update the status to "Displaying data for year {year}"
viewof statusHeader.textContent = `Displaying data for year ${year}.`;
}

updateMap(year);

return svg.node();
}
Insert cell
viewof containerWidth = {
// Create a new HTML element for container width and update it on resize
const container = document.createElement('div');
const updateWidth = () => {
container.value = document.querySelector('div').clientWidth; // Update width on resize
container.dispatchEvent(new CustomEvent('input')); // Dispatch an input event to update `viewof`
};
// Initial width
updateWidth();
// Add resize event listener
window.addEventListener('resize', updateWidth);

return container;
}

Insert cell
viewof scaleLegend = Legend(colorScale, {
title: "Activity ( Bq/m³ )",
tickFormat: "~s",
width: containerWidth*0.8, // define width to be 80% of the 'div' container
height: 80})

Insert cell
// scaleLegend style
html`<style>
/* Legend title styling */
.title {
font-size: 18px; /* Increase font size of the title */
font-weight: bold;
color: #08306b; /* Darker marine blue for the title */
text-align: center;
margin-bottom: 5px;
}

</style>`

Insert cell
Insert cell
Insert cell
selectedSummaryMeasurementData = summaryMeasurementData.filter(d => d.faoRegion === selectedZone);
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
processedDataWithFaoRegion
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof mapWithHexBins = createMapWithHexBinsAndTitle(plotDataYear, {width: '100%', height: 650 });

Insert cell
Insert cell
viewof mapWithPoints = createMapWithPoints(plotDataYear, {width: 700, height: 650 });

Insert cell
create3DScatterPlot = (data, { width = 800, height = 600 } = {}) => {
// Create a container div to hold the plot or message
const containerDiv = document.createElement('div');
if (data.length === 0) {
// Create a message element for no data available
const noDataElement = document.createElement('h2');
noDataElement.innerText = "😢 No data, select an FAO region.";
noDataElement.style.textAlign = "center";
noDataElement.style.margin = "20px";

// Append the no data message to the container
containerDiv.appendChild(noDataElement);
// Return the container div with the no data message
return containerDiv;
}

const yearTitle = data[0].samplingYear; // Assuming all data is from the same year
const faoTitle = data[0].faoRegion; // Assuming all data is from the same FAO region
// Create a div element to hold the 3D scatter plot
const div = document.createElement('div');
div.style.width = `${width}px`;
div.style.height = `${height}px`;

// Extract data for plotting
const latitudes = data.map(d => d.latitude);
const longitudes = data.map(d => d.longitude);
const activities = data.map(d => d.activity);
const unit = data[0].unit;

// Define the scatter plot trace
const scatterTrace = {
x: longitudes,
y: latitudes,
z: activities,
mode: 'markers',
type: 'scatter3d',
marker: {
size: 5,
color: activities, // Color by activity
colorscale: 'Viridis', // Choose a colorscale
colorbar: {
title: `Activity ${unit}`
}
}
};

// Define the 3D scatter plot layout
const scatterLayout = {
title: `${nuclide_selected} Activity Concentrations for FAO Region ${faoTitle} in the Year ${yearTitle}.`,
scene: {
xaxis: { title: 'Longitude' },
yaxis: { title: 'Latitude' },
zaxis: { title: `Activity ${unit}` },
camera: {
eye: {
x: -0.15,
y: -1.5,
z: 2.0
}
}
},
width: width,
height: height
};

// Render the 3D scatter plot
Plotly.newPlot(div, [scatterTrace], scatterLayout)
.then(() => console.log('3D Scatter Plot rendered successfully'))
.catch(error => console.error('Error rendering 3D Scatter Plot:', error));

// Append the plot div to the container
containerDiv.appendChild(div);

// Return the container div element containing the 3D scatter plot or message
return containerDiv;
};

Insert cell
viewof scatterPlot3D = create3DScatterPlot(plotDataYear, { width: containerWidth*0.80 , height: containerWidth*0.80});

Insert cell
TODO: Separate the radio-buttons
Insert cell
viewof mapType = Inputs.radio(["Open-street map", "US GS Imagery Background"], {
value: "Open-street map",
label: "Map Base Layer:",
})
Insert cell
viewof logScale = Inputs.checkbox(["Log10"], {label: "Scale:"})
Insert cell
mapType
Insert cell
logScale
Insert cell
Insert cell
// Radio button styles
html`<style>
input[type="radio"] {
accent-color: #1f78b4; /* Example marine blue for the radio button */
}

label {
color: #08306b; /* Darker marine blue for text */
font-size: 20px;
}

input[type="radio"]:checked {
accent-color: #a6cee3; /* Lighter blue when selected */
}

input[type="radio"]:hover {
accent-color: #6baed6; /* Slightly lighter on hover */
}
</style>`
Insert cell
function createCombinedMapWithHexBinsAndPoints(data, { width = 800, height = 600, mapType = 'Open-street map', logScale = [] } = {}) {
// Function to compute the median of an array
function median(values) {
const sorted = values.slice().sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);
if (sorted.length % 2 === 0) {
return (sorted[middle - 1] + sorted[middle]) / 2;
}
return sorted[middle];
}

// Function to render the map with a given base map style and color scale
function renderMap(mapStyle, useLogScale) {
console.log('Rendering map with style:', mapStyle); // Debugging line
console.log('Using log scale:', useLogScale); // Debugging line

const colorscale = 'Viridis'; // Colorscale choice (same for both linear and log)
const z = useLogScale ? activities.map(a => a > 0 ? Math.log10(a) : 0) : activities;

Plotly.newPlot(mapDiv, [
{
type: 'densitymapbox',
lat: latitudes,
lon: longitudes,
z: z,
radius: 10, // Adjust the radius for hex bins
colorscale: colorscale,
colorbar: {
title: useLogScale ? 'Activity (Log10 Scale)' : `Activity ${unit}`,
tickprefix: useLogScale ? 'log10(' : '',
tickvals: useLogScale ? [0, Math.log10(maxActivity)] : undefined,
ticktext: useLogScale ? ['10^0', `10^${Math.round(Math.log10(maxActivity))}`] : undefined
},
hoverinfo: 'skip',
},
{
type: 'scattermapbox',
mode: 'markers',
lat: latitudes,
lon: longitudes,
marker: {
size: 5,
color: z, // Color by activity
colorscale: colorscale,
},
text: activities.map(a => `Activity: ${a}`), // Tooltip text for points
hoverinfo: 'text'
}
], {
mapbox: {
style: mapStyle === 'Open-street map' ? 'open-street-map' : 'white-bg',
layers: mapStyle === 'US GS Imagery Background' ? [{
sourcetype: 'raster',
source: [
"https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer/tile/{z}/{y}/{x}"
],
below: 'traces',
sourceattribution: 'United States Geological Survey'
}] : undefined,
center: {
lat: medianLat,
lon: medianLon
},
zoom: 3, // Initial zoom level
pitch: 0, // Default pitch angle
bearing: 0, // Default bearing angle
interactive: true // Ensure interactions like zoom and pan are enabled
},
margin: { r: 0, t: 0, l: 0, b: 0 },
width: width,
height: height,
scrollZoom: true // Enable mouse scroll zooming
});
}

// Create a container div to hold the elements
const containerDiv = document.createElement('div');

if (data.length === 0) {
// Create a message element for no data available
const noDataElement = document.createElement('h2');
noDataElement.innerText = "😢 No data, select an FAO Region.";
noDataElement.style.textAlign = "center";
noDataElement.style.margin = "20px";

// Append the no data message to the container
containerDiv.appendChild(noDataElement);
// Return the container div with the no data message
return containerDiv;
}

// Create a title element
const titleElement = document.createElement('h2');
const yearTitle = data[0].samplingYear; // Assuming all data is from the same year
const faoTitle = data[0].faoRegion; // Assuming all data is from the same FAO region
const unit = data[0].unit;
titleElement.innerText = `${nuclide_selected} Activity Concentrations for FAO Region ${faoTitle} in the Year ${yearTitle}.`;
titleElement.style.textAlign = "center";
titleElement.style.marginBottom = "20px"; // Add some margin below the title
// Append the title to the container
containerDiv.appendChild(titleElement);

// Create a div element to hold the map
const mapDiv = document.createElement('div');
mapDiv.style.width = `${width}px`;
mapDiv.style.height = `${height}px`;

// Append the map div to the container
containerDiv.appendChild(mapDiv);

// Extract data for plotting
const latitudes = data.map(d => d.latitude);
const longitudes = data.map(d => d.longitude);
const activities = data.map(d => d.activity);

// Calculate the median latitude and longitude
const medianLat = median(latitudes);
const medianLon = median(longitudes);
// Compute max activity value for log scale
const maxActivity = Math.max(...activities);

// Determine if log scale is enabled based on the checkbox input
const useLogScale = logScale.includes('Log10');

// Debugging output
console.log('Map Type:', mapType);
console.log('Log Scale:', useLogScale);

// Initial render of the map with the default style and scale
renderMap(mapType, useLogScale);

// Return the container div element containing the title and map
return containerDiv;
}

Insert cell
viewof combinedMapWithHexBinsAndPoints = createCombinedMapWithHexBinsAndPoints(plotDataYear, {
width: containerWidth*0.80,
height: containerWidth,
mapType: mapType,
logScale: logScale
});

Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
import { world_fao_zones } from "@francobollo/marine-regions"
Insert cell
Insert cell
Insert cell
Insert cell
import {mdPlus} from "@tmcw/bonus-markdown-flavor"

Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
ol = {
const ol = await require("ol@8.2.0/dist/ol.js").catch(() => window.ol);
if (!ol._style) ol._style = document.head.appendChild(html`<link rel=stylesheet href="${await require.resolve("ol@8.2.0/ol.css")}">`);
return ol;
}
Insert cell
ss = require("simple-statistics")
Insert cell
Insert cell
html `
<style>
//hr { background-color: #edf2fa; height: 0.1px; border: 0; }
hr { background-color: white; height: 0.1px; border: 0; }
</style>
`
Insert cell
import { Plot } from "@observablehq/plot"; // Import Plot library
Insert cell
d3 = require("d3@7.8.1")
Insert cell
d3_3d = require('https://unpkg.com/d3-3d/build/d3-3d.min.js')
Insert cell
import { Legend, Swatches } from "@d3/color-legend"
Insert cell
import {slider, number} from "@jashkenas/inputs"

Insert cell
Insert cell
Insert cell
Insert cell
viewof title = html`<p style="text-align: center; font-size: 24px; font-weight: bold;">
Visualize IAEA Marine Radioactivity Information System (MARIS) Sea Water Data by Food and Agriculture Organization (FAO) Major Fishing Areas 🌊.
</p>`
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more