trazo 0.0.1

Enhanced charts and visualizations for ratatui terminal UIs
Documentation
# BarChart — diseño

Documento de trabajo. No es API estable, no es documentación pública. Su propósito es alinear la API y la arquitectura del primer widget antes de escribir código.

## Objetivo

Cubrir los huecos del `ratatui::widgets::BarChart` con una sola API consistente:

- Múltiples series por categoría (stacked, grouped, percent-stacked).
- Ejes mejores (auto-tick, formato de números, labels rotados).
- Valores `f64` además de `u64`.
- Negativos y orientación horizontal (post-MVP, pero la API debe admitirlos sin rediseño).

## Comparativa con ratatui::BarChart (0.30)

| Feature                  | ratatui::BarChart | trazo::BarChart |
|--------------------------|:-----------------:|:---------------:|
| Single-series vertical   || ✓ (caso N=1)    |
| Grouped (visual)         | parcial¹          | ✓ nativo        |
| Stacked                  |||
| Percent-stacked          |||
| Horizontal               | parcial²          | ✓ (post-MVP)    |
| Valores negativos        || ✓ (post-MVP)    |
| Tipo de valor            | `u64`             | `f64`           |
| Ejes con auto-tick       |||
| Format de números        | manual            | builtin (`AxisFormat`) |
| Labels rotados           || ✓ (post-MVP)    |
| Legend                   || ✓ opcional      |

¹ `BarGroup` en ratatui solo separa visualmente, no permite barras side-by-side por serie.
² `direction(Direction::Horizontal)` existe pero los labels no se reposicionan bien.

## API propuesta

### Types

```rust
pub struct BarChart<'a> { /* ... */ }
pub struct BarGroup<'a> { /* ... */ }
pub struct Bar<'a>      { /* ... */ }

pub enum BarMode {
    Single,         // una sola serie por grupo
    Stacked,        // series apiladas
    Grouped,        // series side-by-side
    PercentStacked, // stacked normalizado a 100%
}

// Post-MVP, pero ya reservado en la API:
pub enum BarOrientation { Vertical, Horizontal }
```

### Modelo de datos

Un `BarChart` contiene N `BarGroup`. Un `BarGroup` representa una **categoría** del eje (p.ej. "Q1") y contiene N `Bar`, una por **serie** (p.ej. "ventas", "costes"). El orden de las series debe ser consistente entre grupos para que stacked/grouped tenga sentido.

```rust
let chart = BarChart::new()
    .mode(BarMode::Stacked)
    .data([
        BarGroup::new("Q1").bars([("ventas", 120.0), ("costes", 80.0)]),
        BarGroup::new("Q2").bars([("ventas", 150.0), ("costes", 90.0)]),
        BarGroup::new("Q3").bars([("ventas", 180.0), ("costes", 100.0)]),
    ])
    .series_colors([Color::Green, Color::Red])
    .legend(true)
    .bar_width(5)
    .group_gap(2);
```

### Builders disponibles

| Método                 | Default               | Notas                                       |
|------------------------|-----------------------|---------------------------------------------|
| `.mode(BarMode)`       | `Single`              | Single si N=1 por grupo                     |
| `.data(impl IntoIterator<Item = BarGroup>)` || Reemplaza datos                |
| `.series_colors(impl IntoIterator<Item = Color>)` | rainbow | Cíclico si hay menos colores que series |
| `.legend(bool)`        | `false`               | Si true, reserva rect para legend           |
| `.bar_width(u16)`      | auto                  | Auto reparte el ancho disponible            |
| `.group_gap(u16)`      | `1`                   | Celdas vacías entre grupos                  |
| `.bar_gap(u16)`        | `0`                   | Solo aplica en `Grouped`                    |
| `.x_axis(AxisConfig)`  | sensato               | Configura ticks, labels, format             |
| `.y_axis(AxisConfig)`  | sensato               | Idem                                        |
| `.block(Block)`        | sin block             | Borde/title estilo ratatui                  |
| `.style(Style)`        | default               | Estilo base aplicado al fondo               |
| `.render_style(RenderStyle)` | `HalfBlock`     | Enum `#[non_exhaustive]`. MVP solo expone/acepta `HalfBlock`; otros backends llegan en el PR de polishing junto con el [playground]playground.md. |

### Trait impl

```rust
impl Widget for BarChart<'_> {
    fn render(self, area: Rect, buf: &mut Buffer) { /* ... */ }
}
```

Stateless (`Widget` no `StatefulWidget`) en el MVP. Cuando llegue interactividad (hover, tooltip) consideraremos `StatefulWidget` con `BarChartState`.

## Mockups ASCII

Resolución vertical: usamos octants (`▁▂▃▄▅▆▇█`) para 8 sub-niveles por celda. Resolución horizontal: 1 carácter por columna mínima.

### Single (BarMode::Single)

```
ventas trimestrales (€k)
180 ┤              ███
    │              ███
    │              ███
150 ┤        ███   ███
    │        ███   ███
120 ┤  ███   ███   ███
    │  ███   ███   ███
    │  ███   ███   ███
  0 └──────────────────
       Q1    Q2    Q3
```

### Stacked (BarMode::Stacked)

```
P&L Q1-Q3 (€k)
280 ┤              ███  ← costes (rojo)
    │              ███
    │              ███
240 ┤        ███   ▒▒▒  ← ventas (verde)
    │        ███   ▒▒▒
200 ┤        ▒▒▒   ▒▒▒
    │        ▒▒▒   ▒▒▒
120 ┤  ███   ▒▒▒   ▒▒▒
    │  ▒▒▒   ▒▒▒   ▒▒▒
    │  ▒▒▒   ▒▒▒   ▒▒▒
  0 └──────────────────
       Q1    Q2    Q3
       ▒ ventas   █ costes
```

### Grouped (BarMode::Grouped)

```
P&L Q1-Q3 (€k)
180 ┤              ▒▒
    │              ▒▒
150 ┤        ▒▒    ▒▒
    │        ▒▒    ▒▒
120 ┤  ▒▒    ▒▒    ▒▒
    │  ▒▒    ▒▒    ▒▒██
    │  ▒▒    ▒▒██  ▒▒██
 80 ┤  ▒▒██  ▒▒██  ▒▒██
    │  ▒▒██  ▒▒██  ▒▒██
  0 └──────────────────
       Q1    Q2    Q3
       ▒ ventas  █ costes
```

### Percent stacked (BarMode::PercentStacked)

```
share of revenue
100% ┤ ███   ███   ███  ← costes
     │ ███   ███   ███
 60% ┤ ▒▒▒   ▒▒▒   ▒▒▒  ← ventas
     │ ▒▒▒   ▒▒▒   ▒▒▒
  0% └──────────────────
       Q1    Q2    Q3
```

## Estructura de módulos propuesta

```
src/
├── lib.rs              — re-exports públicos
└── bar/
    ├── mod.rs          — BarChart widget, impl Widget
    ├── group.rs        — BarGroup, Bar (modelo de datos)
    ├── mode.rs         — BarMode enum + helpers
    ├── render.rs       — primitivas de render (1 fn por modo)
    ├── axis.rs         — ejes específicos del bar chart
    └── legend.rs       — legend opcional
```

`axis.rs` y `legend.rs` arrancan en `bar/` pero si los reusan otros widgets (Line, Area…) los promocionamos a `src/axis/` y `src/legend/` en su momento. No diseñamos para reuse antes de tiempo.

## Alcance del primer PR (MVP)

Hace:
- `BarMode::Single` y `BarMode::Stacked` vertical, con f64.
- Ejes Y con auto-tick y formato básico (`%.0f`, `%.1f`).
- Eje X con labels de grupo, sin rotación.
- `BarGroup` + `Bar` API completa.
- Colores por serie (cíclico).
- `Block` integration (borde/title).
- Render via half-blocks + octants (`▀▄█ ▁..█`). El método `.render_style(RenderStyle)` se incluye en la API desde el MVP con enum `#[non_exhaustive]` que solo expone `HalfBlock`, para añadir backends en el PR de polishing sin breaking change.
- Example runnable: `examples/bar_basic.rs` con los dos modos.
- Tests con `Buffer` snapshots para single y stacked.

NO hace (sigue en backlog explícito):
- `Grouped` y `PercentStacked` (segundo PR).
- Orientation horizontal.
- Valores negativos.
- Labels rotados / truncados.
- Legend (la mention en el mockup pero sin implementación inicial).
- Animaciones / interactividad.
- Otros render styles (`Ascii`, `AsciiColor`, `Braille`, `Octants` standalone). El enum existe en la API pero solo `HalfBlock` está implementado. Los demás backends y el playground interactivo se incorporan en el PR siguiente — ver [docs/design/playground.md]playground.md.

## Open questions

Antes de empezar a codear, decisiones pendientes:

1. **Tipo numérico:** `f64` (más versátil) vs `u64` (lo que usa ratatui). Propuesta: **`f64`**, con `From<u64>` automático.
2. **¿Cómo se especifican los colores de serie?**
   - (a) `.series_colors([…])` global como propongo arriba.
   - (b) `Bar::with_style(Style)` por barra individual.
   - (c) Ambos, con `series_colors` como default y `with_style` como override.
   Propuesta: **(c)**.
3. **¿Anchura de barras por defecto auto o explícita?** Propuesta: **auto**, calcula a partir del ancho disponible y nº de grupos/series. `.bar_width(n)` lo fuerza.
4. **¿Spacing entre grupos vs entre barras dentro de un grupo:** Propuesta: dos métodos separados, `group_gap` y `bar_gap` (este último solo aplica en `Grouped`).
5. **¿Block integration:** Propuesta: aceptar `.block(Block)` igual que ratatui — el widget renderiza dentro del inner del block.
6. **¿Eje Y baseline en 0 forzado o auto?** Propuesta: **forzado a 0** para barras (convención de visualización), con escape hatch `.y_axis(AxisConfig::new().baseline(BaselineMode::Auto))` para casos avanzados.
7. **¿AxisConfig se introduce ya en el MVP o lo simplificamos?** Propuesta: introducir con defaults sensatos. Si el AxisConfig se complica demasiado lo recortamos al MVP a `.y_ticks(Vec<f64>)` y `.y_label_format(impl Fn(f64) -> String)`.
8. **¿Idioma de la documentación pública (rustdoc comments)?** El README está en inglés siguiendo el patrón de gitorii. Propuesta: rustdoc en inglés también para consistencia con el ecosistema crates.io. Este DESIGN.md sí en español por ser interno.
9. **¿Naming de la API:** ¿`BarChart` o `Bars` o `BarPlot`? Propuesta: **`BarChart`** por familiaridad con ratatui/usuarios.
10. **¿Versión del módulo:** ¿Quedaría como `trazo::bar::BarChart` o re-export plano `trazo::BarChart`? Propuesta: **ambos** — interno organizado en submódulos, re-export plano en `lib.rs`.
11. **¿Render styles del segundo PR:** Propuesta inicial: **`Ascii`**, **`AsciiColor`**, **`HalfBlock`** (default), **`Octants`**, **`Braille`**. Cada widget declara qué styles tiene sentido soportar; pedir uno no aplicable cae al default del widget con un debug log. Ver [playground.md § Render styles]playground.md para la tabla completa.

## Para siguientes iteraciones (backlog)

- `Grouped`, `PercentStacked`.
- **Render styles & playground** (PR 2): activar los backends `Ascii`, `AsciiColor`, `Octants`, `Braille` además del `HalfBlock` del MVP, y abrir el playground interactivo en `examples/playground.rs`. Ver [docs/design/playground.md]playground.md.
- `BarOrientation::Horizontal`.
- Valores negativos (eje cruzado en cero).
- Labels rotados, truncados, multi-línea.
- `Legend` widget (compartible con futuros widgets).
- `AxisConfig` extendido (date formatting, log scale, dual axis).
- `StatefulWidget` con hover/tooltip cuando llegue interactividad.