--- 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 `` + ``. ## 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)`.