From 1d9381a004b62421b640885052df8a545e45a96f Mon Sep 17 00:00:00 2001 From: Evan Pezent Date: Wed, 17 Mar 2021 07:38:45 -0500 Subject: [PATCH] Adds PlotHistogram and PlotHistogram2D, Improves Colormaps and Heatmap (#148) --- implot.cpp | 814 +++++++++++++++++++++++++--------------------- implot.h | 270 ++++++++++----- implot_demo.cpp | 274 +++++++++++----- implot_internal.h | 327 +++++++++++++++---- implot_items.cpp | 452 +++++++++++++++++-------- 5 files changed, 1417 insertions(+), 720 deletions(-) diff --git a/implot.cpp b/implot.cpp index 2a739b0..7abfe22 100644 --- a/implot.cpp +++ b/implot.cpp @@ -1,6 +1,6 @@ // MIT License -// Copyright (c) 2020 Evan Pezent +// Copyright (c) 2021 Evan Pezent // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -31,6 +31,8 @@ Below is a change-log of API breaking changes only. If you are using one of the When you are not sure about a old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all implot files. You can read releases logs https://github.com/epezent/implot/releases for more details. +- 2021/03/08 (0.9) - SetColormap and PushColormap(ImVec4*) were removed. Use AddColormap for custom colormap support. LerpColormap was changed to SampleColormap. + ShowColormapScale was changed to ColormapScale and requires additional arguments. - 2021/03/07 (0.9) - The signature of ShowColormapScale was modified to accept a ImVec2 size. - 2021/02/28 (0.9) - BeginLegendDragDropSource was changed to BeginDragDropSourceItem with a number of other drag and drop improvements. - 2021/01/18 (0.9) - The default behavior for opening context menus was change from double right-click to single right-click. ImPlotInputMap and related functions were moved @@ -127,7 +129,7 @@ ImPlotStyle::ImPlotStyle() { LabelPadding = ImVec2(5,5); LegendPadding = ImVec2(10,10); LegendInnerPadding = ImVec2(5,5); - LegendSpacing = ImVec2(0,0); + LegendSpacing = ImVec2(5,0); MousePosPadding = ImVec2(10,10); AnnotationPadding = ImVec2(2,2); FitPadding = ImVec2(0,0); @@ -136,6 +138,8 @@ ImPlotStyle::ImPlotStyle() { ImPlot::StyleColorsAuto(this); + Colormap = ImPlotColormap_Deep; + AntiAliasedLines = false; UseLocalTime = false; Use24HourClock = false; @@ -388,9 +392,46 @@ void SetCurrentContext(ImPlotContext* ctx) { GImPlot = ctx; } +#define IMPLOT_APPEND_CMAP(name, qual) ctx->ColormapData.Append(#name, name, sizeof(name)/sizeof(ImU32), qual) +#define IM_RGB(r,g,b) IM_COL32(r,g,b,255) + void Initialize(ImPlotContext* ctx) { Reset(ctx); - ctx->Colormap = GetColormap(ImPlotColormap_Default, &ctx->ColormapSize); + + const ImU32 Deep[] = {4289753676, 4283598045, 4285048917, 4283584196, 4289950337, 4284512403, 4291005402, 4287401100, 4285839820, 4291671396 }; + const ImU32 Dark[] = {4280031972, 4290281015, 4283084621, 4288892568, 4278222847, 4281597951, 4280833702, 4290740727, 4288256409 }; + const ImU32 Pastel[] = {4289639675, 4293119411, 4291161036, 4293184478, 4289124862, 4291624959, 4290631909, 4293712637, 4294111986 }; + const ImU32 Paired[] = {4293119554, 4290017311, 4287291314, 4281114675, 4288256763, 4280031971, 4285513725, 4278222847, 4292260554, 4288298346, 4288282623, 4280834481}; + const ImU32 Viridis[] = {4283695428, 4285867080, 4287054913, 4287455029, 4287526954, 4287402273, 4286883874, 4285579076, 4283552122, 4280737725, 4280674301 }; + const ImU32 Plasma[] = {4287039501, 4288480321, 4289200234, 4288941455, 4287638193, 4286072780, 4284638433, 4283139314, 4281771772, 4280667900, 4280416752 }; + const ImU32 Hot[] = {4278190144, 4278190208, 4278190271, 4278190335, 4278206719, 4278223103, 4278239231, 4278255615, 4283826175, 4289396735, 4294967295 }; + const ImU32 Cool[] = {4294967040, 4294960666, 4294954035, 4294947661, 4294941030, 4294934656, 4294928025, 4294921651, 4294915020, 4294908646, 4294902015 }; + const ImU32 Pink[] = {4278190154, 4282532475, 4284308894, 4285690554, 4286879686, 4287870160, 4288794330, 4289651940, 4291685869, 4293392118, 4294967295 }; + const ImU32 Jet[] = {4289331200, 4294901760, 4294923520, 4294945280, 4294967040, 4289396565, 4283826090, 4278255615, 4278233855, 4278212095, 4278190335 }; + const ImU32 Twilight[] = {IM_RGB(226,217,226),IM_RGB(166,191,202),IM_RGB(109,144,192),IM_RGB(95,88,176),IM_RGB(83,30,124),IM_RGB(47,20,54),IM_RGB(100,25,75),IM_RGB(159,60,80),IM_RGB(192,117,94),IM_RGB(208,179,158),IM_RGB(226,217,226)}; + const ImU32 RdBu[] = {IM_RGB(103,0,31),IM_RGB(178,24,43),IM_RGB(214,96,77),IM_RGB(244,165,130),IM_RGB(253,219,199),IM_RGB(247,247,247),IM_RGB(209,229,240),IM_RGB(146,197,222),IM_RGB(67,147,195),IM_RGB(33,102,172),IM_RGB(5,48,97)}; + const ImU32 BrBG[] = {IM_RGB(84,48,5),IM_RGB(140,81,10),IM_RGB(191,129,45),IM_RGB(223,194,125),IM_RGB(246,232,195),IM_RGB(245,245,245),IM_RGB(199,234,229),IM_RGB(128,205,193),IM_RGB(53,151,143),IM_RGB(1,102,94),IM_RGB(0,60,48)}; + const ImU32 PiYG[] = {IM_RGB(142,1,82),IM_RGB(197,27,125),IM_RGB(222,119,174),IM_RGB(241,182,218),IM_RGB(253,224,239),IM_RGB(247,247,247),IM_RGB(230,245,208),IM_RGB(184,225,134),IM_RGB(127,188,65),IM_RGB(77,146,33),IM_RGB(39,100,25)}; + const ImU32 Spectral[] = {IM_RGB(158,1,66),IM_RGB(213,62,79),IM_RGB(244,109,67),IM_RGB(253,174,97),IM_RGB(254,224,139),IM_RGB(255,255,191),IM_RGB(230,245,152),IM_RGB(171,221,164),IM_RGB(102,194,165),IM_RGB(50,136,189),IM_RGB(94,79,162)}; + const ImU32 Greys[] = {IM_COL32_WHITE, IM_COL32_BLACK }; + + IMPLOT_APPEND_CMAP(Deep, true); + IMPLOT_APPEND_CMAP(Dark, true); + IMPLOT_APPEND_CMAP(Pastel, true); + IMPLOT_APPEND_CMAP(Paired, true); + IMPLOT_APPEND_CMAP(Viridis, false); + IMPLOT_APPEND_CMAP(Plasma, false); + IMPLOT_APPEND_CMAP(Hot, false); + IMPLOT_APPEND_CMAP(Cool, false); + IMPLOT_APPEND_CMAP(Pink, false); + IMPLOT_APPEND_CMAP(Jet, false); + IMPLOT_APPEND_CMAP(Twilight, false); + IMPLOT_APPEND_CMAP(RdBu, false); + IMPLOT_APPEND_CMAP(BrBG, false); + IMPLOT_APPEND_CMAP(PiYG, false); + IMPLOT_APPEND_CMAP(Spectral, false); + IMPLOT_APPEND_CMAP(Greys, false); + } void Reset(ImPlotContext* ctx) { @@ -599,8 +640,8 @@ void ShowLegendEntries(ImPlotPlot& plot, const ImRect& legend_bb, bool interacta const float txt_ht = ImGui::GetTextLineHeight(); const float icon_size = txt_ht; const float icon_shrink = 2; - ImVec4 col_txt = GetStyleColorVec4(ImPlotCol_LegendText); - ImU32 col_txt_dis = ImGui::GetColorU32(col_txt * ImVec4(1,1,1,0.25f)); + ImU32 col_txt = GetStyleColorU32(ImPlotCol_LegendText); + ImU32 col_txt_dis = ImAlphaU32(col_txt, 0.25f); // render each legend item float sum_label_width = 0; for (int i = 0; i < plot.GetLegendCount(); ++i) { @@ -618,26 +659,24 @@ void ShowLegendEntries(ImPlotPlot& plot, const ImRect& legend_bb, bool interacta label_bb.Min = top_left; label_bb.Max = top_left + ImVec2(label_width + icon_size, icon_size); ImU32 col_hl_txt; + ImU32 col_item = ImAlphaU32(item->Color,1); if (interactable && (icon_bb.Contains(IO.MousePos) || label_bb.Contains(IO.MousePos))) { item->LegendHovered = true; - col_hl_txt = ImGui::GetColorU32(ImLerp(col_txt, item->Color, 0.25f)); + col_hl_txt = ImMixU32(col_txt, col_item, 64); } else { // item->LegendHovered = false; col_hl_txt = ImGui::GetColorU32(col_txt); } ImU32 iconColor; - ImVec4 item_color = item->Color; - item_color.w = 1; if (interactable && icon_bb.Contains(IO.MousePos)) { - ImVec4 colAlpha = item_color; - colAlpha.w = 0.5f; - iconColor = item->Show ? ImGui::GetColorU32(colAlpha) : ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.5f); + ImU32 col_alpha = ImAlphaU32(col_item,0.5f); + iconColor = item->Show ? col_alpha : ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.5f); if (IO.MouseClicked[0]) item->Show = !item->Show; } else { - iconColor = item->Show ? ImGui::GetColorU32(item_color) : col_txt_dis; + iconColor = item->Show ? col_item : col_txt_dis; } DrawList.AddRectFilled(icon_bb.Min, icon_bb.Max, iconColor, 1); const char* text_display_end = ImGui::FindRenderedTextEnd(label, NULL); @@ -1342,7 +1381,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y1_label, con } // AXIS STATES ------------------------------------------------------------ - plot.XAxis.HasRange = gp.NextPlotData.HasXRange; plot.XAxis.RangeCond = gp.NextPlotData.XRangeCond; plot.XAxis.Present = true; + plot.XAxis.HasRange = gp.NextPlotData.HasXRange; plot.XAxis.RangeCond = gp.NextPlotData.XRangeCond; plot.XAxis.Present = true; plot.YAxis[0].HasRange = gp.NextPlotData.HasYRange[0]; plot.YAxis[0].RangeCond = gp.NextPlotData.YRangeCond[0]; plot.YAxis[0].Present = true; plot.YAxis[1].HasRange = gp.NextPlotData.HasYRange[1]; plot.YAxis[1].RangeCond = gp.NextPlotData.YRangeCond[1]; plot.YAxis[1].Present = ImHasFlag(plot.Flags, ImPlotFlags_YAxis2); plot.YAxis[2].HasRange = gp.NextPlotData.HasYRange[2]; plot.YAxis[2].RangeCond = gp.NextPlotData.YRangeCond[2]; plot.YAxis[2].Present = ImHasFlag(plot.Flags, ImPlotFlags_YAxis3); @@ -1363,12 +1402,12 @@ bool BeginPlot(const char* title, const char* x_label, const char* y1_label, con for (int i = 0; i < IMPLOT_Y_AXES; ++i) plot.YAxis[i].Constrain(); - // constain equal axes for primary x and y if not approximately equal - // constains x to y since x pixel size depends on y labels width, and causes feedback loops in opposite case + // constrain equal axes for primary x and y if not approximately equal + // constrains x to y since x pixel size depends on y labels width, and causes feedback loops in opposite case if (ImHasFlag(plot.Flags, ImPlotFlags_Equal)) { double xar = plot.XAxis.GetAspect(); double yar = plot.YAxis[0].GetAspect(); - if (!ImAlmostEqual(xar,yar) && !plot.YAxis[0].IsLocked()) + if (!ImAlmostEqual(xar,yar) && !plot.YAxis[0].IsInputLocked()) plot.XAxis.SetAspect(yar); } @@ -1442,7 +1481,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y1_label, con // plot bb // (1) calc top/bot padding and plot height - ImVec2 title_size = ImVec2(0.0f, 0.0f); + ImVec2 title_size(0.0f, 0.0f); const float txt_height = ImGui::GetTextLineHeight(); if (!ImHasFlag(plot.Flags, ImPlotFlags_NoTitle)){ title_size = ImGui::CalcTextSize(title, NULL, true); @@ -1503,12 +1542,10 @@ bool BeginPlot(const char* title, const char* x_label, const char* y1_label, con // axis label reference gp.YAxisReference[0] = plot.PlotRect.Min.x; gp.YAxisReference[1] = plot.PlotRect.Max.x; - gp.YAxisReference[2] = !plot.YAxis[1].Present - ? plot.PlotRect.Max.x - : gp.YAxisReference[1] - + (plot.YAxis[1].IsLabeled() ? gp.Style.LabelPadding.x + gp.YTicks[1].MaxWidth : 0) - + (show_y2_label ? txt_height + gp.Style.LabelPadding.x : 0) - + gp.Style.LabelPadding.x + gp.Style.MinorTickLen.y; + gp.YAxisReference[2] = !plot.YAxis[1].Present ? plot.PlotRect.Max.x : gp.YAxisReference[1] + + (plot.YAxis[1].IsLabeled() ? gp.Style.LabelPadding.x + gp.YTicks[1].MaxWidth : 0) + + (show_y2_label ? txt_height + gp.Style.LabelPadding.x : 0) + + gp.Style.LabelPadding.x + gp.Style.MinorTickLen.y; // y axis regions bb and hover plot.YAxis[0].HoverRect = ImRect(ImVec2(plot.AxesRect.Min.x, plot.PlotRect.Min.y), ImVec2(plot.PlotRect.Min.x, plot.PlotRect.Max.y)); @@ -1577,7 +1614,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y1_label, con UpdateTransformCache(); bool equal_dragged = false; // special case for axis equal and both x and y0 hovered - if (axis_equal && !plot.XAxis.IsLocked() && plot.XAxis.Dragging && !plot.YAxis[0].IsLocked() && plot.YAxis[0].Dragging) { + if (axis_equal && !plot.XAxis.IsInputLocked() && plot.XAxis.Dragging && !plot.YAxis[0].IsInputLocked() && plot.YAxis[0].Dragging) { ImPlotPoint plot_tl = PixelsToPlot(plot.PlotRect.Min - IO.MouseDelta, 0); ImPlotPoint plot_br = PixelsToPlot(plot.PlotRect.Max - IO.MouseDelta, 0); if (!plot.XAxis.IsLockedMin()) @@ -1590,11 +1627,11 @@ bool BeginPlot(const char* title, const char* x_label, const char* y1_label, con plot.YAxis[0].SetMax(plot.YAxis[0].IsInverted() ? plot_br.y : plot_tl.y); double xar = plot.XAxis.GetAspect(); double yar = plot.YAxis[0].GetAspect(); - if (!ImAlmostEqual(xar,yar) && !plot.YAxis[0].IsLocked()) + if (!ImAlmostEqual(xar,yar) && !plot.YAxis[0].IsInputLocked()) plot.XAxis.SetAspect(yar); equal_dragged = true; } - if (!plot.XAxis.IsLocked() && plot.XAxis.Dragging && !equal_dragged) { + if (!plot.XAxis.IsInputLocked() && plot.XAxis.Dragging && !equal_dragged) { ImPlotPoint plot_tl = PixelsToPlot(plot.PlotRect.Min - IO.MouseDelta, 0); ImPlotPoint plot_br = PixelsToPlot(plot.PlotRect.Max - IO.MouseDelta, 0); if (!plot.XAxis.IsLockedMin()) @@ -1605,7 +1642,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y1_label, con plot.YAxis[0].SetAspect(plot.XAxis.GetAspect()); } for (int i = 0; i < IMPLOT_Y_AXES; i++) { - if (!plot.YAxis[i].IsLocked() && plot.YAxis[i].Dragging && !(i == 0 && equal_dragged)) { + if (!plot.YAxis[i].IsInputLocked() && plot.YAxis[i].Dragging && !(i == 0 && equal_dragged)) { ImPlotPoint plot_tl = PixelsToPlot(plot.PlotRect.Min - IO.MouseDelta, i); ImPlotPoint plot_br = PixelsToPlot(plot.PlotRect.Max - IO.MouseDelta, i); if (!plot.YAxis[i].IsLockedMin()) @@ -1618,12 +1655,12 @@ bool BeginPlot(const char* title, const char* x_label, const char* y1_label, con } // Set the mouse cursor based on which axes are moving. int direction = 0; - if (!plot.XAxis.IsLocked() && plot.XAxis.Dragging) { + if (!plot.XAxis.IsInputLocked() && plot.XAxis.Dragging) { direction |= (1 << 1); } for (int i = 0; i < IMPLOT_Y_AXES; i++) { if (!plot.YAxis[i].Present) { continue; } - if (!plot.YAxis[i].IsLocked() && plot.YAxis[i].Dragging) { + if (!plot.YAxis[i].IsInputLocked() && plot.YAxis[i].Dragging) { direction |= (1 << 2); break; } @@ -1662,7 +1699,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y1_label, con float ty = ImRemap(IO.MousePos.y, plot.PlotRect.Min.y, plot.PlotRect.Max.y, 0.0f, 1.0f); bool equal_zoomed = false; // special case for axis equal and both x and y0 hovered - if (axis_equal && plot.XAxis.AllHovered && !plot.XAxis.IsLocked() && plot.YAxis[0].AllHovered && !plot.YAxis[0].IsLocked()) { + if (axis_equal && plot.XAxis.AllHovered && !plot.XAxis.IsInputLocked() && plot.YAxis[0].AllHovered && !plot.YAxis[0].IsInputLocked()) { const ImPlotPoint& plot_tl = PixelsToPlot(plot.PlotRect.Min - plot.PlotRect.GetSize() * ImVec2(tx * zoom_rate, ty * zoom_rate), 0); const ImPlotPoint& plot_br = PixelsToPlot(plot.PlotRect.Max + plot.PlotRect.GetSize() * ImVec2((1 - tx) * zoom_rate, (1 - ty) * zoom_rate), 0); if (!plot.XAxis.IsLockedMin()) @@ -1675,11 +1712,11 @@ bool BeginPlot(const char* title, const char* x_label, const char* y1_label, con plot.YAxis[0].SetMax(plot.YAxis[0].IsInverted() ? plot_br.y : plot_tl.y); double xar = plot.XAxis.GetAspect(); double yar = plot.YAxis[0].GetAspect(); - if (!ImAlmostEqual(xar,yar) && !plot.YAxis[0].IsLocked()) + if (!ImAlmostEqual(xar,yar) && !plot.YAxis[0].IsInputLocked()) plot.XAxis.SetAspect(yar); equal_zoomed = true; } - if (plot.XAxis.AllHovered && !plot.XAxis.IsLocked() && !equal_zoomed) { + if (plot.XAxis.AllHovered && !plot.XAxis.IsInputLocked() && !equal_zoomed) { const ImPlotPoint& plot_tl = PixelsToPlot(plot.PlotRect.Min - plot.PlotRect.GetSize() * ImVec2(tx * zoom_rate, ty * zoom_rate), 0); const ImPlotPoint& plot_br = PixelsToPlot(plot.PlotRect.Max + plot.PlotRect.GetSize() * ImVec2((1 - tx) * zoom_rate, (1 - ty) * zoom_rate), 0); if (!plot.XAxis.IsLockedMin()) @@ -1690,7 +1727,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y1_label, con plot.YAxis[0].SetAspect(plot.XAxis.GetAspect()); } for (int i = 0; i < IMPLOT_Y_AXES; i++) { - if (plot.YAxis[i].AllHovered && !plot.YAxis[i].IsLocked() && !(i == 0 && equal_zoomed)) { + if (plot.YAxis[i].AllHovered && !plot.YAxis[i].IsInputLocked() && !(i == 0 && equal_zoomed)) { const ImPlotPoint& plot_tl = PixelsToPlot(plot.PlotRect.Min - plot.PlotRect.GetSize() * ImVec2(tx * zoom_rate, ty * zoom_rate), i); const ImPlotPoint& plot_br = PixelsToPlot(plot.PlotRect.Max + plot.PlotRect.GetSize() * ImVec2((1 - tx) * zoom_rate, (1 - ty) * zoom_rate), i); if (!plot.YAxis[i].IsLockedMin()) @@ -1710,21 +1747,25 @@ bool BeginPlot(const char* title, const char* x_label, const char* y1_label, con UpdateTransformCache(); ImVec2 select_size = plot.SelectStart - IO.MousePos; if (!ImHasFlag(plot.Flags, ImPlotFlags_NoBoxSelect)) { - ImPlotPoint p1 = PixelsToPlot(plot.SelectStart); - ImPlotPoint p2 = PixelsToPlot(IO.MousePos); const bool x_can_change = !ImHasFlag(IO.KeyMods,gp.InputMap.HorizontalMod) && ImFabs(select_size.x) > 2; const bool y_can_change = !ImHasFlag(IO.KeyMods,gp.InputMap.VerticalMod) && ImFabs(select_size.y) > 2; - if (!plot.XAxis.IsLockedMin() && x_can_change) - plot.XAxis.SetMin(ImMin(p1.x, p2.x)); - if (!plot.XAxis.IsLockedMax() && x_can_change) - plot.XAxis.SetMax(ImMax(p1.x, p2.x)); + if (!plot.XAxis.IsInputLocked()) { + ImPlotPoint p1 = PixelsToPlot(plot.SelectStart); + ImPlotPoint p2 = PixelsToPlot(IO.MousePos); + if (!plot.XAxis.IsLockedMin() && x_can_change) + plot.XAxis.SetMin(ImMin(p1.x, p2.x)); + if (!plot.XAxis.IsLockedMax() && x_can_change) + plot.XAxis.SetMax(ImMax(p1.x, p2.x)); + } for (int i = 0; i < IMPLOT_Y_AXES; i++) { - p1 = PixelsToPlot(plot.SelectStart, i); - p2 = PixelsToPlot(IO.MousePos, i); - if (!plot.YAxis[i].IsLockedMin() && y_can_change) - plot.YAxis[i].SetMin(ImMin(p1.y, p2.y)); - if (!plot.YAxis[i].IsLockedMax() && y_can_change) - plot.YAxis[i].SetMax(ImMax(p1.y, p2.y)); + if (!plot.YAxis->IsInputLocked()) { + ImPlotPoint p1 = PixelsToPlot(plot.SelectStart, i); + ImPlotPoint p2 = PixelsToPlot(IO.MousePos, i); + if (!plot.YAxis[i].IsLockedMin() && y_can_change) + plot.YAxis[i].SetMin(ImMin(p1.y, p2.y)); + if (!plot.YAxis[i].IsLockedMax() && y_can_change) + plot.YAxis[i].SetMax(ImMax(p1.y, p2.y)); + } } if (x_can_change || y_can_change || (ImHasFlag(IO.KeyMods,gp.InputMap.HorizontalMod) && ImHasFlag(IO.KeyMods,gp.InputMap.VerticalMod))) plot.ContextLocked = gp.InputMap.BoxSelectButton == gp.InputMap.ContextMenuButton; @@ -1732,7 +1773,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y1_label, con plot.Selecting = false; } // bad selection - if (plot.Selecting && (ImHasFlag(plot.Flags, ImPlotFlags_NoBoxSelect) || plot.IsLocked()) && ImLengthSqr(plot.SelectStart - IO.MousePos) > 4) { + if (plot.Selecting && (ImHasFlag(plot.Flags, ImPlotFlags_NoBoxSelect) || plot.IsInputLocked()) && ImLengthSqr(plot.SelectStart - IO.MousePos) > 4) { ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed); plot.ContextLocked = gp.InputMap.BoxSelectButton == gp.InputMap.ContextMenuButton; } @@ -1804,13 +1845,13 @@ bool BeginPlot(const char* title, const char* x_label, const char* y1_label, con for (int i = 0; i < IMPLOT_Y_AXES; i++) gp.FitY[i] = plot.YAxis[i].AllHovered; } - // fit from FitNextPlotAxes - if (gp.NextPlotData.FitX) { + // fit from FitNextPlotAxes or auto fit + if (gp.NextPlotData.FitX || ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_AutoFit)) { gp.FitThisFrame = true; gp.FitX = true; } for (int i = 0; i < IMPLOT_Y_AXES; ++i) { - if (gp.NextPlotData.FitY[i]) { + if (gp.NextPlotData.FitY[i] || ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_AutoFit)) { gp.FitThisFrame = true; gp.FitY[i] = true; } @@ -1891,7 +1932,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y1_label, con // render title if (title_size.x > 0.0f && !ImHasFlag(plot.Flags, ImPlotFlags_NoTitle)) { ImU32 col = GetStyleColorU32(ImPlotCol_TitleText); - const char* title_end = ImGui::FindRenderedTextEnd(title, NULL); + const char* title_end = ImGui::FindRenderedTextEnd(title); DrawList.AddText(ImVec2(plot.CanvasRect.GetCenter().x - title_size.x * 0.5f, plot.CanvasRect.Min.y),col,title,title_end); } @@ -1986,7 +2027,7 @@ inline void EndDisabledControls(bool cond) { void ShowAxisContextMenu(ImPlotAxis& axis, ImPlotAxis* equal_axis, bool time_allowed) { ImGui::PushItemWidth(75); - bool always_locked = axis.IsAlwaysLocked(); + bool always_locked = axis.IsRangeLocked() || axis.IsAutoFitting(); bool label = !ImHasFlag(axis.Flags, ImPlotAxisFlags_NoLabel); bool grid = !ImHasFlag(axis.Flags, ImPlotAxisFlags_NoGridLines); bool ticks = !ImHasFlag(axis.Flags, ImPlotAxisFlags_NoTickMarks); @@ -2001,7 +2042,7 @@ void ShowAxisContextMenu(ImPlotAxis& axis, ImPlotAxis* equal_axis, bool time_all ImGui::CheckboxFlags("##LockMin", (unsigned int*)&axis.Flags, ImPlotAxisFlags_LockMin); EndDisabledControls(always_locked); ImGui::SameLine(); - BeginDisabledControls(axis.IsLockedMin()); + BeginDisabledControls(axis.IsLockedMin() || always_locked); if (ImGui::BeginMenu("Min Time")) { if (ShowTimePicker("mintime", &tmin)) { if (tmin >= tmax) @@ -2017,13 +2058,13 @@ void ShowAxisContextMenu(ImPlotAxis& axis, ImPlotAxis* equal_axis, bool time_all } ImGui::EndMenu(); } - EndDisabledControls(axis.IsLockedMin()); + EndDisabledControls(axis.IsLockedMin() || always_locked); BeginDisabledControls(always_locked); ImGui::CheckboxFlags("##LockMax", (unsigned int*)&axis.Flags, ImPlotAxisFlags_LockMax); EndDisabledControls(always_locked); ImGui::SameLine(); - BeginDisabledControls(axis.IsLockedMax()); + BeginDisabledControls(axis.IsLockedMax() || always_locked); if (ImGui::BeginMenu("Max Time")) { if (ShowTimePicker("maxtime", &tmax)) { if (tmax <= tmin) @@ -2039,39 +2080,39 @@ void ShowAxisContextMenu(ImPlotAxis& axis, ImPlotAxis* equal_axis, bool time_all } ImGui::EndMenu(); } - EndDisabledControls(axis.IsLockedMax()); + EndDisabledControls(axis.IsLockedMax() || always_locked); } else { BeginDisabledControls(always_locked); ImGui::CheckboxFlags("##LockMin", (unsigned int*)&axis.Flags, ImPlotAxisFlags_LockMin); EndDisabledControls(always_locked); ImGui::SameLine(); - BeginDisabledControls(axis.IsLockedMin()); + BeginDisabledControls(axis.IsLockedMin() || always_locked); double temp_min = axis.Range.Min; if (DragFloat("Min", &temp_min, (float)drag_speed, -HUGE_VAL, axis.Range.Max - DBL_EPSILON)) { axis.SetMin(temp_min); if (equal_axis != NULL) equal_axis->SetAspect(axis.GetAspect()); } - EndDisabledControls(axis.IsLockedMin()); + EndDisabledControls(axis.IsLockedMin() || always_locked); BeginDisabledControls(always_locked); ImGui::CheckboxFlags("##LockMax", (unsigned int*)&axis.Flags, ImPlotAxisFlags_LockMax); EndDisabledControls(always_locked); ImGui::SameLine(); - BeginDisabledControls(axis.IsLockedMax()); + BeginDisabledControls(axis.IsLockedMax() || always_locked); double temp_max = axis.Range.Max; if (DragFloat("Max", &temp_max, (float)drag_speed, axis.Range.Min + DBL_EPSILON, HUGE_VAL)) { axis.SetMax(temp_max); if (equal_axis != NULL) equal_axis->SetAspect(axis.GetAspect()); } - EndDisabledControls(axis.IsLockedMax()); + EndDisabledControls(axis.IsLockedMax() || always_locked); } ImGui::Separator(); - + ImGui::CheckboxFlags("Auto-Fit",(unsigned int*)&axis.Flags, ImPlotAxisFlags_AutoFit); ImGui::CheckboxFlags("Invert",(unsigned int*)&axis.Flags, ImPlotAxisFlags_Invert); BeginDisabledControls(axis.IsTime() && time_allowed); ImGui::CheckboxFlags("Log Scale",(unsigned int*)&axis.Flags, ImPlotAxisFlags_LogScale); @@ -2183,7 +2224,7 @@ void EndPlot() { // AXIS STATES ------------------------------------------------------------ - const bool any_y_locked = plot.YAxis[0].IsLocked() || plot.YAxis[1].Present ? plot.YAxis[1].IsLocked() : false || plot.YAxis[2].Present ? plot.YAxis[2].IsLocked() : false; + const bool any_y_locked = plot.YAxis[0].IsInputLocked() || plot.YAxis[1].Present ? plot.YAxis[1].IsInputLocked() : false || plot.YAxis[2].Present ? plot.YAxis[2].IsInputLocked() : false; const bool any_y_dragging = plot.YAxis[0].Dragging || plot.YAxis[1].Dragging || plot.YAxis[2].Dragging; @@ -2279,7 +2320,7 @@ void EndPlot() { const bool wide_enough = ImFabs(select_bb.GetWidth()) > 2; const bool tall_enough = ImFabs(select_bb.GetHeight()) > 2; const bool big_enough = wide_enough && tall_enough; - if (plot.Selecting && !plot.IsLocked() && !ImHasFlag(plot.Flags, ImPlotFlags_NoBoxSelect)) { + if (plot.Selecting && !plot.IsInputLocked() && !ImHasFlag(plot.Flags, ImPlotFlags_NoBoxSelect)) { const ImVec4 col = GetStyleColorVec4(ImPlotCol_Selection); const ImU32 col_bg = ImGui::GetColorU32(col * ImVec4(1,1,1,0.25f)); const ImU32 col_bd = ImGui::GetColorU32(col); @@ -2287,7 +2328,7 @@ void EndPlot() { DrawList.AddRectFilled(plot.PlotRect.Min, plot.PlotRect.Max, col_bg); DrawList.AddRect( plot.PlotRect.Min, plot.PlotRect.Max, col_bd); } - else if ((plot.XAxis.IsLocked() || IO.KeyMods == gp.InputMap.HorizontalMod) && tall_enough) { + else if ((plot.XAxis.IsInputLocked() || IO.KeyMods == gp.InputMap.HorizontalMod) && tall_enough) { DrawList.AddRectFilled(ImVec2(plot.PlotRect.Min.x, select_bb.Min.y), ImVec2(plot.PlotRect.Max.x, select_bb.Max.y), col_bg); DrawList.AddRect( ImVec2(plot.PlotRect.Min.x, select_bb.Min.y), ImVec2(plot.PlotRect.Max.x, select_bb.Max.y), col_bd); } @@ -2440,9 +2481,9 @@ void EndPlot() { const double ext_size = gp.ExtentsX.Size() * 0.5; gp.ExtentsX.Min -= ext_size * gp.Style.FitPadding.x; gp.ExtentsX.Max += ext_size * gp.Style.FitPadding.x; - if (!ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LockMin) && !ImNanOrInf(gp.ExtentsX.Min)) + if (!plot.XAxis.IsLockedMin() && !ImNanOrInf(gp.ExtentsX.Min)) plot.XAxis.Range.Min = (gp.ExtentsX.Min); - if (!ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LockMax) && !ImNanOrInf(gp.ExtentsX.Max)) + if (!plot.XAxis.IsLockedMax() && !ImNanOrInf(gp.ExtentsX.Max)) plot.XAxis.Range.Max = (gp.ExtentsX.Max); if (ImAlmostEqual(plot.XAxis.Range.Max, plot.XAxis.Range.Min)) { plot.XAxis.Range.Max += 0.5; @@ -2457,9 +2498,9 @@ void EndPlot() { const double ext_size = gp.ExtentsY[i].Size() * 0.5; gp.ExtentsY[i].Min -= ext_size * gp.Style.FitPadding.y; gp.ExtentsY[i].Max += ext_size * gp.Style.FitPadding.y; - if (!ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LockMin) && !ImNanOrInf(gp.ExtentsY[i].Min)) + if (!plot.YAxis[i].IsLockedMin() && !ImNanOrInf(gp.ExtentsY[i].Min)) plot.YAxis[i].Range.Min = (gp.ExtentsY[i].Min); - if (!ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LockMax) && !ImNanOrInf(gp.ExtentsY[i].Max)) + if (!plot.YAxis[i].IsLockedMax() && !ImNanOrInf(gp.ExtentsY[i].Max)) plot.YAxis[i].Range.Max = (gp.ExtentsY[i].Max); if (ImAlmostEqual(plot.YAxis[i].Range.Max, plot.YAxis[i].Range.Min)) { plot.YAxis[i].Range.Max += 0.5; @@ -2889,7 +2930,7 @@ bool DragPoint(const char* id, double* x, double* y, bool show_label, const ImVe ImGui::GetCurrentWindow()->DC.CursorPos = old_cursor_pos; if (ImGui::IsItemHovered() || ImGui::IsItemActive()) { gp.CurrentPlot->PlotHovered = false; - ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll); + // ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll); if (show_label) { ImVec2 label_pos = pos + ImVec2(16 * GImGui->Style.MouseCursorScale, 8 * GImGui->Style.MouseCursorScale); char buff1[32]; @@ -3052,6 +3093,18 @@ void ItemIcon(ImU32 col) { ImGui::Dummy(size); } +void ColormapIcon(ImPlotColormap cmap) { + ImPlotContext& gp = *GImPlot; + const float txt_size = ImGui::GetTextLineHeight(); + ImVec2 size(txt_size-4,txt_size); + ImGuiWindow* window = ImGui::GetCurrentWindow(); + ImVec2 pos = window->DC.CursorPos; + ImRect rect(pos+ImVec2(0,2),pos+size-ImVec2(0,2)); + ImDrawList& DrawList = *ImGui::GetWindowDrawList(); + RenderColorBar(gp.ColormapData.GetKeys(cmap),gp.ColormapData.GetKeyCount(cmap),DrawList,rect,false,false,!gp.ColormapData.IsQual(cmap)); + ImGui::Dummy(size); +} + //----------------------------------------------------------------------------- void SetLegendLocation(ImPlotLocation location, ImPlotOrientation orientation, bool outside) { @@ -3165,6 +3218,7 @@ void PushStyleColor(ImPlotCol idx, const ImVec4& col) { void PopStyleColor(int count) { ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(count <= gp.ColorModifiers.Size, "You can't pop more modifiers than have been pushed!"); while (count > 0) { ImGuiColorMod& backup = gp.ColorModifiers.back(); @@ -3220,6 +3274,7 @@ void PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) void PopStyleVar(int count) { ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(count <= gp.StyleModifiers.Size, "You can't pop more modifiers than have been pushed!"); while (count > 0) { ImGuiStyleMod& backup = gp.StyleModifiers.back(); const ImPlotStyleVarInfo* info = GetPlotStyleVarInfo(backup.VarIdx); @@ -3243,264 +3298,174 @@ void PopStyleVar(int count) { // COLORMAPS //------------------------------------------------------------------------------ +ImPlotColormap AddColormap(const char* name, const ImVec4* colormap, int size, bool qual) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(size > 1, "The colormap size must be greater than 1!"); + IM_ASSERT_USER_ERROR(gp.ColormapData.GetIndex(name) == -1, "The colormap name has already been used!"); + ImVector buffer; + buffer.resize(size); + for (int i = 0; i < size; ++i) + buffer[i] = ImGui::ColorConvertFloat4ToU32(colormap[i]); + return gp.ColormapData.Append(name, buffer.Data, size, qual); +} + +ImPlotColormap AddColormap(const char* name, const ImU32* colormap, int size, bool qual) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(size > 1, "The colormap size must be greater than 1!"); + IM_ASSERT_USER_ERROR(gp.ColormapData.GetIndex(name) == -1, "The colormap name has already be used!"); + return gp.ColormapData.Append(name, colormap, size, qual); +} + +int GetColormapCount() { + ImPlotContext& gp = *GImPlot; + return gp.ColormapData.Count; +} + +const char* GetColormapName(ImPlotColormap colormap) { + ImPlotContext& gp = *GImPlot; + return gp.ColormapData.GetName(colormap); +} + +ImPlotColormap GetColormapIndex(const char* name) { + ImPlotContext& gp = *GImPlot; + return gp.ColormapData.GetIndex(name); +} void PushColormap(ImPlotColormap colormap) { ImPlotContext& gp = *GImPlot; - gp.ColormapModifiers.push_back(ImPlotColormapMod(gp.Colormap, gp.ColormapSize)); - gp.Colormap = GetColormap(colormap, &gp.ColormapSize); + IM_ASSERT_USER_ERROR(colormap >= 0 && colormap < gp.ColormapData.Count, "The colormap index is invalid!"); + gp.ColormapModifiers.push_back(gp.Style.Colormap); + gp.Style.Colormap = colormap; } -void PushColormap(const ImVec4* colormap, int size) { +void PushColormap(const char* name) { ImPlotContext& gp = *GImPlot; - gp.ColormapModifiers.push_back(ImPlotColormapMod(gp.Colormap, gp.ColormapSize)); - gp.Colormap = colormap; - gp.ColormapSize = size; + ImPlotColormap idx = gp.ColormapData.GetIndex(name); + IM_ASSERT_USER_ERROR(idx != -1, "The colormap name is invalid!"); + PushColormap(idx); } void PopColormap(int count) { ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(count <= gp.ColormapModifiers.Size, "You can't pop more modifiers than have been pushed!"); while (count > 0) { - const ImPlotColormapMod& backup = gp.ColormapModifiers.back(); - gp.Colormap = backup.Colormap; - gp.ColormapSize = backup.ColormapSize; + const ImPlotColormap& backup = gp.ColormapModifiers.back(); + gp.Style.Colormap = backup; gp.ColormapModifiers.pop_back(); count--; } } -void SetColormap(ImPlotColormap colormap, int samples) { - ImPlotContext& gp = *GImPlot; - gp.Colormap = GetColormap(colormap, &gp.ColormapSize); - if (samples > 1) { - static ImVector resampled; - resampled.resize(samples); - ResampleColormap(gp.Colormap, gp.ColormapSize, &resampled[0], samples); - SetColormap(&resampled[0], samples); - } -} - -void SetColormap(const ImVec4* colors, int size) { - ImPlotContext& gp = *GImPlot; - IM_ASSERT_USER_ERROR(colors != NULL, "You can't set the colors to NULL!"); - IM_ASSERT_USER_ERROR(size > 0, "The number of colors must be greater than 0!"); - static ImVector user_colormap; - user_colormap.shrink(0); - user_colormap.reserve(size); - for (int i = 0; i < size; ++i) - user_colormap.push_back(colors[i]); - gp.Colormap = &user_colormap[0]; - gp.ColormapSize = size; -} - -const ImVec4* GetColormap(ImPlotColormap colormap, int* size_out) { - static const int csizes[ImPlotColormap_COUNT] = {10,10,9,9,12,11,11,11,11,11,11}; - static const ImOffsetCalculator coffs(csizes); - static ImVec4 cdata[] = { - // ImPlotColormap_Default // X11 Named Colors - ImVec4(0.0f, 0.7490196228f, 1.0f, 1.0f), // Blues::DeepSkyBlue, - ImVec4(1.0f, 0.0f, 0.0f, 1.0f), // Reds::Red, - ImVec4(0.4980392158f, 1.0f, 0.0f, 1.0f), // Greens::Chartreuse, - ImVec4(1.0f, 1.0f, 0.0f, 1.0f), // Yellows::Yellow, - ImVec4(0.0f, 1.0f, 1.0f, 1.0f), // Cyans::Cyan, - ImVec4(1.0f, 0.6470588446f, 0.0f, 1.0f), // Oranges::Orange, - ImVec4(1.0f, 0.0f, 1.0f, 1.0f), // Purples::Magenta, - ImVec4(0.5411764979f, 0.1686274558f, 0.8862745166f, 1.0f), // Purples::BlueViolet, - ImVec4(0.5f, 0.5f, 0.5f, 1.0f), // Grays::Gray50, - ImVec4(0.8235294223f, 0.7058823705f, 0.5490196347f, 1.0f), // Browns::Tan - // ImPlotColormap_Deep - ImVec4(0.298f, 0.447f, 0.690f, 1.000f), - ImVec4(0.867f, 0.518f, 0.322f, 1.000f), - ImVec4(0.333f, 0.659f, 0.408f, 1.000f), - ImVec4(0.769f, 0.306f, 0.322f, 1.000f), - ImVec4(0.506f, 0.446f, 0.702f, 1.000f), - ImVec4(0.576f, 0.471f, 0.376f, 1.000f), - ImVec4(0.855f, 0.545f, 0.765f, 1.000f), - ImVec4(0.549f, 0.549f, 0.549f, 1.000f), - ImVec4(0.800f, 0.725f, 0.455f, 1.000f), - ImVec4(0.392f, 0.710f, 0.804f, 1.000f), - // ImPlotColormap_Dark - ImVec4(0.894118f, 0.101961f, 0.109804f, 1.0f), - ImVec4(0.215686f, 0.494118f, 0.721569f, 1.0f), - ImVec4(0.301961f, 0.686275f, 0.290196f, 1.0f), - ImVec4(0.596078f, 0.305882f, 0.639216f, 1.0f), - ImVec4(1.000000f, 0.498039f, 0.000000f, 1.0f), - ImVec4(1.000000f, 1.000000f, 0.200000f, 1.0f), - ImVec4(0.650980f, 0.337255f, 0.156863f, 1.0f), - ImVec4(0.968627f, 0.505882f, 0.749020f, 1.0f), - ImVec4(0.600000f, 0.600000f, 0.600000f, 1.0f), - // ImPlotColormap_Pastel - ImVec4(0.984314f, 0.705882f, 0.682353f, 1.0f), - ImVec4(0.701961f, 0.803922f, 0.890196f, 1.0f), - ImVec4(0.800000f, 0.921569f, 0.772549f, 1.0f), - ImVec4(0.870588f, 0.796078f, 0.894118f, 1.0f), - ImVec4(0.996078f, 0.850980f, 0.650980f, 1.0f), - ImVec4(1.000000f, 1.000000f, 0.800000f, 1.0f), - ImVec4(0.898039f, 0.847059f, 0.741176f, 1.0f), - ImVec4(0.992157f, 0.854902f, 0.925490f, 1.0f), - ImVec4(0.949020f, 0.949020f, 0.949020f, 1.0f), - // ImPlotColormap_Paired - ImVec4(0.258824f, 0.807843f, 0.890196f, 1.0f), - ImVec4(0.121569f, 0.470588f, 0.705882f, 1.0f), - ImVec4(0.698039f, 0.874510f, 0.541176f, 1.0f), - ImVec4(0.200000f, 0.627451f, 0.172549f, 1.0f), - ImVec4(0.984314f, 0.603922f, 0.600000f, 1.0f), - ImVec4(0.890196f, 0.101961f, 0.109804f, 1.0f), - ImVec4(0.992157f, 0.749020f, 0.435294f, 1.0f), - ImVec4(1.000000f, 0.498039f, 0.000000f, 1.0f), - ImVec4(0.792157f, 0.698039f, 0.839216f, 1.0f), - ImVec4(0.415686f, 0.239216f, 0.603922f, 1.0f), - ImVec4(1.000000f, 1.000000f, 0.600000f, 1.0f), - ImVec4(0.694118f, 0.349020f, 0.156863f, 1.0f), - // ImPlotColormap_Viridis - ImVec4(0.267004f, 0.004874f, 0.329415f, 1.0f), - ImVec4(0.282623f, 0.140926f, 0.457517f, 1.0f), - ImVec4(0.253935f, 0.265254f, 0.529983f, 1.0f), - ImVec4(0.206756f, 0.371758f, 0.553117f, 1.0f), - ImVec4(0.163625f, 0.471133f, 0.558148f, 1.0f), - ImVec4(0.127568f, 0.566949f, 0.550556f, 1.0f), - ImVec4(0.134692f, 0.658636f, 0.517649f, 1.0f), - ImVec4(0.266941f, 0.748751f, 0.440573f, 1.0f), - ImVec4(0.477504f, 0.821444f, 0.318195f, 1.0f), - ImVec4(0.741388f, 0.873449f, 0.149561f, 1.0f), - ImVec4(0.993248f, 0.906157f, 0.143936f, 1.0f), - // ImPlotColormap_Plasma - ImVec4(5.03830e-02f, 2.98030e-02f, 5.27975e-01f, 1.00000e+00f), - ImVec4(2.54627e-01f, 1.38820e-02f, 6.15419e-01f, 1.00000e+00f), - ImVec4(4.17642e-01f, 5.64000e-04f, 6.58390e-01f, 1.00000e+00f), - ImVec4(5.62738e-01f, 5.15450e-02f, 6.41509e-01f, 1.00000e+00f), - ImVec4(6.92840e-01f, 1.65141e-01f, 5.64522e-01f, 1.00000e+00f), - ImVec4(7.98216e-01f, 2.80197e-01f, 4.69538e-01f, 1.00000e+00f), - ImVec4(8.81443e-01f, 3.92529e-01f, 3.83229e-01f, 1.00000e+00f), - ImVec4(9.49217e-01f, 5.17763e-01f, 2.95662e-01f, 1.00000e+00f), - ImVec4(9.88260e-01f, 6.52325e-01f, 2.11364e-01f, 1.00000e+00f), - ImVec4(9.88648e-01f, 8.09579e-01f, 1.45357e-01f, 1.00000e+00f), - ImVec4(9.40015e-01f, 9.75158e-01f, 1.31326e-01f, 1.00000e+00f), - // ImPlotColormap_Hot - ImVec4(0.2500f, 0.f, 0.f, 1.0f), - ImVec4(0.5000f, 0.f, 0.f, 1.0f), - ImVec4(0.7500f, 0.f, 0.f, 1.0f), - ImVec4(1.0000f, 0.f, 0.f, 1.0f), - ImVec4(1.0000f, 0.2500f, 0.f, 1.0f), - ImVec4(1.0000f, 0.5000f, 0.f, 1.0f), - ImVec4(1.0000f, 0.7500f, 0.f, 1.0f), - ImVec4(1.0000f, 1.0000f, 0.f, 1.0f), - ImVec4(1.0000f, 1.0000f, 0.3333f, 1.0f), - ImVec4(1.0000f, 1.0000f, 0.6667f, 1.0f), - ImVec4(1.0000f, 1.0000f, 1.0000f, 1.0f), - // ImPlotColormap_Cool - ImVec4( 0.f, 1.0000f, 1.0000f, 1.0f), - ImVec4(0.1000f, 0.9000f, 1.0000f, 1.0f), - ImVec4(0.2000f, 0.8000f, 1.0000f, 1.0f), - ImVec4(0.3000f, 0.7000f, 1.0000f, 1.0f), - ImVec4(0.4000f, 0.6000f, 1.0000f, 1.0f), - ImVec4(0.5000f, 0.5000f, 1.0000f, 1.0f), - ImVec4(0.6000f, 0.4000f, 1.0000f, 1.0f), - ImVec4(0.7000f, 0.3000f, 1.0000f, 1.0f), - ImVec4(0.8000f, 0.2000f, 1.0000f, 1.0f), - ImVec4(0.9000f, 0.1000f, 1.0000f, 1.0f), - ImVec4(1.0000f, 0.f, 1.0000f, 1.0f), - // ImPlotColormap_Pink - ImVec4(0.2887f, 0.f, 0.f, 1.0f), - ImVec4(0.4830f, 0.2582f, 0.2582f, 1.0f), - ImVec4(0.6191f, 0.3651f, 0.3651f, 1.0f), - ImVec4(0.7303f, 0.4472f, 0.4472f, 1.0f), - ImVec4(0.7746f, 0.5916f, 0.5164f, 1.0f), - ImVec4(0.8165f, 0.7071f, 0.5774f, 1.0f), - ImVec4(0.8563f, 0.8062f, 0.6325f, 1.0f), - ImVec4(0.8944f, 0.8944f, 0.6831f, 1.0f), - ImVec4(0.9309f, 0.9309f, 0.8028f, 1.0f), - ImVec4(0.9661f, 0.9661f, 0.9068f, 1.0f), - ImVec4(1.0000f, 1.0000f, 1.0000f, 1.0f), - // ImPlotColormap_Jet - ImVec4( 0.f, 0.f, 0.6667f, 1.0f), - ImVec4( 0.f, 0.f, 1.0000f, 1.0f), - ImVec4( 0.f, 0.3333f, 1.0000f, 1.0f), - ImVec4( 0.f, 0.6667f, 1.0000f, 1.0f), - ImVec4( 0.f, 1.0000f, 1.0000f, 1.0f), - ImVec4(0.3333f, 1.0000f, 0.6667f, 1.0f), - ImVec4(0.6667f, 1.0000f, 0.3333f, 1.0f), - ImVec4(1.0000f, 1.0000f, 0.f, 1.0f), - ImVec4(1.0000f, 0.6667f, 0.f, 1.0f), - ImVec4(1.0000f, 0.3333f, 0.f, 1.0f), - ImVec4(1.0000f, 0.f, 0.f, 1.0f) - }; - *size_out = csizes[colormap]; - return &cdata[coffs.Offsets[colormap]]; -} - -const char* GetColormapName(ImPlotColormap colormap) { - static const char* cmap_names[] = {"Default","Deep","Dark","Pastel","Paired","Viridis","Plasma","Hot","Cool","Pink","Jet"}; - return cmap_names[colormap]; -} - -void ResampleColormap(const ImVec4* colormap_in, int size_in, ImVec4* colormap_out, int size_out) { - for (int i = 0; i < size_out; ++i) { - float t = i * 1.0f / (size_out - 1); - colormap_out[i] = LerpColormap(colormap_in, size_in, t); - } -} - -int GetColormapSize() { - ImPlotContext& gp = *GImPlot; - return gp.ColormapSize; -} - -ImVec4 GetColormapColor(int index) { - ImPlotContext& gp = *GImPlot; - IM_ASSERT_USER_ERROR(index >= 0, "The Colormap index must be greater than zero!"); - return gp.Colormap[index % gp.ColormapSize]; -} - -ImVec4 LerpColormap(const ImVec4* colormap, int size, float t) { - float tc = ImClamp(t,0.0f,1.0f); - int i1 = (int)((size -1 ) * tc); - int i2 = i1 + 1; - if (i2 == size || size == 1) - return colormap[i1]; - float t1 = (float)i1 / (float)(size - 1); - float t2 = (float)i2 / (float)(size - 1); - float tr = ImRemap(t, t1, t2, 0.0f, 1.0f); - return ImLerp(colormap[i1], colormap[i2], tr); -} - -ImVec4 LerpColormap(float t) { - ImPlotContext& gp = *GImPlot; - return LerpColormap(gp.Colormap, gp.ColormapSize, t); -} - -ImVec4 NextColormapColor() { +ImU32 NextColormapColorU32() { ImPlotContext& gp = *GImPlot; IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "NextColormapColor() needs to be called between BeginPlot() and EndPlot()!"); - ImVec4 col = gp.Colormap[gp.CurrentPlot->ColormapIdx % gp.ColormapSize]; + int idx = gp.CurrentPlot->ColormapIdx % gp.ColormapData.GetKeyCount(gp.Style.Colormap); + ImU32 col = gp.ColormapData.GetKeyColor(gp.Style.Colormap, idx); gp.CurrentPlot->ColormapIdx++; return col; } -void ShowColormapScale(double scale_min, double scale_max, const ImVec2& size) { +ImVec4 NextColormapColor() { + return ImGui::ColorConvertU32ToFloat4(NextColormapColorU32()); +} + +int GetColormapSize(ImPlotColormap cmap) { + ImPlotContext& gp = *GImPlot; + cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap; + IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!"); + return gp.ColormapData.GetKeyCount(gp.Style.Colormap); +} + +ImU32 GetColormapColorU32(int idx, ImPlotColormap cmap) { + ImPlotContext& gp = *GImPlot; + cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap; + IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!"); + idx = idx % gp.ColormapData.GetKeyCount(cmap); + return gp.ColormapData.GetKeyColor(cmap, idx); +} + +ImVec4 GetColormapColor(int idx, ImPlotColormap cmap) { + return ImGui::ColorConvertU32ToFloat4(GetColormapColorU32(idx,cmap)); +} + +ImU32 SampleColormapU32(float t, ImPlotColormap cmap) { + ImPlotContext& gp = *GImPlot; + cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap; + IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!"); + return gp.ColormapData.LerpTable(gp.Style.Colormap,t); +} + +ImVec4 SampleColormap(float t, ImPlotColormap cmap) { + return ImGui::ColorConvertU32ToFloat4(SampleColormapU32(t,cmap)); +} + +void RenderColorBar(const ImU32* colors, int size, ImDrawList& DrawList, const ImRect& bounds, bool vert, bool reversed, bool continuous) { + const int n = continuous ? size - 1 : size; + ImU32 col1, col2; + if (vert) { + const float step = bounds.GetHeight() / n; + ImRect rect(bounds.Min.x, bounds.Min.y, bounds.Max.x, bounds.Min.y + step); + for (int i = 0; i < n; ++i) { + if (reversed) { + col1 = colors[size-i-1]; + col2 = continuous ? colors[size-i-2] : col1; + } + else { + col1 = colors[i]; + col2 = continuous ? colors[i+1] : col1; + } + DrawList.AddRectFilledMultiColor(rect.Min, rect.Max, col1, col1, col2, col2); + rect.TranslateY(step); + } + } + else { + const float step = bounds.GetWidth() / n; + ImRect rect(bounds.Min.x, bounds.Min.y, bounds.Min.x + step, bounds.Max.y); + for (int i = 0; i < n; ++i) { + if (reversed) { + col1 = colors[size-i-1]; + col2 = continuous ? colors[size-i-2] : col1; + } + else { + col1 = colors[i]; + col2 = continuous ? colors[i+1] : col1; + } + DrawList.AddRectFilledMultiColor(rect.Min, rect.Max, col1, col2, col2, col1); + rect.TranslateX(step); + } + } +} + +void ColormapScale(const char* label, double scale_min, double scale_max, const ImVec2& size, ImPlotColormap cmap) { ImGuiContext &G = *GImGui; ImGuiWindow * Window = G.CurrentWindow; if (Window->SkipItems) return; + const ImGuiID ID = Window->GetID(label); + ImVec2 label_size(0,0); + label_size = ImGui::CalcTextSize(label,NULL,true); + ImPlotContext& gp = *GImPlot; - gp.CTicks.Reset(); - - ImPlotRange range; - range.Min = scale_min; - range.Max = scale_max; - - const float txt_off = gp.Style.LabelPadding.x; - float bar_w = 20; + cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap; + IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!"); ImVec2 frame_size = ImGui::CalcItemSize(size, 0, gp.Style.PlotDefaultSize.y); if (frame_size.y < gp.Style.PlotMinSize.y && size.y < 0.0f) frame_size.y = gp.Style.PlotMinSize.y; + ImPlotRange range(scale_min,scale_max); + gp.CTicks.Reset(); AddTicksDefault(range, ImMax(2, (int)IM_ROUND(0.0025 * frame_size.y)), IMPLOT_SUB_DIV, gp.CTicks); + + const float txt_off = gp.Style.LabelPadding.x; + const float pad_right = txt_off + gp.CTicks.MaxWidth + (label_size.x > 0 ? txt_off + label_size.y : 0); + float bar_w = 20; + if (frame_size.x == 0) - frame_size.x = bar_w + txt_off + gp.CTicks.MaxWidth + 2 * gp.Style.PlotPadding.x; + frame_size.x = bar_w + pad_right + 2 * gp.Style.PlotPadding.x; else { - bar_w = frame_size.x - (txt_off + gp.CTicks.MaxWidth + 2 * gp.Style.PlotPadding.x); + bar_w = frame_size.x - (pad_right + 2 * gp.Style.PlotPadding.x); if (bar_w < gp.Style.MajorTickLen.y) bar_w = gp.Style.MajorTickLen.y; } @@ -3508,34 +3473,96 @@ void ShowColormapScale(double scale_min, double scale_max, const ImVec2& size) { ImDrawList &DrawList = *Window->DrawList; ImRect bb_frame = ImRect(Window->DC.CursorPos, Window->DC.CursorPos + frame_size); ImGui::ItemSize(bb_frame); - if (!ImGui::ItemAdd(bb_frame, 0, &bb_frame)) + if (!ImGui::ItemAdd(bb_frame, ID, &bb_frame)) return; ImGui::RenderFrame(bb_frame.Min, bb_frame.Max, GetStyleColorU32(ImPlotCol_FrameBg), true, G.Style.FrameRounding); ImRect bb_grad(bb_frame.Min + gp.Style.PlotPadding, bb_frame.Min + ImVec2(bar_w + gp.Style.PlotPadding.x, frame_size.y - gp.Style.PlotPadding.y)); ImGui::PushClipRect(bb_frame.Min, bb_frame.Max, true); - int num_cols = GetColormapSize(); - float h_step = (frame_size.y - 2 * gp.Style.PlotPadding.y) / (num_cols - 1); - for (int i = 0; i < num_cols-1; ++i) { - ImRect rect(bb_grad.Min.x, bb_grad.Min.y + h_step * i, bb_grad.Max.x, bb_grad.Min.y + h_step * (i + 1)); - ImU32 col1 = ImGui::GetColorU32(GetColormapColor(num_cols - i - 1)); - ImU32 col2 = ImGui::GetColorU32(GetColormapColor(num_cols - i - 2)); - DrawList.AddRectFilledMultiColor(rect.Min, rect.Max, col1, col1, col2, col2); - } - const ImU32 col_tick = GetStyleColorU32(ImPlotCol_TitleText); + RenderColorBar(gp.ColormapData.GetKeys(cmap), gp.ColormapData.GetKeyCount(cmap), DrawList, bb_grad, true, true, !gp.ColormapData.IsQual(cmap)); + const ImU32 col_tick = GetStyleColorU32(ImPlotCol_YAxis); + const ImU32 col_text = ImGui::GetColorU32(ImGuiCol_Text); for (int i = 0; i < gp.CTicks.Size; ++i) { const float ypos = ImRemap((float)gp.CTicks.Ticks[i].PlotPos, (float)range.Max, (float)range.Min, bb_grad.Min.y, bb_grad.Max.y); const float tick_width = gp.CTicks.Ticks[i].Major ? gp.Style.MajorTickLen.y : gp.Style.MinorTickLen.y; const float tick_thick = gp.CTicks.Ticks[i].Major ? gp.Style.MajorTickSize.y : gp.Style.MinorTickSize.y; if (ypos < bb_grad.Max.y - 2 && ypos > bb_grad.Min.y + 2) DrawList.AddLine(ImVec2(bb_grad.Max.x-1, ypos), ImVec2(bb_grad.Max.x - tick_width, ypos), col_tick, tick_thick); - DrawList.AddText(ImVec2(bb_grad.Max.x-1, ypos) + ImVec2(txt_off, -gp.CTicks.Ticks[i].LabelSize.y * 0.5f), col_tick, gp.CTicks.GetText(i)); + DrawList.AddText(ImVec2(bb_grad.Max.x-1, ypos) + ImVec2(txt_off, -gp.CTicks.Ticks[i].LabelSize.y * 0.5f), col_text, gp.CTicks.GetText(i)); + } + if (label_size.x > 0) { + ImVec2 label_pos(bb_grad.Max.x - 1 + 2*txt_off + gp.CTicks.MaxWidth, bb_grad.GetCenter().y + label_size.x*0.5f ); + const char* label_end = ImGui::FindRenderedTextEnd(label); + AddTextVertical(&DrawList,label_pos,col_text,label,label_end); } DrawList.AddRect(bb_grad.Min, bb_grad.Max, GetStyleColorU32(ImPlotCol_PlotBorder)); ImGui::PopClipRect(); } +bool ColormapSlider(const char* label, float* t, ImVec4* out, const char* format, ImPlotColormap cmap) { + *t = ImClamp(*t,0.0f,1.0f); + ImGuiContext &G = *GImGui; + ImGuiWindow * Window = G.CurrentWindow; + if (Window->SkipItems) + return false; + ImPlotContext& gp = *GImPlot; + cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap; + IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!"); + const ImU32* keys = GImPlot->ColormapData.GetKeys(cmap); + const int count = GImPlot->ColormapData.GetKeyCount(cmap); + const bool qual = GImPlot->ColormapData.IsQual(cmap); + const ImVec2 pos = ImGui::GetCurrentWindow()->DC.CursorPos; + const float w = ImGui::CalcItemWidth(); + const float h = ImGui::GetFrameHeight(); + const ImRect rect = ImRect(pos.x,pos.y,pos.x+w,pos.y+h); + RenderColorBar(keys,count,*ImGui::GetWindowDrawList(),rect,false,false,!qual); + const ImU32 grab = CalcTextColor(GImPlot->ColormapData.LerpTable(cmap,*t)); + // const ImU32 text = CalcTextColor(GImPlot->ColormapData.LerpTable(cmap,0.5f)); + ImGui::PushStyleColor(ImGuiCol_FrameBg,IM_COL32_BLACK_TRANS); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive,IM_COL32_BLACK_TRANS); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered,ImVec4(1,1,1,0.1f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrab,grab); + ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, grab); + ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize,2); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,0); + const bool changed = ImGui::SliderFloat(label,t,0,1,format); + ImGui::PopStyleColor(5); + ImGui::PopStyleVar(2); + if (out != NULL) + *out = ImGui::ColorConvertU32ToFloat4(GImPlot->ColormapData.LerpTable(cmap,*t)); + return changed; +} + +bool ColormapButton(const char* label, const ImVec2& size_arg, ImPlotColormap cmap) { + ImGuiContext &G = *GImGui; + const ImGuiStyle& style = G.Style; + ImGuiWindow * Window = G.CurrentWindow; + if (Window->SkipItems) + return false; + ImPlotContext& gp = *GImPlot; + cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap; + IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!"); + const ImU32* keys = GImPlot->ColormapData.GetKeys(cmap); + const int count = GImPlot->ColormapData.GetKeyCount(cmap); + const bool qual = GImPlot->ColormapData.IsQual(cmap); + const ImVec2 pos = ImGui::GetCurrentWindow()->DC.CursorPos; + const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); + ImVec2 size = ImGui::CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f); + const ImRect rect = ImRect(pos.x,pos.y,pos.x+size.x,pos.y+size.y); + RenderColorBar(keys,count,*ImGui::GetWindowDrawList(),rect,false,false,!qual); + const ImU32 text = CalcTextColor(GImPlot->ColormapData.LerpTable(cmap,G.Style.ButtonTextAlign.x)); + ImGui::PushStyleColor(ImGuiCol_Button,IM_COL32_BLACK_TRANS); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered,ImVec4(1,1,1,0.1f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive,ImVec4(1,1,1,0.2f)); + ImGui::PushStyleColor(ImGuiCol_Text,text); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,0); + const bool pressed = ImGui::Button(label,size); + ImGui::PopStyleColor(4); + ImGui::PopStyleVar(1); + return pressed; +} + //----------------------------------------------------------------------------- // Style Editor etc. @@ -3570,14 +3597,13 @@ bool ShowStyleSelector(const char* label) } bool ShowColormapSelector(const char* label) { + ImPlotContext& gp = *GImPlot; bool set = false; - static const char* map = ImPlot::GetColormapName(ImPlotColormap_Default); - if (ImGui::BeginCombo(label, map)) { - for (int i = 0; i < ImPlotColormap_COUNT; ++i) { - const char* name = GetColormapName(i); - if (ImGui::Selectable(name, map == name)) { - map = name; - ImPlot::SetColormap(i); + if (ImGui::BeginCombo(label, gp.ColormapData.GetName(gp.Style.Colormap))) { + for (int i = 0; i < gp.ColormapData.Count; ++i) { + const char* name = gp.ColormapData.GetName(i); + if (ImGui::Selectable(name, gp.Style.Colormap == i)) { + gp.Style.Colormap = i; ImPlot::BustItemCache(); set = true; } @@ -3735,92 +3761,100 @@ void ShowStyleEditor(ImPlotStyle* ref) { ImGui::LogToClipboard(); else ImGui::LogToTTY(); - ImGui::LogText("static const ImVec4 colormap[%d] = {\n", gp.ColormapSize); - for (int i = 0; i < gp.ColormapSize; ++i) { - const ImVec4& col = gp.Colormap[i]; - ImGui::LogText(" ImVec4(%.2ff, %.2ff, %.2ff, %.2ff)%s\n", col.x, col.y, col.z, col.w, i == gp.ColormapSize - 1 ? "" : ","); + int size = GetColormapSize(); + const char* name = GetColormapName(gp.Style.Colormap); + ImGui::LogText("static const ImU32 %s_Data[%d] = {\n", name, size); + for (int i = 0; i < size; ++i) { + ImU32 col = GetColormapColorU32(i,gp.Style.Colormap); + ImGui::LogText(" %u%s\n", col, i == size - 1 ? "" : ","); } - ImGui::LogText("};"); + ImGui::LogText("};\nImPlotColormap %s = ImPlot::AddColormap(\"%s\", %s_Data, %d);", name, name, name, size); ImGui::LogFinish(); } ImGui::SameLine(); ImGui::SetNextItemWidth(120); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); - ImGui::SameLine(); HelpMarker("Export code for selected Colormap\n(built in or custom)."); - ImGui::Separator(); - static ImVector custom; - static bool custom_set = false; - for (int i = 0; i < ImPlotColormap_COUNT; ++i) { - ImGui::PushID(i); - int size; - const ImVec4* cmap = GetColormap(i, &size); - bool selected = cmap == gp.Colormap; - if (selected) { - custom_set = false; - } + ImGui::SameLine(); + static bool edit = false; + ImGui::Checkbox("Edit Mode",&edit); + // built-in/added + ImGui::Separator(); + for (int i = 0; i < gp.ColormapData.Count; ++i) { + ImGui::PushID(i); + int size = gp.ColormapData.GetKeyCount(i); + bool selected = i == gp.Style.Colormap; + + const char* name = GetColormapName(i); if (!selected) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.25f); - if (ImGui::Button(GetColormapName(i), ImVec2(75,0))) { - SetColormap(i); + if (ImGui::Button(name, ImVec2(100,0))) { + gp.Style.Colormap = i; BustItemCache(); - custom_set = false; } if (!selected) ImGui::PopStyleVar(); ImGui::SameLine(); - for (int c = 0; c < size; ++c) { - ImGui::PushID(c); - ImGui::ColorButton("",cmap[c]); - if (c != size -1) - ImGui::SameLine(); - ImGui::PopID(); + ImGui::BeginGroup(); + if (edit) { + for (int c = 0; c < size; ++c) { + ImGui::PushID(c); + ImVec4 col4 = ImGui::ColorConvertU32ToFloat4(gp.ColormapData.GetKeyColor(i,c)); + if (ImGui::ColorEdit4("",&col4.x,ImGuiColorEditFlags_NoInputs)) { + ImU32 col32 = ImGui::ColorConvertFloat4ToU32(col4); + gp.ColormapData.SetKeyColor(i,c,col32); + BustItemCache(); + } + if ((c + 1) % 12 != 0 && c != size -1) + ImGui::SameLine(); + ImGui::PopID(); + } } + else { + if (ImPlot::ColormapButton("##",ImVec2(-1,0),i)) + edit = true; + } + ImGui::EndGroup(); ImGui::PopID(); } + + + static ImVector custom; if (custom.Size == 0) { - custom.push_back(ImVec4(1,1,1,1)); - custom.push_back(ImVec4(0.5f,0.5f,0.5f,1)); + custom.push_back(ImVec4(1,0,0,1)); + custom.push_back(ImVec4(0,1,0,1)); + custom.push_back(ImVec4(0,0,1,1)); } ImGui::Separator(); ImGui::BeginGroup(); - bool custom_set_now = custom_set; - if (!custom_set_now) - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.25f); - if (ImGui::Button("Custom", ImVec2(75, 0))) { - SetColormap(&custom[0], custom.Size); - BustItemCache(); - custom_set = true; - } - if (!custom_set_now) - ImGui::PopStyleVar(); - if (ImGui::Button("+", ImVec2((75 - ImGui::GetStyle().ItemSpacing.x)/2,0))) { + static char name[16] = "MyColormap"; + + + if (ImGui::Button("+", ImVec2((100 - ImGui::GetStyle().ItemSpacing.x)/2,0))) custom.push_back(ImVec4(0,0,0,1)); - if (custom_set) { - SetColormap(&custom[0], custom.Size); - BustItemCache(); - } - } ImGui::SameLine(); - if (ImGui::Button("-", ImVec2((75 - ImGui::GetStyle().ItemSpacing.x)/2,0)) && custom.Size > 1) { + if (ImGui::Button("-", ImVec2((100 - ImGui::GetStyle().ItemSpacing.x)/2,0)) && custom.Size > 2) custom.pop_back(); - if (custom_set) { - SetColormap(&custom[0], custom.Size); - BustItemCache(); - } - } + ImGui::SetNextItemWidth(100); + ImGui::InputText("##Name",name,16,ImGuiInputTextFlags_CharsNoBlank); + static bool qual = true; + ImGui::Checkbox("Qualitative",&qual); + if (ImGui::Button("Add", ImVec2(100, 0)) && gp.ColormapData.GetIndex(name)==-1) + AddColormap(name,custom.Data,custom.Size,qual); + ImGui::EndGroup(); ImGui::SameLine(); ImGui::BeginGroup(); for (int c = 0; c < custom.Size; ++c) { ImGui::PushID(c); - if (ImGui::ColorEdit4("##Col1", &custom[c].x, ImGuiColorEditFlags_NoInputs) && custom_set) { - SetColormap(&custom[0], custom.Size); - BustItemCache(); + if (ImGui::ColorEdit4("##Col1", &custom[c].x, ImGuiColorEditFlags_NoInputs)) { + } if ((c + 1) % 12 != 0) ImGui::SameLine(); ImGui::PopID(); } ImGui::EndGroup(); + + ImGui::EndTabItem(); } ImGui::EndTabBar(); @@ -3863,8 +3897,8 @@ void ShowAxisMetrics(ImPlotAxis* axis, bool show_axis_rects) { ImGui::Bullet(); ImGui::Text("AllHovered: %s", axis->AllHovered ? "true" : "false"); ImGui::Bullet(); ImGui::Text("Present: %s", axis->Present ? "true" : "false"); ImGui::Bullet(); ImGui::Text("HasRange: %s", axis->HasRange ? "true" : "false"); - ImGui::Bullet(); ImGui::Text("LinkedMin: %p", axis->LinkedMin); - ImGui::Bullet(); ImGui::Text("LinkedMax: %p", axis->LinkedMax); + ImGui::Bullet(); ImGui::Text("LinkedMin: %p", (void*)axis->LinkedMin); + ImGui::Bullet(); ImGui::Text("LinkedMax: %p", (void*)axis->LinkedMax); if (show_axis_rects) { ImDrawList& fg = *ImGui::GetForegroundDrawList(); fg.AddRect(axis->HoverRect.Min, axis->HoverRect.Max, IM_COL32(0,255,0,255)); @@ -3887,6 +3921,11 @@ void ShowMetricsWindow(bool* p_popen) { ImGui::Separator(); int n_plots = gp.Plots.GetSize(); if (ImGui::TreeNode("Tools")) { + if (ImGui::Button("Bust Plot Cache")) + BustPlotCache(); + ImGui::SameLine(); + if (ImGui::Button("Bust Item Cache")) + BustItemCache(); ImGui::Checkbox("Show Plot Rects", &show_plot_rects); ImGui::Checkbox("Show Axes Rects", &show_axes_rects); ImGui::TreePop(); @@ -3904,10 +3943,14 @@ void ShowMetricsWindow(bool* p_popen) { ImGui::PushID(i); if (ImGui::TreeNode("Item", "Item [ID=%u]", item->ID)) { ImGui::Bullet(); ImGui::Checkbox("Show", &item->Show); - ImGui::Bullet(); ImGui::ColorEdit4("Color",&item->Color.x, ImGuiColorEditFlags_NoInputs); + ImGui::Bullet(); + ImVec4 temp = ImGui::ColorConvertU32ToFloat4(item->Color); + if (ImGui::ColorEdit4("Color",&temp.x, ImGuiColorEditFlags_NoInputs)) + item->Color = ImGui::ColorConvertFloat4ToU32(temp); + ImGui::Bullet(); ImGui::Text("NameOffset: %d",item->NameOffset); ImGui::Bullet(); ImGui::Text("Name: %s", item->NameOffset != -1 ? plot->LegendData.Labels.Buf.Data + item->NameOffset : "N/A"); - ImGui::Bullet(); ImGui::Text("Hovered: %s", item->LegendHovered ? "true" : "false"); + ImGui::Bullet(); ImGui::Text("Hovered: %s",item->LegendHovered ? "true" : "false"); ImGui::TreePop(); } ImGui::PopID(); @@ -3945,6 +3988,47 @@ void ShowMetricsWindow(bool* p_popen) { } ImGui::TreePop(); } + if (ImGui::TreeNode("Colormaps")) { + ImGui::BulletText("Colormaps: %d", gp.ColormapData.Count); + ImGui::BulletText("Memory: %d bytes", gp.ColormapData.Tables.Size * 4); + if (ImGui::TreeNode("Data")) { + for (int m = 0; m < gp.ColormapData.Count; ++m) { + if (ImGui::TreeNode(gp.ColormapData.GetName(m))) { + int count = gp.ColormapData.GetKeyCount(m); + int size = gp.ColormapData.GetTableSize(m); + bool qual = gp.ColormapData.IsQual(m); + ImGui::BulletText("Qualitative: %s", qual ? "true" : "false"); + ImGui::BulletText("Key Count: %d", count); + ImGui::BulletText("Table Size: %d", size); + ImGui::Indent(); + + static float t = 0.5; + ImVec4 samp; + float wid = 32 * 10 - ImGui::GetFrameHeight() - ImGui::GetStyle().ItemSpacing.x; + ImGui::SetNextItemWidth(wid); + ImPlot::ColormapSlider("##Sample",&t,&samp,"%.3f",m); + ImGui::SameLine(); + ImGui::ColorButton("Sampler",samp); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); + for (int c = 0; c < size; ++c) { + ImVec4 col = ImGui::ColorConvertU32ToFloat4(gp.ColormapData.GetTableColor(m,c)); + ImGui::PushID(m*1000+c); + ImGui::ColorButton("",col,0,ImVec2(10,10)); + ImGui::PopID(); + if ((c + 1) % 32 != 0 && c != size - 1) + ImGui::SameLine(); + } + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + ImGui::Unindent(); + ImGui::TreePop(); + } + } + ImGui::TreePop(); + } + ImGui::TreePop(); + } ImGui::End(); } diff --git a/implot.h b/implot.h index 05b554d..8503e81 100644 --- a/implot.h +++ b/implot.h @@ -1,6 +1,6 @@ // MIT License -// Copyright (c) 2020 Evan Pezent +// Copyright (c) 2021 Evan Pezent // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -32,7 +32,7 @@ // Define attributes of all API symbols declarations (e.g. for DLL under Windows) // Using ImPlot via a shared library is not recommended, because we don't guarantee // backward nor forward ABI compatibility and also function call overhead. If you -// do use ImPlot as a DLL, be sure to call SetImGuiContext (details below). +// do use ImPlot as a DLL, be sure to call SetImGuiContext (see Miscellanous section). #ifndef IMPLOT_API #define IMPLOT_API #endif @@ -61,6 +61,7 @@ typedef int ImPlotColormap; // -> enum ImPlotColormap_ typedef int ImPlotLocation; // -> enum ImPlotLocation_ typedef int ImPlotOrientation; // -> enum ImPlotOrientation_ typedef int ImPlotYAxis; // -> enum ImPlotYAxis_; +typedef int ImPlotBin; // -> enum ImPlotBin_ // Options for plots. enum ImPlotFlags_ { @@ -85,14 +86,15 @@ enum ImPlotFlags_ { enum ImPlotAxisFlags_ { ImPlotAxisFlags_None = 0, // default ImPlotAxisFlags_NoLabel = 1 << 0, // the axis label will not be displayed (axis labels also hidden if the supplied string name is NULL) - ImPlotAxisFlags_NoGridLines = 1 << 1, // the axis grid lines will not be displayed - ImPlotAxisFlags_NoTickMarks = 1 << 2, // the axis tick marks will not be displayed - ImPlotAxisFlags_NoTickLabels = 1 << 3, // the axis tick labels will not be displayed + ImPlotAxisFlags_NoGridLines = 1 << 1, // no grid lines will be displayed + ImPlotAxisFlags_NoTickMarks = 1 << 2, // no tick marks will be displayed + ImPlotAxisFlags_NoTickLabels = 1 << 3, // no text labels will be displayed ImPlotAxisFlags_LogScale = 1 << 4, // a logartithmic (base 10) axis scale will be used (mutually exclusive with ImPlotAxisFlags_Time) ImPlotAxisFlags_Time = 1 << 5, // axis will display date/time formatted labels (mutually exclusive with ImPlotAxisFlags_LogScale) ImPlotAxisFlags_Invert = 1 << 6, // the axis will be inverted - ImPlotAxisFlags_LockMin = 1 << 7, // the axis minimum value will be locked when panning/zooming - ImPlotAxisFlags_LockMax = 1 << 8, // the axis maximum value will be locked when panning/zooming + ImPlotAxisFlags_AutoFit = 1 << 7, // axis will be auto-fitting to data extents + ImPlotAxisFlags_LockMin = 1 << 8, // the axis minimum value will be locked when panning/zooming + ImPlotAxisFlags_LockMax = 1 << 9, // the axis maximum value will be locked when panning/zooming ImPlotAxisFlags_Lock = ImPlotAxisFlags_LockMin | ImPlotAxisFlags_LockMax, ImPlotAxisFlags_NoDecorations = ImPlotAxisFlags_NoLabel | ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_NoTickMarks | ImPlotAxisFlags_NoTickLabels }; @@ -180,18 +182,22 @@ enum ImPlotMarker_ { // Built-in colormaps enum ImPlotColormap_ { - ImPlotColormap_Default = 0, // ImPlot default colormap (n=10) - ImPlotColormap_Deep = 1, // a.k.a. seaborn deep (n=10) - ImPlotColormap_Dark = 2, // a.k.a. matplotlib "Set1" (n=9) - ImPlotColormap_Pastel = 3, // a.k.a. matplotlib "Pastel1" (n=9) - ImPlotColormap_Paired = 4, // a.k.a. matplotlib "Paired" (n=12) - ImPlotColormap_Viridis = 5, // a.k.a. matplotlib "viridis" (n=11) - ImPlotColormap_Plasma = 6, // a.k.a. matplotlib "plasma" (n=11) - ImPlotColormap_Hot = 7, // a.k.a. matplotlib/MATLAB "hot" (n=11) - ImPlotColormap_Cool = 8, // a.k.a. matplotlib/MATLAB "cool" (n=11) - ImPlotColormap_Pink = 9, // a.k.a. matplotlib/MATLAB "pink" (n=11) - ImPlotColormap_Jet = 10, // a.k.a. MATLAB "jet" (n=11) - ImPlotColormap_COUNT + ImPlotColormap_Deep = 0, // a.k.a. seaborn deep (qual=true, n=10) (default) + ImPlotColormap_Dark = 1, // a.k.a. matplotlib "Set1" (qual=true, n=9 ) + ImPlotColormap_Pastel = 2, // a.k.a. matplotlib "Pastel1" (qual=true, n=9 ) + ImPlotColormap_Paired = 3, // a.k.a. matplotlib "Paired" (qual=true, n=12) + ImPlotColormap_Viridis = 4, // a.k.a. matplotlib "viridis" (qual=false, n=11) + ImPlotColormap_Plasma = 5, // a.k.a. matplotlib "plasma" (qual=false, n=11) + ImPlotColormap_Hot = 6, // a.k.a. matplotlib/MATLAB "hot" (qual=false, n=11) + ImPlotColormap_Cool = 7, // a.k.a. matplotlib/MATLAB "cool" (qual=false, n=11) + ImPlotColormap_Pink = 8, // a.k.a. matplotlib/MATLAB "pink" (qual=false, n=11) + ImPlotColormap_Jet = 9, // a.k.a. MATLAB "jet" (qual=false, n=11) + ImPlotColormap_Twilight = 10, // a.k.a. matplotlib "twilight" (qual=false, n=11) + ImPlotColormap_RdBu = 11, // red/blue, Color Brewer (qual=false, n=11) + ImPlotColormap_BrBG = 12, // brown/blue-green, Color Brewer (qual=false, n=11) + ImPlotColormap_PiYG = 13, // pink/yellow-green, Color Brewer (qual=false, n=11) + ImPlotColormap_Spectral = 14, // color spectrum, Color Brewer (qual=false, n=11) + ImPlotColormap_Greys = 15, // white/black (qual=false, n=2 ) }; // Used to position items on a plot (e.g. legends, labels, etc.) @@ -220,6 +226,14 @@ enum ImPlotYAxis_ { ImPlotYAxis_3 = 2 // second on right side }; +// Enums for different automatic histogram binning methods (k = bin count or w = bin width) +enum ImPlotBin_ { + ImPlotBin_Sqrt = -1, // k = sqrt(n) + ImPlotBin_Sturges = -2, // k = 1 + log2(n) + ImPlotBin_Rice = -3, // k = 2 * cbrt(n) + ImPlotBin_Scott = -4, // w = 3.49 * sigma / cbrt(n) +}; + // Double precision version of ImVec2 used by ImPlot. Extensible by end users. struct ImPlotPoint { double x, y; @@ -246,8 +260,12 @@ struct ImPlotRange { // Combination of two ranges for X and Y axes. struct ImPlotLimits { ImPlotRange X, Y; - bool Contains(const ImPlotPoint& p) const { return Contains(p.x, p.y); } - bool Contains(double x, double y) const { return X.Contains(x) && Y.Contains(y); } + ImPlotLimits() { } + ImPlotLimits(double x_min, double x_max, double y_min, double y_max) { X.Min = x_min; X.Max = x_max; Y.Min = y_min; Y.Max = y_max; } + bool Contains(const ImPlotPoint& p) const { return Contains(p.x, p.y); } + bool Contains(double x, double y) const { return X.Contains(x) && Y.Contains(y); } + ImPlotPoint Min() const { return ImPlotPoint(X.Min, Y.Min); } + ImPlotPoint Max() const { return ImPlotPoint(X.Max, Y.Max); } }; // Plot style structure @@ -275,14 +293,16 @@ struct ImPlotStyle { ImVec2 LabelPadding; // = 5,5 padding between axes labels, tick labels, and plot edge ImVec2 LegendPadding; // = 10,10 legend padding from plot edges ImVec2 LegendInnerPadding; // = 5,5 legend inner padding from legend edges - ImVec2 LegendSpacing; // = 0,0 spacing between legend entries + ImVec2 LegendSpacing; // = 5,0 spacing between legend entries ImVec2 MousePosPadding; // = 10,10 padding between plot edge and interior mouse location text ImVec2 AnnotationPadding; // = 2,2 text padding around annotation labels ImVec2 FitPadding; // = 0,0 additional fit padding as a percentage of the fit extents (e.g. ImVec2(0.1f,0.1f) adds 10% to the fit extents of X and Y) ImVec2 PlotDefaultSize; // = 400,300 default size used when ImVec2(0,0) is passed to BeginPlot ImVec2 PlotMinSize; // = 300,225 minimum size plot frame can be when shrunk - // colors - ImVec4 Colors[ImPlotCol_COUNT]; // array of plot specific colors + // style colors + ImVec4 Colors[ImPlotCol_COUNT]; // Array of styling colors. Indexable with ImPlotCol_ enums. + // colormap + ImPlotColormap Colormap; // The current colormap. Set this to either an ImPlotColormap_ enum or an index returned by AddColormap. // settings/flags bool AntiAliasedLines; // = false, enable global anti-aliasing on plot lines (overrides ImPlotFlags_AntiAliased) bool UseLocalTime; // = false, axis labels will be formatted for your timezone when ImPlotAxisFlag_Time is enabled @@ -303,22 +323,43 @@ namespace ImPlot { // Creates a new ImPlot context. Call this after ImGui::CreateContext. IMPLOT_API ImPlotContext* CreateContext(); -// Destroys an ImPlot context. Call this before ImGui::DestroyContext. NULL = destroy current context +// Destroys an ImPlot context. Call this before ImGui::DestroyContext. NULL = destroy current context. IMPLOT_API void DestroyContext(ImPlotContext* ctx = NULL); // Returns the current ImPlot context. NULL if no context has ben set. IMPLOT_API ImPlotContext* GetCurrentContext(); // Sets the current ImPlot context. IMPLOT_API void SetCurrentContext(ImPlotContext* ctx); +// Sets the current **ImGui** context. This is ONLY necessary if you are compiling +// ImPlot as a DLL (not recommended) separate from your ImGui compilation. It +// sets the global variable GImGui, which is not shared across DLL boundaries. +// See GImGui documentation in imgui.cpp for more details. +IMPLOT_API void SetImGuiContext(ImGuiContext* ctx); + //----------------------------------------------------------------------------- // Begin/End Plot //----------------------------------------------------------------------------- // Starts a 2D plotting context. If this function returns true, EndPlot() must -// be called, e.g. "if (BeginPlot(...)) { ... EndPlot(); }". #title_id must -// be unique. If you need to avoid ID collisions or don't want to display a -// title in the plot, use double hashes (e.g. "MyPlot##Hidden" or "##NoTitle"). -// If #x_label and/or #y_label are provided, axes labels will be displayed. +// be called! You are encouraged to use the following call convention: +// +// if (BeginPlot(...)) { +// ImPlot::PlotLine(...); +// EndPlot(); +// } +// +// Important notes: +// +// - #title_id must be unique to the current ImGui window. If you need to avoid ID +// collisions or don't want to display a title in the plot, use double hashes +// (e.g. "MyPlot##Hidden" or "##NoTitle"). +// - If #x_label and/or #y_label are provided, axes labels will be displayed. +// - #size is the **frame** size of the plot widget, not the plot area. The default +// size of plots (i.e. when ImVec2(0,0)) can be modified in your ImPlotStyle +// (default is 400x300). +// - Auxiliary y-axes must be enabled with ImPlotFlags_YAxis2/3 to be displayed. +// - See ImPlotFlags and ImPlotAxisFlags for more available options. + IMPLOT_API bool BeginPlot(const char* title_id, const char* x_label = NULL, const char* y_label = NULL, @@ -332,7 +373,7 @@ IMPLOT_API bool BeginPlot(const char* title_id, const char* y3_label = NULL); // Only call EndPlot() if BeginPlot() returns true! Typically called at the end -// of an if statement conditioned on BeginPlot(). +// of an if statement conditioned on BeginPlot(). See above. IMPLOT_API void EndPlot(); //----------------------------------------------------------------------------- @@ -356,9 +397,9 @@ IMPLOT_API void EndPlot(); // Vector2f data[42]; // ImPlot::PlotLine("line", &data[0].x, &data[0].y, 42, 0, sizeof(Vector2f)); // or sizeof(float)*2 // -// 2. Write a custom getter function or C++ lambda and pass it and your data to +// 2. Write a custom getter C function or C++ lambda and pass it and your data to // an ImPlot function post-fixed with a G (e.g. PlotScatterG). This has a -// slight performance cost, but probably not enough to worry about. +// slight performance cost, but probably not enough to worry about. Example: // // ImPlotPoint MyDataGetter(void* data, int idx) { // MyData* my_data = (MyData*)data; @@ -368,8 +409,17 @@ IMPLOT_API void EndPlot(); // return p // } // ... -// MyData my_data; -// ImPlot::PlotScatterG("scatter", MyDataGetter, &my_data, my_data.Size()); +// auto my_lambda = [](void*, int idx) { +// double t = idx / 999.0; +// return ImPlotPoint(t, 0.5+0.5*std::sin(2*PI*10*t)); +// }; +// ... +// if (ImPlot::BeginPlot("MyPlot")) { +// MyData my_data; +// ImPlot::PlotScatterG("scatter", MyDataGetter, &my_data, my_data.Size()); +// ImPlot::PlotLineG("line", my_lambda, nullptr, 1000); +// ImPlot::EndPlot(); +// } // // NB: All types are converted to double before plotting. You may lose information // if you try plotting extremely large 64-bit integral types. Proceed with caution! @@ -427,6 +477,16 @@ template IMPLOT_API void PlotPieChart(const char* const label_ids[] // Plots a 2D heatmap chart. Values are expected to be in row-major order. #label_fmt can be set to NULL for no labels. template IMPLOT_API void PlotHeatmap(const char* label_id, const T* values, int rows, int cols, double scale_min, double scale_max, const char* label_fmt="%.1f", const ImPlotPoint& bounds_min=ImPlotPoint(0,0), const ImPlotPoint& bounds_max=ImPlotPoint(1,1)); +// Plots a horizontal histogram. #bins can be a positive integer or an ImPlotBin_ method. If #cumulative is true, each bin contains its count plus the counts of all previous bins. +// If #density is true, the PDF is visualized. If both are true, the CDF is visualized. If #range is left unspecified, the min/max of #values will be used as the range. +// If #range is specified, outlier values outside of the range are not binned. However, outliers still count toward normalizing and cumulative counts unless #outliers is false. The largest bin count or density is returned. +template IMPLOT_API double PlotHistogram(const char* label_id, const T* values, int count, int bins=ImPlotBin_Sturges, bool cumulative=false, bool density=false, ImPlotRange range=ImPlotRange(), bool outliers=true, double bar_scale=1.0); + +// Plots two dimensional, bivariate histogram as a heatmap. #x_bins and #y_bins can be a positive integer or an ImPlotBin. If #density is true, the PDF is visualized. +// If #range is left unspecified, the min/max of #xs an #ys will be used as the ranges. If #range is specified, outlier values outside of range are not binned. +// However, outliers still count toward the normalizing count for density plots unless #outliers is false. The largest bin count or density is returned. +template IMPLOT_API double PlotHistogram2D(const char* label_id, const T* xs, const T* ys, int count, int x_bins=ImPlotBin_Sturges, int y_bins=ImPlotBin_Sturges, bool density=false, ImPlotLimits range=ImPlotLimits(), bool outliers=true); + // Plots digital data. Digital plots do not respond to y drag or zoom, and are always referenced to the bottom of the plot. template IMPLOT_API void PlotDigital(const char* label_id, const T* xs, const T* ys, int count, int offset=0, int stride=sizeof(T)); IMPLOT_API void PlotDigitalG(const char* label_id, ImPlotPoint (*getter)(void* data, int idx), void* data, int count, int offset=0); @@ -437,7 +497,7 @@ IMPLOT_API void PlotImage(const char* label_id, ImTextureID user_texture_id, con // Plots a centered text label at point x,y with optional pixel offset. Text color can be changed with ImPlot::PushStyleColor(ImPlotCol_InlayText, ...). IMPLOT_API void PlotText(const char* text, double x, double y, bool vertical=false, const ImVec2& pix_offset=ImVec2(0,0)); -// Plots an dummy item (i.e. adds a legend entry colored by ImPlotCol_Line) +// Plots a dummy item (i.e. adds a legend entry colored by ImPlotCol_Line) IMPLOT_API void PlotDummy(const char* label_id); //----------------------------------------------------------------------------- @@ -529,7 +589,7 @@ IMPLOT_API bool DragPoint(const char* id, double* x, double* y, bool show_label // The following functions MUST be called BETWEEN Begin/EndPlot! -// Set the location of the current plot's legend. +// Set the location of the current plot's legend (default = North|West). IMPLOT_API void SetLegendLocation(ImPlotLocation location, ImPlotOrientation orientation = ImPlotOrientation_Vertical, bool outside = false); // Set the location of the current plot's mouse position text (default = South|East). IMPLOT_API void SetMousePosLocation(ImPlotLocation location); @@ -576,26 +636,55 @@ IMPLOT_API void EndDragDropSource(); // Plot and Item Styling //----------------------------------------------------------------------------- +// Styling colors in ImPlot works similarly to styling colors in ImGui, but +// with one important difference. Like ImGui, all style colors are stored in an +// indexable array in ImPlotStyle. You can permanently modify these values through +// GetStyle().Colors, or temporarily modify them with Push/Pop functions below. +// However, by default all style colors in ImPlot default to a special color +// IMPLOT_AUTO_COL. The behavior of this color depends upon the style color to +// which it as applied: +// +// 1) For style colors associated with plot items (e.g. ImPlotCol_Line), +// IMPLOT_AUTO_COL tells ImPlot to color the item with the next unused +// color in the current colormap. Thus, every item will have a different +// color up to the number of colors in the colormap, at which point the +// colormap will roll over. For most use cases, you should not need to +// modify these style colors to anything but IMPLOT_COL_AUTO. You are +// probably better off changing the current colormap. However, if you +// need to explicitly color a particular item you may either Push/Pop +// the style color around the item in question, or use the SetNextXXXStyle +// API below. If you permanently set one of these style colors to a specific +// color, or forget to call Pop, then all subsequent items will be styled +// with the color you set. +// +// 2) For style colors associated with plot styling (e.g. ImPlotCol_PlotBg), +// IMPLOT_AUTO_COL tells ImPlot to set that color from color data in your +// **ImGuiStyle**. The ImGuiCol_ that these style colors default to are +// detailed above, and in general have been mapped to produce plots visually +// consistent with your current ImGui style. Of course, you are free to +// manually set these colors to whatever you like, and further can Push/Pop +// them around individual plots. + // Provides access to plot style structure for permanant modifications to colors, sizes, etc. IMPLOT_API ImPlotStyle& GetStyle(); -// Style colors for current ImGui style (default). +// Style plot colors for current ImGui style (default). IMPLOT_API void StyleColorsAuto(ImPlotStyle* dst = NULL); -// Style colors for ImGui "Classic". +// Style plot colors for ImGui "Classic". IMPLOT_API void StyleColorsClassic(ImPlotStyle* dst = NULL); -// Style colors for ImGui "Dark". +// Style plot colors for ImGui "Dark". IMPLOT_API void StyleColorsDark(ImPlotStyle* dst = NULL); -// Style colors for ImGui "Light". +// Style plot colors for ImGui "Light". IMPLOT_API void StyleColorsLight(ImPlotStyle* dst = NULL); // Use PushStyleX to temporarily modify your ImPlotStyle. The modification // will last until the matching call to PopStyleX. You MUST call a pop for // every push, otherwise you will leak memory! This behaves just like ImGui. -// Temporarily modify a plot color. Don't forget to call PopStyleColor! +// Temporarily modify a style color. Don't forget to call PopStyleColor! IMPLOT_API void PushStyleColor(ImPlotCol idx, ImU32 col); IMPLOT_API void PushStyleColor(ImPlotCol idx, const ImVec4& col); -// Undo temporary color modification. Undo multiple pushes at once by increasing count. +// Undo temporary style color modification(s). Undo multiple pushes at once by increasing count. IMPLOT_API void PopStyleColor(int count = 1); // Temporarily modify a style variable of float type. Don't forget to call PopStyleVar! @@ -604,7 +693,7 @@ IMPLOT_API void PushStyleVar(ImPlotStyleVar idx, float val); IMPLOT_API void PushStyleVar(ImPlotStyleVar idx, int val); // Temporarily modify a style variable of ImVec2 type. Don't forget to call PopStyleVar! IMPLOT_API void PushStyleVar(ImPlotStyleVar idx, const ImVec2& val); -// Undo temporary style modification. Undo multiple pushes at once by increasing count. +// Undo temporary style variable modification(s). Undo multiple pushes at once by increasing count. IMPLOT_API void PopStyleVar(int count = 1); // The following can be used to modify the style of the next plot item ONLY. They do @@ -633,55 +722,82 @@ IMPLOT_API const char* GetMarkerName(ImPlotMarker idx); // Colormaps //----------------------------------------------------------------------------- -// Item styling is based on Colormaps when the relevant ImPlotCol_ is set to -// IMPLOT_AUTO_COL (default). Several built in colormaps are available and can be -// toggled in the demo. You can push/pop or set your own colormaps as well. +// Item styling is based on colormaps when the relevant ImPlotCol_XXX is set to +// IMPLOT_AUTO_COL (default). Several built-in colormaps are available. You can +// add and then push/pop your own colormaps as well. To permanently set a colormap, +// modify the Colormap index member of your ImPlotStyle. -// The Colormap data will be ignored and a custom color will be used if you have done one of the following: +// Colormap data will be ignored and a custom color will be used if you have done one of the following: // 1) Modified an item style color in your ImPlotStyle to anything other than IMPLOT_AUTO_COL. // 2) Pushed an item style color using PushStyleColor(). -// 3) Set the next item style with a SetNextXStyle function. +// 3) Set the next item style with a SetNextXXXStyle function. -// Temporarily switch to one of the built-in colormaps. -IMPLOT_API void PushColormap(ImPlotColormap colormap); -// Temporarily switch to your custom colormap. The pointer data must persist until the matching call to PopColormap! -IMPLOT_API void PushColormap(const ImVec4* colormap, int size); -// Undo temporary colormap modification. +// Add a new colormap. The colormap can be used by pushing either the returned index or the string name with PushColormap. +// The colormap name must be unique and the size must be greater than 1. You will receive an assert otherwise! By default +// colormaps are considered to be qualitative (i.e. discrete). If you want to create a continuous colormap, set #qual=false. +// This will treat the colors you provide as keys, and ImPlot will build a linearly interpolated lookup table that fills +// in the gaps. The memory footprint of this table will be exactly ((size-1)*255+1)*4 bytes. +IMPLOT_API ImPlotColormap AddColormap(const char* name, const ImVec4* cols, int size, bool qual=true); +IMPLOT_API ImPlotColormap AddColormap(const char* name, const ImU32* cols, int size, bool qual=true); + +// Returns the number of available colormaps. +IMPLOT_API int GetColormapCount(); +// Returns a null terminated string name for a colormap given an index. Returns NULL if index is invalid. +IMPLOT_API const char* GetColormapName(ImPlotColormap cmap); +// Returns an index number for a colormap given a valid string name. Returns -1 if name is invalid. +IMPLOT_API ImPlotColormap GetColormapIndex(const char* name); + +// Temporarily switch to one of the built-in (i.e. ImPlotColormap_XXX) or user-added colormaps (i.e. a return value of AddColormap). Don't forget to call PopColormap! +IMPLOT_API void PushColormap(ImPlotColormap cmap); +// Push a colormap by string name. Use built-in names such as "Default", "Deep", "Jet", etc or a string you provided to AddColormap. Don't forget to call PopColormap! +IMPLOT_API void PushColormap(const char* name); +// Undo temporary colormap modification(s). Undo multiple pushes at once by increasing count. IMPLOT_API void PopColormap(int count = 1); -// Permanently sets a custom colormap. The colors will be copied to internal memory. Typically used on startup. Prefer PushColormap instead of calling this each frame. -IMPLOT_API void SetColormap(const ImVec4* colormap, int size); -// Permanently switch to one of the built-in colormaps. If samples is greater than 1, the map will be linearly resampled. Typically used on startup. Don't call this each frame. -IMPLOT_API void SetColormap(ImPlotColormap colormap, int samples = 0); - -// Returns the size of the current colormap. -IMPLOT_API int GetColormapSize(); -// Returns a color from the Color map given an index >= 0 (modulo will be performed). -IMPLOT_API ImVec4 GetColormapColor(int index); -// Linearly interpolates a color from the current colormap given t between 0 and 1. -IMPLOT_API ImVec4 LerpColormap(float t); -// Returns the next unused colormap color and advances the colormap. Can be used to skip colors if desired. +// Returns the next color from the current colormap and advances the colormap for the current plot. +// Can also be used with no return value to skip colors if desired. You need to call this between Begin/EndPlot! IMPLOT_API ImVec4 NextColormapColor(); -// Renders a vertical color scale using the current color map. Call this before or after Begin/EndPlot. -IMPLOT_API void ShowColormapScale(double scale_min, double scale_max, const ImVec2& size = ImVec2(0,0)); +// Colormap utils. If cmap = IMPLOT_AUTO (default), the current colormap is assumed. +// Pass an explicit colormap index (built-in or user added) to specify otherwise. -// Returns a null terminated string name for a built-in colormap. -IMPLOT_API const char* GetColormapName(ImPlotColormap colormap); +// Returns the size of a colormap. +IMPLOT_API int GetColormapSize(ImPlotColormap cmap = IMPLOT_AUTO); +// Returns a color from a colormap given an index >= 0 (modulo will be performed). +IMPLOT_API ImVec4 GetColormapColor(int idx, ImPlotColormap cmap = IMPLOT_AUTO); +// Sample a color from the current colormap given t between 0 and 1. +IMPLOT_API ImVec4 SampleColormap(float t, ImPlotColormap cmap = IMPLOT_AUTO); + +// Shows a vertical color scale with linear spaced ticks using the specified color map. Use double hashes to hide label (e.g. "##NoLabel"). +IMPLOT_API void ColormapScale(const char* label, double scale_min, double scale_max, const ImVec2& size = ImVec2(0,0), ImPlotColormap cmap = IMPLOT_AUTO); +// Shows a horizontal slider with a colormap gradient background. Optionally returns the color sampled at t in [0 1]. +IMPLOT_API bool ColormapSlider(const char* label, float* t, ImVec4* out = NULL, const char* format = "", ImPlotColormap cmap = IMPLOT_AUTO); +// Shows a button with a colormap gradient brackground. +IMPLOT_API bool ColormapButton(const char* label, const ImVec2& size = ImVec2(0,0), ImPlotColormap cmap = IMPLOT_AUTO); + +// When items in a plot sample their color from a colormap, the color is cached and does not change +// unless explicitly overriden. Therefore, if you change the colormap after the item has already been plotted, +// item colors will not update. If you need item colors to resample the new colormap, then use this +// function to bust the cached colors. If #plot_title_id is NULL, then every item in EVERY existing plot +// will be cache busted. Otherwise only the plot specified by #plot_title_id will be busted. For the +// latter, this function must be called in the ImGui window that the plot is in. You should rarely if ever +// need this function, but it is available for applications that require runtime swaps (see Heatmaps demo). +IMPLOT_API void BustColorCache(const char* plot_title_id = NULL); //----------------------------------------------------------------------------- // Miscellaneous //----------------------------------------------------------------------------- -// Render a icon similar to those that appear in legends (nifty for data lists). +// Render icons similar to those that appear in legends (nifty for data lists). IMPLOT_API void ItemIcon(const ImVec4& col); IMPLOT_API void ItemIcon(ImU32 col); +IMPLOT_API void ColormapIcon(ImPlotColormap cmap); -// Get the plot draw list for rendering to the current plot area. +// Get the plot draw list for custom rendering to the current plot area. Call between Begin/EndPlot. IMPLOT_API ImDrawList* GetPlotDrawList(); -// Push clip rect for rendering to current plot area. +// Push clip rect for rendering to current plot area. Call between Begin/EndPlot. IMPLOT_API void PushPlotClipRect(); -// Pop plot clip rect. +// Pop plot clip rect. Call between Begin/EndPlot. IMPLOT_API void PopPlotClipRect(); // Shows ImPlot style selector dropdown menu. @@ -690,17 +806,11 @@ IMPLOT_API bool ShowStyleSelector(const char* label); IMPLOT_API bool ShowColormapSelector(const char* label); // Shows ImPlot style editor block (not a window). IMPLOT_API void ShowStyleEditor(ImPlotStyle* ref = NULL); -// Add basic help/info block (not a window): how to manipulate ImPlot as an end-user. +// Add basic help/info block for end users (not a window). IMPLOT_API void ShowUserGuide(); // Shows ImPlot metrics/debug information. IMPLOT_API void ShowMetricsWindow(bool* p_popen = NULL); -// Sets the current _ImGui_ context. This is ONLY necessary if you are compiling -// ImPlot as a DLL (not recommended) separate from your ImGui compilation. It -// sets the global variable GImGui, which is not shared across DLL boundaries. -// See GImGui documentation in imgui.cpp for more details. -IMPLOT_API void SetImGuiContext(ImGuiContext* ctx); - //----------------------------------------------------------------------------- // Demo (add implot_demo.cpp to your sources!) //----------------------------------------------------------------------------- diff --git a/implot_demo.cpp b/implot_demo.cpp index be17711..d76316d 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -1,6 +1,6 @@ // MIT License -// Copyright (c) 2020 Evan Pezent +// Copyright (c) 2021 Evan Pezent // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -27,7 +27,6 @@ #include #include #include -#include #ifdef _MSC_VER #define sprintf sprintf_s @@ -76,6 +75,44 @@ inline T RandomRange(T min, T max) { return min + scale * ( max - min ); } +ImVec4 RandomColor() { + ImVec4 col; + col.x = RandomRange(0.0f,1.0f); + col.y = RandomRange(0.0f,1.0f); + col.z = RandomRange(0.0f,1.0f); + col.w = 1.0f; + return col; +} + +double RandomGauss() { + static double V1, V2, S; + static int phase = 0; + double X; + if(phase == 0) { + do { + double U1 = (double)rand() / RAND_MAX; + double U2 = (double)rand() / RAND_MAX; + V1 = 2 * U1 - 1; + V2 = 2 * U2 - 1; + S = V1 * V1 + V2 * V2; + } while(S >= 1 || S == 0); + + X = V1 * sqrt(-2 * log(S) / S); + } else + X = V2 * sqrt(-2 * log(S) / S); + phase = 1 - phase; + return X; +} + +template +struct NormalDistribution { + NormalDistribution(double mean, double sd) { + for (int i = 0; i < N; ++i) + Data[i] = RandomGauss()*sd + mean; + } + double Data[N]; +}; + // utility structure for realtime plot struct ScrollingBuffer { int MaxSize; @@ -511,11 +548,17 @@ void ShowDemoWindow(bool* p_open) { static const char* ylabels[] = {"R1","R2","R3","R4","R5","R6","R7"}; static ImPlotColormap map = ImPlotColormap_Viridis; - if (ImGui::Button("Change Colormap",ImVec2(225,0))) - map = (map + 1) % ImPlotColormap_COUNT; + if (ImPlot::ColormapButton(ImPlot::GetColormapName(map),ImVec2(225,0),map)) { + map = (map + 1) % ImPlot::GetColormapCount(); + // We bust the color cache of our plots so that item colors will + // resample the new colormap in the event that they have already + // been created. See documentation in implot.h. + BustColorCache("##Heatmap1"); + BustColorCache("##Heatmap2"); + } ImGui::SameLine(); - ImGui::LabelText("##Colormap Index", "%s", ImPlot::GetColormapName(map)); + ImGui::LabelText("##Colormap Index", "%s", "Change Colormap"); ImGui::SetNextItemWidth(225); ImGui::DragFloatRange2("Min / Max",&scale_min, &scale_max, 0.01f, -20, 20); static ImPlotAxisFlags axes_flags = ImPlotAxisFlags_Lock | ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_NoTickMarks; @@ -528,25 +571,153 @@ void ShowDemoWindow(bool* p_open) { ImPlot::EndPlot(); } ImGui::SameLine(); - ImPlot::ShowColormapScale(scale_min, scale_max, ImVec2(60,225)); - ImPlot::PopColormap(); + ImPlot::ColormapScale("##HeatScale",scale_min, scale_max, ImVec2(60,225)); ImGui::SameLine(); - static double values2[100*100]; + const int size = 200; + static double values2[size*size]; srand((unsigned int)(DEMO_TIME*1000000)); - for (int i = 0; i < 100*100; ++i) + for (int i = 0; i < size*size; ++i) values2[i] = RandomRange(0.0,1.0); - static ImVec4 gray[2] = {ImVec4(0,0,0,1), ImVec4(1,1,1,1)}; - ImPlot::PushColormap(gray, 2); ImPlot::SetNextPlotLimits(-1,1,-1,1); if (ImPlot::BeginPlot("##Heatmap2",NULL,NULL,ImVec2(225,225),0,ImPlotAxisFlags_NoDecorations,ImPlotAxisFlags_NoDecorations)) { - ImPlot::PlotHeatmap("heat1",values2,100,100,0,1,NULL); - ImPlot::PlotHeatmap("heat2",values2,100,100,0,1,NULL, ImPlotPoint(-1,-1), ImPlotPoint(0,0)); + ImPlot::PlotHeatmap("heat1",values2,size,size,0,1,NULL); + ImPlot::PlotHeatmap("heat2",values2,size,size,0,1,NULL, ImPlotPoint(-1,-1), ImPlotPoint(0,0)); ImPlot::EndPlot(); } ImPlot::PopColormap(); + + } + //------------------------------------------------------------------------- + if (ImGui::CollapsingHeader("Histograms")) { + static int bins = 50; + static bool cumulative = false; + static bool density = true; + static bool outliers = true; + static double mu = 5; + static double sigma = 2; + static NormalDistribution<10000> dist(mu, sigma); + static double x[100]; + static double y[100]; + if (density) { + for (int i = 0; i < 100; ++i) { + x[i] = -3 + 16 * (double)i/99.0; + y[i] = exp( - (x[i]-mu)*(x[i]-mu) / (2*sigma*sigma)) / (sigma * sqrt(2*3.141592653589793238)); + } + if (cumulative) { + for (int i = 1; i < 100; ++i) + y[i] += y[i-1]; + for (int i = 0; i < 100; ++i) + y[i] /= y[99]; + } + } + ImGui::SetNextItemWidth(200); + if (ImGui::RadioButton("Sqrt",bins==ImPlotBin_Sqrt)) { bins = ImPlotBin_Sqrt; } ImGui::SameLine(); + if (ImGui::RadioButton("Sturges",bins==ImPlotBin_Sturges)) { bins = ImPlotBin_Sturges; } ImGui::SameLine(); + if (ImGui::RadioButton("Rice",bins==ImPlotBin_Rice)) { bins = ImPlotBin_Rice; } ImGui::SameLine(); + if (ImGui::RadioButton("Scott",bins==ImPlotBin_Scott)) { bins = ImPlotBin_Scott; } ImGui::SameLine(); + if (ImGui::RadioButton("N Bins",bins>=0)) bins = 50; + if (bins>=0) { + ImGui::SameLine(); + ImGui::SetNextItemWidth(200); + ImGui::SliderInt("##Bins", &bins, 1, 100); + } + ImGui::Checkbox("Density", &density); + ImGui::SameLine(); + ImGui::Checkbox("Cumulative", &cumulative); + ImGui::SameLine(); + static bool range = false; + ImGui::Checkbox("Range", &range); + static float rmin = -3; + static float rmax = 13; + if (range) { + ImGui::SameLine(); + ImGui::SetNextItemWidth(200); + ImGui::DragFloat2("##Range",&rmin,0.1f,-3,13); + ImGui::SameLine(); + ImGui::Checkbox("Outliers",&outliers); + } + ImPlot::SetNextPlotLimits(-3, 13, 0, 0.25); + if (ImPlot::BeginPlot("##Histograms")) { + ImPlot::SetNextFillStyle(IMPLOT_AUTO_COL, 0.5f); + ImPlot::PlotHistogram("Empirical", dist.Data, 10000, bins, cumulative, density, range ? ImPlotRange(rmin,rmax) : ImPlotRange(), outliers); + if (density && outliers) + ImPlot::PlotLine("Theoretical",x,y,100); + ImPlot::EndPlot(); + } + + static int count = 500000; + static int xybins[2] = {200,200}; + static bool density2 = false; + ImGui::SliderInt("Count",&count,100,500000); + ImGui::SliderInt2("Bins",xybins,1,500); + ImGui::SameLine(); + ImGui::Checkbox("Density##2",&density2); + static NormalDistribution<500000> dist1(1, 2); + static NormalDistribution<500000> dist2(1, 1); + double max_count = 0; + ImPlot::PushColormap("Twilight"); + if (ImPlot::BeginPlot("##Hist2D",0,0,ImVec2(ImGui::GetContentRegionAvail().x-100-ImGui::GetStyle().ItemSpacing.x,0),0,ImPlotAxisFlags_AutoFit,ImPlotAxisFlags_AutoFit)) { + max_count = ImPlot::PlotHistogram2D("Hist2D",dist1.Data,dist2.Data,count,xybins[0],xybins[1],density2,ImPlotLimits(-6,6,-6,6)); + ImPlot::EndPlot(); + } + ImGui::SameLine(); + ImPlot::ColormapScale(density2 ? "Density" : "Count",0,max_count,ImVec2(100,0)); + ImPlot::PopColormap(); + } + //------------------------------------------------------------------------- + if (ImGui::CollapsingHeader("Digital Plots")) { + ImGui::BulletText("Digital plots do not respond to Y drag and zoom, so that"); + ImGui::Indent(); + ImGui::Text("you can drag analog plots over the rising/falling digital edge."); + ImGui::Unindent(); + + static bool paused = false; + static ScrollingBuffer dataDigital[2]; + static ScrollingBuffer dataAnalog[2]; + static bool showDigital[2] = {true, false}; + static bool showAnalog[2] = {true, false}; + + char label[32]; + ImGui::Checkbox("digital_0", &showDigital[0]); ImGui::SameLine(); + ImGui::Checkbox("digital_1", &showDigital[1]); ImGui::SameLine(); + ImGui::Checkbox("analog_0", &showAnalog[0]); ImGui::SameLine(); + ImGui::Checkbox("analog_1", &showAnalog[1]); + + static float t = 0; + if (!paused) { + t += ImGui::GetIO().DeltaTime; + //digital signal values + if (showDigital[0]) + dataDigital[0].AddPoint(t, sinf(2*t) > 0.45); + if (showDigital[1]) + dataDigital[1].AddPoint(t, sinf(2*t) < 0.45); + //Analog signal values + if (showAnalog[0]) + dataAnalog[0].AddPoint(t, sinf(2*t)); + if (showAnalog[1]) + dataAnalog[1].AddPoint(t, cosf(2*t)); + } + ImPlot::SetNextPlotLimitsY(-1, 1); + ImPlot::SetNextPlotLimitsX(t - 10.0, t, paused ? ImGuiCond_Once : ImGuiCond_Always); + if (ImPlot::BeginPlot("##Digital")) { + for (int i = 0; i < 2; ++i) { + if (showDigital[i] && dataDigital[i].Data.size() > 0) { + sprintf(label, "digital_%d", i); + ImPlot::PlotDigital(label, &dataDigital[i].Data[0].x, &dataDigital[i].Data[0].y, dataDigital[i].Data.size(), dataDigital[i].Offset, 2 * sizeof(float)); + } + } + for (int i = 0; i < 2; ++i) { + if (showAnalog[i]) { + sprintf(label, "analog_%d", i); + if (dataAnalog[i].Data.size() > 0) + ImPlot::PlotLine(label, &dataAnalog[i].Data[0].x, &dataAnalog[i].Data[0].y, dataAnalog[i].Data.size(), dataAnalog[i].Offset, 2 * sizeof(float)); + } + } + ImPlot::EndPlot(); + } } //------------------------------------------------------------------------- if (ImGui::CollapsingHeader("Images")) { @@ -650,7 +821,7 @@ void ShowDemoWindow(bool* p_open) { ys2[i] = log(xs[i]); ys3[i] = pow(10.0, xs[i]); } - ImGui::BulletText("Open the plot context menu (double right click) to change scales."); + ImGui::BulletText("Open the plot context menu (right click) to change scales."); ImPlot::SetNextPlotLimits(0.1, 100, 0, 10); if (ImPlot::BeginPlot("Log Plot", NULL, NULL, ImVec2(-1,0), 0, ImPlotAxisFlags_LogScale )) { @@ -826,9 +997,9 @@ void ShowDemoWindow(bool* p_open) { } ImGui::Text("The current plot limits are: [%g,%g,%g,%g]", range.X.Min, range.X.Max, range.Y.Min, range.Y.Max); ImGui::Text("The current query limits are: [%g,%g,%g,%g]", query.X.Min, query.X.Max, query.Y.Min, query.Y.Max); - } - //------------------------------------------------------------------------- - if (ImGui::CollapsingHeader("Views")) { + + ImGui::Separator(); + // mimic's soulthread's imgui_plot demo static float x_data[512]; static float y_data1[512]; @@ -847,15 +1018,15 @@ void ShowDemoWindow(bool* p_open) { ImGui::BulletText("Query the first plot to render a subview in the second plot (see above for controls)."); ImPlot::SetNextPlotLimits(0,0.01,-1,1); ImPlotAxisFlags flags = ImPlotAxisFlags_NoTickLabels; - ImPlotLimits query; + ImPlotLimits query2; if (ImPlot::BeginPlot("##View1",NULL,NULL,ImVec2(-1,150), ImPlotFlags_Query, flags, flags)) { ImPlot::PlotLine("Signal 1", x_data, y_data1, 512); ImPlot::PlotLine("Signal 2", x_data, y_data2, 512); ImPlot::PlotLine("Signal 3", x_data, y_data3, 512); - query = ImPlot::GetPlotQuery(); + query2 = ImPlot::GetPlotQuery(); ImPlot::EndPlot(); } - ImPlot::SetNextPlotLimits(query.X.Min, query.X.Max, query.Y.Min, query.Y.Max, ImGuiCond_Always); + ImPlot::SetNextPlotLimits(query2.X.Min, query2.X.Max, query2.Y.Min, query2.Y.Max, ImGuiCond_Always); if (ImPlot::BeginPlot("##View2",NULL,NULL,ImVec2(-1,150), ImPlotFlags_CanvasOnly, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations)) { ImPlot::PlotLine("Signal 1", x_data, y_data1, 512); ImPlot::PlotLine("Signal 2", x_data, y_data2, 512); @@ -871,7 +1042,7 @@ void ShowDemoWindow(bool* p_open) { ImGui::CheckboxFlags("South", (unsigned int*)&loc, ImPlotLocation_South); ImGui::SameLine(); ImGui::CheckboxFlags("West", (unsigned int*)&loc, ImPlotLocation_West); ImGui::SameLine(); ImGui::CheckboxFlags("East", (unsigned int*)&loc, ImPlotLocation_East); ImGui::SameLine(); - ImGui::Checkbox("Horizontal", &h); ImGui::SameLine(); + ImGui::Checkbox("Horizontal##2", &h); ImGui::SameLine(); ImGui::Checkbox("Outside", &o); ImGui::SliderFloat2("LegendPadding", (float*)&GetStyle().LegendPadding, 0.0f, 20.0f, "%.0f"); @@ -908,8 +1079,8 @@ void ShowDemoWindow(bool* p_open) { ImPlot::DragLineY("y2",&y2,show_labels); double xs[1000], ys[1000]; for (int i = 0; i < 1000; ++i) { - xs[i] = (x2+x1)/2+abs(x2-x1)*(i/1000.0f - 0.5f); - ys[i] = (y1+y2)/2+abs(y2-y1)/2*sin(f*i/10); + xs[i] = (x2+x1)/2+fabs(x2-x1)*(i/1000.0f - 0.5f); + ys[i] = (y1+y2)/2+fabs(y2-y1)/2*sin(f*i/10); } ImPlot::PlotLine("Interactive Data", xs, ys, 1000); ImPlot::SetPlotYAxis(ImPlotYAxis_2); @@ -992,7 +1163,7 @@ void ShowDemoWindow(bool* p_open) { Plt = 0; Yax = ImPlotYAxis_1; sprintf(Label, "%02d Hz", Idx+1); - Color = ImPlot::GetColormapColor(Idx); + Color = RandomColor(); Data.reserve(1001); for (int k = 0; k < 1001; ++k) { float t = k * 1.0f / 999; @@ -1134,59 +1305,6 @@ void ShowDemoWindow(bool* p_open) { ImGui::EndChild(); } //------------------------------------------------------------------------- - if (ImGui::CollapsingHeader("Digital and Analog Signals")) { - - ImGui::BulletText("You can plot digital and analog signals on the same plot."); - ImGui::BulletText("Digital signals do not respond to Y drag and zoom, so that"); - ImGui::Indent(); - ImGui::Text("you can drag analog signals over the rising/falling digital edge."); - ImGui::Unindent(); - - static bool paused = false; - static ScrollingBuffer dataDigital[2]; - static ScrollingBuffer dataAnalog[2]; - static bool showDigital[2] = {true, false}; - static bool showAnalog[2] = {true, false}; - - char label[32]; - ImGui::Checkbox("digital_0", &showDigital[0]); ImGui::SameLine(); - ImGui::Checkbox("digital_1", &showDigital[1]); ImGui::SameLine(); - ImGui::Checkbox("analog_0", &showAnalog[0]); ImGui::SameLine(); - ImGui::Checkbox("analog_1", &showAnalog[1]); - - static float t = 0; - if (!paused) { - t += ImGui::GetIO().DeltaTime; - //digital signal values - if (showDigital[0]) - dataDigital[0].AddPoint(t, sinf(2*t) > 0.45); - if (showDigital[1]) - dataDigital[1].AddPoint(t, sinf(2*t) < 0.45); - //Analog signal values - if (showAnalog[0]) - dataAnalog[0].AddPoint(t, sinf(2*t)); - if (showAnalog[1]) - dataAnalog[1].AddPoint(t, cosf(2*t)); - } - ImPlot::SetNextPlotLimitsY(-1, 1); - ImPlot::SetNextPlotLimitsX(t - 10.0, t, paused ? ImGuiCond_Once : ImGuiCond_Always); - if (ImPlot::BeginPlot("##Digital")) { - for (int i = 0; i < 2; ++i) { - if (showDigital[i] && dataDigital[i].Data.size() > 0) { - sprintf(label, "digital_%d", i); - ImPlot::PlotDigital(label, &dataDigital[i].Data[0].x, &dataDigital[i].Data[0].y, dataDigital[i].Data.size(), dataDigital[i].Offset, 2 * sizeof(float)); - } - } - for (int i = 0; i < 2; ++i) { - if (showAnalog[i]) { - sprintf(label, "analog_%d", i); - if (dataAnalog[i].Data.size() > 0) - ImPlot::PlotLine(label, &dataAnalog[i].Data[0].x, &dataAnalog[i].Data[0].y, dataAnalog[i].Data.size(), dataAnalog[i].Offset, 2 * sizeof(float)); - } - } - ImPlot::EndPlot(); - } - } if (ImGui::CollapsingHeader("Tables")) { #ifdef IMGUI_HAS_TABLE static ImGuiTableFlags flags = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_RowBg; @@ -1590,7 +1708,7 @@ void PlotCandlestick(const char* label_id, const double* xs, const double* opens // begin plot item if (ImPlot::BeginItem(label_id)) { // override legend icon color - ImPlot::GetCurrentItem()->Color = ImVec4(0.25f,0.25f,0.25f,1); + ImPlot::GetCurrentItem()->Color = IM_COL32(64,64,64,255); // fit data if requested if (ImPlot::FitThisFrame()) { for (int i = 0; i < count; ++i) { diff --git a/implot_internal.h b/implot_internal.h index 05699db..78d0a21 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -1,6 +1,6 @@ // MIT License -// Copyright (c) 2020 Evan Pezent +// Copyright (c) 2021 Evan Pezent // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -43,26 +43,7 @@ #endif //----------------------------------------------------------------------------- -// [SECTION] Forward Declarations -//----------------------------------------------------------------------------- - -struct ImPlotTick; -struct ImPlotAxis; -struct ImPlotAxisState; -struct ImPlotAxisColor; -struct ImPlotItem; -struct ImPlotLegendData; -struct ImPlotPlot; -struct ImPlotNextPlotData; - -//----------------------------------------------------------------------------- -// [SECTION] Context Pointer -//----------------------------------------------------------------------------- - -extern IMPLOT_API ImPlotContext* GImPlot; // Current implicit context pointer - -//----------------------------------------------------------------------------- -// [SECTION] Macros and Constants +// [SECTION] Constants //----------------------------------------------------------------------------- // Constants can be changed unless stated otherwise. We may move some of these @@ -79,6 +60,34 @@ extern IMPLOT_API ImPlotContext* GImPlot; // Current implicit context pointer // Maximum allowable timestamp value 01/01/3000 @ 12:00am (UTC) (DO NOT INCREASE THIS) #define IMPLOT_MAX_TIME 32503680000 +//----------------------------------------------------------------------------- +// [SECTION] Macros +//----------------------------------------------------------------------------- + +// Split ImU32 color into RGB components [0 255] +#define IM_COL32_SPLIT_RGB(col,r,g,b) \ + ImU32 r = ((col >> IM_COL32_R_SHIFT) & 0xFF); \ + ImU32 g = ((col >> IM_COL32_G_SHIFT) & 0xFF); \ + ImU32 b = ((col >> IM_COL32_B_SHIFT) & 0xFF); + +//----------------------------------------------------------------------------- +// [SECTION] Forward Declarations +//----------------------------------------------------------------------------- + +struct ImPlotTick; +struct ImPlotAxis; +struct ImPlotAxisColor; +struct ImPlotItem; +struct ImPlotLegendData; +struct ImPlotPlot; +struct ImPlotNextPlotData; + +//----------------------------------------------------------------------------- +// [SECTION] Context Pointer +//----------------------------------------------------------------------------- + +extern IMPLOT_API ImPlotContext* GImPlot; // Current implicit context pointer + //----------------------------------------------------------------------------- // [SECTION] Generic Helpers //----------------------------------------------------------------------------- @@ -95,6 +104,9 @@ inline void ImFlipFlag(TSet& set, TFlag flag) { ImHasFlag(set, flag) ? set &= ~f // Linearly remaps x from [x0 x1] to [y0 y1]. template inline T ImRemap(T x, T x0, T x1, T y0, T y1) { return y0 + (x - x0) * (y1 - y0) / (x1 - x0); } +// Linear rempas x from [x0 x1] to [0 1] +template +inline T ImRemap01(T x, T x0, T x1) { return (x - x0) / (x1 - x0); } // Returns always positive modulo (assumes r != 0) inline int ImPosMod(int l, int r) { return (l % r + r) % r; } // Returns true if val is NAN or INFINITY @@ -109,17 +121,80 @@ inline double ImConstrainLog(double val) { return val <= 0 ? 0.001f : val; } inline double ImConstrainTime(double val) { return val < IMPLOT_MIN_TIME ? IMPLOT_MIN_TIME : (val > IMPLOT_MAX_TIME ? IMPLOT_MAX_TIME : val); } // True if two numbers are approximately equal using units in the last place. inline bool ImAlmostEqual(double v1, double v2, int ulp = 2) { return ImAbs(v1-v2) < DBL_EPSILON * ImAbs(v1+v2) * ulp || ImAbs(v1-v2) < DBL_MIN; } - -// Offset calculator helper -template -struct ImOffsetCalculator { - ImOffsetCalculator(const int* sizes) { - Offsets[0] = 0; - for (int i = 1; i < Count; ++i) - Offsets[i] = Offsets[i-1] + sizes[i-1]; +// Finds min value in an unsorted array +template +inline T ImMinArray(const T* values, int count) { T m = values[0]; for (int i = 1; i < count; ++i) { if (values[i] < m) { m = values[i]; } } return m; } +// Finds the max value in an unsorted array +template +inline T ImMaxArray(const T* values, int count) { T m = values[0]; for (int i = 1; i < count; ++i) { if (values[i] > m) { m = values[i]; } } return m; } +// Finds the min and max value in an unsorted array +template +inline void ImMinMaxArray(const T* values, int count, T* min_out, T* max_out) { + T Min = values[0]; T Max = values[0]; + for (int i = 1; i < count; ++i) { + if (values[i] < Min) { Min = values[i]; } + if (values[i] > Max) { Max = values[i]; } } - int Offsets[Count]; -}; + *min_out = Min; *max_out = Max; +} +// Finds the mean of an array +template +inline double ImMean(const T* values, int count) { + double den = 1.0 / count; + double mu = 0; + for (int i = 0; i < count; ++i) + mu += values[i] * den; + return mu; +} +// Finds the sample standard deviation of an array +template +inline double ImStdDev(const T* values, int count) { + double den = 1.0 / (count - 1.0); + double mu = ImMean(values, count); + double x = 0; + for (int i = 0; i < count; ++i) + x += (values[i] - mu) * (values[i] - mu) * den; + return sqrt(x); +} +// Mix color a and b by factor s in [0 256] +inline ImU32 ImMixU32(ImU32 a, ImU32 b, ImU32 s) { +#ifdef IMPLOT_MIX64 + const ImU32 af = 256-s; + const ImU32 bf = s; + const ImU64 al = (a & 0x00ff00ff) | (((ImU64)(a & 0xff00ff00)) << 24); + const ImU64 bl = (b & 0x00ff00ff) | (((ImU64)(b & 0xff00ff00)) << 24); + const ImU64 mix = (al * af + bl * bf); + return ((mix >> 32) & 0xff00ff00) | ((mix & 0xff00ff00) >> 8); +#else + const ImU32 af = 256-s; + const ImU32 bf = s; + const ImU32 al = (a & 0x00ff00ff); + const ImU32 ah = (a & 0xff00ff00) >> 8; + const ImU32 bl = (b & 0x00ff00ff); + const ImU32 bh = (b & 0xff00ff00) >> 8; + const ImU32 ml = (al * af + bl * bf); + const ImU32 mh = (ah * af + bh * bf); + return (mh & 0xff00ff00) | ((ml & 0xff00ff00) >> 8); +#endif +} + +// Lerp across an array of 32-bit collors given t in [0.0 1.0] +inline ImU32 ImLerpU32(const ImU32* colors, int size, float t) { + int i1 = (int)((size - 1 ) * t); + int i2 = i1 + 1; + if (i2 == size || size == 1) + return colors[i1]; + float den = 1.0f / (size - 1); + float t1 = i1 * den; + float t2 = i2 * den; + float tr = ImRemap01(t, t1, t2); + return ImMixU32(colors[i1], colors[i2], (ImU32)(tr*256)); +} + +// Set alpha channel of 32-bit color from float in range [0.0 1.0] +inline ImU32 ImAlphaU32(ImU32 col, float alpha) { + return col & ~((ImU32)((1.0f-alpha)*255)<=(const ImPlotTime& lhs, const ImPlotTime& rhs) { return lhs > rhs || lhs == rhs; } -// Storage for colormap modifiers -struct ImPlotColormapMod { - ImPlotColormapMod(const ImVec4* colormap, int colormap_size) { - Colormap = colormap; - ColormapSize = colormap_size; +// Colormap data storage +struct ImPlotColormapData { + ImVector Keys; + ImVector KeyCounts; + ImVector KeyOffsets; + ImVector Tables; + ImVector TableSizes; + ImVector TableOffsets; + ImGuiTextBuffer Text; + ImVector TextOffsets; + ImVector Quals; + ImGuiStorage Map; + int Count; + + ImPlotColormapData() { Count = 0; } + + int Append(const char* name, const ImU32* keys, int count, bool qual) { + if (GetIndex(name) != -1) + return -1; + KeyOffsets.push_back(Keys.size()); + KeyCounts.push_back(count); + Keys.reserve(Keys.size()+count); + for (int i = 0; i < count; ++i) + Keys.push_back(keys[i]); + TextOffsets.push_back(Text.size()); + Text.append(name, name + strlen(name) + 1); + Quals.push_back(qual); + ImGuiID id = ImHashStr(name); + int idx = Count++; + Map.SetInt(id,idx); + _AppendTable(idx); + return idx; } - const ImVec4* Colormap; - int ColormapSize; + + void _AppendTable(ImPlotColormap cmap) { + int key_count = GetKeyCount(cmap); + const ImU32* keys = GetKeys(cmap); + int off = Tables.size(); + TableOffsets.push_back(off); + if (IsQual(cmap)) { + Tables.reserve(key_count); + for (int i = 0; i < key_count; ++i) + Tables.push_back(keys[i]); + TableSizes.push_back(key_count); + } + else { + int max_size = 255 * (key_count-1) + 1; + Tables.reserve(off + max_size); + // ImU32 last = keys[0]; + // Tables.push_back(last); + // int n = 1; + for (int i = 0; i < key_count-1; ++i) { + for (int s = 0; s < 255; ++s) { + ImU32 a = keys[i]; + ImU32 b = keys[i+1]; + ImU32 c = ImMixU32(a,b,s); + // if (c != last) { + Tables.push_back(c); + // last = c; + // n++; + // } + } + } + ImU32 c = keys[key_count-1]; + // if (c != last) { + Tables.push_back(c); + // n++; + // } + // TableSizes.push_back(n); + TableSizes.push_back(max_size); + } + } + + void RebuildTables() { + Tables.resize(0); + TableSizes.resize(0); + TableOffsets.resize(0); + for (int i = 0; i < Count; ++i) + _AppendTable(i); + } + + inline bool IsQual(ImPlotColormap cmap) const { return Quals[cmap]; } + inline const char* GetName(ImPlotColormap cmap) const { return cmap < Count ? Text.Buf.Data + TextOffsets[cmap] : NULL; } + inline ImPlotColormap GetIndex(const char* name) const { ImGuiID key = ImHashStr(name); return Map.GetInt(key,-1); } + + inline const ImU32* GetKeys(ImPlotColormap cmap) const { return &Keys[KeyOffsets[cmap]]; } + inline int GetKeyCount(ImPlotColormap cmap) const { return KeyCounts[cmap]; } + inline ImU32 GetKeyColor(ImPlotColormap cmap, int idx) const { return Keys[KeyOffsets[cmap]+idx]; } + inline void SetKeyColor(ImPlotColormap cmap, int idx, ImU32 value) { Keys[KeyOffsets[cmap]+idx] = value; RebuildTables(); } + + inline const ImU32* GetTable(ImPlotColormap cmap) const { return &Tables[TableOffsets[cmap]]; } + inline int GetTableSize(ImPlotColormap cmap) const { return TableSizes[cmap]; } + inline ImU32 GetTableColor(ImPlotColormap cmap, int idx) const { return Tables[TableOffsets[cmap]+idx]; } + + inline ImU32 LerpTable(ImPlotColormap cmap, float t) const { + int off = TableOffsets[cmap]; + int siz = TableSizes[cmap]; + int idx = Quals[cmap] ? ImClamp((int)(siz*t),0,siz-1) : (int)((siz - 1) * t + 0.5f); + return Tables[off + idx]; + } + }; // ImPlotPoint with positive/negative error values -struct ImPlotPointError -{ +struct ImPlotPointError { double X, Y, Neg, Pos; ImPlotPointError(double x, double y, double neg, double pos) { X = x; Y = y; Neg = neg; Pos = pos; @@ -387,7 +554,7 @@ struct ImPlotTickCollection { Append(tick); } - const char* GetText(int idx) { + const char* GetText(int idx) const { return TextBuffer.Buf.Data + Ticks[idx].TextOffset; } @@ -502,10 +669,12 @@ struct ImPlotAxis inline bool IsLabeled() const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoTickLabels); } inline bool IsInverted() const { return ImHasFlag(Flags, ImPlotAxisFlags_Invert); } - inline bool IsAlwaysLocked() const { return HasRange && RangeCond == ImGuiCond_Always; } - inline bool IsLockedMin() const { return ImHasFlag(Flags, ImPlotAxisFlags_LockMin) || IsAlwaysLocked(); } - inline bool IsLockedMax() const { return ImHasFlag(Flags, ImPlotAxisFlags_LockMax) || IsAlwaysLocked(); } - inline bool IsLocked() const { return !Present || ((IsLockedMin() && IsLockedMax()) || IsAlwaysLocked()); } + inline bool IsAutoFitting() const { return ImHasFlag(Flags, ImPlotAxisFlags_AutoFit); } + inline bool IsRangeLocked() const { return HasRange && RangeCond == ImGuiCond_Always; } + inline bool IsLockedMin() const { return ImHasFlag(Flags, ImPlotAxisFlags_LockMin) || IsRangeLocked(); } + inline bool IsLockedMax() const { return ImHasFlag(Flags, ImPlotAxisFlags_LockMax) || IsRangeLocked(); } + inline bool IsLocked() const { return !Present || ((IsLockedMin() && IsLockedMax()) || IsRangeLocked()); } + inline bool IsInputLocked() const { return IsLocked() || IsAutoFitting(); } inline bool IsTime() const { return ImHasFlag(Flags, ImPlotAxisFlags_Time); } inline bool IsLog() const { return ImHasFlag(Flags, ImPlotAxisFlags_LogScale); } }; @@ -514,7 +683,7 @@ struct ImPlotAxis struct ImPlotItem { ImGuiID ID; - ImVec4 Color; + ImU32 Color; int NameOffset; bool Show; bool LegendHovered; @@ -522,7 +691,7 @@ struct ImPlotItem ImPlotItem() { ID = 0; - Color = ImPlot::NextColormapColor(); + // Color = ImPlot::NextColormapColor(); NameOffset = -1; Show = true; SeenThisFrame = false; @@ -591,7 +760,7 @@ struct ImPlotPlot ImPlotItem* GetLegendItem(int i); const char* GetLegendLabel(int i); - inline bool IsLocked() const { return XAxis.IsLocked() && YAxis[0].IsLocked() && YAxis[1].IsLocked() && YAxis[2].IsLocked(); } + inline bool IsInputLocked() const { return XAxis.IsInputLocked() && YAxis[0].IsInputLocked() && YAxis[1].IsInputLocked() && YAxis[2].IsInputLocked(); } }; // Temporary data storage for upcoming plot @@ -701,13 +870,15 @@ struct ImPlotContext { ImPlotStyle Style; ImVector ColorModifiers; ImVector StyleModifiers; - const ImVec4* Colormap; - int ColormapSize; - ImVector ColormapModifiers; + ImPlotColormapData ColormapData; + ImVector ColormapModifiers; // Time tm Tm; + // Temp data for general use + ImVector Temp1, Temp2; + // Misc int VisibleItemCount; int DigitalPlotItemCnt; @@ -862,19 +1033,16 @@ IMPLOT_API ImVec4 GetAutoColor(ImPlotCol idx); inline ImVec4 GetStyleColorVec4(ImPlotCol idx) { return IsColorAuto(idx) ? GetAutoColor(idx) : GImPlot->Style.Colors[idx]; } inline ImU32 GetStyleColorU32(ImPlotCol idx) { return ImGui::ColorConvertFloat4ToU32(GetStyleColorVec4(idx)); } -// Get built-in colormap data and size -IMPLOT_API const ImVec4* GetColormap(ImPlotColormap colormap, int* size_out); -// Linearly interpolates a color from the current colormap given t between 0 and 1. -IMPLOT_API ImVec4 LerpColormap(const ImVec4* colormap, int size, float t); -// Resamples a colormap. #size_out must be greater than 1. -IMPLOT_API void ResampleColormap(const ImVec4* colormap_in, int size_in, ImVec4* colormap_out, int size_out); - // Draws vertical text. The position is the bottom left of the text rect. IMPLOT_API void AddTextVertical(ImDrawList *DrawList, ImVec2 pos, ImU32 col, const char* text_begin, const char* text_end = NULL); // Calculates the size of vertical text -inline ImVec2 CalcTextSizeVertical(const char *text) { ImVec2 sz = ImGui::CalcTextSize(text); return ImVec2(sz.y, sz.x); } +inline ImVec2 CalcTextSizeVertical(const char *text) { + ImVec2 sz = ImGui::CalcTextSize(text); + return ImVec2(sz.y, sz.x); +} // Returns white or black text given background color inline ImU32 CalcTextColor(const ImVec4& bg) { return (bg.x * 0.299 + bg.y * 0.587 + bg.z * 0.114) > 0.5 ? IM_COL32_BLACK : IM_COL32_WHITE; } +inline ImU32 CalcTextColor(ImU32 bg) { return CalcTextColor(ImGui::ColorConvertU32ToFloat4(bg)); } // Clamps a label position so that it fits a rect defined by Min/Max inline ImVec2 ClampLabelPos(ImVec2 pos, const ImVec2& size, const ImVec2& Min, const ImVec2& Max) { @@ -885,6 +1053,16 @@ inline ImVec2 ClampLabelPos(ImVec2 pos, const ImVec2& size, const ImVec2& Min, c return pos; } +// Returns a color from the Color map given an index >= 0 (modulo will be performed). +IMPLOT_API ImU32 GetColormapColorU32(int idx, ImPlotColormap cmap); +// Returns the next unused colormap color and advances the colormap. Can be used to skip colors if desired. +IMPLOT_API ImU32 NextColormapColorU32(); +// Linearly interpolates a color from the current colormap given t between 0 and 1. +IMPLOT_API ImU32 SampleColormapU32(float t, ImPlotColormap cmap); + +// Render a colormap bar +IMPLOT_API void RenderColorBar(const ImU32* colors, int size, ImDrawList& DrawList, const ImRect& bounds, bool vert, bool reversed, bool continuous); + //----------------------------------------------------------------------------- // [SECTION] Math and Misc Utils //----------------------------------------------------------------------------- @@ -922,6 +1100,27 @@ inline T OffsetAndStride(const T* data, int idx, int count, int offset, int stri return *(const T*)(const void*)((const unsigned char*)data + (size_t)idx * stride); } +// Calculate histogram bin counts and widths +template +void CalculateBins(const T* values, int count, ImPlotBin meth, const ImPlotRange& range, int& bins_out, double& width_out) { + switch (meth) { + case ImPlotBin_Sqrt: + bins_out = (int)ceil(sqrt(count)); + break; + case ImPlotBin_Sturges: + bins_out = (int)ceil(1.0 + log2(count)); + break; + case ImPlotBin_Rice: + bins_out = (int)ceil(2 * cbrt(count)); + break; + case ImPlotBin_Scott: + width_out = 3.49 * ImStdDev(values, count) / cbrt(count); + bins_out = (int)round(range.Size() / width_out); + break; + } + width_out = range.Size() / bins_out; +} + //----------------------------------------------------------------------------- // Time Utils //----------------------------------------------------------------------------- @@ -982,14 +1181,4 @@ IMPLOT_API bool ShowDatePicker(const char* id, int* level, ImPlotTime* t, const // #t will be set when a new hour, minute, or sec is selected or am/pm is toggled, and the function will return true. IMPLOT_API bool ShowTimePicker(const char* id, ImPlotTime* t); -//----------------------------------------------------------------------------- -// [SECTION] Internal / Experimental Plotters -// No guarantee of forward compatibility here! -//----------------------------------------------------------------------------- - -// Plots axis-aligned, filled rectangles. Every two consecutive points defines opposite corners of a single rectangle. -IMPLOT_API void PlotRects(const char* label_id, const float* xs, const float* ys, int count, int offset = 0, int stride = sizeof(float)); -IMPLOT_API void PlotRects(const char* label_id, const double* xs, const double* ys, int count, int offset = 0, int stride = sizeof(double)); -IMPLOT_API void PlotRects(const char* label_id, ImPlotPoint (*getter)(void* data, int idx), void* data, int count, int offset = 0); - } // namespace ImPlot diff --git a/implot_items.cpp b/implot_items.cpp index ad7eca5..99cd6b0 100644 --- a/implot_items.cpp +++ b/implot_items.cpp @@ -119,7 +119,7 @@ void SetNextErrorBarStyle(const ImVec4& col, float size, float weight) { ImVec4 GetLastItemColor() { ImPlotContext& gp = *GImPlot; if (gp.PreviousItem) - return gp.PreviousItem->Color; + return ImGui::ColorConvertU32ToFloat4(gp.PreviousItem->Color); return ImVec4(); } @@ -140,6 +140,21 @@ void BustItemCache() { } } +void BustColorCache(const char* plot_title_id) { + ImPlotContext& gp = *GImPlot; + if (plot_title_id == NULL) { + BustItemCache(); + } + else { + ImPlotPlot* plot = gp.Plots.GetByKey(ImGui::GetCurrentWindow()->GetID(plot_title_id)); + if (plot == NULL) + return; + plot->ColormapIdx = 0; + plot->Items.Clear(); + plot->LegendData.Reset(); + } +} + //----------------------------------------------------------------------------- // Begin/EndItem //----------------------------------------------------------------------------- @@ -153,12 +168,17 @@ bool BeginItem(const char* label_id, ImPlotCol recolor_from) { // set current item gp.CurrentItem = item; ImPlotNextItemData& s = gp.NextItemData; - // override item color + // set/override item color if (recolor_from != -1) { if (!IsColorAuto(s.Colors[recolor_from])) - item->Color = s.Colors[recolor_from]; + item->Color = ImGui::ColorConvertFloat4ToU32(s.Colors[recolor_from]); else if (!IsColorAuto(gp.Style.Colors[recolor_from])) - item->Color = gp.Style.Colors[recolor_from]; + item->Color = ImGui::ColorConvertFloat4ToU32(gp.Style.Colors[recolor_from]); + else if (just_created) + item->Color = NextColormapColorU32(); + } + else if (just_created) { + item->Color = NextColormapColorU32(); } // hide/show item if (gp.NextItemData.HasHidden) { @@ -173,9 +193,10 @@ bool BeginItem(const char* label_id, ImPlotCol recolor_from) { return false; } else { + ImVec4 item_color = ImGui::ColorConvertU32ToFloat4(item->Color); // stage next item colors - s.Colors[ImPlotCol_Line] = IsColorAuto(s.Colors[ImPlotCol_Line]) ? ( IsColorAuto(ImPlotCol_Line) ? item->Color : gp.Style.Colors[ImPlotCol_Line] ) : s.Colors[ImPlotCol_Line]; - s.Colors[ImPlotCol_Fill] = IsColorAuto(s.Colors[ImPlotCol_Fill]) ? ( IsColorAuto(ImPlotCol_Fill) ? item->Color : gp.Style.Colors[ImPlotCol_Fill] ) : s.Colors[ImPlotCol_Fill]; + s.Colors[ImPlotCol_Line] = IsColorAuto(s.Colors[ImPlotCol_Line]) ? ( IsColorAuto(ImPlotCol_Line) ? item_color : gp.Style.Colors[ImPlotCol_Line] ) : s.Colors[ImPlotCol_Line]; + s.Colors[ImPlotCol_Fill] = IsColorAuto(s.Colors[ImPlotCol_Fill]) ? ( IsColorAuto(ImPlotCol_Fill) ? item_color : gp.Style.Colors[ImPlotCol_Fill] ) : s.Colors[ImPlotCol_Fill]; s.Colors[ImPlotCol_MarkerOutline] = IsColorAuto(s.Colors[ImPlotCol_MarkerOutline]) ? ( IsColorAuto(ImPlotCol_MarkerOutline) ? s.Colors[ImPlotCol_Line] : gp.Style.Colors[ImPlotCol_MarkerOutline] ) : s.Colors[ImPlotCol_MarkerOutline]; s.Colors[ImPlotCol_MarkerFill] = IsColorAuto(s.Colors[ImPlotCol_MarkerFill]) ? ( IsColorAuto(ImPlotCol_MarkerFill) ? s.Colors[ImPlotCol_Line] : gp.Style.Colors[ImPlotCol_MarkerFill] ) : s.Colors[ImPlotCol_MarkerFill]; s.Colors[ImPlotCol_ErrorBar] = IsColorAuto(s.Colors[ImPlotCol_ErrorBar]) ? ( GetStyleColorVec4(ImPlotCol_ErrorBar) ) : s.Colors[ImPlotCol_ErrorBar]; @@ -659,51 +680,6 @@ struct ShadedRenderer { static const int VtxConsumed = 5; }; -template -struct RectRenderer { - inline RectRenderer(const TGetter& getter, const TTransformer& transformer, ImU32 col) : - Getter(getter), - Transformer(transformer), - Prims(Getter.Count / 2), - Col(col) - {} - inline bool operator()(ImDrawList& DrawList, const ImRect& /*cull_rect*/, const ImVec2& uv, int prim) const { - // TODO: Culling - ImVec2 P1 = Transformer(Getter(2*prim)); - ImVec2 P2 = Transformer(Getter(2*prim+1)); - DrawList._VtxWritePtr[0].pos = P1; - DrawList._VtxWritePtr[0].uv = uv; - DrawList._VtxWritePtr[0].col = Col; - DrawList._VtxWritePtr[1].pos.x = P1.x; - DrawList._VtxWritePtr[1].pos.y = P2.y; - DrawList._VtxWritePtr[1].uv = uv; - DrawList._VtxWritePtr[1].col = Col; - DrawList._VtxWritePtr[2].pos = P2; - DrawList._VtxWritePtr[2].uv = uv; - DrawList._VtxWritePtr[2].col = Col; - DrawList._VtxWritePtr[3].pos.x = P2.x; - DrawList._VtxWritePtr[3].pos.y = P1.y; - DrawList._VtxWritePtr[3].uv = uv; - DrawList._VtxWritePtr[3].col = Col; - DrawList._VtxWritePtr += 4; - DrawList._IdxWritePtr[0] = (ImDrawIdx)(DrawList._VtxCurrentIdx); - DrawList._IdxWritePtr[1] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 1); - DrawList._IdxWritePtr[2] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 3); - DrawList._IdxWritePtr[3] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 1); - DrawList._IdxWritePtr[4] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 2); - DrawList._IdxWritePtr[5] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 3); - DrawList._IdxWritePtr += 6; - DrawList._VtxCurrentIdx += 4; - return true; - } - const TGetter& Getter; - const TTransformer& Transformer; - const int Prims; - const ImU32 Col; - static const int IdxConsumed = 6; - static const int VtxConsumed = 4; -}; - // Stupid way of calculating maximum index size of ImDrawIdx without integer overflow issues template struct MaxIdx { static const unsigned int Value; }; @@ -1462,6 +1438,7 @@ void PlotErrorBars(const char* label_id, const T* xs, const T* ys, const T* neg, GetterError getter(xs, ys, neg, pos, count, offset, stride); PlotErrorBarsEx(label_id, getter); } + template IMPLOT_API void PlotErrorBars(const char* label_id, const ImS8* xs, const ImS8* ys, const ImS8* neg, const ImS8* pos, int count, int offset, int stride); template IMPLOT_API void PlotErrorBars(const char* label_id, const ImU8* xs, const ImU8* ys, const ImU8* neg, const ImU8* pos, int count, int offset, int stride); template IMPLOT_API void PlotErrorBars(const char* label_id, const ImS16* xs, const ImS16* ys, const ImS16* neg, const ImS16* pos, int count, int offset, int stride); @@ -1729,7 +1706,7 @@ void PlotPieChart(const char* const label_ids[], const T* values, int count, dou double percent = normalize ? (double)values[i] / sum : (double)values[i]; a1 = a0 + 2 * IM_PI * percent; if (BeginItem(label_ids[i])) { - ImU32 col = ImGui::GetColorU32(GetCurrentItem()->Color); + ImU32 col = GetCurrentItem()->Color; if (percent < 0.5) { RenderPieSlice(DrawList, center, radius, a0, a1, col); } @@ -1754,7 +1731,7 @@ void PlotPieChart(const char* const label_ids[], const T* values, int count, dou ImVec2 size = ImGui::CalcTextSize(buffer); double angle = a0 + (a1 - a0) * 0.5; ImVec2 pos = PlotToPixels(center.x + 0.5 * radius * cos(angle), center.y + 0.5 * radius * sin(angle)); - ImU32 col = CalcTextColor(item->Color); + ImU32 col = CalcTextColor(ImGui::ColorConvertU32ToFloat4(item->Color)); DrawList.AddText(pos - size * 0.5f, col, buffer); } a0 = a1; @@ -1778,41 +1755,130 @@ template IMPLOT_API void PlotPieChart(const char* const label_ids[], con // PLOT HEATMAP //----------------------------------------------------------------------------- +struct RectInfo { + ImPlotPoint Min, Max; + ImU32 Color; +}; + +template +struct RectRenderer { + inline RectRenderer(const TGetter& getter, const TTransformer& transformer) : + Getter(getter), + Transformer(transformer), + Prims(Getter.Count) + {} + inline bool operator()(ImDrawList& DrawList, const ImRect& cull_rect, const ImVec2& uv, int prim) const { + RectInfo rect = Getter(prim); + ImVec2 P1 = Transformer(rect.Min); + ImVec2 P2 = Transformer(rect.Max); + + if ((rect.Color & IM_COL32_A_MASK) == 0 || !cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) + return false; + + DrawList._VtxWritePtr[0].pos = P1; + DrawList._VtxWritePtr[0].uv = uv; + DrawList._VtxWritePtr[0].col = rect.Color; + DrawList._VtxWritePtr[1].pos.x = P1.x; + DrawList._VtxWritePtr[1].pos.y = P2.y; + DrawList._VtxWritePtr[1].uv = uv; + DrawList._VtxWritePtr[1].col = rect.Color; + DrawList._VtxWritePtr[2].pos = P2; + DrawList._VtxWritePtr[2].uv = uv; + DrawList._VtxWritePtr[2].col = rect.Color; + DrawList._VtxWritePtr[3].pos.x = P2.x; + DrawList._VtxWritePtr[3].pos.y = P1.y; + DrawList._VtxWritePtr[3].uv = uv; + DrawList._VtxWritePtr[3].col = rect.Color; + DrawList._VtxWritePtr += 4; + DrawList._IdxWritePtr[0] = (ImDrawIdx)(DrawList._VtxCurrentIdx); + DrawList._IdxWritePtr[1] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 1); + DrawList._IdxWritePtr[2] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 3); + DrawList._IdxWritePtr[3] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 1); + DrawList._IdxWritePtr[4] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 2); + DrawList._IdxWritePtr[5] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 3); + DrawList._IdxWritePtr += 6; + DrawList._VtxCurrentIdx += 4; + return true; + } + const TGetter& Getter; + const TTransformer& Transformer; + const int Prims; + static const int IdxConsumed = 6; + static const int VtxConsumed = 4; +}; + +template +struct GetterHeatmap { + GetterHeatmap(const T* values, int rows, int cols, double scale_min, double scale_max, double width, double height, double xref, double yref, double ydir) : + Values(values), + Count(rows*cols), + Rows(rows), + Cols(cols), + ScaleMin(scale_min), + ScaleMax(scale_max), + Width(width), + Height(height), + XRef(xref), + YRef(yref), + YDir(ydir), + HalfSize(Width*0.5, Height*0.5) + { } + + inline RectInfo operator()(int idx) const { + double val = (double)Values[idx]; + const int r = idx / Cols; + const int c = idx % Cols; + const ImPlotPoint p(XRef + HalfSize.x + c*Width, YRef + YDir * (HalfSize.y + r*Height)); + RectInfo rect; + rect.Min.x = p.x - HalfSize.x; + rect.Min.y = p.y - HalfSize.y; + rect.Max.x = p.x + HalfSize.x; + rect.Max.y = p.y + HalfSize.y; + const float t = ImClamp((float)ImRemap01(val, ScaleMin, ScaleMax),0.0f,1.0f); + rect.Color = GImPlot->ColormapData.LerpTable(GImPlot->Style.Colormap, t); + return rect; + } + const T* const Values; + const int Count, Rows, Cols; + const double ScaleMin, ScaleMax, Width, Height, XRef, YRef, YDir; + const ImPlotPoint HalfSize; +}; + template -void RenderHeatmap(Transformer transformer, ImDrawList& DrawList, const T* values, int rows, int cols, double scale_min, double scale_max, const char* fmt, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max) { +void RenderHeatmap(Transformer transformer, ImDrawList& DrawList, const T* values, int rows, int cols, double scale_min, double scale_max, const char* fmt, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max, bool reverse_y) { ImPlotContext& gp = *GImPlot; - const double w = (bounds_max.x - bounds_min.x) / cols; - const double h = (bounds_max.y - bounds_min.y) / rows; - const ImPlotPoint half_size(w*0.5,h*0.5); - int i = 0; - for (int r = 0; r < rows; ++r) { - for (int c = 0; c < cols; ++c) { - ImPlotPoint p; - p.x = bounds_min.x + 0.5*w + c*w; - p.y = bounds_max.y - (0.5*h + r*h); - ImVec2 a = transformer(ImPlotPoint(p.x - half_size.x, p.y - half_size.y)); - ImVec2 b = transformer(ImPlotPoint(p.x + half_size.x, p.y + half_size.y)); - double t = ImRemap((double)values[i], scale_min, scale_max, 0.0, 1.0); - ImVec4 color = LerpColormap((float)t); - color.w *= gp.Style.FillAlpha; - ImU32 col = ImGui::GetColorU32(color); - DrawList.AddRectFilled(a, b, col); - i++; - } + if (scale_min == scale_max) { + ImVec2 a = transformer(bounds_min); + ImVec2 b = transformer(bounds_max); + ImU32 col = GetColormapColorU32(0,gp.Style.Colormap); + DrawList.AddRectFilled(a, b, col); + return; + } + const double yref = reverse_y ? bounds_max.y : bounds_min.y; + const double ydir = reverse_y ? -1 : 1; + GetterHeatmap getter(values, rows, cols, scale_min, scale_max, (bounds_max.x - bounds_min.x) / cols, (bounds_max.y - bounds_min.y) / rows, bounds_min.x, yref, ydir); + switch (GetCurrentScale()) { + case ImPlotScale_LinLin: RenderPrimitives(RectRenderer, TransformerLinLin>(getter, TransformerLinLin()), DrawList, gp.CurrentPlot->PlotRect); break; + case ImPlotScale_LogLin: RenderPrimitives(RectRenderer, TransformerLogLin>(getter, TransformerLogLin()), DrawList, gp.CurrentPlot->PlotRect); break;; + case ImPlotScale_LinLog: RenderPrimitives(RectRenderer, TransformerLinLog>(getter, TransformerLinLog()), DrawList, gp.CurrentPlot->PlotRect); break;; + case ImPlotScale_LogLog: RenderPrimitives(RectRenderer, TransformerLogLog>(getter, TransformerLogLog()), DrawList, gp.CurrentPlot->PlotRect); break;; } if (fmt != NULL) { - i = 0; + const double w = (bounds_max.x - bounds_min.x) / cols; + const double h = (bounds_max.y - bounds_min.y) / rows; + const ImPlotPoint half_size(w*0.5,h*0.5); + int i = 0; for (int r = 0; r < rows; ++r) { for (int c = 0; c < cols; ++c) { ImPlotPoint p; p.x = bounds_min.x + 0.5*w + c*w; - p.y = bounds_max.y - (0.5*h + r*h); + p.y = yref + ydir * (0.5*h + r*h); ImVec2 px = transformer(p); char buff[32]; sprintf(buff, fmt, values[i]); ImVec2 size = ImGui::CalcTextSize(buff); - double t = ImRemap((double)values[i], scale_min, scale_max, 0.0, 1.0); - ImVec4 color = LerpColormap((float)t); + double t = ImClamp(ImRemap01((double)values[i], scale_min, scale_max),0.0,1.0); + ImVec4 color = SampleColormap((float)t); ImU32 col = CalcTextColor(color); DrawList.AddText(px - size * 0.5f, col, buff); i++; @@ -1823,7 +1889,6 @@ void RenderHeatmap(Transformer transformer, ImDrawList& DrawList, const T* value template void PlotHeatmap(const char* label_id, const T* values, int rows, int cols, double scale_min, double scale_max, const char* fmt, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max) { - IM_ASSERT_USER_ERROR(scale_min != scale_max, "Scale values must be different!"); if (BeginItem(label_id)) { if (FitThisFrame()) { FitPoint(bounds_min); @@ -1831,10 +1896,10 @@ void PlotHeatmap(const char* label_id, const T* values, int rows, int cols, doub } ImDrawList& DrawList = *GetPlotDrawList(); switch (GetCurrentScale()) { - case ImPlotScale_LinLin: RenderHeatmap(TransformerLinLin(), DrawList, values, rows, cols, scale_min, scale_max, fmt, bounds_min, bounds_max); break; - case ImPlotScale_LogLin: RenderHeatmap(TransformerLogLin(), DrawList, values, rows, cols, scale_min, scale_max, fmt, bounds_min, bounds_max); break; - case ImPlotScale_LinLog: RenderHeatmap(TransformerLinLog(), DrawList, values, rows, cols, scale_min, scale_max, fmt, bounds_min, bounds_max); break; - case ImPlotScale_LogLog: RenderHeatmap(TransformerLogLog(), DrawList, values, rows, cols, scale_min, scale_max, fmt, bounds_min, bounds_max); break; + case ImPlotScale_LinLin: RenderHeatmap(TransformerLinLin(), DrawList, values, rows, cols, scale_min, scale_max, fmt, bounds_min, bounds_max, true); break; + case ImPlotScale_LogLin: RenderHeatmap(TransformerLogLin(), DrawList, values, rows, cols, scale_min, scale_max, fmt, bounds_min, bounds_max, true); break; + case ImPlotScale_LinLog: RenderHeatmap(TransformerLinLog(), DrawList, values, rows, cols, scale_min, scale_max, fmt, bounds_min, bounds_max, true); break; + case ImPlotScale_LogLog: RenderHeatmap(TransformerLogLog(), DrawList, values, rows, cols, scale_min, scale_max, fmt, bounds_min, bounds_max, true); break; } EndItem(); } @@ -1851,6 +1916,181 @@ template IMPLOT_API void PlotHeatmap(const char* label_id, const ImU64* v template IMPLOT_API void PlotHeatmap(const char* label_id, const float* values, int rows, int cols, double scale_min, double scale_max, const char* fmt, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max); template IMPLOT_API void PlotHeatmap(const char* label_id, const double* values, int rows, int cols, double scale_min, double scale_max, const char* fmt, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max); +//----------------------------------------------------------------------------- +// PLOT HISTOGRAM +//----------------------------------------------------------------------------- + +template +double PlotHistogram(const char* label_id, const T* values, int count, int bins, bool cumulative, bool density, ImPlotRange range, bool outliers, double bar_scale) { + + if (count <= 0 || bins == 0) + return 0; + + if (range.Min == 0 && range.Max == 0) { + T Min, Max; + ImMinMaxArray(values, count, &Min, &Max); + range.Min = (double)Min; + range.Max = (double)Max; + } + + double width; + if (bins < 0) + CalculateBins(values, count, bins, range, bins, width); + else + width = range.Size() / bins; + + ImVector& bin_centers = GImPlot->Temp1; + ImVector& bin_counts = GImPlot->Temp2; + bin_centers.resize(bins); + bin_counts.resize(bins); + int below = 0; + + for (int b = 0; b < bins; ++b) { + bin_centers[b] = range.Min + b * width + width * 0.5; + bin_counts[b] = 0; + } + int counted = 0; + double max_count = 0; + for (int i = 0; i < count; ++i) { + double val = (double)values[i]; + if (range.Contains(val)) { + const int b = ImClamp((int)((val - range.Min) / width), 0, bins - 1); + bin_counts[b] += 1.0; + if (bin_counts[b] > max_count) + max_count = bin_counts[b]; + counted++; + } + else if (val < range.Min) { + below++; + } + } + if (cumulative && density) { + if (outliers) + bin_counts[0] += below; + for (int b = 1; b < bins; ++b) + bin_counts[b] += bin_counts[b-1]; + double scale = 1.0 / (outliers ? count : counted); + for (int b = 0; b < bins; ++b) + bin_counts[b] *= scale; + max_count = bin_counts[bins-1]; + } + else if (cumulative) { + if (outliers) + bin_counts[0] += below; + for (int b = 1; b < bins; ++b) + bin_counts[b] += bin_counts[b-1]; + max_count = bin_counts[bins-1]; + } + else if (density) { + double scale = 1.0 / ((outliers ? count : counted) * width); + for (int b = 0; b < bins; ++b) + bin_counts[b] *= scale; + max_count *= scale; + } + PlotBars(label_id, &bin_centers.Data[0], &bin_counts.Data[0], bins, bar_scale*width); + return max_count; +} + +template IMPLOT_API double PlotHistogram(const char* label_id, const ImS8* values, int count, int bins, bool cumulative, bool density, ImPlotRange range, bool outliers, double bar_scale); +template IMPLOT_API double PlotHistogram(const char* label_id, const ImU8* values, int count, int bins, bool cumulative, bool density, ImPlotRange range, bool outliers, double bar_scale); +template IMPLOT_API double PlotHistogram(const char* label_id, const ImS16* values, int count, int bins, bool cumulative, bool density, ImPlotRange range, bool outliers, double bar_scale); +template IMPLOT_API double PlotHistogram(const char* label_id, const ImU16* values, int count, int bins, bool cumulative, bool density, ImPlotRange range, bool outliers, double bar_scale); +template IMPLOT_API double PlotHistogram(const char* label_id, const ImS32* values, int count, int bins, bool cumulative, bool density, ImPlotRange range, bool outliers, double bar_scale); +template IMPLOT_API double PlotHistogram(const char* label_id, const ImU32* values, int count, int bins, bool cumulative, bool density, ImPlotRange range, bool outliers, double bar_scale); +template IMPLOT_API double PlotHistogram(const char* label_id, const ImS64* values, int count, int bins, bool cumulative, bool density, ImPlotRange range, bool outliers, double bar_scale); +template IMPLOT_API double PlotHistogram(const char* label_id, const ImU64* values, int count, int bins, bool cumulative, bool density, ImPlotRange range, bool outliers, double bar_scale); +template IMPLOT_API double PlotHistogram(const char* label_id, const float* values, int count, int bins, bool cumulative, bool density, ImPlotRange range, bool outliers, double bar_scale); +template IMPLOT_API double PlotHistogram(const char* label_id, const double* values, int count, int bins, bool cumulative, bool density, ImPlotRange range, bool outliers, double bar_scale); + +//----------------------------------------------------------------------------- +// PLOT HISTOGRAM 2D +//----------------------------------------------------------------------------- + +template +double PlotHistogram2D(const char* label_id, const T* xs, const T* ys, int count, int x_bins, int y_bins, bool density, ImPlotLimits range, bool outliers) { + + if (count <= 0 || x_bins == 0 || y_bins == 0) + return 0; + + if (range.X.Min == 0 && range.X.Max == 0) { + T Min, Max; + ImMinMaxArray(xs, count, &Min, &Max); + range.X.Min = (double)Min; + range.X.Max = (double)Max; + } + if (range.Y.Min == 0 && range.Y.Max == 0) { + T Min, Max; + ImMinMaxArray(ys, count, &Min, &Max); + range.Y.Min = (double)Min; + range.Y.Max = (double)Max; + } + + double width, height; + if (x_bins < 0) + CalculateBins(xs, count, x_bins, range.X, x_bins, width); + else + width = range.X.Size() / x_bins; + if (y_bins < 0) + CalculateBins(ys, count, y_bins, range.Y, y_bins, height); + else + height = range.Y.Size() / y_bins; + + const int bins = x_bins * y_bins; + + ImVector& bin_counts = GImPlot->Temp1; + bin_counts.resize(bins); + + for (int b = 0; b < bins; ++b) + bin_counts[b] = 0; + + int counted = 0; + double max_count = 0; + for (int i = 0; i < count; ++i) { + if (range.Contains((double)xs[i], (double)ys[i])) { + const int xb = ImClamp( (int)((double)(xs[i] - range.X.Min) / width) , 0, x_bins - 1); + const int yb = ImClamp( (int)((double)(ys[i] - range.Y.Min) / height) , 0, y_bins - 1); + const int b = yb * x_bins + xb; + bin_counts[b] += 1.0; + if (bin_counts[b] > max_count) + max_count = bin_counts[b]; + counted++; + } + } + if (density) { + double scale = 1.0 / ((outliers ? count : counted) * width * height); + for (int b = 0; b < bins; ++b) + bin_counts[b] *= scale; + max_count *= scale; + } + + if (BeginItem(label_id)) { + if (FitThisFrame()) { + FitPoint(range.Min()); + FitPoint(range.Max()); + } + ImDrawList& DrawList = *GetPlotDrawList(); + switch (GetCurrentScale()) { + case ImPlotScale_LinLin: RenderHeatmap(TransformerLinLin(), DrawList, &bin_counts.Data[0], y_bins, x_bins, 0, max_count, NULL, range.Min(), range.Max(), false); break; + case ImPlotScale_LogLin: RenderHeatmap(TransformerLogLin(), DrawList, &bin_counts.Data[0], y_bins, x_bins, 0, max_count, NULL, range.Min(), range.Max(), false); break; + case ImPlotScale_LinLog: RenderHeatmap(TransformerLinLog(), DrawList, &bin_counts.Data[0], y_bins, x_bins, 0, max_count, NULL, range.Min(), range.Max(), false); break; + case ImPlotScale_LogLog: RenderHeatmap(TransformerLogLog(), DrawList, &bin_counts.Data[0], y_bins, x_bins, 0, max_count, NULL, range.Min(), range.Max(), false); break; + } + EndItem(); + } + return max_count; +} + +template IMPLOT_API double PlotHistogram2D(const char* label_id, const ImS8* xs, const ImS8* ys, int count, int x_bins, int y_bins, bool density, ImPlotLimits range, bool outliers); +template IMPLOT_API double PlotHistogram2D(const char* label_id, const ImU8* xs, const ImU8* ys, int count, int x_bins, int y_bins, bool density, ImPlotLimits range, bool outliers); +template IMPLOT_API double PlotHistogram2D(const char* label_id, const ImS16* xs, const ImS16* ys, int count, int x_bins, int y_bins, bool density, ImPlotLimits range, bool outliers); +template IMPLOT_API double PlotHistogram2D(const char* label_id, const ImU16* xs, const ImU16* ys, int count, int x_bins, int y_bins, bool density, ImPlotLimits range, bool outliers); +template IMPLOT_API double PlotHistogram2D(const char* label_id, const ImS32* xs, const ImS32* ys, int count, int x_bins, int y_bins, bool density, ImPlotLimits range, bool outliers); +template IMPLOT_API double PlotHistogram2D(const char* label_id, const ImU32* xs, const ImU32* ys, int count, int x_bins, int y_bins, bool density, ImPlotLimits range, bool outliers); +template IMPLOT_API double PlotHistogram2D(const char* label_id, const ImS64* xs, const ImS64* ys, int count, int x_bins, int y_bins, bool density, ImPlotLimits range, bool outliers); +template IMPLOT_API double PlotHistogram2D(const char* label_id, const ImU64* xs, const ImU64* ys, int count, int x_bins, int y_bins, bool density, ImPlotLimits range, bool outliers); +template IMPLOT_API double PlotHistogram2D(const char* label_id, const float* xs, const float* ys, int count, int x_bins, int y_bins, bool density, ImPlotLimits range, bool outliers); +template IMPLOT_API double PlotHistogram2D(const char* label_id, const double* xs, const double* ys, int count, int x_bins, int y_bins, bool density, ImPlotLimits range, bool outliers); + //----------------------------------------------------------------------------- // PLOT DIGITAL //----------------------------------------------------------------------------- @@ -1937,51 +2177,6 @@ void PlotDigitalG(const char* label_id, ImPlotPoint (*getter_func)(void* data, i return PlotDigitalEx(label_id, getter); } -//----------------------------------------------------------------------------- -// PLOT RECTS -//----------------------------------------------------------------------------- -template -void PlotRectsEx(const char* label_id, const Getter& getter) { - if (BeginItem(label_id, ImPlotCol_Fill)) { - if (FitThisFrame()) { - for (int i = 0; i < getter.Count; ++i) { - ImPlotPoint p = getter(i); - FitPoint(p); - } - } - const ImPlotNextItemData& s = GetItemData(); - if (s.RenderFill) { - ImDrawList& DrawList = *GetPlotDrawList(); - ImU32 col = ImGui::GetColorU32(s.Colors[ImPlotCol_Fill]); - switch (GetCurrentScale()) { - case ImPlotScale_LinLin: RenderPrimitives(RectRenderer(getter, TransformerLinLin(), col), DrawList, GImPlot->CurrentPlot->PlotRect); break; - case ImPlotScale_LogLin: RenderPrimitives(RectRenderer(getter, TransformerLogLin(), col), DrawList, GImPlot->CurrentPlot->PlotRect); break; - case ImPlotScale_LinLog: RenderPrimitives(RectRenderer(getter, TransformerLinLog(), col), DrawList, GImPlot->CurrentPlot->PlotRect); break; - case ImPlotScale_LogLog: RenderPrimitives(RectRenderer(getter, TransformerLogLog(), col), DrawList, GImPlot->CurrentPlot->PlotRect); break; - } - } - EndItem(); - } -} - -// float -void PlotRects(const char* label_id, const float* xs, const float* ys, int count, int offset, int stride) { - GetterXsYs getter(xs,ys,count,offset,stride); - PlotRectsEx(label_id, getter); -} - -// double -void PlotRects(const char* label_id, const double* xs, const double* ys, int count, int offset, int stride) { - GetterXsYs getter(xs,ys,count,offset,stride); - PlotRectsEx(label_id, getter); -} - -// custom -void PlotRects(const char* label_id, ImPlotPoint (*getter_func)(void* data, int idx), void* data, int count, int offset) { - GetterFuncPtr getter(getter_func,data,count,offset); - return PlotRectsEx(label_id, getter); -} - //----------------------------------------------------------------------------- // PLOT IMAGE //----------------------------------------------------------------------------- @@ -1992,12 +2187,13 @@ void PlotImage(const char* label_id, ImTextureID user_texture_id, const ImPlotPo FitPoint(bmin); FitPoint(bmax); } - GetCurrentItem()->Color = tint_col; + ImU32 tint_col32 = ImGui::ColorConvertFloat4ToU32(tint_col); + GetCurrentItem()->Color = tint_col32; ImDrawList& DrawList = *GetPlotDrawList(); ImVec2 p1 = PlotToPixels(bmin.x, bmax.y); ImVec2 p2 = PlotToPixels(bmax.x, bmin.y); PushPlotClipRect(); - DrawList.AddImage(user_texture_id, p1, p2, uv0, uv1, ImGui::ColorConvertFloat4ToU32(tint_col)); + DrawList.AddImage(user_texture_id, p1, p2, uv0, uv1, tint_col32); PopPlotClipRect(); EndItem(); }