chore: snapshot WIP previo + flow 0008 + 7 sub-issues (0112-0119)
Snapshot de WIP acumulado de sesiones previas antes de merge wave 1 del flow 0008 (kanban_cpp + agent_runner_api + DoD schema). Incluye: - dev/flows/0008-kanban-cpp-and-agent-workflows.md - dev/issues/0112-0119*.md (7 sub-issues) - WIP previo en cmd/fn/doctor.go, registry/*, modules/, cpp/, etc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
// data_table_ai_panel — modal "Ask AI" de la tabla TQL.
|
||||
// Sub-funcion extraida de modules/data_table/data_table.cpp (issue 0107c).
|
||||
//
|
||||
// Rangos de lineas del fuente original (antes de la extraccion):
|
||||
// - Stub llm_anthropic (no-op) : lineas 82-112
|
||||
// - Modal "Ask AI" completo : lineas 4209-4328
|
||||
// - Boton trigger en viz selector: linea 1588
|
||||
//
|
||||
// NOTA DEUDA TECNICA: llm_anthropic no esta en el registry todavia (Wave 4 TODO).
|
||||
// Pendiente promover a cpp/functions/infra/llm_anthropic (.h + .cpp + .md).
|
||||
|
||||
#include "viz/data_table_ai_panel.h"
|
||||
#include "core/data_table_types.h"
|
||||
#include "core/tql_apply.h"
|
||||
#include "imgui.h"
|
||||
|
||||
// llm_anthropic: usar header real si disponible, stub si no.
|
||||
#ifdef FN_LLM_ANTHROPIC
|
||||
# include "core/llm_anthropic.h"
|
||||
#else
|
||||
// Stub no-op (inline en este TU hasta que llm_anthropic se promueva al registry).
|
||||
// TODO: Wave 4 — promover a cpp/functions/infra/llm_anthropic.cpp + .h + .md
|
||||
namespace llm_anthropic {
|
||||
enum class OutputMode { TQL, SQL };
|
||||
struct AskInput {
|
||||
std::string question;
|
||||
std::string tql_current;
|
||||
std::vector<std::string> col_names;
|
||||
std::vector<data_table::ColumnType> col_types;
|
||||
std::vector<std::string> joinable_names;
|
||||
OutputMode mode = OutputMode::TQL;
|
||||
std::string model;
|
||||
int max_tokens = 8192;
|
||||
};
|
||||
struct AskResult {
|
||||
std::string code;
|
||||
std::string raw;
|
||||
std::string error;
|
||||
int tokens_in = 0;
|
||||
int tokens_out = 0;
|
||||
};
|
||||
inline AskResult ask(const AskInput&, const std::string& = "") {
|
||||
AskResult r;
|
||||
r.error = "llm_anthropic not available (stub). Build with FN_LLM_ANTHROPIC=1.";
|
||||
return r;
|
||||
}
|
||||
} // namespace llm_anthropic
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdio>
|
||||
|
||||
namespace data_table {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// draw_ask_ai_modal
|
||||
// Dibuja el modal "Ask AI". Debe llamarse cada frame (patron ImGui).
|
||||
// Abre el popup si ask_ai.open == true.
|
||||
// ---------------------------------------------------------------------------
|
||||
void draw_ask_ai_modal(AskAiState& ask_ai,
|
||||
State& st,
|
||||
const std::vector<std::string>& active_headers,
|
||||
const std::vector<ColumnType>& active_types,
|
||||
int orig_cols)
|
||||
{
|
||||
if (ask_ai.open) ImGui::OpenPopup("Ask AI");
|
||||
ImGui::SetNextWindowSize(ImVec2(820, 560), ImGuiCond_Appearing);
|
||||
if (ImGui::BeginPopupModal("Ask AI", &ask_ai.open,
|
||||
ImGuiWindowFlags_NoSavedSettings)) {
|
||||
ImGui::TextDisabled("Ask en lenguaje natural. Default TQL. SQL solo si DuckDB linkado.");
|
||||
const char* modes[] = {"TQL", "SQL (DuckDB)"};
|
||||
#ifndef FN_TQL_DUCKDB
|
||||
// SQL mode disabled visually pero el toggle existe (informativo)
|
||||
if (ask_ai.mode == 1) ask_ai.mode = 0;
|
||||
#endif
|
||||
ImGui::Combo("Output##askmode", &ask_ai.mode, modes, IM_ARRAYSIZE(modes));
|
||||
#ifndef FN_TQL_DUCKDB
|
||||
if (ask_ai.mode == 1) {
|
||||
ImGui::TextColored(ImVec4(1, 0.5f, 0.3f, 1),
|
||||
"SQL mode requires FN_TQL_DUCKDB=1 build flag.");
|
||||
}
|
||||
#endif
|
||||
ImGui::InputTextMultiline("##ask_q", ask_ai.question, sizeof(ask_ai.question),
|
||||
ImVec2(-1, 80));
|
||||
ImGui::BeginDisabled(ask_ai.busy);
|
||||
if (ImGui::Button("Send")) {
|
||||
ask_ai.busy = true;
|
||||
ask_ai.status = "Sending...";
|
||||
ask_ai.error.clear();
|
||||
ask_ai.response_code.clear();
|
||||
ask_ai.response_raw.clear();
|
||||
|
||||
// Build AskInput desde el state actual.
|
||||
llm_anthropic::AskInput in;
|
||||
in.question = ask_ai.question;
|
||||
in.tql_current = ask_ai.current_tql;
|
||||
in.col_names = active_headers;
|
||||
in.col_types = active_types;
|
||||
in.mode = (ask_ai.mode == 1)
|
||||
? llm_anthropic::OutputMode::SQL
|
||||
: llm_anthropic::OutputMode::TQL;
|
||||
|
||||
// Llamada blocking (UI freeze breve durante red).
|
||||
auto r = llm_anthropic::ask(in);
|
||||
ask_ai.busy = false;
|
||||
if (!r.error.empty()) {
|
||||
ask_ai.error = r.error;
|
||||
ask_ai.status = "Error";
|
||||
} else {
|
||||
ask_ai.response_raw = r.raw;
|
||||
ask_ai.response_code = r.code;
|
||||
ask_ai.status = "Got response.";
|
||||
// Llenar edit buffer
|
||||
std::snprintf(ask_ai.edit_buf, sizeof(ask_ai.edit_buf),
|
||||
"%s", r.code.c_str());
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
if (!ask_ai.status.empty()) {
|
||||
ImGui::TextDisabled("%s", ask_ai.status.c_str());
|
||||
}
|
||||
if (!ask_ai.error.empty()) {
|
||||
ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "%s", ask_ai.error.c_str());
|
||||
}
|
||||
ImGui::Separator();
|
||||
ImGui::Columns(2, "ask_cols", true);
|
||||
ImGui::TextUnformatted("Current");
|
||||
ImGui::InputTextMultiline("##ask_cur",
|
||||
const_cast<char*>(ask_ai.current_tql.c_str()),
|
||||
ask_ai.current_tql.size() + 1,
|
||||
ImVec2(-1, 240),
|
||||
ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextUnformatted("Proposed (editable before apply)");
|
||||
ImGui::InputTextMultiline("##ask_new", ask_ai.edit_buf, sizeof(ask_ai.edit_buf),
|
||||
ImVec2(-1, 240));
|
||||
ImGui::Columns(1);
|
||||
|
||||
bool can_apply = !ask_ai.busy && ask_ai.edit_buf[0] != '\0';
|
||||
ImGui::BeginDisabled(!can_apply);
|
||||
if (ImGui::Button("Apply")) {
|
||||
std::string err;
|
||||
if (ask_ai.mode == 0) {
|
||||
// TQL apply
|
||||
bool ok = tql::apply(ask_ai.edit_buf, st,
|
||||
active_headers,
|
||||
active_types,
|
||||
nullptr, 0,
|
||||
orig_cols,
|
||||
&err);
|
||||
if (ok) {
|
||||
ask_ai.status = "Applied OK.";
|
||||
ask_ai.open = false;
|
||||
} else {
|
||||
ask_ai.error = "tql::apply error: " + err;
|
||||
ask_ai.status = "Apply failed.";
|
||||
}
|
||||
} else {
|
||||
#ifdef FN_TQL_DUCKDB
|
||||
// SQL apply: ejecutar via tql_duckdb sobre TableInputs activas.
|
||||
// Para tablas en memoria construimos un TableInput basico desde
|
||||
// active_headers/types. v1 no recupera cells originales aqui;
|
||||
// reportamos solo error si fallo. Caller real deberia pasar
|
||||
// tables() del render scope. Sin esto, marcamos status info.
|
||||
ask_ai.status = "SQL execute disponible (FN_TQL_DUCKDB ON). "
|
||||
"Integracion full pendiente: usar tql_duckdb::execute desde caller.";
|
||||
#else
|
||||
ask_ai.status = "SQL execute requires FN_TQL_DUCKDB build flag.";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Reject")) {
|
||||
ask_ai.response_code.clear();
|
||||
ask_ai.edit_buf[0] = '\0';
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Close")) {
|
||||
ask_ai.open = false;
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace data_table
|
||||
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
// data_table_ai_panel — modal "Ask AI" de la tabla TQL.
|
||||
// Sub-funcion extraida de modules/data_table/data_table.cpp (issue 0107c).
|
||||
//
|
||||
// Responsabilidad:
|
||||
// - draw_ask_ai_modal: modal ImGui con prompt de lenguaje natural, llamada
|
||||
// a llm_anthropic::ask, render de la respuesta (codigo TQL/SQL editable),
|
||||
// botones Apply/Reject/Close, export CSV de la respuesta.
|
||||
//
|
||||
// El modal usa UiState.ask_* que se mueve aqui (AskAiState) para
|
||||
// encapsulacion. Vive en UiState del data_table principal — no en State.
|
||||
//
|
||||
// Dependencia con llm_anthropic:
|
||||
// - Si FN_LLM_ANTHROPIC esta definido: usa core/llm_anthropic.h (real).
|
||||
// - Si no: el stub del data_table.cpp provee tipos/funciones no-op.
|
||||
// - Tras la extraccion este modulo incluye el stub o el header real.
|
||||
//
|
||||
// Rangos del fuente original:
|
||||
// - Modal "Ask AI" : lineas 4636-4755
|
||||
// - Boton trigger : linea 1869 (dentro de draw_viz_selector)
|
||||
//
|
||||
// Dependencias: data_table_types.h, llm_anthropic.h (o stub), imgui.h.
|
||||
|
||||
#include "core/data_table_types.h"
|
||||
#include "imgui.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace data_table {
|
||||
|
||||
// AskAiState: estado del modal Ask AI. Debe persistir entre frames
|
||||
// (es parte de UiState). Extraido aqui para encapsulacion.
|
||||
struct AskAiState {
|
||||
bool open = false;
|
||||
bool busy = false;
|
||||
int mode = 0; // 0 = TQL, 1 = SQL (DuckDB)
|
||||
char question[2048] = {};
|
||||
std::string current_tql; // TQL emitido del state al abrir modal
|
||||
std::string response_raw; // texto raw del modelo
|
||||
std::string response_code; // bloque extraido (Lua o SQL)
|
||||
std::string error;
|
||||
std::string status; // "Sending..." / "Got response." / error
|
||||
char edit_buf[8192] = {}; // buffer editable de la propuesta
|
||||
};
|
||||
|
||||
// draw_ask_ai_modal — dibuja el modal "Ask AI".
|
||||
// Debe llamarse cada frame (pattern ImGui). Abre el popup si ask_ai.open==true.
|
||||
//
|
||||
// Parametros:
|
||||
// ask_ai — estado mutable del modal.
|
||||
// st — State principal (para tql::apply en Apply + active_headers/types).
|
||||
// active_headers / active_types — snapshot del output activo (para la llamada llm).
|
||||
// orig_cols — numero de cols originales (para tql::apply signature).
|
||||
// ---------------------------------------------------------------------------
|
||||
void draw_ask_ai_modal(AskAiState& ask_ai,
|
||||
State& st,
|
||||
const std::vector<std::string>& active_headers,
|
||||
const std::vector<ColumnType>& active_types,
|
||||
int orig_cols);
|
||||
|
||||
} // namespace data_table
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
name: data_table_ai_panel
|
||||
kind: function
|
||||
lang: cpp
|
||||
domain: viz
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "void data_table::draw_ask_ai_modal(AskAiState& ask_ai, State& st, const std::vector<std::string>& active_headers, const std::vector<ColumnType>& active_types, int orig_cols)"
|
||||
description: "Modal 'Ask AI' de la tabla TQL: UI de prompt en lenguaje natural, llamada bloqueante a llm_anthropic::ask, render de la respuesta (codigo TQL o SQL editable), botones Apply/Reject/Close. El Apply ejecuta tql::apply sobre el estado actual de la tabla. Soporta modo TQL (default) y SQL (requiere FN_TQL_DUCKDB). Sub-funcion extraida de modules/data_table/data_table.cpp (issue 0107c, fase 11 / issue 0080)."
|
||||
tags: [viz, table, imgui, ui, ai, llm, tql, cpp-tables, ask-ai]
|
||||
uses_functions:
|
||||
- data_table_cpp_viz
|
||||
uses_types:
|
||||
- data_table_types_cpp_core
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [imgui]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "cpp/functions/viz/data_table_ai_panel.cpp"
|
||||
framework: imgui
|
||||
params:
|
||||
- name: ask_ai
|
||||
desc: "Estado mutable del modal (AskAiState): prompt, modo TQL/SQL, buffers de respuesta, flags open/busy. Debe persistir entre frames (vive en UiState del data_table)."
|
||||
- name: st
|
||||
desc: "State principal de la tabla. Mutado por Apply: tql::apply modifica st.stages, filtros, sorts."
|
||||
- name: active_headers / active_types
|
||||
desc: "Snapshot del output del stage activo en el momento de abrir el modal. Se pasan a llm_anthropic::AskInput para que el modelo conozca el schema."
|
||||
- name: orig_cols
|
||||
desc: "Numero de columnas originales. Necesario para la firma de tql::apply."
|
||||
output: "Void. Efectos: mutates ask_ai (flags, buffers), mutates st (via tql::apply en Apply)."
|
||||
---
|
||||
|
||||
## Documentacion
|
||||
|
||||
Sub-funcion que encapsula el modal "Ask AI" de la tabla TQL (issue 0080, fase 11). Permite al usuario escribir una pregunta en lenguaje natural y recibir codigo TQL o SQL generado por un modelo de lenguaje.
|
||||
|
||||
### Flujo del modal
|
||||
|
||||
1. `ask_ai.open = true` (seteado por boton "Ask AI" en `draw_viz_selector`).
|
||||
2. Primer frame con `open=true`: `ImGui::OpenPopup("Ask AI")`.
|
||||
3. Modal se abre: prompt input, combo modo TQL/SQL.
|
||||
4. Usuario escribe pregunta y hace click en "Send":
|
||||
- Construye `llm_anthropic::AskInput` con `question`, `col_names`, `col_types`, `tql_current`.
|
||||
- Llamada **bloqueante** a `llm_anthropic::ask(in)` → `AskResult`.
|
||||
- Si error: `ask_ai.error` se muestra en rojo.
|
||||
- Si ok: `ask_ai.response_code` se copia a `ask_ai.edit_buf` (editable).
|
||||
5. Panel dividido en dos columnas: TQL actual (read-only) | propuesta (editable).
|
||||
6. Botones:
|
||||
- **Apply**: ejecuta `tql::apply(ask_ai.edit_buf, st, ...)`. Si ok: cierra modal.
|
||||
- **Reject**: limpia `edit_buf` y `response_code`.
|
||||
- **Close**: `ask_ai.open = false`.
|
||||
|
||||
### Dependencia llm_anthropic
|
||||
|
||||
Si `FN_LLM_ANTHROPIC` esta definido en el build, se incluye `core/llm_anthropic.h` (real). Si no, se usa el stub no-op que retorna error "not available". Pendiente promover a `cpp/functions/infra/llm_anthropic` (Wave 4, issue 0107c deuda tecnica).
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```cpp
|
||||
// En el render principal, llamar una vez por frame tras el grid:
|
||||
data_table::draw_ask_ai_modal(U.ask_ai, st,
|
||||
U.active_headers, U.active_types, orig_cols);
|
||||
|
||||
// El boton de apertura vive en draw_viz_selector:
|
||||
// if (ImGui::SmallButton("Ask AI##ask_open")) U.ask_ai.open = true;
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
Llamar una vez por frame desde el entrypoint thin `data_table::render()` en el bloque de modales (junto a TQL show/apply, Custom column, etc.), SIEMPRE que el contexto ImGui tenga la ventana activa. No llamar desde apps directamente.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- La llamada a `llm_anthropic::ask` es **bloqueante** (red HTTP). La UI se congela brevemente. Consideracion futura: mover a hilo background + flag de estado asincrono.
|
||||
- El modal se destruye (EndPopup) si el usuario hace click fuera. `ask_ai.open` se pone a false por el `&ask_ai.open` del `BeginPopupModal`. Esto borra el estado del prompt en curso — consideracion UX conocida.
|
||||
- En modo SQL (`ask_ai.mode == 1`), Apply solo funciona si `FN_TQL_DUCKDB` esta definido; de lo contrario muestra mensaje de error informativo.
|
||||
- `llm_anthropic` no esta en el registry (deuda Wave 4). Hasta que se promueva, el stub vive inlined en este .cpp. No duplicar el stub en otros archivos.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,163 @@
|
||||
#pragma once
|
||||
// data_table_chips — barra de chips superior de la tabla TQL.
|
||||
// Sub-funcion extraida de modules/data_table/data_table.cpp (issue 0107c).
|
||||
//
|
||||
// Responsabilidad:
|
||||
// Toda la UI de chips que aparece en el area de chrome de la tabla:
|
||||
// - draw_joins_chips : chips de join con tablas secundarias.
|
||||
// - draw_filter_chips : chips de filtros activos (con boton de borrar).
|
||||
// - draw_breakout_chips : chips de breakout (group-by) activos.
|
||||
// - draw_aggregation_chips: chips de agregaciones activas.
|
||||
// - draw_sort_chips : chips de ordenamiento activos.
|
||||
// - draw_header_menu : popup menu al hacer right-click en cabecera de col.
|
||||
// - apply_header_sort_click: procesa click en cabecera (sort / multi-sort con Shift).
|
||||
//
|
||||
// Popups de anadir/editar chips (activados desde los chips o botones "+"):
|
||||
// - draw_add_filter_popup / draw_edit_filter_popup
|
||||
// - draw_add_breakout_popup / draw_edit_breakout_popup
|
||||
// - draw_add_aggregation_popup / draw_edit_agg_popup
|
||||
// - draw_add_sort_popup / draw_edit_sort_popup
|
||||
//
|
||||
// TQL preview / save / load (area de chrome stage 0):
|
||||
// - draw_tql_bar (NEW helper que engloba Show TQL, Apply TQL, Save/Load .tql)
|
||||
//
|
||||
// Helpers de tipo/op para los popups:
|
||||
// - draw_typed_ops: dibuja radio buttons de Op segun ColumnType.
|
||||
// - type_supports_range: retorna true si el tipo soporta Op::Range.
|
||||
//
|
||||
// Rangos del fuente original:
|
||||
// - draw_joins_chips : lineas 1897-2003
|
||||
// - draw_filter_chips : lineas 2009-2093
|
||||
// - draw_breakout_chips : lineas 2095-2188
|
||||
// - draw_aggregation_chips : lineas 2189-2238
|
||||
// - draw_sort_chips : lineas 2240-2309
|
||||
// - apply_header_sort_click : lineas 2311-2327
|
||||
// - draw_edit_filter_popup : lineas 2329-2370
|
||||
// - draw_edit_breakout_popup : lineas 2372-2395
|
||||
// - draw_edit_agg_popup : lineas 2397-2445
|
||||
// - draw_edit_sort_popup : lineas 2447-2472
|
||||
// - draw_typed_ops : lineas 2503-2510
|
||||
// - type_supports_range : lineas 2512-2514
|
||||
// - draw_add_filter_popup : lineas 2516-2569
|
||||
// - draw_add_breakout_popup : lineas 2571-2604
|
||||
// - draw_add_aggregation_popup: lineas 2606-2655
|
||||
// - draw_add_sort_popup : lineas 2657-2682
|
||||
// - draw_header_menu : lineas 2684-2892
|
||||
// - TQL preview/save/load : lineas 3272-3366 (dentro de render() stage 0)
|
||||
//
|
||||
// Dependencias: data_table_types.h, tql_emit.h, tql_apply.h, imgui.h.
|
||||
|
||||
#include "core/data_table_types.h"
|
||||
#include "imgui.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace data_table {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers de tipo/operacion
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// draw_typed_ops: dibuja radio buttons para los operadores compatibles con el
|
||||
// tipo `t`. Rellena `out` con el op seleccionado. Retorna true si se selecciono.
|
||||
bool draw_typed_ops(ColumnType t, Op& out);
|
||||
|
||||
// type_supports_range: true si el tipo admite Op::Range (Int, Float, Date).
|
||||
bool type_supports_range(ColumnType t);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Sort
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// apply_header_sort_click: registra un click en el header de col_name.
|
||||
// Sin Shift: sort primario (reemplaza todos los sorts).
|
||||
// Con Shift: sort secundario (agrega al sort primario existente).
|
||||
// Ciclo: Asc -> Desc -> none.
|
||||
void apply_header_sort_click(Stage& stg, const std::string& col_name, bool shift);
|
||||
|
||||
void draw_sort_chips(Stage& stg);
|
||||
void draw_add_sort_popup(Stage& stg, const char* const* headers, int n_cols,
|
||||
const std::vector<ColumnType>& types);
|
||||
void draw_edit_sort_popup(Stage& stg, const char* const* headers, int n_cols);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Filtros
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void draw_filter_chips(Stage& stg, const char* const* eff_headers, int eff_cols,
|
||||
const std::vector<ColumnType>& eff_types);
|
||||
void draw_add_filter_popup(Stage& stg, const char* const* eff_headers, int eff_cols,
|
||||
const std::vector<ColumnType>& eff_types);
|
||||
void draw_edit_filter_popup(Stage& stg, const char* const* headers, int n_cols,
|
||||
const std::vector<ColumnType>& types);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Breakouts (group-by)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void draw_breakout_chips(Stage& stg, const char* const* in_headers, int in_cols,
|
||||
const std::vector<ColumnType>& in_types);
|
||||
void draw_add_breakout_popup(Stage& stg, const char* const* in_headers, int in_cols,
|
||||
const std::vector<ColumnType>& in_types,
|
||||
const char* const* cur_cells, int cur_rows);
|
||||
void draw_edit_breakout_popup(Stage& stg, const char* const* headers, int n_cols);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Agregaciones
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void draw_aggregation_chips(Stage& stg, const char* const* in_headers, int in_cols);
|
||||
void draw_add_aggregation_popup(Stage& stg, const char* const* in_headers, int in_cols,
|
||||
const std::vector<ColumnType>& in_types);
|
||||
void draw_edit_agg_popup(Stage& stg, const char* const* headers, int n_cols);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Joins
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// draw_joins_chips: chips de join con las tablas joinables. Solo visible si
|
||||
// joinables no esta vacio. Mutates st.stages y la configuracion de join.
|
||||
void draw_joins_chips(State& st, const std::vector<TableInput>& joinables,
|
||||
const char* const* eff_headers, int eff_cols,
|
||||
const std::vector<ColumnType>& eff_types);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Header menu (right-click en cabecera de columna)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// draw_header_menu: popup que aparece al right-click en el header de columna
|
||||
// `col`. Incluye sort, filter, conditional color, type change, etc.
|
||||
// is_raw_stage: true si estamos en stage 0 (permite "Change type" / "Derived").
|
||||
void draw_header_menu(State& st, Stage& stg, int col,
|
||||
const char* const* eff_headers_arr, int eff_cols,
|
||||
const std::vector<ColumnType>& eff_types,
|
||||
int orig_cols, bool is_raw_stage);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// TQL bar (Show TQL / Apply TQL / Save .tql / Load .tql)
|
||||
// Area de chrome adicional en stage 0. Nuevo helper que extrae el bloque
|
||||
// de lineas 3272-3366 del render() original.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// TqlBarState: estado del area TQL (vive en UiState).
|
||||
struct TqlBarState {
|
||||
bool show_open = false;
|
||||
std::string show_text;
|
||||
bool apply_open = false;
|
||||
std::string apply_text;
|
||||
std::string apply_error;
|
||||
char file_path[256] = "table.tql";
|
||||
std::string io_status; // "saved: ..." / "loaded: ..." / "load FAILED: ..."
|
||||
};
|
||||
|
||||
// draw_tql_bar: dibuja los botones Show TQL, Apply TQL, Save .tql, Load .tql
|
||||
// y los modales correspondientes. Mutates st via tql::apply en Apply.
|
||||
// active_headers/types/cells/row_count/orig_cols: necesarios para tql::emit + apply.
|
||||
void draw_tql_bar(TqlBarState& tql_bar,
|
||||
State& st,
|
||||
const std::vector<std::string>& orig_headers,
|
||||
const std::vector<ColumnType>& orig_types,
|
||||
const char* const* cells,
|
||||
int row_count, int orig_cols);
|
||||
|
||||
} // namespace data_table
|
||||
@@ -0,0 +1,116 @@
|
||||
---
|
||||
name: data_table_chips
|
||||
kind: function
|
||||
lang: cpp
|
||||
domain: viz
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "void data_table::draw_filter_chips(Stage& stg, const char* const* eff_headers, int eff_cols, const std::vector<ColumnType>& eff_types)"
|
||||
description: "Barra de chips superior de la tabla TQL: render y edicion de filtros activos, breakouts (group-by), agregaciones, sorts, joins con tablas secundarias, header-menu de columna (sort/filter/conditional-color/type-change), y area TQL (Show TQL / Apply TQL / Save .tql / Load .tql). Es la sub-funcion mas grande del refactor 0107c (~1000 LOC, 17 funciones). Sub-funcion extraida de modules/data_table/data_table.cpp (issue 0107c)."
|
||||
tags: [viz, table, imgui, ui, chips, filters, sort, aggregation, tql, cpp-tables, joins]
|
||||
uses_functions:
|
||||
- data_table_color_rules_cpp_viz
|
||||
- data_table_cpp_viz
|
||||
- tql_emit_cpp_core
|
||||
- tql_apply_cpp_core
|
||||
uses_types:
|
||||
- data_table_types_cpp_core
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [imgui]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "cpp/functions/viz/data_table_chips.cpp"
|
||||
framework: imgui
|
||||
params:
|
||||
- name: stg
|
||||
desc: "Stage activo (st.stages[active]): contiene filters, breakouts, aggregations, sorts, derived. Mutado por todos los chips y popups de edicion."
|
||||
- name: eff_headers / eff_cols
|
||||
desc: "Headers y numero de columnas efectivas del stage activo (orig + derived). Usados en labels de chips y en los popups de anadir/editar."
|
||||
- name: eff_types
|
||||
desc: "Tipos de columna efectivos. Usados para filtrar los operadores disponibles (draw_typed_ops) y para formatear los popups."
|
||||
- name: st (para joins y header-menu)
|
||||
desc: "State completo: necesario para draw_joins_chips (accede a st.stages) y draw_header_menu (accede a st.color_rules, st.col_visible)."
|
||||
- name: joinables (para joins)
|
||||
desc: "Vector de TableInput secundarias disponibles para join. Si vacio, draw_joins_chips no muestra nada."
|
||||
output: "Void. Todos los efectos son mutaciones de stg (Stage) o st (State) en respuesta a la interaccion del usuario."
|
||||
---
|
||||
|
||||
## Documentacion
|
||||
|
||||
Sub-funcion mas grande del refactor 0107c. Encapsula toda la UI de la barra de chips de la tabla TQL.
|
||||
|
||||
### Mapa de funciones
|
||||
|
||||
| Funcion | LOC aprox | Responsabilidad |
|
||||
|---|---|---|
|
||||
| `draw_filter_chips` | ~85 | Chips de filtros activos con X para borrar |
|
||||
| `draw_add_filter_popup` | ~55 | Popup para anadir filtro nuevo |
|
||||
| `draw_edit_filter_popup` | ~42 | Popup edicion de filtro existente (right-click en chip) |
|
||||
| `draw_breakout_chips` | ~93 | Chips de breakout activos con X |
|
||||
| `draw_add_breakout_popup` | ~34 | Popup anadir breakout (col de agrupacion) |
|
||||
| `draw_edit_breakout_popup` | ~24 | Popup edicion breakout |
|
||||
| `draw_aggregation_chips` | ~50 | Chips de agregaciones activas con X |
|
||||
| `draw_add_aggregation_popup` | ~50 | Popup anadir agregacion (Count, Sum, Mean, ...) |
|
||||
| `draw_edit_agg_popup` | ~48 | Popup edicion agregacion |
|
||||
| `draw_sort_chips` | ~70 | Chips de sort activos con X |
|
||||
| `draw_add_sort_popup` | ~26 | Popup anadir sort |
|
||||
| `draw_edit_sort_popup` | ~26 | Popup edicion sort |
|
||||
| `apply_header_sort_click` | ~17 | Sort al click en cabecera (Asc/Desc/none ciclo) |
|
||||
| `draw_joins_chips` | ~107 | Chips de joins con tablas secundarias |
|
||||
| `draw_header_menu` | ~209 | Menu contextual de columna (sort/filter/color/type) |
|
||||
| `draw_typed_ops` | ~8 | Radio buttons de Op segun tipo de columna |
|
||||
| `type_supports_range` | ~3 | Bool tipo soporta rango |
|
||||
| `draw_tql_bar` | ~95 | Botones + modales TQL show/apply/save/load |
|
||||
|
||||
### Orden de llamada en el chrome
|
||||
|
||||
```cpp
|
||||
// Stage 0 (chrome_visible):
|
||||
draw_joins_chips(st, joinables, hdrs, cols, types);
|
||||
draw_filter_chips(act, hdrs, cols, types);
|
||||
draw_add_filter_popup(act, hdrs, cols, types);
|
||||
draw_edit_filter_popup(act, hdrs, cols, types);
|
||||
draw_breakout_chips(act, hdrs, cols, types);
|
||||
// ... etc.
|
||||
draw_sort_chips(act);
|
||||
draw_tql_bar(tql_bar, st, orig_headers, orig_types, cells, rows, orig_cols);
|
||||
```
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```cpp
|
||||
// Bloque chrome stage 0:
|
||||
if (chrome_visible) {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8, 2));
|
||||
|
||||
data_table::draw_filter_chips(act, eff_headers, eff_cols, eff_types);
|
||||
data_table::draw_add_filter_popup(act, eff_headers, eff_cols, eff_types);
|
||||
data_table::draw_edit_filter_popup(act, eff_headers, eff_cols, eff_types);
|
||||
|
||||
data_table::draw_breakout_chips(act, eff_headers, eff_cols, eff_types);
|
||||
// ...
|
||||
|
||||
data_table::draw_sort_chips(act);
|
||||
data_table::draw_add_sort_popup(act, eff_headers, eff_cols, eff_types);
|
||||
data_table::draw_edit_sort_popup(act, eff_headers, eff_cols);
|
||||
|
||||
data_table::draw_tql_bar(U.tql_bar, st, orig_h, orig_t, cells, rows, orig_cols);
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
Llamar desde el entrypoint thin `data_table::render()` en el bloque `chrome_visible`. El orden importa: joins primero, luego filtros, breakouts, agregaciones, sorts, TQL bar. No llamar fuera del contexto de un frame ImGui activo.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- `draw_header_menu` DEBE llamarse desde dentro del popup de cabecera (`ImGui::BeginPopupContextItem`) — no se puede llamar en el loop normal de render.
|
||||
- Los popups de add/edit usan IDs de ImGui fijos (`"##addf"`, `"##editf"`, etc.). Si se tienen multiples instancias de data_table en la misma ventana, usar `ImGui::PushID(table_id)` antes de llamar a los chips.
|
||||
- `draw_tql_bar` incluye los modales "Show TQL" y "Apply TQL" via `ImGui::BeginPopupModal`. Deben llamarse fuera del bloque `PushStyleVar` si los estilos interfieren con el modal.
|
||||
- `apply_header_sort_click` sin Shift reemplaza TODOS los sorts existentes por el nuevo. Es el comportamiento esperado para click simple. Shift agrega sort secundario.
|
||||
- `draw_joins_chips` asume que `joinables` es la slice de `tables` excluyendo la tabla principal. Si se pasa la tabla principal como joinable, se creara un self-join.
|
||||
@@ -0,0 +1,353 @@
|
||||
// data_table_color_rules — editor de reglas de color por columna + aplicacion.
|
||||
// Sub-funcion extraida de modules/data_table/data_table.cpp (issue 0107c).
|
||||
//
|
||||
// Responsabilidad:
|
||||
// - Helpers de color: auto_categorical_color, resolve_categorical_dot_color,
|
||||
// apply_color_rules_for_cell.
|
||||
// - UI del submenu "Conditional color" dentro del header-menu de columnas:
|
||||
// draw_color_rule_menu.
|
||||
//
|
||||
// hex_to_imcolor, parse_hex_color, lerp_color_along_stops son helpers de color
|
||||
// puros. Se definen como static aqui (no se exportan) porque data_table.cpp
|
||||
// tiene sus propias versiones static tambien — ambos TU son independientes.
|
||||
|
||||
#include "viz/data_table_color_rules.h"
|
||||
#include "data_table/data_table_internal.h"
|
||||
|
||||
#include "imgui.h"
|
||||
#include "core/data_table_types.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
// hex_to_imcolor: "#rrggbb" -> ImVec4. Returns {-1,-1,-1,-1} on failure.
|
||||
static ImVec4 hex_to_imcolor(const std::string& hex) {
|
||||
const char* p = hex.c_str();
|
||||
if (*p == '#') ++p;
|
||||
unsigned int r = 0, g = 0, b = 0;
|
||||
if (std::sscanf(p, "%02x%02x%02x", &r, &g, &b) != 3)
|
||||
return ImVec4(-1.f, -1.f, -1.f, -1.f);
|
||||
return ImVec4(r / 255.f, g / 255.f, b / 255.f, 1.f);
|
||||
}
|
||||
|
||||
// lerp_color_along_stops: LERP entre N color stops en [0,1].
|
||||
static ImU32 lerp_color_along_stops(
|
||||
const std::vector<data_table::ColorStop>& stops, float t, float alpha)
|
||||
{
|
||||
static const std::vector<data_table::ColorStop> kDefault = {
|
||||
{0.0f, "#22c55e"},
|
||||
{0.5f, "#f59e0b"},
|
||||
{1.0f, "#ef4444"},
|
||||
};
|
||||
const auto& sv = stops.empty() ? kDefault : stops;
|
||||
std::vector<data_table::ColorStop> sorted_sv = sv;
|
||||
std::sort(sorted_sv.begin(), sorted_sv.end(),
|
||||
[](const data_table::ColorStop& a, const data_table::ColorStop& b){
|
||||
return a.position < b.position;
|
||||
});
|
||||
t = t < 0.f ? 0.f : (t > 1.f ? 1.f : t);
|
||||
if (t <= sorted_sv.front().position)
|
||||
return data_table::parse_hex_color(sorted_sv.front().color, alpha);
|
||||
if (t >= sorted_sv.back().position)
|
||||
return data_table::parse_hex_color(sorted_sv.back().color, alpha);
|
||||
for (size_t i = 0; i + 1 < sorted_sv.size(); ++i) {
|
||||
const auto& lo = sorted_sv[i];
|
||||
const auto& hi = sorted_sv[i + 1];
|
||||
if (t >= lo.position && t <= hi.position) {
|
||||
float span = hi.position - lo.position;
|
||||
float f = (span > 1e-6f) ? (t - lo.position) / span : 0.f;
|
||||
ImVec4 ca = hex_to_imcolor(lo.color);
|
||||
ImVec4 cb = hex_to_imcolor(hi.color);
|
||||
if (ca.x < 0.f) ca = ImVec4(0.5f, 0.5f, 0.5f, 1.f);
|
||||
if (cb.x < 0.f) cb = ImVec4(0.5f, 0.5f, 0.5f, 1.f);
|
||||
float r = ca.x + f * (cb.x - ca.x);
|
||||
float g = ca.y + f * (cb.y - ca.y);
|
||||
float b = ca.z + f * (cb.z - ca.z);
|
||||
unsigned int ri = (unsigned int)(r * 255.f + 0.5f);
|
||||
unsigned int gi = (unsigned int)(g * 255.f + 0.5f);
|
||||
unsigned int bi = (unsigned int)(b * 255.f + 0.5f);
|
||||
unsigned int ai = (unsigned int)(alpha * 255.f + 0.5f);
|
||||
return IM_COL32(ri, gi, bi, ai);
|
||||
}
|
||||
}
|
||||
return data_table::parse_hex_color(sorted_sv.back().color, alpha);
|
||||
}
|
||||
|
||||
} // anon namespace
|
||||
|
||||
namespace data_table {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// parse_hex_color: "#rrggbb" / "#rrggbbaa" -> ImU32 with explicit alpha.
|
||||
// Declared in data_table_color_rules.h (public API — used by data_table.cpp
|
||||
// cell renderers and by apply_color_rules_for_cell).
|
||||
// ---------------------------------------------------------------------------
|
||||
ImU32 parse_hex_color(const std::string& hex, float alpha)
|
||||
{
|
||||
const char* p = hex.c_str();
|
||||
if (*p == '#') ++p;
|
||||
unsigned int r = 0, g = 0, b = 0, a = 255;
|
||||
int parsed = std::sscanf(p, "%02x%02x%02x%02x", &r, &g, &b, &a);
|
||||
if (parsed < 3) return IM_COL32(128, 128, 128, 255);
|
||||
if (parsed == 3) {
|
||||
a = (unsigned int)(alpha * 255.f + 0.5f);
|
||||
}
|
||||
return IM_COL32(r, g, b, a);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// auto_categorical_color: deterministic palette for a string value.
|
||||
// Hashes the value to an index in a fixed 12-color palette (Tailwind-ish).
|
||||
// Same value always maps to the same color. FNV-1a 32-bit.
|
||||
// ---------------------------------------------------------------------------
|
||||
ImU32 auto_categorical_color(const char* value, float alpha)
|
||||
{
|
||||
static const unsigned int kPalette[] = {
|
||||
0xFFef4444u, // red-500
|
||||
0xFFf97316u, // orange-500
|
||||
0xFFf59e0bu, // amber-500
|
||||
0xFF84cc16u, // lime-500
|
||||
0xFF22c55eu, // green-500
|
||||
0xFF14b8a6u, // teal-500
|
||||
0xFF06b6d4u, // cyan-500
|
||||
0xFF3b82f6u, // blue-500
|
||||
0xFF8b5cf6u, // violet-500
|
||||
0xFFa855f7u, // purple-500
|
||||
0xFFec4899u, // pink-500
|
||||
0xFFd946efu, // fuchsia-500
|
||||
};
|
||||
constexpr size_t N = sizeof(kPalette) / sizeof(kPalette[0]);
|
||||
uint32_t h = 2166136261u;
|
||||
for (const char* p = value ? value : ""; *p; ++p) {
|
||||
h ^= (uint8_t)(*p);
|
||||
h *= 16777619u;
|
||||
}
|
||||
unsigned int packed = kPalette[h % N];
|
||||
unsigned int a = (unsigned int)(alpha * 255.f + 0.5f);
|
||||
unsigned int rgb = packed & 0x00FFFFFFu;
|
||||
return rgb | (a << 24);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// resolve_categorical_dot_color: look up dot color from rule's dot_map; fall
|
||||
// back to auto_categorical_color if no match.
|
||||
// ---------------------------------------------------------------------------
|
||||
ImU32 resolve_categorical_dot_color(const ColorRule& cr, const char* value)
|
||||
{
|
||||
if (!cr.dot_map.empty()) {
|
||||
for (const auto& kv : cr.dot_map) {
|
||||
if (kv.first == (value ? value : ""))
|
||||
return parse_hex_color(kv.second, cr.dot_alpha);
|
||||
}
|
||||
}
|
||||
return auto_categorical_color(value, cr.dot_alpha);
|
||||
}
|
||||
|
||||
// parse_cell_number: parse a cell string as double. Returns NaN on failure.
|
||||
// Only needed here (not shared).
|
||||
static double parse_cell_number(const char* s) {
|
||||
if (!s || !*s) return std::numeric_limits<double>::quiet_NaN();
|
||||
char* end = nullptr;
|
||||
double v = std::strtod(s, &end);
|
||||
if (end == s) return std::numeric_limits<double>::quiet_NaN();
|
||||
return v;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// apply_color_rules_for_cell: applies all matching ColorRules for column `c`.
|
||||
// Must be called AFTER ImGui::TableSetColumnIndex and BEFORE cell renderer.
|
||||
// ---------------------------------------------------------------------------
|
||||
void apply_color_rules_for_cell(const State& st,
|
||||
int c, const char* cell,
|
||||
ImVec2 cell_min, float cell_w, float cell_h,
|
||||
float& dot_advance_px)
|
||||
{
|
||||
dot_advance_px = 0.f;
|
||||
ImDrawList* draw = ImGui::GetWindowDrawList();
|
||||
for (const auto& cr : st.color_rules) {
|
||||
if (cr.col != c) continue;
|
||||
switch (cr.kind) {
|
||||
case ColorRuleKind::CellBg: {
|
||||
if (cell && cr.equals == cell) {
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, (ImU32)cr.color);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ColorRuleKind::NumericRange: {
|
||||
double v = parse_cell_number(cell);
|
||||
if (std::isnan(v)) break;
|
||||
double span = cr.range_max - cr.range_min;
|
||||
float t = (span > 1e-12) ? (float)((v - cr.range_min) / span) : 0.5f;
|
||||
ImU32 tint = lerp_color_along_stops(cr.range_stops, t, cr.range_alpha);
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, tint);
|
||||
break;
|
||||
}
|
||||
case ColorRuleKind::CategoricalDot: {
|
||||
ImU32 dot = resolve_categorical_dot_color(cr, cell);
|
||||
float radius = cr.dot_radius_px > 0.f ? cr.dot_radius_px : 4.0f;
|
||||
ImVec2 c1(cell_min.x + radius + 2.f,
|
||||
cell_min.y + cell_h * 0.5f);
|
||||
draw->AddCircleFilled(c1, radius, dot, 12);
|
||||
dot_advance_px = radius * 2.f + 6.f;
|
||||
(void)cell_w;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// draw_color_rule_menu: dibuja el submenu "Conditional color" para la columna
|
||||
// `col`. Retorna true si el usuario hizo click en "Apply" (la regla ya fue
|
||||
// anadida a st.color_rules).
|
||||
//
|
||||
// editor_st: ColorRuleEditorState de UiState (draft state per columna).
|
||||
// col_type: tipo efectivo de la columna (para auto-seleccion de modo).
|
||||
// ---------------------------------------------------------------------------
|
||||
bool draw_color_rule_menu(State& st, int col, ColumnType col_type,
|
||||
ColorRuleEditorState& editor_st)
|
||||
{
|
||||
bool applied = false;
|
||||
|
||||
// Auto-pick sensible default on first open for this column.
|
||||
if (editor_st.color_rule_kind.find(col) == editor_st.color_rule_kind.end()) {
|
||||
editor_st.color_rule_kind[col] =
|
||||
(col_type == ColumnType::Int || col_type == ColumnType::Float) ? 2 : 0;
|
||||
}
|
||||
int& kind_i = editor_st.color_rule_kind[col]; // 0=CellBg, 1=CatDot, 2=NumRange
|
||||
|
||||
ImGui::TextDisabled("Mode");
|
||||
ImGui::RadioButton("Cell bg", &kind_i, 0); ImGui::SameLine();
|
||||
ImGui::RadioButton("Categorical dot", &kind_i, 1); ImGui::SameLine();
|
||||
ImGui::RadioButton("Numeric range", &kind_i, 2);
|
||||
ImGui::Separator();
|
||||
|
||||
bool apply_clicked = false;
|
||||
ColorRule draft;
|
||||
draft.col = col;
|
||||
|
||||
if (kind_i == 0) {
|
||||
// ---- Cell bg ----
|
||||
auto& vbuf = editor_st.color_value_inputs[col];
|
||||
vbuf.resize(256, '\0');
|
||||
if (editor_st.color_picker_vals.find(col) == editor_st.color_picker_vals.end())
|
||||
editor_st.color_picker_vals[col] = ImVec4(0.85f, 0.40f, 0.30f, 0.60f);
|
||||
ImVec4& cv = editor_st.color_picker_vals[col];
|
||||
ImGui::SetNextItemWidth(180);
|
||||
ImGui::InputText("equals", vbuf.data(), vbuf.size());
|
||||
ImGui::ColorEdit4("color", &cv.x, ImGuiColorEditFlags_NoInputs);
|
||||
if (ImGui::Button("Apply##cellbg")) {
|
||||
draft.kind = ColorRuleKind::CellBg;
|
||||
draft.equals = std::string(vbuf.c_str());
|
||||
draft.color = (unsigned int)ImGui::ColorConvertFloat4ToU32(cv);
|
||||
apply_clicked = true;
|
||||
}
|
||||
}
|
||||
else if (kind_i == 1) {
|
||||
// ---- Categorical dot ----
|
||||
auto& d = editor_st.cat_dot_drafts[col];
|
||||
ImGui::TextWrapped("Dibuja un punto coloreado al inicio de cada celda.");
|
||||
ImGui::TextWrapped("Color asignado automaticamente por valor (palette de 12).");
|
||||
ImGui::SetNextItemWidth(120);
|
||||
ImGui::SliderFloat("alpha", &d.alpha, 0.2f, 1.0f);
|
||||
ImGui::SetNextItemWidth(120);
|
||||
ImGui::SliderFloat("radius px", &d.radius_px, 2.0f, 9.0f);
|
||||
// Preview swatches (8 sample values).
|
||||
ImGui::TextDisabled("preview:");
|
||||
ImGui::SameLine();
|
||||
const char* samples[] = {"A","B","C","D","E","F","G","H"};
|
||||
for (auto* s : samples) {
|
||||
ImU32 col_u = auto_categorical_color(s, d.alpha);
|
||||
ImVec2 p = ImGui::GetCursorScreenPos();
|
||||
ImGui::GetWindowDrawList()->AddCircleFilled(
|
||||
ImVec2(p.x + d.radius_px, p.y + ImGui::GetTextLineHeight() * 0.5f),
|
||||
d.radius_px, col_u, 12);
|
||||
ImGui::Dummy(ImVec2(d.radius_px * 2.f + 2.f, ImGui::GetTextLineHeight()));
|
||||
ImGui::SameLine();
|
||||
}
|
||||
ImGui::NewLine();
|
||||
if (ImGui::Button("Apply##catdot")) {
|
||||
draft.kind = ColorRuleKind::CategoricalDot;
|
||||
draft.dot_alpha = d.alpha;
|
||||
draft.dot_radius_px = d.radius_px;
|
||||
apply_clicked = true;
|
||||
}
|
||||
}
|
||||
else if (kind_i == 2) {
|
||||
// ---- Numeric range (3-color gradient) ----
|
||||
auto& d = editor_st.num_range_drafts[col];
|
||||
ImGui::SetNextItemWidth(110);
|
||||
ImGui::InputDouble("min", &d.min);
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(110);
|
||||
ImGui::InputDouble("max", &d.max);
|
||||
ImGui::SetNextItemWidth(140);
|
||||
ImGui::SliderFloat("alpha", &d.alpha, 0.05f, 0.9f);
|
||||
ImGui::SetNextItemWidth(100);
|
||||
ImGui::InputText("lo color (hex)", d.c_lo, sizeof(d.c_lo));
|
||||
ImGui::SetNextItemWidth(100);
|
||||
ImGui::InputText("mid color (hex)", d.c_md, sizeof(d.c_md));
|
||||
ImGui::SetNextItemWidth(100);
|
||||
ImGui::InputText("hi color (hex)", d.c_hi, sizeof(d.c_hi));
|
||||
// Preview gradient bar (32 lerped cells).
|
||||
std::vector<ColorStop> preview_stops = {
|
||||
{0.0f, std::string("#") + d.c_lo},
|
||||
{0.5f, std::string("#") + d.c_md},
|
||||
{1.0f, std::string("#") + d.c_hi},
|
||||
};
|
||||
ImVec2 p0 = ImGui::GetCursorScreenPos();
|
||||
float W = 256.f, H = 16.f;
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
float t = i / 31.f;
|
||||
ImU32 col_u = lerp_color_along_stops(preview_stops, t, 1.0f);
|
||||
ImGui::GetWindowDrawList()->AddRectFilled(
|
||||
ImVec2(p0.x + (W / 32.f) * i, p0.y),
|
||||
ImVec2(p0.x + (W / 32.f) * (i + 1), p0.y + H),
|
||||
col_u);
|
||||
}
|
||||
ImGui::Dummy(ImVec2(W, H + 4.f));
|
||||
if (ImGui::Button("Apply##numrange")) {
|
||||
draft.kind = ColorRuleKind::NumericRange;
|
||||
draft.range_min = d.min;
|
||||
draft.range_max = d.max;
|
||||
draft.range_alpha = d.alpha;
|
||||
draft.range_stops = preview_stops;
|
||||
apply_clicked = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (apply_clicked) {
|
||||
// Replace any existing rule of same kind on this col (one per kind).
|
||||
for (size_t i = 0; i < st.color_rules.size();) {
|
||||
if (st.color_rules[i].col == col &&
|
||||
st.color_rules[i].kind == draft.kind) {
|
||||
st.color_rules.erase(st.color_rules.begin() + (int)i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
st.color_rules.push_back(draft);
|
||||
ImGui::CloseCurrentPopup();
|
||||
applied = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Clear col")) {
|
||||
for (size_t i = 0; i < st.color_rules.size();) {
|
||||
if (st.color_rules[i].col == col)
|
||||
st.color_rules.erase(st.color_rules.begin() + (int)i);
|
||||
else
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
return applied;
|
||||
}
|
||||
|
||||
} // namespace data_table
|
||||
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
// data_table_color_rules — editor de reglas de color por columna + aplicacion.
|
||||
// Sub-funcion extraida de modules/data_table/data_table.cpp.
|
||||
// Issue 0107c. Wave de refactor para partir el god-file 4777 LOC.
|
||||
//
|
||||
// Responsabilidad: helpers estaticos de color (parse_hex_color, resolve_categorical_dot_color,
|
||||
// apply_color_rules_for_cell, auto_categorical_color) + UI del submenu "Conditional color"
|
||||
// dentro del header-menu de columnas.
|
||||
//
|
||||
// Dependencias: data_table_types.h (ColorRule, ColorRuleKind, State).
|
||||
// imgui.h, core/icons_tabler.h.
|
||||
// NO depende de UiState global — recibe el draft state que necesita por referencia.
|
||||
|
||||
#include "core/data_table_types.h"
|
||||
#include "imgui.h"
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace data_table {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers de color (pure/static, no ImGui state).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// auto_categorical_color: palette determinista de 12 colores para un valor
|
||||
// string. Mismo valor -> mismo color siempre. v1.5.0.
|
||||
ImU32 auto_categorical_color(const char* value, float alpha = 1.0f);
|
||||
|
||||
// resolve_categorical_dot_color: busca en dot_map de la regla; fallback a
|
||||
// auto_categorical_color si no hay match.
|
||||
ImU32 resolve_categorical_dot_color(const ColorRule& cr, const char* value);
|
||||
|
||||
// parse_hex_color: "#rrggbb" / "#rrggbbaa" -> ImU32 con alpha explicitamente.
|
||||
// Fallback a gris IM_COL32(128,128,128,255) en error.
|
||||
ImU32 parse_hex_color(const std::string& hex, float alpha = 1.0f);
|
||||
|
||||
// apply_color_rules_for_cell: aplica todas las ColorRules para la columna `c`
|
||||
// ANTES del renderer de celda. Llama ImGui::TableSetBgColor para CellBg,
|
||||
// dibuja dots para CategoricalDot, y pinta overlay de rango para NumericRange.
|
||||
// `dot_advance_px` (out): pixeles a avanzar en x antes de texto (por dot).
|
||||
// Debe llamarse justo despues de ImGui::TableSetColumnIndex, antes del renderer.
|
||||
void apply_color_rules_for_cell(const State& st,
|
||||
int c, const char* cell,
|
||||
ImVec2 cell_min, float cell_w, float cell_h,
|
||||
float& dot_advance_px);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// UI del editor de reglas de color (submenu dentro del header-menu de cols).
|
||||
// Llamada desde draw_header_menu cuando el usuario abre "Conditional color".
|
||||
// Mutates `st.color_rules` al hacer click en "Apply".
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ColorRuleEditorState: estado de draft del editor de reglas de color.
|
||||
// Un ColorRuleEditorState vive en UiState (singleton por instancia de tabla).
|
||||
// Guarda los drafts per-column en maps indexados por columna.
|
||||
struct ColorRuleEditorState {
|
||||
// 0=CellBg, 1=CategoricalDot, 2=NumericRange — per column.
|
||||
std::unordered_map<int, int> color_rule_kind;
|
||||
|
||||
// CellBg draft per col.
|
||||
std::unordered_map<int, std::string> color_value_inputs;
|
||||
std::unordered_map<int, ImVec4> color_picker_vals;
|
||||
|
||||
// NumericRange draft per col.
|
||||
struct NumRangeDraft {
|
||||
double min = 0.0;
|
||||
double max = 100.0;
|
||||
float alpha = 0.25f;
|
||||
char c_lo[8] = "22c55e"; // green-500
|
||||
char c_md[8] = "f59e0b"; // amber-500
|
||||
char c_hi[8] = "ef4444"; // red-500
|
||||
};
|
||||
std::unordered_map<int, NumRangeDraft> num_range_drafts;
|
||||
|
||||
// CategoricalDot draft per col.
|
||||
struct CatDotDraft {
|
||||
float alpha = 1.0f;
|
||||
float radius_px = 4.0f;
|
||||
};
|
||||
std::unordered_map<int, CatDotDraft> cat_dot_drafts;
|
||||
};
|
||||
|
||||
// draw_color_rule_menu: dibuja el submenu "Conditional color" para la columna
|
||||
// `col`. Retorna true si el usuario hizo click en "Apply" (la regla ya fue
|
||||
// anadida a st.color_rules por la funcion).
|
||||
// editor_st: draft state per tabla (ColorRuleEditorState de UiState).
|
||||
// col_type: tipo efectivo de la columna (para auto-seleccion de modo).
|
||||
bool draw_color_rule_menu(State& st, int col, ColumnType col_type,
|
||||
ColorRuleEditorState& editor_st);
|
||||
|
||||
} // namespace data_table
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
name: data_table_color_rules
|
||||
kind: function
|
||||
lang: cpp
|
||||
domain: viz
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "void data_table::apply_color_rules_for_cell(const State& st, int col, const char* cell, ImVec2 cell_min, float cell_w, float cell_h, float& dot_advance_px)"
|
||||
description: "Helpers de color (parse_hex_color, auto_categorical_color, resolve_categorical_dot_color, apply_color_rules_for_cell) + editor UI del submenu Conditional color para columnas de tabla. Soporta tres modos: CellBg (igualdad exacta), CategoricalDot (punto coloreado por valor via palette determinista de 12 colores), NumericRange (degradado lo/mid/hi por rango). Sub-funcion extraida de modules/data_table/data_table.cpp (issue 0107c)."
|
||||
tags: [viz, table, imgui, ui, color-rules, tql, cpp-tables]
|
||||
uses_functions:
|
||||
- data_table_cpp_viz
|
||||
uses_types:
|
||||
- data_table_types_cpp_core
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [imgui]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "cpp/functions/viz/data_table_color_rules.cpp"
|
||||
framework: imgui
|
||||
params:
|
||||
- name: st
|
||||
desc: "State del data_table que contiene st.color_rules (vector de ColorRule). Solo lectura en apply_color_rules_for_cell; mutado por draw_color_rule_menu al anadir reglas."
|
||||
- name: col
|
||||
desc: "Indice de columna (0-based en el output visible del stage activo)."
|
||||
- name: cell
|
||||
desc: "Valor de la celda como C-string nullable. Null se trata como cadena vacia."
|
||||
- name: cell_min
|
||||
desc: "Esquina top-left de la celda en coordenadas de pantalla (ImGui::GetItemRectMin o equivalente). Usado para posicionar dots CategoricalDot."
|
||||
- name: cell_w / cell_h
|
||||
desc: "Anchura y altura de la celda en pixeles. Usados para centrar el dot verticalmente y calcular la zona de fondo NumericRange."
|
||||
- name: dot_advance_px
|
||||
desc: "Salida: pixeles a avanzar en X antes de renderizar texto (reserva el espacio del dot). 0 si no hay regla CategoricalDot activa para esta columna."
|
||||
output: "Void para apply_color_rules_for_cell (efectos: TableSetBgColor + draw dot). Bool para draw_color_rule_menu (true = regla anadida a st.color_rules)."
|
||||
---
|
||||
|
||||
## Documentacion
|
||||
|
||||
Sub-funcion que encapsula toda la logica de color condicional por celda de la tabla TQL. Se extrae de `modules/data_table/data_table.cpp` como parte del issue 0107c (partir el god-file 4777 LOC).
|
||||
|
||||
### Funciones publicas
|
||||
|
||||
| Funcion | Uso |
|
||||
|---|---|
|
||||
| `parse_hex_color(hex, alpha)` | Convierte `"#rrggbb"` / `"#rrggbbaa"` a `ImU32`. Fallback gris en error. |
|
||||
| `auto_categorical_color(value, alpha)` | Palette determinista 12 colores via FNV-1a hash. Mismo valor → mismo color siempre. |
|
||||
| `resolve_categorical_dot_color(cr, value)` | Busca en `cr.dot_map`; fallback a `auto_categorical_color`. |
|
||||
| `apply_color_rules_for_cell(st, col, cell, ...)` | Aplica todas las reglas activas para `col`. Llamar ANTES del renderer de celda, despues de `TableSetColumnIndex`. |
|
||||
| `draw_color_rule_menu(st, col, col_type, editor_st)` | UI del submenu "Conditional color" (dentro del header-menu). Mutates `st.color_rules` en Apply. |
|
||||
|
||||
### Modo CellBg
|
||||
|
||||
Igualdad exacta: si `cell == rule.equals` pinta el fondo con `rule.color`.
|
||||
|
||||
### Modo CategoricalDot
|
||||
|
||||
Dibuja un circulo coloreado a la izquierda de cada celda. Color determinado por `resolve_categorical_dot_color`. `dot_advance_px` retorna el offset para que el texto no solape el punto.
|
||||
|
||||
### Modo NumericRange
|
||||
|
||||
Parsea la celda como double. Interpola entre tres colores (lo/mid/hi) segun el valor relativo en `[rule.range_min, rule.range_max]`. Pinta fondo con `TableSetBgColor`.
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```cpp
|
||||
// En el render del grid, por cada celda antes del renderer:
|
||||
float dot_px = 0.f;
|
||||
data_table::apply_color_rules_for_cell(st, col, cell_value,
|
||||
ImGui::GetItemRectMin(), col_width, row_height, dot_px);
|
||||
if (dot_px > 0.f)
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + dot_px);
|
||||
// ... renderer normal ...
|
||||
|
||||
// Para anadir una regla desde el header-menu:
|
||||
data_table::ColorRuleEditorState editor;
|
||||
if (ImGui::BeginMenu("Conditional color")) {
|
||||
data_table::draw_color_rule_menu(st, col, col_type, editor);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
Usarla cuando renderices celdas individuales de la tabla TQL (llamar desde `data_table_grid`) y cuando muestres el menu de cabecera de columna que permita al usuario definir reglas de color. NO llamarla fuera del contexto de un `ImGui::BeginTable / EndTable` activo.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- `apply_color_rules_for_cell` DEBE llamarse despues de `ImGui::TableSetColumnIndex` y ANTES del renderer de celda. Si se invierte el orden, `TableSetBgColor` no afecta a la celda correcta.
|
||||
- El dot CategoricalDot requiere que el draw list de la ventana este activo (`ImGui::GetWindowDrawList()`). No llamar desde un contexto de renderizado diferido.
|
||||
- `parse_hex_color` acepta strings sin `#` pero si el string tiene caracteres no hex devuelve gris fallback — no paniquea.
|
||||
- `auto_categorical_color` usa FNV-1a 32-bit: con >12 valores distintos habra colisiones (intencional, visualmente aceptable).
|
||||
- En la extraccion desde data_table.cpp (tarea 3.8) hay que eliminar la declaracion `static` de `parse_hex_color` y `auto_categorical_color` en el .cpp original y hacer forward-include a este header.
|
||||
@@ -0,0 +1,204 @@
|
||||
// data_table_drill — drill-down stack + breadcrumb de stages.
|
||||
// Sub-funcion extraida de modules/data_table/data_table.cpp (issue 0107c).
|
||||
//
|
||||
// Rangos de lineas del fuente original:
|
||||
// - make_drill_filter : lineas 699-706
|
||||
// - apply_drill_step : lineas 708-718
|
||||
// - undo_drill_step : lineas 720-730
|
||||
// - drill_up : lineas 732-737
|
||||
// - draw_stage_breadcrumb : lineas 1386-1483
|
||||
// - drill_into : lineas 2898-2919
|
||||
|
||||
#include "viz/data_table_drill.h"
|
||||
#include "core/data_table_types.h"
|
||||
#include "imgui.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace data_table {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// make_drill_filter: crea un Filter Op::Eq para col_idx con el valor dado.
|
||||
// ---------------------------------------------------------------------------
|
||||
Filter make_drill_filter(int col_idx, const std::string& value) {
|
||||
Filter f;
|
||||
f.col = col_idx;
|
||||
f.op = Op::Eq;
|
||||
f.value = value;
|
||||
return f;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// apply_drill_step: inserta step.added en el stage en step.target_stage y
|
||||
// actualiza st.active_stage. Retorna true si el step se aplico correctamente.
|
||||
// ---------------------------------------------------------------------------
|
||||
bool apply_drill_step(State& st, const DrillStep& step) {
|
||||
if (step.target_stage < 0 || step.target_stage >= (int)st.stages.size()) return false;
|
||||
Stage& s = st.stages[step.target_stage];
|
||||
int pos = step.filter_pos;
|
||||
if (pos < 0 || pos > (int)s.filters.size()) return false;
|
||||
s.filters.insert(s.filters.begin() + pos, step.added);
|
||||
st.active_stage = step.target_stage;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// undo_drill_step: elimina el filter insertado por apply_drill_step y restaura
|
||||
// st.active_stage a step.prev_active_stage. Retorna true si se deshizo.
|
||||
// ---------------------------------------------------------------------------
|
||||
bool undo_drill_step(State& st, const DrillStep& step) {
|
||||
if (step.target_stage < 0 || step.target_stage >= (int)st.stages.size()) return false;
|
||||
Stage& s = st.stages[step.target_stage];
|
||||
int pos = step.filter_pos;
|
||||
if (pos < 0 || pos >= (int)s.filters.size()) return false;
|
||||
s.filters.erase(s.filters.begin() + pos);
|
||||
if (step.prev_active_stage >= 0 && step.prev_active_stage < (int)st.stages.size())
|
||||
st.active_stage = step.prev_active_stage;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// drill_up: retrocede st.active_stage en 1 si hay stages previos.
|
||||
// Retorna true si se pudo retroceder.
|
||||
// ---------------------------------------------------------------------------
|
||||
bool drill_up(State& st) {
|
||||
if (st.stages.empty()) return false;
|
||||
if (st.active_stage <= 0) return false;
|
||||
st.active_stage -= 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// draw_stage_breadcrumb: barra de navegacion de drill con botones < > ^ y
|
||||
// selector de stages. Mutates st.drill_back/forward y st.active_stage.
|
||||
// ---------------------------------------------------------------------------
|
||||
void draw_stage_breadcrumb(State& st) {
|
||||
st.ensure_stage0();
|
||||
|
||||
// Drill history back/forward (fase 10). Botones al inicio.
|
||||
{
|
||||
bool can_back = !st.drill_back.empty();
|
||||
ImGui::BeginDisabled(!can_back);
|
||||
if (ImGui::SmallButton("<##drill_back")) {
|
||||
DrillStep s = st.drill_back.back();
|
||||
st.drill_back.pop_back();
|
||||
if (undo_drill_step(st, s)) {
|
||||
st.drill_forward.push_back(s);
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
if (can_back && ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("Drill back (%zu)", st.drill_back.size());
|
||||
ImGui::SameLine();
|
||||
bool can_fwd = !st.drill_forward.empty();
|
||||
ImGui::BeginDisabled(!can_fwd);
|
||||
if (ImGui::SmallButton(">##drill_fwd")) {
|
||||
DrillStep s = st.drill_forward.back();
|
||||
st.drill_forward.pop_back();
|
||||
if (apply_drill_step(st, s)) {
|
||||
st.drill_back.push_back(s);
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
if (can_fwd && ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("Drill forward (%zu)", st.drill_forward.size());
|
||||
ImGui::SameLine();
|
||||
bool can_up = (st.active_stage > 0);
|
||||
ImGui::BeginDisabled(!can_up);
|
||||
if (ImGui::SmallButton("^##drill_up")) drill_up(st);
|
||||
ImGui::EndDisabled();
|
||||
if (can_up && ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("Drill up (stage previo, sin perder filters)");
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("|");
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
for (int si = 0; si < (int)st.stages.size(); ++si) {
|
||||
if (si > 0) { ImGui::SameLine(); ImGui::TextDisabled(">"); ImGui::SameLine(); }
|
||||
|
||||
bool active = (si == st.active_stage);
|
||||
if (active) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32( 80, 140, 200, 240));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(100, 160, 220, 240));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32( 60, 120, 180, 240));
|
||||
} else {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32( 70, 70, 90, 200));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32( 90, 90, 120, 220));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32( 55, 55, 75, 220));
|
||||
}
|
||||
|
||||
char label[256];
|
||||
if (si == 0) {
|
||||
std::snprintf(label, sizeof(label), "Raw##stage%d", si);
|
||||
} else {
|
||||
const Stage& s = st.stages[si];
|
||||
std::string desc;
|
||||
for (size_t i = 0; i < s.breakouts.size() && i < 2; ++i) {
|
||||
if (i > 0) desc += ", ";
|
||||
desc += s.breakouts[i];
|
||||
}
|
||||
if (s.breakouts.size() > 2) desc += "...";
|
||||
if (desc.empty())
|
||||
std::snprintf(label, sizeof(label), "Stage %d##s%d", si, si);
|
||||
else
|
||||
std::snprintf(label, sizeof(label), "Stage %d: by %s##s%d",
|
||||
si, desc.c_str(), si);
|
||||
}
|
||||
if (ImGui::Button(label)) st.active_stage = si;
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
if (si > 0) {
|
||||
ImGui::SameLine();
|
||||
char xlbl[32];
|
||||
std::snprintf(xlbl, sizeof(xlbl), "x##rm_s%d", si);
|
||||
if (ImGui::SmallButton(xlbl)) {
|
||||
// borra ese stage y sucesores
|
||||
while ((int)st.stages.size() > si) st.stages.pop_back();
|
||||
if (st.active_stage >= (int)st.stages.size())
|
||||
st.active_stage = (int)st.stages.size() - 1;
|
||||
if (st.active_stage < 0) st.active_stage = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled(">");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("+ Stage##add_stage")) {
|
||||
st.stages.push_back(Stage{});
|
||||
st.active_stage = (int)st.stages.size() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// drill_into: API publica. Anade un filter Op::Eq sobre col_name=value al
|
||||
// stage (from_stage - 1) y cambia st.active_stage a ese stage previo.
|
||||
// Graba el step en st.drill_back y limpia st.drill_forward (rama nueva).
|
||||
// ---------------------------------------------------------------------------
|
||||
void drill_into(State& st, int from_stage,
|
||||
const std::string& col_name, const std::string& value,
|
||||
const std::vector<std::string>& prev_input_headers)
|
||||
{
|
||||
if (from_stage <= 0 || from_stage >= (int)st.stages.size()) return;
|
||||
int target = from_stage - 1;
|
||||
int ci = -1;
|
||||
for (size_t i = 0; i < prev_input_headers.size(); ++i) {
|
||||
if (prev_input_headers[i] == col_name) { ci = (int)i; break; }
|
||||
}
|
||||
if (ci < 0) return;
|
||||
|
||||
// Fase 10: graba step en drill_back, limpia forward (rama nueva).
|
||||
DrillStep step;
|
||||
step.target_stage = target;
|
||||
step.filter_pos = (int)st.stages[target].filters.size();
|
||||
step.prev_active_stage = st.active_stage;
|
||||
step.added = make_drill_filter(ci, value);
|
||||
apply_drill_step(st, step);
|
||||
st.drill_back.push_back(step);
|
||||
st.drill_forward.clear();
|
||||
}
|
||||
|
||||
} // namespace data_table
|
||||
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
// data_table_drill — drill-down stack + breadcrumb de stages.
|
||||
// Sub-funcion extraida de modules/data_table/data_table.cpp (issue 0107c).
|
||||
//
|
||||
// Responsabilidad:
|
||||
// - make_drill_filter: construye un Filter Op::Eq sobre un col_idx.
|
||||
// - apply_drill_step / undo_drill_step: manipula el stack drill_back/forward.
|
||||
// - drill_up: retrocede un stage.
|
||||
// - drill_into: API publica que anade un filter al stage previo y cambia active.
|
||||
// - draw_stage_breadcrumb: UI de la barra de breadcrumb con botones < > ^.
|
||||
//
|
||||
// Rangos del fuente original:
|
||||
// - make_drill_filter : linea 700-706
|
||||
// - apply_drill_step : lineas 708-718
|
||||
// - undo_drill_step : lineas 720-730
|
||||
// - drill_up : lineas 731-745
|
||||
// - drill_into : lineas 2898-2919
|
||||
// - draw_stage_breadcrumb: lineas 1383-1488
|
||||
//
|
||||
// Dependencias: data_table_types.h (State, DrillStep, Filter, Stage).
|
||||
|
||||
#include "core/data_table_types.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace data_table {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers internos del drill stack (usados por drill_into y draw_stage_breadcrumb).
|
||||
// Se exponen en el header para que data_table_grid pueda llamarlos al
|
||||
// procesar el drill popup de celdas en stage > 0.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// make_drill_filter: crea un Filter Op::Eq para col_idx con el valor dado.
|
||||
Filter make_drill_filter(int col_idx, const std::string& value);
|
||||
|
||||
// apply_drill_step: inserta step.added en el stage en step.target_stage y
|
||||
// actualiza st.active_stage. Retorna true si el step se aplico correctamente.
|
||||
bool apply_drill_step(State& st, const DrillStep& step);
|
||||
|
||||
// undo_drill_step: elimina el filter insertado por apply_drill_step y restaura
|
||||
// st.active_stage a step.prev_active_stage. Retorna true si se deshizo.
|
||||
bool undo_drill_step(State& st, const DrillStep& step);
|
||||
|
||||
// drill_up: retrocede st.active_stage en 1 si hay stages previos.
|
||||
// Retorna true si se pudo retroceder.
|
||||
bool drill_up(State& st);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// API publica: drill_into
|
||||
// Anade un filter Op::Eq sobre col_name=value al stage (from_stage - 1) y
|
||||
// cambia st.active_stage a from_stage - 1. Graba el step en st.drill_back
|
||||
// y limpia st.drill_forward (rama nueva del arbol de drill).
|
||||
//
|
||||
// prev_input_headers: headers del INPUT del stage from_stage (para traducir
|
||||
// col_name a col_idx en el stage previo).
|
||||
// ---------------------------------------------------------------------------
|
||||
void drill_into(State& st, int from_stage,
|
||||
const std::string& col_name, const std::string& value,
|
||||
const std::vector<std::string>& prev_input_headers);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// UI: draw_stage_breadcrumb
|
||||
// Dibuja la barra de navegacion de drill con botones < (back), > (forward),
|
||||
// ^ (up) y el nombre del stage activo. Mutates st.drill_back/forward y
|
||||
// st.active_stage en respuesta a clicks.
|
||||
// ---------------------------------------------------------------------------
|
||||
void draw_stage_breadcrumb(State& st);
|
||||
|
||||
} // namespace data_table
|
||||
@@ -0,0 +1,85 @@
|
||||
---
|
||||
name: data_table_drill
|
||||
kind: function
|
||||
lang: cpp
|
||||
domain: viz
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "void data_table::drill_into(State& st, int from_stage, const std::string& col_name, const std::string& value, const std::vector<std::string>& prev_input_headers)"
|
||||
description: "Drill-down stack + UI breadcrumb para la tabla TQL. Gestiona el arbol de navegacion de stages: drill_into anade un filtro Op::Eq al stage previo y avanza al nivel de detalle, draw_stage_breadcrumb dibuja los botones de navegacion (< back, > forward, ^ up) y el selector de stages activo. Sub-funcion extraida de modules/data_table/data_table.cpp (issue 0107c)."
|
||||
tags: [viz, table, imgui, ui, drill-down, navigation, tql, cpp-tables]
|
||||
uses_functions:
|
||||
- data_table_cpp_viz
|
||||
uses_types:
|
||||
- data_table_types_cpp_core
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [imgui]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "cpp/functions/viz/data_table_drill.cpp"
|
||||
framework: imgui
|
||||
params:
|
||||
- name: st
|
||||
desc: "State mutable que contiene st.stages, st.active_stage, st.drill_back (undo stack), st.drill_forward (redo stack). Todos son mutados por las funciones de este modulo."
|
||||
- name: from_stage
|
||||
desc: "Stage desde el que se origina el drill (stage activo al hacer right-click en una celda de breakout). Debe ser > 0 para que haya stage previo donde anadir el filtro."
|
||||
- name: col_name
|
||||
desc: "Nombre de la columna breakout sobre la que se drilla (debe existir en prev_input_headers)."
|
||||
- name: value
|
||||
desc: "Valor de la celda seleccionada. Se aplica como Filter Op::Eq en el stage from_stage-1."
|
||||
- name: prev_input_headers
|
||||
desc: "Headers del INPUT del stage from_stage (= output del stage from_stage-1). Necesarios para traducir col_name a col_idx."
|
||||
output: "Void. Mutates st.stages[target].filters, st.active_stage, st.drill_back, st.drill_forward."
|
||||
---
|
||||
|
||||
## Documentacion
|
||||
|
||||
Sub-funcion que encapsula la logica de drill-down de la tabla TQL. El drill-down permite al usuario hacer right-click en una celda de una columna breakout (stage > 0) y "entrar" al detalle del grupo seleccionado anadiendo un filtro al stage previo.
|
||||
|
||||
### Funciones publicas
|
||||
|
||||
| Funcion | Uso |
|
||||
|---|---|
|
||||
| `make_drill_filter(col_idx, value)` | Helper: crea `Filter{col_idx, Op::Eq, value}`. |
|
||||
| `apply_drill_step(st, step)` | Inserta `step.added` en `st.stages[step.target_stage].filters` y actualiza `st.active_stage`. |
|
||||
| `undo_drill_step(st, step)` | Invierte `apply_drill_step`: elimina el filtro y restaura `st.active_stage`. |
|
||||
| `drill_up(st)` | Decrementa `st.active_stage` en 1 (sin crear entry en el undo stack). |
|
||||
| `drill_into(st, from, col, val, hdrs)` | API publica: compone make_drill_filter + apply_drill_step + graba en drill_back + limpia drill_forward. |
|
||||
| `draw_stage_breadcrumb(st)` | UI: botones < > ^ + combo de stages. |
|
||||
|
||||
### Invariante del stack
|
||||
|
||||
- `st.drill_back`: historial de drill steps (undo). Cada `drill_into` agrega al final.
|
||||
- `st.drill_forward`: pasos deshecho (redo). Se limpia en cada `drill_into` nueva.
|
||||
- `drill_up` NO agrega al stack — es un atajo que no se puede rehacer.
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```cpp
|
||||
// En el cell popup de una celda de breakout col (stage > 0):
|
||||
if (ImGui::MenuItem(lbl)) {
|
||||
data_table::drill_into(st, active_stage,
|
||||
cur_headers[col], cell_value,
|
||||
input_headers_active);
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
// Breadcrumb al inicio del area de chrome:
|
||||
if (chrome_visible) {
|
||||
data_table::draw_stage_breadcrumb(st);
|
||||
}
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
`drill_into` se llama desde el popup de celda en `data_table_grid_cpp_viz` cuando el usuario hace right-click en una columna breakout. `draw_stage_breadcrumb` se llama una vez por frame en el area de chrome antes de los chips.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- `drill_into` requiere `from_stage > 0`. Si `from_stage <= 0` la funcion retorna sin hacer nada (no hay stage previo).
|
||||
- `col_name` debe existir en `prev_input_headers`; si no se encuentra, la funcion retorna sin efecto (el filter no se anade).
|
||||
- `apply_drill_step` y `undo_drill_step` modifican `st.stages[step.target_stage].filters` por posicion (`filter_pos`). Hay un riesgo de corrupcion si los filtros del stage cambian entre apply y undo por otra via. El design actual asume que el caller no muta los filtros del stage target fuera de este API.
|
||||
- `draw_stage_breadcrumb` dibuja los botones en linea horizontal; si el area es muy estrecha (< 120px) los botones solapan. El caller debe asegurar suficiente ancho o usar `ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ...)`.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
// data_table_grid — render del grid de datos: ImGui::BeginTable, headers con sort,
|
||||
// freeze cols, drag-drop de columnas, renderers declarativos (Badge/Progress/
|
||||
// Duration/Icon/Button/Dots/CategoricalChip/ColorScale), color rules per cell,
|
||||
// selection (Ctrl+C TSV), stats overlay, cell popup.
|
||||
//
|
||||
// Sub-funcion extraida de modules/data_table/data_table.cpp (issue 0107c).
|
||||
// Rangos del fuente original:
|
||||
// - draw_cell_custom (declarative renderers): lineas 376-655
|
||||
// - Grid setup + header row + cell loop (stage 0): lineas 3436-3765
|
||||
// - Grid setup + cell loop (stage > 0): lineas 4040-4311
|
||||
// - Ctrl+C TSV copy: lineas 3725-3766
|
||||
// - Stats overlay en encabezados: lineas 4160-4226
|
||||
//
|
||||
// Dependencias: data_table_types.h, data_table_color_rules.h, imgui.h.
|
||||
|
||||
#include "core/data_table_types.h"
|
||||
#include "imgui.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace data_table {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// draw_cell_custom — renderer declarativo por ColumnSpec.renderer.
|
||||
// Soporta: Text (fallback), Badge, Progress, Duration, Icon, Button, Dots,
|
||||
// CategoricalChip, ColorScale.
|
||||
// Emite TableEvent (ButtonClick, RowDoubleClick) en events_out si no-null.
|
||||
// Llamar dentro del contexto de un ImGui::BeginTable activo.
|
||||
// ---------------------------------------------------------------------------
|
||||
void draw_cell_custom(const ColumnSpec& spec, const char* value,
|
||||
int row, int col,
|
||||
std::vector<TableEvent>* events_out);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// render_grid_stage0 — renderiza el grid para el path stage==0 (datos crudos
|
||||
// + derivados via Lua). Incluye: BeginTable, headers (sort click, drag-drop,
|
||||
// header menu via draw_header_menu), cell loop con draw_cell_custom,
|
||||
// apply_color_rules_for_cell, selection rect, Ctrl+C TSV copy.
|
||||
//
|
||||
// Parametros:
|
||||
// id — ID ImGui unico para el BeginTable.
|
||||
// st — State mutable (sort, selection, color_rules, col_order, ...).
|
||||
// cells — cells originales row-major [rows * orig_cols].
|
||||
// row_count — numero de filas originales.
|
||||
// orig_cols — numero de columnas originales (sin derived).
|
||||
// eff_cols — numero de columnas efectivas (orig + derived).
|
||||
// eff_headers — punteros a los nombres de columna efectivos (size eff_cols).
|
||||
// eff_types — tipos de columna efectivos (size eff_cols).
|
||||
// src_for_eff — mapeo eff col -> src col en orig (size eff_cols).
|
||||
// visible_rows — filas visibles tras filtrado (indices en [0, row_count)).
|
||||
// events_out — si no null, recibe TableEvent de esta frame.
|
||||
// ---------------------------------------------------------------------------
|
||||
void render_grid_stage0(const char* id,
|
||||
State& st,
|
||||
const char* const* cells,
|
||||
int row_count, int orig_cols, int eff_cols,
|
||||
const char* const* eff_headers,
|
||||
const ColumnType* eff_types,
|
||||
const int* src_for_eff,
|
||||
const std::vector<int>& visible_rows,
|
||||
const TableInput& main_t,
|
||||
std::vector<TableEvent>* events_out);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// render_grid_stage_n — renderiza el grid para el path stage > 0 (datos
|
||||
// materializados del compute_stage chain). Similar a stage0 pero opera sobre
|
||||
// cur_cells/cur_rows/cur_cols_n ya materializados.
|
||||
//
|
||||
// input_headers_active — headers del INPUT del stage activo (para drill popup).
|
||||
// n_breakouts — numero de breakout cols en el stage activo.
|
||||
// ---------------------------------------------------------------------------
|
||||
void render_grid_stage_n(const char* id,
|
||||
State& st,
|
||||
const char* const* cur_cells,
|
||||
int cur_rows, int cur_cols_n,
|
||||
const std::vector<std::string>& cur_headers,
|
||||
const std::vector<ColumnType>& cur_types,
|
||||
const std::vector<std::string>& input_headers_active,
|
||||
int n_breakouts,
|
||||
const TableInput& main_t,
|
||||
std::vector<TableEvent>* events_out);
|
||||
|
||||
} // namespace data_table
|
||||
@@ -0,0 +1,103 @@
|
||||
---
|
||||
name: data_table_grid
|
||||
kind: function
|
||||
lang: cpp
|
||||
domain: viz
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "void data_table::render_grid_stage0(const char* id, State& st, const char* const* cells, int row_count, int orig_cols, int eff_cols, const char* const* eff_headers, const ColumnType* eff_types, const int* src_for_eff, const std::vector<int>& visible_rows, const TableInput& main_t, std::vector<TableEvent>* events_out)"
|
||||
description: "Render del grid de datos de la tabla TQL: ImGui::BeginTable con freeze de cabecera, headers clicables (sort), drag-drop de columnas para reordenar, renderers declarativos por ColumnSpec (Badge/Progress/Duration/Icon/Button/Dots/CategoricalChip/ColorScale), aplicacion de ColorRules por celda, seleccion de rango (Ctrl+C TSV), stats overlay en encabezados. Dos entrypoints: render_grid_stage0 (datos crudos + derived Lua) y render_grid_stage_n (output materializado de compute_stage). Sub-funcion extraida de modules/data_table/data_table.cpp (issue 0107c)."
|
||||
tags: [viz, table, imgui, ui, grid, cell-renderer, sorting, tql, cpp-tables]
|
||||
uses_functions:
|
||||
- data_table_color_rules_cpp_viz
|
||||
- data_table_cpp_viz
|
||||
uses_types:
|
||||
- data_table_types_cpp_core
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [imgui]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "cpp/functions/viz/data_table_grid.cpp"
|
||||
framework: imgui
|
||||
params:
|
||||
- name: id
|
||||
desc: "ID unico de ImGui para BeginTable (ej. '##my_table'). Debe ser unico por instancia en la ventana."
|
||||
- name: st
|
||||
desc: "State mutable. El grid lee st.col_order, st.col_visible, st.color_rules, st.sel_anchor/end, st.stages[active].sorts. Lo muta en respuesta a clicks, drag-drop, seleccion."
|
||||
- name: cells
|
||||
desc: "Puntero a cells originales row-major [rows * orig_cols]. Para stage0; stage_n recibe cur_cells ya materializados."
|
||||
- name: row_count / orig_cols / eff_cols
|
||||
desc: "Dimensiones de la tabla. orig_cols = columnas del input; eff_cols = orig_cols + derived cols del stage 0."
|
||||
- name: eff_headers / eff_types
|
||||
desc: "Headers y tipos efectivos (size eff_cols). Usados para headers del BeginTable y para render de celda."
|
||||
- name: src_for_eff
|
||||
desc: "Mapeo eff_col_idx -> src_col_idx en la tabla original. Necesario para leer cells[row * orig_cols + src]. Solo para stage0."
|
||||
- name: visible_rows
|
||||
desc: "Indices de filas visibles tras filtrado (output de compute_visible_rows). Solo para stage0."
|
||||
- name: main_t
|
||||
desc: "TableInput principal: proporciona column_specs para draw_cell_custom y los column_spec ids para eventos."
|
||||
- name: events_out
|
||||
desc: "Si no null, recibe TableEvent (ButtonClick, RowDoubleClick, RowRightClick) generados este frame."
|
||||
output: "Void. Mutates st (sort, selection, col_order). Appends to events_out si no null."
|
||||
---
|
||||
|
||||
## Documentacion
|
||||
|
||||
Sub-funcion que encapsula el render del grid de celdas de la tabla TQL. Es el bloque mas grande del refactor 0107c (~1300 LOC del fuente original).
|
||||
|
||||
### Renderers declarativos (draw_cell_custom)
|
||||
|
||||
| Renderer | Que muestra |
|
||||
|---|---|
|
||||
| `Text` | Selectable de texto, tooltip on-hover si `tooltip_on_hover=true` |
|
||||
| `Badge` | Pastilla de color (background del spec) con texto |
|
||||
| `Progress` | Barra de progreso ImGui [0..100] |
|
||||
| `Duration` | Formato "Xh Ym Zs" desde segundos enteros |
|
||||
| `Icon` | Glyph Tabler (usa `icons_tabler.h`) |
|
||||
| `Button` | Boton clickable; emite `TableEventKind::ButtonClick` con `action_id` |
|
||||
| `Dots` | Circulos coloreados por reglas de chips del ColumnSpec |
|
||||
| `CategoricalChip` | Pastilla con borde coloreado; color por palette hash |
|
||||
| `ColorScale` | Degradado de fondo segun valor numerico normalizado |
|
||||
|
||||
### Flujo render_grid_stage0
|
||||
|
||||
1. Setup `ImGuiTableFlags` (Borders + RowBg + Resizable + ScrollY).
|
||||
2. `BeginTable` → `TableSetupColumn` por cada col visible en `st.col_order`.
|
||||
3. `TableSetupScrollFreeze(0, 1)` para freeze de header.
|
||||
4. Header row: `Selectable` con sort-click (`apply_header_sort_click`), drag-drop source/target para reordenar columnas.
|
||||
5. Por cada fila en `visible_rows`: por cada col visible en `st.col_order`: `apply_color_rules_for_cell` → `draw_cell_custom`.
|
||||
6. Seleccion de rango: mouse drag con `sel_anchor/sel_end`; Ctrl+C genera TSV al portapapeles.
|
||||
7. `EndTable` + `PopStyleColor(3)`.
|
||||
|
||||
### Flujo render_grid_stage_n
|
||||
|
||||
Igual pero opera sobre `cur_cells` ya materializados del chain `compute_stage`. Ademas:
|
||||
- Stats overlay en headers (histograma/top-categories de `st.stats_cache`).
|
||||
- Drill popup: right-click en celda de breakout col ofrece "Drill into: col = val".
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```cpp
|
||||
// Dentro del render() principal, path stage == 0:
|
||||
if (st.display == data_table::ViewMode::Table && visible_cols > 0) {
|
||||
data_table::render_grid_stage0(id, st,
|
||||
cells_in, row_count_in, orig_cols, eff_cols,
|
||||
eff_headers.data(), eff_types.data(), src_for_eff.data(),
|
||||
visible_rows, main_t, events_out);
|
||||
}
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
Llamar desde el entrypoint thin `data_table::render()` tras calcular `visible_rows` (stage 0) o materializar el chain (stage > 0). No llamar directamente desde apps — la API publica es `data_table::render()`.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- `render_grid_stage0` y `render_grid_stage_n` deben llamarse dentro de un `ImGui::BeginChild` o contexto de ventana activo. No funcionan fuera de un frame ImGui.
|
||||
- La funcion hace `PushStyleColor` para Header/HeaderHovered/HeaderActive y debe hacer el correspondiente `PopStyleColor(3)`. No anidar pushes adicionales sin su pop.
|
||||
- `draw_cell_custom` con renderer `Button` emite eventos; si `events_out` es null se ignoran silenciosamente (back-compat).
|
||||
- El `thread_local` en el entrypoint original (main_hdr_ptrs, joinables_v) no se mueve a esta funcion — esos son responsabilidad del entrypoint thin.
|
||||
- Despues del split, `apply_color_rules_for_cell` vive en `data_table_color_rules_cpp_viz` — no re-implementar aqui.
|
||||
@@ -0,0 +1,429 @@
|
||||
// data_table_viz_panels — paneles de visualizacion lateral de la tabla TQL.
|
||||
// Sub-funcion extraida de modules/data_table/data_table.cpp (issue 0107c).
|
||||
//
|
||||
// Funciones implementadas:
|
||||
// - draw_table_toggle (ex lineas 745-767)
|
||||
// - draw_extra_panel (ex lineas 771-856)
|
||||
// - draw_viz_config_popup (ex lineas 858-1021)
|
||||
// - draw_viz_selector (ex lineas 1034-1110)
|
||||
// - maybe_recompute_stats (ex lineas 1118-1145)
|
||||
|
||||
#include "viz/data_table_viz_panels.h"
|
||||
#include "data_table/data_table_internal.h"
|
||||
#include "viz/data_table_grid.h" // draw_cell_custom
|
||||
#include "core/data_table_types.h"
|
||||
#include "core/compute_column_stats.h"
|
||||
#include "core/tql_emit.h"
|
||||
#include "viz/viz_render.h"
|
||||
#include "imgui.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace data_table {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// draw_table_toggle
|
||||
// ---------------------------------------------------------------------------
|
||||
void draw_table_toggle(ViewMode& display, ViewMode& last_non_table,
|
||||
const char* id_suffix,
|
||||
State* st_opt)
|
||||
{
|
||||
bool is_table = (display == ViewMode::Table);
|
||||
char b[64];
|
||||
std::snprintf(b, sizeof(b), "%s##tbl_%s",
|
||||
is_table ? "Show chart" : "Show table", id_suffix);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(80, 140, 200, 240));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(100, 160, 220, 240));
|
||||
if (ImGui::SmallButton(b)) {
|
||||
if (is_table) {
|
||||
ViewMode tgt = (last_non_table == ViewMode::Table)
|
||||
? ViewMode::Bar : last_non_table;
|
||||
display = tgt;
|
||||
if (st_opt && view_mode_needs_aggregation(tgt)) {
|
||||
auto_promote_aggregated(*st_opt);
|
||||
}
|
||||
} else {
|
||||
last_non_table = display;
|
||||
display = ViewMode::Table;
|
||||
}
|
||||
}
|
||||
ImGui::PopStyleColor(2);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// draw_extra_panel
|
||||
// ---------------------------------------------------------------------------
|
||||
bool draw_extra_panel(State& st, VizPanel& p, int idx,
|
||||
const StageOutput& so,
|
||||
const std::vector<ColumnSpec>* col_specs)
|
||||
{
|
||||
bool close_req = false;
|
||||
char child_id[64]; std::snprintf(child_id, sizeof(child_id), "##extra_viz_%d", idx);
|
||||
ImGui::BeginChild(child_id, ImVec2(0, 320), true);
|
||||
|
||||
// Toolbar
|
||||
int n_modes = 0;
|
||||
const ViewMode* modes = all_view_modes(&n_modes);
|
||||
ImGui::TextDisabled("View:");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(180);
|
||||
char combo_id[64]; std::snprintf(combo_id, sizeof(combo_id), "##ev_mode_%d", idx);
|
||||
if (ImGui::BeginCombo(combo_id, view_mode_label(p.display))) {
|
||||
for (int i = 0; i < n_modes; ++i) {
|
||||
bool sel = (modes[i] == p.display);
|
||||
if (ImGui::Selectable(view_mode_label(modes[i]), sel)) {
|
||||
p.display = modes[i];
|
||||
p.config.fit_request = true;
|
||||
}
|
||||
if (sel) ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
char fit_id[32]; std::snprintf(fit_id, sizeof(fit_id), "Fit##ev_fit_%d", idx);
|
||||
if (ImGui::SmallButton(fit_id)) p.config.fit_request = true;
|
||||
ImGui::SameLine();
|
||||
char lock_id[32]; std::snprintf(lock_id, sizeof(lock_id), "%s##ev_lock_%d",
|
||||
p.config.locked ? "Locked" : "Lock", idx);
|
||||
if (p.config.locked) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(180, 60, 60, 230));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(200, 80, 80, 240));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32(150, 40, 40, 240));
|
||||
}
|
||||
if (ImGui::SmallButton(lock_id)) p.config.locked = !p.config.locked;
|
||||
if (p.config.locked) ImGui::PopStyleColor(3);
|
||||
ImGui::SameLine();
|
||||
char close_id[32]; std::snprintf(close_id, sizeof(close_id), "X##ev_close_%d", idx);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(120, 50, 50, 220));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(160, 70, 70, 240));
|
||||
if (ImGui::SmallButton(close_id)) close_req = true;
|
||||
ImGui::PopStyleColor(2);
|
||||
|
||||
// Toggle Table <-> View per-panel
|
||||
char ts[32]; std::snprintf(ts, sizeof(ts), "ep%d", idx);
|
||||
draw_table_toggle(p.display, p.last_non_table, ts);
|
||||
|
||||
// Render: si Table -> mini table; else chart.
|
||||
if (p.display == ViewMode::Table) {
|
||||
ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
|
||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY |
|
||||
ImGuiTableFlags_ScrollX;
|
||||
char tid[64]; std::snprintf(tid, sizeof(tid), "##ep_table_%d", idx);
|
||||
if (so.cols > 0 && ImGui::BeginTable(tid, so.cols, flags, ImVec2(0, 0))) {
|
||||
for (int c = 0; c < so.cols; ++c)
|
||||
ImGui::TableSetupColumn(so.headers[c].c_str());
|
||||
ImGui::TableHeadersRow();
|
||||
for (int r = 0; r < so.rows; ++r) {
|
||||
ImGui::TableNextRow();
|
||||
for (int c = 0; c < so.cols; ++c) {
|
||||
ImGui::TableSetColumnIndex(c);
|
||||
const char* s = so.cells[(size_t)r * so.cols + c];
|
||||
// Issue 0081-N: declarative renderer for extra panel mini-table.
|
||||
// events_out not propagated to mini-table (secondary render).
|
||||
bool custom_ep = false;
|
||||
if (col_specs && c < (int)col_specs->size()) {
|
||||
const ColumnSpec& cs = (*col_specs)[(size_t)c];
|
||||
if (cs.renderer != CellRenderer::Text) {
|
||||
draw_cell_custom(cs, s, r, c, nullptr);
|
||||
custom_ep = true;
|
||||
}
|
||||
}
|
||||
if (!custom_ep) ImGui::TextUnformatted(s ? s : "");
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
} else {
|
||||
viz::render(so, p.display, p.config, ImVec2(-1, -1));
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
(void)st;
|
||||
return close_req;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// draw_viz_config_popup
|
||||
// ---------------------------------------------------------------------------
|
||||
void draw_viz_config_popup(State& st) {
|
||||
if (!ImGui::BeginPopup("##viz_cfg_popup")) return;
|
||||
ImGui::Text("Configure: %s", view_mode_label(st.display));
|
||||
ImGui::Separator();
|
||||
|
||||
auto cols = collect_active_col_info(st);
|
||||
std::vector<const char*> all_names;
|
||||
std::vector<const char*> num_names;
|
||||
std::vector<const char*> cat_names;
|
||||
for (auto& c : cols) {
|
||||
all_names.push_back(c.name.c_str());
|
||||
if (c.type == ColumnType::Int || c.type == ColumnType::Float)
|
||||
num_names.push_back(c.name.c_str());
|
||||
else
|
||||
cat_names.push_back(c.name.c_str());
|
||||
}
|
||||
|
||||
auto& vc = st.viz_config;
|
||||
ViewMode m = st.display;
|
||||
|
||||
auto combo_for_col = [&](const char* label, std::string& target,
|
||||
const std::vector<const char*>& options) {
|
||||
const char* preview = target.empty() ? "(auto)" : target.c_str();
|
||||
ImGui::SetNextItemWidth(220);
|
||||
if (ImGui::BeginCombo(label, preview)) {
|
||||
if (ImGui::Selectable("(auto)", target.empty())) target.clear();
|
||||
for (auto& o : options) {
|
||||
bool sel = (target == o);
|
||||
if (ImGui::Selectable(o, sel)) target = o;
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
};
|
||||
|
||||
// X col: scatter, line, area, stairs, hist2d, bubble
|
||||
bool needs_x = (m == ViewMode::Scatter || m == ViewMode::Line ||
|
||||
m == ViewMode::Area || m == ViewMode::Stairs ||
|
||||
m == ViewMode::Histogram2D || m == ViewMode::Bubble);
|
||||
if (needs_x) combo_for_col("X column", vc.x_col, num_names);
|
||||
|
||||
// Y cols: most modes
|
||||
bool needs_y = (m != ViewMode::Pie && m != ViewMode::Donut && m != ViewMode::Funnel &&
|
||||
m != ViewMode::Candlestick);
|
||||
if (needs_y) {
|
||||
ImGui::Text("Y columns:");
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("(%d selected; empty = auto)", (int)vc.y_cols.size());
|
||||
ImGui::Indent();
|
||||
for (auto& nn : num_names) {
|
||||
std::string ns = nn;
|
||||
bool checked = std::find(vc.y_cols.begin(), vc.y_cols.end(), ns) != vc.y_cols.end();
|
||||
if (ImGui::Checkbox(nn, &checked)) {
|
||||
if (checked) vc.y_cols.push_back(ns);
|
||||
else {
|
||||
auto it = std::find(vc.y_cols.begin(), vc.y_cols.end(), ns);
|
||||
if (it != vc.y_cols.end()) vc.y_cols.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::Unindent();
|
||||
if (ImGui::SmallButton("Clear Y##clr_y")) vc.y_cols.clear();
|
||||
}
|
||||
|
||||
// Cat col: bar/pie/funnel/box/waterfall
|
||||
bool needs_cat = (m == ViewMode::Bar || m == ViewMode::Column ||
|
||||
m == ViewMode::GroupedBar || m == ViewMode::StackedBar ||
|
||||
m == ViewMode::Pie || m == ViewMode::Donut ||
|
||||
m == ViewMode::Funnel || m == ViewMode::BoxPlot ||
|
||||
m == ViewMode::Waterfall);
|
||||
if (needs_cat) {
|
||||
// Si el active stage YA esta agrupado (breakouts != empty), la categoria
|
||||
// del chart la dicta el breakout. Mostrar todas las cols del INPUT del
|
||||
// stage (= cols pre-agrupacion). Selecionar otra = reemplaza breakouts[0]
|
||||
// (re-agrupa).
|
||||
int as = st.active_stage;
|
||||
bool grouped = (as >= 0 && as < (int)st.stages.size() &&
|
||||
!st.stages[as].breakouts.empty());
|
||||
const auto& U = ui();
|
||||
if (grouped) {
|
||||
std::vector<const char*> input_cat_names;
|
||||
for (size_t i = 0; i < U.input_headers_active.size() &&
|
||||
i < U.input_types_active.size(); ++i) {
|
||||
ColumnType t = U.input_types_active[i];
|
||||
if (t == ColumnType::String || t == ColumnType::Date ||
|
||||
t == ColumnType::Bool || t == ColumnType::Json) {
|
||||
input_cat_names.push_back(U.input_headers_active[i].c_str());
|
||||
}
|
||||
}
|
||||
std::string cur_break = st.stages[as].breakouts[0];
|
||||
const char* preview = cur_break.empty() ? "(none)" : cur_break.c_str();
|
||||
ImGui::SetNextItemWidth(220);
|
||||
if (ImGui::BeginCombo("Category (breakout)", preview)) {
|
||||
for (auto& o : input_cat_names) {
|
||||
bool sel = (cur_break == o);
|
||||
if (ImGui::Selectable(o, sel)) {
|
||||
st.stages[as].breakouts[0] = o;
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
} else {
|
||||
combo_for_col("Category", vc.cat_col, cat_names);
|
||||
}
|
||||
}
|
||||
|
||||
// Size col: bubble
|
||||
if (m == ViewMode::Bubble) combo_for_col("Size column", vc.size_col, num_names);
|
||||
|
||||
// Color
|
||||
ImGui::Separator();
|
||||
float col_f[4] = {
|
||||
((vc.primary_color) & 0xFF) / 255.0f,
|
||||
((vc.primary_color >> 8) & 0xFF) / 255.0f,
|
||||
((vc.primary_color >> 16) & 0xFF) / 255.0f,
|
||||
((vc.primary_color >> 24) & 0xFF) / 255.0f,
|
||||
};
|
||||
if (vc.primary_color == 0) { col_f[0]=col_f[1]=col_f[2]=1.0f; col_f[3]=1.0f; }
|
||||
if (ImGui::ColorEdit4("Primary color", col_f, ImGuiColorEditFlags_AlphaBar)) {
|
||||
unsigned int r2 = (unsigned int)(col_f[0] * 255);
|
||||
unsigned int g2 = (unsigned int)(col_f[1] * 255);
|
||||
unsigned int b2 = (unsigned int)(col_f[2] * 255);
|
||||
unsigned int a2 = (unsigned int)(col_f[3] * 255);
|
||||
vc.primary_color = (a2 << 24) | (b2 << 16) | (g2 << 8) | r2;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("Auto##color")) vc.primary_color = 0;
|
||||
|
||||
// Hist bins
|
||||
if (m == ViewMode::Histogram || m == ViewMode::Histogram2D) {
|
||||
ImGui::SetNextItemWidth(120);
|
||||
int bins = vc.hist_bins;
|
||||
if (ImGui::InputInt("Bins (0=auto)", &bins)) {
|
||||
if (bins < 0) bins = 0;
|
||||
vc.hist_bins = bins;
|
||||
}
|
||||
}
|
||||
|
||||
// Pie radius
|
||||
if (m == ViewMode::Pie || m == ViewMode::Donut) {
|
||||
ImGui::SetNextItemWidth(120);
|
||||
float rad = vc.pie_radius;
|
||||
if (ImGui::SliderFloat("Radius (0=auto)", &rad, 0.0f, 0.5f, "%.2f")) {
|
||||
vc.pie_radius = rad;
|
||||
}
|
||||
}
|
||||
|
||||
// Toggles
|
||||
ImGui::Separator();
|
||||
ImGui::Checkbox("Show legend", &vc.show_legend);
|
||||
if (m == ViewMode::Line || m == ViewMode::Area || m == ViewMode::Stairs) {
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Show markers", &vc.show_markers);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
if (ImGui::SmallButton("Reset config")) {
|
||||
vc = ViewConfig{};
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("Close")) ImGui::CloseCurrentPopup();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// draw_viz_selector
|
||||
// ---------------------------------------------------------------------------
|
||||
void draw_viz_selector(State& st) {
|
||||
int n_modes = 0;
|
||||
const ViewMode* modes = all_view_modes(&n_modes);
|
||||
|
||||
// Right-align: reserve "View: [combo] [Fit] [Lock] [Config] [Ask AI] [+ Viz]"
|
||||
const float combo_w = 200.0f;
|
||||
const float total_w = combo_w + 50.0f + 280.0f;
|
||||
float right_edge = ImGui::GetWindowContentRegionMax().x;
|
||||
float target_x = right_edge - total_w;
|
||||
float min_x = ImGui::GetCursorPosX() + 20.0f; // do not overlap breadcrumb
|
||||
if (target_x < min_x) target_x = min_x;
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(target_x);
|
||||
|
||||
ImGui::TextDisabled("View:");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(combo_w);
|
||||
if (ImGui::BeginCombo("##viz_mode", view_mode_label(st.display))) {
|
||||
for (int i = 0; i < n_modes; ++i) {
|
||||
bool sel = (modes[i] == st.display);
|
||||
if (ImGui::Selectable(view_mode_label(modes[i]), sel)) {
|
||||
ViewMode nm = modes[i];
|
||||
if (nm != st.display) {
|
||||
st.display = nm;
|
||||
if (view_mode_needs_aggregation(nm)) {
|
||||
auto_promote_aggregated(st);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sel) ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("Fit##viz_fit")) {
|
||||
st.viz_config.fit_request = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
bool locked = st.viz_config.locked;
|
||||
if (locked) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(180, 60, 60, 230));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(200, 80, 80, 240));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32(150, 40, 40, 240));
|
||||
}
|
||||
if (ImGui::SmallButton(locked ? "Locked##viz_lock" : "Lock##viz_lock")) {
|
||||
st.viz_config.locked = !st.viz_config.locked;
|
||||
}
|
||||
if (locked) ImGui::PopStyleColor(3);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("Config##viz_cfg")) {
|
||||
ImGui::OpenPopup("##viz_cfg_popup");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("Ask AI##ask_open")) {
|
||||
auto& U2 = ui();
|
||||
U2.ask_ai.open = true;
|
||||
U2.ask_ai.busy = false;
|
||||
U2.ask_ai.error.clear();
|
||||
U2.ask_ai.status.clear();
|
||||
U2.ask_ai.response_code.clear();
|
||||
U2.ask_ai.response_raw.clear();
|
||||
U2.ask_ai.current_tql = tql::emit(st,
|
||||
std::vector<std::string>(),
|
||||
std::vector<ColumnType>());
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("+ Viz##viz_add")) {
|
||||
VizPanel p;
|
||||
p.display = ViewMode::Bar;
|
||||
if (view_mode_needs_aggregation(p.display)) {
|
||||
auto_promote_aggregated(st);
|
||||
}
|
||||
st.extra_panels.push_back(p);
|
||||
}
|
||||
draw_viz_config_popup(st);
|
||||
ImGui::NewLine();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// maybe_recompute_stats
|
||||
// ---------------------------------------------------------------------------
|
||||
void maybe_recompute_stats(State& st,
|
||||
const char* const* cells,
|
||||
int row_count, int orig_cols, int eff_cols,
|
||||
const std::vector<Filter>& active_filters,
|
||||
const std::vector<int>& visible_rows,
|
||||
const std::vector<int>& src_for_eff)
|
||||
{
|
||||
if (!st.stats_mode) return;
|
||||
size_t fh = filters_hash(active_filters);
|
||||
bool ds_changed = (cells != st.stats_last_cells || row_count != st.stats_last_rows ||
|
||||
eff_cols != st.stats_last_eff_cols ||
|
||||
(int)st.stats_cache.size() != eff_cols);
|
||||
bool fl_changed = (fh != st.stats_last_filter_h ||
|
||||
(int)visible_rows.size() != st.stats_last_visible);
|
||||
if (!ds_changed && !fl_changed) return;
|
||||
st.stats_cache.resize(eff_cols);
|
||||
const int* idx = visible_rows.empty() ? nullptr : visible_rows.data();
|
||||
int n = (int)visible_rows.size();
|
||||
for (int c = 0; c < eff_cols; ++c) {
|
||||
int src = src_for_eff[c];
|
||||
st.stats_cache[c] = compute_column_stats(cells, row_count, orig_cols, src,
|
||||
100000, idx, n);
|
||||
}
|
||||
st.stats_last_cells = cells;
|
||||
st.stats_last_rows = row_count;
|
||||
st.stats_last_eff_cols = eff_cols;
|
||||
st.stats_last_filter_h = fh;
|
||||
st.stats_last_visible = (int)visible_rows.size();
|
||||
}
|
||||
|
||||
} // namespace data_table
|
||||
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
// data_table_viz_panels — paneles de visualizacion lateral de la tabla TQL.
|
||||
// Sub-funcion extraida de modules/data_table/data_table.cpp (issue 0107c).
|
||||
//
|
||||
// Responsabilidad:
|
||||
// - draw_table_toggle: boton de toggle entre modo tabla y ultimo modo viz.
|
||||
// - draw_extra_panel: render de un panel de viz adicional (VizPanel) con su
|
||||
// toolbar mini (titulo, pin/close, selector de chart).
|
||||
// - draw_viz_config_popup: popup de configuracion de visualizacion (columnas
|
||||
// x/y, modo, config del grafico activo).
|
||||
// - draw_viz_selector: selector de modo de viz (iconos/grid de ViewMode).
|
||||
// - maybe_recompute_stats: recalcula las estadisticas de columna si cambiaron
|
||||
// los datos visibles (para el stats overlay en headers y config popup).
|
||||
//
|
||||
// Rangos del fuente original:
|
||||
// - draw_table_toggle : lineas 1527-1552
|
||||
// - draw_extra_panel : lineas 1553-1638
|
||||
// - draw_viz_config_popup : lineas 1640-1815
|
||||
// - draw_viz_selector : lineas 1816-1892
|
||||
// - maybe_recompute_stats : lineas 2474-2502
|
||||
//
|
||||
// Dependencias: data_table_types.h, viz_render.h (viz::render), imgui.h.
|
||||
|
||||
#include "core/data_table_types.h"
|
||||
#include "imgui.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace data_table {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// draw_table_toggle
|
||||
// Boton que cambia display entre ViewMode::Table y el ultimo modo viz no-tabla.
|
||||
// `id_suffix` diferencia multiples toggles en la misma ventana.
|
||||
// `st_ptr` si no null se usa para actualizar `st.display` directamente;
|
||||
// si null usa display/last_non_table pasados por ref.
|
||||
// ---------------------------------------------------------------------------
|
||||
void draw_table_toggle(ViewMode& display, ViewMode& last_non_table,
|
||||
const char* id_suffix,
|
||||
State* st_ptr = nullptr);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// draw_extra_panel
|
||||
// Dibuja un VizPanel adicional (child window con toolbar mini + chart).
|
||||
// Retorna true si el usuario cerro el panel (el caller debe borrar el entry).
|
||||
// ---------------------------------------------------------------------------
|
||||
bool draw_extra_panel(State& st, VizPanel& p, int idx,
|
||||
const StageOutput& so,
|
||||
const std::vector<ColumnSpec>* col_specs);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// draw_viz_config_popup
|
||||
// Popup de configuracion de visualizacion: columnas x/y, modo de chart,
|
||||
// colores, histogramas bins, pie radius, etc. Mutates st.viz_config y
|
||||
// st.extra_panels.
|
||||
// ---------------------------------------------------------------------------
|
||||
void draw_viz_config_popup(State& st);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// draw_viz_selector
|
||||
// Selector de modo de visualizacion: grid de iconos con ViewMode disponibles.
|
||||
// Se abre via boton "Ask AI" o desde draw_viz_config_popup.
|
||||
// ---------------------------------------------------------------------------
|
||||
void draw_viz_selector(State& st);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// maybe_recompute_stats
|
||||
// Recalcula st.stats_cache si el snapshot de datos ha cambiado
|
||||
// (se detecta por hash de visible_rows + filtros). Solo recalcula si
|
||||
// st.stats_mode == true.
|
||||
// ---------------------------------------------------------------------------
|
||||
void maybe_recompute_stats(State& st,
|
||||
const char* const* cells,
|
||||
int row_count, int orig_cols, int eff_cols,
|
||||
const std::vector<Filter>& active_filters,
|
||||
const std::vector<int>& visible_rows,
|
||||
const std::vector<int>& src_for_eff);
|
||||
|
||||
} // namespace data_table
|
||||
@@ -0,0 +1,97 @@
|
||||
---
|
||||
name: data_table_viz_panels
|
||||
kind: function
|
||||
lang: cpp
|
||||
domain: viz
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "bool data_table::draw_extra_panel(State& st, VizPanel& p, int idx, const StageOutput& so, const std::vector<ColumnSpec>* col_specs)"
|
||||
description: "Paneles de visualizacion lateral de la tabla TQL: toggle tabla/chart, paneles extra independientes (histogramas, linea, scatter, value-counts), popup de configuracion de viz (cols x/y, modo, color, bins), selector de ViewMode via grid de iconos, y recalculo lazy de estadisticas de columna. Sub-funcion extraida de modules/data_table/data_table.cpp (issue 0107c)."
|
||||
tags: [viz, table, imgui, ui, charts, visualization, tql, cpp-tables, implot]
|
||||
uses_functions:
|
||||
- viz_render_cpp_viz
|
||||
- compute_column_stats_cpp_core
|
||||
- data_table_cpp_viz
|
||||
uses_types:
|
||||
- data_table_types_cpp_core
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [imgui, implot]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "cpp/functions/viz/data_table_viz_panels.cpp"
|
||||
framework: imgui
|
||||
params:
|
||||
- name: st
|
||||
desc: "State mutable: st.display (modo actual), st.viz_config (config de viz), st.extra_panels (paneles adicionales), st.stats_cache/stats_mode (estadisticas)."
|
||||
- name: p
|
||||
desc: "VizPanel a renderizar: contiene su propio ViewConfig, titulo, display mode. Mutado si el usuario cambia la config del panel."
|
||||
- name: idx
|
||||
desc: "Indice del panel en st.extra_panels. Usado como ID ImGui para unicidad."
|
||||
- name: so
|
||||
desc: "StageOutput del stage activo: headers, types, cells row-major. Input de viz::render."
|
||||
- name: col_specs
|
||||
desc: "ColumnSpecs del TableInput principal si no vacias; null si no hay specs declarativas."
|
||||
output: "Bool para draw_extra_panel: true si el usuario cerro el panel (caller debe hacer erase de st.extra_panels[idx]). Void para el resto."
|
||||
---
|
||||
|
||||
## Documentacion
|
||||
|
||||
Sub-funcion que encapsula los paneles de visualizacion lateral de la tabla TQL. Permite al usuario ver graficos (bar, line, scatter, pie, etc.) en paralelo a la tabla de datos, con configuracion interactiva.
|
||||
|
||||
### Funciones publicas
|
||||
|
||||
| Funcion | Que hace |
|
||||
|---|---|
|
||||
| `draw_table_toggle(display, last_non_table, id, st_ptr)` | Boton toggle "Table / Chart". Guarda el ultimo modo viz en `last_non_table`. |
|
||||
| `draw_extra_panel(st, p, idx, so, col_specs)` | Child window con header/footer mini: titulo, pin, close. Llama `viz::render`. Retorna true si cerrado. |
|
||||
| `draw_viz_config_popup(st)` | Popup tabbado: config de viz principal + config de cada panel extra. Permite cambiar columnas x/y, modo, color, bins, anadir panel. |
|
||||
| `draw_viz_selector(st)` | Grid de iconos ViewMode; seleccion cambia `st.display`. Incluye boton "Ask AI" -> abre `data_table_ai_panel`. |
|
||||
| `maybe_recompute_stats(st, cells, ...)` | Recalcula stats lazy si `visible_rows` cambio. Solo cuando `st.stats_mode == true`. |
|
||||
|
||||
### draw_extra_panel: ciclo de vida de un VizPanel
|
||||
|
||||
1. El usuario abre un panel desde `draw_viz_selector` (boton "+").
|
||||
2. Se agrega un `VizPanel` a `st.extra_panels` con config por defecto.
|
||||
3. Cada frame: `draw_extra_panel(st, p, i, so, specs)`.
|
||||
4. Si retorna `true`: `st.extra_panels.erase(begin + i)`.
|
||||
|
||||
### maybe_recompute_stats: politica de cache
|
||||
|
||||
Compara un hash de `visible_rows` (FNV-1a sobre el vector de indices) contra `st.stats_last_visible_hash`. Si difiere, llama `compute_column_stats_cpp_core` por cada columna efectiva y actualiza `st.stats_cache`. Costo O(cols * visible_rows) — llamar solo cuando stats_mode este activo.
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```cpp
|
||||
// Llamado desde render() tras el grid (path stage 0):
|
||||
if (st.display != data_table::ViewMode::Table) {
|
||||
data_table::draw_table_toggle(st.display, U.last_non_table_main, "main", &st);
|
||||
}
|
||||
|
||||
// Paneles extra cuando NO estamos en modo tabla:
|
||||
if (st.display != data_table::ViewMode::Table && !st.extra_panels.empty()) {
|
||||
int close_idx = -1;
|
||||
for (int i = 0; i < (int)st.extra_panels.size(); ++i) {
|
||||
if (data_table::draw_extra_panel(st, st.extra_panels[i], i, so, &main_t.column_specs))
|
||||
close_idx = i;
|
||||
}
|
||||
if (close_idx >= 0) st.extra_panels.erase(st.extra_panels.begin() + close_idx);
|
||||
}
|
||||
|
||||
// Config popup (activado por boton en draw_viz_selector):
|
||||
data_table::draw_viz_config_popup(st);
|
||||
data_table::draw_viz_selector(st);
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
Llamar desde el entrypoint thin `data_table::render()` despues del grid y antes de los modales. No llamar directamente desde apps — la API publica es siempre `data_table::render()`.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- `draw_extra_panel` abre un `ImGui::BeginChild` interno — no anidar dentro de otro child que ya recorte el area de pintado.
|
||||
- `draw_viz_selector` incluye la apertura del modal Ask AI (`st.ask_open = true`). El modal real lo dibuja `data_table_ai_panel_cpp_viz`. El order de calls importa: selector primero, luego el modal.
|
||||
- `maybe_recompute_stats` es potencialmente caro (O(visible_rows * cols)). Solo activar con `st.stats_mode = true` via boton "Show stats"; el boton vive en el area de chrome del render principal.
|
||||
- ImPlot context debe estar activo cuando se llama `viz::render` desde `draw_extra_panel`. Garantizado si el caller usa `fn::run_app` con ImPlot inicializado en `cfg`.
|
||||
@@ -5,32 +5,24 @@
|
||||
#include <imgui.h>
|
||||
#include <cstdio>
|
||||
|
||||
void kpi_card(const char* label, float value, float delta_percent,
|
||||
const float* history, int history_count,
|
||||
const char* format,
|
||||
const char* icon) {
|
||||
static void kpi_card_impl(const char* label, float value, float delta_percent,
|
||||
const float* history, int history_count,
|
||||
const char* format, const char* icon,
|
||||
bool fixed_y, float y_min, float y_max) {
|
||||
using namespace fn_tokens;
|
||||
|
||||
// Card container — surface bg, border, rounded, padding.
|
||||
// Mirrors Mantine <Paper withBorder shadow="xs" radius="md" p="md" /> usado
|
||||
// en @fn_library/kpi_card.tsx, adaptado a ImGui dark theme via tokens.
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, colors::surface);
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, colors::border);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, radius::md);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 1.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(spacing::sm, spacing::sm));
|
||||
|
||||
// Unique child id por label para que multiples cards en la misma ventana
|
||||
// no colisionen.
|
||||
char child_id[96];
|
||||
std::snprintf(child_id, sizeof(child_id), "##kpi_%s", label);
|
||||
|
||||
float width = ImGui::GetContentRegionAvail().x;
|
||||
if (width < 1.0f) width = 1.0f;
|
||||
|
||||
// Altura fija (no AutoResizeY) para que:
|
||||
// (a) todas las cards de un grid queden alineadas visualmente,
|
||||
// (b) no haya recalculo de layout por card en cada resize.
|
||||
constexpr float card_height = 86.0f;
|
||||
ImGui::BeginChild(child_id, ImVec2(width, card_height),
|
||||
ImGuiChildFlags_Borders,
|
||||
@@ -39,8 +31,6 @@ void kpi_card(const char* label, float value, float delta_percent,
|
||||
const bool has_history = history != nullptr && history_count > 0;
|
||||
const bool has_delta = delta_percent != 0.0f;
|
||||
|
||||
// Layout de dos columnas: izquierda info (label, value, delta), derecha sparkline.
|
||||
// El sparkline se sitia verticalmente centrado a la derecha de la card.
|
||||
constexpr float spark_w = 100.0f;
|
||||
constexpr float spark_h = 36.0f;
|
||||
|
||||
@@ -50,7 +40,6 @@ void kpi_card(const char* label, float value, float delta_percent,
|
||||
ImGui::BeginGroup();
|
||||
ImGui::PushItemWidth(info_w);
|
||||
|
||||
// Top row: optional icon + label, ambos en text_muted.
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, colors::text_muted);
|
||||
if (icon && *icon) {
|
||||
ImGui::TextUnformatted(icon);
|
||||
@@ -59,14 +48,12 @@ void kpi_card(const char* label, float value, float delta_percent,
|
||||
ImGui::TextUnformatted(label);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
// Value — escala compacta 1.4x, proporcional a una card de 86px.
|
||||
ImGui::SetWindowFontScale(1.4f);
|
||||
char value_buf[64];
|
||||
std::snprintf(value_buf, sizeof(value_buf), format, value);
|
||||
ImGui::TextUnformatted(value_buf);
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
|
||||
// Delta / trend — SIEMPRE se reserva la linea aunque no haya tendencia.
|
||||
if (has_delta) {
|
||||
const bool positive = delta_percent >= 0.0f;
|
||||
const ImVec4 delta_color = positive ? colors::success : colors::error;
|
||||
@@ -80,7 +67,6 @@ void kpi_card(const char* label, float value, float delta_percent,
|
||||
ImGui::TextUnformatted(delta_buf);
|
||||
ImGui::PopStyleColor();
|
||||
} else {
|
||||
// Placeholder para preservar altura.
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, colors::text_dim);
|
||||
ImGui::TextUnformatted(TI_MINUS);
|
||||
ImGui::PopStyleColor();
|
||||
@@ -89,24 +75,23 @@ void kpi_card(const char* label, float value, float delta_percent,
|
||||
ImGui::PopItemWidth();
|
||||
ImGui::EndGroup();
|
||||
|
||||
// Sparkline a la derecha, centrado verticalmente respecto a la card.
|
||||
if (has_history) {
|
||||
const ImVec4 spark_color = has_delta
|
||||
? (delta_percent >= 0.0f ? colors::success : colors::error)
|
||||
: colors::primary;
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
// Centrar verticalmente: la card mide card_height, el sparkline spark_h.
|
||||
// Restamos el padding interno (spacing::sm) ya consumido al inicio.
|
||||
const float card_inner_h = card_height - 2.0f * spacing::sm;
|
||||
float y_offset = (card_inner_h - spark_h) * 0.5f;
|
||||
if (y_offset < 0.0f) y_offset = 0.0f;
|
||||
// Anclamos el sparkline al borde derecho.
|
||||
const float spark_x = inner_w - spark_w;
|
||||
ImGui::SetCursorPos(ImVec2(spacing::sm + spark_x,
|
||||
spacing::sm + y_offset));
|
||||
sparkline(label, history, history_count, spark_color, spark_w, spark_h);
|
||||
ImGui::SetCursorPos(ImVec2(spacing::sm + spark_x, spacing::sm + y_offset));
|
||||
if (fixed_y) {
|
||||
sparkline(label, history, history_count, spark_color,
|
||||
y_min, y_max, spark_w, spark_h);
|
||||
} else {
|
||||
sparkline(label, history, history_count, spark_color, spark_w, spark_h);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
@@ -114,3 +99,20 @@ void kpi_card(const char* label, float value, float delta_percent,
|
||||
ImGui::PopStyleVar(3);
|
||||
ImGui::PopStyleColor(2);
|
||||
}
|
||||
|
||||
void kpi_card(const char* label, float value, float delta_percent,
|
||||
const float* history, int history_count,
|
||||
const char* format,
|
||||
const char* icon) {
|
||||
kpi_card_impl(label, value, delta_percent, history, history_count,
|
||||
format, icon, /*fixed_y=*/false, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
void kpi_card(const char* label, float value, float delta_percent,
|
||||
const float* history, int history_count,
|
||||
float y_min, float y_max,
|
||||
const char* format,
|
||||
const char* icon) {
|
||||
kpi_card_impl(label, value, delta_percent, history, history_count,
|
||||
format, icon, /*fixed_y=*/true, y_min, y_max);
|
||||
}
|
||||
|
||||
@@ -10,9 +10,18 @@
|
||||
// - Optional icon (Tabler glyph) + label (small, muted) on top row
|
||||
// - Value (large font)
|
||||
// - Delta badge (green TI_TRENDING_UP / red TI_TRENDING_DOWN)
|
||||
// - Sparkline of history
|
||||
// - Sparkline of history (auto Y by default; fixed Y in overload v1.4)
|
||||
|
||||
void kpi_card(const char* label, float value, float delta_percent,
|
||||
const float* history = nullptr, int history_count = 0,
|
||||
const char* format = "%.1f",
|
||||
const char* icon = nullptr);
|
||||
|
||||
// Fixed Y-scale variant (v1.4): el sparkline interno usa [y_min, y_max]
|
||||
// absolutos. Util para metricas con dominio conocido (CPU%/RAM% -> 0,100)
|
||||
// donde la comparacion visual entre cards exige misma escala.
|
||||
void kpi_card(const char* label, float value, float delta_percent,
|
||||
const float* history, int history_count,
|
||||
float y_min, float y_max,
|
||||
const char* format = "%.1f",
|
||||
const char* icon = nullptr);
|
||||
|
||||
@@ -3,11 +3,11 @@ name: kpi_card
|
||||
kind: component
|
||||
lang: cpp
|
||||
domain: viz
|
||||
version: "1.3.0"
|
||||
version: "1.4.0"
|
||||
purity: pure
|
||||
signature: "void kpi_card(const char* label, float value, float delta_percent, const float* history = nullptr, int history_count = 0, const char* format = \"%.1f\", const char* icon = nullptr)"
|
||||
description: "Card de KPI con icono opcional + label, valor grande, delta porcentual con TI_TRENDING_UP/DOWN y sparkline historico. Contenedor con surface bg, borde y radius via tokens (Mantine Paper equivalente)."
|
||||
tags: [imgui, kpi, card, dashboard, metrics, sparkline, tokens, tabler]
|
||||
tags: [imgui, kpi, card, dashboard, metrics, sparkline, tokens, tabler, cpp-dashboard-viz]
|
||||
uses_functions: ["sparkline_cpp_viz", "tokens_cpp_core"]
|
||||
uses_types: []
|
||||
returns: []
|
||||
@@ -79,3 +79,7 @@ ImGui::Columns(1);
|
||||
- La linea de trend siempre se reserva (delta, sparkline o em dash placeholder en `text_dim`) para que un grid de KPIs quede alineado vertical.
|
||||
- ~~Los caracteres UTF-8 del triangulo (`▲` U+25B2 y `▼` U+25BC) y del em dash (`—` U+2014) requieren que la fuente ImGui tenga el rango de simbolos geometricos / puntuacion general cargado.~~ → Obsoleto en v1.3: ahora se usan glyphs Tabler que estan en el atlas mergeado por `icon_font_cpp_core`.
|
||||
- Colores: delta usa `fn_tokens::colors::{success, error}`, placeholder `TI_MINUS` usa `text_dim`, label + icono usan `text_muted`.
|
||||
|
||||
## Capability growth log
|
||||
|
||||
- v1.4.0 (2026-05-18) — Overload con `y_min, y_max` que se propaga al sparkline interno. Cards de un grid con dominio fijo (CPU%/RAM% -> 0,100) ahora son visualmente comparables — outliers no aplastan la escala. Default sigue siendo auto-scale.
|
||||
|
||||
@@ -5,28 +5,48 @@
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
void draw_line(const char* title, const T* xs, const T* ys, int count, float height) {
|
||||
void draw_line(const char* title, const T* xs, const T* ys, int count,
|
||||
float height, bool auto_x, double x_lo_in, double x_hi_in,
|
||||
bool auto_y, double y_lo_in, double y_hi_in) {
|
||||
if (count <= 0) return;
|
||||
|
||||
T x_min = xs[0], x_max = xs[0];
|
||||
T y_min = ys[0], y_max = ys[0];
|
||||
for (int i = 1; i < count; i++) {
|
||||
if (xs[i] < x_min) x_min = xs[i];
|
||||
if (xs[i] > x_max) x_max = xs[i];
|
||||
if (ys[i] < y_min) y_min = ys[i];
|
||||
if (ys[i] > y_max) y_max = ys[i];
|
||||
double x_lo, x_hi;
|
||||
if (auto_x) {
|
||||
T x_min = xs[0], x_max = xs[0];
|
||||
for (int i = 1; i < count; i++) {
|
||||
if (xs[i] < x_min) x_min = xs[i];
|
||||
if (xs[i] > x_max) x_max = xs[i];
|
||||
}
|
||||
x_lo = static_cast<double>(x_min);
|
||||
x_hi = static_cast<double>(x_max);
|
||||
} else {
|
||||
x_lo = x_lo_in;
|
||||
x_hi = x_hi_in;
|
||||
}
|
||||
if (x_hi - x_lo < 1e-9) x_hi = x_lo + 1.0;
|
||||
|
||||
double y_lo, y_hi;
|
||||
if (auto_y) {
|
||||
T y_min = ys[0], y_max = ys[0];
|
||||
for (int i = 1; i < count; i++) {
|
||||
if (ys[i] < y_min) y_min = ys[i];
|
||||
if (ys[i] > y_max) y_max = ys[i];
|
||||
}
|
||||
double dy = static_cast<double>(y_max) - static_cast<double>(y_min);
|
||||
if (dy < 1e-9) dy = 1.0;
|
||||
y_lo = static_cast<double>(y_min) - dy * 0.05;
|
||||
y_hi = static_cast<double>(y_max) + dy * 0.05;
|
||||
} else {
|
||||
y_lo = y_lo_in;
|
||||
y_hi = y_hi_in;
|
||||
if (y_hi - y_lo < 1e-9) y_hi = y_lo + 1.0;
|
||||
}
|
||||
double dy = static_cast<double>(y_max) - static_cast<double>(y_min);
|
||||
if (dy < 1e-9) dy = 1.0;
|
||||
double y_lo = static_cast<double>(y_min) - dy * 0.05;
|
||||
double y_hi = static_cast<double>(y_max) + dy * 0.05;
|
||||
|
||||
const ImVec2 plot_size(-1.0f, height > 0.0f ? height : 200.0f);
|
||||
|
||||
if (ImPlot::BeginPlot(title, plot_size, plot_static::kPlotFlags)) {
|
||||
ImPlot::SetupAxes(nullptr, nullptr, plot_static::kAxisFlags, plot_static::kAxisFlags);
|
||||
ImPlot::SetupAxisLimits(ImAxis_X1, static_cast<double>(x_min),
|
||||
static_cast<double>(x_max), ImPlotCond_Always);
|
||||
ImPlot::SetupAxisLimits(ImAxis_X1, x_lo, x_hi, ImPlotCond_Always);
|
||||
ImPlot::SetupAxisLimits(ImAxis_Y1, y_lo, y_hi, ImPlotCond_Always);
|
||||
ImPlot::PlotLine("##data", xs, ys, count);
|
||||
ImPlot::EndPlot();
|
||||
@@ -36,9 +56,41 @@ void draw_line(const char* title, const T* xs, const T* ys, int count, float hei
|
||||
} // namespace
|
||||
|
||||
void line_plot(const char* title, const float* xs, const float* ys, int count, float height) {
|
||||
draw_line<float>(title, xs, ys, count, height);
|
||||
draw_line<float>(title, xs, ys, count, height,
|
||||
/*auto_x=*/true, 0.0, 0.0,
|
||||
/*auto_y=*/true, 0.0, 0.0);
|
||||
}
|
||||
|
||||
void line_plot(const char* title, const double* xs, const double* ys, int count, float height) {
|
||||
draw_line<double>(title, xs, ys, count, height);
|
||||
draw_line<double>(title, xs, ys, count, height,
|
||||
/*auto_x=*/true, 0.0, 0.0,
|
||||
/*auto_y=*/true, 0.0, 0.0);
|
||||
}
|
||||
|
||||
void line_plot(const char* title, const float* xs, const float* ys, int count,
|
||||
float y_min, float y_max, float height) {
|
||||
draw_line<float>(title, xs, ys, count, height,
|
||||
/*auto_x=*/true, 0.0, 0.0,
|
||||
/*auto_y=*/false, (double)y_min, (double)y_max);
|
||||
}
|
||||
|
||||
void line_plot(const char* title, const double* xs, const double* ys, int count,
|
||||
double y_min, double y_max, float height) {
|
||||
draw_line<double>(title, xs, ys, count, height,
|
||||
/*auto_x=*/true, 0.0, 0.0,
|
||||
/*auto_y=*/false, y_min, y_max);
|
||||
}
|
||||
|
||||
void line_plot(const char* title, const float* xs, const float* ys, int count,
|
||||
float x_min, float x_max, float y_min, float y_max, float height) {
|
||||
draw_line<float>(title, xs, ys, count, height,
|
||||
/*auto_x=*/false, (double)x_min, (double)x_max,
|
||||
/*auto_y=*/false, (double)y_min, (double)y_max);
|
||||
}
|
||||
|
||||
void line_plot(const char* title, const double* xs, const double* ys, int count,
|
||||
double x_min, double x_max, double y_min, double y_max, float height) {
|
||||
draw_line<double>(title, xs, ys, count, height,
|
||||
/*auto_x=*/false, x_min, x_max,
|
||||
/*auto_y=*/false, y_min, y_max);
|
||||
}
|
||||
|
||||
@@ -8,3 +8,20 @@ void line_plot(const char* title, const float* xs, const float* ys, int count,
|
||||
float height = 200.0f);
|
||||
void line_plot(const char* title, const double* xs, const double* ys, int count,
|
||||
float height = 200.0f);
|
||||
|
||||
// Fixed Y-range overloads — pinea Y a [y_min, y_max] para series con dominio
|
||||
// conocido (ej. 0-100 en CPU%/RAM%). v1.2.
|
||||
void line_plot(const char* title, const float* xs, const float* ys, int count,
|
||||
float y_min, float y_max, float height = 200.0f);
|
||||
void line_plot(const char* title, const double* xs, const double* ys, int count,
|
||||
double y_min, double y_max, float height = 200.0f);
|
||||
|
||||
// Fixed XY-range overloads — pinea BOTH X y Y. Util para ventanas temporales
|
||||
// fijas (ej. ultimos 5 min de CPU%): xs en [0, WINDOW] y ys en [0, 100]. El
|
||||
// plot NO se aplasta al variar count. v1.3.
|
||||
void line_plot(const char* title, const float* xs, const float* ys, int count,
|
||||
float x_min, float x_max, float y_min, float y_max,
|
||||
float height = 200.0f);
|
||||
void line_plot(const char* title, const double* xs, const double* ys, int count,
|
||||
double x_min, double x_max, double y_min, double y_max,
|
||||
float height = 200.0f);
|
||||
|
||||
@@ -3,11 +3,11 @@ name: line_plot
|
||||
kind: component
|
||||
lang: cpp
|
||||
domain: viz
|
||||
version: "1.1.0"
|
||||
version: "1.3.0"
|
||||
purity: pure
|
||||
signature: "void line_plot(const char* title, const float* xs, const float* ys, int count, float height = 200.0f)"
|
||||
description: "Line plot 2D con ImPlot, ejes pineados y altura explicita para no vibrar al redimensionar"
|
||||
tags: [implot, chart, visualization, gpu, line, locked-axes]
|
||||
tags: [implot, chart, visualization, gpu, line, locked-axes, cpp-dashboard-viz]
|
||||
uses_functions: ["plot_static_cpp_viz"]
|
||||
uses_types: []
|
||||
returns: []
|
||||
@@ -45,3 +45,8 @@ Wrapper atomico sobre `ImPlot::PlotLine` configurado para visualizacion estatica
|
||||
- **Sin inputs, sin auto-fit** — ver `viz/plot_static.h`.
|
||||
|
||||
Soporta `float` y `double`.
|
||||
|
||||
## Capability growth log
|
||||
|
||||
- v1.2.0 (2026-05-18) — Overloads `(..., y_min, y_max, height)` para series con dominio conocido (CPU%/RAM% -> 0,100). Mantiene los overloads de auto-fit historico intactos. Tipos float y double.
|
||||
- v1.3.0 (2026-05-18) — Overloads `(..., x_min, x_max, y_min, y_max, height)` que pinea AMBOS ejes. Util para ventanas temporales fijas (ej. ultimos 5 min): xs en [0, 300], el grafico NO se aplasta al variar count durante warmup.
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#include "viz/sparkline.h"
|
||||
#include "imgui.h"
|
||||
|
||||
void sparkline(const char* id, const float* values, int count, ImVec4 color,
|
||||
float width, float height) {
|
||||
// Implementacion comun. Si auto_y=true, calcula min/max de values; si no,
|
||||
// usa [y_min, y_max] explicitos.
|
||||
static void sparkline_impl(const char* id, const float* values, int count,
|
||||
ImVec4 color, float width, float height,
|
||||
bool auto_y, float y_min, float y_max) {
|
||||
if (count <= 0) return;
|
||||
|
||||
ImGui::PushID(id);
|
||||
@@ -10,67 +13,89 @@ void sparkline(const char* id, const float* values, int count, ImVec4 color,
|
||||
ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
|
||||
// Reserve inline space
|
||||
ImGui::Dummy(ImVec2(width, height));
|
||||
|
||||
// Find min/max for Y auto-scale
|
||||
float min_val = values[0];
|
||||
float max_val = values[0];
|
||||
for (int i = 1; i < count; i++) {
|
||||
if (values[i] < min_val) min_val = values[i];
|
||||
if (values[i] > max_val) max_val = values[i];
|
||||
if (auto_y) {
|
||||
y_min = values[0];
|
||||
y_max = values[0];
|
||||
for (int i = 1; i < count; i++) {
|
||||
if (values[i] < y_min) y_min = values[i];
|
||||
if (values[i] > y_max) y_max = values[i];
|
||||
}
|
||||
}
|
||||
float range = max_val - min_val;
|
||||
if (range < 1e-6f) range = 1.0f; // avoid division by zero for flat lines
|
||||
float range = y_max - y_min;
|
||||
if (range < 1e-6f) range = 1.0f;
|
||||
|
||||
auto y_at = [&](float v) {
|
||||
// Clamp visualmente al rango — para fixed Y sirve para que outliers
|
||||
// no salgan del card; para auto Y es no-op.
|
||||
if (v < y_min) v = y_min;
|
||||
if (v > y_max) v = y_max;
|
||||
return pos.y + height - ((v - y_min) / range) * height;
|
||||
};
|
||||
|
||||
// Fade gradient v1.2: alpha sube de older->newer.
|
||||
// El segmento [i, i+1] se pinta con alpha proporcional al endpoint derecho
|
||||
// (mas cercano a "ahora"). Hace efecto de rastro / trail.
|
||||
const int r_ = (int)(color.x * 255);
|
||||
const int g_ = (int)(color.y * 255);
|
||||
const int b_ = (int)(color.z * 255);
|
||||
|
||||
auto seg_t = [&](int i) {
|
||||
return (count > 1) ? (float)(i + 1) / (float)(count - 1) : 1.0f;
|
||||
};
|
||||
|
||||
// Fill area under curve (low alpha)
|
||||
if (count >= 2) {
|
||||
ImU32 fill_color = IM_COL32(
|
||||
(int)(color.x * 255),
|
||||
(int)(color.y * 255),
|
||||
(int)(color.z * 255),
|
||||
40);
|
||||
|
||||
// Build fill polygon: baseline bottom-left -> points -> baseline bottom-right
|
||||
// We use AddConvexPolyFilled workaround: draw as a series of triangles from baseline
|
||||
float x0 = pos.x;
|
||||
float y_base = pos.y + height;
|
||||
|
||||
for (int i = 0; i + 1 < count; i++) {
|
||||
float t = seg_t(i);
|
||||
int fill_a = (int)((0.10f + 0.70f * t) * 40.0f); // 4..28 alpha
|
||||
ImU32 fill_color = IM_COL32(r_, g_, b_, fill_a);
|
||||
float xa = x0 + ((float)i / (count - 1)) * width;
|
||||
float xb = x0 + ((float)(i + 1) / (count - 1)) * width;
|
||||
float ya = pos.y + height - ((values[i] - min_val) / range) * height;
|
||||
float yb = pos.y + height - ((values[i + 1] - min_val) / range) * height;
|
||||
|
||||
float ya = y_at(values[i]);
|
||||
float yb = y_at(values[i + 1]);
|
||||
draw_list->AddQuadFilled(
|
||||
ImVec2(xa, y_base),
|
||||
ImVec2(xa, ya),
|
||||
ImVec2(xb, yb),
|
||||
ImVec2(xb, y_base),
|
||||
ImVec2(xa, y_base), ImVec2(xa, ya),
|
||||
ImVec2(xb, yb), ImVec2(xb, y_base),
|
||||
fill_color);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw polyline
|
||||
ImU32 line_color = IM_COL32(
|
||||
(int)(color.x * 255),
|
||||
(int)(color.y * 255),
|
||||
(int)(color.z * 255),
|
||||
(int)(color.w * 255));
|
||||
|
||||
for (int i = 0; i + 1 < count; i++) {
|
||||
float t = seg_t(i);
|
||||
float a = 0.20f + 0.80f * t;
|
||||
ImU32 line_color = IM_COL32(r_, g_, b_, (int)(color.w * a * 255));
|
||||
float xa = pos.x + ((float)i / (count - 1)) * width;
|
||||
float xb = pos.x + ((float)(i + 1) / (count - 1)) * width;
|
||||
float ya = pos.y + height - ((values[i] - min_val) / range) * height;
|
||||
float yb = pos.y + height - ((values[i + 1] - min_val) / range) * height;
|
||||
float ya = y_at(values[i]);
|
||||
float yb = y_at(values[i + 1]);
|
||||
draw_list->AddLine(ImVec2(xa, ya), ImVec2(xb, yb), line_color, 1.5f);
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
void sparkline(const char* id, const float* values, int count, ImVec4 color,
|
||||
float width, float height) {
|
||||
sparkline_impl(id, values, count, color, width, height,
|
||||
/*auto_y=*/true, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
void sparkline(const char* id, const float* values, int count,
|
||||
float width, float height) {
|
||||
// Default color: soft green
|
||||
sparkline(id, values, count, ImVec4(0.35f, 0.85f, 0.45f, 1.0f), width, height);
|
||||
}
|
||||
|
||||
void sparkline(const char* id, const float* values, int count, ImVec4 color,
|
||||
float y_min, float y_max, float width, float height) {
|
||||
sparkline_impl(id, values, count, color, width, height,
|
||||
/*auto_y=*/false, y_min, y_max);
|
||||
}
|
||||
|
||||
void sparkline(const char* id, const float* values, int count,
|
||||
float y_min, float y_max, float width, float height) {
|
||||
sparkline(id, values, count, ImVec4(0.35f, 0.85f, 0.45f, 1.0f),
|
||||
y_min, y_max, width, height);
|
||||
}
|
||||
|
||||
@@ -3,10 +3,23 @@
|
||||
#include "imgui.h"
|
||||
|
||||
// Renders a mini inline line chart for use in tables, headers and KPI cards.
|
||||
// Auto-scales Y to the min/max of values.
|
||||
// Uses PushID/PopID with id for uniqueness inside tables.
|
||||
//
|
||||
// Auto Y-scale variants:
|
||||
void sparkline(const char* id, const float* values, int count,
|
||||
float width = 100.0f, float height = 20.0f);
|
||||
|
||||
void sparkline(const char* id, const float* values, int count, ImVec4 color,
|
||||
float width = 100.0f, float height = 20.0f);
|
||||
|
||||
// Fixed Y-scale variants — clamp the polyline to [y_min, y_max] so cards in a
|
||||
// grid stay visually comparable (ej. CPU% / RAM%: pasar 0,100). v1.1.
|
||||
// width y height son explicitos (sin default) para que el compilador no haga
|
||||
// match contra los overloads sin y_min/y_max.
|
||||
void sparkline(const char* id, const float* values, int count,
|
||||
float y_min, float y_max,
|
||||
float width, float height);
|
||||
|
||||
void sparkline(const char* id, const float* values, int count, ImVec4 color,
|
||||
float y_min, float y_max,
|
||||
float width, float height);
|
||||
|
||||
@@ -3,11 +3,11 @@ name: sparkline
|
||||
kind: component
|
||||
lang: cpp
|
||||
domain: viz
|
||||
version: "1.0.0"
|
||||
version: "1.2.0"
|
||||
purity: pure
|
||||
signature: "void sparkline(const char* id, const float* values, int count, float width = 100.0f, float height = 20.0f)"
|
||||
description: "Renderiza un mini grafico de lineas inline para uso en tablas, headers y KPI cards"
|
||||
tags: [imgui, visualization, sparkline, inline, dashboard]
|
||||
tags: [imgui, visualization, sparkline, inline, dashboard, data-table-renderers, cpp-dashboard-viz]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
@@ -69,3 +69,8 @@ sparkline("kpi_rev", data, count);
|
||||
- Si todos los valores son iguales (rango < 1e-6), la linea se dibuja en el centro verticalmente.
|
||||
- El grosor de linea es 1.5px para que sea legible a alturas de 16-24px.
|
||||
- `id` no se muestra visualmente; solo se pasa a `PushID` para que ImGui diferencie widgets con los mismos datos en la misma tabla.
|
||||
|
||||
## Capability growth log
|
||||
|
||||
- v1.1.0 (2026-05-18) — Overloads con `y_min, y_max` explicitos. Cuando la metrica tiene dominio conocido (CPU%/RAM% -> 0,100) varias cards comparten escala y son comparables visualmente. Outliers se clampan al rango. Variant sin bounds preserva auto-scale historico.
|
||||
- v1.2.0 (2026-05-18) — Alpha fade gradient default: segmento i pinta con alpha lerp(0.2, 1.0, i/(count-1)). Older -> faded, newest -> bright. Efecto "trail" hacia el numero actual. Fill bajo curva tambien fade (alpha 4..28). Cambio visual sin cambio de API.
|
||||
|
||||
Reference in New Issue
Block a user