Published
Edited
Sep 8, 2020
1 fork
17 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
localFormat(d3.timeHour.floor(new Date()))
Insert cell
timeFormat(getHour(time))
Insert cell
barheight = 150
Insert cell
hexbylocation
Insert cell
delaunay = d3.Delaunay.from(hexdata, d => projection([d.lng, d.lat])[0], d => projection([d.lng, d.lat])[1])
Insert cell
sel2p = {
let p = projection([-122.277,37.8666])
return hexdata[delaunay.find(p[0],p[1])]
}
Insert cell
sel2 = hexbylocation.get(sel2p.lng + "|" + sel2p.lat) || []
Insert cell
Bars(sel2)
Insert cell
sel3p = {
let p = projection([-122.2501, 37.8295])
return hexdata[delaunay.find(p[0],p[1])]
}
Insert cell
sel3 = hexbylocation.get(sel3p.lng + "|" + sel3p.lat) || []
Insert cell
Bars(sel3)
Insert cell
Insert cell
Insert cell
Insert cell
rundata = FileAttachment("latest_run-2020090712.json").json()
Insert cell
hexdata = d3.csvParse(await FileAttachment("hexbin-2020090712.csv").text(), d3.autoType)
Insert cell
runtime = new Date(rundata.model_initialization_time)
Insert cell
now = getHour(time)
Insert cell
getHour = t => d3.timeHour.offset(runtime, t)
Insert cell
hexbyhour = d3.group(hexdata, d => d.i)
Insert cell
hexbyhour.get(0)
Insert cell
hexbylocation = d3.group(hexdata, d => d.lng + "|" + d.lat)
Insert cell
hextime = hexbyhour.get(time)
Insert cell
// nexthextime = hexbyhour.get(time2.next)
Insert cell
fullhexbingeo = hextime.map((d,i) => {
let sel = {}
if(selected && selected[0]) { sel = selected[0]}
let pm1 = d.pm1
return {
id: i,
properties: {
lat: d.lat,
lng: d.lng,
pm1: pm1,
date: now,
stroke: d.lat == sel.lat && d.lng == sel.lng ? 2 : 0,
pm1color: colorSmoke(pm1),
pm1opacity: opacityPM1(pm1),
},
geometry: {type: "Point", coordinates: [d.lng, d.lat]},
type: "Feature"
}
})
Insert cell
center = projection.invert([width/2, height/2])
Insert cell
metersToPixelsAtMaxZoom = (meters, latitude) =>
meters / 0.075 / Math.cos(latitude * Math.PI / 180)
Insert cell
hexbinLayer = {

return {
id: "hexbins",
type: "circle",
source: "hexbins",
paint: {
// keep the circles the same size
// https://stackoverflow.com/questions/37599561/drawing-a-circle-with-the-radius-in-miles-meters-with-mapbox-gl-js
"circle-radius": {
"stops": [[0,0], [11, metersToPixelsAtMaxZoom(2, center[1])]],
base: 2
},
"circle-stroke-color": "blue",
"circle-stroke-width": ["get", "stroke"],
"circle-color": ["get", "pm1color"],
"circle-opacity": ["get", "pm1opacity"]
}
}
}
Insert cell
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: fullhexbingeo
}
if(map._loaded) {
if(!map.getSource("hexbins")) {
map.addSource("hexbins", {
type: "geojson",
data: fc
});
} else {
// console.log("setting source")
map.getSource("hexbins").setData(fc)
}
if (map.getLayer(hexbinLayer.id)) {
map.removeLayer(hexbinLayer.id);
}
// console.log("adding layer")
map.addLayer(hexbinLayer);
// map.flyTo(projection.invert([100,1000]))
// console.log("flying")
// }
return true
}
return false
}
Insert cell
map._loaded
Insert cell
import {california, countyShapes} with {height as height, hexbin as hexbin} from "@enjalot/air-quality-explorations"
Insert cell
// Choose a couple counties that define the extent I care about
countyExtent = ({
type: "FeatureCollection",
// features: countyShapes.features.filter(d => (
// d.properties.name == "Tehama" ||
// d.properties.name == "San Benito") && d.id.slice(0,2) == "06")
features: countyShapes.features.filter(d => (
d.properties.name == "Marin" ||
d.properties.name == "Alameda") && d.id.slice(0,2) == "06")
// features: countyShapes.features.filter(d => (
// d.properties.name == "Marin" ||
// d.properties.name == "Santa Clara") && d.id.slice(0,2) == "06")

})
Insert cell
widthRatio = width / 955
Insert cell
heightRatio = height / 500
Insert cell
projection = d3.geoAlbersUsa()
.fitSize([width, height], countyExtent)
Insert cell
originalScale = 25355.18980109889
Insert cell
scaleRatio = projection.scale() / originalScale
Insert cell
pixelRadius = 10 * scaleRatio * d3.min([widthRatio, heightRatio])
Insert cell
hexbin = d3.hexbin().extent([[0, 0], [width, height]]).radius(pixelRadius)
Insert cell
hours = d3.range(0, Array.from(hexbyhour.keys()).length)
Insert cell
height = 500
Insert cell
legend({color: colorPM1})
Insert cell
colorPM1 = d3.scaleThreshold()
.domain([50, 100, 150, 200, 250])
.range(["green", "yellow", "orange", "red", "maroon", "maroon"])
Insert cell
legend({color: colorSmoke})
Insert cell
colorSmoke = d3.scaleThreshold()
.domain([50, 100, 150, 200, 250])
.range(["#eee", "#aaa", "#999", "#666", "#222", "#222"])
Insert cell
opacityPM1 = d3.scaleLinear()
.domain([0,50, 100, 150, 200, 250])
.range([0.1, 0.5, 0.75, 0.9, 0.9, 0.9])
Insert cell
numFormat = d3.format(",d")
Insert cell
timeFormat = d3.utcFormat("%Y-%m-%d %H:%M")
Insert cell
localFormat = d3.timeFormat("%Y-%m-%d %H:%M")
Insert cell
import {Scrubber} from "@mbostock/scrubber"
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

Insert cell
import {legend} from "@d3/color-legend"
Insert cell
d3 = require("d3@6.0.0-rc.3", "d3-hexbin@0.2")
Insert cell
Insert cell
mapboxgl = {
const gl = await require("mapbox-gl@1");
if (!gl.accessToken) {
// https://www.mapbox.com/help/how-access-tokens-work/
gl.accessToken = token;
const href = await require.resolve("mapbox-gl@1/dist/mapbox-gl.css");
document.head.appendChild(html`<link href=${href} rel=stylesheet>`);
}
return gl;
}
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