feat(graph): wheel-zoom no scrollea, slider 1M nodos, edges/node configurable

Tres mejoras de UX/escala en el demo de grafos:

1. **Wheel zoom dentro del canvas no scrollea la pagina**
   En graph_viewport.cpp tras procesar MouseWheel para zoom hacemos
   io.MouseWheel = 0 — consume el evento para que el BeginChild padre
   (la galeria) no scrollee a la vez que el grafo se acerca. Antes
   sentia "doble accion" al rodar la rueda sobre el canvas.

2. **graph_force_layout: pool dinamico (soporta 1M nodos)**
   El array static QuadNode[1<<20] (~48MB siempre reservados, tope
   rigido en ~250k nodos por la fan-out) se reemplaza por
   std::vector<QuadNode>. graph_force_layout_step llama a
   quad_pool_reserve(5*N + 1024) ANTES de construir el arbol — asi las
   referencias QuadNode& que mantenemos vivas durante quad_subdivide
   no se invalidan por reallocaciones a mitad del build (resize solo
   ocurre en el reserve inicial). Memoria escala lineal con N: 1M
   nodos ≈ 240MB de pool, una vez por programa.

3. **Demo de grafo: sliders extendidos + cluster_r escala con sqrt(N)**
   - "Nodes" pasa de 100..20k a 100..1M con escala logaritmica
     (ImGuiSliderFlags_Logarithmic) para que el rango medio sea util.
   - Nuevos sliders "Edges/node" (1..10) e "Inter %" (0..30%) — antes
     hardcoded a 3 y 5%.
   - cluster_radius y scatter ahora escalan con sqrt(N): a 1k nodos
     ~370 px de radio, a 1M ~12000 px. Antes era constante a 200/40
     y los nodos quedaban empaquetados al subir N — visualmente "sin
     limite cuadrado", esparcidos sobre un area proporcional al grafo.
   - Golden de graph_viewport regenerado por la nueva fila de sliders.

Notas:
- A 1M nodos sin GPU compute esta limitado por el upload de aristas
  (vertex pulling con TBO llega en 0049d). Render mantenible hasta
  ~200-300k.
- En Linux/Windows ambos builds limpios. 27/27 tests verde.
This commit is contained in:
2026-04-29 21:53:33 +02:00
parent 9a4ff33e68
commit 0e6a013937
4 changed files with 67 additions and 26 deletions
+9
View File
@@ -186,6 +186,11 @@ bool graph_viewport(const char* id, GraphData& graph, GraphViewportState& state,
// -------------------------------------------------------------------
// 5b. Zoom (scroll wheel)
// -------------------------------------------------------------------
// Consumimos el wheel cuando esta sobre el canvas para que la ventana
// padre (BeginChild de la galeria, p.ej.) NO scrollee a la vez que
// hacemos zoom — sin esto la pagina entera se mueve mientras el grafo
// se acerca, sensacion incomoda. Tambien marcamos NoNav del item para
// que ImGui no intente keyboard-scroll al estar enfocado.
if (hovered) {
float wheel = ImGui::GetIO().MouseWheel;
if (wheel != 0.0f) {
@@ -201,6 +206,10 @@ bool graph_viewport(const char* id, GraphData& graph, GraphViewportState& state,
state.cam_y += rel_y / old_zoom - rel_y / new_zoom;
state.zoom = new_zoom;
interacted = true;
// Consumir el evento — ImGui::GetIO().MouseWheel a 0 evita que
// el padre lo procese.
ImGui::GetIO().MouseWheel = 0.0f;
}
}