Public
Edited
Jan 4, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
focus1
Insert cell
viewof focus1 = {
const defaultExtent = [d3.isoParse("2019-11-01"), d3.isoParse("2020-12-01")];
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, width, focusHeight])
.style("display", "block");

//used for the brush and xAxis
const x = d3
.scaleTime()
.domain([
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
])
.range([margin.left, width - margin.right]);

const y = d3
.scaleLinear()
.domain([0, 1])
.range([focusHeight - margin.bottom, margin.top]);

const xAxis = (g, x, height) =>
g.attr("transform", `translate(0,${height - margin.bottom})`).call(
d3
.axisBottom(x)
.ticks(width / 80)
.tickSizeOuter(0)
);

svg.append("g").call(xAxis, x, focusHeight); //.attr("class", "axisWhite");

// used for the bars
const x2 = d3
.scaleBand()
.range([margin.left, width - margin.right], 0.01)
.padding(0.01)
.domain(datesForPlot);

const brush = d3
.brushX()
.extent([
[margin.left, margin.top],
[width - margin.right, focusHeight - margin.bottom]
])
.on("brush", brushed)
.on("end", brushended);

const bars = svg
.selectAll("rect")
.data(filledLineDataScrubber)
.enter()
.append("rect")
.attr("x", (d) => x2(d.date))
.attr("y", (d) => {
return y(1);
})
.attr("width", (d) => x2.bandwidth())
.attr("height", (d) => y(0) - y(1))
.attr("fill", (d) => sstaColors(d.value));
// .attr("opacity", 0.8);

let previousS0, previousS1;

let gb = svg.append("g").call(brush);
if (defaultExtent) gb.call(brush.move, defaultExtent.map(x));

// function brushed(event) {

// svg.node().value =
// event.selection === null ? null : event.selection.map(x.invert);
// svg.node().dispatchEvent(new Event("input", { bubbles: true }));
// const selection = event.selection;
// }

function brushed({ selection }) {
// mutable debug2 = selection;
if (selection) {
// console.log("fire1");
var s = selection || x.range();

svg.property("value", selection.map(x.invert, x).map(d3.utcMonth.ceil));
svg.dispatch("input");

if (
((s[1] - s[0]) / width) * 120 > 30 ||
((s[1] - s[0]) / width) * 120 < 2
) {
gb.call(brush.move, [previousS0, previousS1]);
return;
}
previousS0 = s[0];
previousS1 = s[1];
}
}

function brushended({ selection }) {
if (!selection) {
// mutable debug2 = defaultSelection;
gb.call(brush.move, [previousS0, previousS1]); // changed from defaultSelection so that a single click doesn't mess things up
}
}
// svg.node().value = defaultExtent ? defaultExtent : null;
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
mutable type = "All"
Insert cell
Insert cell
viewof reset = Inputs.button("Reset map", {
value: null,
// width: 100,
reduce: () => {
mutable selectedSite = null;
mutable type = "All";
// fishWithPics.map(preview);
d3.selectAll(".badge").style("border", "3px solid #e8e8e8");
d3.select("#All").style("border", "3px solid #ec4977");
const container = map.getContainer();
container.value.selected = null;
container.dispatchEvent(new CustomEvent("input", { bubbles: true }));
}
})
Insert cell
Insert cell
updateLayer = () => {
// mutable debugLimits = focus;

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.7
});
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: 1
});
});
});
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
});
});
// console.log(outdata);
const datageoZoom = outdata.map((d, i) => {
let sel = {};
if (selected && selected[0]) {
sel = selected[0];
}

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"
};
});

let fc = {
type: "FeatureCollection",
features: datageoZoom.sort((a, b) => b.properties.r - a.properties.r)
};
if (map._loaded) {
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;
}
}
Insert cell
runZoomUpdate = {
map.on("viewreset", updateLayer);
map.on("move", updateLayer);
map.on("moveend", updateLayer);
map.on("click", "stories", function (e) {
// console.log(e);
// set(viewof selectedSite, e.features[0].properties.site);
mutable selectedSite = e.features[0].properties.site;
mutable selectedGroup = type;
const container = map.getContainer();
container.value.selected = {
lng: e.lngLat.lng,
lat: e.lngLat.lat,
data: e.features[0].properties
};
container.dispatchEvent(new CustomEvent("input", { bubbles: true }));
});
map.on("dblclick", "stories", function (ee) {
// clicking outside map unselects site
console.log(ee);
mutable selectedSite = null;
const container = map.getContainer();
container.value.selected = null;
container.dispatchEvent(new CustomEvent("input", { bubbles: true }));
});
invalidation.then(() => {
// console.log("invalidation cell");
map.off("viewreset", updateLayer);
map.off("move", updateLayer);
map.off("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
circleLocations = {
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
});
});
return outdata;
}
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": "rgb(232,97,36)",
"circle-stroke-width": ["get", "stroke"],
"circle-radius": ["get", "radius"],
"circle-color": ["get", "color"],
"circle-opacity": ["get", "opacity"]
},
layout: {}
};
}
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
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
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
focus = [
formatMonth(d3.timeMonth.floor(focus1[0])) + "-01",
formatMonth(d3.timeMonth.ceil(focus1[1])) + "-01"
]
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
html`<style>
.rectImages {
grid-template-columns: repeat(auto-fill, minmax(160px, 5fr))}
</style>`

Insert cell
style = html`<style>
// .annotation path {
// stroke: "pink";
// stroke-width: 1.5
// }
// .annotation text {
// font-size: 0.675em;
// // font: italic 14px sanserif;
// // font-weight: 100;
// }
// .annotation path {
// fill: rgba(255, 0, 0, 1);
// fill-opacity: 0
// }
// circle.handle {
// stroke: crimson;
// stroke-width: 2;
// stroke-dasharray: 0;
// fill: rgba(255, 0, 0, 1);
// cursor: move;
// }
rect.selection {
fill: white;
fill-opacity:.6
}
</style>`
Insert cell
Insert cell
dataRolled = await FileAttachment("dataRolled.json").json()
// .map((d) => (d.date = new Date(d.date)))
Insert cell
familyGroupings = {
const raw = d3.tsvParse(
await FileAttachment("Groups_July8b,2021 (1)-3-1.txt").text(),
d3.autoType
);
const clean = raw.filter((d) => d["Common Name"] != undefined); // remove blank lines
return clean;
}
Insert cell
metadata = {
const raw = d3.tsvParse(
await FileAttachment("Sample_metadata_Plates1-7.txt").text(),
d3.autoType
);
const clean = raw.filter((d) => d.Sample_ID != undefined); // remove blank lines
return clean;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
import { Select, Text, bind } from "@observablehq/inputs"
Insert cell
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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more