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,58 @@
|
||||
# Modulos C++ del registry
|
||||
|
||||
Bundles versionados de funciones del registry expuestos como static libs cmake. Apps opt-in via `app.md::uses_modules` + `target_link_libraries(... fn_module_<X>)`.
|
||||
|
||||
## Catalogo
|
||||
|
||||
| Modulo | Version | Static lib | Header | Entry | Linkage | Descripcion | Contrato |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| [framework_cpp](framework/module.md) | 1.1.0 | `fn_framework` | `framework/app_base.h` | `fn::run_app(cfg, render)` | transitivo via `add_imgui_app` | Shell ImGui (GLFW+OpenGL+ImGui+ImPlot+themas+layouts+logger) | [framework_cpp](../docs/MODULES_API.md#framework_cpp) |
|
||||
| [data_table_cpp](data_table/module.md) | 2.1.0 | `fn_module_data_table` | `data_table/data_table.h` | `data_table::render(id, tables, state, events_out, show_chrome)` | explicito | Tabla completa TQL + 9 renderers declarativos + viz + drill + AI + button events | [data_table_cpp](../docs/MODULES_API.md#data_table_cpp) |
|
||||
|
||||
## Diferencia `members` vs `uses_functions` (post 0107d)
|
||||
|
||||
Cada `module.md` declara DOS listas:
|
||||
|
||||
- **`members`**: funciones del registry que el modulo POSEE. Viven en `cpp/functions/<dominio>/` y NO se usan fuera del modulo. Apps consumidoras del modulo NO listan estos miembros en su `uses_functions` (cobertura transitiva).
|
||||
- **`uses_functions`**: funciones del registry que el modulo CONSUME pero NO posee. Utiles fuera del modulo. Si una app necesita estas STANDALONE, las declara en su propio `uses_functions` directamente (no es duplicacion — es uso independiente).
|
||||
|
||||
Ejemplo `data_table_cpp` v2.1.0:
|
||||
- `members`: las 7 sub-funciones `data_table_*_cpp_viz` (renderizan dentro del modulo).
|
||||
- `uses_functions`: `lua_engine`, `llm_anthropic`, `join_tables`, `auto_detect_type`, `tql_*`, `compute_*`, `viz_render` (consumidas por el modulo pero utiles solas).
|
||||
|
||||
## Como anadir un modulo
|
||||
|
||||
1. `mkdir modules/<name>/` con:
|
||||
- `module.md` (frontmatter: name, version, description, members, uses_functions, dir_path).
|
||||
- `CMakeLists.txt` definiendo `add_library(fn_module_<name> STATIC ...)`.
|
||||
- `<name>.cpp` + `<name>.h` (entry function).
|
||||
- `<name>_internal.h` si tiene >1 archivo + UiState compartido.
|
||||
|
||||
2. Anadir `add_subdirectory(${CMAKE_SOURCE_DIR}/../modules/<name> ${CMAKE_BINARY_DIR}/modules/<name>)` en `cpp/CMakeLists.txt`.
|
||||
|
||||
3. Anadir fila en este catalogo + seccion en `docs/MODULES_API.md` siguiendo el template.
|
||||
|
||||
4. `fn index` registra el modulo en `registry.db::modules`.
|
||||
|
||||
5. Cada bump de version: `/version modules/<name> <major|minor|patch> "<reason>"`. Edita `module.md::version` + `## Capability growth log`.
|
||||
|
||||
## Auditoria
|
||||
|
||||
- `fn doctor modules` (0107a): detecta drift `uses_modules` vs `uses_functions` en apps. Hoy: 0 drift en 8 apps consumidoras de `data_table_cpp` post-0107b.
|
||||
- `audit_data_table_usage_go_infra` (capability del registry): audita patrones de uso del modulo `data_table` en apps. Detecta `inline_begintable`, `state_not_persistent`, `no_child_host`, `no_event_sink`, `cmake_missing_link`. Output en `dev/data_table_integration_audit.md`.
|
||||
|
||||
## Ciclo de vida + version policy
|
||||
|
||||
Ver `docs/MODULES_API.md::Ciclo de vida de un modulo` y `.claude/rules/cpp_apps.md`.
|
||||
|
||||
Semver estricto:
|
||||
- **Major** = breaking ABI/API publica del modulo (entry function, State struct expuesto, eliminacion de members).
|
||||
- **Minor** = additive backward-compatible (nuevo renderer, nuevo evento, refactor interno sin cambio de API).
|
||||
- **Patch** = bugfix sin cambio de API.
|
||||
|
||||
Slash command para bumpear: `/version modules/<name> <bump-type> "<reason>"`.
|
||||
|
||||
## Modulos en roadmap (post 0107)
|
||||
|
||||
- `chat_ia_cpp` — sera el siguiente modulo. Bloqueado hasta cierre completo de 0107 (estandarizacion modulos). Issue 0109 cuando 0107 cierre.
|
||||
- Otros candidatos detectados por uso repetido en apps: TBD (auditar con `audit_data_table_usage` pattern para otros bundles).
|
||||
@@ -19,6 +19,13 @@ set(_FN_CPP_ROOT ${CMAKE_SOURCE_DIR}/../cpp)
|
||||
|
||||
add_library(fn_module_data_table STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/data_table.cpp
|
||||
# Sub-funciones extraidas (issue 0107c)
|
||||
${_FN_CPP_ROOT}/functions/viz/data_table_drill.cpp
|
||||
${_FN_CPP_ROOT}/functions/viz/data_table_color_rules.cpp
|
||||
${_FN_CPP_ROOT}/functions/viz/data_table_ai_panel.cpp
|
||||
${_FN_CPP_ROOT}/functions/viz/data_table_chips.cpp
|
||||
${_FN_CPP_ROOT}/functions/viz/data_table_grid.cpp
|
||||
${_FN_CPP_ROOT}/functions/viz/data_table_viz_panels.cpp
|
||||
${_FN_CPP_ROOT}/functions/core/compute_stage.cpp
|
||||
${_FN_CPP_ROOT}/functions/core/compute_pipeline.cpp
|
||||
${_FN_CPP_ROOT}/functions/core/tql_emit.cpp
|
||||
|
||||
+144
-2873
File diff suppressed because it is too large
Load Diff
@@ -3,10 +3,10 @@ name: data_table
|
||||
kind: function
|
||||
lang: cpp
|
||||
domain: viz
|
||||
version: "1.4.0"
|
||||
version: "1.5.0"
|
||||
purity: impure
|
||||
signature: "void data_table::render(const char* id, const std::vector<TableInput>& tables, State& st, std::vector<TableEvent>* events_out = nullptr, bool show_chrome = true)"
|
||||
description: "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 (dot izquierda + text, siempre visible) y ColorScale (gradient N-color en fondo de celda) en v1.4.0. Entry-point publica del stack data_table. Muta State segun interaccion del usuario."
|
||||
description: "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."
|
||||
tags: [tables, viz, ui, imgui, tql, cpp-tables]
|
||||
uses_functions:
|
||||
- compute_stage_cpp_core
|
||||
@@ -40,8 +40,7 @@ uses_types:
|
||||
- Aggregation_cpp_core
|
||||
- SortClause_cpp_core
|
||||
- ColumnType_cpp_core
|
||||
- TableEvent_cpp_core
|
||||
- TableEventKind_cpp_core
|
||||
- ColorRule_cpp_core
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
@@ -149,6 +148,82 @@ Cuando una app necesita tabla con filtros + agregaciones + viz + joins sobre dat
|
||||
|
||||
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
|
||||
|
||||
```cpp
|
||||
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.
|
||||
@@ -206,5 +281,7 @@ v1.3.6 (2026-05-15) — Selection (drag-range) also paints via TableSetBgColor
|
||||
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,344 @@
|
||||
#pragma once
|
||||
// data_table_internal — contrato compartido entre las 6 sub-funciones del modulo.
|
||||
// NO publico para apps: las apps incluyen `data_table/data_table.h` y nada mas.
|
||||
// Este header lo incluyen SOLO los .cpp del modulo (data_table.cpp + sus 6 sub-funciones).
|
||||
//
|
||||
// Issue 0107c — split de data_table.cpp (4777 LOC) en 6 sub-funciones del registry.
|
||||
//
|
||||
// Provee:
|
||||
// 1. `UiState` agregador (composicion de sub-states declarados en los .h de
|
||||
// cada sub-funcion + estado compartido por >1 sub-funcion).
|
||||
// 2. `ui()` accessor para singleton thread_local — mismo lifetime que el
|
||||
// original UiState del playground.
|
||||
// 3. Helpers compartidos como `inline` puros: `ops_for_type`, `op_label`,
|
||||
// `effective_type`, `view_mode_label`, `join_strategy_label`, etc.
|
||||
// 4. Forward refs de funciones internas que cruzan sub-funciones (ej. el
|
||||
// draw_header_menu de chips llama draw_color_rule_menu de color_rules).
|
||||
//
|
||||
// Politica:
|
||||
// - Si un helper se usa SOLO dentro de UNA sub-funcion -> queda `static` en su .cpp.
|
||||
// - Si se usa en >1 sub-funcion -> aqui como `inline`.
|
||||
// - Si manipula estado -> miembro de UiState (o sub-state).
|
||||
//
|
||||
// API publica externa = data_table/data_table.h (intacta tras refactor).
|
||||
// API interna del modulo = este header.
|
||||
|
||||
#include "core/data_table_types.h"
|
||||
#include "core/auto_detect_type.h"
|
||||
|
||||
// Sub-states declarados en los .h de cada sub-funcion. Se incluyen aqui para
|
||||
// que `UiState` los pueda componer por valor.
|
||||
#include "viz/data_table_ai_panel.h" // AskAiState
|
||||
#include "viz/data_table_chips.h" // TqlBarState
|
||||
#include "viz/data_table_color_rules.h" // ColorRuleEditorState
|
||||
|
||||
#include "imgui.h"
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace data_table {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// UiState — singleton thread_local del modulo. Agrupa:
|
||||
// (a) Sub-states declarados en headers de sub-funciones (AskAiState etc.).
|
||||
// (b) Estado compartido por >1 sub-funcion que no encaja en ningun sub-state
|
||||
// individual.
|
||||
// El entrypoint thin (data_table.cpp) mantiene el UiState via `ui()`.
|
||||
// Cada sub-funcion recibe `UiState&` o solo el sub-state que necesita.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct UiState {
|
||||
// ----- Sub-states de sub-funciones (composicion) -----
|
||||
AskAiState ask_ai; // data_table_ai_panel
|
||||
TqlBarState tql_bar; // data_table_chips
|
||||
ColorRuleEditorState color_rules; // data_table_color_rules
|
||||
|
||||
// ----- Cell popup (grid + chips, drill popup en cell ctx menu) -----
|
||||
int pending_col = -1;
|
||||
std::string pending_value;
|
||||
bool open_cell_popup = false;
|
||||
|
||||
// ----- Header popup (chips draw_header_menu + color_rules + grid header) -----
|
||||
int header_popup_col = -1;
|
||||
std::unordered_map<int, std::string> filter_inputs;
|
||||
// color_value_inputs + color_picker_vals viven en color_rules (ColorRuleEditorState).
|
||||
|
||||
// ----- Add-filter popup state (chips draw_add_filter_popup) -----
|
||||
int addf_col = 0;
|
||||
std::string addf_val;
|
||||
bool addf_range = false;
|
||||
std::string addf_lo;
|
||||
std::string addf_hi;
|
||||
|
||||
// ----- Custom column modal (formula editor — vive en data_table.cpp entrypoint) -----
|
||||
bool cf_open = false;
|
||||
bool cf_editing = false;
|
||||
int cf_edit_idx = -1;
|
||||
int cf_target_stage = 0;
|
||||
std::string cf_formula;
|
||||
std::string cf_name;
|
||||
ColumnType cf_type = ColumnType::String;
|
||||
std::string cf_error;
|
||||
bool cf_ac_open = false;
|
||||
int cf_ac_start = -1;
|
||||
int cf_ac_cursor = -1;
|
||||
std::string cf_ac_filter;
|
||||
bool cf_force_cursor = false;
|
||||
int cf_target_cursor = -1;
|
||||
|
||||
// ----- Add-breakout / add-aggregation / edit chip popups (chips) -----
|
||||
int brk_picker_col = 0;
|
||||
int agg_picker_fn = (int)AggFn::Count;
|
||||
int agg_picker_col = 0;
|
||||
double agg_picker_arg = 0.95;
|
||||
int edit_chip_kind = 0; // 0=none, 1=filter, 2=breakout, 3=agg, 4=sort
|
||||
int edit_chip_idx = -1;
|
||||
int edit_col_idx = 0;
|
||||
int edit_op = (int)Op::Eq;
|
||||
int edit_agg_fn = (int)AggFn::Count;
|
||||
double edit_agg_arg = 0.5;
|
||||
bool edit_sort_desc = false;
|
||||
std::string edit_value;
|
||||
int sort_picker_col = 0;
|
||||
bool sort_picker_desc = false;
|
||||
|
||||
// ----- Snapshot del active stage output/input (viz_panels config popup) -----
|
||||
std::vector<std::string> active_headers;
|
||||
std::vector<ColumnType> active_types;
|
||||
std::vector<std::string> input_headers_active;
|
||||
std::vector<ColumnType> input_types_active;
|
||||
|
||||
// ----- Re-fit triggers para viz panels -----
|
||||
ViewMode prev_viz_display = ViewMode::Table;
|
||||
int prev_viz_stage = 0;
|
||||
std::size_t prev_viz_cfg_h = 0;
|
||||
|
||||
// ----- Toggle Table <-> View (viz_panels) -----
|
||||
ViewMode last_non_table_main = ViewMode::Bar;
|
||||
|
||||
// ----- Export path (chips export action) -----
|
||||
std::string last_export_path;
|
||||
};
|
||||
|
||||
// Singleton accessor. Definido en data_table.cpp (entrypoint).
|
||||
UiState& ui();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ViewMode table — fuente de verdad para label/needs_agg/all_modes.
|
||||
// Compartida por: data_table.cpp (re-fit trigger) y data_table_viz_panels.cpp.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct ViewModeInfo {
|
||||
ViewMode m;
|
||||
const char* token;
|
||||
const char* label;
|
||||
int min_cols;
|
||||
bool needs_num;
|
||||
bool needs_cat;
|
||||
bool needs_agg;
|
||||
};
|
||||
|
||||
static const ViewModeInfo kViewModes[] = {
|
||||
{ ViewMode::Table, "table", "Table", 1, false, false, false },
|
||||
{ ViewMode::Bar, "bar", "Bar (horizontal)", 2, true, true, true },
|
||||
{ ViewMode::Column, "column", "Column (vertical)", 2, true, true, true },
|
||||
{ ViewMode::GroupedBar, "grouped_bar", "Grouped bar", 2, true, true, true },
|
||||
{ ViewMode::StackedBar, "stacked_bar", "Stacked bar", 2, true, true, true },
|
||||
{ ViewMode::Line, "line", "Line", 1, true, false, false },
|
||||
{ ViewMode::Area, "area", "Area", 1, true, false, false },
|
||||
{ ViewMode::Stairs, "stairs", "Stairs", 1, true, false, false },
|
||||
{ ViewMode::Scatter, "scatter", "Scatter", 2, true, false, false },
|
||||
{ ViewMode::Bubble, "bubble", "Bubble", 3, true, false, false },
|
||||
{ ViewMode::Histogram, "histogram", "Histogram", 1, true, false, false },
|
||||
{ ViewMode::Histogram2D, "hist2d", "Histogram 2D", 2, true, false, false },
|
||||
{ ViewMode::Heatmap, "heatmap", "Heatmap", 1, true, false, false },
|
||||
{ ViewMode::BoxPlot, "boxplot", "Box plot", 2, true, true, false },
|
||||
{ ViewMode::Stem, "stem", "Stem", 1, true, false, false },
|
||||
{ ViewMode::ErrorBars, "errorbars", "Error bars", 2, true, false, false },
|
||||
{ ViewMode::Pie, "pie", "Pie", 2, true, true, true },
|
||||
{ ViewMode::Donut, "donut", "Donut", 2, true, true, true },
|
||||
{ ViewMode::Funnel, "funnel", "Funnel", 2, true, true, true },
|
||||
{ ViewMode::Waterfall, "waterfall", "Waterfall", 1, true, false, true },
|
||||
{ ViewMode::KPI, "kpi", "KPI (single)", 1, true, false, true },
|
||||
{ ViewMode::KPIGrid, "kpi_grid", "KPI grid", 1, true, false, true },
|
||||
{ ViewMode::Candlestick, "candlestick", "Candlestick (OHLC)", 4, true, false, false },
|
||||
{ ViewMode::Radar, "radar", "Radar", 2, true, true, false },
|
||||
};
|
||||
static const int kViewModesN = (int)(sizeof(kViewModes) / sizeof(kViewModes[0]));
|
||||
|
||||
inline const char* view_mode_label(ViewMode m) {
|
||||
for (int i = 0; i < kViewModesN; ++i) if (kViewModes[i].m == m) return kViewModes[i].label;
|
||||
return "Table";
|
||||
}
|
||||
|
||||
inline bool view_mode_needs_aggregation(ViewMode m) {
|
||||
for (int i = 0; i < kViewModesN; ++i) if (kViewModes[i].m == m) return kViewModes[i].needs_agg;
|
||||
return false;
|
||||
}
|
||||
|
||||
inline const ViewMode* all_view_modes(int* n_out) {
|
||||
static ViewMode arr[64];
|
||||
static bool init = false;
|
||||
if (!init) {
|
||||
for (int i = 0; i < kViewModesN; ++i) arr[i] = kViewModes[i].m;
|
||||
init = true;
|
||||
}
|
||||
if (n_out) *n_out = kViewModesN;
|
||||
return arr;
|
||||
}
|
||||
|
||||
// filters_hash: FNV-1a hash de filtros activos para detectar cambios en maybe_recompute_stats.
|
||||
inline size_t filters_hash(const std::vector<Filter>& f) {
|
||||
size_t h = 0xcbf29ce484222325ULL;
|
||||
for (const auto& x : f) {
|
||||
h ^= (size_t)x.col; h *= 0x100000001b3ULL;
|
||||
h ^= (size_t)x.op; h *= 0x100000001b3ULL;
|
||||
for (char ch : x.value) { h ^= (size_t)(unsigned char)ch; h *= 0x100000001b3ULL; }
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
// ColInfo: nombre + tipo de columna. Usado por draw_viz_config_popup para
|
||||
// construir las listas de seleccion X/Y/Cat.
|
||||
struct ColInfo { std::string name; ColumnType type; };
|
||||
|
||||
// collect_active_col_info: devuelve snapshot de columnas activas del active_stage
|
||||
// (pobladas por render() en UiState::active_headers / active_types).
|
||||
// Compartida por: data_table.cpp (forward decl) y data_table_viz_panels.cpp.
|
||||
inline std::vector<ColInfo> collect_active_col_info(const State& /*st*/) {
|
||||
auto& U = ui();
|
||||
std::vector<ColInfo> r;
|
||||
int n = (int)std::min(U.active_headers.size(), U.active_types.size());
|
||||
r.reserve(n);
|
||||
for (int i = 0; i < n; ++i) r.push_back({U.active_headers[i], U.active_types[i]});
|
||||
return r;
|
||||
}
|
||||
|
||||
// auto_promote_aggregated: si user en stage 0 elige una viz que necesita
|
||||
// agrupacion, crea stage 1 con breakout=primera cat + agg=sum(primera num) o count.
|
||||
// Compartida por: data_table.cpp y data_table_viz_panels.cpp.
|
||||
inline void auto_promote_aggregated(State& st) {
|
||||
auto& U = ui();
|
||||
if (st.active_stage != 0) return;
|
||||
if (st.stages.size() != 1) return;
|
||||
|
||||
std::string cat_name;
|
||||
std::string num_name;
|
||||
for (size_t i = 0; i < U.active_headers.size() && i < U.active_types.size(); ++i) {
|
||||
ColumnType t = U.active_types[i];
|
||||
if (cat_name.empty() &&
|
||||
(t == ColumnType::String || t == ColumnType::Date ||
|
||||
t == ColumnType::Bool || t == ColumnType::Json)) {
|
||||
cat_name = U.active_headers[i];
|
||||
}
|
||||
if (num_name.empty() &&
|
||||
(t == ColumnType::Int || t == ColumnType::Float)) {
|
||||
num_name = U.active_headers[i];
|
||||
}
|
||||
}
|
||||
|
||||
Stage s1;
|
||||
if (!cat_name.empty()) s1.breakouts.push_back(cat_name);
|
||||
Aggregation a;
|
||||
if (!num_name.empty()) {
|
||||
a.fn = AggFn::Sum;
|
||||
a.col = num_name;
|
||||
} else {
|
||||
a.fn = AggFn::Count;
|
||||
}
|
||||
s1.aggregations.push_back(a);
|
||||
st.stages.push_back(std::move(s1));
|
||||
st.active_stage = (int)st.stages.size() - 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers compartidos (inline, puros). Usados por >1 sub-funcion.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// effective_type: si declared==Auto, llama auto_detect_type sobre el rango.
|
||||
// Usado por: chips (filter popups), grid (cell render), viz_panels (stats).
|
||||
inline ColumnType effective_type(ColumnType declared,
|
||||
const char* const* cells, int rows, int cols, int col) {
|
||||
if (declared != ColumnType::Auto) return declared;
|
||||
return auto_detect_type(cells, rows, cols, col);
|
||||
}
|
||||
|
||||
// ops_for_type: lista de operadores validos para un ColumnType.
|
||||
// Usado por: chips (filter popups, edit popups), grid (cell ctx menu).
|
||||
inline std::vector<Op> ops_for_type(ColumnType t) {
|
||||
switch (t) {
|
||||
case ColumnType::Int:
|
||||
case ColumnType::Float:
|
||||
case ColumnType::Date:
|
||||
return {Op::Eq, Op::Neq, Op::Gt, Op::Gte, Op::Lt, Op::Lte};
|
||||
case ColumnType::Bool:
|
||||
return {Op::Eq, Op::Neq};
|
||||
case ColumnType::Json:
|
||||
return {Op::Eq, Op::Neq, Op::Contains, Op::NotContains};
|
||||
case ColumnType::String:
|
||||
return {Op::Eq, Op::Neq, Op::Contains, Op::NotContains, Op::StartsWith, Op::EndsWith};
|
||||
case ColumnType::Auto:
|
||||
default:
|
||||
return {Op::Eq, Op::Neq, Op::Contains, Op::NotContains};
|
||||
}
|
||||
}
|
||||
|
||||
// op_label: nombre humano del operador para chips + cell ctx menu.
|
||||
// Usado por: chips, grid.
|
||||
// NOTE: `static inline` para evitar clash de simbolos con la definicion
|
||||
// canonica de `op_label` en tql_helpers.cpp (issue 0107c follow-up). MinGW
|
||||
// linker es estricto con inline-no-static vs out-of-line definition.
|
||||
static inline const char* op_label(Op op) {
|
||||
switch (op) {
|
||||
case Op::Eq: return "=";
|
||||
case Op::Neq: return "!=";
|
||||
case Op::Gt: return ">";
|
||||
case Op::Gte: return ">=";
|
||||
case Op::Lt: return "<";
|
||||
case Op::Lte: return "<=";
|
||||
case Op::Contains: return "contains";
|
||||
case Op::NotContains: return "!contains";
|
||||
case Op::StartsWith: return "starts";
|
||||
case Op::EndsWith: return "ends";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
// join_strategy_label: nombre humano de la estrategia de join.
|
||||
// Usado por: chips (joins chips), data_table.cpp (render setup).
|
||||
inline const char* join_strategy_label(JoinStrategy s) {
|
||||
switch (s) {
|
||||
case JoinStrategy::Left: return "left-join";
|
||||
case JoinStrategy::Inner: return "inner-join";
|
||||
case JoinStrategy::Right: return "right-join";
|
||||
case JoinStrategy::Full: return "full-join";
|
||||
}
|
||||
return "left-join";
|
||||
}
|
||||
|
||||
// resolve_main_idx: encuentra el indice de tables[] que coincide con main_source.
|
||||
// Usado por: data_table.cpp render entrypoint.
|
||||
inline int resolve_main_idx(const std::vector<TableInput>& tables, const std::string& main_source) {
|
||||
if (tables.empty()) return -1;
|
||||
if (main_source.empty()) return 0;
|
||||
for (std::size_t i = 0; i < tables.size(); ++i) {
|
||||
if (tables[i].name == main_source) return (int)i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Forward refs de funciones internas que cruzan sub-funciones.
|
||||
// Cada cual definida en SU .cpp; aqui solo declarada para que otros .cpp del
|
||||
// modulo la puedan llamar sin doble include.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// draw_color_rule_menu: llamado desde draw_header_menu (submenu "Conditional color").
|
||||
// Definido en data_table_color_rules.cpp.
|
||||
// Retorna true si el usuario hizo click en "Apply".
|
||||
bool draw_color_rule_menu(State& st, int col, ColumnType col_type,
|
||||
ColorRuleEditorState& editor_st);
|
||||
|
||||
} // namespace data_table
|
||||
@@ -1,10 +1,26 @@
|
||||
---
|
||||
name: data_table
|
||||
version: 1.4.0
|
||||
version: 2.1.0
|
||||
lang: cpp
|
||||
description: "Reusable C++ ImGui module to render a full TQL-aware data table: chips bar, table grid, viz panels, column-stats inline, drill, color rules, joins, TQL editor, Ask AI, Button renderer, event sink, tooltip per-cell. Bundles compute pipeline + TQL stack + Lua engine + viz_render."
|
||||
description: "Reusable C++ ImGui module to render a full TQL-aware data table: chips bar, table grid, viz panels, column-stats inline, drill, color rules, joins, TQL editor, Ask AI, Button renderer, event sink, tooltip per-cell. v2.0.0 splits the 4777 LOC entrypoint into 6 sub-functions. v2.1.0 separates members (module-owned) from uses_functions (consumed registry functions)."
|
||||
# members: funciones del registry que el modulo POSEE (renderizan dentro de el).
|
||||
# Estas funciones viven en cpp/functions/viz/data_table_*.cpp y NO se usan
|
||||
# fuera del modulo. Apps consumidoras NO listan estos miembros en su
|
||||
# `uses_functions` cuando declaran `uses_modules: [data_table_cpp]`.
|
||||
members:
|
||||
- data_table_cpp_viz
|
||||
- data_table_chips_cpp_viz
|
||||
- data_table_grid_cpp_viz
|
||||
- data_table_drill_cpp_viz
|
||||
- data_table_color_rules_cpp_viz
|
||||
- data_table_ai_panel_cpp_viz
|
||||
- data_table_viz_panels_cpp_viz
|
||||
# uses_functions: funciones del registry que el modulo CONSUME pero NO posee.
|
||||
# Estas son utiles fuera del modulo (compute, TQL stack, lua, llm, join, etc.).
|
||||
# Si una app necesita lua_engine/llm_anthropic/join_tables/auto_detect_type
|
||||
# STANDALONE (no a traves de data_table), las declara en su `uses_functions`
|
||||
# directamente. No es duplicacion — es uso independiente.
|
||||
uses_functions:
|
||||
- compute_stage_cpp_core
|
||||
- compute_pipeline_cpp_core
|
||||
- compute_column_stats_cpp_core
|
||||
@@ -44,6 +60,8 @@ Semver. Bumps de version se documentan en `## Capability growth log`. Cambios en
|
||||
|
||||
## Capability growth log
|
||||
|
||||
- v2.0.0 (2026-05-17) — **Major internal refactor (issue 0107c)**: split `data_table.cpp` (4777 LOC entrypoint monolitico) en 6 sub-funciones del registry en `cpp/functions/viz/`: `data_table_chips`, `data_table_grid`, `data_table_drill`, `data_table_color_rules`, `data_table_ai_panel`, `data_table_viz_panels`. Entrypoint reducido a 1818 LOC (custom column modal + render orchestration + `ui()` singleton). Nuevo header interno `modules/data_table/data_table_internal.h` con `UiState` aggregator + helpers compartidos (`ops_for_type`, `op_label`, `effective_type`, `view_mode_label`, `parse_hex_color`, `lerp_color_along_stops`, `filters_hash`, `ColInfo`, `auto_promote_aggregated`, etc.). API publica `data_table::render(...)` INTACTA — apps consumidoras NO necesitan cambios. Cada sub-funcion ahora puede testearse aisladamente (issue 0108 testbed agresivo). Members expandido de 13 a 19 (anaden 6 sub-funciones); `audit_data_table_usage_go_infra` + `fn doctor modules` (0107a) auditan drift contra esta lista.
|
||||
- v1.5.0 (2026-05-17) — Per-table state isolation: `stats_mode/cache`, `sel_*`, `inspect_*`, `drill_*` movidos de UiState singleton a `State` (fix multi-table). Conditional color extendido con tres modos: `CellBg` (legacy), `CategoricalDot` (autopalette de 12 hash-deterministico), `NumericRange` (gradiente N-color sobre bg). Hover del menu contextual de header restaurado.
|
||||
- v1.4.0 (2026-05-16) — CategoricalChip (dot izquierda + text) + ColorScale (gradient N-color en fondo de celda)
|
||||
- v1.3.1 (anterior) — Dots renderer via ImDrawList (font-independent)
|
||||
- v1.3.0 — Dots renderer para sparkline-like de status timelines
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
name: framework
|
||||
version: 1.0.0
|
||||
version: 1.2.1
|
||||
lang: cpp
|
||||
description: "Core C++ ImGui app shell: fn::run_app, AppConfig, GLFW + OpenGL + ImGui + ImPlot bootstrap, theming (Mantine dark + indigo), settings/about/menubar/layouts UI, Tabler icons, logging, viewports & AltSnap-safe sizemove, local_files dir, embedded layout storage."
|
||||
description: "Core C++ ImGui app shell: fn::run_app, AppConfig, GLFW + OpenGL + ImGui + ImPlot bootstrap, theming (Mantine dark + indigo), settings/about/menubar/layouts UI, Tabler icons, logging, multi-viewport & AltSnap-safe sizemove, local_files dir, embedded layout storage, dark titlebar (DWM) en Windows, header badge por-app con el icono del .exe en panels arrastrados fuera del main."
|
||||
members:
|
||||
- tokens_cpp_core
|
||||
- icon_font_cpp_core
|
||||
@@ -43,6 +43,9 @@ Framework does NOT include modules like `data_table`. Apps that want tables opt-
|
||||
|
||||
## Capability growth log
|
||||
|
||||
- v1.2.1 (2026-05-18) — fix: `io.ConfigDockingTransparentPayload = true` en `fn::run_app`. Multi-viewport docking pinta dock preview overlays en el target viewport mientras el payload viewport vive en su propio swap-chain; ambos viewports presentaban frames a destiempo y los rects de dock vibraban 1px contra el payload arrastrado. TransparentPayload hace invisible al payload durante el drag → solo el target dibuja overlays → ningun desync visible. Knob upstream recomendado para "rendering of multiple viewport cannot be synced".
|
||||
- v1.2.0 (2026-05-17) — Header badge en panels arrastrados fuera del main: icono GL extraido del HICON embebido (resource 101, mismo bitmap que el taskbar) y dibujado en el title bar de cada window con `Viewport != main` via iteracion de `g.Windows` con `imgui_internal.h` + `AddImageRounded` sobre `window->DrawList` (no `ForegroundDrawList` — no se renderia en viewports secundarios). Fallback Linux/sin .ico: cuadrado accent redondeado + inicial blanca. Color accent auto-cableado via `codegen_app_modules.py` desde `app.md::icon.accent` (extern `app_header_accent_hex`) con precedencia `cfg.header_badge.accent_hex` > codegen > hash-derived. Permite distinguir de un vistazo de que app viene cada panel flotante con varias apps abiertas.
|
||||
- v1.1.0 (2026-05-17) — Windows dark titlebar. `attach_dark_titlebar_to_hwnd(HWND)` aplica `DWMWA_USE_IMMERSIVE_DARK_MODE = TRUE` via `DwmSetWindowAttribute` sobre el HWND principal + per-frame scan de `pio.Viewports` para cubrir viewports secundarios (paneles dragged-out). Idempotente (set-once por HWND, `g_dark_titlebar_applied`). Sin efecto en Linux/macOS. Cero opt-in: cualquier app que use `fn::run_app` lo hereda.
|
||||
- v1.0.0 (2026-05-16) — Initial framing as a versioned module. Members above are the bundled units of `fn_framework` static lib. Pre-1.0.0 history lives in git.
|
||||
|
||||
## Notes
|
||||
|
||||
Reference in New Issue
Block a user