#include "viz/pie_chart.h" #include "viz/plot_static.h" #include "imgui.h" #include "implot.h" #include namespace { // Localiza que slice del pie esta bajo el cursor. // Devuelve -1 si el cursor esta fuera del radio del pie. // // ImPlot dibuja slices en sentido ANTIHORARIO (matematico) desde angle0=90 // grados (arriba). atan2(dy, dx) en ejes Y-up devuelve radianes CCW desde +x, // asi que en coords de datos: +x=0, +y=90 (arriba), -x=180, -y=-90 (abajo). // El offset desde "arriba" en sentido CCW es angle_deg - 90, normalizado. template int slice_at(const T* values, int count, double total, double mouse_x, double mouse_y, double cx, double cy, double radius) { double dx = mouse_x - cx; double dy = mouse_y - cy; double r = std::sqrt(dx * dx + dy * dy); if (r > radius) return -1; constexpr double kPI = 3.14159265358979323846; double angle_deg = std::atan2(dy, dx) * 180.0 / kPI; double offset = angle_deg - 90.0; // desde arriba, sentido CCW while (offset < 0.0) offset += 360.0; while (offset >= 360.0) offset -= 360.0; double acc = 0.0; for (int i = 0; i < count; i++) { double sweep = (static_cast(values[i]) / total) * 360.0; if (offset >= acc && offset < acc + sweep) return i; acc += sweep; } return count - 1; } template void draw_pie(const char* title, const char* const* labels, const T* values, int count, double radius, float height) { if (count <= 0) return; double total = 0.0; for (int i = 0; i < count; i++) total += static_cast(values[i]); if (total <= 0.0) return; double outer; if (radius < 0.0) outer = -radius; else outer = (radius > 0.0) ? radius : 0.4; const ImVec2 plot_size(-1.0f, height > 0.0f ? height : 200.0f); // Legend SIEMPRE visible para que se distingan las slices por color (los // pies suelen tener muchas categorias). const ImPlotFlags plot_flags = plot_static::kPlotFlags | ImPlotFlags_Equal; if (ImPlot::BeginPlot(title, plot_size, plot_flags)) { ImPlot::SetupAxes(nullptr, nullptr, plot_static::kAxisFlagsHidden, plot_static::kAxisFlagsHidden); ImPlot::SetupAxisLimits(ImAxis_X1, 0.0, 1.0, ImPlotCond_Always); ImPlot::SetupAxisLimits(ImAxis_Y1, 0.0, 1.0, ImPlotCond_Always); ImPlot::SetupLegend(ImPlotLocation_East, ImPlotLegendFlags_Outside | ImPlotLegendFlags_NoButtons); ImPlot::PlotPieChart(labels, values, count, 0.5, 0.5, outer, "%.0f", 90.0); if (ImPlot::IsPlotHovered()) { ImPlotPoint mp = ImPlot::GetPlotMousePos(); int idx = slice_at(values, count, total, mp.x, mp.y, 0.5, 0.5, outer); if (idx >= 0) { double v = static_cast(values[idx]); double pct = 100.0 * v / total; ImGui::BeginTooltip(); ImGui::TextUnformatted(labels[idx]); ImGui::Separator(); ImGui::Text("%.0f (%.1f%%)", v, pct); ImGui::EndTooltip(); } } ImPlot::EndPlot(); } } } // namespace void pie_chart(const char* title, const char* const* labels, const float* values, int count, float radius, float height) { draw_pie(title, labels, values, count, static_cast(radius), height); } void pie_chart(const char* title, const char* const* labels, const double* values, int count, double radius, float height) { draw_pie(title, labels, values, count, radius, height); }