Files
fn_registry/dev/issues/completed/0049i-graph-layouts-static.md

162 lines
6.5 KiB
Markdown

---
id: "0049i"
title: "`graph_layouts` (radial, hierarchical, fixed) + viewport extendido"
status: completado
type: feature
domain: []
scope: multi-app
priority: media
depends: []
blocks: []
related: []
created: 2026-05-17
updated: 2026-05-17
tags: []
---
# 0049i — `graph_layouts` (radial, hierarchical, fixed) + viewport extendido
## Metadata
| Campo | Valor |
|-------|-------|
| **ID** | 0049i |
| **Estado** | pendiente |
| **Prioridad** | media |
| **Tipo** | feature — parte de [#0049](0049-osint-graph-viewer.md) |
## Dependencias
**Bloqueada por:** [0049e](0049e-graph-types-extended.md) (necesita `flags`).
---
## Objetivo
Consolidar las estrategias de layout estatico en una sola funcion `graph_layouts` (anadiendo `radial`, `hierarchical`, `fixed`), y extender `graph_viewport` con lasso, multi-select acumulativo, drag de seleccion entera y callbacks de menu contextual / double-click.
## Contexto
Hoy `graph_force_layout.cpp` incluye `graph_layout_circular` y `graph_layout_grid` como helpers. Para OSINT son utiles:
- **Radial**: arbol con un nodo raiz seleccionado y sus vecinos en circulos concentricos por hop.
- **Hierarchical** (Sugiyama-style): niveles por tipo o por dependencia (Person → Email → Domain).
- **Fixed**: no-op, las posiciones las pone el caller.
`graph_viewport` ya soporta pan/zoom/click + hit-test. Falta el resto de UX para Maltego.
## Arquitectura
```
cpp/functions/viz/
├── graph_layouts.{h,cpp} # NEW (mueve circular/grid + nuevos)
├── graph_layouts.md # NEW
├── graph_viewport.{h,cpp} # MOD: lasso, multi-select, callbacks
└── graph_viewport.md # MOD: bump
cpp/tests/
├── test_graph_layouts.cpp # NEW
└── test_graph_viewport.cpp # NEW (smoke)
```
### `graph_layouts` API
```cpp
namespace graph {
// Estaticos. Mutan posiciones, respetan NF_PINNED.
void layout_grid (GraphData&, float spacing);
void layout_circular (GraphData&, float radius);
void layout_random (GraphData&, float spread);
void layout_radial (GraphData&, int root_node, float ring_spacing);
void layout_hierarchical(GraphData&, int direction); // 0=LR, 1=RL, 2=TB, 3=BT
void layout_fixed (GraphData&); // no-op
} // namespace graph
```
`graph_force_layout.cpp` deja de exportar `_circular`/`_grid` (delegan a `graph_layouts`). Mantener wrappers deprecados un sub-issue maximo, eliminar antes del cierre de 0049.
### `graph_viewport` extensiones
```cpp
struct GraphViewportCallbacks {
void (*on_context_menu)(int node_idx, ImVec2 screen_pos, void* user) = nullptr;
void (*on_double_click)(int node_idx, void* user) = nullptr;
void* user = nullptr;
};
struct GraphViewportState {
// ... existente
int selected_node; // legacy: ultimo seleccionado
std::vector<int> selection; // NEW: multi-seleccion
bool lasso_active;
ImVec2 lasso_start, lasso_end;
};
// Igual firma que hoy, mas un parametro opcional de callbacks.
void graph_viewport(const char* id, GraphData&, GraphViewportState&,
ImVec2 size, const GraphViewportCallbacks& cb = {});
```
Comportamiento:
- **Click**: limpia seleccion, anade nodo bajo cursor.
- **Ctrl+Click**: toggle nodo en seleccion.
- **Shift+Drag (sin nodo bajo cursor)**: lasso. Al soltar, anade los nodos dentro del rect a la seleccion.
- **Drag con un nodo seleccionado bajo el cursor**: arrastra todos los seleccionados como pinned (set `NF_PINNED` mientras se arrastra; mantener pinned al soltar).
- **Right-click sobre un nodo**: invoca `on_context_menu(idx, screen_pos, user)` si esta seteado.
- **Double-click sobre un nodo**: invoca `on_double_click(idx, user)`.
- **Esc**: limpia seleccion.
## Tareas
### Fase 1 — `graph_layouts`
- [ ] **1.1** Crear `graph_layouts.{h,cpp,md}`. Mover impl de `circular`/`grid` desde `graph_force_layout.cpp`.
- [ ] **1.2** Implementar `layout_radial`: BFS desde `root_node`, posicionar cada hop k en un circulo de radio `k * ring_spacing`, distribuir uniformemente.
- [ ] **1.3** Implementar `layout_hierarchical`: BFS levels por longest-path desde nodos sin in-edges; dentro de cada nivel ordenar por minimo cruce (greedy heuristico — no optimo, pero bueno para la UX OSINT).
- [ ] **1.4** Implementar `layout_fixed`: no-op (recordar que existe la funcion).
- [ ] **1.5** Todas respetan `NF_PINNED`.
### Fase 2 — Viewport multi-select + lasso
- [ ] **2.1** En `graph_viewport.cpp`, implementar el comportamiento de la tabla anterior.
- [ ] **2.2** Lasso: `ImDrawList::AddRect` para feedback visual + AABB hit-test al soltar.
- [ ] **2.3** Drag de seleccion: pin todos los nodos seleccionados al inicio del drag, aplicar el delta a todos, mantener pinned al soltar.
### Fase 3 — Callbacks
- [ ] **3.1** Anadir `GraphViewportCallbacks` y wirear `on_context_menu` (right-click) + `on_double_click`.
- [ ] **3.2** Documentar en el `.md` que el callback se invoca dentro del frame ImGui — el caller puede abrir un popup.
### Fase 4 — Tests
- [ ] **4.1** `test_graph_layouts`: smoke de cada layout sobre un grafo pequeño; verificar que `NF_PINNED` no se mueve; que `radial` distribuye correctamente.
- [ ] **4.2** `test_graph_viewport`: setup de un grafo, simular hit-test programatico (no test interactivo, solo helpers puros).
### Fase 5 — Demo
- [ ] **5.1** Anadir toggle de layout en `demos_graph` (`force | grid | circular | radial | hierarchical | fixed`).
- [ ] **5.2** Anadir lasso + multi-select visible en el demo (text overlay con count seleccionados).
### Fase 6 — Cleanup
- [ ] Bump versions: `graph_layouts` 1.0.0 (nuevo), `graph_viewport` 1.x → 1.x+1.
- [ ] Documentar `params`/`output` en el `.md` para FTS5 search.
- [ ] `fn index`.
- [ ] Commit `feat(viz): graph_layouts (radial/hierarchical/fixed) + viewport multi-select+lasso`.
## Criterio de done
- [ ] Switch entre layouts en el demo es instantaneo.
- [ ] Lasso visible, multi-seleccion acumulativa funcional.
- [ ] Drag de N nodos seleccionados los mueve juntos como pinned.
- [ ] Right-click invoca callback si esta seteado.
- [ ] Tests verdes.
## Riesgos
| Riesgo | Mitigacion |
|---|---|
| Hierarchical layout se ve mal en grafos densamente cruzados | Aceptable — Sugiyama optimo es un campo entero; el heuristico es para visualizacion OSINT, no publicacion |
| Multi-select state en GraphViewportState rompe ABI | Es un cambio interno; `selection` es campo nuevo, ok |
| Drag de seleccion gigante (10k nodos) lagueva | Desactivar fuerzas en pinned ya implica que la GPU no los toca. Drag solo aplica delta — O(N seleccionados) trivial |