INSTINCT Code Coverage Report


Directory: src/
File: Nodes/Plotting/Plot.hpp
Date: 2025-11-25 23:34:18
Exec Total Coverage
Lines: 7 22 31.8%
Functions: 6 26 23.1%
Branches: 8 34 23.5%

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 /// @file Plot.hpp
10 /// @brief Plots data into ImPlot Windows
11 /// @author T. Topp (topp@ins.uni-stuttgart.de)
12 /// @date 2021-01-09
13
14 #pragma once
15
16 #include <array>
17 #include <imgui.h>
18 #include <implot.h>
19
20 #include <map>
21 #include <memory>
22 #include <mutex>
23 #include <unordered_set>
24
25 #include "NodeData/NodeData.hpp"
26 #include "internal/Node/Node.hpp"
27 #include "internal/gui/widgets/DynamicInputPins.hpp"
28 #include "internal/gui/widgets/PositionInput.hpp"
29
30 #include "util/Container/ScrollingBuffer.hpp"
31 #include "util/Container/Vector.hpp"
32 #include "util/Plot/PlotEventTooltip.hpp"
33 #include "util/Plot/PlotItemStyle.hpp"
34 #include "util/Plot/PlotTooltip.hpp"
35 #include "util/Logger/CommonLog.hpp"
36
37 #include "NodeData/General/DynamicData.hpp"
38 #include "NodeData/GNSS/GnssCombination.hpp"
39 #include "NodeData/GNSS/GnssObs.hpp"
40 #include "NodeData/GNSS/RtklibPosObs.hpp"
41 #include "NodeData/GNSS/RtkSolution.hpp"
42 #include "NodeData/GNSS/SppSolution.hpp"
43 #include "NodeData/IMU/ImuObs.hpp"
44 #include "NodeData/IMU/ImuObsSimulated.hpp"
45 #include "NodeData/IMU/ImuObsWDelta.hpp"
46 #include "NodeData/IMU/KvhObs.hpp"
47 #include "NodeData/IMU/VectorNavBinaryOutput.hpp"
48 #include "NodeData/State/InsGnssLCKFSolution.hpp"
49 #include "NodeData/State/PosVelAtt.hpp"
50 #include "NodeData/State/InsGnssTCKFSolution.hpp"
51 #include "NodeData/WiFi/WiFiPositioningSolution.hpp"
52 #include "NodeData/Baro/BaroPressObs.hpp"
53 #include "NodeData/Baro/BaroHgt.hpp"
54
55 namespace NAV
56 {
57 /// @brief Plot node which plots all kind of observations
58 class Plot : public Node, public CommonLog
59 {
60 public:
61 /// @brief Default constructor
62 Plot();
63 /// @brief Destructor
64 ~Plot() override;
65 /// @brief Copy constructor
66 Plot(const Plot&) = delete;
67 /// @brief Move constructor
68 Plot(Plot&&) = delete;
69 /// @brief Copy assignment operator
70 Plot& operator=(const Plot&) = delete;
71 /// @brief Move assignment operator
72 Plot& operator=(Plot&&) = delete;
73
74 /// @brief String representation of the Class Type
75 [[nodiscard]] static std::string typeStatic();
76
77 /// @brief String representation of the Class Type
78 [[nodiscard]] std::string type() const override;
79
80 /// @brief String representation of the Class Category
81 [[nodiscard]] static std::string category();
82
83 /// @brief ImGui config window which is shown on double click
84 /// @attention Don't forget to set _hasConfig to true in the constructor of the node
85 void guiConfig() override;
86
87 /// @brief Saves the node into a json object
88 [[nodiscard]] json save() const override;
89
90 /// @brief Restores the node from a json object
91 /// @param[in] j Json object with the node state
92 void restore(const json& j) override;
93
94 /// @brief Called when a new link was established
95 /// @param[in] startPin Pin where the link starts
96 /// @param[in] endPin Pin where the link ends
97 void afterCreateLink(OutputPin& startPin, InputPin& endPin) override;
98
99 /// @brief Information needed to plot the data on a certain pin
100 struct PinData
101 {
102 /// @brief Stores the actual data coming from a pin
103 struct PlotData
104 {
105 /// @brief Default constructor (needed to make serialization with json working)
106
1/2
✓ Branch 2 taken 5217 times.
✗ Branch 3 not taken.
5217 PlotData() = default;
107
108 /// @brief Constructor
109 /// @param[in] displayName Display name of the contained data
110 /// @param[in] size Size of the buffer
111 PlotData(std::string displayName, size_t size);
112
113 /// Display name of the contained data
114 std::string displayName;
115 /// Buffer for the data
116 ScrollingBuffer<double> buffer;
117 /// Flag if data was received, as the buffer contains std::nan("") otherwise
118 bool hasData = false;
119
120 /// When connecting a new link. All data is flagged for delete and only those who are also present in the new link are kept
121 bool markedForDelete = false;
122 /// Bool to show if dynamic data
123 bool isDynamic = false;
124 };
125
126 /// @brief Possible Pin types
127 enum class PinType : uint8_t
128 {
129 Flow, ///< NodeData Trigger
130 Bool, ///< Boolean
131 Int, ///< Integer Number
132 Float, ///< Floating Point Number
133 Matrix, ///< Matrix Object
134 };
135
136 /// @brief Constructor
137
1/2
✓ Branch 3 taken 270 times.
✗ Branch 4 not taken.
270 PinData() = default;
138 /// @brief Destructor
139 402 ~PinData() = default;
140 /// @brief Copy constructor
141 /// @param[in] other The other element to copy
142 PinData(const PinData& other);
143
144 /// @brief Move constructor
145 /// @param[in] other The other element to move
146 PinData(PinData&& other) noexcept;
147
148 /// @brief Copy assignment operator
149 /// @param[in] rhs The other element to copy
150 PinData& operator=(const PinData& rhs);
151 /// @brief Move assignment operator
152 /// @param[in] rhs The other element to move
153 PinData& operator=(PinData&& rhs) noexcept;
154
155 /// @brief Adds a plotData Element to the list
156 /// @param[in] dataIndex Index where to add the data to
157 /// @param[in] displayName Display name of the contained data
158 void addPlotDataItem(size_t dataIndex, const std::string& displayName);
159
160 /// Size of all buffers of the plotData elements
161 int size = 0;
162 /// Data Identifier of the connected pin
163 std::string dataIdentifier;
164 /// List with all the data
165 std::vector<PlotData> plotData;
166 /// List with the raw data received
167 ScrollingBuffer<std::shared_ptr<const NodeData>> rawNodeData;
168 /// Pin Type
169 PinType pinType = PinType::Flow;
170 /// Amount of points to skip for plotting
171 int stride = 1;
172 /// Mutex to lock the buffer so that the GUI thread and the calculation threads don't cause a data race
173 std::mutex mutex;
174 /// Dynamic data start index
175 int dynamicDataStartIndex = -1;
176 /// Events with relative time, absolute time, tooltip text and data Index (-1 means all)
177 std::vector<std::tuple<double, InsTime, std::string, int32_t>> events;
178 };
179
180 /// @brief Information specifying the look of each plot
181 struct PlotInfo
182 {
183 /// Info needed to draw a data set
184 struct PlotItem
185 {
186 /// @brief Default constructor (needed to make serialization with json working)
187
4/8
✓ Branch 14 taken 325 times.
✗ Branch 15 not taken.
✓ Branch 17 taken 325 times.
✗ Branch 18 not taken.
✓ Branch 20 taken 325 times.
✗ Branch 21 not taken.
✓ Branch 23 taken 325 times.
✗ Branch 24 not taken.
325 PlotItem() = default;
188
189 /// @brief Constructor
190 /// @param[in] pinIndex Index of the pin where the data came in
191 /// @param[in] dataIndex Index of the data on the pin
192 /// @param[in] displayName Display name of the data
193 PlotItem(size_t pinIndex, size_t dataIndex, std::string displayName)
194 : pinIndex(pinIndex), dataIndex(dataIndex), displayName(std::move(displayName))
195 {
196 style.colormapMaskDataCmpIdx = dataIndex;
197 style.markerColormapMaskDataCmpIdx = dataIndex;
198 }
199
200 /// @brief Constructor
201 /// @param[in] pinIndex Index of the pin where the data came in
202 /// @param[in] dataIndex Index of the data on the pin
203 /// @param[in] displayName Display name of the data
204 /// @param[in] axis Axis to plot the data on (Y1, Y2, Y3)
205 PlotItem(size_t pinIndex, size_t dataIndex, std::string displayName, ImAxis axis)
206 : PlotItem(pinIndex, dataIndex, std::move(displayName))
207 {
208 this->axis = axis; // NOLINT(cppcoreguidelines-prefer-member-initializer)
209 }
210
211 /// @brief Equal comparison operator (needed to search the vector with std::find)
212 /// @param[in] rhs Right-hand-side of the operator
213 /// @return True if the pin and data indices match
214 constexpr bool operator==(const PlotItem& rhs) const
215 {
216 return pinIndex == rhs.pinIndex && dataIndex == rhs.dataIndex && displayName == rhs.displayName;
217 }
218
219 size_t pinIndex{}; ///< Index of the pin where the data came in
220 size_t dataIndex{}; ///< Index of the data on the pin
221 std::string displayName; ///< Display name of the data (not changing and unique)
222 ImAxis axis{ ImAxis_Y1 }; ///< Axis to plot the data on (Y1, Y2, Y3)
223 PlotItemStyle style{}; ///< Defines how the data should be plotted
224 /// Buffer for the colormap mask
225 ScrollingBuffer<ImU32> colormapMaskColors = ScrollingBuffer<ImU32>(0);
226 /// Colormap version (to track updates of the colormap)
227 size_t colormapMaskVersion = 0;
228 /// Buffer for the colormap mask
229 ScrollingBuffer<ImU32> markerColormapMaskColors = ScrollingBuffer<ImU32>(0);
230 /// Colormap version (to track updates of the colormap)
231 size_t markerColormapMaskVersion = 0;
232
233 /// Buffer for event markers
234 ScrollingBuffer<double> eventMarker = ScrollingBuffer<double>(0);
235
236 /// Error bounds lower and upper data
237 std::array<ScrollingBuffer<double>, 2> errorBoundsData;
238
239 /// List of tooltips (x,y, tooltip)
240 std::vector<std::tuple<double, double, PlotEventTooltip>> eventTooltips;
241 };
242
243 /// @brief Default constructor
244 163 PlotInfo() = default;
245
246 /// @brief Constructor
247 /// @param[in] title Title of the ImPlot
248 /// @param[in] nInputPins Amount of inputPins
249 163 PlotInfo(const std::string& title, size_t nInputPins)
250
2/4
✓ Branch 3 taken 163 times.
✗ Branch 4 not taken.
✓ Branch 10 taken 163 times.
✗ Branch 11 not taken.
326 : title(title), headerText(title), selectedXdata(nInputPins, 1) {}
251
252 /// Size of the plot
253 ImVec2 size{ -1, 300 };
254
255 /// Title of the ImPlot
256 std::string title;
257 /// Title of the CollapsingHeader
258 std::string headerText;
259 /// Flag, whether to override the x axis label
260 bool overrideXAxisLabel = false;
261 /// X axis label
262 std::string xAxisLabel;
263 /// Y1 axis label
264 std::string y1AxisLabel;
265 /// Y2 axis label
266 std::string y2AxisLabel;
267 /// Y3 axis label
268 std::string y3AxisLabel;
269 /// Selected pin in the GUI for the Drag & Drop Data
270 size_t selectedPin = 0;
271 /// Flags which are passed to the plot
272 int plotFlags = 0;
273 /// Flags for the x-Axis
274 ImPlotAxisFlags xAxisFlags = ImPlotAxisFlags_AutoFit;
275 /// Flags for the y-Axes
276 ImPlotAxisFlags yAxisFlags = ImPlotAxisFlags_AutoFit;
277 /// Scale for the x-Axis
278 ImPlotScale xAxisScale = ImPlotScale_Linear;
279 /// Scale for the y-Axes
280 std::array<ImPlotScale, 3> yAxesScale = { ImPlotScale_Linear, ImPlotScale_Linear, ImPlotScale_Linear };
281 /// Line Flags for all items (each item can override the selection)
282 ImPlotLineFlags lineFlags = ImPlotLineFlags_NoClip | ImPlotLineFlags_SkipNaN;
283
284 /// @brief Key: PinIndex, Value: plotData to use for x-Axis
285 std::vector<size_t> selectedXdata;
286
287 /// List containing all elements which should be plotted
288 std::vector<PlotItem> plotItems;
289
290 /// Width of plot Data content
291 float leftPaneWidth = 180.0F;
292 /// Width of the plot
293 float rightPaneWidth = 400.0F;
294
295 /// Flag whether the whole plot is visible. If not, then it should be deleted
296 bool visible = true;
297
298 /// List of tooltip windows to show
299 std::vector<PlotTooltip> tooltips;
300 };
301
302 private:
303 /// @brief Initialize the node
304 bool initialize() override;
305
306 /// @brief Deinitialize the node
307 void deinitialize() override;
308
309 /// @brief Adds/Deletes Plots depending on the variable nPlots
310 void updateNumberOfPlots();
311
312 /// @brief Function to call to add a new pin
313 /// @param[in, out] node Pointer to this node
314 static void pinAddCallback(Node* node);
315 /// @brief Function to call to delete a pin
316 /// @param[in, out] node Pointer to this node
317 /// @param[in] pinIdx Input pin index to delete
318 static void pinDeleteCallback(Node* node, size_t pinIdx);
319
320 /// Index of the GPST data (unix timestamp)
321 size_t GPST_PLOT_IDX = 1;
322
323 /// Data storage for each pin
324 std::vector<PinData> _pinData;
325
326 /// Info for each plot window
327 std::vector<PlotInfo> _plots;
328
329 /// Amount of plot windows (should equal _plots.size())
330 size_t _nPlots = 0;
331 /// Possible data identifiers to connect
332 std::vector<std::string> _dataIdentifier = {
333 // General
334 DynamicData::type(),
335 // GNSS
336 GnssCombination::type(),
337 GnssObs::type(),
338 RtklibPosObs::type(),
339 RtkSolution::type(),
340 SppSolution::type(),
341 // IMU
342 ImuObs::type(),
343 ImuObsSimulated::type(),
344 ImuObsWDelta::type(),
345 KvhObs::type(),
346 VectorNavBinaryOutput::type(),
347 // State
348 InsGnssLCKFSolution::type(),
349 Pos::type(),
350 PosVel::type(),
351 PosVelAtt::type(),
352 InsGnssTCKFSolution::type(),
353 // WiFi
354 WiFiPositioningSolution::type(),
355 // Barometer
356 BaroHgt::type(),
357 BaroPressObs::type(),
358
359 };
360
361 /// Index of the Collapsible Header currently being dragged
362 int _dragAndDropHeaderIndex = -1;
363
364 size_t _screenshotFrameCnt = 0; ///< Frame counter for taking screenshots (> 0 when screenshot in progress)
365 size_t _screenShotPlotIdx = 0; ///< Plot index a screenshot is taken of
366
367 /// Values to force the x axis range to and a set of plotIdx to force
368 std::pair<std::unordered_set<size_t>, ImPlotRange> _forceXaxisRange;
369
370 /// Start position for the calculation of relative North-South and East-West
371 std::optional<gui::widgets::PositionWithFrame> _originPosition;
372
373 /// Flag, whether to override the North/East startValues in the GUI
374 bool _overridePositionStartValues = false;
375
376 /// @brief Dynamic input pins
377 /// @attention This should always be the last variable in the header, because it accesses others through the function callbacks
378 gui::widgets::DynamicInputPins _dynamicInputPins{ 0, this, pinAddCallback, pinDeleteCallback, 1 };
379
380 /// @brief Adds a event to a certain point in time
381 /// @param[in] pinIndex Index of the input pin where the data was received
382 /// @param insTime Absolute time
383 /// @param text Text to display
384 /// @param dataIndex Data Index to add the event for (-1 means all)
385 void addEvent(size_t pinIndex, InsTime insTime, const std::string& text, int32_t dataIndex);
386
387 /// @brief Add Data to the buffer of the pin
388 /// @param[in] pinIndex Index of the input pin where the data was received
389 /// @param[in] dataIndex Index of the data to insert
390 /// @param[in] value The value to insert
391 void addData(size_t pinIndex, size_t dataIndex, double value);
392
393 /// @brief Add Data to the buffer of the pin
394 /// @param[in] pinIndex Index of the input pin where the data was received
395 /// @param[in] displayName Display name of the data
396 /// @param[in] value The value to insert
397 /// @return Data Index where data were inserted
398 size_t addData(size_t pinIndex, std::string displayName, double value);
399
400 /// @brief Calculate the local position offset from the plot origin
401 /// @param[in] lla_position [𝜙, λ, h] Latitude, Longitude, Altitude in [rad, rad, m]
402 /// @return Local positions in north/south and east/west directions in [m]
403 CommonLog::LocalPosition calcLocalPosition(const Eigen::Vector3d& lla_position);
404
405 /// @brief Plots the data on this port
406 /// @param[in] insTime Time the data was received
407 /// @param[in] pinIdx Index of the pin the data is received on
408 void plotBoolean(const InsTime& insTime, size_t pinIdx);
409
410 /// @brief Plots the data on this port
411 /// @param[in] insTime Time the data was received
412 /// @param[in] pinIdx Index of the pin the data is received on
413 void plotInteger(const InsTime& insTime, size_t pinIdx);
414
415 /// @brief Plots the data on this port
416 /// @param[in] insTime Time the data was received
417 /// @param[in] pinIdx Index of the pin the data is received on
418 void plotFloat(const InsTime& insTime, size_t pinIdx);
419
420 /// @brief Plots the data on this port
421 /// @param[in] insTime Time the data was received
422 /// @param[in] pinIdx Index of the pin the data is received on
423 void plotMatrix(const InsTime& insTime, size_t pinIdx);
424
425 /// @brief Plot the data on this port
426 /// @param[in] queue Queue with all the received data messages
427 /// @param[in] pinIdx Index of the pin the data is received on
428 void plotFlowData(InputPin::NodeDataQueue& queue, size_t pinIdx);
429
430 /// @brief Plot the data
431 /// @param[in] obs Observation to plot
432 /// @param[in] pinIndex Index of the input pin where the data was received
433 /// @param[in, out] plotIndex Index for inserting the data into the plot data vector
434 /// @param[in] startIndex Data descriptor start index
435 template<typename T>
436 void plotData(const std::shared_ptr<const T>& obs, size_t pinIndex, size_t& plotIndex, size_t startIndex = 0)
437 {
438 for (size_t i = startIndex; i < T::GetStaticDescriptorCount(); ++i)
439 {
440 addData(pinIndex, plotIndex++, obs->getValueAtOrNaN(i));
441 }
442 }
443 };
444
445 } // namespace NAV
446