Chart
Gráficos desenhados com D3 e montados por partes: grade, eixos, séries, tooltip e legenda, ou uma camada que você mesmo desenha.
Instalação
O Shadwire segue o modelo open code do shadcn/ui: o código-fonte do componente é copiado para o seu app e passa a ser seu. Instale com a CLI:
O item chart do registry instala estes arquivos:
-
app/components/ui_component.rb -
app/components/ui/chart_component.rb -
app/components/ui/chart/layer_component.rb -
app/components/ui/chart/grid_component.rb -
app/components/ui/chart/x_axis_component.rb -
app/components/ui/chart/y_axis_component.rb -
app/components/ui/chart/bar_component.rb -
app/components/ui/chart/line_component.rb -
app/components/ui/chart/area_component.rb -
app/components/ui/chart/pie_component.rb -
app/components/ui/chart/radial_bar_component.rb -
app/components/ui/chart/radar_component.rb -
app/components/ui/chart/polar_grid_component.rb -
app/components/ui/chart/polar_angle_axis_component.rb -
app/components/ui/chart/label_list_component.rb -
app/components/ui/chart/tooltip_component.rb -
app/components/ui/chart/legend_component.rb -
app/helpers/ui/chart_helper.rb -
app/javascript/controllers/ui_chart_controller.js -
app/javascript/controllers/ui_chart_grid_controller.js -
app/javascript/controllers/ui_chart_axis_controller.js -
app/javascript/controllers/ui_chart_bar_controller.js -
app/javascript/controllers/ui_chart_line_controller.js -
app/javascript/controllers/ui_chart_area_controller.js -
app/javascript/controllers/ui_chart_pie_controller.js -
app/javascript/controllers/ui_chart_radial_bar_controller.js -
app/javascript/controllers/ui_chart_radar_controller.js -
app/javascript/controllers/ui_chart_polar_grid_controller.js -
app/javascript/controllers/ui_chart_polar_angle_axis_controller.js -
app/javascript/controllers/ui_chart_label_list_controller.js -
app/javascript/controllers/ui_chart_tooltip_controller.js -
app/javascript/controllers/ui_chart_legend_controller.js -
vendor/shadwire/shadwire.css
Este componente usa Stimulus, então a aplicação precisa de importmap-rails e stimulus-rails com carregamento automático de controllers.
Ainda não instalou a CLI? gem install shadwire e depois shadwire init.
Como um gráfico é montado
Um gráfico é um contêiner com partes dentro. O contêiner, ui_chart, guarda
o que todas as partes compartilham: as linhas de dados, o config e o layout. Cada parte, como
ui_chart_grid, ui_chart_x_axis,
ui_chart_bar ou ui_chart_tooltip, desenha uma camada, e
as camadas se empilham na ordem em que você as escreve, como os filhos de um gráfico do Recharts.
Você não escolhe um tipo de gráfico: barras e uma linha no mesmo contêiner formam um gráfico
combinado. HTML comum dentro do contêiner aparece por cima do desenho.
Quem desenha é o D3. O controller Stimulus do contêiner, ui-chart, mede o
gráfico, calcula as escalas e as pilhas e passa tudo para cada parte num evento
ui-chart:render. Cada parte tem o próprio controller, que desenha o seu
<svg> com D3. Assim, você pode apagar os arquivos das partes que não usa e
escrever partes novas quando precisar. As cores são propriedades CSS, então trocar o tema muda
as cores sem redesenhar o gráfico. O D3 só é carregado nas páginas que têm um gráfico.
Seu primeiro gráfico
Esta seção monta um gráfico de barras e depois acrescenta uma grade, um eixo, um tooltip e uma legenda, uma parte de cada vez.
Comece pelas linhas de dados
Um hash por categoria, com as chaves que você quiser; o data_key: de cada
parte diz qual campo ela lê. Em geral as linhas vêm de uma consulta. Datas e horários são
enviados como strings ISO, que os eixos e o tooltip formatam para você, e um
BigDecimal chega como string e é lido como número.
Defina o config
O config guarda o que as linhas não têm: o rótulo e a cor de cada série. Ele fica separado dos dados para que vários gráficos possam usar o mesmo config, e para que os dados possam vir de qualquer lugar.
Componha o gráfico
Coloque um ui_chart_bar por série. O contêiner é
aspect-video por padrão; dê a ele também um min-h-* ou uma
altura, senão não sobra espaço para desenhar.
Acrescente uma grade
ui_chart_grid traça uma linha em cada marca do eixo de valores.
Acrescente um eixo
O eixo x rotula as categorias com o campo de data_key:. Os meses são datas,
então aqui o tick_format: é um especificador do d3-time-format, e os rótulos
saem Jan, Fev… no idioma da página.
Acrescente um tooltip
Passe o mouse sobre um mês e o ui_chart_tooltip mostra o valor de cada série
naquele mês, abaixo do nome completo do mês.
Acrescente uma legenda
O ui_chart_legend mostra as séries com os rótulos do config, abaixo da área do
gráfico.
Gráfico de barras
Uma grade, um eixo, um tooltip e uma legenda em volta de duas séries de barras: o gráfico montado passo a passo acima.
Composição
O config
Cada chave do config é uma série, ou uma categoria que uma pizza, um anel ou uma legenda mostra.
Ela aceita um label:, uma color: ou um
theme: com uma cor por tema, e, se você quiser, um icon:
do Lucide, que a legenda e o tooltip mostram no lugar da amostra de cor.
Temas
Cada cor do config vira uma propriedade CSS --color-<chave> no gráfico, e
todas as partes desenham com elas. Use os tokens de gráfico do tema nessas cores e o gráfico
acompanha o tema, inclusive no modo escuro.
Qualquer cor CSS também funciona (hex, hsl(), oklch()).
As variáveis não servem só para as partes: a color: de uma parte, o
fill de uma linha e a sua própria marcação dentro do gráfico também podem
usá-las.
Tooltip
O tooltip mostra um rótulo (a categoria) e uma linha por série, com indicador, nome e valor.
indicator: é :dot, :line ou
:dashed; hide_label: e hide_indicator:
escondem um ou outro. cursor: sombreia a categoria sob o ponteiro, e
default_index: já mostra o tooltip de uma categoria antes de alguém passar o
mouse.
label_key: e name_key: tiram o rótulo e os nomes de outra
entrada do config ou de outro campo. Aqui o rótulo é Total visitors e cada nome é o
navegador que a fatia representa:
Legenda
A legenda lista as séries (ou, numa pizza, as fatias) com os rótulos e as cores do config. Com
name_key:, ela lista as linhas de dados, nomeadas por esse campo, e
vertical_align: :top a leva para cima da área do gráfico.
Formatação
O tick_format: dos eixos, o label_format: e o
value_format: do tooltip e o format: das listas de rótulos
são especificadores do D3: d3-format para números, d3-time-format para datas. Sem especificador, os
números são formatados no idioma da página.
Os nomes de meses e dias e os separadores de número vêm das traduções do Rails no próprio app
(date.month_names, date.abbr_day_names,
number.format.delimiter e assim por diante), então um app em português
mostra Fev e 1.234,5 sem configurar nada no gráfico.
Uma camada sua
As partes que vêm prontas funcionam do mesmo jeito que uma escrita por você. O
ui_chart_layer coloca um <svg> sobre o gráfico e o liga a
um controller Stimulus seu, que recebe as escalas do gráfico e desenha com D3. Este aqui desenha
uma linha de meta:
Uma parte recebe três eventos, nesta ordem. event.detail.chart traz as linhas
de dados, o config, a área do gráfico, as escalas de categoria e de valor, a geometria polar, um
formatador e o próprio d3.
Acessibilidade
label: dá o nome acessível do gráfico. O desenho fica oculto para tecnologias
assistivas, e a legenda é texto comum. Quando o gráfico tem tooltip, ele pode receber foco: as
setas, Home e End passam de uma categoria para outra, Escape fecha
o tooltip, e os leitores de tela anunciam o que ele mostra. Se a pessoa preferir movimento
reduzido, nada é animado.
Exemplos
Barras horizontais
Num layout vertical, os meses ficam no eixo y. Duas listas de rótulos dentro das barras imprimem o mês e o valor.
Barras empilhadas
Barras com o mesmo stack id são empilhadas, e o raio arredonda só os cantos externos de cada pilha.
Linhas
Uma curva natural e uma em degraus, ambas com pontos, sob um tooltip com indicador de linha.
Áreas empilhadas
Três meses de linhas diárias sob um degradê. As datas aparecem no idioma da página, e rótulos que ficariam sobrepostos são omitidos.
Rosca
Uma pizza com raio interno e HTML comum no meio: as camadas também se misturam com HTML.
Radar
Dois radares sobre uma grade polar, com os meses em volta.
Barras radiais
Um anel por navegador sobre uma trilha apagada, com o rótulo onde cada anel começa.
Indicadores do tooltip
Os indicadores de ponto, linha e tracejado, cada um já mostrando a terça-feira antes de alguém passar o mouse.
Uma camada sua
Uma linha de meta desenhada na escala de valores do gráfico por um controller Stimulus do próprio app.
Referência da API
Toda parte aceita class / class_name e **attrs.
| Componente | Argumento | Padrão | Descrição |
|---|---|---|---|
Chart |
config |
{} |
As séries, e as categorias que uma pizza ou uma legenda mostram, como { chave: { label:, color:, icon: } }, ou theme: { light:, dark: } no lugar de color:. Cada cor vira --color-<chave> no gráfico. |
Chart |
rows |
[] |
Os dados, um hash por categoria. Chama rows: e não data: para que data: continue definindo atributos data do HTML. |
Chart |
layout |
:horizontal |
:horizontal põe as categorias ao longo do eixo x; :vertical, descendo o eixo y, para barras horizontais. |
Chart |
stack_offset |
:none |
O offset do D3 para as séries que compartilham um stack_id:: :none, :expand (até 100%), :diverging, :silhouette ou :wiggle. |
Chart |
margin |
{} |
Espaço em volta da área do gráfico, em pixels por lado: { left: 12, right: 12 }. Os eixos e a legenda somam o próprio espaço a isso. |
Chart |
inner_radius |
0 |
O raio interno de pizzas, anéis e radares: em pixels, ou uma porcentagem do espaço disponível ("30%"). |
Chart |
outer_radius |
"80%" |
O raio externo, nas mesmas unidades. |
Chart |
start_angle |
0 |
Onde pizzas, anéis e radares começam, em graus no sentido horário a partir do meio-dia. |
Chart |
end_angle |
360 |
Onde terminam, também em graus no sentido horário a partir do meio-dia. |
Chart |
label |
nil |
O nome acessível do gráfico. |
Chart::Layer |
controller |
nil |
O controller Stimulus que desenha a camada. Ele recebe ui-chart:render, com as escalas do gráfico em event.detail.chart. |
Chart::Layer |
options |
{} |
O que esse controller precisar, em event.detail.options. |
Chart::Grid |
horizontal |
nil |
Desenha as linhas horizontais. Se nenhum dos dois for definido, só as linhas que cruzam o eixo de valores aparecem. |
Chart::Grid |
vertical |
nil |
Desenha as linhas verticais, com o mesmo padrão. |
Chart::XAxis |
data_key |
nil |
No eixo de categorias, o campo cujos valores rotulam as categorias e o tooltip. |
Chart::XAxis |
hide |
false |
Esconde o eixo, mas mantém as categorias dele. |
Chart::XAxis |
tick_line |
false |
Desenha um traço em cada rótulo. |
Chart::XAxis |
axis_line |
false |
Desenha a linha do eixo. |
Chart::XAxis |
tick_margin |
8 |
Pixels entre a área do gráfico e os rótulos. |
Chart::XAxis |
tick_count |
5 |
Quantas marcas, aproximadamente, o eixo de valores recebe. |
Chart::XAxis |
tick_format |
nil |
Um especificador do d3-format para números ("~s", "$,.0f") ou do d3-time-format para datas ("%b %d"). |
Chart::XAxis |
min_tick_gap |
5 |
O espaço mínimo entre dois rótulos de categoria, em pixels; rótulos que ficariam sobrepostos são omitidos. |
Chart::XAxis |
domain |
nil |
O [mín, máx] do eixo de valores; nil em uma das pontas deixa essa ponta para os dados. |
Chart::XAxis |
height |
nil |
O espaço que o eixo ocupa, em pixels. Se não for definido, é calculado pelos rótulos. |
Chart::YAxis |
data_key |
nil |
No eixo de categorias (o eixo y num layout vertical), o campo cujos valores rotulam as categorias e o tooltip. |
Chart::YAxis |
hide |
false |
Esconde o eixo, mas mantém as categorias dele. |
Chart::YAxis |
tick_line |
false |
Desenha um traço em cada rótulo. |
Chart::YAxis |
axis_line |
false |
Desenha a linha do eixo. |
Chart::YAxis |
tick_margin |
8 |
Pixels entre a área do gráfico e os rótulos. |
Chart::YAxis |
tick_count |
5 |
Quantas marcas, aproximadamente, o eixo de valores recebe. |
Chart::YAxis |
tick_format |
nil |
Um especificador do d3-format para números ("~s", "$,.0f") ou do d3-time-format para datas ("%b %d"). |
Chart::YAxis |
min_tick_gap |
5 |
O espaço mínimo entre dois rótulos de categoria, em pixels; rótulos que ficariam sobrepostos são omitidos. |
Chart::YAxis |
domain |
nil |
O [mín, máx] do eixo de valores; nil em uma das pontas deixa essa ponta para os dados. |
Chart::YAxis |
width |
nil |
O espaço que o eixo ocupa, em pixels. Se não for definido, é calculado pelos rótulos. |
Chart::Bar |
data_key |
— |
O campo que cada barra mostra. |
Chart::Bar |
color |
nil |
A cor das barras, no lugar da definida no config. |
Chart::Bar |
stack_id |
nil |
Barras com o mesmo stack_id se empilham; as outras ficam lado a lado. |
Chart::Bar |
radius |
0 |
O raio dos cantos em pixels: um número, ou [sup_esq, sup_dir, inf_dir, inf_esq]. |
Chart::Line |
data_key |
— |
O campo por onde a linha passa. |
Chart::Line |
color |
nil |
A cor da linha, no lugar da definida no config. |
Chart::Line |
curve |
:natural |
A curva do D3: :natural, :linear, :monotone, :step, :step_before, :step_after, :basis, :cardinal ou :catmull_rom. |
Chart::Line |
stroke_width |
2 |
A espessura da linha, em pixels. |
Chart::Line |
dot |
false |
Marca cada ponto. |
Chart::Line |
connect_nulls |
false |
Atravessa um valor ausente em vez de interromper a linha ali. |
Chart::Area |
data_key |
— |
O campo até onde a área sobe. |
Chart::Area |
color |
nil |
A cor da área, no lugar da definida no config. |
Chart::Area |
stack_id |
nil |
Áreas com o mesmo stack_id se empilham. |
Chart::Area |
curve |
:natural |
A curva do D3, como na linha. |
Chart::Area |
fill_opacity |
0.4 |
A opacidade do preenchimento, de 0 a 1. |
Chart::Area |
gradient |
false |
Preenche com um degradê da cor, mais forte no alto. |
Chart::Area |
stroke_width |
1 |
A espessura da borda, em pixels. |
Chart::Area |
dot |
false |
Marca cada ponto. |
Chart::Area |
connect_nulls |
false |
Atravessa um valor ausente em vez de interromper a área ali. |
Chart::Pie |
data_key |
— |
O campo de onde vem o tamanho de cada fatia. |
Chart::Pie |
name_key |
nil |
O campo que nomeia cada fatia: a chave do config que lhe dá rótulo e cor. |
Chart::Pie |
inner_radius |
nil |
Substitui o raio do gráfico: pixels ou porcentagem. Qualquer valor acima de zero faz uma rosca. |
Chart::Pie |
outer_radius |
nil |
Substitui o raio do gráfico. |
Chart::Pie |
padding_angle |
0 |
Um vão entre as fatias, em graus. |
Chart::Pie |
corner_radius |
0 |
Arredonda os cantos das fatias, em pixels. |
Chart::Pie |
stroke_width |
0 |
Um contorno entre as fatias na cor do fundo; class: "stroke-card" combina com um card. |
Chart::RadialBar |
data_key |
— |
O campo de onde vem a varredura de cada anel. |
Chart::RadialBar |
name_key |
nil |
O campo que nomeia cada anel, que passa a ter o rótulo e a cor da sua entrada no config. |
Chart::RadialBar |
color |
nil |
A cor dos anéis, no lugar da definida no config. |
Chart::RadialBar |
stack_id |
nil |
Barras radiais com o mesmo stack_id se empilham ao longo dos anéis. |
Chart::RadialBar |
background |
false |
Desenha o resto de cada anel como uma trilha apagada. |
Chart::RadialBar |
corner_radius |
0 |
Arredonda as pontas dos anéis, em pixels. |
Chart::Radar |
data_key |
— |
O campo de onde vem a distância de cada vértice. |
Chart::Radar |
color |
nil |
A cor do polígono, no lugar da definida no config. |
Chart::Radar |
fill_opacity |
0.6 |
A opacidade do preenchimento, de 0 a 1; 0 deixa só o contorno. |
Chart::Radar |
stroke_width |
0 |
Contorna o polígono, em pixels. |
Chart::Radar |
dot |
false |
Marca cada vértice. |
Chart::PolarGrid |
grid_type |
:polygon |
:polygon, anéis que passam pelos raios, ou :circle. |
Chart::PolarGrid |
radial_lines |
true |
Desenha um raio por categoria. |
Chart::PolarAngleAxis |
data_key |
nil |
O campo cujos valores rotulam os raios e o tooltip. |
Chart::PolarAngleAxis |
tick_format |
nil |
Um especificador do d3 para os rótulos. |
Chart::PolarAngleAxis |
tick_margin |
8 |
Pixels entre o anel externo e os rótulos. |
Chart::LabelList |
data_key |
nil |
O campo a imprimir; sem valor, o próprio valor. Um campo que nomeia uma entrada do config imprime o rótulo dela. |
Chart::LabelList |
position |
nil |
Onde fica cada rótulo: :top, :bottom, :left, :right, :center, e :inside_top, :inside_bottom, :inside_left ou :inside_right nas barras; :outside, :inside ou :inside_start nas fatias e anéis. |
Chart::LabelList |
offset |
5 |
Pixels entre um rótulo e o seu ponto. |
Chart::LabelList |
format |
nil |
Um especificador do d3 para os rótulos. |
Chart::Tooltip |
indicator |
:dot |
A marca ao lado de cada nome: :dot, :line ou :dashed. |
Chart::Tooltip |
hide_label |
false |
Deixa o rótulo de fora. |
Chart::Tooltip |
hide_indicator |
false |
Deixa os indicadores de fora. |
Chart::Tooltip |
label_key |
nil |
Tira o rótulo desta entrada do config ou deste campo, em vez da categoria. |
Chart::Tooltip |
name_key |
nil |
Tira cada nome deste campo da linha, passando pelo config. |
Chart::Tooltip |
label_format |
nil |
Um especificador do d3 para o rótulo ("%B"). |
Chart::Tooltip |
value_format |
nil |
Um especificador do d3 para os valores (",d"). |
Chart::Tooltip |
cursor |
true |
Sombreia a categoria sob o ponteiro. |
Chart::Tooltip |
default_index |
nil |
Mostra o tooltip desta categoria antes de alguém passar o mouse. |
Chart::Legend |
name_key |
nil |
Lista cada linha, nomeada por este campo, em vez de cada série. |
Chart::Legend |
hide_icon |
false |
Mostra a amostra de cor mesmo quando o config tem um ícone. |
Chart::Legend |
vertical_align |
:bottom |
A borda onde fica: :bottom ou :top. |