Files
fn_registry/cpp/functions/viz/data_table_viz_panels.cpp
T
egutierrez b9716a7cd6 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>
2026-05-18 18:17:08 +02:00

430 lines
17 KiB
C++

// 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