tetrapoDNA
  • Visão geral
  • Espécies sem DNA
  • Por estado
  • Adote uma espécie
  • Fontes e método
Por estado
cobertura_bruta = FileAttachment("data/cobertura.csv").csv({typed: true})
adocoes = FileAttachment("data/adocoes.csv").csv({typed: true})
status = FileAttachment("data/status.json").json()

REPO = "{{< meta website.repo-url >}}"

ROTULO_GRUPO = new Map([
  ["anfibios", "Anfíbios"], ["repteis", "Répteis"],
  ["aves", "Aves"], ["mamiferos", "Mamíferos"]
])
OPCOES_GRUPO = ["Todos", ...ROTULO_GRUPO.values()]
GRUPO_POR_ROTULO = new Map([...ROTULO_GRUPO].map(([k, v]) => [v, k]))

// Cores de situação (paleta de status: bom / atenção / crítico) e de grupo.
SITUACOES = ["com DNA", "a verificar", "sem DNA"]
COR_SITUACAO = ["#0ca30c", "#fab219", "#d03b3b"]
COR_GRUPO = ["#2a78d6", "#eb6834", "#1baf7a", "#eda100"]

AMEACADAS = new Set(["CR", "EN", "VU"])

cobertura = {
  const adot = new Map(adocoes.map(a => [a.especie, a]));
  return cobertura_bruta.map(d => {
    const a = adot.get(d.especie);
    const ufs = d.ufs ? String(d.ufs).split(";") : [];
    const cat = d.categoria_ameaca ?? "";
    return {
      ...d,
      grupo_rotulo: ROTULO_GRUPO.get(d.grupo),
      ufs_lista: ufs,
      endemica: d.endemica_br === true || d.endemica_br === "TRUE",
      ameacada: AMEACADAS.has(cat),
      adocao: a ? a.situacao : null,
      adotada_por: a ? a.instituicao : null,
      // prioridade: ameaçada > DD > endêmica > descrita há mais tempo
      prioridade: (AMEACADAS.has(cat) ? 0 : cat === "DD" ? 1 : 2) * 10 +
                  (d.endemica_br === true || d.endemica_br === "TRUE" ? 0 : 1)
    };
  });
}

filtra_grupo = (dados, rotulo) =>
  rotulo === "Todos" ? dados : dados.filter(d => d.grupo === GRUPO_POR_ROTULO.get(rotulo))

fmt = d3.format(",d")
fmtpct = d3.format(".0%")
fmtn = n => n == null ? "" : fmt(n).replace(/,/g, ".")

conta = dados => {
  const c = d3.rollup(dados, v => v.length, d => d.situacao);
  return {
    total: dados.length,
    com: c.get("com DNA") ?? 0,
    verificar: c.get("a verificar") ?? 0,
    sem: c.get("sem DNA") ?? 0
  };
}

link_adote = especie =>
  `${REPO}/issues/new?template=adote.yml&title=${encodeURIComponent("Adoção: " + especie)}&especie=${encodeURIComponent(especie)}`

selo_situacao = s => {
  const cls = s === "com DNA" ? "com" : s === "sem DNA" ? "sem" : "verificar";
  const ico = s === "com DNA" ? "✓" : s === "sem DNA" ? "✕" : "?";
  return htl.html`<span class="situacao ${cls}">${ico} ${s}</span>`;
}
sel = filtra_grupo(cobertura, grupo)
sem_uf = sel.filter(d => d.ufs_lista.length === 0).length
eh_sem = d => d.situacao === "sem DNA" || (com_verificar && d.situacao === "a verificar")
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(":")))
COD_UF = Object.fromEntries("11:RO|12:AC|13:AM|14:RR|15:PA|16:AP|17:TO|21:MA|22:PI|23:CE|24:RN|25:PB|26:PE|27:AL|28:SE|29:BA|31:MG|32:ES|33:RJ|35:SP|41:PR|42:SC|43:RS|50:MS|51:MT|52:GO|53:DF".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};
})
valor = d => metrica === "% sem DNA" ? d.pct : metrica === "espécies na lista" ? d.total : d.sem
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
})
viewof grupo = Inputs.radio(OPCOES_GRUPO, {label: "Grupo", value: "Todos"})
viewof metrica = Inputs.radio(["espécies sem DNA", "% sem DNA", "espécies na lista"], {label: "Cor do mapa", value: "espécies sem DNA"})
viewof com_verificar = Inputs.toggle({label: "Somar \"a verificar\" às sem DNA", value: false})
md`Uma espécie entra em todos os estados onde ocorre. Ocorrência por UF: listas de anfíbios (amphiBR, pelo nome de 2021; espécies novas pelo CTFB), répteis (ReptiliaBR) e mamíferos (SBMz); para aves, o CTFB.

<span class="nota-pequena">${fmtn(sem_uf)} espécies do recorte não têm UF em nenhuma fonte e ficam fora do mapa.</span>`

Dados: BOLD Systems, NCBI GenBank, CTFB/JBRJ e listas da SBH, CBRO e SBMz (ver Fontes).

 
  • Criar uma issue
  • Ver o código fonte

Código MIT · Dados CC-BY 4.0