Published
Edited
Sep 8, 2020
4 stars
Insert cell
Insert cell
air2 = d3
.json(
"https://www.purpleair.com/data.json?opt=1/mAQI/a10/cC0&fetch=true&nwlat=43.167493434353474&selat=32.25997597364069&nwlng=-127.87343156694496&selng=-114.15416006614362&fields=pm_1"
)
.then(d =>
Object.assign(
d.data.map(b => Object.fromEntries(d.fields.map((f, i) => [f, b[i]]))),
{ columns: d.fields }
)
)
Insert cell
air = FileAttachment("air.json").json()
Insert cell
d3 = require("d3@6", "d3-geo-voronoi@1")
Insert cell
topojson = require("topojson-client@3")
Insert cell
us = d3.json("https://unpkg.com/us-atlas@3/counties-10m.json")
Insert cell
stateShapes = topojson.feature(us, us.objects.states)//.features
Insert cell
countyShapes = topojson.feature(us, us.objects.counties)
Insert cell
statesByName = new Map(stateShapes.features.map(d => [d.properties.name, d]))
Insert cell
california = statesByName.get("California")
Insert cell
// Choose a couple counties that define the extent I care about
countyExtent = ({
type: "FeatureCollection",
features: countyShapes.features.filter(
d =>
(d.properties.name == "Marin" || d.properties.name == "Santa Clara") &&
d.id.slice(0, 2) == "06"
)
})
Insert cell
caliCounties = countyShapes.features.filter(d => d.id.slice(0, 2) == "06")
Insert cell
// projection = d3.geoAlbersUsa().fitSize([width, height], california)
projection = d3.geoAlbersUsa().fitSize([width, height], countyExtent)
// projection = d3.geoAlbersUsa().fitSize([width, height], stateShapes)
// projection = d3.geoMercator().fitSize([width, height], countyExtent)
Insert cell
height = 500
Insert cell
projection([-121.547, 37.56])
Insert cell
map = {
let canvas = d3.create("canvas").node();
canvas.width = width;
canvas.height = height;

let ctx = canvas.getContext("2d");
// ctx.globalCompositeOperation = "overlay";

ctx.fillStyle = "#111";
ctx.fillRect(0, 0, width, height);

ctx.globalAlpha = 0.5;

// setting up the path function
let path = d3.geoPath(projection).context(ctx);

// rotating the canvas 90 degrees to the left
ctx.translate(width / 2, height / 2);
ctx.rotate((-90 * Math.PI) / 180);
ctx.translate(-width / 2, -height / 2);

ctx.strokeStyle = "#fff";
ctx.fillStyle = "#ccc";
ctx.beginPath();
path(polygons);
// path({ type: "FeatureCollection", features: caliCounties });
// ctx.fill();
ctx.stroke();

ctx.strokeStyle = "#fff";
ctx.beginPath();
path(california);
ctx.stroke();

for (let sensor of filtered) {
let p = projection([sensor.Lon, sensor.Lat]);
ctx.fillStyle = colorPM1(sensor.pm_1);
ctx.fillRect(p[0], p[1], 3, 3);
}
ctx.globalAlpha = 1;
for (let city of cities) {
// for (let city of uscities) {
let p = projection([city.lng, city.lat]);
if (!p) continue;

ctx.fillStyle = "white";
ctx.font = "bold 10px sans-serif";
ctx.save();

ctx.translate(p[0], p[1]);
ctx.rotate((90 * Math.PI) / 180);
ctx.translate(-p[0], -p[1]);
// ctx.rotate((90 * Math.PI) / 180, p[0], p[1]);
ctx.fillText(city.city, p[0], p[1]);
ctx.restore();
}

return canvas;
}
Insert cell
// loop through each voronoi cell and draw its boundaries
// trying to fill each polygon does not work
// for (let polygon of polygons.features) {
// ctx.lineWidth = 1.5;
// ctx.strokeStyle = "#fff";
// ctx.beginPath();
// path(polygon); // renders to the canvas
// ctx.fillStyle = colorPM1(polygon.properties.site[2]);
// ctx.fill();
// ctx.stroke();
// // ctx.beginPath();
// // path(polygon);
// }
Insert cell
uscities = d3.csvParse(await FileAttachment("uscities.csv").text(), d3.autoType)
Insert cell
d3.bin()(uscities.map(d => d.population))
Insert cell
cities = uscities.filter(d => d.population > 100000)
Insert cell
// cities = [
// {
// name: "San Francisco",
// lonlat: [-122.4726194, 37.7577627]
// }
// ]
Insert cell
colorPM1 = d3
// .scaleSequential(d3.interpolateReds)

// .domain([0, 250])
// .range(["black", "white"])

.scaleLinear()
.domain([0, 50, 100, 150, 200, 250])
.range(["green", "yellow", "orange", "red", "maroon", "maroon"])
Insert cell
d3.group(air, d => d.Type)
Insert cell
filtered = air
.filter(d => d.Lat && d.Lon && d.Type === 0)
.sort((a, b) => b.pm_1 - a.pm_1)
Insert cell
points = filtered.map(d => [d.Lon, d.Lat, d.pm_1])
Insert cell
voronoi = d3.geoVoronoi(points)
Insert cell
mesh = voronoi.cellMesh()
Insert cell
triangles = voronoi.triangles()
Insert cell
polygons = voronoi.polygons()
Insert cell
polygons.features.map(d => d.properties.site[2])
Insert cell
d3.bin()(filtered.map(d => d.pm_1))
Insert cell
d3.extent(filtered, d => d.pm_1)
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