#include "viz/treemap.h" #include #include #include namespace { // Worst aspect ratio en la fila si aƱadimos `next` (Bruls et al.). // row: vector de areas (mismo orden que items), w: lado corto del rect actual. float worst_ratio(const std::vector& row, float w) { if (row.empty() || w <= 0.0f) return 1e30f; float sum = 0.0f, mn = row[0], mx = row[0]; for (float v : row) { sum += v; if (v < mn) mn = v; if (v > mx) mx = v; } if (sum <= 0.0f) return 1e30f; float w2 = w * w; float s2 = sum * sum; float r1 = (w2 * mx) / s2; float r2 = s2 / (w2 * mn); return std::max(r1, r2); } // Coloca una fila en el rect actual, devuelve el rect remanente. // Si el rect es mas ancho que alto, la fila ocupa el lado izquierdo (vertical strip). struct RowOut { ImVec2 next_min; ImVec2 next_max; }; RowOut place_row(const std::vector& row, const std::vector& row_idx, ImVec2 rect_min, ImVec2 rect_max, std::vector& out) { float W = rect_max.x - rect_min.x; float H = rect_max.y - rect_min.y; float sum = 0.0f; for (float v : row) sum += v; if (sum <= 0.0f) return {rect_min, rect_max}; bool horizontal_strip = (W >= H); // strip ocupa lado izquierdo (vertical column de cells horizontales) if (horizontal_strip) { float strip_w = sum / H; float y = rect_min.y; for (size_t i = 0; i < row.size(); i++) { float h = row[i] / strip_w; TreemapRect r; r.min = ImVec2(rect_min.x, y); r.max = ImVec2(rect_min.x + strip_w, std::min(y + h, rect_max.y)); r.item = nullptr; out[row_idx[i]] = r; y += h; } return {ImVec2(rect_min.x + strip_w, rect_min.y), rect_max}; } else { float strip_h = sum / W; float x = rect_min.x; for (size_t i = 0; i < row.size(); i++) { float w = row[i] / strip_h; TreemapRect r; r.min = ImVec2(x, rect_min.y); r.max = ImVec2(std::min(x + w, rect_max.x), rect_min.y + strip_h); r.item = nullptr; out[row_idx[i]] = r; x += w; } return {rect_min, ImVec2(rect_max.x, rect_min.y + strip_h)}; } } } // namespace std::vector treemap_layout(const std::vector& items, ImVec2 region) { std::vector out(items.size(), {ImVec2(0,0), ImVec2(0,0), nullptr}); if (items.empty() || region.x <= 0.0f || region.y <= 0.0f) return out; // Filtra y ordena indices por value desc. std::vector idx; idx.reserve(items.size()); float total = 0.0f; for (size_t i = 0; i < items.size(); i++) { if (items[i].value > 0.0f) { idx.push_back((int)i); total += items[i].value; } } if (idx.empty() || total <= 0.0f) return out; std::sort(idx.begin(), idx.end(), [&](int a, int b) { return items[a].value > items[b].value; }); // Areas escaladas al area total del rect. float region_area = region.x * region.y; float scale = region_area / total; std::vector areas(idx.size()); for (size_t i = 0; i < idx.size(); i++) areas[i] = items[idx[i]].value * scale; ImVec2 rmin(0.0f, 0.0f); ImVec2 rmax = region; std::vector row; std::vector row_indices; // indices en `out` (= idx[i]) size_t cursor = 0; while (cursor < idx.size()) { float w = std::min(rmax.x - rmin.x, rmax.y - rmin.y); if (w <= 0.0f) break; std::vector row_try = row; row_try.push_back(areas[cursor]); float wr_now = worst_ratio(row, w); float wr_next = worst_ratio(row_try, w); if (row.empty() || wr_next <= wr_now) { row = row_try; row_indices.push_back(idx[cursor]); cursor++; } else { RowOut ro = place_row(row, row_indices, rmin, rmax, out); rmin = ro.next_min; rmax = ro.next_max; row.clear(); row_indices.clear(); } } if (!row.empty()) { place_row(row, row_indices, rmin, rmax, out); } // Asocia el item. for (size_t i = 0; i < items.size(); i++) { out[i].item = &items[i]; } return out; } void treemap(const char* id, const std::vector& items, ImVec2 size) { ImGui::PushID(id); ImVec2 avail = ImGui::GetContentRegionAvail(); float w = (size.x > 0.0f) ? size.x : avail.x; float h = (size.y > 0.0f) ? size.y : 200.0f; ImVec2 origin = ImGui::GetCursorScreenPos(); ImGui::Dummy(ImVec2(w, h)); auto rects = treemap_layout(items, ImVec2(w, h)); ImDrawList* dl = ImGui::GetWindowDrawList(); // Borde exterior tenue dl->AddRect(origin, ImVec2(origin.x + w, origin.y + h), IM_COL32(80, 80, 90, 200), 0.0f, 0, 1.0f); for (const auto& r : rects) { if (!r.item) continue; ImVec2 a(origin.x + r.min.x, origin.y + r.min.y); ImVec2 b(origin.x + r.max.x, origin.y + r.max.y); if (b.x - a.x < 1.0f || b.y - a.y < 1.0f) continue; dl->AddRectFilled(a, b, r.item->color); dl->AddRect(a, b, IM_COL32(20, 22, 28, 220), 0.0f, 0, 1.0f); // Label si cabe const char* lbl = r.item->label.c_str(); ImVec2 ts = ImGui::CalcTextSize(lbl); float cell_w = b.x - a.x; float cell_h = b.y - a.y; if (ts.x + 6.0f <= cell_w && ts.y + 4.0f <= cell_h) { dl->AddText(ImVec2(a.x + 4.0f, a.y + 3.0f), IM_COL32(245, 246, 250, 240), lbl); // valor en segunda linea si tambien cabe char buf[32]; std::snprintf(buf, sizeof(buf), "%.0f", r.item->value); ImVec2 vs = ImGui::CalcTextSize(buf); if (vs.x + 6.0f <= cell_w && ts.y + vs.y + 6.0f <= cell_h) { dl->AddText(ImVec2(a.x + 4.0f, a.y + 3.0f + ts.y + 1.0f), IM_COL32(220, 222, 235, 200), buf); } } } ImGui::PopID(); }