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.
Experimente
Meios de Transporte Mais Usados para Ir ao Trabalho
Selecione duas barras para comparar os valores.
Código-fonte
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>
|
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 |
}
|
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 |
});
|