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