diff --git a/README.md b/README.md index 5ea1fc0..4b8da34 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ # ImPlot -ImPlot is an immediate mode plotting widget for [Dear ImGui](https://github.com/ocornut/imgui). It aims to provide a first-class API that will make ImGui users feel right at home. ImPlot is well suited for visualizing program data in real-time and requires minimal code to integrate. Just like ImGui, it does not burden the end user with GUI state management, avoids STL containers and C++ headers, and has no external dependencies except for ImGui itself. +ImPlot is an immediate mode plotting widget for [Dear ImGui](https://github.com/ocornut/imgui). It aims to provide a first-class API that will make ImGui users feel right at home. ImPlot is well suited for visualizing program data in real-time and requires minimal code to integrate. Just like ImGui, it does not burden the end user with GUI state management, avoids STL containers and C++ headers, and has no external dependencies except for ImGui itself. - - + + - - + + ## Features -- multiple plot types: +- multiple plot types: - line plots - shaded plots - scatter plots @@ -32,7 +32,7 @@ ImPlot is an immediate mode plotting widget for [Dear ImGui](https://github.com/ - optional plot titles, axis labels, and grid labels - optional legend with toggle buttons to quickly show/hide items - size-aware grid with smart labels that are always power-of-ten multiples of 1, 2, and 5 -- default styling based on current ImGui theme, but most elements can be customized independently +- default styling based on current ImGui theme, but most elements can be customized independently - mouse cursor location display and optional crosshairs cursor - customizable data getters and data striding (just like ImGui:PlotLine) - relatively good performance for high density plots @@ -40,7 +40,7 @@ ImPlot is an immediate mode plotting widget for [Dear ImGui](https://github.com/ ## Usage -The API is used just like any other ImGui `BeginX`/`EndX` pair. First, start a plotting context with `ImPlot::BeginPlot()`. Next, plot as many items as you want with the provided `PlotX` functions (e.g. `PlotLine()`, `PlotBars()`, `PlotErrorBars()`, etc). Finally, wrap things up with a call to `ImPlot::EndPlot()`. That's it! +The API is used just like any other ImGui `BeginX`/`EndX` pair. First, start a new plot with `ImPlot::BeginPlot()`. Next, plot as many items as you want with the provided `PlotX` functions (e.g. `PlotLine()`, `PlotBars()`, `PlotErrorBars()`, etc). Finally, wrap things up with a call to `ImPlot::EndPlot()`. That's it! ```cpp if (ImPlot::BeginPlot("My Plot")) { @@ -51,22 +51,33 @@ if (ImPlot::BeginPlot("My Plot")) { } ``` -Consult `implot_demo.cpp` for a comprehensive example of ImPlot's features. +Consult `implot_demo.cpp` for a comprehensive example of ImPlot's features. ## Integration -Just add `implot.h`, `implot.cpp`, and optionally `implot_demo.cpp` to your sources. This assumes you already have an ImGui-ready environment. If not, consider trying [mahi-gui](https://github.com/mahilab/mahi-gui), which bundles ImGui, ImPlot, and several other packages for you. +1) Add `implot.h`, `implot_internal.h`, `implot.cpp`, `implot_items.cpp` and optionally `implot_demo.cpp` to your sources. +2) Create and destroy an `ImPlotContext` wherever you create your `ImGuiContext`: + +```cpp +ImGui::CreateContext(); +ImPlot::CreateContext(); +... +ImPlot::DestroyContext(); +ImGui::DestroyContext(); +``` + +Of course, this assumes you already have an ImGui-ready environment. If not, consider trying [mahi-gui](https://github.com/mahilab/mahi-gui), which bundles ImGui, ImPlot, and several other packages for you. ## Special Notes -- If you experience data truncation and/or visual glitches, it is **HIGHLY** recommended that you either: +- If you experience data truncation and/or visual glitches, it is **HIGHLY** recommended that you EITHER: 1) Handle the `ImGuiBackendFlags_RendererHasVtxOffset` flag in your renderer when using 16-bit indices (the official OpenGL3 renderer supports this) and use an ImGui version with patch [imgui@f6120f8](https://github.com/ocornut/imgui/commit/f6120f8e16eefcdb37b63974e6915a3dd35414be). 2) Enable 32-bit indices by uncommenting `#define ImDrawIdx unsigned int` in your `imconfig.h` file. - By default, no anti-aliasing is done on line plots for performance reasons. If you use 4x MSAA, then you likely won't even notice. However, you can re-enable AA with the `ImPlotFlags_AntiAliased` flag. ## Interactive Demo -An online version of the demo is hosted [here](https://traineq.org/implot_demo/src/implot_demo.html). You can view the plots and the source code that generated them. Note that this demo may not always be up to date and is not as performant as a desktop implemention, but it should give you a general taste of what's possible with ImPlot. Special thanks to [pthom](https://github.com/pthom) for creating and hosting this! +An online version of the demo is hosted [here](https://traineq.org/implot_demo/src/implot_demo.html). You can view the plots and the source code that generated them. Note that this demo may not always be up to date and is not as performant as a desktop implemention, but it should give you a general taste of what's possible with ImPlot. Special thanks to [pthom](https://github.com/pthom) for creating and hosting this! ## FAQ @@ -76,7 +87,7 @@ A: ImGui is an incredibly powerful tool for rapid prototyping and development, b **Q: Is ImPlot suitable for real-time plotting?** -A: Yes, within reason. You can plot tens to hundreds of thousands of points without issue, but don't expect plotting over a million to be a buttery smooth experience. We do our best to keep it fast and avoid memory allocations. +A: Yes, within reason. You can plot tens to hundreds of thousands of points without issue, but don't expect plotting over a million to be a buttery smooth experience. We do our best to keep it fast and avoid memory allocations. **Q: Can plot styles be modified?** @@ -104,7 +115,7 @@ A: See the note about anti-aliasing under **Special Notes** above. **Q: Does ImPlot provide analytic tools?** -A: Not exactly, but it does give you the ability to query plot sub-ranges, with which you can process your data however you like. +A: Not exactly, but it does give you the ability to query plot sub-ranges, with which you can process your data however you like. **Q: Can plots be exported/saved to image?** @@ -116,6 +127,6 @@ A: Yes, ImPlot accepts both `float` and `double` for all of its plotting functio **Q: Can ImPlot be used with other languages/bindings?** -A: Yes, you can use the C binding, [cimplot](https://github.com/cimgui/cimplot) with most high level languages. +A: Yes, you can use the C binding, [cimplot](https://github.com/cimgui/cimplot) with most high level languages. diff --git a/implot.cpp b/implot.cpp index 570eb90..eac3d09 100644 --- a/implot.cpp +++ b/implot.cpp @@ -57,43 +57,36 @@ You can read releases logs https://github.com/epezent/implot/releases for more d */ -#ifndef IMGUI_DEFINE_MATH_OPERATORS -#define IMGUI_DEFINE_MATH_OPERATORS -#endif - #include "implot.h" -#include "imgui_internal.h" +#include "implot_internal.h" + #ifdef _MSC_VER #define sprintf sprintf_s #endif -#define IM_NORMALIZE2F_OVER_ZERO(VX, VY) \ - { \ - float d2 = VX * VX + VY * VY; \ - if (d2 > 0.0f) { \ - float inv_len = 1.0f / ImSqrt(d2); \ - VX *= inv_len; \ - VY *= inv_len; \ - } \ - } +// Global plot context +ImPlotContext* GImPlot = NULL; +//----------------------------------------------------------------------------- +// Struct Implementations +//----------------------------------------------------------------------------- -// The maximum number of support y-axes -#define MAX_Y_AXES 3 - -// static inline float ImLog10(float x) { return log10f(x); } -static inline double ImLog10(double x) { return log10(x); } +ImPlotRange::ImPlotRange() { + Min = NAN; + Max = NAN; +} ImPlotStyle::ImPlotStyle() { - LineWeight = 1; - Marker = ImPlotMarker_None; - MarkerSize = 4; - MarkerWeight = 1; - FillAlpha = 1; - ErrorBarSize = 5; - ErrorBarWeight = 1.5; + + LineWeight = 1; + Marker = ImPlotMarker_None; + MarkerSize = 4; + MarkerWeight = 1; + FillAlpha = 1; + ErrorBarSize = 5; + ErrorBarWeight = 1.5f; DigitalBitHeight = 8; - DigitalBitGap = 4; + DigitalBitGap = 4; Colors[ImPlotCol_Line] = IMPLOT_COL_AUTO; Colors[ImPlotCol_Fill] = IMPLOT_COL_AUTO; @@ -111,590 +104,52 @@ ImPlotStyle::ImPlotStyle() { Colors[ImPlotCol_Query] = ImVec4(0,1,0,1); } -ImPlotRange::ImPlotRange() { - Min = NAN; - Max = NAN; -} - -bool ImPlotRange::Contains(double v) const { - return v >= Min && v <= Max; -} - -double ImPlotRange::Size() const { - return Max - Min; -} - -ImPlotLimits::ImPlotLimits() {} - -bool ImPlotLimits::Contains(const ImPlotPoint& p) const { - return Contains(p.x, p.y); -} - -bool ImPlotLimits::Contains(double x, double y) const { - return X.Contains(x) && Y.Contains(y); -} - ImPlotInputMap::ImPlotInputMap() { - PanButton = ImGuiMouseButton_Left; - PanMod = ImGuiKeyModFlags_None; - FitButton = ImGuiMouseButton_Left; - ContextMenuButton = ImGuiMouseButton_Right; - BoxSelectButton = ImGuiMouseButton_Right; - BoxSelectMod = ImGuiKeyModFlags_None; + PanButton = ImGuiMouseButton_Left; + PanMod = ImGuiKeyModFlags_None; + FitButton = ImGuiMouseButton_Left; + ContextMenuButton = ImGuiMouseButton_Right; + BoxSelectButton = ImGuiMouseButton_Right; + BoxSelectMod = ImGuiKeyModFlags_None; BoxSelectCancelButton = ImGuiMouseButton_Left; - QueryButton = ImGuiMouseButton_Middle; - QueryMod = ImGuiKeyModFlags_None; - QueryToggleMod = ImGuiKeyModFlags_Ctrl; - HorizontalMod = ImGuiKeyModFlags_Alt; - VerticalMod = ImGuiKeyModFlags_Shift; + QueryButton = ImGuiMouseButton_Middle; + QueryMod = ImGuiKeyModFlags_None; + QueryToggleMod = ImGuiKeyModFlags_Ctrl; + HorizontalMod = ImGuiKeyModFlags_Alt; + VerticalMod = ImGuiKeyModFlags_Shift; } - - -namespace { +namespace ImPlot { //----------------------------------------------------------------------------- -// Private Utils +// Generic Helpers //----------------------------------------------------------------------------- -template -struct OffsetCalculator { - OffsetCalculator(int* sizes) { - Offsets[0] = 0; - for (int i = 1; i < Count; ++i) - Offsets[i] = Offsets[i-1] + sizes[i-1]; - } - int Offsets[Count]; -}; - -template -void FillRange(ImVector& buffer, int n, T vmin, T vmax) { - buffer.resize(n); - T step = (vmax - vmin) / (n - 1); - for (int i = 0; i < n; ++i) { - buffer[i] = vmin + i * step; - } -} - -// Returns true if a flag is set -template -inline bool HasFlag(TSet set, TFlag flag) { - return (set & flag) == flag; -} - -// Flips a flag in a flagset -template -inline void FlipFlag(TSet& set, TFlag flag) { - HasFlag(set, flag) ? set &= ~flag : set |= flag; -} - -// Linearly remaps x from [x0 x1] to [y0 y1]. -template -inline T Remap(T x, T x0, T x1, T y0, T y1) { - return y0 + (x - x0) * (y1 - y0) / (x1 - x0); -} - -// Returns always positive modulo (r != 0) -inline int PosMod(int l, int r) { - return (l % r + r) % r; -} - -// Returns the intersection point of two lines A and B (assumes they are not parallel!) -inline ImVec2 Intersection(const ImVec2& a1, const ImVec2& a2, const ImVec2& b1, const ImVec2& b2) { - float v1 = (a1.x * a2.y - a1.y * a2.x); - float v2 = (b1.x * b2.y - b1.y * b2.x); - float v3 = ((a1.x - a2.x) * (b1.y - b2.y) - (a1.y - a2.y) * (b1.x - b2.x)); - return ImVec2((v1 * (b1.x - b2.x) - v2 * (a1.x - a2.x)) / v3, - (v1 * (b1.y - b2.y) - v2 * (a1.y - a2.y)) / v3); -} - -// Turns NANs to 0s -inline double ConstrainNan(double val) { - return isnan(val) ? 0 : val; -} - -// Turns infinity to floating point maximums -inline double ConstrainInf(double val) { - return val == HUGE_VAL ? DBL_MAX : val == -HUGE_VAL ? - DBL_MAX : val; -} - -// Turns numbers less than or equal to 0 to 0.001 (sort of arbitrary, is there a better way?) -inline double ConstrainLog(double val) { - return val <= 0 ? 0.001f : val; -} - -// Returns true if val is NAN or INFINITY -inline bool NanOrInf(double val) { - return val == HUGE_VAL || val == -HUGE_VAL || isnan(val); -} - -// Computes order of magnitude of double. -inline int OrderOfMagnitude(double val) { - return val == 0 ? 0 : (int)(floor(log10(fabs(val)))); -} - -// Returns the precision required for a order of magnitude. -inline int OrderToPrecision(int order) { - return order > 0 ? 0 : 1 - order; -} - -// Returns a floating point precision to use given a value -inline int Precision(double val) { - return OrderToPrecision(OrderOfMagnitude(val)); -} - -// Draws vertical text. The position is the bottom left of the text rect. -inline void AddTextVertical(ImDrawList *DrawList, const char *text, ImVec2 pos, ImU32 text_color) { - pos.x = IM_ROUND(pos.x); - pos.y = IM_ROUND(pos.y); - ImFont * font = GImGui->Font; +void AddTextVertical(ImDrawList *DrawList, const char *text, ImVec2 pos, ImU32 text_color) { + pos.x = IM_ROUND(pos.x); + pos.y = IM_ROUND(pos.y); + ImFont* font = GImGui->Font; const ImFontGlyph *glyph; for (char c = *text++; c; c = *text++) { glyph = font->FindGlyph(c); if (!glyph) continue; - DrawList->PrimReserve(6, 4); DrawList->PrimQuadUV( pos + ImVec2(glyph->Y0, -glyph->X0), pos + ImVec2(glyph->Y0, -glyph->X1), pos + ImVec2(glyph->Y1, -glyph->X1), pos + ImVec2(glyph->Y1, -glyph->X0), - ImVec2(glyph->U0, glyph->V0), ImVec2(glyph->U1, glyph->V0), ImVec2(glyph->U1, glyph->V1), ImVec2(glyph->U0, glyph->V1), text_color); pos.y -= glyph->AdvanceX; } } -// Calculates the size of vertical text -inline ImVec2 CalcTextSizeVertical(const char *text) { +ImVec2 CalcTextSizeVertical(const char *text) { ImVec2 sz = ImGui::CalcTextSize(text); return ImVec2(sz.y, sz.x); } -/// Returns white or black text given background color -inline ImU32 CalcTextColor(const ImVec4& bg) { - return (bg.x * 0.299 + bg.y * 0.587 + bg.z * 0.114) > 0.729 ? IM_COL32_BLACK : IM_COL32_WHITE; -} - -} // private namespace - -//----------------------------------------------------------------------------- -// Structs -//----------------------------------------------------------------------------- - -// Tick mark info -struct ImPlotTick { - ImPlotTick(double value, bool major, bool render_label = true) { - PlotPos = value; - Major = major; - RenderLabel = render_label; - Labeled = false; - } - double PlotPos; - float PixelPos; - ImVec2 Size; - int TextOffset; - bool Major; - bool RenderLabel; - bool Labeled; -}; - -// Axis state information that must persist after EndPlot -struct ImPlotAxis { - ImPlotAxis() { - Dragging = false; - Hovered = false; - Range.Min = 0; - Range.Max = 1; - Flags = PreviousFlags = ImPlotAxisFlags_Default; - } - bool Dragging; - bool Hovered; - ImPlotRange Range; - ImPlotAxisFlags Flags, PreviousFlags; -}; - -// Axis state information only needed between BeginPlot/EndPlot -struct ImPlotAxisState { - ImPlotAxis* Axis; - bool HasRange; - ImGuiCond RangeCond; - bool Present; - int PresentSoFar; - bool Invert; - bool LockMin; - bool LockMax; - bool Lock; - ImPlotAxisState(ImPlotAxis& axis, bool has_range, ImGuiCond range_cond, bool present, int previous_present) : - Axis(&axis), - HasRange(has_range), - RangeCond(range_cond), - Present(present), - PresentSoFar(previous_present + (Present ? 1 : 0)), - Invert(HasFlag(Axis->Flags, ImPlotAxisFlags_Invert)), - LockMin(HasFlag(Axis->Flags, ImPlotAxisFlags_LockMin) || (HasRange && RangeCond == ImGuiCond_Always)), - LockMax(HasFlag(Axis->Flags, ImPlotAxisFlags_LockMax) || (HasRange && RangeCond == ImGuiCond_Always)), - Lock(!Present || ((LockMin && LockMax) || (HasRange && RangeCond == ImGuiCond_Always))) - {} - - ImPlotAxisState() : - Axis(), HasRange(), RangeCond(), Present(), PresentSoFar(),Invert(),LockMin(), LockMax(), Lock() - {} -}; - -struct ImPlotAxisColor { - ImPlotAxisColor() : Major(), Minor(), Txt() {} - ImU32 Major, Minor, Txt; -}; - -// State information for Plot items -struct ImPlotItem { - ImPlotItem() { - Show = true; - SeenThisFrame = false; - Highlight = false; - Color = ImPlot::NextColormapColor(); - NameOffset = -1; - ID = 0; - } - ~ImPlotItem() { ID = 0; } - bool Show; - bool SeenThisFrame; - bool Highlight; - ImVec4 Color; - int NameOffset; - ImGuiID ID; -}; - -// Holds Plot state information that must persist after EndPlot -struct ImPlotState { - ImPlotState() { - Selecting = Querying = Queried = DraggingQuery = false; - SelectStart = QueryStart = ImVec2(0,0); - Flags = PreviousFlags = ImPlotFlags_Default; - ColorIdx = 0; - CurrentYAxis = 0; - } - ImPool Items; - - ImRect BB_Legend; - ImVec2 SelectStart; - bool Selecting; - bool Querying; - bool Queried; - bool DraggingQuery; - ImVec2 QueryStart; - ImRect QueryRect; // relative to BB_Plot!! - - ImPlotAxis XAxis; - ImPlotAxis YAxis[MAX_Y_AXES]; - - ImPlotFlags Flags, PreviousFlags; - int ColorIdx; - int CurrentYAxis; -}; - -struct ImPlotNextPlotData { - ImPlotNextPlotData() { - HasXRange = false; - ShowDefaultTicksX = true; - for (int i = 0; i < MAX_Y_AXES; ++i) { - HasYRange[i] = false; - ShowDefaultTicksY[i] = true; - } - } - ImGuiCond XRangeCond; - ImGuiCond YRangeCond[MAX_Y_AXES]; - bool HasXRange; - bool HasYRange[MAX_Y_AXES]; - ImPlotRange X; - ImPlotRange Y[MAX_Y_AXES]; - bool ShowDefaultTicksX; - bool ShowDefaultTicksY[MAX_Y_AXES]; -}; - -namespace ImPlot { - void SetColormapEx(ImPlotColormap colormap, int samples, ImPlotContext* ctx); // TODO -} - -// Holds Plot state information that must persist only between calls to BeginPlot()/EndPlot() -struct ImPlotContext { - ImPlotContext() : RenderX(), RenderY() { - ChildWindowMade = false; - Reset(); - ImPlot::SetColormapEx(ImPlotColormap_Default, 0, this); - } - - void Reset() { - // end child window if it was made - if (ChildWindowMade) - ImGui::EndChild(); - ChildWindowMade = false; - // reset the next plot data - NextPlotData = ImPlotNextPlotData(); - // reset items count - VisibleItemCount = 0; - // reset legend items - LegendIndices.shrink(0); - LegendLabels.Buf.shrink(0); - // reset ticks/labels - XTicks.shrink(0); - XTickLabels.Buf.shrink(0); - for (int i = 0; i < 3; ++i) { - YTicks[i].shrink(0); - YTickLabels[i].Buf.shrink(0); - } - // reset extents - FitX = false; - ExtentsX.Min = HUGE_VAL; - ExtentsX.Max = -HUGE_VAL; - for (int i = 0; i < MAX_Y_AXES; i++) { - ExtentsY[i].Min = HUGE_VAL; - ExtentsY[i].Max = -HUGE_VAL; - FitY[i] = false; - } - // reset digital plot items count - DigitalPlotItemCnt = 0; - DigitalPlotOffset = 0; - // nullify plot - CurrentPlot = NULL; - } - - // ALl Plots - ImPool Plots; - // Current Plot - ImPlotState* CurrentPlot; - // Legend - ImVector LegendIndices; - ImGuiTextBuffer LegendLabels; - // Bounding regions - ImRect BB_Frame; - ImRect BB_Canvas; - ImRect BB_Plot; - // Cached Colors - ImU32 Col_Frame, Col_Bg, Col_Border, - Col_Txt, Col_TxtDis, - Col_SlctBg, Col_SlctBd, - Col_QryBg, Col_QryBd; - ImPlotAxisColor Col_X; - ImPlotAxisColor Col_Y[MAX_Y_AXES]; - ImPlotAxisState X; - ImPlotAxisState Y[MAX_Y_AXES]; - // Tick marks - ImVector XTicks, YTicks[MAX_Y_AXES]; - ImGuiTextBuffer XTickLabels, YTickLabels[MAX_Y_AXES]; - float AxisLabelReference[MAX_Y_AXES]; - // Transformation cache - ImRect PixelRange[MAX_Y_AXES]; - // linear scale (slope) - double Mx; - double My[MAX_Y_AXES]; - // log scale denominator - double LogDenX; - double LogDenY[MAX_Y_AXES]; - // Data extents - ImPlotRange ExtentsX; - ImPlotRange ExtentsY[MAX_Y_AXES]; - int VisibleItemCount; - bool FitThisFrame; bool FitX; - bool FitY[MAX_Y_AXES]; - // Hover states - bool Hov_Frame; - bool Hov_Plot; - // Render flags - bool RenderX, RenderY[MAX_Y_AXES]; - // Lock info - bool LockPlot; - bool ChildWindowMade; - // Mouse pos - ImPlotPoint LastMousePos[MAX_Y_AXES]; - // Style - ImVec4* Colormap; - int ColormapSize; - ImPlotStyle Style; - ImPlotInputMap InputMap; - ImVector ColorModifiers; // Stack for PushStyleColor()/PopStyleColor() - ImVector StyleModifiers; // Stack for PushStyleVar()/PopStyleVar() - ImPlotNextPlotData NextPlotData; - // Digital plot item count - int DigitalPlotItemCnt; - int DigitalPlotOffset; -}; - -// Global plot context -ImPlotContext* GImPlot = NULL; - -namespace ImPlot { - -ImPlotContext* CreateContext() { - ImPlotContext* ctx = IM_NEW(ImPlotContext)(); - if (GImPlot == NULL) - SetCurrentContext(ctx); - return ctx; -} - -void DestroyContext(ImPlotContext* ctx) { - if (ctx == NULL) - ctx = GImPlot; - if (GImPlot == ctx) - SetCurrentContext(NULL); - IM_DELETE(ctx); -} - -ImPlotContext* GetCurrentContext() { - return GImPlot; -} - -void SetCurrentContext(ImPlotContext* ctx) { - GImPlot = ctx; -} - -//----------------------------------------------------------------------------- -// Context Utils -//----------------------------------------------------------------------------- - -ImPlotInputMap& GetInputMap() { - return GImPlot->InputMap; -} - -inline void FitPoint(const ImPlotPoint& p) { - ImPlotContext& gp = *GImPlot; - ImPlotRange* extents_x = &gp.ExtentsX; - ImPlotRange* extents_y = &gp.ExtentsY[gp.CurrentPlot->CurrentYAxis]; - if (!NanOrInf(p.x)) { - extents_x->Min = p.x < extents_x->Min ? p.x : extents_x->Min; - extents_x->Max = p.x > extents_x->Max ? p.x : extents_x->Max; - } - if (!NanOrInf(p.y)) { - extents_y->Min = p.y < extents_y->Min ? p.y : extents_y->Min; - extents_y->Max = p.y > extents_y->Max ? p.y : extents_y->Max; - } -} - -//----------------------------------------------------------------------------- -// Coordinate Transforms -//----------------------------------------------------------------------------- - -inline void UpdateTransformCache() { - ImPlotContext& gp = *GImPlot; - // get pixels for transforms - for (int i = 0; i < MAX_Y_AXES; i++) { - gp.PixelRange[i] = ImRect(HasFlag(gp.CurrentPlot->XAxis.Flags, ImPlotAxisFlags_Invert) ? gp.BB_Plot.Max.x : gp.BB_Plot.Min.x, - HasFlag(gp.CurrentPlot->YAxis[i].Flags, ImPlotAxisFlags_Invert) ? gp.BB_Plot.Min.y : gp.BB_Plot.Max.y, - HasFlag(gp.CurrentPlot->XAxis.Flags, ImPlotAxisFlags_Invert) ? gp.BB_Plot.Min.x : gp.BB_Plot.Max.x, - HasFlag(gp.CurrentPlot->YAxis[i].Flags, ImPlotAxisFlags_Invert) ? gp.BB_Plot.Max.y : gp.BB_Plot.Min.y); - - gp.My[i] = (gp.PixelRange[i].Max.y - gp.PixelRange[i].Min.y) / gp.CurrentPlot->YAxis[i].Range.Size(); - } - gp.LogDenX = ImLog10(gp.CurrentPlot->XAxis.Range.Max / gp.CurrentPlot->XAxis.Range.Min); - for (int i = 0; i < MAX_Y_AXES; i++) { - gp.LogDenY[i] = ImLog10(gp.CurrentPlot->YAxis[i].Range.Max / gp.CurrentPlot->YAxis[i].Range.Min); - } - gp.Mx = (gp.PixelRange[0].Max.x - gp.PixelRange[0].Min.x) / gp.CurrentPlot->XAxis.Range.Size(); -} - -inline ImPlotPoint PixelsToPlot(float x, float y, int y_axis_in = -1) { - 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; - 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; - if (HasFlag(gp.CurrentPlot->XAxis.Flags, ImPlotAxisFlags_LogScale)) { - double t = (plt.x - gp.CurrentPlot->XAxis.Range.Min) / gp.CurrentPlot->XAxis.Range.Size(); - plt.x = ImPow(10, t * gp.LogDenX) * gp.CurrentPlot->XAxis.Range.Min; - } - if (HasFlag(gp.CurrentPlot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale)) { - double t = (plt.y - gp.CurrentPlot->YAxis[y_axis].Range.Min) / gp.CurrentPlot->YAxis[y_axis].Range.Size(); - plt.y = ImPow(10, t * gp.LogDenY[y_axis]) * gp.CurrentPlot->YAxis[y_axis].Range.Min; - } - return plt; -} - -ImPlotPoint PixelsToPlot(const ImVec2& pix, int 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. -inline ImVec2 PlotToPixels(double x, double y, int y_axis_in = -1) { - 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; - ImVec2 pix; - if (HasFlag(gp.CurrentPlot->XAxis.Flags, ImPlotAxisFlags_LogScale)) { - double t = ImLog10(x / gp.CurrentPlot->XAxis.Range.Min) / gp.LogDenX; - x = ImLerp(gp.CurrentPlot->XAxis.Range.Min, gp.CurrentPlot->XAxis.Range.Max, (float)t); - } - if (HasFlag(gp.CurrentPlot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale)) { - double t = ImLog10(y / gp.CurrentPlot->YAxis[y_axis].Range.Min) / gp.LogDenY[y_axis]; - y = ImLerp(gp.CurrentPlot->YAxis[y_axis].Range.Min, gp.CurrentPlot->YAxis[y_axis].Range.Max, (float)t); - } - pix.x = (float)(gp.PixelRange[y_axis].Min.x + gp.Mx * (x - gp.CurrentPlot->XAxis.Range.Min)); - pix.y = (float)(gp.PixelRange[y_axis].Min.y + gp.My[y_axis] * (y - gp.CurrentPlot->YAxis[y_axis].Range.Min)); - return pix; -} - -// 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) { - return PlotToPixels(plt.x, plt.y, y_axis); -} - -//----------------------------------------------------------------------------- -// Legend Utils -//----------------------------------------------------------------------------- - -ImPlotItem* RegisterItem(const char* label_id) { - ImPlotContext& gp = *GImPlot; - ImGuiID id = ImGui::GetID(label_id); - ImPlotItem* item = gp.CurrentPlot->Items.GetOrAddByKey(id); - if (item->SeenThisFrame) - return item; - item->SeenThisFrame = true; - 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); - } - else { - item->Show = true; - } - if (item->Show) - gp.VisibleItemCount++; - return item; -} - -int GetLegendCount() { - ImPlotContext& gp = *GImPlot; - return gp.LegendIndices.size(); -} - -ImPlotItem* GetLegendItem(int i) { - ImPlotContext& gp = *GImPlot; - return gp.CurrentPlot->Items.GetByIndex(gp.LegendIndices[i]); -} - -ImPlotItem* GetLegendItem(const char* label_id) { - ImPlotContext& gp = *GImPlot; - ImGuiID id = ImGui::GetID(label_id); - return gp.CurrentPlot->Items.GetByKey(id); -} - -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; -} - -//----------------------------------------------------------------------------- -// Tick Utils -//----------------------------------------------------------------------------- - -// Utility function to that rounds x to powers of 2,5 and 10 for generating axis labels -// Taken from Graphics Gems 1 Chapter 11.2, "Nice Numbers for Graph Labels" -inline double NiceNum(double x, bool round) { +double NiceNum(double x, bool round) { double f; /* fractional part of x */ double nf; /* nice, rounded fraction */ int expv = (int)floor(ImLog10(x)); @@ -719,7 +174,237 @@ inline double NiceNum(double x, bool round) { return nf * ImPow(10.0, expv); } -inline void AddDefaultTicks(const ImPlotRange& range, int nMajor, int nMinor, bool logscale, ImVector &out) { +//----------------------------------------------------------------------------- +// Context Utils +//----------------------------------------------------------------------------- + +ImPlotContext* CreateContext() { + ImPlotContext* ctx = IM_NEW(ImPlotContext)(); + Initialize(ctx); + if (GImPlot == NULL) + SetCurrentContext(ctx); + return ctx; +} + +void DestroyContext(ImPlotContext* ctx) { + if (ctx == NULL) + ctx = GImPlot; + if (GImPlot == ctx) + SetCurrentContext(NULL); + IM_DELETE(ctx); +} + +ImPlotContext* GetCurrentContext() { + return GImPlot; +} + +void SetCurrentContext(ImPlotContext* ctx) { + GImPlot = ctx; +} + +void Initialize(ImPlotContext* ctx) { + Reset(ctx); + SetColormapEx(ImPlotColormap_Default, 0, ctx); +} + +void Reset(ImPlotContext* ctx) { + // end child window if it was made + if (ctx->ChildWindowMade) + ImGui::EndChild(); + ctx->ChildWindowMade = false; + // reset the next plot data + ctx->NextPlotData = ImPlotNextPlotData(); + // reset items count + ctx->VisibleItemCount = 0; + // reset legend items + ctx->LegendIndices.shrink(0); + ctx->LegendLabels.Buf.shrink(0); + // reset ticks/labels + ctx->XTicks.shrink(0); + ctx->XTickLabels.Buf.shrink(0); + for (int i = 0; i < 3; ++i) { + ctx->YTicks[i].shrink(0); + ctx->YTickLabels[i].Buf.shrink(0); + } + // reset extents + ctx->FitX = false; + ctx->ExtentsX.Min = HUGE_VAL; + ctx->ExtentsX.Max = -HUGE_VAL; + for (int i = 0; i < MAX_Y_AXES; i++) { + ctx->ExtentsY[i].Min = HUGE_VAL; + ctx->ExtentsY[i].Max = -HUGE_VAL; + ctx->FitY[i] = false; + } + // reset digital plot items count + ctx->DigitalPlotItemCnt = 0; + ctx->DigitalPlotOffset = 0; + // nullify plot + ctx->CurrentPlot = NULL; +} + +//----------------------------------------------------------------------------- +// Plot Utils +//----------------------------------------------------------------------------- + +ImPlotState* GetPlot(const char* title) { + ImGuiWindow* Window = GImGui->CurrentWindow; + const ImGuiID ID = Window->GetID(title); + return GImPlot->Plots.GetByKey(ID); +} + +ImPlotState* GetCurrentPlot() { + return GImPlot->CurrentPlot; +} + +void FitPoint(const ImPlotPoint& p) { + ImPlotContext& gp = *GImPlot; + ImPlotRange* extents_x = &gp.ExtentsX; + ImPlotRange* extents_y = &gp.ExtentsY[gp.CurrentPlot->CurrentYAxis]; + if (!NanOrInf(p.x)) { + extents_x->Min = p.x < extents_x->Min ? p.x : extents_x->Min; + extents_x->Max = p.x > extents_x->Max ? p.x : extents_x->Max; + } + if (!NanOrInf(p.y)) { + extents_y->Min = p.y < extents_y->Min ? p.y : extents_y->Min; + extents_y->Max = p.y > extents_y->Max ? p.y : extents_y->Max; + } +} + +//----------------------------------------------------------------------------- +// Coordinate Utils +//----------------------------------------------------------------------------- + +void UpdateTransformCache() { + ImPlotContext& gp = *GImPlot; + // get pixels for transforms + for (int i = 0; i < MAX_Y_AXES; i++) { + gp.PixelRange[i] = ImRect(ImHasFlag(gp.CurrentPlot->XAxis.Flags, ImPlotAxisFlags_Invert) ? gp.BB_Plot.Max.x : gp.BB_Plot.Min.x, + ImHasFlag(gp.CurrentPlot->YAxis[i].Flags, ImPlotAxisFlags_Invert) ? gp.BB_Plot.Min.y : gp.BB_Plot.Max.y, + ImHasFlag(gp.CurrentPlot->XAxis.Flags, ImPlotAxisFlags_Invert) ? gp.BB_Plot.Min.x : gp.BB_Plot.Max.x, + ImHasFlag(gp.CurrentPlot->YAxis[i].Flags, ImPlotAxisFlags_Invert) ? gp.BB_Plot.Max.y : gp.BB_Plot.Min.y); + + gp.My[i] = (gp.PixelRange[i].Max.y - gp.PixelRange[i].Min.y) / gp.CurrentPlot->YAxis[i].Range.Size(); + } + gp.LogDenX = ImLog10(gp.CurrentPlot->XAxis.Range.Max / gp.CurrentPlot->XAxis.Range.Min); + for (int i = 0; i < MAX_Y_AXES; i++) { + gp.LogDenY[i] = ImLog10(gp.CurrentPlot->YAxis[i].Range.Max / gp.CurrentPlot->YAxis[i].Range.Min); + } + 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) { + 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; + 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; + if (ImHasFlag(gp.CurrentPlot->XAxis.Flags, ImPlotAxisFlags_LogScale)) { + double t = (plt.x - gp.CurrentPlot->XAxis.Range.Min) / gp.CurrentPlot->XAxis.Range.Size(); + plt.x = ImPow(10, t * gp.LogDenX) * gp.CurrentPlot->XAxis.Range.Min; + } + if (ImHasFlag(gp.CurrentPlot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale)) { + double t = (plt.y - gp.CurrentPlot->YAxis[y_axis].Range.Min) / gp.CurrentPlot->YAxis[y_axis].Range.Size(); + plt.y = ImPow(10, t * gp.LogDenY[y_axis]) * gp.CurrentPlot->YAxis[y_axis].Range.Min; + } + return plt; +} + +ImPlotPoint PixelsToPlot(const ImVec2& pix, int 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) { + 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; + ImVec2 pix; + if (ImHasFlag(gp.CurrentPlot->XAxis.Flags, ImPlotAxisFlags_LogScale)) { + double t = ImLog10(x / gp.CurrentPlot->XAxis.Range.Min) / gp.LogDenX; + x = ImLerp(gp.CurrentPlot->XAxis.Range.Min, gp.CurrentPlot->XAxis.Range.Max, (float)t); + } + if (ImHasFlag(gp.CurrentPlot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale)) { + double t = ImLog10(y / gp.CurrentPlot->YAxis[y_axis].Range.Min) / gp.LogDenY[y_axis]; + y = ImLerp(gp.CurrentPlot->YAxis[y_axis].Range.Min, gp.CurrentPlot->YAxis[y_axis].Range.Max, (float)t); + } + pix.x = (float)(gp.PixelRange[y_axis].Min.x + gp.Mx * (x - gp.CurrentPlot->XAxis.Range.Min)); + pix.y = (float)(gp.PixelRange[y_axis].Min.y + gp.My[y_axis] * (y - gp.CurrentPlot->YAxis[y_axis].Range.Min)); + return pix; +} + +// 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) { + return PlotToPixels(plt.x, plt.y, y_axis); +} + +//----------------------------------------------------------------------------- +// Item Utils +//----------------------------------------------------------------------------- + +ImPlotItem* RegisterItem(const char* label_id) { + ImPlotContext& gp = *GImPlot; + ImGuiID id = ImGui::GetID(label_id); + ImPlotItem* item = gp.CurrentPlot->Items.GetOrAddByKey(id); + if (item->SeenThisFrame) + return item; + item->SeenThisFrame = true; + 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); + } + else { + item->Show = true; + } + if (item->Show) + gp.VisibleItemCount++; + 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) { + ImPlotState* plot = GetPlot(plot_title); + if (plot) { + ImGuiID id = ImGui::GetID(item_label_id); + return plot->Items.GetByKey(id); + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Legend Utils +//----------------------------------------------------------------------------- + +int GetLegendCount() { + ImPlotContext& gp = *GImPlot; + return gp.LegendIndices.size(); +} + +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; +} + +//----------------------------------------------------------------------------- +// Tick Utils +//----------------------------------------------------------------------------- + +void AddDefaultTicks(const ImPlotRange& range, int nMajor, int nMinor, bool logscale, ImVector &out) { if (logscale) { if (range.Min <= 0 || range.Max <= 0) return; @@ -755,7 +440,7 @@ inline void AddDefaultTicks(const ImPlotRange& range, int nMajor, int nMinor, bo } } -inline void AddCustomTicks(const double* values, const char** labels, int n, ImVector& ticks, ImGuiTextBuffer& buffer) { +void AddCustomTicks(const double* values, const char** labels, int n, ImVector& ticks, ImGuiTextBuffer& buffer) { for (int i = 0; i < n; ++i) { ImPlotTick tick(values[i],false); tick.TextOffset = buffer.size(); @@ -768,7 +453,7 @@ inline void AddCustomTicks(const double* values, const char** labels, int n, ImV } } -inline void LabelTicks(ImVector &ticks, bool scientific, ImGuiTextBuffer& buffer) { +void LabelTicks(ImVector &ticks, bool scientific, ImGuiTextBuffer& buffer) { char temp[32]; for (int t = 0; t < ticks.Size; t++) { ImPlotTick *tk = &ticks[t]; @@ -785,7 +470,7 @@ inline void LabelTicks(ImVector &ticks, bool scientific, ImGuiTextBu } } -inline float MaxTickLabelWidth(ImVector& ticks) { +float MaxTickLabelWidth(ImVector& ticks) { float w = 0; for (int i = 0; i < ticks.Size; ++i) w = ticks[i].Size.x > w ? ticks[i].Size.x : w; @@ -807,7 +492,7 @@ class YPadCalculator { if (ImPlotAxisStates[y_axis].PresentSoFar >= 3) { pad_result += 6.0f; } - if (!HasFlag(plot.YAxis[y_axis].Flags, ImPlotAxisFlags_TickLabels)) { + if (!ImHasFlag(plot.YAxis[y_axis].Flags, ImPlotAxisFlags_TickLabels)) { return pad_result; } pad_result += MaxLabelWidths[y_axis] + TxtOff; @@ -832,20 +517,12 @@ void UpdateAxisColor(int axis_flag, ImPlotAxisColor* col) { col->Txt = ImGui::GetColorU32(ImVec4(col_Axis.x, col_Axis.y, col_Axis.z, 1)); } -struct ImPlotAxisScale { - ImPlotAxisScale(int y_axis, float tx, float ty, float zoom_rate) { - ImPlotContext& gp = *GImPlot; - Min = PixelsToPlot(gp.BB_Plot.Min - gp.BB_Plot.GetSize() * ImVec2(tx * zoom_rate, ty * zoom_rate), y_axis); - Max = PixelsToPlot(gp.BB_Plot.Max + gp.BB_Plot.GetSize() * ImVec2((1 - tx) * zoom_rate, (1 - ty) * zoom_rate), y_axis); - } - ImPlotPoint Min, Max; -}; - //----------------------------------------------------------------------------- // BeginPlot() //----------------------------------------------------------------------------- bool BeginPlot(const char* title, const char* x_label, const char* y_label, const ImVec2& size, ImPlotFlags flags, ImPlotAxisFlags x_flags, ImPlotAxisFlags y_flags, ImPlotAxisFlags y2_flags, ImPlotAxisFlags y3_flags) { + IM_ASSERT_USER_ERROR(GImPlot != NULL, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?"); ImPlotContext& gp = *GImPlot; IM_ASSERT_USER_ERROR(gp.CurrentPlot == NULL, "Mismatched BeginPlot()/EndPlot()!"); @@ -854,7 +531,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons ImGuiContext &G = *GImGui; ImGuiWindow * Window = G.CurrentWindow; if (Window->SkipItems) { - gp.Reset(); + Reset(GImPlot); return false; } @@ -897,7 +574,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // capture scroll with a child region const float default_w = 400; const float default_h = 300; - if (!HasFlag(plot.Flags, ImPlotFlags_NoChild)) { + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoChild)) { ImGui::BeginChild(title, ImVec2(size.x == 0 ? default_w : size.x, size.y == 0 ? default_h : size.y)); Window = ImGui::GetCurrentWindow(); Window->ScrollMax.y = 1.0f; @@ -931,9 +608,9 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons gp.X = ImPlotAxisState(plot.XAxis, gp.NextPlotData.HasXRange, gp.NextPlotData.XRangeCond, true, 0); gp.Y[0] = ImPlotAxisState(plot.YAxis[0], gp.NextPlotData.HasYRange[0], gp.NextPlotData.YRangeCond[0], true, 0); gp.Y[1] = ImPlotAxisState(plot.YAxis[1], gp.NextPlotData.HasYRange[1], gp.NextPlotData.YRangeCond[1], - HasFlag(plot.Flags, ImPlotFlags_YAxis2), gp.Y[0].PresentSoFar); + ImHasFlag(plot.Flags, ImPlotFlags_YAxis2), gp.Y[0].PresentSoFar); gp.Y[2] = ImPlotAxisState(plot.YAxis[2], gp.NextPlotData.HasYRange[2], gp.NextPlotData.YRangeCond[2], - HasFlag(plot.Flags, ImPlotFlags_YAxis3), gp.Y[1].PresentSoFar); + ImHasFlag(plot.Flags, ImPlotFlags_YAxis3), gp.Y[1].PresentSoFar); gp.LockPlot = gp.X.Lock && gp.Y[0].Lock && gp.Y[1].Lock && gp.Y[2].Lock; @@ -946,14 +623,14 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons plot.YAxis[i].Range.Max = ConstrainNan(ConstrainInf(plot.YAxis[i].Range.Max)); } - if (HasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LogScale)) + if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LogScale)) plot.XAxis.Range.Min = ConstrainLog(plot.XAxis.Range.Min); - if (HasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LogScale)) + if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LogScale)) plot.XAxis.Range.Max = ConstrainLog(plot.XAxis.Range.Max); for (int i = 0; i < MAX_Y_AXES; i++) { - if (HasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LogScale)) + if (ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LogScale)) plot.YAxis[i].Range.Min = ConstrainLog(plot.YAxis[i].Range.Min); - if (HasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LogScale)) + if (ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LogScale)) plot.YAxis[i].Range.Max = ConstrainLog(plot.YAxis[i].Range.Max); } @@ -989,7 +666,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons gp.BB_Frame = ImRect(Window->DC.CursorPos, Window->DC.CursorPos + frame_size); ImGui::ItemSize(gp.BB_Frame); if (!ImGui::ItemAdd(gp.BB_Frame, 0, &gp.BB_Frame)) { - gp.Reset(); + Reset(GImPlot); return false; } gp.Hov_Frame = ImGui::ItemHoverable(gp.BB_Frame, ID); @@ -1005,33 +682,33 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons y_divisions[i] = ImMax(2, (int)IM_ROUND(0.003 * gp.BB_Canvas.GetHeight())); } - gp.RenderX = (HasFlag(plot.XAxis.Flags, ImPlotAxisFlags_GridLines) || - HasFlag(plot.XAxis.Flags, ImPlotAxisFlags_TickMarks) || - HasFlag(plot.XAxis.Flags, ImPlotAxisFlags_TickLabels)) && x_divisions > 1; + gp.RenderX = (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_GridLines) || + ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_TickMarks) || + ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_TickLabels)) && x_divisions > 1; for (int i = 0; i < MAX_Y_AXES; i++) { gp.RenderY[i] = gp.Y[i].Present && - (HasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_GridLines) || - HasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_TickMarks) || - HasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_TickLabels)) && y_divisions[i] > 1; + (ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_GridLines) || + ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_TickMarks) || + ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_TickLabels)) && y_divisions[i] > 1; } // get ticks if (gp.RenderX && gp.NextPlotData.ShowDefaultTicksX) - AddDefaultTicks(plot.XAxis.Range, x_divisions, 10, HasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LogScale), gp.XTicks); + AddDefaultTicks(plot.XAxis.Range, x_divisions, 10, ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LogScale), gp.XTicks); for (int i = 0; i < MAX_Y_AXES; i++) { if (gp.RenderY[i] && gp.NextPlotData.ShowDefaultTicksY[i]) { - AddDefaultTicks(plot.YAxis[i].Range, y_divisions[i], 10, HasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LogScale), gp.YTicks[i]); + AddDefaultTicks(plot.YAxis[i].Range, y_divisions[i], 10, ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LogScale), gp.YTicks[i]); } } // label ticks - if (HasFlag(plot.XAxis.Flags, ImPlotAxisFlags_TickLabels)) - LabelTicks(gp.XTicks, HasFlag(plot.XAxis.Flags, ImPlotAxisFlags_Scientific), gp.XTickLabels); + if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_TickLabels)) + LabelTicks(gp.XTicks, ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_Scientific), gp.XTickLabels); float max_label_width[MAX_Y_AXES] = {}; for (int i = 0; i < MAX_Y_AXES; i++) { - if (gp.Y[i].Present && HasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_TickLabels)) { - LabelTicks(gp.YTicks[i], HasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_Scientific), gp.YTickLabels[i]); + if (gp.Y[i].Present && ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_TickLabels)) { + LabelTicks(gp.YTicks[i], ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_Scientific), gp.YTickLabels[i]); max_label_width[i] = MaxTickLabelWidth(gp.YTicks[i]); } } @@ -1041,7 +718,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons const float txt_off = 5; const float txt_height = ImGui::GetTextLineHeight(); const float pad_top = title_size.x > 0.0f ? txt_height + txt_off : 0; - const float pad_bot = (HasFlag(plot.XAxis.Flags, ImPlotAxisFlags_TickLabels) ? txt_height + txt_off : 0) + (x_label ? txt_height + txt_off : 0); + const float pad_bot = (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_TickLabels) ? txt_height + txt_off : 0) + (x_label ? txt_height + txt_off : 0); YPadCalculator y_axis_pad(gp.Y, max_label_width, txt_off); const float pad_left = y_axis_pad(0) + (y_label ? txt_height + txt_off : 0); const float pad_right = y_axis_pad(1) + y_axis_pad(2); @@ -1079,7 +756,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons const bool any_hov_y_axis_region = plot.YAxis[0].Hovered || plot.YAxis[1].Hovered || plot.YAxis[2].Hovered; // legend hovered from last frame - const bool hov_legend = HasFlag(plot.Flags, ImPlotFlags_Legend) ? gp.Hov_Frame && plot.BB_Legend.Contains(IO.MousePos) : false; + const bool hov_legend = ImHasFlag(plot.Flags, ImPlotFlags_Legend) ? 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) { @@ -1173,7 +850,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] && HasFlag(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 && !hov_legend && !hov_query && !plot.DraggingQuery) { if (plot.XAxis.Hovered) { plot.XAxis.Dragging = true; } @@ -1191,8 +868,8 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons float zoom_rate = 0.1f; if (IO.MouseWheel > 0) zoom_rate = (-zoom_rate) / (1.0f + (2.0f * zoom_rate)); - float tx = Remap(IO.MousePos.x, gp.BB_Plot.Min.x, gp.BB_Plot.Max.x, 0.0f, 1.0f); - float ty = Remap(IO.MousePos.y, gp.BB_Plot.Min.y, gp.BB_Plot.Max.y, 0.0f, 1.0f); + float tx = ImRemap(IO.MousePos.x, gp.BB_Plot.Min.x, gp.BB_Plot.Max.x, 0.0f, 1.0f); + float ty = ImRemap(IO.MousePos.y, gp.BB_Plot.Min.y, gp.BB_Plot.Max.y, 0.0f, 1.0f); if (plot.XAxis.Hovered && !gp.X.Lock) { ImPlotAxisScale axis_scale(0, tx, ty, zoom_rate); const ImPlotPoint& plot_tl = axis_scale.Min; @@ -1222,7 +899,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons if (plot.Selecting && (IO.MouseReleased[gp.InputMap.BoxSelectButton] || !IO.MouseDown[gp.InputMap.BoxSelectButton])) { UpdateTransformCache(); ImVec2 select_size = plot.SelectStart - IO.MousePos; - if (HasFlag(plot.Flags, ImPlotFlags_BoxSelect) && ImFabs(select_size.x) > 2 && ImFabs(select_size.y) > 2) { + if (ImHasFlag(plot.Flags, ImPlotFlags_BoxSelect) && ImFabs(select_size.x) > 2 && ImFabs(select_size.y) > 2) { ImPlotPoint p1 = PixelsToPlot(plot.SelectStart); ImPlotPoint p2 = PixelsToPlot(IO.MousePos); if (!gp.X.LockMin && IO.KeyMods != gp.InputMap.HorizontalMod) @@ -1241,7 +918,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons plot.Selecting = false; } // bad selection - if (plot.Selecting && (!HasFlag(plot.Flags, ImPlotFlags_BoxSelect) || gp.LockPlot) && ImLengthSqr(plot.SelectStart - IO.MousePos) > 4) { + if (plot.Selecting && (!ImHasFlag(plot.Flags, ImPlotFlags_BoxSelect) || gp.LockPlot) && ImLengthSqr(plot.SelectStart - IO.MousePos) > 4) { ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed); } // cancel selection @@ -1249,17 +926,17 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons plot.Selecting = false; } // begin selection or query - if (gp.Hov_Frame && gp.Hov_Plot && IO.MouseClicked[gp.InputMap.BoxSelectButton] && HasFlag(IO.KeyMods, gp.InputMap.BoxSelectMod)) { + if (gp.Hov_Frame && gp.Hov_Plot && IO.MouseClicked[gp.InputMap.BoxSelectButton] && ImHasFlag(IO.KeyMods, gp.InputMap.BoxSelectMod)) { plot.SelectStart = IO.MousePos; plot.Selecting = true; } // update query if (plot.Querying) { UpdateTransformCache(); - plot.QueryRect.Min.x = HasFlag(IO.KeyMods, gp.InputMap.HorizontalMod) ? gp.BB_Plot.Min.x : ImMin(plot.QueryStart.x, IO.MousePos.x); - plot.QueryRect.Max.x = HasFlag(IO.KeyMods, gp.InputMap.HorizontalMod) ? gp.BB_Plot.Max.x : ImMax(plot.QueryStart.x, IO.MousePos.x); - plot.QueryRect.Min.y = HasFlag(IO.KeyMods, gp.InputMap.VerticalMod) ? gp.BB_Plot.Min.y : ImMin(plot.QueryStart.y, IO.MousePos.y); - plot.QueryRect.Max.y = HasFlag(IO.KeyMods, gp.InputMap.VerticalMod) ? gp.BB_Plot.Max.y : ImMax(plot.QueryStart.y, IO.MousePos.y); + plot.QueryRect.Min.x = ImHasFlag(IO.KeyMods, gp.InputMap.HorizontalMod) ? gp.BB_Plot.Min.x : ImMin(plot.QueryStart.x, IO.MousePos.x); + plot.QueryRect.Max.x = ImHasFlag(IO.KeyMods, gp.InputMap.HorizontalMod) ? gp.BB_Plot.Max.x : ImMax(plot.QueryStart.x, IO.MousePos.x); + plot.QueryRect.Min.y = ImHasFlag(IO.KeyMods, gp.InputMap.VerticalMod) ? gp.BB_Plot.Min.y : ImMin(plot.QueryStart.y, IO.MousePos.y); + plot.QueryRect.Max.y = ImHasFlag(IO.KeyMods, gp.InputMap.VerticalMod) ? gp.BB_Plot.Max.y : ImMax(plot.QueryStart.y, IO.MousePos.y); plot.QueryRect.Min -= gp.BB_Plot.Min; plot.QueryRect.Max -= gp.BB_Plot.Min; @@ -1276,21 +953,21 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons } // begin query - if (HasFlag(plot.Flags, ImPlotFlags_Query) && gp.Hov_Frame && gp.Hov_Plot && IO.MouseClicked[gp.InputMap.QueryButton] && HasFlag(IO.KeyMods, gp.InputMap.QueryMod)) { + if (ImHasFlag(plot.Flags, ImPlotFlags_Query) && gp.Hov_Frame && gp.Hov_Plot && IO.MouseClicked[gp.InputMap.QueryButton] && ImHasFlag(IO.KeyMods, gp.InputMap.QueryMod)) { plot.QueryRect = ImRect(0,0,0,0); plot.Querying = true; plot.Queried = true; plot.QueryStart = IO.MousePos; } // toggle between select/query - if (HasFlag(plot.Flags, ImPlotFlags_Query) && plot.Selecting && HasFlag(IO.KeyMods,gp.InputMap.QueryToggleMod)) { + if (ImHasFlag(plot.Flags, ImPlotFlags_Query) && plot.Selecting && ImHasFlag(IO.KeyMods,gp.InputMap.QueryToggleMod)) { plot.Selecting = false; plot.QueryRect = ImRect(0,0,0,0); plot.Querying = true; plot.Queried = true; plot.QueryStart = plot.SelectStart; } - if (HasFlag(plot.Flags, ImPlotFlags_BoxSelect) && plot.Querying && !HasFlag(IO.KeyMods, gp.InputMap.QueryToggleMod) && !IO.MouseDown[gp.InputMap.QueryButton]) { + if (ImHasFlag(plot.Flags, ImPlotFlags_BoxSelect) && plot.Querying && !ImHasFlag(IO.KeyMods, gp.InputMap.QueryToggleMod) && !IO.MouseDown[gp.InputMap.QueryButton]) { plot.Selecting = true; plot.Querying = false; plot.Queried = false; @@ -1352,7 +1029,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons } // render grid - if (HasFlag(plot.XAxis.Flags, ImPlotAxisFlags_GridLines)) { + if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_GridLines)) { for (int t = 0; t < gp.XTicks.Size; t++) { ImPlotTick *xt = &gp.XTicks[t]; DrawList.AddLine(ImVec2(xt->PixelPos, gp.BB_Plot.Min.y), ImVec2(xt->PixelPos, gp.BB_Plot.Max.y), xt->Major ? gp.Col_X.Major : gp.Col_X.Minor, 1); @@ -1360,7 +1037,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons } for (int i = 0; i < MAX_Y_AXES; i++) { - if (gp.Y[i].Present && HasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_GridLines)) { + if (gp.Y[i].Present && ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_GridLines)) { for (int t = 0; t < gp.YTicks[i].Size; t++) { ImPlotTick *yt = &gp.YTicks[i][t]; DrawList.AddLine(ImVec2(gp.BB_Plot.Min.x, yt->PixelPos), ImVec2(gp.BB_Plot.Max.x, yt->PixelPos), yt->Major ? gp.Col_Y[i].Major : gp.Col_Y[i].Minor, 1); @@ -1376,7 +1053,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons } // render labels - if (HasFlag(plot.XAxis.Flags, ImPlotAxisFlags_TickLabels)) { + if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_TickLabels)) { ImGui::PushClipRect(gp.BB_Frame.Min, gp.BB_Frame.Max, true); for (int t = 0; t < gp.XTicks.Size; t++) { ImPlotTick *xt = &gp.XTicks[t]; @@ -1393,7 +1070,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons } ImGui::PushClipRect(gp.BB_Frame.Min, gp.BB_Frame.Max, true); for (int i = 0; i < MAX_Y_AXES; i++) { - if (gp.Y[i].Present && HasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_TickLabels)) { + if (gp.Y[i].Present && ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_TickLabels)) { for (int t = 0; t < gp.YTicks[i].Size; t++) { const float x_start = gp.AxisLabelReference[i] + (i == 0 ? (-txt_off - gp.YTicks[i][t].Size.x) : txt_off); ImPlotTick *yt = &gp.YTicks[i][t]; @@ -1452,14 +1129,14 @@ inline void EndDisabledControls(bool cond) { inline void AxisMenu(ImPlotAxisState& state) { ImGui::PushItemWidth(75); bool total_lock = state.HasRange && state.RangeCond == ImGuiCond_Always; - bool logscale = HasFlag(state.Axis->Flags, ImPlotAxisFlags_LogScale); - bool grid = HasFlag(state.Axis->Flags, ImPlotAxisFlags_GridLines); - bool ticks = HasFlag(state.Axis->Flags, ImPlotAxisFlags_TickMarks); - bool labels = HasFlag(state.Axis->Flags, ImPlotAxisFlags_TickLabels); + bool logscale = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_LogScale); + bool grid = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_GridLines); + bool ticks = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_TickMarks); + bool labels = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_TickLabels); BeginDisabledControls(total_lock); if (ImGui::Checkbox("##LockMin", &state.LockMin)) - FlipFlag(state.Axis->Flags, ImPlotAxisFlags_LockMin); + ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_LockMin); EndDisabledControls(total_lock); ImGui::SameLine(); @@ -1469,7 +1146,7 @@ inline void AxisMenu(ImPlotAxisState& state) { BeginDisabledControls(total_lock); if (ImGui::Checkbox("##LockMax", &state.LockMax)) - FlipFlag(state.Axis->Flags, ImPlotAxisFlags_LockMax); + ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_LockMax); EndDisabledControls(total_lock); ImGui::SameLine(); @@ -1480,16 +1157,16 @@ inline void AxisMenu(ImPlotAxisState& state) { ImGui::Separator(); if (ImGui::Checkbox("Invert", &state.Invert)) - FlipFlag(state.Axis->Flags, ImPlotAxisFlags_Invert); + ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_Invert); if (ImGui::Checkbox("Log Scale", &logscale)) - FlipFlag(state.Axis->Flags, ImPlotAxisFlags_LogScale); + ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_LogScale); ImGui::Separator(); if (ImGui::Checkbox("Grid Lines", &grid)) - FlipFlag(state.Axis->Flags, ImPlotAxisFlags_GridLines); + ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_GridLines); if (ImGui::Checkbox("Tick Marks", &ticks)) - FlipFlag(state.Axis->Flags, ImPlotAxisFlags_TickMarks); + ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_TickMarks); if (ImGui::Checkbox("Labels", &labels)) - FlipFlag(state.Axis->Flags, ImPlotAxisFlags_TickLabels); + ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_TickLabels); } @@ -1502,10 +1179,10 @@ void PlotContextMenu(ImPlotState& plot) { ImGui::EndMenu(); } for (int i = 0; i < MAX_Y_AXES; i++) { - if (i == 1 && !HasFlag(plot.Flags, ImPlotFlags_YAxis2)) { + if (i == 1 && !ImHasFlag(plot.Flags, ImPlotFlags_YAxis2)) { continue; } - if (i == 2 && !HasFlag(plot.Flags, ImPlotFlags_YAxis3)) { + if (i == 2 && !ImHasFlag(plot.Flags, ImPlotFlags_YAxis3)) { continue; } char buf[10] = {}; @@ -1524,25 +1201,25 @@ void PlotContextMenu(ImPlotState& plot) { ImGui::Separator(); if ((ImGui::BeginMenu("Settings"))) { - if (ImGui::MenuItem("Box Select",NULL,HasFlag(plot.Flags, ImPlotFlags_BoxSelect))) { - FlipFlag(plot.Flags, ImPlotFlags_BoxSelect); + if (ImGui::MenuItem("Box Select",NULL,ImHasFlag(plot.Flags, ImPlotFlags_BoxSelect))) { + ImFlipFlag(plot.Flags, ImPlotFlags_BoxSelect); } - if (ImGui::MenuItem("Query",NULL,HasFlag(plot.Flags, ImPlotFlags_Query))) { - FlipFlag(plot.Flags, ImPlotFlags_Query); + if (ImGui::MenuItem("Query",NULL,ImHasFlag(plot.Flags, ImPlotFlags_Query))) { + ImFlipFlag(plot.Flags, ImPlotFlags_Query); } - if (ImGui::MenuItem("Crosshairs",NULL,HasFlag(plot.Flags, ImPlotFlags_Crosshairs))) { - FlipFlag(plot.Flags, ImPlotFlags_Crosshairs); + if (ImGui::MenuItem("Crosshairs",NULL,ImHasFlag(plot.Flags, ImPlotFlags_Crosshairs))) { + ImFlipFlag(plot.Flags, ImPlotFlags_Crosshairs); } - if (ImGui::MenuItem("Mouse Position",NULL,HasFlag(plot.Flags, ImPlotFlags_MousePos))) { - FlipFlag(plot.Flags, ImPlotFlags_MousePos); + if (ImGui::MenuItem("Mouse Position",NULL,ImHasFlag(plot.Flags, ImPlotFlags_MousePos))) { + ImFlipFlag(plot.Flags, ImPlotFlags_MousePos); } - if (ImGui::MenuItem("Anti-Aliased Lines",NULL,HasFlag(plot.Flags, ImPlotFlags_AntiAliased))) { - FlipFlag(plot.Flags, ImPlotFlags_AntiAliased); + if (ImGui::MenuItem("Anti-Aliased Lines",NULL,ImHasFlag(plot.Flags, ImPlotFlags_AntiAliased))) { + ImFlipFlag(plot.Flags, ImPlotFlags_AntiAliased); } ImGui::EndMenu(); } - if (ImGui::MenuItem("Legend",NULL,HasFlag(plot.Flags, ImPlotFlags_Legend))) { - FlipFlag(plot.Flags, ImPlotFlags_Legend); + if (ImGui::MenuItem("Legend",NULL,ImHasFlag(plot.Flags, ImPlotFlags_Legend))) { + ImFlipFlag(plot.Flags, ImPlotFlags_Legend); } #if 0 if (ImGui::BeginMenu("Metrics")) { @@ -1557,36 +1234,12 @@ void PlotContextMenu(ImPlotState& plot) { } -namespace { -class BufferWriter { - public: - BufferWriter(char* buffer, size_t size) : Buffer(buffer), Pos(0), Size(size) {} - - void Write(const char* fmt, ...) IM_FMTARGS(2) { - va_list argp; - va_start(argp, fmt); - VWrite(fmt, argp); - va_end(argp); - } - - private: - void VWrite(const char* fmt, va_list argp) { - const int written = ::vsnprintf(&Buffer[Pos], Size - Pos - 1, fmt, argp); - if (written > 0) - Pos += ImMin(size_t(written), Size-Pos-1); - } - - char* const Buffer; - size_t Pos; - const size_t Size; -}; -} - //----------------------------------------------------------------------------- // EndPlot() //----------------------------------------------------------------------------- void EndPlot() { + IM_ASSERT_USER_ERROR(GImPlot != NULL, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?"); ImPlotContext& gp = *GImPlot; IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "Mismatched BeginPlot()/EndPlot()!"); ImGuiContext &G = *GImGui; @@ -1605,7 +1258,7 @@ void EndPlot() { // render ticks PushPlotClipRect(); - if (HasFlag(plot.XAxis.Flags, ImPlotAxisFlags_TickMarks)) { + if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_TickMarks)) { for (int t = 0; t < gp.XTicks.Size; t++) { ImPlotTick *xt = &gp.XTicks[t]; DrawList.AddLine(ImVec2(xt->PixelPos, gp.BB_Plot.Max.y),ImVec2(xt->PixelPos, gp.BB_Plot.Max.y - (xt->Major ? 10.0f : 5.0f)), gp.Col_Border, 1); @@ -1620,7 +1273,7 @@ void EndPlot() { axis_count++; float x_start = gp.AxisLabelReference[i]; - if (HasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_TickMarks)) { + if (ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_TickMarks)) { float direction = (i == 0) ? 1.0f : -1.0f; bool no_major = axis_count >= 3; @@ -1645,7 +1298,7 @@ void EndPlot() { } ImGui::PopClipRect(); - // render y-axis drag/drop hover + // render y-axis drag/drop hover if (ImGui::IsDragDropPayloadBeingAccepted()) { ImRect bb_plot_pad = gp.BB_Plot; bb_plot_pad.Min.x += 5; bb_plot_pad.Max.x -= 5; @@ -1667,7 +1320,7 @@ void EndPlot() { if (plot.Selecting) { ImRect select_bb(ImMin(IO.MousePos, plot.SelectStart), ImMax(IO.MousePos, plot.SelectStart)); bool select_big_enough = ImLengthSqr(select_bb.GetSize()) > 4; - if (plot.Selecting && !gp.LockPlot && HasFlag(plot.Flags, ImPlotFlags_BoxSelect) && select_big_enough) { + if (plot.Selecting && !gp.LockPlot && ImHasFlag(plot.Flags, ImPlotFlags_BoxSelect) && select_big_enough) { if (IO.KeyAlt && IO.KeyShift) { DrawList.AddRectFilled(gp.BB_Plot.Min, gp.BB_Plot.Max, gp.Col_SlctBg); DrawList.AddRect( gp.BB_Plot.Min, gp.BB_Plot.Max, gp.Col_SlctBd); @@ -1711,7 +1364,7 @@ void EndPlot() { ImRect legend_content_bb; int nItems = GetLegendCount(); bool hov_legend = false; - if (HasFlag(plot.Flags, ImPlotFlags_Legend) && nItems > 0) { + if (ImHasFlag(plot.Flags, ImPlotFlags_Legend) && nItems > 0) { // get max width float max_label_width = 0; for (int i = 0; i < nItems; ++i) { @@ -1721,13 +1374,13 @@ void EndPlot() { } 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_padding * 2 + ImVec2(legend_icon_size, 0)); - hov_legend = HasFlag(plot.Flags, ImPlotFlags_Legend) ? gp.Hov_Frame && plot.BB_Legend.Contains(IO.MousePos) : false; + hov_legend = ImHasFlag(plot.Flags, ImPlotFlags_Legend) ? gp.Hov_Frame && plot.BB_Legend.Contains(IO.MousePos) : false; // render legend box DrawList.AddRectFilled(plot.BB_Legend.Min, plot.BB_Legend.Max, ImGui::GetColorU32(ImGuiCol_PopupBg)); DrawList.AddRect(plot.BB_Legend.Min, plot.BB_Legend.Max, gp.Col_Border); // render each legend item for (int i = 0; i < nItems; ++i) { - ImPlotItem* item = GetLegendItem(i); + ImPlotItem* item = GetItem(i); ImRect icon_bb; icon_bb.Min = legend_content_bb.Min + legend_padding + ImVec2(0, i * txt_ht) + ImVec2(2, 2); icon_bb.Max = legend_content_bb.Min + legend_padding + ImVec2(0, i * txt_ht) + ImVec2(legend_icon_size - 2, legend_icon_size - 2); @@ -1735,7 +1388,7 @@ void EndPlot() { label_bb.Min = legend_content_bb.Min + legend_padding + ImVec2(0, i * txt_ht) + ImVec2(2, 2); label_bb.Max = legend_content_bb.Min + legend_padding + ImVec2(0, i * txt_ht) + ImVec2(legend_content_bb.Max.x, legend_icon_size - 2); ImU32 col_hl_txt; - if (HasFlag(plot.Flags, ImPlotFlags_Highlight) && hov_legend && (icon_bb.Contains(IO.MousePos) || label_bb.Contains(IO.MousePos))) { + if (ImHasFlag(plot.Flags, ImPlotFlags_Highlight) && hov_legend && (icon_bb.Contains(IO.MousePos) || label_bb.Contains(IO.MousePos))) { item->Highlight = true; col_hl_txt = ImGui::GetColorU32(ImLerp(G.Style.Colors[ImGuiCol_Text], item->Color, 0.25f)); } @@ -1764,7 +1417,7 @@ void EndPlot() { } // render crosshairs - if (HasFlag(plot.Flags, ImPlotFlags_Crosshairs) && gp.Hov_Plot && gp.Hov_Frame && + if (ImHasFlag(plot.Flags, ImPlotFlags_Crosshairs) && gp.Hov_Plot && gp.Hov_Frame && !(plot.XAxis.Dragging || any_y_dragging) && !plot.Selecting && !plot.Querying && !hov_legend) { ImGui::SetMouseCursor(ImGuiMouseCursor_None); ImVec2 xy = IO.MousePos; @@ -1783,19 +1436,19 @@ void EndPlot() { } // render mouse pos - if (HasFlag(plot.Flags, ImPlotFlags_MousePos) && gp.Hov_Plot) { + if (ImHasFlag(plot.Flags, ImPlotFlags_MousePos) && gp.Hov_Plot) { char buffer[128] = {}; - BufferWriter writer(buffer, sizeof(buffer)); + ImBufferWriter writer(buffer, sizeof(buffer)); double range_x = gp.XTicks.Size > 1 ? (gp.XTicks[1].PlotPos - gp.XTicks[0].PlotPos) : plot.XAxis.Range.Size(); double range_y = gp.YTicks[0].Size > 1 ? (gp.YTicks[0][1].PlotPos - gp.YTicks[0][0].PlotPos) : plot.YAxis[0].Range.Size(); writer.Write("%.*f,%.*f", Precision(range_x), gp.LastMousePos[0].x, Precision(range_y), gp.LastMousePos[0].y); - if (HasFlag(plot.Flags, ImPlotFlags_YAxis2)) { + if (ImHasFlag(plot.Flags, ImPlotFlags_YAxis2)) { range_y = gp.YTicks[1].Size > 1 ? (gp.YTicks[1][1].PlotPos - gp.YTicks[1][0].PlotPos) : plot.YAxis[1].Range.Size(); writer.Write(",(%.*f)", Precision(range_y), gp.LastMousePos[1].y); } - if (HasFlag(plot.Flags, ImPlotFlags_YAxis3)) { + if (ImHasFlag(plot.Flags, ImPlotFlags_YAxis3)) { range_y = gp.YTicks[2].Size > 1 ? (gp.YTicks[2][1].PlotPos - gp.YTicks[2][0].PlotPos) : plot.YAxis[2].Range.Size(); writer.Write(",(%.*f)", Precision(range_y), gp.LastMousePos[2].y); } @@ -1812,17 +1465,17 @@ void EndPlot() { // FIT DATA -------------------------------------------------------------- if (gp.FitThisFrame && (gp.VisibleItemCount > 0 || plot.Queried)) { - if (gp.FitX && !HasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LockMin) && !NanOrInf(gp.ExtentsX.Min)) { + if (gp.FitX && !ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LockMin) && !NanOrInf(gp.ExtentsX.Min)) { plot.XAxis.Range.Min = gp.ExtentsX.Min; } - if (gp.FitX && !HasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LockMax) && !NanOrInf(gp.ExtentsX.Max)) { + if (gp.FitX && !ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LockMax) && !NanOrInf(gp.ExtentsX.Max)) { plot.XAxis.Range.Max = gp.ExtentsX.Max; } for (int i = 0; i < MAX_Y_AXES; i++) { - if (gp.FitY[i] && !HasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LockMin) && !NanOrInf(gp.ExtentsY[i].Min)) { + if (gp.FitY[i] && !ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LockMin) && !NanOrInf(gp.ExtentsY[i].Min)) { plot.YAxis[i].Range.Min = gp.ExtentsY[i].Min; } - if (gp.FitY[i] && !HasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LockMax) && !NanOrInf(gp.ExtentsY[i].Max)) { + if (gp.FitY[i] && !ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LockMax) && !NanOrInf(gp.ExtentsY[i].Max)) { plot.YAxis[i].Range.Max = gp.ExtentsY[i].Max; } } @@ -1830,7 +1483,7 @@ void EndPlot() { // CONTEXT MENU ----------------------------------------------------------- - if (HasFlag(plot.Flags, ImPlotFlags_ContextMenu) && gp.Hov_Frame && gp.Hov_Plot && IO.MouseDoubleClicked[gp.InputMap.ContextMenuButton] && !hov_legend) + if (ImHasFlag(plot.Flags, ImPlotFlags_ContextMenu) && gp.Hov_Frame && gp.Hov_Plot && IO.MouseDoubleClicked[gp.InputMap.ContextMenuButton] && !hov_legend) ImGui::OpenPopup("##Context"); if (ImGui::BeginPopup("##Context")) { PlotContextMenu(plot); @@ -1846,13 +1499,17 @@ void EndPlot() { // Pop ImGui::PushID at the end of BeginPlot ImGui::PopID(); // Reset context for next plot - gp.Reset(); + Reset(GImPlot); } //----------------------------------------------------------------------------- // MISC API //----------------------------------------------------------------------------- +ImPlotInputMap& GetInputMap() { + return GImPlot->InputMap; +} + void SetNextPlotLimits(double x_min, double x_max, double y_min, double y_max, ImGuiCond cond) { ImPlotContext& gp = *GImPlot; IM_ASSERT_USER_ERROR(gp.CurrentPlot == NULL, "SetNextPlotLimits() needs to be called before BeginPlot()!"); @@ -2136,1416 +1793,10 @@ void PopStyleVar(int count) { } } -//----------------------------------------------------------------------------- -// GETTERS -//----------------------------------------------------------------------------- - -template -inline T OffsetAndStride(const T* data, int idx, int count, int offset, int stride) { - idx = PosMod(offset + idx, count); - return *(const T*)(const void*)((const unsigned char*)data + (size_t)idx * stride); -} - -template -struct GetterYs { - GetterYs(const T* ys, int count, int offset, int stride) { - Ys = ys; - Count = count; - Offset = count ? PosMod(offset, count) : 0; - Stride = stride; - } - const T* Ys; - int Count; - int Offset; - int Stride; - inline ImPlotPoint operator()(int idx) { - return ImPlotPoint((T)idx, OffsetAndStride(Ys, idx, Count, Offset, Stride)); - } -}; - -template -struct GetterXsYs { - GetterXsYs(const T* xs, const T* ys, int count, int offset, int stride) { - Xs = xs; Ys = ys; - Count = count; - Offset = count ? PosMod(offset, count) : 0; - Stride = stride; - } - const T* Xs; - const T* Ys; - int Count; - int Offset; - int Stride; - inline ImPlotPoint operator()(int idx) { - return ImPlotPoint(OffsetAndStride(Xs, idx, Count, Offset, Stride), OffsetAndStride(Ys, idx, Count, Offset, Stride)); - } -}; - -template -struct GetterXsYRef { - GetterXsYRef(const T* xs, T y_ref, int count, int offset, int stride) { - Xs = xs; - YRef = y_ref; - Count = count; - Offset = count ? PosMod(offset, count) : 0; - Stride = stride; - } - const T* Xs; - T YRef; - int Count; - int Offset; - int Stride; - inline ImPlotPoint operator()(int idx) { - return ImPlotPoint(OffsetAndStride(Xs, idx, Count, Offset, Stride), YRef); - } -}; - -struct GetterImVec2 { - GetterImVec2(const ImVec2* data, int count, int offset) { - Data = data; - Count = count; - Offset = count ? PosMod(offset, count) : 0; - } - inline ImPlotPoint operator()(int idx) { return ImPlotPoint(Data[idx].x, Data[idx].y); } - const ImVec2* Data; - int Count; - int Offset; -}; - -struct GetterImPlotPoint { - GetterImPlotPoint(const ImPlotPoint* data, int count, int offset) { - Data = data; - Count = count; - Offset = count ? PosMod(offset, count) : 0; - } - inline ImPlotPoint operator()(int idx) { return Data[idx]; } - const ImPlotPoint* Data; - int Count; - int Offset; -}; - -struct GetterFuncPtrImPlotPoint { - GetterFuncPtrImPlotPoint(ImPlotPoint (*g)(void* data, int idx), void* d, int count, int offset) { - getter = g; - Data = d; - Count = count; - Offset = count ? PosMod(offset, count) : 0; - } - inline ImPlotPoint operator()(int idx) { return getter(Data, idx); } - ImPlotPoint (*getter)(void* data, int idx); - void* Data; - int Count; - int Offset; -}; - -template -struct GetterBarV { - const T* Ys; T XShift; int Count; int Offset; int Stride; - GetterBarV(const T* ys, T xshift, int count, int offset, int stride) { Ys = ys; XShift = xshift; Count = count; Offset = offset; Stride = stride; } - inline ImPlotPoint operator()(int idx) { return ImPlotPoint((T)idx + XShift, OffsetAndStride(Ys, idx, Count, Offset, Stride)); } -}; - -template -struct GetterBarH { - const T* Xs; T YShift; int Count; int Offset; int Stride; - GetterBarH(const T* xs, T yshift, int count, int offset, int stride) { Xs = xs; YShift = yshift; Count = count; Offset = offset; Stride = stride; } - inline ImPlotPoint operator()(int idx) { return ImPlotPoint(OffsetAndStride(Xs, idx, Count, Offset, Stride), (T)idx + YShift); } -}; - -struct ImPlotPointError { - ImPlotPointError(double _x, double _y, double _neg, double _pos) { - x = _x; y = _y; neg = _neg; pos = _pos; - } - double x, y, neg, pos; -}; - -template -struct GetterError { - const T* Xs; const T* Ys; const T* Neg; const T* Pos; int Count; int Offset; int Stride; - GetterError(const T* xs, const T* ys, const T* neg, const T* pos, int count, int offset, int stride) { - Xs = xs; Ys = ys; Neg = neg; Pos = pos; Count = count; Offset = offset; Stride = stride; - } - ImPlotPointError operator()(int idx) { - return ImPlotPointError(OffsetAndStride(Xs, idx, Count, Offset, Stride), - OffsetAndStride(Ys, idx, Count, Offset, Stride), - OffsetAndStride(Neg, idx, Count, Offset, Stride), - OffsetAndStride(Pos, idx, Count, Offset, Stride)); - } -}; - -//----------------------------------------------------------------------------- -// TRANSFORMERS -//----------------------------------------------------------------------------- - -struct TransformerLinLin { - TransformerLinLin(int y_axis) : YAxis(y_axis) {} - - inline ImVec2 operator()(const ImPlotPoint& plt) { return (*this)(plt.x, plt.y); } - inline ImVec2 operator()(double x, double y) { - ImPlotContext& gp = *GImPlot; - return ImVec2( (float)(gp.PixelRange[YAxis].Min.x + gp.Mx * (x - gp.CurrentPlot->XAxis.Range.Min)), - (float)(gp.PixelRange[YAxis].Min.y + gp.My[YAxis] * (y - gp.CurrentPlot->YAxis[YAxis].Range.Min)) ); - } - - int YAxis; -}; - -struct TransformerLogLin { - TransformerLogLin(int y_axis) : YAxis(y_axis) {} - - inline ImVec2 operator()(const ImPlotPoint& plt) { return (*this)(plt.x, plt.y); } - inline ImVec2 operator()(double x, double y) { - ImPlotContext& gp = *GImPlot; - double t = ImLog10(x / gp.CurrentPlot->XAxis.Range.Min) / gp.LogDenX; - x = ImLerp(gp.CurrentPlot->XAxis.Range.Min, gp.CurrentPlot->XAxis.Range.Max, (float)t); - return ImVec2( (float)(gp.PixelRange[YAxis].Min.x + gp.Mx * (x - gp.CurrentPlot->XAxis.Range.Min)), - (float)(gp.PixelRange[YAxis].Min.y + gp.My[YAxis] * (y - gp.CurrentPlot->YAxis[YAxis].Range.Min)) ); - } - - int YAxis; -}; - -struct TransformerLinLog { - TransformerLinLog(int y_axis) : YAxis(y_axis) {} - - inline ImVec2 operator()(const ImPlotPoint& plt) { return (*this)(plt.x, plt.y); } - inline ImVec2 operator()(double x, double y) { - ImPlotContext& gp = *GImPlot; - double t = ImLog10(y / gp.CurrentPlot->YAxis[YAxis].Range.Min) / gp.LogDenY[YAxis]; - y = ImLerp(gp.CurrentPlot->YAxis[YAxis].Range.Min, gp.CurrentPlot->YAxis[YAxis].Range.Max, (float)t); - return ImVec2( (float)(gp.PixelRange[YAxis].Min.x + gp.Mx * (x - gp.CurrentPlot->XAxis.Range.Min)), - (float)(gp.PixelRange[YAxis].Min.y + gp.My[YAxis] * (y - gp.CurrentPlot->YAxis[YAxis].Range.Min)) ); - } - int YAxis; -}; - -struct TransformerLogLog { - TransformerLogLog(int y_axis) : YAxis(y_axis) {} - - inline ImVec2 operator()(const ImPlotPoint& plt) { return (*this)(plt.x, plt.y); } - inline ImVec2 operator()(double x, double y) { - ImPlotContext& gp = *GImPlot; - double t = ImLog10(x / gp.CurrentPlot->XAxis.Range.Min) / gp.LogDenX; - x = ImLerp(gp.CurrentPlot->XAxis.Range.Min, gp.CurrentPlot->XAxis.Range.Max, (float)t); - t = ImLog10(y / gp.CurrentPlot->YAxis[YAxis].Range.Min) / gp.LogDenY[YAxis]; - y = ImLerp(gp.CurrentPlot->YAxis[YAxis].Range.Min, gp.CurrentPlot->YAxis[YAxis].Range.Max, (float)t); - return ImVec2( (float)(gp.PixelRange[YAxis].Min.x + gp.Mx * (x - gp.CurrentPlot->XAxis.Range.Min)), - (float)(gp.PixelRange[YAxis].Min.y + gp.My[YAxis] * (y - gp.CurrentPlot->YAxis[YAxis].Range.Min)) ); - } - - int YAxis; -}; - -//----------------------------------------------------------------------------- -// RENDERERS -//----------------------------------------------------------------------------- - -/// Renders primitive shapes in bulk as efficiently as possible. -template -inline void RenderPrimitives(Renderer renderer, ImDrawList& DrawList) { - unsigned int prims = renderer.Prims; - unsigned int prims_culled = 0; - unsigned int idx = 0; - static const unsigned int max_idx = (unsigned int)(ImPow(2.0f, (float)(sizeof(ImDrawIdx) * 8)) - 1); - const ImVec2 uv = DrawList._Data->TexUvWhitePixel; - while (prims) { - // find how many can be reserved up to end of current draw command's limit - unsigned int cnt = (unsigned int)ImMin(prims, (max_idx - DrawList._VtxCurrentIdx) / Renderer::VtxConsumed); - // make sure at least this many elements can be rendered to avoid situations where at the end of buffer this slow path is not taken all the time - if (cnt >= ImMin(64u, prims)) { - if (prims_culled >= cnt) - prims_culled -= cnt; // reuse previous reservation - else { - DrawList.PrimReserve((cnt - prims_culled) * Renderer::IdxConsumed, (cnt - prims_culled) * Renderer::VtxConsumed); // add more elements to previous reservation - prims_culled = 0; - } - } - else - { - if (prims_culled > 0) { - DrawList.PrimUnreserve(prims_culled * Renderer::IdxConsumed, prims_culled * Renderer::VtxConsumed); - prims_culled = 0; - } - cnt = (unsigned int)ImMin(prims, (max_idx - 0/*DrawList._VtxCurrentIdx*/) / Renderer::VtxConsumed); - DrawList.PrimReserve(cnt * Renderer::IdxConsumed, cnt * Renderer::VtxConsumed); // reserve new draw command - } - prims -= cnt; - for (unsigned int ie = idx + cnt; idx != ie; ++idx) { - if (!renderer(DrawList, uv, idx)) - prims_culled++; - } - } - if (prims_culled > 0) - DrawList.PrimUnreserve(prims_culled * Renderer::IdxConsumed, prims_culled * Renderer::VtxConsumed); -} - -#define SQRT_1_2 0.70710678118f -#define SQRT_3_2 0.86602540378f - -inline void TransformMarker(ImVec2* points, int n, const ImVec2& c, float s) { - for (int i = 0; i < n; ++i) { - points[i].x = c.x + points[i].x * s; - points[i].y = c.y + points[i].y * s; - } -} - -inline void MarkerGeneral(ImDrawList& DrawList, ImVec2* points, int n, const ImVec2& c, float s, bool outline, ImU32 col_outline, bool fill, ImU32 col_fill, float weight) { - TransformMarker(points, n, c, s); - if (fill) - DrawList.AddConvexPolyFilled(points, n, col_fill); - if (outline && !(fill && col_outline == col_fill)) { - for (int i = 0; i < n; ++i) - DrawList.AddLine(points[i], points[(i+1)%n], col_outline, weight); - } -} - -inline void MarkerCircle(ImDrawList& DrawList, const ImVec2& c, float s, bool outline, ImU32 col_outline, bool fill, ImU32 col_fill, float weight) { - ImVec2 marker[10] = {ImVec2(1.0f, 0.0f), - ImVec2(0.809017f, 0.58778524f), - ImVec2(0.30901697f, 0.95105654f), - ImVec2(-0.30901703f, 0.9510565f), - ImVec2(-0.80901706f, 0.5877852f), - ImVec2(-1.0f, 0.0f), - ImVec2(-0.80901694f, -0.58778536f), - ImVec2(-0.3090171f, -0.9510565f), - ImVec2(0.30901712f, -0.9510565f), - ImVec2(0.80901694f, -0.5877853f)}; - MarkerGeneral(DrawList, marker, 10, c, s, outline, col_outline, fill, col_fill, weight); -} - -inline void MarkerDiamond(ImDrawList& DrawList, const ImVec2& c, float s, bool outline, ImU32 col_outline, bool fill, ImU32 col_fill, float weight) { - ImVec2 marker[4] = {ImVec2(1, 0), ImVec2(0, -1), ImVec2(-1, 0), ImVec2(0, 1)}; - MarkerGeneral(DrawList, marker, 4, c, s, outline, col_outline, fill, col_fill, weight); -} - -inline void MarkerSquare(ImDrawList& DrawList, const ImVec2& c, float s, bool outline, ImU32 col_outline, bool fill, ImU32 col_fill, float weight) { - ImVec2 marker[4] = {ImVec2(SQRT_1_2,SQRT_1_2),ImVec2(SQRT_1_2,-SQRT_1_2),ImVec2(-SQRT_1_2,-SQRT_1_2),ImVec2(-SQRT_1_2,SQRT_1_2)}; - MarkerGeneral(DrawList, marker, 4, c, s, outline, col_outline, fill, col_fill, weight); -} - -inline void MarkerUp(ImDrawList& DrawList, const ImVec2& c, float s, bool outline, ImU32 col_outline, bool fill, ImU32 col_fill, float weight) { - ImVec2 marker[3] = {ImVec2(SQRT_3_2,0.5f),ImVec2(0,-1),ImVec2(-SQRT_3_2,0.5f)}; - MarkerGeneral(DrawList, marker, 3, c, s, outline, col_outline, fill, col_fill, weight); -} - -inline void MarkerDown(ImDrawList& DrawList, const ImVec2& c, float s, bool outline, ImU32 col_outline, bool fill, ImU32 col_fill, float weight) { - ImVec2 marker[3] = {ImVec2(SQRT_3_2,-0.5f),ImVec2(0,1),ImVec2(-SQRT_3_2,-0.5f)}; - MarkerGeneral(DrawList, marker, 3, c, s, outline, col_outline, fill, col_fill, weight); -} - -inline void MarkerLeft(ImDrawList& DrawList, const ImVec2& c, float s, bool outline, ImU32 col_outline, bool fill, ImU32 col_fill, float weight) { - ImVec2 marker[3] = {ImVec2(-1,0), ImVec2(0.5, SQRT_3_2), ImVec2(0.5, -SQRT_3_2)}; - MarkerGeneral(DrawList, marker, 3, c, s, outline, col_outline, fill, col_fill, weight); -} - -inline void MarkerRight(ImDrawList& DrawList, const ImVec2& c, float s, bool outline, ImU32 col_outline, bool fill, ImU32 col_fill, float weight) { - ImVec2 marker[3] = {ImVec2(1,0), ImVec2(-0.5, SQRT_3_2), ImVec2(-0.5, -SQRT_3_2)}; - MarkerGeneral(DrawList, marker, 3, c, s, outline, col_outline, fill, col_fill, weight); -} - -inline void MarkerAsterisk(ImDrawList& DrawList, const ImVec2& c, float s, bool /*outline*/, ImU32 col_outline, bool /*fill*/, ImU32 /*col_fill*/, float weight) { - ImVec2 marker[6] = {ImVec2(SQRT_3_2, 0.5f), ImVec2(0, -1), ImVec2(-SQRT_3_2, 0.5f), ImVec2(SQRT_3_2, -0.5f), ImVec2(0, 1), ImVec2(-SQRT_3_2, -0.5f)}; - TransformMarker(marker, 6, c, s); - DrawList.AddLine(marker[0], marker[5], col_outline, weight); - DrawList.AddLine(marker[1], marker[4], col_outline, weight); - DrawList.AddLine(marker[2], marker[3], col_outline, weight); -} - -inline void MarkerPlus(ImDrawList& DrawList, const ImVec2& c, float s, bool /*outline*/, ImU32 col_outline, bool /*fill*/, ImU32 /*col_fill*/, float weight) { - ImVec2 marker[4] = {ImVec2(1, 0), ImVec2(0, -1), ImVec2(-1, 0), ImVec2(0, 1)}; - TransformMarker(marker, 4, c, s); - DrawList.AddLine(marker[0], marker[2], col_outline, weight); - DrawList.AddLine(marker[1], marker[3], col_outline, weight); -} - -inline void MarkerCross(ImDrawList& DrawList, const ImVec2& c, float s, bool /*outline*/, ImU32 col_outline, bool /*fill*/, ImU32 /*col_fill*/, float weight) { - ImVec2 marker[4] = {ImVec2(SQRT_1_2,SQRT_1_2),ImVec2(SQRT_1_2,-SQRT_1_2),ImVec2(-SQRT_1_2,-SQRT_1_2),ImVec2(-SQRT_1_2,SQRT_1_2)}; - TransformMarker(marker, 4, c, s); - DrawList.AddLine(marker[0], marker[2], col_outline, weight); - DrawList.AddLine(marker[1], marker[3], col_outline, weight); -} - -template -inline void RenderMarkers(Getter getter, Transformer transformer, ImDrawList& DrawList, bool rend_mk_line, ImU32 col_mk_line, bool rend_mk_fill, ImU32 col_mk_fill) { - ImPlotContext& gp = *GImPlot; - for (int i = 0; i < getter.Count; ++i) { - ImVec2 c = transformer(getter(i)); - if (gp.BB_Plot.Contains(c)) { - // TODO: Optimize the loop and if statements, this is atrocious - if (HasFlag(gp.Style.Marker, ImPlotMarker_Circle)) - MarkerCircle(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); - if (HasFlag(gp.Style.Marker, ImPlotMarker_Square)) - MarkerSquare(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); - if (HasFlag(gp.Style.Marker, ImPlotMarker_Diamond)) - MarkerDiamond(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); - if (HasFlag(gp.Style.Marker, ImPlotMarker_Up)) - MarkerUp(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); - if (HasFlag(gp.Style.Marker, ImPlotMarker_Down)) - MarkerDown(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); - if (HasFlag(gp.Style.Marker, ImPlotMarker_Left)) - MarkerLeft(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); - if (HasFlag(gp.Style.Marker, ImPlotMarker_Right)) - MarkerRight(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); - if (HasFlag(gp.Style.Marker, ImPlotMarker_Cross)) - MarkerCross(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); - if (HasFlag(gp.Style.Marker, ImPlotMarker_Plus)) - MarkerPlus(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); - if (HasFlag(gp.Style.Marker, ImPlotMarker_Asterisk)) - MarkerAsterisk(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); - } - } -} - -template -struct LineRenderer { - inline LineRenderer(Getter _getter, Transformer _transformer, ImU32 col, float weight) : - getter(_getter), - transformer(_transformer) - { - Prims = getter.Count - 1; - Col = col; - Weight = weight; - p1 = transformer(getter(0)); - } - inline bool operator()(ImDrawList& DrawList, ImVec2 uv, int prim) { - ImPlotContext& gp = *GImPlot; - ImVec2 p2 = transformer(getter(prim + 1)); - if (!gp.BB_Plot.Overlaps(ImRect(ImMin(p1, p2), ImMax(p1, p2)))) { - p1 = p2; - return false; - } - float dx = p2.x - p1.x; - float dy = p2.y - p1.y; - IM_NORMALIZE2F_OVER_ZERO(dx, dy); - dx *= (Weight * 0.5f); - dy *= (Weight * 0.5f); - DrawList._VtxWritePtr[0].pos.x = p1.x + dy; - DrawList._VtxWritePtr[0].pos.y = p1.y - dx; - DrawList._VtxWritePtr[0].uv = uv; - DrawList._VtxWritePtr[0].col = Col; - DrawList._VtxWritePtr[1].pos.x = p2.x + dy; - DrawList._VtxWritePtr[1].pos.y = p2.y - dx; - DrawList._VtxWritePtr[1].uv = uv; - DrawList._VtxWritePtr[1].col = Col; - DrawList._VtxWritePtr[2].pos.x = p2.x - dy; - DrawList._VtxWritePtr[2].pos.y = p2.y + dx; - DrawList._VtxWritePtr[2].uv = uv; - DrawList._VtxWritePtr[2].col = Col; - DrawList._VtxWritePtr[3].pos.x = p1.x - dy; - DrawList._VtxWritePtr[3].pos.y = p1.y + dx; - DrawList._VtxWritePtr[3].uv = uv; - DrawList._VtxWritePtr[3].col = Col; - DrawList._VtxWritePtr += 4; - DrawList._IdxWritePtr[0] = (ImDrawIdx)(DrawList._VtxCurrentIdx); - DrawList._IdxWritePtr[1] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 1); - DrawList._IdxWritePtr[2] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 2); - DrawList._IdxWritePtr[3] = (ImDrawIdx)(DrawList._VtxCurrentIdx); - DrawList._IdxWritePtr[4] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 2); - DrawList._IdxWritePtr[5] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 3); - DrawList._IdxWritePtr += 6; - DrawList._VtxCurrentIdx += 4; - p1 = p2; - return true; - } - Getter getter; - Transformer transformer; - int Prims; - ImU32 Col; - float Weight; - ImVec2 p1; - static const int IdxConsumed = 6; - static const int VtxConsumed = 4; -}; - -template -inline void RenderLineStrip(Getter getter, Transformer transformer, ImDrawList& DrawList, float line_weight, ImU32 col) { - ImPlotContext& gp = *GImPlot; - if (HasFlag(gp.CurrentPlot->Flags, ImPlotFlags_AntiAliased)) { - ImVec2 p1 = transformer(getter(0)); - for (int i = 0; i < getter.Count; ++i) { - ImVec2 p2 = transformer(getter(i)); - if (gp.BB_Plot.Overlaps(ImRect(ImMin(p1, p2), ImMax(p1, p2)))) - DrawList.AddLine(p1, p2, col, line_weight); - p1 = p2; - } - } - else { - RenderPrimitives(LineRenderer(getter, transformer, col, line_weight), DrawList); - } -} - -template -struct ShadedRenderer { - ShadedRenderer(Getter1 _getter1, Getter2 _getter2, Transformer _transformer, ImU32 col) : - getter1(_getter1), - getter2(_getter2), - transformer(_transformer), - Col(col) - { - Prims = ImMin(getter1.Count, getter2.Count) - 1; - p11 = transformer(getter1(0)); - p12 = transformer(getter2(0)); - } - - inline bool operator()(ImDrawList& DrawList, ImVec2 uv, int prim) { - ImVec2 p21 = transformer(getter1(prim+1)); - ImVec2 p22 = transformer(getter2(prim+1)); - const int intersect = (p11.y > p12.y && p22.y > p21.y) || (p12.y > p11.y && p21.y > p22.y); - ImVec2 intersection = Intersection(p11,p21,p12,p22); - DrawList._VtxWritePtr[0].pos = p11; - DrawList._VtxWritePtr[0].uv = uv; - DrawList._VtxWritePtr[0].col = Col; - DrawList._VtxWritePtr[1].pos = p21; - DrawList._VtxWritePtr[1].uv = uv; - DrawList._VtxWritePtr[1].col = Col; - DrawList._VtxWritePtr[2].pos = intersection; - DrawList._VtxWritePtr[2].uv = uv; - DrawList._VtxWritePtr[2].col = Col; - DrawList._VtxWritePtr[3].pos = p12; - DrawList._VtxWritePtr[3].uv = uv; - DrawList._VtxWritePtr[3].col = Col; - DrawList._VtxWritePtr[4].pos = p22; - DrawList._VtxWritePtr[4].uv = uv; - DrawList._VtxWritePtr[4].col = Col; - DrawList._VtxWritePtr += 5; - DrawList._IdxWritePtr[0] = (ImDrawIdx)(DrawList._VtxCurrentIdx); - DrawList._IdxWritePtr[1] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 1 + intersect); - DrawList._IdxWritePtr[2] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 3); - DrawList._IdxWritePtr[3] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 1); - DrawList._IdxWritePtr[4] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 3 - intersect); - DrawList._IdxWritePtr[5] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 4); - DrawList._IdxWritePtr += 6; - DrawList._VtxCurrentIdx += 5; - p11 = p21; - p12 = p22; - return true; - } - Getter1 getter1; - Getter2 getter2; - Transformer transformer; - int Prims; - ImU32 Col; - ImVec2 p11, p12; - static const int IdxConsumed = 6; - static const int VtxConsumed = 5; -}; - -//----------------------------------------------------------------------------- -// RENDERING UTILS -//----------------------------------------------------------------------------- - -// Returns true if a style color is set to be automaticaly determined -inline bool ColorIsAuto(ImPlotCol idx) { - ImPlotContext& gp = *GImPlot; - return gp.Style.Colors[idx].w == -1; -} -// Recolors an item from an the current ImPlotCol if it is not automatic (i.e. alpha != -1) -inline void TryRecolorItem(ImPlotItem* item, ImPlotCol idx) { - ImPlotContext& gp = *GImPlot; - if (gp.Style.Colors[idx].w != -1) - item->Color = gp.Style.Colors[idx]; -} -// Returns true if lines will render (e.g. basic lines, bar outlines) -inline bool WillLineRender() { - ImPlotContext& gp = *GImPlot; - return gp.Style.Colors[ImPlotCol_Line].w != 0 && gp.Style.LineWeight > 0; -} -// Returns true if fills will render (e.g. shaded plots, bar fills) -inline bool WillFillRender() { - ImPlotContext& gp = *GImPlot; - return gp.Style.Colors[ImPlotCol_Fill].w != 0 && gp.Style.FillAlpha > 0; -} -// Returns true if marker outlines will render -inline bool WillMarkerOutlineRender() { - ImPlotContext& gp = *GImPlot; - return gp.Style.Colors[ImPlotCol_MarkerOutline].w != 0 && gp.Style.MarkerWeight > 0; -} -// Returns true if mark fill will render -inline bool WillMarkerFillRender() { - ImPlotContext& gp = *GImPlot; - return gp.Style.Colors[ImPlotCol_MarkerFill].w != 0 && gp.Style.FillAlpha > 0; -} -// Gets the line color for an item -inline ImVec4 GetLineColor(ImPlotItem* item) { - ImPlotContext& gp = *GImPlot; - return ColorIsAuto(ImPlotCol_Line) ? item->Color : gp.Style.Colors[ImPlotCol_Line]; -} -// Gets the fill color for an item -inline ImVec4 GetItemFillColor(ImPlotItem* item) { - ImPlotContext& gp = *GImPlot; - ImVec4 col = ColorIsAuto(ImPlotCol_Fill) ? item->Color : gp.Style.Colors[ImPlotCol_Fill]; - col.w *= gp.Style.FillAlpha; - return col; -} -// Gets the marker outline color for an item -inline ImVec4 GetMarkerOutlineColor(ImPlotItem* item) { - ImPlotContext& gp = *GImPlot; - return ColorIsAuto(ImPlotCol_MarkerOutline) ? GetLineColor(item) : gp.Style.Colors[ImPlotCol_MarkerOutline]; -} -// Gets the marker fill color for an item -inline ImVec4 GetMarkerFillColor(ImPlotItem* item) { - ImPlotContext& gp = *GImPlot; - ImVec4 col = ColorIsAuto(ImPlotCol_MarkerFill) ? GetLineColor(item) :gp.Style.Colors[ImPlotCol_MarkerFill]; - col.w *= gp.Style.FillAlpha; - return col; -} -// Gets the error bar color -inline ImVec4 GetErrorBarColor() { - ImPlotContext& gp = *GImPlot; - return ColorIsAuto(ImPlotCol_ErrorBar) ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : gp.Style.Colors[ImPlotCol_ErrorBar]; -} - -//----------------------------------------------------------------------------- -// PLOT LINES / MARKERS -//----------------------------------------------------------------------------- - -template -inline void PlotEx(const char* label_id, Getter getter) -{ - ImPlotContext& gp = *GImPlot; - IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotEx() needs to be called between BeginPlot() and EndPlot()!"); - - ImPlotItem* item = RegisterItem(label_id); - if (!item->Show) - return; - TryRecolorItem(item, ImPlotCol_Line); - - // find data extents - if (gp.FitThisFrame) { - for (int i = 0; i < getter.Count; ++i) { - ImPlotPoint p = getter(i); - FitPoint(p); - } - } - - ImDrawList& DrawList = *ImGui::GetWindowDrawList(); - ImPlotState* plot = gp.CurrentPlot; - const int y_axis = plot->CurrentYAxis; - - PushPlotClipRect(); - // render line - if (getter.Count > 1 && WillLineRender()) { - ImU32 col_line = ImGui::GetColorU32(GetLineColor(item)); - const float line_weight = item->Highlight ? gp.Style.LineWeight * 2 : gp.Style.LineWeight; - if (HasFlag(plot->XAxis.Flags, ImPlotAxisFlags_LogScale) && HasFlag(plot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale)) - RenderLineStrip(getter, TransformerLogLog(y_axis), DrawList, line_weight, col_line); - else if (HasFlag(plot->XAxis.Flags, ImPlotAxisFlags_LogScale)) - RenderLineStrip(getter, TransformerLogLin(y_axis), DrawList, line_weight, col_line); - else if (HasFlag(plot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale)) - RenderLineStrip(getter, TransformerLinLog(y_axis), DrawList, line_weight, col_line); - else - RenderLineStrip(getter, TransformerLinLin(y_axis), DrawList, line_weight, col_line); - } - // render markers - if (gp.Style.Marker != ImPlotMarker_None) { - const bool rend_mk_line = WillMarkerOutlineRender(); - const bool rend_mk_fill = WillMarkerFillRender(); - const ImU32 col_mk_line = ImGui::GetColorU32(GetMarkerOutlineColor(item)); - const ImU32 col_mk_fill = ImGui::GetColorU32(GetMarkerFillColor(item)); - if (HasFlag(plot->XAxis.Flags, ImPlotAxisFlags_LogScale) && HasFlag(plot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale)) - RenderMarkers(getter, TransformerLogLog(y_axis), DrawList, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill); - else if (HasFlag(plot->XAxis.Flags, ImPlotAxisFlags_LogScale)) - RenderMarkers(getter, TransformerLogLin(y_axis), DrawList, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill); - else if (HasFlag(plot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale)) - RenderMarkers(getter, TransformerLinLog(y_axis), DrawList, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill); - else - RenderMarkers(getter, TransformerLinLin(y_axis), DrawList, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill); - } - PopPlotClipRect(); -} - -//----------------------------------------------------------------------------- -// float - -void PlotLine(const char* label_id, const float* values, int count, int offset, int stride) { - GetterYs getter(values,count,offset,stride); - PlotEx(label_id, getter); -} - -void PlotLine(const char* label_id, const float* xs, const float* ys, int count, int offset, int stride) { - GetterXsYs getter(xs,ys,count,offset,stride); - return PlotEx(label_id, getter); -} - -void PlotLine(const char* label_id, const ImVec2* data, int count, int offset) { - GetterImVec2 getter(data, count, offset); - return PlotEx(label_id, getter); -} - -//----------------------------------------------------------------------------- -// double - -void PlotLine(const char* label_id, const double* values, int count, int offset, int stride) { - GetterYs getter(values,count,offset,stride); - PlotEx(label_id, getter); -} - -void PlotLine(const char* label_id, const double* xs, const double* ys, int count, int offset, int stride) { - GetterXsYs getter(xs,ys,count,offset,stride); - return PlotEx(label_id, getter); -} - -void PlotLine(const char* label_id, const ImPlotPoint* data, int count, int offset) { - GetterImPlotPoint getter(data, count, offset); - return PlotEx(label_id, getter); -} - -//----------------------------------------------------------------------------- -// custom - -void PlotLine(const char* label_id, ImPlotPoint (*getter_func)(void* data, int idx), void* data, int count, int offset) { - GetterFuncPtrImPlotPoint getter(getter_func,data, count, offset); - return PlotEx(label_id, getter); -} - -//----------------------------------------------------------------------------- -// PLOT SCATTER -//----------------------------------------------------------------------------- - -inline int PushScatterStyle() { - int vars = 1; - PushStyleVar(ImPlotStyleVar_LineWeight, 0); - if (GetStyle().Marker == ImPlotMarker_None) { - PushStyleVar(ImPlotStyleVar_Marker, ImPlotMarker_Circle); - vars++; - } - return vars; -} - -//----------------------------------------------------------------------------- -// float - -void PlotScatter(const char* label_id, const float* values, int count, int offset, int stride) { - int vars = PushScatterStyle(); - PlotLine(label_id, values, count, offset, stride); - PopStyleVar(vars); -} - -void PlotScatter(const char* label_id, const float* xs, const float* ys, int count, int offset, int stride) { - int vars = PushScatterStyle(); - PlotLine(label_id, xs, ys, count, offset, stride); - PopStyleVar(vars); -} - -void PlotScatter(const char* label_id, const ImVec2* data, int count, int offset) { - int vars = PushScatterStyle(); - PlotLine(label_id, data, count, offset); - PopStyleVar(vars); -} - -//----------------------------------------------------------------------------- -// double - -void PlotScatter(const char* label_id, const double* values, int count, int offset, int stride) { - int vars = PushScatterStyle(); - PlotLine(label_id, values, count, offset, stride); - PopStyleVar(vars); -} - -void PlotScatter(const char* label_id, const double* xs, const double* ys, int count, int offset, int stride) { - int vars = PushScatterStyle(); - PlotLine(label_id, xs, ys, count, offset, stride); - PopStyleVar(vars); -} - -void PlotScatter(const char* label_id, const ImPlotPoint* data, int count, int offset) { - int vars = PushScatterStyle(); - PlotLine(label_id, data, count, offset); - PopStyleVar(vars); -} - -//----------------------------------------------------------------------------- -// custom - -void PlotScatter(const char* label_id, ImPlotPoint (*getter)(void* data, int idx), void* data, int count, int offset) { - int vars = PushScatterStyle(); - PlotLine(label_id, getter, data, count, offset); - PopStyleVar(vars); -} - -//----------------------------------------------------------------------------- -// PLOT SHADED -//----------------------------------------------------------------------------- - -template -inline void PlotShadedEx(const char* label_id, Getter1 getter1, Getter2 getter2) { - ImPlotContext& gp = *GImPlot; - IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotShaded() needs to be called between BeginPlot() and EndPlot()!"); - - ImPlotItem* item = RegisterItem(label_id); - if (!item->Show) - return; - TryRecolorItem(item, ImPlotCol_Fill); - - if (!WillFillRender()) - return; - - // find data extents - if (gp.FitThisFrame) { - for (int i = 0; i < ImMin(getter1.Count, getter2.Count); ++i) { - ImPlotPoint p1 = getter1(i); - ImPlotPoint p2 = getter2(i); - FitPoint(p1); - FitPoint(p2); - } - } - - ImDrawList & DrawList = *ImGui::GetWindowDrawList(); - ImPlotState* plot = gp.CurrentPlot; - const int y_axis = plot->CurrentYAxis; - - ImU32 col = ImGui::GetColorU32(GetItemFillColor(item)); - - PushPlotClipRect(); - if (HasFlag(plot->XAxis.Flags, ImPlotAxisFlags_LogScale) && HasFlag(plot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale)) - RenderPrimitives(ShadedRenderer(getter1,getter2,TransformerLogLog(y_axis), col), DrawList); - else if (HasFlag(plot->XAxis.Flags, ImPlotAxisFlags_LogScale)) - RenderPrimitives(ShadedRenderer(getter1,getter2,TransformerLogLin(y_axis), col), DrawList); - else if (HasFlag(plot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale)) - RenderPrimitives(ShadedRenderer(getter1,getter2,TransformerLinLog(y_axis), col), DrawList); - else - RenderPrimitives(ShadedRenderer(getter1,getter2,TransformerLinLin(y_axis), col), DrawList); - PopPlotClipRect(); -} - -//----------------------------------------------------------------------------- -// float - -void PlotShaded(const char* label_id, const float* xs, const float* ys1, const float* ys2, int count, int offset, int stride) { - GetterXsYs getter1(xs, ys1, count, offset, stride); - GetterXsYs getter2(xs, ys2, count, offset, stride); - PlotShadedEx(label_id, getter1, getter2); -} - -void PlotShaded(const char* label_id, const float* xs, const float* ys, int count, float y_ref, int offset, int stride) { - GetterXsYs getter1(xs, ys, count, offset, stride); - GetterXsYRef getter2(xs, y_ref, count, offset, stride); - PlotShadedEx(label_id, getter1, getter2); -} - -//----------------------------------------------------------------------------- -// double - -void PlotShaded(const char* label_id, const double* xs, const double* ys1, const double* ys2, int count, int offset, int stride) { - GetterXsYs getter1(xs, ys1, count, offset, stride); - GetterXsYs getter2(xs, ys2, count, offset, stride); - PlotShadedEx(label_id, getter1, getter2); -} - -void PlotShaded(const char* label_id, const double* xs, const double* ys, int count, double y_ref, int offset, int stride) { - GetterXsYs getter1(xs, ys, count, offset, stride); - GetterXsYRef getter2(xs, y_ref, count, offset, stride); - PlotShadedEx(label_id, getter1, getter2); -} - -//----------------------------------------------------------------------------- -// PLOT BAR V -//----------------------------------------------------------------------------- - -template -void PlotBarsEx(const char* label_id, Getter getter, TWidth width) { - ImPlotContext& gp = *GImPlot; - IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotBars() needs to be called between BeginPlot() and EndPlot()!"); - - ImPlotItem* item = RegisterItem(label_id); - if (!item->Show) - return; - TryRecolorItem(item, ImPlotCol_Fill); - - const TWidth half_width = width / 2; - // find data extents - if (gp.FitThisFrame) { - for (int i = 0; i < getter.Count; ++i) { - ImPlotPoint p = getter(i); - FitPoint(ImPlotPoint(p.x - half_width, p.y)); - FitPoint(ImPlotPoint(p.x + half_width, 0)); - } - } - - ImU32 col_line = ImGui::GetColorU32(GetLineColor(item)); - ImU32 col_fill = ImGui::GetColorU32(GetItemFillColor(item)); - const bool rend_fill = WillFillRender(); - bool rend_line = WillLineRender(); - if (rend_fill && col_line == col_fill) - rend_line = false; - - ImDrawList & DrawList = *ImGui::GetWindowDrawList(); - PushPlotClipRect(); - for (int i = 0; i < getter.Count; ++i) { - ImPlotPoint p = getter(i); - if (p.y == 0) - continue; - ImVec2 a = PlotToPixels(p.x - half_width, p.y); - ImVec2 b = PlotToPixels(p.x + half_width, 0); - if (rend_fill) - DrawList.AddRectFilled(a, b, col_fill); - if (rend_line) - DrawList.AddRect(a, b, col_line); - } - PopPlotClipRect(); -} - -//----------------------------------------------------------------------------- -// float - -void PlotBars(const char* label_id, const float* values, int count, float width, float shift, int offset, int stride) { - GetterBarV getter(values,shift,count,offset,stride); - PlotBarsEx(label_id, getter, width); -} - -void PlotBars(const char* label_id, const float* xs, const float* ys, int count, float width, int offset, int stride) { - GetterXsYs getter(xs,ys,count,offset,stride); - PlotBarsEx(label_id, getter, width); -} - -//----------------------------------------------------------------------------- -// double - -void PlotBars(const char* label_id, const double* values, int count, double width, double shift, int offset, int stride) { - GetterBarV getter(values,shift,count,offset,stride); - PlotBarsEx(label_id, getter, width); -} - -void PlotBars(const char* label_id, const double* xs, const double* ys, int count, double width, int offset, int stride) { - GetterXsYs getter(xs,ys,count,offset,stride); - PlotBarsEx(label_id, getter, width); -} - -//----------------------------------------------------------------------------- -// custom - -void PlotBars(const char* label_id, ImPlotPoint (*getter_func)(void* data, int idx), void* data, int count, double width, int offset) { - GetterFuncPtrImPlotPoint getter(getter_func, data, count, offset); - PlotBarsEx(label_id, getter, width); -} - -//----------------------------------------------------------------------------- -// PLOT BAR H -//----------------------------------------------------------------------------- - -// TODO: Migrate to RenderPrimitives - -template -void PlotBarsHEx(const char* label_id, Getter getter, THeight height) { - ImPlotContext& gp = *GImPlot; - IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotBarsH() needs to be called between BeginPlot() and EndPlot()!"); - - ImPlotItem* item = RegisterItem(label_id); - if (!item->Show) - return; - TryRecolorItem(item, ImPlotCol_Fill); - - const THeight half_height = height / 2; - // find data extents - if (gp.FitThisFrame) { - for (int i = 0; i < getter.Count; ++i) { - ImPlotPoint p = getter(i); - FitPoint(ImPlotPoint(0, p.y - half_height)); - FitPoint(ImPlotPoint(p.x, p.y + half_height)); - } - } - - ImU32 col_line = ImGui::GetColorU32(GetLineColor(item)); - ImU32 col_fill = ImGui::GetColorU32(GetItemFillColor(item)); - const bool rend_fill = WillFillRender(); - bool rend_line = WillLineRender(); - if (rend_fill && col_line == col_fill) - rend_line = false; - - PushPlotClipRect(); - ImDrawList & DrawList = *ImGui::GetWindowDrawList(); - for (int i = 0; i < getter.Count; ++i) { - ImPlotPoint p = getter(i); - if (p.x == 0) - continue; - ImVec2 a = PlotToPixels(0, p.y - half_height); - ImVec2 b = PlotToPixels(p.x, p.y + half_height); - if (rend_fill) - DrawList.AddRectFilled(a, b, col_fill); - if (rend_line) - DrawList.AddRect(a, b, col_line); - } - PopPlotClipRect(); -} - -//----------------------------------------------------------------------------- -// float - -void PlotBarsH(const char* label_id, const float* values, int count, float height, float shift, int offset, int stride) { - GetterBarH getter(values,shift,count,offset,stride); - PlotBarsHEx(label_id, getter, height); -} - -void PlotBarsH(const char* label_id, const float* xs, const float* ys, int count, float height, int offset, int stride) { - GetterXsYs getter(xs,ys,count,offset,stride); - PlotBarsHEx(label_id, getter, height); -} - -//----------------------------------------------------------------------------- -// double - -void PlotBarsH(const char* label_id, const double* values, int count, double height, double shift, int offset, int stride) { - GetterBarH getter(values,shift,count,offset,stride); - PlotBarsHEx(label_id, getter, height); -} - -void PlotBarsH(const char* label_id, const double* xs, const double* ys, int count, double height, int offset, int stride) { - GetterXsYs getter(xs,ys,count,offset,stride); - PlotBarsHEx(label_id, getter, height); -} - -//----------------------------------------------------------------------------- -// custom - -void PlotBarsH(const char* label_id, ImPlotPoint (*getter_func)(void* data, int idx), void* data, int count, double height, int offset) { - GetterFuncPtrImPlotPoint getter(getter_func, data, count, offset); - PlotBarsHEx(label_id, getter, height); -} - -//----------------------------------------------------------------------------- -// PLOT ERROR BARS -//----------------------------------------------------------------------------- - -template -void PlotErrorBarsEx(const char* label_id, Getter getter) { - ImPlotContext& gp = *GImPlot; - IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotErrorBars() needs to be called between BeginPlot() and EndPlot()!"); - - ImPlotItem* item = RegisterItem(label_id); - if (!item->Show) - return; - - // find data extents - if (gp.FitThisFrame) { - for (int i = 0; i < getter.Count; ++i) { - ImPlotPointError e = getter(i); - FitPoint(ImPlotPoint(e.x , e.y - e.neg)); - FitPoint(ImPlotPoint(e.x , e.y + e.pos )); - } - } - - const ImU32 col = ImGui::GetColorU32(GetErrorBarColor()); - const bool rend_whisker = gp.Style.ErrorBarSize > 0; - const float half_whisker = gp.Style.ErrorBarSize * 0.5f; - - ImDrawList & DrawList = *ImGui::GetWindowDrawList(); - - PushPlotClipRect(); - for (int i = 0; i < getter.Count; ++i) { - ImPlotPointError e = getter(i); - ImVec2 p1 = PlotToPixels(e.x, e.y - e.neg); - ImVec2 p2 = PlotToPixels(e.x, e.y + e.pos); - DrawList.AddLine(p1,p2,col, gp.Style.ErrorBarWeight); - if (rend_whisker) { - DrawList.AddLine(p1 - ImVec2(half_whisker, 0), p1 + ImVec2(half_whisker, 0), col, gp.Style.ErrorBarWeight); - DrawList.AddLine(p2 - ImVec2(half_whisker, 0), p2 + ImVec2(half_whisker, 0), col, gp.Style.ErrorBarWeight); - } - } - PopPlotClipRect(); -} - -//----------------------------------------------------------------------------- -// float - -void PlotErrorBars(const char* label_id, const float* xs, const float* ys, const float* err, int count, int offset, int stride) { - GetterError getter(xs, ys, err, err, count, offset, stride); - PlotErrorBarsEx(label_id, getter); -} - -void PlotErrorBars(const char* label_id, const float* xs, const float* ys, const float* neg, const float* pos, int count, int offset, int stride) { - GetterError getter(xs, ys, neg, pos, count, offset, stride); - PlotErrorBarsEx(label_id, getter); -} - -//----------------------------------------------------------------------------- -// double - -void PlotErrorBars(const char* label_id, const double* xs, const double* ys, const double* err, int count, int offset, int stride) { - GetterError getter(xs, ys, err, err, count, offset, stride); - PlotErrorBarsEx(label_id, getter); -} - -void PlotErrorBars(const char* label_id, const double* xs, const double* ys, const double* neg, const double* pos, int count, int offset, int stride) { - GetterError getter(xs, ys, neg, pos, count, offset, stride); - PlotErrorBarsEx(label_id, getter); -} - -//----------------------------------------------------------------------------- -// PLOT ERROR BARS H -//----------------------------------------------------------------------------- - -template -void PlotErrorBarsHEx(const char* label_id, Getter getter) { - ImPlotContext& gp = *GImPlot; - IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotErrorBarsH() needs to be called between BeginPlot() and EndPlot()!"); - - ImPlotItem* item = RegisterItem(label_id); - if (!item->Show) - return; - - // find data extents - if (gp.FitThisFrame) { - for (int i = 0; i < getter.Count; ++i) { - ImPlotPointError e = getter(i); - FitPoint(ImPlotPoint(e.x - e.neg, e.y)); - FitPoint(ImPlotPoint(e.x + e.pos, e.y)); - } - } - - const ImU32 col = ImGui::GetColorU32(GetErrorBarColor()); - const bool rend_whisker = gp.Style.ErrorBarSize > 0; - const float half_whisker = gp.Style.ErrorBarSize * 0.5f; - - ImDrawList& DrawList = *ImGui::GetWindowDrawList(); - - PushPlotClipRect(); - for (int i = 0; i < getter.Count; ++i) { - ImPlotPointError e = getter(i); - ImVec2 p1 = PlotToPixels(e.x - e.neg, e.y); - ImVec2 p2 = PlotToPixels(e.x + e.pos, e.y); - DrawList.AddLine(p1, p2, col, gp.Style.ErrorBarWeight); - if (rend_whisker) { - DrawList.AddLine(p1 - ImVec2(0, half_whisker), p1 + ImVec2(0, half_whisker), col, gp.Style.ErrorBarWeight); - DrawList.AddLine(p2 - ImVec2(0, half_whisker), p2 + ImVec2(0, half_whisker), col, gp.Style.ErrorBarWeight); - } - } - PopPlotClipRect(); -} - -//----------------------------------------------------------------------------- -// float - -void PlotErrorBarsH(const char* label_id, const float* xs, const float* ys, const float* err, int count, int offset, int stride) { - GetterError getter(xs, ys, err, err, count, offset, stride); - PlotErrorBarsHEx(label_id, getter); -} - -void PlotErrorBarsH(const char* label_id, const float* xs, const float* ys, const float* neg, const float* pos, int count, int offset, int stride) { - GetterError getter(xs, ys, neg, pos, count, offset, stride); - PlotErrorBarsHEx(label_id, getter); -} - -//----------------------------------------------------------------------------- -// double - -void PlotErrorBarsH(const char* label_id, const double* xs, const double* ys, const double* err, int count, int offset, int stride) { - GetterError getter(xs, ys, err, err, count, offset, stride); - PlotErrorBarsHEx(label_id, getter); -} - -void PlotErrorBarsH(const char* label_id, const double* xs, const double* ys, const double* neg, const double* pos, int count, int offset, int stride) { - GetterError getter(xs, ys, neg, pos, count, offset, stride); - PlotErrorBarsHEx(label_id, getter); -} - - -//----------------------------------------------------------------------------- -// PLOT PIE CHART -//----------------------------------------------------------------------------- - -inline void RenderPieSlice(ImDrawList& DrawList, const ImPlotPoint& center, double radius, double a0, double a1, ImU32 col) { - static const float resolution = 50 / (2 * IM_PI); - static ImVec2 buffer[50]; - buffer[0] = PlotToPixels(center); - int n = ImMax(3, (int)((a1 - a0) * resolution)); - double da = (a1 - a0) / (n - 1); - for (int i = 0; i < n; ++i) { - double a = a0 + i * da; - buffer[i + 1] = PlotToPixels(center.x + radius * cos(a), center.y + radius * sin(a)); - } - DrawList.AddConvexPolyFilled(buffer, n + 1, col); -} - -template -void PlotPieChartEx(const char** label_ids, const T* values, int count, T x, T y, T radius, bool normalize, const char* fmt, T angle0) { - ImPlotContext& gp = *GImPlot; - IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotPieChart() needs to be called between BeginPlot() and EndPlot()!"); - ImDrawList & DrawList = *ImGui::GetWindowDrawList(); - - T sum = 0; - for (int i = 0; i < count; ++i) - sum += values[i]; - - normalize = normalize || sum > 1.0f; - - ImPlotPoint center(x,y); - - PushPlotClipRect(); - T a0 = angle0 * 2 * IM_PI / 360.0f; - T a1 = angle0 * 2 * IM_PI / 360.0f; - for (int i = 0; i < count; ++i) { - ImPlotItem* item = RegisterItem(label_ids[i]); - ImU32 col = ImGui::GetColorU32(GetItemFillColor(item)); - T percent = normalize ? values[i] / sum : values[i]; - a1 = a0 + 2 * IM_PI * percent; - if (item->Show) { - if (percent < 0.5) { - RenderPieSlice(DrawList, center, radius, a0, a1, col); - } - else { - RenderPieSlice(DrawList, center, radius, a0, a0 + (a1 - a0) * 0.5f, col); - RenderPieSlice(DrawList, center, radius, a0 + (a1 - a0) * 0.5f, a1, col); - } - } - a0 = a1; - } - if (fmt != NULL) { - a0 = angle0 * 2 * IM_PI / 360.0f; - a1 = angle0 * 2 * IM_PI / 360.0f; - char buffer[32]; - for (int i = 0; i < count; ++i) { - ImPlotItem* item = GetLegendItem(label_ids[i]); - T percent = normalize ? values[i] / sum : values[i]; - a1 = a0 + 2 * IM_PI * percent; - if (item->Show) { - sprintf(buffer, fmt, values[i]); - ImVec2 size = ImGui::CalcTextSize(buffer); - T angle = a0 + (a1 - a0) * 0.5f; - ImVec2 pos = PlotToPixels(center.x + 0.5f * radius * cos(angle), center.y + 0.5f * radius * sin(angle)); - ImU32 col = CalcTextColor(GetItemFillColor(item)); - DrawList.AddText(pos - size * 0.5f, col, buffer); - } - a0 = a1; - } - } - PopPlotClipRect(); -} - -//----------------------------------------------------------------------------- -// float - -void PlotPieChart(const char** label_ids, const float* values, int count, float x, float y, float radius, bool normalize, const char* fmt, float angle0) { - return PlotPieChartEx(label_ids, values, count, x, y, radius, normalize, fmt, angle0); -} - -//----------------------------------------------------------------------------- -// double - -void PlotPieChart(const char** label_ids, const double* values, int count, double x, double y, double radius, bool normalize, const char* fmt, double angle0) { - return PlotPieChartEx(label_ids, values, count, x, y, radius, normalize, fmt, angle0); -} - -//----------------------------------------------------------------------------- -// PLOT HEATMAP -//----------------------------------------------------------------------------- - -template -void RenderHeatmap(Transformer transformer, ImDrawList& DrawList, const T* values, int rows, int cols, T scale_min, T scale_max, const char* fmt, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max) { - ImPlotContext& gp = *GImPlot; - const double w = (bounds_max.x - bounds_min.x) / cols; - const double h = (bounds_max.y - bounds_min.y) / rows; - const ImPlotPoint half_size(w*0.5,h*0.5); - int i = 0; - for (int r = 0; r < rows; ++r) { - for (int c = 0; c < cols; ++c) { - ImPlotPoint p; - p.x = bounds_min.x + 0.5*w + c*w; - p.y = bounds_max.y - (0.5*h + r*h); - ImVec2 a = transformer(p.x - half_size.x, p.y - half_size.y); - ImVec2 b = transformer(p.x + half_size.x, p.y + half_size.y); - float t = (float)Remap(values[i], scale_min, scale_max, T(0), T(1)); - ImVec4 color = LerpColormap(t); - color.w *= gp.Style.FillAlpha; - ImU32 col = ImGui::GetColorU32(color); - DrawList.AddRectFilled(a, b, col); - i++; - } - } - if (fmt != NULL) { - i = 0; - for (int r = 0; r < rows; ++r) { - for (int c = 0; c < cols; ++c) { - ImPlotPoint p; - p.x = bounds_min.x + 0.5*w + c*w; - p.y = bounds_min.y + 1 - (0.5*h + r*h); - ImVec2 px = transformer(p); - char buff[32]; - sprintf(buff, fmt, values[i]); - ImVec2 size = ImGui::CalcTextSize(buff); - float t = (float)Remap(values[i], scale_min, scale_max, T(0), T(1)); - ImVec4 color = LerpColormap(t); - ImU32 col = CalcTextColor(color); - DrawList.AddText(px - size * 0.5f, col, buff); - i++; - } - } - } -} - -template -void PlotHeatmapEx(const char* label_id, const T* values, int rows, int cols, T scale_min, T scale_max, const char* fmt, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max) { - ImPlotContext& gp = *GImPlot; - IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotHeatmap() needs to be called between BeginPlot() and EndPlot()!"); - IM_ASSERT_USER_ERROR(scale_min != scale_max, "Scale values must be different!"); - ImPlotItem* item = RegisterItem(label_id); - if (!item->Show) - return; - if (gp.FitThisFrame) { - FitPoint(bounds_min); - FitPoint(bounds_max); - } - ImDrawList& DrawList = *ImGui::GetWindowDrawList(); - ImGui::PushClipRect(gp.BB_Plot.Min, gp.BB_Plot.Max, true); - ImPlotState* plot = gp.CurrentPlot; - int y_axis = plot->CurrentYAxis; - if (HasFlag(plot->XAxis.Flags, ImPlotAxisFlags_LogScale) && HasFlag(plot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale)) - RenderHeatmap(TransformerLogLog(y_axis), DrawList, values, rows, cols, scale_min, scale_max, fmt, bounds_min, bounds_max); - else if (HasFlag(plot->XAxis.Flags, ImPlotAxisFlags_LogScale)) - RenderHeatmap(TransformerLogLin(y_axis), DrawList, values, rows, cols, scale_min, scale_max, fmt, bounds_min, bounds_max); - else if (HasFlag(plot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale)) - RenderHeatmap(TransformerLinLog(y_axis), DrawList, values, rows, cols, scale_min, scale_max, fmt, bounds_min, bounds_max); - else - RenderHeatmap(TransformerLinLin(y_axis), DrawList, values, rows, cols, scale_min, scale_max, fmt, bounds_min, bounds_max); - ImGui::PopClipRect(); -} - -//----------------------------------------------------------------------------- -// float - -void PlotHeatmap(const char* label_id, const float* values, int rows, int cols, float scale_min, float scale_max, const char* fmt, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max) { - return PlotHeatmapEx(label_id, values, rows, cols, scale_min, scale_max, fmt, bounds_min, bounds_max); -} - -//----------------------------------------------------------------------------- -// double - -void PlotHeatmap(const char* label_id, const double* values, int rows, int cols, double scale_min, double scale_max, const char* fmt, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max) { - return PlotHeatmapEx(label_id, values, rows, cols, scale_min, scale_max, fmt, bounds_min, bounds_max); -} - -//----------------------------------------------------------------------------- -// PLOT DIGITAL -//----------------------------------------------------------------------------- - -template -inline void PlotDigitalEx(const char* label_id, Getter getter) -{ - ImPlotContext& gp = *GImPlot; - IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotDigital() needs to be called between BeginPlot() and EndPlot()!"); - - ImPlotItem* item = RegisterItem(label_id); - if (!item->Show) - return; - TryRecolorItem(item, ImPlotCol_Line); - - // render digital signals as "pixel bases" rectangles - PushPlotClipRect(); - if (getter.Count > 1 && WillLineRender()) { - ImDrawList & DrawList = *ImGui::GetWindowDrawList(); - const float line_weight = item->Highlight ? gp.Style.LineWeight * 2 : gp.Style.LineWeight; - const int y_axis = gp.CurrentPlot->CurrentYAxis; - int pixYMax = 0; - ImPlotPoint itemData1 = getter(0); - for (int i = 0; i < getter.Count; ++i) { - ImPlotPoint itemData2 = getter(i); - if (NanOrInf(itemData1.y)) { - itemData1 = itemData2; - continue; - } - if (NanOrInf(itemData2.y)) itemData2.y = ConstrainNan(ConstrainInf(itemData2.y)); - int pixY_0 = (int)(line_weight); - itemData1.y = ImMax(0.0, itemData1.y); - float pixY_1_float = gp.Style.DigitalBitHeight * (float)itemData1.y; - int pixY_1 = (int)(pixY_1_float); //allow only positive values - int pixY_chPosOffset = (int)(ImMax(gp.Style.DigitalBitHeight, pixY_1_float) + gp.Style.DigitalBitGap); - pixYMax = ImMax(pixYMax, pixY_chPosOffset); - ImVec2 pMin = PlotToPixels(itemData1); - ImVec2 pMax = PlotToPixels(itemData2); - int pixY_Offset = 20; //20 pixel from bottom due to mouse cursor label - pMin.y = (gp.PixelRange[y_axis].Min.y) + ((-gp.DigitalPlotOffset) - pixY_Offset); - pMax.y = (gp.PixelRange[y_axis].Min.y) + ((-gp.DigitalPlotOffset) - pixY_0 - pixY_1 - pixY_Offset); - //plot only one rectangle for same digital state - while (((i+2) < getter.Count) && (itemData1.y == itemData2.y)) { - const int in = (i + 1); - itemData2 = getter(in); - if (NanOrInf(itemData2.y)) break; - pMax.x = PlotToPixels(itemData2).x; - i++; - } - //do not extend plot outside plot range - if (pMin.x < gp.PixelRange[y_axis].Min.x) pMin.x = gp.PixelRange[y_axis].Min.x; - if (pMax.x < gp.PixelRange[y_axis].Min.x) pMax.x = gp.PixelRange[y_axis].Min.x; - if (pMin.x > gp.PixelRange[y_axis].Max.x) pMin.x = gp.PixelRange[y_axis].Max.x; - if (pMax.x > gp.PixelRange[y_axis].Max.x) pMax.x = gp.PixelRange[y_axis].Max.x; - //plot a rectangle that extends up to x2 with y1 height - if ((pMax.x > pMin.x) && (gp.BB_Plot.Contains(pMin) || gp.BB_Plot.Contains(pMax))) { - ImVec4 colAlpha = item->Color; - colAlpha.w = item->Highlight ? 1.0f : 0.9f; - DrawList.AddRectFilled(pMin, pMax, ImGui::GetColorU32(colAlpha)); - } - itemData1 = itemData2; - } - gp.DigitalPlotItemCnt++; - gp.DigitalPlotOffset += pixYMax; - } - PopPlotClipRect(); -} - -//----------------------------------------------------------------------------- -// float - -void PlotDigital(const char* label_id, const float* xs, const float* ys, int count, int offset, int stride) { - GetterXsYs getter(xs,ys,count,offset,stride); - return PlotDigitalEx(label_id, getter); -} - -//----------------------------------------------------------------------------- -// double - -void PlotDigital(const char* label_id, const double* xs, const double* ys, int count, int offset, int stride) { - GetterXsYs getter(xs,ys,count,offset,stride); - return PlotDigitalEx(label_id, getter); -} - -//----------------------------------------------------------------------------- -// custom - -void PlotDigital(const char* label_id, ImPlotPoint (*getter_func)(void* data, int idx), void* data, int count, int offset) { - GetterFuncPtrImPlotPoint getter(getter_func,data,count,offset); - return PlotDigitalEx(label_id, getter); -} - -//----------------------------------------------------------------------------- -// PLOT TEXT -//----------------------------------------------------------------------------- -// float - -void PlotText(const char* text, float x, float y, bool vertical, const ImVec2& pixel_offset) { - return PlotText(text, (double)x, (double)y, vertical, pixel_offset); -} - -//----------------------------------------------------------------------------- -// double -void PlotText(const char* text, double x, double y, bool vertical, const ImVec2& pixel_offset) { - ImPlotContext& gp = *GImPlot; - IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotText() needs to be called between BeginPlot() and EndPlot()!"); - ImDrawList & DrawList = *ImGui::GetWindowDrawList(); - PushPlotClipRect(); - ImVec2 pos = PlotToPixels(ImPlotPoint(x,y)) + pixel_offset; - ImU32 colTxt = ImGui::GetColorU32(ImGuiCol_Text); - if (vertical) - AddTextVertical(&DrawList, text, pos, colTxt); - else - DrawList.AddText(pos, colTxt, text); - PopPlotClipRect(); -} - //------------------------------------------------------------------------------ // COLORMAPS //------------------------------------------------------------------------------ -void SetColormap(const ImVec4* colors, int num_colors) { - ImPlotContext& gp = *GImPlot; - IM_ASSERT_USER_ERROR(num_colors > 1, "The number of colors must be greater than 1!"); - static ImVector user_colormap; - user_colormap.shrink(0); - user_colormap.reserve(num_colors); - for (int i = 0; i < num_colors; ++i) - user_colormap.push_back(colors[i]); - gp.Colormap = &user_colormap[0]; - gp.ColormapSize = num_colors; -} - // Returns the size of the current colormap int GetColormapSize() { ImPlotContext& gp = *GImPlot; @@ -3568,7 +1819,7 @@ ImVec4 LerpColormap(float t) { return gp.Colormap[i1]; float t1 = (float)i1 / (float)(gp.ColormapSize - 1); float t2 = (float)i2 / (float)(gp.ColormapSize - 1); - float tr = Remap(t, t1, t2, 0.0f, 1.0f); + float tr = ImRemap(t, t1, t2, 0.0f, 1.0f); return ImLerp(gp.Colormap[i1], gp.Colormap[i2], tr); } @@ -3623,7 +1874,7 @@ void ShowColormapScale(double scale_min, double scale_max, float height) { ImGui::PushClipRect(bb_frame.Min, bb_frame.Max, true); for (int i = 0; i < ticks.Size; ++i) { - float ypos = Remap((float)ticks[i].PlotPos, (float)range.Max, (float)range.Min, bb_grad.Min.y, bb_grad.Max.y); + float ypos = ImRemap((float)ticks[i].PlotPos, (float)range.Max, (float)range.Min, bb_grad.Min.y, bb_grad.Max.y); if (ypos < bb_grad.Max.y - 2 && ypos > bb_grad.Min.y + 2) DrawList.AddLine(ImVec2(bb_grad.Max.x-1, ypos), ImVec2(bb_grad.Max.x - (ticks[i].Major ? 10.0f : 5.0f), ypos), col_border, 1.0f); DrawList.AddText(ImVec2(bb_grad.Max.x-1, ypos) + ImVec2(txt_off, -ticks[i].Size.y * 0.5f), ImGui::GetColorU32(ImGuiCol_Text), txt_buff.Buf.Data + ticks[i].TextOffset); @@ -3636,7 +1887,7 @@ void ShowColormapScale(double scale_min, double scale_max, float height) { void SetColormapEx(ImPlotColormap colormap, int samples, ImPlotContext* ctx) { static int csizes[ImPlotColormap_COUNT] = {10,9,9,12,11,11,11,11,11,11}; - static OffsetCalculator coffs(csizes); + static ImOffsetCalculator coffs(csizes); static ImVec4 cdata[] = { // ImPlotColormap_Default // X11 Named Colors ImVec4(0.0f, 0.7490196228f, 1.0f, 1.0f), // Blues::DeepSkyBlue, @@ -3764,12 +2015,27 @@ void SetColormapEx(ImPlotColormap colormap, int samples, ImPlotContext* ctx) { float t = i * 1.0f / (samples - 1); resampled[i] = LerpColormap(t); } - SetColormap(&resampled[0], samples); + SetColormapEx(&resampled[0], samples, ctx); } } +void SetColormapEx(const ImVec4* colors, int num_colors, ImPlotContext* ctx) { + IM_ASSERT_USER_ERROR(num_colors > 1, "The number of colors must be greater than 1!"); + static ImVector user_colormap; + user_colormap.shrink(0); + user_colormap.reserve(num_colors); + for (int i = 0; i < num_colors; ++i) + user_colormap.push_back(colors[i]); + ctx->Colormap = &user_colormap[0]; + ctx->ColormapSize = num_colors; +} + void SetColormap(ImPlotColormap colormap, int samples) { SetColormapEx(colormap, samples, GImPlot); } +void SetColormap(const ImVec4* colors, int num_colors) { + SetColormapEx(colors, num_colors, GImPlot); +} + } // namespace ImPlot diff --git a/implot.h b/implot.h index 2fb0156..621b6b4 100644 --- a/implot.h +++ b/implot.h @@ -25,12 +25,14 @@ #pragma once #include "imgui.h" +#define IMPLOT_VERSION "0.5 WIP" + //----------------------------------------------------------------------------- -// Forward declarations and basic types +// Forward Declarations and Basic Types //----------------------------------------------------------------------------- // Forward declarations -struct ImPlotContext; // ImPlot context (opaque struct) +struct ImPlotContext; // ImPlot context (opaque struct, see implot_internal.h) // Enums/Flags typedef int ImPlotFlags; // -> enum ImPlotFlags_ @@ -137,7 +139,7 @@ enum ImPlotColormap_ { struct ImPlotPoint { double x, y; ImPlotPoint() { x = y = 0.0; } - ImPlotPoint(double _x, double _y) { x = _x; y = _y; } + ImPlotPoint(double _x, double _y) { x = _x; y = _y; } double operator[] (size_t idx) const { return (&x)[idx]; } double& operator[] (size_t idx) { return (&x)[idx]; } #ifdef IMPLOT_POINT_CLASS_EXTRA @@ -149,16 +151,15 @@ struct ImPlotPoint { struct ImPlotRange { double Min, Max; ImPlotRange(); - bool Contains(double value) const; - double Size() const; + bool Contains(double value) const { return value >= Min && value <= Max; }; + double Size() const { return Max - Min; }; }; // Combination of two ranges for X and Y axes. struct ImPlotLimits { ImPlotRange X, Y; - ImPlotLimits(); - bool Contains(const ImPlotPoint& p) const; - bool Contains(double x, double y) const; + bool Contains(const ImPlotPoint& p) const { return Contains(p.x, p.y); } + bool Contains(double x, double y) const { return X.Contains(x) && Y.Contains(y); } }; // Plot style structure @@ -193,9 +194,8 @@ struct ImPlotInputMap { ImPlotInputMap(); }; - //----------------------------------------------------------------------------- -// ImPlot end-user API +// ImPlot End-User API //----------------------------------------------------------------------------- namespace ImPlot { @@ -207,10 +207,10 @@ namespace ImPlot { // Creates a new ImPlot context. Call this after ImGui::CreateContext. ImPlotContext* CreateContext(); // Destroys an ImPlot context. Call this before ImGui::DestroyContext. NULL = destroy current context -void DestroyContext(ImPlotContext* ctx = NULL); +void DestroyContext(ImPlotContext* ctx = NULL); // Returns the current context. NULL if not context has ben set. ImPlotContext* GetCurrentContext(); -// Sets the current context. +// Sets the current context. void SetCurrentContext(ImPlotContext* ctx); //----------------------------------------------------------------------------- @@ -368,7 +368,7 @@ int GetColormapSize(); ImVec4 GetColormapColor(int index); // Linearly interpolates a color from the current colormap given t between 0 and 1. ImVec4 LerpColormap(float t); -// Returns the next unused colormap color and advances the colormap. Can be used to skip colors if desired. +// Returns the next unused colormap color and advances the colormap. Can be used to skip colors if desired. ImVec4 NextColormapColor(); //----------------------------------------------------------------------------- @@ -385,6 +385,7 @@ void SetNextPlotLimitsY(double y_min, double y_max, ImGuiCond cond = ImGuiCond_O // Set the X axis ticks and optionally the labels for the next plot. void SetNextPlotTicksX(const double* values, int n_ticks, const char** labels = NULL, bool show_default = false); void SetNextPlotTicksX(double x_min, double x_max, int n_ticks, const char** labels = NULL, bool show_default = false); + // Set the Y axis ticks and optionally the labels for the next plot. void SetNextPlotTicksY(const double* values, int n_ticks, const char** labels = NULL, bool show_default = false, int y_axis = 0); void SetNextPlotTicksY(double y_min, double y_max, int n_ticks, const char** labels = NULL, bool show_default = false, int y_axis = 0); @@ -399,8 +400,11 @@ ImVec2 GetPlotSize(); // Convert pixels to a position in the current plot's coordinate system. A negative y_axis uses the current value of SetPlotYAxis (0 initially). ImPlotPoint PixelsToPlot(const ImVec2& pix, int y_axis = -1); +ImPlotPoint PixelsToPlot(float x, float y, int y_axis = -1); + // Convert a position in the current plot's coordinate system to pixels. A negative y_axis uses the current value of SetPlotYAxis (0 initially). ImVec2 PlotToPixels(const ImPlotPoint& plt, int y_axis = -1); +ImVec2 PlotToPixels(double x, double y, int y_axis = -1); // Renders a vertical color scale using the current color map void ShowColormapScale(double scale_min, double scale_max, float height); diff --git a/implot_internal.h b/implot_internal.h index e69de29..9307a77 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -0,0 +1,447 @@ +// MIT License + +// Copyright (c) 2020 Evan Pezent + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// ImPlot v0.5 WIP + +// You may use this file to debug, understand or extend ImPlot features but we don't provide any guarantee of forward compatibility! + +//----------------------------------------------------------------------------- +// [SECTION] Header mess +//----------------------------------------------------------------------------- + +#pragma once + +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif + +#include "imgui_internal.h" + +#ifndef IMPLOT_VERSION +#error Must include implot.h before implot_internal.h +#endif + +//----------------------------------------------------------------------------- +// [SECTION] Forward declarations +//----------------------------------------------------------------------------- + +struct ImPlotTick; +struct ImPlotAxis; +struct ImPlotAxisState; +struct ImPlotAxisColor; +struct ImPlotItem; +struct ImPlotState; +struct ImPlotNextPlotData; + +//----------------------------------------------------------------------------- +// [SECTION] Context pointer +//----------------------------------------------------------------------------- + +extern ImPlotContext* GImPlot; // Current implicit context pointer + +//----------------------------------------------------------------------------- +// [SECTION] Macros +//----------------------------------------------------------------------------- + +// The maximum number of supported y-axes (DO NOT CHANGE THIS) +#define MAX_Y_AXES 3 + +//----------------------------------------------------------------------------- +// [SECTION] Generic helpers +//----------------------------------------------------------------------------- + +// Computes the common (base-10) logarithm +static inline float ImLog10(float x) { return log10f(x); } +static inline double ImLog10(double x) { return log10(x); } + +// Returns true if a flag is set +template +inline bool ImHasFlag(TSet set, TFlag flag) { return (set & flag) == flag; } + +// Flips a flag in a flagset +template +inline void ImFlipFlag(TSet& set, TFlag flag) { ImHasFlag(set, flag) ? set &= ~flag : set |= flag; } + +// Linearly remaps x from [x0 x1] to [y0 y1]. +template +inline T ImRemap(T x, T x0, T x1, T y0, T y1) { return y0 + (x - x0) * (y1 - y0) / (x1 - x0); } + +// Returns always positive modulo (r != 0) +inline int ImPosMod(int l, int r) { return (l % r + r) % r; } + +// Offset calculator helper +template +struct ImOffsetCalculator { + ImOffsetCalculator(int* sizes) { + Offsets[0] = 0; + for (int i = 1; i < Count; ++i) + Offsets[i] = Offsets[i-1] + sizes[i-1]; + } + int Offsets[Count]; +}; + +// Character buffer writer helper +struct ImBufferWriter +{ + char* const Buffer; + size_t Pos; + const size_t Size; + + ImBufferWriter(char* buffer, size_t size) : Buffer(buffer), Pos(0), Size(size) {} + + void Write(const char* fmt, ...) IM_FMTARGS(2) { + va_list argp; + va_start(argp, fmt); + const int written = ::vsnprintf(&Buffer[Pos], Size - Pos - 1, fmt, argp); + if (written > 0) + Pos += ImMin(size_t(written), Size-Pos-1); + va_end(argp); + } +}; + +//----------------------------------------------------------------------------- +// [SECTION] ImPlot Structs +//----------------------------------------------------------------------------- + +// Tick mark info +struct ImPlotTick +{ + double PlotPos; + float PixelPos; + ImVec2 Size; + int TextOffset; + bool Major; + bool RenderLabel; + bool Labeled; + + ImPlotTick(double value, bool major, bool render_label = true) { + PlotPos = value; + Major = major; + RenderLabel = render_label; + Labeled = false; + } +}; + +// Axis state information that must persist after EndPlot +struct ImPlotAxis +{ + bool Dragging; + bool Hovered; + ImPlotRange Range; + ImPlotAxisFlags Flags, PreviousFlags; + ImPlotAxis() { + Dragging = false; + Hovered = false; + Range.Min = 0; + Range.Max = 1; + Flags = PreviousFlags = ImPlotAxisFlags_Default; + } +}; + +// Axis state information only needed between BeginPlot/EndPlot +struct ImPlotAxisState +{ + ImPlotAxis* Axis; + bool HasRange; + ImGuiCond RangeCond; + bool Present; + int PresentSoFar; + bool Invert; + bool LockMin; + bool LockMax; + bool Lock; + + ImPlotAxisState(ImPlotAxis& axis, bool has_range, ImGuiCond range_cond, bool present, int previous_present) : + Axis(&axis), + HasRange(has_range), + RangeCond(range_cond), + Present(present), + PresentSoFar(previous_present + (Present ? 1 : 0)), + Invert(ImHasFlag(Axis->Flags, ImPlotAxisFlags_Invert)), + LockMin(ImHasFlag(Axis->Flags, ImPlotAxisFlags_LockMin) || (HasRange && RangeCond == ImGuiCond_Always)), + LockMax(ImHasFlag(Axis->Flags, ImPlotAxisFlags_LockMax) || (HasRange && RangeCond == ImGuiCond_Always)), + Lock(!Present || ((LockMin && LockMax) || (HasRange && RangeCond == ImGuiCond_Always))) + {} + + ImPlotAxisState() : + Axis(), HasRange(), RangeCond(), Present(), PresentSoFar(),Invert(),LockMin(), LockMax(), Lock() + {} +}; + +struct ImPlotAxisColor +{ + ImU32 Major, Minor, Txt; + ImPlotAxisColor() { Major = Minor = Txt = 0; } +}; + +// State information for Plot items +struct ImPlotItem +{ + bool Show; + bool SeenThisFrame; + bool Highlight; + ImVec4 Color; + int NameOffset; + ImGuiID ID; + + ImPlotItem() { + Show = true; + SeenThisFrame = false; + Highlight = false; + Color = ImPlot::NextColormapColor(); + NameOffset = -1; + ID = 0; + } + + ~ImPlotItem() { ID = 0; } +}; + +// Holds Plot state information that must persist after EndPlot +struct ImPlotState +{ + ImPool Items; + ImRect BB_Legend; + ImVec2 SelectStart; + bool Selecting; + bool Querying; + bool Queried; + bool DraggingQuery; + ImVec2 QueryStart; + ImRect QueryRect; + ImPlotAxis XAxis; + ImPlotAxis YAxis[MAX_Y_AXES]; + ImPlotFlags Flags, PreviousFlags; + int ColorIdx; + int CurrentYAxis; + + ImPlotState() { + Selecting = Querying = Queried = DraggingQuery = false; + SelectStart = QueryStart = ImVec2(0,0); + Flags = PreviousFlags = ImPlotFlags_Default; + ColorIdx = 0; + CurrentYAxis = 0; + } +}; + +// Temporary data storage for upcoming plot +struct ImPlotNextPlotData +{ + ImGuiCond XRangeCond; + ImGuiCond YRangeCond[MAX_Y_AXES]; + ImPlotRange X; + ImPlotRange Y[MAX_Y_AXES]; + bool HasXRange; + bool HasYRange[MAX_Y_AXES]; + bool ShowDefaultTicksX; + bool ShowDefaultTicksY[MAX_Y_AXES]; + + ImPlotNextPlotData() { + HasXRange = false; + ShowDefaultTicksX = true; + for (int i = 0; i < MAX_Y_AXES; ++i) { + HasYRange[i] = false; + ShowDefaultTicksY[i] = true; + } + } +}; + +// Holds state information that must persist between calls to BeginPlot()/EndPlot() +struct ImPlotContext { + // Plot States + ImPool Plots; + ImPlotState* CurrentPlot; + + // Legend + ImVector LegendIndices; + ImGuiTextBuffer LegendLabels; + + // Bounding Boxes + ImRect BB_Frame; + ImRect BB_Canvas; + ImRect BB_Plot; + + // Cached Colors + ImU32 Col_Frame; + ImU32 Col_Bg; + ImU32 Col_Border; + ImU32 Col_Txt; + ImU32 Col_TxtDis; + ImU32 Col_SlctBg; + ImU32 Col_SlctBd; + ImU32 Col_QryBg; + ImU32 Col_QryBd; + + // Axis States + ImPlotAxisColor Col_X; + ImPlotAxisColor Col_Y[MAX_Y_AXES]; + ImPlotAxisState X; + ImPlotAxisState Y[MAX_Y_AXES]; + + // Tick Marks and Labels + ImVector XTicks; + ImVector YTicks[MAX_Y_AXES]; + ImGuiTextBuffer XTickLabels; + ImGuiTextBuffer YTickLabels[MAX_Y_AXES]; + float AxisLabelReference[MAX_Y_AXES]; + + // Transformations and Data Extents + ImRect PixelRange[MAX_Y_AXES]; + double Mx; + double My[MAX_Y_AXES]; + double LogDenX; + double LogDenY[MAX_Y_AXES]; + ImPlotRange ExtentsX; + ImPlotRange ExtentsY[MAX_Y_AXES]; + + // Data Fitting Flags + bool FitThisFrame; + bool FitX; + bool FitY[MAX_Y_AXES]; + + // Hover states + bool Hov_Frame; + bool Hov_Plot; + + // Axis Rendering Flags + bool RenderX; + bool RenderY[MAX_Y_AXES]; + + // Axis Locking Flags + bool LockPlot; + bool ChildWindowMade; + + // Style and Colormaps + ImPlotStyle Style; + ImVector ColorModifiers; + ImVector StyleModifiers; + ImVec4* Colormap; + int ColormapSize; + + // Misc + int VisibleItemCount; + int DigitalPlotItemCnt; + int DigitalPlotOffset; + ImPlotNextPlotData NextPlotData; + ImPlotInputMap InputMap; + ImPlotPoint LastMousePos[MAX_Y_AXES]; +}; + +struct ImPlotAxisScale { + ImPlotAxisScale(int y_axis, float tx, float ty, float zoom_rate) { + ImPlotContext& gp = *GImPlot; + Min = ImPlot::PixelsToPlot(gp.BB_Plot.Min - gp.BB_Plot.GetSize() * ImVec2(tx * zoom_rate, ty * zoom_rate), y_axis); + Max = ImPlot::PixelsToPlot(gp.BB_Plot.Max + gp.BB_Plot.GetSize() * ImVec2((1 - tx) * zoom_rate, (1 - ty) * zoom_rate), y_axis); + } + ImPlotPoint Min, Max; +}; + +//----------------------------------------------------------------------------- +// [SECTION] Internal API +// No guarantee of forward compatibility here! +//----------------------------------------------------------------------------- + +namespace ImPlot { + +// Initializes an ImPlotContext +void Initialize(ImPlotContext* ctx); +// Resets an ImPlot context for the next call to BeginPlot +void Reset(ImPlotContext* ctx); +// Gets a plot from the current ImPlotContext +ImPlotState* GetPlot(const char* title); +// Gets the current plot from the current ImPlotContext +ImPlotState* GetCurrentPlot(); +// Updates pixel space to plot space transformation variables for the current plot +void UpdateTransformCache(); +// Extends the current plots axes so that it encompasses point p +void FitPoint(const ImPlotPoint& p); +// Register or get an existing item from the current plot +ImPlotItem* RegisterItem(const char* label_id); +// Get the ith plot item from the current plot +ImPlotItem* GetItem(int i); +// Get a plot item from the current plot +ImPlotItem* GetItem(const char* label_id); +// Gets a plot item from a specific plot +ImPlotItem* GetItem(const char* plot_title, const char* item_label_id); +// Returns the number of entries in the current legend +int GetLegendCount(); +// Gets the ith entry string for the current legend +const char* GetLegendLabel(int i); +// Populates a list of ImPlotTicks with automatically spaced ticks +void AddDefaultTicks(const ImPlotRange& range, int nMajor, int nMinor, bool logscale, ImVector &out); +// Populates a list of ImPlotTicks with custom spaced and labeled ticks +void AddCustomTicks(const double* values, const char** labels, int n, ImVector& ticks, ImGuiTextBuffer& buffer); +// Creates label information for a list of ImPlotTick +void LabelTicks(ImVector &ticks, bool scientific, ImGuiTextBuffer& buffer); +// Calculates the maximum width of a list of ImPlotTick +float MaxTickLabelWidth(ImVector& ticks); +// Updates axis ticks, lins, and label colors +void UpdateAxisColor(int axis_flag, ImPlotAxisColor* col); +// Sets the colormap for a particular ImPlotContext +void SetColormapEx(ImPlotColormap colormap, int samples, ImPlotContext* ctx); +void SetColormapEx(const ImVec4* colors, int num_colors, ImPlotContext* ctx); +// Draws vertical text. The position is the bottom left of the text rect. +void AddTextVertical(ImDrawList *DrawList, const char *text, ImVec2 pos, ImU32 text_color); +// Calculates the size of vertical text +ImVec2 CalcTextSizeVertical(const char *text); +// Returns white or black text given background color +inline ImU32 CalcTextColor(const ImVec4& bg) { return (bg.x * 0.299 + bg.y * 0.587 + bg.z * 0.114) > 0.729 ? IM_COL32_BLACK : IM_COL32_WHITE; } +// Rounds x to powers of 2,5 and 10 for generating axis labels +// Taken from Graphics Gems 1 Chapter 11.2, "Nice Numbers for Graph Labels" +double NiceNum(double x, bool round); +// Turns NANs to 0s +inline double ConstrainNan(double val) { return isnan(val) ? 0 : val; } +// Turns infinity to floating point maximums +inline double ConstrainInf(double val) { return val == HUGE_VAL ? DBL_MAX : val == -HUGE_VAL ? - DBL_MAX : val; } +// Turns numbers less than or equal to 0 to 0.001 (sort of arbitrary, is there a better way?) +inline double ConstrainLog(double val) { return val <= 0 ? 0.001f : val; } +// Returns true if val is NAN or INFINITY +inline bool NanOrInf(double val) { return val == HUGE_VAL || val == -HUGE_VAL || isnan(val); } +// Computes order of magnitude of double. +inline int OrderOfMagnitude(double val) { return val == 0 ? 0 : (int)(floor(log10(fabs(val)))); } +// Returns the precision required for a order of magnitude. +inline int OrderToPrecision(int order) { return order > 0 ? 0 : 1 - order; } +// Returns a floating point precision to use given a value +inline int Precision(double val) { return OrderToPrecision(OrderOfMagnitude(val)); } +// Returns the intersection point of two lines A and B (assumes they are not parallel!) +inline ImVec2 Intersection(const ImVec2& a1, const ImVec2& a2, const ImVec2& b1, const ImVec2& b2) { + float v1 = (a1.x * a2.y - a1.y * a2.x); + float v2 = (b1.x * b2.y - b1.y * b2.x); + float v3 = ((a1.x - a2.x) * (b1.y - b2.y) - (a1.y - a2.y) * (b1.x - b2.x)); + return ImVec2((v1 * (b1.x - b2.x) - v2 * (a1.x - a2.x)) / v3, (v1 * (b1.y - b2.y) - v2 * (a1.y - a2.y)) / v3); +} +// Fills a buffer with n samples linear interpolated from vmin to vmax +template +void FillRange(ImVector& buffer, int n, T vmin, T vmax) { + buffer.resize(n); + T step = (vmax - vmin) / (n - 1); + for (int i = 0; i < n; ++i) { + buffer[i] = vmin + i * step; + } +} +// Offsets and strides a data buffer +template +inline T OffsetAndStride(const T* data, int idx, int count, int offset, int stride) { + idx = ImPosMod(offset + idx, count); + return *(const T*)(const void*)((const unsigned char*)data + (size_t)idx * stride); +} + +} // namespace ImPlot \ No newline at end of file diff --git a/implot_items.cpp b/implot_items.cpp new file mode 100644 index 0000000..5837e52 --- /dev/null +++ b/implot_items.cpp @@ -0,0 +1,1442 @@ +// MIT License + +// Copyright (c) 2020 Evan Pezent + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// ImPlot v0.5 WIP + +#include "implot.h" +#include "implot_internal.h" + +#ifdef _MSC_VER +#define sprintf sprintf_s +#endif + +#define SQRT_1_2 0.70710678118f +#define SQRT_3_2 0.86602540378f + +#define IM_NORMALIZE2F_OVER_ZERO(VX, VY) \ + { \ + float d2 = VX * VX + VY * VY; \ + if (d2 > 0.0f) { \ + float inv_len = 1.0f / ImSqrt(d2); \ + VX *= inv_len; \ + VY *= inv_len; \ + } \ + } + +namespace ImPlot { + +//----------------------------------------------------------------------------- +// GETTERS +//----------------------------------------------------------------------------- + +template +struct GetterYs { + GetterYs(const T* ys, int count, int offset, int stride) { + Ys = ys; + Count = count; + Offset = count ? ImPosMod(offset, count) : 0; + Stride = stride; + } + const T* Ys; + int Count; + int Offset; + int Stride; + inline ImPlotPoint operator()(int idx) { + return ImPlotPoint((T)idx, OffsetAndStride(Ys, idx, Count, Offset, Stride)); + } +}; + +template +struct GetterXsYs { + GetterXsYs(const T* xs, const T* ys, int count, int offset, int stride) { + Xs = xs; Ys = ys; + Count = count; + Offset = count ? ImPosMod(offset, count) : 0; + Stride = stride; + } + const T* Xs; + const T* Ys; + int Count; + int Offset; + int Stride; + inline ImPlotPoint operator()(int idx) { + return ImPlotPoint(OffsetAndStride(Xs, idx, Count, Offset, Stride), OffsetAndStride(Ys, idx, Count, Offset, Stride)); + } +}; + +template +struct GetterXsYRef { + GetterXsYRef(const T* xs, T y_ref, int count, int offset, int stride) { + Xs = xs; + YRef = y_ref; + Count = count; + Offset = count ? ImPosMod(offset, count) : 0; + Stride = stride; + } + const T* Xs; + T YRef; + int Count; + int Offset; + int Stride; + inline ImPlotPoint operator()(int idx) { + return ImPlotPoint(OffsetAndStride(Xs, idx, Count, Offset, Stride), YRef); + } +}; + +struct GetterImVec2 { + GetterImVec2(const ImVec2* data, int count, int offset) { + Data = data; + Count = count; + Offset = count ? ImPosMod(offset, count) : 0; + } + inline ImPlotPoint operator()(int idx) { return ImPlotPoint(Data[idx].x, Data[idx].y); } + const ImVec2* Data; + int Count; + int Offset; +}; + +struct GetterImPlotPoint { + GetterImPlotPoint(const ImPlotPoint* data, int count, int offset) { + Data = data; + Count = count; + Offset = count ? ImPosMod(offset, count) : 0; + } + inline ImPlotPoint operator()(int idx) { return Data[idx]; } + const ImPlotPoint* Data; + int Count; + int Offset; +}; + +struct GetterFuncPtrImPlotPoint { + GetterFuncPtrImPlotPoint(ImPlotPoint (*g)(void* data, int idx), void* d, int count, int offset) { + getter = g; + Data = d; + Count = count; + Offset = count ? ImPosMod(offset, count) : 0; + } + inline ImPlotPoint operator()(int idx) { return getter(Data, idx); } + ImPlotPoint (*getter)(void* data, int idx); + void* Data; + int Count; + int Offset; +}; + +template +struct GetterBarV { + const T* Ys; T XShift; int Count; int Offset; int Stride; + GetterBarV(const T* ys, T xshift, int count, int offset, int stride) { Ys = ys; XShift = xshift; Count = count; Offset = offset; Stride = stride; } + inline ImPlotPoint operator()(int idx) { return ImPlotPoint((T)idx + XShift, OffsetAndStride(Ys, idx, Count, Offset, Stride)); } +}; + +template +struct GetterBarH { + const T* Xs; T YShift; int Count; int Offset; int Stride; + GetterBarH(const T* xs, T yshift, int count, int offset, int stride) { Xs = xs; YShift = yshift; Count = count; Offset = offset; Stride = stride; } + inline ImPlotPoint operator()(int idx) { return ImPlotPoint(OffsetAndStride(Xs, idx, Count, Offset, Stride), (T)idx + YShift); } +}; + +struct ImPlotPointError { + ImPlotPointError(double _x, double _y, double _neg, double _pos) { + x = _x; y = _y; neg = _neg; pos = _pos; + } + double x, y, neg, pos; +}; + +template +struct GetterError { + const T* Xs; const T* Ys; const T* Neg; const T* Pos; int Count; int Offset; int Stride; + GetterError(const T* xs, const T* ys, const T* neg, const T* pos, int count, int offset, int stride) { + Xs = xs; Ys = ys; Neg = neg; Pos = pos; Count = count; Offset = offset; Stride = stride; + } + ImPlotPointError operator()(int idx) { + return ImPlotPointError(OffsetAndStride(Xs, idx, Count, Offset, Stride), + OffsetAndStride(Ys, idx, Count, Offset, Stride), + OffsetAndStride(Neg, idx, Count, Offset, Stride), + OffsetAndStride(Pos, idx, Count, Offset, Stride)); + } +}; + +//----------------------------------------------------------------------------- +// TRANSFORMERS +//----------------------------------------------------------------------------- + +struct TransformerLinLin { + TransformerLinLin(int y_axis) : YAxis(y_axis) {} + + inline ImVec2 operator()(const ImPlotPoint& plt) { return (*this)(plt.x, plt.y); } + inline ImVec2 operator()(double x, double y) { + ImPlotContext& gp = *GImPlot; + return ImVec2( (float)(gp.PixelRange[YAxis].Min.x + gp.Mx * (x - gp.CurrentPlot->XAxis.Range.Min)), + (float)(gp.PixelRange[YAxis].Min.y + gp.My[YAxis] * (y - gp.CurrentPlot->YAxis[YAxis].Range.Min)) ); + } + + int YAxis; +}; + +struct TransformerLogLin { + TransformerLogLin(int y_axis) : YAxis(y_axis) {} + + inline ImVec2 operator()(const ImPlotPoint& plt) { return (*this)(plt.x, plt.y); } + inline ImVec2 operator()(double x, double y) { + ImPlotContext& gp = *GImPlot; + double t = ImLog10(x / gp.CurrentPlot->XAxis.Range.Min) / gp.LogDenX; + x = ImLerp(gp.CurrentPlot->XAxis.Range.Min, gp.CurrentPlot->XAxis.Range.Max, (float)t); + return ImVec2( (float)(gp.PixelRange[YAxis].Min.x + gp.Mx * (x - gp.CurrentPlot->XAxis.Range.Min)), + (float)(gp.PixelRange[YAxis].Min.y + gp.My[YAxis] * (y - gp.CurrentPlot->YAxis[YAxis].Range.Min)) ); + } + + int YAxis; +}; + +struct TransformerLinLog { + TransformerLinLog(int y_axis) : YAxis(y_axis) {} + + inline ImVec2 operator()(const ImPlotPoint& plt) { return (*this)(plt.x, plt.y); } + inline ImVec2 operator()(double x, double y) { + ImPlotContext& gp = *GImPlot; + double t = ImLog10(y / gp.CurrentPlot->YAxis[YAxis].Range.Min) / gp.LogDenY[YAxis]; + y = ImLerp(gp.CurrentPlot->YAxis[YAxis].Range.Min, gp.CurrentPlot->YAxis[YAxis].Range.Max, (float)t); + return ImVec2( (float)(gp.PixelRange[YAxis].Min.x + gp.Mx * (x - gp.CurrentPlot->XAxis.Range.Min)), + (float)(gp.PixelRange[YAxis].Min.y + gp.My[YAxis] * (y - gp.CurrentPlot->YAxis[YAxis].Range.Min)) ); + } + int YAxis; +}; + +struct TransformerLogLog { + TransformerLogLog(int y_axis) : YAxis(y_axis) {} + + inline ImVec2 operator()(const ImPlotPoint& plt) { return (*this)(plt.x, plt.y); } + inline ImVec2 operator()(double x, double y) { + ImPlotContext& gp = *GImPlot; + double t = ImLog10(x / gp.CurrentPlot->XAxis.Range.Min) / gp.LogDenX; + x = ImLerp(gp.CurrentPlot->XAxis.Range.Min, gp.CurrentPlot->XAxis.Range.Max, (float)t); + t = ImLog10(y / gp.CurrentPlot->YAxis[YAxis].Range.Min) / gp.LogDenY[YAxis]; + y = ImLerp(gp.CurrentPlot->YAxis[YAxis].Range.Min, gp.CurrentPlot->YAxis[YAxis].Range.Max, (float)t); + return ImVec2( (float)(gp.PixelRange[YAxis].Min.x + gp.Mx * (x - gp.CurrentPlot->XAxis.Range.Min)), + (float)(gp.PixelRange[YAxis].Min.y + gp.My[YAxis] * (y - gp.CurrentPlot->YAxis[YAxis].Range.Min)) ); + } + + int YAxis; +}; + +//----------------------------------------------------------------------------- +// RENDERERS +//----------------------------------------------------------------------------- + +/// Renders primitive shapes in bulk as efficiently as possible. +template +inline void RenderPrimitives(Renderer renderer, ImDrawList& DrawList) { + unsigned int prims = renderer.Prims; + unsigned int prims_culled = 0; + unsigned int idx = 0; + static const unsigned int max_idx = (unsigned int)(ImPow(2.0f, (float)(sizeof(ImDrawIdx) * 8)) - 1); + const ImVec2 uv = DrawList._Data->TexUvWhitePixel; + while (prims) { + // find how many can be reserved up to end of current draw command's limit + unsigned int cnt = (unsigned int)ImMin(prims, (max_idx - DrawList._VtxCurrentIdx) / Renderer::VtxConsumed); + // make sure at least this many elements can be rendered to avoid situations where at the end of buffer this slow path is not taken all the time + if (cnt >= ImMin(64u, prims)) { + if (prims_culled >= cnt) + prims_culled -= cnt; // reuse previous reservation + else { + DrawList.PrimReserve((cnt - prims_culled) * Renderer::IdxConsumed, (cnt - prims_culled) * Renderer::VtxConsumed); // add more elements to previous reservation + prims_culled = 0; + } + } + else + { + if (prims_culled > 0) { + DrawList.PrimUnreserve(prims_culled * Renderer::IdxConsumed, prims_culled * Renderer::VtxConsumed); + prims_culled = 0; + } + cnt = (unsigned int)ImMin(prims, (max_idx - 0/*DrawList._VtxCurrentIdx*/) / Renderer::VtxConsumed); + DrawList.PrimReserve(cnt * Renderer::IdxConsumed, cnt * Renderer::VtxConsumed); // reserve new draw command + } + prims -= cnt; + for (unsigned int ie = idx + cnt; idx != ie; ++idx) { + if (!renderer(DrawList, uv, idx)) + prims_culled++; + } + } + if (prims_culled > 0) + DrawList.PrimUnreserve(prims_culled * Renderer::IdxConsumed, prims_culled * Renderer::VtxConsumed); +} + +inline void TransformMarker(ImVec2* points, int n, const ImVec2& c, float s) { + for (int i = 0; i < n; ++i) { + points[i].x = c.x + points[i].x * s; + points[i].y = c.y + points[i].y * s; + } +} + +inline void MarkerGeneral(ImDrawList& DrawList, ImVec2* points, int n, const ImVec2& c, float s, bool outline, ImU32 col_outline, bool fill, ImU32 col_fill, float weight) { + TransformMarker(points, n, c, s); + if (fill) + DrawList.AddConvexPolyFilled(points, n, col_fill); + if (outline && !(fill && col_outline == col_fill)) { + for (int i = 0; i < n; ++i) + DrawList.AddLine(points[i], points[(i+1)%n], col_outline, weight); + } +} + +inline void MarkerCircle(ImDrawList& DrawList, const ImVec2& c, float s, bool outline, ImU32 col_outline, bool fill, ImU32 col_fill, float weight) { + ImVec2 marker[10] = {ImVec2(1.0f, 0.0f), + ImVec2(0.809017f, 0.58778524f), + ImVec2(0.30901697f, 0.95105654f), + ImVec2(-0.30901703f, 0.9510565f), + ImVec2(-0.80901706f, 0.5877852f), + ImVec2(-1.0f, 0.0f), + ImVec2(-0.80901694f, -0.58778536f), + ImVec2(-0.3090171f, -0.9510565f), + ImVec2(0.30901712f, -0.9510565f), + ImVec2(0.80901694f, -0.5877853f)}; + MarkerGeneral(DrawList, marker, 10, c, s, outline, col_outline, fill, col_fill, weight); +} + +inline void MarkerDiamond(ImDrawList& DrawList, const ImVec2& c, float s, bool outline, ImU32 col_outline, bool fill, ImU32 col_fill, float weight) { + ImVec2 marker[4] = {ImVec2(1, 0), ImVec2(0, -1), ImVec2(-1, 0), ImVec2(0, 1)}; + MarkerGeneral(DrawList, marker, 4, c, s, outline, col_outline, fill, col_fill, weight); +} + +inline void MarkerSquare(ImDrawList& DrawList, const ImVec2& c, float s, bool outline, ImU32 col_outline, bool fill, ImU32 col_fill, float weight) { + ImVec2 marker[4] = {ImVec2(SQRT_1_2,SQRT_1_2),ImVec2(SQRT_1_2,-SQRT_1_2),ImVec2(-SQRT_1_2,-SQRT_1_2),ImVec2(-SQRT_1_2,SQRT_1_2)}; + MarkerGeneral(DrawList, marker, 4, c, s, outline, col_outline, fill, col_fill, weight); +} + +inline void MarkerUp(ImDrawList& DrawList, const ImVec2& c, float s, bool outline, ImU32 col_outline, bool fill, ImU32 col_fill, float weight) { + ImVec2 marker[3] = {ImVec2(SQRT_3_2,0.5f),ImVec2(0,-1),ImVec2(-SQRT_3_2,0.5f)}; + MarkerGeneral(DrawList, marker, 3, c, s, outline, col_outline, fill, col_fill, weight); +} + +inline void MarkerDown(ImDrawList& DrawList, const ImVec2& c, float s, bool outline, ImU32 col_outline, bool fill, ImU32 col_fill, float weight) { + ImVec2 marker[3] = {ImVec2(SQRT_3_2,-0.5f),ImVec2(0,1),ImVec2(-SQRT_3_2,-0.5f)}; + MarkerGeneral(DrawList, marker, 3, c, s, outline, col_outline, fill, col_fill, weight); +} + +inline void MarkerLeft(ImDrawList& DrawList, const ImVec2& c, float s, bool outline, ImU32 col_outline, bool fill, ImU32 col_fill, float weight) { + ImVec2 marker[3] = {ImVec2(-1,0), ImVec2(0.5, SQRT_3_2), ImVec2(0.5, -SQRT_3_2)}; + MarkerGeneral(DrawList, marker, 3, c, s, outline, col_outline, fill, col_fill, weight); +} + +inline void MarkerRight(ImDrawList& DrawList, const ImVec2& c, float s, bool outline, ImU32 col_outline, bool fill, ImU32 col_fill, float weight) { + ImVec2 marker[3] = {ImVec2(1,0), ImVec2(-0.5, SQRT_3_2), ImVec2(-0.5, -SQRT_3_2)}; + MarkerGeneral(DrawList, marker, 3, c, s, outline, col_outline, fill, col_fill, weight); +} + +inline void MarkerAsterisk(ImDrawList& DrawList, const ImVec2& c, float s, bool /*outline*/, ImU32 col_outline, bool /*fill*/, ImU32 /*col_fill*/, float weight) { + ImVec2 marker[6] = {ImVec2(SQRT_3_2, 0.5f), ImVec2(0, -1), ImVec2(-SQRT_3_2, 0.5f), ImVec2(SQRT_3_2, -0.5f), ImVec2(0, 1), ImVec2(-SQRT_3_2, -0.5f)}; + TransformMarker(marker, 6, c, s); + DrawList.AddLine(marker[0], marker[5], col_outline, weight); + DrawList.AddLine(marker[1], marker[4], col_outline, weight); + DrawList.AddLine(marker[2], marker[3], col_outline, weight); +} + +inline void MarkerPlus(ImDrawList& DrawList, const ImVec2& c, float s, bool /*outline*/, ImU32 col_outline, bool /*fill*/, ImU32 /*col_fill*/, float weight) { + ImVec2 marker[4] = {ImVec2(1, 0), ImVec2(0, -1), ImVec2(-1, 0), ImVec2(0, 1)}; + TransformMarker(marker, 4, c, s); + DrawList.AddLine(marker[0], marker[2], col_outline, weight); + DrawList.AddLine(marker[1], marker[3], col_outline, weight); +} + +inline void MarkerCross(ImDrawList& DrawList, const ImVec2& c, float s, bool /*outline*/, ImU32 col_outline, bool /*fill*/, ImU32 /*col_fill*/, float weight) { + ImVec2 marker[4] = {ImVec2(SQRT_1_2,SQRT_1_2),ImVec2(SQRT_1_2,-SQRT_1_2),ImVec2(-SQRT_1_2,-SQRT_1_2),ImVec2(-SQRT_1_2,SQRT_1_2)}; + TransformMarker(marker, 4, c, s); + DrawList.AddLine(marker[0], marker[2], col_outline, weight); + DrawList.AddLine(marker[1], marker[3], col_outline, weight); +} + +template +inline void RenderMarkers(Getter getter, Transformer transformer, ImDrawList& DrawList, bool rend_mk_line, ImU32 col_mk_line, bool rend_mk_fill, ImU32 col_mk_fill) { + ImPlotContext& gp = *GImPlot; + for (int i = 0; i < getter.Count; ++i) { + ImVec2 c = transformer(getter(i)); + if (gp.BB_Plot.Contains(c)) { + // TODO: Optimize the loop and if statements, this is atrocious + if (ImHasFlag(gp.Style.Marker, ImPlotMarker_Circle)) + MarkerCircle(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); + if (ImHasFlag(gp.Style.Marker, ImPlotMarker_Square)) + MarkerSquare(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); + if (ImHasFlag(gp.Style.Marker, ImPlotMarker_Diamond)) + MarkerDiamond(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); + if (ImHasFlag(gp.Style.Marker, ImPlotMarker_Up)) + MarkerUp(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); + if (ImHasFlag(gp.Style.Marker, ImPlotMarker_Down)) + MarkerDown(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); + if (ImHasFlag(gp.Style.Marker, ImPlotMarker_Left)) + MarkerLeft(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); + if (ImHasFlag(gp.Style.Marker, ImPlotMarker_Right)) + MarkerRight(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); + if (ImHasFlag(gp.Style.Marker, ImPlotMarker_Cross)) + MarkerCross(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); + if (ImHasFlag(gp.Style.Marker, ImPlotMarker_Plus)) + MarkerPlus(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); + if (ImHasFlag(gp.Style.Marker, ImPlotMarker_Asterisk)) + MarkerAsterisk(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); + } + } +} + +template +struct LineRenderer { + inline LineRenderer(Getter _getter, Transformer _transformer, ImU32 col, float weight) : + getter(_getter), + transformer(_transformer) + { + Prims = getter.Count - 1; + Col = col; + Weight = weight; + p1 = transformer(getter(0)); + } + inline bool operator()(ImDrawList& DrawList, ImVec2 uv, int prim) { + ImPlotContext& gp = *GImPlot; + ImVec2 p2 = transformer(getter(prim + 1)); + if (!gp.BB_Plot.Overlaps(ImRect(ImMin(p1, p2), ImMax(p1, p2)))) { + p1 = p2; + return false; + } + float dx = p2.x - p1.x; + float dy = p2.y - p1.y; + IM_NORMALIZE2F_OVER_ZERO(dx, dy); + dx *= (Weight * 0.5f); + dy *= (Weight * 0.5f); + DrawList._VtxWritePtr[0].pos.x = p1.x + dy; + DrawList._VtxWritePtr[0].pos.y = p1.y - dx; + DrawList._VtxWritePtr[0].uv = uv; + DrawList._VtxWritePtr[0].col = Col; + DrawList._VtxWritePtr[1].pos.x = p2.x + dy; + DrawList._VtxWritePtr[1].pos.y = p2.y - dx; + DrawList._VtxWritePtr[1].uv = uv; + DrawList._VtxWritePtr[1].col = Col; + DrawList._VtxWritePtr[2].pos.x = p2.x - dy; + DrawList._VtxWritePtr[2].pos.y = p2.y + dx; + DrawList._VtxWritePtr[2].uv = uv; + DrawList._VtxWritePtr[2].col = Col; + DrawList._VtxWritePtr[3].pos.x = p1.x - dy; + DrawList._VtxWritePtr[3].pos.y = p1.y + dx; + DrawList._VtxWritePtr[3].uv = uv; + DrawList._VtxWritePtr[3].col = Col; + DrawList._VtxWritePtr += 4; + DrawList._IdxWritePtr[0] = (ImDrawIdx)(DrawList._VtxCurrentIdx); + DrawList._IdxWritePtr[1] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 1); + DrawList._IdxWritePtr[2] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 2); + DrawList._IdxWritePtr[3] = (ImDrawIdx)(DrawList._VtxCurrentIdx); + DrawList._IdxWritePtr[4] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 2); + DrawList._IdxWritePtr[5] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 3); + DrawList._IdxWritePtr += 6; + DrawList._VtxCurrentIdx += 4; + p1 = p2; + return true; + } + Getter getter; + Transformer transformer; + int Prims; + ImU32 Col; + float Weight; + ImVec2 p1; + static const int IdxConsumed = 6; + static const int VtxConsumed = 4; +}; + +template +inline void RenderLineStrip(Getter getter, Transformer transformer, ImDrawList& DrawList, float line_weight, ImU32 col) { + ImPlotContext& gp = *GImPlot; + if (ImHasFlag(gp.CurrentPlot->Flags, ImPlotFlags_AntiAliased)) { + ImVec2 p1 = transformer(getter(0)); + for (int i = 0; i < getter.Count; ++i) { + ImVec2 p2 = transformer(getter(i)); + if (gp.BB_Plot.Overlaps(ImRect(ImMin(p1, p2), ImMax(p1, p2)))) + DrawList.AddLine(p1, p2, col, line_weight); + p1 = p2; + } + } + else { + RenderPrimitives(LineRenderer(getter, transformer, col, line_weight), DrawList); + } +} + +template +struct ShadedRenderer { + ShadedRenderer(Getter1 _getter1, Getter2 _getter2, Transformer _transformer, ImU32 col) : + getter1(_getter1), + getter2(_getter2), + transformer(_transformer), + Col(col) + { + Prims = ImMin(getter1.Count, getter2.Count) - 1; + p11 = transformer(getter1(0)); + p12 = transformer(getter2(0)); + } + + inline bool operator()(ImDrawList& DrawList, ImVec2 uv, int prim) { + ImVec2 p21 = transformer(getter1(prim+1)); + ImVec2 p22 = transformer(getter2(prim+1)); + const int intersect = (p11.y > p12.y && p22.y > p21.y) || (p12.y > p11.y && p21.y > p22.y); + ImVec2 intersection = Intersection(p11,p21,p12,p22); + DrawList._VtxWritePtr[0].pos = p11; + DrawList._VtxWritePtr[0].uv = uv; + DrawList._VtxWritePtr[0].col = Col; + DrawList._VtxWritePtr[1].pos = p21; + DrawList._VtxWritePtr[1].uv = uv; + DrawList._VtxWritePtr[1].col = Col; + DrawList._VtxWritePtr[2].pos = intersection; + DrawList._VtxWritePtr[2].uv = uv; + DrawList._VtxWritePtr[2].col = Col; + DrawList._VtxWritePtr[3].pos = p12; + DrawList._VtxWritePtr[3].uv = uv; + DrawList._VtxWritePtr[3].col = Col; + DrawList._VtxWritePtr[4].pos = p22; + DrawList._VtxWritePtr[4].uv = uv; + DrawList._VtxWritePtr[4].col = Col; + DrawList._VtxWritePtr += 5; + DrawList._IdxWritePtr[0] = (ImDrawIdx)(DrawList._VtxCurrentIdx); + DrawList._IdxWritePtr[1] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 1 + intersect); + DrawList._IdxWritePtr[2] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 3); + DrawList._IdxWritePtr[3] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 1); + DrawList._IdxWritePtr[4] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 3 - intersect); + DrawList._IdxWritePtr[5] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 4); + DrawList._IdxWritePtr += 6; + DrawList._VtxCurrentIdx += 5; + p11 = p21; + p12 = p22; + return true; + } + Getter1 getter1; + Getter2 getter2; + Transformer transformer; + int Prims; + ImU32 Col; + ImVec2 p11, p12; + static const int IdxConsumed = 6; + static const int VtxConsumed = 5; +}; + +//----------------------------------------------------------------------------- +// RENDERING UTILS +//----------------------------------------------------------------------------- + +// Returns true if a style color is set to be automaticaly determined +inline bool ColorIsAuto(ImPlotCol idx) { + ImPlotContext& gp = *GImPlot; + return gp.Style.Colors[idx].w == -1; +} + +// Recolors an item from an the current ImPlotCol if it is not automatic (i.e. alpha != -1) +inline void TryRecolorItem(ImPlotItem* item, ImPlotCol idx) { + ImPlotContext& gp = *GImPlot; + if (gp.Style.Colors[idx].w != -1) + item->Color = gp.Style.Colors[idx]; +} + +// Returns true if lines will render (e.g. basic lines, bar outlines) +inline bool WillLineRender() { + ImPlotContext& gp = *GImPlot; + return gp.Style.Colors[ImPlotCol_Line].w != 0 && gp.Style.LineWeight > 0; +} + +// Returns true if fills will render (e.g. shaded plots, bar fills) +inline bool WillFillRender() { + ImPlotContext& gp = *GImPlot; + return gp.Style.Colors[ImPlotCol_Fill].w != 0 && gp.Style.FillAlpha > 0; +} + +// Returns true if marker outlines will render +inline bool WillMarkerOutlineRender() { + ImPlotContext& gp = *GImPlot; + return gp.Style.Colors[ImPlotCol_MarkerOutline].w != 0 && gp.Style.MarkerWeight > 0; +} + +// Returns true if mark fill will render +inline bool WillMarkerFillRender() { + ImPlotContext& gp = *GImPlot; + return gp.Style.Colors[ImPlotCol_MarkerFill].w != 0 && gp.Style.FillAlpha > 0; +} + +// Gets the line color for an item +inline ImVec4 GetLineColor(ImPlotItem* item) { + ImPlotContext& gp = *GImPlot; + return ColorIsAuto(ImPlotCol_Line) ? item->Color : gp.Style.Colors[ImPlotCol_Line]; +} + +// Gets the fill color for an item +inline ImVec4 GetItemFillColor(ImPlotItem* item) { + ImPlotContext& gp = *GImPlot; + ImVec4 col = ColorIsAuto(ImPlotCol_Fill) ? item->Color : gp.Style.Colors[ImPlotCol_Fill]; + col.w *= gp.Style.FillAlpha; + return col; +} + +// Gets the marker outline color for an item +inline ImVec4 GetMarkerOutlineColor(ImPlotItem* item) { + ImPlotContext& gp = *GImPlot; + return ColorIsAuto(ImPlotCol_MarkerOutline) ? GetLineColor(item) : gp.Style.Colors[ImPlotCol_MarkerOutline]; +} + +// Gets the marker fill color for an item +inline ImVec4 GetMarkerFillColor(ImPlotItem* item) { + ImPlotContext& gp = *GImPlot; + ImVec4 col = ColorIsAuto(ImPlotCol_MarkerFill) ? GetLineColor(item) :gp.Style.Colors[ImPlotCol_MarkerFill]; + col.w *= gp.Style.FillAlpha; + return col; +} + +// Gets the error bar color +inline ImVec4 GetErrorBarColor() { + ImPlotContext& gp = *GImPlot; + return ColorIsAuto(ImPlotCol_ErrorBar) ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : gp.Style.Colors[ImPlotCol_ErrorBar]; +} + +//----------------------------------------------------------------------------- +// PLOT LINES / MARKERS +//----------------------------------------------------------------------------- + +template +inline void PlotEx(const char* label_id, Getter getter) +{ + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotEx() needs to be called between BeginPlot() and EndPlot()!"); + + ImPlotItem* item = RegisterItem(label_id); + if (!item->Show) + return; + TryRecolorItem(item, ImPlotCol_Line); + + // find data extents + if (gp.FitThisFrame) { + for (int i = 0; i < getter.Count; ++i) { + ImPlotPoint p = getter(i); + FitPoint(p); + } + } + + ImDrawList& DrawList = *ImGui::GetWindowDrawList(); + ImPlotState* plot = gp.CurrentPlot; + const int y_axis = plot->CurrentYAxis; + + PushPlotClipRect(); + // render line + if (getter.Count > 1 && WillLineRender()) { + ImU32 col_line = ImGui::GetColorU32(GetLineColor(item)); + const float line_weight = item->Highlight ? gp.Style.LineWeight * 2 : gp.Style.LineWeight; + if (ImHasFlag(plot->XAxis.Flags, ImPlotAxisFlags_LogScale) && ImHasFlag(plot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale)) + RenderLineStrip(getter, TransformerLogLog(y_axis), DrawList, line_weight, col_line); + else if (ImHasFlag(plot->XAxis.Flags, ImPlotAxisFlags_LogScale)) + RenderLineStrip(getter, TransformerLogLin(y_axis), DrawList, line_weight, col_line); + else if (ImHasFlag(plot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale)) + RenderLineStrip(getter, TransformerLinLog(y_axis), DrawList, line_weight, col_line); + else + RenderLineStrip(getter, TransformerLinLin(y_axis), DrawList, line_weight, col_line); + } + // render markers + if (gp.Style.Marker != ImPlotMarker_None) { + const bool rend_mk_line = WillMarkerOutlineRender(); + const bool rend_mk_fill = WillMarkerFillRender(); + const ImU32 col_mk_line = ImGui::GetColorU32(GetMarkerOutlineColor(item)); + const ImU32 col_mk_fill = ImGui::GetColorU32(GetMarkerFillColor(item)); + if (ImHasFlag(plot->XAxis.Flags, ImPlotAxisFlags_LogScale) && ImHasFlag(plot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale)) + RenderMarkers(getter, TransformerLogLog(y_axis), DrawList, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill); + else if (ImHasFlag(plot->XAxis.Flags, ImPlotAxisFlags_LogScale)) + RenderMarkers(getter, TransformerLogLin(y_axis), DrawList, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill); + else if (ImHasFlag(plot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale)) + RenderMarkers(getter, TransformerLinLog(y_axis), DrawList, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill); + else + RenderMarkers(getter, TransformerLinLin(y_axis), DrawList, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill); + } + PopPlotClipRect(); +} + +//----------------------------------------------------------------------------- +// float + +void PlotLine(const char* label_id, const float* values, int count, int offset, int stride) { + GetterYs getter(values,count,offset,stride); + PlotEx(label_id, getter); +} + +void PlotLine(const char* label_id, const float* xs, const float* ys, int count, int offset, int stride) { + GetterXsYs getter(xs,ys,count,offset,stride); + return PlotEx(label_id, getter); +} + +void PlotLine(const char* label_id, const ImVec2* data, int count, int offset) { + GetterImVec2 getter(data, count, offset); + return PlotEx(label_id, getter); +} + +//----------------------------------------------------------------------------- +// double + +void PlotLine(const char* label_id, const double* values, int count, int offset, int stride) { + GetterYs getter(values,count,offset,stride); + PlotEx(label_id, getter); +} + +void PlotLine(const char* label_id, const double* xs, const double* ys, int count, int offset, int stride) { + GetterXsYs getter(xs,ys,count,offset,stride); + return PlotEx(label_id, getter); +} + +void PlotLine(const char* label_id, const ImPlotPoint* data, int count, int offset) { + GetterImPlotPoint getter(data, count, offset); + return PlotEx(label_id, getter); +} + +//----------------------------------------------------------------------------- +// custom + +void PlotLine(const char* label_id, ImPlotPoint (*getter_func)(void* data, int idx), void* data, int count, int offset) { + GetterFuncPtrImPlotPoint getter(getter_func,data, count, offset); + return PlotEx(label_id, getter); +} + +//----------------------------------------------------------------------------- +// PLOT SCATTER +//----------------------------------------------------------------------------- + +inline int PushScatterStyle() { + int vars = 1; + PushStyleVar(ImPlotStyleVar_LineWeight, 0); + if (GetStyle().Marker == ImPlotMarker_None) { + PushStyleVar(ImPlotStyleVar_Marker, ImPlotMarker_Circle); + vars++; + } + return vars; +} + +//----------------------------------------------------------------------------- +// float + +void PlotScatter(const char* label_id, const float* values, int count, int offset, int stride) { + int vars = PushScatterStyle(); + PlotLine(label_id, values, count, offset, stride); + PopStyleVar(vars); +} + +void PlotScatter(const char* label_id, const float* xs, const float* ys, int count, int offset, int stride) { + int vars = PushScatterStyle(); + PlotLine(label_id, xs, ys, count, offset, stride); + PopStyleVar(vars); +} + +void PlotScatter(const char* label_id, const ImVec2* data, int count, int offset) { + int vars = PushScatterStyle(); + PlotLine(label_id, data, count, offset); + PopStyleVar(vars); +} + +//----------------------------------------------------------------------------- +// double + +void PlotScatter(const char* label_id, const double* values, int count, int offset, int stride) { + int vars = PushScatterStyle(); + PlotLine(label_id, values, count, offset, stride); + PopStyleVar(vars); +} + +void PlotScatter(const char* label_id, const double* xs, const double* ys, int count, int offset, int stride) { + int vars = PushScatterStyle(); + PlotLine(label_id, xs, ys, count, offset, stride); + PopStyleVar(vars); +} + +void PlotScatter(const char* label_id, const ImPlotPoint* data, int count, int offset) { + int vars = PushScatterStyle(); + PlotLine(label_id, data, count, offset); + PopStyleVar(vars); +} + +//----------------------------------------------------------------------------- +// custom + +void PlotScatter(const char* label_id, ImPlotPoint (*getter)(void* data, int idx), void* data, int count, int offset) { + int vars = PushScatterStyle(); + PlotLine(label_id, getter, data, count, offset); + PopStyleVar(vars); +} + +//----------------------------------------------------------------------------- +// PLOT SHADED +//----------------------------------------------------------------------------- + +template +inline void PlotShadedEx(const char* label_id, Getter1 getter1, Getter2 getter2) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotShaded() needs to be called between BeginPlot() and EndPlot()!"); + + ImPlotItem* item = RegisterItem(label_id); + if (!item->Show) + return; + TryRecolorItem(item, ImPlotCol_Fill); + + if (!WillFillRender()) + return; + + // find data extents + if (gp.FitThisFrame) { + for (int i = 0; i < ImMin(getter1.Count, getter2.Count); ++i) { + ImPlotPoint p1 = getter1(i); + ImPlotPoint p2 = getter2(i); + FitPoint(p1); + FitPoint(p2); + } + } + + ImDrawList & DrawList = *ImGui::GetWindowDrawList(); + ImPlotState* plot = gp.CurrentPlot; + const int y_axis = plot->CurrentYAxis; + + ImU32 col = ImGui::GetColorU32(GetItemFillColor(item)); + + PushPlotClipRect(); + if (ImHasFlag(plot->XAxis.Flags, ImPlotAxisFlags_LogScale) && ImHasFlag(plot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale)) + RenderPrimitives(ShadedRenderer(getter1,getter2,TransformerLogLog(y_axis), col), DrawList); + else if (ImHasFlag(plot->XAxis.Flags, ImPlotAxisFlags_LogScale)) + RenderPrimitives(ShadedRenderer(getter1,getter2,TransformerLogLin(y_axis), col), DrawList); + else if (ImHasFlag(plot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale)) + RenderPrimitives(ShadedRenderer(getter1,getter2,TransformerLinLog(y_axis), col), DrawList); + else + RenderPrimitives(ShadedRenderer(getter1,getter2,TransformerLinLin(y_axis), col), DrawList); + PopPlotClipRect(); +} + +//----------------------------------------------------------------------------- +// float + +void PlotShaded(const char* label_id, const float* xs, const float* ys1, const float* ys2, int count, int offset, int stride) { + GetterXsYs getter1(xs, ys1, count, offset, stride); + GetterXsYs getter2(xs, ys2, count, offset, stride); + PlotShadedEx(label_id, getter1, getter2); +} + +void PlotShaded(const char* label_id, const float* xs, const float* ys, int count, float y_ref, int offset, int stride) { + GetterXsYs getter1(xs, ys, count, offset, stride); + GetterXsYRef getter2(xs, y_ref, count, offset, stride); + PlotShadedEx(label_id, getter1, getter2); +} + +//----------------------------------------------------------------------------- +// double + +void PlotShaded(const char* label_id, const double* xs, const double* ys1, const double* ys2, int count, int offset, int stride) { + GetterXsYs getter1(xs, ys1, count, offset, stride); + GetterXsYs getter2(xs, ys2, count, offset, stride); + PlotShadedEx(label_id, getter1, getter2); +} + +void PlotShaded(const char* label_id, const double* xs, const double* ys, int count, double y_ref, int offset, int stride) { + GetterXsYs getter1(xs, ys, count, offset, stride); + GetterXsYRef getter2(xs, y_ref, count, offset, stride); + PlotShadedEx(label_id, getter1, getter2); +} + +//----------------------------------------------------------------------------- +// PLOT BAR V +//----------------------------------------------------------------------------- + +template +void PlotBarsEx(const char* label_id, Getter getter, TWidth width) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotBars() needs to be called between BeginPlot() and EndPlot()!"); + + ImPlotItem* item = RegisterItem(label_id); + if (!item->Show) + return; + TryRecolorItem(item, ImPlotCol_Fill); + + const TWidth half_width = width / 2; + // find data extents + if (gp.FitThisFrame) { + for (int i = 0; i < getter.Count; ++i) { + ImPlotPoint p = getter(i); + FitPoint(ImPlotPoint(p.x - half_width, p.y)); + FitPoint(ImPlotPoint(p.x + half_width, 0)); + } + } + + ImU32 col_line = ImGui::GetColorU32(GetLineColor(item)); + ImU32 col_fill = ImGui::GetColorU32(GetItemFillColor(item)); + const bool rend_fill = WillFillRender(); + bool rend_line = WillLineRender(); + if (rend_fill && col_line == col_fill) + rend_line = false; + + ImDrawList & DrawList = *ImGui::GetWindowDrawList(); + PushPlotClipRect(); + for (int i = 0; i < getter.Count; ++i) { + ImPlotPoint p = getter(i); + if (p.y == 0) + continue; + ImVec2 a = PlotToPixels(p.x - half_width, p.y); + ImVec2 b = PlotToPixels(p.x + half_width, 0); + if (rend_fill) + DrawList.AddRectFilled(a, b, col_fill); + if (rend_line) + DrawList.AddRect(a, b, col_line); + } + PopPlotClipRect(); +} + +//----------------------------------------------------------------------------- +// float + +void PlotBars(const char* label_id, const float* values, int count, float width, float shift, int offset, int stride) { + GetterBarV getter(values,shift,count,offset,stride); + PlotBarsEx(label_id, getter, width); +} + +void PlotBars(const char* label_id, const float* xs, const float* ys, int count, float width, int offset, int stride) { + GetterXsYs getter(xs,ys,count,offset,stride); + PlotBarsEx(label_id, getter, width); +} + +//----------------------------------------------------------------------------- +// double + +void PlotBars(const char* label_id, const double* values, int count, double width, double shift, int offset, int stride) { + GetterBarV getter(values,shift,count,offset,stride); + PlotBarsEx(label_id, getter, width); +} + +void PlotBars(const char* label_id, const double* xs, const double* ys, int count, double width, int offset, int stride) { + GetterXsYs getter(xs,ys,count,offset,stride); + PlotBarsEx(label_id, getter, width); +} + +//----------------------------------------------------------------------------- +// custom + +void PlotBars(const char* label_id, ImPlotPoint (*getter_func)(void* data, int idx), void* data, int count, double width, int offset) { + GetterFuncPtrImPlotPoint getter(getter_func, data, count, offset); + PlotBarsEx(label_id, getter, width); +} + +//----------------------------------------------------------------------------- +// PLOT BAR H +//----------------------------------------------------------------------------- + +// TODO: Migrate to RenderPrimitives + +template +void PlotBarsHEx(const char* label_id, Getter getter, THeight height) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotBarsH() needs to be called between BeginPlot() and EndPlot()!"); + + ImPlotItem* item = RegisterItem(label_id); + if (!item->Show) + return; + TryRecolorItem(item, ImPlotCol_Fill); + + const THeight half_height = height / 2; + // find data extents + if (gp.FitThisFrame) { + for (int i = 0; i < getter.Count; ++i) { + ImPlotPoint p = getter(i); + FitPoint(ImPlotPoint(0, p.y - half_height)); + FitPoint(ImPlotPoint(p.x, p.y + half_height)); + } + } + + ImU32 col_line = ImGui::GetColorU32(GetLineColor(item)); + ImU32 col_fill = ImGui::GetColorU32(GetItemFillColor(item)); + const bool rend_fill = WillFillRender(); + bool rend_line = WillLineRender(); + if (rend_fill && col_line == col_fill) + rend_line = false; + + PushPlotClipRect(); + ImDrawList & DrawList = *ImGui::GetWindowDrawList(); + for (int i = 0; i < getter.Count; ++i) { + ImPlotPoint p = getter(i); + if (p.x == 0) + continue; + ImVec2 a = PlotToPixels(0, p.y - half_height); + ImVec2 b = PlotToPixels(p.x, p.y + half_height); + if (rend_fill) + DrawList.AddRectFilled(a, b, col_fill); + if (rend_line) + DrawList.AddRect(a, b, col_line); + } + PopPlotClipRect(); +} + +//----------------------------------------------------------------------------- +// float + +void PlotBarsH(const char* label_id, const float* values, int count, float height, float shift, int offset, int stride) { + GetterBarH getter(values,shift,count,offset,stride); + PlotBarsHEx(label_id, getter, height); +} + +void PlotBarsH(const char* label_id, const float* xs, const float* ys, int count, float height, int offset, int stride) { + GetterXsYs getter(xs,ys,count,offset,stride); + PlotBarsHEx(label_id, getter, height); +} + +//----------------------------------------------------------------------------- +// double + +void PlotBarsH(const char* label_id, const double* values, int count, double height, double shift, int offset, int stride) { + GetterBarH getter(values,shift,count,offset,stride); + PlotBarsHEx(label_id, getter, height); +} + +void PlotBarsH(const char* label_id, const double* xs, const double* ys, int count, double height, int offset, int stride) { + GetterXsYs getter(xs,ys,count,offset,stride); + PlotBarsHEx(label_id, getter, height); +} + +//----------------------------------------------------------------------------- +// custom + +void PlotBarsH(const char* label_id, ImPlotPoint (*getter_func)(void* data, int idx), void* data, int count, double height, int offset) { + GetterFuncPtrImPlotPoint getter(getter_func, data, count, offset); + PlotBarsHEx(label_id, getter, height); +} + +//----------------------------------------------------------------------------- +// PLOT ERROR BARS +//----------------------------------------------------------------------------- + +template +void PlotErrorBarsEx(const char* label_id, Getter getter) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotErrorBars() needs to be called between BeginPlot() and EndPlot()!"); + + ImPlotItem* item = RegisterItem(label_id); + if (!item->Show) + return; + + // find data extents + if (gp.FitThisFrame) { + for (int i = 0; i < getter.Count; ++i) { + ImPlotPointError e = getter(i); + FitPoint(ImPlotPoint(e.x , e.y - e.neg)); + FitPoint(ImPlotPoint(e.x , e.y + e.pos )); + } + } + + const ImU32 col = ImGui::GetColorU32(GetErrorBarColor()); + const bool rend_whisker = gp.Style.ErrorBarSize > 0; + const float half_whisker = gp.Style.ErrorBarSize * 0.5f; + + ImDrawList & DrawList = *ImGui::GetWindowDrawList(); + + PushPlotClipRect(); + for (int i = 0; i < getter.Count; ++i) { + ImPlotPointError e = getter(i); + ImVec2 p1 = PlotToPixels(e.x, e.y - e.neg); + ImVec2 p2 = PlotToPixels(e.x, e.y + e.pos); + DrawList.AddLine(p1,p2,col, gp.Style.ErrorBarWeight); + if (rend_whisker) { + DrawList.AddLine(p1 - ImVec2(half_whisker, 0), p1 + ImVec2(half_whisker, 0), col, gp.Style.ErrorBarWeight); + DrawList.AddLine(p2 - ImVec2(half_whisker, 0), p2 + ImVec2(half_whisker, 0), col, gp.Style.ErrorBarWeight); + } + } + PopPlotClipRect(); +} + +//----------------------------------------------------------------------------- +// float + +void PlotErrorBars(const char* label_id, const float* xs, const float* ys, const float* err, int count, int offset, int stride) { + GetterError getter(xs, ys, err, err, count, offset, stride); + PlotErrorBarsEx(label_id, getter); +} + +void PlotErrorBars(const char* label_id, const float* xs, const float* ys, const float* neg, const float* pos, int count, int offset, int stride) { + GetterError getter(xs, ys, neg, pos, count, offset, stride); + PlotErrorBarsEx(label_id, getter); +} + +//----------------------------------------------------------------------------- +// double + +void PlotErrorBars(const char* label_id, const double* xs, const double* ys, const double* err, int count, int offset, int stride) { + GetterError getter(xs, ys, err, err, count, offset, stride); + PlotErrorBarsEx(label_id, getter); +} + +void PlotErrorBars(const char* label_id, const double* xs, const double* ys, const double* neg, const double* pos, int count, int offset, int stride) { + GetterError getter(xs, ys, neg, pos, count, offset, stride); + PlotErrorBarsEx(label_id, getter); +} + +//----------------------------------------------------------------------------- +// PLOT ERROR BARS H +//----------------------------------------------------------------------------- + +template +void PlotErrorBarsHEx(const char* label_id, Getter getter) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotErrorBarsH() needs to be called between BeginPlot() and EndPlot()!"); + + ImPlotItem* item = RegisterItem(label_id); + if (!item->Show) + return; + + // find data extents + if (gp.FitThisFrame) { + for (int i = 0; i < getter.Count; ++i) { + ImPlotPointError e = getter(i); + FitPoint(ImPlotPoint(e.x - e.neg, e.y)); + FitPoint(ImPlotPoint(e.x + e.pos, e.y)); + } + } + + const ImU32 col = ImGui::GetColorU32(GetErrorBarColor()); + const bool rend_whisker = gp.Style.ErrorBarSize > 0; + const float half_whisker = gp.Style.ErrorBarSize * 0.5f; + + ImDrawList& DrawList = *ImGui::GetWindowDrawList(); + + PushPlotClipRect(); + for (int i = 0; i < getter.Count; ++i) { + ImPlotPointError e = getter(i); + ImVec2 p1 = PlotToPixels(e.x - e.neg, e.y); + ImVec2 p2 = PlotToPixels(e.x + e.pos, e.y); + DrawList.AddLine(p1, p2, col, gp.Style.ErrorBarWeight); + if (rend_whisker) { + DrawList.AddLine(p1 - ImVec2(0, half_whisker), p1 + ImVec2(0, half_whisker), col, gp.Style.ErrorBarWeight); + DrawList.AddLine(p2 - ImVec2(0, half_whisker), p2 + ImVec2(0, half_whisker), col, gp.Style.ErrorBarWeight); + } + } + PopPlotClipRect(); +} + +//----------------------------------------------------------------------------- +// float + +void PlotErrorBarsH(const char* label_id, const float* xs, const float* ys, const float* err, int count, int offset, int stride) { + GetterError getter(xs, ys, err, err, count, offset, stride); + PlotErrorBarsHEx(label_id, getter); +} + +void PlotErrorBarsH(const char* label_id, const float* xs, const float* ys, const float* neg, const float* pos, int count, int offset, int stride) { + GetterError getter(xs, ys, neg, pos, count, offset, stride); + PlotErrorBarsHEx(label_id, getter); +} + +//----------------------------------------------------------------------------- +// double + +void PlotErrorBarsH(const char* label_id, const double* xs, const double* ys, const double* err, int count, int offset, int stride) { + GetterError getter(xs, ys, err, err, count, offset, stride); + PlotErrorBarsHEx(label_id, getter); +} + +void PlotErrorBarsH(const char* label_id, const double* xs, const double* ys, const double* neg, const double* pos, int count, int offset, int stride) { + GetterError getter(xs, ys, neg, pos, count, offset, stride); + PlotErrorBarsHEx(label_id, getter); +} + + +//----------------------------------------------------------------------------- +// PLOT PIE CHART +//----------------------------------------------------------------------------- + +inline void RenderPieSlice(ImDrawList& DrawList, const ImPlotPoint& center, double radius, double a0, double a1, ImU32 col) { + static const float resolution = 50 / (2 * IM_PI); + static ImVec2 buffer[50]; + buffer[0] = PlotToPixels(center); + int n = ImMax(3, (int)((a1 - a0) * resolution)); + double da = (a1 - a0) / (n - 1); + for (int i = 0; i < n; ++i) { + double a = a0 + i * da; + buffer[i + 1] = PlotToPixels(center.x + radius * cos(a), center.y + radius * sin(a)); + } + DrawList.AddConvexPolyFilled(buffer, n + 1, col); +} + +template +void PlotPieChartEx(const char** label_ids, const T* values, int count, T x, T y, T radius, bool normalize, const char* fmt, T angle0) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotPieChart() needs to be called between BeginPlot() and EndPlot()!"); + ImDrawList & DrawList = *ImGui::GetWindowDrawList(); + + T sum = 0; + for (int i = 0; i < count; ++i) + sum += values[i]; + + normalize = normalize || sum > 1.0f; + + ImPlotPoint center(x,y); + + PushPlotClipRect(); + T a0 = angle0 * 2 * IM_PI / 360.0f; + T a1 = angle0 * 2 * IM_PI / 360.0f; + for (int i = 0; i < count; ++i) { + ImPlotItem* item = RegisterItem(label_ids[i]); + ImU32 col = ImGui::GetColorU32(GetItemFillColor(item)); + T percent = normalize ? values[i] / sum : values[i]; + a1 = a0 + 2 * IM_PI * percent; + if (item->Show) { + if (percent < 0.5) { + RenderPieSlice(DrawList, center, radius, a0, a1, col); + } + else { + RenderPieSlice(DrawList, center, radius, a0, a0 + (a1 - a0) * 0.5f, col); + RenderPieSlice(DrawList, center, radius, a0 + (a1 - a0) * 0.5f, a1, col); + } + } + a0 = a1; + } + if (fmt != NULL) { + a0 = angle0 * 2 * IM_PI / 360.0f; + a1 = angle0 * 2 * IM_PI / 360.0f; + char buffer[32]; + for (int i = 0; i < count; ++i) { + ImPlotItem* item = GetItem(label_ids[i]); + T percent = normalize ? values[i] / sum : values[i]; + a1 = a0 + 2 * IM_PI * percent; + if (item->Show) { + sprintf(buffer, fmt, values[i]); + ImVec2 size = ImGui::CalcTextSize(buffer); + T angle = a0 + (a1 - a0) * 0.5f; + ImVec2 pos = PlotToPixels(center.x + 0.5f * radius * cos(angle), center.y + 0.5f * radius * sin(angle)); + ImU32 col = CalcTextColor(GetItemFillColor(item)); + DrawList.AddText(pos - size * 0.5f, col, buffer); + } + a0 = a1; + } + } + PopPlotClipRect(); +} + +//----------------------------------------------------------------------------- +// float + +void PlotPieChart(const char** label_ids, const float* values, int count, float x, float y, float radius, bool normalize, const char* fmt, float angle0) { + return PlotPieChartEx(label_ids, values, count, x, y, radius, normalize, fmt, angle0); +} + +//----------------------------------------------------------------------------- +// double + +void PlotPieChart(const char** label_ids, const double* values, int count, double x, double y, double radius, bool normalize, const char* fmt, double angle0) { + return PlotPieChartEx(label_ids, values, count, x, y, radius, normalize, fmt, angle0); +} + +//----------------------------------------------------------------------------- +// PLOT HEATMAP +//----------------------------------------------------------------------------- + +template +void RenderHeatmap(Transformer transformer, ImDrawList& DrawList, const T* values, int rows, int cols, T scale_min, T scale_max, const char* fmt, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max) { + ImPlotContext& gp = *GImPlot; + const double w = (bounds_max.x - bounds_min.x) / cols; + const double h = (bounds_max.y - bounds_min.y) / rows; + const ImPlotPoint half_size(w*0.5,h*0.5); + int i = 0; + for (int r = 0; r < rows; ++r) { + for (int c = 0; c < cols; ++c) { + ImPlotPoint p; + p.x = bounds_min.x + 0.5*w + c*w; + p.y = bounds_max.y - (0.5*h + r*h); + ImVec2 a = transformer(p.x - half_size.x, p.y - half_size.y); + ImVec2 b = transformer(p.x + half_size.x, p.y + half_size.y); + float t = (float)ImRemap(values[i], scale_min, scale_max, T(0), T(1)); + ImVec4 color = LerpColormap(t); + color.w *= gp.Style.FillAlpha; + ImU32 col = ImGui::GetColorU32(color); + DrawList.AddRectFilled(a, b, col); + i++; + } + } + if (fmt != NULL) { + i = 0; + for (int r = 0; r < rows; ++r) { + for (int c = 0; c < cols; ++c) { + ImPlotPoint p; + p.x = bounds_min.x + 0.5*w + c*w; + p.y = bounds_min.y + 1 - (0.5*h + r*h); + ImVec2 px = transformer(p); + char buff[32]; + sprintf(buff, fmt, values[i]); + ImVec2 size = ImGui::CalcTextSize(buff); + float t = (float)ImRemap(values[i], scale_min, scale_max, T(0), T(1)); + ImVec4 color = LerpColormap(t); + ImU32 col = CalcTextColor(color); + DrawList.AddText(px - size * 0.5f, col, buff); + i++; + } + } + } +} + +template +void PlotHeatmapEx(const char* label_id, const T* values, int rows, int cols, T scale_min, T scale_max, const char* fmt, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotHeatmap() needs to be called between BeginPlot() and EndPlot()!"); + IM_ASSERT_USER_ERROR(scale_min != scale_max, "Scale values must be different!"); + ImPlotItem* item = RegisterItem(label_id); + if (!item->Show) + return; + if (gp.FitThisFrame) { + FitPoint(bounds_min); + FitPoint(bounds_max); + } + ImDrawList& DrawList = *ImGui::GetWindowDrawList(); + ImGui::PushClipRect(gp.BB_Plot.Min, gp.BB_Plot.Max, true); + ImPlotState* plot = gp.CurrentPlot; + int y_axis = plot->CurrentYAxis; + if (ImHasFlag(plot->XAxis.Flags, ImPlotAxisFlags_LogScale) && ImHasFlag(plot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale)) + RenderHeatmap(TransformerLogLog(y_axis), DrawList, values, rows, cols, scale_min, scale_max, fmt, bounds_min, bounds_max); + else if (ImHasFlag(plot->XAxis.Flags, ImPlotAxisFlags_LogScale)) + RenderHeatmap(TransformerLogLin(y_axis), DrawList, values, rows, cols, scale_min, scale_max, fmt, bounds_min, bounds_max); + else if (ImHasFlag(plot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale)) + RenderHeatmap(TransformerLinLog(y_axis), DrawList, values, rows, cols, scale_min, scale_max, fmt, bounds_min, bounds_max); + else + RenderHeatmap(TransformerLinLin(y_axis), DrawList, values, rows, cols, scale_min, scale_max, fmt, bounds_min, bounds_max); + ImGui::PopClipRect(); +} + +//----------------------------------------------------------------------------- +// float + +void PlotHeatmap(const char* label_id, const float* values, int rows, int cols, float scale_min, float scale_max, const char* fmt, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max) { + return PlotHeatmapEx(label_id, values, rows, cols, scale_min, scale_max, fmt, bounds_min, bounds_max); +} + +//----------------------------------------------------------------------------- +// double + +void PlotHeatmap(const char* label_id, const double* values, int rows, int cols, double scale_min, double scale_max, const char* fmt, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max) { + return PlotHeatmapEx(label_id, values, rows, cols, scale_min, scale_max, fmt, bounds_min, bounds_max); +} + +//----------------------------------------------------------------------------- +// PLOT DIGITAL +//----------------------------------------------------------------------------- + +template +inline void PlotDigitalEx(const char* label_id, Getter getter) +{ + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotDigital() needs to be called between BeginPlot() and EndPlot()!"); + + ImPlotItem* item = RegisterItem(label_id); + if (!item->Show) + return; + TryRecolorItem(item, ImPlotCol_Line); + + // render digital signals as "pixel bases" rectangles + PushPlotClipRect(); + if (getter.Count > 1 && WillLineRender()) { + ImDrawList & DrawList = *ImGui::GetWindowDrawList(); + const float line_weight = item->Highlight ? gp.Style.LineWeight * 2 : gp.Style.LineWeight; + const int y_axis = gp.CurrentPlot->CurrentYAxis; + int pixYMax = 0; + ImPlotPoint itemData1 = getter(0); + for (int i = 0; i < getter.Count; ++i) { + ImPlotPoint itemData2 = getter(i); + if (NanOrInf(itemData1.y)) { + itemData1 = itemData2; + continue; + } + if (NanOrInf(itemData2.y)) itemData2.y = ConstrainNan(ConstrainInf(itemData2.y)); + int pixY_0 = (int)(line_weight); + itemData1.y = ImMax(0.0, itemData1.y); + float pixY_1_float = gp.Style.DigitalBitHeight * (float)itemData1.y; + int pixY_1 = (int)(pixY_1_float); //allow only positive values + int pixY_chPosOffset = (int)(ImMax(gp.Style.DigitalBitHeight, pixY_1_float) + gp.Style.DigitalBitGap); + pixYMax = ImMax(pixYMax, pixY_chPosOffset); + ImVec2 pMin = PlotToPixels(itemData1); + ImVec2 pMax = PlotToPixels(itemData2); + int pixY_Offset = 20; //20 pixel from bottom due to mouse cursor label + pMin.y = (gp.PixelRange[y_axis].Min.y) + ((-gp.DigitalPlotOffset) - pixY_Offset); + pMax.y = (gp.PixelRange[y_axis].Min.y) + ((-gp.DigitalPlotOffset) - pixY_0 - pixY_1 - pixY_Offset); + //plot only one rectangle for same digital state + while (((i+2) < getter.Count) && (itemData1.y == itemData2.y)) { + const int in = (i + 1); + itemData2 = getter(in); + if (NanOrInf(itemData2.y)) break; + pMax.x = PlotToPixels(itemData2).x; + i++; + } + //do not extend plot outside plot range + if (pMin.x < gp.PixelRange[y_axis].Min.x) pMin.x = gp.PixelRange[y_axis].Min.x; + if (pMax.x < gp.PixelRange[y_axis].Min.x) pMax.x = gp.PixelRange[y_axis].Min.x; + if (pMin.x > gp.PixelRange[y_axis].Max.x) pMin.x = gp.PixelRange[y_axis].Max.x; + if (pMax.x > gp.PixelRange[y_axis].Max.x) pMax.x = gp.PixelRange[y_axis].Max.x; + //plot a rectangle that extends up to x2 with y1 height + if ((pMax.x > pMin.x) && (gp.BB_Plot.Contains(pMin) || gp.BB_Plot.Contains(pMax))) { + ImVec4 colAlpha = item->Color; + colAlpha.w = item->Highlight ? 1.0f : 0.9f; + DrawList.AddRectFilled(pMin, pMax, ImGui::GetColorU32(colAlpha)); + } + itemData1 = itemData2; + } + gp.DigitalPlotItemCnt++; + gp.DigitalPlotOffset += pixYMax; + } + PopPlotClipRect(); +} + +//----------------------------------------------------------------------------- +// float + +void PlotDigital(const char* label_id, const float* xs, const float* ys, int count, int offset, int stride) { + GetterXsYs getter(xs,ys,count,offset,stride); + return PlotDigitalEx(label_id, getter); +} + +//----------------------------------------------------------------------------- +// double + +void PlotDigital(const char* label_id, const double* xs, const double* ys, int count, int offset, int stride) { + GetterXsYs getter(xs,ys,count,offset,stride); + return PlotDigitalEx(label_id, getter); +} + +//----------------------------------------------------------------------------- +// custom + +void PlotDigital(const char* label_id, ImPlotPoint (*getter_func)(void* data, int idx), void* data, int count, int offset) { + GetterFuncPtrImPlotPoint getter(getter_func,data,count,offset); + return PlotDigitalEx(label_id, getter); +} + +//----------------------------------------------------------------------------- +// PLOT TEXT +//----------------------------------------------------------------------------- +// float + +void PlotText(const char* text, float x, float y, bool vertical, const ImVec2& pixel_offset) { + return PlotText(text, (double)x, (double)y, vertical, pixel_offset); +} + +//----------------------------------------------------------------------------- +// double +void PlotText(const char* text, double x, double y, bool vertical, const ImVec2& pixel_offset) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotText() needs to be called between BeginPlot() and EndPlot()!"); + ImDrawList & DrawList = *ImGui::GetWindowDrawList(); + PushPlotClipRect(); + ImVec2 pos = PlotToPixels(ImPlotPoint(x,y)) + pixel_offset; + ImU32 colTxt = ImGui::GetColorU32(ImGuiCol_Text); + if (vertical) + AddTextVertical(&DrawList, text, pos, colTxt); + else + DrawList.AddText(pos, colTxt, text); + PopPlotClipRect(); +} + +} // namespace ImPlot \ No newline at end of file