Published
Edited
Sep 2, 2020
6 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
Insert cell
// viewof time = Scrubber(hours, { delay: 200, format: d => "" })
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-2020083100.json").json()
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
hexdata = d3.csvParse(await FileAttachment("hexbin-2020083100.csv").text(), d3.autoType)
Insert cell
hexbyhour = d3.group(hexdata, d => d.i)
Insert cell
hexbyhour.get(0)
Insert cell
hextime = hexbyhour.get(time2.value)
Insert cell
nexthextime = hexbyhour.get(time2.next)
Insert cell
loc = function(d) { return d.lng + "|" + d.lat }
Insert cell
hexbylocation = d3.group(hexdata, loc)
Insert cell
hexgroup = d3.group(hextime, loc)
Insert cell
nextgroup = d3.group(nexthextime, loc)
Insert cell
together = d3.group(hextime.concat(nexthextime), loc)
Insert cell
fullhexbingeo = Array.from(together).map((l,i) => {
let sel = {}
if(selected && selected[0]) { sel = selected[0]}
let pair = l[1]
let d = { ...pair[0]}
if(pair.length == 2) {
d.pm1 = d3.interpolate(pair[0].pm1, pair[1].pm1)(time2.t)
} else if(d.i == time) {
d.pm1 = d3.interpolate(pair[0].pm1, 0)(time2.t)
} else {
d.pm1 = d3.interpolate(0, pair[0].pm1)(time2.t)
}
// let nd = nexthextime.get(d.lng+"|"+d.lat)
// let pm1 = d3.interpolate(d.pm1, nd ? nd[0].pm1 : 0)(time2.t)
return {
id: i,
properties: {
lat: d.lat,
lng: d.lng,
pm1: d.pm1,
date: now,
stroke: d.lat == sel.lat && d.lng == sel.lng ? 2 : 0,
pm1color: colorSmoke(d.pm1),
pm1opacity: opacityPM1(d.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.scaleLinear()
.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
import {Keyframer} from "@enjalot/keyframe-scrubber"
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