NOME_UF = Object.fromEntries("AC:Acre|AL:Alagoas|AP:Amapá|AM:Amazonas|BA:Bahia|CE:Ceará|DF:Distrito Federal|ES:Espírito Santo|GO:Goiás|MA:Maranhão|MT:Mato Grosso|MS:Mato Grosso do Sul|MG:Minas Gerais|PA:Pará|PB:Paraíba|PR:Paraná|PE:Pernambuco|PI:Piauí|RJ:Rio de Janeiro|RN:Rio Grande do Norte|RS:Rio Grande do Sul|RO:Rondônia|RR:Roraima|SC:Santa Catarina|SP:São Paulo|SE:Sergipe|TO:Tocantins".split("|").map(x => x.split(":")))por_uf = Object.keys(NOME_UF).map(uf => {
const sp = sel.filter(d => d.ufs_lista.includes(uf));
const sem = sp.filter(eh_sem).length;
return {uf, nome: NOME_UF[uf], total: sp.length, com: sp.filter(d => d.situacao === "com DNA").length,
verificar: sp.filter(d => d.situacao === "a verificar").length,
sem, pct: sp.length ? sem / sp.length : 0};
})geo = {
// A malha do IBGE segue a regra da mão direita (RFC 7946); o d3-geo espera o
// sentido oposto. Inverte os anéis de cada polígono que "cobre o mundo".
const g = await FileAttachment("data-raw/uf_ibge.geojson").json();
for (const f of g.features) {
if (d3.geoArea(f) > 2 * Math.PI) {
const polys = f.geometry.type === "Polygon" ? [f.geometry.coordinates] : f.geometry.coordinates;
for (const p of polys) for (const r of p) r.reverse();
}
}
return g;
}
Mapa
{
const idx = new Map(por_uf.map(d => [d.uf, d]));
const feats = geo.features.map(f => ({...f, properties: {...f.properties, ...idx.get(COD_UF[f.properties.codarea])}}));
const fc = {type: "FeatureCollection", features: feats};
return Plot.plot({
width: 620, height: 600, style: {fontSize: "13px"},
projection: {type: "mercator", domain: fc},
color: {type: "linear", range: ["#cde2fb", "#0d366b"], legend: true, label: metrica,
tickFormat: metrica === "% sem DNA" ? fmtpct : fmtn},
marks: [
Plot.geo(fc, {fill: f => valor(f.properties), stroke: "white", strokeWidth: 0.8,
tip: true,
title: f => `${f.properties.nome}\n${fmtn(f.properties.total)} espécies\n${fmtn(f.properties.sem)} sem DNA (${fmtpct(f.properties.pct)})\n${fmtn(f.properties.verificar)} a verificar`}),
Plot.text(fc.features, Plot.centroid({text: f => f.properties.uf, fill: "#0b0b0b", stroke: "white", strokeWidth: 3, fontSize: 11}))
]
});
}
Tabela por estado
Inputs.table(por_uf.slice().sort((a, b) => d3.descending(valor(a), valor(b))), {
columns: ["nome", "total", "verificar", "sem", "pct"],
header: {nome: "Estado", total: "Espécies", com: "Com DNA", verificar: "A verificar",
sem: com_verificar ? "Sem DNA + a verificar" : "Sem DNA", pct: "% sem DNA"},
format: {total: fmtn, com: fmtn, verificar: fmtn, sem: fmtn, pct: fmtpct},
width: {nome: 150}, rows: 27, layout: "auto", select: false
})