From 587c8b6221dda1f83424f9f71fea11371f35b0c4 Mon Sep 17 00:00:00 2001 From: Evan Pezent Date: Sun, 18 Oct 2020 23:26:34 -0500 Subject: [PATCH] Configurable Legend Locations (#135) * add support for ISO 8601 timestamps * clean up work on ISO 8601 * legend location proto * location docs * add horizontal legends, and ability to position mouse location * add ShowAltLegend * add ShowAltLegend * default sizing for ShowAltLegend * finish up legend locations --- implot.cpp | 511 +++++++++++++++++++++++++++++----------------- implot.h | 138 ++++++++----- implot_demo.cpp | 47 ++++- implot_internal.h | 105 +++++----- implot_items.cpp | 29 ++- 5 files changed, 529 insertions(+), 301 deletions(-) diff --git a/implot.cpp b/implot.cpp index 58ed184..72fccdb 100644 --- a/implot.cpp +++ b/implot.cpp @@ -31,6 +31,7 @@ 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. +- 2020/10/16 (0.8) - ImPlotStyleVar_InfoPadding was changed to ImPlotStyleVar_MousePosPadding - 2020/09/10 (0.8) - The single array versions of PlotLine, PlotScatter, PlotStems, and PlotShaded were given additional arguments for x-scale and x0. - 2020/09/07 (0.8) - Plotting functions which accept a custom getter function pointer have been post-fixed with a G (e.g. PlotLineG) - 2020/09/06 (0.7) - Several flags under ImPlotFlags and ImPlotAxisFlags were inverted (e.g. ImPlotFlags_Legend -> ImPlotFlags_NoLegend) so that the default flagset @@ -95,30 +96,33 @@ ImPlotInputMap::ImPlotInputMap() { ImPlotStyle::ImPlotStyle() { - LineWeight = 1; - Marker = ImPlotMarker_None; - MarkerSize = 4; - MarkerWeight = 1; - FillAlpha = 1; - ErrorBarSize = 5; - ErrorBarWeight = 1.5f; - DigitalBitHeight = 8; - DigitalBitGap = 4; + LineWeight = 1; + Marker = ImPlotMarker_None; + MarkerSize = 4; + MarkerWeight = 1; + FillAlpha = 1; + ErrorBarSize = 5; + ErrorBarWeight = 1.5f; + DigitalBitHeight = 8; + DigitalBitGap = 4; - PlotBorderSize = 1; - MinorAlpha = 0.25f; - MajorTickLen = ImVec2(10,10); - MinorTickLen = ImVec2(5,5); - MajorTickSize = ImVec2(1,1); - MinorTickSize = ImVec2(1,1); - MajorGridSize = ImVec2(1,1); - MinorGridSize = ImVec2(1,1); - PlotPadding = ImVec2(8,8); - LabelPadding = ImVec2(5,5); - LegendPadding = ImVec2(10,10); - InfoPadding = ImVec2(10,10); - AnnotationPadding = ImVec2(2,2); - PlotMinSize = ImVec2(300,225); + PlotBorderSize = 1; + MinorAlpha = 0.25f; + MajorTickLen = ImVec2(10,10); + MinorTickLen = ImVec2(5,5); + MajorTickSize = ImVec2(1,1); + MinorTickSize = ImVec2(1,1); + MajorGridSize = ImVec2(1,1); + MinorGridSize = ImVec2(1,1); + PlotPadding = ImVec2(10,10); + LabelPadding = ImVec2(5,5); + LegendPadding = ImVec2(10,10); + LegendInnerPadding = ImVec2(5,5); + LegendSpacing = ImVec2(0,0); + MousePosPadding = ImVec2(10,10); + AnnotationPadding = ImVec2(2,2); + PlotDefaultSize = ImVec2(400,300); + PlotMinSize = ImVec2(300,225); ImPlot::StyleColorsAuto(this); @@ -128,6 +132,20 @@ ImPlotStyle::ImPlotStyle() { UseISO8601 = false; } +ImPlotItem* ImPlotLegend::GetItem(int i) { + return Plot->Items.GetByIndex(Indices[i]); +} + +const char* ImPlotLegend::GetLabel(int i) { + ImPlotItem* item = GetItem(i); + IM_ASSERT(item->NameOffset != -1 && item->NameOffset < Labels.Buf.Size); + return Labels.Buf.Data + item->NameOffset; +} + +//----------------------------------------------------------------------------- +// Style +//----------------------------------------------------------------------------- + namespace ImPlot { const char* GetStyleColorName(ImPlotCol col) { @@ -217,30 +235,34 @@ struct ImPlotStyleVarInfo { static const ImPlotStyleVarInfo GPlotStyleVarInfo[] = { - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, LineWeight) }, // ImPlotStyleVar_LineWeight - { ImGuiDataType_S32, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, Marker) }, // ImPlotStyleVar_Marker - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, MarkerSize) }, // ImPlotStyleVar_MarkerSize - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, MarkerWeight) }, // ImPlotStyleVar_MarkerWeight - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, FillAlpha) }, // ImPlotStyleVar_FillAlpha - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, ErrorBarSize) }, // ImPlotStyleVar_ErrorBarSize - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, ErrorBarWeight) }, // ImPlotStyleVar_ErrorBarWeight - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, DigitalBitHeight) }, // ImPlotStyleVar_DigitalBitHeight - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, DigitalBitGap) }, // ImPlotStyleVar_DigitalBitGap + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, LineWeight) }, // ImPlotStyleVar_LineWeight + { ImGuiDataType_S32, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, Marker) }, // ImPlotStyleVar_Marker + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, MarkerSize) }, // ImPlotStyleVar_MarkerSize + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, MarkerWeight) }, // ImPlotStyleVar_MarkerWeight + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, FillAlpha) }, // ImPlotStyleVar_FillAlpha + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, ErrorBarSize) }, // ImPlotStyleVar_ErrorBarSize + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, ErrorBarWeight) }, // ImPlotStyleVar_ErrorBarWeight + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, DigitalBitHeight) }, // ImPlotStyleVar_DigitalBitHeight + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, DigitalBitGap) }, // ImPlotStyleVar_DigitalBitGap - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, PlotBorderSize) }, // ImPlotStyleVar_PlotBorderSize - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, MinorAlpha) }, // ImPlotStyleVar_MinorAlpha - { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, MajorTickLen) }, // ImPlotStyleVar_MajorTickLen - { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, MinorTickLen) }, // ImPlotStyleVar_MinorTickLen - { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, MajorTickSize) }, // ImPlotStyleVar_MajorTickSize - { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, MinorTickSize) }, // ImPlotStyleVar_MinorTickSize - { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, MajorGridSize) }, // ImPlotStyleVar_MajorGridSize - { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, MinorGridSize) }, // ImPlotStyleVar_MinorGridSize - { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, PlotPadding) }, // ImPlotStyleVar_PlotPadding - { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, LabelPadding) }, // ImPlotStyleVar_LabelPaddine - { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, LegendPadding) }, // ImPlotStyleVar_LegendPadding - { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, InfoPadding) }, // ImPlotStyleVar_InfoPadding - { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, AnnotationPadding) }, // ImPlotStyleVar_AnnotationPadding - { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, PlotMinSize) } // ImPlotStyleVar_PlotMinSize + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, PlotBorderSize) }, // ImPlotStyleVar_PlotBorderSize + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, MinorAlpha) }, // ImPlotStyleVar_MinorAlpha + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, MajorTickLen) }, // ImPlotStyleVar_MajorTickLen + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, MinorTickLen) }, // ImPlotStyleVar_MinorTickLen + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, MajorTickSize) }, // ImPlotStyleVar_MajorTickSize + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, MinorTickSize) }, // ImPlotStyleVar_MinorTickSize + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, MajorGridSize) }, // ImPlotStyleVar_MajorGridSize + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, MinorGridSize) }, // ImPlotStyleVar_MinorGridSize + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, PlotPadding) }, // ImPlotStyleVar_PlotPadding + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, LabelPadding) }, // ImPlotStyleVar_LabelPaddine + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, LegendPadding) }, // ImPlotStyleVar_LegendPadding + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, LegendInnerPadding) }, // ImPlotStyleVar_LegendInnerPadding + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, LegendSpacing) }, // ImPlotStyleVar_LegendSpacing + + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, MousePosPadding) }, // ImPlotStyleVar_MousePosPadding + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, AnnotationPadding) }, // ImPlotStyleVar_AnnotationPadding + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, PlotDefaultSize) }, // ImPlotStyleVar_PlotDefaultSize + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, PlotMinSize) } // ImPlotStyleVar_PlotMinSize }; static const ImPlotStyleVarInfo* GetPlotStyleVarInfo(ImPlotStyleVar idx) { @@ -359,14 +381,10 @@ void Reset(ImPlotContext* ctx) { ctx->NextItemData = ImPlotNextItemData(); // reset items count ctx->VisibleItemCount = 0; - // reset legend items - ctx->LegendIndices.shrink(0); - ctx->LegendLabels.Buf.shrink(0); // reset ticks/labels ctx->XTicks.Reset(); - for (int i = 0; i < 3; ++i) { + for (int i = 0; i < 3; ++i) ctx->YTicks[i].Reset(); - } // reset labels ctx->Annotations.Reset(); // reset extents/fit @@ -408,7 +426,7 @@ void BustPlotCache() { void FitPoint(const ImPlotPoint& p) { ImPlotContext& gp = *GImPlot; - const int y_axis = gp.CurrentPlot->CurrentYAxis; + const ImPlotYAxis y_axis = gp.CurrentPlot->CurrentYAxis; ImPlotRange& ex_x = gp.ExtentsX; ImPlotRange& ex_y = gp.ExtentsY[y_axis]; const bool log_x = ImHasFlag(gp.CurrentPlot->XAxis.Flags, ImPlotAxisFlags_LogScale); @@ -453,10 +471,10 @@ void UpdateTransformCache() { gp.Mx = (gp.PixelRange[0].Max.x - gp.PixelRange[0].Min.x) / gp.CurrentPlot->XAxis.Range.Size(); } -ImPlotPoint PixelsToPlot(float x, float y, int y_axis_in) { +ImPlotPoint PixelsToPlot(float x, float y, ImPlotYAxis y_axis_in) { ImPlotContext& gp = *GImPlot; IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PixelsToPlot() needs to be called between BeginPlot() and EndPlot()!"); - const int y_axis = y_axis_in >= 0 ? y_axis_in : gp.CurrentPlot->CurrentYAxis; + const ImPlotYAxis y_axis = y_axis_in >= 0 ? y_axis_in : gp.CurrentPlot->CurrentYAxis; ImPlotPoint plt; plt.x = (x - gp.PixelRange[y_axis].Min.x) / gp.Mx + gp.CurrentPlot->XAxis.Range.Min; plt.y = (y - gp.PixelRange[y_axis].Min.y) / gp.My[y_axis] + gp.CurrentPlot->YAxis[y_axis].Range.Min; @@ -471,15 +489,15 @@ ImPlotPoint PixelsToPlot(float x, float y, int y_axis_in) { return plt; } -ImPlotPoint PixelsToPlot(const ImVec2& pix, int y_axis) { +ImPlotPoint PixelsToPlot(const ImVec2& pix, ImPlotYAxis y_axis) { return PixelsToPlot(pix.x, pix.y, y_axis); } // This function is convenient but should not be used to process a high volume of points. Use the Transformer structs below instead. -ImVec2 PlotToPixels(double x, double y, int y_axis_in) { +ImVec2 PlotToPixels(double x, double y, ImPlotYAxis y_axis_in) { ImPlotContext& gp = *GImPlot; IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotToPixels() needs to be called between BeginPlot() and EndPlot()!"); - const int y_axis = y_axis_in >= 0 ? y_axis_in : gp.CurrentPlot->CurrentYAxis; + const ImPlotYAxis y_axis = y_axis_in >= 0 ? y_axis_in : gp.CurrentPlot->CurrentYAxis; ImVec2 pix; if (ImHasFlag(gp.CurrentPlot->XAxis.Flags, ImPlotAxisFlags_LogScale)) { double t = ImLog10(x / gp.CurrentPlot->XAxis.Range.Min) / gp.LogDenX; @@ -495,7 +513,7 @@ ImVec2 PlotToPixels(double x, double y, int y_axis_in) { } // This function is convenient but should not be used to process a high volume of points. Use the Transformer structs below instead. -ImVec2 PlotToPixels(const ImPlotPoint& plt, int y_axis) { +ImVec2 PlotToPixels(const ImPlotPoint& plt, ImPlotYAxis y_axis) { return PlotToPixels(plt.x, plt.y, y_axis); } @@ -503,16 +521,98 @@ ImVec2 PlotToPixels(const ImPlotPoint& plt, int y_axis) { // Legend Utils //----------------------------------------------------------------------------- -int GetLegendCount() { - ImPlotContext& gp = *GImPlot; - return gp.LegendIndices.size(); +ImVec2 GetLocationPos(const ImRect& outer_rect, const ImVec2& inner_size, ImPlotLocation loc, const ImVec2& pad) { + ImVec2 pos; + if (ImHasFlag(loc, ImPlotLocation_West) && !ImHasFlag(loc, ImPlotLocation_East)) + pos.x = outer_rect.Min.x + pad.x; + else if (!ImHasFlag(loc, ImPlotLocation_West) && ImHasFlag(loc, ImPlotLocation_East)) + pos.x = outer_rect.Max.x - pad.x - inner_size.x; + else + pos.x = outer_rect.GetCenter().x - inner_size.x * 0.5f; + // legend reference point y + if (ImHasFlag(loc, ImPlotLocation_North) && !ImHasFlag(loc, ImPlotLocation_South)) + pos.y = outer_rect.Min.y + pad.y; + else if (!ImHasFlag(loc, ImPlotLocation_North) && ImHasFlag(loc, ImPlotLocation_South)) + pos.y = outer_rect.Max.y - pad.y - inner_size.y; + else + pos.y = outer_rect.GetCenter().y - inner_size.y * 0.5f; + pos.x = IM_ROUND(pos.x); + pos.y = IM_ROUND(pos.y); + return pos; } -const char* GetLegendLabel(int i) { - ImPlotContext& gp = *GImPlot; - ImPlotItem* item = gp.CurrentPlot->Items.GetByIndex(gp.LegendIndices[i]); - IM_ASSERT(item->NameOffset != -1 && item->NameOffset < gp.LegendLabels.Buf.Size); - return gp.LegendLabels.Buf.Data + item->NameOffset; +ImVec2 CalcLegendSize(ImPlotLegend& legend, const ImVec2& pad, const ImVec2& spacing, ImPlotOrientation orn) { + // vars + const int nItems = legend.Count(); + const float txt_ht = ImGui::GetTextLineHeight(); + const float icon_size = txt_ht; + // get label max width + float max_label_width = 0; + float sum_label_width = 0; + for (int i = 0; i < nItems; ++i) { + const char* label = legend.GetLabel(i); + const float label_width = ImGui::CalcTextSize(label, NULL, true).x; + max_label_width = label_width > max_label_width ? label_width : max_label_width; + sum_label_width += label_width; + } + // calc legend size + const ImVec2 legend_size = orn == ImPlotOrientation_Vertical ? + ImVec2(pad.x * 2 + icon_size + max_label_width, pad.y * 2 + nItems * txt_ht + (nItems - 1) * spacing.y) : + ImVec2(pad.x * 2 + icon_size * nItems + sum_label_width + (nItems - 1) * spacing.x, pad.y * 2 + txt_ht); + return legend_size; +} + +void ShowLegendEntries(ImPlotLegend& legend, const ImRect& legend_bb, bool interactable, const ImVec2& pad, const ImVec2& spacing, ImPlotOrientation orn, ImDrawList& DrawList) { + ImGuiIO& IO = ImGui::GetIO(); + // vars + 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)); + // render each legend item + float sum_label_width = 0; + for (int i = 0; i < legend.Count(); ++i) { + ImPlotItem* item = legend.GetItem(i); + const char* label = legend.GetLabel(i); + const float label_width = ImGui::CalcTextSize(label, NULL, true).x; + const ImVec2 top_left = orn == ImPlotOrientation_Vertical ? + legend_bb.Min + pad + ImVec2(0, i * (txt_ht + spacing.y)) : + legend_bb.Min + pad + ImVec2(i * (icon_size + spacing.x) + sum_label_width, 0); + sum_label_width += label_width; + ImRect icon_bb; + icon_bb.Min = top_left + ImVec2(icon_shrink,icon_shrink); + icon_bb.Max = top_left + ImVec2(icon_size - icon_shrink, icon_size - icon_shrink); + ImRect label_bb; + label_bb.Min = top_left; + label_bb.Max = top_left + ImVec2(label_width + icon_size, icon_size); + ImU32 col_hl_txt; + 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)); + } + 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); + if (IO.MouseClicked[0]) + item->Show = !item->Show; + } + else { + iconColor = item->Show ? ImGui::GetColorU32(item_color) : col_txt_dis; + } + DrawList.AddRectFilled(icon_bb.Min, icon_bb.Max, iconColor, 1); + const char* text_display_end = ImGui::FindRenderedTextEnd(label, NULL); + if (label != text_display_end) + DrawList.AddText(top_left + ImVec2(icon_size, 0), item->Show ? col_hl_txt : col_txt_dis, label, text_display_end); + } } //----------------------------------------------------------------------------- @@ -881,7 +981,7 @@ int FormatDate(const ImPlotTime& t, char* buffer, int size, ImPlotDateFmt fmt, b case ImPlotDateFmt_DayMo: return snprintf(buffer, size, "--%02d-%02d", mon, day); case ImPlotDateFmt_DayMoYr: return snprintf(buffer, size, "%d-%02d-%02d", year, mon, day); case ImPlotDateFmt_MoYr: return snprintf(buffer, size, "%d-%02d", year, mon); - case ImPlotDateFmt_Mo: return snprintf(buffer, size, "--%02d-01", mon); + case ImPlotDateFmt_Mo: return snprintf(buffer, size, "--%02d", mon); case ImPlotDateFmt_Yr: return snprintf(buffer, size, "%d", year); default: return 0; } @@ -1092,7 +1192,7 @@ int LabelAxisValue(const ImPlotAxis& axis, const ImPlotTickCollection& ticks, do return snprintf(buff, size, "%.3E", value); } else if (ImHasFlag(axis.Flags, ImPlotAxisFlags_Time)) { - ImPlotTimeUnit unit = (axis.Direction == ImPlotDirection_Horizontal) + ImPlotTimeUnit unit = (axis.Direction == ImPlotOrientation_Horizontal) ? GetUnitForRange(axis.Range.Size() / (gp.BB_Plot.GetWidth() / 100)) : GetUnitForRange(axis.Range.Size() / (gp.BB_Plot.GetHeight() / 100)); return FormatDateTime(ImPlotTime::FromDouble(value), buff, size, GetDateTimeFmt(TimeFormatMouseCursor, unit)); @@ -1174,7 +1274,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // capture scroll with a child region if (!ImHasFlag(plot.Flags, ImPlotFlags_NoChild)) { - ImGui::BeginChild(title, ImVec2(size.x == 0 ? IMPLOT_DEFAULT_W : size.x, size.y == 0 ? IMPLOT_DEFAULT_H : size.y), false, ImGuiWindowFlags_NoScrollbar); + ImGui::BeginChild(title, ImVec2(size.x == 0 ? gp.Style.PlotDefaultSize.x : size.x, size.y == 0 ? gp.Style.PlotDefaultSize.y : size.y), false, ImGuiWindowFlags_NoScrollbar); Window = ImGui::GetCurrentWindow(); Window->ScrollMax.y = 1.0f; gp.ChildWindowMade = true; @@ -1244,7 +1344,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // BB, PADDING, HOVER ----------------------------------------------------------- // frame - ImVec2 frame_size = ImGui::CalcItemSize(size, IMPLOT_DEFAULT_W, IMPLOT_DEFAULT_H); + ImVec2 frame_size = ImGui::CalcItemSize(size, gp.Style.PlotDefaultSize.x, gp.Style.PlotDefaultSize.y); if (frame_size.x < gp.Style.PlotMinSize.x && size.x < 0.0f) frame_size.x = gp.Style.PlotMinSize.x; if (frame_size.y < gp.Style.PlotMinSize.y && size.y < 0.0f) @@ -1261,8 +1361,35 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons ImGui::SetItemAllowOverlap(); ImGui::RenderFrame(gp.BB_Frame.Min, gp.BB_Frame.Max, GetStyleColorU32(ImPlotCol_FrameBg), true, Style.FrameRounding); - // canvas bb + // canvas/axes bb gp.BB_Canvas = ImRect(gp.BB_Frame.Min + gp.Style.PlotPadding, gp.BB_Frame.Max - gp.Style.PlotPadding); + gp.BB_Axes = gp.BB_Frame; + + // outside legend adjustments + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoLegend) && plot.Legend.Count() > 0 && plot.LegendOutside) { + const ImVec2 legend_size = CalcLegendSize(plot.Legend, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, plot.LegendOrientation); + const bool west = ImHasFlag(plot.LegendLocation, ImPlotLocation_West) && !ImHasFlag(plot.LegendLocation, ImPlotLocation_East); + const bool east = ImHasFlag(plot.LegendLocation, ImPlotLocation_East) && !ImHasFlag(plot.LegendLocation, ImPlotLocation_West); + const bool north = ImHasFlag(plot.LegendLocation, ImPlotLocation_North) && !ImHasFlag(plot.LegendLocation, ImPlotLocation_South); + const bool south = ImHasFlag(plot.LegendLocation, ImPlotLocation_South) && !ImHasFlag(plot.LegendLocation, ImPlotLocation_North); + const bool horz = plot.LegendOrientation == ImPlotOrientation_Horizontal; + if ((west && !horz) || (west && horz && !north && !south)) { + gp.BB_Canvas.Min.x += (legend_size.x + gp.Style.LegendPadding.x); + gp.BB_Axes.Min.x += (legend_size.x + gp.Style.PlotPadding.x); + } + if ((east && !horz) || (east && horz && !north && !south)) { + gp.BB_Canvas.Max.x -= (legend_size.x + gp.Style.LegendPadding.x); + gp.BB_Axes.Max.x -= (legend_size.x + gp.Style.PlotPadding.x); + } + if ((north && horz) || (north && !horz && !west && !east)) { + gp.BB_Canvas.Min.y += (legend_size.y + gp.Style.LegendPadding.y); + gp.BB_Axes.Min.y += (legend_size.y + gp.Style.PlotPadding.y); + } + if ((south && horz) || (south && !horz && !west && !east)) { + gp.BB_Canvas.Max.y -= (legend_size.y + gp.Style.LegendPadding.y); + gp.BB_Axes.Max.y -= (legend_size.y + gp.Style.PlotPadding.y); + } + } gp.RenderX = (!ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_NoGridLines) || !ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_NoTickMarks) || @@ -1320,8 +1447,8 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons gp.Hov_Plot = gp.BB_Plot.Contains(IO.MousePos) && gp.Hov_Frame; // x axis region bb and hover - const ImRect xAxisRegion_bb(gp.BB_Plot.GetBL(), ImVec2(gp.BB_Plot.Max.x, gp.BB_Frame.Max.y)); - plot.XAxis.HoveredExt = xAxisRegion_bb.Contains(IO.MousePos); + gp.BB_X = ImRect(gp.BB_Plot.GetBL(), ImVec2(gp.BB_Plot.Max.x, gp.BB_Axes.Max.y)); + plot.XAxis.HoveredExt = gp.BB_X.Contains(IO.MousePos); plot.XAxis.HoveredTot = plot.XAxis.HoveredExt || gp.Hov_Plot; // axis label reference @@ -1330,34 +1457,20 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons gp.YAxisReference[2] = !gp.Y[1].Present ? gp.BB_Plot.Max.x : (gp.YAxisReference[1] + (gp.Y[1].HasLabels ? gp.Style.LabelPadding.x + gp.YTicks[1].MaxWidth : 0) + gp.Style.LabelPadding.x + gp.Style.MinorTickLen.y); // y axis regions bb and hover - ImRect yAxisRegion_bb[IMPLOT_Y_AXES]; - yAxisRegion_bb[0] = ImRect(ImVec2(gp.BB_Frame.Min.x, gp.BB_Plot.Min.y), ImVec2(gp.BB_Plot.Min.x, gp.BB_Plot.Max.y)); - yAxisRegion_bb[1] = gp.Y[2].Present + gp.BB_Y[0] = ImRect(ImVec2(gp.BB_Axes.Min.x, gp.BB_Plot.Min.y), ImVec2(gp.BB_Plot.Min.x, gp.BB_Plot.Max.y)); + gp.BB_Y[1] = gp.Y[2].Present ? ImRect(gp.BB_Plot.GetTR(), ImVec2(gp.YAxisReference[2], gp.BB_Plot.Max.y)) - : ImRect(gp.BB_Plot.GetTR(), ImVec2(gp.BB_Frame.Max.x, gp.BB_Plot.Max.y)); + : ImRect(gp.BB_Plot.GetTR(), ImVec2(gp.BB_Axes.Max.x, gp.BB_Plot.Max.y)); - yAxisRegion_bb[2] = ImRect(ImVec2(gp.YAxisReference[2], gp.BB_Plot.Min.y), ImVec2(gp.BB_Frame.Max.x, gp.BB_Plot.Max.y)); + gp.BB_Y[2] = ImRect(ImVec2(gp.YAxisReference[2], gp.BB_Plot.Min.y), ImVec2(gp.BB_Axes.Max.x, gp.BB_Plot.Max.y)); for (int i = 0; i < IMPLOT_Y_AXES; ++i) { - plot.YAxis[i].HoveredExt = gp.Y[i].Present && yAxisRegion_bb[i].Contains(IO.MousePos); + plot.YAxis[i].HoveredExt = gp.Y[i].Present && gp.BB_Y[i].Contains(IO.MousePos); plot.YAxis[i].HoveredTot = plot.YAxis[i].HoveredExt || gp.Hov_Plot; } -#if 0 - ImGui::GetForegroundDrawList()->AddRect(gp.BB_Canvas.Min, gp.BB_Canvas.Max, IM_COL32_WHITE); - ImGui::GetForegroundDrawList()->AddRectFilled(xAxisRegion_bb.Min, xAxisRegion_bb.Max, IM_COL32(255,0,0,plot.XAxis.HoveredTot ? 128 : 64)); - ImGui::GetForegroundDrawList()->AddRectFilled(yAxisRegion_bb[0].Min, yAxisRegion_bb[0].Max, IM_COL32(255,255,0,plot.YAxis[0].HoveredTot ? 128 : 64)); - if (gp.Y[1].Present) - ImGui::GetForegroundDrawList()->AddRectFilled(yAxisRegion_bb[1].Min, yAxisRegion_bb[1].Max, IM_COL32(0,255,0,plot.YAxis[1].HoveredTot ? 128 : 64)); - if (gp.Y[2].Present) - ImGui::GetForegroundDrawList()->AddRectFilled(yAxisRegion_bb[2].Min, yAxisRegion_bb[2].Max, IM_COL32(0,0,255,plot.YAxis[2].HoveredTot ? 128 : 64)); -#endif - const bool any_hov_y_axis_region = plot.YAxis[0].HoveredTot || plot.YAxis[1].HoveredTot || plot.YAxis[2].HoveredTot; - // legend hovered from last frame - const bool hov_legend = !ImHasFlag(plot.Flags, ImPlotFlags_NoLegend) ? gp.Hov_Frame && plot.BB_Legend.Contains(IO.MousePos) : false; - bool hov_query = false; if (gp.Hov_Frame && gp.Hov_Plot && plot.Queried && !plot.Querying) { ImRect bb_query = plot.QueryRect; @@ -1377,7 +1490,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons plot.QueryRect.Min += IO.MouseDelta; plot.QueryRect.Max += IO.MouseDelta; } - if (gp.Hov_Frame && gp.Hov_Plot && hov_query && !plot.DraggingQuery && !plot.Selecting && !hov_legend) { + if (gp.Hov_Frame && gp.Hov_Plot && hov_query && !plot.DraggingQuery && !plot.Selecting && !plot.LegendHovered) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll); const bool any_y_dragging = plot.YAxis[0].Dragging || plot.YAxis[1].Dragging || plot.YAxis[2].Dragging; if (IO.MouseDown[gp.InputMap.PanButton] && !plot.XAxis.Dragging && !any_y_dragging) { @@ -1445,7 +1558,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons } } // start drag - if (!drag_in_progress && gp.Hov_Frame && IO.MouseClicked[gp.InputMap.PanButton] && ImHasFlag(IO.KeyMods, gp.InputMap.PanMod) && !plot.Selecting && !hov_legend && !hov_query && !plot.DraggingQuery) { + if (!drag_in_progress && gp.Hov_Frame && IO.MouseClicked[gp.InputMap.PanButton] && ImHasFlag(IO.KeyMods, gp.InputMap.PanMod) && !plot.Selecting && !plot.LegendHovered && !hov_query && !plot.DraggingQuery) { if (plot.XAxis.HoveredTot) { plot.XAxis.Dragging = true; } @@ -1576,7 +1689,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // FIT ----------------------------------------------------------- // fit from double click - if ( IO.MouseDoubleClicked[gp.InputMap.FitButton] && gp.Hov_Frame && (plot.XAxis.HoveredTot || any_hov_y_axis_region) && !hov_legend && !hov_query ) { + if ( IO.MouseDoubleClicked[gp.InputMap.FitButton] && gp.Hov_Frame && (plot.XAxis.HoveredTot || any_hov_y_axis_region) && !plot.LegendHovered && !hov_query ) { gp.FitThisFrame = true; gp.FitX = plot.XAxis.HoveredTot; for (int i = 0; i < IMPLOT_Y_AXES; i++) @@ -1708,6 +1821,8 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons } } ImGui::PopClipRect(); + // clear legend + plot.Legend.Reset(); // push plot ID into stack ImGui::PushID(ID); return true; @@ -1915,11 +2030,15 @@ void ShowPlotContextMenu(ImPlotPlot& plot) { ImGui::LabelText("Plots", "%d", gp.Plots.GetSize()); ImGui::LabelText("Color Mods", "%d", gp.ColorModifiers.size()); ImGui::LabelText("Style Mods", "%d", gp.StyleModifiers.size()); - ImGui::TextUnformatted(gp.XTicks.TextBuffer.Buf.Data, gp.XTicks.TextBuffer.Buf.Data + gp.XTicks.TextBuffer.size()); - ImGui::TextUnformatted(gp.YTicks[0].TextBuffer.Buf.Data, gp.YTicks[0].TextBuffer.Buf.Data + gp.YTicks[0].TextBuffer.size()); - // ImGui::TextUnformatted(gp.YTicks[1].Labels.Buf.Data, gp.YTicks[1].Labels.Buf.Data + gp.YTicks[1].Labels.size()); - // ImGui::TextUnformatted(gp.YTicks[2].Labels.Buf.Data, gp.YTicks[2].Labels.Buf.Data + gp.YTicks[2].Labels.size()); - + bool f = false; + ImGui::Selectable("BB_Frame",&f); if (ImGui::IsItemHovered()) ImGui::GetForegroundDrawList()->AddRect(gp.BB_Frame.Min, gp.BB_Frame.Max, IM_COL32(255,255,0,255)); + ImGui::Selectable("BB_Canvas",&f); if (ImGui::IsItemHovered()) ImGui::GetForegroundDrawList()->AddRect(gp.BB_Canvas.Min, gp.BB_Canvas.Max, IM_COL32(255,255,0,255)); + ImGui::Selectable("BB_Plot",&f); if (ImGui::IsItemHovered()) ImGui::GetForegroundDrawList()->AddRect(gp.BB_Plot.Min, gp.BB_Plot.Max, IM_COL32(255,255,0,255)); + ImGui::Selectable("BB_Axes",&f); if (ImGui::IsItemHovered()) ImGui::GetForegroundDrawList()->AddRect(gp.BB_Axes.Min, gp.BB_Axes.Max, IM_COL32(255,255,0,255)); + ImGui::Selectable("BB_X",&f); if (ImGui::IsItemHovered()) ImGui::GetForegroundDrawList()->AddRect(gp.BB_X.Min, gp.BB_X.Max, IM_COL32(255,255,0,255)); + ImGui::Selectable("BB_Y[0]",&f); if (ImGui::IsItemHovered()) ImGui::GetForegroundDrawList()->AddRect(gp.BB_Y[0].Min, gp.BB_Y[0].Max, IM_COL32(255,255,0,255)); + ImGui::Selectable("BB_Y[1]",&f); if (ImGui::IsItemHovered()) ImGui::GetForegroundDrawList()->AddRect(gp.BB_Y[1].Min, gp.BB_Y[1].Max, IM_COL32(255,255,0,255)); + ImGui::Selectable("BB_Y[2]",&f); if (ImGui::IsItemHovered()) ImGui::GetForegroundDrawList()->AddRect(gp.BB_Y[2].Min, gp.BB_Y[2].Max, IM_COL32(255,255,0,255)); ImGui::PopItemWidth(); ImGui::EndMenu(); } @@ -2096,75 +2215,9 @@ void EndPlot() { } } - // render legend - const float txt_ht = ImGui::GetTextLineHeight(); - const ImVec2 legend_offset = gp.Style.LegendPadding; - const ImVec2 legend_spacing(5, 5); - const float legend_icon_size = txt_ht; - ImRect legend_content_bb; - int nItems = GetLegendCount(); - bool hov_legend = false; - if (!ImHasFlag(plot.Flags, ImPlotFlags_NoLegend) && nItems > 0) { - // get max width - float max_label_width = 0; - for (int i = 0; i < nItems; ++i) { - const char* label = GetLegendLabel(i); - ImVec2 labelWidth = ImGui::CalcTextSize(label, NULL, true); - max_label_width = labelWidth.x > max_label_width ? labelWidth.x : max_label_width; - } - legend_content_bb = ImRect(gp.BB_Plot.Min + legend_offset, gp.BB_Plot.Min + legend_offset + ImVec2(max_label_width, nItems * txt_ht)); - plot.BB_Legend = ImRect(legend_content_bb.Min, legend_content_bb.Max + legend_spacing * 2 + ImVec2(legend_icon_size, 0)); - hov_legend = !ImHasFlag(plot.Flags, ImPlotFlags_NoLegend) ? gp.Hov_Frame && plot.BB_Legend.Contains(IO.MousePos) : false; - // render legend box - ImU32 col_bg = GetStyleColorU32(ImPlotCol_LegendBg); - ImU32 col_bd = GetStyleColorU32(ImPlotCol_LegendBorder); - ImVec4 col_txt = GetStyleColorVec4(ImPlotCol_LegendText); - ImU32 col_txt_dis = ImGui::GetColorU32(col_txt * ImVec4(1,1,1,0.25f)); - DrawList.AddRectFilled(plot.BB_Legend.Min, plot.BB_Legend.Max, col_bg); - DrawList.AddRect(plot.BB_Legend.Min, plot.BB_Legend.Max, col_bd); - // render each legend item - for (int i = 0; i < nItems; ++i) { - ImPlotItem* item = GetItem(i); - ImRect icon_bb; - icon_bb.Min = legend_content_bb.Min + legend_spacing + ImVec2(0, i * txt_ht) + ImVec2(2, 2); - icon_bb.Max = legend_content_bb.Min + legend_spacing + ImVec2(0, i * txt_ht) + ImVec2(legend_icon_size - 2, legend_icon_size - 2); - ImRect label_bb; - label_bb.Min = legend_content_bb.Min + legend_spacing + ImVec2(0, i * txt_ht) + ImVec2(2, 2); - label_bb.Max = legend_content_bb.Min + legend_spacing + ImVec2(0, i * txt_ht) + ImVec2(legend_content_bb.Max.x, legend_icon_size - 2); - ImU32 col_hl_txt; - if (hov_legend && (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)); - } - else - { - item->LegendHovered = false; - col_hl_txt = ImGui::GetColorU32(col_txt); - } - ImU32 iconColor; - ImVec4 item_color = item->Color; - item_color.w = 1; - if (hov_legend && 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); - if (IO.MouseClicked[0]) - item->Show = !item->Show; - } else { - iconColor = item->Show ? ImGui::GetColorU32(item_color) : col_txt_dis; - } - DrawList.AddRectFilled(icon_bb.Min, icon_bb.Max, iconColor, 1); - const char* label = GetLegendLabel(i); - const char* text_display_end = ImGui::FindRenderedTextEnd(label, NULL); - if (label != text_display_end) - DrawList.AddText(legend_content_bb.Min + legend_spacing + ImVec2(legend_icon_size, i * txt_ht), item->Show ? col_hl_txt : col_txt_dis, label, text_display_end); - } - } - // render crosshairs if (ImHasFlag(plot.Flags, ImPlotFlags_Crosshairs) && gp.Hov_Plot && gp.Hov_Frame && - !(plot.XAxis.Dragging || any_y_dragging) && !plot.Selecting && !plot.Querying && !hov_legend) { + !(plot.XAxis.Dragging || any_y_dragging) && !plot.Selecting && !plot.Querying && !plot.LegendHovered) { ImGui::SetMouseCursor(ImGuiMouseCursor_None); ImVec2 xy = IO.MousePos; ImVec2 h1(gp.BB_Plot.Min.x, xy.y); @@ -2229,13 +2282,43 @@ void EndPlot() { writer.Write(",(%.*f)", Precision(range_y), gp.MousePos[2].y); } } - ImVec2 size = ImGui::CalcTextSize(buffer); - ImVec2 pos = gp.BB_Plot.Max - size - gp.Style.InfoPadding; + const ImVec2 size = ImGui::CalcTextSize(buffer); + const ImVec2 pos = GetLocationPos(gp.BB_Plot, size, plot.MousePosLocation, gp.Style.MousePosPadding); DrawList.AddText(pos, GetStyleColorU32(ImPlotCol_InlayText), buffer); } - PopPlotClipRect(); + // reset legend hovers + plot.LegendHovered = false; + for (int i = 0; i < plot.Items.GetSize(); ++i) + plot.Items.GetByIndex(i)->LegendHovered = false; + // render legend + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoLegend) && plot.Legend.Count() > 0) { + const ImVec2 legend_size = CalcLegendSize(plot.Legend, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, plot.LegendOrientation); + const ImVec2 legend_pos = GetLocationPos(plot.LegendOutside ? gp.BB_Frame : gp.BB_Plot, + legend_size, + plot.LegendLocation, + plot.LegendOutside ? gp.Style.PlotPadding : gp.Style.LegendPadding); + const ImRect legend_bb(legend_pos, legend_pos + legend_size); + // test hover + plot.LegendHovered = gp.Hov_Frame && legend_bb.Contains(IO.MousePos); + + if (plot.LegendOutside) + ImGui::PushClipRect(gp.BB_Frame.Min, gp.BB_Frame.Max, true); + else + PushPlotClipRect(); + ImU32 col_bg = GetStyleColorU32(ImPlotCol_LegendBg); + ImU32 col_bd = GetStyleColorU32(ImPlotCol_LegendBorder); + DrawList.AddRectFilled(legend_bb.Min, legend_bb.Max, col_bg); + DrawList.AddRect(legend_bb.Min, legend_bb.Max, col_bd); + ShowLegendEntries(plot.Legend, legend_bb, plot.LegendHovered, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, plot.LegendOrientation, DrawList); + ImGui::PopClipRect(); + } + if (plot.LegendFlipSide) { + plot.LegendOutside = !plot.LegendOutside; + plot.LegendFlipSide = false; + } + // render border if (gp.Style.PlotBorderSize > 0) DrawList.AddRect(gp.BB_Plot.Min, gp.BB_Plot.Max, GetStyleColorU32(ImPlotCol_PlotBorder), 0, ImDrawCornerFlags_All, gp.Style.PlotBorderSize); @@ -2271,14 +2354,14 @@ void EndPlot() { // CONTEXT MENUS ----------------------------------------------------------- - if (!ImHasFlag(plot.Flags, ImPlotFlags_NoMenus) && gp.Hov_Frame && gp.Hov_Plot && IO.MouseDoubleClicked[gp.InputMap.ContextMenuButton] && !hov_legend) + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoMenus) && gp.Hov_Frame && gp.Hov_Plot && IO.MouseDoubleClicked[gp.InputMap.ContextMenuButton] && !plot.LegendHovered) ImGui::OpenPopup("##PlotContext"); if (ImGui::BeginPopup("##PlotContext")) { ShowPlotContextMenu(plot); ImGui::EndPopup(); } - if (!ImHasFlag(plot.Flags, ImPlotFlags_NoMenus) && gp.Hov_Frame && plot.XAxis.HoveredExt && IO.MouseDoubleClicked[gp.InputMap.ContextMenuButton] && !hov_legend) + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoMenus) && gp.Hov_Frame && plot.XAxis.HoveredExt && IO.MouseDoubleClicked[gp.InputMap.ContextMenuButton] && !plot.LegendHovered) ImGui::OpenPopup("##XContext"); if (ImGui::BeginPopup("##XContext")) { ImGui::Text("X-Axis"); ImGui::Separator(); @@ -2288,7 +2371,7 @@ void EndPlot() { for (int i = 0; i < IMPLOT_Y_AXES; ++i) { ImGui::PushID(i); - if (!ImHasFlag(plot.Flags, ImPlotFlags_NoMenus) && gp.Hov_Frame && plot.YAxis[i].HoveredExt && IO.MouseDoubleClicked[gp.InputMap.ContextMenuButton] && !hov_legend) + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoMenus) && gp.Hov_Frame && plot.YAxis[i].HoveredExt && IO.MouseDoubleClicked[gp.InputMap.ContextMenuButton] && !plot.LegendHovered) ImGui::OpenPopup("##YContext"); if (ImGui::BeginPopup("##YContext")) { if (i == 0) { @@ -2347,7 +2430,7 @@ void SetNextPlotLimitsX(double x_min, double x_max, ImGuiCond cond) { gp.NextPlotData.X.Max = x_max; } -void SetNextPlotLimitsY(double y_min, double y_max, ImGuiCond cond, int y_axis) { +void SetNextPlotLimitsY(double y_min, double y_max, ImGuiCond cond, ImPlotYAxis y_axis) { ImPlotContext& gp = *GImPlot; IM_ASSERT_USER_ERROR(gp.CurrentPlot == NULL, "SetNextPlotLimitsY() needs to be called before BeginPlot()!"); IM_ASSERT_USER_ERROR(y_axis >= 0 && y_axis < IMPLOT_Y_AXES, "y_axis needs to be between 0 and IMPLOT_Y_AXES"); @@ -2393,7 +2476,7 @@ void SetNextPlotTicksX(double x_min, double x_max, int n_ticks, const char* cons SetNextPlotTicksX(&buffer[0], n_ticks, labels, show_default); } -void SetNextPlotTicksY(const double* values, int n_ticks, const char* const labels[], bool show_default, int y_axis) { +void SetNextPlotTicksY(const double* values, int n_ticks, const char* const labels[], bool show_default, ImPlotYAxis y_axis) { ImPlotContext& gp = *GImPlot; IM_ASSERT_USER_ERROR(gp.CurrentPlot == NULL, "SetNextPlotTicksY() needs to be called before BeginPlot()!"); IM_ASSERT_USER_ERROR(y_axis >= 0 && y_axis < IMPLOT_Y_AXES, "y_axis needs to be between 0 and IMPLOT_Y_AXES"); @@ -2401,14 +2484,14 @@ void SetNextPlotTicksY(const double* values, int n_ticks, const char* const labe AddTicksCustom(values, labels, n_ticks, gp.YTicks[y_axis]); } -void SetNextPlotTicksY(double y_min, double y_max, int n_ticks, const char* const labels[], bool show_default, int y_axis) { +void SetNextPlotTicksY(double y_min, double y_max, int n_ticks, const char* const labels[], bool show_default, ImPlotYAxis y_axis) { IM_ASSERT_USER_ERROR(n_ticks > 1, "The number of ticks must be greater than 1"); static ImVector buffer; FillRange(buffer, n_ticks, y_min, y_max); SetNextPlotTicksY(&buffer[0], n_ticks, labels, show_default,y_axis); } -void SetPlotYAxis(int y_axis) { +void SetPlotYAxis(ImPlotYAxis y_axis) { ImPlotContext& gp = *GImPlot; IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "SetPlotYAxis() needs to be called between BeginPlot() and EndPlot()!"); IM_ASSERT_USER_ERROR(y_axis >= 0 && y_axis < IMPLOT_Y_AXES, "y_axis needs to be between 0 and IMPLOT_Y_AXES"); @@ -2453,28 +2536,28 @@ bool IsPlotXAxisHovered() { return gp.CurrentPlot->XAxis.HoveredExt; } -bool IsPlotYAxisHovered(int y_axis_in) { +bool IsPlotYAxisHovered(ImPlotYAxis y_axis_in) { ImPlotContext& gp = *GImPlot; IM_ASSERT_USER_ERROR(y_axis_in >= -1 && y_axis_in < IMPLOT_Y_AXES, "y_axis needs to between -1 and IMPLOT_Y_AXES"); IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "IsPlotYAxisHovered() needs to be called between BeginPlot() and EndPlot()!"); - const int y_axis = y_axis_in >= 0 ? y_axis_in : gp.CurrentPlot->CurrentYAxis; + const ImPlotYAxis y_axis = y_axis_in >= 0 ? y_axis_in : gp.CurrentPlot->CurrentYAxis; return gp.CurrentPlot->YAxis[y_axis].HoveredExt; } -ImPlotPoint GetPlotMousePos(int y_axis_in) { +ImPlotPoint GetPlotMousePos(ImPlotYAxis y_axis_in) { ImPlotContext& gp = *GImPlot; IM_ASSERT_USER_ERROR(y_axis_in >= -1 && y_axis_in < IMPLOT_Y_AXES, "y_axis needs to between -1 and IMPLOT_Y_AXES"); IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "GetPlotMousePos() needs to be called between BeginPlot() and EndPlot()!"); - const int y_axis = y_axis_in >= 0 ? y_axis_in : gp.CurrentPlot->CurrentYAxis; + const ImPlotYAxis y_axis = y_axis_in >= 0 ? y_axis_in : gp.CurrentPlot->CurrentYAxis; return gp.MousePos[y_axis]; } -ImPlotLimits GetPlotLimits(int y_axis_in) { +ImPlotLimits GetPlotLimits(ImPlotYAxis y_axis_in) { ImPlotContext& gp = *GImPlot; IM_ASSERT_USER_ERROR(y_axis_in >= -1 && y_axis_in < IMPLOT_Y_AXES, "y_axis needs to between -1 and IMPLOT_Y_AXES"); IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "GetPlotLimits() needs to be called between BeginPlot() and EndPlot()!"); - const int y_axis = y_axis_in >= 0 ? y_axis_in : gp.CurrentPlot->CurrentYAxis; + const ImPlotYAxis y_axis = y_axis_in >= 0 ? y_axis_in : gp.CurrentPlot->CurrentYAxis; ImPlotPlot& plot = *gp.CurrentPlot; ImPlotLimits limits; @@ -2489,12 +2572,12 @@ bool IsPlotQueried() { return gp.CurrentPlot->Queried; } -ImPlotLimits GetPlotQuery(int y_axis_in) { +ImPlotLimits GetPlotQuery(ImPlotYAxis y_axis_in) { ImPlotContext& gp = *GImPlot; IM_ASSERT_USER_ERROR(y_axis_in >= -1 && y_axis_in < IMPLOT_Y_AXES, "y_axis needs to between -1 and IMPLOT_Y_AXES"); IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "GetPlotQuery() needs to be called between BeginPlot() and EndPlot()!"); ImPlotPlot& plot = *gp.CurrentPlot; - const int y_axis = y_axis_in >= 0 ? y_axis_in : gp.CurrentPlot->CurrentYAxis; + const ImPlotYAxis y_axis = y_axis_in >= 0 ? y_axis_in : gp.CurrentPlot->CurrentYAxis; UpdateTransformCache(); ImPlotPoint p1 = PixelsToPlot(plot.QueryRect.Min + gp.BB_Plot.Min, y_axis); @@ -2694,6 +2777,21 @@ bool DragPoint(const char* id, double* x, double* y, bool show_label, const ImVe return dragging; } +void SetLegendLocation(ImPlotLocation location, ImPlotOrientation orientation, bool outside) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "SetLegendLocation() needs to be called between BeginPlot() and EndPlot()!"); + gp.CurrentPlot->LegendLocation = location; + gp.CurrentPlot->LegendOrientation = orientation; + if (gp.CurrentPlot->LegendOutside != outside) + gp.CurrentPlot->LegendFlipSide = true; +} + +void SetMousePosLocation(ImPlotLocation location) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "SetMousePosLocation() needs to be called between BeginPlot() and EndPlot()!"); + gp.CurrentPlot->MousePosLocation = location; +} + bool IsLegendEntryHovered(const char* label_id) { ImPlotContext& gp = *GImPlot; IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "IsPlotItemHighlight() needs to be called between BeginPlot() and EndPlot()!"); @@ -2790,6 +2888,42 @@ void EndLegendPopup() { ImGui::EndPopup(); } +void ShowAltLegend(const char* title_id, ImPlotOrientation orientation, const ImVec2 size, bool interactable) { + ImPlotContext& gp = *GImPlot; + ImGuiContext &G = *GImGui; + ImGuiWindow * Window = G.CurrentWindow; + if (Window->SkipItems) + return; + ImDrawList &DrawList = *Window->DrawList; + ImPlotPlot* plot = GetPlot(title_id); + ImVec2 legend_size; + ImVec2 default_size = gp.Style.LegendPadding * 2; + if (plot != NULL) { + legend_size = CalcLegendSize(plot->Legend, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, orientation); + default_size = legend_size + gp.Style.LegendPadding * 2; + } + ImVec2 frame_size = ImGui::CalcItemSize(size, default_size.x, default_size.y); + ImRect bb_frame = ImRect(Window->DC.CursorPos, Window->DC.CursorPos + frame_size); + ImGui::ItemSize(bb_frame); + if (!ImGui::ItemAdd(bb_frame, 0, &bb_frame)) + return; + ImGui::RenderFrame(bb_frame.Min, bb_frame.Max, GetStyleColorU32(ImPlotCol_FrameBg), true, G.Style.FrameRounding); + DrawList.PushClipRect(bb_frame.Min, bb_frame.Max, true); + if (plot != NULL) { + const ImVec2 legend_pos = GetLocationPos(bb_frame, legend_size, 0, gp.Style.LegendPadding); + const ImRect legend_bb(legend_pos, legend_pos + legend_size); + interactable = interactable && bb_frame.Contains(ImGui::GetIO().MousePos); + // render legend box + ImU32 col_bg = GetStyleColorU32(ImPlotCol_LegendBg); + ImU32 col_bd = GetStyleColorU32(ImPlotCol_LegendBorder); + DrawList.AddRectFilled(legend_bb.Min, legend_bb.Max, col_bg); + DrawList.AddRect(legend_bb.Min, legend_bb.Max, col_bd); + // render entries + ShowLegendEntries(plot->Legend, legend_bb, interactable, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, orientation, DrawList); + } + DrawList.PopClipRect(); +} + //----------------------------------------------------------------------------- // STYLING //----------------------------------------------------------------------------- @@ -3154,7 +3288,7 @@ void ShowColormapScale(double scale_min, double scale_max, float height) { ImGui::ItemSize(bb_frame); if (!ImGui::ItemAdd(bb_frame, 0, &bb_frame)) return; - ImGui::RenderFrame(bb_frame.Min, bb_frame.Max, GetStyleColorU32(ImPlotCol_FrameBg)); + 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, height - gp.Style.PlotPadding.y)); int num_cols = GetColormapSize(); @@ -3280,12 +3414,15 @@ void ShowStyleEditor(ImPlotStyle* ref) { ImGui::SliderFloat2("MinorTickSize", (float*)&style.MinorTickSize, 0.0f, 2.0f, "%.1f"); ImGui::SliderFloat2("MajorGridSize", (float*)&style.MajorGridSize, 0.0f, 2.0f, "%.1f"); ImGui::SliderFloat2("MinorGridSize", (float*)&style.MinorGridSize, 0.0f, 2.0f, "%.1f"); + ImGui::SliderFloat2("PlotDefaultSize", (float*)&style.PlotDefaultSize, 0.0f, 1000, "%.0f"); ImGui::SliderFloat2("PlotMinSize", (float*)&style.PlotMinSize, 0.0f, 300, "%.0f"); ImGui::Text("Plot Padding"); ImGui::SliderFloat2("PlotPadding", (float*)&style.PlotPadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2("LabelPadding", (float*)&style.LabelPadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2("LegendPadding", (float*)&style.LegendPadding, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("InfoPadding", (float*)&style.InfoPadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("LegendInnerPadding", (float*)&style.LegendInnerPadding, 0.0f, 10.0f, "%.0f"); + ImGui::SliderFloat2("LegendSpacing", (float*)&style.LegendSpacing, 0.0f, 5.0f, "%.0f"); + ImGui::SliderFloat2("MousePosPadding", (float*)&style.MousePosPadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2("AnnotationPadding", (float*)&style.AnnotationPadding, 0.0f, 5.0f, "%.0f"); ImGui::EndTabItem(); } diff --git a/implot.h b/implot.h index d4cdd3b..a87c590 100644 --- a/implot.h +++ b/implot.h @@ -58,6 +58,9 @@ typedef int ImPlotCol; // -> enum ImPlotCol_ typedef int ImPlotStyleVar; // -> enum ImPlotStyleVar_ typedef int ImPlotMarker; // -> enum ImPlotMarker_ typedef int ImPlotColormap; // -> enum ImPlotColormap_ +typedef int ImPlotLocation; // -> enum ImPlotLocation_ +typedef int ImPlotOrientation; // -> enum ImPlotOrientation_ +typedef int ImPlotYAxis; // -> enum ImPlotYAxis_; // Options for plots. enum ImPlotFlags_ { @@ -65,7 +68,7 @@ enum ImPlotFlags_ { ImPlotFlags_NoLegend = 1 << 0, // the top-left legend will not be displayed ImPlotFlags_NoMenus = 1 << 1, // the user will not be able to open context menus with double-right click ImPlotFlags_NoBoxSelect = 1 << 2, // the user will not be able to box-select with right-mouse - ImPlotFlags_NoMousePos = 1 << 3, // the mouse position, in plot coordinates, will not be displayed in the bottom-right + ImPlotFlags_NoMousePos = 1 << 3, // the mouse position, in plot coordinates, will not be displayed inside of the plot ImPlotFlags_NoHighlight = 1 << 4, // plot items will not be highlighted when their legend entry is hovered ImPlotFlags_NoChild = 1 << 5, // a child window region will not be used to capture mouse scroll (can boost performance for single ImGui window applications) ImPlotFlags_YAxis2 = 1 << 6, // enable a 2nd y-axis on the right side @@ -125,30 +128,33 @@ enum ImPlotCol_ { // Plot styling variables. enum ImPlotStyleVar_ { // item styling variables - ImPlotStyleVar_LineWeight, // float, plot item line weight in pixels - ImPlotStyleVar_Marker, // int, marker specification - ImPlotStyleVar_MarkerSize, // float, marker size in pixels (roughly the marker's "radius") - ImPlotStyleVar_MarkerWeight, // float, plot outline weight of markers in pixels - ImPlotStyleVar_FillAlpha, // float, alpha modifier applied to all plot item fills - ImPlotStyleVar_ErrorBarSize, // float, error bar whisker width in pixels - ImPlotStyleVar_ErrorBarWeight, // float, error bar whisker weight in pixels - ImPlotStyleVar_DigitalBitHeight, // float, digital channels bit height (at 1) in pixels - ImPlotStyleVar_DigitalBitGap, // float, digital channels bit padding gap in pixels + ImPlotStyleVar_LineWeight, // float, plot item line weight in pixels + ImPlotStyleVar_Marker, // int, marker specification + ImPlotStyleVar_MarkerSize, // float, marker size in pixels (roughly the marker's "radius") + ImPlotStyleVar_MarkerWeight, // float, plot outline weight of markers in pixels + ImPlotStyleVar_FillAlpha, // float, alpha modifier applied to all plot item fills + ImPlotStyleVar_ErrorBarSize, // float, error bar whisker width in pixels + ImPlotStyleVar_ErrorBarWeight, // float, error bar whisker weight in pixels + ImPlotStyleVar_DigitalBitHeight, // float, digital channels bit height (at 1) in pixels + ImPlotStyleVar_DigitalBitGap, // float, digital channels bit padding gap in pixels // plot styling variables - ImPlotStyleVar_PlotBorderSize, // float, thickness of border around plot area - ImPlotStyleVar_MinorAlpha, // float, alpha multiplier applied to minor axis grid lines - ImPlotStyleVar_MajorTickLen, // ImVec2, major tick lengths for X and Y axes - ImPlotStyleVar_MinorTickLen, // ImVec2, minor tick lengths for X and Y axes - ImPlotStyleVar_MajorTickSize, // ImVec2, line thickness of major ticks - ImPlotStyleVar_MinorTickSize, // ImVec2, line thickness of minor ticks - ImPlotStyleVar_MajorGridSize, // ImVec2, line thickness of major grid lines - ImPlotStyleVar_MinorGridSize, // ImVec2, line thickness of minor grid lines - ImPlotStyleVar_PlotPadding, // ImVec2, padding between widget frame and plot area and/or labels - ImPlotStyleVar_LabelPadding, // ImVec2, padding between axes labels, tick labels, and plot edge - ImPlotStyleVar_LegendPadding, // ImVec2, legend padding from top-left of plot - ImPlotStyleVar_InfoPadding, // ImVec2, padding between plot edge and interior info text - ImPlotStyleVar_AnnotationPadding, // ImVec2, text padding around annotation labels - ImPlotStyleVar_PlotMinSize, // ImVec2, minimum size plot frame can be when shrunk + ImPlotStyleVar_PlotBorderSize, // float, thickness of border around plot area + ImPlotStyleVar_MinorAlpha, // float, alpha multiplier applied to minor axis grid lines + ImPlotStyleVar_MajorTickLen, // ImVec2, major tick lengths for X and Y axes + ImPlotStyleVar_MinorTickLen, // ImVec2, minor tick lengths for X and Y axes + ImPlotStyleVar_MajorTickSize, // ImVec2, line thickness of major ticks + ImPlotStyleVar_MinorTickSize, // ImVec2, line thickness of minor ticks + ImPlotStyleVar_MajorGridSize, // ImVec2, line thickness of major grid lines + ImPlotStyleVar_MinorGridSize, // ImVec2, line thickness of minor grid lines + ImPlotStyleVar_PlotPadding, // ImVec2, padding between widget frame and plot area, labels, or outside legends (i.e. main padding) + ImPlotStyleVar_LabelPadding, // ImVec2, padding between axes labels, tick labels, and plot edge + ImPlotStyleVar_LegendPadding, // ImVec2, legend padding from plot edges + ImPlotStyleVar_LegendInnerPadding, // ImVec2, legend inner padding from legend edges + ImPlotStyleVar_LegendSpacing, // ImVec2, spacing between legend entries + ImPlotStyleVar_MousePosPadding, // ImVec2, padding between plot edge and interior info text + ImPlotStyleVar_AnnotationPadding, // ImVec2, text padding around annotation labels + ImPlotStyleVar_PlotDefaultSize, // ImVec2, default size used when ImVec2(0,0) is passed to BeginPlot + ImPlotStyleVar_PlotMinSize, // ImVec2, minimum size plot frame can be when shrunk ImPlotStyleVar_COUNT }; @@ -184,6 +190,32 @@ enum ImPlotColormap_ { ImPlotColormap_COUNT }; +// Used to position items on a plot (e.g. legends, labels, etc.) +enum ImPlotLocation_ { + ImPlotLocation_Center = 0, // center-center + ImPlotLocation_North = 1 << 0, // top-center + ImPlotLocation_South = 1 << 1, // bottom-center + ImPlotLocation_West = 1 << 2, // center-left + ImPlotLocation_East = 1 << 3, // center-right + ImPlotLocation_NorthWest = ImPlotLocation_North | ImPlotLocation_West, // top-left + ImPlotLocation_NorthEast = ImPlotLocation_North | ImPlotLocation_East, // top-right + ImPlotLocation_SouthWest = ImPlotLocation_South | ImPlotLocation_West, // bottom-left + ImPlotLocation_SouthEast = ImPlotLocation_South | ImPlotLocation_East // bottom-right +}; + +// Used to orient items on a plot (e.g. legends, labels, etc.) +enum ImPlotOrientation_ { + ImPlotOrientation_Horizontal, // left/right + ImPlotOrientation_Vertical // up/down +}; + +// Enums for different y-axes. +enum ImPlotYAxis_ { + ImPlotYAxis_1 = 0, // left (default) + ImPlotYAxis_2 = 1, // first on right side + ImPlotYAxis_3 = 2 // second on right side +}; + // Double precision version of ImVec2 used by ImPlot. Extensible by end users. struct ImPlotPoint { double x, y; @@ -235,11 +267,14 @@ struct ImPlotStyle { ImVec2 MinorTickSize; // = 1,1 line thickness of minor ticks ImVec2 MajorGridSize; // = 1,1 line thickness of major grid lines ImVec2 MinorGridSize; // = 1,1 line thickness of minor grid lines - ImVec2 PlotPadding; // = 8,8 padding between widget frame and plot area and/or labels + ImVec2 PlotPadding; // = 10,10 padding between widget frame and plot area, labels, or outside legends (i.e. main padding) ImVec2 LabelPadding; // = 5,5 padding between axes labels, tick labels, and plot edge - ImVec2 LegendPadding; // = 10,10 legend padding from top-left of plot - ImVec2 InfoPadding; // = 10,10 padding between plot edge and interior info text + 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 MousePosPadding; // = 10,10 padding between plot edge and interior mouse location text ImVec2 AnnotationPadding; // = 2,2 text padding around annotation labels + 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 @@ -247,7 +282,7 @@ struct ImPlotStyle { 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 bool UseISO8601; // = false, dates will be formatted according to ISO 8601 where applicable (e.g. YYYY-MM-DD, YYYY-MM, --MM-DD, etc.) - bool Use24HourClock; // = false, times will be formatted using 24 hour clock + bool Use24HourClock; // = false, times will be formatted using a 24 hour clock IMPLOT_API ImPlotStyle(); }; @@ -403,6 +438,9 @@ 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) +IMPLOT_API void PlotDummy(const char* label_id); + //----------------------------------------------------------------------------- // Plot Utils //----------------------------------------------------------------------------- @@ -414,7 +452,7 @@ IMPLOT_API void SetNextPlotLimits(double xmin, double xmax, double ymin, double // Set the X axis range limits of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the X axis limits will be locked. IMPLOT_API void SetNextPlotLimitsX(double xmin, double xmax, ImGuiCond cond = ImGuiCond_Once); // Set the Y axis range limits of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the Y axis limits will be locked. -IMPLOT_API void SetNextPlotLimitsY(double ymin, double ymax, ImGuiCond cond = ImGuiCond_Once, int y_axis = 0); +IMPLOT_API void SetNextPlotLimitsY(double ymin, double ymax, ImGuiCond cond = ImGuiCond_Once, ImPlotYAxis y_axis = 0); // Links the next plot limits to external values. Set to NULL for no linkage. The pointer data must remain valid until the matching call EndPlot. IMPLOT_API void LinkNextPlotLimits(double* xmin, double* xmax, double* ymin, double* ymax, double* ymin2 = NULL, double* ymax2 = NULL, double* ymin3 = NULL, double* ymax3 = NULL); // Fits the next plot axes to all plotted data if they are unlocked (equivalent to double-clicks). @@ -425,22 +463,22 @@ IMPLOT_API void SetNextPlotTicksX(const double* values, int n_ticks, const char* IMPLOT_API void SetNextPlotTicksX(double x_min, double x_max, int n_ticks, const char* const labels[] = NULL, bool show_default = false); // Set the Y axis ticks and optionally the labels for the next plot. -IMPLOT_API void SetNextPlotTicksY(const double* values, int n_ticks, const char* const labels[] = NULL, bool show_default = false, int y_axis = 0); -IMPLOT_API void SetNextPlotTicksY(double y_min, double y_max, int n_ticks, const char* const labels[] = NULL, bool show_default = false, int y_axis = 0); +IMPLOT_API void SetNextPlotTicksY(const double* values, int n_ticks, const char* const labels[] = NULL, bool show_default = false, ImPlotYAxis y_axis = 0); +IMPLOT_API void SetNextPlotTicksY(double y_min, double y_max, int n_ticks, const char* const labels[] = NULL, bool show_default = false, ImPlotYAxis y_axis = 0); // The following functions MUST be called between Begin/EndPlot! -// Select which Y axis will be used for subsequent plot elements. The default is '0', or the first (left) Y axis. Enable 2nd and 3rd axes with ImPlotFlags_YAxisX. -IMPLOT_API void SetPlotYAxis(int y_axis); -// Hides or shows the next plot item (i.e. as if it were toggled from the legend). Use ImGuiCond_Always if you need to change this every frame. +// Select which Y axis will be used for subsequent plot elements. The default is ImPlotYAxis_1, or the first (left) Y axis. Enable 2nd and 3rd axes with ImPlotFlags_YAxisX. +IMPLOT_API void SetPlotYAxis(ImPlotYAxis y_axis); +// Hides or shows the next plot item (i.e. as if it were toggled from the legend). Use ImGuiCond_Always if you need to forcefully set this every frame. IMPLOT_API void HideNextItem(bool hidden = true, ImGuiCond cond = ImGuiCond_Once); -// Convert pixels to a position in the current plot's coordinate system. A negative y_axis uses the current value of SetPlotYAxis (0 initially). -IMPLOT_API ImPlotPoint PixelsToPlot(const ImVec2& pix, int y_axis = IMPLOT_AUTO); -IMPLOT_API ImPlotPoint PixelsToPlot(float x, float y, int y_axis = IMPLOT_AUTO); -// Convert a position in the current plot's coordinate system to pixels. A negative y_axis uses the current value of SetPlotYAxis (0 initially). -IMPLOT_API ImVec2 PlotToPixels(const ImPlotPoint& plt, int y_axis = IMPLOT_AUTO); -IMPLOT_API ImVec2 PlotToPixels(double x, double y, int y_axis = IMPLOT_AUTO); +// Convert pixels to a position in the current plot's coordinate system. A negative y_axis uses the current value of SetPlotYAxis (ImPlotYAxis_1 initially). +IMPLOT_API ImPlotPoint PixelsToPlot(const ImVec2& pix, ImPlotYAxis y_axis = IMPLOT_AUTO); +IMPLOT_API ImPlotPoint PixelsToPlot(float x, float y, ImPlotYAxis y_axis = IMPLOT_AUTO); +// Convert a position in the current plot's coordinate system to pixels. A negative y_axis uses the current value of SetPlotYAxis (ImPlotYAxis_1 initially). +IMPLOT_API ImVec2 PlotToPixels(const ImPlotPoint& plt, ImPlotYAxis y_axis = IMPLOT_AUTO); +IMPLOT_API ImVec2 PlotToPixels(double x, double y, ImPlotYAxis y_axis = IMPLOT_AUTO); // Get the current Plot position (top-left) in pixels. IMPLOT_API ImVec2 GetPlotPos(); // Get the curent Plot size in pixels. @@ -450,21 +488,23 @@ IMPLOT_API bool IsPlotHovered(); // Returns true if the XAxis plot area in the current plot is hovered. IMPLOT_API bool IsPlotXAxisHovered(); // Returns true if the YAxis[n] plot area in the current plot is hovered. -IMPLOT_API bool IsPlotYAxisHovered(int y_axis = 0); -// Returns the mouse position in x,y coordinates of the current plot. A negative y_axis uses the current value of SetPlotYAxis (0 initially). -IMPLOT_API ImPlotPoint GetPlotMousePos(int y_axis = IMPLOT_AUTO); -// Returns the current plot axis range. A negative y_axis uses the current value of SetPlotYAxis (0 initially). -IMPLOT_API ImPlotLimits GetPlotLimits(int y_axis = IMPLOT_AUTO); +IMPLOT_API bool IsPlotYAxisHovered(ImPlotYAxis y_axis = 0); +// Returns the mouse position in x,y coordinates of the current plot. A negative y_axis uses the current value of SetPlotYAxis (ImPlotYAxis_1 initially). +IMPLOT_API ImPlotPoint GetPlotMousePos(ImPlotYAxis y_axis = IMPLOT_AUTO); +// Returns the current plot axis range. A negative y_axis uses the current value of SetPlotYAxis (ImPlotYAxis_1 initially). +IMPLOT_API ImPlotLimits GetPlotLimits(ImPlotYAxis y_axis = IMPLOT_AUTO); // Returns true if the current plot is being queried. Query must be enabled with ImPlotFlags_Query. IMPLOT_API bool IsPlotQueried(); // Returns the current plot query bounds. Query must be enabled with ImPlotFlags_Query. -IMPLOT_API ImPlotLimits GetPlotQuery(int y_axis = IMPLOT_AUTO); +IMPLOT_API ImPlotLimits GetPlotQuery(ImPlotYAxis y_axis = IMPLOT_AUTO); //----------------------------------------------------------------------------- // Plot Tools //----------------------------------------------------------------------------- +// The following functions MUST be called between Begin/EndPlot! + // Shows an annotation callout at a chosen point. IMPLOT_API void Annotate(double x, double y, const ImVec2& pix_offset, const char* fmt, ...) IM_FMTARGS(4); IMPLOT_API void Annotate(double x, double y, const ImVec2& pix_offset, const ImVec4& color, const char* fmt, ...) IM_FMTARGS(5); @@ -488,6 +528,12 @@ IMPLOT_API bool DragPoint(const char* id, double* x, double* y, bool show_label // Legend Utils and Tools //----------------------------------------------------------------------------- +// The following functions MUST be called between Begin/EndPlot! + +// Set the location of the current plot's legend. +IMPLOT_API void SetLegendLocation(ImPlotLocation location, ImPlotOrientation orientation = ImPlotOrientation_Vertical, bool outside = false); +// Set the locaton of the current plot's mouse position text (default = South|East). +IMPLOT_API void SetMousePosLocation(ImPlotLocation location); // Returns true if a plot item legend entry is hovered. IMPLOT_API bool IsLegendEntryHovered(const char* label_id); // Begin a drag and drop source from a legend entry. The only supported flag is SourceNoPreviewTooltip diff --git a/implot_demo.cpp b/implot_demo.cpp index 8a87bab..5768acb 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -350,11 +350,13 @@ void ShowDemoWindow(bool* p_open) { ImVec2(-1,0), 0, 0, horz ? ImPlotAxisFlags_Invert : 0)) { if (horz) { + ImPlot::SetLegendLocation(ImPlotLocation_West, ImPlotOrientation_Vertical); ImPlot::PlotBarsH("Midterm Exam", midtm, 10, 0.2, -0.2); ImPlot::PlotBarsH("Final Exam", final, 10, 0.2, 0); ImPlot::PlotBarsH("Course Grade", grade, 10, 0.2, 0.2); } else { + ImPlot::SetLegendLocation(ImPlotLocation_South, ImPlotOrientation_Horizontal); ImPlot::PlotBars("Midterm Exam", midtm, 10, 0.2, -0.2); ImPlot::PlotBars("Final Exam", final, 10, 0.2, 0); ImPlot::PlotBars("Course Grade", grade, 10, 0.2, 0.2); @@ -686,11 +688,11 @@ void ShowDemoWindow(bool* p_open) { ImPlot::PlotLine("f(x) = x", xs, xs, 1001); ImPlot::PlotLine("f(x) = sin(x)*3+1", xs, ys1, 1001); if (y2_axis) { - ImPlot::SetPlotYAxis(1); + ImPlot::SetPlotYAxis(ImPlotYAxis_2); ImPlot::PlotLine("f(x) = cos(x)*.2+.5 (Y2)", xs, ys2, 1001); } if (y3_axis) { - ImPlot::SetPlotYAxis(2); + ImPlot::SetPlotYAxis(ImPlotYAxis_3); ImPlot::PlotLine("f(x) = sin(x+.5)*100+200 (Y3)", xs2, ys3, 1001); } ImPlot::EndPlot(); @@ -797,6 +799,41 @@ void ShowDemoWindow(bool* p_open) { } } //------------------------------------------------------------------------- + if (ImGui::CollapsingHeader("Legend")) { + static bool n = false; static bool s = false; static bool w = false; static bool e = true; + static bool h = false; static bool o = true; + + ImGui::Checkbox("North", &n); ImGui::SameLine(); + ImGui::Checkbox("South", &s); ImGui::SameLine(); + ImGui::Checkbox("West", &w); ImGui::SameLine(); + ImGui::Checkbox("East", &e); ImGui::SameLine(); + ImGui::Checkbox("Horizontal", &h); ImGui::SameLine(); + ImGui::Checkbox("Outside", &o); + + ImPlotLocation loc = 0; + loc = n ? loc | ImPlotLocation_North : loc; + loc = s ? loc | ImPlotLocation_South : loc; + loc = w ? loc | ImPlotLocation_West : loc; + loc = e ? loc | ImPlotLocation_East : loc; + + ImGui::SliderFloat2("LegendPadding", (float*)&GetStyle().LegendPadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("LegendInnerPadding", (float*)&GetStyle().LegendInnerPadding, 0.0f, 10.0f, "%.0f"); + ImGui::SliderFloat2("LegendSpacing", (float*)&GetStyle().LegendSpacing, 0.0f, 5.0f, "%.0f"); + + if (ImPlot::BeginPlot("##Legend","x","y",ImVec2(-1,0))) { + ImPlot::SetLegendLocation(loc, h ? ImPlotOrientation_Horizontal : ImPlotOrientation_Vertical, o); + static MyImPlot::WaveData data1(0.001, 0.2, 2, 0.75); + static MyImPlot::WaveData data2(0.001, 0.2, 4, 0.25); + static MyImPlot::WaveData data3(0.001, 0.2, 6, 0.5); + ImPlot::PlotLineG("Item 1", MyImPlot::SineWave, &data1, 1000); // "Item 1" added to legend + ImPlot::PlotLineG("Item 2##IDText", MyImPlot::SawWave, &data2, 1000); // "Item 2" added to legend, text after ## used for ID only + ImPlot::PlotLineG("##NotDisplayed", MyImPlot::SawWave, &data3, 1000); // plotted, but not added to legend + ImPlot::PlotLineG("Item 3", MyImPlot::SineWave, &data1, 1000); // "Item 3" added to legend + ImPlot::PlotLineG("Item 3", MyImPlot::SawWave, &data2, 1000); // combined with previous "Item 3" + ImPlot::EndPlot(); + } + } + //------------------------------------------------------------------------- if (ImGui::CollapsingHeader("Drag Lines and Points")) { ImGui::BulletText("Click and drag the horizontal and vertical lines."); static double x1 = 0.2; @@ -817,7 +854,7 @@ void ShowDemoWindow(bool* p_open) { ys[i] = (y1+y2)/2+abs(y2-y1)/2*sin(f*i/10); } ImPlot::PlotLine("Interactive Data", xs, ys, 1000); - ImPlot::SetPlotYAxis(1); + ImPlot::SetPlotYAxis(ImPlotYAxis_2); ImPlot::DragLineY("f",&f,show_labels,ImVec4(1,0.5f,1,1)); ImPlot::EndPlot(); } @@ -1402,7 +1439,7 @@ void StyleSeaborn() { style.PlotPadding = ImVec2(12,12); style.LabelPadding = ImVec2(5,5); style.LegendPadding = ImVec2(5,5); - style.InfoPadding = ImVec2(5,5); + style.MousePosPadding = ImVec2(5,5); style.PlotMinSize = ImVec2(300,225); } @@ -1639,4 +1676,4 @@ void ShowBenchmarkTool() { } } -} +} \ No newline at end of file diff --git a/implot_internal.h b/implot_internal.h index 0890687..78b31c4 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -51,6 +51,7 @@ struct ImPlotAxis; struct ImPlotAxisState; struct ImPlotAxisColor; struct ImPlotItem; +struct ImPlotLegend; struct ImPlotPlot; struct ImPlotNextPlotData; @@ -67,20 +68,16 @@ extern IMPLOT_API ImPlotContext* GImPlot; // Current implicit context pointer // Constants can be changed unless stated otherwise. We may move some of these // to ImPlotStyleVar_ over time. -// Default plot frame width when requested width is auto (i.e. 0). This is not the plot area width! -#define IMPLOT_DEFAULT_W 400 -// Default plot frame height when requested height is auto (i.e. 0). This is not the plot area height! -#define IMPLOT_DEFAULT_H 300 // The maximum number of supported y-axes (DO NOT CHANGE THIS) -#define IMPLOT_Y_AXES 3 +#define IMPLOT_Y_AXES 3 // The number of times to subdivided grid divisions (best if a multiple of 1, 2, and 5) -#define IMPLOT_SUB_DIV 10 +#define IMPLOT_SUB_DIV 10 // Zoom rate for scroll (e.g. 0.1f = 10% plot range every scroll click) -#define IMPLOT_ZOOM_RATE 0.1f +#define IMPLOT_ZOOM_RATE 0.1f // Mimimum allowable timestamp value 01/01/1970 @ 12:00am (UTC) (DO NOT DECREASE THIS) -#define IMPLOT_MIN_TIME 0 +#define IMPLOT_MIN_TIME 0 // Maximum allowable timestamp value 01/01/3000 @ 12:00am (UTC) (DO NOT INCREASE THIS) -#define IMPLOT_MAX_TIME 32503680000 +#define IMPLOT_MAX_TIME 32503680000 //----------------------------------------------------------------------------- // [SECTION] Generic Helpers @@ -158,17 +155,10 @@ struct ImPlotPointArray { // [SECTION] ImPlot Enums //----------------------------------------------------------------------------- -typedef int ImPlotDirection; // -> enum ImPlotDirection_ -typedef int ImPlotScale; // -> enum ImPlotScale_ -typedef int ImPlotTimeUnit; // -> enum ImPlotTimeUnit_ -typedef int ImPlotDateFmt; // -> enum ImPlotDateFmt_ -typedef int ImPlotTimeFmt; // -> enum ImPlotTimeFmt_ - -// Axis direction -enum ImPlotDirection_ { - ImPlotDirection_Horizontal, // left/right - ImPlotDirection_Vertical // up/down -}; +typedef int ImPlotScale; // -> enum ImPlotScale_ +typedef int ImPlotTimeUnit; // -> enum ImPlotTimeUnit_ +typedef int ImPlotDateFmt; // -> enum ImPlotDateFmt_ +typedef int ImPlotTimeFmt; // -> enum ImPlotTimeFmt_ // XY axes scaling combinations enum ImPlotScale_ { @@ -195,7 +185,7 @@ enum ImPlotDateFmt_ { // default [ ISO 8601 ] ImPlotDateFmt_DayMo, // 10/3 [ --10-03 ] ImPlotDateFmt_DayMoYr, // 10/3/91 [ 1991-10-03 ] ImPlotDateFmt_MoYr, // Oct 1991 [ 1991-10 ] - ImPlotDateFmt_Mo, // Oct [ --10-01 ] + ImPlotDateFmt_Mo, // Oct [ --10 ] ImPlotDateFmt_Yr // 1991 [ 1991 ] }; @@ -392,7 +382,7 @@ struct ImPlotAxis ImPlotAxisFlags Flags; ImPlotAxisFlags PreviousFlags; ImPlotRange Range; - ImPlotDirection Direction; + ImPlotOrientation Direction; bool Dragging; bool HoveredExt; bool HoveredTot; @@ -524,6 +514,20 @@ struct ImPlotItem ~ImPlotItem() { ID = 0; } }; +// Holds Legend state labels and item references +struct ImPlotLegend +{ + ImPlotPlot* Plot; + ImVector Indices; + ImGuiTextBuffer Labels; + + ImPlotLegend(ImPlotPlot* plot) { Plot = plot; } + void Reset() { Indices.shrink(0); Labels.Buf.shrink(0); } + int Count() const { return Indices.size(); } + ImPlotItem* GetItem(int i); + const char* GetLabel(int i); +}; + // Holds Plot state information that must persist after EndPlot struct ImPlotPlot { @@ -531,27 +535,37 @@ struct ImPlotPlot ImPlotFlags PreviousFlags; ImPlotAxis XAxis; ImPlotAxis YAxis[IMPLOT_Y_AXES]; + ImPlotLegend Legend; ImPool Items; ImVec2 SelectStart; ImVec2 QueryStart; ImRect QueryRect; - ImRect BB_Legend; bool Selecting; bool Querying; bool Queried; bool DraggingQuery; + bool LegendHovered; + bool LegendOutside; + bool LegendFlipSide; int ColormapIdx; int CurrentYAxis; + ImPlotLocation MousePosLocation; + ImPlotLocation LegendLocation; + ImPlotOrientation LegendOrientation; - ImPlotPlot() { + ImPlotPlot() : Legend(this) { Flags = PreviousFlags = ImPlotFlags_None; - XAxis.Direction = ImPlotDirection_Horizontal; + XAxis.Direction = ImPlotOrientation_Horizontal; for (int i = 0; i < IMPLOT_Y_AXES; ++i) - YAxis[i].Direction = ImPlotDirection_Vertical; - SelectStart = QueryStart = ImVec2(0,0); - Selecting = Querying = Queried = DraggingQuery = false; - ColormapIdx = CurrentYAxis = 0; + YAxis[i].Direction = ImPlotOrientation_Vertical; + SelectStart = QueryStart = ImVec2(0,0); + Selecting = Querying = Queried = DraggingQuery = LegendHovered = LegendOutside = LegendFlipSide = false; + ColormapIdx = CurrentYAxis = 0; + LegendLocation = ImPlotLocation_North | ImPlotLocation_West; + LegendOrientation = ImPlotOrientation_Vertical; + MousePosLocation = ImPlotLocation_South | ImPlotLocation_East; } + }; // Temporary data storage for upcoming plot @@ -619,17 +633,16 @@ struct ImPlotContext { // Plot States ImPool Plots; ImPlotPlot* CurrentPlot; - ImPlotItem* CurrentItem; - ImPlotItem* PreviousItem; - - // Legend - ImVector LegendIndices; - ImGuiTextBuffer LegendLabels; + ImPlotItem* CurrentItem; + ImPlotItem* PreviousItem; // Bounding Boxes ImRect BB_Frame; ImRect BB_Canvas; ImRect BB_Plot; + ImRect BB_Axes; + ImRect BB_X; + ImRect BB_Y[IMPLOT_Y_AXES]; // Axis States ImPlotAxisColor Col_X; @@ -743,15 +756,11 @@ IMPLOT_API bool BeginItem(const char* label_id, ImPlotCol recolor_from = -1); // Ends an item (call only if BeginItem returns true). Pops PlotClipRect. IMPLOT_API void EndItem(); -// Register or get an existing item from the current plot +// Register or get an existing item from the current plot. IMPLOT_API ImPlotItem* RegisterOrGetItem(const char* label_id, bool* just_created = NULL); -// Get the ith plot item from the current plot -IMPLOT_API ImPlotItem* GetItem(int i); -// Get a plot item from the current plot +// Get a plot item from the current plot. IMPLOT_API ImPlotItem* GetItem(const char* label_id); -// Gets a plot item from a specific plot -IMPLOT_API ImPlotItem* GetItem(const char* plot_title, const char* item_label_id); -// Gets the current item +// Gets the current item. IMPLOT_API ImPlotItem* GetCurrentItem(); // Busts the cache for every item for every plot in the current context. IMPLOT_API void BustItemCache(); @@ -791,10 +800,14 @@ IMPLOT_API void ShowAxisContextMenu(ImPlotAxisState& state, bool time_allowed = // [SECTION] Legend Utils //----------------------------------------------------------------------------- -// Returns the number of entries in the current legend -IMPLOT_API int GetLegendCount(); -// Gets the ith entry string for the current legend -IMPLOT_API const char* GetLegendLabel(int i); +// Gets the position of an inner rect that is located inside of an outer rect according to an ImPlotLocation and padding amount. +IMPLOT_API ImVec2 GetLocationPos(const ImRect& outer_rect, const ImVec2& inner_size, ImPlotLocation location, const ImVec2& pad = ImVec2(0,0)); +// Calculates the bounding box size of a legend +IMPLOT_API ImVec2 CalcLegendSize(ImPlotLegend& legend, const ImVec2& pad, const ImVec2& spacing, ImPlotOrientation orientation); +// Renders legend entries into a bounding box +IMPLOT_API void ShowLegendEntries(ImPlotLegend& legend, const ImRect& legend_bb, bool interactable, const ImVec2& pad, const ImVec2& spacing, ImPlotOrientation orientation, ImDrawList& DrawList); +// Shows an alternate legend for the plot identified by #title_id, outside of the plot frame (can be called before or after of Begin/EndPlot but must occur in the same ImGui window!). +IMPLOT_API void ShowAltLegend(const char* title_id, ImPlotOrientation orientation = ImPlotOrientation_Vertical, const ImVec2 size = ImVec2(0,0), bool interactable = true); //----------------------------------------------------------------------------- // [SECTION] Tick Utils diff --git a/implot_items.cpp b/implot_items.cpp index a5f0c1e..953d32c 100644 --- a/implot_items.cpp +++ b/implot_items.cpp @@ -60,9 +60,9 @@ ImPlotItem* RegisterOrGetItem(const char* label_id, bool* just_created) { int idx = gp.CurrentPlot->Items.GetIndex(item); item->ID = id; if (ImGui::FindRenderedTextEnd(label_id, NULL) != label_id) { - gp.LegendIndices.push_back(idx); - item->NameOffset = gp.LegendLabels.size(); - gp.LegendLabels.append(label_id, label_id + strlen(label_id) + 1); + gp.CurrentPlot->Legend.Indices.push_back(idx); + item->NameOffset = gp.CurrentPlot->Legend.Labels.size(); + gp.CurrentPlot->Legend.Labels.append(label_id, label_id + strlen(label_id) + 1); } else { item->Show = true; @@ -72,26 +72,12 @@ ImPlotItem* RegisterOrGetItem(const char* label_id, bool* just_created) { return item; } -ImPlotItem* GetItem(int i) { - ImPlotContext& gp = *GImPlot; - return gp.CurrentPlot->Items.GetByIndex(gp.LegendIndices[i]); -} - ImPlotItem* GetItem(const char* label_id) { ImPlotContext& gp = *GImPlot; ImGuiID id = ImGui::GetID(label_id); return gp.CurrentPlot->Items.GetByKey(id); } -ImPlotItem* GetItem(const char* plot_title, const char* item_label_id) { - ImPlotPlot* plot = GetPlot(plot_title); - if (plot) { - ImGuiID id = ImGui::GetID(item_label_id); - return plot->Items.GetByKey(id); - } - return NULL; -} - ImPlotItem* GetCurrentItem() { ImPlotContext& gp = *GImPlot; return gp.CurrentItem; @@ -1756,4 +1742,13 @@ void PlotText(const char* text, double x, double y, bool vertical, const ImVec2& PopPlotClipRect(); } +//----------------------------------------------------------------------------- +// PLOT DUMMY +//----------------------------------------------------------------------------- + +void PlotDummy(const char* label_id) { + if (BeginItem(label_id, ImPlotCol_Line)) + EndItem(); +} + } // namespace ImPlot \ No newline at end of file