// 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 #include #include #include 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* 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 all_names; std::vector num_names; std::vector 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& 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 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::vector()); } 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& active_filters, const std::vector& visible_rows, const std::vector& 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