Files
fn_registry/cpp/functions/viz/graph_labels.cpp
T
egutierrez 4b28ef6774 feat(viz): graph_labels render con LabelPolicy + ImDrawList (issue 0049j)
graph_labels_draw pinta etiquetas de nodos sobre el FBO del graph_renderer
via ImDrawList. Politica configurable: always-on para selected/hovered/
pinned, top-N por size*(degree+1), culling por viewport AABB y
min_node_pixel_size. Cap duro = max_visible + |always_*|.

API:
- graph_labels_draw(graph, viewport_state, policy, cb, user)
- graph_labels_draw_at(...)  — variante con rect explicito
- graph_labels_select(...)   — helper puro testeable
- graph_compute_degrees(...) — O(E)

Splitting en dos TUs:
- graph_labels.cpp          — funciones draw (depende de ImGui)
- graph_labels_select.cpp   — helpers puros para tests sin ImGui

12 tests en test_graph_labels (culling, max_visible cap, min_pixel_size,
always_* gating por viewport, top-N por score, edge cases). Todos verdes.

Integrado en demos_graph con UI: toggle Labels, sliders Max visible /
Font / Min px, checkboxes Selected/Hovered/Pinned. Golden de
graph_viewport regenerado.

Cierra issue 0049j.
2026-04-29 23:53:32 +02:00

84 lines
2.9 KiB
C++

// Pintado de etiquetas via ImDrawList (issue 0049j). La logica de seleccion
// de candidatos (frustum cull + top-N) vive en graph_labels_select.cpp para
// que sea testeable sin enlazar ImGui.
#include "viz/graph_labels.h"
#include "viz/graph_types.h"
#include "viz/graph_viewport.h"
#include "imgui.h"
#include <cfloat>
#include <vector>
namespace graph {
void graph_labels_draw_at(const GraphData& g,
float cam_x, float cam_y, float zoom,
float wmin_x, float wmin_y,
float w, float h,
const LabelPolicy& p,
GetLabelFn cb, void* user)
{
if (!cb) return;
if (g.node_count <= 0) return;
if (w <= 0.0f || h <= 0.0f) return;
// Degrees on-the-fly: O(E) por frame. Cachear si se vuelve hot path.
static thread_local std::vector<int> degrees;
degrees.assign((size_t)g.node_count, 0);
graph_compute_degrees(g, degrees.data());
// Cap duro: max_visible + |always_*|. |always_*| <= node_count.
const int max_total = p.max_visible + g.node_count;
static thread_local std::vector<int> indices;
indices.resize((size_t)max_total);
const int n_labels = graph_labels_select(
g, p, cam_x, cam_y, zoom, w, h,
degrees.data(), indices.data(), max_total);
if (n_labels <= 0) return;
ImDrawList* dl = ImGui::GetWindowDrawList();
ImFont* font = ImGui::GetFont();
if (!dl || !font) return;
const float cx = wmin_x + w * 0.5f;
const float cy = wmin_y + h * 0.5f;
for (int k = 0; k < n_labels; ++k) {
const int idx = indices[k];
const GraphNode& n = g.nodes[idx];
const char* text = cb(idx, user);
if (!text || !*text) continue;
const float vx = (n.x - cam_x) * zoom + cx;
const float vy = (n.y - cam_y) * zoom + cy;
const float node_px = resolve_node_size(n, g.types, g.type_count) * zoom;
const float ox = node_px * 0.5f + p.padding_x;
const float oy = -p.font_size * 0.5f;
const ImVec2 ts = font->CalcTextSizeA(p.font_size, FLT_MAX, 0.0f, text);
const ImVec2 a(vx + ox - p.padding_x, vy + oy - p.padding_y);
const ImVec2 b(vx + ox + ts.x + p.padding_x, vy + oy + ts.y + p.padding_y);
dl->AddRectFilled(a, b, p.bg_color, 2.0f);
dl->AddText(font, p.font_size, ImVec2(vx + ox, vy + oy),
p.color, text);
}
}
void graph_labels_draw(const GraphData& g, const GraphViewportState& s,
const LabelPolicy& p, GetLabelFn cb, void* user)
{
const ImVec2 wmin = ImGui::GetItemRectMin();
const ImVec2 wmax = ImGui::GetItemRectMax();
const float w = wmax.x - wmin.x;
const float h = wmax.y - wmin.y;
graph_labels_draw_at(g, s.cam_x, s.cam_y, s.zoom,
wmin.x, wmin.y, w, h, p, cb, user);
}
} // namespace graph