htl.html`<div class="cc-numeros">
<div><span class="cc-num">${fmtn(n.total)}</span><span class="cc-rotulo">espécies nativas na Flora e Funga do Brasil</span></div>
<div><span class="cc-num">${fmtn(n.com)}</span><span class="cc-rotulo"><b>${pct(n.com)}</b> com DNA público</span></div>
<div class="cc-sem"><span class="cc-num">${fmtn(n.sem)}</span><span class="cc-rotulo"><b>${pct(n.sem)}</b> sem nenhum DNA</span></div>
<div><span class="cc-num">${fmtn(n.verificar)}</span><span class="cc-rotulo">a verificar</span></div>
</div>`cobertura_bruta = FileAttachment("data/site.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([
["angiospermas", "Angiospermas"], ["gimnospermas", "Gimnospermas"],
["samambaias", "Samambaias e licófitas"]
])
OPCOES_GRUPO = ["Todos", ...ROTULO_GRUPO.values()]
GRUPO_POR_ROTULO = new Map([...ROTULO_GRUPO].map(([k, v]) => [v, k]))
// Domínios fitogeográficos da Flora e Funga, do maior para o menor número de espécies.
DOMINIOS = ["Mata Atlântica", "Amazônia", "Cerrado", "Caatinga", "Pampa", "Pantanal"]
FORMAS_VIDA = ["Árvore", "Arbusto", "Subarbusto", "Erva", "Liana/volúvel/trepadeira", "Palmeira",
"Bambu", "Suculenta", "Dracenóide"]
SUBSTRATOS = ["Terrícola", "Rupícola", "Epífita", "Hemiepífita", "Aquática", "Parasita",
"Hemiparasita", "Saprófita"]
// Cores de situação (as mesmas de tema-claro.scss; tons médios que funcionam
// no claro e no escuro): verde-folha / ocre / terracota. E uma cor por grupo.
SITUACOES = ["com DNA", "a verificar", "sem DNA"]
COR_SITUACAO = ["#8cc283", "#d6a23a", "#c4633a"]
COR_GRUPO = ["#3a8a55", "#b8532c", "#5b6f8a"]
// Portaria MMA 1.783/2026: CR, EN e VU (com "(infra)" quando só há infraespecíficos
// listados e "(PE)" para possivelmente extinta).
eh_ameacada = c => /^(CR|EN|VU)/.test(c ?? "")
AMEACADAS = new Set(["CR", "EN", "VU"])
lista_de = v => v ? String(v).split(";") : []
cobertura = {
const adot = new Map(adocoes.map(a => [a.especie, a]));
return cobertura_bruta.map(d => {
const a = adot.get(d.especie);
const cat = d.categoria_ameaca ?? "";
const endemica = d.endemica_br === true || d.endemica_br === "TRUE";
return {
...d,
grupo_rotulo: ROTULO_GRUPO.get(d.grupo),
ufs_lista: lista_de(d.ufs),
biomas_lista: lista_de(d.biomas),
formas_lista: lista_de(d.forma_vida),
substratos_lista: lista_de(d.substrato),
endemica,
ameacada: eh_ameacada(cat),
// leituras brutas no SRA, sem sequência montada (marca, não é situação)
brutos: d.dados_brutos === true || d.dados_brutos === "TRUE",
adocao: a ? a.situacao : null,
adotada_por: a ? a.instituicao : null,
// prioridade: ameaçada > endêmica > descrita há mais tempo (a portaria não traz DD)
prioridade: (eh_ameacada(cat) ? 0 : 1) * 10 + (endemica ? 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,
brutos: dados.filter(d => d.brutos).length
};
}
// Observação completa: nas "sem DNA", site.csv omite a frase padrão.
observacao = d => d.situacao === "sem DNA"
? "Nada encontrado no BOLD ou no GenBank sob este nome e os sinônimos conhecidos" + (d.observacao ? `; ${d.observacao}` : "") + "."
: (d.observacao ?? "")
link_adote = especie =>
`${REPO}/issues/new?template=adote.yml&title=${encodeURIComponent("Adoção: " + especie)}&especie=${encodeURIComponent(especie)}`
link_sra = especie =>
`https://www.ncbi.nlm.nih.gov/sra/?term=${encodeURIComponent('"' + especie + '"[Organism]')}`
link_ffb = id => id ? `https://floradobrasil.jbrj.gov.br/FB${id}` : null
// Marca das espécies sem sequência montada que têm leituras brutas no SRA.
selo_brutos = d => d.brutos
? htl.html`<a class="cc-chip cc-brutos" href="${link_sra(d.especie)}" target="_blank" title="Há leituras de sequenciamento no SRA, mas nenhuma sequência montada no BOLD ou no GenBank">só dados brutos</a>`
: ""
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>`;
}
cor_ameaca = c => c?.startsWith("CR") ? "cr" : c?.startsWith("EN") ? "en" : c?.startsWith("VU") ? "vu" : "outra"
selo_ameaca = c => eh_ameacada(c)
? htl.html`<span class="cc-chip cc-${cor_ameaca(c)}" title="Portaria MMA 1.783/2026">${c}</span>` : ""
// Lista curta de espécies (painéis de estado e de domínio).
item_especie = (x, com_situacao) => htl.html`<li>
<a href="${link_adote(x.especie)}" target="_blank" title="Adotar esta espécie"><em>${x.especie}</em></a>
<span class="cc-uf-chips">${com_situacao ? selo_situacao(x.situacao) : ""}${selo_brutos(x)}${selo_ameaca(x.categoria_ameaca)}${x.endemica ? htl.html`<span class="cc-chip">endêmica</span>` : ""}</span>
<small>${x.grupo_rotulo} · ${x.familia}${x.ano_descricao ? ` · ${x.ano_descricao}` : ""}</small>
</li>`
ordena_prioridade = (a, b) =>
d3.ascending(a.situacao === "sem DNA" ? 0 : 1, b.situacao === "sem DNA" ? 0 : 1) ||
d3.ascending(a.prioridade, b.prioridade) ||
d3.ascending(a.ano_descricao ?? 9999, b.ano_descricao ?? 9999) ||
d3.ascending(a.especie, b.especie)traqueophyDNA
Quais plantas nativas do Brasil (angiospermas, gimnospermas, samambaias e licófitas) ainda não têm nenhuma sequência de DNA pública? Hoje são espécies, da flora vascular do país.
Atualizado em , com dados do BOLD Systems, do GenBank e do SRA. Atualização automática toda semana.
O retrato
Cada quadradinho é uma espécie
As espécies de plantas vasculares nativas do Brasil. Passe o mouse (ou toque) para ver o nome.
{
// Canvas em vez de SVG: são 35 mil quadradinhos.
const ordem = new Map(SITUACOES.map((s, i) => [s, i]));
const grupos = [...ROTULO_GRUPO.keys()];
const dados = [...cobertura].sort((a, b) =>
d3.ascending(ordem.get(a.situacao), ordem.get(b.situacao)) ||
d3.ascending(grupos.indexOf(a.grupo), grupos.indexOf(b.grupo)) ||
d3.ascending(a.familia, b.familia) || d3.ascending(a.especie, b.especie));
const cols = width < 640 ? 100 : 200;
const lado = width / cols;
const linhas = Math.ceil(dados.length / cols);
const altura = Math.ceil(linhas * lado);
const dpr = window.devicePixelRatio || 1;
const canvas = htl.html`<canvas role="img" aria-label="Mosaico das espécies por situação" style="width:${width}px;height:${altura}px"></canvas>`;
canvas.width = Math.round(width * dpr);
canvas.height = Math.round(altura * dpr);
const ctx = canvas.getContext("2d");
ctx.scale(dpr, dpr);
const cor = new Map(SITUACOES.map((s, i) => [s, COR_SITUACAO[i]]));
const vao = Math.min(1, lado * 0.15);
dados.forEach((d, i) => {
ctx.fillStyle = cor.get(d.situacao);
ctx.fillRect((i % cols) * lado, Math.floor(i / cols) * lado, lado - vao, lado - vao);
});
const dica = htl.html`<div class="cc-mosaico-dica" hidden></div>`;
const mostra = e => {
const r = canvas.getBoundingClientRect();
const x = e.clientX - r.left, y = e.clientY - r.top;
const d = dados[Math.floor(y / lado) * cols + Math.floor(x / lado)];
if (!d || x < 0 || x >= r.width) { dica.hidden = true; return; }
dica.replaceChildren(htl.html`<em>${d.especie}</em><small>${d.grupo_rotulo} · ${d.familia}</small>${selo_situacao(d.situacao)}`);
dica.hidden = false;
dica.style.left = `${Math.min(x + 12, r.width - dica.offsetWidth - 4)}px`;
dica.style.top = `${y + 14}px`;
};
canvas.addEventListener("pointermove", mostra);
canvas.addEventListener("pointerdown", mostra);
canvas.addEventListener("pointerleave", () => { dica.hidden = true; });
return htl.html`<div class="cc-mosaico">${canvas}${dica}</div>`;
}Com DNA: ao menos um registro público no BOLD ou no GenBank sob o nome da lista, sob outra combinação do mesmo epíteto (inclusive variedades e subespécies) ou sob um sinônimo da Flora e Funga. A verificar: o DNA está só sob um sinônimo do WCVP, que pode ser um conceito mais amplo da espécie. Sem DNA: nada encontrado sob o nome e os sinônimos conhecidos. O painel conta sequências de DNA depositadas, de qualquer marcador; não é um levantamento de genomas nem de bases de referência para eDNA. Detalhes em Fontes e método.
Por grupo
Três grupos, três lacunas
{
const cartoes = [...ROTULO_GRUPO].map(([g, rot]) => {
const c = conta(cobertura.filter(d => d.grupo === g));
const barra = [["com", c.com], ["verificar", c.verificar], ["sem", c.sem]].map(([k, v], i) =>
htl.html`<i style="width:${100 * v / c.total}%;background:${COR_SITUACAO[i]}"></i>`);
return htl.html`<a class="cc-grupo" href="especies.html" style="color:inherit;text-decoration:none">
<div class="cc-silhueta" role="img" aria-label="${rot}" style="mask-image:url(assets/silhuetas/${g}.svg);-webkit-mask-image:url(assets/silhuetas/${g}.svg)"></div>
<h3>${rot}</h3>
<span class="cc-total">${fmtn(c.total)} espécies</span>
<div class="cc-barra">${barra}</div>
<span class="cc-linha"><span>com DNA</span><b>${fmtpct(c.com / c.total)}</b></span>
<span class="cc-linha cc-sem"><span>sem DNA</span><b>${fmtn(c.sem)}</b></span>
<span class="cc-linha"><span>a verificar</span><b>${fmtn(c.verificar)}</b></span>
<span class="cc-linha cc-brutos-linha"><span>só dados brutos</span><b>${fmtn(c.brutos)}</b></span>
</a>`;
});
return htl.html`<div class="cc-grupos">${cartoes}</div>`;
}Onde estão
Domínios fitogeográficos
Proporção das espécies de cada domínio ainda sem DNA. Uma espécie entra em todos os domínios onde ocorre; exclusivas são as que só ocorrem naquele domínio.
{
const linhas = DOMINIOS.map(dom => {
const sp = cobertura.filter(d => d.biomas_lista.includes(dom));
const excl = sp.filter(d => d.biomas_lista.length === 1);
return {dom, total: sp.length, sem: sp.filter(d => d.situacao === "sem DNA").length,
excl: excl.length, excl_sem: excl.filter(d => d.situacao === "sem DNA").length};
});
return htl.html`<ol class="cc-ufs cc-dominios-lista">${linhas.map(d => htl.html`<li>
<a class="cc-uf-nome" href="dominios.html#${encodeURIComponent(d.dom)}">${d.dom}</a>
<span class="cc-uf-barra"><i style="width:${100 * d.sem / d.total}%"></i></span>
<span class="cc-uf-valor">${fmtpct(d.sem / d.total)}</span>
<span class="cc-uf-detalhe">${fmtn(d.sem)} de ${fmtn(d.total)} espécies sem DNA · das ${fmtn(d.excl)} exclusivas, ${fmtpct(d.excl_sem / d.excl)} sem DNA</span>
</li>`)}</ol>
<p class="nota-pequena"><a href="dominios.html">Ver as espécies por domínio e por forma de vida →</a></p>`;
}As maiores lacunas
Famílias com mais espécies sem DNA
Sem DNA e a verificar, somados. As quinze primeiras.
{
const fam = d3.rollups(cobertura.filter(d => d.situacao !== "com DNA"),
v => ({sem: v.filter(d => d.situacao === "sem DNA").length,
verificar: v.filter(d => d.situacao === "a verificar").length, total: v.length,
grupo: v[0].grupo_rotulo}),
d => d.familia)
.map(([familia, v]) => ({familia, ...v}))
.sort((a, b) => d3.descending(a.total, b.total)).slice(0, 15);
const tam = new Map(d3.rollups(cobertura, v => v.length, d => d.familia));
const linhas = fam.flatMap(f => [
{familia: f.familia, grupo: f.grupo, situacao: "sem DNA", n: f.sem},
{familia: f.familia, grupo: f.grupo, situacao: "a verificar", n: f.verificar}]);
return Plot.plot({
width: Math.min(width, 760), height: fam.length * 26 + 30, marginLeft: 140, marginRight: 90,
style: {fontFamily: "Inter, sans-serif", fontSize: "13px", background: "transparent"},
x: {label: null, axis: "top", grid: true, tickFormat: fmtn},
y: {label: null, domain: fam.map(d => d.familia), tickSize: 0},
color: {domain: ["sem DNA", "a verificar"], range: [COR_SITUACAO[2], COR_SITUACAO[1]]},
marks: [
Plot.barX(linhas, Plot.stackX({x: "n", y: "familia", fill: "situacao",
order: ["sem DNA", "a verificar"], inset: 1.5, rx: 2, tip: true,
title: d => `${d.familia} (${d.grupo})\n${d.situacao}: ${fmtn(d.n)}`})),
Plot.text(fam, {x: "total", y: "familia", dx: 6, textAnchor: "start",
text: d => `${fmtn(d.total)} (${fmtpct(d.total / tam.get(d.familia))})`,
fill: "currentColor", fillOpacity: 0.7}),
Plot.ruleX([0])
]
});
}Esquecidas
Descritas há mais tempo e ainda sem DNA
Espécies conhecidas pela ciência há mais de dois séculos e que nunca tiveram uma sequência depositada. Clique no nome para adotar.
{
const velhas = cobertura.filter(d => d.situacao === "sem DNA" && d.ano_descricao)
.sort((a, b) => d3.ascending(a.ano_descricao, b.ano_descricao) || d3.ascending(a.especie, b.especie))
.slice(0, 12);
return htl.html`<ul class="cc-antigas">${velhas.map(d => htl.html`<li>
<span class="cc-ano">${d.ano_descricao}</span>
<span class="cc-quem"><a href="${link_adote(d.especie)}" target="_blank" title="Quero sequenciar"><em>${d.especie}</em></a>
<small>${d.grupo_rotulo} · ${d.familia}${d.categoria_ameaca ? ` · ${d.categoria_ameaca}` : ""}${d.endemica ? " · endêmica" : ""}</small></span>
</li>`)}</ul>`;
}Novidades
Espécies que ganharam DNA
{
const ev = await FileAttachment("data/eventos.csv").csv({typed: true});
if (!ev.length) return md`Nenhuma ainda. A cada atualização semanal, toda espécie que sair da lista "sem DNA" aparece aqui.`;
return htl.html`<ul class="cc-antigas">${ev.reverse().slice(0, 12).map(d => htl.html`<li>
<span class="cc-ano" style="color:var(--cc-mata)">${String(d.data).slice(0, 10)}</span>
<span class="cc-quem"><em>${d.especie}</em><small>${ROTULO_GRUPO.get(d.grupo) ?? d.grupo}</small></span>
</li>`)}</ul>`;
}Adote uma espécie
Tem material de uma dessas espécies no herbário ou no laboratório? Diga em público que vai sequenciá-la: evita esforço duplicado e ajuda a achar parceiros.