Published
Edited
Sep 13, 2020
2 forks
Importers
29 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// fullhexbin = FileAttachment("ids-all-2020-08-27-22-hexbins.json").json()
hexdata = d3.csvParse(await FileAttachment("ids-all-2020-09-13-10-hexbins.csv").text(), d3.autoType)
Insert cell
hexbytime = d3.group(hexdata, d => d.day)
Insert cell
hex = hexbytime.get(time)
Insert cell
hexgeo = hex.map((d,i) => {
return {
id: i,
properties: {
...d,
pm1color: colorPM1(d.pm1),
pm1opacity: opacityPM1(d.pm1),
p03color: colorP03(d.p03),
p03opacity: opacityP03(d.p03)
},
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], [10, metersToPixelsAtMaxZoom(2, center[1])]],
base: 2
},
// "circle-stroke-color": "#fff",
"circle-stroke-width": 0,
"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: hexgeo
}
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
map.getSource("hexbins")
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
projection.scale()
Insert cell
originalScale = 25355.18980109889
Insert cell
scaleRatio = projection.scale() / originalScale
Insert cell
widthRatio = width / 955
Insert cell
heightRatio = height / 500
Insert cell
projection = d3.geoAlbersUsa()
.fitSize([width, height], countyExtent)
Insert cell
pixelRadius = 10 * scaleRatio * d3.min([widthRatio, heightRatio])
Insert cell
hexbin = d3.hexbin().extent([[0, 0], [width, height]]).radius(pixelRadius)
Insert cell
// fullhexbinmapped = fullhexbin.map(d => {
// let ret = {...d}
// let p = projection([d.lng, d.lat])
// ret.x = p[0]
// ret.y = p[1]
// ret.pm1 = d.pm1
// ret.p03 = d.p03
// ret.pm1color = colorPM1(d.pm1)
// ret.days = new Map(ret.days.map(day => [day.t, day]))
// return ret
// })
Insert cell
// dayExtent = [d3.min(fullhexbin, d => d3.min(d.days,day => day.t)),
// d3.max(fullhexbin, d => d3.max(d.days,day => day.t))]
dayExtent = d3.extent(hexdata, d => d.day)
Insert cell
days =
d3.timeHour
.range(new Date(dayExtent[0]*1000), new Date(dayExtent[1]*1000))
.map(d => +d/1000)

Insert cell
height = 500
Insert cell
colorPM1 = d3.scaleLinear()
.domain([0,50, 100, 150, 200, 250])
.range(["green", "yellow", "orange", "red", "maroon", "maroon"])
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
colorP03 = d3.scaleLinear()
.domain([1,1000, 5000, 10000, 20000, 25000])
.range(["green", "yellow", "orange", "red", "maroon", "maroon"])
Insert cell
opacityP03 = d3.scaleLinear()
.domain([1,1000, 5000, 10000, 20000, 25000])
.range([0.1, 0.5, 0.75, 0.9, 0.9, 0.9])
Insert cell
numFormat = d3.format(",d")
Insert cell
timeFormat = d3.timeFormat("%Y-%m-%d %H:%M")
Insert cell
import {Scrubber} from "@mbostock/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