| 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 |