Files
fn_registry/cpp/functions/viz/graph_layouts.md
T
egutierrez 4a0750445c feat(viz): graph_layouts (radial/hierarchical/fixed) + viewport multi-select+lasso (issue 0049i)
Phase 1 — graph_layouts:
- New module cpp/functions/viz/graph_layouts.{h,cpp,md} v1.0.0
- layout_grid, layout_circular, layout_random (migrated from graph_force_layout.cpp)
- layout_radial: BFS rings from root, hop k -> circle of radius k*ring_spacing
- layout_hierarchical: Sugiyama-style heuristic (longest-path levels + barycenter ordering)
- layout_fixed: no-op
- All respect NF_PINNED. graph_layout_circular/grid kept as deprecated wrappers.

Phase 2-3 — graph_viewport v1.2.0:
- Multi-selection via state.selection (vector<int>); NF_SELECTED kept in sync
- Lasso: Shift+Drag on empty area; AABB hit-test on release
- Drag of N-selection: all selected pinned + moved by mouse delta
- Ctrl+click toggle, Esc clears selection
- Right-click on node -> on_context_menu callback
- Double-click on node -> on_double_click callback
- Helpers exposed: graph_viewport_clear/add_to/toggle/is_selected (own TU for tests)

Phase 4 — tests:
- test_graph_layouts: 12 cases / 364 assertions covering geometry, pin, edges
- test_graph_viewport: 5 cases for selection helpers (pure logic, no GL)

Phase 5 — demo (primitives_gallery):
- Layout combo (force/grid/circular/radial/hierarchical/fixed) + Apply button
- Right-click popup with Pin/Unpin/Add-to-selection
- Status overlay shows [N selected] when selection non-empty
- Updated golden images

Issue moved to dev/issues/completed/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 23:42:31 +02:00

127 lines
5.5 KiB
Markdown

---
name: graph_layouts
kind: function
lang: cpp
domain: viz
version: "1.0.0"
purity: pure
signature: "void graph::layout_grid(GraphData&, float spacing); void graph::layout_circular(GraphData&, float radius); void graph::layout_random(GraphData&, float spread); void graph::layout_radial(GraphData&, int root_node, float ring_spacing); void graph::layout_hierarchical(GraphData&, int direction, float layer_spacing, float node_spacing); void graph::layout_fixed(GraphData&)"
description: "Conjunto de layouts estaticos (no iterativos) para GraphData: grid, circular, random, radial, hierarchical (Sugiyama heuristico), fixed. Todos respetan NF_PINNED."
tags: [graph, layout, static, radial, hierarchical, sugiyama, osint]
uses_functions: []
uses_types: ["GraphData_cpp_viz"]
returns: []
returns_optional: false
error_type: ""
imports: []
tested: true
tests: ["grid centers and respects pin", "circular places on circle", "radial root at center, hop ring", "hierarchical levels by longest path", "fixed is no-op"]
test_file_path: "cpp/tests/test_graph_layouts.cpp"
file_path: "cpp/functions/viz/graph_layouts.cpp"
framework: imgui
params:
- name: graph
desc: "Referencia al grafo (GraphData) cuyos nodos se reposicionan in-place. Mutaciones: x, y, vx=0, vy=0 por nodo no pinned."
- name: spacing
desc: "(layout_grid) Distancia en world units entre celdas adyacentes de la cuadricula."
- name: radius
desc: "(layout_circular) Radio del circulo unico en world units."
- name: spread
desc: "(layout_random) Mitad del lado del cuadrado en el que se distribuyen aleatoriamente las posiciones (rango [-spread, spread])."
- name: root_node
desc: "(layout_radial) Indice del nodo raiz. BFS desde aqui asigna el hop. Si es invalido, usa 0."
- name: ring_spacing
desc: "(layout_radial) Distancia radial entre anillos de hops consecutivos."
- name: direction
desc: "(layout_hierarchical) Orientacion: 0=LR (left->right), 1=RL, 2=TB (top->bottom), 3=BT."
- name: layer_spacing
desc: "(layout_hierarchical) Distancia entre niveles consecutivos en el eje del layout."
- name: node_spacing
desc: "(layout_hierarchical) Distancia entre nodos del mismo nivel en el eje transversal."
output: "Ninguno (void). Los nodos no pinned se mueven in-place; nodos con NF_PINNED conservan posicion y velocidad."
notes: "Issue 0049i. layout_circular/layout_grid migrados desde graph_force_layout.cpp; los wrappers deprecados graph_layout_circular/graph_layout_grid siguen funcionando como compat hasta cierre de 0049."
---
# graph_layouts
Funciones de layout estatico (instantaneo, sin iterar). Todas escriben las
posiciones de los nodos no pinned y cero las velocidades; los nodos con
`NF_PINNED` no se tocan. Util para inicializar un grafo antes del force layout
o para ofrecer alternativas de visualizacion en TUIs OSINT.
## API
```cpp
namespace graph {
void layout_grid (GraphData&, float spacing = 20.0f);
void layout_circular (GraphData&, float radius = 100.0f);
void layout_random (GraphData&, float spread = 200.0f);
void layout_radial (GraphData&, int root_node = 0,
float ring_spacing = 80.0f);
void layout_hierarchical(GraphData&, int direction = 0,
float layer_spacing = 120.0f,
float node_spacing = 60.0f);
void layout_fixed (GraphData&); // no-op
}
```
## Algoritmos
### `layout_grid`
Cuadricula uniforme. `cols = ceil(sqrt(N))`, `rows = ceil(N/cols)`. Centro del
grafo en `(0,0)`.
### `layout_circular`
Circulo unico. Cada nodo en el angulo `2*pi*i/N`.
### `layout_random`
Posiciones uniformes en `[-spread, spread]^2`. Usa `rand()` — llamar
`srand(seed)` antes para reproducibilidad.
### `layout_radial`
BFS no dirigido desde `root_node`. Cada hop `k` se coloca en un circulo de
radio `k * ring_spacing`. Nodos del mismo hop se distribuyen uniformemente
en su circulo. Las componentes desconectadas van a un anillo extra al final.
Util para vistas de "vecinos a N saltos" tipicas en OSINT.
### `layout_hierarchical`
Sugiyama-style heuristico:
1. Niveles por longest-path BFS desde nodos sin in-edges.
2. Reduccion de cruces greedy: cada nivel `L>0` se reordena por el baricentro
de los indices de sus padres en el nivel `L-1`.
3. Posiciones: el nivel define el eje principal (X o Y segun `direction`),
los nodos dentro del nivel se centran en el eje transversal.
No es Sugiyama optimo (eso es un problema NP-hard). Es suficientemente bueno
para grafos OSINT pequenios/medianos: jerarquias `Person -> Email -> Domain`,
arboles de dependencia, etc.
### `layout_fixed`
No-op. Se mantiene en el conjunto para que un caller pueda escribir un switch
que cubra todos los modos sin un caso especial.
## Composicion con graph_force_layout
Tipico: layout estatico inicial -> force-directed para refinar.
```cpp
graph::layout_circular(g, 200.0f); // arranque uniforme
ForceLayoutConfig cfg;
for (int i = 0; i < 300; ++i) {
graph_force_layout_step(g, cfg);
}
```
Para grafos jerarquicos donde no se quiere que el force layout deshaga la
estructura: pinnar nodos clave (`NF_PINNED`) tras `layout_hierarchical` y
dejar que el force solo refine los del medio.
## Notas
- Wrappers deprecados: `graph_layout_circular` y `graph_layout_grid` siguen en
`graph_force_layout.h` y delegan aqui. Codigo nuevo debe usar el namespace
`graph::`.
- `layout_radial` y `layout_hierarchical` construyen una lista de adyacencia
temporal `O(V+E)` por llamada. Para grafos enormes (>>1M aristas) considerar
cachear la adyacencia entre llamadas.