Public
Edited
Mar 5, 2023
Insert cell
Insert cell
testp1 = Scrubber(datesp1, {
autoplay: false, loop: false,
format: date => date.toLocaleString("en", {month: "long", day: "numeric"})
})
Insert cell
chartp1 = {
const svg = d3.create("svg")
.style("display", "block")
.attr("viewBox", [0, 0, widthp1 + 25, heightp1 + 25]);

const defs = svg.append("defs");

defs.append("path")
.attr("id", "outline")
.attr("d", path(outline));

defs.append("clipPath")
.attr("id", "clip")
.append("use")
.attr("xlink:href", new URL("#outline", location));

const g = svg.append("g")
.attr("clip-path", `url(${new URL("#clip", location)})`);

g.append("use")
.attr("xlink:href", new URL("#outline", location))
.attr("fill", "#edfbff");
let animationstate = testp1.b.textContent

var div = d3.select("body").append("div")
.attr("class", "tool-tip")
.style("opacity", 0);
g.selectAll("path")
.append("g")
.data(countriesp1.features)
.join("path")
.attr("fill", d => color(currentDatap1.get(d.properties.name)))
.attr("d", path)
.on('mouseover', function(d, i) {
if(animationstate == "Play"){
d3.select(this).transition()
.duration('50')
.attr('opacity', '.75');
div.transition()
.duration(50)
.style("opacity", 1);
div.html(`${d.properties.name} <br> ${currentDatap1.has(d.properties.name) && currentDatap1.get(d.properties.name) != "N/A" ? "AQI: " + currentDatap1.get(d.properties.name) : "N/A"}`)
.style("left", (d3.event.pageX + 10) + "px")
.style("top", (d3.event.pageY - 15) + "px");
}})
.on('mouseout', function(d, i) {
d3.select(this).transition()
.duration('50')
.attr('opacity', '1');
div.transition()
.duration(50)
.style("opacity", 0);});

g.append("path")
.datum(topojson.mesh(worldp1, worldp1.objects.countries1, (a, b) => a !== b))
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-linejoin", "round")
.attr("d", path);
const graticule = g.append("path")
.attr("class", "graticule")
.attr("d", path(d3.geoGraticule10()));
svg.call(d3.zoom()
.extent([[0, 0], [widthp1, heightp1]])
.scaleExtent([1, 8])
.on("zoom", zoomed));
function zoomed() {
var e = d3.event,
tx = Math.min(0, Math.max(e.transform.x, widthp1 - widthp1 * e.transform.k)),
ty = Math.min(0, Math.max(e.transform.y, heightp1 - heightp1 * e.transform.k));
e.transform.translate([tx, ty]);
g.attr("transform", [
"translate(" + [tx, ty] + ")",
"scale(" + e.transform.k + ")"].join(" "));
}
return svg.node();
}
Insert cell
worldp1 = FileAttachment("world-countries_new.json").json()
Insert cell
widthp1 = 975

Insert cell
heightp1 = 342

Insert cell
projection = d3.geoEqualEarth()
Insert cell
outline = ({type: "Sphere"})
Insert cell
//dates = d3.set(data.map(function(d){return d.date; })).values()
datesp1 = Array.from({length: datevaluesp1.length}, (_, i) => {
const date = new Date(2020, 0, 1);
date.setDate(i + 1);
date => date.toLocaleString("en", {month: "long", day: "numeric"});
return date;
})
Insert cell
datevaluesp1 = Array.from(d3.rollup(datap1, ([d]) => d.AQI, d => +d.date, d => d.Country))
.map(([date, data]) => [new Date(date), data])
.sort(([a], [b]) => d3.ascending(a, b))
Insert cell
datap1 = d3.csvParse(await FileAttachment("AQI_longer_MayUpdate_new.csv").text(), d3.autoType)
Insert cell
countriesp1 = topojson.feature(worldp1, worldp1.objects.countries1)
Insert cell
import {Scrubber} from "@mbostock/scrubber"
Insert cell
color = d3.scaleSequential()
.domain(d3.extent(airQuality))
.interpolator(d3.interpolateCubehelix("orange", "purple"))//d3.interpolateHsl("orange", "purple"))
.unknown("#ccc")
Insert cell
airQuality = new Set(datap1.map(d => d.AQI))
Insert cell
currentDatap1 = Object.assign(dataAt(datescrubp1))
Insert cell
viewof datescrubp1 = new View(datesp1[0])
Insert cell
path = d3.geoPath(projection)
Insert cell
class View {
constructor(value) {
Object.defineProperties(this, {
_list: {value: [], writable: true},
_value: {value, writable: true}
});
}
get value() {
return this._value;
}
set value(value) {
this._value = value;
this.dispatchEvent(new CustomEvent("input", {detail: value}));
}
addEventListener(type, listener) {
if (type != "input" || this._list.includes(listener)) return;
this._list = [listener].concat(this._list);
}
removeEventListener(type, listener) {
if (type != "input") return;
this._list = this._list.filter(l => l !== listener);
}
dispatchEvent(event) {
const p = Promise.resolve(event);
this._list.forEach(l => p.then(l));
}
bind(input, invalidation) {
return bind(input, this, invalidation);
}
}
Insert cell
function bind(input, view, invalidation = disposal(input)) {
input.value = view.value;
input[`on${eventof(input)}`] = () => view.value = valueof(input);
const update = ({detail: value}) => valueof(input) === value || (input.value = value);
view.addEventListener("input", update);
invalidation.then(() => view.removeEventListener("input", update));
return input;
}
Insert cell
function valueof(input) {
switch (input.type) {
case "range":
case "number": return input.valueAsNumber;
case "date": return input.valueAsDate;
case "checkbox": return input.checked;
case "file": return input.multiple ? input.files : input.files[0];
default: return input.value;
}
}
Insert cell
function eventof(input) {
switch (input.type) {
case "button":
case "submit":
case "checkbox": return "click";
case "file": return "change";
default: return "input";
}
}
Insert cell
import {disposal} from "@mbostock/disposal"
Insert cell
function dataAt(date) {
let index = datesp1.indexOf(date);
return datevaluesp1[index][1];
}
Insert cell
viewof datescrubp1.bind(testp1)
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