Todas as atividades
Exibição Visual

Gráfico de Coluna Horizontal

Exibe dados quantitativos em barras horizontais, com comparação interativa entre categorias. Transforma números em insights visuais imediatos, facilitando a análise de diferenças.

Análise gráfico dados visualização chart

Experimente

Meios de Transporte Mais Usados para Ir ao Trabalho

Selecione duas barras para comparar os valores.

Código-fonte

PHP

Dados de exemplo

Cada barra tem um rótulo e um valor. Quando a unidade é "%", a largura da barra reflete o valor real (42% preenche 42% da largura) — se preenchesse relativo ao maior valor do conjunto, uma barra cheia pareceria "100%" mesmo valendo bem menos.

1 <?php
2 $dados = [
3 ['rotulo' => 'Segunda', 'valor' => 42],
4 ['rotulo' => 'Terça', 'valor' => 28],
5 ['rotulo' => 'Quarta', 'valor' => 15],
6 ['rotulo' => 'Quinta', 'valor' => 10],
7 ['rotulo' => 'Sexta', 'valor' => 5],
8 ];
9 $unidade = '%';
10 $maxValor = max(array_column($dados, 'valor'));
11 ?>
12

Estrutura das barras

Cada linha é um <button>, não só decoração: clicar em duas seleciona pra comparar (ver grafico.js). O selo "maior valor" é sempre renderizado, só fica invisível quando a barra não é a líder — isso reserva o mesmo espaço em toda linha, evitando que a linha com selo fique mais alta que as outras.

1 <div class="grafico" data-tipo="grafico" data-unidade="<?= htmlspecialchars($unidade) ?>">
2 <?php foreach ($dados as $item): ?>
3 <?php
4 $pct = $unidade === '%' ? min(100, max(0, $item['valor'])) : round($item['valor'] / $maxValor * 100);
5 $lider = $item['valor'] === $maxValor;
6 ?>
7 <button type="button"
8 class="grafico-linha"
9 data-valor="<?= $item['valor'] ?>"
10 data-rotulo="<?= htmlspecialchars($item['rotulo']) ?>"
11 aria-pressed="false"
12 aria-label="<?= htmlspecialchars($item['rotulo'] . ': ' . $item['valor'] . $unidade) ?>"
13 <?= $lider ? 'data-lider="true"' : '' ?>>
14 <span class="grafico-rotulo">
15 <?= htmlspecialchars($item['rotulo']) ?>
16 <span class="grafico-selo<?= $lider ? '' : ' invisivel' ?>">maior valor</span>
17 </span>
18 <span class="grafico-trilha">
19 <span class="grafico-preenchimento" style="width: <?= $pct ?>%"></span>
20 </span>
21 <span class="grafico-valor"><?= $item['valor'] . $unidade ?></span>
22 </button>
23 <?php endforeach; ?>
24

Painel de comparação

Fica oculto até duas barras serem selecionadas — o texto é preenchido pelo JS (grafico.js) com a diferença exata entre os valores escolhidos.

1 <div class="grafico-comparacao" hidden></div>
2 </div>
CSS

Layout geral

Cada linha (rótulo + trilha + valor) tem uma altura mínima fixa, pra nenhuma barra ficar desalinhada das outras — mesmo quando só uma delas mostra o selo "maior valor".

1 .grafico {
2 display: flex;
3 flex-direction: column;
4 gap: 12px;
5 }
6 .grafico-linha {
7 display: flex;
8 align-items: center;
9 gap: 12px;
10 width: 100%;
11 min-height: 44px;
12 padding: 0;
13 border: 0;
14 background: transparent;
15 cursor: pointer;
16 text-align: left;
17 border-radius: 8px;
18 }
19 .grafico-linha:focus-visible {
20 outline: 2px solid #6366f1;
21 outline-offset: 2px;
22 }
23

Rótulo e selo

O selo "maior valor" fica sempre no HTML (ver grafico.php) — a classe .invisivel só o esconde visualmente, sem remover o espaço que ele ocupa. Assim toda linha reserva a mesma altura, mesmo a barra não sendo a líder.

1 .grafico-rotulo {
2 width: 90px;
3 flex-shrink: 0;
4 text-align: right;
5 font-size: 14px;
6 color: #475569;
7 }
8 .grafico-selo {
9 display: block;
10 font-size: 11px;
11 font-weight: 600;
12 color: #4f46e5;
13 }
14 .invisivel {
15 visibility: hidden;
16 }
17

Trilha e preenchimento

A trilha é a faixa cinza de fundo; o preenchimento colorido cresce até a largura calculada em PHP. A transição anima a barra ao carregar a página (ver grafico.js).

1 .grafico-trilha {
2 position: relative;
3 flex: 1;
4 height: 32px;
5 border-radius: 8px;
6 overflow: hidden;
7 background: #f1f5f9;
8 }
9 .grafico-preenchimento {
10 position: absolute;
11 inset-block: 0;
12 left: 0;
13 height: 100%;
14 border-radius: 8px;
15 background: linear-gradient(to right, #4f46e5, #9333ea);
16 transition: width .7s ease;
17 }
18 .grafico-valor {
19 width: 48px;
20 flex-shrink: 0;
21 text-align: right;
22 font-size: 14px;
23 font-weight: 700;
24 color: #334155;
25 }
26

Estado de destaque

A barra líder (data-lider, calculada em PHP) ganha um contorno; a barra selecionada pra comparação (data-selecionada, alternada em grafico.js) ganha outro — cores diferentes pra não confundir os dois estados quando coincidem na mesma barra.

1 .grafico-linha[data-lider="true"] .grafico-trilha {
2 box-shadow: 0 0 0 2px rgba(79, 70, 229, .45);
3 }
4 .grafico-linha[data-selecionada="true"] .grafico-trilha {
5 box-shadow: 0 0 0 2px rgba(234, 179, 8, .65);
6 }
7

Painel de comparação

Some enquanto não houver duas barras selecionadas (controlado pelo atributo "hidden" em grafico.js).

1 .grafico-comparacao {
2 margin-top: 8px;
3 padding: 12px;
4 border-radius: 8px;
5 background: #eef2ff;
6 color: #3730a3;
7 font-size: 14px;
8 }
JavaScript

Anima a barra ao carregar

Zera a largura e, 100ms depois, aplica a largura real via setTimeout — a transição definida no CSS faz a barra "crescer" visualmente ao entrar na tela, em vez de já aparecer cheia.

1 document.addEventListener('DOMContentLoaded', function () {
2 document.querySelectorAll('.grafico').forEach(function (grafico) {
3 grafico.querySelectorAll('.grafico-preenchimento').forEach(function (barra) {
4 var largura = barra.style.width;
5 barra.style.width = '0%';
6 setTimeout(function () { barra.style.width = largura; }, 100);
7 });
8

Seleciona até duas barras pra comparar

Clicar numa barra seleciona (até duas ao mesmo tempo); clicar de novo na mesma desmarca. Selecionar uma terceira descarta a mais antiga (fila FIFO), então sempre sobram só as duas mais recentes — o painel mostra a diferença exata entre elas.

1 var unidade = grafico.dataset.unidade || '';
2 var botoes = grafico.querySelectorAll('.grafico-linha');
3 var painel = grafico.querySelector('.grafico-comparacao');
4 var selecionadas = [];
5
6 function atualizarPainel() {
7 if (selecionadas.length < 2) {
8 painel.hidden = true;
9 painel.textContent = '';
10 return;
11 }
12
13 var a = selecionadas[0];
14 var b = selecionadas[1];
15 var valorA = parseFloat(a.dataset.valor);
16 var valorB = parseFloat(b.dataset.valor);
17 var maior = valorA >= valorB ? a : b;
18 var menor = maior === a ? b : a;
19 var diferenca = Math.abs(valorA - valorB);
20
21 painel.hidden = false;
22 painel.textContent = maior.dataset.rotulo + ' é ' + diferenca + unidade +
23 ' maior que ' + menor.dataset.rotulo + ' (' + maior.dataset.valor + unidade +
24 ' vs ' + menor.dataset.valor + unidade + ').';
25 }
26
27 botoes.forEach(function (botao) {
28 botao.addEventListener('click', function () {
29 var indice = selecionadas.indexOf(botao);
30 if (indice !== -1) {
31 selecionadas.splice(indice, 1);
32 botao.removeAttribute('data-selecionada');
33 botao.setAttribute('aria-pressed', 'false');
34 } else {
35 if (selecionadas.length === 2) {
36 var removida = selecionadas.shift();
37 removida.removeAttribute('data-selecionada');
38 removida.setAttribute('aria-pressed', 'false');
39 }
40 selecionadas.push(botao);
41 botao.setAttribute('data-selecionada', 'true');
42 botao.setAttribute('aria-pressed', 'true');
43 }
44 atualizarPainel();
45 });
46 });
47 });
48 });