---
name: graph_viewport
kind: component
lang: cpp
domain: viz
version: "1.2.0"
purity: impure
signature: "bool graph_viewport(const char* id, GraphData& graph, GraphViewportState& state, ImVec2 size)"
description: "Widget ImGui completo para visualizacion interactiva de grafos con pan, zoom, hover, seleccion y layout en vivo"
tags: [graph, viewport, imgui, interactive, pan, zoom, dashboard, lasso, multi-select]
uses_functions: ["graph_force_layout_cpp_viz", "graph_renderer_cpp_viz", "graph_spatial_hash_cpp_core", "graph_viewport_selection_cpp_viz"]
tested: true
tests: ["selection add/clear/toggle/is_selected", "out-of-range indices ignored"]
test_file_path: "cpp/tests/test_graph_viewport.cpp"
uses_types: ["GraphData_cpp_viz"]
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [imgui]
file_path: "cpp/functions/viz/graph_viewport.cpp"
framework: imgui
props:
  - name: id
    type: "const char*"
    required: true
    description: "Identificador unico del widget ImGui"
  - name: graph
    type: "GraphData&"
    required: true
    description: "Referencia al grafo (lectura de datos, escritura de posiciones al drag)"
  - name: state
    type: "GraphViewportState&"
    required: true
    description: "Estado persistente del viewport (camera, seleccion, renderer). Debe vivir mas que los frames."
  - name: size
    type: "ImVec2"
    required: false
    description: "Tamanio del widget en pixeles. ImVec2(0,0) usa todo el espacio disponible."
  - name: cb
    type: "GraphViewportCallbacks"
    required: false
    description: "Callbacks opcionales para right-click (on_context_menu) y double-click (on_double_click). Se invocan dentro del frame ImGui."
emits: []
has_state: true
params:
  - name: id
    desc: "Identificador unico del widget ImGui. Debe ser estable entre frames."
  - name: graph
    desc: "Grafo a visualizar. Las posiciones de nodos se modifican al arrastrar."
  - name: state
    desc: "Estado persistente: camara (cam_x, cam_y, zoom), nodo seleccionado/hovereado, renderer GPU, spatial hash. Alojado por el caller."
  - name: size
    desc: "Tamanio del widget en pixeles. (0,0) ocupa todo el espacio disponible en la ventana ImGui."
  - name: cb
    desc: "Callbacks opcionales: on_context_menu(idx, screen_pos, user) en right-click; on_double_click(idx, user) en doble click. Se invocan dentro del frame ImGui — el caller puede llamar OpenPopup."
output: "true si hubo alguna interaccion del usuario en el frame actual (hover, click, drag, zoom, teclado, lasso, callbacks)"
---

# graph_viewport

Widget ImGui self-contained para visualizar grafos interactivos. Integra rendering GPU, force-directed layout y hit-testing espacial en una sola llamada por frame.

## Uso basico

```cpp
// Declarar estado persistente (fuera del loop de render)
GraphViewportState vp_state;

// En el loop de render (dentro de una ventana ImGui):
if (graph_viewport("mi_grafo", my_graph, vp_state)) {
    // hubo interaccion este frame
    if (vp_state.selected_node >= 0) {
        auto& n = my_graph.nodes[vp_state.selected_node];
        // mostrar panel de detalle de n
    }
}

// Al terminar:
graph_viewport_destroy(vp_state);
```

## Estado de camara

La camara usa coordenadas del espacio del grafo:

- `cam_x`, `cam_y`: centro de la camara en espacio del grafo
- `zoom`: pixeles por unidad de grafo

`graph_viewport_fit()` centra y ajusta el zoom para que el grafo quepa con 10% de padding.

## Controles

| Accion | Control |
|--------|---------|
| Pan | Boton medio o derecho + arrastrar (sobre area vacia) |
| Zoom | Rueda del raton (hacia el cursor) |
| Seleccionar nodo (single) | Click izquierdo sobre nodo |
| Toggle nodo en seleccion | Ctrl + Click izquierdo |
| Lasso (multi-seleccion) | Shift + Click izquierdo + arrastrar sobre area vacia |
| Arrastrar seleccion entera | Click izquierdo sobre nodo seleccionado + arrastrar |
| Menu contextual | Click derecho sobre nodo (callback `on_context_menu`) |
| Activar (double-click) | Doble click sobre nodo (callback `on_double_click`) |
| Limpiar seleccion | Esc, o click en area vacia |
| Toggle layout | Barra espaciadora |
| Fit camara | F |

## Force layout

El layout se ejecuta automaticamente cada frame mientras `state.layout_running == true`. Se detiene solo cuando la energia cinetica cae por debajo de `0.01`. Se puede pausar/reanudar con la barra espaciadora.

Los nodos arrastrados se marcan como `pinned = true` durante el drag, impidiendo que el force layout los mueva. Al soltar, `pinned` vuelve a `false`.

## Tooltip

Al hacer hover sobre un nodo se muestra un tooltip con: label, id numerico, community, degree (aristas conectadas) y value.

## Status bar

En la parte inferior del widget aparece: numero de nodos, aristas, zoom actual, energia del layout y recordatorio de atajos de teclado.

## Inicializacion lazy

El renderer OpenGL y el spatial hash se crean en el primer frame. La camara se ajusta automaticamente con `graph_viewport_fit` en la inicializacion.

## Notas de version

- **v1.2** (2026-04-29, issue 0049i): multi-seleccion con `state.selection`, lasso (Shift+Drag sobre area vacia), drag de seleccion entera (todos los nodos seleccionados pinnean y se mueven juntos), Ctrl+click toggle, Esc limpia seleccion. Callbacks opcionales `GraphViewportCallbacks` para right-click (menu contextual) y double-click. Los nodos arrastrados se quedan pinned al soltar. Tooltip suprimido durante drag/lasso. Helpers expuestos: `graph_viewport_clear_selection`, `graph_viewport_add_to_selection`, `graph_viewport_toggle_selection`, `graph_viewport_is_selected`. Tests: `cpp/tests/test_graph_viewport.cpp`.
- **v1.1** (2026-04-29, issue 0049e): adapta el viewport al modelo extendido. Hover/seleccion ahora se publican tambien como `flags |= NF_HOVERED` / `NF_SELECTED` en el grafo (clear-then-set) — los `state.hovered_node` / `selected_node` siguen siendo la API estable. El drag usa `flags |= NF_PINNED` en lugar del campo `pinned` desaparecido. El tooltip muestra `Type` (nombre del EntityType si esta) y `user_data` en lugar de `community`/`value`/`label`/`id`.

## Notas de implementacion

- Usa `ImGui::InvisibleButton` con flags para los tres botones del raton, capturando input sin dibujar ningun boton visible.
- La textura del renderer se muestra con UV volteado en Y (`ImVec2(0,1)` a `ImVec2(1,0)`) para corregir la convencion de coordenadas de OpenGL vs ImGui.
- El spatial hash se reconstruye cada frame desde las posiciones actuales de los nodos, garantizando hit-testing correcto despues de drag o layout.
- El zoom hacia el cursor mantiene el punto del grafo bajo el cursor fijo en pantalla ajustando `cam_x`/`cam_y`.

## Split de TU (2026-05-04, ADR 0003)

Los helpers puros `graph_viewport_clear_selection`, `graph_viewport_is_selected`,
`graph_viewport_add_to_selection` y `graph_viewport_toggle_selection` viven en
`graph_viewport_selection.cpp` y se indexan como entrada propia
`graph_viewport_selection_cpp_viz`. Esta entrada solo cubre el widget completo
con pan/zoom/click (impuro, ImGui).

Apps que reusan `graph_viewport` enlazan ambos `.cpp` y declaran ambas IDs en
su `app.md`. Apps que solo necesitan la maquinaria de seleccion (poco probable)
pueden declarar solo `graph_viewport_selection_cpp_viz`.
