# Widget Recipes — Horus BI

## Conteúdo

- 1. KPI — Semântico com alerta
- 1b. KPI v2 — Layout icon-left com ícone Iconify
- 1c. KPI v2 — Progress com meta e delta
- 1d. KPI v2 — Estilo de card (moldura, accent, fill)
- 1e. KPI v2 — Ícone com pílula de fundo
- 1f. KPI v2 — Tipografia por elemento e por contexto
- 1g. KPI v2 — Delta como badge
- 1h. KPI v2 — Sparkline como fundo
- 1i. KPI v2 — Meta a partir de coluna / secundário
- 1j. KPI v2 — Descrição rica (botão ⓘ)
- 1k. KPI v2 — Quando usar cada layout sem recipe próprio
- 2. KPI — Blocos com cor sólida (identidade de marca)
- 3. Barline — Expressão de cor min/max
- 4. Barline — Ano atual destacado
- 5. Table — Estilo neutro corporativo
- 6. ListBox — Cross-filter como pills clicáveis (DESTAQUE)
- 7. Gauge — Anel fino moderno
- 8. Map — Notas de configuração
- 9. Calendar — Notas de configuração
- 10. Forecast — Notas de configuração
- 11. Pie / Donut — Legenda é opt-in
- 12. Matrix — Os três eixos são três chaves diferentes

Receitas copy-ready por widget. Cada recipe parte de um snippet REAL comprovado em
dashboards de produção e adiciona a camada de design que transforma um widget funcional
num widget bonito.

**Como usar:**
1. Rode `lumo info app:<id> --full` para obter os IDs reais de colunas.
2. Cole o snippet no YAML do app, sob `app.body.dashboards[n].widgets[]`.
3. Substitua cada `<placeholder>` pelo ID real.
4. `lumo push app:<id>` e depois `lumo app export app:<id>` para verificar visualmente.

**Referências cruzadas:**
- Paleta e cores semânticas: `design-tokens.md`
- Qual widget escolher: `chart-selection.md`
- Este arquivo: como configurar cada widget com qualidade visual.

---

> **Antes de copiar qualquer recipe:** todas elas usam `:SUM` no `dataSource`
> por simplicidade de exemplo. `:SUM` só vale para métrica **aditiva**. Ticket
> médio, margem, conversão e qualquer percentual são razão e quebram com `:SUM`
> **e** com `:AVG`; saldo e estoque são semi-aditivos e quebram com `:SUM` no
> eixo de tempo. Leia [`aditividade.md`](aditividade.md) e confira o campo
> `aditividade:` da ficha da métrica antes de trocar o `<col>` do exemplo pelo
> da sua métrica.

## 1. KPI — Semântico com alerta

O KPI semântico comunica estado além do valor: cor saturada + sparkline indicam
tendência sem o usuário precisar ler outra linha.

Quando usar: indicadores críticos (cancelamentos, inadimplência, ocorrências) que
merecem atenção imediata. A cor de fundo `#fef2f2` (tint pálido de vermelho) cria
um halo de alerta sem agredir — o número `#ef4444` carrega o peso.

> Snippet real: SB Hotel — "UHs Cancelados" (alerta vermelho).

```yaml
chart:
  primaryData:
    title:
      value: "UHs Cancelados"     # NUNCA deixar "Kpi" — sempre descreva o que é
  visual:
    backgroundColor: { value: "#fef2f2" }   # tint pálido — halo de alerta
    numberColor: { value: "#ef4444" }        # número saturado, carrega o peso
    numberSize: 30
    titleSize: 16
    numberFormat: reduced
  historicalComparison:
    variation: true
    variationType: relative         # relative | samePeriodLastYear | primarySecondary | primarySecondaryVariation
    column_id: "<date_col>"         # coluna de data p/ a comparação (de lumo info --full)
    display: true                   # sparkline ao fundo (mostra tendência; requer h >= 2)
    defaultPeriod: last12Months
```

**Variante dinâmica** — verde/vermelho pelo valor atual:
```yaml
visual:
  numberColor:
    value: "#ef4444"   # fallback, usado se a expression não resolver
    expression:
      js: "return this.primaryValue > 0 ? '#ef4444' : '#22c55e'"
```

`expression` só é avaliada quando é objeto com `.js` (string não vazia). Uma
string crua como o exemplo antigo desta recipe não casa nenhuma forma aceita e o
renderer cai em silêncio no `.value`. O corpo do `.js` precisa de `return` e lê
`this.primaryValue` / `this.secondaryValue`, nunca `value` solto. `numberFormat.expression`
tem o mesmo destino (nunca é lida, use `numberFormat.format`), e `kpiVersion` é
comparado com `=== 2` estrito, então `kpiVersion: "2"` em string cai no KPI clássico.

**Variante positiva** — mesma estrutura, cores trocadas para métricas que crescer
é bom (receita, NPS, conversão):
```yaml
visual:
  backgroundColor: { value: "#f0fdf4" }   # tint verde
  numberColor: { value: "#16a34a" }
```

> Paleta completa de estado em `design-tokens.md § 4`.

---

## 1b. KPI v2 — Layout icon-left com ícone Iconify

Disponível a partir do `kpiVersion: 2`. O layout `icon-left` posiciona um ícone à
esquerda do valor, criando o padrão "card de métrica" moderno: ícone + número + título
lado a lado. Ideal quando a linha tem vários KPIs e o ícone substitui a leitura do título.

Quando usar: dashboards com muitos KPIs por linha onde o ícone desambigua rapidamente
(ex: 💰 receita, 📦 pedidos, 👥 clientes) sem o usuário precisar ler o título.

> ✅ **Combinação validada em cliente real (AR4K, 2026-07)** para o "card de produto"
> dos overviews modernos: `icon-left` + pílula de ícone (recipe 1e) + `delta.badge`
> (recipe 1g) + card `shadow: soft` / `border.radius: lg` (recipe 1d). Renderiza
> corretamente inclusive no export headless (PNG). É a recipe do
> "Quadrante Executivo" (`layout.md § 2`).

```yaml
chart:
  kpiVersion: 2
  layout: icon-left
  primaryData:
    title:
      value: "Receita Bruta"
  visual:
    numberSize: 28
    numberFormat: reduced
  icon:
    name: "mdi:cash-multiple"   # código Iconify — mdi:*, carbon:*, heroicons:*
    position: left              # left | top
    color: { value: "#16a34a" }
    size: 32                    # px
  text:
    prefix: "R$ "               # aparece antes do número formatado
    subtitle: { value: "Competência corrente" }   # linha abaixo do valor
```

---

## 1c. KPI v2 — Progress com meta e delta

O layout `progress` exibe o valor + barra/anel de avanço em relação a uma meta, mais
a variação vs período anterior. Requer `historicalComparison` para o cálculo do delta;
o bloco `delta` controla apenas posição e modo de exibição do resultado.

Quando usar: indicadores de acompanhamento de meta (vendas vs budget, NPS vs target,
ocupação vs capacidade) onde o usuário precisa ver percentual de atingimento de um
relance.

```yaml
chart:
  kpiVersion: 2
  layout: progress
  primaryData:
    title:
      value: "Vendas vs Meta"
    dataSource: "<vendas_col>:SUM"
  historicalComparison:           # fonte do cálculo de variação (campo clássico)
    variation: true
    variationType: relative       # relative | samePeriodLastYear | primarySecondary | primarySecondaryVariation
    column_id: "<date_col>"
  target:
    enabled: true
    value: { value: "1000000" }   # meta fixa; ou expression: "<meta_col>:SUM" dinâmico
    shape: bar                    # bar | ring | none
  delta:
    position: below               # below | right
    mode: percentOfTarget         # value | absolute | percent | percentOfTarget
  text:
    helper: { value: "vs mês anterior" }   # legenda discreta abaixo do delta
  visual:
    numberFormat: reduced
```

---

## 1d. KPI v2 — Estilo de card (moldura, accent, fill)

Com `kpiVersion: 2` + `visualType: custom`, o KPI desenha a própria moldura: borda,
sombra, raio, borda de destaque (accent) numa lateral e preenchimento (sólido/suave/
gradiente). No editor, o campo `card.style` aplica um preset e preenche os campos
finos; **em YAML declarativo o que renderiza são os campos reais** (`border`, `shadow`,
`accent`, `fill`) — `style` aqui é só o marcador do preset, opcional.

Quando usar: dar identidade a uma linha de KPIs sem fundo sólido pesado — um accent à
esquerda ou um card elevado já cria hierarquia.

```yaml
chart:
  kpiVersion: 2
  visualType: custom              # ativa a moldura própria do card v2
  primaryData:
    title: { value: "Receita" }
    dataSource: "<receita_col>:SUM"
  card:
    style: accent-left            # opcional — marcador do preset
    accent: { side: left, color: { value: "#4E46DD" }, width: lg }   # ← isto é o que pinta
    shadow: soft                  # none | soft | medium
    border: { radius: md }        # sm | md | lg
```

Card de fundo preenchido (alternativa v2 ao truque legado de `backgroundColor`):

```yaml
  card:
    style: gradient
    fill:
      type: gradient              # none | solid | tint | gradient
      color:  { value: "#4E46DD" }
      color2: { value: "#7c3aed" }   # 2ª cor (só gradient)
    border: { radius: lg }
    shadow: soft
  visual:
    numberColor: { value: "#ffffff" }   # fill escuro → defina o contraste do texto à mão
    titleColor:  { value: "#ffffff" }   # (no editor o preset faz isso automaticamente)
```

Tint sutil (wash de alpha 0.12) — alternativa leve ao fundo sólido pesado:

```yaml
  card:
    style: tint
    fill:
      type: tint                  # render aplica chroma(color).alpha(0.12) → wash claro
      color: { value: "#16a34a" }
    border: { radius: lg }
  visual:
    numberColor: { value: "#16a34a" }   # número na cor de destaque; título em slate escuro
    titleColor:  { value: "#1e293b" }
```

Barra lateral larga (preset `split`) — `accent.width: xl` = 12px (sm/md/lg/xl → 3/4/6/12px):

```yaml
  card:
    accent: { side: left, color: { value: "#4E46DD" }, width: xl }   # bloco grosso à esquerda
```

> ⚠ As cores do card v2 (`card.*.color`) usam SÓ `.value` — expressão é ignorada nelas
> (diferente de `visual.numberColor`, que aceita `expression`).

---

## 1e. KPI v2 — Ícone com pílula de fundo

A partir de `kpiVersion: 2`, o ícone pode ganhar uma pílula/círculo de fundo tingido —
o padrão "ícone em chip" dos dashboards modernos. Quando a cor da pílula fica vazia, o
render deriva um tint da própria cor do ícone @ 0.5; com cor definida, usa-a sólida no
`opacity` do slider.

```yaml
chart:
  kpiVersion: 2
  layout: icon-left
  icon:
    name: "mdi:cash-multiple"
    color: { value: "#16a34a" }
    size: 28
    background:
      shape: circle             # none | circle | rounded
      color: { value: "#16a34a" }
      opacity: 0.15             # 0..1 — wash claro atrás do ícone
      padding: 6                # px — entra no tamanho da caixa (lado = ícone + 2·padding)
```

---

## 1f. KPI v2 — Tipografia por elemento e por contexto

Cada texto do KPI tem bold/italic próprios; `number/title` = dados PRIMÁRIOS,
`secondaryNumber/secondaryTitle` = SECUNDÁRIOS (independentes). `overline/subtitle/helper`
aceitam ainda `color`, `size` (px) e `align` próprios. `align: ""` herda o alinhamento
dos Dados Primários.

```yaml
chart:
  kpiVersion: 2
  typography:
    number:         { bold: true }
    secondaryTitle: { italic: true }
    subtitle:
      align: end                # ""=herda primário | start | center | end
      size: 14                  # px (0 = tamanho padrão do elemento)
      color: { value: "#64748b" }
```

---

## 1g. KPI v2 — Delta como badge

Variante do recipe Progresso (1c): renderiza a variação numa pílula tingida em vez de
texto puro. O fundo do badge deriva da cor da variação @ 0.15.

```yaml
chart:
  kpiVersion: 2
  layout: progress
  historicalComparison:
    variation: true
    variationType: relative
    column_id: "<date_col>"
  target:
    enabled: true
    value: { value: "1000000" }
  delta:
    position: below             # below | right
    mode: percentOfTarget       # value | absolute | percent | percentOfTarget
    badge: true                 # ← variação numa pílula tingida
```

---

## 1h. KPI v2 — Sparkline como fundo

`sparkline.background: true` renderiza o histórico esmaecido ATRÁS do valor. O gating é
estrito: só aparece quando o layout tem o slot **trend** ligado
(`classic | value-first | value-trend`) **E** `h >= 2` **E**
`historicalComparison.display: true` **E** `historicalComparison.column_id` definido.
Faltando qualquer um, nada renderiza.

```yaml
chart:
  kpiVersion: 2
  layout: value-trend          # trend-slot ON (classic | value-first | value-trend)
  primaryData:
    title: { value: "Receita" }
    dataSource: "<receita_col>:SUM"
  historicalComparison:
    variation: true
    variationType: relative
    column_id: "<date_col>"     # obrigatório p/ o sparkline
    display: true               # obrigatório p/ o sparkline
  sparkline:
    background: true            # histórico esmaecido atrás do número
  visual:
    numberFormat: reduced
# Requer também o widget com h >= 2 (ex: h: 2) — senão o sparkline não carrega.
```

---

## 1i. KPI v2 — Meta a partir de coluna / secundário

Além de `source: fixed` (valor/expressão), a meta pode vir agregada de uma COLUNA
(`source: column`, agregada no mesmo contexto de filtro dos dados primários) ou reusar o
valor secundário já carregado (`source: secondary`, sem fetch extra).

```yaml
chart:
  kpiVersion: 2
  layout: progress
  target:
    enabled: true
    source: column             # fixed | secondary | column
    column: "<meta_col>:SUM"    # columnId agregado (usado quando source: column)
    shape: bar                 # bar | ring | none
# source: secondary → reusa o secondaryValue já carregado (defina secondaryData).
```

---

## 1j. KPI v2 — Descrição rica (botão ⓘ)

`info.enabled: true` + `info.html` adicionam um botão ⓘ no canto que abre um modal com
descrição em rich-text (HTML). O botão só aparece quando há conteúdo visível além de
markup vazio.

```yaml
chart:
  kpiVersion: 2
  info:
    enabled: true
    html: "<p>Receita líquida = bruta − impostos − devoluções. Fonte: DW fato_vendas.</p>"
```

---

## 1k. KPI v2 — Quando usar cada layout sem recipe próprio

- `value-trend` — valor grande + sparkline de tendência ao fundo/abaixo; para métricas
  cuja DIREÇÃO importa tanto quanto o número (receita, usuários ativos).
- `value-delta` — valor + variação INLINE à direita (delta.position forçado "right");
  linha compacta de KPIs onde a variação fica ao lado do número.
- `compact` — eixo horizontal [valor  rótulo] numa única baseline; máxima densidade,
  vários KPIs por linha estreita.
- `icon-top` — ícone centralizado no topo + título abaixo do valor; cards "tile" mais
  altos, identidade visual forte. **É o único layout que mostra ícone E número
  secundário ao mesmo tempo.**

> **O layout decide o que EXISTE no card, não só como fica.** O que ele não desenha
> não se liga por chave nenhuma:
>
> | layout | secundário | ícone | meta | sparkline |
> |---|---|---|---|---|
> | classic, value-first, value-trend | ✅ | ❌ | ✅ | ✅ |
> | value-delta, progress | ✅ | ❌ | ✅ | ❌ |
> | icon-top | ✅ | ✅ | ✅ | ❌ |
> | icon-left | ❌ | ✅ | ❌ | ❌ |
> | compact | ❌ | ❌ | ❌ | ❌ |
>
> Em `icon-left` e `compact` o `secondaryData` é buscado e simplesmente não é
> desenhado — o número some sem aviso. Pediram "ícone + dois números"? É `icon-top`.

> numberFormat é **string**, não objeto: `currency | percent | integer | decimal |
> reduced | default | null` (null = usa o formato da própria coluna). A forma antiga
> `{format, expression}` ainda é aceita pelo formatador, mas `.expression` nunca é
> avaliada e o objeto não sobrevive a uma edição no editor visual.

---

## 2. KPI — Blocos com cor sólida (identidade de marca)

Quando o dashboard precisa de personalidade visual forte, use KPIs com fundo sólido
e texto claro. O segredo é consistência: escolha 2–3 cores da paleta da empresa e
mantenha-as em todos os blocos da linha.

> Snippet real: Platinão — linha de KPIs com identidade teal.

```yaml
chart:
  visual:
    backgroundColor: { value: "#005956" }   # teal da marca
    numberColor: { value: "#f8fafc" }        # branco-neve — contraste máximo
    titleColor: { value: "#f8fafc" }
```

**Regras para blocos sólidos:**
- Contraste texto/fundo mínimo 4.5:1 (WCAG AA). Branco sobre escuro quase sempre passa.
- Limite 3 cores diferentes na mesma linha de KPIs — mais que isso vira confetti.
- Não misture blocos sólidos com KPIs de fundo branco na mesma linha; separe por
  seção de dashboard.

**Variantes cromáticas comuns:**

| Identidade | backgroundColor | numberColor | titleColor |
|---|---|---|---|
| Teal (Platinão) | `#005956` | `#f8fafc` | `#f8fafc` |
| Navy | `#1e3a5f` | `#f8fafc` | `#94a3b8` |
| Slate dark | `#1e293b` | `#f1f5f9` | `#94a3b8` |
| Brand coral | `#c2410c` | `#fff7ed` | `#fed7aa` |

---

## 3. Barline — Expressão de cor min/max

Destaca visualmente qual categoria performou melhor (verde) e pior (vermelho) — sem
o usuário precisar varrer os números. As demais barras ficam em azul neutro.

> Snippet real: SB Hotel — comparativo de performance por unidade.

```yaml
chart:
  globalType: bar
  visualType: transparent
  showDataLabels: true
  gridLineY: false
  valueConfig:
    "0":
      coloringType: byExpression
      colorExpression:
        js: |
          if (typeof this.value !== 'number') return '#d1d5db';
          const def = '#3b82f6';
          const vals = Array.isArray(this.result)
            ? this.result.map(r => r['<COLKEY>_SUM']).filter(v => typeof v === 'number')
            : [];
          if (!vals.length) return def;
          if (this.value === Math.max(...vals)) return '#22c55e';
          if (this.value === Math.min(...vals)) return '#ef4444';
          return def;
```

**ATENÇÃO:** troque `<COLKEY>` pela chave SQL da coluna agregada (ex: `RECEITA_TOTAL`).
Este é o erro mais comum ao copiar esta recipe de um gráfico para outro — a key fica
apontando para a coluna errada e todas as barras ficam cinza.

Para descobrir a chave correta: `lumo info app:<id> --full` mostra a `sqlKey` de cada
coluna.

**Por que `visualType: transparent`?** Remove a borda do card — o gráfico "flutua"
dentro do grid, dando mais respiro visual. Use com `gridLineY: false` para máxima
limpeza.

---

## 4. Barline — Ano atual destacado

Quando o gráfico compara múltiplos anos, destacar o ano corrente em verde e acinzentar
os demais direciona o olhar para o que importa agora.

> Snippet real: Platinão — histórico anual com destaque no ano vigente.

```yaml
chart:
  axisColoringType: byExpression
  axisColoringExpression:
    js: |
      const ano = new Date().getFullYear().toString();
      return this.name === ano ? '#22c55e' : '#6b7280';
```

`axisColoringType: byExpression` é o campo de dimensão (eixo X / categorias) —
diferente do `valueConfig[n].coloringType`, que colore as barras individualmente.
Use este quando a dimensão é o ano e você quer colorir por categoria.

---

## 4b. Barline — Série temporal executiva (clean)

Num dashboard executivo/vitrine, data label em cada ponto de série temporal é a
principal fonte de poluição visual (feedback real de cliente). A leitura executiva
é a FORMA da curva, não o valor de cada mês — o valor exato fica no tooltip.

> Snippet real: AR4K — "Recebimentos por Mês" / "Pagamentos por Mês" (2026-07).

```yaml
chart:
  globalType: area                 # area (1 série) | bar (colunas mensais) | line
  dimensionColumns:
    - "<mesColId>:AGP"
  valueColumns:
    - "<valorColId>:SUM"
  showDataLabels: false            # ← a regra de ouro do executivo
  showDataScale: true              # escala no eixo substitui os labels
  showDataLegend: false            # 1 série + título do widget já dizem tudo
  gridLineX: false
  gridLineY: false
  orderColumns: ["<mesColId>:AGP"]
  orderColumnsDir: ["ASC"]
  visualType: transparent
  # Colunas mensais finas demais no espaço disponível? Fixe a largura:
  # barWidth: custom
  # customBarWidth: 28
```

**`customBarWidth` também salva rankings/agings com poucas categorias:** 4–5 barras
em `barWidth: auto` num widget alto viram palitos finos com oceanos de espaço.
`barWidth: custom` + `customBarWidth: 40–48` devolve o peso visual.

---

## 5. Table — Estilo neutro corporativo

Uma tabela sem estilo parece planilha. Com as configurações abaixo, ela se integra
ao dashboard com profissionalismo: header destacado, zebra suave, bordas horizontais
apenas.

> Snippet real: Platinão — tabela de detalhamento com identidade corporativa.

```yaml
chart:
  useCustomColors: true
  headerBackgroundColor: "#f1f5f9"
  headerTextColor: "#000000"
  bodyBackgroundColor: "#ffffff"
  bodyTextColor: "#334155"
  oddRowBackgroundColor: "#f8fafc"
  zebraLines: true
  footerBackgroundColor: "#f1f5f9"
  cellBorderStyle: horizontal
  cellBorderThickness: thin
```

**Por que `cellBorderStyle: horizontal`?** Bordas verticais criam uma grade densa.
Só a borda horizontal separa as linhas sem peso visual desnecessário.

**Variante dark** (dashboard escuro):
```yaml
  headerBackgroundColor: "#1e293b"
  headerTextColor: "#f1f5f9"
  bodyBackgroundColor: "#0f172a"
  bodyTextColor: "#cbd5e1"
  oddRowBackgroundColor: "#1e293b"
  footerBackgroundColor: "#1e293b"
```

**Variante status/heatmap** — colore células por valor ou correspondência exata:
```yaml
# Por escala numérica (fieldOptions index-based):
fieldOptions:
  "2":
    colorStyle: cell
    colorType: byScale
    colorScale:
      - { from: 0,    to: 1000, bgColor: "#fee2e2", textColor: "#991b1b" }
      - { from: 1000, to: 5000, bgColor: "#fef9c3", textColor: "#854d0e" }
      - { from: 5000,           bgColor: "#dcfce7", textColor: "#166534" }

# Por valor textual (status):
fieldOptions:
  "1":
    colorStyle: cell
    colorType: byValue
    colorValues:
      - { value: "Aprovado",  bgColor: "#dcfce7", textColor: "#166534" }
      - { value: "Rejeitado", bgColor: "#fee2e2", textColor: "#991b1b" }
      - { value: "Pendente",  bgColor: "#fef9c3", textColor: "#854d0e" }
```

---

## 6. ListBox — Cross-filter como pills clicáveis (DESTAQUE)

O ListBox é o widget de filtro mais poderoso do Horus BI quando bem configurado.
A recipe abaixo transforma uma lista genérica numa experiência de "pills" clicáveis
com cor semântica por valor e barra de peso relativo — o usuário entende a distribuição
antes de clicar.

> Snippet real: SB Hotel — "Status reservas ativas" (filtro principal do dashboard).

```yaml
chart:
  dimensionColumn: "<statusColId>:agp"             # REPLACE — ID da coluna de status
  inlineChartValueColumn: "<measureColId>:sum"      # REPLACE — ID da medida de contagem/peso
  showInlineChart: true
  showInlineChartValue: true
  orderBy: numeric_desc
  orientation: vertical
  displayType: single_column
  selectionMode: single_click
  selectionBehavior: multiple
  borderRadius: heavy
  borderStyle: light
  backgroundColorType: custom
  backgroundColor: { value: "#ffffff" }
  itemColorExpressionEnabled: true
  itemColorExpression:
    js: |
      let background, text;
      if (this.value === 'Confirmada')       { background = '#dcfce7'; text = '#16a34a'; }
      else if (this.value === 'Em andamento'){ background = '#fff7ed'; text = '#ea580c'; }
      return { background, text };
```

**Por que esta recipe é boa (o porquê importa tanto quanto o snippet):**

1. **Substitui dropdown por interação visual** — pills são mais rápidos de entender
   que um `<select>`. O usuário não precisa abrir nada; o estado de todas as opções
   está visível de uma vez.

2. **Barra de peso antes de filtrar** — `showInlineChart: true` desenha uma barra
   horizontal proporcional à medida (ex: contagem de reservas). O usuário vê que
   "Confirmada" tem 3× mais itens que "Cancelada" antes de clicar — informa sem
   obstruir.

3. **Cor semântica por valor** — `itemColorExpressionEnabled` + `itemColorExpression`
   aplica verde para estado positivo, laranja para em andamento, controlando tanto
   `background` quanto `text` de cada pill. O sistema de cor é autoexplicativo;
   nenhuma legenda adicional é necessária.

4. **Cross-filtra automaticamente** — quando o usuário clica, o ListBox chama
   `currentappStore.addFilter(dimensionCol, "=", value)` internamente. Todos os
   outros widgets do dashboard reagem sem nenhuma configuração adicional de wiring.

5. **`selectionBehavior: multiple`** — acumula seleções. O usuário pode selecionar
   "Confirmada" + "Em andamento" ao mesmo tempo; clicar novamente deseleciona.

6. **Posicionamento** — ancore na linha `y: 0` do dashboard, ao lado dos KPIs.
   O filtro precisa estar visível imediatamente ao carregar a página.

**IDs de coluna:** obtenha com `lumo info app:<id> --full`. Procure colunas do tipo
`Dimension` para `dimensionColumn` e do tipo `Measure` para `inlineChartValueColumn`.

---

## 7. Gauge — Anel fino moderno

O gauge padrão parece "termômetro de carro antigo". Com `innerRadius: 70` (anel fino),
`roundedArcs: true` e `coloring: gradient`, o resultado é contemporâneo e profissional.

```yaml
chart:
  gaugeType: speedometer
  innerRadius: 70          # anel fino — 0 = disco cheio, 95 = fio quase invisível
  roundedArcs: true
  coloring: gradient
  colorScale:
    - { from: 0,  to: 60,  bgColor: "#ef4444", name: "Abaixo" }
    - { from: 60, to: 85,  bgColor: "#f59e0b", name: "Atenção" }
    - { from: 85, to: 100, bgColor: "#22c55e", name: "Meta" }
  centerStyle: pill
```

**Por que `coloring: gradient`?** Com `coloring: bands`, cada zona é monocromática
e cria bordas duras entre as faixas. O gradiente suaviza a transição e se aproxima
mais dos dashboards modernos de BI. Use `bands` só quando precisar que as fronteiras
sejam explícitas (ex: SLA de suporte com zonas bem definidas).

**`centerStyle: pill`** — envolve o valor central num pill sólido arredondado, que
destaca a leitura contra o arco colorido.

**Variante com marcador de meta:**
```yaml
  targetMarker:
    constant: 850000        # meta fixa; ou: field: "columnId:SUM" para meta dinâmica
  targetStyle: dashed
  targetColor: "#1e40af"
  markerText: "Meta"
```

---

## 8. Map — Notas de configuração

O Map choropleth usa `colorStart` e `colorEnd` para mapear valor mínimo e máximo
do dataset a uma rampa, e `colorNone` para as geografias sem linha correspondente.
As três chaves ficam **dentro da camada**, não no `chart`.

```yaml
chart:
  baseLayers: ["map"]                 # tiles; [] = sem mapa de fundo
  dataLayers:
    - type: "ufcidade"
      fieldUf: "<ufColId>:AGP"        # REPLACE — coluna de estado (UF)
      fieldValue: "<metricColId>:SUM" # REPLACE — medida para intensidade de cor
      colorStart: "#dbeafe"           # azul claro — valores baixos
      colorEnd: "#1e40af"             # azul escuro — valores altos
      colorNone: "#EFEFEF"            # UF sem dado
```

Para receita: Blues (`#dbeafe` → `#1e40af`). Para conversão/saúde: Greens
(`#dcfce7` → `#15803d`). Paleta completa em `design-tokens.md § 2`.

⚠ `colorScale: {min, max}` **não existe no Map** — é chave de Gauge/Matrix. Escrita
aqui ela é ignorada em silêncio e o mapa sai na rampa padrão branco → índigo.

⚠ Não há controle de viewport. `initialZoom`, `initialCenter` e `baseMap` não
existem; o mapa sempre dá zoom automático no bounding box dos dados. O fundo é
`baseLayers`, um array com `"map"` e/ou `"satellite"`.

⚠ Marcadores (`coordenadas`) e rotas (`rotas`) **não são coloríveis** — não existe
`markerColor` nem `lineColor`. Rotas usam uma paleta fixa de 12 cores por índice.
`defaultColor` só é lido pela camada `geojson`.

---

## 9. Calendar — Notas de configuração

O Calendar heat map usa `startColor` e `endColor` para gradiente de intensidade:

```yaml
chart:
  visual:
    startColor: "#e6f3ff"    # cor para dias de baixo volume
    endColor: "#0066cc"      # cor para dias de alto volume
    noDataColor: "#f0f0f0"   # cinza neutro — dias sem dado
    displayMode: "both"      # mostra número do dia + valor
    viewType: "select-month" # navegação com seletor de mês
```

Dica: para metrics onde "mais é pior" (ex: incidentes, cancelamentos), inverta a
rampa: `startColor: "#dcfce7"` (verde claro = poucos) → `endColor: "#dc2626"` (vermelho
escuro = muitos).

---

## 10. Forecast — Notas de configuração

O Forecast usa `realizadoColor` e `forecastColor` para distinguir o histórico da
projeção:

```yaml
chart:
  realizadoColor:
    value: "#6567ea"         # roxo Horus — série histórica
  forecastColor:
    value: "#30b1ec"         # azul claro — projeção + banda de confiança
  daysPredict: 60
  periodToFetch: "allPeriod" # usa todo o histórico disponível para melhor acurácia
```

Use cores contrastantes mas da mesma família tonal. Evite vermelho para forecast —
a associação com "errado" ou "alerta" confunde. Azul/roxo são neutros e convencionais
para projeção de séries temporais.

---

## 11. Pie / Donut — Legenda é opt-in

A legenda vem **desligada** por padrão: o render lê `showInLegend: !!chart.showLegend`,
então a chave ausente significa sem legenda. Um donut sem legenda e sem rótulo de
categoria é um anel colorido que não diz o que é cada fatia — reclamação garantida.

```yaml
chart:
  chartType: "donut"
  showLegend: true                # ← sempre, salvo se as fatias já vêm rotuladas
  dataLegendPosition: "bottom"    # bottom | top
  showDataLabels: true
  showDataLabelsAxis: true        # nome da categoria na própria fatia
  showDataLabelsPercent: true     # participação — é aqui que "% do total" vive
```

Se você rotula as fatias com `showDataLabelsAxis: true` e `dataLabelsPosition:
"external"`, a legenda vira redundância — aí sim pode desligar. Nunca desligue as duas.

---

## 12. Matrix — Os três eixos são três chaves diferentes

A Matrix **não** é uma Table com configuração extra. Linhas, colunas e medidas moram
em chaves separadas, e errar isso não degrada o visual: o widget não renderiza.

```yaml
chart:
  matrixType: "levels"            # obrigatório — levels | hierarchy
  fields: ["<dimColId>:agp"]      # dimensões de LINHA (níveis de drill)
  columnField: "<dateColId>:yearmonth"   # dimensão de COLUNA — SINGULAR, string
  valueFields: ["<measureColId>:sum"]    # MEDIDAS
  showTotal: true
  orderColumns: ["<dateColId>:yearmonth"]
  orderColumnsDir: ["ASC"]
```

⚠ `matrixConfig` (`rowDimensions` / `columnDimensions` / `values` / `showRowTotals`…)
**não existe**. Nada lê essas chaves. Uma Matrix escrita assim deixa `valueFields`
vazio e renderiza o erro "Não há campos de valor definidos para esta tabela".

⚠ `fieldOptions` na Matrix indexa **`valueFields[]`**, não `fields[]` — `"0"` é a
primeira MEDIDA. (Na Table indexa `fields[]`; é a mesma chave com base diferente.)

Estilo: as mesmas chaves da Table (`useCustomColors`, `cellBorderStyle`, `zebraLines`,
`cellPadding`, `fontSize`) valem aqui — ver § 5. Adicionalmente,
`showColumnDelimiters` **assume `true`** quando ausente; escreva `false` explicitamente
se quiser a grade sem as réguas verticais entre grupos de coluna.

**"% do total" numa Matrix** é `enableVerticalAnalysis: true` (cada célula como % do
total da sua coluna), com `verticalAnalysisReference` quando o denominador precisa ser
uma linha específica em vez do total — padrão DRE. Cada análise ligada acrescenta uma
sub-coluna por medida, então reserve largura.
