feat(cpp/viz): static-plot primitive + tooltips + rotated labels + card compacta

Nuevo primitivo compartido:
- cpp/functions/viz/plot_static.h: header-only con flags ImPlotFlags /
  ImPlotAxisFlags agrupados (NoFrame|NoMenus|NoBoxSelect|NoMouseText +
  Lock|NoInitialFit|NoHighlight) para visualizacion estatica en
  dashboards. Lo usan todos los charts de viz/.

Charts refactorizados a v1.1 con parametro `height` explicito (rompe el
feedback loop con contenedores AutoResizeY que producia vibracion al
redimensionar) y ejes pineados con ImPlotCond_Always:
- bar_chart v1.2: tooltip al hover (label + valor) + auto-rotacion de
  labels a 45 cuando no caben horizontalmente (medidos con CalcTextSize
  vs ancho del plot). Los labels rotados se dibujan manualmente con
  ImDrawList::PrimQuadUV + ImFontBaked::FindGlyph (API ImGui 1.92+).
- pie_chart v1.1: tooltip por slice (detecta cual via atan2 desde centro
  en sentido CCW matematico, que es como ImPlot dibuja los slices desde
  angle0=90) con label + valor + porcentaje. Aspect 1:1 mantenido.
- line_plot, scatter_plot, histogram v1.1: ejes pineados con limites
  calculados de min/max + 5% headroom (histogram usa AutoFit por los
  bins dinamicos, con Lock para bloquear pan/zoom).

kpi_card v1.2: card mas compacta — altura 78px (antes 108), font scale
1.4x (antes 1.8x), padding sm (antes md). Apto para densidades altas
de KPIs en dashboards.

fullscreen_window v0.2: NoScrollbar|NoScrollWithMouse para eliminar el
scrollbar fugaz que aparecia cuando el contenido excedia por 1-2px la
ventana, reflow de ancho y vibracion visible al redimensionar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-24 21:31:00 +02:00
parent 6f269949f1
commit 3f622561ce
20 changed files with 582 additions and 156 deletions
+16 -17
View File
@@ -3,11 +3,11 @@ name: bar_chart
kind: component
lang: cpp
domain: viz
version: "1.1.0"
version: "1.2.0"
purity: pure
signature: "void bar_chart(const char* title, const char* const* labels, const float* values, int count, float bar_width = 0.67f, float height = 200.0f)"
description: "Renderiza un grafico de barras verticales con ImPlot, ejes pineados (Lock + Cond_Always) y altura explicita para evitar feedback loops visuales"
tags: [implot, chart, visualization, gpu, bar, locked-axes]
description: "Barras verticales ImPlot con ejes pineados, altura explicita, tooltip al hover y auto-rotacion 45 de labels cuando no caben horizontales"
tags: [implot, chart, visualization, gpu, bar, tooltip, rotated-labels, locked-axes]
uses_functions: []
uses_types: []
returns: []
@@ -21,36 +21,35 @@ file_path: "cpp/functions/viz/bar_chart.cpp"
framework: imgui
params:
- name: title
desc: "Titulo del grafico de barras (tambien se usa como id interno del plot)"
desc: "Titulo / id interno del plot"
- name: labels
desc: "Array de etiquetas para el eje X, una por barra"
- name: values
desc: "Array de valores numericos para la altura de cada barra"
desc: "Array de valores numericos (altura de cada barra)"
- name: count
desc: "Numero de barras (longitud de labels y values)"
- name: bar_width
desc: "Ancho de cada barra como fraccion del hueco de celda (default 0.67)"
- name: height
desc: "Altura del plot en pixeles (default 200). Explicita para evitar feedback loops con contenedores AutoResizeY"
output: "Renderiza el grafico de barras en el frame ImGui actual con ejes X/Y pineados"
desc: "Altura del plot en pixeles (default 200). Explicita para evitar feedback loops con AutoResizeY"
output: "Renderiza barras, tooltip al hover con label+valor, y si los labels horizontales no caben los dibuja rotados 45 grados"
---
# bar_chart
Barras verticales con ImPlot, pensado para dashboards estaticos (resumenes, KPIs). Diseno:
Barras verticales ImPlot pensadas para dashboards. Tres cosas no triviales:
- **Ejes pineados** — `ImPlotCond_Always` + `ImPlotAxisFlags_Lock` + `ImPlotAxisFlags_NoInitialFit`. Sin esto, ImPlot auto-fitea cada frame y las barras oscilan los primeros frames.
- **Y max con 15% de headroom** — calculado a partir de `values` una vez, mas estetico que el fit apretado de ImPlot.
- **Altura explicita** — `ImVec2(-1, height)` en vez de `ImVec2(-1, 0)`. Dentro de un `dashboard_panel` (BeginChild con AutoResizeY), un height=0 crea un feedback loop: el plot pide espacio al padre, el padre se redimensiona al plot, el plot recalcula y asi. El efecto visual es que las barras se deslizan de lado al abrir la ventana. Con height fija no hay loop.
- **Sin inputs** — `ImPlotFlags_NoInputs | NoFrame | NoBoxSelect | NoMouseText`. Pensado para visualizacion, no exploracion.
1. **Ejes pineados**`plot_static::kPlotFlags` + `kAxisFlags` (Lock + NoInitialFit + Cond_Always) con `y_max` pre-calculado + 15% headroom. Sin esto ImPlot auto-fitea cada frame y las barras oscilan.
2. **Tooltip**si `IsPlotHovered()`, detecta la barra bajo el cursor (`round(mouse.x)` con tolerancia `bar_width/2`) y muestra `label` + valor.
3. **Labels auto-rotados**mide la suma de `CalcTextSize(label) + 12px` contra el ancho del plot; si no caben, dibuja los labels rotados 45° manualmente con `ImDrawList::PrimQuadUV` + glyphs del font atlas (`ImFontBaked::FindGlyph` — API ImGui 1.92+). Reserva 48px abajo del plot para los labels rotados. Si caben se usan los ticks horizontales normales de ImPlot.
Debe llamarse dentro del render callback de `fn::run_app`.
Altura explicita (`height`) rompe el feedback loop con contenedores `AutoResizeY` (ver `viz/plot_static.h`).
## Ejemplo
```cpp
const char* langs[] = {"go", "py", "ts", "sh", "cpp"};
float counts[] = {412, 187, 94, 63, 36};
bar_chart("Functions by lang", langs, counts, 5); // height por defecto 200
bar_chart("Compact", langs, counts, 5, 0.8f, 120.0f); // compacto
const char* domains[] = {"core", "finance", "cybersecurity", "datascience", "infra"};
float counts[] = {412, 187, 94, 63, 36};
bar_chart("##domains", domains, counts, 5); // horizontal si cabe
bar_chart("##domains", domains, counts, 5, 0.8f, 240); // rotated si no cabe
```