b15106fc09
- CMakeLists.txt - app.md - appicon.ico - main.cpp - perf_tests.cpp - perf_tests.h - qa_panel.cpp - qa_panel.h - qa_state.cpp - qa_state.h - ... Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
204 lines
7.3 KiB
C++
204 lines
7.3 KiB
C++
// tab_drill — drill-down stages + breadcrumb demo (issue 0108 fase 2).
|
|
// Muestra tabla de ventas jerarquica (region/country/product/quarter/revenue).
|
|
// Stage 0: raw. Stage 1: agrupado por region (Sum revenue).
|
|
// La UI arranca en stage 1. Right-click en celda de region -> popup nativo
|
|
// "Drill into" del modulo -> crea stage 2 con filter region == valor.
|
|
// Breadcrumb (<>) navega back/forward entre stages.
|
|
#include "tabs.h"
|
|
#include "qa_state.h"
|
|
#include "data_table/data_table.h"
|
|
#include <imgui.h>
|
|
#include <cstdio> // snprintf
|
|
|
|
namespace tables_qa::tabs {
|
|
|
|
namespace {
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Datos: 30 filas x 5 columnas (region, country, product, quarter, revenue)
|
|
// 3 regiones x 3 paises x (Q1..Q4 repartidos) x 3 productos = 30 filas aprox.
|
|
// ---------------------------------------------------------------------------
|
|
struct Row {
|
|
const char* region;
|
|
const char* country;
|
|
const char* product;
|
|
const char* quarter;
|
|
const char* revenue;
|
|
};
|
|
|
|
constexpr int kRows = 30;
|
|
constexpr int kCols = 5;
|
|
|
|
// Revenues: mezcla de 1000..50000 con variacion por region/producto.
|
|
static const Row kSalesData[kRows] = {
|
|
// EMEA
|
|
{"EMEA", "Germany", "A", "Q1", "12500"},
|
|
{"EMEA", "Germany", "B", "Q2", "18300"},
|
|
{"EMEA", "Germany", "C", "Q3", "9700"},
|
|
{"EMEA", "France", "A", "Q4", "21000"},
|
|
{"EMEA", "France", "B", "Q1", "14200"},
|
|
{"EMEA", "France", "C", "Q2", "7800"},
|
|
{"EMEA", "Spain", "A", "Q3", "11500"},
|
|
{"EMEA", "Spain", "B", "Q4", "16800"},
|
|
{"EMEA", "Spain", "C", "Q1", "5400"},
|
|
{"EMEA", "Germany", "A", "Q2", "19900"},
|
|
// AMER
|
|
{"AMER", "USA", "A", "Q1", "45000"},
|
|
{"AMER", "USA", "B", "Q2", "38500"},
|
|
{"AMER", "USA", "C", "Q3", "22000"},
|
|
{"AMER", "Canada", "A", "Q4", "17600"},
|
|
{"AMER", "Canada", "B", "Q1", "13200"},
|
|
{"AMER", "Canada", "C", "Q2", "8900"},
|
|
{"AMER", "Brazil", "A", "Q3", "27400"},
|
|
{"AMER", "Brazil", "B", "Q4", "31000"},
|
|
{"AMER", "Brazil", "C", "Q1", "11100"},
|
|
{"AMER", "USA", "A", "Q2", "49800"},
|
|
// APAC
|
|
{"APAC", "Japan", "A", "Q1", "32000"},
|
|
{"APAC", "Japan", "B", "Q2", "25600"},
|
|
{"APAC", "Japan", "C", "Q3", "14300"},
|
|
{"APAC", "Australia", "A", "Q4", "18700"},
|
|
{"APAC", "Australia", "B", "Q1", "12400"},
|
|
{"APAC", "Australia", "C", "Q2", "6100"},
|
|
{"APAC", "Singapore", "A", "Q3", "23800"},
|
|
{"APAC", "Singapore", "B", "Q4", "29500"},
|
|
{"APAC", "Singapore", "C", "Q1", "9200"},
|
|
{"APAC", "Japan", "A", "Q2", "35400"},
|
|
};
|
|
|
|
// Backing strings (flat row-major) + ptrs (rebuilt by seed).
|
|
static std::string g_back[kRows * kCols];
|
|
static const char* g_ptrs[kRows * kCols];
|
|
|
|
// data_table state — persistent across frames.
|
|
static data_table::State g_dt;
|
|
static bool g_seeded = false;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// seed — fill g_back + g_ptrs, preset stages, set active_stage = 1.
|
|
// ---------------------------------------------------------------------------
|
|
void seed() {
|
|
for (int r = 0; r < kRows; ++r) {
|
|
int base = r * kCols;
|
|
g_back[base + 0] = kSalesData[r].region;
|
|
g_back[base + 1] = kSalesData[r].country;
|
|
g_back[base + 2] = kSalesData[r].product;
|
|
g_back[base + 3] = kSalesData[r].quarter;
|
|
g_back[base + 4] = kSalesData[r].revenue;
|
|
}
|
|
for (int i = 0; i < kRows * kCols; ++i)
|
|
g_ptrs[i] = g_back[i].c_str();
|
|
|
|
// ------------------------------------------------------------------
|
|
// Preset stages:
|
|
// stages[0] = raw (empty Stage — no breakouts, no aggregations).
|
|
// stages[1] = breakout by region + Sum of revenue.
|
|
// ------------------------------------------------------------------
|
|
g_dt.stages.clear();
|
|
g_dt.stages.emplace_back(); // Stage 0: raw
|
|
|
|
data_table::Stage agg;
|
|
agg.breakouts = {"region"};
|
|
{
|
|
data_table::Aggregation a;
|
|
a.fn = data_table::AggFn::Sum;
|
|
a.col = "revenue";
|
|
a.alias = "revenue_sum";
|
|
agg.aggregations.push_back(a);
|
|
}
|
|
g_dt.stages.push_back(agg); // Stage 1: group by region + sum revenue
|
|
|
|
// Start on the aggregated view.
|
|
g_dt.active_stage = 1;
|
|
|
|
// Clear drill history.
|
|
g_dt.drill_back.clear();
|
|
g_dt.drill_forward.clear();
|
|
|
|
g_seeded = true;
|
|
}
|
|
|
|
} // anon
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// render_drill
|
|
// ---------------------------------------------------------------------------
|
|
void render_drill() {
|
|
if (!g_seeded) seed();
|
|
|
|
// ------------------------------------------------------------------
|
|
// UI controls above the table.
|
|
// ------------------------------------------------------------------
|
|
if (ImGui::SmallButton("Reset to grouped (stage 1)")) {
|
|
// Keep only stage 0 + stage 1; drop any drill stages.
|
|
if (g_dt.stages.size() > 2) {
|
|
g_dt.stages.resize(2);
|
|
}
|
|
g_dt.active_stage = 1;
|
|
g_dt.drill_back.clear();
|
|
g_dt.drill_forward.clear();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::SmallButton("Reset to raw (stage 0)")) {
|
|
g_dt.active_stage = 0;
|
|
g_dt.drill_back.clear();
|
|
g_dt.drill_forward.clear();
|
|
}
|
|
ImGui::SameLine();
|
|
ImGui::TextDisabled(
|
|
"Right-click any region cell -> 'Drill into' to filter down. "
|
|
"Use breadcrumb < > to navigate back/forward.");
|
|
|
|
// ------------------------------------------------------------------
|
|
// Build TableInput.
|
|
// ------------------------------------------------------------------
|
|
data_table::TableInput tbl;
|
|
tbl.name = "sales";
|
|
tbl.headers = {"region", "country", "product", "quarter", "revenue"};
|
|
tbl.types = {
|
|
data_table::ColumnType::String,
|
|
data_table::ColumnType::String,
|
|
data_table::ColumnType::String,
|
|
data_table::ColumnType::String,
|
|
data_table::ColumnType::Float,
|
|
};
|
|
tbl.cells = g_ptrs;
|
|
tbl.rows = kRows;
|
|
tbl.cols = kCols;
|
|
|
|
// Renderers: CategoricalChip on region, ColorScale on revenue.
|
|
tbl.column_specs.resize(kCols);
|
|
for (int i = 0; i < kCols; i++) tbl.column_specs[i].id = tbl.headers[i];
|
|
|
|
// region: CategoricalChip — one dot per region for fast scanning.
|
|
{
|
|
auto& cs = tbl.column_specs[0];
|
|
cs.renderer = data_table::CellRenderer::CategoricalChip;
|
|
cs.chips = {
|
|
{"EMEA", "#3b82f6"}, // blue
|
|
{"AMER", "#22c55e"}, // green
|
|
{"APAC", "#f59e0b"}, // amber
|
|
};
|
|
}
|
|
|
|
// revenue: ColorScale green→amber→red over [0, 50000].
|
|
{
|
|
auto& cs = tbl.column_specs[4];
|
|
cs.renderer = data_table::CellRenderer::ColorScale;
|
|
cs.range_min = 0.0;
|
|
cs.range_max = 50000.0;
|
|
cs.range_alpha = 0.30f;
|
|
// Default 3-stop gradient (green→amber→red) via empty range_stops.
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// Render.
|
|
// ------------------------------------------------------------------
|
|
ImGui::BeginChild("##drill_host", ImVec2(-1, -1));
|
|
data_table::render("##drill_tbl", {tbl}, g_dt, nullptr,
|
|
qa::toggles().show_chrome);
|
|
ImGui::EndChild();
|
|
}
|
|
|
|
} // namespace tables_qa::tabs
|