351 ImGui::SetNextItemOpen(
false, ImGuiCond_FirstUseEver);
352 if (ImGui::CollapsingHeader(fmt::format(
"Options##{}",
size_t(
id)).c_str()))
354 auto columnContentPinType = [&](
size_t pinIndex) ->
bool {
355 auto& pinData =
_pinData.at(pinIndex);
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"))
361 pinData.pinType =
static_cast<decltype(pinData.pinType)
>(pinType);
362 if (
inputPins.at(pinIndex).isPinLinked())
364 inputPins.at(pinIndex).deleteLink();
367 switch (pinData.pinType)
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);
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);
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);
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);
389 case PinData::PinType::Matrix:
390 inputPins.at(pinIndex).type = Pin::Type::Matrix;
391 inputPins.at(pinIndex).dataIdentifier = {
"Eigen::MatrixXd",
"Eigen::VectorXd" };
400 auto columnContentDataPoints = [&](
size_t pinIndex) ->
bool {
401 bool changed =
false;
402 auto& pinData =
_pinData.at(pinIndex);
404 if (ImGui::DragInt(fmt::format(
"##Data Points {} - {}",
size_t(
id), pinIndex + 1).c_str(),
405 &pinData.size, 10.0F, 0, INT32_MAX / 2))
407 pinData.size = std::max(pinData.size, 0);
408 std::scoped_lock<std::mutex> guard(pinData.mutex);
409 for (
auto&
plotData : pinData.plotData)
412 plotData.buffer.resize(
static_cast<size_t>(pinData.size));
415 if (ImGui::IsItemHovered())
417 ImGui::SetTooltip(
"The amount of data which should be stored before the buffer gets reused.\nEnter 0 to show all data.");
421 auto columnContentStride = [&](
size_t pinIndex) ->
bool {
422 bool changed =
false;
423 auto& pinData =
_pinData.at(pinIndex);
425 if (ImGui::InputInt(fmt::format(
"##Stride {} - {}",
size_t(
id), pinIndex + 1).c_str(),
428 pinData.stride = std::max(pinData.stride, 1);
431 if (ImGui::IsItemHovered())
433 ImGui::SetTooltip(
"The amount of points to skip when plotting. This greatly reduces lag when plotting");
439 { { .header =
"Pin Type", .content = columnContentPinType },
440 { .header =
"# Data Points", .content = columnContentDataPoints },
441 { .header =
"Stride", .content = columnContentStride } }))
446 if (ImGui::Checkbox(fmt::format(
"Override local position origin (North/East)##{}",
size_t(
id)).c_str(), &_overridePositionStartValues))
449 LOG_DEBUG(
"{}: overridePositionStartValues changed to {}", nameId(), _overridePositionStartValues);
450 if (!_originPosition) { _originPosition = gui::widgets::PositionWithFrame(); }
452 if (_overridePositionStartValues)
461 if (!_overridePositionStartValues)
469 ImGui::BulletText(
"Tipp: Ctrl + Hover = Tooltip (+ LClick = Tooltip window)");
472 bool dragAndDropHeaderStillInProgress =
false;
474 auto showDragDropTargetHeader = [
this](
size_t plotIdxTarget) {
475 ImGui::Dummy(ImVec2(-1.F, 2.F));
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();
484 if (ImGui::BeginDragDropTarget())
486 if (
const ImGuiPayload* payloadData = ImGui::AcceptDragDropPayload(fmt::format(
"DND ColHead {}",
size_t(
id)).c_str()))
488 auto plotIdxSource = *
static_cast<size_t*
>(payloadData->Data);
490 if (plotIdxSource < plotIdxTarget)
495 move(_plots, plotIdxSource, plotIdxTarget);
498 ImGui::EndDragDropTarget();
500 ImGui::Dummy(ImVec2(-1.F, 2.F));
503 if (_dragAndDropHeaderIndex > 0)
505 showDragDropTargetHeader(0);
508 for (
size_t plotIdx = 0; plotIdx < _plots.size(); plotIdx++)
510 auto& plot = _plots.at(plotIdx);
512 size_t plotElementIdx = 0;
516 LOG_DEBUG(
"{}: # Plot '{}' at index {} was deleted", nameId(), plot.headerText, plotIdx);
517 _plots.erase(_plots.begin() +
static_cast<int64_t
>(plotIdx));
523 ImGui::SetNextItemOpen(
true, ImGuiCond_Once);
524 if (ImGui::CollapsingHeader(fmt::format(
"{}##Plot Header {} - {}", plot.headerText,
size_t(
id), plotIdx).c_str(), &plot.visible))
526 if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None))
528 dragAndDropHeaderStillInProgress =
true;
529 _dragAndDropHeaderIndex =
static_cast<int>(plotIdx);
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();
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()))
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())
548 plot.headerText = headerTitle;
550 LOG_DEBUG(
"{}: Header changed to {}", nameId(), plot.headerText);
552 if (ImGui::InputText(fmt::format(
"Plot Title##{} - {}",
size_t(
id), plotIdx).c_str(), &plot.title))
555 LOG_DEBUG(
"{}: Plot Title changed to {}", nameId(), plot.title);
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))
561 if (plotWidthAutomatic) { plot.size.x = -1.0; }
562 else { plot.size.x = ImGui::GetWindowContentRegionWidth() - plot.leftPaneWidth - ImGui::GetStyle().ItemSpacing.x; }
565 if (!plotWidthAutomatic)
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"))
574 if (ImGui::SliderFloat(fmt::format(
"Plot Height##{} - {}",
size_t(
id), plotIdx).c_str(), &plot.size.y, 0.0F, 1000,
"%.0f"))
578 if (ImGui::Checkbox(fmt::format(
"Override X Axis Label##{} - {}",
size_t(
id), plotIdx).c_str(), &plot.overrideXAxisLabel))
582 if (plot.overrideXAxisLabel)
584 if (ImGui::InputText(fmt::format(
"X Axis Label##{} - {}",
size_t(
id), plotIdx).c_str(), &plot.xAxisLabel))
589 if (ImGui::InputText(fmt::format(
"Y1 Axis Label##{} - {}",
size_t(
id), plotIdx).c_str(), &plot.y1AxisLabel))
593 if (plot.plotFlags & ImPlotFlags_YAxis2)
595 if (ImGui::InputText(fmt::format(
"Y2 Axis Label##{} - {}",
size_t(
id), plotIdx).c_str(), &plot.y2AxisLabel))
600 if (plot.plotFlags & ImPlotFlags_YAxis3)
602 if (ImGui::InputText(fmt::format(
"Y3 Axis Label##{} - {}",
size_t(
id), plotIdx).c_str(), &plot.y3AxisLabel))
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)))
610 ImGui::TableSetupColumn(
"Pin");
611 ImGui::TableSetupColumn(
"X Data");
612 ImGui::TableHeadersRow();
614 for (
size_t pinIndex = 0; pinIndex < _pinData.size(); pinIndex++)
616 auto& pinData = _pinData.at(pinIndex);
618 ImGui::TableNextRow();
619 ImGui::TableNextColumn();
620 ImGui::Text(
"%zu - %s", pinIndex + 1, pinData.dataIdentifier.c_str());
622 ImGui::TableNextColumn();
623 if (!pinData.plotData.empty())
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()))
629 for (
size_t plotDataIndex = 0; plotDataIndex < pinData.plotData.size(); plotDataIndex++)
631 auto& plotData = pinData.plotData.at(plotDataIndex);
633 if (!plotData.hasData)
635 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5F);
637 const bool is_selected = (plot.selectedXdata.at(pinIndex) == plotDataIndex);
638 if (ImGui::Selectable(pinData.plotData.at(plotDataIndex).displayName.c_str(), is_selected))
641 plot.selectedXdata.at(pinIndex) = plotDataIndex;
642 if (plotDataIndex == GPST_PLOT_IDX)
645 for (
auto& selectedX : plot.selectedXdata)
647 selectedX = plotDataIndex;
653 for (
auto& selectedX : plot.selectedXdata)
655 if (selectedX == GPST_PLOT_IDX) { selectedX = 0; }
659 for (
auto& plotItem : plot.plotItems)
661 plotItem.eventMarker.clear();
662 plotItem.eventTooltips.clear();
665 if (!plotData.hasData)
667 ImGui::PopStyleVar();
673 ImGui::SetItemDefaultFocus();
684 if (ImGui::CheckboxFlags(fmt::format(
"Y-Axis 2##{} - {}",
size_t(
id), plotIdx).c_str(),
685 &plot.plotFlags, ImPlotFlags_YAxis2))
690 if (ImGui::CheckboxFlags(fmt::format(
"Y-Axis 3##{} - {}",
size_t(
id), plotIdx).c_str(),
691 &plot.plotFlags, ImPlotFlags_YAxis3))
697 if (ImGui::CheckboxFlags(fmt::format(
"Auto Limit X-Axis##{} - {}",
size_t(
id), plotIdx).c_str(),
698 &plot.xAxisFlags, ImPlotAxisFlags_AutoFit))
703 if (ImGui::CheckboxFlags(fmt::format(
"Auto Limit Y-Axis##{} - {}",
size_t(
id), plotIdx).c_str(),
704 &plot.yAxisFlags, ImPlotAxisFlags_AutoFit))
709 if (ImGui::Button(fmt::format(
"Same X range all plots##{} - {}",
size_t(
id), plotIdx).c_str()))
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++)
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); }
725 auto axisScaleCombo = [&](
const char* label, ImPlotScale& axisScale) {
726 auto getImPlotScaleString = [](ImPlotScale scale) {
729 case ImPlotScale_Linear:
733 case ImPlotScale_Log10:
735 case ImPlotScale_SymLog:
743 ImGui::SetNextItemWidth(100.0F);
744 if (ImGui::BeginCombo(fmt::format(
"{}-Axis Scale##{} - {}", label,
size_t(
id), plotIdx).c_str(), getImPlotScaleString(axisScale)))
746 for (
size_t n = 0; n < 4; ++n)
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))
752 axisScale =
static_cast<ImPlotScale
>(n);
755 if (is_selected) { ImGui::SetItemDefaultFocus(); }
760 axisScaleCombo(
"X", plot.xAxisScale);
762 axisScaleCombo(
"Y1", plot.yAxesScale[0]);
763 if (plot.plotFlags & ImPlotFlags_YAxis2)
766 axisScaleCombo(
"Y2", plot.yAxesScale[1]);
768 if (plot.plotFlags & ImPlotFlags_YAxis3)
771 axisScaleCombo(
"Y3", plot.yAxesScale[2]);
775 if (ImGui::CheckboxFlags(fmt::format(
"NoClip##LineFlags {} - {}",
size_t(
id), plotIdx).c_str(), &plot.lineFlags, ImPlotLineFlags_NoClip))
779 if (ImGui::IsItemHovered()) { ImGui::SetTooltip(
"Markers (if displayed) on the edge of a plot will not be clipped"); }
781 if (ImGui::CheckboxFlags(fmt::format(
"SkipNaN##LineFlags {} - {}",
size_t(
id), plotIdx).c_str(), &plot.lineFlags, ImPlotLineFlags_SkipNaN))
785 if (ImGui::IsItemHovered()) { ImGui::SetTooltip(
"NaNs values will be skipped instead of rendered as missing data"); }
787 if (ImGui::CheckboxFlags(fmt::format(
"Loop##LineFlags {} - {}",
size_t(
id), plotIdx).c_str(), &plot.lineFlags, ImPlotLineFlags_Loop))
791 if (ImGui::IsItemHovered()) { ImGui::SetTooltip(
"The last and first point will be connected to form a closed loop"); }
796#ifdef IMGUI_IMPL_OPENGL_LOADER_GL3W
798 auto afterOptionsCursorPos = ImGui::GetCursorPos();
801 ImGui::SetCursorPos(ImVec2(ImGui::GetWindowContentRegionWidth() - buttonSize - ImGui::GetStyle().ItemInnerSpacing.x, optionsCursorPos.y));
802 ImGui::PushID(fmt::format(
"{}{}",
size_t(
id), plotIdx).c_str());
805 if (_screenshotFrameCnt == 0)
807 _screenShotPlotIdx = plotIdx;
808 _screenshotFrameCnt = 1;
812 ImGui::SetCursorPos(afterOptionsCursorPos);
816 auto plotSize = plot.size;
820 true, 4.0F, &plot.leftPaneWidth, &plot.rightPaneWidth, 3.0F, 80.0F, plotSize.y);
822 ImGui::SetNextItemWidth(plot.leftPaneWidth - 2.0F);
826 if (ImGui::BeginCombo(fmt::format(
"##Data source pin selection{} - {}",
size_t(
id), plotIdx).c_str(),
827 inputPins.at(plot.selectedPin).name.c_str()))
829 for (
size_t n = 0; n < inputPins.size(); ++n)
831 const bool is_selected = (plot.selectedPin == n);
832 if (ImGui::Selectable(inputPins.at(n).name.c_str(), is_selected, 0))
834 plot.selectedPin = n;
840 ImGui::SetItemDefaultFocus();
845 auto comboBoxSize = ImGui::GetItemRectSize();
846 if (ImGui::Button(fmt::format(
"Clear##{} - {}",
size_t(
id), plotIdx).c_str(), ImVec2(plot.leftPaneWidth - 2.0F, 0)))
848 plot.plotItems.clear();
851 if (ImGui::BeginDragDropTarget())
853 if (
const ImGuiPayload* payloadData = ImGui::AcceptDragDropPayload(fmt::format(
"DND PlotItem {} - {}",
size_t(
id), plotIdx).c_str()))
855 auto [pinIndex, dataIndex, displayName] = *
static_cast<std::tuple<size_t, size_t, std::string*>*
>(payloadData->Data);
857 auto iter = std::ranges::find(plot.plotItems, PlotInfo::PlotItem{ pinIndex, dataIndex, *displayName });
858 if (iter != plot.plotItems.end())
860 plot.plotItems.erase(iter);
864 ImGui::EndDragDropTarget();
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),
872 for (
size_t dataIndex = 0; dataIndex < _pinData.at(plot.selectedPin).plotData.size(); ++dataIndex)
874 auto& plotData = _pinData.at(plot.selectedPin).plotData.at(dataIndex);
875 auto plotDataHasData = plotData.hasData;
876 if (!plotDataHasData)
878 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5F);
880 std::string label = plotData.displayName;
882 if (
auto iter = std::ranges::find(plot.plotItems, PlotInfo::PlotItem{ plot.selectedPin, dataIndex, plotData.displayName });
883 iter != plot.plotItems.end())
885 label += fmt::format(
" (Y{})", iter->axis + 1 - 3);
890 ImGui::Selectable(label.c_str(),
false, 0);
891 if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None))
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();
902 if (!plotDataHasData)
904 ImGui::PopStyleVar();
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);
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;
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)
926 if (plot.selectedXdata.at(plotItem.pinIndex) == GPST_PLOT_IDX)
928 auto& pinData = _pinData.at(plotItem.pinIndex);
929 const auto& plotDataX = pinData.plotData.at(plot.selectedXdata.at(plotItem.pinIndex));
932 std::scoped_lock<std::mutex> guard(pinData.mutex);
933 if (!plotDataX.buffer.empty())
935 timeScaleXaxis =
true;
936 timeAxisMinMax[0] = std::min(timeAxisMinMax[0], plotDataX.buffer.front());
937 timeAxisMinMax[1] = std::max(timeAxisMinMax[1], plotDataX.buffer.back());
942 if (ImPlot::BeginPlot(fmt::format(
"{}##{} - {}", plot.title,
size_t(
id), plotIdx).c_str(), plotSize, plot.plotFlags))
944#ifdef IMGUI_IMPL_OPENGL_LOADER_GL3W
945 if (_screenShotPlotIdx == plotIdx)
948 static ImVec4 windowBgColor = ImGui::GetStyle().Colors[ImGuiCol_WindowBg];
949 static json imPlotStyle = ImPlot::GetStyle();
950 if (_screenshotFrameCnt == 1)
952 windowBgColor = ImGui::GetStyle().Colors[ImGuiCol_WindowBg];
953 imPlotStyle = ImPlot::GetStyle();
955 if (!ImPlot::IsColorAuto(ImPlot::GetStyle().Colors[ImPlotCol_FrameBg])
956 && ImPlot::GetStyle().Colors[ImPlotCol_FrameBg].w == 1.0F)
958 ImGui::GetStyle().Colors[ImGuiCol_WindowBg] = ImPlot::GetStyle().Colors[ImPlotCol_FrameBg];
960 ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w = 1.0;
961 _screenshotFrameCnt++;
963 else if (_screenshotFrameCnt == 4)
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)
976 gui::windows::CopyFileToClipboard(savePath.c_str());
979 ImGui::GetStyle().Colors[ImGuiCol_WindowBg] = windowBgColor;
980 imPlotStyle.get_to(ImPlot::GetStyle());
981 _screenshotFrameCnt = 0;
983 else if (_screenshotFrameCnt != 0) { _screenshotFrameCnt++; }
987 if (saveForceXaxisRange)
989 std::get<ImPlotRange>(_forceXaxisRange) = ImPlot::GetCurrentPlot()->XAxis(ImAxis_X1).Range;
991 else if (_forceXaxisRange.first.contains(plotIdx))
993 if (plot.xAxisFlags & ImPlotAxisFlags_AutoFit)
995 plot.xAxisFlags &= ~ImPlotAxisFlags_AutoFit;
998 ImPlot::GetCurrentPlot()->XAxis(ImAxis_X1).SetRange(std::get<ImPlotRange>(_forceXaxisRange));
999 _forceXaxisRange.first.erase(plotIdx);
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)
1008 ImPlot::SetupAxis(ImAxis_Y2, y2Label, plot.yAxisFlags | ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_Opposite);
1009 ImPlot::SetupAxisScale(ImAxis_Y2, plot.yAxesScale[1]);
1011 if (plot.plotFlags & ImPlotFlags_YAxis3)
1013 ImPlot::SetupAxis(ImAxis_Y3, y3Label, plot.yAxisFlags | ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_Opposite);
1014 ImPlot::SetupAxisScale(ImAxis_Y3, plot.yAxesScale[2]);
1017 std::vector<PlotInfo::PlotItem> plotItemsToRemove;
1018 bool hoverTooltipShown =
false;
1019 for (
size_t plotItemIdx = 0; plotItemIdx < plot.plotItems.size(); plotItemIdx++)
1021 auto& plotItem = plot.plotItems.at(plotItemIdx);
1022 auto& pinData = _pinData.at(plotItem.pinIndex);
1025 std::scoped_lock<std::mutex> guard(pinData.mutex);
1028 if (pinData.plotData.size() <= plotItem.dataIndex) {
continue; }
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)
1033 if (plotItem.displayName.empty())
1035 plotItem.displayName = plotData.displayName;
1039 plotItemsToRemove.push_back(plotItem);
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))))
1049 ImPlot::SetAxis(plotItem.axis);
1053 if (
const auto& cmap =
ColormapSearch(plotItem.style.colormapMask.first, plotItem.style.colormapMask.second))
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())
1059 plotItem.colormapMaskVersion = cmap->get().version;
1060 const auto& cmpData = pinData.plotData.at(plotItem.style.colormapMaskDataCmpIdx);
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++)
1068 plotItem.colormapMaskColors.push_back(i >= cmpData.buffer.size() ? ImColor(color) : cmap->get().getColor(cmpData.buffer.at(i), color));
1075 plotItem.style.colormapMask.second = -1;
1076 plotItem.colormapMaskColors.clear();
1081 if (
const auto& cmap =
ColormapSearch(plotItem.style.markerColormapMask.first, plotItem.style.markerColormapMask.second))
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())
1087 plotItem.markerColormapMaskVersion = cmap->get().version;
1088 const auto& cmpData = pinData.plotData.at(plotItem.style.markerColormapMaskDataCmpIdx);
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++)
1096 plotItem.markerColormapMaskColors.push_back(i >= cmpData.buffer.size() ? ImColor(color) : cmap->get().getColor(cmpData.buffer.at(i), color));
1103 plotItem.style.markerColormapMask.second = -1;
1104 plotItem.markerColormapMaskColors.clear();
1107 if (plotItem.style.errorBoundsEnabled)
1109 if (plotItem.errorBoundsData[0].size() != plotData.buffer.size()
1110 && plotItem.style.errorBoundsDataIdx < pinData.plotData.size())
1112 const auto& errorData = pinData.plotData.at(plotItem.style.errorBoundsDataIdx);
1113 for (
size_t i = plotItem.errorBoundsData[0].size(); i < plotData.buffer.size(); i++)
1115 double errorValue = errorData.buffer.at(i);
1116 if (!plotItem.style.errorBoundsModifierExpression.empty())
1121 double x = errorValue;
1122 p.DefineVar(
"x", &x);
1123 p.SetExpr(plotItem.style.errorBoundsModifierExpression);
1124 errorValue = p.Eval();
1126 catch (mu::Parser::exception_type& e)
1128 LOG_ERROR(
"{}: Error bound modifier parse error on '{}': {}", nameId(), plotItem.style.legendName, e.GetMsg());
1131 plotItem.errorBoundsData[0].push_back(plotData.buffer.at(i) - errorValue);
1132 plotItem.errorBoundsData[1].push_back(plotData.buffer.at(i) + errorValue);
1136 if (plotItem.style.eventsEnabled)
1138 if (plotItem.eventMarker.size() != plotData.buffer.size())
1140 auto& plotDataRelTime = pinData.plotData.at(0);
1141 for (
size_t i = plotItem.eventMarker.size(); i < plotData.buffer.size(); i++)
1143 double relTime = plotDataRelTime.buffer.at(i);
1147 std::regex filter(plotItem.style.eventTooltipFilterRegex,
1148 std::regex_constants::ECMAScript | std::regex_constants::icase);
1149 for (
const auto& e : pinData.events)
1151 if (std::abs(std::get<0>(e) - relTime) <= 1e-6)
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)))
1157 tooltip.texts.push_back(std::get<2>(e));
1167 plotItem.eventMarker.push_back(plotData.buffer.at(i));
1168 plotItem.eventTooltips.emplace_back(plotDataX.buffer.at(i), plotData.buffer.at(i),
tooltip);
1172 plotItem.eventMarker.push_back(std::nan(
""));
1178 if (plotItem.style.legendName.empty())
1180 plotItem.style.legendName = fmt::format(
"{} ({})", plotData.displayName, inputPins.at(plotItem.pinIndex).name);
1182 std::string plotName = fmt::format(
"{}##{} - {} - {}", plotItem.style.legendName,
size_t(
id), plotItem.pinIndex + 1, plotData.displayName);
1184 plotItem.style.plotData(plotName.c_str(),
1187 static_cast<int>(plotElementIdx),
1190 &plotItem.colormapMaskColors,
1191 &plotItem.markerColormapMaskColors,
1192 &plotItem.errorBoundsData);
1195 ImGuiWindow* plotWindow = ImGui::GetCurrentWindow();
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();
1209 ImGui::SetNextItemOpen(false, ImGuiCond_Once);
1210 if (ImGui::TreeNode(fmt::format(
"Events: {}", nEvents).c_str()))
1212 for (const auto& text : nodeData->events())
1214 ImGui::BulletText(
"%s", text.c_str());
1219 else { ImGui::BulletText(
"Events: 0"); }
1220 if (nodeData->hasTooltip())
1222 nodeData->guiTooltip(true, false, plotItem.displayName.c_str(),
1223 tooltipUID, reinterpret_cast<int*>(plotWindow));
1228 plot.tooltips, plotItemIdx,
1229 plotName, plotItem.axis,
1230 plotDataX.buffer, plotData.buffer,
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))
1239 auto nEvents = nodeData->events().size();
1242 ImGui::SetNextItemOpen(false, ImGuiCond_Always);
1243 if (ImGui::TreeNode(fmt::format(
"Events: {}", nEvents).c_str())) { ImGui::TreePop(); }
1245 else { ImGui::BulletText(
"Events: 0"); }
1247 if (pinData.rawNodeData.at(dataIdx)->hasTooltip())
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));
1258 if (plotItem.style.eventsEnabled)
1260 if (
const auto* item = ImPlot::GetCurrentPlot()->Items.GetItem(plotName.c_str());
1263 auto stride = plotItem.style.stride ? plotItem.style.stride
1265 auto dataPointCount =
static_cast<int>(std::ceil(
static_cast<double>(plotData.buffer.size())
1266 /
static_cast<double>(stride)));
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(),
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)));
1281 if (ImPlot::IsPlotHovered())
1283 constexpr double HOVER_PIXEL_SIZE = 5.0;
1284 auto limits = ImPlot::GetPlotLimits(IMPLOT_AUTO, plotItem.axis);
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();
1290 std::vector<PlotEventTooltip> tooltips;
1291 for (
const auto& e : plotItem.eventTooltips)
1293 if (std::abs(mouse.x - std::get<0>(e)) < scaling.x
1294 && std::abs(mouse.y - std::get<1>(e)) < scaling.y)
1296 tooltips.push_back(std::get<2>(e));
1299 if (!tooltips.empty())
1301 ImGui::BeginTooltip();
1302 ImGui::PushFont(Application::MonoFont());
1303 for (
size_t i = 0; i < tooltips.size(); i++)
1305 ImGui::SetNextItemOpen(
true, ImGuiCond_Always);
1306 if (ImGui::TreeNode(fmt::format(
"{} GPST", tooltips.at(i).time.toYMDHMS(
GPST)).c_str()))
1308 for (
const auto& text : tooltips.at(i).texts)
1310 ImGui::BulletText(
"%s", text.c_str());
1314 if (i != tooltips.size() - 1) { ImGui::Separator(); }
1317 ImGui::EndTooltip();
1324 if (ImPlot::BeginDragDropSourceItem(plotName.c_str()))
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();
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()
1338 if (ImGui::BeginCombo(label, preview))
1340 for (
size_t plotDataIndex = 0; plotDataIndex < pinData.plotData.size(); plotDataIndex++)
1342 auto& plotData = pinData.plotData.at(plotDataIndex);
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))
1349 dataIdx = plotDataIndex;
1351 if (!plotData.hasData) { ImGui::PopStyleVar(); }
1354 if (is_selected) { ImGui::SetItemDefaultFocus(); }
1361 if (
auto legendReturn = plotItem.style.showLegendPopup(
1363 fmt::format(
"Pin {} - {}: {}", plotItem.pinIndex + 1,
1364 pinData.dataIdentifier, plotData.displayName)
1366 static_cast<int>(plotData.buffer.size()),
1367 static_cast<int>(plotElementIdx),
1370 &plotItem.colormapMaskColors,
1371 &plotItem.markerColormapMaskColors,
1372 ShowDataReferenceChooser,
1373 &plotItem.eventMarker,
1374 &plotItem.eventTooltips);
1375 legendReturn.changed)
1379 if (legendReturn.errorBoundsReCalcNeeded)
1381 for (
auto& data : plotItem.errorBoundsData) { data.clear(); }
1389 for (
const auto& plotItem : plotItemsToRemove)
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);
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()))
1400 auto [pinIndex, dataIndex, displayName] = *
static_cast<std::tuple<size_t, size_t, std::string*>*
>(payloadData->Data);
1402 auto iter = std::ranges::find(plot.plotItems, PlotInfo::PlotItem{ pinIndex, dataIndex, *displayName });
1403 if (iter != plot.plotItems.end())
1405 iter->axis = dragDropAxis;
1409 plot.plotItems.emplace_back(pinIndex, dataIndex, *displayName, dragDropAxis);
1416 if (ImPlot::BeginDragDropTargetPlot())
1418 addDragDropPlotToAxis(ImAxis_Y1);
1419 ImPlot::EndDragDropTarget();
1422 for (ImAxis y = ImAxis_Y1; y <= ImAxis_Y3; ++y)
1424 if ((y == ImAxis_Y2 && !(plot.plotFlags & ImPlotFlags_YAxis2))
1425 || (y == ImAxis_Y3 && !(plot.plotFlags & ImPlotFlags_YAxis3)))
1429 if (ImPlot::BeginDragDropTargetAxis(y))
1431 addDragDropPlotToAxis(y);
1432 ImPlot::EndDragDropTarget();
1440 if (_dragAndDropHeaderIndex >= 0
1441 && plotIdx !=
static_cast<size_t>(_dragAndDropHeaderIndex - 1)
1442 && plotIdx !=
static_cast<size_t>(_dragAndDropHeaderIndex))
1444 showDragDropTargetHeader(plotIdx + 1);
1448 if (!dragAndDropHeaderStillInProgress)
1450 _dragAndDropHeaderIndex = -1;
1454 if (ImGui::Button(fmt::format(
"Add Plot##{}",
size_t(
id)).c_str()))
1457 LOG_DEBUG(
"{}: # Plots changed to {}", nameId(), _nPlots);
1459 updateNumberOfPlots();