---
name: graph_labels_select
kind: function
lang: cpp
domain: viz
version: "1.0.0"
purity: pure
signature: "void graph::graph_compute_degrees(const GraphData& g, int* out_degrees); int graph::graph_labels_select(const GraphData& g, const LabelPolicy& p, float cam_x, float cam_y, float zoom, float widget_w, float widget_h, const int* degrees, int* out_indices, int out_capacity)"
description: "Helpers puros (sin ImGui ni OpenGL) que calculan los nodos a etiquetar cada frame: graph_compute_degrees calcula el grado por nodo (solo aristas EF_VISIBLE), graph_labels_select aplica frustum cull + always_for_* + top-N por (size * (degree+1)) sobre min_node_pixel_size."
tags: [graph, labels, pure, culling, top-n, frustum, testable]
uses_functions: []
uses_types: ["GraphData_cpp_viz"]
returns: []
returns_optional: false
error_type: ""
imports: []
tested: true
tests: ["select respects max_visible cap", "min_node_pixel_size culling", "always_for_selected gates by viewport", "no callback / empty graph no-op"]
test_file_path: "cpp/tests/test_graph_labels.cpp"
file_path: "cpp/functions/viz/graph_labels_select.cpp"
framework: imgui
params:
  - name: g
    desc: "Grafo a procesar (lectura). Se respetan NF_VISIBLE, NF_SELECTED, NF_HOVERED, NF_PINNED, EF_VISIBLE."
  - name: out_degrees
    desc: "(compute_degrees) Array de tamaño node_count que recibe el grado por nodo. Cuenta solo aristas con EF_VISIBLE."
  - name: p
    desc: "LabelPolicy: max_visible (top-N), always_for_*, min_zoom_for_all, min_node_pixel_size."
  - name: cam_x
    desc: "Centro de la camara en world coords (X)."
  - name: cam_y
    desc: "Centro de la camara en world coords (Y)."
  - name: zoom
    desc: "Pixels por unidad world. zoom <= 0 → no-op."
  - name: widget_w
    desc: "Ancho del widget en pixeles."
  - name: widget_h
    desc: "Alto del widget en pixeles."
  - name: degrees
    desc: "Array de tamaño node_count con el grado por nodo. NULL → score = size sin factor degree."
  - name: out_indices
    desc: "Array de salida con los indices de nodos a etiquetar. Capacidad = out_capacity."
  - name: out_capacity
    desc: "Capacidad maxima de out_indices. La funcion no escribe mas alla."
output: "graph_compute_degrees: void. graph_labels_select: numero de indices escritos en out_indices. Cap duro = max_visible + |always_*|."
notes: "Issue 0049j. TU separado de graph_labels.cpp para que los tests unitarios puedan cubrir la seleccion de candidatos sin pintar nada (graph_labels.cpp depende de ImGui). Estrategia: (A) nodos always_* en viewport sin chequeo de pixel size; (B) resto de nodos visibles que cumplen min_node_pixel_size, capados al top-N por (size * (degree+1)). Off-screen always_* se OMITEN intencionadamente. El degree se puede precalcular (O(E)) y pasar al select para evitar recomputar."
---

# graph_labels_select

Logica pura de seleccion de etiquetas para `graph_labels`. Vive en su propio TU
(`graph_labels_select.cpp`) — separada de `graph_labels.cpp` (que tiene la parte
de pintado con ImGui) — para que los tests unitarios puedan ejercerla sin abrir
una ventana.

## API

```cpp
namespace graph {
    // Grado por nodo (cuenta solo aristas EF_VISIBLE). O(E).
    void graph_compute_degrees(const GraphData& g, int* out_degrees);

    // Devuelve los indices de los nodos a etiquetar este frame.
    // O(N) frustum cull + O(K log K) si K candidatos > max_visible.
    int graph_labels_select(const GraphData& g, const LabelPolicy& p,
                            float cam_x, float cam_y, float zoom,
                            float widget_w, float widget_h,
                            const int* degrees,
                            int* out_indices, int out_capacity);
}
```

## Composabilidad

- Lo usa `graph_labels_draw` y `graph_labels_draw_at` internamente cada frame.
- Una app puede llamarlo directamente para pintar etiquetas con su propio
  renderer (no-ImGui) reusando la misma politica.
- Sin dependencias de ImGui ni OpenGL — puro `<algorithm>` + `<vector>`.

## Estrategia (resumen)

1. **Pase A — always_***: nodos selected/hovered/pinned visibles en viewport.
   Skip de `min_node_pixel_size`. Off-screen NO entra (decision documentada).
2. **Pase B — top-N**: del resto de nodos visibles en viewport con
   `size * zoom >= min_node_pixel_size`, ordena por `score = size * (degree+1)`
   y coge los `max_visible` primeros (`std::partial_sort`).
3. **Cap duro**: `max_visible + |always_*|`. Imposible saltarse — si quieres 0
   labels excepto always, pon `max_visible = 0`.

## Tests

Cubiertos en `cpp/tests/test_graph_labels.cpp`. La funcion devuelve int sin
tocar ImGui — pasable a `assert(graph_labels_select(...) == N)`.
