Docs›Componentes (Admin)›mad-db-chart
Componentes (Admin)

mad-db-chart

Gráfico ECharts auto-query (bar/line/pie/donut/funnel/treemap).

Componente Blade auto-contido que monta um gráfico ECharts a partir de props HTML — sem PHP no controller.

Props

Prop Tipo Default Descrição
type string 'bar' bar, line, pie, donut, rose, funnel, treemap, radar, mixed
name string auto ID único do chart (auto-gerado quando omitido)
model string '' Classe do model Eloquent (ex: PedidoVenda)
database string MAIN_DATABASE Conexão do banco
group-by string/array '' Campo(s) de agrupamento (máx. 2 — 2º vira sub-série)
field string '' Campo para sum/avg/min/max
total string 'count' count, sum, avg, min, max
query Builder null Eloquent/Query Builder pré-montado (alternativa a model+filters)
filters array [] Filtros inline: [['campo', 'op', 'val'], ...]
joins array [] Joins keyed por tabela: ['tabela' => ['fk', 'pk']] ou ['tabela' => ['fk', 'op', 'pk']]. Ex: ['sale_status' => ['sale.sale_status_id', 'sale_status.id']]. A PK precisa de prefixo explícito (tabela.pk).
data array null Dados manuais: ['Label' => valor, ...] (dispensa model)
title string '' Título do card
subtitle string '' Subtítulo
height int|'auto' 300 Altura em px
width string '100%' Largura
no-panel bool false Sem card wrapper (bare chart)
legend bool false Mostrar legenda
legend-position string 'bottom' bottom, right
format string '' 'currency:R$', 'currency:R$:2:,:.', 'numeric:2'
suffix string '' Sufixo nos valores (ex: ' kg')
legend-format string '' Formato declarativo dos labels: date|date-short|date-long|datetime|time|year|month|month-short|month-year|month-year-short|weekday|weekday-short|quarter|quarter-year|integer|numeric|currency|percent|abbreviate
transformer callable null Transformer de valor. Qualquer callable (closure ou Transformers::nome(...)). Roda 1× por ponto — evite query no banco por valor (N+1).
legend-transformer callable null Transformer de categoria/legenda (id → label)
sub-legend-transformer callable null Transformer da sub-série (multi-série)
options array [] Extra ECharts options (merge bruto)
colors array [] Paleta custom
horizontal bool false Barras horizontais
stacked bool false Barras empilhadas
area bool false Line com área preenchida
smooth bool true Curva suave (line/area)
percentage bool false Mostra % (pie/donut/rose)
abbreviate bool false Valores abreviados (K, M, B)
filter-prop string '' Prop do host setada ao clicar numa categoria/fatia (ver "Clique-para-filtrar")
filter-mode string auto direct, lookup, period — auto-detectado a partir de filter-prop/filter-model/legend-format
filter-model string '' Model usado pra resolver o label clicado num ID (modo lookup)
filter-field string '' Coluna do filter-model comparada com o label (modo lookup)
filter-toggle bool true 2º clique na mesma categoria limpa o filtro
click-action string '' Método PHP custom chamado com ($label, $value, $dataIndex) — alternativa 100% manual ao filter-*
line-series array|string [] mixed modo A — séries do group-by que viram linha. Nomes (case-insensitive) ou índices int; CSV aceito. Exige group-by de 2 dimensões
line-total string '' mixed modo B — agregação da linha: count, sum, avg, min, max. Exige group-by de 1 dimensão
line-field string '' mixed modo B — campo da 2ª agregação. Vazio só é válido com line-total="count" (vira count(*))
line-label string '' mixed modo B — nome da série de linha. Default "{total}({field})"
line-secondary-axis bool false mixed A e B — põe a linha no eixo Y da direita
class string '' Classes CSS extras

Uso básico — bar chart com ORM

<mad-db-chart type="bar" model="PedidoVenda" database="minierp"
    group-by="status" total="count"
    title="Pedidos por Status" height="300" />

Soma de campo

<mad-db-chart type="bar" model="PedidoVenda"
    group-by="mes" field="valor_total" total="sum"
    title="Vendas por Mês" format="currency:R$" height="350" />

Com query externa (Eloquent Builder pronto)

@php
    $query = \App\Models\PedidoVenda::where('ano', '=', date('Y'));
@endphp
<mad-db-chart type="line" :query="$query"
    group-by="mes" field="valor_total" total="sum"
    title="Vendas do Ano" format="currency:R$" />

Util para reaproveitar a mesma query (período + filtros) de um dashboard via $that->baseQuery('PedidoVenda') (ver MadDashboard em dashboard.md).

Com filtros inline

<mad-db-chart type="bar" model="PedidoVenda"
    :filters="[['ativo', '=', '1']]"
    group-by="categoria" total="count"
    title="Ativos por Categoria" />

Pie / Donut

<mad-db-chart type="donut" model="Cliente"
    group-by="estado" total="count"
    title="Clientes por Estado" legend percentage />

Dados manuais (sem model)

<mad-db-chart type="pie" :data="['Eletrônicos' => 4500, 'Roupas' => 2100, 'Alimentos' => 3200]"
    title="Distribuição" legend />

Sem card wrapper (bare)

<mad-db-chart type="bar" model="MadQueueJob" database="permission"
    group-by="queue" total="count"
    title="Jobs por Fila" height="260" no-panel />

Barras horizontais / empilhadas

<mad-db-chart type="bar" model="Vendas"
    group-by="categoria" field="valor" total="sum"
    title="Por Categoria" horizontal stacked />

Line com área

<mad-db-chart type="line" model="Acesso"
    group-by="dia" total="count"
    title="Acessos Diários" area height="280" />

Radar (type="radar")

Categorias viram eixos radiais (indicators); cada série vira um polígono. Bom pra comparar perfis multi-dimensão (KPIs por filial, skills por pessoa).

{{-- 1 dimensão → 1 polígono, categorias = eixos --}}
<mad-db-chart type="radar" model="IamUserGroup" database="iam"
    group-by="mad_iam_user_group.group_id" total="count"
    title="Usuários por grupo" height="320" />

{{-- 2 dimensões → N polígonos (1 por valor da 2ª dim), com legenda --}}
<mad-db-chart type="radar" :query="$vendasQuery"
    :group-by="['mes', 'canal']" total="sum" field="valor" legend
    title="Vendas por mês × canal" height="360" />

Alias de tag: <mad-radar-chart ... /> ≡ <mad-db-chart type="radar" ... />.

Regras automáticas da engine:

  • Max único global em todos os indicators, com folga de 5% arredondada (30 → 40, 730 → 800) — mantém os polígonos comparáveis. Tudo zero → max = 1.
  • Negativos são clampados em 0 (o radar do ECharts não tem eixo negativo); o tooltip ainda mostra o valor raw quando há transformer.
  • Combinação categoria×série ausente vira 0 (vetores sempre alinhados aos indicators).
  • Ordem das séries é alfabética (ksort) — :colors posicionais funcionam.
  • legend-transformer/legend-format formatam os NOMES dos indicators; sub-legend-transformer formata os nomes das séries (2ª dimensão).

Limitações do radar

  • Sem drill-through: filter-prop/filter-mode são ignorados (o clique no radar devolve o polígono inteiro, não uma categoria). click-action continua funcionando, com essa semântica.
  • horizontal, stacked, area e smooth não se aplicam.
  • Recomendado ≥ 3 categorias — com 1 ou 2 o polígono degenera em ponto/linha.
  • legend só tem efeito com group-by de 2 dimensões.

Mixed — colunas + linha (type="mixed")

Dois modos, mutuamente exclusivos (configurar os dois lança exception):

Modo Quando usar group-by Props
A — séries do group-by As séries JÁ existem nos dados (2ª dimensão) e algumas devem virar linha 2 dimensões line-series
B — 2ª métrica Colunas = uma agregação, linha = OUTRA agregação dos mesmos registros 1 dimensão line-total, line-field, line-label

Modo A — séries do group-by viram linha

{{-- 'Meta' (valor da 2ª dimensão "canal") vira linha sobre as colunas --}}
<mad-db-chart type="mixed" :query="$vendasQuery"
    :group-by="['mes', 'canal']" total="sum" field="valor"
    line-series="Meta" legend stacked
    title="Vendas × Meta" height="340" />

{{-- múltiplas séries + eixo Y secundário --}}
<mad-db-chart type="mixed" :query="$vendasQuery"
    :group-by="['mes', 'canal']" total="sum" field="valor"
    :line-series="['Meta', 'Média']" line-secondary-axis legend />

O match é por nome case-insensitive contra o nome final da série (depois do sub-legend-transformer) OU por índice int (posição na ordem alfabética). Nome sem match é no-op silencioso — renderiza como bar puro.

Modo B — 2ª métrica agregada como linha

A 2ª agregação roda no mesmo SELECT (uma query só, alias line_total):

{{-- colunas = logins/dia; linha = logouts (count de não-nulos) --}}
<mad-db-chart type="mixed" :query="$accessQuery"
    group-by="date(mad_log_access.login_time)" total="count"
    line-total="count" line-field="mad_log_access.logout_time"
    line-label="Logouts" legend
    title="Acessos" height="320" />

{{-- colunas = soma; linha = média (eixo secundário: escalas diferem) --}}
<mad-db-chart type="mixed" model="Venda"
    group-by="mes" total="sum" field="valor"
    line-total="avg" line-field="ticket" line-label="Ticket médio"
    line-secondary-axis legend />

Regras do modo B:

  • Exige group-by de 1 dimensão — com 2 dims lança exception ("line-metric exige group-by de 1 dimensão; use line-series").
  • line-total ∈ count|sum|avg|min|max (mesmo allowlist do total).
  • line-field vazio só é válido com line-total="count" (vira count(*)). count(campo) conta não-nulos — útil pra "quantos têm X preenchido".
  • line-field passa pelo mesmo guard anti-SQL-injection do field (OrderGuard::validateExpression).
  • A série de colunas (single-series não tem nome) ganha name = título do chart; sobrescreva com :options="['series' => [0 => ['name' => 'Logins']]]".
  • A legenda vai pro topo (o default bottom do ECharts v6 colide com os labels do eixo X).

Comum aos dois modos

  • A linha é smooth, símbolo circle, z:3 (na frente das colunas), cor = próximo stroke da paleta (ou posicional do :colors).
  • line-secondary-axis faz o yAxis virar uma lista [esquerdo, direito] e a linha usar yAxisIndex:1 — quem sobrescreve yAxis via :options precisa tratar como lista nesse caso.
  • stacked empilha só as colunas; a linha fica fora do stack (caso clássico: colunas empilhadas + linha de meta).
  • horizontal é ignorado por construção — mixed é sempre vertical.
  • Click-to-filter funciona normal (filter-prop/filter-mode): clique na coluna OU no ponto da linha resolve a categoria via dataIndex.

Auto-promoção bar → mixed

type="bar" (ou omitido) + qualquer line-series/line-total promove automaticamente para mixed. Prefira emitir type="mixed" explícito.

Alias de tag: <mad-mixed-chart ... /> ≡ <mad-db-chart type="mixed" ... />.

Clique-para-filtrar (drill-down)

Em telas de dashboard (MadDashboard), o gráfico pode setar um filtro do componente host ao clicar numa categoria/fatia — sem JS manual:

{{-- direct: seta a prop "estado_nome" do host com o label clicado --}}
<mad-db-chart type="bar" model="Cliente"
    group-by="estado" total="count" title="Por estado"
    filter-prop="estado_nome" filter-mode="direct" />

{{-- lookup: resolve o label num registro e seta o ID (FK) --}}
<mad-db-chart type="donut" model="Negociacao"
    group-by="estado.nome" total="count" title="Por estado da negociação"
    filter-prop="estado_id" filter-mode="lookup"
    filter-model="EstadoNegociacao" filter-field="nome" />

{{-- period: categoria é uma coluna ano-mes ("2026-06") — seta mes/ano do dashboard --}}
<mad-db-chart type="line" model="Acesso"
    group-by="ano_mes" total="count" title="Acessos por período"
    filter-mode="period" legend-format="month-year" />

filter-mode é auto-detectado quando omitido: filter-model setado → lookup; legend-format de data → period; caso contrário → direct. filter-toggle (default true) faz o 2º clique na mesma categoria limpar o filtro.

Para handler 100% customizado, use click-action="metodoPhp" — chama $this->metodoPhp($label, $value, $dataIndex) no host.

SEMPRE usar <mad-db-chart> para gráficos

{{-- ERRADO: montar gráfico ECharts manualmente com <script> --}}
<div id="meu-chart" style="height:300px;"></div>
<script>
    var c = echarts.init(document.getElementById('meu-chart'));
    c.setOption({ ... });
</script>

{{-- ERRADO: usar MadChart builder no controller e passar pro Blade --}}
$chart = MadChart::bar('vendas')->fromModel('PedidoVenda')...;
return ['view', ['chart' => $chart]];
<mad-chart :config="$chart" />

{{-- CERTO: declarativo no Blade, zero PHP no controller --}}
<mad-db-chart type="bar" model="PedidoVenda"
    group-by="mes" field="valor_total" total="sum"
    title="Vendas" format="currency:R$" />

Transformer de legenda (id → label)

Para traduzir uma categoria (ex: status_id) num label legível, use a prop legend-transformer. Aceita qualquer callable — closure ou first-class callable. O motor chama call_user_func($fn, $valor, $item, $data) 1× por valor distinto de legenda, então a regra prática NÃO é "closure é proibida" — é não fazer query no banco por valor (isso vira N+1). Feche sobre um mapa pré-carregado (1 query), ou use um Transformers::nome(...) que já faz batch:

@php $statusMap = StatusPedido::pluck('nome', 'id')->all(); @endphp
<mad-db-chart type="donut" model="PedidoVenda"
    group-by="status_id" total="count" title="Por Status" legend
    :legend-transformer="fn($id) => $statusMap[$id] ?? $id" />

{{-- ou, com Transformers global que faz batch internamente: --}}
<mad-db-chart type="donut" model="PedidoVenda"
    group-by="status_id" total="count" title="Por Status" legend
    :legend-transformer="Transformers::statusPedidoNome(...)" />

Anti-padrão (N+1): :legend-transformer="fn($v) => StatusPedido::find($v)?->nome" — um find() por fatia. Tecnicamente funciona, mas dispara uma query por valor.

Quando usar <mad-chart :config="$builder"> (exceção)

Apenas quando precisar de lógica condicional complexa que não pode ser expressa via props do <mad-db-chart> — ex: múltiplas séries com regras de negócio distintas. O transformer pode ser uma closure ou Transformers::nome(...) — só evite lookup no banco por valor (N+1):

@php
    $chart = \Mad\Chart\MadChart::bar('custom')
        ->fromModel('PedidoVenda')
        ->groupBy('status_id')
        ->count()
        ->legendTransformer(Transformers::statusPedidoNome(...));
@endphp
<mad-chart :config="$chart" />