Published
Edited
Jun 22, 2021
3 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
delay = 800 // ms
Insert cell
autoplay = true
Insert cell
loop = false
Insert cell
Insert cell
<style>
.numbers, .country {
border-bottom: 1px solid rgb(150,150,150);
display: grid;
grid-template-columns: 50% 50%;
}
.country {
background: rgb(252,252,252);
position: relative;
}
.name {
position: absolute;
top: 3px;
left: 3px;
font-size: 13px;
color: rgb(100,100,100);
width: 130px;
}
.releasedNumber {
text-align: right;
}
.detained, .detainedNumber {
border-right: 2px solid black;
}
.detained, .released {
display: inline-block;
display: grid;
grid-gap: 1px;
grid-template-rows: repeat(5,8px);
grid-template-columns: repeat(auto-fill,8px);
grid-auto-flow: column;
}
.detained {
justify-content: right;
direction: rtl;
}
.detainee {
background-color: #ffd289;
width: 8px;
}
.detained .dead, .key .dead {
background-color: #7c152a;
}
.key .detainee {
width: 9px;
height: 9px;
display: inline-block;
}
.key {
font-size: 13px;
color: rgb(100,100,100);
}
@media screen and (max-width: 900px) {
.detained, .released {
grid-template-rows: repeat(10,8px);
grid-template-columns: repeat(auto-fill,8px);
}
}
@media screen and (max-width: 400px) {
.detained, .released {
grid-template-rows: repeat(13,8px);
grid-template-columns: repeat(auto-fill,8px);
}
}
</style>
Insert cell
Insert cell
died = ({ death_year }) => year >= +death_year && +death_year !== 0
Insert cell
released = ({ arrival_year, release_year }) =>
year >= +arrival_year && year >= +release_year && +release_year > 0
Insert cell
detained = ({ arrival_year, release_year }) =>
year >= +arrival_year && (year < +release_year || +release_year === 0)
Insert cell
Insert cell
renderDetainee = (detainee) =>
`<div class="detainee ${died(detainee) ? "dead" : ""}" title="${
detainee.name
}"></div>`
Insert cell
Insert cell
proxy = (url) => `https://zubakskees-cors-proxy.herokuapp.com/${url}`
Insert cell
detaineeHtml = await (
await fetch(
proxy(
"https://www.nytimes.com/interactive/2021/us/guantanamo-bay-detainees.html"
)
)
).text()
Insert cell
detaineeDoc = new DOMParser().parseFromString(detaineeHtml, "text/html")
Insert cell
detaineeRows = [...detaineeDoc.querySelectorAll(".g-row")]
Insert cell
detaineeDetailsObject = await (
await fetch(
"https://static01.nyt.com/newsgraphics/2021/01/20/guantanamo-docket/bcce6c9de959335da7d9348cc257bbfd1e2ded31/detaineeDocs.json"
)
).json()
Insert cell
detaineeDetails = Object.values(detaineeDetailsObject).map(
({ detainee }) => detainee
)
Insert cell
detainees = detaineeRows
.map((row) =>
[...row.querySelectorAll(".g-cell")].map((cell) => cell.textContent)
)
.map((row) => ({
isn: row[0],
name: row[1],
citizenship: row[3],
arrival_year: row[4],
...detaineeDetails.find(({ isn }) => isn == row[0])
}))
Insert cell
Insert cell
firstYear = 2001
Insert cell
lastYear = new Date().getFullYear()
Insert cell
years = Array(lastYear - firstYear + 1)
.fill()
.map((element, index) => index + firstYear)
Insert cell
Insert cell
detaineesByCountry = _(detainees)
.groupBy("citizenship")
.entries()
.map(([name, detainees]) => ({ name, detainees }))
.orderBy(({ detainees }) => detainees.length, "desc")
.value()
Insert cell
topCountries = detaineesByCountry.slice(0, 4)
Insert cell
otherCountries = detaineesByCountry.slice(4)
Insert cell
countryCategories = topCountries.concat([
{
name: "Other (incluing those with dual citizenship)",
detainees: otherCountries.flatMap((country) => country.detainees)
}
])
Insert cell
Insert cell
import { Scrubber } from "@mbostock/scrubber"
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