The open source OpenXR runtime
1// MIT License
2
3// Copyright (c) 2023 Evan Pezent
4
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23// ImPlot v0.17
24
25/*
26
27API BREAKING CHANGES
28====================
29Occasionally introducing changes that are breaking the API. We try to make the breakage minor and easy to fix.
30Below is a change-log of API breaking changes only. If you are using one of the functions listed, expect to have to fix some code.
31When you are not sure about a old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all implot files.
32You can read releases logs https://github.com/epezent/implot/releases for more details.
33
34- 2023/08/20 (0.17) - ImPlotFlags_NoChild was removed as child windows are no longer needed to capture scroll. You can safely remove this flag if you were using it.
35- 2023/06/26 (0.15) - Various build fixes related to updates in Dear ImGui internals.
36- 2022/11/25 (0.15) - Make PlotText honor ImPlotItemFlags_NoFit.
37- 2022/06/19 (0.14) - The signature of ColormapScale has changed to accommodate a new ImPlotColormapScaleFlags parameter
38- 2022/06/17 (0.14) - **IMPORTANT** All PlotX functions now take an ImPlotX_Flags `flags` parameter. Where applicable, it is located before the existing `offset` and `stride` parameters.
39 If you were providing offset and stride values, you will need to update your function call to include a `flags` value. If you fail to do this, you will likely see
40 unexpected results or crashes without a compiler warning since these three are all default args. We apologize for the inconvenience, but this was a necessary evil.
41 - PlotBarsH has been removed; use PlotBars + ImPlotBarsFlags_Horizontal instead
42 - PlotErrorBarsH has been removed; use PlotErrorBars + ImPlotErrorBarsFlags_Horizontal
43 - PlotHistogram/PlotHistogram2D signatures changed; `cumulative`, `density`, and `outliers` options now specified via ImPlotHistogramFlags
44 - PlotPieChart signature changed; `normalize` option now specified via ImPlotPieChartFlags
45 - PlotText signature changes; `vertical` option now specified via `ImPlotTextFlags_Vertical`
46 - `PlotVLines` and `PlotHLines` replaced with `PlotInfLines` (+ ImPlotInfLinesFlags_Horizontal )
47 - arguments of ImPlotGetter have been reversed to be consistent with other API callbacks
48 - SetupAxisScale + ImPlotScale have replaced ImPlotAxisFlags_LogScale and ImPlotAxisFlags_Time flags
49 - ImPlotFormatters should now return an int indicating the size written
50 - the signature of ImPlotGetter has been reversed so that void* user_data is the last argument and consistent with other callbacks
51- 2021/10/19 (0.13) - MAJOR API OVERHAUL! See #168 and #272
52 - TRIVIAL RENAME:
53 - ImPlotLimits -> ImPlotRect
54 - ImPlotYAxis_ -> ImAxis_
55 - SetPlotYAxis -> SetAxis
56 - BeginDragDropTarget -> BeginDragDropTargetPlot
57 - BeginDragDropSource -> BeginDragDropSourcePlot
58 - ImPlotFlags_NoMousePos -> ImPlotFlags_NoMouseText
59 - SetNextPlotLimits -> SetNextAxesLimits
60 - SetMouseTextLocation -> SetupMouseText
61 - SIGNATURE MODIFIED:
62 - PixelsToPlot/PlotToPixels -> added optional X-Axis arg
63 - GetPlotMousePos -> added optional X-Axis arg
64 - GetPlotLimits -> added optional X-Axis arg
65 - GetPlotSelection -> added optional X-Axis arg
66 - DragLineX/Y/DragPoint -> now takes int id; removed labels (render with Annotation/Tag instead)
67 - REPLACED:
68 - IsPlotXAxisHovered/IsPlotXYAxisHovered -> IsAxisHovered(ImAxis)
69 - BeginDragDropTargetX/BeginDragDropTargetY -> BeginDragDropTargetAxis(ImAxis)
70 - BeginDragDropSourceX/BeginDragDropSourceY -> BeginDragDropSourceAxis(ImAxis)
71 - ImPlotCol_XAxis, ImPlotCol_YAxis1, etc. -> ImPlotCol_AxisText (push/pop this around SetupAxis to style individual axes)
72 - ImPlotCol_XAxisGrid, ImPlotCol_Y1AxisGrid -> ImPlotCol_AxisGrid (push/pop this around SetupAxis to style individual axes)
73 - SetNextPlotLimitsX/Y -> SetNextAxisLimits(ImAxis)
74 - LinkNextPlotLimits -> SetNextAxisLinks(ImAxis)
75 - FitNextPlotAxes -> SetNextAxisToFit(ImAxis)/SetNextAxesToFit
76 - SetLegendLocation -> SetupLegend
77 - ImPlotFlags_NoHighlight -> ImPlotLegendFlags_NoHighlight
78 - ImPlotOrientation -> ImPlotLegendFlags_Horizontal
79 - Annotate -> Annotation
80 - REMOVED:
81 - GetPlotQuery, SetPlotQuery, IsPlotQueried -> use DragRect
82 - SetNextPlotTicksX, SetNextPlotTicksY -> use SetupAxisTicks
83 - SetNextPlotFormatX, SetNextPlotFormatY -> use SetupAxisFormat
84 - AnnotateClamped -> use Annotation(bool clamp = true)
85 - OBSOLETED:
86 - BeginPlot (original signature) -> use simplified signature + Setup API
87- 2021/07/30 (0.12) - The offset argument of `PlotXG` functions was been removed. Implement offsetting in your getter callback instead.
88- 2021/03/08 (0.9) - SetColormap and PushColormap(ImVec4*) were removed. Use AddColormap for custom colormap support. LerpColormap was changed to SampleColormap.
89 ShowColormapScale was changed to ColormapScale and requires additional arguments.
90- 2021/03/07 (0.9) - The signature of ShowColormapScale was modified to accept a ImVec2 size.
91- 2021/02/28 (0.9) - BeginLegendDragDropSource was changed to BeginDragDropSourceItem with a number of other drag and drop improvements.
92- 2021/01/18 (0.9) - The default behavior for opening context menus was change from double right-click to single right-click. ImPlotInputMap and related functions were moved
93 to implot_internal.h due to its immaturity.
94- 2020/10/16 (0.8) - ImPlotStyleVar_InfoPadding was changed to ImPlotStyleVar_MousePosPadding
95- 2020/09/10 (0.8) - The single array versions of PlotLine, PlotScatter, PlotStems, and PlotShaded were given additional arguments for x-scale and x0.
96- 2020/09/07 (0.8) - Plotting functions which accept a custom getter function pointer have been post-fixed with a G (e.g. PlotLineG)
97- 2020/09/06 (0.7) - Several flags under ImPlotFlags and ImPlotAxisFlags were inverted (e.g. ImPlotFlags_Legend -> ImPlotFlags_NoLegend) so that the default flagset
98 is simply 0. This more closely matches ImGui's style and makes it easier to enable non-default but commonly used flags (e.g. ImPlotAxisFlags_Time).
99- 2020/08/28 (0.5) - ImPlotMarker_ can no longer be combined with bitwise OR, |. This features caused unecessary slow-down, and almost no one used it.
100- 2020/08/25 (0.5) - ImPlotAxisFlags_Scientific was removed. Logarithmic axes automatically uses scientific notation.
101- 2020/08/17 (0.5) - PlotText was changed so that text is centered horizontally and vertically about the desired point.
102- 2020/08/16 (0.5) - An ImPlotContext must be explicitly created and destroyed now with `CreateContext` and `DestroyContext`. Previously, the context was statically initialized in this source file.
103- 2020/06/13 (0.4) - The flags `ImPlotAxisFlag_Adaptive` and `ImPlotFlags_Cull` were removed. Both are now done internally by default.
104- 2020/06/03 (0.3) - The signature and behavior of PlotPieChart was changed so that data with sum less than 1 can optionally be normalized. The label format can now be specified as well.
105- 2020/06/01 (0.3) - SetPalette was changed to `SetColormap` for consistency with other plotting libraries. `RestorePalette` was removed. Use `SetColormap(ImPlotColormap_Default)`.
106- 2020/05/31 (0.3) - Plot functions taking custom ImVec2* getters were removed. Use the ImPlotPoint* getter versions instead.
107- 2020/05/29 (0.3) - The signature of ImPlotLimits::Contains was changed to take two doubles instead of ImVec2
108- 2020/05/16 (0.2) - All plotting functions were reverted to being prefixed with "Plot" to maintain a consistent VerbNoun style. `Plot` was split into `PlotLine`
109 and `PlotScatter` (however, `PlotLine` can still be used to plot scatter points as `Plot` did before.). `Bar` is not `PlotBars`, to indicate
110 that multiple bars will be plotted.
111- 2020/05/13 (0.2) - `ImMarker` was change to `ImPlotMarker` and `ImAxisFlags` was changed to `ImPlotAxisFlags`.
112- 2020/05/11 (0.2) - `ImPlotFlags_Selection` was changed to `ImPlotFlags_BoxSelect`
113- 2020/05/11 (0.2) - The namespace ImGui:: was replaced with ImPlot::. As a result, the following additional changes were made:
114 - Functions that were prefixed or decorated with the word "Plot" have been truncated. E.g., `ImGui::PlotBars` is now just `ImPlot::Bar`.
115 It should be fairly obvious what was what.
116 - Some functions have been given names that would have otherwise collided with the ImGui namespace. This has been done to maintain a consistent
117 style with ImGui. E.g., 'ImGui::PushPlotStyleVar` is now 'ImPlot::PushStyleVar'.
118- 2020/05/10 (0.2) - The following function/struct names were changes:
119 - ImPlotRange -> ImPlotLimits
120 - GetPlotRange() -> GetPlotLimits()
121 - SetNextPlotRange -> SetNextPlotLimits
122 - SetNextPlotRangeX -> SetNextPlotLimitsX
123 - SetNextPlotRangeY -> SetNextPlotLimitsY
124- 2020/05/10 (0.2) - Plot queries are pixel based by default. Query rects that maintain relative plot position have been removed. This was done to support multi-y-axis.
125
126*/
127
128#define IMGUI_DEFINE_MATH_OPERATORS
129#include "implot.h"
130#include "implot_internal.h"
131
132#include <stdlib.h>
133
134// Support for pre-1.82 versions. Users on 1.82+ can use 0 (default) flags to mean "all corners" but in order to support older versions we are more explicit.
135#if (IMGUI_VERSION_NUM < 18102) && !defined(ImDrawFlags_RoundCornersAll)
136#define ImDrawFlags_RoundCornersAll ImDrawCornerFlags_All
137#endif
138
139// Support for pre-1.89.7 versions.
140#if (IMGUI_VERSION_NUM < 18966)
141#define ImGuiButtonFlags_AllowOverlap ImGuiButtonFlags_AllowItemOverlap
142#endif
143
144// Visual Studio warnings
145#ifdef _MSC_VER
146#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
147#endif
148
149// Clang/GCC warnings with -Weverything
150#if defined(__clang__)
151#pragma clang diagnostic ignored "-Wformat-nonliteral" // warning: format string is not a string literal
152#elif defined(__GNUC__)
153#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked
154#endif
155
156// Global plot context
157#ifndef GImPlot
158ImPlotContext* GImPlot = nullptr;
159#endif
160
161//-----------------------------------------------------------------------------
162// Struct Implementations
163//-----------------------------------------------------------------------------
164
165ImPlotInputMap::ImPlotInputMap() {
166 ImPlot::MapInputDefault(this);
167}
168
169ImPlotStyle::ImPlotStyle() {
170
171 LineWeight = 1;
172 Marker = ImPlotMarker_None;
173 MarkerSize = 4;
174 MarkerWeight = 1;
175 FillAlpha = 1;
176 ErrorBarSize = 5;
177 ErrorBarWeight = 1.5f;
178 DigitalBitHeight = 8;
179 DigitalBitGap = 4;
180
181 PlotBorderSize = 1;
182 MinorAlpha = 0.25f;
183 MajorTickLen = ImVec2(10,10);
184 MinorTickLen = ImVec2(5,5);
185 MajorTickSize = ImVec2(1,1);
186 MinorTickSize = ImVec2(1,1);
187 MajorGridSize = ImVec2(1,1);
188 MinorGridSize = ImVec2(1,1);
189 PlotPadding = ImVec2(10,10);
190 LabelPadding = ImVec2(5,5);
191 LegendPadding = ImVec2(10,10);
192 LegendInnerPadding = ImVec2(5,5);
193 LegendSpacing = ImVec2(5,0);
194 MousePosPadding = ImVec2(10,10);
195 AnnotationPadding = ImVec2(2,2);
196 FitPadding = ImVec2(0,0);
197 PlotDefaultSize = ImVec2(400,300);
198 PlotMinSize = ImVec2(200,150);
199
200 ImPlot::StyleColorsAuto(this);
201
202 Colormap = ImPlotColormap_Deep;
203
204 UseLocalTime = false;
205 Use24HourClock = false;
206 UseISO8601 = false;
207}
208
209//-----------------------------------------------------------------------------
210// Style
211//-----------------------------------------------------------------------------
212
213namespace ImPlot {
214
215const char* GetStyleColorName(ImPlotCol col) {
216 static const char* col_names[ImPlotCol_COUNT] = {
217 "Line",
218 "Fill",
219 "MarkerOutline",
220 "MarkerFill",
221 "ErrorBar",
222 "FrameBg",
223 "PlotBg",
224 "PlotBorder",
225 "LegendBg",
226 "LegendBorder",
227 "LegendText",
228 "TitleText",
229 "InlayText",
230 "AxisText",
231 "AxisGrid",
232 "AxisTick",
233 "AxisBg",
234 "AxisBgHovered",
235 "AxisBgActive",
236 "Selection",
237 "Crosshairs"
238 };
239 return col_names[col];
240}
241
242const char* GetMarkerName(ImPlotMarker marker) {
243 switch (marker) {
244 case ImPlotMarker_None: return "None";
245 case ImPlotMarker_Circle: return "Circle";
246 case ImPlotMarker_Square: return "Square";
247 case ImPlotMarker_Diamond: return "Diamond";
248 case ImPlotMarker_Up: return "Up";
249 case ImPlotMarker_Down: return "Down";
250 case ImPlotMarker_Left: return "Left";
251 case ImPlotMarker_Right: return "Right";
252 case ImPlotMarker_Cross: return "Cross";
253 case ImPlotMarker_Plus: return "Plus";
254 case ImPlotMarker_Asterisk: return "Asterisk";
255 default: return "";
256 }
257}
258
259ImVec4 GetAutoColor(ImPlotCol idx) {
260 ImVec4 col(0,0,0,1);
261 switch(idx) {
262 case ImPlotCol_Line: return col; // these are plot dependent!
263 case ImPlotCol_Fill: return col; // these are plot dependent!
264 case ImPlotCol_MarkerOutline: return col; // these are plot dependent!
265 case ImPlotCol_MarkerFill: return col; // these are plot dependent!
266 case ImPlotCol_ErrorBar: return ImGui::GetStyleColorVec4(ImGuiCol_Text);
267 case ImPlotCol_FrameBg: return ImGui::GetStyleColorVec4(ImGuiCol_FrameBg);
268 case ImPlotCol_PlotBg: return ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
269 case ImPlotCol_PlotBorder: return ImGui::GetStyleColorVec4(ImGuiCol_Border);
270 case ImPlotCol_LegendBg: return ImGui::GetStyleColorVec4(ImGuiCol_PopupBg);
271 case ImPlotCol_LegendBorder: return GetStyleColorVec4(ImPlotCol_PlotBorder);
272 case ImPlotCol_LegendText: return GetStyleColorVec4(ImPlotCol_InlayText);
273 case ImPlotCol_TitleText: return ImGui::GetStyleColorVec4(ImGuiCol_Text);
274 case ImPlotCol_InlayText: return ImGui::GetStyleColorVec4(ImGuiCol_Text);
275 case ImPlotCol_AxisText: return ImGui::GetStyleColorVec4(ImGuiCol_Text);
276 case ImPlotCol_AxisGrid: return GetStyleColorVec4(ImPlotCol_AxisText) * ImVec4(1,1,1,0.25f);
277 case ImPlotCol_AxisTick: return GetStyleColorVec4(ImPlotCol_AxisGrid);
278 case ImPlotCol_AxisBg: return ImVec4(0,0,0,0);
279 case ImPlotCol_AxisBgHovered: return ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered);
280 case ImPlotCol_AxisBgActive: return ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive);
281 case ImPlotCol_Selection: return ImVec4(1,1,0,1);
282 case ImPlotCol_Crosshairs: return GetStyleColorVec4(ImPlotCol_PlotBorder);
283 default: return col;
284 }
285}
286
287struct ImPlotStyleVarInfo {
288 ImGuiDataType Type;
289 ImU32 Count;
290 ImU32 Offset;
291 void* GetVarPtr(ImPlotStyle* style) const { return (void*)((unsigned char*)style + Offset); }
292};
293
294static const ImPlotStyleVarInfo GPlotStyleVarInfo[] =
295{
296 { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, LineWeight) }, // ImPlotStyleVar_LineWeight
297 { ImGuiDataType_S32, 1, (ImU32)offsetof(ImPlotStyle, Marker) }, // ImPlotStyleVar_Marker
298 { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, MarkerSize) }, // ImPlotStyleVar_MarkerSize
299 { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, MarkerWeight) }, // ImPlotStyleVar_MarkerWeight
300 { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, FillAlpha) }, // ImPlotStyleVar_FillAlpha
301 { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, ErrorBarSize) }, // ImPlotStyleVar_ErrorBarSize
302 { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, ErrorBarWeight) }, // ImPlotStyleVar_ErrorBarWeight
303 { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, DigitalBitHeight) }, // ImPlotStyleVar_DigitalBitHeight
304 { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, DigitalBitGap) }, // ImPlotStyleVar_DigitalBitGap
305
306 { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, PlotBorderSize) }, // ImPlotStyleVar_PlotBorderSize
307 { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, MinorAlpha) }, // ImPlotStyleVar_MinorAlpha
308 { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MajorTickLen) }, // ImPlotStyleVar_MajorTickLen
309 { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MinorTickLen) }, // ImPlotStyleVar_MinorTickLen
310 { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MajorTickSize) }, // ImPlotStyleVar_MajorTickSize
311 { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MinorTickSize) }, // ImPlotStyleVar_MinorTickSize
312 { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MajorGridSize) }, // ImPlotStyleVar_MajorGridSize
313 { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MinorGridSize) }, // ImPlotStyleVar_MinorGridSize
314 { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, PlotPadding) }, // ImPlotStyleVar_PlotPadding
315 { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, LabelPadding) }, // ImPlotStyleVar_LabelPaddine
316 { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, LegendPadding) }, // ImPlotStyleVar_LegendPadding
317 { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, LegendInnerPadding) }, // ImPlotStyleVar_LegendInnerPadding
318 { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, LegendSpacing) }, // ImPlotStyleVar_LegendSpacing
319
320 { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MousePosPadding) }, // ImPlotStyleVar_MousePosPadding
321 { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, AnnotationPadding) }, // ImPlotStyleVar_AnnotationPadding
322 { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, FitPadding) }, // ImPlotStyleVar_FitPadding
323 { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, PlotDefaultSize) }, // ImPlotStyleVar_PlotDefaultSize
324 { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, PlotMinSize) } // ImPlotStyleVar_PlotMinSize
325};
326
327static const ImPlotStyleVarInfo* GetPlotStyleVarInfo(ImPlotStyleVar idx) {
328 IM_ASSERT(idx >= 0 && idx < ImPlotStyleVar_COUNT);
329 IM_ASSERT(IM_ARRAYSIZE(GPlotStyleVarInfo) == ImPlotStyleVar_COUNT);
330 return &GPlotStyleVarInfo[idx];
331}
332
333//-----------------------------------------------------------------------------
334// Generic Helpers
335//-----------------------------------------------------------------------------
336
337void AddTextVertical(ImDrawList *DrawList, ImVec2 pos, ImU32 col, const char *text_begin, const char* text_end) {
338 // the code below is based loosely on ImFont::RenderText
339 if (!text_end)
340 text_end = text_begin + strlen(text_begin);
341 ImGuiContext& g = *GImGui;
342 ImFont* font = g.Font;
343 // Align to be pixel perfect
344 pos.x = ImFloor(pos.x);
345 pos.y = ImFloor(pos.y);
346 const float scale = g.FontSize / font->FontSize;
347 const char* s = text_begin;
348 int chars_exp = (int)(text_end - s);
349 int chars_rnd = 0;
350 const int vtx_count_max = chars_exp * 4;
351 const int idx_count_max = chars_exp * 6;
352 DrawList->PrimReserve(idx_count_max, vtx_count_max);
353 while (s < text_end) {
354 unsigned int c = (unsigned int)*s;
355 if (c < 0x80) {
356 s += 1;
357 }
358 else {
359 s += ImTextCharFromUtf8(&c, s, text_end);
360 if (c == 0) // Malformed UTF-8?
361 break;
362 }
363 const ImFontGlyph * glyph = font->FindGlyph((ImWchar)c);
364 if (glyph == nullptr) {
365 continue;
366 }
367 DrawList->PrimQuadUV(pos + ImVec2(glyph->Y0, -glyph->X0) * scale, pos + ImVec2(glyph->Y0, -glyph->X1) * scale,
368 pos + ImVec2(glyph->Y1, -glyph->X1) * scale, pos + ImVec2(glyph->Y1, -glyph->X0) * scale,
369 ImVec2(glyph->U0, glyph->V0), ImVec2(glyph->U1, glyph->V0),
370 ImVec2(glyph->U1, glyph->V1), ImVec2(glyph->U0, glyph->V1),
371 col);
372 pos.y -= glyph->AdvanceX * scale;
373 chars_rnd++;
374 }
375 // Give back unused vertices
376 int chars_skp = chars_exp-chars_rnd;
377 DrawList->PrimUnreserve(chars_skp*6, chars_skp*4);
378}
379
380void AddTextCentered(ImDrawList* DrawList, ImVec2 top_center, ImU32 col, const char* text_begin, const char* text_end) {
381 float txt_ht = ImGui::GetTextLineHeight();
382 const char* title_end = ImGui::FindRenderedTextEnd(text_begin, text_end);
383 ImVec2 text_size;
384 float y = 0;
385 while (const char* tmp = (const char*)memchr(text_begin, '\n', title_end-text_begin)) {
386 text_size = ImGui::CalcTextSize(text_begin,tmp,true);
387 DrawList->AddText(ImVec2(top_center.x - text_size.x * 0.5f, top_center.y+y),col,text_begin,tmp);
388 text_begin = tmp + 1;
389 y += txt_ht;
390 }
391 text_size = ImGui::CalcTextSize(text_begin,title_end,true);
392 DrawList->AddText(ImVec2(top_center.x - text_size.x * 0.5f, top_center.y+y),col,text_begin,title_end);
393}
394
395double NiceNum(double x, bool round) {
396 double f;
397 double nf;
398 int expv = (int)floor(ImLog10(x));
399 f = x / ImPow(10.0, (double)expv);
400 if (round)
401 if (f < 1.5)
402 nf = 1;
403 else if (f < 3)
404 nf = 2;
405 else if (f < 7)
406 nf = 5;
407 else
408 nf = 10;
409 else if (f <= 1)
410 nf = 1;
411 else if (f <= 2)
412 nf = 2;
413 else if (f <= 5)
414 nf = 5;
415 else
416 nf = 10;
417 return nf * ImPow(10.0, expv);
418}
419
420//-----------------------------------------------------------------------------
421// Context Utils
422//-----------------------------------------------------------------------------
423
424void SetImGuiContext(ImGuiContext* ctx) {
425 ImGui::SetCurrentContext(ctx);
426}
427
428ImPlotContext* CreateContext() {
429 ImPlotContext* ctx = IM_NEW(ImPlotContext)();
430 Initialize(ctx);
431 if (GImPlot == nullptr)
432 SetCurrentContext(ctx);
433 return ctx;
434}
435
436void DestroyContext(ImPlotContext* ctx) {
437 if (ctx == nullptr)
438 ctx = GImPlot;
439 if (GImPlot == ctx)
440 SetCurrentContext(nullptr);
441 IM_DELETE(ctx);
442}
443
444ImPlotContext* GetCurrentContext() {
445 return GImPlot;
446}
447
448void SetCurrentContext(ImPlotContext* ctx) {
449 GImPlot = ctx;
450}
451
452#define IMPLOT_APPEND_CMAP(name, qual) ctx->ColormapData.Append(#name, name, sizeof(name)/sizeof(ImU32), qual)
453#define IM_RGB(r,g,b) IM_COL32(r,g,b,255)
454
455void Initialize(ImPlotContext* ctx) {
456 ResetCtxForNextPlot(ctx);
457 ResetCtxForNextAlignedPlots(ctx);
458 ResetCtxForNextSubplot(ctx);
459
460 const ImU32 Deep[] = {4289753676, 4283598045, 4285048917, 4283584196, 4289950337, 4284512403, 4291005402, 4287401100, 4285839820, 4291671396 };
461 const ImU32 Dark[] = {4280031972, 4290281015, 4283084621, 4288892568, 4278222847, 4281597951, 4280833702, 4290740727, 4288256409 };
462 const ImU32 Pastel[] = {4289639675, 4293119411, 4291161036, 4293184478, 4289124862, 4291624959, 4290631909, 4293712637, 4294111986 };
463 const ImU32 Paired[] = {4293119554, 4290017311, 4287291314, 4281114675, 4288256763, 4280031971, 4285513725, 4278222847, 4292260554, 4288298346, 4288282623, 4280834481};
464 const ImU32 Viridis[] = {4283695428, 4285867080, 4287054913, 4287455029, 4287526954, 4287402273, 4286883874, 4285579076, 4283552122, 4280737725, 4280674301 };
465 const ImU32 Plasma[] = {4287039501, 4288480321, 4289200234, 4288941455, 4287638193, 4286072780, 4284638433, 4283139314, 4281771772, 4280667900, 4280416752 };
466 const ImU32 Hot[] = {4278190144, 4278190208, 4278190271, 4278190335, 4278206719, 4278223103, 4278239231, 4278255615, 4283826175, 4289396735, 4294967295 };
467 const ImU32 Cool[] = {4294967040, 4294960666, 4294954035, 4294947661, 4294941030, 4294934656, 4294928025, 4294921651, 4294915020, 4294908646, 4294902015 };
468 const ImU32 Pink[] = {4278190154, 4282532475, 4284308894, 4285690554, 4286879686, 4287870160, 4288794330, 4289651940, 4291685869, 4293392118, 4294967295 };
469 const ImU32 Jet[] = {4289331200, 4294901760, 4294923520, 4294945280, 4294967040, 4289396565, 4283826090, 4278255615, 4278233855, 4278212095, 4278190335 };
470 const ImU32 Twilight[] = {IM_RGB(226,217,226),IM_RGB(166,191,202),IM_RGB(109,144,192),IM_RGB(95,88,176),IM_RGB(83,30,124),IM_RGB(47,20,54),IM_RGB(100,25,75),IM_RGB(159,60,80),IM_RGB(192,117,94),IM_RGB(208,179,158),IM_RGB(226,217,226)};
471 const ImU32 RdBu[] = {IM_RGB(103,0,31),IM_RGB(178,24,43),IM_RGB(214,96,77),IM_RGB(244,165,130),IM_RGB(253,219,199),IM_RGB(247,247,247),IM_RGB(209,229,240),IM_RGB(146,197,222),IM_RGB(67,147,195),IM_RGB(33,102,172),IM_RGB(5,48,97)};
472 const ImU32 BrBG[] = {IM_RGB(84,48,5),IM_RGB(140,81,10),IM_RGB(191,129,45),IM_RGB(223,194,125),IM_RGB(246,232,195),IM_RGB(245,245,245),IM_RGB(199,234,229),IM_RGB(128,205,193),IM_RGB(53,151,143),IM_RGB(1,102,94),IM_RGB(0,60,48)};
473 const ImU32 PiYG[] = {IM_RGB(142,1,82),IM_RGB(197,27,125),IM_RGB(222,119,174),IM_RGB(241,182,218),IM_RGB(253,224,239),IM_RGB(247,247,247),IM_RGB(230,245,208),IM_RGB(184,225,134),IM_RGB(127,188,65),IM_RGB(77,146,33),IM_RGB(39,100,25)};
474 const ImU32 Spectral[] = {IM_RGB(158,1,66),IM_RGB(213,62,79),IM_RGB(244,109,67),IM_RGB(253,174,97),IM_RGB(254,224,139),IM_RGB(255,255,191),IM_RGB(230,245,152),IM_RGB(171,221,164),IM_RGB(102,194,165),IM_RGB(50,136,189),IM_RGB(94,79,162)};
475 const ImU32 Greys[] = {IM_COL32_WHITE, IM_COL32_BLACK };
476
477 IMPLOT_APPEND_CMAP(Deep, true);
478 IMPLOT_APPEND_CMAP(Dark, true);
479 IMPLOT_APPEND_CMAP(Pastel, true);
480 IMPLOT_APPEND_CMAP(Paired, true);
481 IMPLOT_APPEND_CMAP(Viridis, false);
482 IMPLOT_APPEND_CMAP(Plasma, false);
483 IMPLOT_APPEND_CMAP(Hot, false);
484 IMPLOT_APPEND_CMAP(Cool, false);
485 IMPLOT_APPEND_CMAP(Pink, false);
486 IMPLOT_APPEND_CMAP(Jet, false);
487 IMPLOT_APPEND_CMAP(Twilight, false);
488 IMPLOT_APPEND_CMAP(RdBu, false);
489 IMPLOT_APPEND_CMAP(BrBG, false);
490 IMPLOT_APPEND_CMAP(PiYG, false);
491 IMPLOT_APPEND_CMAP(Spectral, false);
492 IMPLOT_APPEND_CMAP(Greys, false);
493}
494
495void ResetCtxForNextPlot(ImPlotContext* ctx) {
496 // reset the next plot/item data
497 ctx->NextPlotData.Reset();
498 ctx->NextItemData.Reset();
499 // reset labels
500 ctx->Annotations.Reset();
501 ctx->Tags.Reset();
502 // reset extents/fit
503 ctx->OpenContextThisFrame = false;
504 // reset digital plot items count
505 ctx->DigitalPlotItemCnt = 0;
506 ctx->DigitalPlotOffset = 0;
507 // nullify plot
508 ctx->CurrentPlot = nullptr;
509 ctx->CurrentItem = nullptr;
510 ctx->PreviousItem = nullptr;
511}
512
513void ResetCtxForNextAlignedPlots(ImPlotContext* ctx) {
514 ctx->CurrentAlignmentH = nullptr;
515 ctx->CurrentAlignmentV = nullptr;
516}
517
518void ResetCtxForNextSubplot(ImPlotContext* ctx) {
519 ctx->CurrentSubplot = nullptr;
520 ctx->CurrentAlignmentH = nullptr;
521 ctx->CurrentAlignmentV = nullptr;
522}
523
524//-----------------------------------------------------------------------------
525// Plot Utils
526//-----------------------------------------------------------------------------
527
528ImPlotPlot* GetPlot(const char* title) {
529 ImGuiWindow* Window = GImGui->CurrentWindow;
530 const ImGuiID ID = Window->GetID(title);
531 return GImPlot->Plots.GetByKey(ID);
532}
533
534ImPlotPlot* GetCurrentPlot() {
535 return GImPlot->CurrentPlot;
536}
537
538void BustPlotCache() {
539 ImPlotContext& gp = *GImPlot;
540 gp.Plots.Clear();
541 gp.Subplots.Clear();
542}
543
544//-----------------------------------------------------------------------------
545// Legend Utils
546//-----------------------------------------------------------------------------
547
548ImVec2 GetLocationPos(const ImRect& outer_rect, const ImVec2& inner_size, ImPlotLocation loc, const ImVec2& pad) {
549 ImVec2 pos;
550 if (ImHasFlag(loc, ImPlotLocation_West) && !ImHasFlag(loc, ImPlotLocation_East))
551 pos.x = outer_rect.Min.x + pad.x;
552 else if (!ImHasFlag(loc, ImPlotLocation_West) && ImHasFlag(loc, ImPlotLocation_East))
553 pos.x = outer_rect.Max.x - pad.x - inner_size.x;
554 else
555 pos.x = outer_rect.GetCenter().x - inner_size.x * 0.5f;
556 // legend reference point y
557 if (ImHasFlag(loc, ImPlotLocation_North) && !ImHasFlag(loc, ImPlotLocation_South))
558 pos.y = outer_rect.Min.y + pad.y;
559 else if (!ImHasFlag(loc, ImPlotLocation_North) && ImHasFlag(loc, ImPlotLocation_South))
560 pos.y = outer_rect.Max.y - pad.y - inner_size.y;
561 else
562 pos.y = outer_rect.GetCenter().y - inner_size.y * 0.5f;
563 pos.x = IM_ROUND(pos.x);
564 pos.y = IM_ROUND(pos.y);
565 return pos;
566}
567
568ImVec2 CalcLegendSize(ImPlotItemGroup& items, const ImVec2& pad, const ImVec2& spacing, bool vertical) {
569 // vars
570 const int nItems = items.GetLegendCount();
571 const float txt_ht = ImGui::GetTextLineHeight();
572 const float icon_size = txt_ht;
573 // get label max width
574 float max_label_width = 0;
575 float sum_label_width = 0;
576 for (int i = 0; i < nItems; ++i) {
577 const char* label = items.GetLegendLabel(i);
578 const float label_width = ImGui::CalcTextSize(label, nullptr, true).x;
579 max_label_width = label_width > max_label_width ? label_width : max_label_width;
580 sum_label_width += label_width;
581 }
582 // calc legend size
583 const ImVec2 legend_size = vertical ?
584 ImVec2(pad.x * 2 + icon_size + max_label_width, pad.y * 2 + nItems * txt_ht + (nItems - 1) * spacing.y) :
585 ImVec2(pad.x * 2 + icon_size * nItems + sum_label_width + (nItems - 1) * spacing.x, pad.y * 2 + txt_ht);
586 return legend_size;
587}
588
589bool ClampLegendRect(ImRect& legend_rect, const ImRect& outer_rect, const ImVec2& pad) {
590 bool clamped = false;
591 ImRect outer_rect_pad(outer_rect.Min + pad, outer_rect.Max - pad);
592 if (legend_rect.Min.x < outer_rect_pad.Min.x) {
593 legend_rect.Min.x = outer_rect_pad.Min.x;
594 clamped = true;
595 }
596 if (legend_rect.Min.y < outer_rect_pad.Min.y) {
597 legend_rect.Min.y = outer_rect_pad.Min.y;
598 clamped = true;
599 }
600 if (legend_rect.Max.x > outer_rect_pad.Max.x) {
601 legend_rect.Max.x = outer_rect_pad.Max.x;
602 clamped = true;
603 }
604 if (legend_rect.Max.y > outer_rect_pad.Max.y) {
605 legend_rect.Max.y = outer_rect_pad.Max.y;
606 clamped = true;
607 }
608 return clamped;
609}
610
611int LegendSortingComp(const void* _a, const void* _b) {
612 ImPlotItemGroup* items = GImPlot->SortItems;
613 const int a = *(const int*)_a;
614 const int b = *(const int*)_b;
615 const char* label_a = items->GetLegendLabel(a);
616 const char* label_b = items->GetLegendLabel(b);
617 return strcmp(label_a,label_b);
618}
619
620bool ShowLegendEntries(ImPlotItemGroup& items, const ImRect& legend_bb, bool hovered, const ImVec2& pad, const ImVec2& spacing, bool vertical, ImDrawList& DrawList) {
621 // vars
622 const float txt_ht = ImGui::GetTextLineHeight();
623 const float icon_size = txt_ht;
624 const float icon_shrink = 2;
625 ImU32 col_txt = GetStyleColorU32(ImPlotCol_LegendText);
626 ImU32 col_txt_dis = ImAlphaU32(col_txt, 0.25f);
627 // render each legend item
628 float sum_label_width = 0;
629 bool any_item_hovered = false;
630
631 const int num_items = items.GetLegendCount();
632 if (num_items < 1)
633 return hovered;
634 // build render order
635 ImPlotContext& gp = *GImPlot;
636 ImVector<int>& indices = gp.TempInt1;
637 indices.resize(num_items);
638 for (int i = 0; i < num_items; ++i)
639 indices[i] = i;
640 if (ImHasFlag(items.Legend.Flags, ImPlotLegendFlags_Sort) && num_items > 1) {
641 gp.SortItems = &items;
642 qsort(indices.Data, num_items, sizeof(int), LegendSortingComp);
643 }
644 // render
645 for (int i = 0; i < num_items; ++i) {
646 const int idx = indices[i];
647 ImPlotItem* item = items.GetLegendItem(idx);
648 const char* label = items.GetLegendLabel(idx);
649 const float label_width = ImGui::CalcTextSize(label, nullptr, true).x;
650 const ImVec2 top_left = vertical ?
651 legend_bb.Min + pad + ImVec2(0, i * (txt_ht + spacing.y)) :
652 legend_bb.Min + pad + ImVec2(i * (icon_size + spacing.x) + sum_label_width, 0);
653 sum_label_width += label_width;
654 ImRect icon_bb;
655 icon_bb.Min = top_left + ImVec2(icon_shrink,icon_shrink);
656 icon_bb.Max = top_left + ImVec2(icon_size - icon_shrink, icon_size - icon_shrink);
657 ImRect label_bb;
658 label_bb.Min = top_left;
659 label_bb.Max = top_left + ImVec2(label_width + icon_size, icon_size);
660 ImU32 col_txt_hl;
661 ImU32 col_item = ImAlphaU32(item->Color,1);
662
663 ImRect button_bb(icon_bb.Min, label_bb.Max);
664
665 ImGui::KeepAliveID(item->ID);
666
667 bool item_hov = false;
668 bool item_hld = false;
669 bool item_clk = ImHasFlag(items.Legend.Flags, ImPlotLegendFlags_NoButtons)
670 ? false
671 : ImGui::ButtonBehavior(button_bb, item->ID, &item_hov, &item_hld);
672
673 if (item_clk)
674 item->Show = !item->Show;
675
676
677 const bool can_hover = (item_hov)
678 && (!ImHasFlag(items.Legend.Flags, ImPlotLegendFlags_NoHighlightItem)
679 || !ImHasFlag(items.Legend.Flags, ImPlotLegendFlags_NoHighlightAxis));
680
681 if (can_hover) {
682 item->LegendHoverRect.Min = icon_bb.Min;
683 item->LegendHoverRect.Max = label_bb.Max;
684 item->LegendHovered = true;
685 col_txt_hl = ImMixU32(col_txt, col_item, 64);
686 any_item_hovered = true;
687 }
688 else {
689 col_txt_hl = ImGui::GetColorU32(col_txt);
690 }
691 ImU32 col_icon;
692 if (item_hld)
693 col_icon = item->Show ? ImAlphaU32(col_item,0.5f) : ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.5f);
694 else if (item_hov)
695 col_icon = item->Show ? ImAlphaU32(col_item,0.75f) : ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.75f);
696 else
697 col_icon = item->Show ? col_item : col_txt_dis;
698
699 DrawList.AddRectFilled(icon_bb.Min, icon_bb.Max, col_icon);
700 const char* text_display_end = ImGui::FindRenderedTextEnd(label, nullptr);
701 if (label != text_display_end)
702 DrawList.AddText(top_left + ImVec2(icon_size, 0), item->Show ? col_txt_hl : col_txt_dis, label, text_display_end);
703 }
704 return hovered && !any_item_hovered;
705}
706
707//-----------------------------------------------------------------------------
708// Locators
709//-----------------------------------------------------------------------------
710
711static const float TICK_FILL_X = 0.8f;
712static const float TICK_FILL_Y = 1.0f;
713
714void Locator_Default(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data) {
715 if (range.Min == range.Max)
716 return;
717 const int nMinor = 10;
718 const int nMajor = ImMax(2, (int)IM_ROUND(pixels / (vertical ? 300.0f : 400.0f)));
719 const double nice_range = NiceNum(range.Size() * 0.99, false);
720 const double interval = NiceNum(nice_range / (nMajor - 1), true);
721 const double graphmin = floor(range.Min / interval) * interval;
722 const double graphmax = ceil(range.Max / interval) * interval;
723 bool first_major_set = false;
724 int first_major_idx = 0;
725 const int idx0 = ticker.TickCount(); // ticker may have user custom ticks
726 ImVec2 total_size(0,0);
727 for (double major = graphmin; major < graphmax + 0.5 * interval; major += interval) {
728 // is this zero? combat zero formatting issues
729 if (major-interval < 0 && major+interval > 0)
730 major = 0;
731 if (range.Contains(major)) {
732 if (!first_major_set) {
733 first_major_idx = ticker.TickCount();
734 first_major_set = true;
735 }
736 total_size += ticker.AddTick(major, true, 0, true, formatter, formatter_data).LabelSize;
737 }
738 for (int i = 1; i < nMinor; ++i) {
739 double minor = major + i * interval / nMinor;
740 if (range.Contains(minor)) {
741 total_size += ticker.AddTick(minor, false, 0, true, formatter, formatter_data).LabelSize;
742 }
743 }
744 }
745 // prune if necessary
746 if ((!vertical && total_size.x > pixels*TICK_FILL_X) || (vertical && total_size.y > pixels*TICK_FILL_Y)) {
747 for (int i = first_major_idx-1; i >= idx0; i -= 2)
748 ticker.Ticks[i].ShowLabel = false;
749 for (int i = first_major_idx+1; i < ticker.TickCount(); i += 2)
750 ticker.Ticks[i].ShowLabel = false;
751 }
752}
753
754bool CalcLogarithmicExponents(const ImPlotRange& range, float pix, bool vertical, int& exp_min, int& exp_max, int& exp_step) {
755 if (range.Min * range.Max > 0) {
756 const int nMajor = vertical ? ImMax(2, (int)IM_ROUND(pix * 0.02f)) : ImMax(2, (int)IM_ROUND(pix * 0.01f)); // TODO: magic numbers
757 double log_min = ImLog10(ImAbs(range.Min));
758 double log_max = ImLog10(ImAbs(range.Max));
759 double log_a = ImMin(log_min,log_max);
760 double log_b = ImMax(log_min,log_max);
761 exp_step = ImMax(1,(int)(log_b - log_a) / nMajor);
762 exp_min = (int)log_a;
763 exp_max = (int)log_b;
764 if (exp_step != 1) {
765 while(exp_step % 3 != 0) exp_step++; // make step size multiple of three
766 while(exp_min % exp_step != 0) exp_min--; // decrease exp_min until exp_min + N * exp_step will be 0
767 }
768 return true;
769 }
770 return false;
771}
772
773void AddTicksLogarithmic(const ImPlotRange& range, int exp_min, int exp_max, int exp_step, ImPlotTicker& ticker, ImPlotFormatter formatter, void* data) {
774 const double sign = ImSign(range.Max);
775 for (int e = exp_min - exp_step; e < (exp_max + exp_step); e += exp_step) {
776 double major1 = sign*ImPow(10, (double)(e));
777 double major2 = sign*ImPow(10, (double)(e + 1));
778 double interval = (major2 - major1) / 9;
779 if (major1 >= (range.Min - DBL_EPSILON) && major1 <= (range.Max + DBL_EPSILON))
780 ticker.AddTick(major1, true, 0, true, formatter, data);
781 for (int j = 0; j < exp_step; ++j) {
782 major1 = sign*ImPow(10, (double)(e+j));
783 major2 = sign*ImPow(10, (double)(e+j+1));
784 interval = (major2 - major1) / 9;
785 for (int i = 1; i < (9 + (int)(j < (exp_step - 1))); ++i) {
786 double minor = major1 + i * interval;
787 if (minor >= (range.Min - DBL_EPSILON) && minor <= (range.Max + DBL_EPSILON))
788 ticker.AddTick(minor, false, 0, false, formatter, data);
789 }
790 }
791 }
792}
793
794void Locator_Log10(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data) {
795 int exp_min, exp_max, exp_step;
796 if (CalcLogarithmicExponents(range, pixels, vertical, exp_min, exp_max, exp_step))
797 AddTicksLogarithmic(range, exp_min, exp_max, exp_step, ticker, formatter, formatter_data);
798}
799
800float CalcSymLogPixel(double plt, const ImPlotRange& range, float pixels) {
801 double scaleToPixels = pixels / range.Size();
802 double scaleMin = TransformForward_SymLog(range.Min,nullptr);
803 double scaleMax = TransformForward_SymLog(range.Max,nullptr);
804 double s = TransformForward_SymLog(plt, nullptr);
805 double t = (s - scaleMin) / (scaleMax - scaleMin);
806 plt = range.Min + range.Size() * t;
807
808 return (float)(0 + scaleToPixels * (plt - range.Min));
809}
810
811void Locator_SymLog(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data) {
812 if (range.Min >= -1 && range.Max <= 1) {
813 Locator_Default(ticker, range, pixels, vertical, formatter, formatter_data);
814 }
815 else if (range.Min * range.Max < 0) { // cross zero
816 const float pix_min = 0;
817 const float pix_max = pixels;
818 const float pix_p1 = CalcSymLogPixel(1, range, pixels);
819 const float pix_n1 = CalcSymLogPixel(-1, range, pixels);
820 int exp_min_p, exp_max_p, exp_step_p;
821 int exp_min_n, exp_max_n, exp_step_n;
822 CalcLogarithmicExponents(ImPlotRange(1,range.Max), ImAbs(pix_max-pix_p1),vertical,exp_min_p,exp_max_p,exp_step_p);
823 CalcLogarithmicExponents(ImPlotRange(range.Min,-1),ImAbs(pix_n1-pix_min),vertical,exp_min_n,exp_max_n,exp_step_n);
824 int exp_step = ImMax(exp_step_n, exp_step_p);
825 ticker.AddTick(0,true,0,true,formatter,formatter_data);
826 AddTicksLogarithmic(ImPlotRange(1,range.Max), exp_min_p,exp_max_p,exp_step,ticker,formatter,formatter_data);
827 AddTicksLogarithmic(ImPlotRange(range.Min,-1),exp_min_n,exp_max_n,exp_step,ticker,formatter,formatter_data);
828 }
829 else {
830 Locator_Log10(ticker, range, pixels, vertical, formatter, formatter_data);
831 }
832}
833
834void AddTicksCustom(const double* values, const char* const labels[], int n, ImPlotTicker& ticker, ImPlotFormatter formatter, void* data) {
835 for (int i = 0; i < n; ++i) {
836 if (labels != nullptr)
837 ticker.AddTick(values[i], false, 0, true, labels[i]);
838 else
839 ticker.AddTick(values[i], false, 0, true, formatter, data);
840 }
841}
842
843//-----------------------------------------------------------------------------
844// Time Ticks and Utils
845//-----------------------------------------------------------------------------
846
847// this may not be thread safe?
848static const double TimeUnitSpans[ImPlotTimeUnit_COUNT] = {
849 0.000001,
850 0.001,
851 1,
852 60,
853 3600,
854 86400,
855 2629800,
856 31557600
857};
858
859inline ImPlotTimeUnit GetUnitForRange(double range) {
860 static double cutoffs[ImPlotTimeUnit_COUNT] = {0.001, 1, 60, 3600, 86400, 2629800, 31557600, IMPLOT_MAX_TIME};
861 for (int i = 0; i < ImPlotTimeUnit_COUNT; ++i) {
862 if (range <= cutoffs[i])
863 return (ImPlotTimeUnit)i;
864 }
865 return ImPlotTimeUnit_Yr;
866}
867
868inline int LowerBoundStep(int max_divs, const int* divs, const int* step, int size) {
869 if (max_divs < divs[0])
870 return 0;
871 for (int i = 1; i < size; ++i) {
872 if (max_divs < divs[i])
873 return step[i-1];
874 }
875 return step[size-1];
876}
877
878inline int GetTimeStep(int max_divs, ImPlotTimeUnit unit) {
879 if (unit == ImPlotTimeUnit_Ms || unit == ImPlotTimeUnit_Us) {
880 static const int step[] = {500,250,200,100,50,25,20,10,5,2,1};
881 static const int divs[] = {2,4,5,10,20,40,50,100,200,500,1000};
882 return LowerBoundStep(max_divs, divs, step, 11);
883 }
884 if (unit == ImPlotTimeUnit_S || unit == ImPlotTimeUnit_Min) {
885 static const int step[] = {30,15,10,5,1};
886 static const int divs[] = {2,4,6,12,60};
887 return LowerBoundStep(max_divs, divs, step, 5);
888 }
889 else if (unit == ImPlotTimeUnit_Hr) {
890 static const int step[] = {12,6,3,2,1};
891 static const int divs[] = {2,4,8,12,24};
892 return LowerBoundStep(max_divs, divs, step, 5);
893 }
894 else if (unit == ImPlotTimeUnit_Day) {
895 static const int step[] = {14,7,2,1};
896 static const int divs[] = {2,4,14,28};
897 return LowerBoundStep(max_divs, divs, step, 4);
898 }
899 else if (unit == ImPlotTimeUnit_Mo) {
900 static const int step[] = {6,3,2,1};
901 static const int divs[] = {2,4,6,12};
902 return LowerBoundStep(max_divs, divs, step, 4);
903 }
904 return 0;
905}
906
907ImPlotTime MkGmtTime(struct tm *ptm) {
908 ImPlotTime t;
909#ifdef _WIN32
910 t.S = _mkgmtime(ptm);
911#else
912 t.S = timegm(ptm);
913#endif
914 if (t.S < 0)
915 t.S = 0;
916 return t;
917}
918
919tm* GetGmtTime(const ImPlotTime& t, tm* ptm)
920{
921#ifdef _WIN32
922 if (gmtime_s(ptm, &t.S) == 0)
923 return ptm;
924 else
925 return nullptr;
926#else
927 return gmtime_r(&t.S, ptm);
928#endif
929}
930
931ImPlotTime MkLocTime(struct tm *ptm) {
932 ImPlotTime t;
933 t.S = mktime(ptm);
934 if (t.S < 0)
935 t.S = 0;
936 return t;
937}
938
939tm* GetLocTime(const ImPlotTime& t, tm* ptm) {
940#ifdef _WIN32
941 if (localtime_s(ptm, &t.S) == 0)
942 return ptm;
943 else
944 return nullptr;
945#else
946 return localtime_r(&t.S, ptm);
947#endif
948}
949
950inline ImPlotTime MkTime(struct tm *ptm) {
951 if (GetStyle().UseLocalTime)
952 return MkLocTime(ptm);
953 else
954 return MkGmtTime(ptm);
955}
956
957inline tm* GetTime(const ImPlotTime& t, tm* ptm) {
958 if (GetStyle().UseLocalTime)
959 return GetLocTime(t,ptm);
960 else
961 return GetGmtTime(t,ptm);
962}
963
964ImPlotTime MakeTime(int year, int month, int day, int hour, int min, int sec, int us) {
965 tm& Tm = GImPlot->Tm;
966
967 int yr = year - 1900;
968 if (yr < 0)
969 yr = 0;
970
971 sec = sec + us / 1000000;
972 us = us % 1000000;
973
974 Tm.tm_sec = sec;
975 Tm.tm_min = min;
976 Tm.tm_hour = hour;
977 Tm.tm_mday = day;
978 Tm.tm_mon = month;
979 Tm.tm_year = yr;
980
981 ImPlotTime t = MkTime(&Tm);
982
983 t.Us = us;
984 return t;
985}
986
987int GetYear(const ImPlotTime& t) {
988 tm& Tm = GImPlot->Tm;
989 GetTime(t, &Tm);
990 return Tm.tm_year + 1900;
991}
992
993ImPlotTime AddTime(const ImPlotTime& t, ImPlotTimeUnit unit, int count) {
994 tm& Tm = GImPlot->Tm;
995 ImPlotTime t_out = t;
996 switch(unit) {
997 case ImPlotTimeUnit_Us: t_out.Us += count; break;
998 case ImPlotTimeUnit_Ms: t_out.Us += count * 1000; break;
999 case ImPlotTimeUnit_S: t_out.S += count; break;
1000 case ImPlotTimeUnit_Min: t_out.S += count * 60; break;
1001 case ImPlotTimeUnit_Hr: t_out.S += count * 3600; break;
1002 case ImPlotTimeUnit_Day: t_out.S += count * 86400; break;
1003 case ImPlotTimeUnit_Mo: for (int i = 0; i < abs(count); ++i) {
1004 GetTime(t_out, &Tm);
1005 if (count > 0)
1006 t_out.S += 86400 * GetDaysInMonth(Tm.tm_year + 1900, Tm.tm_mon);
1007 else if (count < 0)
1008 t_out.S -= 86400 * GetDaysInMonth(Tm.tm_year + 1900 - (Tm.tm_mon == 0 ? 1 : 0), Tm.tm_mon == 0 ? 11 : Tm.tm_mon - 1); // NOT WORKING
1009 }
1010 break;
1011 case ImPlotTimeUnit_Yr: for (int i = 0; i < abs(count); ++i) {
1012 if (count > 0)
1013 t_out.S += 86400 * (365 + (int)IsLeapYear(GetYear(t_out)));
1014 else if (count < 0)
1015 t_out.S -= 86400 * (365 + (int)IsLeapYear(GetYear(t_out) - 1));
1016 // this is incorrect if leap year and we are past Feb 28
1017 }
1018 break;
1019 default: break;
1020 }
1021 t_out.RollOver();
1022 return t_out;
1023}
1024
1025ImPlotTime FloorTime(const ImPlotTime& t, ImPlotTimeUnit unit) {
1026 ImPlotContext& gp = *GImPlot;
1027 GetTime(t, &gp.Tm);
1028 switch (unit) {
1029 case ImPlotTimeUnit_S: return ImPlotTime(t.S, 0);
1030 case ImPlotTimeUnit_Ms: return ImPlotTime(t.S, (t.Us / 1000) * 1000);
1031 case ImPlotTimeUnit_Us: return t;
1032 case ImPlotTimeUnit_Yr: gp.Tm.tm_mon = 0; // fall-through
1033 case ImPlotTimeUnit_Mo: gp.Tm.tm_mday = 1; // fall-through
1034 case ImPlotTimeUnit_Day: gp.Tm.tm_hour = 0; // fall-through
1035 case ImPlotTimeUnit_Hr: gp.Tm.tm_min = 0; // fall-through
1036 case ImPlotTimeUnit_Min: gp.Tm.tm_sec = 0; break;
1037 default: return t;
1038 }
1039 return MkTime(&gp.Tm);
1040}
1041
1042ImPlotTime CeilTime(const ImPlotTime& t, ImPlotTimeUnit unit) {
1043 return AddTime(FloorTime(t, unit), unit, 1);
1044}
1045
1046ImPlotTime RoundTime(const ImPlotTime& t, ImPlotTimeUnit unit) {
1047 ImPlotTime t1 = FloorTime(t, unit);
1048 ImPlotTime t2 = AddTime(t1,unit,1);
1049 if (t1.S == t2.S)
1050 return t.Us - t1.Us < t2.Us - t.Us ? t1 : t2;
1051 return t.S - t1.S < t2.S - t.S ? t1 : t2;
1052}
1053
1054ImPlotTime CombineDateTime(const ImPlotTime& date_part, const ImPlotTime& tod_part) {
1055 ImPlotContext& gp = *GImPlot;
1056 tm& Tm = gp.Tm;
1057 GetTime(date_part, &gp.Tm);
1058 int y = Tm.tm_year;
1059 int m = Tm.tm_mon;
1060 int d = Tm.tm_mday;
1061 GetTime(tod_part, &gp.Tm);
1062 Tm.tm_year = y;
1063 Tm.tm_mon = m;
1064 Tm.tm_mday = d;
1065 ImPlotTime t = MkTime(&Tm);
1066 t.Us = tod_part.Us;
1067 return t;
1068}
1069
1070// TODO: allow users to define these
1071static const char* MONTH_NAMES[] = {"January","February","March","April","May","June","July","August","September","October","November","December"};
1072static const char* WD_ABRVS[] = {"Su","Mo","Tu","We","Th","Fr","Sa"};
1073static const char* MONTH_ABRVS[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
1074
1075int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt, bool use_24_hr_clk) {
1076 tm& Tm = GImPlot->Tm;
1077 GetTime(t, &Tm);
1078 const int us = t.Us % 1000;
1079 const int ms = t.Us / 1000;
1080 const int sec = Tm.tm_sec;
1081 const int min = Tm.tm_min;
1082 if (use_24_hr_clk) {
1083 const int hr = Tm.tm_hour;
1084 switch(fmt) {
1085 case ImPlotTimeFmt_Us: return ImFormatString(buffer, size, ".%03d %03d", ms, us);
1086 case ImPlotTimeFmt_SUs: return ImFormatString(buffer, size, ":%02d.%03d %03d", sec, ms, us);
1087 case ImPlotTimeFmt_SMs: return ImFormatString(buffer, size, ":%02d.%03d", sec, ms);
1088 case ImPlotTimeFmt_S: return ImFormatString(buffer, size, ":%02d", sec);
1089 case ImPlotTimeFmt_MinSMs: return ImFormatString(buffer, size, ":%02d:%02d.%03d", min, sec, ms);
1090 case ImPlotTimeFmt_HrMinSMs: return ImFormatString(buffer, size, "%02d:%02d:%02d.%03d", hr, min, sec, ms);
1091 case ImPlotTimeFmt_HrMinS: return ImFormatString(buffer, size, "%02d:%02d:%02d", hr, min, sec);
1092 case ImPlotTimeFmt_HrMin: return ImFormatString(buffer, size, "%02d:%02d", hr, min);
1093 case ImPlotTimeFmt_Hr: return ImFormatString(buffer, size, "%02d:00", hr);
1094 default: return 0;
1095 }
1096 }
1097 else {
1098 const char* ap = Tm.tm_hour < 12 ? "am" : "pm";
1099 const int hr = (Tm.tm_hour == 0 || Tm.tm_hour == 12) ? 12 : Tm.tm_hour % 12;
1100 switch(fmt) {
1101 case ImPlotTimeFmt_Us: return ImFormatString(buffer, size, ".%03d %03d", ms, us);
1102 case ImPlotTimeFmt_SUs: return ImFormatString(buffer, size, ":%02d.%03d %03d", sec, ms, us);
1103 case ImPlotTimeFmt_SMs: return ImFormatString(buffer, size, ":%02d.%03d", sec, ms);
1104 case ImPlotTimeFmt_S: return ImFormatString(buffer, size, ":%02d", sec);
1105 case ImPlotTimeFmt_MinSMs: return ImFormatString(buffer, size, ":%02d:%02d.%03d", min, sec, ms);
1106 case ImPlotTimeFmt_HrMinSMs: return ImFormatString(buffer, size, "%d:%02d:%02d.%03d%s", hr, min, sec, ms, ap);
1107 case ImPlotTimeFmt_HrMinS: return ImFormatString(buffer, size, "%d:%02d:%02d%s", hr, min, sec, ap);
1108 case ImPlotTimeFmt_HrMin: return ImFormatString(buffer, size, "%d:%02d%s", hr, min, ap);
1109 case ImPlotTimeFmt_Hr: return ImFormatString(buffer, size, "%d%s", hr, ap);
1110 default: return 0;
1111 }
1112 }
1113}
1114
1115int FormatDate(const ImPlotTime& t, char* buffer, int size, ImPlotDateFmt fmt, bool use_iso_8601) {
1116 tm& Tm = GImPlot->Tm;
1117 GetTime(t, &Tm);
1118 const int day = Tm.tm_mday;
1119 const int mon = Tm.tm_mon + 1;
1120 const int year = Tm.tm_year + 1900;
1121 const int yr = year % 100;
1122 if (use_iso_8601) {
1123 switch (fmt) {
1124 case ImPlotDateFmt_DayMo: return ImFormatString(buffer, size, "--%02d-%02d", mon, day);
1125 case ImPlotDateFmt_DayMoYr: return ImFormatString(buffer, size, "%d-%02d-%02d", year, mon, day);
1126 case ImPlotDateFmt_MoYr: return ImFormatString(buffer, size, "%d-%02d", year, mon);
1127 case ImPlotDateFmt_Mo: return ImFormatString(buffer, size, "--%02d", mon);
1128 case ImPlotDateFmt_Yr: return ImFormatString(buffer, size, "%d", year);
1129 default: return 0;
1130 }
1131 }
1132 else {
1133 switch (fmt) {
1134 case ImPlotDateFmt_DayMo: return ImFormatString(buffer, size, "%d/%d", mon, day);
1135 case ImPlotDateFmt_DayMoYr: return ImFormatString(buffer, size, "%d/%d/%02d", mon, day, yr);
1136 case ImPlotDateFmt_MoYr: return ImFormatString(buffer, size, "%s %d", MONTH_ABRVS[Tm.tm_mon], year);
1137 case ImPlotDateFmt_Mo: return ImFormatString(buffer, size, "%s", MONTH_ABRVS[Tm.tm_mon]);
1138 case ImPlotDateFmt_Yr: return ImFormatString(buffer, size, "%d", year);
1139 default: return 0;
1140 }
1141 }
1142 }
1143
1144int FormatDateTime(const ImPlotTime& t, char* buffer, int size, ImPlotDateTimeSpec fmt) {
1145 int written = 0;
1146 if (fmt.Date != ImPlotDateFmt_None)
1147 written += FormatDate(t, buffer, size, fmt.Date, fmt.UseISO8601);
1148 if (fmt.Time != ImPlotTimeFmt_None) {
1149 if (fmt.Date != ImPlotDateFmt_None)
1150 buffer[written++] = ' ';
1151 written += FormatTime(t, &buffer[written], size - written, fmt.Time, fmt.Use24HourClock);
1152 }
1153 return written;
1154}
1155
1156inline float GetDateTimeWidth(ImPlotDateTimeSpec fmt) {
1157 static const ImPlotTime t_max_width = MakeTime(2888, 12, 22, 12, 58, 58, 888888); // best guess at time that maximizes pixel width
1158 char buffer[32];
1159 FormatDateTime(t_max_width, buffer, 32, fmt);
1160 return ImGui::CalcTextSize(buffer).x;
1161}
1162
1163inline bool TimeLabelSame(const char* l1, const char* l2) {
1164 size_t len1 = strlen(l1);
1165 size_t len2 = strlen(l2);
1166 size_t n = len1 < len2 ? len1 : len2;
1167 return strcmp(l1 + len1 - n, l2 + len2 - n) == 0;
1168}
1169
1170static const ImPlotDateTimeSpec TimeFormatLevel0[ImPlotTimeUnit_COUNT] = {
1171 ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_Us),
1172 ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_SMs),
1173 ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_S),
1174 ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_HrMin),
1175 ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_Hr),
1176 ImPlotDateTimeSpec(ImPlotDateFmt_DayMo, ImPlotTimeFmt_None),
1177 ImPlotDateTimeSpec(ImPlotDateFmt_Mo, ImPlotTimeFmt_None),
1178 ImPlotDateTimeSpec(ImPlotDateFmt_Yr, ImPlotTimeFmt_None)
1179};
1180
1181static const ImPlotDateTimeSpec TimeFormatLevel1[ImPlotTimeUnit_COUNT] = {
1182 ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_HrMin),
1183 ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_HrMinS),
1184 ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_HrMin),
1185 ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_HrMin),
1186 ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_None),
1187 ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_None),
1188 ImPlotDateTimeSpec(ImPlotDateFmt_Yr, ImPlotTimeFmt_None),
1189 ImPlotDateTimeSpec(ImPlotDateFmt_Yr, ImPlotTimeFmt_None)
1190};
1191
1192static const ImPlotDateTimeSpec TimeFormatLevel1First[ImPlotTimeUnit_COUNT] = {
1193 ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_HrMinS),
1194 ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_HrMinS),
1195 ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_HrMin),
1196 ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_HrMin),
1197 ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_None),
1198 ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_None),
1199 ImPlotDateTimeSpec(ImPlotDateFmt_Yr, ImPlotTimeFmt_None),
1200 ImPlotDateTimeSpec(ImPlotDateFmt_Yr, ImPlotTimeFmt_None)
1201};
1202
1203static const ImPlotDateTimeSpec TimeFormatMouseCursor[ImPlotTimeUnit_COUNT] = {
1204 ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_Us),
1205 ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_SUs),
1206 ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_SMs),
1207 ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_HrMinS),
1208 ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_HrMin),
1209 ImPlotDateTimeSpec(ImPlotDateFmt_DayMo, ImPlotTimeFmt_Hr),
1210 ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_None),
1211 ImPlotDateTimeSpec(ImPlotDateFmt_MoYr, ImPlotTimeFmt_None)
1212};
1213
1214inline ImPlotDateTimeSpec GetDateTimeFmt(const ImPlotDateTimeSpec* ctx, ImPlotTimeUnit idx) {
1215 ImPlotStyle& style = GetStyle();
1216 ImPlotDateTimeSpec fmt = ctx[idx];
1217 fmt.UseISO8601 = style.UseISO8601;
1218 fmt.Use24HourClock = style.Use24HourClock;
1219 return fmt;
1220}
1221
1222void Locator_Time(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data) {
1223 IM_ASSERT_USER_ERROR(vertical == false, "Cannot locate Time ticks on vertical axis!");
1224 (void)vertical;
1225 // get units for level 0 and level 1 labels
1226 const ImPlotTimeUnit unit0 = GetUnitForRange(range.Size() / (pixels / 100)); // level = 0 (top)
1227 const ImPlotTimeUnit unit1 = ImClamp(unit0 + 1, 0, ImPlotTimeUnit_COUNT-1); // level = 1 (bottom)
1228 // get time format specs
1229 const ImPlotDateTimeSpec fmt0 = GetDateTimeFmt(TimeFormatLevel0, unit0);
1230 const ImPlotDateTimeSpec fmt1 = GetDateTimeFmt(TimeFormatLevel1, unit1);
1231 const ImPlotDateTimeSpec fmtf = GetDateTimeFmt(TimeFormatLevel1First, unit1);
1232 // min max times
1233 const ImPlotTime t_min = ImPlotTime::FromDouble(range.Min);
1234 const ImPlotTime t_max = ImPlotTime::FromDouble(range.Max);
1235 // maximum allowable density of labels
1236 const float max_density = 0.5f;
1237 // book keeping
1238 int last_major_offset = -1;
1239 // formatter data
1240 Formatter_Time_Data ftd;
1241 ftd.UserFormatter = formatter;
1242 ftd.UserFormatterData = formatter_data;
1243 if (unit0 != ImPlotTimeUnit_Yr) {
1244 // pixels per major (level 1) division
1245 const float pix_per_major_div = pixels / (float)(range.Size() / TimeUnitSpans[unit1]);
1246 // nominal pixels taken up by labels
1247 const float fmt0_width = GetDateTimeWidth(fmt0);
1248 const float fmt1_width = GetDateTimeWidth(fmt1);
1249 const float fmtf_width = GetDateTimeWidth(fmtf);
1250 // the maximum number of minor (level 0) labels that can fit between major (level 1) divisions
1251 const int minor_per_major = (int)(max_density * pix_per_major_div / fmt0_width);
1252 // the minor step size (level 0)
1253 const int step = GetTimeStep(minor_per_major, unit0);
1254 // generate ticks
1255 ImPlotTime t1 = FloorTime(ImPlotTime::FromDouble(range.Min), unit1);
1256 while (t1 < t_max) {
1257 // get next major
1258 const ImPlotTime t2 = AddTime(t1, unit1, 1);
1259 // add major tick
1260 if (t1 >= t_min && t1 <= t_max) {
1261 // minor level 0 tick
1262 ftd.Time = t1; ftd.Spec = fmt0;
1263 ticker.AddTick(t1.ToDouble(), true, 0, true, Formatter_Time, &ftd);
1264 // major level 1 tick
1265 ftd.Time = t1; ftd.Spec = last_major_offset < 0 ? fmtf : fmt1;
1266 ImPlotTick& tick_maj = ticker.AddTick(t1.ToDouble(), true, 1, true, Formatter_Time, &ftd);
1267 const char* this_major = ticker.GetText(tick_maj);
1268 if (last_major_offset >= 0 && TimeLabelSame(ticker.TextBuffer.Buf.Data + last_major_offset, this_major))
1269 tick_maj.ShowLabel = false;
1270 last_major_offset = tick_maj.TextOffset;
1271 }
1272 // add minor ticks up until next major
1273 if (minor_per_major > 1 && (t_min <= t2 && t1 <= t_max)) {
1274 ImPlotTime t12 = AddTime(t1, unit0, step);
1275 while (t12 < t2) {
1276 float px_to_t2 = (float)((t2 - t12).ToDouble()/range.Size()) * pixels;
1277 if (t12 >= t_min && t12 <= t_max) {
1278 ftd.Time = t12; ftd.Spec = fmt0;
1279 ticker.AddTick(t12.ToDouble(), false, 0, px_to_t2 >= fmt0_width, Formatter_Time, &ftd);
1280 if (last_major_offset < 0 && px_to_t2 >= fmt0_width && px_to_t2 >= (fmt1_width + fmtf_width) / 2) {
1281 ftd.Time = t12; ftd.Spec = fmtf;
1282 ImPlotTick& tick_maj = ticker.AddTick(t12.ToDouble(), true, 1, true, Formatter_Time, &ftd);
1283 last_major_offset = tick_maj.TextOffset;
1284 }
1285 }
1286 t12 = AddTime(t12, unit0, step);
1287 }
1288 }
1289 t1 = t2;
1290 }
1291 }
1292 else {
1293 const ImPlotDateTimeSpec fmty = GetDateTimeFmt(TimeFormatLevel0, ImPlotTimeUnit_Yr);
1294 const float label_width = GetDateTimeWidth(fmty);
1295 const int max_labels = (int)(max_density * pixels / label_width);
1296 const int year_min = GetYear(t_min);
1297 const int year_max = GetYear(CeilTime(t_max, ImPlotTimeUnit_Yr));
1298 const double nice_range = NiceNum((year_max - year_min)*0.99,false);
1299 const double interval = NiceNum(nice_range / (max_labels - 1), true);
1300 const int graphmin = (int)(floor(year_min / interval) * interval);
1301 const int graphmax = (int)(ceil(year_max / interval) * interval);
1302 const int step = (int)interval <= 0 ? 1 : (int)interval;
1303
1304 for (int y = graphmin; y < graphmax; y += step) {
1305 ImPlotTime t = MakeTime(y);
1306 if (t >= t_min && t <= t_max) {
1307 ftd.Time = t; ftd.Spec = fmty;
1308 ticker.AddTick(t.ToDouble(), true, 0, true, Formatter_Time, &ftd);
1309 }
1310 }
1311 }
1312}
1313
1314//-----------------------------------------------------------------------------
1315// Context Menu
1316//-----------------------------------------------------------------------------
1317
1318template <typename F>
1319bool DragFloat(const char*, F*, float, F, F) {
1320 return false;
1321}
1322
1323template <>
1324bool DragFloat<double>(const char* label, double* v, float v_speed, double v_min, double v_max) {
1325 return ImGui::DragScalar(label, ImGuiDataType_Double, v, v_speed, &v_min, &v_max, "%.3g", 1);
1326}
1327
1328template <>
1329bool DragFloat<float>(const char* label, float* v, float v_speed, float v_min, float v_max) {
1330 return ImGui::DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, "%.3g", 1);
1331}
1332
1333inline void BeginDisabledControls(bool cond) {
1334 if (cond) {
1335 ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
1336 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.25f);
1337 }
1338}
1339
1340inline void EndDisabledControls(bool cond) {
1341 if (cond) {
1342 ImGui::PopItemFlag();
1343 ImGui::PopStyleVar();
1344 }
1345}
1346
1347void ShowAxisContextMenu(ImPlotAxis& axis, ImPlotAxis* equal_axis, bool /*time_allowed*/) {
1348
1349 ImGui::PushItemWidth(75);
1350 bool always_locked = axis.IsRangeLocked() || axis.IsAutoFitting();
1351 bool label = axis.HasLabel();
1352 bool grid = axis.HasGridLines();
1353 bool ticks = axis.HasTickMarks();
1354 bool labels = axis.HasTickLabels();
1355 double drag_speed = (axis.Range.Size() <= DBL_EPSILON) ? DBL_EPSILON * 1.0e+13 : 0.01 * axis.Range.Size(); // recover from almost equal axis limits.
1356
1357 if (axis.Scale == ImPlotScale_Time) {
1358 ImPlotTime tmin = ImPlotTime::FromDouble(axis.Range.Min);
1359 ImPlotTime tmax = ImPlotTime::FromDouble(axis.Range.Max);
1360
1361 BeginDisabledControls(always_locked);
1362 ImGui::CheckboxFlags("##LockMin", (unsigned int*)&axis.Flags, ImPlotAxisFlags_LockMin);
1363 EndDisabledControls(always_locked);
1364 ImGui::SameLine();
1365 BeginDisabledControls(axis.IsLockedMin() || always_locked);
1366 if (ImGui::BeginMenu("Min Time")) {
1367 if (ShowTimePicker("mintime", &tmin)) {
1368 if (tmin >= tmax)
1369 tmax = AddTime(tmin, ImPlotTimeUnit_S, 1);
1370 axis.SetRange(tmin.ToDouble(),tmax.ToDouble());
1371 }
1372 ImGui::Separator();
1373 if (ShowDatePicker("mindate",&axis.PickerLevel,&axis.PickerTimeMin,&tmin,&tmax)) {
1374 tmin = CombineDateTime(axis.PickerTimeMin, tmin);
1375 if (tmin >= tmax)
1376 tmax = AddTime(tmin, ImPlotTimeUnit_S, 1);
1377 axis.SetRange(tmin.ToDouble(), tmax.ToDouble());
1378 }
1379 ImGui::EndMenu();
1380 }
1381 EndDisabledControls(axis.IsLockedMin() || always_locked);
1382
1383 BeginDisabledControls(always_locked);
1384 ImGui::CheckboxFlags("##LockMax", (unsigned int*)&axis.Flags, ImPlotAxisFlags_LockMax);
1385 EndDisabledControls(always_locked);
1386 ImGui::SameLine();
1387 BeginDisabledControls(axis.IsLockedMax() || always_locked);
1388 if (ImGui::BeginMenu("Max Time")) {
1389 if (ShowTimePicker("maxtime", &tmax)) {
1390 if (tmax <= tmin)
1391 tmin = AddTime(tmax, ImPlotTimeUnit_S, -1);
1392 axis.SetRange(tmin.ToDouble(),tmax.ToDouble());
1393 }
1394 ImGui::Separator();
1395 if (ShowDatePicker("maxdate",&axis.PickerLevel,&axis.PickerTimeMax,&tmin,&tmax)) {
1396 tmax = CombineDateTime(axis.PickerTimeMax, tmax);
1397 if (tmax <= tmin)
1398 tmin = AddTime(tmax, ImPlotTimeUnit_S, -1);
1399 axis.SetRange(tmin.ToDouble(), tmax.ToDouble());
1400 }
1401 ImGui::EndMenu();
1402 }
1403 EndDisabledControls(axis.IsLockedMax() || always_locked);
1404 }
1405 else {
1406 BeginDisabledControls(always_locked);
1407 ImGui::CheckboxFlags("##LockMin", (unsigned int*)&axis.Flags, ImPlotAxisFlags_LockMin);
1408 EndDisabledControls(always_locked);
1409 ImGui::SameLine();
1410 BeginDisabledControls(axis.IsLockedMin() || always_locked);
1411 double temp_min = axis.Range.Min;
1412 if (DragFloat("Min", &temp_min, (float)drag_speed, -HUGE_VAL, axis.Range.Max - DBL_EPSILON)) {
1413 axis.SetMin(temp_min,true);
1414 if (equal_axis != nullptr)
1415 equal_axis->SetAspect(axis.GetAspect());
1416 }
1417 EndDisabledControls(axis.IsLockedMin() || always_locked);
1418
1419 BeginDisabledControls(always_locked);
1420 ImGui::CheckboxFlags("##LockMax", (unsigned int*)&axis.Flags, ImPlotAxisFlags_LockMax);
1421 EndDisabledControls(always_locked);
1422 ImGui::SameLine();
1423 BeginDisabledControls(axis.IsLockedMax() || always_locked);
1424 double temp_max = axis.Range.Max;
1425 if (DragFloat("Max", &temp_max, (float)drag_speed, axis.Range.Min + DBL_EPSILON, HUGE_VAL)) {
1426 axis.SetMax(temp_max,true);
1427 if (equal_axis != nullptr)
1428 equal_axis->SetAspect(axis.GetAspect());
1429 }
1430 EndDisabledControls(axis.IsLockedMax() || always_locked);
1431 }
1432
1433 ImGui::Separator();
1434
1435 ImGui::CheckboxFlags("Auto-Fit",(unsigned int*)&axis.Flags, ImPlotAxisFlags_AutoFit);
1436 // TODO
1437 // BeginDisabledControls(axis.IsTime() && time_allowed);
1438 // ImGui::CheckboxFlags("Log Scale",(unsigned int*)&axis.Flags, ImPlotAxisFlags_LogScale);
1439 // EndDisabledControls(axis.IsTime() && time_allowed);
1440 // if (time_allowed) {
1441 // BeginDisabledControls(axis.IsLog() || axis.IsSymLog());
1442 // ImGui::CheckboxFlags("Time",(unsigned int*)&axis.Flags, ImPlotAxisFlags_Time);
1443 // EndDisabledControls(axis.IsLog() || axis.IsSymLog());
1444 // }
1445 ImGui::Separator();
1446 ImGui::CheckboxFlags("Invert",(unsigned int*)&axis.Flags, ImPlotAxisFlags_Invert);
1447 ImGui::CheckboxFlags("Opposite",(unsigned int*)&axis.Flags, ImPlotAxisFlags_Opposite);
1448 ImGui::Separator();
1449 BeginDisabledControls(axis.LabelOffset == -1);
1450 if (ImGui::Checkbox("Label", &label))
1451 ImFlipFlag(axis.Flags, ImPlotAxisFlags_NoLabel);
1452 EndDisabledControls(axis.LabelOffset == -1);
1453 if (ImGui::Checkbox("Grid Lines", &grid))
1454 ImFlipFlag(axis.Flags, ImPlotAxisFlags_NoGridLines);
1455 if (ImGui::Checkbox("Tick Marks", &ticks))
1456 ImFlipFlag(axis.Flags, ImPlotAxisFlags_NoTickMarks);
1457 if (ImGui::Checkbox("Tick Labels", &labels))
1458 ImFlipFlag(axis.Flags, ImPlotAxisFlags_NoTickLabels);
1459
1460}
1461
1462bool ShowLegendContextMenu(ImPlotLegend& legend, bool visible) {
1463 const float s = ImGui::GetFrameHeight();
1464 bool ret = false;
1465 if (ImGui::Checkbox("Show",&visible))
1466 ret = true;
1467 if (legend.CanGoInside)
1468 ImGui::CheckboxFlags("Outside",(unsigned int*)&legend.Flags, ImPlotLegendFlags_Outside);
1469 if (ImGui::RadioButton("H", ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal)))
1470 legend.Flags |= ImPlotLegendFlags_Horizontal;
1471 ImGui::SameLine();
1472 if (ImGui::RadioButton("V", !ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal)))
1473 legend.Flags &= ~ImPlotLegendFlags_Horizontal;
1474 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2,2));
1475 if (ImGui::Button("NW",ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_NorthWest; } ImGui::SameLine();
1476 if (ImGui::Button("N", ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_North; } ImGui::SameLine();
1477 if (ImGui::Button("NE",ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_NorthEast; }
1478 if (ImGui::Button("W", ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_West; } ImGui::SameLine();
1479 if (ImGui::InvisibleButton("C", ImVec2(1.5f*s,s))) { } ImGui::SameLine();
1480 if (ImGui::Button("E", ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_East; }
1481 if (ImGui::Button("SW",ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_SouthWest; } ImGui::SameLine();
1482 if (ImGui::Button("S", ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_South; } ImGui::SameLine();
1483 if (ImGui::Button("SE",ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_SouthEast; }
1484 ImGui::PopStyleVar();
1485 return ret;
1486}
1487
1488void ShowSubplotsContextMenu(ImPlotSubplot& subplot) {
1489 if ((ImGui::BeginMenu("Linking"))) {
1490 if (ImGui::MenuItem("Link Rows",nullptr,ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkRows)))
1491 ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_LinkRows);
1492 if (ImGui::MenuItem("Link Cols",nullptr,ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkCols)))
1493 ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_LinkCols);
1494 if (ImGui::MenuItem("Link All X",nullptr,ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllX)))
1495 ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllX);
1496 if (ImGui::MenuItem("Link All Y",nullptr,ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllY)))
1497 ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllY);
1498 ImGui::EndMenu();
1499 }
1500 if ((ImGui::BeginMenu("Settings"))) {
1501 BeginDisabledControls(!subplot.HasTitle);
1502 if (ImGui::MenuItem("Title",nullptr,subplot.HasTitle && !ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoTitle)))
1503 ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_NoTitle);
1504 EndDisabledControls(!subplot.HasTitle);
1505 if (ImGui::MenuItem("Resizable",nullptr,!ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoResize)))
1506 ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_NoResize);
1507 if (ImGui::MenuItem("Align",nullptr,!ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoAlign)))
1508 ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_NoAlign);
1509 if (ImGui::MenuItem("Share Items",nullptr,ImHasFlag(subplot.Flags, ImPlotSubplotFlags_ShareItems)))
1510 ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_ShareItems);
1511 ImGui::EndMenu();
1512 }
1513}
1514
1515void ShowPlotContextMenu(ImPlotPlot& plot) {
1516 ImPlotContext& gp = *GImPlot;
1517 const bool owns_legend = gp.CurrentItems == &plot.Items;
1518 const bool equal = ImHasFlag(plot.Flags, ImPlotFlags_Equal);
1519
1520 char buf[16] = {};
1521
1522 for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) {
1523 ImPlotAxis& x_axis = plot.XAxis(i);
1524 if (!x_axis.Enabled || !x_axis.HasMenus())
1525 continue;
1526 ImGui::PushID(i);
1527 ImFormatString(buf, sizeof(buf) - 1, i == 0 ? "X-Axis" : "X-Axis %d", i + 1);
1528 if (ImGui::BeginMenu(x_axis.HasLabel() ? plot.GetAxisLabel(x_axis) : buf)) {
1529 ShowAxisContextMenu(x_axis, equal ? x_axis.OrthoAxis : nullptr, false);
1530 ImGui::EndMenu();
1531 }
1532 ImGui::PopID();
1533 }
1534
1535 for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) {
1536 ImPlotAxis& y_axis = plot.YAxis(i);
1537 if (!y_axis.Enabled || !y_axis.HasMenus())
1538 continue;
1539 ImGui::PushID(i);
1540 ImFormatString(buf, sizeof(buf) - 1, i == 0 ? "Y-Axis" : "Y-Axis %d", i + 1);
1541 if (ImGui::BeginMenu(y_axis.HasLabel() ? plot.GetAxisLabel(y_axis) : buf)) {
1542 ShowAxisContextMenu(y_axis, equal ? y_axis.OrthoAxis : nullptr, false);
1543 ImGui::EndMenu();
1544 }
1545 ImGui::PopID();
1546 }
1547
1548 ImGui::Separator();
1549 if (!ImHasFlag(gp.CurrentItems->Legend.Flags, ImPlotLegendFlags_NoMenus)) {
1550 if ((ImGui::BeginMenu("Legend"))) {
1551 if (owns_legend) {
1552 if (ShowLegendContextMenu(plot.Items.Legend, !ImHasFlag(plot.Flags, ImPlotFlags_NoLegend)))
1553 ImFlipFlag(plot.Flags, ImPlotFlags_NoLegend);
1554 }
1555 else if (gp.CurrentSubplot != nullptr) {
1556 if (ShowLegendContextMenu(gp.CurrentSubplot->Items.Legend, !ImHasFlag(gp.CurrentSubplot->Flags, ImPlotSubplotFlags_NoLegend)))
1557 ImFlipFlag(gp.CurrentSubplot->Flags, ImPlotSubplotFlags_NoLegend);
1558 }
1559 ImGui::EndMenu();
1560 }
1561 }
1562 if ((ImGui::BeginMenu("Settings"))) {
1563 if (ImGui::MenuItem("Equal", nullptr, ImHasFlag(plot.Flags, ImPlotFlags_Equal)))
1564 ImFlipFlag(plot.Flags, ImPlotFlags_Equal);
1565 if (ImGui::MenuItem("Box Select",nullptr,!ImHasFlag(plot.Flags, ImPlotFlags_NoBoxSelect)))
1566 ImFlipFlag(plot.Flags, ImPlotFlags_NoBoxSelect);
1567 BeginDisabledControls(plot.TitleOffset == -1);
1568 if (ImGui::MenuItem("Title",nullptr,plot.HasTitle()))
1569 ImFlipFlag(plot.Flags, ImPlotFlags_NoTitle);
1570 EndDisabledControls(plot.TitleOffset == -1);
1571 if (ImGui::MenuItem("Mouse Position",nullptr,!ImHasFlag(plot.Flags, ImPlotFlags_NoMouseText)))
1572 ImFlipFlag(plot.Flags, ImPlotFlags_NoMouseText);
1573 if (ImGui::MenuItem("Crosshairs",nullptr,ImHasFlag(plot.Flags, ImPlotFlags_Crosshairs)))
1574 ImFlipFlag(plot.Flags, ImPlotFlags_Crosshairs);
1575 ImGui::EndMenu();
1576 }
1577 if (gp.CurrentSubplot != nullptr && !ImHasFlag(gp.CurrentSubplot->Flags, ImPlotSubplotFlags_NoMenus)) {
1578 ImGui::Separator();
1579 if ((ImGui::BeginMenu("Subplots"))) {
1580 ShowSubplotsContextMenu(*gp.CurrentSubplot);
1581 ImGui::EndMenu();
1582 }
1583 }
1584}
1585
1586//-----------------------------------------------------------------------------
1587// Axis Utils
1588//-----------------------------------------------------------------------------
1589
1590static inline int AxisPrecision(const ImPlotAxis& axis) {
1591 const double range = axis.Ticker.TickCount() > 1 ? (axis.Ticker.Ticks[1].PlotPos - axis.Ticker.Ticks[0].PlotPos) : axis.Range.Size();
1592 return Precision(range);
1593}
1594
1595static inline double RoundAxisValue(const ImPlotAxis& axis, double value) {
1596 return RoundTo(value, AxisPrecision(axis));
1597}
1598
1599void LabelAxisValue(const ImPlotAxis& axis, double value, char* buff, int size, bool round) {
1600 ImPlotContext& gp = *GImPlot;
1601 // TODO: We shouldn't explicitly check that the axis is Time here. Ideally,
1602 // Formatter_Time would handle the formatting for us, but the code below
1603 // needs additional arguments which are not currently available in ImPlotFormatter
1604 if (axis.Locator == Locator_Time) {
1605 ImPlotTimeUnit unit = axis.Vertical
1606 ? GetUnitForRange(axis.Range.Size() / (gp.CurrentPlot->PlotRect.GetHeight() / 100)) // TODO: magic value!
1607 : GetUnitForRange(axis.Range.Size() / (gp.CurrentPlot->PlotRect.GetWidth() / 100)); // TODO: magic value!
1608 FormatDateTime(ImPlotTime::FromDouble(value), buff, size, GetDateTimeFmt(TimeFormatMouseCursor, unit));
1609 }
1610 else {
1611 if (round)
1612 value = RoundAxisValue(axis, value);
1613 axis.Formatter(value, buff, size, axis.FormatterData);
1614 }
1615}
1616
1617void UpdateAxisColors(ImPlotAxis& axis) {
1618 const ImVec4 col_grid = GetStyleColorVec4(ImPlotCol_AxisGrid);
1619 axis.ColorMaj = ImGui::GetColorU32(col_grid);
1620 axis.ColorMin = ImGui::GetColorU32(col_grid*ImVec4(1,1,1,GImPlot->Style.MinorAlpha));
1621 axis.ColorTick = GetStyleColorU32(ImPlotCol_AxisTick);
1622 axis.ColorTxt = GetStyleColorU32(ImPlotCol_AxisText);
1623 axis.ColorBg = GetStyleColorU32(ImPlotCol_AxisBg);
1624 axis.ColorHov = GetStyleColorU32(ImPlotCol_AxisBgHovered);
1625 axis.ColorAct = GetStyleColorU32(ImPlotCol_AxisBgActive);
1626 // axis.ColorHiLi = IM_COL32_BLACK_TRANS;
1627}
1628
1629void PadAndDatumAxesX(ImPlotPlot& plot, float& pad_T, float& pad_B, ImPlotAlignmentData* align) {
1630
1631 ImPlotContext& gp = *GImPlot;
1632
1633 const float T = ImGui::GetTextLineHeight();
1634 const float P = gp.Style.LabelPadding.y;
1635 const float K = gp.Style.MinorTickLen.x;
1636
1637 int count_T = 0;
1638 int count_B = 0;
1639 float last_T = plot.AxesRect.Min.y;
1640 float last_B = plot.AxesRect.Max.y;
1641
1642 for (int i = IMPLOT_NUM_X_AXES; i-- > 0;) { // FYI: can iterate forward
1643 ImPlotAxis& axis = plot.XAxis(i);
1644 if (!axis.Enabled)
1645 continue;
1646 const bool label = axis.HasLabel();
1647 const bool ticks = axis.HasTickLabels();
1648 const bool opp = axis.IsOpposite();
1649 const bool time = axis.Scale == ImPlotScale_Time;
1650 if (opp) {
1651 if (count_T++ > 0)
1652 pad_T += K + P;
1653 if (label)
1654 pad_T += T + P;
1655 if (ticks)
1656 pad_T += ImMax(T, axis.Ticker.MaxSize.y) + P + (time ? T + P : 0);
1657 axis.Datum1 = plot.CanvasRect.Min.y + pad_T;
1658 axis.Datum2 = last_T;
1659 last_T = axis.Datum1;
1660 }
1661 else {
1662 if (count_B++ > 0)
1663 pad_B += K + P;
1664 if (label)
1665 pad_B += T + P;
1666 if (ticks)
1667 pad_B += ImMax(T, axis.Ticker.MaxSize.y) + P + (time ? T + P : 0);
1668 axis.Datum1 = plot.CanvasRect.Max.y - pad_B;
1669 axis.Datum2 = last_B;
1670 last_B = axis.Datum1;
1671 }
1672 }
1673
1674 if (align) {
1675 count_T = count_B = 0;
1676 float delta_T, delta_B;
1677 align->Update(pad_T,pad_B,delta_T,delta_B);
1678 for (int i = IMPLOT_NUM_X_AXES; i-- > 0;) {
1679 ImPlotAxis& axis = plot.XAxis(i);
1680 if (!axis.Enabled)
1681 continue;
1682 if (axis.IsOpposite()) {
1683 axis.Datum1 += delta_T;
1684 axis.Datum2 += count_T++ > 1 ? delta_T : 0;
1685 }
1686 else {
1687 axis.Datum1 -= delta_B;
1688 axis.Datum2 -= count_B++ > 1 ? delta_B : 0;
1689 }
1690 }
1691 }
1692}
1693
1694void PadAndDatumAxesY(ImPlotPlot& plot, float& pad_L, float& pad_R, ImPlotAlignmentData* align) {
1695
1696 // [ pad_L ] [ pad_R ]
1697 // .................CanvasRect................
1698 // :TPWPK.PTPWP _____PlotRect____ PWPTP.KPWPT:
1699 // :A # |- A # |- -| # A -| # A:
1700 // :X | X | | X | x:
1701 // :I # |- I # |- -| # I -| # I:
1702 // :S | S | | S | S:
1703 // :3 # |- 0 # |-_______________-| # 1 -| # 2:
1704 // :.........................................:
1705 //
1706 // T = text height
1707 // P = label padding
1708 // K = minor tick length
1709 // W = label width
1710
1711 ImPlotContext& gp = *GImPlot;
1712
1713 const float T = ImGui::GetTextLineHeight();
1714 const float P = gp.Style.LabelPadding.x;
1715 const float K = gp.Style.MinorTickLen.y;
1716
1717 int count_L = 0;
1718 int count_R = 0;
1719 float last_L = plot.AxesRect.Min.x;
1720 float last_R = plot.AxesRect.Max.x;
1721
1722 for (int i = IMPLOT_NUM_Y_AXES; i-- > 0;) { // FYI: can iterate forward
1723 ImPlotAxis& axis = plot.YAxis(i);
1724 if (!axis.Enabled)
1725 continue;
1726 const bool label = axis.HasLabel();
1727 const bool ticks = axis.HasTickLabels();
1728 const bool opp = axis.IsOpposite();
1729 if (opp) {
1730 if (count_R++ > 0)
1731 pad_R += K + P;
1732 if (label)
1733 pad_R += T + P;
1734 if (ticks)
1735 pad_R += axis.Ticker.MaxSize.x + P;
1736 axis.Datum1 = plot.CanvasRect.Max.x - pad_R;
1737 axis.Datum2 = last_R;
1738 last_R = axis.Datum1;
1739 }
1740 else {
1741 if (count_L++ > 0)
1742 pad_L += K + P;
1743 if (label)
1744 pad_L += T + P;
1745 if (ticks)
1746 pad_L += axis.Ticker.MaxSize.x + P;
1747 axis.Datum1 = plot.CanvasRect.Min.x + pad_L;
1748 axis.Datum2 = last_L;
1749 last_L = axis.Datum1;
1750 }
1751 }
1752
1753 plot.PlotRect.Min.x = plot.CanvasRect.Min.x + pad_L;
1754 plot.PlotRect.Max.x = plot.CanvasRect.Max.x - pad_R;
1755
1756 if (align) {
1757 count_L = count_R = 0;
1758 float delta_L, delta_R;
1759 align->Update(pad_L,pad_R,delta_L,delta_R);
1760 for (int i = IMPLOT_NUM_Y_AXES; i-- > 0;) {
1761 ImPlotAxis& axis = plot.YAxis(i);
1762 if (!axis.Enabled)
1763 continue;
1764 if (axis.IsOpposite()) {
1765 axis.Datum1 -= delta_R;
1766 axis.Datum2 -= count_R++ > 1 ? delta_R : 0;
1767 }
1768 else {
1769 axis.Datum1 += delta_L;
1770 axis.Datum2 += count_L++ > 1 ? delta_L : 0;
1771 }
1772 }
1773 }
1774}
1775
1776//-----------------------------------------------------------------------------
1777// RENDERING
1778//-----------------------------------------------------------------------------
1779
1780static inline void RenderGridLinesX(ImDrawList& DrawList, const ImPlotTicker& ticker, const ImRect& rect, ImU32 col_maj, ImU32 col_min, float size_maj, float size_min) {
1781 const float density = ticker.TickCount() / rect.GetWidth();
1782 ImVec4 col_min4 = ImGui::ColorConvertU32ToFloat4(col_min);
1783 col_min4.w *= ImClamp(ImRemap(density, 0.1f, 0.2f, 1.0f, 0.0f), 0.0f, 1.0f);
1784 col_min = ImGui::ColorConvertFloat4ToU32(col_min4);
1785 for (int t = 0; t < ticker.TickCount(); t++) {
1786 const ImPlotTick& xt = ticker.Ticks[t];
1787 if (xt.PixelPos < rect.Min.x || xt.PixelPos > rect.Max.x)
1788 continue;
1789 if (xt.Level == 0) {
1790 if (xt.Major)
1791 DrawList.AddLine(ImVec2(xt.PixelPos, rect.Min.y), ImVec2(xt.PixelPos, rect.Max.y), col_maj, size_maj);
1792 else if (density < 0.2f)
1793 DrawList.AddLine(ImVec2(xt.PixelPos, rect.Min.y), ImVec2(xt.PixelPos, rect.Max.y), col_min, size_min);
1794 }
1795 }
1796}
1797
1798static inline void RenderGridLinesY(ImDrawList& DrawList, const ImPlotTicker& ticker, const ImRect& rect, ImU32 col_maj, ImU32 col_min, float size_maj, float size_min) {
1799 const float density = ticker.TickCount() / rect.GetHeight();
1800 ImVec4 col_min4 = ImGui::ColorConvertU32ToFloat4(col_min);
1801 col_min4.w *= ImClamp(ImRemap(density, 0.1f, 0.2f, 1.0f, 0.0f), 0.0f, 1.0f);
1802 col_min = ImGui::ColorConvertFloat4ToU32(col_min4);
1803 for (int t = 0; t < ticker.TickCount(); t++) {
1804 const ImPlotTick& yt = ticker.Ticks[t];
1805 if (yt.PixelPos < rect.Min.y || yt.PixelPos > rect.Max.y)
1806 continue;
1807 if (yt.Major)
1808 DrawList.AddLine(ImVec2(rect.Min.x, yt.PixelPos), ImVec2(rect.Max.x, yt.PixelPos), col_maj, size_maj);
1809 else if (density < 0.2f)
1810 DrawList.AddLine(ImVec2(rect.Min.x, yt.PixelPos), ImVec2(rect.Max.x, yt.PixelPos), col_min, size_min);
1811 }
1812}
1813
1814static inline void RenderSelectionRect(ImDrawList& DrawList, const ImVec2& p_min, const ImVec2& p_max, const ImVec4& col) {
1815 const ImU32 col_bg = ImGui::GetColorU32(col * ImVec4(1,1,1,0.25f));
1816 const ImU32 col_bd = ImGui::GetColorU32(col);
1817 DrawList.AddRectFilled(p_min, p_max, col_bg);
1818 DrawList.AddRect(p_min, p_max, col_bd);
1819}
1820
1821//-----------------------------------------------------------------------------
1822// Input Handling
1823//-----------------------------------------------------------------------------
1824
1825static const float MOUSE_CURSOR_DRAG_THRESHOLD = 5.0f;
1826static const float BOX_SELECT_DRAG_THRESHOLD = 4.0f;
1827
1828bool UpdateInput(ImPlotPlot& plot) {
1829
1830 bool changed = false;
1831
1832 ImPlotContext& gp = *GImPlot;
1833 ImGuiIO& IO = ImGui::GetIO();
1834
1835 // BUTTON STATE -----------------------------------------------------------
1836
1837 const ImGuiButtonFlags plot_button_flags = ImGuiButtonFlags_AllowOverlap
1838 | ImGuiButtonFlags_PressedOnClick
1839 | ImGuiButtonFlags_PressedOnDoubleClick
1840 | ImGuiButtonFlags_MouseButtonLeft
1841 | ImGuiButtonFlags_MouseButtonRight
1842 | ImGuiButtonFlags_MouseButtonMiddle;
1843 const ImGuiButtonFlags axis_button_flags = ImGuiButtonFlags_FlattenChildren
1844 | plot_button_flags;
1845
1846 const bool plot_clicked = ImGui::ButtonBehavior(plot.PlotRect,plot.ID,&plot.Hovered,&plot.Held,plot_button_flags);
1847#if (IMGUI_VERSION_NUM < 18966)
1848 ImGui::SetItemAllowOverlap(); // Handled by ButtonBehavior()
1849#endif
1850
1851 if (plot_clicked) {
1852 if (!ImHasFlag(plot.Flags, ImPlotFlags_NoBoxSelect) && IO.MouseClicked[gp.InputMap.Select] && ImHasFlag(IO.KeyMods, gp.InputMap.SelectMod)) {
1853 plot.Selecting = true;
1854 plot.SelectStart = IO.MousePos;
1855 plot.SelectRect = ImRect(0,0,0,0);
1856 }
1857 if (IO.MouseDoubleClicked[gp.InputMap.Fit]) {
1858 plot.FitThisFrame = true;
1859 for (int i = 0; i < ImAxis_COUNT; ++i)
1860 plot.Axes[i].FitThisFrame = true;
1861 }
1862 }
1863
1864 const bool can_pan = IO.MouseDown[gp.InputMap.Pan] && ImHasFlag(IO.KeyMods, gp.InputMap.PanMod);
1865
1866 plot.Held = plot.Held && can_pan;
1867
1868 bool x_click[IMPLOT_NUM_X_AXES] = {false};
1869 bool x_held[IMPLOT_NUM_X_AXES] = {false};
1870 bool x_hov[IMPLOT_NUM_X_AXES] = {false};
1871
1872 bool y_click[IMPLOT_NUM_Y_AXES] = {false};
1873 bool y_held[IMPLOT_NUM_Y_AXES] = {false};
1874 bool y_hov[IMPLOT_NUM_Y_AXES] = {false};
1875
1876 for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) {
1877 ImPlotAxis& xax = plot.XAxis(i);
1878 if (xax.Enabled) {
1879 ImGui::KeepAliveID(xax.ID);
1880 x_click[i] = ImGui::ButtonBehavior(xax.HoverRect,xax.ID,&xax.Hovered,&xax.Held,axis_button_flags);
1881 if (x_click[i] && IO.MouseDoubleClicked[gp.InputMap.Fit])
1882 plot.FitThisFrame = xax.FitThisFrame = true;
1883 xax.Held = xax.Held && can_pan;
1884 x_hov[i] = xax.Hovered || plot.Hovered;
1885 x_held[i] = xax.Held || plot.Held;
1886 }
1887 }
1888
1889 for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) {
1890 ImPlotAxis& yax = plot.YAxis(i);
1891 if (yax.Enabled) {
1892 ImGui::KeepAliveID(yax.ID);
1893 y_click[i] = ImGui::ButtonBehavior(yax.HoverRect,yax.ID,&yax.Hovered,&yax.Held,axis_button_flags);
1894 if (y_click[i] && IO.MouseDoubleClicked[gp.InputMap.Fit])
1895 plot.FitThisFrame = yax.FitThisFrame = true;
1896 yax.Held = yax.Held && can_pan;
1897 y_hov[i] = yax.Hovered || plot.Hovered;
1898 y_held[i] = yax.Held || plot.Held;
1899 }
1900 }
1901
1902 // cancel due to DND activity
1903 if (GImGui->DragDropActive || (IO.KeyMods == gp.InputMap.OverrideMod && gp.InputMap.OverrideMod != 0))
1904 return false;
1905
1906 // STATE -------------------------------------------------------------------
1907
1908 const bool axis_equal = ImHasFlag(plot.Flags, ImPlotFlags_Equal);
1909
1910 const bool any_x_hov = plot.Hovered || AnyAxesHovered(&plot.Axes[ImAxis_X1], IMPLOT_NUM_X_AXES);
1911 const bool any_x_held = plot.Held || AnyAxesHeld(&plot.Axes[ImAxis_X1], IMPLOT_NUM_X_AXES);
1912 const bool any_y_hov = plot.Hovered || AnyAxesHovered(&plot.Axes[ImAxis_Y1], IMPLOT_NUM_Y_AXES);
1913 const bool any_y_held = plot.Held || AnyAxesHeld(&plot.Axes[ImAxis_Y1], IMPLOT_NUM_Y_AXES);
1914 const bool any_hov = any_x_hov || any_y_hov;
1915 const bool any_held = any_x_held || any_y_held;
1916
1917 const ImVec2 select_drag = ImGui::GetMouseDragDelta(gp.InputMap.Select);
1918 const ImVec2 pan_drag = ImGui::GetMouseDragDelta(gp.InputMap.Pan);
1919 const float select_drag_sq = ImLengthSqr(select_drag);
1920 const float pan_drag_sq = ImLengthSqr(pan_drag);
1921 const bool selecting = plot.Selecting && select_drag_sq > MOUSE_CURSOR_DRAG_THRESHOLD;
1922 const bool panning = any_held && pan_drag_sq > MOUSE_CURSOR_DRAG_THRESHOLD;
1923
1924 // CONTEXT MENU -----------------------------------------------------------
1925
1926 if (IO.MouseReleased[gp.InputMap.Menu] && !plot.ContextLocked)
1927 gp.OpenContextThisFrame = true;
1928
1929 if (selecting || panning)
1930 plot.ContextLocked = true;
1931 else if (!(IO.MouseDown[gp.InputMap.Menu] || IO.MouseReleased[gp.InputMap.Menu]))
1932 plot.ContextLocked = false;
1933
1934 // DRAG INPUT -------------------------------------------------------------
1935
1936 if (any_held && !plot.Selecting) {
1937 int drag_direction = 0;
1938 for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) {
1939 ImPlotAxis& x_axis = plot.XAxis(i);
1940 if (x_held[i] && !x_axis.IsInputLocked()) {
1941 drag_direction |= (1 << 1);
1942 bool increasing = x_axis.IsInverted() ? IO.MouseDelta.x > 0 : IO.MouseDelta.x < 0;
1943 if (IO.MouseDelta.x != 0 && !x_axis.IsPanLocked(increasing)) {
1944 const double plot_l = x_axis.PixelsToPlot(plot.PlotRect.Min.x - IO.MouseDelta.x);
1945 const double plot_r = x_axis.PixelsToPlot(plot.PlotRect.Max.x - IO.MouseDelta.x);
1946 x_axis.SetMin(x_axis.IsInverted() ? plot_r : plot_l);
1947 x_axis.SetMax(x_axis.IsInverted() ? plot_l : plot_r);
1948 if (axis_equal && x_axis.OrthoAxis != nullptr)
1949 x_axis.OrthoAxis->SetAspect(x_axis.GetAspect());
1950 changed = true;
1951 }
1952 }
1953 }
1954 for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) {
1955 ImPlotAxis& y_axis = plot.YAxis(i);
1956 if (y_held[i] && !y_axis.IsInputLocked()) {
1957 drag_direction |= (1 << 2);
1958 bool increasing = y_axis.IsInverted() ? IO.MouseDelta.y < 0 : IO.MouseDelta.y > 0;
1959 if (IO.MouseDelta.y != 0 && !y_axis.IsPanLocked(increasing)) {
1960 const double plot_t = y_axis.PixelsToPlot(plot.PlotRect.Min.y - IO.MouseDelta.y);
1961 const double plot_b = y_axis.PixelsToPlot(plot.PlotRect.Max.y - IO.MouseDelta.y);
1962 y_axis.SetMin(y_axis.IsInverted() ? plot_t : plot_b);
1963 y_axis.SetMax(y_axis.IsInverted() ? plot_b : plot_t);
1964 if (axis_equal && y_axis.OrthoAxis != nullptr)
1965 y_axis.OrthoAxis->SetAspect(y_axis.GetAspect());
1966 changed = true;
1967 }
1968 }
1969 }
1970 if (IO.MouseDragMaxDistanceSqr[gp.InputMap.Pan] > MOUSE_CURSOR_DRAG_THRESHOLD) {
1971 switch (drag_direction) {
1972 case 0 : ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed); break;
1973 case (1 << 1) : ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); break;
1974 case (1 << 2) : ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS); break;
1975 default : ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll); break;
1976 }
1977 }
1978 }
1979
1980 // SCROLL INPUT -----------------------------------------------------------
1981
1982 if (any_hov && ImHasFlag(IO.KeyMods, gp.InputMap.ZoomMod)) {
1983
1984 float zoom_rate = gp.InputMap.ZoomRate;
1985 if (IO.MouseWheel == 0.0f)
1986 zoom_rate = 0;
1987 else if (IO.MouseWheel > 0)
1988 zoom_rate = (-zoom_rate) / (1.0f + (2.0f * zoom_rate));
1989 ImVec2 rect_size = plot.PlotRect.GetSize();
1990 float tx = ImRemap(IO.MousePos.x, plot.PlotRect.Min.x, plot.PlotRect.Max.x, 0.0f, 1.0f);
1991 float ty = ImRemap(IO.MousePos.y, plot.PlotRect.Min.y, plot.PlotRect.Max.y, 0.0f, 1.0f);
1992
1993 for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) {
1994 ImPlotAxis& x_axis = plot.XAxis(i);
1995 const bool equal_zoom = axis_equal && x_axis.OrthoAxis != nullptr;
1996 const bool equal_locked = (equal_zoom != false) && x_axis.OrthoAxis->IsInputLocked();
1997 if (x_hov[i] && !x_axis.IsInputLocked() && !equal_locked) {
1998 ImGui::SetKeyOwner(ImGuiKey_MouseWheelY, plot.ID);
1999 if (zoom_rate != 0.0f) {
2000 float correction = (plot.Hovered && equal_zoom) ? 0.5f : 1.0f;
2001 const double plot_l = x_axis.PixelsToPlot(plot.PlotRect.Min.x - rect_size.x * tx * zoom_rate * correction);
2002 const double plot_r = x_axis.PixelsToPlot(plot.PlotRect.Max.x + rect_size.x * (1 - tx) * zoom_rate * correction);
2003 x_axis.SetMin(x_axis.IsInverted() ? plot_r : plot_l);
2004 x_axis.SetMax(x_axis.IsInverted() ? plot_l : plot_r);
2005 if (axis_equal && x_axis.OrthoAxis != nullptr)
2006 x_axis.OrthoAxis->SetAspect(x_axis.GetAspect());
2007 changed = true;
2008 }
2009 }
2010 }
2011 for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) {
2012 ImPlotAxis& y_axis = plot.YAxis(i);
2013 const bool equal_zoom = axis_equal && y_axis.OrthoAxis != nullptr;
2014 const bool equal_locked = equal_zoom && y_axis.OrthoAxis->IsInputLocked();
2015 if (y_hov[i] && !y_axis.IsInputLocked() && !equal_locked) {
2016 ImGui::SetKeyOwner(ImGuiKey_MouseWheelY, plot.ID);
2017 if (zoom_rate != 0.0f) {
2018 float correction = (plot.Hovered && equal_zoom) ? 0.5f : 1.0f;
2019 const double plot_t = y_axis.PixelsToPlot(plot.PlotRect.Min.y - rect_size.y * ty * zoom_rate * correction);
2020 const double plot_b = y_axis.PixelsToPlot(plot.PlotRect.Max.y + rect_size.y * (1 - ty) * zoom_rate * correction);
2021 y_axis.SetMin(y_axis.IsInverted() ? plot_t : plot_b);
2022 y_axis.SetMax(y_axis.IsInverted() ? plot_b : plot_t);
2023 if (axis_equal && y_axis.OrthoAxis != nullptr)
2024 y_axis.OrthoAxis->SetAspect(y_axis.GetAspect());
2025 changed = true;
2026 }
2027 }
2028 }
2029 }
2030
2031 // BOX-SELECTION ----------------------------------------------------------
2032
2033 if (plot.Selecting) {
2034 const ImVec2 d = plot.SelectStart - IO.MousePos;
2035 const bool x_can_change = !ImHasFlag(IO.KeyMods,gp.InputMap.SelectHorzMod) && ImFabs(d.x) > 2;
2036 const bool y_can_change = !ImHasFlag(IO.KeyMods,gp.InputMap.SelectVertMod) && ImFabs(d.y) > 2;
2037 // confirm
2038 if (IO.MouseReleased[gp.InputMap.Select]) {
2039 for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) {
2040 ImPlotAxis& x_axis = plot.XAxis(i);
2041 if (!x_axis.IsInputLocked() && x_can_change) {
2042 const double p1 = x_axis.PixelsToPlot(plot.SelectStart.x);
2043 const double p2 = x_axis.PixelsToPlot(IO.MousePos.x);
2044 x_axis.SetMin(ImMin(p1, p2));
2045 x_axis.SetMax(ImMax(p1, p2));
2046 changed = true;
2047 }
2048 }
2049 for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) {
2050 ImPlotAxis& y_axis = plot.YAxis(i);
2051 if (!y_axis.IsInputLocked() && y_can_change) {
2052 const double p1 = y_axis.PixelsToPlot(plot.SelectStart.y);
2053 const double p2 = y_axis.PixelsToPlot(IO.MousePos.y);
2054 y_axis.SetMin(ImMin(p1, p2));
2055 y_axis.SetMax(ImMax(p1, p2));
2056 changed = true;
2057 }
2058 }
2059 if (x_can_change || y_can_change || (ImHasFlag(IO.KeyMods,gp.InputMap.SelectHorzMod) && ImHasFlag(IO.KeyMods,gp.InputMap.SelectVertMod)))
2060 gp.OpenContextThisFrame = false;
2061 plot.Selected = plot.Selecting = false;
2062 }
2063 // cancel
2064 else if (IO.MouseReleased[gp.InputMap.SelectCancel]) {
2065 plot.Selected = plot.Selecting = false;
2066 gp.OpenContextThisFrame = false;
2067 }
2068 else if (ImLengthSqr(d) > BOX_SELECT_DRAG_THRESHOLD) {
2069 // bad selection
2070 if (plot.IsInputLocked()) {
2071 ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed);
2072 gp.OpenContextThisFrame = false;
2073 plot.Selected = false;
2074 }
2075 else {
2076 // TODO: Handle only min or max locked cases
2077 const bool full_width = ImHasFlag(IO.KeyMods, gp.InputMap.SelectHorzMod) || AllAxesInputLocked(&plot.Axes[ImAxis_X1], IMPLOT_NUM_X_AXES);
2078 const bool full_height = ImHasFlag(IO.KeyMods, gp.InputMap.SelectVertMod) || AllAxesInputLocked(&plot.Axes[ImAxis_Y1], IMPLOT_NUM_Y_AXES);
2079 plot.SelectRect.Min.x = full_width ? plot.PlotRect.Min.x : ImMin(plot.SelectStart.x, IO.MousePos.x);
2080 plot.SelectRect.Max.x = full_width ? plot.PlotRect.Max.x : ImMax(plot.SelectStart.x, IO.MousePos.x);
2081 plot.SelectRect.Min.y = full_height ? plot.PlotRect.Min.y : ImMin(plot.SelectStart.y, IO.MousePos.y);
2082 plot.SelectRect.Max.y = full_height ? plot.PlotRect.Max.y : ImMax(plot.SelectStart.y, IO.MousePos.y);
2083 plot.SelectRect.Min -= plot.PlotRect.Min;
2084 plot.SelectRect.Max -= plot.PlotRect.Min;
2085 plot.Selected = true;
2086 }
2087 }
2088 else {
2089 plot.Selected = false;
2090 }
2091 }
2092 return changed;
2093}
2094
2095//-----------------------------------------------------------------------------
2096// Next Plot Data (Legacy)
2097//-----------------------------------------------------------------------------
2098
2099void ApplyNextPlotData(ImAxis idx) {
2100 ImPlotContext& gp = *GImPlot;
2101 ImPlotPlot& plot = *gp.CurrentPlot;
2102 ImPlotAxis& axis = plot.Axes[idx];
2103 if (!axis.Enabled)
2104 return;
2105 double* npd_lmin = gp.NextPlotData.LinkedMin[idx];
2106 double* npd_lmax = gp.NextPlotData.LinkedMax[idx];
2107 bool npd_rngh = gp.NextPlotData.HasRange[idx];
2108 ImPlotCond npd_rngc = gp.NextPlotData.RangeCond[idx];
2109 ImPlotRange npd_rngv = gp.NextPlotData.Range[idx];
2110 axis.LinkedMin = npd_lmin;
2111 axis.LinkedMax = npd_lmax;
2112 axis.PullLinks();
2113 if (npd_rngh) {
2114 if (!plot.Initialized || npd_rngc == ImPlotCond_Always)
2115 axis.SetRange(npd_rngv);
2116 }
2117 axis.HasRange = npd_rngh;
2118 axis.RangeCond = npd_rngc;
2119}
2120
2121//-----------------------------------------------------------------------------
2122// Setup
2123//-----------------------------------------------------------------------------
2124
2125void SetupAxis(ImAxis idx, const char* label, ImPlotAxisFlags flags) {
2126 ImPlotContext& gp = *GImPlot;
2127 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
2128 "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!");
2129 // get plot and axis
2130 ImPlotPlot& plot = *gp.CurrentPlot;
2131 ImPlotAxis& axis = plot.Axes[idx];
2132 // set ID
2133 axis.ID = plot.ID + idx + 1;
2134 // check and set flags
2135 if (plot.JustCreated || flags != axis.PreviousFlags)
2136 axis.Flags = flags;
2137 axis.PreviousFlags = flags;
2138 // enable axis
2139 axis.Enabled = true;
2140 // set label
2141 plot.SetAxisLabel(axis,label);
2142 // cache colors
2143 UpdateAxisColors(axis);
2144}
2145
2146void SetupAxisLimits(ImAxis idx, double min_lim, double max_lim, ImPlotCond cond) {
2147 ImPlotContext& gp = *GImPlot;
2148 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
2149 "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); // get plot and axis
2150 ImPlotPlot& plot = *gp.CurrentPlot;
2151 ImPlotAxis& axis = plot.Axes[idx];
2152 IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?");
2153 if (!plot.Initialized || cond == ImPlotCond_Always)
2154 axis.SetRange(min_lim, max_lim);
2155 axis.HasRange = true;
2156 axis.RangeCond = cond;
2157}
2158
2159void SetupAxisFormat(ImAxis idx, const char* fmt) {
2160 ImPlotContext& gp = *GImPlot;
2161 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
2162 "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!");
2163 ImPlotPlot& plot = *gp.CurrentPlot;
2164 ImPlotAxis& axis = plot.Axes[idx];
2165 IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?");
2166 axis.HasFormatSpec = fmt != nullptr;
2167 if (fmt != nullptr)
2168 ImStrncpy(axis.FormatSpec,fmt,sizeof(axis.FormatSpec));
2169}
2170
2171void SetupAxisLinks(ImAxis idx, double* min_lnk, double* max_lnk) {
2172 ImPlotContext& gp = *GImPlot;
2173 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
2174 "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!");
2175 ImPlotPlot& plot = *gp.CurrentPlot;
2176 ImPlotAxis& axis = plot.Axes[idx];
2177 IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?");
2178 axis.LinkedMin = min_lnk;
2179 axis.LinkedMax = max_lnk;
2180 axis.PullLinks();
2181}
2182
2183void SetupAxisFormat(ImAxis idx, ImPlotFormatter formatter, void* data) {
2184 ImPlotContext& gp = *GImPlot;
2185 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
2186 "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!");
2187 ImPlotPlot& plot = *gp.CurrentPlot;
2188 ImPlotAxis& axis = plot.Axes[idx];
2189 IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?");
2190 axis.Formatter = formatter;
2191 axis.FormatterData = data;
2192}
2193
2194void SetupAxisTicks(ImAxis idx, const double* values, int n_ticks, const char* const labels[], bool show_default) {
2195 ImPlotContext& gp = *GImPlot;
2196 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
2197 "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!");
2198 ImPlotPlot& plot = *gp.CurrentPlot;
2199 ImPlotAxis& axis = plot.Axes[idx];
2200 IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?");
2201 axis.ShowDefaultTicks = show_default;
2202 AddTicksCustom(values,
2203 labels,
2204 n_ticks,
2205 axis.Ticker,
2206 axis.Formatter ? axis.Formatter : Formatter_Default,
2207 (axis.Formatter && axis.FormatterData) ? axis.FormatterData : axis.HasFormatSpec ? axis.FormatSpec : (void*)IMPLOT_LABEL_FORMAT);
2208}
2209
2210void SetupAxisTicks(ImAxis idx, double v_min, double v_max, int n_ticks, const char* const labels[], bool show_default) {
2211 ImPlotContext& gp = *GImPlot;
2212 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
2213 "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!");
2214 n_ticks = n_ticks < 2 ? 2 : n_ticks;
2215 FillRange(gp.TempDouble1, n_ticks, v_min, v_max);
2216 SetupAxisTicks(idx, gp.TempDouble1.Data, n_ticks, labels, show_default);
2217}
2218
2219void SetupAxisScale(ImAxis idx, ImPlotScale scale) {
2220 ImPlotContext& gp = *GImPlot;
2221 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
2222 "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!");
2223 ImPlotPlot& plot = *gp.CurrentPlot;
2224 ImPlotAxis& axis = plot.Axes[idx];
2225 IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?");
2226 axis.Scale = scale;
2227 switch (scale)
2228 {
2229 case ImPlotScale_Time:
2230 axis.TransformForward = nullptr;
2231 axis.TransformInverse = nullptr;
2232 axis.TransformData = nullptr;
2233 axis.Locator = Locator_Time;
2234 axis.ConstraintRange = ImPlotRange(IMPLOT_MIN_TIME, IMPLOT_MAX_TIME);
2235 axis.Ticker.Levels = 2;
2236 break;
2237 case ImPlotScale_Log10:
2238 axis.TransformForward = TransformForward_Log10;
2239 axis.TransformInverse = TransformInverse_Log10;
2240 axis.TransformData = nullptr;
2241 axis.Locator = Locator_Log10;
2242 axis.ConstraintRange = ImPlotRange(DBL_MIN, INFINITY);
2243 break;
2244 case ImPlotScale_SymLog:
2245 axis.TransformForward = TransformForward_SymLog;
2246 axis.TransformInverse = TransformInverse_SymLog;
2247 axis.TransformData = nullptr;
2248 axis.Locator = Locator_SymLog;
2249 axis.ConstraintRange = ImPlotRange(-INFINITY, INFINITY);
2250 break;
2251 default:
2252 axis.TransformForward = nullptr;
2253 axis.TransformInverse = nullptr;
2254 axis.TransformData = nullptr;
2255 axis.Locator = nullptr;
2256 axis.ConstraintRange = ImPlotRange(-INFINITY, INFINITY);
2257 break;
2258 }
2259}
2260
2261void SetupAxisScale(ImAxis idx, ImPlotTransform fwd, ImPlotTransform inv, void* data) {
2262 ImPlotContext& gp = *GImPlot;
2263 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
2264 "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!");
2265 ImPlotPlot& plot = *gp.CurrentPlot;
2266 ImPlotAxis& axis = plot.Axes[idx];
2267 IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?");
2268 axis.Scale = IMPLOT_AUTO;
2269 axis.TransformForward = fwd;
2270 axis.TransformInverse = inv;
2271 axis.TransformData = data;
2272}
2273
2274void SetupAxisLimitsConstraints(ImAxis idx, double v_min, double v_max) {
2275 ImPlotContext& gp = *GImPlot;
2276 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
2277 "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!");
2278 ImPlotPlot& plot = *gp.CurrentPlot;
2279 ImPlotAxis& axis = plot.Axes[idx];
2280 IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?");
2281 axis.ConstraintRange.Min = v_min;
2282 axis.ConstraintRange.Max = v_max;
2283}
2284
2285void SetupAxisZoomConstraints(ImAxis idx, double z_min, double z_max) {
2286 ImPlotContext& gp = *GImPlot;
2287 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
2288 "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!");
2289 ImPlotPlot& plot = *gp.CurrentPlot;
2290 ImPlotAxis& axis = plot.Axes[idx];
2291 IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?");
2292 axis.ConstraintZoom.Min = z_min;
2293 axis.ConstraintZoom.Max = z_max;
2294}
2295
2296void SetupAxes(const char* x_label, const char* y_label, ImPlotAxisFlags x_flags, ImPlotAxisFlags y_flags) {
2297 SetupAxis(ImAxis_X1, x_label, x_flags);
2298 SetupAxis(ImAxis_Y1, y_label, y_flags);
2299}
2300
2301void SetupAxesLimits(double x_min, double x_max, double y_min, double y_max, ImPlotCond cond) {
2302 SetupAxisLimits(ImAxis_X1, x_min, x_max, cond);
2303 SetupAxisLimits(ImAxis_Y1, y_min, y_max, cond);
2304}
2305
2306void SetupLegend(ImPlotLocation location, ImPlotLegendFlags flags) {
2307 ImPlotContext& gp = *GImPlot;
2308 IM_ASSERT_USER_ERROR((gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked) || (gp.CurrentSubplot != nullptr && gp.CurrentPlot == nullptr),
2309 "Setup needs to be called after BeginPlot or BeginSubplots and before any setup locking functions (e.g. PlotX)!");
2310 if (gp.CurrentItems) {
2311 ImPlotLegend& legend = gp.CurrentItems->Legend;
2312 // check and set location
2313 if (location != legend.PreviousLocation)
2314 legend.Location = location;
2315 legend.PreviousLocation = location;
2316 // check and set flags
2317 if (flags != legend.PreviousFlags)
2318 legend.Flags = flags;
2319 legend.PreviousFlags = flags;
2320 }
2321}
2322
2323void SetupMouseText(ImPlotLocation location, ImPlotMouseTextFlags flags) {
2324 ImPlotContext& gp = *GImPlot;
2325 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
2326 "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!");
2327 gp.CurrentPlot->MouseTextLocation = location;
2328 gp.CurrentPlot->MouseTextFlags = flags;
2329}
2330
2331//-----------------------------------------------------------------------------
2332// SetNext
2333//-----------------------------------------------------------------------------
2334
2335void SetNextAxisLimits(ImAxis axis, double v_min, double v_max, ImPlotCond cond) {
2336 ImPlotContext& gp = *GImPlot;
2337 IM_ASSERT_USER_ERROR(gp.CurrentPlot == nullptr, "SetNextAxisLimits() needs to be called before BeginPlot()!");
2338 IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
2339 gp.NextPlotData.HasRange[axis] = true;
2340 gp.NextPlotData.RangeCond[axis] = cond;
2341 gp.NextPlotData.Range[axis].Min = v_min;
2342 gp.NextPlotData.Range[axis].Max = v_max;
2343}
2344
2345void SetNextAxisLinks(ImAxis axis, double* link_min, double* link_max) {
2346 ImPlotContext& gp = *GImPlot;
2347 IM_ASSERT_USER_ERROR(gp.CurrentPlot == nullptr, "SetNextAxisLinks() needs to be called before BeginPlot()!");
2348 gp.NextPlotData.LinkedMin[axis] = link_min;
2349 gp.NextPlotData.LinkedMax[axis] = link_max;
2350}
2351
2352void SetNextAxisToFit(ImAxis axis) {
2353 ImPlotContext& gp = *GImPlot;
2354 IM_ASSERT_USER_ERROR(gp.CurrentPlot == nullptr, "SetNextAxisToFit() needs to be called before BeginPlot()!");
2355 gp.NextPlotData.Fit[axis] = true;
2356}
2357
2358void SetNextAxesLimits(double x_min, double x_max, double y_min, double y_max, ImPlotCond cond) {
2359 SetNextAxisLimits(ImAxis_X1, x_min, x_max, cond);
2360 SetNextAxisLimits(ImAxis_Y1, y_min, y_max, cond);
2361}
2362
2363void SetNextAxesToFit() {
2364 for (int i = 0; i < ImAxis_COUNT; ++i)
2365 SetNextAxisToFit(i);
2366}
2367
2368//-----------------------------------------------------------------------------
2369// BeginPlot
2370//-----------------------------------------------------------------------------
2371
2372bool BeginPlot(const char* title_id, const ImVec2& size, ImPlotFlags flags) {
2373 IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?");
2374 ImPlotContext& gp = *GImPlot;
2375 IM_ASSERT_USER_ERROR(gp.CurrentPlot == nullptr, "Mismatched BeginPlot()/EndPlot()!");
2376
2377 // FRONT MATTER -----------------------------------------------------------
2378
2379 if (gp.CurrentSubplot != nullptr)
2380 ImGui::PushID(gp.CurrentSubplot->CurrentIdx);
2381
2382 // get globals
2383 ImGuiContext &G = *GImGui;
2384 ImGuiWindow* Window = G.CurrentWindow;
2385
2386 // skip if needed
2387 if (Window->SkipItems && !gp.CurrentSubplot) {
2388 ResetCtxForNextPlot(GImPlot);
2389 return false;
2390 }
2391
2392 // ID and age (TODO: keep track of plot age in frames)
2393 const ImGuiID ID = Window->GetID(title_id);
2394 const bool just_created = gp.Plots.GetByKey(ID) == nullptr;
2395 gp.CurrentPlot = gp.Plots.GetOrAddByKey(ID);
2396
2397 ImPlotPlot &plot = *gp.CurrentPlot;
2398 plot.ID = ID;
2399 plot.Items.ID = ID - 1;
2400 plot.JustCreated = just_created;
2401 plot.SetupLocked = false;
2402
2403 // check flags
2404 if (plot.JustCreated)
2405 plot.Flags = flags;
2406 else if (flags != plot.PreviousFlags)
2407 plot.Flags = flags;
2408 plot.PreviousFlags = flags;
2409
2410 // setup default axes
2411 if (plot.JustCreated) {
2412 SetupAxis(ImAxis_X1);
2413 SetupAxis(ImAxis_Y1);
2414 }
2415
2416 // reset axes
2417 for (int i = 0; i < ImAxis_COUNT; ++i) {
2418 plot.Axes[i].Reset();
2419 UpdateAxisColors(plot.Axes[i]);
2420 }
2421 // ensure first axes enabled
2422 plot.Axes[ImAxis_X1].Enabled = true;
2423 plot.Axes[ImAxis_Y1].Enabled = true;
2424 // set initial axes
2425 plot.CurrentX = ImAxis_X1;
2426 plot.CurrentY = ImAxis_Y1;
2427
2428 // process next plot data (legacy)
2429 for (int i = 0; i < ImAxis_COUNT; ++i)
2430 ApplyNextPlotData(i);
2431
2432 // clear text buffers
2433 plot.ClearTextBuffer();
2434 plot.SetTitle(title_id);
2435
2436 // set frame size
2437 ImVec2 frame_size;
2438 if (gp.CurrentSubplot != nullptr)
2439 frame_size = gp.CurrentSubplot->CellSize;
2440 else
2441 frame_size = ImGui::CalcItemSize(size, gp.Style.PlotDefaultSize.x, gp.Style.PlotDefaultSize.y);
2442
2443 if (frame_size.x < gp.Style.PlotMinSize.x && (size.x < 0.0f || gp.CurrentSubplot != nullptr))
2444 frame_size.x = gp.Style.PlotMinSize.x;
2445 if (frame_size.y < gp.Style.PlotMinSize.y && (size.y < 0.0f || gp.CurrentSubplot != nullptr))
2446 frame_size.y = gp.Style.PlotMinSize.y;
2447
2448 plot.FrameRect = ImRect(Window->DC.CursorPos, Window->DC.CursorPos + frame_size);
2449 ImGui::ItemSize(plot.FrameRect);
2450 if (!ImGui::ItemAdd(plot.FrameRect, plot.ID, &plot.FrameRect) && !gp.CurrentSubplot) {
2451 ResetCtxForNextPlot(GImPlot);
2452 return false;
2453 }
2454
2455 // setup items (or dont)
2456 if (gp.CurrentItems == nullptr)
2457 gp.CurrentItems = &plot.Items;
2458
2459 return true;
2460}
2461
2462//-----------------------------------------------------------------------------
2463// SetupFinish
2464//-----------------------------------------------------------------------------
2465
2466void SetupFinish() {
2467 IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?");
2468 ImPlotContext& gp = *GImPlot;
2469 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "SetupFinish needs to be called after BeginPlot!");
2470
2471 ImGuiContext& G = *GImGui;
2472 ImDrawList& DrawList = *G.CurrentWindow->DrawList;
2473 const ImGuiStyle& Style = G.Style;
2474
2475 ImPlotPlot &plot = *gp.CurrentPlot;
2476
2477 // lock setup
2478 plot.SetupLocked = true;
2479
2480 // finalize axes and set default formatter/locator
2481 for (int i = 0; i < ImAxis_COUNT; ++i) {
2482 ImPlotAxis& axis = plot.Axes[i];
2483 if (axis.Enabled) {
2484 axis.Constrain();
2485 if (!plot.Initialized && axis.CanInitFit())
2486 plot.FitThisFrame = axis.FitThisFrame = true;
2487 }
2488 if (axis.Formatter == nullptr) {
2489 axis.Formatter = Formatter_Default;
2490 if (axis.HasFormatSpec)
2491 axis.FormatterData = axis.FormatSpec;
2492 else
2493 axis.FormatterData = (void*)IMPLOT_LABEL_FORMAT;
2494 }
2495 if (axis.Locator == nullptr) {
2496 axis.Locator = Locator_Default;
2497 }
2498 }
2499
2500 // setup nullptr orthogonal axes
2501 const bool axis_equal = ImHasFlag(plot.Flags, ImPlotFlags_Equal);
2502 for (int ix = ImAxis_X1, iy = ImAxis_Y1; ix < ImAxis_Y1 || iy < ImAxis_COUNT; ++ix, ++iy) {
2503 ImPlotAxis& x_axis = plot.Axes[ix];
2504 ImPlotAxis& y_axis = plot.Axes[iy];
2505 if (x_axis.Enabled && y_axis.Enabled) {
2506 if (x_axis.OrthoAxis == nullptr)
2507 x_axis.OrthoAxis = &y_axis;
2508 if (y_axis.OrthoAxis == nullptr)
2509 y_axis.OrthoAxis = &x_axis;
2510 }
2511 else if (x_axis.Enabled)
2512 {
2513 if (x_axis.OrthoAxis == nullptr && !axis_equal)
2514 x_axis.OrthoAxis = &plot.Axes[ImAxis_Y1];
2515 }
2516 else if (y_axis.Enabled) {
2517 if (y_axis.OrthoAxis == nullptr && !axis_equal)
2518 y_axis.OrthoAxis = &plot.Axes[ImAxis_X1];
2519 }
2520 }
2521
2522 // canvas/axes bb
2523 plot.CanvasRect = ImRect(plot.FrameRect.Min + gp.Style.PlotPadding, plot.FrameRect.Max - gp.Style.PlotPadding);
2524 plot.AxesRect = plot.FrameRect;
2525
2526 // outside legend adjustments
2527 if (!ImHasFlag(plot.Flags, ImPlotFlags_NoLegend) && plot.Items.GetLegendCount() > 0 && ImHasFlag(plot.Items.Legend.Flags, ImPlotLegendFlags_Outside)) {
2528 ImPlotLegend& legend = plot.Items.Legend;
2529 const bool horz = ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal);
2530 const ImVec2 legend_size = CalcLegendSize(plot.Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !horz);
2531 const bool west = ImHasFlag(legend.Location, ImPlotLocation_West) && !ImHasFlag(legend.Location, ImPlotLocation_East);
2532 const bool east = ImHasFlag(legend.Location, ImPlotLocation_East) && !ImHasFlag(legend.Location, ImPlotLocation_West);
2533 const bool north = ImHasFlag(legend.Location, ImPlotLocation_North) && !ImHasFlag(legend.Location, ImPlotLocation_South);
2534 const bool south = ImHasFlag(legend.Location, ImPlotLocation_South) && !ImHasFlag(legend.Location, ImPlotLocation_North);
2535 if ((west && !horz) || (west && horz && !north && !south)) {
2536 plot.CanvasRect.Min.x += (legend_size.x + gp.Style.LegendPadding.x);
2537 plot.AxesRect.Min.x += (legend_size.x + gp.Style.PlotPadding.x);
2538 }
2539 if ((east && !horz) || (east && horz && !north && !south)) {
2540 plot.CanvasRect.Max.x -= (legend_size.x + gp.Style.LegendPadding.x);
2541 plot.AxesRect.Max.x -= (legend_size.x + gp.Style.PlotPadding.x);
2542 }
2543 if ((north && horz) || (north && !horz && !west && !east)) {
2544 plot.CanvasRect.Min.y += (legend_size.y + gp.Style.LegendPadding.y);
2545 plot.AxesRect.Min.y += (legend_size.y + gp.Style.PlotPadding.y);
2546 }
2547 if ((south && horz) || (south && !horz && !west && !east)) {
2548 plot.CanvasRect.Max.y -= (legend_size.y + gp.Style.LegendPadding.y);
2549 plot.AxesRect.Max.y -= (legend_size.y + gp.Style.PlotPadding.y);
2550 }
2551 }
2552
2553 // plot bb
2554 float pad_top = 0, pad_bot = 0, pad_left = 0, pad_right = 0;
2555
2556 // (0) calc top padding form title
2557 ImVec2 title_size(0.0f, 0.0f);
2558 if (plot.HasTitle())
2559 title_size = ImGui::CalcTextSize(plot.GetTitle(), nullptr, true);
2560 if (title_size.x > 0) {
2561 pad_top += title_size.y + gp.Style.LabelPadding.y;
2562 plot.AxesRect.Min.y += gp.Style.PlotPadding.y + pad_top;
2563 }
2564
2565 // (1) calc addition top padding and bot padding
2566 PadAndDatumAxesX(plot,pad_top,pad_bot,gp.CurrentAlignmentH);
2567
2568 const float plot_height = plot.CanvasRect.GetHeight() - pad_top - pad_bot;
2569
2570 // (2) get y tick labels (needed for left/right pad)
2571 for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) {
2572 ImPlotAxis& axis = plot.YAxis(i);
2573 if (axis.WillRender() && axis.ShowDefaultTicks && plot_height > 0) {
2574 axis.Locator(axis.Ticker, axis.Range, plot_height, true, axis.Formatter, axis.FormatterData);
2575 }
2576 }
2577
2578 // (3) calc left/right pad
2579 PadAndDatumAxesY(plot,pad_left,pad_right,gp.CurrentAlignmentV);
2580
2581 const float plot_width = plot.CanvasRect.GetWidth() - pad_left - pad_right;
2582
2583 // (4) get x ticks
2584 for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) {
2585 ImPlotAxis& axis = plot.XAxis(i);
2586 if (axis.WillRender() && axis.ShowDefaultTicks && plot_width > 0) {
2587 axis.Locator(axis.Ticker, axis.Range, plot_width, false, axis.Formatter, axis.FormatterData);
2588 }
2589 }
2590
2591 // (5) calc plot bb
2592 plot.PlotRect = ImRect(plot.CanvasRect.Min + ImVec2(pad_left, pad_top), plot.CanvasRect.Max - ImVec2(pad_right, pad_bot));
2593
2594 // HOVER------------------------------------------------------------
2595
2596 // axes hover rect, pixel ranges
2597 for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) {
2598 ImPlotAxis& xax = plot.XAxis(i);
2599 xax.HoverRect = ImRect(ImVec2(plot.PlotRect.Min.x, ImMin(xax.Datum1,xax.Datum2)),
2600 ImVec2(plot.PlotRect.Max.x, ImMax(xax.Datum1,xax.Datum2)));
2601 xax.PixelMin = xax.IsInverted() ? plot.PlotRect.Max.x : plot.PlotRect.Min.x;
2602 xax.PixelMax = xax.IsInverted() ? plot.PlotRect.Min.x : plot.PlotRect.Max.x;
2603 xax.UpdateTransformCache();
2604 }
2605
2606 for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) {
2607 ImPlotAxis& yax = plot.YAxis(i);
2608 yax.HoverRect = ImRect(ImVec2(ImMin(yax.Datum1,yax.Datum2),plot.PlotRect.Min.y),
2609 ImVec2(ImMax(yax.Datum1,yax.Datum2),plot.PlotRect.Max.y));
2610 yax.PixelMin = yax.IsInverted() ? plot.PlotRect.Min.y : plot.PlotRect.Max.y;
2611 yax.PixelMax = yax.IsInverted() ? plot.PlotRect.Max.y : plot.PlotRect.Min.y;
2612 yax.UpdateTransformCache();
2613 }
2614 // Equal axis constraint. Must happen after we set Pixels
2615 // constrain equal axes for primary x and y if not approximately equal
2616 // constrains x to y since x pixel size depends on y labels width, and causes feedback loops in opposite case
2617 if (axis_equal) {
2618 for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) {
2619 ImPlotAxis& x_axis = plot.XAxis(i);
2620 if (x_axis.OrthoAxis == nullptr)
2621 continue;
2622 double xar = x_axis.GetAspect();
2623 double yar = x_axis.OrthoAxis->GetAspect();
2624 // edge case: user has set x range this frame, so fit y to x so that we honor their request for x range
2625 // NB: because of feedback across several frames, the user's x request may not be perfectly honored
2626 if (x_axis.HasRange)
2627 x_axis.OrthoAxis->SetAspect(xar);
2628 else if (!ImAlmostEqual(xar,yar) && !x_axis.OrthoAxis->IsInputLocked())
2629 x_axis.SetAspect(yar);
2630 }
2631 }
2632
2633 // INPUT ------------------------------------------------------------------
2634 if (!ImHasFlag(plot.Flags, ImPlotFlags_NoInputs))
2635 UpdateInput(plot);
2636
2637 // fit from FitNextPlotAxes or auto fit
2638 for (int i = 0; i < ImAxis_COUNT; ++i) {
2639 if (gp.NextPlotData.Fit[i] || plot.Axes[i].IsAutoFitting()) {
2640 plot.FitThisFrame = true;
2641 plot.Axes[i].FitThisFrame = true;
2642 }
2643 }
2644
2645 // RENDER -----------------------------------------------------------------
2646
2647 const float txt_height = ImGui::GetTextLineHeight();
2648
2649 // render frame
2650 if (!ImHasFlag(plot.Flags, ImPlotFlags_NoFrame))
2651 ImGui::RenderFrame(plot.FrameRect.Min, plot.FrameRect.Max, GetStyleColorU32(ImPlotCol_FrameBg), true, Style.FrameRounding);
2652
2653 // grid bg
2654 DrawList.AddRectFilled(plot.PlotRect.Min, plot.PlotRect.Max, GetStyleColorU32(ImPlotCol_PlotBg));
2655
2656 // transform ticks
2657 for (int i = 0; i < ImAxis_COUNT; i++) {
2658 ImPlotAxis& axis = plot.Axes[i];
2659 if (axis.WillRender()) {
2660 for (int t = 0; t < axis.Ticker.TickCount(); t++) {
2661 ImPlotTick& tk = axis.Ticker.Ticks[t];
2662 tk.PixelPos = IM_ROUND(axis.PlotToPixels(tk.PlotPos));
2663 }
2664 }
2665 }
2666
2667 // render grid (background)
2668 for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) {
2669 ImPlotAxis& x_axis = plot.XAxis(i);
2670 if (x_axis.Enabled && x_axis.HasGridLines() && !x_axis.IsForeground())
2671 RenderGridLinesX(DrawList, x_axis.Ticker, plot.PlotRect, x_axis.ColorMaj, x_axis.ColorMin, gp.Style.MajorGridSize.x, gp.Style.MinorGridSize.x);
2672 }
2673 for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) {
2674 ImPlotAxis& y_axis = plot.YAxis(i);
2675 if (y_axis.Enabled && y_axis.HasGridLines() && !y_axis.IsForeground())
2676 RenderGridLinesY(DrawList, y_axis.Ticker, plot.PlotRect, y_axis.ColorMaj, y_axis.ColorMin, gp.Style.MajorGridSize.y, gp.Style.MinorGridSize.y);
2677 }
2678
2679 // render x axis button, label, tick labels
2680 for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) {
2681 ImPlotAxis& ax = plot.XAxis(i);
2682 if (!ax.Enabled)
2683 continue;
2684 if ((ax.Hovered || ax.Held) && !plot.Held && !ImHasFlag(ax.Flags, ImPlotAxisFlags_NoHighlight))
2685 DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.Held ? ax.ColorAct : ax.ColorHov);
2686 else if (ax.ColorHiLi != IM_COL32_BLACK_TRANS) {
2687 DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.ColorHiLi);
2688 ax.ColorHiLi = IM_COL32_BLACK_TRANS;
2689 }
2690 else if (ax.ColorBg != IM_COL32_BLACK_TRANS) {
2691 DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.ColorBg);
2692 }
2693 const ImPlotTicker& tkr = ax.Ticker;
2694 const bool opp = ax.IsOpposite();
2695 if (ax.HasLabel()) {
2696 const char* label = plot.GetAxisLabel(ax);
2697 const ImVec2 label_size = ImGui::CalcTextSize(label);
2698 const float label_offset = (ax.HasTickLabels() ? tkr.MaxSize.y + gp.Style.LabelPadding.y : 0.0f)
2699 + (tkr.Levels - 1) * (txt_height + gp.Style.LabelPadding.y)
2700 + gp.Style.LabelPadding.y;
2701 const ImVec2 label_pos(plot.PlotRect.GetCenter().x - label_size.x * 0.5f,
2702 opp ? ax.Datum1 - label_offset - label_size.y : ax.Datum1 + label_offset);
2703 DrawList.AddText(label_pos, ax.ColorTxt, label);
2704 }
2705 if (ax.HasTickLabels()) {
2706 for (int j = 0; j < tkr.TickCount(); ++j) {
2707 const ImPlotTick& tk = tkr.Ticks[j];
2708 const float datum = ax.Datum1 + (opp ? (-gp.Style.LabelPadding.y -txt_height -tk.Level * (txt_height + gp.Style.LabelPadding.y))
2709 : gp.Style.LabelPadding.y + tk.Level * (txt_height + gp.Style.LabelPadding.y));
2710 if (tk.ShowLabel && tk.PixelPos >= plot.PlotRect.Min.x - 1 && tk.PixelPos <= plot.PlotRect.Max.x + 1) {
2711 ImVec2 start(tk.PixelPos - 0.5f * tk.LabelSize.x, datum);
2712 DrawList.AddText(start, ax.ColorTxt, tkr.GetText(j));
2713 }
2714 }
2715 }
2716 }
2717
2718 // render y axis button, label, tick labels
2719 for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) {
2720 ImPlotAxis& ax = plot.YAxis(i);
2721 if (!ax.Enabled)
2722 continue;
2723 if ((ax.Hovered || ax.Held) && !plot.Held && !ImHasFlag(ax.Flags, ImPlotAxisFlags_NoHighlight))
2724 DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.Held ? ax.ColorAct : ax.ColorHov);
2725 else if (ax.ColorHiLi != IM_COL32_BLACK_TRANS) {
2726 DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.ColorHiLi);
2727 ax.ColorHiLi = IM_COL32_BLACK_TRANS;
2728 }
2729 else if (ax.ColorBg != IM_COL32_BLACK_TRANS) {
2730 DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.ColorBg);
2731 }
2732 const ImPlotTicker& tkr = ax.Ticker;
2733 const bool opp = ax.IsOpposite();
2734 if (ax.HasLabel()) {
2735 const char* label = plot.GetAxisLabel(ax);
2736 const ImVec2 label_size = CalcTextSizeVertical(label);
2737 const float label_offset = (ax.HasTickLabels() ? tkr.MaxSize.x + gp.Style.LabelPadding.x : 0.0f)
2738 + gp.Style.LabelPadding.x;
2739 const ImVec2 label_pos(opp ? ax.Datum1 + label_offset : ax.Datum1 - label_offset - label_size.x,
2740 plot.PlotRect.GetCenter().y + label_size.y * 0.5f);
2741 AddTextVertical(&DrawList, label_pos, ax.ColorTxt, label);
2742 }
2743 if (ax.HasTickLabels()) {
2744 for (int j = 0; j < tkr.TickCount(); ++j) {
2745 const ImPlotTick& tk = tkr.Ticks[j];
2746 const float datum = ax.Datum1 + (opp ? gp.Style.LabelPadding.x : (-gp.Style.LabelPadding.x - tk.LabelSize.x));
2747 if (tk.ShowLabel && tk.PixelPos >= plot.PlotRect.Min.y - 1 && tk.PixelPos <= plot.PlotRect.Max.y + 1) {
2748 ImVec2 start(datum, tk.PixelPos - 0.5f * tk.LabelSize.y);
2749 DrawList.AddText(start, ax.ColorTxt, tkr.GetText(j));
2750 }
2751 }
2752 }
2753 }
2754
2755
2756 // clear legend (TODO: put elsewhere)
2757 plot.Items.Legend.Reset();
2758 // push ID to set item hashes (NB: !!!THIS PROBABLY NEEDS TO BE IN BEGIN PLOT!!!!)
2759 ImGui::PushOverrideID(gp.CurrentItems->ID);
2760}
2761
2762//-----------------------------------------------------------------------------
2763// EndPlot()
2764//-----------------------------------------------------------------------------
2765
2766void EndPlot() {
2767 IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?");
2768 ImPlotContext& gp = *GImPlot;
2769 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "Mismatched BeginPlot()/EndPlot()!");
2770
2771 SetupLock();
2772
2773 ImGuiContext &G = *GImGui;
2774 ImPlotPlot &plot = *gp.CurrentPlot;
2775 ImGuiWindow * Window = G.CurrentWindow;
2776 ImDrawList & DrawList = *Window->DrawList;
2777 const ImGuiIO & IO = ImGui::GetIO();
2778
2779 // FINAL RENDER -----------------------------------------------------------
2780
2781 const bool render_border = gp.Style.PlotBorderSize > 0 && GetStyleColorVec4(ImPlotCol_PlotBorder).w > 0;
2782 const bool any_x_held = plot.Held || AnyAxesHeld(&plot.Axes[ImAxis_X1], IMPLOT_NUM_X_AXES);
2783 const bool any_y_held = plot.Held || AnyAxesHeld(&plot.Axes[ImAxis_Y1], IMPLOT_NUM_Y_AXES);
2784
2785 ImGui::PushClipRect(plot.FrameRect.Min, plot.FrameRect.Max, true);
2786
2787 // render grid (foreground)
2788 for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) {
2789 ImPlotAxis& x_axis = plot.XAxis(i);
2790 if (x_axis.Enabled && x_axis.HasGridLines() && x_axis.IsForeground())
2791 RenderGridLinesX(DrawList, x_axis.Ticker, plot.PlotRect, x_axis.ColorMaj, x_axis.ColorMin, gp.Style.MajorGridSize.x, gp.Style.MinorGridSize.x);
2792 }
2793 for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) {
2794 ImPlotAxis& y_axis = plot.YAxis(i);
2795 if (y_axis.Enabled && y_axis.HasGridLines() && y_axis.IsForeground())
2796 RenderGridLinesY(DrawList, y_axis.Ticker, plot.PlotRect, y_axis.ColorMaj, y_axis.ColorMin, gp.Style.MajorGridSize.y, gp.Style.MinorGridSize.y);
2797 }
2798
2799
2800 // render title
2801 if (plot.HasTitle()) {
2802 ImU32 col = GetStyleColorU32(ImPlotCol_TitleText);
2803 AddTextCentered(&DrawList,ImVec2(plot.PlotRect.GetCenter().x, plot.CanvasRect.Min.y),col,plot.GetTitle());
2804 }
2805
2806 // render x ticks
2807 int count_B = 0, count_T = 0;
2808 for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) {
2809 const ImPlotAxis& ax = plot.XAxis(i);
2810 if (!ax.Enabled)
2811 continue;
2812 const ImPlotTicker& tkr = ax.Ticker;
2813 const bool opp = ax.IsOpposite();
2814 const bool aux = ((opp && count_T > 0)||(!opp && count_B > 0));
2815 if (ax.HasTickMarks()) {
2816 const float direction = opp ? 1.0f : -1.0f;
2817 for (int j = 0; j < tkr.TickCount(); ++j) {
2818 const ImPlotTick& tk = tkr.Ticks[j];
2819 if (tk.Level != 0 || tk.PixelPos < plot.PlotRect.Min.x || tk.PixelPos > plot.PlotRect.Max.x)
2820 continue;
2821 const ImVec2 start(tk.PixelPos, ax.Datum1);
2822 const float len = (!aux && tk.Major) ? gp.Style.MajorTickLen.x : gp.Style.MinorTickLen.x;
2823 const float thk = (!aux && tk.Major) ? gp.Style.MajorTickSize.x : gp.Style.MinorTickSize.x;
2824 DrawList.AddLine(start, start + ImVec2(0,direction*len), ax.ColorTick, thk);
2825 }
2826 if (aux || !render_border)
2827 DrawList.AddLine(ImVec2(plot.PlotRect.Min.x,ax.Datum1), ImVec2(plot.PlotRect.Max.x,ax.Datum1), ax.ColorTick, gp.Style.MinorTickSize.x);
2828 }
2829 count_B += !opp;
2830 count_T += opp;
2831 }
2832
2833 // render y ticks
2834 int count_L = 0, count_R = 0;
2835 for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) {
2836 const ImPlotAxis& ax = plot.YAxis(i);
2837 if (!ax.Enabled)
2838 continue;
2839 const ImPlotTicker& tkr = ax.Ticker;
2840 const bool opp = ax.IsOpposite();
2841 const bool aux = ((opp && count_R > 0)||(!opp && count_L > 0));
2842 if (ax.HasTickMarks()) {
2843 const float direction = opp ? -1.0f : 1.0f;
2844 for (int j = 0; j < tkr.TickCount(); ++j) {
2845 const ImPlotTick& tk = tkr.Ticks[j];
2846 if (tk.Level != 0 || tk.PixelPos < plot.PlotRect.Min.y || tk.PixelPos > plot.PlotRect.Max.y)
2847 continue;
2848 const ImVec2 start(ax.Datum1, tk.PixelPos);
2849 const float len = (!aux && tk.Major) ? gp.Style.MajorTickLen.y : gp.Style.MinorTickLen.y;
2850 const float thk = (!aux && tk.Major) ? gp.Style.MajorTickSize.y : gp.Style.MinorTickSize.y;
2851 DrawList.AddLine(start, start + ImVec2(direction*len,0), ax.ColorTick, thk);
2852 }
2853 if (aux || !render_border)
2854 DrawList.AddLine(ImVec2(ax.Datum1, plot.PlotRect.Min.y), ImVec2(ax.Datum1, plot.PlotRect.Max.y), ax.ColorTick, gp.Style.MinorTickSize.y);
2855 }
2856 count_L += !opp;
2857 count_R += opp;
2858 }
2859 ImGui::PopClipRect();
2860
2861 // render annotations
2862 PushPlotClipRect();
2863 for (int i = 0; i < gp.Annotations.Size; ++i) {
2864 const char* txt = gp.Annotations.GetText(i);
2865 ImPlotAnnotation& an = gp.Annotations.Annotations[i];
2866 const ImVec2 txt_size = ImGui::CalcTextSize(txt);
2867 const ImVec2 size = txt_size + gp.Style.AnnotationPadding * 2;
2868 ImVec2 pos = an.Pos;
2869 if (an.Offset.x == 0)
2870 pos.x -= size.x / 2;
2871 else if (an.Offset.x > 0)
2872 pos.x += an.Offset.x;
2873 else
2874 pos.x -= size.x - an.Offset.x;
2875 if (an.Offset.y == 0)
2876 pos.y -= size.y / 2;
2877 else if (an.Offset.y > 0)
2878 pos.y += an.Offset.y;
2879 else
2880 pos.y -= size.y - an.Offset.y;
2881 if (an.Clamp)
2882 pos = ClampLabelPos(pos, size, plot.PlotRect.Min, plot.PlotRect.Max);
2883 ImRect rect(pos,pos+size);
2884 if (an.Offset.x != 0 || an.Offset.y != 0) {
2885 ImVec2 corners[4] = {rect.GetTL(), rect.GetTR(), rect.GetBR(), rect.GetBL()};
2886 int min_corner = 0;
2887 float min_len = FLT_MAX;
2888 for (int c = 0; c < 4; ++c) {
2889 float len = ImLengthSqr(an.Pos - corners[c]);
2890 if (len < min_len) {
2891 min_corner = c;
2892 min_len = len;
2893 }
2894 }
2895 DrawList.AddLine(an.Pos, corners[min_corner], an.ColorBg);
2896 }
2897 DrawList.AddRectFilled(rect.Min, rect.Max, an.ColorBg);
2898 DrawList.AddText(pos + gp.Style.AnnotationPadding, an.ColorFg, txt);
2899 }
2900
2901 // render selection
2902 if (plot.Selected)
2903 RenderSelectionRect(DrawList, plot.SelectRect.Min + plot.PlotRect.Min, plot.SelectRect.Max + plot.PlotRect.Min, GetStyleColorVec4(ImPlotCol_Selection));
2904
2905 // render crosshairs
2906 if (ImHasFlag(plot.Flags, ImPlotFlags_Crosshairs) && plot.Hovered && !(any_x_held || any_y_held) && !plot.Selecting && !plot.Items.Legend.Hovered) {
2907 ImGui::SetMouseCursor(ImGuiMouseCursor_None);
2908 ImVec2 xy = IO.MousePos;
2909 ImVec2 h1(plot.PlotRect.Min.x, xy.y);
2910 ImVec2 h2(xy.x - 5, xy.y);
2911 ImVec2 h3(xy.x + 5, xy.y);
2912 ImVec2 h4(plot.PlotRect.Max.x, xy.y);
2913 ImVec2 v1(xy.x, plot.PlotRect.Min.y);
2914 ImVec2 v2(xy.x, xy.y - 5);
2915 ImVec2 v3(xy.x, xy.y + 5);
2916 ImVec2 v4(xy.x, plot.PlotRect.Max.y);
2917 ImU32 col = GetStyleColorU32(ImPlotCol_Crosshairs);
2918 DrawList.AddLine(h1, h2, col);
2919 DrawList.AddLine(h3, h4, col);
2920 DrawList.AddLine(v1, v2, col);
2921 DrawList.AddLine(v3, v4, col);
2922 }
2923
2924 // render mouse pos
2925 if (!ImHasFlag(plot.Flags, ImPlotFlags_NoMouseText) && (plot.Hovered || ImHasFlag(plot.MouseTextFlags, ImPlotMouseTextFlags_ShowAlways))) {
2926
2927 const bool no_aux = ImHasFlag(plot.MouseTextFlags, ImPlotMouseTextFlags_NoAuxAxes);
2928 const bool no_fmt = ImHasFlag(plot.MouseTextFlags, ImPlotMouseTextFlags_NoFormat);
2929
2930 ImGuiTextBuffer& builder = gp.MousePosStringBuilder;
2931 builder.Buf.shrink(0);
2932 char buff[IMPLOT_LABEL_MAX_SIZE];
2933
2934 const int num_x = no_aux ? 1 : IMPLOT_NUM_X_AXES;
2935 for (int i = 0; i < num_x; ++i) {
2936 ImPlotAxis& x_axis = plot.XAxis(i);
2937 if (!x_axis.Enabled)
2938 continue;
2939 if (i > 0)
2940 builder.append(", (");
2941 double v = x_axis.PixelsToPlot(IO.MousePos.x);
2942 if (no_fmt)
2943 Formatter_Default(v,buff,IMPLOT_LABEL_MAX_SIZE,(void*)IMPLOT_LABEL_FORMAT);
2944 else
2945 LabelAxisValue(x_axis,v,buff,IMPLOT_LABEL_MAX_SIZE,true);
2946 builder.append(buff);
2947 if (i > 0)
2948 builder.append(")");
2949 }
2950 builder.append(", ");
2951 const int num_y = no_aux ? 1 : IMPLOT_NUM_Y_AXES;
2952 for (int i = 0; i < num_y; ++i) {
2953 ImPlotAxis& y_axis = plot.YAxis(i);
2954 if (!y_axis.Enabled)
2955 continue;
2956 if (i > 0)
2957 builder.append(", (");
2958 double v = y_axis.PixelsToPlot(IO.MousePos.y);
2959 if (no_fmt)
2960 Formatter_Default(v,buff,IMPLOT_LABEL_MAX_SIZE,(void*)IMPLOT_LABEL_FORMAT);
2961 else
2962 LabelAxisValue(y_axis,v,buff,IMPLOT_LABEL_MAX_SIZE,true);
2963 builder.append(buff);
2964 if (i > 0)
2965 builder.append(")");
2966 }
2967
2968 if (!builder.empty()) {
2969 const ImVec2 size = ImGui::CalcTextSize(builder.c_str());
2970 const ImVec2 pos = GetLocationPos(plot.PlotRect, size, plot.MouseTextLocation, gp.Style.MousePosPadding);
2971 DrawList.AddText(pos, GetStyleColorU32(ImPlotCol_InlayText), builder.c_str());
2972 }
2973 }
2974 PopPlotClipRect();
2975
2976 // axis side switch
2977 if (!plot.Held) {
2978 ImVec2 mouse_pos = ImGui::GetIO().MousePos;
2979 ImRect trigger_rect = plot.PlotRect;
2980 trigger_rect.Expand(-10);
2981 for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) {
2982 ImPlotAxis& x_axis = plot.XAxis(i);
2983 if (ImHasFlag(x_axis.Flags, ImPlotAxisFlags_NoSideSwitch))
2984 continue;
2985 if (x_axis.Held && plot.PlotRect.Contains(mouse_pos)) {
2986 const bool opp = ImHasFlag(x_axis.Flags, ImPlotAxisFlags_Opposite);
2987 if (!opp) {
2988 ImRect rect(plot.PlotRect.Min.x - 5, plot.PlotRect.Min.y - 5,
2989 plot.PlotRect.Max.x + 5, plot.PlotRect.Min.y + 5);
2990 if (mouse_pos.y < plot.PlotRect.Max.y - 10)
2991 DrawList.AddRectFilled(rect.Min, rect.Max, x_axis.ColorHov);
2992 if (rect.Contains(mouse_pos))
2993 x_axis.Flags |= ImPlotAxisFlags_Opposite;
2994 }
2995 else {
2996 ImRect rect(plot.PlotRect.Min.x - 5, plot.PlotRect.Max.y - 5,
2997 plot.PlotRect.Max.x + 5, plot.PlotRect.Max.y + 5);
2998 if (mouse_pos.y > plot.PlotRect.Min.y + 10)
2999 DrawList.AddRectFilled(rect.Min, rect.Max, x_axis.ColorHov);
3000 if (rect.Contains(mouse_pos))
3001 x_axis.Flags &= ~ImPlotAxisFlags_Opposite;
3002 }
3003 }
3004 }
3005 for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) {
3006 ImPlotAxis& y_axis = plot.YAxis(i);
3007 if (ImHasFlag(y_axis.Flags, ImPlotAxisFlags_NoSideSwitch))
3008 continue;
3009 if (y_axis.Held && plot.PlotRect.Contains(mouse_pos)) {
3010 const bool opp = ImHasFlag(y_axis.Flags, ImPlotAxisFlags_Opposite);
3011 if (!opp) {
3012 ImRect rect(plot.PlotRect.Max.x - 5, plot.PlotRect.Min.y - 5,
3013 plot.PlotRect.Max.x + 5, plot.PlotRect.Max.y + 5);
3014 if (mouse_pos.x > plot.PlotRect.Min.x + 10)
3015 DrawList.AddRectFilled(rect.Min, rect.Max, y_axis.ColorHov);
3016 if (rect.Contains(mouse_pos))
3017 y_axis.Flags |= ImPlotAxisFlags_Opposite;
3018 }
3019 else {
3020 ImRect rect(plot.PlotRect.Min.x - 5, plot.PlotRect.Min.y - 5,
3021 plot.PlotRect.Min.x + 5, plot.PlotRect.Max.y + 5);
3022 if (mouse_pos.x < plot.PlotRect.Max.x - 10)
3023 DrawList.AddRectFilled(rect.Min, rect.Max, y_axis.ColorHov);
3024 if (rect.Contains(mouse_pos))
3025 y_axis.Flags &= ~ImPlotAxisFlags_Opposite;
3026 }
3027 }
3028 }
3029 }
3030
3031 // reset legend hovers
3032 plot.Items.Legend.Hovered = false;
3033 for (int i = 0; i < plot.Items.GetItemCount(); ++i)
3034 plot.Items.GetItemByIndex(i)->LegendHovered = false;
3035 // render legend
3036 if (!ImHasFlag(plot.Flags, ImPlotFlags_NoLegend) && plot.Items.GetLegendCount() > 0) {
3037 ImPlotLegend& legend = plot.Items.Legend;
3038 const bool legend_out = ImHasFlag(legend.Flags, ImPlotLegendFlags_Outside);
3039 const bool legend_horz = ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal);
3040 const ImVec2 legend_size = CalcLegendSize(plot.Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !legend_horz);
3041 const ImVec2 legend_pos = GetLocationPos(legend_out ? plot.FrameRect : plot.PlotRect,
3042 legend_size,
3043 legend.Location,
3044 legend_out ? gp.Style.PlotPadding : gp.Style.LegendPadding);
3045 legend.Rect = ImRect(legend_pos, legend_pos + legend_size);
3046 legend.RectClamped = legend.Rect;
3047 const bool legend_scrollable = ClampLegendRect(legend.RectClamped,
3048 legend_out ? plot.FrameRect : plot.PlotRect,
3049 legend_out ? gp.Style.PlotPadding : gp.Style.LegendPadding
3050 );
3051 const ImGuiButtonFlags legend_button_flags = ImGuiButtonFlags_AllowOverlap
3052 | ImGuiButtonFlags_PressedOnClick
3053 | ImGuiButtonFlags_PressedOnDoubleClick
3054 | ImGuiButtonFlags_MouseButtonLeft
3055 | ImGuiButtonFlags_MouseButtonRight
3056 | ImGuiButtonFlags_MouseButtonMiddle
3057 | ImGuiButtonFlags_FlattenChildren;
3058 ImGui::KeepAliveID(plot.Items.ID);
3059 ImGui::ButtonBehavior(legend.RectClamped, plot.Items.ID, &legend.Hovered, &legend.Held, legend_button_flags);
3060 legend.Hovered = legend.Hovered || (ImGui::IsWindowHovered() && legend.RectClamped.Contains(IO.MousePos));
3061
3062 if (legend_scrollable) {
3063 if (legend.Hovered) {
3064 ImGui::SetKeyOwner(ImGuiKey_MouseWheelY, plot.Items.ID);
3065 if (IO.MouseWheel != 0.0f) {
3066 ImVec2 max_step = legend.Rect.GetSize() * 0.67f;
3067 float font_size = ImGui::GetCurrentWindow()->CalcFontSize();
3068 float scroll_step = ImFloor(ImMin(2 * font_size, max_step.x));
3069 legend.Scroll.x += scroll_step * IO.MouseWheel;
3070 legend.Scroll.y += scroll_step * IO.MouseWheel;
3071 }
3072 }
3073 const ImVec2 min_scroll_offset = legend.RectClamped.GetSize() - legend.Rect.GetSize();
3074 legend.Scroll.x = ImClamp(legend.Scroll.x, min_scroll_offset.x, 0.0f);
3075 legend.Scroll.y = ImClamp(legend.Scroll.y, min_scroll_offset.y, 0.0f);
3076 const ImVec2 scroll_offset = legend_horz ? ImVec2(legend.Scroll.x, 0) : ImVec2(0, legend.Scroll.y);
3077 ImVec2 legend_offset = legend.RectClamped.Min - legend.Rect.Min + scroll_offset;
3078 legend.Rect.Min += legend_offset;
3079 legend.Rect.Max += legend_offset;
3080 } else {
3081 legend.Scroll = ImVec2(0,0);
3082 }
3083
3084 const ImU32 col_bg = GetStyleColorU32(ImPlotCol_LegendBg);
3085 const ImU32 col_bd = GetStyleColorU32(ImPlotCol_LegendBorder);
3086 ImGui::PushClipRect(legend.RectClamped.Min, legend.RectClamped.Max, true);
3087 DrawList.AddRectFilled(legend.RectClamped.Min, legend.RectClamped.Max, col_bg);
3088 bool legend_contextable = ShowLegendEntries(plot.Items, legend.Rect, legend.Hovered, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !legend_horz, DrawList)
3089 && !ImHasFlag(legend.Flags, ImPlotLegendFlags_NoMenus);
3090 DrawList.AddRect(legend.RectClamped.Min, legend.RectClamped.Max, col_bd);
3091 ImGui::PopClipRect();
3092
3093 // main ctx menu
3094 if (gp.OpenContextThisFrame && legend_contextable && !ImHasFlag(plot.Flags, ImPlotFlags_NoMenus))
3095 ImGui::OpenPopup("##LegendContext");
3096
3097 if (ImGui::BeginPopup("##LegendContext")) {
3098 ImGui::Text("Legend"); ImGui::Separator();
3099 if (ShowLegendContextMenu(legend, !ImHasFlag(plot.Flags, ImPlotFlags_NoLegend)))
3100 ImFlipFlag(plot.Flags, ImPlotFlags_NoLegend);
3101 ImGui::EndPopup();
3102 }
3103 }
3104 else {
3105 plot.Items.Legend.Rect = ImRect();
3106 }
3107
3108 // render border
3109 if (render_border)
3110 DrawList.AddRect(plot.PlotRect.Min, plot.PlotRect.Max, GetStyleColorU32(ImPlotCol_PlotBorder), 0, ImDrawFlags_RoundCornersAll, gp.Style.PlotBorderSize);
3111
3112 // render tags
3113 for (int i = 0; i < gp.Tags.Size; ++i) {
3114 ImPlotTag& tag = gp.Tags.Tags[i];
3115 ImPlotAxis& axis = plot.Axes[tag.Axis];
3116 if (!axis.Enabled || !axis.Range.Contains(tag.Value))
3117 continue;
3118 const char* txt = gp.Tags.GetText(i);
3119 ImVec2 text_size = ImGui::CalcTextSize(txt);
3120 ImVec2 size = text_size + gp.Style.AnnotationPadding * 2;
3121 ImVec2 pos;
3122 axis.Ticker.OverrideSizeLate(size);
3123 float pix = IM_ROUND(axis.PlotToPixels(tag.Value));
3124 if (axis.Vertical) {
3125 if (axis.IsOpposite()) {
3126 pos = ImVec2(axis.Datum1 + gp.Style.LabelPadding.x, pix - size.y * 0.5f);
3127 DrawList.AddTriangleFilled(ImVec2(axis.Datum1,pix), pos, pos + ImVec2(0,size.y), tag.ColorBg);
3128 }
3129 else {
3130 pos = ImVec2(axis.Datum1 - size.x - gp.Style.LabelPadding.x, pix - size.y * 0.5f);
3131 DrawList.AddTriangleFilled(pos + ImVec2(size.x,0), ImVec2(axis.Datum1,pix), pos+size, tag.ColorBg);
3132 }
3133 }
3134 else {
3135 if (axis.IsOpposite()) {
3136 pos = ImVec2(pix - size.x * 0.5f, axis.Datum1 - size.y - gp.Style.LabelPadding.y );
3137 DrawList.AddTriangleFilled(pos + ImVec2(0,size.y), pos + size, ImVec2(pix,axis.Datum1), tag.ColorBg);
3138 }
3139 else {
3140 pos = ImVec2(pix - size.x * 0.5f, axis.Datum1 + gp.Style.LabelPadding.y);
3141 DrawList.AddTriangleFilled(pos, ImVec2(pix,axis.Datum1), pos + ImVec2(size.x, 0), tag.ColorBg);
3142 }
3143 }
3144 DrawList.AddRectFilled(pos,pos+size,tag.ColorBg);
3145 DrawList.AddText(pos+gp.Style.AnnotationPadding,tag.ColorFg,txt);
3146 }
3147
3148 // FIT DATA --------------------------------------------------------------
3149 const bool axis_equal = ImHasFlag(plot.Flags, ImPlotFlags_Equal);
3150 if (plot.FitThisFrame) {
3151 for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) {
3152 ImPlotAxis& x_axis = plot.XAxis(i);
3153 if (x_axis.FitThisFrame) {
3154 x_axis.ApplyFit(gp.Style.FitPadding.x);
3155 if (axis_equal && x_axis.OrthoAxis != nullptr) {
3156 double aspect = x_axis.GetAspect();
3157 ImPlotAxis& y_axis = *x_axis.OrthoAxis;
3158 if (y_axis.FitThisFrame) {
3159 y_axis.ApplyFit(gp.Style.FitPadding.y);
3160 y_axis.FitThisFrame = false;
3161 aspect = ImMax(aspect, y_axis.GetAspect());
3162 }
3163 x_axis.SetAspect(aspect);
3164 y_axis.SetAspect(aspect);
3165 }
3166 }
3167 }
3168 for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) {
3169 ImPlotAxis& y_axis = plot.YAxis(i);
3170 if (y_axis.FitThisFrame) {
3171 y_axis.ApplyFit(gp.Style.FitPadding.y);
3172 if (axis_equal && y_axis.OrthoAxis != nullptr) {
3173 double aspect = y_axis.GetAspect();
3174 ImPlotAxis& x_axis = *y_axis.OrthoAxis;
3175 if (x_axis.FitThisFrame) {
3176 x_axis.ApplyFit(gp.Style.FitPadding.x);
3177 x_axis.FitThisFrame = false;
3178 aspect = ImMax(x_axis.GetAspect(), aspect);
3179 }
3180 x_axis.SetAspect(aspect);
3181 y_axis.SetAspect(aspect);
3182 }
3183 }
3184 }
3185 plot.FitThisFrame = false;
3186 }
3187
3188 // CONTEXT MENUS -----------------------------------------------------------
3189
3190 ImGui::PushOverrideID(plot.ID);
3191
3192 const bool can_ctx = gp.OpenContextThisFrame &&
3193 !ImHasFlag(plot.Flags, ImPlotFlags_NoMenus) &&
3194 !plot.Items.Legend.Hovered;
3195
3196
3197
3198 // main ctx menu
3199 if (can_ctx && plot.Hovered)
3200 ImGui::OpenPopup("##PlotContext");
3201 if (ImGui::BeginPopup("##PlotContext")) {
3202 ShowPlotContextMenu(plot);
3203 ImGui::EndPopup();
3204 }
3205
3206 // axes ctx menus
3207 for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) {
3208 ImGui::PushID(i);
3209 ImPlotAxis& x_axis = plot.XAxis(i);
3210 if (can_ctx && x_axis.Hovered && x_axis.HasMenus())
3211 ImGui::OpenPopup("##XContext");
3212 if (ImGui::BeginPopup("##XContext")) {
3213 ImGui::Text(x_axis.HasLabel() ? plot.GetAxisLabel(x_axis) : i == 0 ? "X-Axis" : "X-Axis %d", i + 1);
3214 ImGui::Separator();
3215 ShowAxisContextMenu(x_axis, axis_equal ? x_axis.OrthoAxis : nullptr, true);
3216 ImGui::EndPopup();
3217 }
3218 ImGui::PopID();
3219 }
3220 for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) {
3221 ImGui::PushID(i);
3222 ImPlotAxis& y_axis = plot.YAxis(i);
3223 if (can_ctx && y_axis.Hovered && y_axis.HasMenus())
3224 ImGui::OpenPopup("##YContext");
3225 if (ImGui::BeginPopup("##YContext")) {
3226 ImGui::Text(y_axis.HasLabel() ? plot.GetAxisLabel(y_axis) : i == 0 ? "Y-Axis" : "Y-Axis %d", i + 1);
3227 ImGui::Separator();
3228 ShowAxisContextMenu(y_axis, axis_equal ? y_axis.OrthoAxis : nullptr, false);
3229 ImGui::EndPopup();
3230 }
3231 ImGui::PopID();
3232 }
3233 ImGui::PopID();
3234
3235 // LINKED AXES ------------------------------------------------------------
3236
3237 for (int i = 0; i < ImAxis_COUNT; ++i)
3238 plot.Axes[i].PushLinks();
3239
3240
3241 // CLEANUP ----------------------------------------------------------------
3242
3243 // remove items
3244 if (gp.CurrentItems == &plot.Items)
3245 gp.CurrentItems = nullptr;
3246 // reset the plot items for the next frame
3247 for (int i = 0; i < plot.Items.GetItemCount(); ++i) {
3248 plot.Items.GetItemByIndex(i)->SeenThisFrame = false;
3249 }
3250
3251 // mark the plot as initialized, i.e. having made it through one frame completely
3252 plot.Initialized = true;
3253 // Pop ImGui::PushID at the end of BeginPlot
3254 ImGui::PopID();
3255 // Reset context for next plot
3256 ResetCtxForNextPlot(GImPlot);
3257
3258 // setup next subplot
3259 if (gp.CurrentSubplot != nullptr) {
3260 ImGui::PopID();
3261 SubplotNextCell();
3262 }
3263}
3264
3265//-----------------------------------------------------------------------------
3266// BEGIN/END SUBPLOT
3267//-----------------------------------------------------------------------------
3268
3269static const float SUBPLOT_BORDER_SIZE = 1.0f;
3270static const float SUBPLOT_SPLITTER_HALF_THICKNESS = 4.0f;
3271static const float SUBPLOT_SPLITTER_FEEDBACK_TIMER = 0.06f;
3272
3273void SubplotSetCell(int row, int col) {
3274 ImPlotContext& gp = *GImPlot;
3275 ImPlotSubplot& subplot = *gp.CurrentSubplot;
3276 if (row >= subplot.Rows || col >= subplot.Cols)
3277 return;
3278 float xoff = 0;
3279 float yoff = 0;
3280 for (int c = 0; c < col; ++c)
3281 xoff += subplot.ColRatios[c];
3282 for (int r = 0; r < row; ++r)
3283 yoff += subplot.RowRatios[r];
3284 const ImVec2 grid_size = subplot.GridRect.GetSize();
3285 ImVec2 cpos = subplot.GridRect.Min + ImVec2(xoff*grid_size.x,yoff*grid_size.y);
3286 cpos.x = IM_ROUND(cpos.x);
3287 cpos.y = IM_ROUND(cpos.y);
3288 ImGui::GetCurrentWindow()->DC.CursorPos = cpos;
3289 // set cell size
3290 subplot.CellSize.x = IM_ROUND(subplot.GridRect.GetWidth() * subplot.ColRatios[col]);
3291 subplot.CellSize.y = IM_ROUND(subplot.GridRect.GetHeight() * subplot.RowRatios[row]);
3292 // setup links
3293 const bool lx = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllX);
3294 const bool ly = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllY);
3295 const bool lr = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkRows);
3296 const bool lc = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkCols);
3297
3298 SetNextAxisLinks(ImAxis_X1, lx ? &subplot.ColLinkData[0].Min : lc ? &subplot.ColLinkData[col].Min : nullptr,
3299 lx ? &subplot.ColLinkData[0].Max : lc ? &subplot.ColLinkData[col].Max : nullptr);
3300 SetNextAxisLinks(ImAxis_Y1, ly ? &subplot.RowLinkData[0].Min : lr ? &subplot.RowLinkData[row].Min : nullptr,
3301 ly ? &subplot.RowLinkData[0].Max : lr ? &subplot.RowLinkData[row].Max : nullptr);
3302 // setup alignment
3303 if (!ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoAlign)) {
3304 gp.CurrentAlignmentH = &subplot.RowAlignmentData[row];
3305 gp.CurrentAlignmentV = &subplot.ColAlignmentData[col];
3306 }
3307 // set idx
3308 if (ImHasFlag(subplot.Flags, ImPlotSubplotFlags_ColMajor))
3309 subplot.CurrentIdx = col * subplot.Rows + row;
3310 else
3311 subplot.CurrentIdx = row * subplot.Cols + col;
3312}
3313
3314void SubplotSetCell(int idx) {
3315 ImPlotContext& gp = *GImPlot;
3316 ImPlotSubplot& subplot = *gp.CurrentSubplot;
3317 if (idx >= subplot.Rows * subplot.Cols)
3318 return;
3319 int row = 0, col = 0;
3320 if (ImHasFlag(subplot.Flags, ImPlotSubplotFlags_ColMajor)) {
3321 row = idx % subplot.Rows;
3322 col = idx / subplot.Rows;
3323 }
3324 else {
3325 row = idx / subplot.Cols;
3326 col = idx % subplot.Cols;
3327 }
3328 return SubplotSetCell(row, col);
3329}
3330
3331void SubplotNextCell() {
3332 ImPlotContext& gp = *GImPlot;
3333 ImPlotSubplot& subplot = *gp.CurrentSubplot;
3334 SubplotSetCell(++subplot.CurrentIdx);
3335}
3336
3337bool BeginSubplots(const char* title, int rows, int cols, const ImVec2& size, ImPlotSubplotFlags flags, float* row_sizes, float* col_sizes) {
3338 IM_ASSERT_USER_ERROR(rows > 0 && cols > 0, "Invalid sizing arguments!");
3339 IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?");
3340 ImPlotContext& gp = *GImPlot;
3341 IM_ASSERT_USER_ERROR(gp.CurrentSubplot == nullptr, "Mismatched BeginSubplots()/EndSubplots()!");
3342 ImGuiContext &G = *GImGui;
3343 ImGuiWindow * Window = G.CurrentWindow;
3344 if (Window->SkipItems)
3345 return false;
3346 const ImGuiID ID = Window->GetID(title);
3347 bool just_created = gp.Subplots.GetByKey(ID) == nullptr;
3348 gp.CurrentSubplot = gp.Subplots.GetOrAddByKey(ID);
3349 ImPlotSubplot& subplot = *gp.CurrentSubplot;
3350 subplot.ID = ID;
3351 subplot.Items.ID = ID - 1;
3352 subplot.HasTitle = ImGui::FindRenderedTextEnd(title, nullptr) != title;
3353 // push ID
3354 ImGui::PushID(ID);
3355
3356 if (just_created)
3357 subplot.Flags = flags;
3358 else if (flags != subplot.PreviousFlags)
3359 subplot.Flags = flags;
3360 subplot.PreviousFlags = flags;
3361
3362 // check for change in rows and cols
3363 if (subplot.Rows != rows || subplot.Cols != cols) {
3364 subplot.RowAlignmentData.resize(rows);
3365 subplot.RowLinkData.resize(rows);
3366 subplot.RowRatios.resize(rows);
3367 for (int r = 0; r < rows; ++r) {
3368 subplot.RowAlignmentData[r].Reset();
3369 subplot.RowLinkData[r] = ImPlotRange(0,1);
3370 subplot.RowRatios[r] = 1.0f / rows;
3371 }
3372 subplot.ColAlignmentData.resize(cols);
3373 subplot.ColLinkData.resize(cols);
3374 subplot.ColRatios.resize(cols);
3375 for (int c = 0; c < cols; ++c) {
3376 subplot.ColAlignmentData[c].Reset();
3377 subplot.ColLinkData[c] = ImPlotRange(0,1);
3378 subplot.ColRatios[c] = 1.0f / cols;
3379 }
3380 }
3381 // check incoming size requests
3382 float row_sum = 0, col_sum = 0;
3383 if (row_sizes != nullptr) {
3384 row_sum = ImSum(row_sizes, rows);
3385 for (int r = 0; r < rows; ++r)
3386 subplot.RowRatios[r] = row_sizes[r] / row_sum;
3387 }
3388 if (col_sizes != nullptr) {
3389 col_sum = ImSum(col_sizes, cols);
3390 for (int c = 0; c < cols; ++c)
3391 subplot.ColRatios[c] = col_sizes[c] / col_sum;
3392 }
3393 subplot.Rows = rows;
3394 subplot.Cols = cols;
3395
3396 // calc plot frame sizes
3397 ImVec2 title_size(0.0f, 0.0f);
3398 if (!ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoTitle))
3399 title_size = ImGui::CalcTextSize(title, nullptr, true);
3400 const float pad_top = title_size.x > 0.0f ? title_size.y + gp.Style.LabelPadding.y : 0;
3401 const ImVec2 half_pad = gp.Style.PlotPadding/2;
3402 const ImVec2 frame_size = ImGui::CalcItemSize(size, gp.Style.PlotDefaultSize.x, gp.Style.PlotDefaultSize.y);
3403 subplot.FrameRect = ImRect(Window->DC.CursorPos, Window->DC.CursorPos + frame_size);
3404 subplot.GridRect.Min = subplot.FrameRect.Min + half_pad + ImVec2(0,pad_top);
3405 subplot.GridRect.Max = subplot.FrameRect.Max - half_pad;
3406 subplot.FrameHovered = subplot.FrameRect.Contains(ImGui::GetMousePos()) && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows|ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
3407
3408 // outside legend adjustments (TODO: make function)
3409 const bool share_items = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_ShareItems);
3410 if (share_items)
3411 gp.CurrentItems = &subplot.Items;
3412 if (share_items && !ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoLegend) && subplot.Items.GetLegendCount() > 0) {
3413 ImPlotLegend& legend = subplot.Items.Legend;
3414 const bool horz = ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal);
3415 const ImVec2 legend_size = CalcLegendSize(subplot.Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !horz);
3416 const bool west = ImHasFlag(legend.Location, ImPlotLocation_West) && !ImHasFlag(legend.Location, ImPlotLocation_East);
3417 const bool east = ImHasFlag(legend.Location, ImPlotLocation_East) && !ImHasFlag(legend.Location, ImPlotLocation_West);
3418 const bool north = ImHasFlag(legend.Location, ImPlotLocation_North) && !ImHasFlag(legend.Location, ImPlotLocation_South);
3419 const bool south = ImHasFlag(legend.Location, ImPlotLocation_South) && !ImHasFlag(legend.Location, ImPlotLocation_North);
3420 if ((west && !horz) || (west && horz && !north && !south))
3421 subplot.GridRect.Min.x += (legend_size.x + gp.Style.LegendPadding.x);
3422 if ((east && !horz) || (east && horz && !north && !south))
3423 subplot.GridRect.Max.x -= (legend_size.x + gp.Style.LegendPadding.x);
3424 if ((north && horz) || (north && !horz && !west && !east))
3425 subplot.GridRect.Min.y += (legend_size.y + gp.Style.LegendPadding.y);
3426 if ((south && horz) || (south && !horz && !west && !east))
3427 subplot.GridRect.Max.y -= (legend_size.y + gp.Style.LegendPadding.y);
3428 }
3429
3430 // render single background frame
3431 ImGui::RenderFrame(subplot.FrameRect.Min, subplot.FrameRect.Max, GetStyleColorU32(ImPlotCol_FrameBg), true, ImGui::GetStyle().FrameRounding);
3432 // render title
3433 if (title_size.x > 0.0f && !ImHasFlag(subplot.Flags, ImPlotFlags_NoTitle)) {
3434 const ImU32 col = GetStyleColorU32(ImPlotCol_TitleText);
3435 AddTextCentered(ImGui::GetWindowDrawList(),ImVec2(subplot.GridRect.GetCenter().x, subplot.GridRect.Min.y - pad_top + half_pad.y),col,title);
3436 }
3437
3438 // render splitters
3439 if (!ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoResize)) {
3440 ImDrawList& DrawList = *ImGui::GetWindowDrawList();
3441 const ImU32 hov_col = ImGui::ColorConvertFloat4ToU32(GImGui->Style.Colors[ImGuiCol_SeparatorHovered]);
3442 const ImU32 act_col = ImGui::ColorConvertFloat4ToU32(GImGui->Style.Colors[ImGuiCol_SeparatorActive]);
3443 float xpos = subplot.GridRect.Min.x;
3444 float ypos = subplot.GridRect.Min.y;
3445 int separator = 1;
3446 // bool pass = false;
3447 for (int r = 0; r < subplot.Rows-1; ++r) {
3448 ypos += subplot.RowRatios[r] * subplot.GridRect.GetHeight();
3449 const ImGuiID sep_id = subplot.ID + separator;
3450 ImGui::KeepAliveID(sep_id);
3451 const ImRect sep_bb = ImRect(subplot.GridRect.Min.x, ypos-SUBPLOT_SPLITTER_HALF_THICKNESS, subplot.GridRect.Max.x, ypos+SUBPLOT_SPLITTER_HALF_THICKNESS);
3452 bool sep_hov = false, sep_hld = false;
3453 const bool sep_clk = ImGui::ButtonBehavior(sep_bb, sep_id, &sep_hov, &sep_hld, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick);
3454 if ((sep_hov && G.HoveredIdTimer > SUBPLOT_SPLITTER_FEEDBACK_TIMER) || sep_hld) {
3455 if (sep_clk && ImGui::IsMouseDoubleClicked(0)) {
3456 float p = (subplot.RowRatios[r] + subplot.RowRatios[r+1])/2;
3457 subplot.RowRatios[r] = subplot.RowRatios[r+1] = p;
3458 }
3459 if (sep_clk) {
3460 subplot.TempSizes[0] = subplot.RowRatios[r];
3461 subplot.TempSizes[1] = subplot.RowRatios[r+1];
3462 }
3463 if (sep_hld) {
3464 float dp = ImGui::GetMouseDragDelta(0).y / subplot.GridRect.GetHeight();
3465 if (subplot.TempSizes[0] + dp > 0.1f && subplot.TempSizes[1] - dp > 0.1f) {
3466 subplot.RowRatios[r] = subplot.TempSizes[0] + dp;
3467 subplot.RowRatios[r+1] = subplot.TempSizes[1] - dp;
3468 }
3469 }
3470 DrawList.AddLine(ImVec2(IM_ROUND(subplot.GridRect.Min.x),IM_ROUND(ypos)),
3471 ImVec2(IM_ROUND(subplot.GridRect.Max.x),IM_ROUND(ypos)),
3472 sep_hld ? act_col : hov_col, SUBPLOT_BORDER_SIZE);
3473 ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);
3474 }
3475 separator++;
3476 }
3477 for (int c = 0; c < subplot.Cols-1; ++c) {
3478 xpos += subplot.ColRatios[c] * subplot.GridRect.GetWidth();
3479 const ImGuiID sep_id = subplot.ID + separator;
3480 ImGui::KeepAliveID(sep_id);
3481 const ImRect sep_bb = ImRect(xpos-SUBPLOT_SPLITTER_HALF_THICKNESS, subplot.GridRect.Min.y, xpos+SUBPLOT_SPLITTER_HALF_THICKNESS, subplot.GridRect.Max.y);
3482 bool sep_hov = false, sep_hld = false;
3483 const bool sep_clk = ImGui::ButtonBehavior(sep_bb, sep_id, &sep_hov, &sep_hld, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick);
3484 if ((sep_hov && G.HoveredIdTimer > SUBPLOT_SPLITTER_FEEDBACK_TIMER) || sep_hld) {
3485 if (sep_clk && ImGui::IsMouseDoubleClicked(0)) {
3486 float p = (subplot.ColRatios[c] + subplot.ColRatios[c+1])/2;
3487 subplot.ColRatios[c] = subplot.ColRatios[c+1] = p;
3488 }
3489 if (sep_clk) {
3490 subplot.TempSizes[0] = subplot.ColRatios[c];
3491 subplot.TempSizes[1] = subplot.ColRatios[c+1];
3492 }
3493 if (sep_hld) {
3494 float dp = ImGui::GetMouseDragDelta(0).x / subplot.GridRect.GetWidth();
3495 if (subplot.TempSizes[0] + dp > 0.1f && subplot.TempSizes[1] - dp > 0.1f) {
3496 subplot.ColRatios[c] = subplot.TempSizes[0] + dp;
3497 subplot.ColRatios[c+1] = subplot.TempSizes[1] - dp;
3498 }
3499 }
3500 DrawList.AddLine(ImVec2(IM_ROUND(xpos),IM_ROUND(subplot.GridRect.Min.y)),
3501 ImVec2(IM_ROUND(xpos),IM_ROUND(subplot.GridRect.Max.y)),
3502 sep_hld ? act_col : hov_col, SUBPLOT_BORDER_SIZE);
3503 ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
3504 }
3505 separator++;
3506 }
3507 }
3508
3509 // set outgoing sizes
3510 if (row_sizes != nullptr) {
3511 for (int r = 0; r < rows; ++r)
3512 row_sizes[r] = subplot.RowRatios[r] * row_sum;
3513 }
3514 if (col_sizes != nullptr) {
3515 for (int c = 0; c < cols; ++c)
3516 col_sizes[c] = subplot.ColRatios[c] * col_sum;
3517 }
3518
3519 // push styling
3520 PushStyleColor(ImPlotCol_FrameBg, IM_COL32_BLACK_TRANS);
3521 PushStyleVar(ImPlotStyleVar_PlotPadding, half_pad);
3522 PushStyleVar(ImPlotStyleVar_PlotMinSize, ImVec2(0,0));
3523 ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize,0);
3524
3525 // set initial cursor pos
3526 Window->DC.CursorPos = subplot.GridRect.Min;
3527 // begin alignments
3528 for (int r = 0; r < subplot.Rows; ++r)
3529 subplot.RowAlignmentData[r].Begin();
3530 for (int c = 0; c < subplot.Cols; ++c)
3531 subplot.ColAlignmentData[c].Begin();
3532 // clear legend data
3533 subplot.Items.Legend.Reset();
3534 // Setup first subplot
3535 SubplotSetCell(0,0);
3536 return true;
3537}
3538
3539void EndSubplots() {
3540 IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?");
3541 ImPlotContext& gp = *GImPlot;
3542 IM_ASSERT_USER_ERROR(gp.CurrentSubplot != nullptr, "Mismatched BeginSubplots()/EndSubplots()!");
3543 ImPlotSubplot& subplot = *gp.CurrentSubplot;
3544 const ImGuiIO& IO = ImGui::GetIO();
3545 // set alignments
3546 for (int r = 0; r < subplot.Rows; ++r)
3547 subplot.RowAlignmentData[r].End();
3548 for (int c = 0; c < subplot.Cols; ++c)
3549 subplot.ColAlignmentData[c].End();
3550 // pop styling
3551 PopStyleColor();
3552 PopStyleVar();
3553 PopStyleVar();
3554 ImGui::PopStyleVar();
3555 // legend
3556 subplot.Items.Legend.Hovered = false;
3557 for (int i = 0; i < subplot.Items.GetItemCount(); ++i)
3558 subplot.Items.GetItemByIndex(i)->LegendHovered = false;
3559 // render legend
3560 const bool share_items = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_ShareItems);
3561 ImDrawList& DrawList = *ImGui::GetWindowDrawList();
3562 if (share_items && !ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoLegend) && subplot.Items.GetLegendCount() > 0) {
3563 ImPlotLegend& legend = subplot.Items.Legend;
3564 const bool legend_horz = ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal);
3565 const ImVec2 legend_size = CalcLegendSize(subplot.Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !legend_horz);
3566 const ImVec2 legend_pos = GetLocationPos(subplot.FrameRect, legend_size, legend.Location, gp.Style.PlotPadding);
3567 legend.Rect = ImRect(legend_pos, legend_pos + legend_size);
3568 legend.RectClamped = legend.Rect;
3569 const bool legend_scrollable = ClampLegendRect(legend.RectClamped,subplot.FrameRect, gp.Style.PlotPadding);
3570 const ImGuiButtonFlags legend_button_flags = ImGuiButtonFlags_AllowOverlap
3571 | ImGuiButtonFlags_PressedOnClick
3572 | ImGuiButtonFlags_PressedOnDoubleClick
3573 | ImGuiButtonFlags_MouseButtonLeft
3574 | ImGuiButtonFlags_MouseButtonRight
3575 | ImGuiButtonFlags_MouseButtonMiddle
3576 | ImGuiButtonFlags_FlattenChildren;
3577 ImGui::KeepAliveID(subplot.Items.ID);
3578 ImGui::ButtonBehavior(legend.RectClamped, subplot.Items.ID, &legend.Hovered, &legend.Held, legend_button_flags);
3579 legend.Hovered = legend.Hovered || (subplot.FrameHovered && legend.RectClamped.Contains(ImGui::GetIO().MousePos));
3580
3581 if (legend_scrollable) {
3582 if (legend.Hovered) {
3583 ImGui::SetKeyOwner(ImGuiKey_MouseWheelY, subplot.Items.ID);
3584 if (IO.MouseWheel != 0.0f) {
3585 ImVec2 max_step = legend.Rect.GetSize() * 0.67f;
3586 float font_size = ImGui::GetCurrentWindow()->CalcFontSize();
3587 float scroll_step = ImFloor(ImMin(2 * font_size, max_step.x));
3588 legend.Scroll.x += scroll_step * IO.MouseWheel;
3589 legend.Scroll.y += scroll_step * IO.MouseWheel;
3590 }
3591 }
3592 const ImVec2 min_scroll_offset = legend.RectClamped.GetSize() - legend.Rect.GetSize();
3593 legend.Scroll.x = ImClamp(legend.Scroll.x, min_scroll_offset.x, 0.0f);
3594 legend.Scroll.y = ImClamp(legend.Scroll.y, min_scroll_offset.y, 0.0f);
3595 const ImVec2 scroll_offset = legend_horz ? ImVec2(legend.Scroll.x, 0) : ImVec2(0, legend.Scroll.y);
3596 ImVec2 legend_offset = legend.RectClamped.Min - legend.Rect.Min + scroll_offset;
3597 legend.Rect.Min += legend_offset;
3598 legend.Rect.Max += legend_offset;
3599 } else {
3600 legend.Scroll = ImVec2(0,0);
3601 }
3602
3603 const ImU32 col_bg = GetStyleColorU32(ImPlotCol_LegendBg);
3604 const ImU32 col_bd = GetStyleColorU32(ImPlotCol_LegendBorder);
3605 ImGui::PushClipRect(legend.RectClamped.Min, legend.RectClamped.Max, true);
3606 DrawList.AddRectFilled(legend.RectClamped.Min, legend.RectClamped.Max, col_bg);
3607 bool legend_contextable = ShowLegendEntries(subplot.Items, legend.Rect, legend.Hovered, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !legend_horz, DrawList)
3608 && !ImHasFlag(legend.Flags, ImPlotLegendFlags_NoMenus);
3609 DrawList.AddRect(legend.RectClamped.Min, legend.RectClamped.Max, col_bd);
3610 ImGui::PopClipRect();
3611
3612 if (legend_contextable && !ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoMenus) && ImGui::GetIO().MouseReleased[gp.InputMap.Menu])
3613 ImGui::OpenPopup("##LegendContext");
3614 if (ImGui::BeginPopup("##LegendContext")) {
3615 ImGui::Text("Legend"); ImGui::Separator();
3616 if (ShowLegendContextMenu(legend, !ImHasFlag(subplot.Flags, ImPlotFlags_NoLegend)))
3617 ImFlipFlag(subplot.Flags, ImPlotFlags_NoLegend);
3618 ImGui::EndPopup();
3619 }
3620 }
3621 else {
3622 subplot.Items.Legend.Rect = ImRect();
3623 }
3624 // remove items
3625 if (gp.CurrentItems == &subplot.Items)
3626 gp.CurrentItems = nullptr;
3627 // reset the plot items for the next frame (TODO: put this elswhere)
3628 for (int i = 0; i < subplot.Items.GetItemCount(); ++i) {
3629 subplot.Items.GetItemByIndex(i)->SeenThisFrame = false;
3630 }
3631 // pop id
3632 ImGui::PopID();
3633 // set DC back correctly
3634 GImGui->CurrentWindow->DC.CursorPos = subplot.FrameRect.Min;
3635 ImGui::Dummy(subplot.FrameRect.GetSize());
3636 ResetCtxForNextSubplot(GImPlot);
3637
3638}
3639
3640//-----------------------------------------------------------------------------
3641// [SECTION] Plot Utils
3642//-----------------------------------------------------------------------------
3643
3644void SetAxis(ImAxis axis) {
3645 ImPlotContext& gp = *GImPlot;
3646 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "SetAxis() needs to be called between BeginPlot() and EndPlot()!");
3647 IM_ASSERT_USER_ERROR(axis >= ImAxis_X1 && axis < ImAxis_COUNT, "Axis index out of bounds!");
3648 IM_ASSERT_USER_ERROR(gp.CurrentPlot->Axes[axis].Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?");
3649 SetupLock();
3650 if (axis < ImAxis_Y1)
3651 gp.CurrentPlot->CurrentX = axis;
3652 else
3653 gp.CurrentPlot->CurrentY = axis;
3654}
3655
3656void SetAxes(ImAxis x_idx, ImAxis y_idx) {
3657 ImPlotContext& gp = *GImPlot;
3658 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "SetAxes() needs to be called between BeginPlot() and EndPlot()!");
3659 IM_ASSERT_USER_ERROR(x_idx >= ImAxis_X1 && x_idx < ImAxis_Y1, "X-Axis index out of bounds!");
3660 IM_ASSERT_USER_ERROR(y_idx >= ImAxis_Y1 && y_idx < ImAxis_COUNT, "Y-Axis index out of bounds!");
3661 IM_ASSERT_USER_ERROR(gp.CurrentPlot->Axes[x_idx].Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?");
3662 IM_ASSERT_USER_ERROR(gp.CurrentPlot->Axes[y_idx].Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?");
3663 SetupLock();
3664 gp.CurrentPlot->CurrentX = x_idx;
3665 gp.CurrentPlot->CurrentY = y_idx;
3666}
3667
3668ImPlotPoint PixelsToPlot(float x, float y, ImAxis x_idx, ImAxis y_idx) {
3669 ImPlotContext& gp = *GImPlot;
3670 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PixelsToPlot() needs to be called between BeginPlot() and EndPlot()!");
3671 IM_ASSERT_USER_ERROR(x_idx == IMPLOT_AUTO || (x_idx >= ImAxis_X1 && x_idx < ImAxis_Y1), "X-Axis index out of bounds!");
3672 IM_ASSERT_USER_ERROR(y_idx == IMPLOT_AUTO || (y_idx >= ImAxis_Y1 && y_idx < ImAxis_COUNT), "Y-Axis index out of bounds!");
3673 SetupLock();
3674 ImPlotPlot& plot = *gp.CurrentPlot;
3675 ImPlotAxis& x_axis = x_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentX] : plot.Axes[x_idx];
3676 ImPlotAxis& y_axis = y_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentY] : plot.Axes[y_idx];
3677 return ImPlotPoint( x_axis.PixelsToPlot(x), y_axis.PixelsToPlot(y) );
3678}
3679
3680ImPlotPoint PixelsToPlot(const ImVec2& pix, ImAxis x_idx, ImAxis y_idx) {
3681 return PixelsToPlot(pix.x, pix.y, x_idx, y_idx);
3682}
3683
3684ImVec2 PlotToPixels(double x, double y, ImAxis x_idx, ImAxis y_idx) {
3685 ImPlotContext& gp = *GImPlot;
3686 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PlotToPixels() needs to be called between BeginPlot() and EndPlot()!");
3687 IM_ASSERT_USER_ERROR(x_idx == IMPLOT_AUTO || (x_idx >= ImAxis_X1 && x_idx < ImAxis_Y1), "X-Axis index out of bounds!");
3688 IM_ASSERT_USER_ERROR(y_idx == IMPLOT_AUTO || (y_idx >= ImAxis_Y1 && y_idx < ImAxis_COUNT), "Y-Axis index out of bounds!");
3689 SetupLock();
3690 ImPlotPlot& plot = *gp.CurrentPlot;
3691 ImPlotAxis& x_axis = x_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentX] : plot.Axes[x_idx];
3692 ImPlotAxis& y_axis = y_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentY] : plot.Axes[y_idx];
3693 return ImVec2( x_axis.PlotToPixels(x), y_axis.PlotToPixels(y) );
3694}
3695
3696ImVec2 PlotToPixels(const ImPlotPoint& plt, ImAxis x_idx, ImAxis y_idx) {
3697 return PlotToPixels(plt.x, plt.y, x_idx, y_idx);
3698}
3699
3700ImVec2 GetPlotPos() {
3701 ImPlotContext& gp = *GImPlot;
3702 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetPlotPos() needs to be called between BeginPlot() and EndPlot()!");
3703 SetupLock();
3704 return gp.CurrentPlot->PlotRect.Min;
3705}
3706
3707ImVec2 GetPlotSize() {
3708 ImPlotContext& gp = *GImPlot;
3709 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetPlotSize() needs to be called between BeginPlot() and EndPlot()!");
3710 SetupLock();
3711 return gp.CurrentPlot->PlotRect.GetSize();
3712}
3713
3714ImPlotPoint GetPlotMousePos(ImAxis x_idx, ImAxis y_idx) {
3715 IM_ASSERT_USER_ERROR(GImPlot->CurrentPlot != nullptr, "GetPlotMousePos() needs to be called between BeginPlot() and EndPlot()!");
3716 SetupLock();
3717 return PixelsToPlot(ImGui::GetMousePos(), x_idx, y_idx);
3718}
3719
3720ImPlotRect GetPlotLimits(ImAxis x_idx, ImAxis y_idx) {
3721 ImPlotContext& gp = *GImPlot;
3722 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetPlotLimits() needs to be called between BeginPlot() and EndPlot()!");
3723 IM_ASSERT_USER_ERROR(x_idx == IMPLOT_AUTO || (x_idx >= ImAxis_X1 && x_idx < ImAxis_Y1), "X-Axis index out of bounds!");
3724 IM_ASSERT_USER_ERROR(y_idx == IMPLOT_AUTO || (y_idx >= ImAxis_Y1 && y_idx < ImAxis_COUNT), "Y-Axis index out of bounds!");
3725 SetupLock();
3726 ImPlotPlot& plot = *gp.CurrentPlot;
3727 ImPlotAxis& x_axis = x_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentX] : plot.Axes[x_idx];
3728 ImPlotAxis& y_axis = y_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentY] : plot.Axes[y_idx];
3729 ImPlotRect limits;
3730 limits.X = x_axis.Range;
3731 limits.Y = y_axis.Range;
3732 return limits;
3733}
3734
3735bool IsPlotHovered() {
3736 ImPlotContext& gp = *GImPlot;
3737 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "IsPlotHovered() needs to be called between BeginPlot() and EndPlot()!");
3738 SetupLock();
3739 return gp.CurrentPlot->Hovered;
3740}
3741
3742bool IsAxisHovered(ImAxis axis) {
3743 ImPlotContext& gp = *GImPlot;
3744 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "IsPlotXAxisHovered() needs to be called between BeginPlot() and EndPlot()!");
3745 SetupLock();
3746 return gp.CurrentPlot->Axes[axis].Hovered;
3747}
3748
3749bool IsSubplotsHovered() {
3750 ImPlotContext& gp = *GImPlot;
3751 IM_ASSERT_USER_ERROR(gp.CurrentSubplot != nullptr, "IsSubplotsHovered() needs to be called between BeginSubplots() and EndSubplots()!");
3752 return gp.CurrentSubplot->FrameHovered;
3753}
3754
3755bool IsPlotSelected() {
3756 ImPlotContext& gp = *GImPlot;
3757 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "IsPlotSelected() needs to be called between BeginPlot() and EndPlot()!");
3758 SetupLock();
3759 return gp.CurrentPlot->Selected;
3760}
3761
3762ImPlotRect GetPlotSelection(ImAxis x_idx, ImAxis y_idx) {
3763 ImPlotContext& gp = *GImPlot;
3764 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetPlotSelection() needs to be called between BeginPlot() and EndPlot()!");
3765 SetupLock();
3766 ImPlotPlot& plot = *gp.CurrentPlot;
3767 if (!plot.Selected)
3768 return ImPlotRect(0,0,0,0);
3769 ImPlotPoint p1 = PixelsToPlot(plot.SelectRect.Min + plot.PlotRect.Min, x_idx, y_idx);
3770 ImPlotPoint p2 = PixelsToPlot(plot.SelectRect.Max + plot.PlotRect.Min, x_idx, y_idx);
3771 ImPlotRect result;
3772 result.X.Min = ImMin(p1.x, p2.x);
3773 result.X.Max = ImMax(p1.x, p2.x);
3774 result.Y.Min = ImMin(p1.y, p2.y);
3775 result.Y.Max = ImMax(p1.y, p2.y);
3776 return result;
3777}
3778
3779void CancelPlotSelection() {
3780 ImPlotContext& gp = *GImPlot;
3781 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "CancelPlotSelection() needs to be called between BeginPlot() and EndPlot()!");
3782 SetupLock();
3783 ImPlotPlot& plot = *gp.CurrentPlot;
3784 if (plot.Selected)
3785 plot.Selected = plot.Selecting = false;
3786}
3787
3788void HideNextItem(bool hidden, ImPlotCond cond) {
3789 ImPlotContext& gp = *GImPlot;
3790 gp.NextItemData.HasHidden = true;
3791 gp.NextItemData.Hidden = hidden;
3792 gp.NextItemData.HiddenCond = cond;
3793}
3794
3795//-----------------------------------------------------------------------------
3796// [SECTION] Plot Tools
3797//-----------------------------------------------------------------------------
3798
3799void Annotation(double x, double y, const ImVec4& col, const ImVec2& offset, bool clamp, bool round) {
3800 ImPlotContext& gp = *GImPlot;
3801 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "Annotation() needs to be called between BeginPlot() and EndPlot()!");
3802 SetupLock();
3803 char x_buff[IMPLOT_LABEL_MAX_SIZE];
3804 char y_buff[IMPLOT_LABEL_MAX_SIZE];
3805 ImPlotAxis& x_axis = gp.CurrentPlot->Axes[gp.CurrentPlot->CurrentX];
3806 ImPlotAxis& y_axis = gp.CurrentPlot->Axes[gp.CurrentPlot->CurrentY];
3807 LabelAxisValue(x_axis, x, x_buff, sizeof(x_buff), round);
3808 LabelAxisValue(y_axis, y, y_buff, sizeof(y_buff), round);
3809 Annotation(x,y,col,offset,clamp,"%s, %s",x_buff,y_buff);
3810}
3811
3812void AnnotationV(double x, double y, const ImVec4& col, const ImVec2& offset, bool clamp, const char* fmt, va_list args) {
3813 ImPlotContext& gp = *GImPlot;
3814 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "Annotation() needs to be called between BeginPlot() and EndPlot()!");
3815 SetupLock();
3816 ImVec2 pos = PlotToPixels(x,y,IMPLOT_AUTO,IMPLOT_AUTO);
3817 ImU32 bg = ImGui::GetColorU32(col);
3818 ImU32 fg = col.w == 0 ? GetStyleColorU32(ImPlotCol_InlayText) : CalcTextColor(col);
3819 gp.Annotations.AppendV(pos, offset, bg, fg, clamp, fmt, args);
3820}
3821
3822void Annotation(double x, double y, const ImVec4& col, const ImVec2& offset, bool clamp, const char* fmt, ...) {
3823 va_list args;
3824 va_start(args, fmt);
3825 AnnotationV(x,y,col,offset,clamp,fmt,args);
3826 va_end(args);
3827}
3828
3829void TagV(ImAxis axis, double v, const ImVec4& col, const char* fmt, va_list args) {
3830 ImPlotContext& gp = *GImPlot;
3831 SetupLock();
3832 ImU32 bg = ImGui::GetColorU32(col);
3833 ImU32 fg = col.w == 0 ? GetStyleColorU32(ImPlotCol_AxisText) : CalcTextColor(col);
3834 gp.Tags.AppendV(axis,v,bg,fg,fmt,args);
3835}
3836
3837void Tag(ImAxis axis, double v, const ImVec4& col, const char* fmt, ...) {
3838 va_list args;
3839 va_start(args, fmt);
3840 TagV(axis,v,col,fmt,args);
3841 va_end(args);
3842}
3843
3844void Tag(ImAxis axis, double v, const ImVec4& color, bool round) {
3845 ImPlotContext& gp = *GImPlot;
3846 SetupLock();
3847 char buff[IMPLOT_LABEL_MAX_SIZE];
3848 ImPlotAxis& ax = gp.CurrentPlot->Axes[axis];
3849 LabelAxisValue(ax, v, buff, sizeof(buff), round);
3850 Tag(axis,v,color,"%s",buff);
3851}
3852
3853IMPLOT_API void TagX(double x, const ImVec4& color, bool round) {
3854 ImPlotContext& gp = *GImPlot;
3855 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "TagX() needs to be called between BeginPlot() and EndPlot()!");
3856 Tag(gp.CurrentPlot->CurrentX, x, color, round);
3857}
3858
3859IMPLOT_API void TagX(double x, const ImVec4& color, const char* fmt, ...) {
3860 ImPlotContext& gp = *GImPlot;
3861 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "TagX() needs to be called between BeginPlot() and EndPlot()!");
3862 va_list args;
3863 va_start(args, fmt);
3864 TagV(gp.CurrentPlot->CurrentX,x,color,fmt,args);
3865 va_end(args);
3866}
3867
3868IMPLOT_API void TagXV(double x, const ImVec4& color, const char* fmt, va_list args) {
3869 ImPlotContext& gp = *GImPlot;
3870 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "TagX() needs to be called between BeginPlot() and EndPlot()!");
3871 TagV(gp.CurrentPlot->CurrentX, x, color, fmt, args);
3872}
3873
3874IMPLOT_API void TagY(double y, const ImVec4& color, bool round) {
3875 ImPlotContext& gp = *GImPlot;
3876 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "TagY() needs to be called between BeginPlot() and EndPlot()!");
3877 Tag(gp.CurrentPlot->CurrentY, y, color, round);
3878}
3879
3880IMPLOT_API void TagY(double y, const ImVec4& color, const char* fmt, ...) {
3881 ImPlotContext& gp = *GImPlot;
3882 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "TagY() needs to be called between BeginPlot() and EndPlot()!");
3883 va_list args;
3884 va_start(args, fmt);
3885 TagV(gp.CurrentPlot->CurrentY,y,color,fmt,args);
3886 va_end(args);
3887}
3888
3889IMPLOT_API void TagYV(double y, const ImVec4& color, const char* fmt, va_list args) {
3890 ImPlotContext& gp = *GImPlot;
3891 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "TagY() needs to be called between BeginPlot() and EndPlot()!");
3892 TagV(gp.CurrentPlot->CurrentY, y, color, fmt, args);
3893}
3894
3895static const float DRAG_GRAB_HALF_SIZE = 4.0f;
3896
3897bool DragPoint(int n_id, double* x, double* y, const ImVec4& col, float radius, ImPlotDragToolFlags flags, bool* out_clicked, bool* out_hovered, bool* out_held) {
3898 ImGui::PushID("#IMPLOT_DRAG_POINT");
3899 IM_ASSERT_USER_ERROR(GImPlot->CurrentPlot != nullptr, "DragPoint() needs to be called between BeginPlot() and EndPlot()!");
3900 SetupLock();
3901
3902 if (!ImHasFlag(flags,ImPlotDragToolFlags_NoFit) && FitThisFrame()) {
3903 FitPoint(ImPlotPoint(*x,*y));
3904 }
3905
3906 const bool input = !ImHasFlag(flags, ImPlotDragToolFlags_NoInputs);
3907 const bool show_curs = !ImHasFlag(flags, ImPlotDragToolFlags_NoCursors);
3908 const bool no_delay = !ImHasFlag(flags, ImPlotDragToolFlags_Delayed);
3909 const float grab_half_size = ImMax(DRAG_GRAB_HALF_SIZE, radius);
3910 const ImVec4 color = IsColorAuto(col) ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : col;
3911 const ImU32 col32 = ImGui::ColorConvertFloat4ToU32(color);
3912
3913 ImVec2 pos = PlotToPixels(*x,*y,IMPLOT_AUTO,IMPLOT_AUTO);
3914 const ImGuiID id = ImGui::GetCurrentWindow()->GetID(n_id);
3915 ImRect rect(pos.x-grab_half_size,pos.y-grab_half_size,pos.x+grab_half_size,pos.y+grab_half_size);
3916 bool hovered = false, held = false;
3917
3918 ImGui::KeepAliveID(id);
3919 if (input) {
3920 bool clicked = ImGui::ButtonBehavior(rect,id,&hovered,&held);
3921 if (out_clicked) *out_clicked = clicked;
3922 if (out_hovered) *out_hovered = hovered;
3923 if (out_held) *out_held = held;
3924 }
3925
3926 bool modified = false;
3927 if (held && ImGui::IsMouseDragging(0)) {
3928 *x = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x;
3929 *y = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y;
3930 modified = true;
3931 }
3932
3933 PushPlotClipRect();
3934 ImDrawList& DrawList = *GetPlotDrawList();
3935 if ((hovered || held) && show_curs)
3936 ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
3937 if (modified && no_delay)
3938 pos = PlotToPixels(*x,*y,IMPLOT_AUTO,IMPLOT_AUTO);
3939 DrawList.AddCircleFilled(pos, radius, col32);
3940 PopPlotClipRect();
3941
3942 ImGui::PopID();
3943 return modified;
3944}
3945
3946bool DragLineX(int n_id, double* value, const ImVec4& col, float thickness, ImPlotDragToolFlags flags, bool* out_clicked, bool* out_hovered, bool* out_held) {
3947 // ImGui::PushID("#IMPLOT_DRAG_LINE_X");
3948 ImPlotContext& gp = *GImPlot;
3949 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "DragLineX() needs to be called between BeginPlot() and EndPlot()!");
3950 SetupLock();
3951
3952 if (!ImHasFlag(flags,ImPlotDragToolFlags_NoFit) && FitThisFrame()) {
3953 FitPointX(*value);
3954 }
3955
3956 const bool input = !ImHasFlag(flags, ImPlotDragToolFlags_NoInputs);
3957 const bool show_curs = !ImHasFlag(flags, ImPlotDragToolFlags_NoCursors);
3958 const bool no_delay = !ImHasFlag(flags, ImPlotDragToolFlags_Delayed);
3959 const float grab_half_size = ImMax(DRAG_GRAB_HALF_SIZE, thickness/2);
3960 float yt = gp.CurrentPlot->PlotRect.Min.y;
3961 float yb = gp.CurrentPlot->PlotRect.Max.y;
3962 float x = IM_ROUND(PlotToPixels(*value,0,IMPLOT_AUTO,IMPLOT_AUTO).x);
3963 const ImGuiID id = ImGui::GetCurrentWindow()->GetID(n_id);
3964 ImRect rect(x-grab_half_size,yt,x+grab_half_size,yb);
3965 bool hovered = false, held = false;
3966
3967 ImGui::KeepAliveID(id);
3968 if (input) {
3969 bool clicked = ImGui::ButtonBehavior(rect,id,&hovered,&held);
3970 if (out_clicked) *out_clicked = clicked;
3971 if (out_hovered) *out_hovered = hovered;
3972 if (out_held) *out_held = held;
3973 }
3974
3975 if ((hovered || held) && show_curs)
3976 ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
3977
3978 float len = gp.Style.MajorTickLen.x;
3979 ImVec4 color = IsColorAuto(col) ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : col;
3980 ImU32 col32 = ImGui::ColorConvertFloat4ToU32(color);
3981
3982 bool modified = false;
3983 if (held && ImGui::IsMouseDragging(0)) {
3984 *value = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x;
3985 modified = true;
3986 }
3987
3988 PushPlotClipRect();
3989 ImDrawList& DrawList = *GetPlotDrawList();
3990 if (modified && no_delay)
3991 x = IM_ROUND(PlotToPixels(*value,0,IMPLOT_AUTO,IMPLOT_AUTO).x);
3992 DrawList.AddLine(ImVec2(x,yt), ImVec2(x,yb), col32, thickness);
3993 DrawList.AddLine(ImVec2(x,yt), ImVec2(x,yt+len), col32, 3*thickness);
3994 DrawList.AddLine(ImVec2(x,yb), ImVec2(x,yb-len), col32, 3*thickness);
3995 PopPlotClipRect();
3996
3997 // ImGui::PopID();
3998 return modified;
3999}
4000
4001bool DragLineY(int n_id, double* value, const ImVec4& col, float thickness, ImPlotDragToolFlags flags, bool* out_clicked, bool* out_hovered, bool* out_held) {
4002 ImGui::PushID("#IMPLOT_DRAG_LINE_Y");
4003 ImPlotContext& gp = *GImPlot;
4004 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "DragLineY() needs to be called between BeginPlot() and EndPlot()!");
4005 SetupLock();
4006
4007 if (!ImHasFlag(flags,ImPlotDragToolFlags_NoFit) && FitThisFrame()) {
4008 FitPointY(*value);
4009 }
4010
4011 const bool input = !ImHasFlag(flags, ImPlotDragToolFlags_NoInputs);
4012 const bool show_curs = !ImHasFlag(flags, ImPlotDragToolFlags_NoCursors);
4013 const bool no_delay = !ImHasFlag(flags, ImPlotDragToolFlags_Delayed);
4014 const float grab_half_size = ImMax(DRAG_GRAB_HALF_SIZE, thickness/2);
4015 float xl = gp.CurrentPlot->PlotRect.Min.x;
4016 float xr = gp.CurrentPlot->PlotRect.Max.x;
4017 float y = IM_ROUND(PlotToPixels(0, *value,IMPLOT_AUTO,IMPLOT_AUTO).y);
4018
4019 const ImGuiID id = ImGui::GetCurrentWindow()->GetID(n_id);
4020 ImRect rect(xl,y-grab_half_size,xr,y+grab_half_size);
4021 bool hovered = false, held = false;
4022
4023 ImGui::KeepAliveID(id);
4024 if (input) {
4025 bool clicked = ImGui::ButtonBehavior(rect,id,&hovered,&held);
4026 if (out_clicked) *out_clicked = clicked;
4027 if (out_hovered) *out_hovered = hovered;
4028 if (out_held) *out_held = held;
4029 }
4030
4031 if ((hovered || held) && show_curs)
4032 ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);
4033
4034 float len = gp.Style.MajorTickLen.y;
4035 ImVec4 color = IsColorAuto(col) ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : col;
4036 ImU32 col32 = ImGui::ColorConvertFloat4ToU32(color);
4037
4038 bool modified = false;
4039 if (held && ImGui::IsMouseDragging(0)) {
4040 *value = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y;
4041 modified = true;
4042 }
4043
4044 PushPlotClipRect();
4045 ImDrawList& DrawList = *GetPlotDrawList();
4046 if (modified && no_delay)
4047 y = IM_ROUND(PlotToPixels(0, *value,IMPLOT_AUTO,IMPLOT_AUTO).y);
4048 DrawList.AddLine(ImVec2(xl,y), ImVec2(xr,y), col32, thickness);
4049 DrawList.AddLine(ImVec2(xl,y), ImVec2(xl+len,y), col32, 3*thickness);
4050 DrawList.AddLine(ImVec2(xr,y), ImVec2(xr-len,y), col32, 3*thickness);
4051 PopPlotClipRect();
4052
4053 ImGui::PopID();
4054 return modified;
4055}
4056
4057bool DragRect(int n_id, double* x_min, double* y_min, double* x_max, double* y_max, const ImVec4& col, ImPlotDragToolFlags flags, bool* out_clicked, bool* out_hovered, bool* out_held) {
4058 ImGui::PushID("#IMPLOT_DRAG_RECT");
4059 IM_ASSERT_USER_ERROR(GImPlot->CurrentPlot != nullptr, "DragRect() needs to be called between BeginPlot() and EndPlot()!");
4060 SetupLock();
4061
4062 if (!ImHasFlag(flags,ImPlotDragToolFlags_NoFit) && FitThisFrame()) {
4063 FitPoint(ImPlotPoint(*x_min,*y_min));
4064 FitPoint(ImPlotPoint(*x_max,*y_max));
4065 }
4066
4067 const bool input = !ImHasFlag(flags, ImPlotDragToolFlags_NoInputs);
4068 const bool show_curs = !ImHasFlag(flags, ImPlotDragToolFlags_NoCursors);
4069 const bool no_delay = !ImHasFlag(flags, ImPlotDragToolFlags_Delayed);
4070 bool h[] = {true,false,true,false};
4071 double* x[] = {x_min,x_max,x_max,x_min};
4072 double* y[] = {y_min,y_min,y_max,y_max};
4073 ImVec2 p[4];
4074 for (int i = 0; i < 4; ++i)
4075 p[i] = PlotToPixels(*x[i],*y[i],IMPLOT_AUTO,IMPLOT_AUTO);
4076 ImVec2 pc = PlotToPixels((*x_min+*x_max)/2,(*y_min+*y_max)/2,IMPLOT_AUTO,IMPLOT_AUTO);
4077 ImRect rect(ImMin(p[0],p[2]),ImMax(p[0],p[2]));
4078 ImRect rect_grab = rect; rect_grab.Expand(DRAG_GRAB_HALF_SIZE);
4079
4080 ImGuiMouseCursor cur[4];
4081 if (show_curs) {
4082 cur[0] = (rect.Min.x == p[0].x && rect.Min.y == p[0].y) || (rect.Max.x == p[0].x && rect.Max.y == p[0].y) ? ImGuiMouseCursor_ResizeNWSE : ImGuiMouseCursor_ResizeNESW;
4083 cur[1] = cur[0] == ImGuiMouseCursor_ResizeNWSE ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE;
4084 cur[2] = cur[1] == ImGuiMouseCursor_ResizeNWSE ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE;
4085 cur[3] = cur[2] == ImGuiMouseCursor_ResizeNWSE ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE;
4086 }
4087
4088 ImVec4 color = IsColorAuto(col) ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : col;
4089 ImU32 col32 = ImGui::ColorConvertFloat4ToU32(color);
4090 color.w *= 0.25f;
4091 ImU32 col32_a = ImGui::ColorConvertFloat4ToU32(color);
4092 const ImGuiID id = ImGui::GetCurrentWindow()->GetID(n_id);
4093
4094 bool modified = false;
4095 bool clicked = false, hovered = false, held = false;
4096 ImRect b_rect(pc.x-DRAG_GRAB_HALF_SIZE,pc.y-DRAG_GRAB_HALF_SIZE,pc.x+DRAG_GRAB_HALF_SIZE,pc.y+DRAG_GRAB_HALF_SIZE);
4097
4098 ImGui::KeepAliveID(id);
4099 if (input) {
4100 // middle point
4101 clicked = ImGui::ButtonBehavior(b_rect,id,&hovered,&held);
4102 if (out_clicked) *out_clicked = clicked;
4103 if (out_hovered) *out_hovered = hovered;
4104 if (out_held) *out_held = held;
4105 }
4106
4107 if ((hovered || held) && show_curs)
4108 ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll);
4109 if (held && ImGui::IsMouseDragging(0)) {
4110 for (int i = 0; i < 4; ++i) {
4111 ImPlotPoint pp = PixelsToPlot(p[i] + ImGui::GetIO().MouseDelta,IMPLOT_AUTO,IMPLOT_AUTO);
4112 *y[i] = pp.y;
4113 *x[i] = pp.x;
4114 }
4115 modified = true;
4116 }
4117
4118 for (int i = 0; i < 4; ++i) {
4119 // points
4120 b_rect = ImRect(p[i].x-DRAG_GRAB_HALF_SIZE,p[i].y-DRAG_GRAB_HALF_SIZE,p[i].x+DRAG_GRAB_HALF_SIZE,p[i].y+DRAG_GRAB_HALF_SIZE);
4121 ImGuiID p_id = id + i + 1;
4122 ImGui::KeepAliveID(p_id);
4123 if (input) {
4124 clicked = ImGui::ButtonBehavior(b_rect,p_id,&hovered,&held);
4125 if (out_clicked) *out_clicked = *out_clicked || clicked;
4126 if (out_hovered) *out_hovered = *out_hovered || hovered;
4127 if (out_held) *out_held = *out_held || held;
4128 }
4129 if ((hovered || held) && show_curs)
4130 ImGui::SetMouseCursor(cur[i]);
4131
4132 if (held && ImGui::IsMouseDragging(0)) {
4133 *x[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x;
4134 *y[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y;
4135 modified = true;
4136 }
4137
4138 // edges
4139 ImVec2 e_min = ImMin(p[i],p[(i+1)%4]);
4140 ImVec2 e_max = ImMax(p[i],p[(i+1)%4]);
4141 b_rect = h[i] ? ImRect(e_min.x + DRAG_GRAB_HALF_SIZE, e_min.y - DRAG_GRAB_HALF_SIZE, e_max.x - DRAG_GRAB_HALF_SIZE, e_max.y + DRAG_GRAB_HALF_SIZE)
4142 : ImRect(e_min.x - DRAG_GRAB_HALF_SIZE, e_min.y + DRAG_GRAB_HALF_SIZE, e_max.x + DRAG_GRAB_HALF_SIZE, e_max.y - DRAG_GRAB_HALF_SIZE);
4143 ImGuiID e_id = id + i + 5;
4144 ImGui::KeepAliveID(e_id);
4145 if (input) {
4146 clicked = ImGui::ButtonBehavior(b_rect,e_id,&hovered,&held);
4147 if (out_clicked) *out_clicked = *out_clicked || clicked;
4148 if (out_hovered) *out_hovered = *out_hovered || hovered;
4149 if (out_held) *out_held = *out_held || held;
4150 }
4151 if ((hovered || held) && show_curs)
4152 h[i] ? ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS) : ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
4153 if (held && ImGui::IsMouseDragging(0)) {
4154 if (h[i])
4155 *y[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y;
4156 else
4157 *x[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x;
4158 modified = true;
4159 }
4160 if (hovered && ImGui::IsMouseDoubleClicked(0))
4161 {
4162 ImPlotRect b = GetPlotLimits(IMPLOT_AUTO,IMPLOT_AUTO);
4163 if (h[i])
4164 *y[i] = ((y[i] == y_min && *y_min < *y_max) || (y[i] == y_max && *y_max < *y_min)) ? b.Y.Min : b.Y.Max;
4165 else
4166 *x[i] = ((x[i] == x_min && *x_min < *x_max) || (x[i] == x_max && *x_max < *x_min)) ? b.X.Min : b.X.Max;
4167 modified = true;
4168 }
4169 }
4170
4171 const bool mouse_inside = rect_grab.Contains(ImGui::GetMousePos());
4172 const bool mouse_clicked = ImGui::IsMouseClicked(0);
4173 const bool mouse_down = ImGui::IsMouseDown(0);
4174 if (input && mouse_inside) {
4175 if (out_clicked) *out_clicked = *out_clicked || mouse_clicked;
4176 if (out_hovered) *out_hovered = true;
4177 if (out_held) *out_held = *out_held || mouse_down;
4178 }
4179
4180 PushPlotClipRect();
4181 ImDrawList& DrawList = *GetPlotDrawList();
4182 if (modified && no_delay) {
4183 for (int i = 0; i < 4; ++i)
4184 p[i] = PlotToPixels(*x[i],*y[i],IMPLOT_AUTO,IMPLOT_AUTO);
4185 pc = PlotToPixels((*x_min+*x_max)/2,(*y_min+*y_max)/2,IMPLOT_AUTO,IMPLOT_AUTO);
4186 rect = ImRect(ImMin(p[0],p[2]),ImMax(p[0],p[2]));
4187 }
4188 DrawList.AddRectFilled(rect.Min, rect.Max, col32_a);
4189 DrawList.AddRect(rect.Min, rect.Max, col32);
4190 if (input && (modified || mouse_inside)) {
4191 DrawList.AddCircleFilled(pc,DRAG_GRAB_HALF_SIZE,col32);
4192 for (int i = 0; i < 4; ++i)
4193 DrawList.AddCircleFilled(p[i],DRAG_GRAB_HALF_SIZE,col32);
4194 }
4195 PopPlotClipRect();
4196 ImGui::PopID();
4197 return modified;
4198}
4199
4200bool DragRect(int id, ImPlotRect* bounds, const ImVec4& col, ImPlotDragToolFlags flags, bool* out_clicked, bool* out_hovered, bool* out_held) {
4201 return DragRect(id, &bounds->X.Min, &bounds->Y.Min,&bounds->X.Max, &bounds->Y.Max, col, flags, out_clicked, out_hovered, out_held);
4202}
4203
4204//-----------------------------------------------------------------------------
4205// [SECTION] Legend Utils and Tools
4206//-----------------------------------------------------------------------------
4207
4208bool IsLegendEntryHovered(const char* label_id) {
4209 ImPlotContext& gp = *GImPlot;
4210 IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr, "IsPlotItemHighlight() needs to be called within an itemized context!");
4211 SetupLock();
4212 ImGuiID id = ImGui::GetIDWithSeed(label_id, nullptr, gp.CurrentItems->ID);
4213 ImPlotItem* item = gp.CurrentItems->GetItem(id);
4214 return item && item->LegendHovered;
4215}
4216
4217bool BeginLegendPopup(const char* label_id, ImGuiMouseButton mouse_button) {
4218 ImPlotContext& gp = *GImPlot;
4219 IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr, "BeginLegendPopup() needs to be called within an itemized context!");
4220 SetupLock();
4221 ImGuiWindow* window = GImGui->CurrentWindow;
4222 if (window->SkipItems)
4223 return false;
4224 ImGuiID id = ImGui::GetIDWithSeed(label_id, nullptr, gp.CurrentItems->ID);
4225 if (ImGui::IsMouseReleased(mouse_button)) {
4226 ImPlotItem* item = gp.CurrentItems->GetItem(id);
4227 if (item && item->LegendHovered)
4228 ImGui::OpenPopupEx(id);
4229 }
4230 return ImGui::BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings);
4231}
4232
4233void EndLegendPopup() {
4234 SetupLock();
4235 ImGui::EndPopup();
4236}
4237
4238void ShowAltLegend(const char* title_id, bool vertical, const ImVec2 size, bool interactable) {
4239 ImPlotContext& gp = *GImPlot;
4240 ImGuiContext &G = *GImGui;
4241 ImGuiWindow * Window = G.CurrentWindow;
4242 if (Window->SkipItems)
4243 return;
4244 ImDrawList &DrawList = *Window->DrawList;
4245 ImPlotPlot* plot = GetPlot(title_id);
4246 ImVec2 legend_size;
4247 ImVec2 default_size = gp.Style.LegendPadding * 2;
4248 if (plot != nullptr) {
4249 legend_size = CalcLegendSize(plot->Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, vertical);
4250 default_size = legend_size + gp.Style.LegendPadding * 2;
4251 }
4252 ImVec2 frame_size = ImGui::CalcItemSize(size, default_size.x, default_size.y);
4253 ImRect bb_frame = ImRect(Window->DC.CursorPos, Window->DC.CursorPos + frame_size);
4254 ImGui::ItemSize(bb_frame);
4255 if (!ImGui::ItemAdd(bb_frame, 0, &bb_frame))
4256 return;
4257 ImGui::RenderFrame(bb_frame.Min, bb_frame.Max, GetStyleColorU32(ImPlotCol_FrameBg), true, G.Style.FrameRounding);
4258 DrawList.PushClipRect(bb_frame.Min, bb_frame.Max, true);
4259 if (plot != nullptr) {
4260 const ImVec2 legend_pos = GetLocationPos(bb_frame, legend_size, 0, gp.Style.LegendPadding);
4261 const ImRect legend_bb(legend_pos, legend_pos + legend_size);
4262 interactable = interactable && bb_frame.Contains(ImGui::GetIO().MousePos);
4263 // render legend box
4264 ImU32 col_bg = GetStyleColorU32(ImPlotCol_LegendBg);
4265 ImU32 col_bd = GetStyleColorU32(ImPlotCol_LegendBorder);
4266 DrawList.AddRectFilled(legend_bb.Min, legend_bb.Max, col_bg);
4267 DrawList.AddRect(legend_bb.Min, legend_bb.Max, col_bd);
4268 // render entries
4269 ShowLegendEntries(plot->Items, legend_bb, interactable, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, vertical, DrawList);
4270 }
4271 DrawList.PopClipRect();
4272}
4273
4274//-----------------------------------------------------------------------------
4275// [SECTION] Drag and Drop Utils
4276//-----------------------------------------------------------------------------
4277
4278bool BeginDragDropTargetPlot() {
4279 SetupLock();
4280 ImPlotContext& gp = *GImPlot;
4281 ImRect rect = gp.CurrentPlot->PlotRect;
4282 return ImGui::BeginDragDropTargetCustom(rect, gp.CurrentPlot->ID);
4283}
4284
4285bool BeginDragDropTargetAxis(ImAxis axis) {
4286 SetupLock();
4287 ImPlotPlot& plot = *GImPlot->CurrentPlot;
4288 ImPlotAxis& ax = plot.Axes[axis];
4289 ImRect rect = ax.HoverRect;
4290 rect.Expand(-3.5f);
4291 return ImGui::BeginDragDropTargetCustom(rect, ax.ID);
4292}
4293
4294bool BeginDragDropTargetLegend() {
4295 SetupLock();
4296 ImPlotItemGroup& items = *GImPlot->CurrentItems;
4297 ImRect rect = items.Legend.RectClamped;
4298 return ImGui::BeginDragDropTargetCustom(rect, items.ID);
4299}
4300
4301void EndDragDropTarget() {
4302 SetupLock();
4303 ImGui::EndDragDropTarget();
4304}
4305
4306bool BeginDragDropSourcePlot(ImGuiDragDropFlags flags) {
4307 SetupLock();
4308 ImPlotContext& gp = *GImPlot;
4309 ImPlotPlot* plot = gp.CurrentPlot;
4310 if (GImGui->IO.KeyMods == gp.InputMap.OverrideMod || GImGui->DragDropPayload.SourceId == plot->ID)
4311 return ImGui::ItemAdd(plot->PlotRect, plot->ID) && ImGui::BeginDragDropSource(flags);
4312 return false;
4313}
4314
4315bool BeginDragDropSourceAxis(ImAxis idx, ImGuiDragDropFlags flags) {
4316 SetupLock();
4317 ImPlotContext& gp = *GImPlot;
4318 ImPlotAxis& axis = gp.CurrentPlot->Axes[idx];
4319 if (GImGui->IO.KeyMods == gp.InputMap.OverrideMod || GImGui->DragDropPayload.SourceId == axis.ID)
4320 return ImGui::ItemAdd(axis.HoverRect, axis.ID) && ImGui::BeginDragDropSource(flags);
4321 return false;
4322}
4323
4324bool BeginDragDropSourceItem(const char* label_id, ImGuiDragDropFlags flags) {
4325 SetupLock();
4326 ImPlotContext& gp = *GImPlot;
4327 IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr, "BeginDragDropSourceItem() needs to be called within an itemized context!");
4328 ImGuiID item_id = ImGui::GetIDWithSeed(label_id, nullptr, gp.CurrentItems->ID);
4329 ImPlotItem* item = gp.CurrentItems->GetItem(item_id);
4330 if (item != nullptr) {
4331 return ImGui::ItemAdd(item->LegendHoverRect, item->ID) && ImGui::BeginDragDropSource(flags);
4332 }
4333 return false;
4334}
4335
4336void EndDragDropSource() {
4337 SetupLock();
4338 ImGui::EndDragDropSource();
4339}
4340
4341//-----------------------------------------------------------------------------
4342// [SECTION] Aligned Plots
4343//-----------------------------------------------------------------------------
4344
4345bool BeginAlignedPlots(const char* group_id, bool vertical) {
4346 IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?");
4347 ImPlotContext& gp = *GImPlot;
4348 IM_ASSERT_USER_ERROR(gp.CurrentAlignmentH == nullptr && gp.CurrentAlignmentV == nullptr, "Mismatched BeginAlignedPlots()/EndAlignedPlots()!");
4349 ImGuiContext &G = *GImGui;
4350 ImGuiWindow * Window = G.CurrentWindow;
4351 if (Window->SkipItems)
4352 return false;
4353 const ImGuiID ID = Window->GetID(group_id);
4354 ImPlotAlignmentData* alignment = gp.AlignmentData.GetOrAddByKey(ID);
4355 if (vertical)
4356 gp.CurrentAlignmentV = alignment;
4357 else
4358 gp.CurrentAlignmentH = alignment;
4359 if (alignment->Vertical != vertical)
4360 alignment->Reset();
4361 alignment->Vertical = vertical;
4362 alignment->Begin();
4363 return true;
4364}
4365
4366void EndAlignedPlots() {
4367 IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?");
4368 ImPlotContext& gp = *GImPlot;
4369 IM_ASSERT_USER_ERROR(gp.CurrentAlignmentH != nullptr || gp.CurrentAlignmentV != nullptr, "Mismatched BeginAlignedPlots()/EndAlignedPlots()!");
4370 ImPlotAlignmentData* alignment = gp.CurrentAlignmentH != nullptr ? gp.CurrentAlignmentH : (gp.CurrentAlignmentV != nullptr ? gp.CurrentAlignmentV : nullptr);
4371 if (alignment)
4372 alignment->End();
4373 ResetCtxForNextAlignedPlots(GImPlot);
4374}
4375
4376//-----------------------------------------------------------------------------
4377// [SECTION] Plot and Item Styling
4378//-----------------------------------------------------------------------------
4379
4380ImPlotStyle& GetStyle() {
4381 IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?");
4382 ImPlotContext& gp = *GImPlot;
4383 return gp.Style;
4384}
4385
4386void PushStyleColor(ImPlotCol idx, ImU32 col) {
4387 ImPlotContext& gp = *GImPlot;
4388 ImGuiColorMod backup;
4389 backup.Col = (ImGuiCol)idx;
4390 backup.BackupValue = gp.Style.Colors[idx];
4391 gp.ColorModifiers.push_back(backup);
4392 gp.Style.Colors[idx] = ImGui::ColorConvertU32ToFloat4(col);
4393}
4394
4395void PushStyleColor(ImPlotCol idx, const ImVec4& col) {
4396 ImPlotContext& gp = *GImPlot;
4397 ImGuiColorMod backup;
4398 backup.Col = (ImGuiCol)idx;
4399 backup.BackupValue = gp.Style.Colors[idx];
4400 gp.ColorModifiers.push_back(backup);
4401 gp.Style.Colors[idx] = col;
4402}
4403
4404void PopStyleColor(int count) {
4405 ImPlotContext& gp = *GImPlot;
4406 IM_ASSERT_USER_ERROR(count <= gp.ColorModifiers.Size, "You can't pop more modifiers than have been pushed!");
4407 while (count > 0)
4408 {
4409 ImGuiColorMod& backup = gp.ColorModifiers.back();
4410 gp.Style.Colors[backup.Col] = backup.BackupValue;
4411 gp.ColorModifiers.pop_back();
4412 count--;
4413 }
4414}
4415
4416void PushStyleVar(ImPlotStyleVar idx, float val) {
4417 ImPlotContext& gp = *GImPlot;
4418 const ImPlotStyleVarInfo* var_info = GetPlotStyleVarInfo(idx);
4419 if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1) {
4420 float* pvar = (float*)var_info->GetVarPtr(&gp.Style);
4421 gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar));
4422 *pvar = val;
4423 return;
4424 }
4425 IM_ASSERT(0 && "Called PushStyleVar() float variant but variable is not a float!");
4426}
4427
4428void PushStyleVar(ImPlotStyleVar idx, int val) {
4429 ImPlotContext& gp = *GImPlot;
4430 const ImPlotStyleVarInfo* var_info = GetPlotStyleVarInfo(idx);
4431 if (var_info->Type == ImGuiDataType_S32 && var_info->Count == 1) {
4432 int* pvar = (int*)var_info->GetVarPtr(&gp.Style);
4433 gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar));
4434 *pvar = val;
4435 return;
4436 }
4437 else if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1) {
4438 float* pvar = (float*)var_info->GetVarPtr(&gp.Style);
4439 gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar));
4440 *pvar = (float)val;
4441 return;
4442 }
4443 IM_ASSERT(0 && "Called PushStyleVar() int variant but variable is not a int!");
4444}
4445
4446void PushStyleVar(ImPlotStyleVar idx, const ImVec2& val)
4447{
4448 ImPlotContext& gp = *GImPlot;
4449 const ImPlotStyleVarInfo* var_info = GetPlotStyleVarInfo(idx);
4450 if (var_info->Type == ImGuiDataType_Float && var_info->Count == 2)
4451 {
4452 ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&gp.Style);
4453 gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar));
4454 *pvar = val;
4455 return;
4456 }
4457 IM_ASSERT(0 && "Called PushStyleVar() ImVec2 variant but variable is not a ImVec2!");
4458}
4459
4460void PopStyleVar(int count) {
4461 ImPlotContext& gp = *GImPlot;
4462 IM_ASSERT_USER_ERROR(count <= gp.StyleModifiers.Size, "You can't pop more modifiers than have been pushed!");
4463 while (count > 0) {
4464 ImGuiStyleMod& backup = gp.StyleModifiers.back();
4465 const ImPlotStyleVarInfo* info = GetPlotStyleVarInfo(backup.VarIdx);
4466 void* data = info->GetVarPtr(&gp.Style);
4467 if (info->Type == ImGuiDataType_Float && info->Count == 1) {
4468 ((float*)data)[0] = backup.BackupFloat[0];
4469 }
4470 else if (info->Type == ImGuiDataType_Float && info->Count == 2) {
4471 ((float*)data)[0] = backup.BackupFloat[0];
4472 ((float*)data)[1] = backup.BackupFloat[1];
4473 }
4474 else if (info->Type == ImGuiDataType_S32 && info->Count == 1) {
4475 ((int*)data)[0] = backup.BackupInt[0];
4476 }
4477 gp.StyleModifiers.pop_back();
4478 count--;
4479 }
4480}
4481
4482//------------------------------------------------------------------------------
4483// [Section] Colormaps
4484//------------------------------------------------------------------------------
4485
4486ImPlotColormap AddColormap(const char* name, const ImVec4* colormap, int size, bool qual) {
4487 ImPlotContext& gp = *GImPlot;
4488 IM_ASSERT_USER_ERROR(size > 1, "The colormap size must be greater than 1!");
4489 IM_ASSERT_USER_ERROR(gp.ColormapData.GetIndex(name) == -1, "The colormap name has already been used!");
4490 ImVector<ImU32> buffer;
4491 buffer.resize(size);
4492 for (int i = 0; i < size; ++i)
4493 buffer[i] = ImGui::ColorConvertFloat4ToU32(colormap[i]);
4494 return gp.ColormapData.Append(name, buffer.Data, size, qual);
4495}
4496
4497ImPlotColormap AddColormap(const char* name, const ImU32* colormap, int size, bool qual) {
4498 ImPlotContext& gp = *GImPlot;
4499 IM_ASSERT_USER_ERROR(size > 1, "The colormap size must be greater than 1!");
4500 IM_ASSERT_USER_ERROR(gp.ColormapData.GetIndex(name) == -1, "The colormap name has already be used!");
4501 return gp.ColormapData.Append(name, colormap, size, qual);
4502}
4503
4504int GetColormapCount() {
4505 ImPlotContext& gp = *GImPlot;
4506 return gp.ColormapData.Count;
4507}
4508
4509const char* GetColormapName(ImPlotColormap colormap) {
4510 ImPlotContext& gp = *GImPlot;
4511 return gp.ColormapData.GetName(colormap);
4512}
4513
4514ImPlotColormap GetColormapIndex(const char* name) {
4515 ImPlotContext& gp = *GImPlot;
4516 return gp.ColormapData.GetIndex(name);
4517}
4518
4519void PushColormap(ImPlotColormap colormap) {
4520 ImPlotContext& gp = *GImPlot;
4521 IM_ASSERT_USER_ERROR(colormap >= 0 && colormap < gp.ColormapData.Count, "The colormap index is invalid!");
4522 gp.ColormapModifiers.push_back(gp.Style.Colormap);
4523 gp.Style.Colormap = colormap;
4524}
4525
4526void PushColormap(const char* name) {
4527 ImPlotContext& gp = *GImPlot;
4528 ImPlotColormap idx = gp.ColormapData.GetIndex(name);
4529 IM_ASSERT_USER_ERROR(idx != -1, "The colormap name is invalid!");
4530 PushColormap(idx);
4531}
4532
4533void PopColormap(int count) {
4534 ImPlotContext& gp = *GImPlot;
4535 IM_ASSERT_USER_ERROR(count <= gp.ColormapModifiers.Size, "You can't pop more modifiers than have been pushed!");
4536 while (count > 0) {
4537 const ImPlotColormap& backup = gp.ColormapModifiers.back();
4538 gp.Style.Colormap = backup;
4539 gp.ColormapModifiers.pop_back();
4540 count--;
4541 }
4542}
4543
4544ImU32 NextColormapColorU32() {
4545 ImPlotContext& gp = *GImPlot;
4546 IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr, "NextColormapColor() needs to be called between BeginPlot() and EndPlot()!");
4547 int idx = gp.CurrentItems->ColormapIdx % gp.ColormapData.GetKeyCount(gp.Style.Colormap);
4548 ImU32 col = gp.ColormapData.GetKeyColor(gp.Style.Colormap, idx);
4549 gp.CurrentItems->ColormapIdx++;
4550 return col;
4551}
4552
4553ImVec4 NextColormapColor() {
4554 return ImGui::ColorConvertU32ToFloat4(NextColormapColorU32());
4555}
4556
4557int GetColormapSize(ImPlotColormap cmap) {
4558 ImPlotContext& gp = *GImPlot;
4559 cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap;
4560 IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!");
4561 return gp.ColormapData.GetKeyCount(cmap);
4562}
4563
4564ImU32 GetColormapColorU32(int idx, ImPlotColormap cmap) {
4565 ImPlotContext& gp = *GImPlot;
4566 cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap;
4567 IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!");
4568 idx = idx % gp.ColormapData.GetKeyCount(cmap);
4569 return gp.ColormapData.GetKeyColor(cmap, idx);
4570}
4571
4572ImVec4 GetColormapColor(int idx, ImPlotColormap cmap) {
4573 return ImGui::ColorConvertU32ToFloat4(GetColormapColorU32(idx,cmap));
4574}
4575
4576ImU32 SampleColormapU32(float t, ImPlotColormap cmap) {
4577 ImPlotContext& gp = *GImPlot;
4578 cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap;
4579 IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!");
4580 return gp.ColormapData.LerpTable(cmap, t);
4581}
4582
4583ImVec4 SampleColormap(float t, ImPlotColormap cmap) {
4584 return ImGui::ColorConvertU32ToFloat4(SampleColormapU32(t,cmap));
4585}
4586
4587void RenderColorBar(const ImU32* colors, int size, ImDrawList& DrawList, const ImRect& bounds, bool vert, bool reversed, bool continuous) {
4588 const int n = continuous ? size - 1 : size;
4589 ImU32 col1, col2;
4590 if (vert) {
4591 const float step = bounds.GetHeight() / n;
4592 ImRect rect(bounds.Min.x, bounds.Min.y, bounds.Max.x, bounds.Min.y + step);
4593 for (int i = 0; i < n; ++i) {
4594 if (reversed) {
4595 col1 = colors[size-i-1];
4596 col2 = continuous ? colors[size-i-2] : col1;
4597 }
4598 else {
4599 col1 = colors[i];
4600 col2 = continuous ? colors[i+1] : col1;
4601 }
4602 DrawList.AddRectFilledMultiColor(rect.Min, rect.Max, col1, col1, col2, col2);
4603 rect.TranslateY(step);
4604 }
4605 }
4606 else {
4607 const float step = bounds.GetWidth() / n;
4608 ImRect rect(bounds.Min.x, bounds.Min.y, bounds.Min.x + step, bounds.Max.y);
4609 for (int i = 0; i < n; ++i) {
4610 if (reversed) {
4611 col1 = colors[size-i-1];
4612 col2 = continuous ? colors[size-i-2] : col1;
4613 }
4614 else {
4615 col1 = colors[i];
4616 col2 = continuous ? colors[i+1] : col1;
4617 }
4618 DrawList.AddRectFilledMultiColor(rect.Min, rect.Max, col1, col2, col2, col1);
4619 rect.TranslateX(step);
4620 }
4621 }
4622}
4623
4624void ColormapScale(const char* label, double scale_min, double scale_max, const ImVec2& size, const char* format, ImPlotColormapScaleFlags flags, ImPlotColormap cmap) {
4625 ImGuiContext &G = *GImGui;
4626 ImGuiWindow * Window = G.CurrentWindow;
4627 if (Window->SkipItems)
4628 return;
4629
4630 const ImGuiID ID = Window->GetID(label);
4631 ImVec2 label_size(0,0);
4632 if (!ImHasFlag(flags, ImPlotColormapScaleFlags_NoLabel)) {
4633 label_size = ImGui::CalcTextSize(label,nullptr,true);
4634 }
4635
4636 ImPlotContext& gp = *GImPlot;
4637 cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap;
4638 IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!");
4639
4640 ImVec2 frame_size = ImGui::CalcItemSize(size, 0, gp.Style.PlotDefaultSize.y);
4641 if (frame_size.y < gp.Style.PlotMinSize.y && size.y < 0.0f)
4642 frame_size.y = gp.Style.PlotMinSize.y;
4643
4644 ImPlotRange range(ImMin(scale_min,scale_max), ImMax(scale_min,scale_max));
4645 gp.CTicker.Reset();
4646 Locator_Default(gp.CTicker, range, frame_size.y, true, Formatter_Default, (void*)format);
4647
4648 const bool rend_label = label_size.x > 0;
4649 const float txt_off = gp.Style.LabelPadding.x;
4650 const float pad = txt_off + gp.CTicker.MaxSize.x + (rend_label ? txt_off + label_size.y : 0);
4651 float bar_w = 20;
4652 if (frame_size.x == 0)
4653 frame_size.x = bar_w + pad + 2 * gp.Style.PlotPadding.x;
4654 else {
4655 bar_w = frame_size.x - (pad + 2 * gp.Style.PlotPadding.x);
4656 if (bar_w < gp.Style.MajorTickLen.y)
4657 bar_w = gp.Style.MajorTickLen.y;
4658 }
4659
4660 ImDrawList &DrawList = *Window->DrawList;
4661 ImRect bb_frame = ImRect(Window->DC.CursorPos, Window->DC.CursorPos + frame_size);
4662 ImGui::ItemSize(bb_frame);
4663 if (!ImGui::ItemAdd(bb_frame, ID, &bb_frame))
4664 return;
4665
4666 ImGui::RenderFrame(bb_frame.Min, bb_frame.Max, GetStyleColorU32(ImPlotCol_FrameBg), true, G.Style.FrameRounding);
4667
4668 const bool opposite = ImHasFlag(flags, ImPlotColormapScaleFlags_Opposite);
4669 const bool inverted = ImHasFlag(flags, ImPlotColormapScaleFlags_Invert);
4670 const bool reversed = scale_min > scale_max;
4671
4672 float bb_grad_shift = opposite ? pad : 0;
4673 ImRect bb_grad(bb_frame.Min + gp.Style.PlotPadding + ImVec2(bb_grad_shift, 0),
4674 bb_frame.Min + ImVec2(bar_w + gp.Style.PlotPadding.x + bb_grad_shift,
4675 frame_size.y - gp.Style.PlotPadding.y));
4676
4677 ImGui::PushClipRect(bb_frame.Min, bb_frame.Max, true);
4678 const ImU32 col_text = ImGui::GetColorU32(ImGuiCol_Text);
4679
4680 const bool invert_scale = inverted ? (reversed ? false : true) : (reversed ? true : false);
4681 const float y_min = invert_scale ? bb_grad.Max.y : bb_grad.Min.y;
4682 const float y_max = invert_scale ? bb_grad.Min.y : bb_grad.Max.y;
4683
4684 RenderColorBar(gp.ColormapData.GetKeys(cmap), gp.ColormapData.GetKeyCount(cmap), DrawList, bb_grad, true, !inverted, !gp.ColormapData.IsQual(cmap));
4685 for (int i = 0; i < gp.CTicker.TickCount(); ++i) {
4686 const double y_pos_plt = gp.CTicker.Ticks[i].PlotPos;
4687 const float y_pos = ImRemap((float)y_pos_plt, (float)range.Max, (float)range.Min, y_min, y_max);
4688 const float tick_width = gp.CTicker.Ticks[i].Major ? gp.Style.MajorTickLen.y : gp.Style.MinorTickLen.y;
4689 const float tick_thick = gp.CTicker.Ticks[i].Major ? gp.Style.MajorTickSize.y : gp.Style.MinorTickSize.y;
4690 const float tick_t = (float)((y_pos_plt - scale_min) / (scale_max - scale_min));
4691 const ImU32 tick_col = CalcTextColor(gp.ColormapData.LerpTable(cmap,tick_t));
4692 if (y_pos < bb_grad.Max.y - 2 && y_pos > bb_grad.Min.y + 2) {
4693 DrawList.AddLine(opposite ? ImVec2(bb_grad.Min.x+1, y_pos) : ImVec2(bb_grad.Max.x-1, y_pos),
4694 opposite ? ImVec2(bb_grad.Min.x + tick_width, y_pos) : ImVec2(bb_grad.Max.x - tick_width, y_pos),
4695 tick_col,
4696 tick_thick);
4697 }
4698 const float txt_x = opposite ? bb_grad.Min.x - txt_off - gp.CTicker.Ticks[i].LabelSize.x : bb_grad.Max.x + txt_off;
4699 const float txt_y = y_pos - gp.CTicker.Ticks[i].LabelSize.y * 0.5f;
4700 DrawList.AddText(ImVec2(txt_x, txt_y), col_text, gp.CTicker.GetText(i));
4701 }
4702
4703 if (rend_label) {
4704 const float pos_x = opposite ? bb_frame.Min.x + gp.Style.PlotPadding.x : bb_grad.Max.x + 2 * txt_off + gp.CTicker.MaxSize.x;
4705 const float pos_y = bb_grad.GetCenter().y + label_size.x * 0.5f;
4706 const char* label_end = ImGui::FindRenderedTextEnd(label);
4707 AddTextVertical(&DrawList,ImVec2(pos_x,pos_y),col_text,label,label_end);
4708 }
4709 DrawList.AddRect(bb_grad.Min, bb_grad.Max, GetStyleColorU32(ImPlotCol_PlotBorder));
4710 ImGui::PopClipRect();
4711}
4712
4713bool ColormapSlider(const char* label, float* t, ImVec4* out, const char* format, ImPlotColormap cmap) {
4714 *t = ImClamp(*t,0.0f,1.0f);
4715 ImGuiContext &G = *GImGui;
4716 ImGuiWindow * Window = G.CurrentWindow;
4717 if (Window->SkipItems)
4718 return false;
4719 ImPlotContext& gp = *GImPlot;
4720 cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap;
4721 IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!");
4722 const ImU32* keys = gp.ColormapData.GetKeys(cmap);
4723 const int count = gp.ColormapData.GetKeyCount(cmap);
4724 const bool qual = gp.ColormapData.IsQual(cmap);
4725 const ImVec2 pos = ImGui::GetCurrentWindow()->DC.CursorPos;
4726 const float w = ImGui::CalcItemWidth();
4727 const float h = ImGui::GetFrameHeight();
4728 const ImRect rect = ImRect(pos.x,pos.y,pos.x+w,pos.y+h);
4729 RenderColorBar(keys,count,*ImGui::GetWindowDrawList(),rect,false,false,!qual);
4730 const ImU32 grab = CalcTextColor(gp.ColormapData.LerpTable(cmap,*t));
4731 // const ImU32 text = CalcTextColor(gp.ColormapData.LerpTable(cmap,0.5f));
4732 ImGui::PushStyleColor(ImGuiCol_FrameBg,IM_COL32_BLACK_TRANS);
4733 ImGui::PushStyleColor(ImGuiCol_FrameBgActive,IM_COL32_BLACK_TRANS);
4734 ImGui::PushStyleColor(ImGuiCol_FrameBgHovered,ImVec4(1,1,1,0.1f));
4735 ImGui::PushStyleColor(ImGuiCol_SliderGrab,grab);
4736 ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, grab);
4737 ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize,2);
4738 ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,0);
4739 const bool changed = ImGui::SliderFloat(label,t,0,1,format);
4740 ImGui::PopStyleColor(5);
4741 ImGui::PopStyleVar(2);
4742 if (out != nullptr)
4743 *out = ImGui::ColorConvertU32ToFloat4(gp.ColormapData.LerpTable(cmap,*t));
4744 return changed;
4745}
4746
4747bool ColormapButton(const char* label, const ImVec2& size_arg, ImPlotColormap cmap) {
4748 ImGuiContext &G = *GImGui;
4749 const ImGuiStyle& style = G.Style;
4750 ImGuiWindow * Window = G.CurrentWindow;
4751 if (Window->SkipItems)
4752 return false;
4753 ImPlotContext& gp = *GImPlot;
4754 cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap;
4755 IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!");
4756 const ImU32* keys = gp.ColormapData.GetKeys(cmap);
4757 const int count = gp.ColormapData.GetKeyCount(cmap);
4758 const bool qual = gp.ColormapData.IsQual(cmap);
4759 const ImVec2 pos = ImGui::GetCurrentWindow()->DC.CursorPos;
4760 const ImVec2 label_size = ImGui::CalcTextSize(label, nullptr, true);
4761 ImVec2 size = ImGui::CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
4762 const ImRect rect = ImRect(pos.x,pos.y,pos.x+size.x,pos.y+size.y);
4763 RenderColorBar(keys,count,*ImGui::GetWindowDrawList(),rect,false,false,!qual);
4764 const ImU32 text = CalcTextColor(gp.ColormapData.LerpTable(cmap,G.Style.ButtonTextAlign.x));
4765 ImGui::PushStyleColor(ImGuiCol_Button,IM_COL32_BLACK_TRANS);
4766 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,ImVec4(1,1,1,0.1f));
4767 ImGui::PushStyleColor(ImGuiCol_ButtonActive,ImVec4(1,1,1,0.2f));
4768 ImGui::PushStyleColor(ImGuiCol_Text,text);
4769 ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,0);
4770 const bool pressed = ImGui::Button(label,size);
4771 ImGui::PopStyleColor(4);
4772 ImGui::PopStyleVar(1);
4773 return pressed;
4774}
4775
4776//-----------------------------------------------------------------------------
4777// [Section] Miscellaneous
4778//-----------------------------------------------------------------------------
4779
4780ImPlotInputMap& GetInputMap() {
4781 IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?");
4782 ImPlotContext& gp = *GImPlot;
4783 return gp.InputMap;
4784}
4785
4786void MapInputDefault(ImPlotInputMap* dst) {
4787 ImPlotInputMap& map = dst ? *dst : GetInputMap();
4788 map.Pan = ImGuiMouseButton_Left;
4789 map.PanMod = ImGuiMod_None;
4790 map.Fit = ImGuiMouseButton_Left;
4791 map.Menu = ImGuiMouseButton_Right;
4792 map.Select = ImGuiMouseButton_Right;
4793 map.SelectMod = ImGuiMod_None;
4794 map.SelectCancel = ImGuiMouseButton_Left;
4795 map.SelectHorzMod = ImGuiMod_Alt;
4796 map.SelectVertMod = ImGuiMod_Shift;
4797 map.OverrideMod = ImGuiMod_Ctrl;
4798 map.ZoomMod = ImGuiMod_None;
4799 map.ZoomRate = 0.1f;
4800}
4801
4802void MapInputReverse(ImPlotInputMap* dst) {
4803 ImPlotInputMap& map = dst ? *dst : GetInputMap();
4804 map.Pan = ImGuiMouseButton_Right;
4805 map.PanMod = ImGuiMod_None;
4806 map.Fit = ImGuiMouseButton_Left;
4807 map.Menu = ImGuiMouseButton_Right;
4808 map.Select = ImGuiMouseButton_Left;
4809 map.SelectMod = ImGuiMod_None;
4810 map.SelectCancel = ImGuiMouseButton_Right;
4811 map.SelectHorzMod = ImGuiMod_Alt;
4812 map.SelectVertMod = ImGuiMod_Shift;
4813 map.OverrideMod = ImGuiMod_Ctrl;
4814 map.ZoomMod = ImGuiMod_None;
4815 map.ZoomRate = 0.1f;
4816}
4817
4818//-----------------------------------------------------------------------------
4819// [Section] Miscellaneous
4820//-----------------------------------------------------------------------------
4821
4822void ItemIcon(const ImVec4& col) {
4823 ItemIcon(ImGui::ColorConvertFloat4ToU32(col));
4824}
4825
4826void ItemIcon(ImU32 col) {
4827 const float txt_size = ImGui::GetTextLineHeight();
4828 ImVec2 size(txt_size-4,txt_size);
4829 ImGuiWindow* window = ImGui::GetCurrentWindow();
4830 ImVec2 pos = window->DC.CursorPos;
4831 ImGui::GetWindowDrawList()->AddRectFilled(pos + ImVec2(0,2), pos + size - ImVec2(0,2), col);
4832 ImGui::Dummy(size);
4833}
4834
4835void ColormapIcon(ImPlotColormap cmap) {
4836 ImPlotContext& gp = *GImPlot;
4837 const float txt_size = ImGui::GetTextLineHeight();
4838 ImVec2 size(txt_size-4,txt_size);
4839 ImGuiWindow* window = ImGui::GetCurrentWindow();
4840 ImVec2 pos = window->DC.CursorPos;
4841 ImRect rect(pos+ImVec2(0,2),pos+size-ImVec2(0,2));
4842 ImDrawList& DrawList = *ImGui::GetWindowDrawList();
4843 RenderColorBar(gp.ColormapData.GetKeys(cmap),gp.ColormapData.GetKeyCount(cmap),DrawList,rect,false,false,!gp.ColormapData.IsQual(cmap));
4844 ImGui::Dummy(size);
4845}
4846
4847ImDrawList* GetPlotDrawList() {
4848 return ImGui::GetWindowDrawList();
4849}
4850
4851void PushPlotClipRect(float expand) {
4852 ImPlotContext& gp = *GImPlot;
4853 IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PushPlotClipRect() needs to be called between BeginPlot() and EndPlot()!");
4854 SetupLock();
4855 ImRect rect = gp.CurrentPlot->PlotRect;
4856 rect.Expand(expand);
4857 ImGui::PushClipRect(rect.Min, rect.Max, true);
4858}
4859
4860void PopPlotClipRect() {
4861 SetupLock();
4862 ImGui::PopClipRect();
4863}
4864
4865static void HelpMarker(const char* desc) {
4866 ImGui::TextDisabled("(?)");
4867 if (ImGui::IsItemHovered()) {
4868 ImGui::BeginTooltip();
4869 ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
4870 ImGui::TextUnformatted(desc);
4871 ImGui::PopTextWrapPos();
4872 ImGui::EndTooltip();
4873 }
4874}
4875
4876bool ShowStyleSelector(const char* label)
4877{
4878 static int style_idx = -1;
4879 if (ImGui::Combo(label, &style_idx, "Auto\0Classic\0Dark\0Light\0"))
4880 {
4881 switch (style_idx)
4882 {
4883 case 0: StyleColorsAuto(); break;
4884 case 1: StyleColorsClassic(); break;
4885 case 2: StyleColorsDark(); break;
4886 case 3: StyleColorsLight(); break;
4887 }
4888 return true;
4889 }
4890 return false;
4891}
4892
4893bool ShowColormapSelector(const char* label) {
4894 ImPlotContext& gp = *GImPlot;
4895 bool set = false;
4896 if (ImGui::BeginCombo(label, gp.ColormapData.GetName(gp.Style.Colormap))) {
4897 for (int i = 0; i < gp.ColormapData.Count; ++i) {
4898 const char* name = gp.ColormapData.GetName(i);
4899 if (ImGui::Selectable(name, gp.Style.Colormap == i)) {
4900 gp.Style.Colormap = i;
4901 ImPlot::BustItemCache();
4902 set = true;
4903 }
4904 }
4905 ImGui::EndCombo();
4906 }
4907 return set;
4908}
4909
4910bool ShowInputMapSelector(const char* label) {
4911 static int map_idx = -1;
4912 if (ImGui::Combo(label, &map_idx, "Default\0Reversed\0"))
4913 {
4914 switch (map_idx)
4915 {
4916 case 0: MapInputDefault(); break;
4917 case 1: MapInputReverse(); break;
4918 }
4919 return true;
4920 }
4921 return false;
4922}
4923
4924
4925void ShowStyleEditor(ImPlotStyle* ref) {
4926 ImPlotContext& gp = *GImPlot;
4927 ImPlotStyle& style = GetStyle();
4928 static ImPlotStyle ref_saved_style;
4929 // Default to using internal storage as reference
4930 static bool init = true;
4931 if (init && ref == nullptr)
4932 ref_saved_style = style;
4933 init = false;
4934 if (ref == nullptr)
4935 ref = &ref_saved_style;
4936
4937 if (ImPlot::ShowStyleSelector("Colors##Selector"))
4938 ref_saved_style = style;
4939
4940 // Save/Revert button
4941 if (ImGui::Button("Save Ref"))
4942 *ref = ref_saved_style = style;
4943 ImGui::SameLine();
4944 if (ImGui::Button("Revert Ref"))
4945 style = *ref;
4946 ImGui::SameLine();
4947 HelpMarker("Save/Revert in local non-persistent storage. Default Colors definition are not affected. "
4948 "Use \"Export\" below to save them somewhere.");
4949 if (ImGui::BeginTabBar("##StyleEditor")) {
4950 if (ImGui::BeginTabItem("Variables")) {
4951 ImGui::Text("Item Styling");
4952 ImGui::SliderFloat("LineWeight", &style.LineWeight, 0.0f, 5.0f, "%.1f");
4953 ImGui::SliderFloat("MarkerSize", &style.MarkerSize, 2.0f, 10.0f, "%.1f");
4954 ImGui::SliderFloat("MarkerWeight", &style.MarkerWeight, 0.0f, 5.0f, "%.1f");
4955 ImGui::SliderFloat("FillAlpha", &style.FillAlpha, 0.0f, 1.0f, "%.2f");
4956 ImGui::SliderFloat("ErrorBarSize", &style.ErrorBarSize, 0.0f, 10.0f, "%.1f");
4957 ImGui::SliderFloat("ErrorBarWeight", &style.ErrorBarWeight, 0.0f, 5.0f, "%.1f");
4958 ImGui::SliderFloat("DigitalBitHeight", &style.DigitalBitHeight, 0.0f, 20.0f, "%.1f");
4959 ImGui::SliderFloat("DigitalBitGap", &style.DigitalBitGap, 0.0f, 20.0f, "%.1f");
4960 ImGui::Text("Plot Styling");
4961 ImGui::SliderFloat("PlotBorderSize", &style.PlotBorderSize, 0.0f, 2.0f, "%.0f");
4962 ImGui::SliderFloat("MinorAlpha", &style.MinorAlpha, 0.0f, 1.0f, "%.2f");
4963 ImGui::SliderFloat2("MajorTickLen", (float*)&style.MajorTickLen, 0.0f, 20.0f, "%.0f");
4964 ImGui::SliderFloat2("MinorTickLen", (float*)&style.MinorTickLen, 0.0f, 20.0f, "%.0f");
4965 ImGui::SliderFloat2("MajorTickSize", (float*)&style.MajorTickSize, 0.0f, 2.0f, "%.1f");
4966 ImGui::SliderFloat2("MinorTickSize", (float*)&style.MinorTickSize, 0.0f, 2.0f, "%.1f");
4967 ImGui::SliderFloat2("MajorGridSize", (float*)&style.MajorGridSize, 0.0f, 2.0f, "%.1f");
4968 ImGui::SliderFloat2("MinorGridSize", (float*)&style.MinorGridSize, 0.0f, 2.0f, "%.1f");
4969 ImGui::SliderFloat2("PlotDefaultSize", (float*)&style.PlotDefaultSize, 0.0f, 1000, "%.0f");
4970 ImGui::SliderFloat2("PlotMinSize", (float*)&style.PlotMinSize, 0.0f, 300, "%.0f");
4971 ImGui::Text("Plot Padding");
4972 ImGui::SliderFloat2("PlotPadding", (float*)&style.PlotPadding, 0.0f, 20.0f, "%.0f");
4973 ImGui::SliderFloat2("LabelPadding", (float*)&style.LabelPadding, 0.0f, 20.0f, "%.0f");
4974 ImGui::SliderFloat2("LegendPadding", (float*)&style.LegendPadding, 0.0f, 20.0f, "%.0f");
4975 ImGui::SliderFloat2("LegendInnerPadding", (float*)&style.LegendInnerPadding, 0.0f, 10.0f, "%.0f");
4976 ImGui::SliderFloat2("LegendSpacing", (float*)&style.LegendSpacing, 0.0f, 5.0f, "%.0f");
4977 ImGui::SliderFloat2("MousePosPadding", (float*)&style.MousePosPadding, 0.0f, 20.0f, "%.0f");
4978 ImGui::SliderFloat2("AnnotationPadding", (float*)&style.AnnotationPadding, 0.0f, 5.0f, "%.0f");
4979 ImGui::SliderFloat2("FitPadding", (float*)&style.FitPadding, 0, 0.2f, "%.2f");
4980
4981 ImGui::EndTabItem();
4982 }
4983 if (ImGui::BeginTabItem("Colors")) {
4984 static int output_dest = 0;
4985 static bool output_only_modified = false;
4986
4987 if (ImGui::Button("Export", ImVec2(75,0))) {
4988 if (output_dest == 0)
4989 ImGui::LogToClipboard();
4990 else
4991 ImGui::LogToTTY();
4992 ImGui::LogText("ImVec4* colors = ImPlot::GetStyle().Colors;\n");
4993 for (int i = 0; i < ImPlotCol_COUNT; i++) {
4994 const ImVec4& col = style.Colors[i];
4995 const char* name = ImPlot::GetStyleColorName(i);
4996 if (!output_only_modified || memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0) {
4997 if (IsColorAuto(i))
4998 ImGui::LogText("colors[ImPlotCol_%s]%*s= IMPLOT_AUTO_COL;\n",name,14 - (int)strlen(name), "");
4999 else
5000 ImGui::LogText("colors[ImPlotCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);\n",
5001 name, 14 - (int)strlen(name), "", col.x, col.y, col.z, col.w);
5002 }
5003 }
5004 ImGui::LogFinish();
5005 }
5006 ImGui::SameLine(); ImGui::SetNextItemWidth(120); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0");
5007 ImGui::SameLine(); ImGui::Checkbox("Only Modified Colors", &output_only_modified);
5008
5009 static ImGuiTextFilter filter;
5010 filter.Draw("Filter colors", ImGui::GetFontSize() * 16);
5011
5012 static ImGuiColorEditFlags alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf;
5013 if (ImGui::RadioButton("Opaque", alpha_flags == ImGuiColorEditFlags_None)) { alpha_flags = ImGuiColorEditFlags_None; } ImGui::SameLine();
5014 if (ImGui::RadioButton("Alpha", alpha_flags == ImGuiColorEditFlags_AlphaPreview)) { alpha_flags = ImGuiColorEditFlags_AlphaPreview; } ImGui::SameLine();
5015 if (ImGui::RadioButton("Both", alpha_flags == ImGuiColorEditFlags_AlphaPreviewHalf)) { alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf; } ImGui::SameLine();
5016 HelpMarker(
5017 "In the color list:\n"
5018 "Left-click on colored square to open color picker,\n"
5019 "Right-click to open edit options menu.");
5020 ImGui::Separator();
5021 ImGui::PushItemWidth(-160);
5022 for (int i = 0; i < ImPlotCol_COUNT; i++) {
5023 const char* name = ImPlot::GetStyleColorName(i);
5024 if (!filter.PassFilter(name))
5025 continue;
5026 ImGui::PushID(i);
5027 ImVec4 temp = GetStyleColorVec4(i);
5028 const bool is_auto = IsColorAuto(i);
5029 if (!is_auto)
5030 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.25f);
5031 if (ImGui::Button("Auto")) {
5032 if (is_auto)
5033 style.Colors[i] = temp;
5034 else
5035 style.Colors[i] = IMPLOT_AUTO_COL;
5036 BustItemCache();
5037 }
5038 if (!is_auto)
5039 ImGui::PopStyleVar();
5040 ImGui::SameLine();
5041 if (ImGui::ColorEdit4(name, &temp.x, ImGuiColorEditFlags_NoInputs | alpha_flags)) {
5042 style.Colors[i] = temp;
5043 BustItemCache();
5044 }
5045 if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) {
5046 ImGui::SameLine(175); if (ImGui::Button("Save")) { ref->Colors[i] = style.Colors[i]; }
5047 ImGui::SameLine(); if (ImGui::Button("Revert")) {
5048 style.Colors[i] = ref->Colors[i];
5049 BustItemCache();
5050 }
5051 }
5052 ImGui::PopID();
5053 }
5054 ImGui::PopItemWidth();
5055 ImGui::Separator();
5056 ImGui::Text("Colors that are set to Auto (i.e. IMPLOT_AUTO_COL) will\n"
5057 "be automatically deduced from your ImGui style or the\n"
5058 "current ImPlot Colormap. If you want to style individual\n"
5059 "plot items, use Push/PopStyleColor around its function.");
5060 ImGui::EndTabItem();
5061 }
5062 if (ImGui::BeginTabItem("Colormaps")) {
5063 static int output_dest = 0;
5064 if (ImGui::Button("Export", ImVec2(75,0))) {
5065 if (output_dest == 0)
5066 ImGui::LogToClipboard();
5067 else
5068 ImGui::LogToTTY();
5069 int size = GetColormapSize();
5070 const char* name = GetColormapName(gp.Style.Colormap);
5071 ImGui::LogText("static const ImU32 %s_Data[%d] = {\n", name, size);
5072 for (int i = 0; i < size; ++i) {
5073 ImU32 col = GetColormapColorU32(i,gp.Style.Colormap);
5074 ImGui::LogText(" %u%s\n", col, i == size - 1 ? "" : ",");
5075 }
5076 ImGui::LogText("};\nImPlotColormap %s = ImPlot::AddColormap(\"%s\", %s_Data, %d);", name, name, name, size);
5077 ImGui::LogFinish();
5078 }
5079 ImGui::SameLine(); ImGui::SetNextItemWidth(120); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0");
5080 ImGui::SameLine();
5081 static bool edit = false;
5082 ImGui::Checkbox("Edit Mode",&edit);
5083
5084 // built-in/added
5085 ImGui::Separator();
5086 for (int i = 0; i < gp.ColormapData.Count; ++i) {
5087 ImGui::PushID(i);
5088 int size = gp.ColormapData.GetKeyCount(i);
5089 bool selected = i == gp.Style.Colormap;
5090
5091 const char* name = GetColormapName(i);
5092 if (!selected)
5093 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.25f);
5094 if (ImGui::Button(name, ImVec2(100,0))) {
5095 gp.Style.Colormap = i;
5096 BustItemCache();
5097 }
5098 if (!selected)
5099 ImGui::PopStyleVar();
5100 ImGui::SameLine();
5101 ImGui::BeginGroup();
5102 if (edit) {
5103 for (int c = 0; c < size; ++c) {
5104 ImGui::PushID(c);
5105 ImVec4 col4 = ImGui::ColorConvertU32ToFloat4(gp.ColormapData.GetKeyColor(i,c));
5106 if (ImGui::ColorEdit4("",&col4.x,ImGuiColorEditFlags_NoInputs)) {
5107 ImU32 col32 = ImGui::ColorConvertFloat4ToU32(col4);
5108 gp.ColormapData.SetKeyColor(i,c,col32);
5109 BustItemCache();
5110 }
5111 if ((c + 1) % 12 != 0 && c != size -1)
5112 ImGui::SameLine();
5113 ImGui::PopID();
5114 }
5115 }
5116 else {
5117 if (ImPlot::ColormapButton("##",ImVec2(-1,0),i))
5118 edit = true;
5119 }
5120 ImGui::EndGroup();
5121 ImGui::PopID();
5122 }
5123
5124
5125 static ImVector<ImVec4> custom;
5126 if (custom.Size == 0) {
5127 custom.push_back(ImVec4(1,0,0,1));
5128 custom.push_back(ImVec4(0,1,0,1));
5129 custom.push_back(ImVec4(0,0,1,1));
5130 }
5131 ImGui::Separator();
5132 ImGui::BeginGroup();
5133 static char name[16] = "MyColormap";
5134
5135
5136 if (ImGui::Button("+", ImVec2((100 - ImGui::GetStyle().ItemSpacing.x)/2,0)))
5137 custom.push_back(ImVec4(0,0,0,1));
5138 ImGui::SameLine();
5139 if (ImGui::Button("-", ImVec2((100 - ImGui::GetStyle().ItemSpacing.x)/2,0)) && custom.Size > 2)
5140 custom.pop_back();
5141 ImGui::SetNextItemWidth(100);
5142 ImGui::InputText("##Name",name,16,ImGuiInputTextFlags_CharsNoBlank);
5143 static bool qual = true;
5144 ImGui::Checkbox("Qualitative",&qual);
5145 if (ImGui::Button("Add", ImVec2(100, 0)) && gp.ColormapData.GetIndex(name)==-1)
5146 AddColormap(name,custom.Data,custom.Size,qual);
5147
5148 ImGui::EndGroup();
5149 ImGui::SameLine();
5150 ImGui::BeginGroup();
5151 for (int c = 0; c < custom.Size; ++c) {
5152 ImGui::PushID(c);
5153 if (ImGui::ColorEdit4("##Col1", &custom[c].x, ImGuiColorEditFlags_NoInputs)) {
5154
5155 }
5156 if ((c + 1) % 12 != 0)
5157 ImGui::SameLine();
5158 ImGui::PopID();
5159 }
5160 ImGui::EndGroup();
5161
5162
5163 ImGui::EndTabItem();
5164 }
5165 ImGui::EndTabBar();
5166 }
5167}
5168
5169void ShowUserGuide() {
5170 ImGui::BulletText("Left-click drag within the plot area to pan X and Y axes.");
5171 ImGui::Indent();
5172 ImGui::BulletText("Left-click drag on axis labels to pan an individual axis.");
5173 ImGui::Unindent();
5174 ImGui::BulletText("Scroll in the plot area to zoom both X any Y axes.");
5175 ImGui::Indent();
5176 ImGui::BulletText("Scroll on axis labels to zoom an individual axis.");
5177 ImGui::Unindent();
5178 ImGui::BulletText("Right-click drag to box select data.");
5179 ImGui::Indent();
5180 ImGui::BulletText("Hold Alt to expand box selection horizontally.");
5181 ImGui::BulletText("Hold Shift to expand box selection vertically.");
5182 ImGui::BulletText("Left-click while box selecting to cancel the selection.");
5183 ImGui::Unindent();
5184 ImGui::BulletText("Double left-click to fit all visible data.");
5185 ImGui::Indent();
5186 ImGui::BulletText("Double left-click axis labels to fit the individual axis.");
5187 ImGui::Unindent();
5188 ImGui::BulletText("Right-click open the full plot context menu.");
5189 ImGui::Indent();
5190 ImGui::BulletText("Right-click axis labels to open an individual axis context menu.");
5191 ImGui::Unindent();
5192 ImGui::BulletText("Click legend label icons to show/hide plot items.");
5193}
5194
5195void ShowTicksMetrics(const ImPlotTicker& ticker) {
5196 ImGui::BulletText("Size: %d", ticker.TickCount());
5197 ImGui::BulletText("MaxSize: [%f,%f]", ticker.MaxSize.x, ticker.MaxSize.y);
5198}
5199
5200void ShowAxisMetrics(const ImPlotPlot& plot, const ImPlotAxis& axis) {
5201 ImGui::BulletText("Label: %s", axis.LabelOffset == -1 ? "[none]" : plot.GetAxisLabel(axis));
5202 ImGui::BulletText("Flags: 0x%08X", axis.Flags);
5203 ImGui::BulletText("Range: [%f,%f]",axis.Range.Min, axis.Range.Max);
5204 ImGui::BulletText("Pixels: %f", axis.PixelSize());
5205 ImGui::BulletText("Aspect: %f", axis.GetAspect());
5206 ImGui::BulletText(axis.OrthoAxis == nullptr ? "OrtherAxis: NULL" : "OrthoAxis: 0x%08X", axis.OrthoAxis->ID);
5207 ImGui::BulletText("LinkedMin: %p", (void*)axis.LinkedMin);
5208 ImGui::BulletText("LinkedMax: %p", (void*)axis.LinkedMax);
5209 ImGui::BulletText("HasRange: %s", axis.HasRange ? "true" : "false");
5210 ImGui::BulletText("Hovered: %s", axis.Hovered ? "true" : "false");
5211 ImGui::BulletText("Held: %s", axis.Held ? "true" : "false");
5212
5213 if (ImGui::TreeNode("Transform")) {
5214 ImGui::BulletText("PixelMin: %f", axis.PixelMin);
5215 ImGui::BulletText("PixelMax: %f", axis.PixelMax);
5216 ImGui::BulletText("ScaleToPixel: %f", axis.ScaleToPixel);
5217 ImGui::BulletText("ScaleMax: %f", axis.ScaleMax);
5218 ImGui::TreePop();
5219 }
5220
5221 if (ImGui::TreeNode("Ticks")) {
5222 ShowTicksMetrics(axis.Ticker);
5223 ImGui::TreePop();
5224 }
5225}
5226
5227void ShowMetricsWindow(bool* p_popen) {
5228
5229 static bool show_plot_rects = false;
5230 static bool show_axes_rects = false;
5231 static bool show_axis_rects = false;
5232 static bool show_canvas_rects = false;
5233 static bool show_frame_rects = false;
5234 static bool show_subplot_frame_rects = false;
5235 static bool show_subplot_grid_rects = false;
5236 static bool show_legend_rects = false;
5237
5238 ImDrawList& fg = *ImGui::GetForegroundDrawList();
5239
5240 ImPlotContext& gp = *GImPlot;
5241 // ImGuiContext& g = *GImGui;
5242 ImGuiIO& io = ImGui::GetIO();
5243 ImGui::Begin("ImPlot Metrics", p_popen);
5244 ImGui::Text("ImPlot " IMPLOT_VERSION);
5245 ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
5246 ImGui::Text("Mouse Position: [%.0f,%.0f]", io.MousePos.x, io.MousePos.y);
5247 ImGui::Separator();
5248 if (ImGui::TreeNode("Tools")) {
5249 if (ImGui::Button("Bust Plot Cache"))
5250 BustPlotCache();
5251 ImGui::SameLine();
5252 if (ImGui::Button("Bust Item Cache"))
5253 BustItemCache();
5254 ImGui::Checkbox("Show Frame Rects", &show_frame_rects);
5255 ImGui::Checkbox("Show Canvas Rects",&show_canvas_rects);
5256 ImGui::Checkbox("Show Plot Rects", &show_plot_rects);
5257 ImGui::Checkbox("Show Axes Rects", &show_axes_rects);
5258 ImGui::Checkbox("Show Axis Rects", &show_axis_rects);
5259 ImGui::Checkbox("Show Subplot Frame Rects", &show_subplot_frame_rects);
5260 ImGui::Checkbox("Show Subplot Grid Rects", &show_subplot_grid_rects);
5261 ImGui::Checkbox("Show Legend Rects", &show_legend_rects);
5262 ImGui::TreePop();
5263 }
5264 const int n_plots = gp.Plots.GetBufSize();
5265 const int n_subplots = gp.Subplots.GetBufSize();
5266 // render rects
5267 for (int p = 0; p < n_plots; ++p) {
5268 ImPlotPlot* plot = gp.Plots.GetByIndex(p);
5269 if (show_frame_rects)
5270 fg.AddRect(plot->FrameRect.Min, plot->FrameRect.Max, IM_COL32(255,0,255,255));
5271 if (show_canvas_rects)
5272 fg.AddRect(plot->CanvasRect.Min, plot->CanvasRect.Max, IM_COL32(0,255,255,255));
5273 if (show_plot_rects)
5274 fg.AddRect(plot->PlotRect.Min, plot->PlotRect.Max, IM_COL32(255,255,0,255));
5275 if (show_axes_rects)
5276 fg.AddRect(plot->AxesRect.Min, plot->AxesRect.Max, IM_COL32(0,255,128,255));
5277 if (show_axis_rects) {
5278 for (int i = 0; i < ImAxis_COUNT; ++i) {
5279 if (plot->Axes[i].Enabled)
5280 fg.AddRect(plot->Axes[i].HoverRect.Min, plot->Axes[i].HoverRect.Max, IM_COL32(0,255,0,255));
5281 }
5282 }
5283 if (show_legend_rects && plot->Items.GetLegendCount() > 0) {
5284 fg.AddRect(plot->Items.Legend.Rect.Min, plot->Items.Legend.Rect.Max, IM_COL32(255,192,0,255));
5285 fg.AddRect(plot->Items.Legend.RectClamped.Min, plot->Items.Legend.RectClamped.Max, IM_COL32(255,128,0,255));
5286 }
5287 }
5288 for (int p = 0; p < n_subplots; ++p) {
5289 ImPlotSubplot* subplot = gp.Subplots.GetByIndex(p);
5290 if (show_subplot_frame_rects)
5291 fg.AddRect(subplot->FrameRect.Min, subplot->FrameRect.Max, IM_COL32(255,0,0,255));
5292 if (show_subplot_grid_rects)
5293 fg.AddRect(subplot->GridRect.Min, subplot->GridRect.Max, IM_COL32(0,0,255,255));
5294 if (show_legend_rects && subplot->Items.GetLegendCount() > 0) {
5295 fg.AddRect(subplot->Items.Legend.Rect.Min, subplot->Items.Legend.Rect.Max, IM_COL32(255,192,0,255));
5296 fg.AddRect(subplot->Items.Legend.RectClamped.Min, subplot->Items.Legend.RectClamped.Max, IM_COL32(255,128,0,255));
5297 }
5298 }
5299 if (ImGui::TreeNode("Plots","Plots (%d)", n_plots)) {
5300 for (int p = 0; p < n_plots; ++p) {
5301 // plot
5302 ImPlotPlot& plot = *gp.Plots.GetByIndex(p);
5303 ImGui::PushID(p);
5304 if (ImGui::TreeNode("Plot", "Plot [0x%08X]", plot.ID)) {
5305 int n_items = plot.Items.GetItemCount();
5306 if (ImGui::TreeNode("Items", "Items (%d)", n_items)) {
5307 for (int i = 0; i < n_items; ++i) {
5308 ImPlotItem* item = plot.Items.GetItemByIndex(i);
5309 ImGui::PushID(i);
5310 if (ImGui::TreeNode("Item", "Item [0x%08X]", item->ID)) {
5311 ImGui::Bullet(); ImGui::Checkbox("Show", &item->Show);
5312 ImGui::Bullet();
5313 ImVec4 temp = ImGui::ColorConvertU32ToFloat4(item->Color);
5314 if (ImGui::ColorEdit4("Color",&temp.x, ImGuiColorEditFlags_NoInputs))
5315 item->Color = ImGui::ColorConvertFloat4ToU32(temp);
5316
5317 ImGui::BulletText("NameOffset: %d",item->NameOffset);
5318 ImGui::BulletText("Name: %s", item->NameOffset != -1 ? plot.Items.Legend.Labels.Buf.Data + item->NameOffset : "N/A");
5319 ImGui::BulletText("Hovered: %s",item->LegendHovered ? "true" : "false");
5320 ImGui::TreePop();
5321 }
5322 ImGui::PopID();
5323 }
5324 ImGui::TreePop();
5325 }
5326 char buff[16];
5327 for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) {
5328 ImFormatString(buff,16,"X-Axis %d", i+1);
5329 if (plot.XAxis(i).Enabled && ImGui::TreeNode(buff, "X-Axis %d [0x%08X]", i+1, plot.XAxis(i).ID)) {
5330 ShowAxisMetrics(plot, plot.XAxis(i));
5331 ImGui::TreePop();
5332 }
5333 }
5334 for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) {
5335 ImFormatString(buff,16,"Y-Axis %d", i+1);
5336 if (plot.YAxis(i).Enabled && ImGui::TreeNode(buff, "Y-Axis %d [0x%08X]", i+1, plot.YAxis(i).ID)) {
5337 ShowAxisMetrics(plot, plot.YAxis(i));
5338 ImGui::TreePop();
5339 }
5340 }
5341 ImGui::BulletText("Title: %s", plot.HasTitle() ? plot.GetTitle() : "none");
5342 ImGui::BulletText("Flags: 0x%08X", plot.Flags);
5343 ImGui::BulletText("Initialized: %s", plot.Initialized ? "true" : "false");
5344 ImGui::BulletText("Selecting: %s", plot.Selecting ? "true" : "false");
5345 ImGui::BulletText("Selected: %s", plot.Selected ? "true" : "false");
5346 ImGui::BulletText("Hovered: %s", plot.Hovered ? "true" : "false");
5347 ImGui::BulletText("Held: %s", plot.Held ? "true" : "false");
5348 ImGui::BulletText("LegendHovered: %s", plot.Items.Legend.Hovered ? "true" : "false");
5349 ImGui::BulletText("ContextLocked: %s", plot.ContextLocked ? "true" : "false");
5350 ImGui::TreePop();
5351 }
5352 ImGui::PopID();
5353 }
5354 ImGui::TreePop();
5355 }
5356
5357 if (ImGui::TreeNode("Subplots","Subplots (%d)", n_subplots)) {
5358 for (int p = 0; p < n_subplots; ++p) {
5359 // plot
5360 ImPlotSubplot& plot = *gp.Subplots.GetByIndex(p);
5361 ImGui::PushID(p);
5362 if (ImGui::TreeNode("Subplot", "Subplot [0x%08X]", plot.ID)) {
5363 int n_items = plot.Items.GetItemCount();
5364 if (ImGui::TreeNode("Items", "Items (%d)", n_items)) {
5365 for (int i = 0; i < n_items; ++i) {
5366 ImPlotItem* item = plot.Items.GetItemByIndex(i);
5367 ImGui::PushID(i);
5368 if (ImGui::TreeNode("Item", "Item [0x%08X]", item->ID)) {
5369 ImGui::Bullet(); ImGui::Checkbox("Show", &item->Show);
5370 ImGui::Bullet();
5371 ImVec4 temp = ImGui::ColorConvertU32ToFloat4(item->Color);
5372 if (ImGui::ColorEdit4("Color",&temp.x, ImGuiColorEditFlags_NoInputs))
5373 item->Color = ImGui::ColorConvertFloat4ToU32(temp);
5374
5375 ImGui::BulletText("NameOffset: %d",item->NameOffset);
5376 ImGui::BulletText("Name: %s", item->NameOffset != -1 ? plot.Items.Legend.Labels.Buf.Data + item->NameOffset : "N/A");
5377 ImGui::BulletText("Hovered: %s",item->LegendHovered ? "true" : "false");
5378 ImGui::TreePop();
5379 }
5380 ImGui::PopID();
5381 }
5382 ImGui::TreePop();
5383 }
5384 ImGui::BulletText("Flags: 0x%08X", plot.Flags);
5385 ImGui::BulletText("FrameHovered: %s", plot.FrameHovered ? "true" : "false");
5386 ImGui::BulletText("LegendHovered: %s", plot.Items.Legend.Hovered ? "true" : "false");
5387 ImGui::TreePop();
5388 }
5389 ImGui::PopID();
5390 }
5391 ImGui::TreePop();
5392 }
5393 if (ImGui::TreeNode("Colormaps")) {
5394 ImGui::BulletText("Colormaps: %d", gp.ColormapData.Count);
5395 ImGui::BulletText("Memory: %d bytes", gp.ColormapData.Tables.Size * 4);
5396 if (ImGui::TreeNode("Data")) {
5397 for (int m = 0; m < gp.ColormapData.Count; ++m) {
5398 if (ImGui::TreeNode(gp.ColormapData.GetName(m))) {
5399 int count = gp.ColormapData.GetKeyCount(m);
5400 int size = gp.ColormapData.GetTableSize(m);
5401 bool qual = gp.ColormapData.IsQual(m);
5402 ImGui::BulletText("Qualitative: %s", qual ? "true" : "false");
5403 ImGui::BulletText("Key Count: %d", count);
5404 ImGui::BulletText("Table Size: %d", size);
5405 ImGui::Indent();
5406
5407 static float t = 0.5;
5408 ImVec4 samp;
5409 float wid = 32 * 10 - ImGui::GetFrameHeight() - ImGui::GetStyle().ItemSpacing.x;
5410 ImGui::SetNextItemWidth(wid);
5411 ImPlot::ColormapSlider("##Sample",&t,&samp,"%.3f",m);
5412 ImGui::SameLine();
5413 ImGui::ColorButton("Sampler",samp);
5414 ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0));
5415 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0));
5416 for (int c = 0; c < size; ++c) {
5417 ImVec4 col = ImGui::ColorConvertU32ToFloat4(gp.ColormapData.GetTableColor(m,c));
5418 ImGui::PushID(m*1000+c);
5419 ImGui::ColorButton("",col,0,ImVec2(10,10));
5420 ImGui::PopID();
5421 if ((c + 1) % 32 != 0 && c != size - 1)
5422 ImGui::SameLine();
5423 }
5424 ImGui::PopStyleVar();
5425 ImGui::PopStyleColor();
5426 ImGui::Unindent();
5427 ImGui::TreePop();
5428 }
5429 }
5430 ImGui::TreePop();
5431 }
5432 ImGui::TreePop();
5433 }
5434 ImGui::End();
5435}
5436
5437bool ShowDatePicker(const char* id, int* level, ImPlotTime* t, const ImPlotTime* t1, const ImPlotTime* t2) {
5438
5439 ImGui::PushID(id);
5440 ImGui::BeginGroup();
5441
5442 ImGuiStyle& style = ImGui::GetStyle();
5443 ImVec4 col_txt = style.Colors[ImGuiCol_Text];
5444 ImVec4 col_dis = style.Colors[ImGuiCol_TextDisabled];
5445 ImVec4 col_btn = style.Colors[ImGuiCol_Button];
5446 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
5447 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0));
5448
5449 const float ht = ImGui::GetFrameHeight();
5450 ImVec2 cell_size(ht*1.25f,ht);
5451 char buff[32];
5452 bool clk = false;
5453 tm& Tm = GImPlot->Tm;
5454
5455 const int min_yr = 1970;
5456 const int max_yr = 2999;
5457
5458 // t1 parts
5459 int t1_mo = 0; int t1_md = 0; int t1_yr = 0;
5460 if (t1 != nullptr) {
5461 GetTime(*t1,&Tm);
5462 t1_mo = Tm.tm_mon;
5463 t1_md = Tm.tm_mday;
5464 t1_yr = Tm.tm_year + 1900;
5465 }
5466
5467 // t2 parts
5468 int t2_mo = 0; int t2_md = 0; int t2_yr = 0;
5469 if (t2 != nullptr) {
5470 GetTime(*t2,&Tm);
5471 t2_mo = Tm.tm_mon;
5472 t2_md = Tm.tm_mday;
5473 t2_yr = Tm.tm_year + 1900;
5474 }
5475
5476 // day widget
5477 if (*level == 0) {
5478 *t = FloorTime(*t, ImPlotTimeUnit_Day);
5479 GetTime(*t, &Tm);
5480 const int this_year = Tm.tm_year + 1900;
5481 const int last_year = this_year - 1;
5482 const int next_year = this_year + 1;
5483 const int this_mon = Tm.tm_mon;
5484 const int last_mon = this_mon == 0 ? 11 : this_mon - 1;
5485 const int next_mon = this_mon == 11 ? 0 : this_mon + 1;
5486 const int days_this_mo = GetDaysInMonth(this_year, this_mon);
5487 const int days_last_mo = GetDaysInMonth(this_mon == 0 ? last_year : this_year, last_mon);
5488 ImPlotTime t_first_mo = FloorTime(*t,ImPlotTimeUnit_Mo);
5489 GetTime(t_first_mo,&Tm);
5490 const int first_wd = Tm.tm_wday;
5491 // month year
5492 ImFormatString(buff, 32, "%s %d", MONTH_NAMES[this_mon], this_year);
5493 if (ImGui::Button(buff))
5494 *level = 1;
5495 ImGui::SameLine(5*cell_size.x);
5496 BeginDisabledControls(this_year <= min_yr && this_mon == 0);
5497 if (ImGui::ArrowButtonEx("##Up",ImGuiDir_Up,cell_size))
5498 *t = AddTime(*t, ImPlotTimeUnit_Mo, -1);
5499 EndDisabledControls(this_year <= min_yr && this_mon == 0);
5500 ImGui::SameLine();
5501 BeginDisabledControls(this_year >= max_yr && this_mon == 11);
5502 if (ImGui::ArrowButtonEx("##Down",ImGuiDir_Down,cell_size))
5503 *t = AddTime(*t, ImPlotTimeUnit_Mo, 1);
5504 EndDisabledControls(this_year >= max_yr && this_mon == 11);
5505 // render weekday abbreviations
5506 ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
5507 for (int i = 0; i < 7; ++i) {
5508 ImGui::Button(WD_ABRVS[i],cell_size);
5509 if (i != 6) { ImGui::SameLine(); }
5510 }
5511 ImGui::PopItemFlag();
5512 // 0 = last mo, 1 = this mo, 2 = next mo
5513 int mo = first_wd > 0 ? 0 : 1;
5514 int day = mo == 1 ? 1 : days_last_mo - first_wd + 1;
5515 for (int i = 0; i < 6; ++i) {
5516 for (int j = 0; j < 7; ++j) {
5517 if (mo == 0 && day > days_last_mo) {
5518 mo = 1;
5519 day = 1;
5520 }
5521 else if (mo == 1 && day > days_this_mo) {
5522 mo = 2;
5523 day = 1;
5524 }
5525 const int now_yr = (mo == 0 && this_mon == 0) ? last_year : ((mo == 2 && this_mon == 11) ? next_year : this_year);
5526 const int now_mo = mo == 0 ? last_mon : (mo == 1 ? this_mon : next_mon);
5527 const int now_md = day;
5528
5529 const bool off_mo = mo == 0 || mo == 2;
5530 const bool t1_or_t2 = (t1 != nullptr && t1_mo == now_mo && t1_yr == now_yr && t1_md == now_md) ||
5531 (t2 != nullptr && t2_mo == now_mo && t2_yr == now_yr && t2_md == now_md);
5532
5533 if (off_mo)
5534 ImGui::PushStyleColor(ImGuiCol_Text, col_dis);
5535 if (t1_or_t2) {
5536 ImGui::PushStyleColor(ImGuiCol_Button, col_btn);
5537 ImGui::PushStyleColor(ImGuiCol_Text, col_txt);
5538 }
5539 ImGui::PushID(i*7+j);
5540 ImFormatString(buff,32,"%d",day);
5541 if (now_yr == min_yr-1 || now_yr == max_yr+1) {
5542 ImGui::Dummy(cell_size);
5543 }
5544 else if (ImGui::Button(buff,cell_size) && !clk) {
5545 *t = MakeTime(now_yr, now_mo, now_md);
5546 clk = true;
5547 }
5548 ImGui::PopID();
5549 if (t1_or_t2)
5550 ImGui::PopStyleColor(2);
5551 if (off_mo)
5552 ImGui::PopStyleColor();
5553 if (j != 6)
5554 ImGui::SameLine();
5555 day++;
5556 }
5557 }
5558 }
5559 // month widget
5560 else if (*level == 1) {
5561 *t = FloorTime(*t, ImPlotTimeUnit_Mo);
5562 GetTime(*t, &Tm);
5563 int this_yr = Tm.tm_year + 1900;
5564 ImFormatString(buff, 32, "%d", this_yr);
5565 if (ImGui::Button(buff))
5566 *level = 2;
5567 BeginDisabledControls(this_yr <= min_yr);
5568 ImGui::SameLine(5*cell_size.x);
5569 if (ImGui::ArrowButtonEx("##Up",ImGuiDir_Up,cell_size))
5570 *t = AddTime(*t, ImPlotTimeUnit_Yr, -1);
5571 EndDisabledControls(this_yr <= min_yr);
5572 ImGui::SameLine();
5573 BeginDisabledControls(this_yr >= max_yr);
5574 if (ImGui::ArrowButtonEx("##Down",ImGuiDir_Down,cell_size))
5575 *t = AddTime(*t, ImPlotTimeUnit_Yr, 1);
5576 EndDisabledControls(this_yr >= max_yr);
5577 // ImGui::Dummy(cell_size);
5578 cell_size.x *= 7.0f/4.0f;
5579 cell_size.y *= 7.0f/3.0f;
5580 int mo = 0;
5581 for (int i = 0; i < 3; ++i) {
5582 for (int j = 0; j < 4; ++j) {
5583 const bool t1_or_t2 = (t1 != nullptr && t1_yr == this_yr && t1_mo == mo) ||
5584 (t2 != nullptr && t2_yr == this_yr && t2_mo == mo);
5585 if (t1_or_t2)
5586 ImGui::PushStyleColor(ImGuiCol_Button, col_btn);
5587 if (ImGui::Button(MONTH_ABRVS[mo],cell_size) && !clk) {
5588 *t = MakeTime(this_yr, mo);
5589 *level = 0;
5590 }
5591 if (t1_or_t2)
5592 ImGui::PopStyleColor();
5593 if (j != 3)
5594 ImGui::SameLine();
5595 mo++;
5596 }
5597 }
5598 }
5599 else if (*level == 2) {
5600 *t = FloorTime(*t, ImPlotTimeUnit_Yr);
5601 int this_yr = GetYear(*t);
5602 int yr = this_yr - this_yr % 20;
5603 ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
5604 ImFormatString(buff,32,"%d-%d",yr,yr+19);
5605 ImGui::Button(buff);
5606 ImGui::PopItemFlag();
5607 ImGui::SameLine(5*cell_size.x);
5608 BeginDisabledControls(yr <= min_yr);
5609 if (ImGui::ArrowButtonEx("##Up",ImGuiDir_Up,cell_size))
5610 *t = MakeTime(yr-20);
5611 EndDisabledControls(yr <= min_yr);
5612 ImGui::SameLine();
5613 BeginDisabledControls(yr + 20 >= max_yr);
5614 if (ImGui::ArrowButtonEx("##Down",ImGuiDir_Down,cell_size))
5615 *t = MakeTime(yr+20);
5616 EndDisabledControls(yr+ 20 >= max_yr);
5617 // ImGui::Dummy(cell_size);
5618 cell_size.x *= 7.0f/4.0f;
5619 cell_size.y *= 7.0f/5.0f;
5620 for (int i = 0; i < 5; ++i) {
5621 for (int j = 0; j < 4; ++j) {
5622 const bool t1_or_t2 = (t1 != nullptr && t1_yr == yr) || (t2 != nullptr && t2_yr == yr);
5623 if (t1_or_t2)
5624 ImGui::PushStyleColor(ImGuiCol_Button, col_btn);
5625 ImFormatString(buff,32,"%d",yr);
5626 if (yr<1970||yr>3000) {
5627 ImGui::Dummy(cell_size);
5628 }
5629 else if (ImGui::Button(buff,cell_size)) {
5630 *t = MakeTime(yr);
5631 *level = 1;
5632 }
5633 if (t1_or_t2)
5634 ImGui::PopStyleColor();
5635 if (j != 3)
5636 ImGui::SameLine();
5637 yr++;
5638 }
5639 }
5640 }
5641 ImGui::PopStyleVar();
5642 ImGui::PopStyleColor();
5643 ImGui::EndGroup();
5644 ImGui::PopID();
5645 return clk;
5646}
5647
5648bool ShowTimePicker(const char* id, ImPlotTime* t) {
5649 ImPlotContext& gp = *GImPlot;
5650 ImGui::PushID(id);
5651 tm& Tm = gp.Tm;
5652 GetTime(*t,&Tm);
5653
5654 static const char* nums[] = { "00","01","02","03","04","05","06","07","08","09",
5655 "10","11","12","13","14","15","16","17","18","19",
5656 "20","21","22","23","24","25","26","27","28","29",
5657 "30","31","32","33","34","35","36","37","38","39",
5658 "40","41","42","43","44","45","46","47","48","49",
5659 "50","51","52","53","54","55","56","57","58","59"};
5660
5661 static const char* am_pm[] = {"am","pm"};
5662
5663 bool hour24 = gp.Style.Use24HourClock;
5664
5665 int hr = hour24 ? Tm.tm_hour : ((Tm.tm_hour == 0 || Tm.tm_hour == 12) ? 12 : Tm.tm_hour % 12);
5666 int min = Tm.tm_min;
5667 int sec = Tm.tm_sec;
5668 int ap = Tm.tm_hour < 12 ? 0 : 1;
5669
5670 bool changed = false;
5671
5672 ImVec2 spacing = ImGui::GetStyle().ItemSpacing;
5673 spacing.x = 0;
5674 float width = ImGui::CalcTextSize("888").x;
5675 float height = ImGui::GetFrameHeight();
5676
5677 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, spacing);
5678 ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize,2.0f);
5679 ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0));
5680 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
5681 ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered));
5682
5683 ImGui::SetNextItemWidth(width);
5684 if (ImGui::BeginCombo("##hr",nums[hr],ImGuiComboFlags_NoArrowButton)) {
5685 const int ia = hour24 ? 0 : 1;
5686 const int ib = hour24 ? 24 : 13;
5687 for (int i = ia; i < ib; ++i) {
5688 if (ImGui::Selectable(nums[i],i==hr)) {
5689 hr = i;
5690 changed = true;
5691 }
5692 }
5693 ImGui::EndCombo();
5694 }
5695 ImGui::SameLine();
5696 ImGui::Text(":");
5697 ImGui::SameLine();
5698 ImGui::SetNextItemWidth(width);
5699 if (ImGui::BeginCombo("##min",nums[min],ImGuiComboFlags_NoArrowButton)) {
5700 for (int i = 0; i < 60; ++i) {
5701 if (ImGui::Selectable(nums[i],i==min)) {
5702 min = i;
5703 changed = true;
5704 }
5705 }
5706 ImGui::EndCombo();
5707 }
5708 ImGui::SameLine();
5709 ImGui::Text(":");
5710 ImGui::SameLine();
5711 ImGui::SetNextItemWidth(width);
5712 if (ImGui::BeginCombo("##sec",nums[sec],ImGuiComboFlags_NoArrowButton)) {
5713 for (int i = 0; i < 60; ++i) {
5714 if (ImGui::Selectable(nums[i],i==sec)) {
5715 sec = i;
5716 changed = true;
5717 }
5718 }
5719 ImGui::EndCombo();
5720 }
5721 if (!hour24) {
5722 ImGui::SameLine();
5723 if (ImGui::Button(am_pm[ap],ImVec2(0,height))) {
5724 ap = 1 - ap;
5725 changed = true;
5726 }
5727 }
5728
5729 ImGui::PopStyleColor(3);
5730 ImGui::PopStyleVar(2);
5731 ImGui::PopID();
5732
5733 if (changed) {
5734 if (!hour24)
5735 hr = hr % 12 + ap * 12;
5736 Tm.tm_hour = hr;
5737 Tm.tm_min = min;
5738 Tm.tm_sec = sec;
5739 *t = MkTime(&Tm);
5740 }
5741
5742 return changed;
5743}
5744
5745void StyleColorsAuto(ImPlotStyle* dst) {
5746 ImPlotStyle* style = dst ? dst : &ImPlot::GetStyle();
5747 ImVec4* colors = style->Colors;
5748
5749 style->MinorAlpha = 0.25f;
5750
5751 colors[ImPlotCol_Line] = IMPLOT_AUTO_COL;
5752 colors[ImPlotCol_Fill] = IMPLOT_AUTO_COL;
5753 colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL;
5754 colors[ImPlotCol_MarkerFill] = IMPLOT_AUTO_COL;
5755 colors[ImPlotCol_ErrorBar] = IMPLOT_AUTO_COL;
5756 colors[ImPlotCol_FrameBg] = IMPLOT_AUTO_COL;
5757 colors[ImPlotCol_PlotBg] = IMPLOT_AUTO_COL;
5758 colors[ImPlotCol_PlotBorder] = IMPLOT_AUTO_COL;
5759 colors[ImPlotCol_LegendBg] = IMPLOT_AUTO_COL;
5760 colors[ImPlotCol_LegendBorder] = IMPLOT_AUTO_COL;
5761 colors[ImPlotCol_LegendText] = IMPLOT_AUTO_COL;
5762 colors[ImPlotCol_TitleText] = IMPLOT_AUTO_COL;
5763 colors[ImPlotCol_InlayText] = IMPLOT_AUTO_COL;
5764 colors[ImPlotCol_PlotBorder] = IMPLOT_AUTO_COL;
5765 colors[ImPlotCol_AxisText] = IMPLOT_AUTO_COL;
5766 colors[ImPlotCol_AxisGrid] = IMPLOT_AUTO_COL;
5767 colors[ImPlotCol_AxisTick] = IMPLOT_AUTO_COL;
5768 colors[ImPlotCol_AxisBg] = IMPLOT_AUTO_COL;
5769 colors[ImPlotCol_AxisBgHovered] = IMPLOT_AUTO_COL;
5770 colors[ImPlotCol_AxisBgActive] = IMPLOT_AUTO_COL;
5771 colors[ImPlotCol_Selection] = IMPLOT_AUTO_COL;
5772 colors[ImPlotCol_Crosshairs] = IMPLOT_AUTO_COL;
5773}
5774
5775void StyleColorsClassic(ImPlotStyle* dst) {
5776 ImPlotStyle* style = dst ? dst : &ImPlot::GetStyle();
5777 ImVec4* colors = style->Colors;
5778
5779 style->MinorAlpha = 0.5f;
5780
5781 colors[ImPlotCol_Line] = IMPLOT_AUTO_COL;
5782 colors[ImPlotCol_Fill] = IMPLOT_AUTO_COL;
5783 colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL;
5784 colors[ImPlotCol_MarkerFill] = IMPLOT_AUTO_COL;
5785 colors[ImPlotCol_ErrorBar] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
5786 colors[ImPlotCol_FrameBg] = ImVec4(0.43f, 0.43f, 0.43f, 0.39f);
5787 colors[ImPlotCol_PlotBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.35f);
5788 colors[ImPlotCol_PlotBorder] = ImVec4(0.50f, 0.50f, 0.50f, 0.50f);
5789 colors[ImPlotCol_LegendBg] = ImVec4(0.11f, 0.11f, 0.14f, 0.92f);
5790 colors[ImPlotCol_LegendBorder] = ImVec4(0.50f, 0.50f, 0.50f, 0.50f);
5791 colors[ImPlotCol_LegendText] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
5792 colors[ImPlotCol_TitleText] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
5793 colors[ImPlotCol_InlayText] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
5794 colors[ImPlotCol_AxisText] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
5795 colors[ImPlotCol_AxisGrid] = ImVec4(0.90f, 0.90f, 0.90f, 0.25f);
5796 colors[ImPlotCol_AxisTick] = IMPLOT_AUTO_COL; // TODO
5797 colors[ImPlotCol_AxisBg] = IMPLOT_AUTO_COL; // TODO
5798 colors[ImPlotCol_AxisBgHovered] = IMPLOT_AUTO_COL; // TODO
5799 colors[ImPlotCol_AxisBgActive] = IMPLOT_AUTO_COL; // TODO
5800 colors[ImPlotCol_Selection] = ImVec4(0.97f, 0.97f, 0.39f, 1.00f);
5801 colors[ImPlotCol_Crosshairs] = ImVec4(0.50f, 0.50f, 0.50f, 0.75f);
5802}
5803
5804void StyleColorsDark(ImPlotStyle* dst) {
5805 ImPlotStyle* style = dst ? dst : &ImPlot::GetStyle();
5806 ImVec4* colors = style->Colors;
5807
5808 style->MinorAlpha = 0.25f;
5809
5810 colors[ImPlotCol_Line] = IMPLOT_AUTO_COL;
5811 colors[ImPlotCol_Fill] = IMPLOT_AUTO_COL;
5812 colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL;
5813 colors[ImPlotCol_MarkerFill] = IMPLOT_AUTO_COL;
5814 colors[ImPlotCol_ErrorBar] = IMPLOT_AUTO_COL;
5815 colors[ImPlotCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.07f);
5816 colors[ImPlotCol_PlotBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.50f);
5817 colors[ImPlotCol_PlotBorder] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
5818 colors[ImPlotCol_LegendBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f);
5819 colors[ImPlotCol_LegendBorder] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
5820 colors[ImPlotCol_LegendText] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
5821 colors[ImPlotCol_TitleText] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
5822 colors[ImPlotCol_InlayText] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
5823 colors[ImPlotCol_AxisText] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
5824 colors[ImPlotCol_AxisGrid] = ImVec4(1.00f, 1.00f, 1.00f, 0.25f);
5825 colors[ImPlotCol_AxisTick] = IMPLOT_AUTO_COL; // TODO
5826 colors[ImPlotCol_AxisBg] = IMPLOT_AUTO_COL; // TODO
5827 colors[ImPlotCol_AxisBgHovered] = IMPLOT_AUTO_COL; // TODO
5828 colors[ImPlotCol_AxisBgActive] = IMPLOT_AUTO_COL; // TODO
5829 colors[ImPlotCol_Selection] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
5830 colors[ImPlotCol_Crosshairs] = ImVec4(1.00f, 1.00f, 1.00f, 0.50f);
5831}
5832
5833void StyleColorsLight(ImPlotStyle* dst) {
5834 ImPlotStyle* style = dst ? dst : &ImPlot::GetStyle();
5835 ImVec4* colors = style->Colors;
5836
5837 style->MinorAlpha = 1.0f;
5838
5839 colors[ImPlotCol_Line] = IMPLOT_AUTO_COL;
5840 colors[ImPlotCol_Fill] = IMPLOT_AUTO_COL;
5841 colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL;
5842 colors[ImPlotCol_MarkerFill] = IMPLOT_AUTO_COL;
5843 colors[ImPlotCol_ErrorBar] = IMPLOT_AUTO_COL;
5844 colors[ImPlotCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
5845 colors[ImPlotCol_PlotBg] = ImVec4(0.42f, 0.57f, 1.00f, 0.13f);
5846 colors[ImPlotCol_PlotBorder] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
5847 colors[ImPlotCol_LegendBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.98f);
5848 colors[ImPlotCol_LegendBorder] = ImVec4(0.82f, 0.82f, 0.82f, 0.80f);
5849 colors[ImPlotCol_LegendText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
5850 colors[ImPlotCol_TitleText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
5851 colors[ImPlotCol_InlayText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
5852 colors[ImPlotCol_AxisText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
5853 colors[ImPlotCol_AxisGrid] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
5854 colors[ImPlotCol_AxisTick] = ImVec4(0.00f, 0.00f, 0.00f, 0.25f);
5855 colors[ImPlotCol_AxisBg] = IMPLOT_AUTO_COL; // TODO
5856 colors[ImPlotCol_AxisBgHovered] = IMPLOT_AUTO_COL; // TODO
5857 colors[ImPlotCol_AxisBgActive] = IMPLOT_AUTO_COL; // TODO
5858 colors[ImPlotCol_Selection] = ImVec4(0.82f, 0.64f, 0.03f, 1.00f);
5859 colors[ImPlotCol_Crosshairs] = ImVec4(0.00f, 0.00f, 0.00f, 0.50f);
5860}
5861
5862//-----------------------------------------------------------------------------
5863// [SECTION] Obsolete Functions/Types
5864//-----------------------------------------------------------------------------
5865
5866#ifndef IMPLOT_DISABLE_OBSOLETE_FUNCTIONS
5867
5868bool BeginPlot(const char* title, const char* x_label, const char* y1_label, const ImVec2& size,
5869 ImPlotFlags flags, ImPlotAxisFlags x_flags, ImPlotAxisFlags y1_flags, ImPlotAxisFlags y2_flags, ImPlotAxisFlags y3_flags,
5870 const char* y2_label, const char* y3_label)
5871{
5872 if (!BeginPlot(title, size, flags))
5873 return false;
5874 SetupAxis(ImAxis_X1, x_label, x_flags);
5875 SetupAxis(ImAxis_Y1, y1_label, y1_flags);
5876 if (ImHasFlag(flags, ImPlotFlags_YAxis2))
5877 SetupAxis(ImAxis_Y2, y2_label, y2_flags);
5878 if (ImHasFlag(flags, ImPlotFlags_YAxis3))
5879 SetupAxis(ImAxis_Y3, y3_label, y3_flags);
5880 return true;
5881}
5882
5883#endif
5884
5885} // namespace ImPlot