feat(resample): 实验性功能新增「重采样」(位于「自由标记」上方)
- Lane::ResampleItem + resample/resampleItems/resamplePanel/resampleScroll, 并在 LaneCarry / SplitLane / PruneLaneSignalItems / RemapAfterSignalInsert|Removal 同步 - 面板:可滚动分组列表(信号色条目栏 + 复选框 + 展开三角),展开后 4 行: 最大/最小数据间隔(只读,勾选时一次性缓存,绘制不扫描采样)+ 重采样间隔/频率(输入) - 输入框联动:改间隔同步频率,改频率同步间隔(输入单位分别为秒 / Hz) - 「构建为新信号」:DataSet::AppendGroupAxis 追加新时间轴 + InsertDerivedSignal, 采样值先按公式/系数/绝对值/镜像/Y偏移变换再等间隔线性插值,时间轴偏移不烘值 - 新信号名 <原信号> (重采样 <自适应单位频率>);Utf8Util::FormatFrequency 为唯一实现 - 非单调轴 / 点太少 / 间隔过大 / 点数超 2000 万 ⇒ 拒绝构建并给出自绘提示 - 顺带加入 FormulaEval(公式编译/求值,供 ApplyDrawTransform 与后续 UI 使用)
This commit is contained in:
1 parent
30e9d65c30
commit
1e1c2432ae
19 files changed
+1979
-56
No files matched your search
@@ -1811,8 +1811,187 @@ namespace jdi
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlotView::SetLaneEdgeCenterScroll(int laneIndex, float scroll)
|
||||
// ============================ 「重采样」 ============================
|
||||
|
||||
bool PlotView::SignalDataInterval(const SignalRef& ref, double& maxInterval,
|
||||
double& minInterval) const
|
||||
{
|
||||
maxInterval = 0.0;
|
||||
minInterval = 0.0;
|
||||
if (m_dataSet == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
if (resolved.data == nullptr || resolved.axis == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const TimeAxis& axis = *resolved.axis;
|
||||
const SignalData& signal = *resolved.data;
|
||||
const std::size_t count = std::min(axis.time.size(), signal.values.size());
|
||||
bool havePrevious = false;
|
||||
double previousTime = 0.0;
|
||||
bool any = false;
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
const double time = axis.time[index];
|
||||
if (!std::isfinite(time) || !std::isfinite(signal.values[index]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (havePrevious)
|
||||
{
|
||||
const double gap = time - previousTime;
|
||||
if (gap > 0.0)
|
||||
{
|
||||
if (!any)
|
||||
{
|
||||
maxInterval = gap;
|
||||
minInterval = gap;
|
||||
any = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxInterval = std::max(maxInterval, gap);
|
||||
minInterval = std::min(minInterval, gap);
|
||||
}
|
||||
}
|
||||
}
|
||||
previousTime = time;
|
||||
havePrevious = true;
|
||||
}
|
||||
return any;
|
||||
}
|
||||
|
||||
bool PlotView::SetLaneResample(int laneIndex, bool enabled)
|
||||
{
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
if (lane.resample == enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
lane.resample = enabled;
|
||||
if (enabled)
|
||||
{
|
||||
// 默认只勾选网格内**第一个可见信号**(与其它实验性功能一致)。
|
||||
if (lane.resampleItems.empty())
|
||||
{
|
||||
for (const SignalRef& ref : lane.signals)
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
if (resolved.data != nullptr && resolved.visible)
|
||||
{
|
||||
Lane::ResampleItem& item = lane.EnsureResampleItem(ref);
|
||||
double maxInterval = 0.0;
|
||||
double minInterval = 0.0;
|
||||
if (SignalDataInterval(ref, maxInterval, minInterval))
|
||||
{
|
||||
item.maxInterval = maxInterval;
|
||||
item.minInterval = minInterval;
|
||||
item.interval = minInterval;
|
||||
}
|
||||
item.initialized = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
lane.resamplePanel.open = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
lane.resamplePanel.open = false;
|
||||
}
|
||||
EnsureLaneLayersPrefetched();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlotView::LaneResample(int laneIndex) const
|
||||
{
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return m_tab->Lanes()[static_cast<std::size_t>(laneIndex)].resample;
|
||||
}
|
||||
|
||||
bool PlotView::ToggleLaneResample(int laneIndex)
|
||||
{
|
||||
const bool enabled = !LaneResample(laneIndex);
|
||||
SetLaneResample(laneIndex, enabled);
|
||||
return enabled;
|
||||
}
|
||||
|
||||
bool PlotView::ToggleLaneResampleSignal(int laneIndex, const SignalRef& ref)
|
||||
{
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
if (lane.ResampleEnabled(ref))
|
||||
{
|
||||
lane.RemoveResampleItem(ref);
|
||||
return false;
|
||||
}
|
||||
// 勾选时一次性算好最大 / 最小数据间隔(面板绘制只读缓存值)。
|
||||
Lane::ResampleItem& item = lane.EnsureResampleItem(ref);
|
||||
double maxInterval = 0.0;
|
||||
double minInterval = 0.0;
|
||||
if (SignalDataInterval(ref, maxInterval, minInterval))
|
||||
{
|
||||
item.maxInterval = maxInterval;
|
||||
item.minInterval = minInterval;
|
||||
item.interval = minInterval;
|
||||
}
|
||||
item.initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlotView::LaneResampleSignalEnabled(int laneIndex, const SignalRef& ref) const
|
||||
{
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return m_tab->Lanes()[static_cast<std::size_t>(laneIndex)].ResampleEnabled(ref);
|
||||
}
|
||||
|
||||
bool PlotView::SetLaneResampleScroll(int laneIndex, float scroll)
|
||||
{
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
const float clamped = std::max(0.0f, scroll);
|
||||
if (std::fabs(lane.resampleScroll - clamped) < 0.5f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
lane.resampleScroll = clamped;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlotView::TakePendingResampleBuild(int& laneIndex, std::vector<ResampleBuild>& items)
|
||||
{
|
||||
if (m_pendingResampleLane < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
laneIndex = m_pendingResampleLane;
|
||||
items = m_pendingResampleItems;
|
||||
m_pendingResampleLane = -1;
|
||||
m_pendingResampleItems.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool PlotView::SetLaneEdgeCenterScroll(int laneIndex, float scroll) {
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
return false;
|
||||
@@ -1916,6 +2095,9 @@ namespace jdi
|
||||
case EdgePanelKind::MARK:
|
||||
state = &lane.freeMarkerPanel;
|
||||
break;
|
||||
case EdgePanelKind::RESAMPLE:
|
||||
state = &lane.resamplePanel;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -1946,6 +2128,8 @@ namespace jdi
|
||||
return lane.weakPeakPanel.collapsed;
|
||||
case EdgePanelKind::MARK:
|
||||
return lane.freeMarkerPanel.collapsed;
|
||||
case EdgePanelKind::RESAMPLE:
|
||||
return lane.resamplePanel.collapsed;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -3393,7 +3577,8 @@ namespace jdi
|
||||
const bool mirrored = (resolvedStyle != nullptr) && resolvedStyle->mirrorHorizontal;
|
||||
const bool transform = (resolvedStyle != nullptr) &&
|
||||
(resolvedStyle->gain != 1.0f || resolvedStyle->absolute ||
|
||||
resolvedStyle->mirrorVertical || resolvedStyle->offsetY != 0.0f);
|
||||
resolvedStyle->mirrorVertical || resolvedStyle->offsetY != 0.0f ||
|
||||
resolvedStyle->HasFormula());
|
||||
|
||||
// ⚠️ **整段自适应直接取信号已缓存的 min/max**(O(1)):
|
||||
// 当视界覆盖整条信号、且该信号没有任何数值变换时,逐点扫描的结果与
|
||||
|
||||
Reference in new issue
Block a user