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:
DeepSeek V4.1 Flash committed 2026-11-02 20:41:26 +08:00
1 parent 30e9d65c30
commit 1e1c2432ae
19 files changed
+1979 -56

No files matched your search

+187 -2
View File
@@ -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)):
// 当视界覆盖整条信号、且该信号没有任何数值变换时,逐点扫描的结果与