Published
Edited
Jul 27, 2022
Insert cell
Insert cell
Insert cell
viewof limits = brushFilterX([new Date("2018-12-01"), new Date("2020-11-01")], {
defaultExtent: [d3.isoParse("2020-02-01"), d3.isoParse("2020-10-10")]
})
Insert cell
Insert cell
limits
Insert cell
Insert cell
mutable debugLimits = null // after moving the date brush several times, you'll see a flickering of these dates as you zoom in and out. You can also see the circles flickering.
Insert cell
Insert cell
mutable countReEvaluation = -1
Insert cell
{
mutable countReEvaluation++;
const updateLayer = () => {
const sitesPacked = createSitePacks();
const values = [...new Map(sitesPacked).values()];
const valuesCollide = applySimulation(values);
const flatData = [];

valuesCollide.map((d) => {
flatData.push({
r: d.r,
x: d.x,
y: d.y,
site: d.k,
species: "All",
opacity: 0.6
});
d.nodes.map((node) => {
flatData.push({
r: node.r,
x: d.x + node.x,
y: d.y + node.y,
site: node.data.site,
species: node.data.key,
opacity: 0.9
});
});
});
let outdata = [];
flatData.map((d) => {
let loc = map.unproject(d); // key to getting the lat, lons back
outdata.push({
...d,
lat: loc.lat,
lng: loc.lng
});
});

const datageoZoom = outdata.map((d, i) => {
let sel = {};
if (selected && selected[0]) {
sel = selected[0];
}

// if (d.acf.geolocation != null) {
return {
id: i,
properties: {
...d,
stroke: d.site === sel.site && d.species === "All" ? 1 : 0,
color: d.species === "All" ? "white" : colorScale(d.species),
radius: d.r,
opacity: d.opacity
},
geometry: {
type: "Point",
coordinates: [d.lng, d.lat]
},
type: "Feature"
};
// }
});
// console.log(datageoZoom);
let fc = {
type: "FeatureCollection",
features: datageoZoom.sort((a, b) => b.properties.r - a.properties.r)
};
if (map._loaded) {
// update();
// console.log(!map.getSource("stories"));
if (!map.getSource("stories")) {
map.addSource("stories", {
type: "geojson",
data: fc
});
} else {
map.getSource("stories").setData(fc);
}
if (map.getLayer(hexbinLayer.id)) {
// console.log("remove layer");
map.removeLayer(hexbinLayer.id);
}
map.addLayer(hexbinLayer);

return true;
}
};
// Updates after zooming
if (mutable countReEvaluation <= 0) {
map.on("viewreset", () => {
console.log("viewreset");
updateLayer();
});
map.on("move", () => {
console.log("move");
updateLayer();
});
map.on("moveend", () => {
console.log("moveend");
updateLayer();
});
}
}
Insert cell
updateMapbox = {
// Updates after brushing
console.log("updateMapBox");

// This allows us to update the map with data without re-rendering the whole cell
// There is a bit of weirdness around adding and removing the layer to make sure mapbox rerenders
let fc = {
type: "FeatureCollection",
features: hexgeo.sort((a, b) => b.properties.r - a.properties.r)
};
if (map._loaded) {
// update();

if (!map.getSource("stories")) {
map.addSource("stories", {
type: "geojson",
data: fc
});
} else {
map.getSource("stories").setData(fc);
}
if (map.getLayer(hexbinLayer.id)) {
map.removeLayer(hexbinLayer.id);
}
map.addLayer(hexbinLayer);
return true;
}
// console.log("does this run?");
return false;
}
Insert cell
Insert cell
createSitePacks = () => {
const data = dataBySiteandGroup;
const statesPacked = new Map();

for (let [k, v] of data) {
let obs = Array.from(v, ([key, value]) => {
let uniqSpe = [...new Set(value.map((d) => d.species))];
var myString = "";
var i = 0;
while (i < uniqSpe.length) {
myString = myString + (" <br>" + uniqSpe[i]);
i++;
}

return {
key: key,
species: myString,
speciesNum: [...new Set(value.map((d) => d.species))].length,
value: value.length,
site: k
};
});

try {
// v.sort((a, b) => b.group - a.group); // step 0
let total_count = 0;
//iterate regions
v = obs.map((d) => {
if (d && d.value) {
total_count = total_count + d.value;
let rad = radius(d.speciesNum);
return { data: d, r: rad };
}
}); // step 1

const nodes = packSiblings(v); // step 1
nodes.site = k;
const { r } = packEnclose(nodes); // step 2
const site = bcSiteLocs.find((d) => {
if (d.site) {
if (d.site === k) {
return d.site === k;
}
}
}); // step 3
if (site) {
// const coords = projection([site.long, site.lat]); // step 3
const coords = map.project(new mapboxgl.LngLat(site.long, site.lat));
const x = coords.x;
const y = coords.y;
const name = site;
statesPacked.set(k, { nodes, r, x, y, total_count, k, name, obs }); // step 4
}
} catch {}
}

return statesPacked;
}
Insert cell
hexbinLayer = {
return {
id: "stories",
type: "circle",
source: "stories",
paint: {
"circle-stroke-color": "blue",
"circle-stroke-width": ["get", "stroke"],
"circle-radius": ["get", "radius"],
"circle-color": ["get", "color"],
"circle-opacity": ["get", "opacity"]
}
};
}
Insert cell
focusHeight = 60
Insert cell
margin = ({ top: 4, right: 20, bottom: 30, left: 10 })
Insert cell
packEnclose = (nodes) => d3.packEnclose(nodes)
Insert cell
// circleLocations = updateBubbles()
Insert cell
Insert cell
packSiblings = (values) => d3.packSiblings(values)
Insert cell
Insert cell
circleLocations
Insert cell
hex
Insert cell
hexgeo = circleLocations.map((d, i) => {
let sel = {};
if (selected && selected[0]) {
sel = selected[0];
}
// if (d.acf.geolocation != null) {
return {
id: i,
properties: {
...d,
stroke: d.site === sel.site && d.species === "All" ? 1 : 0,
color: d.species === "All" ? "white" : colorScale(d.species),
radius: d.r,
opacity: d.opacity
},
geometry: {
type: "Point",
coordinates: [d.lng, d.lat]
},
type: "Feature"
};
// }
})
Insert cell
Insert cell
datesForPlot = allDates.map((d) => new Date(d).toISOString().substring(0, 10))
Insert cell
maxRadius = 12
Insert cell
minRadius = {
return speciesList == null ? 3 : 5;
}
Insert cell
radius = d3
.scaleSqrt()
.domain([1, d3.max(groupNumBySite, (d) => d3.max(d[1], (v) => v[1]))])
.range([NODE.MIN_RADIUS, NODE.MAX_RADIUS])
Insert cell
NODE = ({ MIN_RADIUS: minRadius, MAX_RADIUS: maxRadius, PADDING: 2 })
Insert cell
groupNumBySite = d3.rollups(
dataRolled.filter((d) => d.count > 0),
(v) => [...new Set(d3.map(v, (d) => d.species))].length,
(d) => d.site,
(d) => d.group
)
Insert cell
formatMonth = d3.timeFormat("%Y-%m")
Insert cell
limits
Insert cell
startDate = new Date(formatMonth(d3.timeMonth.floor(limits[0])) + "-01") //new Date(filledLineData[timeSlider[0]].date)
Insert cell
endDate = new Date(formatMonth(d3.timeMonth.ceil(limits[1])) + "-01")
Insert cell
dateExtent = [
d3.utcParse("%Y-%m-%d")("2018-12-01"),
d3.utcParse("%Y-%m-%d")("2020-12-01") // one month extra to show the final month's bar
]
Insert cell
dataBySiteandGroup = d3.group(
dataWithCount,
(d) => d.site,
(d) => d.group
)
Insert cell
selected = hexbyLocation.get(map.selected ? map.selected.data.site : "") || []
Insert cell
hexbyLocation = d3.group(hex, (d) => d.site)
Insert cell
hex = {
const data = [];
for (let i = 0; i < bcSiteLocs.length; i++) {
data.push({
site: bcSiteLocs[i].site,
lat: bcSiteLocs[i].lat,
lng: bcSiteLocs[i].long,
category: "none"
});
}
// replace categories with real value
if (sitesForDate.length > 0) {
data.forEach((d) => {
let sta = sitesForDate.find((h) => h.site === d.site);

if (sta === undefined) {
d.category = "noMeasurements";
}
if (sta) {
d.catCount = d3.sum(sta.species_present[0][1], (d) => d.count);
d.catCount > 0 ? (d.category = "present") : (d.category = "none");
}
});
}

return data.filter((d) => d.category != "noMeasurements"); // lets hide sites not visited
}
Insert cell
bcSiteLocs = _.uniqBy(metadata, ({ lat, long }) => lat)
Insert cell
filledLineData = {
const out = [];
// add a day so that the last month is included in the range
const endMonth = new Date(
+d3.max(dataRolled, (d) => new Date(d.date)) + 24 * 60 * 60 * 1000
);
const monthsStartToFinish = d3.timeMonth.range(
d3.min(dataRolled, (d) => new Date(d.date)),
endMonth
);

monthsStartToFinish.forEach((d) => {
d = d.toISOString().substring(0, 10);
let match = monthlySpeciesCount.find((j) => j.date === d);

match === undefined ? out.push({ date: d, value: 0 }) : out.push(match);
});

return out;
}
Insert cell
dataForBars = dataRolled.filter((d) => {
d.date = new Date(d.date).toISOString().substring(0, 10);
return speciesList === null
? type === "All"
? d.group !== "nothing"
: d.group === type
: type === "All"
? d.group !== "nothing"
: d.group === type && d.species === speciesList;
})
Insert cell
speciesCountbyMonth = d3.rollup(
dataForBars.filter((d) => d.count === 1),
(v) => [...new Set(d3.map(v, (d) => d.species))].length,
(d) => d.date
)
Insert cell
monthlySpeciesCount = Array.from(speciesCountbyMonth, ([key, values]) => {
return {
date: key,
// datestr: key,
value: values
};
}).sort((a, b) => a.date - b.date)
Insert cell
sitesForDate = {
const data = d3.groups(
dataWithCount,
(d) => d.site,
(d) => d.group
);

return data.map((d) => {
return {
site: d[0],
species_present: d[1]
};
});
}
Insert cell
speciesList = null
Insert cell
focusData = dataRolled.filter(
(d) =>
new Date(d.date) >= new Date(startDate) &&
new Date(d.date) <= new Date(endDate)
)
Insert cell
dataWithCount = focusData.filter((d) => {
return speciesList === null
? type === "All"
? d.count === 1
: d.count === 1 && d.group === type
: type === "All"
? d.count === 1 && d.species === speciesList
: d.count === 1 && d.group === type && d.species === speciesList;
})
Insert cell
type = "All"
Insert cell
height = 600
Insert cell
uniqSpecies = type === "All"
? [...new Set(dataRolled.map((d) => d.species))]
: [
...new Set(
dataRolled.filter((d) => d.group === type).map((d) => d.species)
)
]
Insert cell
mapboxgl = {
const gl = await require("mapbox-gl");
if (!gl.accessToken) {
gl.accessToken =
"pk.eyJ1IjoiaGFrYWkiLCJhIjoiY2lyNTcwYzY5MDAwZWc3bm5ubTdzOWtzaiJ9.6QhxH6sQEgK634qO7a8MoQ";
const href = await require.resolve("mapbox-gl/dist/mapbox-gl.css");
document.head.appendChild(html`<link href=${href} rel=stylesheet>`);
}
return gl;
}
Insert cell
Insert cell
import { dataRolled, metadata, sstaColors, colorScale } from "59936145deac3274"
Insert cell
import { brushFilterX } from "@observablehq/discovering-date-patterns"
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