Pular para o conteúdo

Painel profissional

Indicadores, a grade que pagina no servidor, 27 tipos de gráfico e o fragmento que redesenha um pedaço só.

Os componentes da página anterior montam um relatório. O que faz um painel é o que vem aqui: a faixa de indicadores, o bloco com título discreto, a tabela que pagina sem recarregar a tela, e os gráficos que respondem às perguntas que uma linha e uma barra não respondem.

O indicador#

V.metrica mostra um número. O indicador é o bloco de que um painel é feito: faixa de cor à esquerda, nota embaixo, meta com barra de progresso e uma série miúda ao fundo.

dataforge
V.indicador("Patrimônio líquido", 1091947.91,
    formato := "moeda", cor := "aviso", variacao := 4.2,
    nota := "Total em ago/26")

V.indicador("Aportes", 8495.0,
    formato := "moeda", cor := "info", alvo := 12000.0,
    mini := [6200, 7100, 6800, 9300, 8495])

V.indicadores põe vários numa faixa que se ajusta sozinha à largura — quatro cartões numa tela larga, um no celular, sem ninguém escolher o número.

dataforge
V.indicadores([
    {"rotulo": "Receita", "valor": 128400.0, "formato": "moeda"},
    {"rotulo": "Clientes", "valor": 2500, "variacao": 8.1},
    {"rotulo": "Churn", "valor": 2.4, "formato": "percentual", "cor": "erro"}])

Uma chave que não existe é ignorada, e não vira um cartão com o padrão: formatto seria um engano passando calado.

Malha e painel#

V.malha quebra pela largura, e não pelo número de colunas: com minimo := 260, três painéis numa tela larga viram dois numa média e um no celular. O servidor não mede a tela, então quem decide é o CSS.

dataforge
m := V.malha(3, minimo := 260)

carteira := m.painel("Balanceamento", subtitulo := "6 tipos", icone := "pizza")
carteira.grafico_rosca({"Ações": 303561.0, "Fundos": 377733.0})

fluxo := m.painel("Receitas × despesas", cor := "info")
fluxo.grafico_barras(movimento, x := "mes", y := ["receitas", "despesas"])

O título de um painel é pequeno e em caixa alta de propósito: num painel com doze blocos, o que precisa saltar é o dado — não doze títulos competindo com ele.

A barra do topo#

dataforge
topo := V.barra_superior("Gestão financeira",
    subtitulo := "Cliente",
    itens := ["Dados", "Carteira", "Painel"],
    ativo := "Painel")

ano := topo.escolha("Período", ["2026", "2025"])

Ela devolve a área da direita, que é onde os filtros ficam. O seletor é lido ali e devolvido para quem chamou: o programa roda de cima para baixo, e a linha seguinte já usa o ano.

A grade#

A V.frame ordena e filtra no navegador, com o que já está na tela: é instantâneo e mente sobre um conjunto de cem mil linhas, porque só as que vieram participam. A grade ordena, filtra e pagina no servidor, sobre o conjunto inteiro.

dataforge
escolhidas := V.grade(pedidos,
    colunas := [
        V.coluna("cliente", "Cliente", largura := 240),
        V.coluna("total", "Total", tipo := "moeda"),
        V.coluna("margem", "Margem", tipo := "barra", maximo := 100),
        V.coluna("situacao", "Situação", tipo := "selo")],
    destacar := [V.regra("margem", "menor", 10, "erro")],
    totais := {"total": "soma"},
    selecionar := "varias",
    paginar := 25)

given len(escolhidas) bigger 0:
    V.exportar_csv(escolhidas, "selecionadas.csv")

A paginação vem ligada porque uma listagem sem teto é a forma mais comum de um painel travar: mil linhas viram mil vezes o custo de desenhar, a cada clique. paginar := 0 a desliga.

Tipo de colunaO que ele desenha
textoo valor como veio
moeda · numero · percentual · compactoformatado em pt-BR, alinhado à direita
data2026-09-20 vira 20/09/2026
barra · progressouma barra dentro da célula, com o valor ao lado
miniuma série miúda — a coluna carrega um cluster
selouma etiqueta colorida; a cor sai do valor
link · imagem · logicoâncora, miniatura e ✓/—

O formato é aplicado na hora de desenhar, nunca no dado: a coluna continua numérica, e é isso que permite ordenar por valor e somar o rodapé depois de a célula já dizer R$ 1.091.947,91.

O editor#

dataforge
linhas := V.editor(precos, colunas := [
    V.coluna("produto", editavel := no),
    V.coluna("preco", tipo := "moeda")])

given V.botao("Salvar"):
    cycle linha in linhas:
        Banco.executar("UPDATE precos SET preco = ? WHERE produto = ?",
                       [linha["preco"], linha["produto"]])

O que sai é um cluster de vaults novo — o original não é tocado, como em record/with. E o valor volta no tipo da coluna: devolver "12" onde havia 12 faria a soma do rodapé concatenar, e o sintoma seria um total absurdo, não um erro.

Os 27 tipos de gráfico#

Para responderO gráfico
quanto, ao longo do tempografico_linha, grafico_area, grafico_area_empilhada
quanto, por categoriagrafico_barras, grafico_barras_h, grafico_pareto
de que isto é feitografico_pizza, grafico_rosca, grafico_treemap, grafico_barras_100
duas grandezas de escalas diferentesgrafico_combo com eixo_direito
de onde veio a diferençagrafico_cascata
quanto sobra em cada etapagrafico_funil
estamos perto da meta?medidor, grafico_bala
quando acontecemapa_de_calor, grafico_calendario
como se distribuihistograma, grafico_caixa
um está ligado ao outro?grafico_dispersao_xy, grafico_bolhas
comparar em vários critériosgrafico_radar
para onde foigrafico_sankey, grafico_rede
quando cada coisa acontecegrafico_gantt
o mercadografico_velas
ondegrafico_mapa
a tendência ao lado de um númeromini_grafico
dataforge
g := V.grafico_combo(mes_a_mes, x := "mes",
    barras := ["receita", "despesa"],
    linhas := ["margem"],
    direita := ["margem"])

direita põe aquelas séries numa segunda escala. Sem ela, uma margem de 0 a 100 desenhada ao lado de uma receita de milhões vira uma linha colada no zero: o gráfico existe e não mostra nada.

Três decisões que o desenho toma por você#

A barra é ancorada no zero; a linha, não. Numa barra o que significa é o comprimento, e cortar o eixo faz uma barra 3% maior parecer o dobro — o gráfico enganoso clássico. Numa linha o que significa é a posição: forçar o zero num patrimônio que vai de 1,02 a 1,13 milhão desenha uma reta horizontal, e a variação some.

`void` é um vão, não um zero. A linha de um acumulado para no mês que ainda não aconteceu, em vez de despencar. Um gráfico que mostra uma queda de um milhão onde só falta o dado é pior que um gráfico que para: ele afirma um número.

O texto cresce quando o espaço encolhe. O desenho é feito num sistema de 800 unidades e o CSS o encolhe para caber no bloco. Num painel de um terço da tela o fator é 0,45, e um rótulo de 11px chegaria ao olho com 5px. O layout informa a largura, e o desenho compensa.

Fragmento: redesenhar um pedaço#

dataforge
f := V.fragmento("cotacoes", a_cada := 5)
f.metrica("Dólar", cotacao("USD"))
f.metrica("Euro", cotacao("EUR"))

Um clique dentro do fragmento ainda roda o programa inteiro no servidor — isso não muda —, mas a resposta carrega só este pedaço. A diferença aparece onde importa: o filtro da barra lateral não perde o foco, a rolagem da tabela ao lado não volta ao topo, e um gráfico pesado em outro canto não é redesenhado.

A resposta volta a ser a página inteira quando houve falha, quando mais de um campo mudou, ou quando o fragmento saiu da árvore. Responder a página inteira sem precisar custa desempenho; responder um pedaço sem poder custa correção.

Conversa, situação e aviso#

dataforge
pergunta := V.chat_entrada()
given pergunta is not void:
    V.guardar_no_chat("usuario", pergunta)
    V.guardar_no_chat("assistente", responder(pergunta))

cycle m in V.historico_de_chat():
    V.chat_mensagem(m["quem"], m["conteudo"])
dataforge
passo := V.status("Consultando o banco…")
passo.texto("1.200 linhas")
passo.concluir("Pronto")

V.toast("Salvo", nivel := "sucesso")

O V.toast não ocupa espaço no fluxo: é para confirmar o que acabou de acontecer sem empurrar o resto da tela para baixo. O V.esqueleto faz o contrário — ocupa o espaço do que ainda não chegou, para a página não saltar quando o dado chegar.

Conexão, recurso e segredo#

dataforge
banco := V.conexao("vendas", arquivo := "dados/vendas.db")
linhas := banco.consultar("SELECT * FROM pedidos WHERE mes = ?",
                          [mes], validade := 300)

A conexão é do processo: chamar de novo com o mesmo nome devolve a mesma. Sem isso, cada execução da página abriria outra — o gargalo mais fácil de criar e o mais difícil de notar, porque cada abertura é rápida.

V.cacheV.recurso
guardao resultado de um cálculoo objeto em si
vencepor validade, ou por tetonunca, enquanto o processo viver
vai a discopodenão
serve paraconsulta, API, agregaçãoconexão, modelo, cliente

A diferença não é de tamanho. Um cache com teto solta o menos usado; se a conexão do banco caísse fora por isso, a próxima página abriria outra e o pool do banco acabaria — o bug que faz um painel morrer só depois de uma hora no ar.

dataforge
chave := V.segredo("OPENAI_API_KEY")
V.vault(V.segredos_mascarados())

O ambiente vence o arquivo: é assim que um deploy troca a senha sem reescrever nada. E V.segredos_mascarados mostra só os quatro últimos caracteres — o suficiente para conferir qual chave está configurada sem entregá-la a quem está olhando a tela junto.

Tema e densidade#

dataforge
V.app("Painel", tema := "meia-noite", densidade := "compacta")

Seis temas prontos: claro, escuro, meia-noite, oceano, contraste e papel. Um tema escuro pedido pelo nome vale sempre — deixá-lo no automático faria o tema escolhido deixar de valer numa máquina configurada como clara.

A densidade muda espaçamento e tamanho sem tocar em cor nenhuma. Um painel de operação cabe um terço mais de linha na mesma tela com compacta, e é a diferença entre rolar e não rolar.

Onde continuar#