Public
Edited
Mar 16, 2023
Insert cell
// Import D3 and TopoJSON libraries
d3 = require("d3-geo@3", "d3-geo-projection@4")

Insert cell
topojson = require("topojson-client@3")
Insert cell
// Import projectionInput
import {projectionInput} from "@d3/projection-comparison"

Insert cell
import {slider} from "@jashkenas/inputs"

Insert cell
d3_scale = require("d3-scale@4")

Insert cell
// Data and configuration
outline = ({type: "Sphere"})


Insert cell
d3_array = require("d3-array@3")

Insert cell
graticule = d3.geoGraticule10()

Insert cell
world = FileAttachment("land-50m.json").json()

Insert cell
land = topojson.feature(world, world.objects.land)

Insert cell
height = 480

Insert cell
width = 960

Insert cell
fixedDate = new Date("2021-01-01");

Insert cell
projection = d3.geoMercator().fitSize([width, height], outline)

Insert cell
airQualityData = await fetchAirQualityData(fixedDate)
Insert cell
minPM25 = d3_array.min(airQualityData, d => d.value);

Insert cell
maxPM25 = d3_array.max(airQualityData, d => d.value);

Insert cell
map = {
const context = DOM.context2d(width, height);
const path = d3.geoPath(projection, context);

// Fetch air quality data for the fixed date
const airQualityData = await fetchAirQualityData(fixedDate);

// Calculate the minimum and maximum PM2.5 values in the data
const minPM25 = d3.min(airQualityData, d => d.value);
const maxPM25 = d3.max(airQualityData, d => d.value);

// Create a color scale based on the actual data domain
const colorScale = d3_scale.scaleSequential()
.domain([minPM25, maxPM25])
.interpolator(d3_scale.interpolateRainbow);

context.save();
context.beginPath(), path(outline), context.clip(), context.fillStyle = "#fff", context.fillRect(0, 0, width, height);
context.beginPath(), path(graticule), context.strokeStyle = "#ccc", context.stroke();
context.beginPath(), path(land), context.fillStyle = "#000", context.fill();

// Draw air quality data
drawAirQualityData(context, path, airQualityData, projection, colorScale);

context.restore();
context.beginPath(), path(outline), context.strokeStyle = "#000", context.stroke();
return context.canvas;
}

Insert cell
async function fetchAirQualityData(timestamp) {
const date = new Date(timestamp);
const dateString = date.toISOString().split("T")[0];
const url = `https://api.openaq.org/v2/measurements?date_from=${dateString}&date_to=${dateString}&limit=10000&parameter=pm25`;
const response = await fetch(url);
const data = await response.json();
return data.results;
}

Insert cell
function drawAirQualityData(context, path, airQualityData, projection, colorScale) {
if (!airQualityData) return;

airQualityData.forEach(d => {
const [x, y] = projection([d.coordinates.longitude, d.coordinates.latitude]);
context.beginPath();
context.arc(x, y, 2, 0, 2 * Math.PI);
context.fillStyle = colorScale(d.value);
context.fill();
});
}

Insert cell
colorScale = d3_scale.scaleSequential()
.domain([0, 200]) // PM2.5 values ranging from 0 to 200
.interpolator(d3_scale.interpolateRainbow);

Insert cell
date = new Date("2021-01-01"); // Replace this with the desired date

Insert cell
dateString = date.toISOString().split("T")[0];


Insert cell
csvData = FileAttachment("openaq.csv").text()

Insert cell
data = d3.c(csvData, (d, i) => {
if (i === 0) return null; // Skip header row
return {
CountryCode: d[0],
City: d[1],
Location: d[2],
Coordinates: d[3],
Pollutant: d[4],
SourceName: d[5],
Unit: d[6],
Value: +d[7],
LastUpdated: d[8],
CountryLabel: d[9]
};
}).filter(d => d && d.Pollutant === "PM2.5")
Insert cell
d3.csv(csvData)
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