commit 647e767e6525e5cc2373d37c4f08ee04405abb61 Author: fn-registry agent Date: Fri May 8 00:07:54 2026 +0200 chore: sync from fn-registry agent diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..89ac54f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,22 @@ +add_imgui_app(chart_demo + main.cpp + ${CMAKE_SOURCE_DIR}/functions/viz/line_plot.cpp + ${CMAKE_SOURCE_DIR}/functions/viz/scatter_plot.cpp + ${CMAKE_SOURCE_DIR}/functions/viz/bar_chart.cpp + ${CMAKE_SOURCE_DIR}/functions/viz/heatmap.cpp + # fps_overlay vive en fn_framework +) + +# --- E2E tests (opt-in via -DFN_BUILD_TESTS=ON) --- +if(FN_BUILD_TESTS) + add_imgui_app(chart_demo_tests + main.cpp + tests/chart_demo_tests.cpp + ${CMAKE_SOURCE_DIR}/functions/viz/line_plot.cpp + ${CMAKE_SOURCE_DIR}/functions/viz/scatter_plot.cpp + ${CMAKE_SOURCE_DIR}/functions/viz/bar_chart.cpp + ${CMAKE_SOURCE_DIR}/functions/viz/heatmap.cpp + ) + # Excludes int main() from main.cpp so the test target provides its own. + target_compile_definitions(chart_demo_tests PRIVATE FN_TEST_BUILD) +endif() diff --git a/app.md b/app.md new file mode 100644 index 0000000..e2cf01d --- /dev/null +++ b/app.md @@ -0,0 +1,60 @@ +--- +name: chart_demo +lang: cpp +domain: viz +description: "Demo ImGui de primitivos viz del registry: line_plot, scatter_plot, bar_chart, heatmap. Cada chart en su propia tab del TabBar. Usado como showcase y como build gate de las funciones viz/." +tags: [imgui, demo, charts, viz, showcase] +uses_functions: + - line_plot_cpp_viz + - scatter_plot_cpp_viz + - bar_chart_cpp_viz + - heatmap_cpp_viz + # logger, app_menubar viven en fn_framework — no se listan aqui +uses_types: [] +framework: "imgui" +entry_point: "main.cpp" +dir_path: "cpp/apps/chart_demo" +repo_url: "" +--- + +## Que hace + +App de una sola ventana con cuatro tabs (Line / Scatter / Bar / Heatmap) que +renderiza datos sinteticos para mostrar el aspecto y la API de los primitivos +viz del registry. Sirve como: + +- **Showcase visual** de las funciones viz existentes — al añadir una nueva + primitiva, anadir su tab aqui es la forma natural de probar el binding. +- **Build gate**: si una de las funciones rompe API, esta app deja de + compilar y lo cazamos sin tener que tocar `registry_dashboard` o + `graph_explorer`. + +## Estructura + +`main.cpp` (~93 lineas): + +- `init_data()` — genera arrays sinteticos una vez (estado modulo). +- `render()` — DockSpaceOverViewport + TabBar con 4 tabs, cada una invoca + un primitivo del registry. +- `main()` → `fn::run_app(...)` con AppConfig estandar (titulo, tamaño, + about, log). + +## Build + +```bash +# Linux +cd cpp && cmake -B build/linux -S . && cmake --build build/linux --target chart_demo + +# Windows (cross-compile) +cd cpp && cmake -B build/windows -S . -DCMAKE_TOOLCHAIN_FILE=toolchains/mingw-w64.cmake \ + && cmake --build build/windows --target chart_demo +``` + +## Decisiones + +- `viewports = true` (default de `fn::run_app`): las ventanas se pueden + arrastrar fuera del main window. +- `init_gl_loader = false`: solo usa ImGui/ImPlot, sin gl* directo. +- Sin persistencia propia (no abre BD). +- `log: file_path = "chart_demo.log"` con nivel Debug — el `init_data` + emite info+debug para verificar que el logger funciona. diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..599b04e --- /dev/null +++ b/main.cpp @@ -0,0 +1,95 @@ +#include "app_base.h" +#include "imgui.h" +#include "implot.h" + +#include "viz/line_plot.h" +#include "viz/scatter_plot.h" +#include "viz/bar_chart.h" +#include "viz/heatmap.h" +#include "core/app_menubar.h" +#include "core/logger.h" + +#include +#include + +// Generate sample data +static constexpr int N = 500; +static float xs[N], ys_sin[N], ys_cos[N]; +static float scatter_x[200], scatter_y[200]; +static const char* bar_labels[] = {"Go", "Python", "Bash", "TypeScript", "C++"}; +static float bar_values[] = {201.0f, 202.0f, 38.0f, 80.0f, 5.0f}; +static float heat_data[10 * 10]; + +static bool data_initialized = false; + +static void init_data() { + if (data_initialized) return; + fn_log::log_info("init_data: generando %d puntos sin/cos, 200 scatter, 10x10 heatmap", N); + for (int i = 0; i < N; i++) { + xs[i] = static_cast(i) * 0.02f; + ys_sin[i] = sinf(xs[i]); + ys_cos[i] = cosf(xs[i]); + } + for (int i = 0; i < 200; i++) { + scatter_x[i] = static_cast(rand()) / RAND_MAX * 10.0f; + scatter_y[i] = scatter_x[i] * 0.5f + (static_cast(rand()) / RAND_MAX - 0.5f) * 3.0f; + } + for (int i = 0; i < 100; i++) { + int r = i / 10, c = i % 10; + heat_data[i] = sinf(r * 0.5f) * cosf(c * 0.5f); + } + data_initialized = true; + fn_log::log_debug("init_data: ok"); +} + +void render() { + init_data(); + + // MainMenuBar (solo Settings — chart_demo no tiene paneles toggleables) + fn_ui::app_menubar(nullptr, 0, nullptr); + + // Full-window dockspace + ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport()); + + if (ImGui::Begin("fn_registry — Chart Demo")) { + if (ImGui::BeginTabBar("##charts")) { + if (ImGui::BeginTabItem("Line Plot")) { + ImGui::Text("sin(x) — %d points", N); + line_plot("Sine Wave", xs, ys_sin, N); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Scatter Plot")) { + ImGui::Text("y = 0.5x + noise — 200 points"); + scatter_plot("Scatter Data", scatter_x, scatter_y, 200); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Bar Chart")) { + ImGui::Text("Functions per language in fn_registry"); + bar_chart("Registry Languages", bar_labels, bar_values, 5); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Heatmap")) { + ImGui::Text("sin(r) * cos(c) — 10x10 matrix"); + heatmap("Correlation Matrix", heat_data, 10, 10, -1.0f, 1.0f); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + } + ImGui::End(); +} + +#ifndef FN_TEST_BUILD +int main() { + return fn::run_app({ + .title = "fn_registry — Chart Demo", + .width = 1400, + .height = 900, + .about = {.name = "chart demo", + .version = "0.2.0", + .description = "Demo de primitivos viz: line, scatter, bar, heatmap. AppConfig estandar + multi-viewport."}, + .log = {.file_path = "chart_demo.log", + .level = static_cast(fn_log::Level::Debug)} + }, render); +} +#endif diff --git a/tests/chart_demo_tests.cpp b/tests/chart_demo_tests.cpp new file mode 100644 index 0000000..51afd1f --- /dev/null +++ b/tests/chart_demo_tests.cpp @@ -0,0 +1,41 @@ +// E2E tests for chart_demo — Dear ImGui Test Engine. +// Built only when -DFN_BUILD_TESTS=ON. The same main.cpp from chart_demo is +// compiled here with FN_TEST_BUILD defined so its int main() is excluded and +// only render() is reused. + +#include "app_base.h" +#include "imgui.h" +#include "imgui_te_engine.h" +#include "imgui_te_context.h" + +void render(); // defined in chart_demo/main.cpp + +static void register_tests(ImGuiTestEngine* e) { + ImGuiTest* t = nullptr; + + // Smoke test: the main window appears and is non-empty. + t = IM_REGISTER_TEST(e, "chart_demo", "smoke_window_visible"); + t->TestFunc = [](ImGuiTestContext* ctx) { + ctx->SetRef("fn_registry \xe2\x80\x94 Chart Demo"); // em-dash + IM_CHECK(ctx->WindowInfo("").ID != 0); + }; + + // Cycle through all four tabs. Test engine fails the test if any tab item + // is not found or cannot be activated — that is our implicit assertion. + t = IM_REGISTER_TEST(e, "chart_demo", "tabs_cycle_all"); + t->TestFunc = [](ImGuiTestContext* ctx) { + ctx->SetRef("fn_registry \xe2\x80\x94 Chart Demo"); + ctx->ItemClick("##charts/Line Plot"); + ctx->ItemClick("##charts/Scatter Plot"); + ctx->ItemClick("##charts/Bar Chart"); + ctx->ItemClick("##charts/Heatmap"); + }; +} + +int main() { + fn::AppConfig cfg{}; + cfg.title = "chart_demo_tests"; + cfg.width = 1280; + cfg.height = 800; + return fn::run_app_test(cfg, render, register_tests); +}