0.3.0
Loading...
Searching...
No Matches
Plot.cpp
Go to the documentation of this file.
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 "Plot.hpp"
10#include <array>
11#include <cmath>
12#include <cstddef>
13#include <cstdint>
14#include <imgui.h>
15#include <imgui_internal.h>
16#include <implot.h>
17#include <utility>
18
20#include "util/Assert.h"
22#include "util/Logger.hpp"
23
26#include <fmt/core.h>
27namespace nm = NAV::NodeManager;
30#include "NodeRegistry.hpp"
31
38#include "util/ImPlot.hpp"
39#include "util/Json.hpp"
40#include "util/StringUtil.hpp"
42
44
49
50#include <implot_internal.h>
51#include <muParser.h>
52
53#include <algorithm>
54#include <regex>
55
56namespace NAV
57{
58/// @brief Write info to a json object
59/// @param[out] j Json output
60/// @param[in] data Object to read info from
61static void to_json(json& j, const Plot::PinData::PlotData& data) // NOLINT(misc-use-anonymous-namespace)
62{
63 j = json{
64 { "displayName", data.displayName },
65 };
66}
67/// @brief Read info from a json object
68/// @param[in] j Json variable to read info from
69/// @param[out] data Output object
70static void from_json(const json& j, Plot::PinData::PlotData& data) // NOLINT(misc-use-anonymous-namespace)
71{
72 if (j.contains("displayName")) { j.at("displayName").get_to(data.displayName); }
73}
74
75/// @brief Write info to a json object
76/// @param[out] j Json output
77/// @param[in] data Object to read info from
78static void to_json(json& j, const std::vector<Plot::PinData::PlotData>& data) // NOLINT(misc-use-anonymous-namespace)
79{
80 for (const auto& i : data)
81 {
82 if (i.isDynamic) { continue; }
83 j.push_back(i);
84 }
85}
86
87/// @brief Write info to a json object
88/// @param[out] j Json output
89/// @param[in] data Object to read info from
90static void to_json(json& j, const Plot::PinData& data) // NOLINT(misc-use-anonymous-namespace)
91{
92 j = json{
93 { "dataIdentifier", data.dataIdentifier },
94 { "size", data.size },
95 { "plotData", data.plotData },
96 { "pinType", data.pinType },
97 { "stride", data.stride },
98 };
99}
100/// @brief Read info from a json object
101/// @param[in] j Json variable to read info from
102/// @param[out] data Output object
103static void from_json(const json& j, Plot::PinData& data) // NOLINT(misc-use-anonymous-namespace)
104{
105 if (j.contains("dataIdentifier")) { j.at("dataIdentifier").get_to(data.dataIdentifier); }
106 if (j.contains("size"))
107 {
108 j.at("size").get_to(data.size);
109 data.rawNodeData.resize(static_cast<size_t>(data.size));
110 }
111 if (j.contains("plotData") && j.at("plotData").is_array())
112 {
113 j.at("plotData").get_to(data.plotData);
114 for (auto& plotData : data.plotData)
115 {
116 plotData.buffer = ScrollingBuffer<double>(static_cast<size_t>(data.size));
117 }
118 }
119 if (j.contains("pinType")) { j.at("pinType").get_to(data.pinType); }
120 if (j.contains("stride")) { j.at("stride").get_to(data.stride); }
121}
122
123/// @brief Write info to a json object
124/// @param[out] j Json output
125/// @param[in] data Object to read info from
126static void to_json(json& j, const Plot::PlotInfo::PlotItem& data) // NOLINT(misc-use-anonymous-namespace)
127{
128 j = json{
129 { "pinIndex", data.pinIndex },
130 { "dataIndex", data.dataIndex },
131 { "displayName", data.displayName },
132 { "axis", data.axis },
133 { "style", data.style },
134 };
135}
136/// @brief Read info from a json object
137/// @param[in] j Json variable to read info from
138/// @param[out] data Output object
139static void from_json(const json& j, Plot::PlotInfo::PlotItem& data) // NOLINT(misc-use-anonymous-namespace)
140{
141 if (j.contains("pinIndex")) { j.at("pinIndex").get_to(data.pinIndex); }
142 if (j.contains("dataIndex")) { j.at("dataIndex").get_to(data.dataIndex); }
143 if (j.contains("displayName")) { j.at("displayName").get_to(data.displayName); }
144 if (j.contains("axis")) { j.at("axis").get_to(data.axis); }
145 if (j.contains("style")) { j.at("style").get_to(data.style); }
146}
147
148/// @brief Write info to a json object
149/// @param[out] j Json output
150/// @param[in] data Object to read info from
151static void to_json(json& j, const Plot::PlotInfo& data) // NOLINT(misc-use-anonymous-namespace)
152{
153 j = json{
154 { "size", data.size },
155 { "xAxisFlags", data.xAxisFlags },
156 { "yAxisFlags", data.yAxisFlags },
157 { "xAxisScale", data.xAxisScale },
158 { "yAxesScale", data.yAxesScale },
159 { "lineFlags", data.lineFlags },
160 { "headerText", data.headerText },
161 { "leftPaneWidth", data.leftPaneWidth },
162 { "plotFlags", data.plotFlags },
163 { "rightPaneWidth", data.rightPaneWidth },
164 { "selectedPin", data.selectedPin },
165 { "selectedXdata", data.selectedXdata },
166 { "plotItems", data.plotItems },
167 { "title", data.title },
168 { "overrideXAxisLabel", data.overrideXAxisLabel },
169 { "xAxisLabel", data.xAxisLabel },
170 { "y1AxisLabel", data.y1AxisLabel },
171 { "y2AxisLabel", data.y2AxisLabel },
172 { "y3AxisLabel", data.y3AxisLabel },
173 };
174}
175/// @brief Read info from a json object
176/// @param[in] j Json variable to read info from
177/// @param[out] data Output object
178static void from_json(const json& j, Plot::PlotInfo& data) // NOLINT(misc-use-anonymous-namespace)
179{
180 if (j.contains("size")) { j.at("size").get_to(data.size); }
181 if (j.contains("xAxisFlags")) { j.at("xAxisFlags").get_to(data.xAxisFlags); }
182 if (j.contains("yAxisFlags")) { j.at("yAxisFlags").get_to(data.yAxisFlags); }
183 if (j.contains("xAxisScale")) { j.at("xAxisScale").get_to(data.xAxisScale); }
184 if (j.contains("yAxesScale")) { j.at("yAxesScale").get_to(data.yAxesScale); }
185 if (j.contains("lineFlags")) { j.at("lineFlags").get_to(data.lineFlags); }
186 if (j.contains("headerText")) { j.at("headerText").get_to(data.headerText); }
187 if (j.contains("leftPaneWidth")) { j.at("leftPaneWidth").get_to(data.leftPaneWidth); }
188 if (j.contains("plotFlags")) { j.at("plotFlags").get_to(data.plotFlags); }
189 if (j.contains("rightPaneWidth")) { j.at("rightPaneWidth").get_to(data.rightPaneWidth); }
190 if (j.contains("selectedPin")) { j.at("selectedPin").get_to(data.selectedPin); }
191 if (j.contains("selectedXdata")) { j.at("selectedXdata").get_to(data.selectedXdata); }
192 if (j.contains("plotItems")) { j.at("plotItems").get_to(data.plotItems); }
193 if (j.contains("title")) { j.at("title").get_to(data.title); }
194 if (j.contains("overrideXAxisLabel")) { j.at("overrideXAxisLabel").get_to(data.overrideXAxisLabel); }
195 if (j.contains("xAxisLabel")) { j.at("xAxisLabel").get_to(data.xAxisLabel); }
196 if (j.contains("y1AxisLabel")) { j.at("y1AxisLabel").get_to(data.y1AxisLabel); }
197 if (j.contains("y2AxisLabel")) { j.at("y2AxisLabel").get_to(data.y2AxisLabel); }
198 if (j.contains("y3AxisLabel")) { j.at("y3AxisLabel").get_to(data.y3AxisLabel); }
199}
200
201} // namespace NAV
202
205
207 : size(other.size),
209 plotData(other.plotData),
211 pinType(other.pinType),
212 stride(other.stride) {}
213
215 : size(other.size),
216 dataIdentifier(std::move(other.dataIdentifier)),
217 plotData(std::move(other.plotData)),
218 rawNodeData(std::move(other.rawNodeData)),
219 pinType(other.pinType),
220 stride(other.stride) {}
221
223{
224 if (&rhs != this)
225 {
226 size = rhs.size;
228 plotData = rhs.plotData;
230 pinType = rhs.pinType;
231 stride = rhs.stride;
232 }
233
234 return *this;
235}
236
238{
239 if (&rhs != this)
240 {
241 size = rhs.size;
242 dataIdentifier = std::move(rhs.dataIdentifier);
243 plotData = std::move(rhs.plotData);
244 rawNodeData = std::move(rhs.rawNodeData);
245 pinType = rhs.pinType;
246 stride = rhs.stride;
247 }
248
249 return *this;
250}
251
252void NAV::Plot::PinData::addPlotDataItem(size_t dataIndex, const std::string& displayName)
253{
254 if (plotData.size() > dataIndex)
255 {
256 if (plotData.at(dataIndex).displayName == displayName) // Item was restored already at this position
257 {
258 plotData.at(dataIndex).markedForDelete = false;
259 return;
260 }
261
262 // Some other item was restored at this position
263 if (!plotData.at(dataIndex).markedForDelete)
264 {
265 LOG_WARN("Adding PlotData item '{}' at position {}, but at this position exists already the item '{}'. Reordering the items to match the data. Consider resaving the flow file.",
266 displayName, dataIndex, plotData.at(dataIndex).displayName);
267 }
268 auto searchIter = std::ranges::find_if(plotData, [displayName](const PlotData& plotData) { return plotData.displayName == displayName; });
269 auto iter = plotData.begin();
270 std::advance(iter, dataIndex);
271 iter->markedForDelete = false;
272 if (searchIter == plotData.end()) // Item does not exist yet. Developer added a new item to the list
273 {
274 plotData.insert(iter, PlotData{ displayName, static_cast<size_t>(size) });
275 }
276 else // Item exists already. Developer reordered the items in the list
277 {
278 move(plotData, static_cast<size_t>(searchIter - plotData.begin()), dataIndex);
279 }
280 }
281 else if (std::ranges::find_if(plotData, [displayName](const PlotData& plotData) { return plotData.displayName == displayName; })
282 != plotData.end())
283 {
284 LOG_ERROR("Adding the PlotData item {} at position {}, but this plot item was found at another position already",
285 displayName, dataIndex);
286 }
287 else // Item not there yet. Add to the end of the list
288 {
289 plotData.emplace_back(displayName, static_cast<size_t>(size));
290 }
291}
292
293// ###########################################################################################################
294
296 : Node(typeStatic())
297{
298 LOG_TRACE("{}: called", name);
299
300 _hasConfig = true;
301 _lockConfigDuringRun = false;
302 _guiConfigDefaultWindowSize = { 750, 650 };
303
304 // PinData::PinType::Flow:
305 _pinData.at(0).pinType = PinData::PinType::Flow;
306 inputPins.at(0).type = Pin::Type::Flow;
307 inputPins.at(0).dataIdentifier = _dataIdentifier;
308 inputPins.at(0).callback = static_cast<InputPin::FlowFirableCallbackFunc>(&Plot::plotFlowData);
309 // // PinData::PinType::Bool:
310 // _pinData.at(1).pinType = PinData::PinType::Bool;
311 // inputPins.at(1).type = Pin::Type::Bool;
312 // inputPins.at(1).dataIdentifier.clear();
313 // inputPins.at(1).callback = static_cast<InputPin::DataChangedNotifyFunc>(&Plot::plotBoolean);
314 // // PinData::PinType::Int:
315 // _pinData.at(2).pinType = PinData::PinType::Int;
316 // inputPins.at(2).type = Pin::Type::Int;
317 // inputPins.at(2).dataIdentifier.clear();
318 // inputPins.at(2).callback = static_cast<InputPin::DataChangedNotifyFunc>(&Plot::plotInteger);
319 // // PinData::PinType::Float:
320 // _pinData.at(3).pinType = PinData::PinType::Float;
321 // inputPins.at(3).type = Pin::Type::Float;
322 // inputPins.at(3).dataIdentifier.clear();
323 // inputPins.at(3).callback = static_cast<InputPin::DataChangedNotifyFunc>(&Plot::plotFloat);
324 // // PinData::PinType::Matrix:
325 // _pinData.at(4).pinType = PinData::PinType::Matrix;
326 // inputPins.at(4).type = Pin::Type::Matrix;
327 // inputPins.at(4).dataIdentifier = { "Eigen::MatrixXd", "Eigen::VectorXd" };
328 // inputPins.at(4).callback = static_cast<InputPin::DataChangedNotifyFunc>(&Plot::plotMatrix);
329}
330
332{
333 LOG_TRACE("{}: called", nameId());
334}
335
337{
338 return "Plot";
339}
340
341std::string NAV::Plot::type() const
342{
343 return typeStatic();
344}
345
347{
348 return "Plot";
349}
350
352{
353 ImGui::SetNextItemOpen(false, ImGuiCond_FirstUseEver);
354 if (ImGui::CollapsingHeader(fmt::format("Options##{}", size_t(id)).c_str()))
355 {
356 auto columnContentPinType = [&](size_t pinIndex) -> bool {
357 auto& pinData = _pinData.at(pinIndex);
358 ImGui::SetNextItemWidth(100.0F * gui::NodeEditorApplication::windowFontRatio());
359 if (auto pinType = static_cast<int>(pinData.pinType);
360 ImGui::Combo(fmt::format("##Pin Type for Pin {} - {}", pinIndex + 1, size_t(id)).c_str(),
361 &pinType, "Flow\0Bool\0Int\0Float\0Matrix\0\0"))
362 {
363 pinData.pinType = static_cast<decltype(pinData.pinType)>(pinType);
364 if (inputPins.at(pinIndex).isPinLinked())
365 {
366 inputPins.at(pinIndex).deleteLink();
367 }
368
369 switch (pinData.pinType)
370 {
371 case PinData::PinType::Flow:
372 inputPins.at(pinIndex).type = Pin::Type::Flow;
373 inputPins.at(pinIndex).dataIdentifier = _dataIdentifier;
374 inputPins.at(pinIndex).callback = static_cast<InputPin::FlowFirableCallbackFunc>(&Plot::plotFlowData);
375 break;
376 case PinData::PinType::Bool:
377 inputPins.at(pinIndex).type = Pin::Type::Bool;
378 inputPins.at(pinIndex).dataIdentifier.clear();
379 inputPins.at(pinIndex).callback = static_cast<InputPin::DataChangedNotifyFunc>(&Plot::plotBoolean);
380 break;
381 case PinData::PinType::Int:
382 inputPins.at(pinIndex).type = Pin::Type::Int;
383 inputPins.at(pinIndex).dataIdentifier.clear();
384 inputPins.at(pinIndex).callback = static_cast<InputPin::DataChangedNotifyFunc>(&Plot::plotInteger);
385 break;
386 case PinData::PinType::Float:
387 inputPins.at(pinIndex).type = Pin::Type::Float;
388 inputPins.at(pinIndex).dataIdentifier.clear();
389 inputPins.at(pinIndex).callback = static_cast<InputPin::DataChangedNotifyFunc>(&Plot::plotFloat);
390 break;
391 case PinData::PinType::Matrix:
392 inputPins.at(pinIndex).type = Pin::Type::Matrix;
393 inputPins.at(pinIndex).dataIdentifier = { "Eigen::MatrixXd", "Eigen::VectorXd" };
394 inputPins.at(pinIndex).callback = static_cast<InputPin::DataChangedNotifyFunc>(&Plot::plotMatrix);
395 break;
396 }
397
398 return true;
399 }
400 return false;
401 };
402 auto columnContentDataPoints = [&](size_t pinIndex) -> bool {
403 bool changed = false;
404 auto& pinData = _pinData.at(pinIndex);
405 ImGui::SetNextItemWidth(100.0F * gui::NodeEditorApplication::windowFontRatio());
406 if (ImGui::DragInt(fmt::format("##Data Points {} - {}", size_t(id), pinIndex + 1).c_str(),
407 &pinData.size, 10.0F, 0, INT32_MAX / 2))
408 {
409 pinData.size = std::max(pinData.size, 0);
410 std::scoped_lock<std::mutex> guard(pinData.mutex);
411 for (auto& plotData : pinData.plotData)
412 {
413 changed = true;
414 plotData.buffer.resize(static_cast<size_t>(pinData.size));
415 }
416 }
417 if (ImGui::IsItemHovered())
418 {
419 ImGui::SetTooltip("The amount of data which should be stored before the buffer gets reused.\nEnter 0 to show all data.");
420 }
421 return changed;
422 };
423 auto columnContentStride = [&](size_t pinIndex) -> bool {
424 bool changed = false;
425 auto& pinData = _pinData.at(pinIndex);
426 ImGui::SetNextItemWidth(100.0F * gui::NodeEditorApplication::windowFontRatio());
427 if (ImGui::InputInt(fmt::format("##Stride {} - {}", size_t(id), pinIndex + 1).c_str(),
428 &pinData.stride))
429 {
430 pinData.stride = std::max(pinData.stride, 1);
431 changed = true;
432 }
433 if (ImGui::IsItemHovered())
434 {
435 ImGui::SetTooltip("The amount of points to skip when plotting. This greatly reduces lag when plotting");
436 }
437 return changed;
438 };
439
440 if (_dynamicInputPins.ShowGuiWidgets(size_t(id), inputPins, this,
441 { { .header = "Pin Type", .content = columnContentPinType },
442 { .header = "# Data Points", .content = columnContentDataPoints },
443 { .header = "Stride", .content = columnContentStride } }))
444 {
446 }
447
448 if (ImGui::Checkbox(fmt::format("Override local position origin (North/East)##{}", size_t(id)).c_str(), &_overridePositionStartValues))
449 {
451 LOG_DEBUG("{}: overridePositionStartValues changed to {}", nameId(), _overridePositionStartValues);
452 if (!_originPosition) { _originPosition = gui::widgets::PositionWithFrame(); }
453 }
454 if (_overridePositionStartValues)
455 {
456 ImGui::Indent();
457 if (gui::widgets::PositionInput(fmt::format("Origin##{}", size_t(id)).c_str(), _originPosition.value(), gui::widgets::PositionInputLayout::SINGLE_ROW))
458 {
460 }
461 ImGui::Unindent();
462 }
463 if (!_overridePositionStartValues)
464 {
465 if (CommonLog::ShowOriginInput(nameId().c_str()))
466 {
468 }
469 }
470 }
471 ImGui::BulletText("Tipp: Ctrl + Hover = Tooltip (+ LClick = Tooltip window)");
472
473 // Used to reset the member variabel _dragAndDropHeaderIndex in case no plot does a drag and drop action
474 bool dragAndDropHeaderStillInProgress = false;
475
476 auto showDragDropTargetHeader = [this](size_t plotIdxTarget) {
477 ImGui::Dummy(ImVec2(-1.F, 2.F));
478
479 bool selectableSelectedDummy = true;
480 ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5F, 0.5F));
481 ImGui::PushStyleColor(ImGuiCol_Header, IM_COL32(16, 173, 44, 79));
482 ImGui::Selectable(fmt::format("[drop here]").c_str(), &selectableSelectedDummy, ImGuiSelectableFlags_None, ImVec2(ImGui::GetWindowContentRegionWidth(), 20.F));
483 ImGui::PopStyleColor();
484 ImGui::PopStyleVar();
485
486 if (ImGui::BeginDragDropTarget())
487 {
488 if (const ImGuiPayload* payloadData = ImGui::AcceptDragDropPayload(fmt::format("DND ColHead {}", size_t(id)).c_str()))
489 {
490 auto plotIdxSource = *static_cast<size_t*>(payloadData->Data);
491
492 if (plotIdxSource < plotIdxTarget)
493 {
494 --plotIdxTarget;
495 }
496
497 move(_plots, plotIdxSource, plotIdxTarget);
499 }
500 ImGui::EndDragDropTarget();
501 }
502 ImGui::Dummy(ImVec2(-1.F, 2.F));
503 };
504
505 if (_dragAndDropHeaderIndex > 0)
506 {
507 showDragDropTargetHeader(0);
508 }
509
510 for (size_t plotIdx = 0; plotIdx < _plots.size(); plotIdx++)
511 {
512 auto& plot = _plots.at(plotIdx);
513
514 size_t plotElementIdx = 0;
515
516 if (!plot.visible) // In the previous frame the x was pressed on the plot
517 {
518 LOG_DEBUG("{}: # Plot '{}' at index {} was deleted", nameId(), plot.headerText, plotIdx);
519 _plots.erase(_plots.begin() + static_cast<int64_t>(plotIdx));
520 _nPlots -= 1;
522 continue;
523 }
524
525 ImGui::SetNextItemOpen(true, ImGuiCond_Once);
526 if (ImGui::CollapsingHeader(fmt::format("{}##Plot Header {} - {}", plot.headerText, size_t(id), plotIdx).c_str(), &plot.visible))
527 {
528 if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None))
529 {
530 dragAndDropHeaderStillInProgress = true;
531 _dragAndDropHeaderIndex = static_cast<int>(plotIdx);
532 // Data is copied into heap inside the drag and drop
533 ImGui::SetDragDropPayload(fmt::format("DND ColHead {}", size_t(id)).c_str(),
534 &plotIdx, sizeof(plotIdx));
535 ImGui::Dummy(ImVec2(ImGui::CalcTextSize(plot.headerText.c_str()).x + 60.F, -1.F));
536 bool dnd_display_close = true;
537 ImGui::CollapsingHeader(fmt::format("{}##Plot DND Header {} - {}", plot.headerText, size_t(id), plotIdx).c_str(), &dnd_display_close);
538 ImGui::EndDragDropSource();
539 }
540
541 bool saveForceXaxisRange = false;
542 ImGui::SetNextItemOpen(false, ImGuiCond_FirstUseEver);
543 auto optionsCursorPos = ImGui::GetCursorPos();
544 if (ImGui::TreeNode(fmt::format("Options##{} - {}", size_t(id), plotIdx).c_str()))
545 {
546 std::string headerTitle = plot.headerText;
547 ImGui::InputText(fmt::format("Header Title##{} - {}", size_t(id), plotIdx).c_str(), &headerTitle);
548 if (plot.headerText != headerTitle && !ImGui::IsItemActive())
549 {
550 plot.headerText = headerTitle;
552 LOG_DEBUG("{}: Header changed to {}", nameId(), plot.headerText);
553 }
554 if (ImGui::InputText(fmt::format("Plot Title##{} - {}", size_t(id), plotIdx).c_str(), &plot.title))
555 {
557 LOG_DEBUG("{}: Plot Title changed to {}", nameId(), plot.title);
558 }
559 bool plotWidthAutomatic = plot.size.x == -1.0;
560 float checkBoxStartX = ImGui::GetCursorPosX();
561 if (ImGui::Checkbox(fmt::format("{}Automatic Plot Width##{} - {}", plotWidthAutomatic ? "" : "##", size_t(id), plotIdx).c_str(), &plotWidthAutomatic))
562 {
563 if (plotWidthAutomatic) { plot.size.x = -1.0; }
564 else { plot.size.x = ImGui::GetWindowContentRegionWidth() - plot.leftPaneWidth - ImGui::GetStyle().ItemSpacing.x; }
566 }
567 if (!plotWidthAutomatic)
568 {
569 ImGui::SameLine();
570 ImGui::SetNextItemWidth(ImGui::CalcItemWidth() - (ImGui::GetCursorPosX() - checkBoxStartX));
571 if (ImGui::SliderFloat(fmt::format("Plot Width##{} - {}", size_t(id), plotIdx).c_str(), &plot.size.x, 1.0F, 2000, "%.0f"))
572 {
574 }
575 }
576 if (ImGui::SliderFloat(fmt::format("Plot Height##{} - {}", size_t(id), plotIdx).c_str(), &plot.size.y, 0.0F, 1000, "%.0f"))
577 {
579 }
580 if (ImGui::Checkbox(fmt::format("Override X Axis Label##{} - {}", size_t(id), plotIdx).c_str(), &plot.overrideXAxisLabel))
581 {
583 }
584 if (plot.overrideXAxisLabel)
585 {
586 if (ImGui::InputText(fmt::format("X Axis Label##{} - {}", size_t(id), plotIdx).c_str(), &plot.xAxisLabel))
587 {
589 }
590 }
591 if (ImGui::InputText(fmt::format("Y1 Axis Label##{} - {}", size_t(id), plotIdx).c_str(), &plot.y1AxisLabel))
592 {
594 }
595 if (plot.plotFlags & ImPlotFlags_YAxis2)
596 {
597 if (ImGui::InputText(fmt::format("Y2 Axis Label##{} - {}", size_t(id), plotIdx).c_str(), &plot.y2AxisLabel))
598 {
600 }
601 }
602 if (plot.plotFlags & ImPlotFlags_YAxis3)
603 {
604 if (ImGui::InputText(fmt::format("Y3 Axis Label##{} - {}", size_t(id), plotIdx).c_str(), &plot.y3AxisLabel))
605 {
607 }
608 }
609 if (ImGui::BeginTable(fmt::format("Pin Settings##{} - {}", size_t(id), plotIdx).c_str(), 2,
610 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
611 {
612 ImGui::TableSetupColumn("Pin");
613 ImGui::TableSetupColumn("X Data");
614 ImGui::TableHeadersRow();
615
616 for (size_t pinIndex = 0; pinIndex < _pinData.size(); pinIndex++)
617 {
618 auto& pinData = _pinData.at(pinIndex);
619
620 ImGui::TableNextRow();
621 ImGui::TableNextColumn(); // Pin
622 ImGui::Text("%zu - %s", pinIndex + 1, pinData.dataIdentifier.c_str());
623
624 ImGui::TableNextColumn(); // X Data
625 if (!pinData.plotData.empty())
626 {
627 ImGui::SetNextItemWidth(200.0F * gui::NodeEditorApplication::windowFontRatio());
628 if (ImGui::BeginCombo(fmt::format("##X Data for Pin {} - {} - {}", pinIndex + 1, size_t(id), plotIdx).c_str(),
629 pinData.plotData.at(plot.selectedXdata.at(pinIndex)).displayName.c_str()))
630 {
631 for (size_t plotDataIndex = 0; plotDataIndex < pinData.plotData.size(); plotDataIndex++)
632 {
633 auto& plotData = pinData.plotData.at(plotDataIndex);
634
635 if (!plotData.hasData)
636 {
637 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5F);
638 }
639 const bool is_selected = (plot.selectedXdata.at(pinIndex) == plotDataIndex);
640 if (ImGui::Selectable(pinData.plotData.at(plotDataIndex).displayName.c_str(), is_selected))
641 {
643 plot.selectedXdata.at(pinIndex) = plotDataIndex;
644 if (plotDataIndex == GPST_PLOT_IDX) // GPST Time
645 {
646 // Set all data to plot over GPST Time
647 for (auto& selectedX : plot.selectedXdata)
648 {
649 selectedX = plotDataIndex;
650 }
651 }
652 else
653 {
654 // Remove all GPST Time on the x axis
655 for (auto& selectedX : plot.selectedXdata)
656 {
657 if (selectedX == GPST_PLOT_IDX) { selectedX = 0; }
658 }
659 }
660
661 for (auto& plotItem : plot.plotItems)
662 {
663 plotItem.eventMarker.clear();
664 plotItem.eventTooltips.clear();
665 }
666 }
667 if (!plotData.hasData)
668 {
669 ImGui::PopStyleVar();
670 }
671
672 // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
673 if (is_selected)
674 {
675 ImGui::SetItemDefaultFocus();
676 }
677 }
678 ImGui::EndCombo();
679 }
680 }
681 }
682
683 ImGui::EndTable();
684 }
685
686 if (ImGui::CheckboxFlags(fmt::format("Y-Axis 2##{} - {}", size_t(id), plotIdx).c_str(),
687 &plot.plotFlags, ImPlotFlags_YAxis2))
688 {
690 }
691 ImGui::SameLine();
692 if (ImGui::CheckboxFlags(fmt::format("Y-Axis 3##{} - {}", size_t(id), plotIdx).c_str(),
693 &plot.plotFlags, ImPlotFlags_YAxis3))
694 {
696 }
697 ImGui::SameLine();
698
699 if (ImGui::CheckboxFlags(fmt::format("Auto Limit X-Axis##{} - {}", size_t(id), plotIdx).c_str(),
700 &plot.xAxisFlags, ImPlotAxisFlags_AutoFit))
701 {
703 }
704 ImGui::SameLine();
705 if (ImGui::CheckboxFlags(fmt::format("Auto Limit Y-Axis##{} - {}", size_t(id), plotIdx).c_str(),
706 &plot.yAxisFlags, ImPlotAxisFlags_AutoFit))
707 {
709 }
710 ImGui::SameLine();
711 if (ImGui::Button(fmt::format("Same X range all plots##{} - {}", size_t(id), plotIdx).c_str()))
712 {
713 saveForceXaxisRange = true;
714 _forceXaxisRange.first.clear();
715 size_t pinIdx = plot.plotItems.empty() ? 0 : plot.plotItems.front().pinIndex;
716 const auto& xName = _pinData.at(pinIdx).plotData.at(plot.selectedXdata.at(pinIdx)).displayName;
717 for (size_t p = 0; p < _plots.size(); p++)
718 {
719 if (p == plotIdx) { continue; }
720 auto& plot = _plots.at(p);
721 size_t pinIdx = plot.plotItems.empty() ? 0 : plot.plotItems.front().pinIndex;
722 const auto& dispName = _pinData.at(pinIdx).plotData.at(plot.selectedXdata.at(pinIdx)).displayName;
723 if (xName == dispName) { _forceXaxisRange.first.insert(p); }
724 }
725 }
726
727 auto axisScaleCombo = [&](const char* label, ImPlotScale& axisScale) {
728 auto getImPlotScaleString = [](ImPlotScale scale) {
729 switch (scale)
730 {
731 case ImPlotScale_Linear: // default linear scale
732 return "Linear";
733 // case ImPlotScale_Time: // date/time scale
734 // return "Time";
735 case ImPlotScale_Log10: // base 10 logartithmic scale
736 return "Log10";
737 case ImPlotScale_SymLog: // symmetric log scale
738 return "SymLog";
739 default:
740 return "-";
741 }
742 return "-";
743 };
744
745 ImGui::SetNextItemWidth(100.0F);
746 if (ImGui::BeginCombo(fmt::format("{}-Axis Scale##{} - {}", label, size_t(id), plotIdx).c_str(), getImPlotScaleString(axisScale)))
747 {
748 for (size_t n = 0; n < 4; ++n)
749 {
750 if (n == ImPlotScale_Time) { continue; }
751 const bool is_selected = (static_cast<size_t>(axisScale) == n);
752 if (ImGui::Selectable(getImPlotScaleString(static_cast<ImPlotScale>(n)), is_selected))
753 {
754 axisScale = static_cast<ImPlotScale>(n);
756 }
757 if (is_selected) { ImGui::SetItemDefaultFocus(); } // Set the initial focus when opening the combo
758 }
759 ImGui::EndCombo();
760 }
761 };
762 axisScaleCombo("X", plot.xAxisScale);
763 ImGui::SameLine();
764 axisScaleCombo("Y1", plot.yAxesScale[0]);
765 if (plot.plotFlags & ImPlotFlags_YAxis2)
766 {
767 ImGui::SameLine();
768 axisScaleCombo("Y2", plot.yAxesScale[1]);
769 }
770 if (plot.plotFlags & ImPlotFlags_YAxis3)
771 {
772 ImGui::SameLine();
773 axisScaleCombo("Y3", plot.yAxesScale[2]);
774 }
775
776 ImGui::SameLine();
777 if (ImGui::CheckboxFlags(fmt::format("NoClip##LineFlags {} - {}", size_t(id), plotIdx).c_str(), &plot.lineFlags, ImPlotLineFlags_NoClip))
778 {
780 }
781 if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Markers (if displayed) on the edge of a plot will not be clipped"); }
782 ImGui::SameLine();
783 if (ImGui::CheckboxFlags(fmt::format("SkipNaN##LineFlags {} - {}", size_t(id), plotIdx).c_str(), &plot.lineFlags, ImPlotLineFlags_SkipNaN))
784 {
786 }
787 if (ImGui::IsItemHovered()) { ImGui::SetTooltip("NaNs values will be skipped instead of rendered as missing data"); }
788 ImGui::SameLine();
789 if (ImGui::CheckboxFlags(fmt::format("Loop##LineFlags {} - {}", size_t(id), plotIdx).c_str(), &plot.lineFlags, ImPlotLineFlags_Loop))
790 {
792 }
793 if (ImGui::IsItemHovered()) { ImGui::SetTooltip("The last and first point will be connected to form a closed loop"); }
794
795 ImGui::TreePop();
796 }
797
798#ifdef IMGUI_IMPL_OPENGL_LOADER_GL3W
799 {
800 auto afterOptionsCursorPos = ImGui::GetCursorPos();
801 float buttonSize = 0.6F * gui::NodeEditorApplication::windowFontRatio() * 24.0F;
802
803 ImGui::SetCursorPos(ImVec2(ImGui::GetWindowContentRegionWidth() - buttonSize - ImGui::GetStyle().ItemInnerSpacing.x, optionsCursorPos.y));
804 ImGui::PushID(fmt::format("{}{}", size_t(id), plotIdx).c_str());
805 if (ImGui::ImageButton(gui::NodeEditorApplication::m_SaveButtonImage, ImVec2(buttonSize, buttonSize)))
806 {
807 if (_screenshotFrameCnt == 0)
808 {
809 _screenShotPlotIdx = plotIdx;
810 _screenshotFrameCnt = 1;
811 }
812 }
813 ImGui::PopID();
814 ImGui::SetCursorPos(afterOptionsCursorPos);
815 }
816#endif
817
818 auto plotSize = plot.size;
819 if (gui::NodeEditorApplication::isUsingBigWindowFont()) { plotSize.y *= 0.8F * gui::NodeEditorApplication::windowFontRatio(); }
820
821 gui::widgets::Splitter(fmt::format("Splitter {} - {}", size_t(id), plotIdx).c_str(),
822 true, 4.0F, &plot.leftPaneWidth, &plot.rightPaneWidth, 3.0F, 80.0F, plotSize.y);
823
824 ImGui::SetNextItemWidth(plot.leftPaneWidth - 2.0F);
825
826 ImGui::BeginGroup();
827 {
828 if (ImGui::BeginCombo(fmt::format("##Data source pin selection{} - {}", size_t(id), plotIdx).c_str(),
829 inputPins.at(plot.selectedPin).name.c_str()))
830 {
831 for (size_t n = 0; n < inputPins.size(); ++n)
832 {
833 const bool is_selected = (plot.selectedPin == n);
834 if (ImGui::Selectable(inputPins.at(n).name.c_str(), is_selected, 0))
835 {
836 plot.selectedPin = n;
837 }
838
839 // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
840 if (is_selected)
841 {
842 ImGui::SetItemDefaultFocus();
843 }
844 }
845 ImGui::EndCombo();
846 }
847 auto comboBoxSize = ImGui::GetItemRectSize();
848 if (ImGui::Button(fmt::format("Clear##{} - {}", size_t(id), plotIdx).c_str(), ImVec2(plot.leftPaneWidth - 2.0F, 0)))
849 {
850 plot.plotItems.clear();
852 }
853 if (ImGui::BeginDragDropTarget())
854 {
855 if (const ImGuiPayload* payloadData = ImGui::AcceptDragDropPayload(fmt::format("DND PlotItem {} - {}", size_t(id), plotIdx).c_str()))
856 {
857 auto [pinIndex, dataIndex, displayName] = *static_cast<std::tuple<size_t, size_t, std::string*>*>(payloadData->Data);
858
859 auto iter = std::ranges::find(plot.plotItems, PlotInfo::PlotItem{ pinIndex, dataIndex, *displayName });
860 if (iter != plot.plotItems.end())
861 {
862 plot.plotItems.erase(iter);
864 }
865 }
866 ImGui::EndDragDropTarget();
867 }
868 auto buttonSize = ImGui::GetItemRectSize();
869 ImGui::BeginChild(fmt::format("Data Drag{} - {}", size_t(id), plotIdx).c_str(),
870 ImVec2(plot.leftPaneWidth - 2.0F, plotSize.y - comboBoxSize.y - buttonSize.y - 2 * ImGui::GetStyle().ItemSpacing.y),
871 true);
872 {
873 // Left Data Selectables
874 for (size_t dataIndex = 0; dataIndex < _pinData.at(plot.selectedPin).plotData.size(); ++dataIndex)
875 {
876 auto& plotData = _pinData.at(plot.selectedPin).plotData.at(dataIndex);
877 auto plotDataHasData = plotData.hasData;
878 if (!plotDataHasData)
879 {
880 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5F);
881 }
882 std::string label = plotData.displayName;
883
884 if (auto iter = std::ranges::find(plot.plotItems, PlotInfo::PlotItem{ plot.selectedPin, dataIndex, plotData.displayName });
885 iter != plot.plotItems.end())
886 {
887 label += fmt::format(" (Y{})", iter->axis + 1 - 3);
888 }
889
890 if (!label.empty())
891 {
892 ImGui::Selectable(label.c_str(), false, 0);
893 if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None))
894 {
895 // Data is copied into heap inside the drag and drop
896 auto pinAndDataIndex = std::make_tuple(plot.selectedPin, dataIndex, &plotData.displayName);
897 ImGui::SetDragDropPayload(fmt::format("DND PlotItem {} - {}", size_t(id), plotIdx).c_str(),
898 &pinAndDataIndex, sizeof(pinAndDataIndex));
899 ImGui::TextUnformatted(label.c_str());
900 ImGui::EndDragDropSource();
901 }
902 }
903
904 if (!plotDataHasData)
905 {
906 ImGui::PopStyleVar();
907 }
908 }
909
910 ImGui::EndChild();
911 }
912 ImGui::EndGroup();
913 }
914
915 ImGui::SameLine();
916
917 const char* xLabel = plot.overrideXAxisLabel ? (!plot.xAxisLabel.empty() ? plot.xAxisLabel.c_str() : nullptr)
918 : (!_pinData.at(0).plotData.empty() ? _pinData.at(0).plotData.at(plot.selectedXdata.at(0)).displayName.c_str() : nullptr);
919
920 const char* y1Label = !plot.y1AxisLabel.empty() ? plot.y1AxisLabel.c_str() : nullptr;
921 const char* y2Label = (plot.plotFlags & ImPlotFlags_YAxis2) && !plot.y2AxisLabel.empty() ? plot.y2AxisLabel.c_str() : nullptr;
922 const char* y3Label = (plot.plotFlags & ImPlotFlags_YAxis3) && !plot.y3AxisLabel.empty() ? plot.y3AxisLabel.c_str() : nullptr;
923
924 bool timeScaleXaxis = false;
925 std::array<double, 2> timeAxisMinMax = { std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity() };
926 for (auto& plotItem : plot.plotItems)
927 {
928 if (plot.selectedXdata.at(plotItem.pinIndex) == GPST_PLOT_IDX)
929 {
930 auto& pinData = _pinData.at(plotItem.pinIndex);
931 const auto& plotDataX = pinData.plotData.at(plot.selectedXdata.at(plotItem.pinIndex));
932
933 // Lock the buffer so no data can be inserted
934 std::scoped_lock<std::mutex> guard(pinData.mutex);
935 if (!plotDataX.buffer.empty())
936 {
937 timeScaleXaxis = true;
938 timeAxisMinMax[0] = std::min(timeAxisMinMax[0], plotDataX.buffer.front());
939 timeAxisMinMax[1] = std::max(timeAxisMinMax[1], plotDataX.buffer.back());
940 }
941 }
942 }
943
944 if (ImPlot::BeginPlot(fmt::format("{}##{} - {}", plot.title, size_t(id), plotIdx).c_str(), plotSize, plot.plotFlags))
945 {
946#ifdef IMGUI_IMPL_OPENGL_LOADER_GL3W
947 if (_screenShotPlotIdx == plotIdx)
948 {
949 // can be static as its impossible to trigger multiple screenshots in different nodes at once
950 static ImVec4 windowBgColor = ImGui::GetStyle().Colors[ImGuiCol_WindowBg];
951 static json imPlotStyle = ImPlot::GetStyle();
952 if (_screenshotFrameCnt == 1)
953 {
954 windowBgColor = ImGui::GetStyle().Colors[ImGuiCol_WindowBg];
955 imPlotStyle = ImPlot::GetStyle();
956 loadImPlotStyleFromConfigFile(gui::windows::plotScreenshotImPlotStyleFile.c_str(), ImPlot::GetStyle());
957 if (!ImPlot::IsColorAuto(ImPlot::GetStyle().Colors[ImPlotCol_FrameBg])
958 && ImPlot::GetStyle().Colors[ImPlotCol_FrameBg].w == 1.0F)
959 {
960 ImGui::GetStyle().Colors[ImGuiCol_WindowBg] = ImPlot::GetStyle().Colors[ImPlotCol_FrameBg];
961 }
962 ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w = 1.0;
963 _screenshotFrameCnt++;
964 }
965 else if (_screenshotFrameCnt == 4) // Need to delay a few frames in order for the background color to apply
966 {
967 ImRect CaptureRect = ImPlot::GetCurrentPlot()->FrameRect;
968 ImGuiIO& io = ImGui::GetIO();
969 ImGuiScreenshotImageBuf Output(static_cast<int>(CaptureRect.Min.x),
970 static_cast<int>(io.DisplaySize.y) - static_cast<int>(CaptureRect.Max.y),
971 static_cast<size_t>(CaptureRect.GetWidth()),
972 static_cast<size_t>(CaptureRect.GetHeight()));
973 auto savePath = flow::GetOutputPath() / fmt::format("Plot-{}_{}.png", size_t(id), plotIdx);
974 Output.SaveFile(savePath.c_str());
975 LOG_INFO("{}: Plot image saved as: {}", nameId(), savePath);
976 if (gui::windows::copyScreenshotsToClipboard)
977 {
978 gui::windows::CopyFileToClipboard(savePath.c_str());
979 }
980
981 ImGui::GetStyle().Colors[ImGuiCol_WindowBg] = windowBgColor;
982 imPlotStyle.get_to(ImPlot::GetStyle());
983 _screenshotFrameCnt = 0;
984 }
985 else if (_screenshotFrameCnt != 0) { _screenshotFrameCnt++; } // Increment frame counter
986 }
987#endif
988
989 if (saveForceXaxisRange)
990 {
991 std::get<ImPlotRange>(_forceXaxisRange) = ImPlot::GetCurrentPlot()->XAxis(ImAxis_X1).Range;
992 }
993 else if (_forceXaxisRange.first.contains(plotIdx))
994 {
995 if (plot.xAxisFlags & ImPlotAxisFlags_AutoFit)
996 {
997 plot.xAxisFlags &= ~ImPlotAxisFlags_AutoFit;
999 }
1000 ImPlot::GetCurrentPlot()->XAxis(ImAxis_X1).SetRange(std::get<ImPlotRange>(_forceXaxisRange));
1001 _forceXaxisRange.first.erase(plotIdx);
1002 }
1003
1004 ImPlot::SetupAxis(ImAxis_X1, xLabel, plot.xAxisFlags);
1005 ImPlot::SetupAxisScale(ImAxis_X1, timeScaleXaxis ? ImPlotScale_Time : plot.xAxisScale);
1006 ImPlot::SetupAxis(ImAxis_Y1, y1Label, plot.yAxisFlags);
1007 ImPlot::SetupAxisScale(ImAxis_Y1, plot.yAxesScale[0]);
1008 if (plot.plotFlags & ImPlotFlags_YAxis2)
1009 {
1010 ImPlot::SetupAxis(ImAxis_Y2, y2Label, plot.yAxisFlags | ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_Opposite);
1011 ImPlot::SetupAxisScale(ImAxis_Y2, plot.yAxesScale[1]);
1012 }
1013 if (plot.plotFlags & ImPlotFlags_YAxis3)
1014 {
1015 ImPlot::SetupAxis(ImAxis_Y3, y3Label, plot.yAxisFlags | ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_Opposite);
1016 ImPlot::SetupAxisScale(ImAxis_Y3, plot.yAxesScale[2]);
1017 }
1018
1019 std::vector<PlotInfo::PlotItem> plotItemsToRemove;
1020 bool hoverTooltipShown = false;
1021 for (size_t plotItemIdx = 0; plotItemIdx < plot.plotItems.size(); plotItemIdx++)
1022 {
1023 auto& plotItem = plot.plotItems.at(plotItemIdx);
1024 auto& pinData = _pinData.at(plotItem.pinIndex);
1025
1026 // Lock the buffer so no data can be inserted till plotting finishes
1027 std::scoped_lock<std::mutex> guard(pinData.mutex);
1028 // The next line needs already be locked, otherwise we have a data race
1029
1030 if (pinData.plotData.size() <= plotItem.dataIndex) { continue; } // Dynamic data can not be available yet
1031 auto& plotData = pinData.plotData.at(plotItem.dataIndex);
1032 const auto& plotDataX = pinData.plotData.at(plot.selectedXdata.at(plotItem.pinIndex));
1033 if (plotData.displayName != plotItem.displayName)
1034 {
1035 if (plotItem.displayName.empty())
1036 {
1037 plotItem.displayName = plotData.displayName; // old flow file where it was not set yet
1038 }
1039 else
1040 {
1041 plotItemsToRemove.push_back(plotItem);
1042 continue;
1043 }
1044 }
1045
1046 if (plotData.hasData
1047 && (plotItem.axis == ImAxis_Y1
1048 || (plotItem.axis == ImAxis_Y2 && (plot.plotFlags & ImPlotFlags_YAxis2))
1049 || (plotItem.axis == ImAxis_Y3 && (plot.plotFlags & ImPlotFlags_YAxis3))))
1050 {
1051 ImPlot::SetAxis(plotItem.axis);
1052
1053 if (plotItem.style.colormapMask.first != ColormapMaskType::None)
1054 {
1055 if (const auto& cmap = ColormapSearch(plotItem.style.colormapMask.first, plotItem.style.colormapMask.second))
1056 {
1057 if (plotItem.colormapMaskVersion != cmap->get().version) { plotItem.colormapMaskColors.clear(); }
1058 if (plotItem.colormapMaskColors.size() != plotData.buffer.size()
1059 && plotItem.style.colormapMaskDataCmpIdx < pinData.plotData.size())
1060 {
1061 plotItem.colormapMaskVersion = cmap->get().version;
1062 const auto& cmpData = pinData.plotData.at(plotItem.style.colormapMaskDataCmpIdx);
1063
1064 auto color = plotItem.style.lineType == PlotItemStyle::LineType::Line
1065 ? (ImPlot::IsColorAuto(plotItem.style.color) ? ImPlot::GetColormapColor(static_cast<int>(plotElementIdx)) : plotItem.style.color)
1066 : (ImPlot::IsColorAuto(plotItem.style.markerFillColor) ? ImPlot::GetColormapColor(static_cast<int>(plotElementIdx)) : plotItem.style.markerFillColor);
1067 plotItem.colormapMaskColors.reserve(plotData.buffer.size());
1068 for (size_t i = plotItem.colormapMaskColors.size(); i < plotData.buffer.size(); i++)
1069 {
1070 plotItem.colormapMaskColors.push_back(i >= cmpData.buffer.size() ? ImColor(color) : cmap->get().getColor(cmpData.buffer.at(i), color));
1071 }
1072 }
1073 }
1074 else
1075 {
1076 plotItem.style.colormapMask.first = ColormapMaskType::None;
1077 plotItem.style.colormapMask.second = -1;
1078 plotItem.colormapMaskColors.clear();
1079 }
1080 }
1081 if (plotItem.style.markers && plotItem.style.markerColormapMask.first != ColormapMaskType::None)
1082 {
1083 if (const auto& cmap = ColormapSearch(plotItem.style.markerColormapMask.first, plotItem.style.markerColormapMask.second))
1084 {
1085 if (plotItem.markerColormapMaskVersion != cmap->get().version) { plotItem.markerColormapMaskColors.clear(); }
1086 if (plotItem.markerColormapMaskColors.size() != plotData.buffer.size()
1087 && plotItem.style.markerColormapMaskDataCmpIdx < pinData.plotData.size())
1088 {
1089 plotItem.markerColormapMaskVersion = cmap->get().version;
1090 const auto& cmpData = pinData.plotData.at(plotItem.style.markerColormapMaskDataCmpIdx);
1091
1092 auto color = plotItem.style.lineType == PlotItemStyle::LineType::Line
1093 ? (ImPlot::IsColorAuto(plotItem.style.color) ? ImPlot::GetColormapColor(static_cast<int>(plotElementIdx)) : plotItem.style.color)
1094 : (ImPlot::IsColorAuto(plotItem.style.markerFillColor) ? ImPlot::GetColormapColor(static_cast<int>(plotElementIdx)) : plotItem.style.markerFillColor);
1095 plotItem.markerColormapMaskColors.reserve(plotData.buffer.size());
1096 for (size_t i = plotItem.markerColormapMaskColors.size(); i < plotData.buffer.size(); i++)
1097 {
1098 plotItem.markerColormapMaskColors.push_back(i >= cmpData.buffer.size() ? ImColor(color) : cmap->get().getColor(cmpData.buffer.at(i), color));
1099 }
1100 }
1101 }
1102 else
1103 {
1104 plotItem.style.markerColormapMask.first = ColormapMaskType::None;
1105 plotItem.style.markerColormapMask.second = -1;
1106 plotItem.markerColormapMaskColors.clear();
1107 }
1108 }
1109 if (plotItem.style.errorBoundsEnabled)
1110 {
1111 if (plotItem.errorBoundsData[0].size() != plotData.buffer.size()
1112 && plotItem.style.errorBoundsDataIdx < pinData.plotData.size())
1113 {
1114 const auto& errorData = pinData.plotData.at(plotItem.style.errorBoundsDataIdx);
1115 for (size_t i = plotItem.errorBoundsData[0].size(); i < plotData.buffer.size(); i++)
1116 {
1117 double errorValue = errorData.buffer.at(i);
1118 if (!plotItem.style.errorBoundsModifierExpression.empty())
1119 {
1120 try
1121 {
1122 mu::Parser p;
1123 double x = errorValue;
1124 p.DefineVar("x", &x);
1125 p.SetExpr(plotItem.style.errorBoundsModifierExpression);
1126 errorValue = p.Eval();
1127 }
1128 catch (mu::Parser::exception_type& e)
1129 {
1130 LOG_ERROR("{}: Error bound modifier parse error on '{}': {}", nameId(), plotItem.style.legendName, e.GetMsg());
1131 }
1132 }
1133 plotItem.errorBoundsData[0].push_back(plotData.buffer.at(i) - errorValue);
1134 plotItem.errorBoundsData[1].push_back(plotData.buffer.at(i) + errorValue);
1135 }
1136 }
1137 }
1138 if (plotItem.style.eventsEnabled)
1139 {
1140 if (plotItem.eventMarker.size() != plotData.buffer.size())
1141 {
1142 auto& plotDataRelTime = pinData.plotData.at(0);
1143 for (size_t i = plotItem.eventMarker.size(); i < plotData.buffer.size(); i++)
1144 {
1145 double relTime = plotDataRelTime.buffer.at(i);
1147 try
1148 {
1149 std::regex filter(plotItem.style.eventTooltipFilterRegex,
1150 std::regex_constants::ECMAScript | std::regex_constants::icase);
1151 for (const auto& e : pinData.events)
1152 {
1153 if (std::abs(std::get<0>(e) - relTime) <= 1e-6)
1154 {
1155 tooltip.time = std::get<1>(e);
1156 if ((std::get<3>(e) == -1 || static_cast<size_t>(std::get<3>(e)) == plotItem.dataIndex)
1157 && (plotItem.style.eventTooltipFilterRegex.empty() || std::regex_search(std::get<2>(e), filter)))
1158 {
1159 tooltip.texts.push_back(std::get<2>(e));
1160 }
1161 }
1162 }
1163 }
1164 catch (...) // NOLINT(bugprone-empty-catch)
1165 {}
1166
1167 if (!tooltip.texts.empty())
1168 {
1169 plotItem.eventMarker.push_back(plotData.buffer.at(i));
1170 plotItem.eventTooltips.emplace_back(plotDataX.buffer.at(i), plotData.buffer.at(i), tooltip);
1171 }
1172 else
1173 {
1174 plotItem.eventMarker.push_back(std::nan(""));
1175 }
1176 }
1177 }
1178 }
1179
1180 if (plotItem.style.legendName.empty())
1181 {
1182 plotItem.style.legendName = fmt::format("{} ({})", plotData.displayName, inputPins.at(plotItem.pinIndex).name);
1183 }
1184 std::string plotName = fmt::format("{}##{} - {} - {}", plotItem.style.legendName, size_t(id), plotItem.pinIndex + 1, plotData.displayName);
1185
1186 plotItem.style.plotData(plotName.c_str(),
1187 plotDataX.buffer,
1188 plotData.buffer,
1189 static_cast<int>(plotElementIdx),
1190 pinData.stride,
1191 plot.lineFlags,
1192 &plotItem.colormapMaskColors,
1193 &plotItem.markerColormapMaskColors,
1194 &plotItem.errorBoundsData);
1195
1196 // ----------------------------------- Tooltips --------------------------------------
1197 ImGuiWindow* plotWindow = ImGui::GetCurrentWindow();
1198
1200 plot.tooltips,
1201 plotItemIdx,
1202 plotItem.style.legendName,
1203 fmt::format("{} {} {}", size_t(id), plotItem.pinIndex, plotItem.displayName),
1204 { reinterpret_cast<int*>(plotWindow) },
1205 [&](size_t dataIdx) { return pinData.rawNodeData.at(dataIdx)->insTime; },
1206 [&](size_t dataIdx, const char* tooltipUID) {
1207 const auto& nodeData = pinData.rawNodeData.at(dataIdx);
1208 auto nEvents = nodeData->events().size();
1209 if (nEvents > 0)
1210 {
1211 ImGui::SetNextItemOpen(false, ImGuiCond_Once);
1212 if (ImGui::TreeNode(fmt::format("Events: {}", nEvents).c_str()))
1213 {
1214 for (const auto& text : nodeData->events())
1215 {
1216 ImGui::BulletText("%s", text.c_str());
1217 }
1218 ImGui::TreePop();
1219 }
1220 }
1221 else { ImGui::BulletText("Events: 0"); }
1222 if (nodeData->hasTooltip())
1223 {
1224 nodeData->guiTooltip(true, false, plotItem.displayName.c_str(),
1225 tooltipUID, reinterpret_cast<int*>(plotWindow));
1226 }
1227 });
1228
1229 hoverTooltipShown |= ShowPlotTooltip(
1230 plot.tooltips, plotItemIdx,
1231 plotName, plotItem.axis,
1232 plotDataX.buffer, plotData.buffer,
1233 hoverTooltipShown,
1234 [&](size_t dataIdx) {
1235 const auto& nodeData = pinData.rawNodeData.at(dataIdx);
1236 ImGui::TextUnformatted(fmt::format("{} - {}", plotItem.style.legendName,
1237 nodeData->insTime.toYMDHMS(GPST))
1238 .c_str());
1239 ImGui::Separator();
1240
1241 auto nEvents = nodeData->events().size();
1242 if (nEvents > 0)
1243 {
1244 ImGui::SetNextItemOpen(false, ImGuiCond_Always);
1245 if (ImGui::TreeNode(fmt::format("Events: {}", nEvents).c_str())) { ImGui::TreePop(); }
1246 }
1247 else { ImGui::BulletText("Events: 0"); }
1248
1249 if (pinData.rawNodeData.at(dataIdx)->hasTooltip())
1250 {
1251 auto tooltipUID = fmt::format("{} {} {} {}", size_t(id), plotItem.pinIndex, plotItem.displayName, dataIdx);
1252 pinData.rawNodeData.at(dataIdx)->guiTooltip(ImGui::IsKeyDown(ImGuiKey_ModShift), true,
1253 plotItem.displayName.c_str(), tooltipUID.c_str(),
1254 reinterpret_cast<int*>(plotWindow));
1255 }
1256 });
1257
1258 // ###################################################################################
1259
1260 if (plotItem.style.eventsEnabled)
1261 {
1262 if (const auto* item = ImPlot::GetCurrentPlot()->Items.GetItem(plotName.c_str());
1263 item && item->Show)
1264 {
1265 auto stride = plotItem.style.stride ? plotItem.style.stride
1266 : pinData.stride;
1267 auto dataPointCount = static_cast<int>(std::ceil(static_cast<double>(plotData.buffer.size())
1268 / static_cast<double>(stride)));
1269
1270 ImPlot::SetNextMarkerStyle(plotItem.style.eventMarkerStyle,
1271 plotItem.style.eventMarkerSize,
1272 ImPlot::IsColorAuto(plotItem.style.eventMarkerFillColor) ? ImPlot::GetColormapColor(static_cast<int>(plotElementIdx)) : plotItem.style.eventMarkerFillColor,
1273 plotItem.style.eventMarkerWeight,
1274 ImPlot::IsColorAuto(plotItem.style.eventMarkerOutlineColor) ? ImPlot::GetColormapColor(static_cast<int>(plotElementIdx)) : plotItem.style.eventMarkerOutlineColor);
1275 ImPlot::PlotScatter(fmt::format("##{} - Events", plotName).c_str(),
1276 plotDataX.buffer.data(),
1277 plotItem.eventMarker.data(),
1278 dataPointCount,
1279 ImPlotScatterFlags_None,
1280 static_cast<int>(std::ceil(static_cast<double>(plotItem.eventMarker.offset()) / static_cast<double>(stride))),
1281 stride * static_cast<int>(sizeof(double)));
1282
1283 if (ImPlot::IsPlotHovered())
1284 {
1285 constexpr double HOVER_PIXEL_SIZE = 5.0;
1286 auto limits = ImPlot::GetPlotLimits(IMPLOT_AUTO, plotItem.axis);
1287
1288 ImVec2 scaling = ImVec2(static_cast<float>(HOVER_PIXEL_SIZE * (limits.X.Max - limits.X.Min) / ImPlot::GetCurrentPlot()->PlotRect.GetWidth()),
1289 static_cast<float>(HOVER_PIXEL_SIZE * (limits.Y.Max - limits.Y.Min) / ImPlot::GetCurrentPlot()->PlotRect.GetHeight()));
1290 ImPlotPoint mouse = ImPlot::GetPlotMousePos();
1291
1292 std::vector<PlotEventTooltip> tooltips;
1293 for (const auto& e : plotItem.eventTooltips)
1294 {
1295 if (std::abs(mouse.x - std::get<0>(e)) < scaling.x
1296 && std::abs(mouse.y - std::get<1>(e)) < scaling.y)
1297 {
1298 tooltips.push_back(std::get<2>(e));
1299 }
1300 }
1301 if (!tooltips.empty())
1302 {
1303 ImGui::BeginTooltip();
1304 ImGui::PushFont(Application::MonoFont());
1305 for (size_t i = 0; i < tooltips.size(); i++)
1306 {
1307 ImGui::SetNextItemOpen(true, ImGuiCond_Always);
1308 if (ImGui::TreeNode(fmt::format("{} GPST", tooltips.at(i).time.toYMDHMS(GPST)).c_str()))
1309 {
1310 for (const auto& text : tooltips.at(i).texts)
1311 {
1312 ImGui::BulletText("%s", text.c_str());
1313 }
1314 ImGui::TreePop();
1315 }
1316 if (i != tooltips.size() - 1) { ImGui::Separator(); }
1317 }
1318 ImGui::PopFont();
1319 ImGui::EndTooltip();
1320 }
1321 }
1322 }
1323 }
1324
1325 // allow legend item labels to be DND sources
1326 if (ImPlot::BeginDragDropSourceItem(plotName.c_str()))
1327 {
1328 // Data is copied into heap inside the drag and drop
1329 auto pinAndDataIndex = std::make_tuple(plotItem.pinIndex, plotItem.dataIndex, &plotItem.displayName);
1330 ImGui::SetDragDropPayload(fmt::format("DND PlotItem {} - {}", size_t(id), plotIdx).c_str(), &pinAndDataIndex, sizeof(pinAndDataIndex));
1331 ImGui::TextUnformatted(plotData.displayName.c_str());
1332 ImPlot::EndDragDropSource();
1333 }
1334
1335 auto ShowDataReferenceChooser = [&](size_t& dataIdx, const char* label = "") -> bool {
1336 bool changed = false;
1337 const char* preview = dataIdx < pinData.plotData.size()
1338 ? pinData.plotData.at(dataIdx).displayName.c_str()
1339 : "";
1340 if (ImGui::BeginCombo(label, preview))
1341 {
1342 for (size_t plotDataIndex = 0; plotDataIndex < pinData.plotData.size(); plotDataIndex++)
1343 {
1344 auto& plotData = pinData.plotData.at(plotDataIndex);
1345
1346 if (!plotData.hasData) { ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5F); }
1347 const bool is_selected = (dataIdx == plotDataIndex);
1348 if (ImGui::Selectable(pinData.plotData.at(plotDataIndex).displayName.c_str(), is_selected))
1349 {
1350 changed = true;
1351 dataIdx = plotDataIndex;
1352 }
1353 if (!plotData.hasData) { ImGui::PopStyleVar(); }
1354
1355 // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
1356 if (is_selected) { ImGui::SetItemDefaultFocus(); }
1357 }
1358 ImGui::EndCombo();
1359 }
1360 return changed;
1361 };
1362
1363 if (auto legendReturn = plotItem.style.showLegendPopup(
1364 plotName.c_str(),
1365 fmt::format("Pin {} - {}: {}", plotItem.pinIndex + 1,
1366 pinData.dataIdentifier, plotData.displayName)
1367 .c_str(),
1368 static_cast<int>(plotData.buffer.size()),
1369 static_cast<int>(plotElementIdx),
1370 nameId().c_str(),
1371 plot.lineFlags,
1372 &plotItem.colormapMaskColors,
1373 &plotItem.markerColormapMaskColors,
1374 ShowDataReferenceChooser,
1375 &plotItem.eventMarker,
1376 &plotItem.eventTooltips);
1377 legendReturn.changed)
1378 {
1380
1381 if (legendReturn.errorBoundsReCalcNeeded)
1382 {
1383 for (auto& data : plotItem.errorBoundsData) { data.clear(); }
1384 }
1385 }
1386
1387 plotElementIdx++;
1388 }
1389 }
1390
1391 for (const auto& plotItem : plotItemsToRemove)
1392 {
1393 LOG_WARN("{}: Erasing plot item '{}' from plot '{}', because it does not match the order the data came in",
1394 nameId(), plotItem.displayName, plot.headerText);
1395 std::erase(plot.plotItems, plotItem);
1397 }
1398
1399 auto addDragDropPlotToAxis = [this, plotIdx, &plot](ImAxis dragDropAxis) {
1400 if (const ImGuiPayload* payloadData = ImGui::AcceptDragDropPayload(fmt::format("DND PlotItem {} - {}", size_t(id), plotIdx).c_str()))
1401 {
1402 auto [pinIndex, dataIndex, displayName] = *static_cast<std::tuple<size_t, size_t, std::string*>*>(payloadData->Data);
1403
1404 auto iter = std::ranges::find(plot.plotItems, PlotInfo::PlotItem{ pinIndex, dataIndex, *displayName });
1405 if (iter != plot.plotItems.end()) // Item gets dragged from one axis to another
1406 {
1407 iter->axis = dragDropAxis;
1408 }
1409 else
1410 {
1411 plot.plotItems.emplace_back(pinIndex, dataIndex, *displayName, dragDropAxis);
1412 }
1414 }
1415 };
1416
1417 // allow the main plot area to be a DND target
1418 if (ImPlot::BeginDragDropTargetPlot())
1419 {
1420 addDragDropPlotToAxis(ImAxis_Y1);
1421 ImPlot::EndDragDropTarget();
1422 }
1423 // allow each y-axis to be a DND target
1424 for (ImAxis y = ImAxis_Y1; y <= ImAxis_Y3; ++y)
1425 {
1426 if ((y == ImAxis_Y2 && !(plot.plotFlags & ImPlotFlags_YAxis2))
1427 || (y == ImAxis_Y3 && !(plot.plotFlags & ImPlotFlags_YAxis3)))
1428 {
1429 continue;
1430 }
1431 if (ImPlot::BeginDragDropTargetAxis(y))
1432 {
1433 addDragDropPlotToAxis(y);
1434 ImPlot::EndDragDropTarget();
1435 }
1436 }
1437
1438 ImPlot::EndPlot();
1439 }
1440 }
1441
1442 if (_dragAndDropHeaderIndex >= 0
1443 && plotIdx != static_cast<size_t>(_dragAndDropHeaderIndex - 1)
1444 && plotIdx != static_cast<size_t>(_dragAndDropHeaderIndex))
1445 {
1446 showDragDropTargetHeader(plotIdx + 1);
1447 }
1448 }
1449
1450 if (!dragAndDropHeaderStillInProgress)
1451 {
1452 _dragAndDropHeaderIndex = -1;
1453 }
1454
1455 ImGui::Separator();
1456 if (ImGui::Button(fmt::format("Add Plot##{}", size_t(id)).c_str()))
1457 {
1458 ++_nPlots;
1459 LOG_DEBUG("{}: # Plots changed to {}", nameId(), _nPlots);
1461 updateNumberOfPlots();
1462 }
1463}
1464
1465[[nodiscard]] json NAV::Plot::save() const
1466{
1467 LOG_TRACE("{}: called", nameId());
1468
1469 json j;
1470
1471 j["dynamicInputPins"] = _dynamicInputPins;
1472 j["nPlots"] = _nPlots;
1473 j["pinData"] = _pinData;
1474 j["plots"] = _plots;
1475 j["overridePositionStartValues"] = _overridePositionStartValues;
1477 {
1478 j["originPosition"] = _originPosition.value();
1479 }
1480
1481 return j;
1482}
1483
1485{
1486 LOG_TRACE("{}: called", nameId());
1487
1488 if (j.contains("dynamicInputPins"))
1489 {
1490 NAV::gui::widgets::from_json(j.at("dynamicInputPins"), _dynamicInputPins, this);
1491 }
1492 if (j.contains("nPlots"))
1493 {
1494 j.at("nPlots").get_to(_nPlots);
1496 }
1497 if (j.contains("pinData"))
1498 {
1499 j.at("pinData").get_to(_pinData);
1500
1501 for (size_t inputPinIndex = 0; inputPinIndex < inputPins.size(); inputPinIndex++)
1502 {
1503 if (inputPinIndex >= _pinData.size()) { break; }
1504 switch (_pinData.at(inputPinIndex).pinType)
1505 {
1507 inputPins.at(inputPinIndex).type = Pin::Type::Flow;
1508 inputPins.at(inputPinIndex).dataIdentifier = _dataIdentifier;
1509 inputPins.at(inputPinIndex).callback = static_cast<InputPin::FlowFirableCallbackFunc>(&Plot::plotFlowData);
1510 break;
1512 inputPins.at(inputPinIndex).type = Pin::Type::Bool;
1513 inputPins.at(inputPinIndex).dataIdentifier.clear();
1514 inputPins.at(inputPinIndex).callback = static_cast<InputPin::DataChangedNotifyFunc>(&Plot::plotBoolean);
1515 break;
1517 inputPins.at(inputPinIndex).type = Pin::Type::Int;
1518 inputPins.at(inputPinIndex).dataIdentifier.clear();
1519 inputPins.at(inputPinIndex).callback = static_cast<InputPin::DataChangedNotifyFunc>(&Plot::plotInteger);
1520 break;
1522 inputPins.at(inputPinIndex).type = Pin::Type::Float;
1523 inputPins.at(inputPinIndex).dataIdentifier.clear();
1524 inputPins.at(inputPinIndex).callback = static_cast<InputPin::DataChangedNotifyFunc>(&Plot::plotFloat);
1525 break;
1527 inputPins.at(inputPinIndex).type = Pin::Type::Matrix;
1528 inputPins.at(inputPinIndex).dataIdentifier = { "Eigen::MatrixXd", "Eigen::VectorXd" };
1529 inputPins.at(inputPinIndex).callback = static_cast<InputPin::DataChangedNotifyFunc>(&Plot::plotMatrix);
1530 break;
1531 default:
1532 break;
1533 }
1534 }
1535 }
1536 if (j.contains("plots"))
1537 {
1538 j.at("plots").get_to(_plots);
1539 }
1540 if (j.contains("overridePositionStartValues"))
1541 {
1542 j.at("overridePositionStartValues").get_to(_overridePositionStartValues);
1543 }
1545 {
1546 if (j.contains("originPosition"))
1547 {
1548 _originPosition = j.at("originPosition").get<gui::widgets::PositionWithFrame>();
1549 }
1550 else
1551 {
1553 }
1554 }
1555}
1556
1558{
1559 LOG_TRACE("{}: called", nameId());
1560
1562
1563 _startTime.reset();
1565
1566 for (auto& plot : _plots)
1567 {
1568 plot.tooltips.clear();
1569 for (auto& plotItem : plot.plotItems)
1570 {
1571 plotItem.colormapMaskColors.clear();
1572 plotItem.markerColormapMaskColors.clear();
1573 for (auto& data : plotItem.errorBoundsData) { data.clear(); }
1574 plotItem.eventMarker.clear();
1575 plotItem.eventTooltips.clear();
1576 }
1577 }
1578 for (auto& pinData : _pinData)
1579 {
1580 std::scoped_lock<std::mutex> guard(pinData.mutex); // Lock the buffer for multithreaded access
1581
1582 pinData.rawNodeData.clear();
1583
1584 for (auto& plotData : pinData.plotData)
1585 {
1586 plotData.hasData = false;
1587 plotData.buffer.clear();
1588 }
1589 if (pinData.dynamicDataStartIndex != -1 && static_cast<int>(pinData.plotData.size()) >= pinData.dynamicDataStartIndex) // Erase all dynamic data
1590 {
1591 pinData.plotData.erase(pinData.plotData.begin() + pinData.dynamicDataStartIndex, pinData.plotData.end());
1592 }
1593 pinData.events.clear();
1594 }
1595
1596 return true;
1597}
1598
1600{
1601 LOG_TRACE("{}: called", nameId());
1602}
1603
1605{
1606 LOG_TRACE("{}: called for {} ==> {}", nameId(), size_t(startPin.id), size_t(endPin.id));
1607
1608 size_t pinIndex = inputPinIndexFromId(endPin.id);
1609
1610 for (auto& plotData : _pinData.at(pinIndex).plotData) // Mark all plot data for deletion
1611 {
1612 plotData.markedForDelete = true;
1613 }
1614
1615 size_t i = 0;
1616
1617 if (inputPins.at(pinIndex).type == Pin::Type::Flow)
1618 {
1619 if (startPin.dataIdentifier.size() > 1)
1620 {
1621 // Happens if connected Node supports multiple output values which can be chosen, but the node did not load yet.
1622 // But it will and then recreate the link
1623 return;
1624 }
1625 if (_pinData.at(pinIndex).dataIdentifier != startPin.dataIdentifier.front())
1626 {
1627 _pinData.at(pinIndex).plotData.clear();
1628 for (auto& plot : _plots)
1629 {
1630 while (true)
1631 {
1632 auto plotItemIter = std::ranges::find_if(plot.plotItems,
1633 [pinIndex](const PlotInfo::PlotItem& plotItem) { return plotItem.pinIndex == pinIndex; });
1634 if (plotItemIter != plot.plotItems.end())
1635 {
1636 plot.plotItems.erase(plotItemIter);
1637 }
1638 else
1639 {
1640 break;
1641 }
1642 }
1643 }
1644 }
1645
1646 _pinData.at(pinIndex).dataIdentifier = startPin.dataIdentifier.front();
1647
1648 // NodeData
1649 _pinData.at(pinIndex).addPlotDataItem(i++, "Time [s]");
1650 _pinData.at(pinIndex).addPlotDataItem(i++, "GPST Time");
1651 _pinData.at(pinIndex).addPlotDataItem(i++, "GPS time of week [s]");
1652
1653 for (const auto& desc : NAV::NodeRegistry::GetStaticDataDescriptors(startPin.dataIdentifier))
1654 {
1655 _pinData.at(pinIndex).addPlotDataItem(i++, desc);
1656 }
1658 {
1659 _pinData.at(pinIndex).dynamicDataStartIndex = static_cast<int>(i);
1660 }
1661 }
1662 else
1663 {
1664 _pinData.at(pinIndex).dataIdentifier = startPin.name;
1665
1666 // NodeData
1667 _pinData.at(pinIndex).addPlotDataItem(i++, "Time [s]");
1668 _pinData.at(pinIndex).addPlotDataItem(i++, "GPST Time");
1669 _pinData.at(pinIndex).addPlotDataItem(i++, "GPS time of week [s]");
1670
1671 if (inputPins.at(pinIndex).type == Pin::Type::Bool)
1672 {
1673 _pinData.at(pinIndex).addPlotDataItem(i++, "Boolean");
1674 }
1675 else if (inputPins.at(pinIndex).type == Pin::Type::Int)
1676 {
1677 _pinData.at(pinIndex).addPlotDataItem(i++, "Integer");
1678 }
1679 else if (inputPins.at(pinIndex).type == Pin::Type::Float)
1680 {
1681 _pinData.at(pinIndex).addPlotDataItem(i++, "Float");
1682 }
1683 else if (inputPins.at(pinIndex).type == Pin::Type::Matrix)
1684 {
1685 if (startPin.dataIdentifier.front() == "Eigen::MatrixXd")
1686 {
1687 if (auto matrix = getInputValue<Eigen::MatrixXd>(pinIndex))
1688 {
1689 for (int row = 0; row < matrix->v->rows(); row++)
1690 {
1691 for (int col = 0; col < matrix->v->cols(); col++)
1692 {
1693 _pinData.at(pinIndex).addPlotDataItem(i++, fmt::format("{}, {}", row, col));
1694 }
1695 }
1696 }
1697 }
1698 else if (startPin.dataIdentifier.front() == "Eigen::VectorXd")
1699 {
1700 if (auto matrix = getInputValue<Eigen::VectorXd>(pinIndex))
1701 {
1702 for (int row = 0; row < matrix->v->rows(); row++)
1703 {
1704 _pinData.at(pinIndex).addPlotDataItem(i++, fmt::format("{}", row));
1705 }
1706 }
1707 }
1708
1709 for (size_t dataIndex = i; dataIndex < _pinData.at(pinIndex).plotData.size(); dataIndex++)
1710 {
1711 const auto& displayName = _pinData.at(pinIndex).plotData.at(dataIndex).displayName;
1712 for (auto& plot : _plots)
1713 {
1714 auto plotItemIter = std::ranges::find(plot.plotItems, PlotInfo::PlotItem{ pinIndex, dataIndex, displayName });
1715 if (plotItemIter != plot.plotItems.end())
1716 {
1717 plot.plotItems.erase(plotItemIter);
1718 }
1719 }
1720 }
1721 }
1722 }
1723
1724 for (size_t dataIndex = 0; dataIndex < _pinData.at(pinIndex).plotData.size(); ++dataIndex)
1725 {
1726 auto iter = _pinData.at(pinIndex).plotData.begin();
1727 std::advance(iter, dataIndex);
1728 if (iter->markedForDelete)
1729 {
1730 _pinData.at(pinIndex).plotData.erase(iter);
1731 --dataIndex;
1732 }
1733 }
1734
1735 for (auto& plot : _plots)
1736 {
1737 if (plot.selectedXdata.at(pinIndex) > _pinData.at(pinIndex).plotData.size())
1738 {
1739 plot.selectedXdata.at(pinIndex) = 1;
1740 }
1741 }
1742}
1743
1745{
1746 while (_nPlots > _plots.size())
1747 {
1748 _plots.emplace_back(fmt::format("Plot {}", _plots.size() + 1), inputPins.size());
1749 }
1750 while (_nPlots < _plots.size())
1751 {
1752 _plots.pop_back();
1753 }
1754}
1755
1757{
1758 auto* plotNode = static_cast<Plot*>(node); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
1759
1760 nm::CreateInputPin(node, fmt::format("Pin {}", node->inputPins.size() + 1).c_str(), Pin::Type::Flow, plotNode->_dataIdentifier, &Plot::plotFlowData);
1761 plotNode->_pinData.emplace_back();
1762 for (auto& plot : plotNode->_plots)
1763 {
1764 plot.selectedXdata.emplace_back(1);
1765 }
1766}
1767
1768void NAV::Plot::pinDeleteCallback(Node* node, size_t pinIdx)
1769{
1770 auto* plotNode = static_cast<Plot*>(node); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
1771
1772 for (auto& plot : plotNode->_plots)
1773 {
1774 if (plot.selectedPin >= pinIdx && plot.selectedPin > 0)
1775 {
1776 plot.selectedPin -= 1;
1777 }
1778 for (int64_t plotItemIdx = 0; plotItemIdx < static_cast<int64_t>(plot.plotItems.size()); ++plotItemIdx)
1779 {
1780 auto& plotItem = plot.plotItems.at(static_cast<size_t>(plotItemIdx));
1781
1782 if (plotItem.pinIndex == pinIdx) // The index we want to delete
1783 {
1784 plot.plotItems.erase(plot.plotItems.begin() + plotItemIdx);
1785 plotItemIdx -= 1;
1786 }
1787 else if (plotItem.pinIndex > pinIdx) // Index higher -> Decrement
1788 {
1789 plotItem.pinIndex -= 1;
1790 }
1791 }
1792
1793 plot.selectedXdata.erase(std::next(plot.selectedXdata.begin(), static_cast<int64_t>(pinIdx)));
1794 }
1795
1796 nm::DeleteInputPin(node->inputPins.at(pinIdx));
1797 plotNode->_pinData.erase(std::next(plotNode->_pinData.begin(), static_cast<int64_t>(pinIdx)));
1798}
1799
1800void NAV::Plot::addEvent(size_t pinIndex, InsTime insTime, const std::string& text, int32_t dataIndex)
1801{
1802 if (!insTime.empty() && !_startTime.empty())
1803 {
1804 double relTime = static_cast<double>((insTime - _startTime).count());
1805 _pinData.at(pinIndex).events.emplace_back(relTime, insTime, text, dataIndex);
1806 }
1807}
1808
1809void NAV::Plot::addData(size_t pinIndex, size_t dataIndex, double value)
1810{
1811 auto& plotData = _pinData.at(pinIndex).plotData.at(dataIndex);
1812
1813 plotData.buffer.push_back(value);
1814 if (!std::isnan(value))
1815 {
1816 plotData.hasData = true;
1817 }
1818}
1819
1820size_t NAV::Plot::addData(size_t pinIndex, std::string displayName, double value)
1821{
1822 auto& pinData = _pinData.at(pinIndex);
1823
1824 auto plotData = std::ranges::find_if(pinData.plotData, [&](const auto& data) {
1825 return data.displayName == displayName;
1826 });
1827 if (plotData == pinData.plotData.end()) // Item is new
1828 {
1829 pinData.addPlotDataItem(pinData.plotData.size(), displayName);
1830 plotData = pinData.plotData.end() - 1;
1831 plotData->isDynamic = true;
1832
1833 // We assume, there is a static item at the front (the time)
1834 for (size_t i = plotData->buffer.size(); i < pinData.plotData.front().buffer.size() - 1; i++) // Add empty NaN values to shift it to the correct start point
1835 {
1836 plotData->buffer.push_back(std::nan(""));
1837 }
1838 }
1839 else
1840 {
1841 // Item was there, but it could have been missing and came again
1842 for (size_t i = plotData->buffer.size(); i < pinData.plotData.front().buffer.size() - 1; i++) // Add empty NaN values to shift it to the correct start point
1843 {
1844 plotData->buffer.push_back(std::nan(""));
1845 }
1846 }
1847 auto dataIndex = static_cast<size_t>(plotData - pinData.plotData.begin());
1848 addData(pinIndex, dataIndex, value);
1849 return dataIndex;
1850}
1851
1853{
1854 if (!_originPosition)
1855 {
1857 .e_position = trafo::lla2ecef_WGS84(lla_position) };
1858 }
1859
1860 int sign = lla_position.x() > _originPosition->latitude() ? 1 : -1;
1861 // North/South deviation [m]
1862 double northSouth = calcGeographicalDistance(lla_position.x(), lla_position.y(),
1863 _originPosition->latitude(), lla_position.y())
1864 * sign;
1865
1866 sign = lla_position.y() > _originPosition->longitude() ? 1 : -1;
1867 // East/West deviation [m]
1868 double eastWest = calcGeographicalDistance(lla_position.x(), lla_position.y(),
1869 lla_position.x(), _originPosition->longitude())
1870 * sign;
1871
1872 return { .northSouth = northSouth, .eastWest = eastWest };
1873}
1874
1875void NAV::Plot::plotBoolean(const InsTime& insTime, size_t pinIdx)
1876{
1877 LOG_DATA("{}: Plotting boolean on pin '{}' with time {}", nameId(), inputPins[pinIdx].name, insTime.toYMDHMS());
1878 if (ConfigManager::Get<bool>("nogui"))
1879 {
1880 releaseInputValue(pinIdx);
1881 return;
1882 }
1883
1884 if (auto value = getInputValue<bool>(pinIdx);
1885 value && !insTime.empty())
1886 {
1887 if (_startTime.empty()) { _startTime = insTime; }
1888 size_t i = 0;
1889
1890 std::scoped_lock<std::mutex> guard(_pinData.at(pinIdx).mutex);
1891
1892 // NodeData
1893 addData(pinIdx, i++, static_cast<double>((insTime - _startTime).count()));
1894 addData(pinIdx, i++, static_cast<double>(insTime.toUnixTime() + insTime.differenceToUTC(GPST)));
1895 addData(pinIdx, i++, static_cast<double>(insTime.toGPSweekTow().tow));
1896 // Boolean
1897 addData(pinIdx, i++, static_cast<double>(*value->v));
1898 }
1899}
1900
1901void NAV::Plot::plotInteger(const InsTime& insTime, size_t pinIdx)
1902{
1903 LOG_DATA("{}: Plotting integer on pin '{}' with time {}", nameId(), inputPins[pinIdx].name, insTime.toYMDHMS());
1904 if (ConfigManager::Get<bool>("nogui"))
1905 {
1906 releaseInputValue(pinIdx);
1907 return;
1908 }
1909
1910 if (auto value = getInputValue<int>(pinIdx);
1911 value && !insTime.empty())
1912 {
1913 if (_startTime.empty()) { _startTime = insTime; }
1914 size_t i = 0;
1915
1916 std::scoped_lock<std::mutex> guard(_pinData.at(pinIdx).mutex);
1917
1918 // NodeData
1919 addData(pinIdx, i++, static_cast<double>((insTime - _startTime).count()));
1920 addData(pinIdx, i++, static_cast<double>(insTime.toUnixTime() + insTime.differenceToUTC(GPST)));
1921 addData(pinIdx, i++, static_cast<double>(insTime.toGPSweekTow().tow));
1922 // Integer
1923 addData(pinIdx, i++, static_cast<double>(*value->v));
1924 }
1925}
1926
1927void NAV::Plot::plotFloat(const InsTime& insTime, size_t pinIdx)
1928{
1929 LOG_DATA("{}: Plotting float on pin '{}' with time {}", nameId(), inputPins[pinIdx].name, insTime.toYMDHMS());
1930 if (ConfigManager::Get<bool>("nogui"))
1931 {
1932 releaseInputValue(pinIdx);
1933 return;
1934 }
1935
1936 if (auto value = getInputValue<double>(pinIdx);
1937 value && !insTime.empty())
1938 {
1939 if (_startTime.empty()) { _startTime = insTime; }
1940 size_t i = 0;
1941
1942 std::scoped_lock<std::mutex> guard(_pinData.at(pinIdx).mutex);
1943
1944 // NodeData
1945 addData(pinIdx, i++, static_cast<double>((insTime - _startTime).count()));
1946 addData(pinIdx, i++, static_cast<double>(insTime.toUnixTime() + insTime.differenceToUTC(GPST)));
1947 addData(pinIdx, i++, static_cast<double>(insTime.toGPSweekTow().tow));
1948 // Double
1949 addData(pinIdx, i++, *value->v);
1950 }
1951}
1952
1953void NAV::Plot::plotMatrix(const InsTime& insTime, size_t pinIdx)
1954{
1955 LOG_DATA("{}: Plotting matrix on pin '{}' with time {}", nameId(), inputPins[pinIdx].name, insTime.toYMDHMS());
1956 if (ConfigManager::Get<bool>("nogui"))
1957 {
1958 releaseInputValue(pinIdx);
1959 return;
1960 }
1961
1962 if (auto* sourcePin = inputPins.at(pinIdx).link.getConnectedPin())
1963 {
1964 if (sourcePin->dataIdentifier.front() == "Eigen::MatrixXd")
1965 {
1966 if (auto value = getInputValue<Eigen::MatrixXd>(pinIdx);
1967 value && !insTime.empty())
1968 {
1969 if (_startTime.empty()) { _startTime = insTime; }
1970 size_t i = 0;
1971
1972 std::scoped_lock<std::mutex> guard(_pinData.at(pinIdx).mutex);
1973
1974 // NodeData
1975 addData(pinIdx, i++, static_cast<double>((insTime - _startTime).count()));
1976 addData(pinIdx, i++, static_cast<double>(insTime.toUnixTime() + insTime.differenceToUTC(GPST)));
1977 addData(pinIdx, i++, static_cast<double>(insTime.toGPSweekTow().tow));
1978 // Matrix
1979 for (int row = 0; row < value->v->rows(); row++)
1980 {
1981 for (int col = 0; col < value->v->cols(); col++)
1982 {
1983 addData(pinIdx, i++, (*value->v)(row, col));
1984 }
1985 }
1986 }
1987 }
1988 else if (sourcePin->dataIdentifier.front() == "Eigen::VectorXd")
1989 {
1990 if (auto value = getInputValue<Eigen::VectorXd>(pinIdx);
1991 value && !insTime.empty())
1992 {
1993 if (_startTime.empty()) { _startTime = insTime; }
1994 size_t i = 0;
1995
1996 std::scoped_lock<std::mutex> guard(_pinData.at(pinIdx).mutex);
1997
1998 // NodeData
1999 addData(pinIdx, i++, static_cast<double>((insTime - _startTime).count()));
2000 addData(pinIdx, i++, static_cast<double>(insTime.toUnixTime() + insTime.differenceToUTC(GPST)));
2001 addData(pinIdx, i++, static_cast<double>(insTime.toGPSweekTow().tow));
2002 // Vector
2003 for (int row = 0; row < value->v->rows(); row++)
2004 {
2005 addData(pinIdx, i++, (*value->v)(row));
2006 }
2007 }
2008 }
2009 else
2010 {
2011 releaseInputValue(pinIdx);
2012 }
2013 }
2014}
2015
2017{
2018 auto nodeData = queue.extract_front();
2019
2020 if (ConfigManager::Get<bool>("nogui")) { return; }
2021
2022 LOG_DATA("{}: Plotting data on pin '{}' with time {} GPST", nameId(), inputPins[pinIdx].name, nodeData->insTime.toYMDHMS(GPST));
2023
2024 std::scoped_lock<std::mutex> guard(_pinData.at(pinIdx).mutex);
2025 _pinData.at(pinIdx).rawNodeData.push_back(nodeData);
2026
2027 // NodeData
2028 size_t i = 0;
2029 addData(pinIdx, i++, CommonLog::calcTimeIntoRun(nodeData->insTime));
2030 addData(pinIdx, i++, static_cast<double>(nodeData->insTime.toUnixTime() + nodeData->insTime.differenceToUTC(GPST)));
2031 addData(pinIdx, i++, static_cast<double>(nodeData->insTime.toGPSweekTow(GPST).tow));
2032
2033 if (auto* sourcePin = inputPins.at(pinIdx).link.getConnectedPin())
2034 {
2035 LOG_DATA("{}: Connected Pin data identifier: [{}]", nameId(), joinToString(sourcePin->dataIdentifier));
2036 // -------------------------------------------- General ----------------------------------------------
2037 if (sourcePin->dataIdentifier.front() == DynamicData::type())
2038 {
2039 plotData(std::static_pointer_cast<const DynamicData>(nodeData), pinIdx, i);
2040 }
2041 // --------------------------------------------- GNSS ------------------------------------------------
2042 else if (NAV::NodeRegistry::NodeDataTypeAnyIsChildOf(sourcePin->dataIdentifier, { GnssCombination::type() }))
2043 {
2044 plotData(std::static_pointer_cast<const GnssCombination>(nodeData), pinIdx, i);
2045 }
2046 else if (NAV::NodeRegistry::NodeDataTypeAnyIsChildOf(sourcePin->dataIdentifier, { GnssObs::type() }))
2047 {
2048 plotData(std::static_pointer_cast<const GnssObs>(nodeData), pinIdx, i);
2049 }
2050 // ---------------------------------------------- IMU ------------------------------------------------
2051 else if (NAV::NodeRegistry::NodeDataTypeAnyIsChildOf(sourcePin->dataIdentifier, { ImuObsSimulated::type() }))
2052 {
2053 plotData(std::static_pointer_cast<const ImuObsSimulated>(nodeData), pinIdx, i);
2054 }
2055 else if (NAV::NodeRegistry::NodeDataTypeAnyIsChildOf(sourcePin->dataIdentifier, { ImuObsWDelta::type() }))
2056 {
2057 plotData(std::static_pointer_cast<const ImuObsWDelta>(nodeData), pinIdx, i);
2058 }
2059 else if (NAV::NodeRegistry::NodeDataTypeAnyIsChildOf(sourcePin->dataIdentifier, { KvhObs::type() }))
2060 {
2061 plotData(std::static_pointer_cast<const KvhObs>(nodeData), pinIdx, i);
2062 }
2063 else if (NAV::NodeRegistry::NodeDataTypeAnyIsChildOf(sourcePin->dataIdentifier, { ImuObs::type() }))
2064 {
2065 plotData(std::static_pointer_cast<const ImuObs>(nodeData), pinIdx, i);
2066 }
2067 else if (NAV::NodeRegistry::NodeDataTypeAnyIsChildOf(sourcePin->dataIdentifier, { VectorNavBinaryOutput::type() }))
2068 {
2069 plotData(std::static_pointer_cast<const VectorNavBinaryOutput>(nodeData), pinIdx, i);
2070 }
2071 // ---------------------------------------------- BARO -----------------------------------------------
2072 else if (NAV::NodeRegistry::NodeDataTypeAnyIsChildOf(sourcePin->dataIdentifier, { BaroPressObs::type() }))
2073 {
2074 plotData(std::static_pointer_cast<const BaroPressObs>(nodeData), pinIdx, i);
2075 }
2076 else if (NAV::NodeRegistry::NodeDataTypeAnyIsChildOf(sourcePin->dataIdentifier, { BaroHgt::type() }))
2077 {
2078 plotData(std::static_pointer_cast<const BaroHgt>(nodeData), pinIdx, i);
2079 }
2080 // --------------------------------------------- State -----------------------------------------------
2081 else if (NAV::NodeRegistry::NodeDataTypeAnyIsChildOf(sourcePin->dataIdentifier, { Pos::type() }))
2082 {
2083 auto obs = std::static_pointer_cast<const Pos>(nodeData);
2084 auto localPosition = calcLocalPosition(obs->lla_position());
2085
2086 for (size_t j = 0; j < Pos::GetStaticDescriptorCount(); ++j)
2087 {
2088 if (j == 3) { addData(pinIdx, i++, localPosition.northSouth); }
2089 else if (j == 4) { addData(pinIdx, i++, localPosition.eastWest); }
2090 else { addData(pinIdx, i++, obs->getValueAtOrNaN(j)); }
2091 }
2092
2093 if (NAV::NodeRegistry::NodeDataTypeAnyIsChildOf(sourcePin->dataIdentifier, { InsGnssTCKFSolution::type() }))
2094 {
2095 plotData(std::static_pointer_cast<const InsGnssTCKFSolution>(nodeData), pinIdx, i, Pos::GetStaticDescriptorCount());
2096 }
2097 else if (NAV::NodeRegistry::NodeDataTypeAnyIsChildOf(sourcePin->dataIdentifier, { InsGnssLCKFSolution::type() }))
2098 {
2099 plotData(std::static_pointer_cast<const InsGnssLCKFSolution>(nodeData), pinIdx, i, Pos::GetStaticDescriptorCount());
2100 }
2101 else if (NAV::NodeRegistry::NodeDataTypeAnyIsChildOf(sourcePin->dataIdentifier, { PosVelAtt::type() }))
2102 {
2103 plotData(std::static_pointer_cast<const PosVelAtt>(nodeData), pinIdx, i, Pos::GetStaticDescriptorCount());
2104 }
2105 else if (NAV::NodeRegistry::NodeDataTypeAnyIsChildOf(sourcePin->dataIdentifier, { SppSolution::type() }))
2106 {
2107 plotData(std::static_pointer_cast<const SppSolution>(nodeData), pinIdx, i, Pos::GetStaticDescriptorCount());
2108 }
2109 else if (NAV::NodeRegistry::NodeDataTypeAnyIsChildOf(sourcePin->dataIdentifier, { RtklibPosObs::type() }))
2110 {
2111 plotData(std::static_pointer_cast<const RtklibPosObs>(nodeData), pinIdx, i, Pos::GetStaticDescriptorCount());
2112 }
2113 else if (NAV::NodeRegistry::NodeDataTypeAnyIsChildOf(sourcePin->dataIdentifier, { PosVel::type() }))
2114 {
2115 plotData(std::static_pointer_cast<const PosVel>(nodeData), pinIdx, i, Pos::GetStaticDescriptorCount());
2116 }
2117 }
2118
2119 for (const auto& [displayName, value] : nodeData->getDynamicData())
2120 {
2121 addData(pinIdx, displayName, value);
2122 }
2123
2124 for (const auto& event : nodeData->events())
2125 {
2126 addEvent(pinIdx, nodeData->insTime, event, -1);
2127 }
2128 }
2129
2130 size_t s = 0;
2131 for (auto& plotData : _pinData.at(pinIdx).plotData)
2132 {
2133 if (s == 0) { s = plotData.buffer.size(); }
2134 if (s != plotData.buffer.size())
2135 {
2136 plotData.buffer.push_back(std::nan(""));
2137 LOG_DATA("{}: [{}] Adding NaN to pin '{} (idx {})' data buffer for '{}' to make all buffers same size", nameId(), nodeData->insTime.toYMDHMS(GPST),
2138 inputPins.at(pinIdx).name, pinIdx, plotData.displayName);
2139 }
2140 INS_ASSERT_USER_ERROR(s == plotData.buffer.size(), "All buffers should have the same size");
2141 }
2142}
Assertion helpers.
#define INS_ASSERT_USER_ERROR(_EXP, _MSG)
Assert function with message.
Definition Assert.h:21
Config management for the Project.
Transformation collection.
Functions concerning the ellipsoid model.
Save/Load the Nodes.
nlohmann::json json
json namespace
Text Help Marker (?) with Tooltip.
ImPlot style editor window.
ImPlot utilities.
Data storage class for one VectorNavImu observation.
Defines how to save certain datatypes to json.
Utility class for logging to console and file.
#define LOG_DEBUG
Debug information. Should not be called on functions which receive observations (spamming)
Definition Logger.hpp:67
#define LOG_DATA
All output which occurs repeatedly every time observations are received.
Definition Logger.hpp:29
#define LOG_ERROR
Error occurred, which stops part of the program to work, but not everything.
Definition Logger.hpp:73
#define LOG_WARN
Error occurred, but a fallback option exists and program continues to work normally.
Definition Logger.hpp:71
#define LOG_INFO
Info to the user on the state of the program.
Definition Logger.hpp:69
#define LOG_TRACE
Detailled info to trace the execution of the program. Should not be called on functions which receive...
Definition Logger.hpp:65
Manages all Nodes.
Utility class which specifies available nodes.
Plot Tooltips on hover.
Plots data into ImPlot Windows.
Algorithms concerning the STL containers.
Screenshot utility.
A buffer which is overwriting itself from the start when full.
Screen Divider.
Utility functions for working with std::strings.
Keeps track of the current real/simulation time.
Vector Utility functions.
static bool ShowOriginInput(const char *id)
Shows a GUI to input a origin location.
void initialize() const
Initialize the common log variables.
Definition CommonLog.cpp:51
static double calcTimeIntoRun(const InsTime &insTime)
Calculates the relative time into he run.
Definition CommonLog.cpp:70
static InsTime _startTime
Start Time for calculation of relative time.
Definition CommonLog.hpp:80
static std::string type()
Returns the type of the data class.
Input pins of nodes.
Definition Pin.hpp:491
TsDeque< std::shared_ptr< const NAV::NodeData > > NodeDataQueue
Node data queue type.
Definition Pin.hpp:707
void(Node::*)(NodeDataQueue &, size_t) FlowFirableCallbackFunc
Definition Pin.hpp:712
void(Node::*)(const InsTime &, size_t) DataChangedNotifyFunc
Definition Pin.hpp:716
The class is responsible for all time-related tasks.
Definition InsTime.hpp:710
constexpr int differenceToUTC(TimeSystem timesys) const
Returns the time difference in [s] of a time system and UTC.
Definition InsTime.hpp:1118
constexpr InsTime_GPSweekTow toGPSweekTow(TimeSystem timesys=GPST) const
Converts this time object into a different format.
Definition InsTime.hpp:854
constexpr InsTime_YMDHMS toYMDHMS(TimeSystem timesys=UTC, int digits=-1) const
Converts this time object into a different format.
Definition InsTime.hpp:871
constexpr bool empty() const
Checks if the Time object has a value.
Definition InsTime.hpp:1089
constexpr long double toUnixTime() const
Converts this time object into a UNIX timestamp in [s].
Definition InsTime.hpp:932
void releaseInputValue(size_t portIndex)
Unblocks the connected node. Has to be called when the input value should be released and getInputVal...
Definition Node.cpp:148
ImVec2 _guiConfigDefaultWindowSize
Definition Node.hpp:410
Node(std::string name)
Constructor.
Definition Node.cpp:30
std::optional< InputPin::IncomingLink::ValueWrapper< T > > getInputValue(size_t portIndex) const
Get Input Value connected on the pin. Only const data types.
Definition Node.hpp:290
std::vector< InputPin > inputPins
List of input pins.
Definition Node.hpp:397
std::string nameId() const
Node name and id.
Definition Node.cpp:253
bool _lockConfigDuringRun
Lock the config when executing post-processing.
Definition Node.hpp:416
size_t inputPinIndexFromId(ax::NodeEditor::PinId pinId) const
Returns the index of the pin.
Definition Node.cpp:233
std::string name
Name of the Node.
Definition Node.hpp:395
bool _hasConfig
Flag if the config window should be shown.
Definition Node.hpp:413
Output pins of nodes.
Definition Pin.hpp:338
std::string name
Name of the Pin.
Definition Pin.hpp:299
ax::NodeEditor::PinId id
Unique Id of the Pin.
Definition Pin.hpp:297
std::vector< std::string > dataIdentifier
One or multiple Data Identifiers (Unique name which is used for data flows)
Definition Pin.hpp:305
static std::string typeStatic()
String representation of the Class Type.
Definition Plot.cpp:336
void plotInteger(const InsTime &insTime, size_t pinIdx)
Plots the data on this port.
Definition Plot.cpp:1901
static void pinDeleteCallback(Node *node, size_t pinIdx)
Function to call to delete a pin.
Definition Plot.cpp:1768
gui::widgets::DynamicInputPins _dynamicInputPins
Dynamic input pins.
Definition Plot.hpp:376
void restore(const json &j) override
Restores the node from a json object.
Definition Plot.cpp:1484
void plotData(const std::shared_ptr< const T > &obs, size_t pinIndex, size_t &plotIndex, size_t startIndex=0)
Plot the data.
Definition Plot.hpp:434
void plotFlowData(InputPin::NodeDataQueue &queue, size_t pinIdx)
Plot the data on this port.
Definition Plot.cpp:2016
void plotFloat(const InsTime &insTime, size_t pinIdx)
Plots the data on this port.
Definition Plot.cpp:1927
std::string type() const override
String representation of the Class Type.
Definition Plot.cpp:341
bool _overridePositionStartValues
Flag, whether to override the North/East startValues in the GUI.
Definition Plot.hpp:372
CommonLog::LocalPosition calcLocalPosition(const Eigen::Vector3d &lla_position)
Calculate the local position offset from the plot origin.
Definition Plot.cpp:1852
std::vector< PinData > _pinData
Data storage for each pin.
Definition Plot.hpp:323
std::optional< gui::widgets::PositionWithFrame > _originPosition
Start position for the calculation of relative North-South and East-West.
Definition Plot.hpp:369
void deinitialize() override
Deinitialize the node.
Definition Plot.cpp:1599
void plotMatrix(const InsTime &insTime, size_t pinIdx)
Plots the data on this port.
Definition Plot.cpp:1953
Plot()
Default constructor.
Definition Plot.cpp:295
json save() const override
Saves the node into a json object.
Definition Plot.cpp:1465
static std::string category()
String representation of the Class Category.
Definition Plot.cpp:346
static void pinAddCallback(Node *node)
Function to call to add a new pin.
Definition Plot.cpp:1756
void addEvent(size_t pinIndex, InsTime insTime, const std::string &text, int32_t dataIndex)
Adds a event to a certain point in time.
Definition Plot.cpp:1800
void addData(size_t pinIndex, size_t dataIndex, double value)
Add Data to the buffer of the pin.
Definition Plot.cpp:1809
void plotBoolean(const InsTime &insTime, size_t pinIdx)
Plots the data on this port.
Definition Plot.cpp:1875
~Plot() override
Destructor.
Definition Plot.cpp:331
void updateNumberOfPlots()
Adds/Deletes Plots depending on the variable nPlots.
Definition Plot.cpp:1744
void afterCreateLink(OutputPin &startPin, InputPin &endPin) override
Called when a new link was established.
Definition Plot.cpp:1604
std::vector< PlotInfo > _plots
Info for each plot window.
Definition Plot.hpp:326
void guiConfig() override
ImGui config window which is shown on double click.
Definition Plot.cpp:351
size_t _nPlots
Amount of plot windows (should equal _plots.size())
Definition Plot.hpp:329
std::vector< std::string > _dataIdentifier
Possible data identifiers to connect.
Definition Plot.hpp:331
bool initialize() override
Initialize the node.
Definition Plot.cpp:1557
static constexpr size_t GetStaticDescriptorCount()
Get the amount of descriptors.
Definition Pos.hpp:80
A buffer which is overwriting itself from the start when full.
auto extract_front()
Returns a copy of the first element in the container and removes it from the container.
Definition TsDeque.hpp:494
static float windowFontRatio()
Ratio to multiply for GUI window elements.
static ImTextureID m_SaveButtonImage
Pointer to the texture for the save button.
ImGui extensions.
const T & Get(const std::string &key, const T &&defaultValue)
Retrieves the value of a corresponding key from the configuration, if one exists.
bool DeleteInputPin(InputPin &pin)
Deletes the input pin. Invalidates the pin reference given.
InputPin * CreateInputPin(Node *node, const char *name, Pin::Type pinType, const std::vector< std::string > &dataIdentifier={}, InputPin::Callback callback=static_cast< InputPin::FlowFirableCallbackFunc >(nullptr), InputPin::FlowFirableCheckFunc firable=nullptr, int priority=0, int idx=-1)
Create an Input Pin object.
bool NodeDataTypeAnyIsChildOf(const std::vector< std::string > &childTypes, const std::vector< std::string > &parentTypes)
Checks if any of the provided child types is a child of any of the provided parent types.
bool TypeHasDynamicData(const std::string &type)
Wether the specified Node Data Type can have dynamic data.
std::vector< std::string > GetStaticDataDescriptors(const std::vector< std::string > &dataIdentifier)
Returns a vector of data descriptors for the pin data identifiers.
std::filesystem::path GetOutputPath()
Get the path where logs and outputs are stored.
void ApplyChanges()
Signals that there have been changes to the flow.
bool Splitter(const char *str_id, bool split_vertically, float thickness, float *size1, float *size2, float min_size1, float min_size2, float splitter_long_axis_size=-1.0F)
Vertical or horizontal Screen Divider.
Definition Splitter.cpp:16
void from_json(const json &j, DynamicInputPins &obj, Node *node)
Converts the provided json object into a node object.
bool PositionInput(const char *str, PositionWithFrame &position, PositionInputLayout layout=PositionInputLayout::SINGLE_COLUMN, float itemWidth=140.0F)
Inputs to edit an Position object.
@ SINGLE_ROW
All elements in a single row.
Eigen::Vector3< typename Derived::Scalar > lla2ecef_WGS84(const Eigen::MatrixBase< Derived > &lla_position)
Converts latitude, longitude and altitude into Earth-centered-Earth-fixed coordinates using WGS84.
std::string joinToString(const T &container, const char *delimiter=", ", const std::string &elementFormat="")
Joins the container to a string.
Definition STL.hpp:30
@ GPST
GPS Time.
void to_json(json &j, const Node &node)
Converts the provided node into a json object.
Definition Node.cpp:990
@ None
Do not use a colormap mask.
Definition Colormap.hpp:119
Scalar calcGeographicalDistance(Scalar lat1, Scalar lon1, Scalar lat2, Scalar lon2)
Measure the distance between two points over an ellipsoidal-surface.
const char * tooltip(vendor::RINEX::ObsHeader::MarkerTypes markerType)
Converts the enum to a string tooltip.
void move(std::vector< T > &v, size_t sourceIdx, size_t targetIdx)
Moves an element within a vector to a new position.
Definition Vector.hpp:27
void from_json(const json &j, Node &node)
Converts the provided json object into a node object.
Definition Node.cpp:1007
void ShowPlotTooltipWindows(std::vector< PlotTooltip > &tooltips, size_t plotItemIdx, const std::string &plotName, const std::string &uid, const std::vector< int * > &parentWindows, const std::function< InsTime(size_t)> &getInsTime, const std::function< void(size_t, const char *)> &showTooltipCallback)
Shows all tooltip windows in the vector.
std::optional< std::reference_wrapper< const Colormap > > ColormapSearch(const ColormapMaskType &type, const int64_t &id)
Searches for the colormap in the Global and Flow colormaps.
Definition Colormap.cpp:301
void loadImPlotStyleFromConfigFile(const char *path, ImPlotStyle &imPlotStyle)
Loads the ImPlotStyle from a json file.
Definition ImPlot.cpp:28
bool ShowPlotTooltip(std::vector< PlotTooltip > &tooltips, size_t plotItemIdx, const std::string &plotName, ImAxis axis, const ScrollingBuffer< double > &xData, const ScrollingBuffer< double > &yData, bool otherHoverTooltipsShown, const std::function< void(size_t)> &showTooltipCallback)
Shows a tooltip if the plot is hovered.
Local position offset from a reference point.
Definition CommonLog.hpp:54
long double tow
Contains GPS time of week in seconds in GPS standard time [GPST].
Definition InsTime.hpp:372
@ Matrix
Matrix Object.
Definition Pin.hpp:58
@ Int
Integer Number.
Definition Pin.hpp:54
@ Float
Floating Point Number.
Definition Pin.hpp:55
@ Flow
NodeData Trigger.
Definition Pin.hpp:52
@ Bool
Boolean.
Definition Pin.hpp:53
Tooltip for plot events.
Stores the actual data coming from a pin.
Definition Plot.hpp:103
PlotData()=default
Default constructor (needed to make serialization with json working)
ScrollingBuffer< double > buffer
Buffer for the data.
Definition Plot.hpp:115
std::string displayName
Display name of the contained data.
Definition Plot.hpp:113
Information needed to plot the data on a certain pin.
Definition Plot.hpp:100
@ Int
Integer Number.
Definition Plot.hpp:130
@ Float
Floating Point Number.
Definition Plot.hpp:131
@ Flow
NodeData Trigger.
Definition Plot.hpp:128
@ Matrix
Matrix Object.
Definition Plot.hpp:132
PinData()=default
Constructor.
int size
Size of all buffers of the plotData elements.
Definition Plot.hpp:160
ScrollingBuffer< std::shared_ptr< const NodeData > > rawNodeData
List with the raw data received.
Definition Plot.hpp:166
int stride
Amount of points to skip for plotting.
Definition Plot.hpp:170
PinData & operator=(const PinData &rhs)
Copy assignment operator.
Definition Plot.cpp:222
PinType pinType
Pin Type.
Definition Plot.hpp:168
std::vector< PlotData > plotData
List with all the data.
Definition Plot.hpp:164
std::string dataIdentifier
Data Identifier of the connected pin.
Definition Plot.hpp:162
void addPlotDataItem(size_t dataIndex, const std::string &displayName)
Adds a plotData Element to the list.
Definition Plot.cpp:252
Info needed to draw a data set.
Definition Plot.hpp:184
std::string displayName
Display name of the data (not changing and unique)
Definition Plot.hpp:220
size_t pinIndex
Index of the pin where the data came in.
Definition Plot.hpp:218
size_t dataIndex
Index of the data on the pin.
Definition Plot.hpp:219
ImAxis axis
Axis to plot the data on (Y1, Y2, Y3)
Definition Plot.hpp:221
Information specifying the look of each plot.
Definition Plot.hpp:181
ImVec2 size
Size of the plot.
Definition Plot.hpp:252
std::string y2AxisLabel
Y2 axis label.
Definition Plot.hpp:265
std::string headerText
Title of the CollapsingHeader.
Definition Plot.hpp:257
std::array< ImPlotScale, 3 > yAxesScale
Scale for the y-Axes.
Definition Plot.hpp:279
ImPlotLineFlags lineFlags
Line Flags for all items (each item can override the selection)
Definition Plot.hpp:281
ImPlotAxisFlags xAxisFlags
Flags for the x-Axis.
Definition Plot.hpp:273
float leftPaneWidth
Width of plot Data content.
Definition Plot.hpp:290
int plotFlags
Flags which are passed to the plot.
Definition Plot.hpp:271
bool overrideXAxisLabel
Flag, whether to override the x axis label.
Definition Plot.hpp:259
std::string xAxisLabel
X axis label.
Definition Plot.hpp:261
size_t selectedPin
Selected pin in the GUI for the Drag & Drop Data.
Definition Plot.hpp:269
ImPlotScale xAxisScale
Scale for the x-Axis.
Definition Plot.hpp:277
float rightPaneWidth
Width of the plot.
Definition Plot.hpp:292
std::vector< PlotItem > plotItems
List containing all elements which should be plotted.
Definition Plot.hpp:287
std::vector< size_t > selectedXdata
Key: PinIndex, Value: plotData to use for x-Axis.
Definition Plot.hpp:284
std::string title
Title of the ImPlot.
Definition Plot.hpp:255
std::string y1AxisLabel
Y1 axis label.
Definition Plot.hpp:263
ImPlotAxisFlags yAxisFlags
Flags for the y-Axes.
Definition Plot.hpp:275
std::string y3AxisLabel
Y3 axis label.
Definition Plot.hpp:267
Position with Reference frame, used for GUI input.