Files
fn_registry/modules/data_table/data_table.md
T
egutierrez fe0265c3bf docs(0133): MIGRATION.md + growth log placeholder + drift fix
- modules/data_table/MIGRATION.md: porting guide + release checklist 1.0.0-stable
- data_table.md: growth log entry commented for post-gate bump
- data_table.md: fix error_type Go remnant ("error_go_core" -> "") in C++ module
- cpp/CMakeLists.txt: SQLite3 optional dep for data_table_bench (cross-windows)
- agent_cleanup_worktree.go: !windows build tag (uses unix-only syscalls)
- dev/issues/0133-cpp-data-table-10m-rows.md: issue tracking

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 23:56:14 +02:00

19 KiB

name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, tested, tests, test_file_path, file_path, params, output
name kind lang domain version purity signature description tags uses_functions uses_types returns returns_optional error_type imports tested tests test_file_path file_path params output
data_table function cpp viz 1.5.0 impure void data_table::render(const char* id, const std::vector<TableInput>& tables, State& st, std::vector<TableEvent>* events_out = nullptr, bool show_chrome = true) Render UI completa de tabla TQL: chips bar, tabla, viz panels, column-stats inline, drill, color rules, joins, TQL editor, Ask AI, Button renderer, event sink (ButtonClick/RowDoubleClick/RowRightClick), tooltip per-cell, column_specs persisted in TQL. Dots renderer para sparkline-like de status (v1.3.0). CategoricalChip + ColorScale en v1.4.0. v1.5.0: stats/seleccion/drill-history/row-inspector pasan a vivir en State (por tabla, antes eran globales — multiples tablas en pantalla compartian estado). Conditional color extendido con tres modos: CellBg (legacy), CategoricalDot (autopalette por valor) y NumericRange (gradiente N-color). Hover del menu contextual de header restaurado. Entry-point publica del stack data_table. Muta State segun interaccion del usuario.
tables
viz
ui
imgui
tql
cpp-tables
compute_stage_cpp_core
compute_pipeline_cpp_core
compute_column_stats_cpp_core
auto_detect_type_cpp_core
tql_emit_cpp_core
tql_apply_cpp_core
tql_helpers_cpp_core
tql_to_sql_cpp_core
lua_engine_cpp_core
join_tables_cpp_core
viz_render_cpp_viz
data_table_types_cpp_core
ColumnSpec_cpp_core
CellRenderer_cpp_core
BadgeRule_cpp_core
IconMapEntry_cpp_core
TableInput_cpp_core
State_cpp_core
Stage_cpp_core
StageOutput_cpp_core
ViewMode_cpp_viz
ViewConfig_cpp_viz
VizPanel_cpp_viz
Join_cpp_core
Filter_cpp_core
DrillStep_cpp_core
DerivedColumn_cpp_core
Aggregation_cpp_core
SortClause_cpp_core
ColumnType_cpp_core
ColorRule_cpp_core
false
imgui.h
app_base.h
core/data_table_types.h
core/lua_engine.h
core/tql_apply.h
core/tql_emit.h
core/tql_helpers.h
core/tql_to_sql.h
core/compute_stage.h
core/compute_pipeline.h
core/compute_column_stats.h
core/auto_detect_type.h
core/join_tables.h
viz/viz_render.h
true
back-compat: TableInput without column_specs does not crash
Badge: TableInput with Badge column_spec compiles and links
Progress: TableInput with Progress column_spec compiles and links
Duration: TableInput with Duration column_spec compiles and links
Icon: TableInput with Icon column_spec compiles and links
Button: TableEvent struct constructible; render() with events_out links
Tooltip: ColumnSpec with tooltip_on_hover=true compiles and links
Back-compat: both render() signatures (with/without events_out) link
Dots: ColumnSpec with CellRenderer::Dots + badges constructs correctly
Dots TQL roundtrip: State::aux_column_specs accepts Dots spec
TestCategoricalChipRule: chip rule with match='success' produces correct color
TestColorScaleLerpTwoStops: t=0→first color, t=1→last color, t=0.5→midpoint
TestColorScaleLerpThreeStops: t=0.25 lies between stop0 and stop1
TestColorScaleOutOfRange: t<0 saturates at first; t>1 saturates at last
cpp/tests/test_column_specs.cpp modules/data_table/data_table.cpp
name desc
id ID unico ImGui para esta instancia, ej. '##orders_table'. Debe ser estable entre frames.
name desc
tables Lista de TableInput materializadas en memoria. tables[0] es la main por defecto; si State.main_source no-vacio se usa por nombre. Tablas extra se exponen como joinables en la UI de joins.
name desc
st Estado mutable completo: pipeline de stages, joins, viz config, ui tweaks, aux_column_specs (Phase 2). Debe persistir entre frames — no declarar en el stack del frame.
name desc
events_out Puntero a vector de TableEvent. Si non-null, se puebla con eventos de este frame (ButtonClick, RowDoubleClick, RowRightClick). El caller limpia/lee el vector despues de cada render. Pasar nullptr para desactivar (back-compat).
name desc
show_chrome Si false, oculta chips bar + breadcrumb por defecto. El usuario puede reactivar con el boton 'Show UI'. El State persiste el override del usuario entre frames.
void. Muta st en respuesta a la interaccion del usuario (filtros, breakouts, sorts, drill, joins, viz mode). Los cambios son visibles en st al retornar. Events emitted via events_out.

Ejemplo

#include "viz/data_table.h"
#include "core/data_table_types.h"

// --- Setup (una vez) ---
data_table::TableInput t;
t.name = "orders";
t.rows = num_rows;
t.cols = num_cols;
t.cells = cells_ptr;          // row-major flat array, owner externo
t.headers = {"id", "amount", "status", "actions"};
t.types   = {data_table::ColumnType::Int,
             data_table::ColumnType::Float,
             data_table::ColumnType::String,
             data_table::ColumnType::String};

// Phase 2: declarative renderers + tooltip
t.column_specs.resize(4);
t.column_specs[2].renderer = data_table::CellRenderer::Badge;
t.column_specs[2].badges   = {{"paid","#22c55e","Paid"},{"pending","#f59e0b",""}};
t.column_specs[2].tooltip         = "auto";
t.column_specs[2].tooltip_on_hover = true;
t.column_specs[3].renderer     = data_table::CellRenderer::Button;
t.column_specs[3].button_action = "cancel_order";
t.column_specs[3].button_label  = "Cancel";

data_table::State st;          // persiste entre frames
std::vector<data_table::TableEvent> events;

// --- Render (cada frame) ---
ImGui::Begin("Orders");
ImGui::BeginChild("##tbl", ImVec2(-1, -1));
events.clear();
data_table::render("##orders", {t}, st, &events);
ImGui::EndChild();
ImGui::End();

// --- Process events ---
for (const auto& ev : events) {
    if (ev.kind == data_table::TableEventKind::ButtonClick &&
        ev.action_id == "cancel_order") {
        cancel_order(ev.row);   // app handles the action
    }
    if (ev.kind == data_table::TableEventKind::RowDoubleClick) {
        open_order_detail(ev.row);
    }
}

Cuando usarla

Cuando una app necesita tabla con filtros + agregaciones + viz + joins sobre datos en memoria. Reemplaza ImGui::BeginTable inline + toda la logica TQL manual. Sustituye directamente el include del playground (tables/data_table.h) cambiando solo el path a viz/data_table.h.

Usar CategoricalChip cuando quieras un indicador visual (dot de color) siempre visible a la izquierda del texto de la celda, para columnas categoricas (estado, tipo, severidad). Mas discreto que Badge y sin hover-only. Usar ColorScale cuando la columna sea numerica continua y quieras dar contexto visual de "alto/bajo/medio" con un fondo tintado proporcional al valor — util para latencias, scores, porcentajes, metricas.

Per-table state (v1.5.0)

Antes de v1.5.0 los siguientes campos vivian en un singleton UiState interno y se aplicaban a TODAS las tablas a la vez. Desde v1.5.0 viven en State (parametro st), por tabla:

Campo Significado
stats_mode, stats_cache, stats_last_* Toggle "Show stats" + cache de stats por col.
sel_anchor_row/col, sel_end_row/col, sel_active, sel_dragging Seleccion rectangular drag para Ctrl+C (TSV).
inspect_row, inspect_open Modal "Inspect row..." del menu contextual.
drill_back, drill_forward Historial de drill back/forward.

Estado modal (un solo popup abierto a la vez en toda la app) sigue en el singleton interno: edit-chip popups, addfilter/addbreakout/addagg/addsort, Ask AI, Custom column formula, TQL show/apply, drill popup, header context. Estos NO necesitan separacion por tabla porque ImGui solo permite un popup abierto a la vez.

Conditional color rules (v1.5.0)

State::color_rules (tipo std::vector<ColorRule>) acepta tres modos via ColorRule::kind (enum ColorRuleKind):

Modo Cuando usar Campos relevantes
CellBg (legacy) Resaltar bg de celda completa cuando valor == equals. Un color por regla, multiples reglas por col. equals, color
CategoricalDot Categoricas con muchos valores distintos. Dibuja un dot circular a la izquierda del texto. Color autoasignado por hash (palette de 12 vibrant Tailwind-500) o por mapeo explicito dot_map. dot_alpha, dot_radius_px, dot_map
NumericRange Numericas continuas. Gradiente N-color sobre bg de celda. Valores fuera de [range_min, range_max] saturan en los stops extremos. range_min, range_max, range_alpha, range_stops

Menu UI

Right-click cabecera → Conditional color → [Cell bg / Categorical dot / Numeric range]. La auto-deteccion preselecciona NumericRange para columnas Int/Float, CellBg para el resto. Cada Apply REEMPLAZA la regla existente del mismo kind en esa columna (puedes combinar las tres en la misma columna). "Clear col" borra todas las reglas de la columna.

Ejemplo programatico

data_table::State st;

// Cell bg cuando status == "error" -> rojo.
data_table::ColorRule r1;
r1.col    = 3;
r1.kind   = data_table::ColorRuleKind::CellBg;
r1.equals = "error";
r1.color  = IM_COL32(220, 60, 60, 120);
st.color_rules.push_back(r1);

// Dot autoasignado por categoria de `region`.
data_table::ColorRule r2;
r2.col           = 1;
r2.kind          = data_table::ColorRuleKind::CategoricalDot;
r2.dot_alpha     = 1.0f;
r2.dot_radius_px = 4.0f;
// (dot_map vacio -> autopalette por hash. Para mapeo fijo:)
// r2.dot_map = {{"EU","#3b82f6"},{"NA","#22c55e"},{"AS","#f59e0b"}};
st.color_rules.push_back(r2);

// Gradient verde→ambar→rojo sobre latency_ms en [0, 500].
data_table::ColorRule r3;
r3.col          = 5;
r3.kind         = data_table::ColorRuleKind::NumericRange;
r3.range_min    = 0.0;
r3.range_max    = 500.0;
r3.range_alpha  = 0.25f;
r3.range_stops  = {
    {0.0f, "#22c55e"},
    {0.5f, "#f59e0b"},
    {1.0f, "#ef4444"},
};
st.color_rules.push_back(r3);

CategoricalDot vs CategoricalChip renderer

Hay dos rutas para dots categoricos. Cuando elegir cada una:

Mecanismo Donde se define Cuando
ColorRule::CategoricalDot (v1.5.0) State::color_rules (ad-hoc por usuario via menu) Exploracion interactiva: el usuario decide en runtime si pintar dots y con que palette. Autopalette por hash sin definir mapeo.
CellRenderer::CategoricalChip (v1.4.0) TableInput::column_specs[c] (declarativo por el caller) App quiere dots SIEMPRE para una columna especifica con mapeo explicito chips: [{match, color}].

Ambos son compatibles — pueden coexistir sin colision.

Gotchas

  • ImGui + ImPlot context activos: render() llama a APIs de ambas librerias. Llamar fuera de un frame activo causa UB.
  • State no stack-local: State contiene el historial de drill, pipeline de stages, cache de stats y buffers de UI. Declarar en el stack del frame reset todo el estado del usuario en cada frame.
  • Drill-down propaga en State: st.active_stage y st.stages se mutan por click en charts. El caller puede leer st tras render() para reaccionar.
  • Thread-safety: render() usa static thread_local para buffers intermedios. Llamar solo desde el main thread de ImGui.
  • TableInput owner externo: cells es un puntero raw al array del caller. Los datos deben sobrevivir durante toda la llamada a render(). No pasar puntero a vector que puede reallocarse.
  • events_out no se limpia: render() solo hace push_back. El caller debe llamar events.clear() antes de cada frame o acumulara eventos de frames anteriores.
  • Button + celda vacia: si el cell value es vacio, el boton NO se dibuja. La app controla cuando mostrar el boton poniendo un value no vacio (ej. "1" o el ID de la fila).
  • RowRightClick emite evento Y abre popup interno: la tabla de stages (stage>0) sigue abriendo su popup de drill. En el raw table (stage 0), se emite el evento pero el popup de drill antiguo tambien puede abrirse via U.open_cell_popup. El caller puede ignorar el popup interno y gestionar su propio menu al detectar RowRightClick.
  • CategoricalChip sin regla coincidente → sin dot: si ninguna ChipRule.match coincide con el valor de la celda, solo se renderiza el texto. Definir una regla de fallback explicita si se necesita dot para valores no mapeados.
  • ColorScale clampa fuera de rango: valores por debajo de range_min se tratan como t=0 (primer stop) y valores por encima de range_max como t=1 (ultimo stop). Definir range_min/range_max sensatos para que el gradiente sea informativo; valores muy alejados de la mayoria hacen que todo el gradiente aparezca en un extremo.
  • aux_column_specs merge: si TableInput.column_specs esta vacio pero State.aux_column_specs[0] no, render() los aplica automaticamente. Si el caller pasa column_specs no vacios, ganan sobre los del State.
  • Ask AI modal (llm_anthropic): el boton "Ask AI" usa un stub interno de llm_anthropic que retorna error por defecto. Para activar la feature real, compilar con -DFN_LLM_ANTHROPIC=1 y proveer infra/llm_anthropic.h en el include path. Pendiente Wave 4: promover al registry.
  • FN_TQL_DUCKDB: modo SQL del Ask AI requiere compilar con -DFN_TQL_DUCKDB=1 y la libreria DuckDB disponible.

Notas

No hay tests unitarios directos: render() requiere ImGui + ImPlot context activos (imposible sin ventana GL). Cobertura via:

  1. cpp/apps/primitives_gallery/playground/tables/ — playground original con self_test.cpp y e2e_run.sh.
  2. Wave 4: migration self-tests en las apps que migren desde el playground.

Estado Wave 3.5 (issue 0081-I):

  • Todos los includes del playground (data_table_logic.h, tql.h, tql_to_sql.h) eliminados. data_table.cpp compila sin el playground en el include path.
  • tql::apply firma extendida ya en tql_apply_cpp_core (wave anterior). Resuelto.
  • tql_to_sql promovido a core/tql_to_sql.h. Resuelto.
  • data_table_logic helpers (row_to_tsv, drill, view_mode, etc.) declarados como static en data_table.cpp. No son API pública.
  • State::ensure_stage0/raw/active implementados en compute_stage.cpp.
  • ColStats struct: usa el de compute_column_stats_cpp_core. Unificado.

Deuda tecnica restante (Wave 4):

  • llm_anthropic (Ask AI modal, issue 0080): stub interno activo. Promover a cpp/functions/infra/llm_anthropic para activar feature real.
  • FN_TQL_DUCKDB: modo SQL del Ask AI sin soporte en stub. Requiere DuckDB + flag de compilacion.
  • column_specs TQL roundtrip (Phase 2): actualmente caller-managed. No persisten en TQL emit/apply. Planificado en issue 0081-O.

Capability growth log

v1.1.0 (2026-05-15) — declarative CellRenderer (Badge/Progress/Duration/Icon) via TableInput.column_specs sidecar. Back-compat preservado: apps existentes sin column_specs siguen funcionando sin cambios.

v1.2.0 (2026-05-15) — Button renderer + event sink (ButtonClick/RowDoubleClick/RowRightClick) + tooltip per cell + column_specs persisted in TQL (aux_column_specs roundtrip). Back-compat preserved: events_out=nullptr by default; existing render() callers unchanged.

v1.3.0 (2026-05-15) — Dots renderer for inline status timelines (sparkline-like). Reuses badges for color mapping. dots_max/dots_separator/dots_show_count/dots_glyph_size fields. TQL roundtrip. dag_engine_ui canonical use case (10-col antipattern -> 6-col fix).

v1.3.1 (2026-05-15) — Dots renderer now draws filled circles via ImDrawList instead of Unicode glyph. Font-independent: works regardless of TTF glyph coverage. Closes "dots show as ?" bug in dag_engine_ui.

v1.3.2 (2026-05-15) — Hover dimming: row uses muted alpha (0.05 vs default 0.31); hovered cell gets a subtle overlay (~9% white) via ImDrawList. Badge no longer SpanAllColumns. Closes "table-wide bright highlight on hover".

v1.3.3 (2026-05-15) — Selectable bg disabled for Text/empty cells (was duplicating with manual overlay → gray double-hover). Explicit ImVec2 size on Selectable so empty cells get a hit area (fixes drag-select skipping empties). Single uniform hover layer across all cell renderers.

v1.3.4 (2026-05-15) — Row height tightened: GetTextLineHeight() (no spacing) replaces GetTextLineHeightWithSpacing() in Selectable size + manual overlay rect. Removes inflated row vertical padding introduced in v1.3.3.

v1.3.5 (2026-05-15) — Cell hover paints via TableSetBgColor (covers entire cell bg including CellPadding) instead of manual AddRectFilled inside content area. Hit-test expanded by CellPadding for proper edge-to-edge coverage. Closes "hover has gap between cell borders".

v1.3.6 (2026-05-15) — Selection (drag-range) also paints via TableSetBgColor — same edge-to-edge coverage as hover. Header/HeaderHovered/HeaderActive colors set to fully transparent so Selectable doesn't paint anything; all cell bg states (hover, selected, selected+hover) go through TableSetBgColor uniformly.

v1.4.0 (2026-05-16) — new renderers: CategoricalChip (dot izquierda + text, always visible, replaces hover-only color-on-text for categorical) + ColorScale (continuous N-color LERP gradient for numeric cells, configurable range_min/range_max/range_stops/range_alpha). New types: ChipRule{match,color} + ColorStop{position,color} in data_table_types.h. TQL roundtrip (emit+apply) for both renderers. 4 headless tests added to test_column_specs.cpp.

v1.5.0 (2026-05-17) — per-table state isolation: stats_mode, stats_cache, stats_last_*, sel_*, inspect_*, drill_back/forward movidos de singleton UiState interno a State. Antes: toggle "Show stats" / drag-select / Inspect row se aplicaban a TODAS las tablas visibles a la vez. Ahora cada tabla los lleva. Conditional color extendido: nuevo enum ColorRuleKind { CellBg, CategoricalDot, NumericRange } + campos en ColorRule (dot_alpha, dot_radius_px, dot_map, range_min/max/alpha/stops). Menu del header autodetecta el modo por tipo de columna (Int/Float → NumericRange; resto → CellBg). CategoricalDot autoasigna colores desde palette de 12 hash-deterministico — no hace falta mapear cada valor a mano. Fix: hover del menu contextual del header invisible cuando las table-styles pisaban HeaderHovered a transparente — restaurado dentro del scope del popup. Breaking en data_table_types.h: ColStop + DrillStep movidos arriba para que State pueda contenerlos por valor; consumidores que redefinian ColStats localmente (playground data_table_logic.h) eliminados — usar el del registry (compute_column_stats.h, ya transitivamente incluido).


Promovido desde cpp/apps/primitives_gallery/playground/tables/data_table.{h,cpp} — issue 0081-H.