From 3e0d3d612a10cf41ccc71397538ab2d8021901f2 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Tue, 28 Apr 2026 23:38:51 +0200 Subject: [PATCH] fix(cpp/viz,core): bell icon TI_BELL, candlestick Setup-inside-BeginPlot, pie legend, kpi sparkline a la derecha MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - toast.cpp: TI_BELL en lugar de \xf0\x9f\x94\x94 (fuera del rango cargado por icon_font, renderizaba como ?) - candlestick.cpp: SetupAxes/SetupAxisScale/SetupAxisLimits movidos dentro de BeginPlot/EndPlot — antes el plot desaparecia al entrar - pie_chart.cpp: SetupLegend(East, Outside, NoButtons), eliminado NoLegend - kpi_card.cpp: layout 2 cols con sparkline a la derecha centrado verticalmente --- cpp/functions/core/toast.cpp | 8 ++++-- cpp/functions/viz/candlestick.cpp | 21 ++++++++------ cpp/functions/viz/kpi_card.cpp | 46 +++++++++++++++++++++++++------ cpp/functions/viz/pie_chart.cpp | 7 +++-- 4 files changed, 59 insertions(+), 23 deletions(-) diff --git a/cpp/functions/core/toast.cpp b/cpp/functions/core/toast.cpp index 30dda5d9..a5d10f38 100644 --- a/cpp/functions/core/toast.cpp +++ b/cpp/functions/core/toast.cpp @@ -1,5 +1,6 @@ #include "core/toast.h" #include "core/tokens.h" +#include "core/icons_tabler.h" #include #include #include @@ -171,10 +172,11 @@ void toast_inbox_button(const char* id) { ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, radius::sm); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(spacing::xs, spacing::xs)); - // Label: campana UTF-8 (U+1F514 = bell). Si la fuente no lo tiene, - // fallback a "!". + // Label: TI_BELL (Tabler icon, dentro del rango 0xE000-0xFCFF cargado por + // icon_font.cpp). Antes usabamos un emoji U+1F514 (4-byte UTF-8) que cae + // fuera del rango y se renderizaba como '?'. char label[64]; - std::snprintf(label, sizeof(label), "\xf0\x9f\x94\x94%s", id); + std::snprintf(label, sizeof(label), TI_BELL "%s", id); ImVec2 btn_pos = ImGui::GetCursorScreenPos(); const bool clicked = ImGui::Button(label, ImVec2(28.0f, 28.0f)); diff --git a/cpp/functions/viz/candlestick.cpp b/cpp/functions/viz/candlestick.cpp index 508ee571..1c72e2e5 100644 --- a/cpp/functions/viz/candlestick.cpp +++ b/cpp/functions/viz/candlestick.cpp @@ -12,16 +12,19 @@ void candlestick(const char* title, const double* dates, const double* opens, double spacing = (count > 1) ? (dates[1] - dates[0]) : 1.0; double half_w = spacing * (double)width_percent * 0.5; - ImPlot::SetupAxes("Date", "Price", ImPlotAxisFlags_None, ImPlotAxisFlags_AutoFit); - ImPlot::SetupAxisScale(ImAxis_X1, ImPlotScale_Time); - - // Auto-fit X axis to the data range with a small margin. - ImPlot::SetupAxisLimits(ImAxis_X1, - dates[0] - spacing, - dates[count - 1] + spacing, - ImGuiCond_Always); - if (ImPlot::BeginPlot(title, ImVec2(-1, 0))) { + // ImPlot Setup* calls DEBEN ir dentro de BeginPlot/EndPlot. Antes + // estaban fuera y dejaban el plot sin ejes, lo que hacia que el demo + // de candlestick "desapareciera" al entrar en la galeria. + ImPlot::SetupAxes("Date", "Price", + ImPlotAxisFlags_None, + ImPlotAxisFlags_AutoFit); + ImPlot::SetupAxisScale(ImAxis_X1, ImPlotScale_Time); + ImPlot::SetupAxisLimits(ImAxis_X1, + dates[0] - spacing, + dates[count - 1] + spacing, + ImGuiCond_Always); + ImDrawList* draw = ImPlot::GetPlotDrawList(); const ImU32 col_bull = IM_COL32(0, 200, 80, 255); // green — close >= open diff --git a/cpp/functions/viz/kpi_card.cpp b/cpp/functions/viz/kpi_card.cpp index 0398be82..e520e013 100644 --- a/cpp/functions/viz/kpi_card.cpp +++ b/cpp/functions/viz/kpi_card.cpp @@ -36,6 +36,20 @@ void kpi_card(const char* label, float value, float delta_percent, ImGuiChildFlags_Borders, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + const bool has_history = history != nullptr && history_count > 0; + const bool has_delta = delta_percent != 0.0f; + + // Layout de dos columnas: izquierda info (label, value, delta), derecha sparkline. + // El sparkline se sitia verticalmente centrado a la derecha de la card. + constexpr float spark_w = 100.0f; + constexpr float spark_h = 36.0f; + + const float inner_w = ImGui::GetContentRegionAvail().x; + const float info_w = has_history ? (inner_w - spark_w - spacing::sm) : inner_w; + + ImGui::BeginGroup(); + ImGui::PushItemWidth(info_w); + // Top row: optional icon + label, ambos en text_muted. ImGui::PushStyleColor(ImGuiCol_Text, colors::text_muted); if (icon && *icon) { @@ -53,9 +67,6 @@ void kpi_card(const char* label, float value, float delta_percent, ImGui::SetWindowFontScale(1.0f); // Delta / trend — SIEMPRE se reserva la linea aunque no haya tendencia. - const bool has_delta = delta_percent != 0.0f; - const bool has_history = history != nullptr && history_count > 0; - if (has_delta) { const bool positive = delta_percent >= 0.0f; const ImVec4 delta_color = positive ? colors::success : colors::error; @@ -68,12 +79,6 @@ void kpi_card(const char* label, float value, float delta_percent, ImGui::PushStyleColor(ImGuiCol_Text, delta_color); ImGui::TextUnformatted(delta_buf); ImGui::PopStyleColor(); - - if (has_history) { - sparkline(label, history, history_count, delta_color, 120.0f, 24.0f); - } - } else if (has_history) { - sparkline(label, history, history_count, colors::primary, 120.0f, 24.0f); } else { // Placeholder para preservar altura. ImGui::PushStyleColor(ImGuiCol_Text, colors::text_dim); @@ -81,6 +86,29 @@ void kpi_card(const char* label, float value, float delta_percent, ImGui::PopStyleColor(); } + ImGui::PopItemWidth(); + ImGui::EndGroup(); + + // Sparkline a la derecha, centrado verticalmente respecto a la card. + if (has_history) { + const ImVec4 spark_color = has_delta + ? (delta_percent >= 0.0f ? colors::success : colors::error) + : colors::primary; + + ImGui::SameLine(); + + // Centrar verticalmente: la card mide card_height, el sparkline spark_h. + // Restamos el padding interno (spacing::sm) ya consumido al inicio. + const float card_inner_h = card_height - 2.0f * spacing::sm; + float y_offset = (card_inner_h - spark_h) * 0.5f; + if (y_offset < 0.0f) y_offset = 0.0f; + // Anclamos el sparkline al borde derecho. + const float spark_x = inner_w - spark_w; + ImGui::SetCursorPos(ImVec2(spacing::sm + spark_x, + spacing::sm + y_offset)); + sparkline(label, history, history_count, spark_color, spark_w, spark_h); + } + ImGui::EndChild(); ImGui::PopStyleVar(3); diff --git a/cpp/functions/viz/pie_chart.cpp b/cpp/functions/viz/pie_chart.cpp index 02a2bf3e..9a4d481e 100644 --- a/cpp/functions/viz/pie_chart.cpp +++ b/cpp/functions/viz/pie_chart.cpp @@ -52,9 +52,10 @@ void draw_pie(const char* title, const char* const* labels, const T* values, 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 - | ImPlotFlags_NoLegend; + | ImPlotFlags_Equal; if (ImPlot::BeginPlot(title, plot_size, plot_flags)) { ImPlot::SetupAxes(nullptr, nullptr, @@ -62,6 +63,8 @@ void draw_pie(const char* title, const char* const* labels, const T* values, 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);