353 ImGui::SetNextItemOpen(
false, ImGuiCond_FirstUseEver);
354 if (ImGui::CollapsingHeader(fmt::format(
"Options##{}",
size_t(
id)).c_str()))
356 auto columnContentPinType = [&](
size_t pinIndex) ->
bool {
357 auto& pinData =
_pinData.at(pinIndex);
359 if (
auto pinType =
static_cast<int>(pinData.pinType);
360 ImGui::Combo(fmt::format(
"##Pin Type for Pin {} - {}", pinIndex + 1,
size_t(
id)).c_str(),
361 &pinType,
"Flow\0Bool\0Int\0Float\0Matrix\0\0"))
363 pinData.pinType =
static_cast<decltype(pinData.pinType)
>(pinType);
364 if (
inputPins.at(pinIndex).isPinLinked())
366 inputPins.at(pinIndex).deleteLink();
369 switch (pinData.pinType)
371 case PinData::PinType::Flow:
372 inputPins.at(pinIndex).type = Pin::Type::Flow;
373 inputPins.at(pinIndex).dataIdentifier = _dataIdentifier;
374 inputPins.at(pinIndex).callback = static_cast<InputPin::FlowFirableCallbackFunc>(&Plot::plotFlowData);
376 case PinData::PinType::Bool:
377 inputPins.at(pinIndex).type = Pin::Type::Bool;
378 inputPins.at(pinIndex).dataIdentifier.clear();
379 inputPins.at(pinIndex).callback = static_cast<InputPin::DataChangedNotifyFunc>(&Plot::plotBoolean);
381 case PinData::PinType::Int:
382 inputPins.at(pinIndex).type = Pin::Type::Int;
383 inputPins.at(pinIndex).dataIdentifier.clear();
384 inputPins.at(pinIndex).callback = static_cast<InputPin::DataChangedNotifyFunc>(&Plot::plotInteger);
386 case PinData::PinType::Float:
387 inputPins.at(pinIndex).type = Pin::Type::Float;
388 inputPins.at(pinIndex).dataIdentifier.clear();
389 inputPins.at(pinIndex).callback = static_cast<InputPin::DataChangedNotifyFunc>(&Plot::plotFloat);
391 case PinData::PinType::Matrix:
392 inputPins.at(pinIndex).type = Pin::Type::Matrix;
393 inputPins.at(pinIndex).dataIdentifier = {
"Eigen::MatrixXd",
"Eigen::VectorXd" };
402 auto columnContentDataPoints = [&](
size_t pinIndex) ->
bool {
403 bool changed =
false;
404 auto& pinData =
_pinData.at(pinIndex);
406 if (ImGui::DragInt(fmt::format(
"##Data Points {} - {}",
size_t(
id), pinIndex + 1).c_str(),
407 &pinData.size, 10.0F, 0, INT32_MAX / 2))
409 pinData.size = std::max(pinData.size, 0);
410 std::scoped_lock<std::mutex> guard(pinData.mutex);
411 for (
auto&
plotData : pinData.plotData)
414 plotData.buffer.resize(
static_cast<size_t>(pinData.size));
417 if (ImGui::IsItemHovered())
419 ImGui::SetTooltip(
"The amount of data which should be stored before the buffer gets reused.\nEnter 0 to show all data.");
423 auto columnContentStride = [&](
size_t pinIndex) ->
bool {
424 bool changed =
false;
425 auto& pinData =
_pinData.at(pinIndex);
427 if (ImGui::InputInt(fmt::format(
"##Stride {} - {}",
size_t(
id), pinIndex + 1).c_str(),
430 pinData.stride = std::max(pinData.stride, 1);
433 if (ImGui::IsItemHovered())
435 ImGui::SetTooltip(
"The amount of points to skip when plotting. This greatly reduces lag when plotting");
441 { { .header =
"Pin Type", .content = columnContentPinType },
442 { .header =
"# Data Points", .content = columnContentDataPoints },
443 { .header =
"Stride", .content = columnContentStride } }))
448 if (ImGui::Checkbox(fmt::format(
"Override local position origin (North/East)##{}",
size_t(
id)).c_str(), &_overridePositionStartValues))
451 LOG_DEBUG(
"{}: overridePositionStartValues changed to {}", nameId(), _overridePositionStartValues);
452 if (!_originPosition) { _originPosition = gui::widgets::PositionWithFrame(); }
454 if (_overridePositionStartValues)
463 if (!_overridePositionStartValues)
471 ImGui::BulletText(
"Tipp: Ctrl + Hover = Tooltip (+ LClick = Tooltip window)");
474 bool dragAndDropHeaderStillInProgress =
false;
476 auto showDragDropTargetHeader = [
this](
size_t plotIdxTarget) {
477 ImGui::Dummy(ImVec2(-1.F, 2.F));
479 bool selectableSelectedDummy =
true;
480 ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5F, 0.5F));
481 ImGui::PushStyleColor(ImGuiCol_Header, IM_COL32(16, 173, 44, 79));
482 ImGui::Selectable(fmt::format(
"[drop here]").c_str(), &selectableSelectedDummy, ImGuiSelectableFlags_None, ImVec2(ImGui::GetWindowContentRegionWidth(), 20.F));
483 ImGui::PopStyleColor();
484 ImGui::PopStyleVar();
486 if (ImGui::BeginDragDropTarget())
488 if (
const ImGuiPayload* payloadData = ImGui::AcceptDragDropPayload(fmt::format(
"DND ColHead {}",
size_t(
id)).c_str()))
490 auto plotIdxSource = *
static_cast<size_t*
>(payloadData->Data);
492 if (plotIdxSource < plotIdxTarget)
497 move(_plots, plotIdxSource, plotIdxTarget);
500 ImGui::EndDragDropTarget();
502 ImGui::Dummy(ImVec2(-1.F, 2.F));
505 if (_dragAndDropHeaderIndex > 0)
507 showDragDropTargetHeader(0);
510 for (
size_t plotIdx = 0; plotIdx < _plots.size(); plotIdx++)
512 auto& plot = _plots.at(plotIdx);
514 size_t plotElementIdx = 0;
518 LOG_DEBUG(
"{}: # Plot '{}' at index {} was deleted", nameId(), plot.headerText, plotIdx);
519 _plots.erase(_plots.begin() +
static_cast<int64_t
>(plotIdx));
525 ImGui::SetNextItemOpen(
true, ImGuiCond_Once);
526 if (ImGui::CollapsingHeader(fmt::format(
"{}##Plot Header {} - {}", plot.headerText,
size_t(
id), plotIdx).c_str(), &plot.visible))
528 if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None))
530 dragAndDropHeaderStillInProgress =
true;
531 _dragAndDropHeaderIndex =
static_cast<int>(plotIdx);
533 ImGui::SetDragDropPayload(fmt::format(
"DND ColHead {}",
size_t(
id)).c_str(),
534 &plotIdx,
sizeof(plotIdx));
535 ImGui::Dummy(ImVec2(ImGui::CalcTextSize(plot.headerText.c_str()).x + 60.F, -1.F));
536 bool dnd_display_close =
true;
537 ImGui::CollapsingHeader(fmt::format(
"{}##Plot DND Header {} - {}", plot.headerText,
size_t(
id), plotIdx).c_str(), &dnd_display_close);
538 ImGui::EndDragDropSource();
541 bool saveForceXaxisRange =
false;
542 ImGui::SetNextItemOpen(
false, ImGuiCond_FirstUseEver);
543 auto optionsCursorPos = ImGui::GetCursorPos();
544 if (ImGui::TreeNode(fmt::format(
"Options##{} - {}",
size_t(
id), plotIdx).c_str()))
546 std::string headerTitle = plot.headerText;
547 ImGui::InputText(fmt::format(
"Header Title##{} - {}",
size_t(
id), plotIdx).c_str(), &headerTitle);
548 if (plot.headerText != headerTitle && !ImGui::IsItemActive())
550 plot.headerText = headerTitle;
552 LOG_DEBUG(
"{}: Header changed to {}", nameId(), plot.headerText);
554 if (ImGui::InputText(fmt::format(
"Plot Title##{} - {}",
size_t(
id), plotIdx).c_str(), &plot.title))
557 LOG_DEBUG(
"{}: Plot Title changed to {}", nameId(), plot.title);
559 bool plotWidthAutomatic = plot.size.x == -1.0;
560 float checkBoxStartX = ImGui::GetCursorPosX();
561 if (ImGui::Checkbox(fmt::format(
"{}Automatic Plot Width##{} - {}", plotWidthAutomatic ?
"" :
"##",
size_t(
id), plotIdx).c_str(), &plotWidthAutomatic))
563 if (plotWidthAutomatic) { plot.size.x = -1.0; }
564 else { plot.size.x = ImGui::GetWindowContentRegionWidth() - plot.leftPaneWidth - ImGui::GetStyle().ItemSpacing.x; }
567 if (!plotWidthAutomatic)
570 ImGui::SetNextItemWidth(ImGui::CalcItemWidth() - (ImGui::GetCursorPosX() - checkBoxStartX));
571 if (ImGui::SliderFloat(fmt::format(
"Plot Width##{} - {}",
size_t(
id), plotIdx).c_str(), &plot.size.x, 1.0F, 2000,
"%.0f"))
576 if (ImGui::SliderFloat(fmt::format(
"Plot Height##{} - {}",
size_t(
id), plotIdx).c_str(), &plot.size.y, 0.0F, 1000,
"%.0f"))
580 if (ImGui::Checkbox(fmt::format(
"Override X Axis Label##{} - {}",
size_t(
id), plotIdx).c_str(), &plot.overrideXAxisLabel))
584 if (plot.overrideXAxisLabel)
586 if (ImGui::InputText(fmt::format(
"X Axis Label##{} - {}",
size_t(
id), plotIdx).c_str(), &plot.xAxisLabel))
591 if (ImGui::InputText(fmt::format(
"Y1 Axis Label##{} - {}",
size_t(
id), plotIdx).c_str(), &plot.y1AxisLabel))
595 if (plot.plotFlags & ImPlotFlags_YAxis2)
597 if (ImGui::InputText(fmt::format(
"Y2 Axis Label##{} - {}",
size_t(
id), plotIdx).c_str(), &plot.y2AxisLabel))
602 if (plot.plotFlags & ImPlotFlags_YAxis3)
604 if (ImGui::InputText(fmt::format(
"Y3 Axis Label##{} - {}",
size_t(
id), plotIdx).c_str(), &plot.y3AxisLabel))
609 if (ImGui::BeginTable(fmt::format(
"Pin Settings##{} - {}",
size_t(
id), plotIdx).c_str(), 2,
610 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
612 ImGui::TableSetupColumn(
"Pin");
613 ImGui::TableSetupColumn(
"X Data");
614 ImGui::TableHeadersRow();
616 for (
size_t pinIndex = 0; pinIndex < _pinData.size(); pinIndex++)
618 auto& pinData = _pinData.at(pinIndex);
620 ImGui::TableNextRow();
621 ImGui::TableNextColumn();
622 ImGui::Text(
"%zu - %s", pinIndex + 1, pinData.dataIdentifier.c_str());
624 ImGui::TableNextColumn();
625 if (!pinData.plotData.empty())
628 if (ImGui::BeginCombo(fmt::format(
"##X Data for Pin {} - {} - {}", pinIndex + 1,
size_t(
id), plotIdx).c_str(),
629 pinData.plotData.at(plot.selectedXdata.at(pinIndex)).displayName.c_str()))
631 for (
size_t plotDataIndex = 0; plotDataIndex < pinData.plotData.size(); plotDataIndex++)
633 auto& plotData = pinData.plotData.at(plotDataIndex);
635 if (!plotData.hasData)
637 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5F);
639 const bool is_selected = (plot.selectedXdata.at(pinIndex) == plotDataIndex);
640 if (ImGui::Selectable(pinData.plotData.at(plotDataIndex).displayName.c_str(), is_selected))
643 plot.selectedXdata.at(pinIndex) = plotDataIndex;
644 if (plotDataIndex == GPST_PLOT_IDX)
647 for (
auto& selectedX : plot.selectedXdata)
649 selectedX = plotDataIndex;
655 for (
auto& selectedX : plot.selectedXdata)
657 if (selectedX == GPST_PLOT_IDX) { selectedX = 0; }
661 for (
auto& plotItem : plot.plotItems)
663 plotItem.eventMarker.clear();
664 plotItem.eventTooltips.clear();
667 if (!plotData.hasData)
669 ImGui::PopStyleVar();
675 ImGui::SetItemDefaultFocus();
686 if (ImGui::CheckboxFlags(fmt::format(
"Y-Axis 2##{} - {}",
size_t(
id), plotIdx).c_str(),
687 &plot.plotFlags, ImPlotFlags_YAxis2))
692 if (ImGui::CheckboxFlags(fmt::format(
"Y-Axis 3##{} - {}",
size_t(
id), plotIdx).c_str(),
693 &plot.plotFlags, ImPlotFlags_YAxis3))
699 if (ImGui::CheckboxFlags(fmt::format(
"Auto Limit X-Axis##{} - {}",
size_t(
id), plotIdx).c_str(),
700 &plot.xAxisFlags, ImPlotAxisFlags_AutoFit))
705 if (ImGui::CheckboxFlags(fmt::format(
"Auto Limit Y-Axis##{} - {}",
size_t(
id), plotIdx).c_str(),
706 &plot.yAxisFlags, ImPlotAxisFlags_AutoFit))
711 if (ImGui::Button(fmt::format(
"Same X range all plots##{} - {}",
size_t(
id), plotIdx).c_str()))
713 saveForceXaxisRange =
true;
714 _forceXaxisRange.first.clear();
715 size_t pinIdx = plot.plotItems.empty() ? 0 : plot.plotItems.front().pinIndex;
716 const auto& xName = _pinData.at(pinIdx).plotData.at(plot.selectedXdata.at(pinIdx)).displayName;
717 for (
size_t p = 0; p < _plots.size(); p++)
719 if (p == plotIdx) {
continue; }
720 auto& plot = _plots.at(p);
721 size_t pinIdx = plot.plotItems.empty() ? 0 : plot.plotItems.front().pinIndex;
722 const auto& dispName = _pinData.at(pinIdx).plotData.at(plot.selectedXdata.at(pinIdx)).displayName;
723 if (xName == dispName) { _forceXaxisRange.first.insert(p); }
727 auto axisScaleCombo = [&](
const char* label, ImPlotScale& axisScale) {
728 auto getImPlotScaleString = [](ImPlotScale scale) {
731 case ImPlotScale_Linear:
735 case ImPlotScale_Log10:
737 case ImPlotScale_SymLog:
745 ImGui::SetNextItemWidth(100.0F);
746 if (ImGui::BeginCombo(fmt::format(
"{}-Axis Scale##{} - {}", label,
size_t(
id), plotIdx).c_str(), getImPlotScaleString(axisScale)))
748 for (
size_t n = 0; n < 4; ++n)
750 if (n == ImPlotScale_Time) {
continue; }
751 const bool is_selected = (
static_cast<size_t>(axisScale) == n);
752 if (ImGui::Selectable(getImPlotScaleString(
static_cast<ImPlotScale
>(n)), is_selected))
754 axisScale =
static_cast<ImPlotScale
>(n);
757 if (is_selected) { ImGui::SetItemDefaultFocus(); }
762 axisScaleCombo(
"X", plot.xAxisScale);
764 axisScaleCombo(
"Y1", plot.yAxesScale[0]);
765 if (plot.plotFlags & ImPlotFlags_YAxis2)
768 axisScaleCombo(
"Y2", plot.yAxesScale[1]);
770 if (plot.plotFlags & ImPlotFlags_YAxis3)
773 axisScaleCombo(
"Y3", plot.yAxesScale[2]);
777 if (ImGui::CheckboxFlags(fmt::format(
"NoClip##LineFlags {} - {}",
size_t(
id), plotIdx).c_str(), &plot.lineFlags, ImPlotLineFlags_NoClip))
781 if (ImGui::IsItemHovered()) { ImGui::SetTooltip(
"Markers (if displayed) on the edge of a plot will not be clipped"); }
783 if (ImGui::CheckboxFlags(fmt::format(
"SkipNaN##LineFlags {} - {}",
size_t(
id), plotIdx).c_str(), &plot.lineFlags, ImPlotLineFlags_SkipNaN))
787 if (ImGui::IsItemHovered()) { ImGui::SetTooltip(
"NaNs values will be skipped instead of rendered as missing data"); }
789 if (ImGui::CheckboxFlags(fmt::format(
"Loop##LineFlags {} - {}",
size_t(
id), plotIdx).c_str(), &plot.lineFlags, ImPlotLineFlags_Loop))
793 if (ImGui::IsItemHovered()) { ImGui::SetTooltip(
"The last and first point will be connected to form a closed loop"); }
798#ifdef IMGUI_IMPL_OPENGL_LOADER_GL3W
800 auto afterOptionsCursorPos = ImGui::GetCursorPos();
803 ImGui::SetCursorPos(ImVec2(ImGui::GetWindowContentRegionWidth() - buttonSize - ImGui::GetStyle().ItemInnerSpacing.x, optionsCursorPos.y));
804 ImGui::PushID(fmt::format(
"{}{}",
size_t(
id), plotIdx).c_str());
807 if (_screenshotFrameCnt == 0)
809 _screenShotPlotIdx = plotIdx;
810 _screenshotFrameCnt = 1;
814 ImGui::SetCursorPos(afterOptionsCursorPos);
818 auto plotSize = plot.size;
822 true, 4.0F, &plot.leftPaneWidth, &plot.rightPaneWidth, 3.0F, 80.0F, plotSize.y);
824 ImGui::SetNextItemWidth(plot.leftPaneWidth - 2.0F);
828 if (ImGui::BeginCombo(fmt::format(
"##Data source pin selection{} - {}",
size_t(
id), plotIdx).c_str(),
829 inputPins.at(plot.selectedPin).name.c_str()))
831 for (
size_t n = 0; n < inputPins.size(); ++n)
833 const bool is_selected = (plot.selectedPin == n);
834 if (ImGui::Selectable(inputPins.at(n).name.c_str(), is_selected, 0))
836 plot.selectedPin = n;
842 ImGui::SetItemDefaultFocus();
847 auto comboBoxSize = ImGui::GetItemRectSize();
848 if (ImGui::Button(fmt::format(
"Clear##{} - {}",
size_t(
id), plotIdx).c_str(), ImVec2(plot.leftPaneWidth - 2.0F, 0)))
850 plot.plotItems.clear();
853 if (ImGui::BeginDragDropTarget())
855 if (
const ImGuiPayload* payloadData = ImGui::AcceptDragDropPayload(fmt::format(
"DND PlotItem {} - {}",
size_t(
id), plotIdx).c_str()))
857 auto [pinIndex, dataIndex, displayName] = *
static_cast<std::tuple<size_t, size_t, std::string*>*
>(payloadData->Data);
859 auto iter = std::ranges::find(plot.plotItems, PlotInfo::PlotItem{ pinIndex, dataIndex, *displayName });
860 if (iter != plot.plotItems.end())
862 plot.plotItems.erase(iter);
866 ImGui::EndDragDropTarget();
868 auto buttonSize = ImGui::GetItemRectSize();
869 ImGui::BeginChild(fmt::format(
"Data Drag{} - {}",
size_t(
id), plotIdx).c_str(),
870 ImVec2(plot.leftPaneWidth - 2.0F, plotSize.y - comboBoxSize.y - buttonSize.y - 2 * ImGui::GetStyle().ItemSpacing.y),
874 for (
size_t dataIndex = 0; dataIndex < _pinData.at(plot.selectedPin).plotData.size(); ++dataIndex)
876 auto& plotData = _pinData.at(plot.selectedPin).plotData.at(dataIndex);
877 auto plotDataHasData = plotData.hasData;
878 if (!plotDataHasData)
880 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5F);
882 std::string label = plotData.displayName;
884 if (
auto iter = std::ranges::find(plot.plotItems, PlotInfo::PlotItem{ plot.selectedPin, dataIndex, plotData.displayName });
885 iter != plot.plotItems.end())
887 label += fmt::format(
" (Y{})", iter->axis + 1 - 3);
892 ImGui::Selectable(label.c_str(),
false, 0);
893 if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None))
896 auto pinAndDataIndex = std::make_tuple(plot.selectedPin, dataIndex, &plotData.displayName);
897 ImGui::SetDragDropPayload(fmt::format(
"DND PlotItem {} - {}",
size_t(
id), plotIdx).c_str(),
898 &pinAndDataIndex,
sizeof(pinAndDataIndex));
899 ImGui::TextUnformatted(label.c_str());
900 ImGui::EndDragDropSource();
904 if (!plotDataHasData)
906 ImGui::PopStyleVar();
917 const char* xLabel = plot.overrideXAxisLabel ? (!plot.xAxisLabel.empty() ? plot.xAxisLabel.c_str() :
nullptr)
918 : (!_pinData.at(0).plotData.empty() ? _pinData.at(0).plotData.at(plot.selectedXdata.at(0)).displayName.c_str() :
nullptr);
920 const char* y1Label = !plot.y1AxisLabel.empty() ? plot.y1AxisLabel.c_str() :
nullptr;
921 const char* y2Label = (plot.plotFlags & ImPlotFlags_YAxis2) && !plot.y2AxisLabel.empty() ? plot.y2AxisLabel.c_str() :
nullptr;
922 const char* y3Label = (plot.plotFlags & ImPlotFlags_YAxis3) && !plot.y3AxisLabel.empty() ? plot.y3AxisLabel.c_str() :
nullptr;
924 bool timeScaleXaxis =
false;
925 std::array<double, 2> timeAxisMinMax = { std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity() };
926 for (
auto& plotItem : plot.plotItems)
928 if (plot.selectedXdata.at(plotItem.pinIndex) == GPST_PLOT_IDX)
930 auto& pinData = _pinData.at(plotItem.pinIndex);
931 const auto& plotDataX = pinData.plotData.at(plot.selectedXdata.at(plotItem.pinIndex));
934 std::scoped_lock<std::mutex> guard(pinData.mutex);
935 if (!plotDataX.buffer.empty())
937 timeScaleXaxis =
true;
938 timeAxisMinMax[0] = std::min(timeAxisMinMax[0], plotDataX.buffer.front());
939 timeAxisMinMax[1] = std::max(timeAxisMinMax[1], plotDataX.buffer.back());
944 if (ImPlot::BeginPlot(fmt::format(
"{}##{} - {}", plot.title,
size_t(
id), plotIdx).c_str(), plotSize, plot.plotFlags))
946#ifdef IMGUI_IMPL_OPENGL_LOADER_GL3W
947 if (_screenShotPlotIdx == plotIdx)
950 static ImVec4 windowBgColor = ImGui::GetStyle().Colors[ImGuiCol_WindowBg];
951 static json imPlotStyle = ImPlot::GetStyle();
952 if (_screenshotFrameCnt == 1)
954 windowBgColor = ImGui::GetStyle().Colors[ImGuiCol_WindowBg];
955 imPlotStyle = ImPlot::GetStyle();
957 if (!ImPlot::IsColorAuto(ImPlot::GetStyle().Colors[ImPlotCol_FrameBg])
958 && ImPlot::GetStyle().Colors[ImPlotCol_FrameBg].w == 1.0F)
960 ImGui::GetStyle().Colors[ImGuiCol_WindowBg] = ImPlot::GetStyle().Colors[ImPlotCol_FrameBg];
962 ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w = 1.0;
963 _screenshotFrameCnt++;
965 else if (_screenshotFrameCnt == 4)
967 ImRect CaptureRect = ImPlot::GetCurrentPlot()->FrameRect;
968 ImGuiIO& io = ImGui::GetIO();
969 ImGuiScreenshotImageBuf Output(
static_cast<int>(CaptureRect.Min.x),
970 static_cast<int>(io.DisplaySize.y) -
static_cast<int>(CaptureRect.Max.y),
971 static_cast<size_t>(CaptureRect.GetWidth()),
972 static_cast<size_t>(CaptureRect.GetHeight()));
973 auto savePath =
flow::GetOutputPath() / fmt::format(
"Plot-{}_{}.png",
size_t(
id), plotIdx);
974 Output.SaveFile(savePath.c_str());
975 LOG_INFO(
"{}: Plot image saved as: {}", nameId(), savePath);
976 if (gui::windows::copyScreenshotsToClipboard)
978 gui::windows::CopyFileToClipboard(savePath.c_str());
981 ImGui::GetStyle().Colors[ImGuiCol_WindowBg] = windowBgColor;
982 imPlotStyle.get_to(ImPlot::GetStyle());
983 _screenshotFrameCnt = 0;
985 else if (_screenshotFrameCnt != 0) { _screenshotFrameCnt++; }
989 if (saveForceXaxisRange)
991 std::get<ImPlotRange>(_forceXaxisRange) = ImPlot::GetCurrentPlot()->XAxis(ImAxis_X1).Range;
993 else if (_forceXaxisRange.first.contains(plotIdx))
995 if (plot.xAxisFlags & ImPlotAxisFlags_AutoFit)
997 plot.xAxisFlags &= ~ImPlotAxisFlags_AutoFit;
1000 ImPlot::GetCurrentPlot()->XAxis(ImAxis_X1).SetRange(std::get<ImPlotRange>(_forceXaxisRange));
1001 _forceXaxisRange.first.erase(plotIdx);
1004 ImPlot::SetupAxis(ImAxis_X1, xLabel, plot.xAxisFlags);
1005 ImPlot::SetupAxisScale(ImAxis_X1, timeScaleXaxis ? ImPlotScale_Time : plot.xAxisScale);
1006 ImPlot::SetupAxis(ImAxis_Y1, y1Label, plot.yAxisFlags);
1007 ImPlot::SetupAxisScale(ImAxis_Y1, plot.yAxesScale[0]);
1008 if (plot.plotFlags & ImPlotFlags_YAxis2)
1010 ImPlot::SetupAxis(ImAxis_Y2, y2Label, plot.yAxisFlags | ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_Opposite);
1011 ImPlot::SetupAxisScale(ImAxis_Y2, plot.yAxesScale[1]);
1013 if (plot.plotFlags & ImPlotFlags_YAxis3)
1015 ImPlot::SetupAxis(ImAxis_Y3, y3Label, plot.yAxisFlags | ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_Opposite);
1016 ImPlot::SetupAxisScale(ImAxis_Y3, plot.yAxesScale[2]);
1019 std::vector<PlotInfo::PlotItem> plotItemsToRemove;
1020 bool hoverTooltipShown =
false;
1021 for (
size_t plotItemIdx = 0; plotItemIdx < plot.plotItems.size(); plotItemIdx++)
1023 auto& plotItem = plot.plotItems.at(plotItemIdx);
1024 auto& pinData = _pinData.at(plotItem.pinIndex);
1027 std::scoped_lock<std::mutex> guard(pinData.mutex);
1030 if (pinData.plotData.size() <= plotItem.dataIndex) {
continue; }
1031 auto& plotData = pinData.plotData.at(plotItem.dataIndex);
1032 const auto& plotDataX = pinData.plotData.at(plot.selectedXdata.at(plotItem.pinIndex));
1033 if (plotData.displayName != plotItem.displayName)
1035 if (plotItem.displayName.empty())
1037 plotItem.displayName = plotData.displayName;
1041 plotItemsToRemove.push_back(plotItem);
1046 if (plotData.hasData
1047 && (plotItem.axis == ImAxis_Y1
1048 || (plotItem.axis == ImAxis_Y2 && (plot.plotFlags & ImPlotFlags_YAxis2))
1049 || (plotItem.axis == ImAxis_Y3 && (plot.plotFlags & ImPlotFlags_YAxis3))))
1051 ImPlot::SetAxis(plotItem.axis);
1055 if (
const auto& cmap =
ColormapSearch(plotItem.style.colormapMask.first, plotItem.style.colormapMask.second))
1057 if (plotItem.colormapMaskVersion != cmap->get().version) { plotItem.colormapMaskColors.clear(); }
1058 if (plotItem.colormapMaskColors.size() != plotData.buffer.size()
1059 && plotItem.style.colormapMaskDataCmpIdx < pinData.plotData.size())
1061 plotItem.colormapMaskVersion = cmap->get().version;
1062 const auto& cmpData = pinData.plotData.at(plotItem.style.colormapMaskDataCmpIdx);
1065 ? (ImPlot::IsColorAuto(plotItem.style.color) ? ImPlot::GetColormapColor(
static_cast<int>(plotElementIdx)) : plotItem.style.color)
1066 : (ImPlot::IsColorAuto(plotItem.style.markerFillColor) ? ImPlot::GetColormapColor(static_cast<int>(plotElementIdx)) : plotItem.style.markerFillColor);
1067 plotItem.colormapMaskColors.reserve(plotData.buffer.size());
1068 for (
size_t i = plotItem.colormapMaskColors.size(); i < plotData.buffer.size(); i++)
1070 plotItem.colormapMaskColors.push_back(i >= cmpData.buffer.size() ? ImColor(color) : cmap->get().getColor(cmpData.buffer.at(i), color));
1077 plotItem.style.colormapMask.second = -1;
1078 plotItem.colormapMaskColors.clear();
1083 if (
const auto& cmap =
ColormapSearch(plotItem.style.markerColormapMask.first, plotItem.style.markerColormapMask.second))
1085 if (plotItem.markerColormapMaskVersion != cmap->get().version) { plotItem.markerColormapMaskColors.clear(); }
1086 if (plotItem.markerColormapMaskColors.size() != plotData.buffer.size()
1087 && plotItem.style.markerColormapMaskDataCmpIdx < pinData.plotData.size())
1089 plotItem.markerColormapMaskVersion = cmap->get().version;
1090 const auto& cmpData = pinData.plotData.at(plotItem.style.markerColormapMaskDataCmpIdx);
1093 ? (ImPlot::IsColorAuto(plotItem.style.color) ? ImPlot::GetColormapColor(
static_cast<int>(plotElementIdx)) : plotItem.style.color)
1094 : (ImPlot::IsColorAuto(plotItem.style.markerFillColor) ? ImPlot::GetColormapColor(static_cast<int>(plotElementIdx)) : plotItem.style.markerFillColor);
1095 plotItem.markerColormapMaskColors.reserve(plotData.buffer.size());
1096 for (
size_t i = plotItem.markerColormapMaskColors.size(); i < plotData.buffer.size(); i++)
1098 plotItem.markerColormapMaskColors.push_back(i >= cmpData.buffer.size() ? ImColor(color) : cmap->get().getColor(cmpData.buffer.at(i), color));
1105 plotItem.style.markerColormapMask.second = -1;
1106 plotItem.markerColormapMaskColors.clear();
1109 if (plotItem.style.errorBoundsEnabled)
1111 if (plotItem.errorBoundsData[0].size() != plotData.buffer.size()
1112 && plotItem.style.errorBoundsDataIdx < pinData.plotData.size())
1114 const auto& errorData = pinData.plotData.at(plotItem.style.errorBoundsDataIdx);
1115 for (
size_t i = plotItem.errorBoundsData[0].size(); i < plotData.buffer.size(); i++)
1117 double errorValue = errorData.buffer.at(i);
1118 if (!plotItem.style.errorBoundsModifierExpression.empty())
1123 double x = errorValue;
1124 p.DefineVar(
"x", &x);
1125 p.SetExpr(plotItem.style.errorBoundsModifierExpression);
1126 errorValue = p.Eval();
1128 catch (mu::Parser::exception_type& e)
1130 LOG_ERROR(
"{}: Error bound modifier parse error on '{}': {}", nameId(), plotItem.style.legendName, e.GetMsg());
1133 plotItem.errorBoundsData[0].push_back(plotData.buffer.at(i) - errorValue);
1134 plotItem.errorBoundsData[1].push_back(plotData.buffer.at(i) + errorValue);
1138 if (plotItem.style.eventsEnabled)
1140 if (plotItem.eventMarker.size() != plotData.buffer.size())
1142 auto& plotDataRelTime = pinData.plotData.at(0);
1143 for (
size_t i = plotItem.eventMarker.size(); i < plotData.buffer.size(); i++)
1145 double relTime = plotDataRelTime.buffer.at(i);
1149 std::regex filter(plotItem.style.eventTooltipFilterRegex,
1150 std::regex_constants::ECMAScript | std::regex_constants::icase);
1151 for (
const auto& e : pinData.events)
1153 if (std::abs(std::get<0>(e) - relTime) <= 1e-6)
1155 tooltip.time = std::get<1>(e);
1156 if ((std::get<3>(e) == -1 ||
static_cast<size_t>(std::get<3>(e)) == plotItem.dataIndex)
1157 && (plotItem.style.eventTooltipFilterRegex.empty() || std::regex_search(std::get<2>(e), filter)))
1159 tooltip.texts.push_back(std::get<2>(e));
1169 plotItem.eventMarker.push_back(plotData.buffer.at(i));
1170 plotItem.eventTooltips.emplace_back(plotDataX.buffer.at(i), plotData.buffer.at(i),
tooltip);
1174 plotItem.eventMarker.push_back(std::nan(
""));
1180 if (plotItem.style.legendName.empty())
1182 plotItem.style.legendName = fmt::format(
"{} ({})", plotData.displayName, inputPins.at(plotItem.pinIndex).name);
1184 std::string plotName = fmt::format(
"{}##{} - {} - {}", plotItem.style.legendName,
size_t(
id), plotItem.pinIndex + 1, plotData.displayName);
1186 plotItem.style.plotData(plotName.c_str(),
1189 static_cast<int>(plotElementIdx),
1192 &plotItem.colormapMaskColors,
1193 &plotItem.markerColormapMaskColors,
1194 &plotItem.errorBoundsData);
1197 ImGuiWindow* plotWindow = ImGui::GetCurrentWindow();
1202 plotItem.style.legendName,
1203 fmt::format(
"{} {} {}",
size_t(
id), plotItem.pinIndex, plotItem.displayName),
1204 { reinterpret_cast<int*>(plotWindow) },
1205 [&](
size_t dataIdx) { return pinData.rawNodeData.at(dataIdx)->insTime; },
1206 [&](
size_t dataIdx,
const char* tooltipUID) {
1207 const auto& nodeData = pinData.rawNodeData.at(dataIdx);
1208 auto nEvents = nodeData->events().size();
1211 ImGui::SetNextItemOpen(false, ImGuiCond_Once);
1212 if (ImGui::TreeNode(fmt::format(
"Events: {}", nEvents).c_str()))
1214 for (const auto& text : nodeData->events())
1216 ImGui::BulletText(
"%s", text.c_str());
1221 else { ImGui::BulletText(
"Events: 0"); }
1222 if (nodeData->hasTooltip())
1224 nodeData->guiTooltip(true, false, plotItem.displayName.c_str(),
1225 tooltipUID, reinterpret_cast<int*>(plotWindow));
1230 plot.tooltips, plotItemIdx,
1231 plotName, plotItem.axis,
1232 plotDataX.buffer, plotData.buffer,
1234 [&](
size_t dataIdx) {
1235 const auto& nodeData = pinData.rawNodeData.at(dataIdx);
1236 ImGui::TextUnformatted(fmt::format(
"{} - {}", plotItem.style.legendName,
1237 nodeData->insTime.toYMDHMS(GPST))
1241 auto nEvents = nodeData->events().size();
1244 ImGui::SetNextItemOpen(false, ImGuiCond_Always);
1245 if (ImGui::TreeNode(fmt::format(
"Events: {}", nEvents).c_str())) { ImGui::TreePop(); }
1247 else { ImGui::BulletText(
"Events: 0"); }
1249 if (pinData.rawNodeData.at(dataIdx)->hasTooltip())
1251 auto tooltipUID = fmt::format(
"{} {} {} {}", size_t(id), plotItem.pinIndex, plotItem.displayName, dataIdx);
1252 pinData.rawNodeData.at(dataIdx)->guiTooltip(ImGui::IsKeyDown(ImGuiKey_ModShift), true,
1253 plotItem.displayName.c_str(), tooltipUID.c_str(),
1254 reinterpret_cast<int*>(plotWindow));
1260 if (plotItem.style.eventsEnabled)
1262 if (
const auto* item = ImPlot::GetCurrentPlot()->Items.GetItem(plotName.c_str());
1265 auto stride = plotItem.style.stride ? plotItem.style.stride
1267 auto dataPointCount =
static_cast<int>(std::ceil(
static_cast<double>(plotData.buffer.size())
1268 /
static_cast<double>(stride)));
1270 ImPlot::SetNextMarkerStyle(plotItem.style.eventMarkerStyle,
1271 plotItem.style.eventMarkerSize,
1272 ImPlot::IsColorAuto(plotItem.style.eventMarkerFillColor) ? ImPlot::GetColormapColor(
static_cast<int>(plotElementIdx)) : plotItem.style.eventMarkerFillColor,
1273 plotItem.style.eventMarkerWeight,
1274 ImPlot::IsColorAuto(plotItem.style.eventMarkerOutlineColor) ? ImPlot::GetColormapColor(
static_cast<int>(plotElementIdx)) : plotItem.style.eventMarkerOutlineColor);
1275 ImPlot::PlotScatter(fmt::format(
"##{} - Events", plotName).c_str(),
1276 plotDataX.buffer.data(),
1277 plotItem.eventMarker.data(),
1279 ImPlotScatterFlags_None,
1280 static_cast<int>(std::ceil(
static_cast<double>(plotItem.eventMarker.offset()) /
static_cast<double>(stride))),
1281 stride *
static_cast<int>(
sizeof(
double)));
1283 if (ImPlot::IsPlotHovered())
1285 constexpr double HOVER_PIXEL_SIZE = 5.0;
1286 auto limits = ImPlot::GetPlotLimits(IMPLOT_AUTO, plotItem.axis);
1288 ImVec2 scaling = ImVec2(
static_cast<float>(HOVER_PIXEL_SIZE * (limits.X.Max - limits.X.Min) / ImPlot::GetCurrentPlot()->PlotRect.GetWidth()),
1289 static_cast<float>(HOVER_PIXEL_SIZE * (limits.Y.Max - limits.Y.Min) / ImPlot::GetCurrentPlot()->PlotRect.GetHeight()));
1290 ImPlotPoint mouse = ImPlot::GetPlotMousePos();
1292 std::vector<PlotEventTooltip> tooltips;
1293 for (
const auto& e : plotItem.eventTooltips)
1295 if (std::abs(mouse.x - std::get<0>(e)) < scaling.x
1296 && std::abs(mouse.y - std::get<1>(e)) < scaling.y)
1298 tooltips.push_back(std::get<2>(e));
1301 if (!tooltips.empty())
1303 ImGui::BeginTooltip();
1304 ImGui::PushFont(Application::MonoFont());
1305 for (
size_t i = 0; i < tooltips.size(); i++)
1307 ImGui::SetNextItemOpen(
true, ImGuiCond_Always);
1308 if (ImGui::TreeNode(fmt::format(
"{} GPST", tooltips.at(i).time.toYMDHMS(
GPST)).c_str()))
1310 for (
const auto& text : tooltips.at(i).texts)
1312 ImGui::BulletText(
"%s", text.c_str());
1316 if (i != tooltips.size() - 1) { ImGui::Separator(); }
1319 ImGui::EndTooltip();
1326 if (ImPlot::BeginDragDropSourceItem(plotName.c_str()))
1329 auto pinAndDataIndex = std::make_tuple(plotItem.pinIndex, plotItem.dataIndex, &plotItem.displayName);
1330 ImGui::SetDragDropPayload(fmt::format(
"DND PlotItem {} - {}",
size_t(
id), plotIdx).c_str(), &pinAndDataIndex,
sizeof(pinAndDataIndex));
1331 ImGui::TextUnformatted(plotData.displayName.c_str());
1332 ImPlot::EndDragDropSource();
1335 auto ShowDataReferenceChooser = [&](
size_t& dataIdx,
const char* label =
"") ->
bool {
1336 bool changed =
false;
1337 const char* preview = dataIdx < pinData.plotData.size()
1338 ? pinData.plotData.at(dataIdx).displayName.c_str()
1340 if (ImGui::BeginCombo(label, preview))
1342 for (
size_t plotDataIndex = 0; plotDataIndex < pinData.plotData.size(); plotDataIndex++)
1344 auto& plotData = pinData.plotData.at(plotDataIndex);
1346 if (!plotData.hasData) { ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5F); }
1347 const bool is_selected = (dataIdx == plotDataIndex);
1348 if (ImGui::Selectable(pinData.plotData.at(plotDataIndex).displayName.c_str(), is_selected))
1351 dataIdx = plotDataIndex;
1353 if (!plotData.hasData) { ImGui::PopStyleVar(); }
1356 if (is_selected) { ImGui::SetItemDefaultFocus(); }
1363 if (
auto legendReturn = plotItem.style.showLegendPopup(
1365 fmt::format(
"Pin {} - {}: {}", plotItem.pinIndex + 1,
1366 pinData.dataIdentifier, plotData.displayName)
1368 static_cast<int>(plotData.buffer.size()),
1369 static_cast<int>(plotElementIdx),
1372 &plotItem.colormapMaskColors,
1373 &plotItem.markerColormapMaskColors,
1374 ShowDataReferenceChooser,
1375 &plotItem.eventMarker,
1376 &plotItem.eventTooltips);
1377 legendReturn.changed)
1381 if (legendReturn.errorBoundsReCalcNeeded)
1383 for (
auto& data : plotItem.errorBoundsData) { data.clear(); }
1391 for (
const auto& plotItem : plotItemsToRemove)
1393 LOG_WARN(
"{}: Erasing plot item '{}' from plot '{}', because it does not match the order the data came in",
1394 nameId(), plotItem.displayName, plot.headerText);
1395 std::erase(plot.plotItems, plotItem);
1399 auto addDragDropPlotToAxis = [
this, plotIdx, &plot](ImAxis dragDropAxis) {
1400 if (
const ImGuiPayload* payloadData = ImGui::AcceptDragDropPayload(fmt::format(
"DND PlotItem {} - {}",
size_t(
id), plotIdx).c_str()))
1402 auto [pinIndex, dataIndex, displayName] = *
static_cast<std::tuple<size_t, size_t, std::string*>*
>(payloadData->Data);
1404 auto iter = std::ranges::find(plot.plotItems, PlotInfo::PlotItem{ pinIndex, dataIndex, *displayName });
1405 if (iter != plot.plotItems.end())
1407 iter->axis = dragDropAxis;
1411 plot.plotItems.emplace_back(pinIndex, dataIndex, *displayName, dragDropAxis);
1418 if (ImPlot::BeginDragDropTargetPlot())
1420 addDragDropPlotToAxis(ImAxis_Y1);
1421 ImPlot::EndDragDropTarget();
1424 for (ImAxis y = ImAxis_Y1; y <= ImAxis_Y3; ++y)
1426 if ((y == ImAxis_Y2 && !(plot.plotFlags & ImPlotFlags_YAxis2))
1427 || (y == ImAxis_Y3 && !(plot.plotFlags & ImPlotFlags_YAxis3)))
1431 if (ImPlot::BeginDragDropTargetAxis(y))
1433 addDragDropPlotToAxis(y);
1434 ImPlot::EndDragDropTarget();
1442 if (_dragAndDropHeaderIndex >= 0
1443 && plotIdx !=
static_cast<size_t>(_dragAndDropHeaderIndex - 1)
1444 && plotIdx !=
static_cast<size_t>(_dragAndDropHeaderIndex))
1446 showDragDropTargetHeader(plotIdx + 1);
1450 if (!dragAndDropHeaderStillInProgress)
1452 _dragAndDropHeaderIndex = -1;
1456 if (ImGui::Button(fmt::format(
"Add Plot##{}",
size_t(
id)).c_str()))
1459 LOG_DEBUG(
"{}: # Plots changed to {}", nameId(), _nPlots);
1461 updateNumberOfPlots();