feat(cpp/viz): kpi_card v1.1 + bar_chart v1.1 — contenedor y altura fijas

kpi_card:
- v1.1: envuelve el contenido en BeginChild con surface bg + border +
  radius::md + padding::md (tokens). Replica Mantine Paper withBorder
  radius="md" p="md" usado en @fn_library/kpi_card.tsx.
- Ancho adaptativo via GetContentRegionAvail — requiere contenedor que
  propague ancho constrained (ImGui::BeginTable). dashboard_grid / BeginGroup
  no funcionan porque no constrainen ancho y la card desborda la celda.
- Linea de trend SIEMPRE visible: delta, sparkline, o em dash (text_dim)
  como placeholder, para que un grid de KPIs quede alineado vertical.
- Colores del delta via tokens (success/error) en vez de hardcoded ImVec4.

bar_chart:
- v1.1: altura explicita como parametro (default 200px). Sin esto, ImPlot
  con ImVec2(-1, 0) entra en feedback loop cuando esta dentro de un
  dashboard_panel (BeginChild con AutoResizeY): plot pide espacio -> padre
  se redimensiona -> plot recalcula. Efecto visual: las barras se deslizan
  los primeros frames.
- Ejes blindados: Lock + NoInitialFit + Cond_Always ademas de los flags
  previos. Y max pre-calculado con 15% de headroom.
- Sin inputs (NoInputs|NoFrame|NoBoxSelect|NoMouseText): estos charts son
  de resumen, no de exploracion.

Actualizados los .md correspondientes con el contrato visual + requisitos
de contenedor, para que cualquier dashboard que componga estos primitivos
obtenga el mismo look.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-24 20:59:51 +02:00
parent 4a95407d0e
commit 8e11c5cfce
5 changed files with 97 additions and 51 deletions
+29 -14
View File
@@ -5,12 +5,19 @@
namespace {
// Plot bars con ejes pineados (no se mueven entre frames) y labels categoricos.
// ImPlot por defecto auto-fitea Y en cada frame, lo que provoca oscilacion visual.
// Lo resolvemos calculando y_max una vez y forzandolo con ImPlotCond_Always.
// Render bars con ejes fijos y tamano de plot explicito. Dos cosas clave:
//
// 1) Limites pineados con ImPlotCond_Always + Lock: ImPlot por defecto
// auto-fitea Y cada frame, lo que provoca oscilacion visual.
//
// 2) Altura explicita (height > 0 -> ImVec2(-1, height)): sin esto, ImPlot
// toma el espacio disponible del contenedor. Si el contenedor es un
// BeginChild con AutoResizeY (como dashboard_panel), aparece un feedback
// loop: plot pide espacio al padre -> padre se redimensiona al plot ->
// plot recalcula -> las barras "se deslizan" en los primeros frames.
template <typename T>
void draw_bars(const char* title, const char* const* labels, const T* values,
int count, double bar_width) {
int count, double bar_width, float height) {
if (count <= 0) return;
double y_max = 0.0;
@@ -21,13 +28,21 @@ void draw_bars(const char* title, const char* const* labels, const T* values,
y_max *= 1.15; // 15% headroom sobre la barra mas alta
const ImPlotFlags plot_flags =
ImPlotFlags_NoMenus | ImPlotFlags_NoBoxSelect | ImPlotFlags_NoMouseText;
const ImPlotAxisFlags x_flags =
ImPlotAxisFlags_NoMenus | ImPlotAxisFlags_Lock | ImPlotAxisFlags_NoGridLines;
const ImPlotAxisFlags y_flags =
ImPlotAxisFlags_NoMenus | ImPlotAxisFlags_Lock;
ImPlotFlags_NoMenus | ImPlotFlags_NoBoxSelect | ImPlotFlags_NoMouseText
| ImPlotFlags_NoInputs | ImPlotFlags_NoFrame;
if (ImPlot::BeginPlot(title, ImVec2(-1, 0), plot_flags)) {
const ImPlotAxisFlags x_flags =
ImPlotAxisFlags_NoMenus | ImPlotAxisFlags_Lock
| ImPlotAxisFlags_NoInitialFit | ImPlotAxisFlags_NoGridLines
| ImPlotAxisFlags_NoHighlight;
const ImPlotAxisFlags y_flags =
ImPlotAxisFlags_NoMenus | ImPlotAxisFlags_Lock
| ImPlotAxisFlags_NoInitialFit | ImPlotAxisFlags_NoHighlight;
const ImVec2 plot_size(-1.0f, height > 0.0f ? height : 200.0f);
if (ImPlot::BeginPlot(title, plot_size, plot_flags)) {
std::vector<double> positions(count);
for (int i = 0; i < count; i++) positions[i] = i;
@@ -45,11 +60,11 @@ void draw_bars(const char* title, const char* const* labels, const T* values,
} // namespace
void bar_chart(const char* title, const char* const* labels, const float* values,
int count, float bar_width) {
draw_bars<float>(title, labels, values, count, static_cast<double>(bar_width));
int count, float bar_width, float height) {
draw_bars<float>(title, labels, values, count, static_cast<double>(bar_width), height);
}
void bar_chart(const char* title, const char* const* labels, const double* values,
int count, double bar_width) {
draw_bars<double>(title, labels, values, count, bar_width);
int count, double bar_width, float height) {
draw_bars<double>(title, labels, values, count, bar_width, height);
}