INSTINCT Code Coverage Report


Directory: src/
File: util/Plot/PlotItemStyle.cpp
Date: 2025-02-07 16:54:41
Exec Total Coverage
Lines: 29 370 7.8%
Functions: 1 5 20.0%
Branches: 27 716 3.8%

Line Branch Exec Source
1 // This file is part of INSTINCT, the INS Toolkit for Integrated
2 // Navigation Concepts and Training by the Institute of Navigation of
3 // the University of Stuttgart, Germany.
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
9 #include "PlotItemStyle.hpp"
10
11 #include <cstddef>
12 #include <functional>
13 #include <imgui.h>
14 #include <imgui_stdlib.h>
15 #include <implot.h>
16 #include <implot_internal.h>
17 #include <muParser.h>
18 #include <string>
19
20 #include "internal/gui/widgets/HelpMarker.hpp"
21 #include "util/Logger.hpp"
22
23 namespace NAV
24 {
25
26 /// @brief Write info to a json object
27 /// @param[out] j Json output
28 /// @param[in] style Object to read info from
29 void to_json(json& j, const PlotItemStyle& style)
30 {
31 j = json{
32 { "legendName", style.legendName },
33 { "stride", style.stride },
34 { "lineType", style.lineType },
35 { "color", style.color },
36 { "colormapMask", style.colormapMask },
37 { "colormapMaskDataCmpIdx", style.colormapMaskDataCmpIdx },
38 { "thickness", style.thickness },
39 { "markerColormapMask", style.markerColormapMask },
40 { "markerColormapMaskDataCmpIdx", style.markerColormapMaskDataCmpIdx },
41 { "markers", style.markers },
42 { "markerStyle", style.markerStyle },
43 { "markerSize", style.markerSize },
44 { "markerWeight", style.markerWeight },
45 { "markerFillColor", style.markerFillColor },
46 { "markerOutlineColor", style.markerOutlineColor },
47 { "errorBoundsEnabled", style.errorBoundsEnabled },
48 { "errorBoundsDataIdx", style.errorBoundsDataIdx },
49 { "errorBoundsAlpha", style.errorBoundsAlpha },
50 { "errorBoundsModifierExpression", style.errorBoundsModifierExpression },
51 { "eventsEnabled", style.eventsEnabled },
52 { "eventMarkerStyle", style.eventMarkerStyle },
53 { "eventMarkerSize", style.eventMarkerSize },
54 { "eventMarkerWeight", style.eventMarkerWeight },
55 { "eventMarkerFillColor", style.eventMarkerFillColor },
56 { "eventMarkerOutlineColor", style.eventMarkerOutlineColor },
57 { "eventTooltipFilterRegex", style.eventTooltipFilterRegex },
58 };
59 if (style.lineFlags) { j["lineFlags"] = style.lineFlags.value(); }
60 }
61 /// @brief Read info from a json object
62 /// @param[in] j Json variable to read info from
63 /// @param[out] style Output object
64 404 void from_json(const json& j, PlotItemStyle& style)
65 {
66
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("legendName")) { j.at("legendName").get_to(style.legendName); }
67
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("stride")) { j.at("stride").get_to(style.stride); }
68
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("lineType")) { j.at("lineType").get_to(style.lineType); }
69
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("color")) { j.at("color").get_to(style.color); }
70
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("colormapMask")) { j.at("colormapMask").get_to(style.colormapMask); }
71
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("colormapMaskDataCmpIdx")) { j.at("colormapMaskDataCmpIdx").get_to(style.colormapMaskDataCmpIdx); }
72
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("thickness")) { j.at("thickness").get_to(style.thickness); }
73
1/6
✗ Branch 1 not taken.
✓ Branch 2 taken 404 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
404 if (j.contains("lineFlags")) { style.lineFlags.emplace(j.at("lineFlags").get<uint32_t>()); }
74
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("markerColormapMask")) { j.at("markerColormapMask").get_to(style.markerColormapMask); }
75
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("markerColormapMaskDataCmpIdx")) { j.at("markerColormapMaskDataCmpIdx").get_to(style.markerColormapMaskDataCmpIdx); }
76
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("markers")) { j.at("markers").get_to(style.markers); }
77
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("markerStyle")) { j.at("markerStyle").get_to(style.markerStyle); }
78
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("markerSize")) { j.at("markerSize").get_to(style.markerSize); }
79
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("markerWeight")) { j.at("markerWeight").get_to(style.markerWeight); }
80
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("markerFillColor")) { j.at("markerFillColor").get_to(style.markerFillColor); }
81
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("markerOutlineColor")) { j.at("markerOutlineColor").get_to(style.markerOutlineColor); }
82
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 404 times.
404 if (j.contains("errorBoundsEnabled")) { j.at("errorBoundsEnabled").get_to(style.errorBoundsEnabled); }
83
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 404 times.
404 if (j.contains("errorBoundsDataIdx")) { j.at("errorBoundsDataIdx").get_to(style.errorBoundsDataIdx); }
84
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 404 times.
404 if (j.contains("errorBoundsAlpha")) { j.at("errorBoundsAlpha").get_to(style.errorBoundsAlpha); }
85
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 404 times.
404 if (j.contains("errorBoundsModifierExpression")) { j.at("errorBoundsModifierExpression").get_to(style.errorBoundsModifierExpression); }
86
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("eventsEnabled")) { j.at("eventsEnabled").get_to(style.eventsEnabled); }
87
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("eventMarkerStyle")) { j.at("eventMarkerStyle").get_to(style.eventMarkerStyle); }
88
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("eventMarkerSize")) { j.at("eventMarkerSize").get_to(style.eventMarkerSize); }
89
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("eventMarkerWeight")) { j.at("eventMarkerWeight").get_to(style.eventMarkerWeight); }
90
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("eventMarkerFillColor")) { j.at("eventMarkerFillColor").get_to(style.eventMarkerFillColor); }
91
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("eventMarkerOutlineColor")) { j.at("eventMarkerOutlineColor").get_to(style.eventMarkerOutlineColor); }
92
1/2
✓ Branch 1 taken 404 times.
✗ Branch 2 not taken.
404 if (j.contains("eventTooltipFilterRegex")) { j.at("eventTooltipFilterRegex").get_to(style.eventTooltipFilterRegex); }
93 404 }
94
95 PlotItemStyle::LegendPopupReturn PlotItemStyle::showLegendPopup(const char* id,
96 const char* displayTitle,
97 int plotDataBufferSize,
98 int plotElementIdx,
99 [[maybe_unused]] const char* nameId,
100 ImPlotLineFlags plotLineFlags,
101 ScrollingBuffer<ImU32>* colormapMaskColors,
102 ScrollingBuffer<ImU32>* markerColormapMaskColors,
103 const std::function<bool(size_t&, const char*)>& ShowDataReferenceChooser,
104 ScrollingBuffer<double>* eventMarker,
105 std::vector<std::tuple<double, double, PlotEventTooltip>>* eventTooltips)
106 {
107 LegendPopupReturn ret;
108
109 // Legend item context menu (right click on legend item)
110 if (ImPlot::BeginLegendPopup(id))
111 {
112 if (displayTitle)
113 {
114 ImGui::TextUnformatted(displayTitle);
115 ImGui::Separator();
116 }
117
118 if (legendNameGui.empty())
119 {
120 legendNameGui = legendName;
121 }
122 ImGui::InputText("Legend name", &legendNameGui);
123 if (legendNameGui != legendName && !ImGui::IsItemActive())
124 {
125 legendName = legendNameGui;
126 ret.changed = true;
127 LOG_DEBUG("{}: Legend changed to {}", nameId, legendName);
128 }
129
130 if (ImGui::InputInt("Stride", &stride))
131 {
132 stride = std::max(stride, 0);
133 stride = std::min(stride, plotDataBufferSize - 1);
134 ret.changed = true;
135 LOG_DEBUG("{}: Stride changed to {}", nameId, stride);
136 }
137
138 if (auto lt = static_cast<int>(lineType);
139 ImGui::Combo("Style", &lt, "Scatter\0Line\0\0"))
140 {
141 lineType = static_cast<decltype(lineType)>(lt);
142 ret.changed = true;
143 }
144 ImPlotLineFlags lFlags = lineFlags.value_or(plotLineFlags);
145 bool plotItemLineFlagsAuto = !lineFlags.has_value();
146 if (plotItemLineFlagsAuto) { ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.8F); }
147 if (ImGui::CheckboxFlags("NoClip", &lFlags, ImPlotLineFlags_NoClip))
148 {
149 lineFlags = lFlags;
150 ret.changed = true;
151 }
152 if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Markers (if displayed) on the edge of a plot will not be clipped"); }
153 ImGui::SameLine();
154 if (ImGui::CheckboxFlags("SkipNaN", &lFlags, ImPlotLineFlags_SkipNaN))
155 {
156 lineFlags = lFlags;
157 ret.changed = true;
158 }
159 if (ImGui::IsItemHovered()) { ImGui::SetTooltip("NaNs values will be skipped instead of rendered as missing data"); }
160 ImGui::SameLine();
161 if (ImGui::CheckboxFlags("Loop", &lFlags, ImPlotLineFlags_Loop))
162 {
163 lineFlags = lFlags;
164 ret.changed = true;
165 }
166 if (ImGui::IsItemHovered()) { ImGui::SetTooltip("The last and first point will be connected to form a closed loop"); }
167 if (lineFlags)
168 {
169 ImGui::SameLine();
170 if (ImGui::Button("Auto##Line Flags"))
171 {
172 lineFlags.reset();
173 }
174 }
175 if (plotItemLineFlagsAuto) { ImGui::PopStyleVar(); }
176 if (lineType == PlotItemStyle::LineType::Line)
177 {
178 if (ImGui::DragFloat("Line Thickness", &thickness, 0.1F, 0.0F, 8.0F, "%.2f px"))
179 {
180 ret.changed = true;
181 }
182 if (colormapMaskColors && ShowColormapSelector(colormapMask.first, colormapMask.second))
183 {
184 colormapMaskColors->clear();
185 ret.changed = true;
186 }
187 if (colormapMaskColors && colormapMask.first != ColormapMaskType::None
188 && ShowDataReferenceChooser(colormapMaskDataCmpIdx, "Colormap Ref"))
189 {
190 colormapMaskColors->clear();
191 ret.changed = true;
192 }
193 if (colormapMask.first == ColormapMaskType::None)
194 {
195 bool isColorAuto = ImPlot::IsColorAuto(color);
196 auto col = isColorAuto ? ImPlot::GetColormapColor(plotElementIdx) : color;
197 if (ImGui::ColorEdit4("Line Color", &col.x))
198 {
199 color = col;
200 ret.changed = true;
201 }
202 if (!isColorAuto)
203 {
204 ImGui::SameLine();
205 if (ImGui::Button("Auto##Line Color"))
206 {
207 color = IMPLOT_AUTO_COL;
208 }
209 }
210 }
211 if (ImGui::Checkbox("Markers", &markers))
212 {
213 ret.changed = true;
214 }
215 }
216 if (lineType == PlotItemStyle::LineType::Scatter || markers)
217 {
218 if (ImGui::Combo("Marker Style", &markerStyle,
219 "Circle\0Square\0Diamond\0Up\0Down\0Left\0Right\0Cross\0Plus\0Asterisk\0\0"))
220 {
221 ret.changed = true;
222 }
223 if (ImGui::DragFloat("Marker Size", &markerSize, 0.1F, 1.0F, 10.0F, "%.2f px"))
224 {
225 ret.changed = true;
226 }
227 if (ImGui::DragFloat("Marker Weight", &markerWeight, 0.05F, 0.5F, 3.0F, "%.2f px"))
228 {
229 ret.changed = true;
230 }
231 if (!markers)
232 {
233 if (colormapMaskColors && ShowColormapSelector(colormapMask.first, colormapMask.second))
234 {
235 colormapMaskColors->clear();
236 ret.changed = true;
237 }
238 if (colormapMaskColors && colormapMask.first != ColormapMaskType::None
239 && ShowDataReferenceChooser(colormapMaskDataCmpIdx, "Colormap Ref"))
240 {
241 colormapMaskColors->clear();
242 ret.changed = true;
243 }
244 }
245 if (markers && lineType != PlotItemStyle::LineType::Scatter)
246 {
247 if (colormapMaskColors && ShowColormapSelector(markerColormapMask.first, markerColormapMask.second, "Marker "))
248 {
249 markerColormapMaskColors->clear();
250 ret.changed = true;
251 }
252 if (colormapMaskColors && markerColormapMask.first != ColormapMaskType::None
253 && ShowDataReferenceChooser(markerColormapMaskDataCmpIdx, "Marker Colormap Ref"))
254 {
255 markerColormapMaskColors->clear();
256 ret.changed = true;
257 }
258 }
259 if (markerColormapMask.first == ColormapMaskType::None
260 && (lineType != PlotItemStyle::LineType::Scatter
261 || colormapMask.first == ColormapMaskType::None))
262 {
263 bool isColorAuto = ImPlot::IsColorAuto(markerFillColor);
264 auto col = isColorAuto ? ImPlot::GetColormapColor(plotElementIdx) : markerFillColor;
265 if (ImGui::ColorEdit4("Marker Fill Color", &col.x))
266 {
267 markerFillColor = col;
268 ret.changed = true;
269 }
270 if (!isColorAuto)
271 {
272 ImGui::SameLine();
273 if (ImGui::Button("Auto##Marker Fill Color"))
274 {
275 markerFillColor = IMPLOT_AUTO_COL;
276 }
277 }
278
279 isColorAuto = ImPlot::IsColorAuto(markerOutlineColor);
280 col = isColorAuto ? ImPlot::GetColormapColor(plotElementIdx) : markerOutlineColor;
281 if (ImGui::ColorEdit4("Marker Outline Color", &col.x))
282 {
283 markerOutlineColor = col;
284 ret.changed = true;
285 }
286 if (!isColorAuto)
287 {
288 ImGui::SameLine();
289 if (ImGui::Button("Auto##Marker Outline Color"))
290 {
291 markerOutlineColor = IMPLOT_AUTO_COL;
292 }
293 }
294 }
295 }
296 if (lineType == PlotItemStyle::LineType::Line)
297 {
298 ImGui::Separator();
299 ret.changed |= ImGui::Checkbox("Show Error Bounds", &errorBoundsEnabled);
300 if (errorBoundsEnabled)
301 {
302 auto idx = errorBoundsDataIdx;
303 if (ShowDataReferenceChooser(errorBoundsDataIdx, "Error Bounds Ref") && errorBoundsDataIdx != idx)
304 {
305 ret.changed = true;
306 ret.errorBoundsReCalcNeeded = true;
307 }
308 ret.changed |= ImGui::SliderFloat("Error Bounds Alpha", &errorBoundsAlpha, 0.0F, 1.0F, "%.2f");
309 if (errorBoundsModifierExpressionTemp.empty()) { errorBoundsModifierExpressionTemp = errorBoundsModifierExpression; }
310 std::string expression = errorBoundsModifierExpressionTemp;
311 if (errorBoundsModifierExpressionTemp != errorBoundsModifierExpression)
312 {
313 ImGui::PushStyleColor(ImGuiCol_Text, ImColor(220, 20, 60).Value);
314 }
315 ImGui::InputTextWithHint("Error Bounds Modifier", "e.g. '3 * x' or 'sqrt(x)'", &expression);
316 if (errorBoundsModifierExpressionTemp != errorBoundsModifierExpression)
317 {
318 ImGui::PopStyleColor();
319 if (!errorBoundsModifierExpression.empty() && ImGui::IsItemHovered()) { ImGui::SetTooltip("Currently used expression:\n%s", errorBoundsModifierExpression.c_str()); }
320 }
321 if (expression != errorBoundsModifierExpressionTemp && !ImGui::IsItemActive())
322 {
323 errorBoundsModifierExpressionTemp = expression;
324 try
325 {
326 if (!errorBoundsModifierExpressionTemp.empty())
327 {
328 double x = 1.0;
329 mu::Parser p;
330 p.DefineVar("x", &x);
331 p.SetExpr(errorBoundsModifierExpressionTemp);
332 x = p.Eval();
333 }
334
335 errorBoundsModifierExpression = errorBoundsModifierExpressionTemp;
336 errorBoundsModifierExpressionTemp.clear();
337 ret.changed = true;
338 ret.errorBoundsReCalcNeeded = true;
339 }
340 catch (mu::Parser::exception_type& e)
341 {
342 LOG_ERROR("{}: Error bound modifier parse error on '{}': {} in expression: {}", nameId, legendName, e.GetMsg(), errorBoundsModifierExpressionTemp);
343 }
344 }
345 ImGui::SameLine();
346 if (gui::widgets::BeginHelpMarker("(?)", 0.0F))
347 {
348 auto tableEntry = [](const char* first, const char* second, const char* third) {
349 ImGui::TableNextRow();
350 ImGui::TableNextColumn();
351 ImGui::TextUnformatted(first);
352 ImGui::TableNextColumn();
353 ImGui::TextUnformatted(second);
354 ImGui::TableNextColumn();
355 ImGui::TextUnformatted(third);
356 };
357
358 ImGui::BeginGroup();
359 {
360 ImGui::TextUnformatted("Functions");
361 if (ImGui::BeginTable(fmt::format("Functions##{}", id).c_str(), 3,
362 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
363 {
364 ImGui::TableSetupColumn("Name");
365 ImGui::TableSetupColumn("Argc.");
366 ImGui::TableSetupColumn("Explanation");
367 ImGui::TableHeadersRow();
368
369 tableEntry("sin", "1", "sine function");
370 tableEntry("cos", "1", "cosine function");
371 tableEntry("tan", "1", "tangens function");
372 tableEntry("asin", "1", "arcus sine function");
373 tableEntry("acos", "1", "arcus cosine function");
374 tableEntry("atan", "1", "arcus tangens function");
375 tableEntry("sinh", "1", "hyperbolic sine function");
376 tableEntry("cosh", "1", "hyperbolic cosine");
377 tableEntry("tanh", "1", "hyperbolic tangens function");
378 tableEntry("asinh", "1", "hyperbolic arcus sine function");
379 tableEntry("acosh", "1", "hyperbolic arcus tangens function");
380 tableEntry("atanh", "1", "hyperbolic arcur tangens function");
381 tableEntry("log2", "1", "logarithm to the base 2");
382 tableEntry("log10", "1", "logarithm to the base 10");
383 tableEntry("log", "1", "logarithm to base e (2.71828...)");
384 tableEntry("ln", "1", "logarithm to base e (2.71828...)");
385 tableEntry("exp", "1", "e raised to the power of x");
386 tableEntry("sqrt", "1", "square root of a value");
387 tableEntry("sign", "1", "sign function -1 if x<0; 1 if x>0");
388 tableEntry("rint", "1", "round to nearest integer");
389 tableEntry("abs", "1", "absolute value");
390 tableEntry("min", "var.", "min of all arguments");
391 tableEntry("max", "var.", "max of all arguments");
392 tableEntry("sum", "var.", "sum of all arguments");
393 tableEntry("avg", "var.", "mean value of all arguments");
394
395 ImGui::EndTable();
396 }
397 }
398 ImGui::EndGroup();
399
400 ImGui::SameLine();
401
402 ImGui::BeginGroup();
403 {
404 ImGui::TextUnformatted("Binary operators");
405 if (ImGui::BeginTable(fmt::format("Binary operators##{}", id).c_str(), 3,
406 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
407 {
408 ImGui::TableSetupColumn("Operator");
409 ImGui::TableSetupColumn("Description");
410 ImGui::TableSetupColumn("Priority");
411 ImGui::TableHeadersRow();
412
413 tableEntry("=", "assignement *", "0");
414 tableEntry("||", "logical or", "1");
415 tableEntry("&&", "logical and", "2");
416 tableEntry("|", "bitwise or", "3");
417 tableEntry("&", "bitwise and", "4");
418 tableEntry("<=", "less or equal", "5");
419 tableEntry(">=", "greater or equal", "5");
420 tableEntry("!=", "not equal", "5");
421 tableEntry("==", "equal", "5");
422 tableEntry(">", "greater than", "5");
423 tableEntry("<", "less than", "5");
424 tableEntry("+", "addition", "6");
425 tableEntry("-", "subtraction", "6");
426 tableEntry("*", "multiplication", "7");
427 tableEntry("/", "division", "7");
428 tableEntry("^", "raise x to the power of y", "8");
429
430 ImGui::EndTable();
431 }
432
433 ImGui::TextUnformatted("Ternary Operators");
434 if (ImGui::BeginTable(fmt::format("Ternary Operators##{}", id).c_str(), 3,
435 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
436 {
437 ImGui::TableSetupColumn("Operator");
438 ImGui::TableSetupColumn("Description");
439 ImGui::TableSetupColumn("Remarks");
440 ImGui::TableHeadersRow();
441
442 tableEntry("?:", "if then else operator", "C++ style syntax");
443
444 ImGui::EndTable();
445 }
446
447 ImGui::TextUnformatted("Constants");
448 if (ImGui::BeginTable(fmt::format("Constant##{}", id).c_str(), 3,
449 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
450 {
451 ImGui::TableSetupColumn("Constant");
452 ImGui::TableSetupColumn("Description");
453 ImGui::TableSetupColumn("Remarks");
454 ImGui::TableHeadersRow();
455
456 tableEntry("_pi", "The one and only pi", "3.14159265359");
457 tableEntry("_e", "Euler's number", "2.71828182846");
458
459 ImGui::EndTable();
460 }
461 }
462 ImGui::EndGroup();
463
464 gui::widgets::EndHelpMarker(false);
465 }
466 }
467 }
468 if (eventMarker && eventTooltips)
469 {
470 ImGui::Separator();
471 if (ImGui::Checkbox("Events", &eventsEnabled))
472 {
473 ret.changed = true;
474 }
475 if (eventsEnabled)
476 {
477 if (ImGui::Combo("Event Marker Style", &eventMarkerStyle,
478 "Circle\0Square\0Diamond\0Up\0Down\0Left\0Right\0Cross\0Plus\0Asterisk\0\0"))
479 {
480 ret.changed = true;
481 }
482 if (ImGui::DragFloat("Event Marker Size", &eventMarkerSize, 0.1F, 1.0F, 10.0F, "%.2f px"))
483 {
484 ret.changed = true;
485 }
486 if (ImGui::DragFloat("Event Marker Weight", &eventMarkerWeight, 0.05F, 0.5F, 3.0F, "%.2f px"))
487 {
488 ret.changed = true;
489 }
490 bool isColorAuto = ImPlot::IsColorAuto(eventMarkerFillColor);
491 auto col = isColorAuto ? ImPlot::GetColormapColor(plotElementIdx) : eventMarkerFillColor;
492 if (ImGui::ColorEdit4("Event Marker Fill Color", &col.x))
493 {
494 eventMarkerFillColor = col;
495 ret.changed = true;
496 }
497 if (!isColorAuto)
498 {
499 ImGui::SameLine();
500 if (ImGui::Button("Auto##Event Marker Fill Color"))
501 {
502 eventMarkerFillColor = IMPLOT_AUTO_COL;
503 }
504 }
505
506 isColorAuto = ImPlot::IsColorAuto(eventMarkerOutlineColor);
507 col = isColorAuto ? ImPlot::GetColormapColor(plotElementIdx) : eventMarkerOutlineColor;
508 if (ImGui::ColorEdit4("Event Marker Outline Color", &col.x))
509 {
510 eventMarkerOutlineColor = col;
511 ret.changed = true;
512 }
513 if (!isColorAuto)
514 {
515 ImGui::SameLine();
516 if (ImGui::Button("Auto##Event Marker Outline Color"))
517 {
518 eventMarkerOutlineColor = IMPLOT_AUTO_COL;
519 }
520 }
521
522 if (ImGui::InputText("Event Filter Regex", &eventTooltipFilterRegex))
523 {
524 eventMarker->clear();
525 eventTooltips->clear();
526 ret.changed = true;
527 }
528 }
529 }
530
531 ImPlot::EndLegendPopup();
532 }
533 return ret;
534 }
535
536 void PlotItemStyle::plotData(const char* plotName,
537 const ScrollingBuffer<double>& xData,
538 const ScrollingBuffer<double>& yData,
539 int plotElementIdx,
540 int defaultStride,
541 ImPlotLineFlags plotLineFlags,
542 ScrollingBuffer<ImU32>* colormapMaskColors,
543 ScrollingBuffer<ImU32>* markerColormapMaskColors,
544 const std::array<ScrollingBuffer<double>, 2>* yErrorData) const
545 {
546 auto lineColor = ImPlot::IsColorAuto(color) ? ImPlot::GetColormapColor(plotElementIdx) : color;
547 if (lineType == PlotItemStyle::LineType::Line)
548 {
549 ImPlot::SetNextLineStyle(lineColor, thickness);
550 }
551 if (lineType == PlotItemStyle::LineType::Scatter || markers)
552 {
553 ImPlot::SetNextMarkerStyle(markerStyle,
554 markerSize,
555 ImPlot::IsColorAuto(markerFillColor) ? ImPlot::GetColormapColor(plotElementIdx) : markerFillColor,
556 markerWeight,
557 ImPlot::IsColorAuto(markerOutlineColor) ? ImPlot::GetColormapColor(plotElementIdx) : markerOutlineColor);
558 }
559
560 auto stride = this->stride ? this->stride : defaultStride;
561 auto dataPointCount = static_cast<int>(std::ceil(static_cast<double>(yData.size())
562 / static_cast<double>(stride)));
563
564 // Plot the data
565 if (lineType == PlotItemStyle::LineType::Line)
566 {
567 if (colormapMaskColors && markerColormapMaskColors && colormapMask.first != ColormapMaskType::None)
568 {
569 ImPlot::SetNextColorsData(ImPlotCol_Line, colormapMaskColors->data(), stride * static_cast<int>(sizeof(ImU32)));
570 if (markers)
571 {
572 ImPlot::SetNextColorsData(ImPlotCol_MarkerFill, markerColormapMask.first != ColormapMaskType::None ? markerColormapMaskColors->data() : colormapMaskColors->data(), stride * static_cast<int>(sizeof(ImU32)));
573 ImPlot::SetNextColorsData(ImPlotCol_MarkerOutline, markerColormapMask.first != ColormapMaskType::None ? markerColormapMaskColors->data() : colormapMaskColors->data(), stride * static_cast<int>(sizeof(ImU32)));
574 }
575 }
576 else if (markerColormapMaskColors && markers && markerColormapMask.first != ColormapMaskType::None)
577 {
578 ImPlot::SetNextColorsData(ImPlotCol_MarkerFill, markerColormapMaskColors->data(), stride * static_cast<int>(sizeof(ImU32)));
579 ImPlot::SetNextColorsData(ImPlotCol_MarkerOutline, markerColormapMaskColors->data(), stride * static_cast<int>(sizeof(ImU32)));
580 }
581 ImPlot::PlotLine(plotName,
582 xData.data(),
583 yData.data(),
584 dataPointCount,
585 lineFlags.value_or(plotLineFlags),
586 static_cast<int>(std::ceil(static_cast<double>(yData.offset()) / static_cast<double>(stride))),
587 stride * static_cast<int>(sizeof(double)));
588
589 if (errorBoundsEnabled && yErrorData && ImPlot::GetCurrentPlot()->Items.GetItemByIndex(plotElementIdx)->Show)
590 {
591 ImPlot::SetNextFillStyle(lineColor, errorBoundsAlpha);
592 ImPlot::PlotShaded("",
593 xData.data(),
594 (*yErrorData)[0].data(),
595 (*yErrorData)[1].data(),
596 dataPointCount,
597 ImPlotShadedFlags_None,
598 static_cast<int>(std::ceil(static_cast<double>(yData.offset()) / static_cast<double>(stride))),
599 stride * static_cast<int>(sizeof(double)));
600 }
601 }
602 else if (lineType == PlotItemStyle::LineType::Scatter)
603 {
604 if (colormapMaskColors && colormapMask.first != ColormapMaskType::None && colormapMaskColors->isInfiniteBuffer())
605 {
606 ImPlot::SetNextColorsData(ImPlotCol_MarkerFill, colormapMaskColors->data(), stride * static_cast<int>(sizeof(ImU32)));
607 ImPlot::SetNextColorsData(ImPlotCol_MarkerOutline, colormapMaskColors->data(), stride * static_cast<int>(sizeof(ImU32)));
608 }
609 ImPlot::PlotScatter(plotName,
610 xData.data(),
611 yData.data(),
612 dataPointCount,
613 lineFlags.value_or(plotLineFlags) & ImPlotLineFlags_NoClip ? ImPlotScatterFlags_NoClip : ImPlotScatterFlags_None,
614 static_cast<int>(std::ceil(static_cast<double>(yData.offset()) / static_cast<double>(stride))),
615 stride * static_cast<int>(sizeof(double)));
616 }
617 }
618
619 } // namespace NAV
620