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

+128 -28
View File
@@ -28,12 +28,13 @@ namespace jdi
const bool centerAxis = (kind == EdgePanelKind::CENTER);
const bool meanFilter = (kind == EdgePanelKind::MEAN);
const bool weakPeak = (kind == EdgePanelKind::PEAK);
// 「均值滤波」「中心轴边沿计数」「弱峰调整」= 可滚动的分组列表
const bool resamplePanel = (kind == EdgePanelKind::RESAMPLE);
// 「均值滤波」「中心轴边沿计数」「弱峰调整」「重采样」= 可滚动的分组列表
//(信号色叠底条目栏 + 右侧复选框 + 展开后的参数行);
// **「边沿计数」= 表格**(表头:信号名 / 上升沿计数 / 下降沿计数)。
// ⚠️ 旧式「逐条目」排版(无滚动的名称+参数行)已彻底删除:上百个信号时它会算出
// 上万像素高的面板,超出绘图区后**面板像素缓存一次都存不进去** ⇒ 每帧整块重画。
const bool listStyle = (centerAxis || meanFilter || weakPeak);
const bool listStyle = (centerAxis || meanFilter || weakPeak || resamplePanel);
const bool countTable = (kind == EdgePanelKind::COUNT);
// 标题行 = “GX” 徽标 + 标题(“边沿计数” / “中心轴边沿计数” / “均值滤波” / “弱峰调整”
@@ -41,7 +42,10 @@ namespace jdi
const std::wstring title =
meanFilter ? std::wstring(L"均值滤波")
: (weakPeak ? std::wstring(kWeakPeakTitle)
: ((centerAxis ? L"中心轴边沿计数" : L"边沿计数") + EdgeRangeSuffix()));
: (resamplePanel
? std::wstring(kResampleTitle)
: ((centerAxis ? L"中心轴边沿计数" : L"边沿计数") +
EdgeRangeSuffix())));
float width = kEdgePanelPad * 2.0f * m_scale + kLaneBadgeWidth * m_scale +
8.0f * m_scale + UiMeasureTextWidth(m_fonts->uiSmall, title) +
kEdgePanelTitleHeight * m_scale;
@@ -162,6 +166,22 @@ namespace jdi
kWeakPeakResetGap * m_scale + kWeakPeakResetButtonSize * m_scale;
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + sliderRowWidth +
(kFreeMarkerScrollWidth + 4.0f) * m_scale);
if (resamplePanel)
{
// 「重采样」四行 = 「标签列 + 数值列」(两行统计 + 两行输入框),
// 标签取四者中最宽的一个,数值列按间隔 / 频率的典型文本宽度取。
const float resampleLabelColumn =
std::max(std::max(UiMeasureTextWidth(m_fonts->uiSmall, kResampleMaxIntervalLabel),
UiMeasureTextWidth(m_fonts->uiSmall, kResampleMinIntervalLabel)),
std::max(UiMeasureTextWidth(m_fonts->uiSmall, kResampleIntervalLabel),
UiMeasureTextWidth(m_fonts->uiSmall, kResampleFrequencyLabel)));
const float resampleValueColumn =
std::max(UiMeasureTextWidth(m_fonts->monoSmall, L"1.23456 GHz"),
UiMeasureTextWidth(m_fonts->monoSmall, L"1h 1min 2s"));
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + resampleLabelColumn +
8.0f * m_scale + resampleValueColumn +
(kFreeMarkerScrollWidth + 4.0f) * m_scale);
}
}
if (weakPeak)
@@ -174,9 +194,9 @@ namespace jdi
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + rowWidth);
}
if (meanFilter || weakPeak)
if (meanFilter || weakPeak || resamplePanel)
{
// 底部按钮行 = 「构建为新信号」(仅均值滤波 / 弱峰调整有),按按钮文字实测宽度确定下限。
// 底部按钮行 = 「构建为新信号」(均值滤波 / 弱峰调整 / 重采样有),按按钮文字实测宽度确定下限。
const float buttonWidth =
UiMeasureTextWidth(m_fonts->uiSmall, kBuildSignalLabel) + 28.0f * m_scale;
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + buttonWidth);
@@ -276,21 +296,26 @@ namespace jdi
const bool meanFilter = (kind == EdgePanelKind::MEAN);
const bool weakPeak = (kind == EdgePanelKind::PEAK);
const bool freeMarker = (kind == EdgePanelKind::MARK);
// 「均值滤波」「中心轴边沿计数」「弱峰调整」共用同一套**可滚动分组列表**排布
const bool resamplePanel = (kind == EdgePanelKind::RESAMPLE);
// 「均值滤波」「中心轴边沿计数」「弱峰调整」「重采样」共用同一套**可滚动分组列表**排布
//(信号色叠底条目栏 + 展开后的参数行);「边沿计数」自 2026-11-02 起走**表格**分支。
const bool listStyle = (meanFilter || centerAxis || weakPeak);
const bool listStyle = (meanFilter || centerAxis || weakPeak || resamplePanel);
const bool countTable = (kind == EdgePanelKind::COUNT);
const bool hasButton = (meanFilter || weakPeak);
const bool hasButton = (meanFilter || weakPeak || resamplePanel);
const bool enabled = meanFilter ? lane.meanFilter
: (weakPeak ? lane.weakPeak
: (freeMarker ? lane.freeMarker
: (centerAxis ? lane.edgeCenter
: lane.edgeCount)));
: (resamplePanel ? lane.resample
: (centerAxis ? lane.edgeCenter
: lane.edgeCount))));
const bool open = meanFilter ? lane.meanFilterPanel.open
: (weakPeak ? lane.weakPeakPanel.open
: (freeMarker ? lane.freeMarkerPanel.open
: (centerAxis ? lane.edgeCenterPanel.open
: lane.edgePanel.open)));
: (resamplePanel
? lane.resamplePanel.open
: (centerAxis
? lane.edgeCenterPanel.open
: lane.edgePanel.open))));
if (!enabled || !open)
{
return layout;
@@ -299,9 +324,11 @@ namespace jdi
const bool collapsed = meanFilter ? lane.meanFilterPanel.collapsed
: (weakPeak ? lane.weakPeakPanel.collapsed
: (freeMarker ? lane.freeMarkerPanel.collapsed
: (centerAxis
? lane.edgeCenterPanel.collapsed
: lane.edgePanel.collapsed)));
: (resamplePanel
? lane.resamplePanel.collapsed
: (centerAxis
? lane.edgeCenterPanel.collapsed
: lane.edgePanel.collapsed))));
layout.collapsed = collapsed;
int rowCount = 0;
@@ -368,7 +395,12 @@ namespace jdi
}
content += groupRow;
bool expanded = false;
if (weakPeak)
if (resamplePanel)
{
const Lane::ResampleItem* item = lane.FindResampleItem(ref);
expanded = (item != nullptr && !item->collapsed);
}
else if (weakPeak)
{
const Lane::WeakPeakItem* item = lane.FindWeakPeakItem(ref);
expanded = (item != nullptr && !item->collapsed);
@@ -385,10 +417,18 @@ namespace jdi
}
if (expanded)
{
content += sliderRow * 2.0f;
if (centerAxis)
if (resamplePanel)
{
content += groupRow; // 统计行(上升沿 / 下降沿)
// 四行:最大间隔 / 最小间隔 / 重采样间隔 / 重采样频率。
content += sliderRow * 4.0f;
}
else
{
content += sliderRow * 2.0f;
if (centerAxis)
{
content += groupRow; // 统计行(上升沿 / 下降沿)
}
}
}
}
@@ -489,13 +529,19 @@ namespace jdi
const double storedX = meanFilter ? lane.meanFilterPanel.x
: (weakPeak ? lane.weakPeakPanel.x
: (freeMarker ? lane.freeMarkerPanel.x
: (centerAxis ? lane.edgeCenterPanel.x
: lane.edgePanel.x)));
: (resamplePanel
? lane.resamplePanel.x
: (centerAxis
? lane.edgeCenterPanel.x
: lane.edgePanel.x))));
const double storedY = meanFilter ? lane.meanFilterPanel.y
: (weakPeak ? lane.weakPeakPanel.y
: (freeMarker ? lane.freeMarkerPanel.y
: (centerAxis ? lane.edgeCenterPanel.y
: lane.edgePanel.y)));
: (resamplePanel
? lane.resamplePanel.y
: (centerAxis
? lane.edgeCenterPanel.y
: lane.edgePanel.y))));
int openPanelOrdinal = 0;
{
// ⚠️ 错开量用「**当前真正显示出来的面板**中排在本面板之前的个数」:
@@ -519,12 +565,14 @@ namespace jdi
return lane.weakPeak && lane.weakPeakPanel.open;
case EdgePanelKind::MARK:
return lane.freeMarker && lane.freeMarkerPanel.open;
case EdgePanelKind::RESAMPLE:
return lane.resample && lane.resamplePanel.open;
}
return false;
};
static const EdgePanelKind kOrder[5] = {EdgePanelKind::COUNT, EdgePanelKind::CENTER,
EdgePanelKind::MEAN, EdgePanelKind::PEAK,
EdgePanelKind::MARK};
static const EdgePanelKind kOrder[6] = {EdgePanelKind::COUNT, EdgePanelKind::CENTER,
EdgePanelKind::MEAN, EdgePanelKind::PEAK,
EdgePanelKind::MARK, EdgePanelKind::RESAMPLE};
int openSeen = 0;
bool found = false;
for (std::size_t lane = 0; lane < m_lanes.size() && !found; ++lane)
@@ -701,7 +749,9 @@ namespace jdi
const bool scrollable = meanContentHeight > viewHeight + 0.5f;
const float maxScroll = std::max(0.0f, meanContentHeight - viewHeight);
const float storedScroll = meanFilter ? lane.meanFilterScroll
: (weakPeak ? lane.weakPeakScroll : lane.edgeCenterScroll);
: (weakPeak ? lane.weakPeakScroll
: (resamplePanel ? lane.resampleScroll
: lane.edgeCenterScroll));
const float scroll = std::max(0.0f, std::min(storedScroll, maxScroll));
const float innerWidth =
std::max(0.0f, width - pad * 2.0f - (scrollable ? (scrollW + 4.0f * m_scale) : 0.0f));
@@ -734,6 +784,14 @@ namespace jdi
const float resetGap = kWeakPeakResetGap * m_scale;
const float trackHeight = kWeakPeakTrackHeight * m_scale;
const float closeSize = kEdgePanelMeanCheckSize * m_scale;
// 「重采样」四行的标签列(「最大数据间隔」比均值/弱峰的标签更宽):
// 与 `EdgePanelWidth` 用同一组实测宽度,保证字段不会压到标签上。
const float resampleLabelColumn =
std::max(std::max(UiMeasureTextWidth(m_fonts->uiSmall, kResampleMaxIntervalLabel),
UiMeasureTextWidth(m_fonts->uiSmall, kResampleMinIntervalLabel)),
std::max(UiMeasureTextWidth(m_fonts->uiSmall, kResampleIntervalLabel),
UiMeasureTextWidth(m_fonts->uiSmall, kResampleFrequencyLabel)));
const float rowLabelColumn = resamplePanel ? resampleLabelColumn : labelColumn;
const float contentLeft = layout.content.X;
const float trackLeft = contentLeft + labelColumn + 6.0f * m_scale;
const float resetLeft = contentLeft + innerWidth - resetSize;
@@ -751,7 +809,13 @@ namespace jdi
}
bool checked = false;
bool itemCollapsed = false;
if (weakPeak)
if (resamplePanel)
{
const Lane::ResampleItem* item = lane.FindResampleItem(ref);
checked = (item != nullptr);
itemCollapsed = (item != nullptr && item->collapsed);
}
else if (weakPeak)
{
const Lane::WeakPeakItem* item = lane.FindWeakPeakItem(ref);
checked = (item != nullptr);
@@ -797,6 +861,42 @@ namespace jdi
layout.rowIterTracks.push_back(gd::RectF());
layout.rowIterValues.push_back(gd::RectF());
layout.rowIterResets.push_back(gd::RectF());
if (resamplePanel)
{
layout.rowInfoMaxInterval.push_back(gd::RectF());
layout.rowInfoMinInterval.push_back(gd::RectF());
layout.rowIntervalField.push_back(gd::RectF());
layout.rowFrequencyField.push_back(gd::RectF());
}
continue;
}
if (resamplePanel)
{
// 「重采样」四行:① 最大间隔(只读)② 最小间隔(只读)
// ③ 重采样间隔(输入)④ 重采样频率(输入,与③联动)。
// 每行 = 左侧标签 + 右侧数值/输入框区域。
layout.rowLevelTracks.push_back(gd::RectF());
layout.rowLevelValues.push_back(gd::RectF());
layout.rowLevelResets.push_back(gd::RectF());
layout.rowIterTracks.push_back(gd::RectF());
layout.rowIterValues.push_back(gd::RectF());
layout.rowIterResets.push_back(gd::RectF());
layout.rowInfoMaxInterval.push_back(
gd::RectF(contentLeft, rowY, innerWidth, sliderRow));
rowY += sliderRow;
layout.rowInfoMinInterval.push_back(
gd::RectF(contentLeft, rowY, innerWidth, sliderRow));
rowY += sliderRow;
// 输入行的**数值矩形**靠右对齐,宽度取剩余可用宽度(绘制框与编辑框同源)。
const float fieldLeft = contentLeft + rowLabelColumn + 8.0f * m_scale;
const float fieldWidth =
std::max(24.0f * m_scale, innerWidth - rowLabelColumn - 8.0f * m_scale);
layout.rowIntervalField.push_back(
gd::RectF(fieldLeft, rowY, fieldWidth, sliderRow));
rowY += sliderRow;
layout.rowFrequencyField.push_back(
gd::RectF(fieldLeft, rowY, fieldWidth, sliderRow));
rowY += sliderRow;
continue;
}
// 「中心轴边沿计数」:参数行最上面是**同一行**的「上升沿 / 下降沿」统计。