b9716a7cd6
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>
1158 lines
49 KiB
C++
1158 lines
49 KiB
C++
// data_table_chips — barra de chips superior de la tabla TQL.
|
|
// Sub-funcion extraida de modules/data_table/data_table.cpp (issue 0107c).
|
|
//
|
|
// Contiene: draw_joins_chips, draw_filter_chips, draw_breakout_chips,
|
|
// draw_aggregation_chips, draw_sort_chips, apply_header_sort_click,
|
|
// draw_edit_*_popup, draw_add_*_popup, draw_header_menu, draw_tql_bar.
|
|
|
|
#include "viz/data_table_chips.h"
|
|
#include "data_table/data_table_internal.h" // UiState, ui(), helpers inline
|
|
|
|
#include "core/tql_emit.h"
|
|
#include "core/tql_apply.h"
|
|
#include "core/tql_helpers.h" // parse_breakout_granularity, date_granularity_token
|
|
#include "viz/data_table_color_rules.h" // draw_color_rule_menu
|
|
#include "imgui.h"
|
|
#include "app_base.h" // fn::local_path
|
|
|
|
#include <algorithm>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <ctime>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace data_table {
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Static helpers (solo usados en este TU)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// column_type_icon: returns a Tabler icon UTF-8 sequence for each ColumnType.
|
|
// (duplicado de data_table.cpp — lo dejamos static aqui para no depender del
|
|
// entrypoint; si se necesita compartir mover a data_table_internal.h inline.)
|
|
static const char* column_type_icon_chips(ColumnType t) {
|
|
switch (t) {
|
|
case ColumnType::Auto: return "\xef\xa4\x9d"; // TI_HELP_CIRCLE
|
|
case ColumnType::String: return "\xef\x95\xa7"; // TI_ABC
|
|
case ColumnType::Int: return "\xef\x95\x94"; // TI_123
|
|
case ColumnType::Float: return "\xef\xa8\xa6"; // TI_DECIMAL
|
|
case ColumnType::Bool: return "\xee\xae\xa6"; // TI_CHECKBOX
|
|
case ColumnType::Date: return "\xee\xa9\x93"; // TI_CALENDAR
|
|
case ColumnType::Json: return "\xee\xaf\x8c"; // TI_BRACES
|
|
}
|
|
return "?";
|
|
}
|
|
|
|
// agg_fn_label_chips: nombre del fn de agregacion.
|
|
static const char* agg_fn_label_chips(AggFn f) {
|
|
switch (f) {
|
|
case AggFn::Count: return "count";
|
|
case AggFn::Sum: return "sum";
|
|
case AggFn::Avg: return "avg";
|
|
case AggFn::Min: return "min";
|
|
case AggFn::Max: return "max";
|
|
case AggFn::Distinct: return "distinct";
|
|
case AggFn::Stddev: return "stddev";
|
|
case AggFn::Median: return "median";
|
|
case AggFn::P25: return "p25";
|
|
case AggFn::P75: return "p75";
|
|
case AggFn::P90: return "p90";
|
|
case AggFn::P99: return "p99";
|
|
case AggFn::Percentile: return "percentile";
|
|
}
|
|
return "?";
|
|
}
|
|
|
|
// date helpers (solo para draw_add_breakout_popup)
|
|
namespace {
|
|
|
|
static bool parse_ymd_chips(const std::string& s, int& y, int& m, int& d) {
|
|
if (s.size() < 10) return false;
|
|
for (int i : {0,1,2,3,5,6,8,9}) {
|
|
if (s[(size_t)i] < '0' || s[(size_t)i] > '9') return false;
|
|
}
|
|
if (s[4] != '-' || s[7] != '-') return false;
|
|
y = (s[0]-'0')*1000 + (s[1]-'0')*100 + (s[2]-'0')*10 + (s[3]-'0');
|
|
m = (s[5]-'0')*10 + (s[6]-'0');
|
|
d = (s[8]-'0')*10 + (s[9]-'0');
|
|
if (m < 1 || m > 12 || d < 1 || d > 31) return false;
|
|
return true;
|
|
}
|
|
|
|
static long ymd_to_days_chips(int y, int m, int d) {
|
|
if (m <= 2) { y -= 1; m += 12; }
|
|
long era = (y >= 0 ? y : y - 399) / 400;
|
|
unsigned yoe = (unsigned)(y - era * 400);
|
|
unsigned doy = (unsigned)((153 * (m - 3) + 2) / 5 + d - 1);
|
|
unsigned doe = yoe * 365 + yoe/4 - yoe/100 + doy;
|
|
return era * 146097 + (long)doe;
|
|
}
|
|
|
|
static DateGranularity auto_date_gran_chips(const std::string& min_ymd,
|
|
const std::string& max_ymd) {
|
|
int y1,m1,d1, y2,m2,d2;
|
|
if (!parse_ymd_chips(min_ymd, y1,m1,d1)) return DateGranularity::Day;
|
|
if (!parse_ymd_chips(max_ymd, y2,m2,d2)) return DateGranularity::Day;
|
|
long span = ymd_to_days_chips(y2,m2,d2) - ymd_to_days_chips(y1,m1,d1);
|
|
if (span < 0) span = -span;
|
|
if (span > 730) return DateGranularity::Year;
|
|
if (span > 60) return DateGranularity::Month;
|
|
if (span > 14) return DateGranularity::Week;
|
|
return DateGranularity::Day;
|
|
}
|
|
|
|
static void column_min_max_chips(const char* const* cells, int rows, int cols, int col_idx,
|
|
std::string& min_out, std::string& max_out) {
|
|
min_out.clear(); max_out.clear();
|
|
if (col_idx < 0 || col_idx >= cols) return;
|
|
bool first = true;
|
|
for (int r = 0; r < rows; ++r) {
|
|
const char* v = cells[r * cols + col_idx];
|
|
if (!v || !*v) continue;
|
|
std::string s(v);
|
|
if (first) { min_out = s; max_out = s; first = false; }
|
|
else { if (s < min_out) min_out = s; if (s > max_out) max_out = s; }
|
|
}
|
|
}
|
|
|
|
} // anon
|
|
|
|
// compose_breakout helper (local)
|
|
static std::string compose_breakout_chips(const std::string& col, DateGranularity g) {
|
|
if (g == DateGranularity::None) return col;
|
|
return col + ":" + date_granularity_token(g);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// draw_typed_ops
|
|
// ---------------------------------------------------------------------------
|
|
bool draw_typed_ops(ColumnType t, Op& out) {
|
|
auto ops = ops_for_type(t);
|
|
for (size_t i = 0; i < ops.size(); ++i) {
|
|
if (i % 5 != 0) ImGui::SameLine();
|
|
if (ImGui::SmallButton(op_label(ops[i]))) { out = ops[i]; return true; }
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// type_supports_range
|
|
// ---------------------------------------------------------------------------
|
|
bool type_supports_range(ColumnType t) {
|
|
return t == ColumnType::Int || t == ColumnType::Float || t == ColumnType::Date;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// apply_header_sort_click
|
|
// ---------------------------------------------------------------------------
|
|
void apply_header_sort_click(Stage& stg, const std::string& col_name, bool shift) {
|
|
if (shift) {
|
|
stg.sorts.clear();
|
|
stg.sorts.push_back({col_name, false});
|
|
return;
|
|
}
|
|
int idx = -1;
|
|
for (size_t i = 0; i < stg.sorts.size(); ++i) {
|
|
if (stg.sorts[i].col == col_name) { idx = (int)i; break; }
|
|
}
|
|
if (idx < 0) {
|
|
stg.sorts.push_back({col_name, false});
|
|
} else {
|
|
if (!stg.sorts[idx].desc) stg.sorts[idx].desc = true;
|
|
else stg.sorts.erase(stg.sorts.begin() + idx);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// draw_sort_chips
|
|
// ---------------------------------------------------------------------------
|
|
void draw_sort_chips(Stage& stg) {
|
|
auto& U = ui();
|
|
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(220, 130, 50, 230));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(240, 155, 75, 245));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32(180, 100, 30, 240));
|
|
if (ImGui::SmallButton("+##addsort_btn")) ImGui::OpenPopup("##addsort");
|
|
ImGui::PopStyleColor(3);
|
|
ImGui::SameLine();
|
|
|
|
if (stg.sorts.empty()) {
|
|
ImGui::TextDisabled("Sort: ninguno.");
|
|
return;
|
|
}
|
|
int erase_idx = -1;
|
|
int drag_src = -1;
|
|
int drag_dst = -1;
|
|
for (size_t i = 0; i < stg.sorts.size(); ++i) {
|
|
const auto& sc = stg.sorts[i];
|
|
char buf[256];
|
|
std::snprintf(buf, sizeof(buf), "%zu. %s %s x##srt%zu",
|
|
i + 1, sc.col.c_str(), sc.desc ? "desc" : "asc", i);
|
|
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(220, 130, 50, 230));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(240, 155, 75, 245));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32(180, 100, 30, 240));
|
|
bool clicked = ImGui::SmallButton(buf);
|
|
ImGui::PopStyleColor(3);
|
|
|
|
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) {
|
|
int idx = (int)i;
|
|
ImGui::SetDragDropPayload("##sortreorder", &idx, sizeof(int));
|
|
ImGui::Text("Move sort #%zu", i + 1);
|
|
ImGui::EndDragDropSource();
|
|
}
|
|
if (ImGui::BeginDragDropTarget()) {
|
|
if (const ImGuiPayload* p = ImGui::AcceptDragDropPayload("##sortreorder")) {
|
|
drag_src = *(const int*)p->Data;
|
|
drag_dst = (int)i;
|
|
}
|
|
ImGui::EndDragDropTarget();
|
|
}
|
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
|
U.edit_chip_kind = 4;
|
|
U.edit_chip_idx = (int)i;
|
|
U.edit_value = sc.col;
|
|
U.edit_sort_desc = sc.desc;
|
|
ImGui::OpenPopup("##edit_sort");
|
|
}
|
|
if (clicked) erase_idx = (int)i;
|
|
ImGui::SameLine();
|
|
}
|
|
ImGui::NewLine();
|
|
|
|
if (drag_src >= 0 && drag_dst >= 0 && drag_src != drag_dst &&
|
|
drag_src < (int)stg.sorts.size() && drag_dst < (int)stg.sorts.size())
|
|
{
|
|
SortClause moved = std::move(stg.sorts[drag_src]);
|
|
stg.sorts.erase(stg.sorts.begin() + drag_src);
|
|
int insert_at = drag_dst;
|
|
if (insert_at > (int)stg.sorts.size()) insert_at = (int)stg.sorts.size();
|
|
stg.sorts.insert(stg.sorts.begin() + insert_at, std::move(moved));
|
|
} else if (erase_idx >= 0 && erase_idx < (int)stg.sorts.size()) {
|
|
stg.sorts.erase(stg.sorts.begin() + erase_idx);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// draw_add_sort_popup
|
|
// ---------------------------------------------------------------------------
|
|
void draw_add_sort_popup(Stage& stg, const char* const* in_headers, int in_cols,
|
|
const std::vector<ColumnType>& in_types) {
|
|
auto& U = ui();
|
|
if (!ImGui::BeginPopup("##addsort")) return;
|
|
if (U.sort_picker_col < 0 || U.sort_picker_col >= in_cols) U.sort_picker_col = 0;
|
|
ImGui::SetNextItemWidth(220);
|
|
if (ImGui::BeginCombo("col##sortcol", in_headers[U.sort_picker_col])) {
|
|
for (int c = 0; c < in_cols; ++c) {
|
|
char it[160];
|
|
std::snprintf(it, sizeof(it), "%s %s",
|
|
column_type_icon_chips(in_types[c]), in_headers[c]);
|
|
bool sel = (U.sort_picker_col == c);
|
|
if (ImGui::Selectable(it, sel)) U.sort_picker_col = c;
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
ImGui::Checkbox("desc", &U.sort_picker_desc);
|
|
if (ImGui::Button("Add##srt")) {
|
|
SortClause sc;
|
|
sc.col = in_headers[U.sort_picker_col];
|
|
sc.desc = U.sort_picker_desc;
|
|
stg.sorts.push_back(sc);
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// draw_edit_sort_popup
|
|
// ---------------------------------------------------------------------------
|
|
void draw_edit_sort_popup(Stage& stg, const char* const* headers, int n_cols) {
|
|
auto& U = ui();
|
|
if (!ImGui::BeginPopup("##edit_sort")) return;
|
|
if (U.edit_chip_idx >= 0 && U.edit_chip_idx < (int)stg.sorts.size()) {
|
|
ImGui::SetNextItemWidth(240);
|
|
if (ImGui::BeginCombo("col", U.edit_value.c_str())) {
|
|
for (int c = 0; c < n_cols; ++c) {
|
|
bool sel = (U.edit_value == headers[c]);
|
|
if (ImGui::Selectable(headers[c], sel)) U.edit_value = headers[c];
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
if (ImGui::RadioButton("asc", !U.edit_sort_desc)) U.edit_sort_desc = false;
|
|
ImGui::SameLine();
|
|
if (ImGui::RadioButton("desc", U.edit_sort_desc)) U.edit_sort_desc = true;
|
|
if (ImGui::Button("Save")) {
|
|
auto& sc = stg.sorts[U.edit_chip_idx];
|
|
sc.col = U.edit_value;
|
|
sc.desc = U.edit_sort_desc;
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Cancel")) ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// draw_filter_chips
|
|
// ---------------------------------------------------------------------------
|
|
void draw_filter_chips(Stage& stg, const char* const* eff_headers, int eff_cols,
|
|
const std::vector<ColumnType>& eff_types) {
|
|
auto& U = ui();
|
|
// Preset filter helpers needed locally
|
|
auto today_iso_chips = []() -> std::string {
|
|
std::time_t t = std::time(nullptr);
|
|
std::tm tm = *std::gmtime(&t);
|
|
char buf[16];
|
|
std::snprintf(buf, sizeof(buf), "%04d-%02d-%02d",
|
|
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
|
|
return buf;
|
|
};
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(120, 60, 170, 220));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(150, 85, 200, 240));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32( 95, 45, 140, 240));
|
|
if (ImGui::SmallButton("+##addfilter_btn")) ImGui::OpenPopup("##addfilter");
|
|
ImGui::PopStyleColor(3);
|
|
ImGui::SameLine();
|
|
|
|
// Presets menu
|
|
if (ImGui::SmallButton("Presets##fpresets")) ImGui::OpenPopup("##presets_menu");
|
|
if (ImGui::BeginPopup("##presets_menu")) {
|
|
int first_date = -1, first_num = -1;
|
|
for (int c = 0; c < eff_cols && c < (int)eff_types.size(); ++c) {
|
|
if (first_date < 0 && eff_types[c] == ColumnType::Date) first_date = c;
|
|
if (first_num < 0 && (eff_types[c] == ColumnType::Int ||
|
|
eff_types[c] == ColumnType::Float)) first_num = c;
|
|
}
|
|
// build_preset_filters lambda
|
|
auto build_pf = [&](FilterPreset preset, int col) -> std::vector<Filter> {
|
|
std::vector<Filter> out;
|
|
std::string today = today_iso_chips();
|
|
auto last_n = [&](int n) {
|
|
int y, m, d;
|
|
if (!parse_ymd_chips(today, y, m, d)) return;
|
|
long days = ymd_to_days_chips(y, m, d) - n;
|
|
// reverse
|
|
long era = (days >= 0 ? days : days - 146096) / 146097;
|
|
unsigned doe = (unsigned)(days - era * 146097);
|
|
unsigned yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365;
|
|
int yr = (int)yoe + (int)era * 400;
|
|
unsigned doy2 = doe - (365*yoe + yoe/4 - yoe/100);
|
|
unsigned mp = (5*doy2 + 2)/153;
|
|
unsigned day = doy2 - (153*mp + 2)/5 + 1;
|
|
unsigned mon = mp < 10 ? mp + 3 : mp - 9;
|
|
if (mon <= 2) yr += 1;
|
|
char buf[16];
|
|
std::snprintf(buf, sizeof(buf), "%04d-%02d-%02d", yr, (int)mon, (int)day);
|
|
Filter f; f.col = col; f.op = Op::Gte; f.value = buf;
|
|
out.push_back(f);
|
|
};
|
|
switch (preset) {
|
|
case FilterPreset::Last7d: last_n(7); break;
|
|
case FilterPreset::Last30d: last_n(30); break;
|
|
case FilterPreset::Last90d: last_n(90); break;
|
|
case FilterPreset::ExcludeNulls: {
|
|
Filter f; f.col = col; f.op = Op::Neq; f.value = "";
|
|
out.push_back(f); break;
|
|
}
|
|
case FilterPreset::NonZero: {
|
|
Filter f1; f1.col = col; f1.op = Op::Neq; f1.value = "";
|
|
Filter f2; f2.col = col; f2.op = Op::Neq; f2.value = "0";
|
|
out.push_back(f1); out.push_back(f2); break;
|
|
}
|
|
}
|
|
return out;
|
|
};
|
|
auto apply_preset = [&](FilterPreset p, int col) {
|
|
auto fs = build_pf(p, col);
|
|
for (auto& f : fs) stg.filters.push_back(f);
|
|
};
|
|
|
|
if (first_date >= 0) {
|
|
char l1[96], l2[96], l3[96];
|
|
std::snprintf(l1, sizeof(l1), "Last 7 days on \"%s\"", eff_headers[first_date]);
|
|
std::snprintf(l2, sizeof(l2), "Last 30 days on \"%s\"", eff_headers[first_date]);
|
|
std::snprintf(l3, sizeof(l3), "Last 90 days on \"%s\"", eff_headers[first_date]);
|
|
if (ImGui::MenuItem(l1)) apply_preset(FilterPreset::Last7d, first_date);
|
|
if (ImGui::MenuItem(l2)) apply_preset(FilterPreset::Last30d, first_date);
|
|
if (ImGui::MenuItem(l3)) apply_preset(FilterPreset::Last90d, first_date);
|
|
ImGui::Separator();
|
|
}
|
|
if (ImGui::BeginMenu("Exclude nulls in...")) {
|
|
for (int c = 0; c < eff_cols; ++c) {
|
|
if (ImGui::MenuItem(eff_headers[c])) apply_preset(FilterPreset::ExcludeNulls, c);
|
|
}
|
|
ImGui::EndMenu();
|
|
}
|
|
if (first_num >= 0) {
|
|
if (ImGui::BeginMenu("Non-zero in...")) {
|
|
for (int c = 0; c < eff_cols && c < (int)eff_types.size(); ++c) {
|
|
if (eff_types[c] == ColumnType::Int || eff_types[c] == ColumnType::Float) {
|
|
if (ImGui::MenuItem(eff_headers[c])) apply_preset(FilterPreset::NonZero, c);
|
|
}
|
|
}
|
|
ImGui::EndMenu();
|
|
}
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
ImGui::SameLine();
|
|
|
|
if (stg.filters.empty()) {
|
|
ImGui::TextDisabled("Sin filtros.");
|
|
return;
|
|
}
|
|
for (size_t i = 0; i < stg.filters.size(); ) {
|
|
const auto& f = stg.filters[i];
|
|
const char* hdr = (f.col >= 0 && f.col < eff_cols) ? eff_headers[f.col] : "?";
|
|
char buf[256];
|
|
std::snprintf(buf, sizeof(buf), "%s %s %s x##chip%zu",
|
|
hdr, op_label(f.op), f.value.c_str(), i);
|
|
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(120, 60, 170, 220));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(150, 85, 200, 240));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32( 95, 45, 140, 240));
|
|
bool clicked = ImGui::SmallButton(buf);
|
|
ImGui::PopStyleColor(3);
|
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
|
U.edit_chip_kind = 1;
|
|
U.edit_chip_idx = (int)i;
|
|
U.edit_col_idx = f.col;
|
|
U.edit_op = (int)f.op;
|
|
U.edit_value = f.value;
|
|
ImGui::OpenPopup("##edit_filter");
|
|
}
|
|
if (clicked) { stg.filters.erase(stg.filters.begin() + i); continue; }
|
|
ImGui::SameLine();
|
|
++i;
|
|
}
|
|
ImGui::NewLine();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// draw_add_filter_popup
|
|
// ---------------------------------------------------------------------------
|
|
void draw_add_filter_popup(Stage& stg, const char* const* eff_headers_arr, int eff_cols,
|
|
const std::vector<ColumnType>& eff_types) {
|
|
auto& U = ui();
|
|
if (!ImGui::BeginPopup("##addfilter")) return;
|
|
if (U.addf_col < 0 || U.addf_col >= eff_cols) U.addf_col = 0;
|
|
ImGui::SetNextItemWidth(220);
|
|
if (ImGui::BeginCombo("col", eff_headers_arr[U.addf_col])) {
|
|
for (int c = 0; c < eff_cols; ++c) {
|
|
char it[160];
|
|
std::snprintf(it, sizeof(it), "%s %s",
|
|
column_type_icon_chips(eff_types[c]), eff_headers_arr[c]);
|
|
bool sel = (U.addf_col == c);
|
|
if (ImGui::Selectable(it, sel)) U.addf_col = c;
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
ColumnType t = eff_types[U.addf_col];
|
|
ImGui::TextDisabled("type: %s %s", column_type_icon_chips(t), column_type_name(t));
|
|
|
|
bool can_range = type_supports_range(t);
|
|
if (can_range) ImGui::Checkbox("Range (min/max)", &U.addf_range);
|
|
else U.addf_range = false;
|
|
|
|
if (!U.addf_range) {
|
|
char buf[256] = {0};
|
|
std::snprintf(buf, sizeof(buf), "%s", U.addf_val.c_str());
|
|
ImGui::SetNextItemWidth(220);
|
|
if (ImGui::InputText("val", buf, sizeof(buf))) U.addf_val = buf;
|
|
Op picked;
|
|
if (draw_typed_ops(t, picked)) {
|
|
stg.filters.push_back({U.addf_col, picked, U.addf_val});
|
|
U.addf_val.clear();
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
} else {
|
|
char lo[128] = {0}, hi[128] = {0};
|
|
std::snprintf(lo, sizeof(lo), "%s", U.addf_lo.c_str());
|
|
std::snprintf(hi, sizeof(hi), "%s", U.addf_hi.c_str());
|
|
ImGui::SetNextItemWidth(100);
|
|
if (ImGui::InputText("min", lo, sizeof(lo))) U.addf_lo = lo;
|
|
ImGui::SameLine();
|
|
ImGui::SetNextItemWidth(100);
|
|
if (ImGui::InputText("max", hi, sizeof(hi))) U.addf_hi = hi;
|
|
ImGui::SameLine();
|
|
if (ImGui::SmallButton("Add range")) {
|
|
if (!U.addf_lo.empty()) stg.filters.push_back({U.addf_col, Op::Gte, U.addf_lo});
|
|
if (!U.addf_hi.empty()) stg.filters.push_back({U.addf_col, Op::Lte, U.addf_hi});
|
|
U.addf_lo.clear(); U.addf_hi.clear();
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// draw_edit_filter_popup
|
|
// ---------------------------------------------------------------------------
|
|
void draw_edit_filter_popup(Stage& stg, const char* const* headers, int n_cols,
|
|
const std::vector<ColumnType>& types) {
|
|
auto& U = ui();
|
|
if (!ImGui::BeginPopup("##edit_filter")) return;
|
|
if (U.edit_chip_idx >= 0 && U.edit_chip_idx < (int)stg.filters.size()) {
|
|
auto& f = stg.filters[U.edit_chip_idx];
|
|
ImGui::SetNextItemWidth(200);
|
|
const char* cur = (U.edit_col_idx >= 0 && U.edit_col_idx < n_cols)
|
|
? headers[U.edit_col_idx] : "?";
|
|
if (ImGui::BeginCombo("col", cur)) {
|
|
for (int c = 0; c < n_cols; ++c) {
|
|
bool sel = (U.edit_col_idx == c);
|
|
if (ImGui::Selectable(headers[c], sel)) U.edit_col_idx = c;
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
ColumnType t = (U.edit_col_idx >= 0 && U.edit_col_idx < (int)types.size())
|
|
? types[U.edit_col_idx] : ColumnType::String;
|
|
auto ops = ops_for_type(t);
|
|
ImGui::SetNextItemWidth(140);
|
|
if (ImGui::BeginCombo("op", op_label((Op)U.edit_op))) {
|
|
for (auto o : ops) {
|
|
bool sel = ((int)o == U.edit_op);
|
|
if (ImGui::Selectable(op_label(o), sel)) U.edit_op = (int)o;
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
char vbuf[256] = {0};
|
|
std::snprintf(vbuf, sizeof(vbuf), "%s", U.edit_value.c_str());
|
|
ImGui::SetNextItemWidth(220);
|
|
if (ImGui::InputText("value", vbuf, sizeof(vbuf))) U.edit_value = vbuf;
|
|
if (ImGui::Button("Save")) {
|
|
f.col = U.edit_col_idx;
|
|
f.op = (Op)U.edit_op;
|
|
f.value = U.edit_value;
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Cancel")) ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// draw_breakout_chips
|
|
// ---------------------------------------------------------------------------
|
|
void draw_breakout_chips(Stage& stg, const char* const* in_headers, int in_cols,
|
|
const std::vector<ColumnType>& in_types) {
|
|
auto& U = ui();
|
|
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32( 60, 160, 170, 220));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32( 80, 190, 200, 240));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32( 40, 130, 140, 240));
|
|
if (ImGui::SmallButton("+##addbreakout_btn")) ImGui::OpenPopup("##addbreakout");
|
|
ImGui::PopStyleColor(3);
|
|
ImGui::SameLine();
|
|
|
|
if (stg.breakouts.empty()) {
|
|
ImGui::TextDisabled("Group by: ninguna col.");
|
|
return;
|
|
}
|
|
for (size_t i = 0; i < stg.breakouts.size(); ) {
|
|
std::string col_name;
|
|
DateGranularity g = parse_breakout_granularity(stg.breakouts[i], col_name);
|
|
|
|
int col_idx = -1;
|
|
for (int c = 0; c < in_cols; ++c) {
|
|
if (std::strcmp(in_headers[c], col_name.c_str()) == 0) { col_idx = c; break; }
|
|
}
|
|
bool is_date_col = (col_idx >= 0 && col_idx < (int)in_types.size()
|
|
&& in_types[col_idx] == ColumnType::Date);
|
|
|
|
char buf[256];
|
|
std::snprintf(buf, sizeof(buf), "%s x##bk%zu", stg.breakouts[i].c_str(), i);
|
|
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32( 60, 160, 170, 220));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32( 80, 190, 200, 240));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32( 40, 130, 140, 240));
|
|
bool clicked = ImGui::SmallButton(buf);
|
|
ImGui::PopStyleColor(3);
|
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
|
U.edit_chip_kind = 2;
|
|
U.edit_chip_idx = (int)i;
|
|
U.edit_col_idx = (col_idx >= 0) ? col_idx : 0;
|
|
ImGui::OpenPopup("##edit_breakout");
|
|
}
|
|
if (clicked) { stg.breakouts.erase(stg.breakouts.begin() + i); continue; }
|
|
|
|
if (is_date_col) {
|
|
ImGui::SameLine();
|
|
const char* preview = (g == DateGranularity::None)
|
|
? "(raw)" : date_granularity_token(g);
|
|
char combo_id[32];
|
|
std::snprintf(combo_id, sizeof(combo_id), "##gran%zu", i);
|
|
ImGui::SetNextItemWidth(72);
|
|
if (ImGui::BeginCombo(combo_id, preview)) {
|
|
DateGranularity opts[] = {
|
|
DateGranularity::None,
|
|
DateGranularity::Year,
|
|
DateGranularity::Month,
|
|
DateGranularity::Week,
|
|
DateGranularity::Day,
|
|
DateGranularity::Hour,
|
|
};
|
|
for (auto o : opts) {
|
|
const char* lbl = (o == DateGranularity::None)
|
|
? "(raw)" : date_granularity_token(o);
|
|
if (ImGui::Selectable(lbl, o == g)) {
|
|
stg.breakouts[i] = compose_breakout_chips(col_name, o);
|
|
}
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
}
|
|
ImGui::SameLine();
|
|
++i;
|
|
}
|
|
ImGui::NewLine();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// draw_add_breakout_popup
|
|
// ---------------------------------------------------------------------------
|
|
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) {
|
|
auto& U = ui();
|
|
if (!ImGui::BeginPopup("##addbreakout")) return;
|
|
if (U.brk_picker_col < 0 || U.brk_picker_col >= in_cols) U.brk_picker_col = 0;
|
|
ImGui::SetNextItemWidth(220);
|
|
if (ImGui::BeginCombo("col##bkcol", in_headers[U.brk_picker_col])) {
|
|
for (int c = 0; c < in_cols; ++c) {
|
|
char it[160];
|
|
std::snprintf(it, sizeof(it), "%s %s",
|
|
column_type_icon_chips(in_types[c]), in_headers[c]);
|
|
bool sel = (U.brk_picker_col == c);
|
|
if (ImGui::Selectable(it, sel)) U.brk_picker_col = c;
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
if (ImGui::Button("Add##bk")) {
|
|
int c = U.brk_picker_col;
|
|
std::string col = in_headers[c];
|
|
if (c >= 0 && c < (int)in_types.size() && in_types[c] == ColumnType::Date) {
|
|
std::string lo, hi;
|
|
column_min_max_chips(cur_cells, cur_rows, in_cols, c, lo, hi);
|
|
DateGranularity g = auto_date_gran_chips(lo, hi);
|
|
stg.breakouts.emplace_back(compose_breakout_chips(col, g));
|
|
} else {
|
|
stg.breakouts.emplace_back(col);
|
|
}
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// draw_edit_breakout_popup
|
|
// ---------------------------------------------------------------------------
|
|
void draw_edit_breakout_popup(Stage& stg, const char* const* headers, int n_cols) {
|
|
auto& U = ui();
|
|
if (!ImGui::BeginPopup("##edit_breakout")) return;
|
|
if (U.edit_chip_idx >= 0 && U.edit_chip_idx < (int)stg.breakouts.size()) {
|
|
ImGui::SetNextItemWidth(240);
|
|
const char* cur = (U.edit_col_idx >= 0 && U.edit_col_idx < n_cols)
|
|
? headers[U.edit_col_idx] : "?";
|
|
if (ImGui::BeginCombo("col", cur)) {
|
|
for (int c = 0; c < n_cols; ++c) {
|
|
bool sel = (U.edit_col_idx == c);
|
|
if (ImGui::Selectable(headers[c], sel)) U.edit_col_idx = c;
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
if (ImGui::Button("Save")) {
|
|
if (U.edit_col_idx >= 0 && U.edit_col_idx < n_cols)
|
|
stg.breakouts[U.edit_chip_idx] = headers[U.edit_col_idx];
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Cancel")) ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// draw_aggregation_chips
|
|
// ---------------------------------------------------------------------------
|
|
void draw_aggregation_chips(Stage& stg, const char* const* in_headers, int in_cols) {
|
|
auto& U = ui();
|
|
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32( 40, 140, 60, 220));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32( 60, 170, 85, 240));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32( 30, 110, 45, 240));
|
|
if (ImGui::SmallButton("+##addagg_btn")) ImGui::OpenPopup("##addagg");
|
|
ImGui::PopStyleColor(3);
|
|
ImGui::SameLine();
|
|
|
|
if (stg.aggregations.empty()) {
|
|
ImGui::TextDisabled("Aggregations: ninguna.");
|
|
return;
|
|
}
|
|
for (size_t i = 0; i < stg.aggregations.size(); ) {
|
|
const auto& a = stg.aggregations[i];
|
|
char buf[256];
|
|
if (a.fn == AggFn::Count) {
|
|
std::snprintf(buf, sizeof(buf), "count x##ag%zu", i);
|
|
} else if (a.fn == AggFn::Percentile) {
|
|
std::snprintf(buf, sizeof(buf), "percentile(%s, %g) x##ag%zu",
|
|
a.col.c_str(), a.arg, i);
|
|
} else {
|
|
std::snprintf(buf, sizeof(buf), "%s(%s) x##ag%zu",
|
|
agg_fn_label_chips(a.fn), a.col.c_str(), i);
|
|
}
|
|
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32( 40, 140, 60, 220));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32( 60, 170, 85, 240));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32( 30, 110, 45, 240));
|
|
bool clicked = ImGui::SmallButton(buf);
|
|
ImGui::PopStyleColor(3);
|
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
|
U.edit_chip_kind = 3;
|
|
U.edit_chip_idx = (int)i;
|
|
U.edit_agg_fn = (int)a.fn;
|
|
U.edit_agg_arg = a.arg;
|
|
U.edit_col_idx = 0;
|
|
for (int c = 0; c < in_cols; ++c) {
|
|
if (std::strcmp(in_headers[c], a.col.c_str()) == 0) {
|
|
U.edit_col_idx = c; break;
|
|
}
|
|
}
|
|
ImGui::OpenPopup("##edit_agg");
|
|
}
|
|
if (clicked) { stg.aggregations.erase(stg.aggregations.begin() + i); continue; }
|
|
ImGui::SameLine();
|
|
++i;
|
|
}
|
|
(void)in_headers; (void)in_cols;
|
|
ImGui::NewLine();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// draw_add_aggregation_popup
|
|
// ---------------------------------------------------------------------------
|
|
void draw_add_aggregation_popup(Stage& stg, const char* const* in_headers, int in_cols,
|
|
const std::vector<ColumnType>& in_types) {
|
|
auto& U = ui();
|
|
if (!ImGui::BeginPopup("##addagg")) return;
|
|
|
|
AggFn cur_fn = (AggFn)U.agg_picker_fn;
|
|
ImGui::SetNextItemWidth(160);
|
|
if (ImGui::BeginCombo("fn##aggfn", agg_fn_label_chips(cur_fn))) {
|
|
AggFn all[] = {AggFn::Count, AggFn::Sum, AggFn::Avg, AggFn::Min, AggFn::Max,
|
|
AggFn::Distinct, AggFn::Stddev, AggFn::Median,
|
|
AggFn::P25, AggFn::P75, AggFn::P90, AggFn::P99,
|
|
AggFn::Percentile};
|
|
for (AggFn f : all) {
|
|
bool sel = (f == cur_fn);
|
|
if (ImGui::Selectable(agg_fn_label_chips(f), sel)) U.agg_picker_fn = (int)f;
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
if (cur_fn != AggFn::Count) {
|
|
if (U.agg_picker_col < 0 || U.agg_picker_col >= in_cols) U.agg_picker_col = 0;
|
|
ImGui::SetNextItemWidth(220);
|
|
if (ImGui::BeginCombo("col##aggcol", in_headers[U.agg_picker_col])) {
|
|
for (int c = 0; c < in_cols; ++c) {
|
|
char it[160];
|
|
std::snprintf(it, sizeof(it), "%s %s",
|
|
column_type_icon_chips(in_types[c]), in_headers[c]);
|
|
bool sel = (U.agg_picker_col == c);
|
|
if (ImGui::Selectable(it, sel)) U.agg_picker_col = c;
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
}
|
|
if (cur_fn == AggFn::Percentile) {
|
|
double v = U.agg_picker_arg;
|
|
ImGui::SetNextItemWidth(120);
|
|
if (ImGui::InputDouble("p (0..1)", &v, 0.05, 0.1, "%.2f")) {
|
|
if (v < 0) v = 0; if (v > 1) v = 1;
|
|
U.agg_picker_arg = v;
|
|
}
|
|
}
|
|
if (ImGui::Button("Add##ag")) {
|
|
Aggregation a;
|
|
a.fn = cur_fn;
|
|
a.col = (cur_fn == AggFn::Count) ? "" : std::string(in_headers[U.agg_picker_col]);
|
|
a.arg = (cur_fn == AggFn::Percentile) ? U.agg_picker_arg : 0.0;
|
|
stg.aggregations.push_back(a);
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// draw_edit_agg_popup
|
|
// ---------------------------------------------------------------------------
|
|
void draw_edit_agg_popup(Stage& stg, const char* const* headers, int n_cols) {
|
|
auto& U = ui();
|
|
if (!ImGui::BeginPopup("##edit_agg")) return;
|
|
if (U.edit_chip_idx >= 0 && U.edit_chip_idx < (int)stg.aggregations.size()) {
|
|
const AggFn all_fns[] = {AggFn::Count, AggFn::Sum, AggFn::Avg, AggFn::Min, AggFn::Max,
|
|
AggFn::Distinct, AggFn::Stddev, AggFn::Median,
|
|
AggFn::P25, AggFn::P75, AggFn::P90, AggFn::P99,
|
|
AggFn::Percentile};
|
|
ImGui::SetNextItemWidth(160);
|
|
if (ImGui::BeginCombo("fn", agg_fn_label_chips((AggFn)U.edit_agg_fn))) {
|
|
for (auto f : all_fns) {
|
|
bool sel = ((int)f == U.edit_agg_fn);
|
|
if (ImGui::Selectable(agg_fn_label_chips(f), sel)) U.edit_agg_fn = (int)f;
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
if ((AggFn)U.edit_agg_fn != AggFn::Count) {
|
|
ImGui::SetNextItemWidth(200);
|
|
const char* cur = (U.edit_col_idx >= 0 && U.edit_col_idx < n_cols)
|
|
? headers[U.edit_col_idx] : "?";
|
|
if (ImGui::BeginCombo("col", cur)) {
|
|
for (int c = 0; c < n_cols; ++c) {
|
|
bool sel = (U.edit_col_idx == c);
|
|
if (ImGui::Selectable(headers[c], sel)) U.edit_col_idx = c;
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
}
|
|
if ((AggFn)U.edit_agg_fn == AggFn::Percentile) {
|
|
float v = (float)U.edit_agg_arg;
|
|
ImGui::SetNextItemWidth(140);
|
|
if (ImGui::InputFloat("p (0..1)", &v, 0.05f, 0.1f, "%.2f"))
|
|
U.edit_agg_arg = v;
|
|
}
|
|
if (ImGui::Button("Save")) {
|
|
auto& a = stg.aggregations[U.edit_chip_idx];
|
|
a.fn = (AggFn)U.edit_agg_fn;
|
|
if (a.fn != AggFn::Count && U.edit_col_idx >= 0 && U.edit_col_idx < n_cols)
|
|
a.col = headers[U.edit_col_idx];
|
|
else if (a.fn == AggFn::Count) a.col.clear();
|
|
a.arg = U.edit_agg_arg;
|
|
a.alias.clear();
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Cancel")) ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// draw_joins_chips
|
|
// Firma del header: (State&, const std::vector<TableInput>&, const char* const*, int, const std::vector<ColumnType>&)
|
|
// ---------------------------------------------------------------------------
|
|
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*/) {
|
|
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32( 80, 130, 90, 220));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(100, 160, 110, 240));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32( 60, 110, 70, 220));
|
|
ImGui::TextDisabled("Joins:");
|
|
ImGui::SameLine();
|
|
|
|
int remove_idx = -1;
|
|
for (size_t i = 0; i < st.joins.size(); ++i) {
|
|
const auto& jn = st.joins[i];
|
|
char lbl[256];
|
|
std::string on_str;
|
|
for (size_t k = 0; k < jn.on.size(); ++k) {
|
|
if (k) on_str += ",";
|
|
on_str += jn.on[k].first + "=" + jn.on[k].second;
|
|
}
|
|
std::snprintf(lbl, sizeof(lbl), "%s <- %s on %s (%s)##join_%zu",
|
|
jn.alias.empty() ? "_" : jn.alias.c_str(),
|
|
jn.source.c_str(),
|
|
on_str.c_str(),
|
|
join_strategy_label(jn.strategy),
|
|
i);
|
|
ImGui::Button(lbl);
|
|
ImGui::SameLine();
|
|
char xlbl[32]; std::snprintf(xlbl, sizeof(xlbl), "x##rm_join_%zu", i);
|
|
if (ImGui::SmallButton(xlbl)) remove_idx = (int)i;
|
|
ImGui::SameLine();
|
|
}
|
|
if (remove_idx >= 0) st.joins.erase(st.joins.begin() + remove_idx);
|
|
|
|
if (ImGui::SmallButton("+##add_join")) {
|
|
ImGui::OpenPopup("##add_join_popup");
|
|
}
|
|
ImGui::PopStyleColor(3);
|
|
|
|
// Popup add-join. State variables are static (one join popup at a time).
|
|
static int pick_source_idx = 0;
|
|
static char pick_alias[64] = "";
|
|
static int pick_strategy = 0;
|
|
static int pick_left_col = 0;
|
|
static int pick_right_col = 0;
|
|
if (ImGui::BeginPopup("##add_join_popup")) {
|
|
ImGui::Text("Add join");
|
|
if (pick_source_idx >= (int)joinables.size()) pick_source_idx = 0;
|
|
ImGui::SetNextItemWidth(180);
|
|
if (ImGui::BeginCombo("source", joinables[pick_source_idx].name.c_str())) {
|
|
for (int k = 0; k < (int)joinables.size(); ++k) {
|
|
bool sel = (k == pick_source_idx);
|
|
if (ImGui::Selectable(joinables[k].name.c_str(), sel)) {
|
|
pick_source_idx = k;
|
|
pick_right_col = 0;
|
|
if (pick_alias[0] == 0)
|
|
std::snprintf(pick_alias, sizeof(pick_alias), "%s", joinables[k].name.c_str());
|
|
}
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
ImGui::SetNextItemWidth(180);
|
|
ImGui::InputText("alias", pick_alias, sizeof(pick_alias));
|
|
|
|
const char* strategies[] = {"left", "inner", "right", "full"};
|
|
ImGui::SetNextItemWidth(120);
|
|
ImGui::Combo("strategy", &pick_strategy, strategies, IM_ARRAYSIZE(strategies));
|
|
|
|
// left col combo (de eff_headers)
|
|
ImGui::SetNextItemWidth(180);
|
|
const char* lcur = (pick_left_col >= 0 && pick_left_col < eff_cols)
|
|
? eff_headers[pick_left_col] : "?";
|
|
if (ImGui::BeginCombo("left col", lcur)) {
|
|
for (int k = 0; k < eff_cols; ++k) {
|
|
bool sel = (k == pick_left_col);
|
|
if (ImGui::Selectable(eff_headers[k], sel)) pick_left_col = k;
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
|
|
// right col combo (de joinables[pick_source_idx].headers)
|
|
const TableInput& src = joinables[pick_source_idx];
|
|
const char* rcur = (pick_right_col >= 0 && pick_right_col < (int)src.headers.size())
|
|
? src.headers[pick_right_col].c_str() : "?";
|
|
ImGui::SetNextItemWidth(180);
|
|
if (ImGui::BeginCombo("right col", rcur)) {
|
|
for (int k = 0; k < (int)src.headers.size(); ++k) {
|
|
bool sel = (k == pick_right_col);
|
|
if (ImGui::Selectable(src.headers[k].c_str(), sel)) pick_right_col = k;
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
|
|
ImGui::Separator();
|
|
if (ImGui::SmallButton("Add") && pick_left_col < eff_cols &&
|
|
pick_right_col < (int)src.headers.size()) {
|
|
Join jn;
|
|
jn.alias = pick_alias;
|
|
jn.source = src.name;
|
|
jn.on.push_back({std::string(eff_headers[pick_left_col]),
|
|
src.headers[pick_right_col]});
|
|
jn.strategy = (JoinStrategy)pick_strategy;
|
|
st.joins.push_back(jn);
|
|
pick_alias[0] = 0;
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::SmallButton("Cancel")) ImGui::CloseCurrentPopup();
|
|
|
|
ImGui::EndPopup();
|
|
}
|
|
ImGui::NewLine();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// draw_header_menu
|
|
// ---------------------------------------------------------------------------
|
|
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) {
|
|
auto& U = ui();
|
|
ColumnType t = eff_types[col];
|
|
|
|
if (ImGui::MenuItem("Sort ascending")) {
|
|
stg.sorts.clear();
|
|
stg.sorts.push_back({eff_headers_arr[col], false});
|
|
}
|
|
if (ImGui::MenuItem("Sort descending")) {
|
|
stg.sorts.clear();
|
|
stg.sorts.push_back({eff_headers_arr[col], true});
|
|
}
|
|
if (!stg.sorts.empty() && ImGui::MenuItem("Clear sort")) stg.sorts.clear();
|
|
ImGui::Separator();
|
|
|
|
auto& fbuf = U.filter_inputs[col];
|
|
fbuf.resize(256, '\0');
|
|
if (ImGui::BeginMenu("Filter...")) {
|
|
ImGui::SetNextItemWidth(220);
|
|
ImGui::InputText("##filterval", fbuf.data(), fbuf.size());
|
|
std::string val(fbuf.c_str());
|
|
auto ops = ops_for_type(t);
|
|
for (size_t i = 0; i < ops.size(); ++i) {
|
|
if (i % 5 != 0) ImGui::SameLine();
|
|
if (ImGui::SmallButton(op_label(ops[i]))) {
|
|
stg.filters.push_back({col, ops[i], val});
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
}
|
|
ImGui::EndMenu();
|
|
}
|
|
|
|
if (is_raw_stage) {
|
|
if (ImGui::BeginMenu("Change type")) {
|
|
const ColumnType types[] = {
|
|
ColumnType::String, ColumnType::Int, ColumnType::Float,
|
|
ColumnType::Bool, ColumnType::Date, ColumnType::Json
|
|
};
|
|
for (auto nt : types) {
|
|
char lab[64];
|
|
std::snprintf(lab, sizeof(lab), "%s %s",
|
|
column_type_icon_chips(nt), column_type_name(nt));
|
|
if (ImGui::MenuItem(lab)) {
|
|
DerivedColumn d;
|
|
d.source_col = (col < orig_cols) ? col : stg.derived[col - orig_cols].source_col;
|
|
d.type = nt;
|
|
d.name = std::string(eff_headers_arr[col]) + "_" + column_type_name(nt);
|
|
stg.derived.push_back(d);
|
|
}
|
|
}
|
|
ImGui::EndMenu();
|
|
}
|
|
}
|
|
|
|
if (ImGui::BeginMenu("Conditional color")) {
|
|
draw_color_rule_menu(st, col, t, U.color_rules);
|
|
ImGui::EndMenu();
|
|
}
|
|
|
|
if (ImGui::MenuItem("Hide column")) st.col_visible[col] = false;
|
|
|
|
if (is_raw_stage && col >= orig_cols && ImGui::MenuItem("Remove derived column")) {
|
|
int k = col - orig_cols;
|
|
stg.derived.erase(stg.derived.begin() + k);
|
|
}
|
|
|
|
ImGui::Separator();
|
|
if (ImGui::BeginMenu("Columns")) {
|
|
for (int k = 0; k < eff_cols; ++k) {
|
|
bool v = st.col_visible[k];
|
|
char lab[160];
|
|
std::snprintf(lab, sizeof(lab), "%s %s",
|
|
column_type_icon_chips(eff_types[k]), eff_headers_arr[k]);
|
|
if (ImGui::Checkbox(lab, &v)) st.col_visible[k] = v;
|
|
}
|
|
if (ImGui::MenuItem("Show all")) {
|
|
for (int k = 0; k < eff_cols; ++k) st.col_visible[k] = true;
|
|
}
|
|
ImGui::EndMenu();
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// draw_tql_bar
|
|
// Extrae el bloque TQL de render() (Show/Apply/Save/Load + modales).
|
|
// En el entrypoint data_table.cpp, reemplazar el bloque inline por llamadas
|
|
// a draw_tql_bar(U.tql_bar, st, orig_headers, orig_types, cells, row_count, orig_cols).
|
|
// ---------------------------------------------------------------------------
|
|
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) {
|
|
if (ImGui::SmallButton("Show TQL")) {
|
|
tql_bar.show_text = tql::emit(st, orig_headers, orig_types);
|
|
tql_bar.show_open = true;
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::SmallButton("Apply TQL")) {
|
|
tql_bar.apply_open = true;
|
|
tql_bar.apply_error.clear();
|
|
}
|
|
ImGui::SameLine();
|
|
ImGui::SetNextItemWidth(160);
|
|
ImGui::InputText("##tql_file", tql_bar.file_path, sizeof(tql_bar.file_path));
|
|
ImGui::SameLine();
|
|
if (ImGui::SmallButton("Save .tql")) {
|
|
std::string text = tql::emit(st, orig_headers, orig_types);
|
|
const char* path = fn::local_path(tql_bar.file_path);
|
|
std::ofstream f(path);
|
|
if (f) { f << text; tql_bar.io_status = std::string("saved: ") + path; }
|
|
else { tql_bar.io_status = std::string("save FAILED: ") + path; }
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::SmallButton("Load .tql")) {
|
|
const char* path = fn::local_path(tql_bar.file_path);
|
|
std::ifstream f(path);
|
|
if (!f) { tql_bar.io_status = std::string("load FAILED: ") + path; }
|
|
else {
|
|
std::string text((std::istreambuf_iterator<char>(f)),
|
|
std::istreambuf_iterator<char>());
|
|
std::string err;
|
|
bool ok = tql::apply(text, st, orig_headers, orig_types,
|
|
cells, row_count, orig_cols, &err);
|
|
if (ok) tql_bar.io_status = std::string("loaded: ") + path +
|
|
(err.empty() ? "" : " (warn: " + err + ")");
|
|
else tql_bar.io_status = std::string("load parse error: ") + err;
|
|
}
|
|
}
|
|
if (!tql_bar.io_status.empty()) {
|
|
ImGui::SameLine();
|
|
ImGui::TextDisabled("%s", tql_bar.io_status.c_str());
|
|
}
|
|
|
|
// Modal "Show TQL"
|
|
if (tql_bar.show_open) {
|
|
ImGui::OpenPopup("##tql_show_modal");
|
|
tql_bar.show_open = false;
|
|
}
|
|
ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_Appearing);
|
|
if (ImGui::BeginPopupModal("##tql_show_modal", nullptr,
|
|
ImGuiWindowFlags_NoSavedSettings)) {
|
|
ImGui::TextUnformatted("TQL (read-only):");
|
|
ImGui::SameLine();
|
|
if (ImGui::SmallButton("Copy")) {
|
|
ImGui::SetClipboardText(tql_bar.show_text.c_str());
|
|
}
|
|
ImGui::InputTextMultiline("##tql_show_text",
|
|
const_cast<char*>(tql_bar.show_text.c_str()),
|
|
tql_bar.show_text.size() + 1,
|
|
ImVec2(-1, -40),
|
|
ImGuiInputTextFlags_ReadOnly);
|
|
if (ImGui::Button("Close")) ImGui::CloseCurrentPopup();
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
// Modal "Apply TQL"
|
|
if (tql_bar.apply_open) {
|
|
ImGui::OpenPopup("##tql_apply_modal");
|
|
tql_bar.apply_open = false;
|
|
// Pre-fill apply_text with current TQL
|
|
tql_bar.apply_text = tql::emit(st, orig_headers, orig_types);
|
|
}
|
|
ImGui::SetNextWindowSize(ImVec2(600, 420), ImGuiCond_Appearing);
|
|
if (ImGui::BeginPopupModal("##tql_apply_modal", nullptr,
|
|
ImGuiWindowFlags_NoSavedSettings)) {
|
|
ImGui::TextUnformatted("Paste / edit TQL and click Apply:");
|
|
tql_bar.apply_text.resize(4096, '\0');
|
|
ImGui::InputTextMultiline("##tql_apply_text",
|
|
tql_bar.apply_text.data(),
|
|
tql_bar.apply_text.size(),
|
|
ImVec2(-1, -80),
|
|
ImGuiInputTextFlags_None);
|
|
if (!tql_bar.apply_error.empty()) {
|
|
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 100, 100, 255));
|
|
ImGui::TextWrapped("Error: %s", tql_bar.apply_error.c_str());
|
|
ImGui::PopStyleColor();
|
|
}
|
|
if (ImGui::Button("Apply")) {
|
|
std::string err;
|
|
bool ok = tql::apply(std::string(tql_bar.apply_text.c_str()),
|
|
st, orig_headers, orig_types,
|
|
cells, row_count, orig_cols, &err);
|
|
if (ok) {
|
|
tql_bar.apply_error.clear();
|
|
ImGui::CloseCurrentPopup();
|
|
} else {
|
|
tql_bar.apply_error = err;
|
|
}
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Cancel")) {
|
|
tql_bar.apply_error.clear();
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
}
|
|
|
|
} // namespace data_table
|