feat(viz): surface_plot_3d real (ImPlot3D) + scatter_3d nuevo

surface_plot_3d (v2.0.0): quita el STUB. API basada en
SurfacePlot3DConfig (z[nx*ny] row-major + ranges X/Y) que delega en
ImPlot3D::PlotSurface. Las coordenadas X/Y por vertice se generan
internamente desde [x_min, x_max] x [y_min, y_max].

scatter_3d (v1.0.0): nuevo primitivo. Scatter 3D con tamano y color
opcionales por punto via ImPlot3DSpec::MarkerSizes / MarkerFillColors.
Util para PCA / clustering / nubes de puntos sinteticas.

Ambos namespace fn::, kind component, purity pure. Orbit / zoom / pan
los aporta ImPlot3D nativo.

Issue 0028.
This commit is contained in:
2026-04-25 21:48:43 +02:00
parent b3e55f7abe
commit cebf87cb4e
6 changed files with 259 additions and 45 deletions
+47 -9
View File
@@ -1,12 +1,50 @@
#include "viz/surface_plot_3d.h"
#include "imgui.h"
void surface_plot_3d(const char* title, const float* values, int rows, int cols,
float z_min, float z_max) {
ImGui::BeginGroup();
ImGui::TextDisabled("[STUB] %s", title);
ImGui::TextWrapped("surface_plot_3d requires ImPlot3D. "
"Add it to cpp/vendor/implot3d/ and rebuild.");
ImGui::Text("Data: %dx%d, range [%.2f, %.2f]", rows, cols, z_min, z_max);
ImGui::EndGroup();
#include "imgui.h"
#include "implot3d.h"
#include <vector>
namespace fn {
void surface_plot_3d(const char* title, const SurfacePlot3DConfig& cfg) {
if (!cfg.z || cfg.nx < 2 || cfg.ny < 2) {
ImGui::BeginGroup();
ImGui::TextDisabled("%s", title ? title : "##surface_plot_3d");
ImGui::TextWrapped("surface_plot_3d: necesita z != nullptr y nx,ny >= 2 (recibido %dx%d)",
cfg.nx, cfg.ny);
ImGui::EndGroup();
return;
}
// Genera coordenadas X / Y por vertice (row-major, mismo layout que z).
// ImPlot3D::PlotSurface espera arrays paralelos de nx*ny valores.
const int total = cfg.nx * cfg.ny;
std::vector<float> xs(total);
std::vector<float> ys(total);
const float dx = (cfg.nx > 1) ? (cfg.x_max - cfg.x_min) / float(cfg.nx - 1) : 0.f;
const float dy = (cfg.ny > 1) ? (cfg.y_max - cfg.y_min) / float(cfg.ny - 1) : 0.f;
for (int j = 0; j < cfg.ny; ++j) {
const float y = cfg.y_min + dy * float(j);
const int row = j * cfg.nx;
for (int i = 0; i < cfg.nx; ++i) {
xs[row + i] = cfg.x_min + dx * float(i);
ys[row + i] = y;
}
}
if (ImPlot3D::BeginPlot(title, cfg.size)) {
ImPlot3D::SetupAxes(cfg.x_label, cfg.y_label, cfg.z_label);
ImPlot3D::PlotSurface("##surface", xs.data(), ys.data(), cfg.z,
cfg.nx, cfg.ny);
ImPlot3D::EndPlot();
}
if (cfg.show_colormap) {
// ImPlot3D rinde su propia leyenda dentro del plot; el flag aqui se
// reserva como contrato API por si en el futuro se necesita una
// colormap-bar externa (no implementado todavia).
}
}
} // namespace fn