From d515493aef2412a76fcc22a254907b686df49e13 Mon Sep 17 00:00:00 2001 From: Evan Pezent Date: Mon, 27 Apr 2020 23:57:49 -0500 Subject: [PATCH] add plot queries, improve box selection controls with modifiers --- implot.cpp | 148 ++++++++++++++++++++++++++++++++++++++++-------- implot.h | 41 ++++++++++---- implot_demo.cpp | 43 ++++++++++++-- 3 files changed, 191 insertions(+), 41 deletions(-) diff --git a/implot.cpp b/implot.cpp index c0498e3..bacf217 100644 --- a/implot.cpp +++ b/implot.cpp @@ -61,6 +61,15 @@ ImPlotStyle::ImPlotStyle() { Colors[ImPlotCol_XAxis] = IM_COL_AUTO; Colors[ImPlotCol_YAxis] = IM_COL_AUTO; Colors[ImPlotCol_Selection] = ImVec4(1,1,0,1); + Colors[ImPlotCol_Query] = ImVec4(0,1,0,1); +} + +ImPlotRange::ImPlotRange() { + XMin = XMax = YMin = YMax = NAN; +} + +bool ImPlotRange::Contains(const ImVec2& p) { + return p.x >= XMin && p.x <= XMax && p.y >= YMin && p.y <= YMax; } namespace ImGui { @@ -212,6 +221,7 @@ struct ImPlotAxis { struct ImPlot { ImPlot() { Selecting = false; + Querying = false; SelectStart = {0,0}; Flags = ImPlotFlags_Default; ColorIdx = 0; @@ -220,6 +230,7 @@ struct ImPlot { ImRect BB_Legend; bool Selecting; + bool Querying; ImVec2 SelectStart; ImPlotAxis XAxis; ImPlotAxis YAxis; @@ -243,15 +254,17 @@ struct ImNextPlotData { struct ImPlotContext { ImPlotContext() { CurrentPlot = NULL; + PreviousPlot = NULL; RestorePlotPalette(); } /// ALl Plots ImPool Plots; /// Current Plot ImPlot* CurrentPlot; + /// Previous Plot + ImPlot* PreviousPlot; // Legend - ImVector _LegendIndices; ImGuiTextBuffer _LegendLabels; @@ -296,6 +309,7 @@ struct ImPlotContext { ImU32 Col_Frame, Col_Bg, Col_Border, Col_Txt, Col_TxtDis, Col_SlctBg, Col_SlctBd, + Col_QryBg, Col_QryBd, Col_XMajor, Col_XMinor, Col_XTxt, Col_YMajor, Col_YMinor, Col_YTxt; @@ -569,6 +583,8 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons gp.Col_TxtDis = GetColorU32(ImGuiCol_TextDisabled); gp.Col_SlctBg = GetColorU32(gp.Style.Colors[ImPlotCol_Selection] * ImVec4(1,1,1,0.25f)); gp.Col_SlctBd = GetColorU32(gp.Style.Colors[ImPlotCol_Selection]); + gp.Col_QryBg = GetColorU32(gp.Style.Colors[ImPlotCol_Query] * ImVec4(1,1,1,0.25f)); + gp.Col_QryBd = GetColorU32(gp.Style.Colors[ImPlotCol_Query]); // BB AND HOVER ----------------------------------------------------------- @@ -702,37 +718,59 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // BOX-SELECTION ---------------------------------------------------------- // confirm selection - if (plot.Selecting && (IO.MouseReleased[1] || !IO.MouseDown[1])) { - if (HasFlag(plot.Flags, ImPlotFlags_Selection)) { + if ((plot.Selecting || plot.Querying) && IO.MouseReleased[1]) { + if (plot.Selecting && (plot.Flags, ImPlotFlags_Selection)) { gp.UpdateTransforms(); ImVec2 select_size = plot.SelectStart - IO.MousePos; if (ImFabs(select_size.x) > 2 && ImFabs(select_size.y) > 2) { ImVec2 p1 = gp.FromPixels(plot.SelectStart); ImVec2 p2 = gp.FromPixels(IO.MousePos); - if (!lock_x_min) + if (!lock_x_min && !IO.KeyAlt) plot.XAxis.Min = ImMin(p1.x, p2.x); - if (!lock_x_max) + if (!lock_x_max && !IO.KeyAlt) plot.XAxis.Max = ImMax(p1.x, p2.x); - if (!lock_y_min) + if (!lock_y_min && !IO.KeyShift) plot.YAxis.Min = ImMin(p1.y, p2.y); - if (!lock_y_max) + if (!lock_y_max && !IO.KeyShift) plot.YAxis.Max = ImMax(p1.y, p2.y); } } plot.Selecting = false; + plot.Querying = false; + } + if (plot.Querying && IO.MouseReleased[2]) { + plot.Querying = false; } // bad selection if (plot.Selecting && (!HasFlag(plot.Flags, ImPlotFlags_Selection) || lock_plot) && ImLengthSqr(plot.SelectStart - IO.MousePos) > 4) { ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed); } - // cancel selection - if (plot.Selecting && (IO.MouseClicked[0] || IO.MouseDown[0])) { + // toggle between select/query + if (plot.Selecting && IO.KeyCtrl) { plot.Selecting = false; + plot.Querying = true; } - // begin selection + if (plot.Querying && !IO.KeyCtrl && !IO.MouseDown[2]) { + plot.Selecting = true; + plot.Querying = false; + } + // cancel selection + if ((plot.Selecting || plot.Querying) && (IO.MouseClicked[0] || IO.MouseDown[0])) { + plot.Selecting = false; + plot.Querying = false; + } + // begin selection or query if (gp.Hov_Frame && gp.Hov_Grid && IO.MouseClicked[1]) { plot.SelectStart = IO.MousePos; - plot.Selecting = true; + if (IO.KeyCtrl) + plot.Querying = true; + else + plot.Selecting = true; + } + // begin query + if (gp.Hov_Frame && gp.Hov_Grid && IO.MouseClicked[2]) { + plot.SelectStart = IO.MousePos; + plot.Querying = true; } // DOUBLE CLICK ----------------------------------------------------------- @@ -1025,26 +1063,50 @@ void EndPlot() { DrawList.AddLine({gp.BB_Grid.Min.x, yt.PixelPos}, {gp.BB_Grid.Min.x + (yt.Major ? 10.0f : 5.0f), yt.PixelPos}, gp.Col_Border, 1); } - // render selection - if (HasFlag(plot.Flags, ImPlotFlags_Selection) && plot.Selecting && !lock_plot) { + // render selection/query + if (plot.Selecting || plot.Querying) { ImRect select_bb(ImMin(IO.MousePos, plot.SelectStart), ImMax(IO.MousePos, plot.SelectStart)); - if (lock_x && select_bb.GetHeight() > 2) { - DrawList.AddRectFilled(ImVec2(gp.BB_Grid.Min.x, select_bb.Min.y), ImVec2(gp.BB_Grid.Max.x, select_bb.Max.y), gp.Col_SlctBg); - DrawList.AddRect( ImVec2(gp.BB_Grid.Min.x, select_bb.Min.y), ImVec2(gp.BB_Grid.Max.x, select_bb.Max.y), gp.Col_SlctBd); + if (plot.Selecting && !lock_plot && HasFlag(plot.Flags, ImPlotFlags_Selection)) { + if (IO.KeyAlt && IO.KeyShift && select_bb.GetWidth() > 2 && select_bb.GetHeight() > 2) { + DrawList.AddRectFilled(gp.BB_Grid.Min, gp.BB_Grid.Max, gp.Col_SlctBg); + DrawList.AddRect( gp.BB_Grid.Min, gp.BB_Grid.Max, gp.Col_SlctBd); + } + else if ((lock_x || IO.KeyAlt) && select_bb.GetHeight() > 2) { + DrawList.AddRectFilled(ImVec2(gp.BB_Grid.Min.x, select_bb.Min.y), ImVec2(gp.BB_Grid.Max.x, select_bb.Max.y), gp.Col_SlctBg); + DrawList.AddRect( ImVec2(gp.BB_Grid.Min.x, select_bb.Min.y), ImVec2(gp.BB_Grid.Max.x, select_bb.Max.y), gp.Col_SlctBd); + } + else if ((lock_y || IO.KeyShift) && select_bb.GetWidth() > 2) { + DrawList.AddRectFilled(ImVec2(select_bb.Min.x, gp.BB_Grid.Min.y), ImVec2(select_bb.Max.x, gp.BB_Grid.Max.y), gp.Col_SlctBg); + DrawList.AddRect( ImVec2(select_bb.Min.x, gp.BB_Grid.Min.y), ImVec2(select_bb.Max.x, gp.BB_Grid.Max.y), gp.Col_SlctBd); + } + else if (select_bb.GetWidth() > 2 && select_bb.GetHeight() > 2) { + DrawList.AddRectFilled(select_bb.Min, select_bb.Max, gp.Col_SlctBg); + DrawList.AddRect( select_bb.Min, select_bb.Max, gp.Col_SlctBd); + } } - else if (lock_y && select_bb.GetWidth() > 2) { - DrawList.AddRectFilled(ImVec2(select_bb.Min.x, gp.BB_Grid.Min.y), ImVec2(select_bb.Max.x, gp.BB_Grid.Max.y), gp.Col_SlctBg); - DrawList.AddRect( ImVec2(select_bb.Min.x, gp.BB_Grid.Min.y), ImVec2(select_bb.Max.x, gp.BB_Grid.Max.y), gp.Col_SlctBd); - } - else if (select_bb.GetWidth() > 2 && select_bb.GetHeight() > 2) { - DrawList.AddRectFilled(select_bb.Min, select_bb.Max, gp.Col_SlctBg); - DrawList.AddRect( select_bb.Min, select_bb.Max, gp.Col_SlctBd); + else if (plot.Querying && select_bb.GetWidth() > 2 && select_bb.GetHeight() > 2) { + if (IO.KeyAlt && IO.KeyShift && select_bb.GetWidth() > 2 && select_bb.GetHeight() > 2) { + DrawList.AddRectFilled(gp.BB_Grid.Min, gp.BB_Grid.Max, gp.Col_QryBg); + DrawList.AddRect( gp.BB_Grid.Min, gp.BB_Grid.Max, gp.Col_QryBd); + } + else if (IO.KeyAlt && select_bb.GetHeight() > 2) { + DrawList.AddRectFilled(ImVec2(gp.BB_Grid.Min.x, select_bb.Min.y), ImVec2(gp.BB_Grid.Max.x, select_bb.Max.y), gp.Col_QryBg); + DrawList.AddRect( ImVec2(gp.BB_Grid.Min.x, select_bb.Min.y), ImVec2(gp.BB_Grid.Max.x, select_bb.Max.y), gp.Col_QryBd); + } + else if (IO.KeyShift && select_bb.GetWidth() > 2) { + DrawList.AddRectFilled(ImVec2(select_bb.Min.x, gp.BB_Grid.Min.y), ImVec2(select_bb.Max.x, gp.BB_Grid.Max.y), gp.Col_QryBg); + DrawList.AddRect( ImVec2(select_bb.Min.x, gp.BB_Grid.Min.y), ImVec2(select_bb.Max.x, gp.BB_Grid.Max.y), gp.Col_QryBd); + } + else if (select_bb.GetWidth() > 2 && select_bb.GetHeight() > 2) { + DrawList.AddRectFilled(select_bb.Min, select_bb.Max, gp.Col_QryBg); + DrawList.AddRect( select_bb.Min, select_bb.Max, gp.Col_QryBd); + } } } // render crosshairs if (HasFlag(plot.Flags, ImPlotFlags_Crosshairs) && gp.Hov_Grid && gp.Hov_Frame && - !(plot.XAxis.Dragging || plot.YAxis.Dragging) && !plot.Selecting && !hov_legend) { + !(plot.XAxis.Dragging || plot.YAxis.Dragging) && !plot.Selecting && !plot.Querying && !hov_legend) { ImGui::SetMouseCursor(ImGuiMouseCursor_None); ImVec2 xy = IO.MousePos; ImVec2 h1(gp.BB_Grid.Min.x, xy.y); @@ -1101,6 +1163,7 @@ void EndPlot() { // Reset legend items gp._LegendIndices.shrink(0); // Null current plot/data + gp.PreviousPlot = gp.CurrentPlot; gp.CurrentPlot = NULL; // Reset next plot data gp.NextPlotData = ImNextPlotData(); @@ -1138,6 +1201,43 @@ void SetNextPlotRangeY(float y_min, float y_max, ImGuiCond cond) { bool IsPlotHovered() { return gp.Hov_Grid; } ImVec2 GetPlotMousePos() { return gp.LastMousePos; } +ImPlotRange GetPlotRange() { + ImPlot* plt = gp.CurrentPlot ? gp.CurrentPlot : gp.PreviousPlot ? gp.PreviousPlot : NULL; + if (plt == NULL) + return ImPlotRange(); + ImPlotRange range; + range.XMin = plt->XAxis.Min; + range.XMax = plt->XAxis.Max; + range.YMin = plt->YAxis.Min; + range.YMax = plt->YAxis.Max; + return range; +} + +bool IsPlotQueried() { + if (gp.CurrentPlot) { + return gp.CurrentPlot->Querying; + } + else if (gp.PreviousPlot) { + return gp.PreviousPlot->Querying; + } + return false; +} + +ImPlotRange GetPlotQuery() { + ImPlot* plt = gp.CurrentPlot ? gp.CurrentPlot : gp.PreviousPlot ? gp.PreviousPlot : NULL; + if (plt == NULL) + return ImPlotRange(); + ImVec2 p1 = gp.FromPixels(plt->SelectStart); + ImVec2 p2 = gp.FromPixels(ImGui::GetIO().MousePos); + + ImPlotRange range; + range.XMin = ImGui::GetIO().KeyAlt ? plt->XAxis.Min : ImMin(p1.x, p2.x); + range.XMax = ImGui::GetIO().KeyAlt ? plt->XAxis.Max : ImMax(p1.x, p2.x); + range.YMin = ImGui::GetIO().KeyShift ? plt->YAxis.Min : ImMin(p1.y, p2.y); + range.YMax = ImGui::GetIO().KeyShift ? plt->YAxis.Max : ImMax(p1.y, p2.y); + return range; +} + //============================================================================= // STYLING //============================================================================= diff --git a/implot.h b/implot.h index fa5b85b..e5df450 100644 --- a/implot.h +++ b/implot.h @@ -74,6 +74,7 @@ enum ImPlotCol_ { ImPlotCol_XAxis, // x-axis grid/label color (defaults to ImGuiCol_Text) ImPlotCol_YAxis, // x-axis grid/label color (defaults to ImGuiCol_Text) ImPlotCol_Selection, // box-selection color (defaults to yellow) + ImPlotCol_Query, // box-query color (defaults to green) ImPlotCol_COUNT }; @@ -102,6 +103,13 @@ enum ImMarker_ { ImMarker_Asterisk = 1 << 10, // a asterisk marker will be rendered at each point (not filled) }; +/// Plot range utility struct +struct ImPlotRange { + float XMin, XMax, YMin, YMax; + ImPlotRange(); + bool Contains(const ImVec2& p); +}; + // Plot style structure struct ImPlotStyle { float LineWeight; // = 1, line weight in pixels @@ -115,7 +123,7 @@ struct ImPlotStyle { }; //----------------------------------------------------------------------------- -// Plot API +// Core API //----------------------------------------------------------------------------- namespace ImGui { @@ -143,32 +151,43 @@ void SetNextPlotRange(float x_min, float x_max, float y_min, float y_max, ImGuiC void SetNextPlotRangeX(float x_min, float x_max, ImGuiCond cond = ImGuiCond_Once); /// Set the X axis range of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the axis will be locked. void SetNextPlotRangeY(float y_min, float y_max, ImGuiCond cond = ImGuiCond_Once); -/// Returns true if the plot area in the current or most recent call to BeginPlot() is hovered + +//----------------------------------------------------------------------------- +// Plot Queries +//----------------------------------------------------------------------------- + +/// Returns true if the plot area in the current or most recent plot is hovered. bool IsPlotHovered(); /// Returns the mouse position in x,y coordinates of the current or most recent plot. ImVec2 GetPlotMousePos(); +/// Returns the current or most recent plot axis range. +ImPlotRange GetPlotRange(); +/// Returns true if the current or most recent plot is being queried. +bool IsPlotQueried(); +/// Returns the current or most recent plot querey range. +ImPlotRange GetPlotQuery(); //----------------------------------------------------------------------------- // Plot Items //----------------------------------------------------------------------------- -// Plots a standard 2D line and/or scatter plot +// Plots a standard 2D line and/or scatter plot . void Plot(const char* label_id, const float* values, int count, int offset = 0, int stride = sizeof(float)); void Plot(const char* label_id, const float* xs, const float* ys, int count, int offset = 0, int stride = sizeof(float)); void Plot(const char* label_id, ImVec2 (*getter)(void* data, int idx), void* data, int count, int offset = 0); -// Plots vertical bars +// Plots vertical bars. void PlotBar(const char* label_id, const float* values, int count, float width = 0.67f, float shift = 0, int offset = 0, int stride = sizeof(float)); void PlotBar(const char* label_id, const float* xs, const float* ys, int count, float width, int offset = 0, int stride = sizeof(float)); void PlotBar(const char* label_id, ImVec2 (*getter)(void* data, int idx), void* data, int count, float width, int offset = 0); -// Plots horizontal bars +// Plots horizontal bars. void PlotBarH(const char* label_id, const float* values, int count, float height = 0.67f, float shift = 0, int offset = 0, int stride = sizeof(float)); void PlotBarH(const char* label_id, const float* xs, const float* ys, int count, float height, int offset = 0, int stride = sizeof(float)); void PlotBarH(const char* label_id, ImVec2 (*getter)(void* data, int idx), void* data, int count, float height, int offset = 0); -// Plots vertical error bars +// Plots vertical error bars. void PlotErrorBars(const char* label_id, const float* xs, const float* ys, const float* err, int count, int offset = 0, int stride = sizeof(float)); void PlotErrorBars(const char* label_id, const float* xs, const float* ys, const float* neg, const float* pos, int count, int offset = 0, int stride = sizeof(float)); void PlotErrorBars(const char* label_id, ImVec4 (*getter)(void* data, int idx), void* data, int count, int offset = 0); -// Plots a text label at point x,y +// Plots a text label at point x,y. void PlotLabel(const char* text, float x, float y, const ImVec2& pixel_offset = ImVec2(0,0)); //----------------------------------------------------------------------------- @@ -178,9 +197,9 @@ void PlotLabel(const char* text, float x, float y, const ImVec2& pixel_offset = // Provides access to plot style structure for permanant modifications to colors, sizes, etc. ImPlotStyle& GetPlotStyle(); -// Sets the color palette to be used for plot items +// Sets the color palette to be used for plot items. void SetPlotPalette(const ImVec4* colors, int num_colors); -// Restores the default ImPlot color map +// Restores the default ImPlot color map. void RestorePlotPalette(); // Temporarily modify a plot color. @@ -190,9 +209,9 @@ void PushPlotColor(ImPlotCol idx, const ImVec4& col); // Undo temporary color modification. void PopPlotColor(int count = 1); -// Temporarily modify a style variable of float type +// Temporarily modify a style variable of float type. void PushPlotStyleVar(ImPlotStyleVar idx, float val); -// Temporarily modify a style variable of int type +// Temporarily modify a style variable of int type. void PushPlotStyleVar(ImPlotStyleVar idx, int val); // Undo temporary style modification. void PopPlotStyleVar(int count = 1); diff --git a/implot_demo.cpp b/implot_demo.cpp index bc7e7b6..63354c0 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -70,9 +70,18 @@ void ShowImPlotDemoWindow(bool* p_open) { ImGui::Text("USER GUIDE:"); ImGui::BulletText("Left click and drag within the plot area to pan X and Y axes."); ImGui::BulletText("Left click and drag on an axis to pan an individual axis."); - ImGui::BulletText("Scroll in the plot area to zoom both X any Y axes"); + ImGui::BulletText("Scroll in the plot area to zoom both X any Y axes."); ImGui::BulletText("Scroll on an axis to zoom an individual axis."); ImGui::BulletText("Right click and drag to box select data."); + ImGui::Indent(); + ImGui::BulletText("Hold Alt to expand box selection horizontally."); + ImGui::BulletText("Hold Shift to expand box selection vertically."); + ImGui::Unindent(); + ImGui::BulletText("Middle click (or Ctrl + right click) and drag to create a query range."); + ImGui::Indent(); + ImGui::BulletText("Hold Alt to expand query horizontally."); + ImGui::BulletText("Hold Shift to expand query vertically."); + ImGui::Unindent(); ImGui::BulletText("Double left click to fit all visible data."); ImGui::BulletText("Double right click to open the plot context menu."); ImGui::BulletText("Click legend label icons to show/hide plot items."); @@ -296,17 +305,39 @@ void ShowImPlotDemoWindow(bool* p_open) { } //------------------------------------------------------------------------- if (ImGui::CollapsingHeader("Querying")) { - ImGui::BulletText("Click in the plot area to draw"); + ImGui::BulletText("Ctrl + click in the plot area to draw points."); + ImGui::BulletText("Middle click (or Ctrl + right click) and drag to query points."); + ImGui::BulletText("Hold the Alt and/or Shift keys to expand the query range."); static ImVector data; - if (ImGui::BeginPlot("##Drawing", NULL, NULL, ImVec2(-1,300), ImPlotFlags_Default, 0, 0)) { - if (ImGui::IsPlotHovered() && ImGui::IsMouseClicked(0)) + if (ImGui::BeginPlot("##Drawing", NULL, NULL, ImVec2(-1,300), ImPlotFlags_Default, ImAxisFlags_GridLines, ImAxisFlags_GridLines)) { + if (ImGui::IsPlotHovered() && ImGui::IsMouseClicked(0) && ImGui::GetIO().KeyCtrl) data.push_back(ImGui::GetPlotMousePos()); + ImGui::PushPlotStyleVar(ImPlotStyleVar_LineWeight, 0); ImGui::PushPlotStyleVar(ImPlotStyleVar_Marker, ImMarker_Diamond); if (data.size() > 0) - ImGui::Plot("Art", &data[0].x, &data[0].y, data.size(), 0, 2 * sizeof(float)); - ImGui::PopPlotStyleVar(); + ImGui::Plot("Points", &data[0].x, &data[0].y, data.size(), 0, 2 * sizeof(float)); + if (ImGui::IsPlotQueried() && data.size() > 0) { + ImPlotRange range = ImGui::GetPlotQuery(); + int cnt = 0; + ImVec2 avg; + for (int i = 0; i < data.size(); ++i) { + if (range.Contains(data[i])) { + avg.x += data[i].x; + avg.y += data[i].y; + cnt++; + } + } + if (cnt > 0) { + avg.x = avg.x / cnt; + avg.y = avg.y / cnt; + ImGui::Plot("Average", &avg.x, &avg.y, 1); + } + } + ImGui::PopPlotStyleVar(2); ImGui::EndPlot(); } + ImPlotRange range = ImGui::GetPlotRange(); + ImGui::Text("The current plot range is: [%g,%g,%g,%g]", range.XMin, range.XMax, range.YMin, range.YMax); } //-------------------------------------------------------------------------