Public
Edited
May 13, 2024
2 forks
3 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
let container = html`<div style='height:800px; .custom' />`;
let hexagons = null;
let zoom = 2;
let hoveredHexId = null;

// const colors = asMapboxStepExpr(colorScale);
const colors = asMapboxStepExpr(colorScaleAuto);

// Give the container dimensions.
yield container;

// Create the \`map\` object with the mapboxgl.Map constructor, referencing
// the container div
let map = new mapboxgl.Map({
container,
zoom: zoom,
style: "mapbox://styles/franckalbinet/ckyaawysn1qlg15ny2auxlk8k",
// style: "mapbox://styles/mapbox/light-v11",
projection: {
name: projMapbox
}
});

// const el = document.createElement("div");
// // const width = marker.properties.iconSize[0];
// // const height = marker.properties.iconSize[1];
// el.className = "marker";
// // el.style.backgroundImage = `url(https://static-00.iconduck.com/assets.00/openai-icon-1011x1024-uztb7qme.png)`;
// el.style.backgroundImage = `url(https://drive.google.com/uc?export=view&id=1WSmGVZUclejhr3MBsj_pR8dKDITsSSRM)`;
// el.style.width = "50px";
// el.style.height = "50px";
// // el.style.width = `${width}px`;
// // el.style.height = `${height}px`;
// el.style.backgroundSize = "100%";
// const marker = new mapboxgl.Marker(el)
// .setLngLat([-1.608943147966467, 43.425281054110535])
// .addTo(map);

const marker = new mapboxgl.Marker({
draggable: true,
anchor: "center",
className: "custom-icon"
// color: "steelblue"
})
.setLngLat([-1.608943147966467, 43.425281054110535])
.addTo(map);

// new mapboxgl.Marker(el)
// .setLngLat(marker.geometry.coordinates)
// .addTo(map);

// Create a popup, but don't add it to the map yet.
const popup = new mapboxgl.Popup({
closeButton: false,
closeOnClick: false
});

map.addControl(new mapboxgl.NavigationControl(), "top-right");

map.on("boxzoomend", (e) => {
// let coords = map.unproject([
// e.originalEvent.clientX,
// e.originalEvent.clientY
// ]);
// debugger;
// map.boxZoom.disable();
// e.stopPropagation();
map.fitBounds(
[
[-1.59, 43.43], // [lng, lat] - southwestern corner of the bounds
[-1.58, 43.44] // [lng, lat] - northeastern corner of the bounds
],
{ center: [-1.585, 43.435] }
);
e.preventDefault();

// console.log("event type:", e.type);
});

map.on("load", () => {
https: map.addSource("hexbins", {
type: "geojson",
data: getHexagons(
dataRdnDepth,
zoomScale(map.getZoom()),
map.getBounds()
),
generateId: true
});

// Hexbins fill
map.addLayer({
id: "hexbins-fill",
type: "fill",
source: "hexbins",
layout: {},
paint: {
"fill-color": ["step", ["get", "max", ["get", "value"]], ...colors],
"fill-opacity": 0.8
}
});
map.addLayer({
id: "hexbins-line",
type: "line",
source: "hexbins",
layout: {},
paint: {
"line-color": [
"case",
["boolean", ["feature-state", "hover"], false],
"black",
"#bbb"
],
"line-opacity": 0.5,
"line-width": [
"case",
["boolean", ["feature-state", "hover"], false],
4,
0.5
]
}
});
});

map.on("zoomend", () => {
// console.log(map.getZoom());
map
.getSource("hexbins")
.setData(
getHexagons(dataRdnDepth, zoomScale(map.getZoom()), map.getBounds())
);
});
map.on("moveend", () => {
map
.getSource("hexbins")
.setData(
getHexagons(dataRdnDepth, zoomScale(map.getZoom()), map.getBounds())
);
});

// When the user moves their mouse over the state-fill layer, we'll update the
// feature state for the feature under the mouse.
map.on("mousemove", "hexbins-fill", (e) => {
map.getCanvas().style.cursor = "pointer";
if (e.features.length > 0) {
if (hoveredHexId !== null) {
map.setFeatureState(
{ source: "hexbins", id: hoveredHexId },
{ hover: false }
);
}
hoveredHexId = e.features[0].id;
map.setFeatureState(
{ source: "hexbins", id: hoveredHexId },
{ hover: true }
);
}

// Copy coordinates array.
const coordinates = turf.centroid(turf.centroid(e.features[0].geometry))
.geometry.coordinates;
const description = JSON.parse(e.features[0].properties.value).max;
// Ensure that if the map is zoomed out such that multiple
// copies of the feature are visible, the popup appears
// over the copy being pointed to.
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}
// Populate the popup and set its coordinates
// based on the feature found.
popup.setLngLat(coordinates).setHTML(description).addTo(map);
});

// When the mouse leaves the state-fill layer, update the feature state of the
// previously hovered feature.
map.on("mouseleave", "hexbins-fill", () => {
map.getCanvas().style.cursor = "";
if (hoveredHexId !== null) {
map.setFeatureState(
{ source: "hexbins", id: hoveredHexId },
{ hover: false }
);
}
hoveredHexId = null;
popup.remove();
});

map.addControl(new mapboxgl.FullscreenControl());
// Be careful to clean up the map's resources using \`map.remove()\` whenever
// this cell is re-evaluated.
invalidation.then(() => map.remove());
}
Insert cell
Insert cell
function bboxToGeojson(bbox) {
return turf.bboxPolygon([
bbox._sw.lng,
bbox._sw.lat,
bbox._ne.lng,
bbox._ne.lat
]);
}
Insert cell
function getHexagons(
data,
zoom,
bbox,
lon = "longitude",
lat = "latitude",
attr = "activity"
) {
// Filter out measurements outside bbox
// let dataInBbox = data.filter((d) => {
// // d3.polygonContains(bbox, [d[lon], d[lat]])
// const inBbox = d3.geoContains(bboxToGeojson(bbox), [d[lon], d[lat]]);
// console.log(inBbox);
// return inBbox;
// });

// Generate H3 index and grouping
const hexIdxGrp = d3.rollup(
data.map((d) => ({
...d,
// h3Idx: h3.geoToH3(d.latitude, d.longitude, zoom) -- version 3
h3Idx: h3.latLngToCell(d[lat], d[lon], zoom)
})),
(v) => {
return { max: d3.max(v, (d) => d[attr]), length: v.length };
},
(d) => d.h3Idx
);
// console.log(hexIdxGrp);
// Convert to GeoJSON
const hexagons = geojson2h3.h3SetToFeatureCollection(
[...hexIdxGrp.keys()],
(hex) => ({
value: hexIdxGrp.get(hex)
})
);
// console.log(hexagons);

fixTransmeridian(hexagons);

// console.log("Nb. of hexagon features: ", hexagons.features.length);
return hexagons;
}
Insert cell
Insert cell
Insert cell
Insert cell
// zoomScale = d3
// .scaleThreshold()
// .domain([2.5, 3.5, 4.5, 6, 8, 10, 12])
// .range([3, 4, 5, 6, 7, 8, 9, 10])
Insert cell
radiusScale = d3
.scaleSqrt() // instead of scaleLinear()
.domain([0, d3.max(dataRdnDepth, (d) => d.value)])
.range([0, 100])
Insert cell
colorScale = d3.scaleThreshold(
[100, 200, 500, 1000, 1500, 10000, 100000],
d3.schemeOrRd[8]
// d3.schemeRdPu[8]
)
Insert cell
d3.range(100)
Insert cell
quantiles = [0.25, 0.5, 0.75, 0.95, 0.99]
Insert cell
colorDomain = quantiles.map((d) =>
Math.round(
d3.quantile(
dataRdnDepth.map((d) => d.activity),
d
)
)
)
Insert cell
colorScaleAuto = d3.scaleThreshold(
colorDomain,
d3.schemeOrRd[colorDomain.length + 1]
)
Insert cell
d3.schemeOrRd[6]
Insert cell
colorScaleAuto(1)
Insert cell
d3.schemeRdBu[9]
Insert cell
// color: {
// type: "threshold",
// scheme: "orrd",
// domain: [0.25, 0.5, 0.75, 0.95, 0.99].map((d) =>
// Math.round(d3.quantile(value, d))
// )
// },
Insert cell
Insert cell
maris-seawater-all.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
dataRdn = data.filter((d) => d.nusymbol == rdn)
Insert cell
maxDepth = d3.max(dataRdn, (d) => d.sampdepth)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
dataRdnDepth = dataRdn.filter(
(d) => d.sampdepth >= depthRange[0] && d.sampdepth <= depthRange[1]
)
Insert cell
width
Insert cell
Insert cell
import { interval } from "@mootari/range-slider@1781"
Insert cell
lodash = require("lodash")
Insert cell
d3 = require("d3@7.8.1")
Insert cell
mapboxgl = {
let mapboxgl = await require("mapbox-gl@2.12");
mapboxgl.accessToken =
"pk.eyJ1IjoiZnJhbmNrYWxiaW5ldCIsImEiOiJja3hpdHExMHExZ3JvMnducHJzeWl6cHl1In0.XnOi6i1FZRYwL6j1m5WR1g";

const href = await require.resolve("mapbox-gl@2.6.0/dist/mapbox-gl.css");
document.head.appendChild(html`<link href=${href} rel=stylesheet>`);
return mapboxgl;
}
Insert cell
h3 = require("h3-js")
Insert cell
geojson2h3 = require("https://bundle.run/geojson2h3@1.0.1")
Insert cell
Insert cell
turf = require("@turf/turf@6")
Insert cell
MapboxDraw = require("@mapbox/mapbox-gl-draw@1.4.0")
Insert cell
import { Legend, Swatches } from "@d3/color-legend"
Insert cell
Insert cell
[0.5].map((d) => d3.quantile([1, 2, 3, 4, 5], d))
Insert cell
d3.schemeOrRd[3]
Insert cell
// values = [1, 1, 1, 1]
values = d3.range(100)
Insert cell
// [0.25, 0.5, 0.75].map((d) => d3.quantile(values, d))
Insert cell
col1 col2 col3 (domain)
v1 v2 (range)
Insert cell
quantilesScale = d3.scaleThreshold(
[2, 5, 10],
[[], [0.5], [0.25, 0.5, 0.75], [0.25, 0.5, 0.75, 0.95, 0.99]]
)
Insert cell
// quantilesScale(new Set(values).size)
Insert cell
// quantilesScale(10).map((d) => d3.quantile(values, d))
Insert cell
// d3.scaleThreshold([1.5], d3.schemeOrRd[2])
Insert cell
d3.schemeOrRd[3]
Insert cell
colorScaleD = function (values) {
const distinctValues = new Set(values);
const nDistinct = distinctValues.size;

let range = quantilesScale(nDistinct);
let domain = d3.schemeOrRd[range.length + 1];

if (range.length == 0) {
domain = ["#fee8c8", "#fee8c8"];
range = [values[0]];
} else if (range.length == 1) {
domain = ["#fee8c8", "#e34a33"];
}

return d3.scaleThreshold(
range.map((d) => d3.quantile(values, d)),
domain
);
}
Insert cell
// refs: https://observablehq.com/@d3/color-legend
// import { Legend, Swatches } from "@d3/color-legend"
Legend(colorScaleD(values), {
title: "Activity"
})
Insert cell
Swatches(d3.scaleOrdinal([1.12], ["#fdbb84"]))
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