Files
fn_registry/cpp/functions/viz/graph_force_layout.md
T
egutierrez c29428a187 feat(viz): graph_types modelo extendido + EntityType/RelationType + flags (issue 0049e)
Extiende el modelo agnostico de graph_types.h para soportar shapes/iconos/
filtros/labels/streaming sin acoplar a backend. Migra el unico consumer
(demos_graph) en el mismo cambio.

- GraphNode v2: type_id + shape_override/color_override/size_override +
  flags (NF_PINNED/VISIBLE/SELECTED/HOVERED) + label_idx + user_data.
- GraphEdge v2: type_id + style_override + flags (EF_DIRECTED/VISIBLE).
- EntityType / RelationType: tablas en GraphData (types, rel_types).
- Helpers de resolucion (resolve_node_color/shape/size, resolve_edge_*)
  y constructores ergonomicos (graph_node, graph_edge, entity_type,
  relation_type) — sentinel-based para herencia automatica del tipo.
- graph_renderer v1.4: lee NF_VISIBLE / EF_VISIBLE, resuelve apariencia
  via override → EntityType → fallback indexado por type_id. Skipea
  aristas con endpoints invisibles. Shapes siguen pintandose como
  circulo (0049f cableara el dispatch real).
- graph_force_layout v1.2: pinned ahora vive en flags & NF_PINNED.
- graph_viewport v1.1: hover/seleccion publican NF_HOVERED/SELECTED en
  el grafo (clear-then-set). Drag usa NF_PINNED. Tooltip muestra Type/
  user_data en lugar de community/value/label.
- demos_graph: 8 EntityType (paleta antigua) + 1 RelationType. type_id
  por cluster. user_data = indice numerico del nodo. Apariencia visual
  identica al pre-cambio.
- test_graph_types.cpp: 12 casos cubriendo helpers, defaults, bitmask
  manipulation y resoluciones override-vs-EntityType. test_graph_edge_
  static actualizado al nuevo modelo (ya no tiene .color directo).
- 4 .md de tipos nuevos (graph_node, graph_edge, entity_type,
  relation_type) + GraphData v2.0 actualizado.

Tests: 31/31 ctest verdes (incluye test_visual golden).

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

4.7 KiB

name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, tested, tests, test_file_path, file_path, framework, params, output, notes
name kind lang domain version purity signature description tags uses_functions uses_types returns returns_optional error_type imports tested tests test_file_path file_path framework params output notes
graph_force_layout function cpp viz 1.2.0 pure float graph_force_layout_step(GraphData& graph, const ForceLayoutConfig& config) Layout force-directed con aproximacion Barnes-Hut para grafos grandes, ejecuta un paso de simulacion por llamada
graph
layout
force-directed
barnes-hut
physics
gpu
GraphData_cpp_viz
false
true
should_pause threshold
should_pause requires consecutive frames
should_pause emulating low->high->low sequence
cpp/tests/test_graph_should_pause.cpp cpp/functions/viz/graph_force_layout.cpp imgui
name desc
graph Referencia al grafo (GraphData) cuyos nodos se actualizan in-place. Modifica x, y, vx, vy de cada nodo no pinned.
name desc
config Parametros de la simulacion: repulsion (fuerza coulombiana), attraction (spring constant), damping (decay de velocidad), theta (precision Barnes-Hut 0=exacto/1=rapido), gravity (atraccion al centro), max_velocity, iterations.
Energia cinetica total (suma de |v|^2). Cuando cae por debajo de un umbral elegido por el caller, el layout ha convergido y se puede dejar de llamar. scaffolding/demo en primitives_gallery

graph_force_layout

Implementa el algoritmo de layout force-directed clasico (Fruchterman-Reingold / Eades) con aproximacion Barnes-Hut O(n log n) para escalar a grafos de miles de nodos.

Algoritmo

Cada llamada a graph_force_layout_step ejecuta config.iterations pasos. Un paso:

  1. Construccion del quadtree (Barnes-Hut): se calcula el bounding box de las posiciones actuales, se construye un quadtree flat en quad_pool (sin allocaciones por nodo). Cada celda acumula centro de masa y masa total.
  2. Repulsion: para cada nodo se recorre el quadtree. Si el cociente cell_size / distance < theta, la celda se trata como una sola masa puntual (multipolo de orden 0). Si no, se desciende a los hijos. Con theta=0 es O(n²) exacto; con theta=0.5 es O(n log n).
  3. Atraccion: para cada arista (s, t), fuerza de Hooke F = k * dist * weight en la direccion del arco.
  4. Gravedad: fuerza proporcional a la distancia al origen, evita que el grafo derive fuera de pantalla.
  5. Integracion: v = v * damping + F, pos += v, con clamping de velocidad.
  6. Nodos con pinned = true no se mueven en ningun paso.

Funciones auxiliares

// Randomizar posiciones para empezar la simulacion
graph_force_layout_reset(graph, 200.0f);

// Layout circular instantaneo (sin iteracion)
graph_layout_circular(graph, 150.0f);

// Layout en grid instantaneo
graph_layout_grid(graph, 25.0f);

// Auto-pause: parar la simulacion cuando la energia se ha estabilizado.
// Pure: el caller mantiene el contador, la funcion solo decide.
//   bool graph_force_layout_should_pause(int low_frames, int min_consecutive);

Ejemplo de uso tipico (loop ImGui)

static ForceLayoutConfig cfg;
static bool running = true;
static int  low_frames = 0;
const int   k_min_consecutive = 30;
const float k_threshold_per_node = 0.001f;

if (running) {
    float energy = graph_force_layout_step(my_graph, cfg);
    float per_node = my_graph.node_count > 0
        ? energy / my_graph.node_count : 0.0f;
    if (per_node < k_threshold_per_node) ++low_frames;
    else                                 low_frames = 0;
    if (graph_force_layout_should_pause(low_frames, k_min_consecutive)) {
        running    = false;
        low_frames = 0;
    }
}

Notas de implementacion

  • El quadtree usa un pool dinamico (std::vector<QuadNode>) que se redimensiona una vez por step a 5*N + 1024 celdas. La pila de traversal en quad_force es local en pila (256 entradas) — thread-safe bajo OpenMP.
  • graph_force_layout_reset usa rand(). Para reproducibilidad llama srand(seed) antes.
  • Los buffers de fuerza (fx_buf, fy_buf) se realocan una sola vez cuando el conteo de nodos supera la capacidad previa; en el uso normal (tamano fijo) no hay allocaciones por frame.

Notas de version

  • v1.2 (2026-04-29, issue 0049e): el campo pinned desaparece del modelo de GraphNode y se sustituye por flags & NF_PINNED. La logica del integrador, atraccion, gravedad y reset usa el bit equivalente. Sin cambios en la API publica ni en el comportamiento.
  • v1.1 (2026-04-29, issue 0049c): añade el helper puro graph_force_layout_should_pause(low_frames, min_consecutive) para que las apps detecten convergencia sin replicar el contador por todas partes. Sin cambios en graph_force_layout_step ni en la API existente. Test: cpp/tests/test_graph_should_pause.cpp.