实验性功能新增「差异放大」,并修正「构建为新信号」按钮的间距
1. 新增「实验性功能 → 差异放大」(与其它实验功能一样是带勾选的开关)
- 用途:纹波类信号某个周期(甚至半个周期)的 M 形下凹明显偏弱时,
把这段下凹按比例拉回周边振幅,恢复正常周期的对比度,便于看出波形异常。
- 算法(全 O(n),不额外占用与信号长度成正比以外的内存):
以半径约半个纹波周期的滑动平均作为纹波中心线,用邻域内「交流分量的最大值」
作为局部纹波振幅,取全信号振幅的 75 分位作为目标振幅,
逐点增益 = 目标振幅 / 局部振幅(夹在 1 到放大倍数之间),
输出 = 中心线 + 增益 × 交流分量,再夹在周边包络内。
- 关键性质(均已用合成正弦逐点验证):
* 增益下界恒为 1,因此**只有被判定为"偏弱"的采样点会改变**,
其余采样点输出与原值逐位相同 —— 实测逐周期表格中只有一个周期发生变化;
* 放大结果夹在周边包络内,因此**任何一点都不超出原有的振幅范围**,
整体值域与其它周期的峰峰值都不变;
* 端点自动收敛到不放大,避免记录首尾被整片抬升。
- 参数「窗口点数 X」(窗口长度,建议略大于纹波周期的一半)与
「放大倍数 N」(增益上限)都可在浮动面板内按信号编辑。
2. 浮动面板
- 与「均值滤波」完全同一套排版:自上而下逐信号条目,
第一排信号名与复选框、第二排两个参数输入框;取消勾选即不显示该信号的参数与预览曲线;
启用时默认只勾选网格内第一个可见信号。
- 新增「构建为新信号」按钮,按各信号自己的参数把结果插入到源信号下方,
与「均值滤波」的构建机制完全一致(统一走同一套请求与插入流程)。
3. 光标交互
- 纵向光标额外标注差异放大曲线在该时刻的取值,配色与原始信号一致,
数值尾部括号内容为「差异化」,双光标时同样带增量。
4. 修复「构建为新信号」按钮与面板下边界的间距
- 原按钮行直接贴着面板下边框,与面板左右内边距及内容间距都不协调;
现在按钮上方与下方各保留一个与左右一致的内边距(均值滤波与差异放大同时生效)。
测试:离屏验证套件新增两组检查(合成正弦上算法只改动一个周期、放大不越界、
参数上限生效、面板排版与按钮间距;再写一份合成纹波文件真正加载渲染并核对曲线数值),
合计 289 项检查全部通过;验证过程不显示窗口、不移动鼠标键盘、不抓屏。
This commit is contained in:
1 parent
8801c8f019
commit
d4206a8d67
6 files changed
+1188
-185
No files matched your search
@@ -57,7 +57,9 @@ namespace jdi
|
||||
EDGE_CENTER_POINTS, /**< 编辑「中心轴边沿计数」的均值点数 X。 */
|
||||
EDGE_CENTER_ORDER, /**< 编辑「中心轴边沿计数」的阶数 N。 */
|
||||
MEAN_FILTER_POINTS, /**< 编辑「均值滤波」的均值点数 X。 */
|
||||
MEAN_FILTER_ORDER /**< 编辑「均值滤波」的阶数 N。 */
|
||||
MEAN_FILTER_ORDER, /**< 编辑「均值滤波」的阶数 N。 */
|
||||
DIFF_AMP_POINTS, /**< 编辑「差异放大」的窗口点数 X。 */
|
||||
DIFF_AMP_ORDER /**< 编辑「差异放大」的放大倍数 N。 */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -74,7 +76,8 @@ namespace jdi
|
||||
PLOT_CMD_LANE_EDGE_COUNT = 7, /**< 切换本网格的「实验性功能 → 边沿计数」开关。 */
|
||||
PLOT_CMD_LANE_EDGE_CENTER = 8, /**< 切换本网格的「实验性功能 → 边沿计数(中心轴)」开关。 */
|
||||
PLOT_CMD_LANE_SPLIT = 9, /**< 拆分当前网格:把网格内信号按个拆成独立网格并顺次下移编号。 */
|
||||
PLOT_CMD_LANE_MEAN_FILTER = 10 /**< 切换本网格的「实验性功能 → 均值滤波」开关。 */
|
||||
PLOT_CMD_LANE_MEAN_FILTER = 10, /**< 切换本网格的「实验性功能 → 均值滤波」开关。 */
|
||||
PLOT_CMD_LANE_DIFF_AMP = 11 /**< 切换本网格的「实验性功能 → 差异放大」开关。 */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -84,7 +87,8 @@ namespace jdi
|
||||
{
|
||||
COUNT = 0, /**< 边沿计数。 */
|
||||
CENTER = 1, /**< 边沿计数(中心轴)。 */
|
||||
MEAN = 2 /**< 均值滤波。 */
|
||||
MEAN = 2, /**< 均值滤波。 */
|
||||
DIFF_AMP = 3 /**< 差异放大。 */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -518,6 +522,86 @@ namespace jdi
|
||||
*/
|
||||
bool TakePendingMeanFilterBuild(int& laneIndex, std::vector<MeanFilterBuild>& items);
|
||||
|
||||
/**
|
||||
* @brief 「实验性功能 → 差异放大」的「构建为新信号」单个请求
|
||||
* (一个被勾选的信号 + 自己的参数)。
|
||||
*/
|
||||
struct DiffAmpBuild
|
||||
{
|
||||
int fileIndex = -1; /**< 信号所属文件下标。 */
|
||||
int signalIndex = -1; /**< 文件内信号下标。 */
|
||||
int points = 80; /**< 窗口点数 X(略大于纹波周期的一半)。 */
|
||||
int gain = 2; /**< 放大倍数 N。 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 取出「差异放大 → 构建为新信号」请求(由面板底部按钮触发)。
|
||||
* @param [OUT] laneIndex 触发该请求的网格下标。
|
||||
* @param [OUT] items 需要构建的信号及其参数(按信号在网格内的顺序)。
|
||||
* @return 存在待处理请求返回 true(取出后清空)。
|
||||
*/
|
||||
bool TakePendingDiffAmpBuild(int& laneIndex, std::vector<DiffAmpBuild>& items);
|
||||
|
||||
/**
|
||||
* @brief 切换某网格的「差异放大」开关。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] enabled 是否开启。
|
||||
* @return 状态发生变化返回 true。
|
||||
*/
|
||||
bool SetLaneDiffAmp(int laneIndex, bool enabled);
|
||||
|
||||
/**
|
||||
* @brief 查询某网格的「差异放大」开关。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @return 已开启返回 true。
|
||||
*/
|
||||
bool LaneDiffAmp(int laneIndex) const;
|
||||
|
||||
/**
|
||||
* @brief 切换某网格的「差异放大」开关。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @return 切换后的状态。
|
||||
*/
|
||||
bool ToggleLaneDiffAmp(int laneIndex);
|
||||
|
||||
/**
|
||||
* @brief 获取差异放大曲线(按「信号 + 窗口点数 + 放大倍数」缓存)。
|
||||
* @param [IN] signal 目标信号。
|
||||
* @param [IN] points 窗口点数 X。
|
||||
* @param [IN] gain 放大倍数 N。
|
||||
* @return 曲线数值数组(长度与 `signal.values` 相同;缺失处为 NaN)。
|
||||
*/
|
||||
const std::vector<float>& DiffAmpCurveFor(const SignalData& signal, int points, int gain) const;
|
||||
|
||||
/**
|
||||
* @brief 取某个时刻的差异放大曲线取值(用于纵向光标的取值标注)。
|
||||
* @param [IN] file 信号所属文件。
|
||||
* @param [IN] signal 目标信号。
|
||||
* @param [IN] points 窗口点数 X(略大于纹波周期的一半)。
|
||||
* @param [IN] gain 放大倍数 N。
|
||||
* @param [IN] time 采样时刻。
|
||||
* @param [OUT] value 输出取值。
|
||||
* @return 取到有效值返回 true。
|
||||
*/
|
||||
bool DiffAmpValueAt(const DataFile& file, const SignalData& signal, int points, int gain,
|
||||
double time, double& value) const;
|
||||
|
||||
/**
|
||||
* @brief 切换某个信号在「差异放大」面板中的预览勾选状态。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] ref 目标信号。
|
||||
* @return 切换后的勾选状态。
|
||||
*/
|
||||
bool ToggleLaneDiffAmpSignal(int laneIndex, const SignalRef& ref);
|
||||
|
||||
/**
|
||||
* @brief 查询某个信号在「差异放大」面板中的预览勾选状态。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] ref 目标信号。
|
||||
* @return 已勾选返回 true。
|
||||
*/
|
||||
bool LaneDiffAmpSignalEnabled(int laneIndex, const SignalRef& ref) const;
|
||||
|
||||
/**
|
||||
* @brief 直接把文本解析为网格 Y 轴上限/下限并应用(供菜单内联输入框提交使用)。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
@@ -1091,6 +1175,17 @@ namespace jdi
|
||||
void PaintMeanFilterCurve(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane,
|
||||
int points, int order) const;
|
||||
|
||||
/**
|
||||
* @brief 绘制某条信号的差异放大曲线(与均值滤波曲线同一配色派生规则)。
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
* @param [IN] resolved 信号视图。
|
||||
* @param [IN] lane 网格绘制信息。
|
||||
* @param [IN] points 窗口点数 X(局部差异强度统计半径)。
|
||||
* @param [IN] gain 放大倍数 N(最大增益)。
|
||||
*/
|
||||
void PaintDiffAmpCurve(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane,
|
||||
int points, int gain) const;
|
||||
|
||||
/**
|
||||
* @brief 数据点标记的位置与"越界方向"(用于决定画圆点还是贴边三角)。
|
||||
*/
|
||||
@@ -1305,7 +1400,9 @@ namespace jdi
|
||||
bool m_pendingRename = false; /**< 是否存在待应用的重命名请求。 */
|
||||
|
||||
int m_pendingMeanFilterLane = -1; /**< 待构建的均值滤波信号所属网格下标(-1 表示无请求)。 */
|
||||
int m_pendingDiffAmpLane = -1; /**< 待构建的差异放大信号所属网格下标(-1 表示无请求)。 */
|
||||
std::vector<MeanFilterBuild> m_pendingMeanFilterItems; /**< 待构建的信号及其参数。 */
|
||||
std::vector<DiffAmpBuild> m_pendingDiffAmpItems; /**< 待构建的差异放大信号及其参数。 */
|
||||
|
||||
/**
|
||||
* @brief 实验性功能浮动面板的 ✕ 处于"已按下、等待抬起确认"状态。
|
||||
@@ -1361,6 +1458,16 @@ namespace jdi
|
||||
};
|
||||
mutable std::deque<MeanCurveEntry> m_meanCurveCache;
|
||||
|
||||
/** 多阶均值滤波曲线缓存项(按「信号指针 + 均值点数 + 阶数」标识)。 */
|
||||
struct DiffAmpCurveEntry
|
||||
{
|
||||
const SignalData* signal = nullptr;
|
||||
int points = 80; /**< 窗口点数 X。 */
|
||||
int gain = 2; /**< 放大倍数 N。 */
|
||||
std::vector<float> values; /**< 与 signal->values 等长;缺失处为 NaN。 */
|
||||
};
|
||||
mutable std::deque<DiffAmpCurveEntry> m_diffAmpCurveCache;
|
||||
|
||||
/** 中心轴边沿统计缓存项(增量统计机制与 `EdgeCountEntry` 完全一致)。 */
|
||||
struct EdgeCenterCountEntry
|
||||
{
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace jdi
|
||||
const float kEdgePanelMeanEditHeight = 24.0f; /**< 逐条目面板中每条信号的参数行高度。 */
|
||||
const float kEdgePanelMeanRowGap = 2.0f; /**< 名称行与参数行之间的间距。 */
|
||||
const float kEdgePanelMeanCheckSize = 15.0f; /**< 逐条目面板中条目复选框尺寸。 */
|
||||
const wchar_t* const kMeanFilterBuildLabel = L"构建为新信号"; /**< 均值滤波面板底部按钮文案。 */
|
||||
const wchar_t* const kBuildSignalLabel = L"构建为新信号"; /**< 逐条目面板底部按钮文案(均值滤波 / 差异放大共用)。 */
|
||||
const std::size_t kEdgeCountCacheLimit = 48; /**< 边沿统计缓存条目上限(超出时淘汰最早的一条)。 */
|
||||
|
||||
/**
|
||||
@@ -228,8 +228,11 @@ namespace jdi
|
||||
*
|
||||
* 顺序即绘制层序:先绘制的在下方,最后绘制的在最上方。
|
||||
*/
|
||||
const EdgePanelKind kEdgePanelKinds[] = {
|
||||
EdgePanelKind::COUNT, EdgePanelKind::CENTER, EdgePanelKind::MEAN};
|
||||
const EdgePanelKind kEdgePanelKinds[] = {EdgePanelKind::COUNT, EdgePanelKind::CENTER,
|
||||
EdgePanelKind::MEAN, EdgePanelKind::DIFF_AMP};
|
||||
|
||||
/** 实验性功能浮动面板的种类数(遍历与命中测试统一用它,避免新增功能时漏改)。 */
|
||||
const int kEdgePanelKindCount = static_cast<int>(sizeof(kEdgePanelKinds) / sizeof(kEdgePanelKinds[0]));
|
||||
|
||||
/**
|
||||
* @brief 时间间隔的显示单位。
|
||||
@@ -1126,6 +1129,7 @@ namespace jdi
|
||||
m_edgeCountCache.clear();
|
||||
m_edgeCenterCountCache.clear();
|
||||
m_meanCurveCache.clear();
|
||||
m_diffAmpCurveCache.clear();
|
||||
// 信号下标可能已经整体位移:清掉按引用缓存的悬停/拖拽目标,避免指向错误的信号。
|
||||
m_hoverSignal = SignalRef();
|
||||
m_hoverLegend = SignalRef();
|
||||
@@ -1700,6 +1704,96 @@ namespace jdi
|
||||
return m_tab->Lanes()[static_cast<std::size_t>(laneIndex)].meanFilter;
|
||||
}
|
||||
|
||||
bool PlotView::LaneDiffAmp(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)].diffAmp;
|
||||
}
|
||||
|
||||
bool PlotView::SetLaneDiffAmp(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.diffAmp == enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
lane.diffAmp = enabled;
|
||||
if (enabled)
|
||||
{
|
||||
// 与「均值滤波」完全一致:默认只勾选网格内**第一个可见信号**。
|
||||
if (lane.diffAmpItems.empty())
|
||||
{
|
||||
for (const SignalRef& ref : lane.signals)
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
if (resolved.data != nullptr && resolved.visible)
|
||||
{
|
||||
lane.EnsureDiffAmpItem(ref);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
lane.diffAmpPanelOpen = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
lane.diffAmpPanelOpen = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlotView::ToggleLaneDiffAmp(int laneIndex)
|
||||
{
|
||||
const bool enabled = !LaneDiffAmp(laneIndex);
|
||||
SetLaneDiffAmp(laneIndex, enabled);
|
||||
return enabled;
|
||||
}
|
||||
|
||||
bool PlotView::ToggleLaneDiffAmpSignal(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.DiffAmpEnabled(ref))
|
||||
{
|
||||
lane.RemoveDiffAmpItem(ref);
|
||||
return false;
|
||||
}
|
||||
lane.EnsureDiffAmpItem(ref);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlotView::LaneDiffAmpSignalEnabled(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)].DiffAmpEnabled(ref);
|
||||
}
|
||||
|
||||
bool PlotView::TakePendingDiffAmpBuild(int& laneIndex, std::vector<DiffAmpBuild>& items)
|
||||
{
|
||||
if (m_pendingDiffAmpLane < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
laneIndex = m_pendingDiffAmpLane;
|
||||
items = m_pendingDiffAmpItems;
|
||||
m_pendingDiffAmpLane = -1;
|
||||
m_pendingDiffAmpItems.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlotView::SetLaneMeanFilter(int laneIndex, bool enabled)
|
||||
{
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
@@ -1828,6 +1922,53 @@ namespace jdi
|
||||
return m_meanCurveCache.back().values;
|
||||
}
|
||||
|
||||
const std::vector<float>& PlotView::DiffAmpCurveFor(const SignalData& signal, int points,
|
||||
int gain) const
|
||||
{
|
||||
const int window = std::max(1, points);
|
||||
const int multiplier = std::max(1, gain);
|
||||
for (const DiffAmpCurveEntry& entry : m_diffAmpCurveCache)
|
||||
{
|
||||
if (entry.signal == &signal && entry.points == window && entry.gain == multiplier)
|
||||
{
|
||||
return entry.values;
|
||||
}
|
||||
}
|
||||
|
||||
DiffAmpCurveEntry entry;
|
||||
entry.signal = &signal;
|
||||
entry.points = window;
|
||||
entry.gain = multiplier;
|
||||
// 与「构建为新信号」共用同一实现(Utilities 内的自由函数),
|
||||
// 因此预览曲线与构建出的采样值逐位一致。
|
||||
ComputeDiffAmplify(signal.values, window, multiplier, entry.values);
|
||||
|
||||
m_diffAmpCurveCache.push_back(std::move(entry));
|
||||
return m_diffAmpCurveCache.back().values;
|
||||
}
|
||||
|
||||
bool PlotView::DiffAmpValueAt(const DataFile& file, const SignalData& signal, int points, int gain,
|
||||
double time, double& value) const
|
||||
{
|
||||
const int index = FindNearestSample(file, time);
|
||||
if (index < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const std::vector<float>& curve = DiffAmpCurveFor(signal, points, gain);
|
||||
if (static_cast<std::size_t>(index) >= curve.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const float sampled = curve[static_cast<std::size_t>(index)];
|
||||
if (!std::isfinite(sampled))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
value = static_cast<double>(sampled);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlotView::EdgeCenterCountsFor(const DataFile& file, const SignalData& signal, int points,
|
||||
int order, bool hasRange, double rangeMin, double rangeMax,
|
||||
std::size_t& rising, std::size_t& falling) const
|
||||
@@ -2067,11 +2208,16 @@ namespace jdi
|
||||
const Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
const bool centerAxis = (kind == EdgePanelKind::CENTER);
|
||||
const bool meanFilter = (kind == EdgePanelKind::MEAN);
|
||||
const bool diffAmp = (kind == EdgePanelKind::DIFF_AMP);
|
||||
// 「均值滤波」「中心轴边沿计数」「差异放大」都是逐条目面板(名称 + 输入框 [+ 统计])。
|
||||
const bool itemized = (centerAxis || meanFilter || diffAmp);
|
||||
|
||||
// 标题行 = “GX” 徽标 + 标题(“边沿计数” / “中心轴边沿计数” / “均值滤波” + 光标区间尾缀)。
|
||||
// 标题行 = “GX” 徽标 + 标题(“边沿计数” / “中心轴边沿计数” / “均值滤波” / “差异放大”
|
||||
// + 光标区间尾缀)。
|
||||
const std::wstring title =
|
||||
meanFilter ? std::wstring(L"均值滤波")
|
||||
: ((centerAxis ? L"中心轴边沿计数" : L"边沿计数") + EdgeRangeSuffix());
|
||||
: (diffAmp ? std::wstring(L"差异放大")
|
||||
: ((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;
|
||||
@@ -2086,7 +2232,7 @@ namespace jdi
|
||||
continue;
|
||||
}
|
||||
const float nameWidth = UiMeasureTextWidth(m_fonts->uiSmall, resolved.data->Title());
|
||||
if (meanFilter || centerAxis)
|
||||
if (itemized)
|
||||
{
|
||||
// 逐条目面板:第一排 = 名称 + 复选框;第三排(中心轴)= 统计结果。
|
||||
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + nameWidth +
|
||||
@@ -2094,7 +2240,7 @@ namespace jdi
|
||||
}
|
||||
std::size_t rising = 0;
|
||||
std::size_t falling = 0;
|
||||
if (meanFilter)
|
||||
if (meanFilter || diffAmp)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -2120,12 +2266,14 @@ namespace jdi
|
||||
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + std::max(nameWidth, countWidth));
|
||||
}
|
||||
|
||||
if (centerAxis || meanFilter)
|
||||
if (centerAxis || meanFilter || diffAmp)
|
||||
{
|
||||
// 参数行 = 两个「说明文字 + 数值框」,必须保证文字不被数值框压住。
|
||||
// (「均值点数 X」/「阶数 N」的中心轴面板与均值滤波面板排布完全一致。)
|
||||
// (「均值点数 X」/「阶数 N」的布局对三个逐条目面板通用:标签与数值框各占半行,
|
||||
// 标签取较宽者以保证“放大倍数 N”这类更长的标签不被压住。)
|
||||
const float labelX = UiMeasureTextWidth(m_fonts->uiSmall, L"均值点数 X");
|
||||
const float labelN = UiMeasureTextWidth(m_fonts->uiSmall, L"阶数 N");
|
||||
const float labelN = std::max(UiMeasureTextWidth(m_fonts->uiSmall, L"阶数 N"),
|
||||
UiMeasureTextWidth(m_fonts->uiSmall, L"放大倍数 N"));
|
||||
const float fieldWidth =
|
||||
std::max(kEdgePanelEditFieldWidth * m_scale * 0.75f,
|
||||
UiMeasureTextWidth(m_fonts->monoSmall, L"1000000") + 18.0f * m_scale);
|
||||
@@ -2134,11 +2282,11 @@ namespace jdi
|
||||
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + rowWidth);
|
||||
}
|
||||
|
||||
if (meanFilter)
|
||||
if (meanFilter || diffAmp)
|
||||
{
|
||||
// 底部按钮行 = 「构建为新信号」,按按钮文字实测宽度确定下限。
|
||||
const float buttonWidth =
|
||||
UiMeasureTextWidth(m_fonts->uiSmall, kMeanFilterBuildLabel) + 28.0f * m_scale;
|
||||
UiMeasureTextWidth(m_fonts->uiSmall, kBuildSignalLabel) + 28.0f * m_scale;
|
||||
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + buttonWidth);
|
||||
}
|
||||
|
||||
@@ -2661,10 +2809,12 @@ namespace jdi
|
||||
if (m_editTarget == PlotEditTarget::EDGE_CENTER_POINTS ||
|
||||
m_editTarget == PlotEditTarget::EDGE_CENTER_ORDER ||
|
||||
m_editTarget == PlotEditTarget::MEAN_FILTER_POINTS ||
|
||||
m_editTarget == PlotEditTarget::MEAN_FILTER_ORDER)
|
||||
m_editTarget == PlotEditTarget::MEAN_FILTER_ORDER ||
|
||||
m_editTarget == PlotEditTarget::DIFF_AMP_POINTS ||
|
||||
m_editTarget == PlotEditTarget::DIFF_AMP_ORDER)
|
||||
{
|
||||
// 「均值点数 X」/「阶数 N」:输入框就落在对应的字段矩形上
|
||||
//(中心轴面板与均值滤波面板的字段矩形含义相同)。
|
||||
//(中心轴、均值滤波与差异放大面板的字段矩形含义相同)。
|
||||
if (m_editFieldRect.Width <= 0.0f)
|
||||
{
|
||||
return gd::RectF();
|
||||
@@ -2831,30 +2981,37 @@ namespace jdi
|
||||
}
|
||||
|
||||
// 「均值点数 X」/「阶数 N」:作用于被勾选信号的均值滤波曲线,夹在合理范围内。
|
||||
// 「中心轴边沿计数」用**完全相同的参数与语义**,只是作用对象是它自己的条目列表。
|
||||
// 「中心轴边沿计数」「差异放大」用**完全相同的参数与语义**,
|
||||
// 只是各自作用于自己的条目列表。
|
||||
const bool centerTarget =
|
||||
(target == PlotEditTarget::EDGE_CENTER_POINTS || target == PlotEditTarget::EDGE_CENTER_ORDER);
|
||||
const bool diffTarget =
|
||||
(target == PlotEditTarget::DIFF_AMP_POINTS || target == PlotEditTarget::DIFF_AMP_ORDER);
|
||||
if (target == PlotEditTarget::MEAN_FILTER_POINTS || target == PlotEditTarget::MEAN_FILTER_ORDER ||
|
||||
centerTarget)
|
||||
centerTarget || diffTarget)
|
||||
{
|
||||
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
const bool isPoints = (target == PlotEditTarget::MEAN_FILTER_POINTS ||
|
||||
target == PlotEditTarget::EDGE_CENTER_POINTS);
|
||||
target == PlotEditTarget::EDGE_CENTER_POINTS ||
|
||||
target == PlotEditTarget::DIFF_AMP_POINTS);
|
||||
const Lane::MeanFilterItem* existing =
|
||||
centerTarget ? lane.FindEdgeCenterItem(m_editMeanRef) : lane.FindMeanFilterItem(m_editMeanRef);
|
||||
centerTarget ? lane.FindEdgeCenterItem(m_editMeanRef)
|
||||
: (diffTarget ? lane.FindDiffAmpItem(m_editMeanRef)
|
||||
: lane.FindMeanFilterItem(m_editMeanRef));
|
||||
if (existing == nullptr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
const int maximum = isPoints ? 1000000 : 1000;
|
||||
int value = static_cast<int>(std::lround(parsed));
|
||||
// 阶数至少为 1(0 阶等于不做滤波,没有意义);均值点数可以为 0(不平滑)。
|
||||
// 阶数/倍数至少为 1(1 阶等于不做滤波、1 倍等于不放大);均值点数可以为 0。
|
||||
value = isPoints ? std::max(0, std::min(value, maximum))
|
||||
: std::max(1, std::min(value, maximum));
|
||||
|
||||
Lane::MeanFilterItem& item =
|
||||
centerTarget ? lane.EnsureEdgeCenterItem(m_editMeanRef)
|
||||
: lane.EnsureMeanFilterItem(m_editMeanRef);
|
||||
: (diffTarget ? lane.EnsureDiffAmpItem(m_editMeanRef)
|
||||
: lane.EnsureMeanFilterItem(m_editMeanRef));
|
||||
if (isPoints)
|
||||
{
|
||||
item.points = value;
|
||||
@@ -2866,6 +3023,7 @@ namespace jdi
|
||||
// 参数变化会改变曲线:清掉派生缓存后重绘。
|
||||
m_meanCurveCache.clear();
|
||||
m_edgeCenterCountCache.clear();
|
||||
m_diffAmpCurveCache.clear();
|
||||
InvalidateSceneCache();
|
||||
return true;
|
||||
}
|
||||
@@ -3108,7 +3266,8 @@ namespace jdi
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_SIZEALL);
|
||||
}
|
||||
if (kind == EdgePanelKind::MEAN && layout.button.Width > 0.0f &&
|
||||
if ((kind == EdgePanelKind::MEAN || kind == EdgePanelKind::DIFF_AMP) &&
|
||||
layout.button.Width > 0.0f &&
|
||||
UiPointInRect(layout.button, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
// 「构建为新信号」按钮:手型光标作为可点击提示。
|
||||
@@ -3379,6 +3538,10 @@ namespace jdi
|
||||
lane.meanFilterPanelX = left;
|
||||
lane.meanFilterPanelY = top;
|
||||
break;
|
||||
case EdgePanelKind::DIFF_AMP:
|
||||
lane.diffAmpPanelX = left;
|
||||
lane.diffAmpPanelY = top;
|
||||
break;
|
||||
case EdgePanelKind::COUNT:
|
||||
default:
|
||||
lane.edgePanelX = left;
|
||||
@@ -3584,7 +3747,7 @@ namespace jdi
|
||||
// * 底部按钮 → 触发「构建为新信号」请求;
|
||||
// * 标题栏 → 拖动面板;
|
||||
// * 面板主体 → 消费点击,但**不拖动也不穿透**(用户明确要求只有标题栏可拖拽)。
|
||||
for (int pass = 0; pass < 3; ++pass)
|
||||
for (int pass = 0; pass < kEdgePanelKindCount; ++pass)
|
||||
{
|
||||
const EdgePanelKind kind = kEdgePanelKinds[pass];
|
||||
bool handledPanel = false;
|
||||
@@ -3607,17 +3770,23 @@ namespace jdi
|
||||
m_pendingPanelCloseKind = kind;
|
||||
}
|
||||
|
||||
// 「中心轴边沿计数」/「均值滤波」面板:逐信号条目(同一套排布与交互)。
|
||||
if ((kind == EdgePanelKind::MEAN || kind == EdgePanelKind::CENTER) && m_tab != nullptr)
|
||||
// 「中心轴边沿计数」/「均值滤波」/「差异放大」面板:逐信号条目
|
||||
// (同一套排布与交互,只是输入框绑定的编辑目标与条目列表不同)。
|
||||
if ((kind == EdgePanelKind::MEAN || kind == EdgePanelKind::CENTER ||
|
||||
kind == EdgePanelKind::DIFF_AMP) &&
|
||||
m_tab != nullptr)
|
||||
{
|
||||
const bool meanMode = (kind == EdgePanelKind::MEAN);
|
||||
const bool diffMode = (kind == EdgePanelKind::DIFF_AMP);
|
||||
Lane& tabLane = m_tab->Lanes()[static_cast<std::size_t>(index)];
|
||||
for (std::size_t rowIndex = 0; rowIndex < layout.rowRefs.size(); ++rowIndex)
|
||||
{
|
||||
const SignalRef& ref = layout.rowRefs[rowIndex];
|
||||
const auto findItem = [&]() -> const Lane::MeanFilterItem*
|
||||
{
|
||||
return meanMode ? tabLane.FindMeanFilterItem(ref) : tabLane.FindEdgeCenterItem(ref);
|
||||
return meanMode ? tabLane.FindMeanFilterItem(ref)
|
||||
: (diffMode ? tabLane.FindDiffAmpItem(ref)
|
||||
: tabLane.FindEdgeCenterItem(ref));
|
||||
};
|
||||
// 第一排右侧的复选框 → 切换该信号的预览勾选状态。
|
||||
if (layout.rowChecks[rowIndex].Width > 0.0f &&
|
||||
@@ -3627,25 +3796,33 @@ namespace jdi
|
||||
{
|
||||
ToggleLaneMeanFilterSignal(index, ref);
|
||||
}
|
||||
else if (diffMode)
|
||||
{
|
||||
ToggleLaneDiffAmpSignal(index, ref);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToggleLaneEdgeCenterSignal(index, ref);
|
||||
}
|
||||
m_meanCurveCache.clear();
|
||||
m_edgeCenterCountCache.clear();
|
||||
m_diffAmpCurveCache.clear();
|
||||
InvalidateSceneCache();
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
}
|
||||
// 第二排的两个输入框 → 就地编辑该信号的均值点数 X / 阶数 N。
|
||||
// 第二排的两个输入框 → 就地编辑该信号的均值点数 X / 阶数 N
|
||||
//(差异放大面板是窗口点数 X / 放大倍数 N,交互完全一致)。
|
||||
if (layout.rowPoints[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowPoints[rowIndex], fx, fy))
|
||||
{
|
||||
const Lane::MeanFilterItem* item = findItem();
|
||||
m_editMeanRef = ref;
|
||||
BeginEdit(meanMode ? PlotEditTarget::MEAN_FILTER_POINTS
|
||||
: PlotEditTarget::EDGE_CENTER_POINTS,
|
||||
index, std::to_wstring(item != nullptr ? item->points : 5),
|
||||
: (diffMode ? PlotEditTarget::DIFF_AMP_POINTS
|
||||
: PlotEditTarget::EDGE_CENTER_POINTS),
|
||||
index,
|
||||
std::to_wstring(item != nullptr ? item->points : (diffMode ? 64 : 5)),
|
||||
layout.rowPoints[rowIndex]);
|
||||
return true;
|
||||
}
|
||||
@@ -3655,13 +3832,51 @@ namespace jdi
|
||||
const Lane::MeanFilterItem* item = findItem();
|
||||
m_editMeanRef = ref;
|
||||
BeginEdit(meanMode ? PlotEditTarget::MEAN_FILTER_ORDER
|
||||
: PlotEditTarget::EDGE_CENTER_ORDER,
|
||||
index, std::to_wstring(item != nullptr ? item->order : 1),
|
||||
: (diffMode ? PlotEditTarget::DIFF_AMP_ORDER
|
||||
: PlotEditTarget::EDGE_CENTER_ORDER),
|
||||
index,
|
||||
std::to_wstring(item != nullptr ? item->order : (diffMode ? 2 : 1)),
|
||||
layout.rowOrder[rowIndex]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (diffMode && layout.button.Width > 0.0f && UiPointInRect(layout.button, fx, fy))
|
||||
{
|
||||
// 「差异放大 → 构建为新信号」:与均值滤波同一套请求机制。
|
||||
const Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(index)];
|
||||
m_pendingDiffAmpItems.clear();
|
||||
for (const SignalRef& ref : lane.signals)
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
if (resolved.data == nullptr || !resolved.visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const Lane::DiffAmpItem* item = lane.FindDiffAmpItem(ref);
|
||||
if (item == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
DiffAmpBuild build;
|
||||
build.fileIndex = ref.fileIndex;
|
||||
build.signalIndex = ref.signalIndex;
|
||||
build.points = item->points;
|
||||
build.gain = item->order;
|
||||
m_pendingDiffAmpItems.push_back(build);
|
||||
}
|
||||
if (!m_pendingDiffAmpItems.empty())
|
||||
{
|
||||
const DiffAmpBuild& first = m_pendingDiffAmpItems.front();
|
||||
SetHint(L"正在构建差异放大信号(" + std::to_wstring(first.points) +
|
||||
L" 点 " + std::to_wstring(first.gain) + L" 倍,共 " +
|
||||
std::to_wstring(m_pendingDiffAmpItems.size()) + L" 条)…");
|
||||
m_pendingDiffAmpLane = index;
|
||||
}
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (meanMode && layout.button.Width > 0.0f && UiPointInRect(layout.button, fx, fy))
|
||||
{
|
||||
// 「构建为新信号」:只登记请求(含每个信号自己的 X/N),
|
||||
@@ -3844,6 +4059,9 @@ namespace jdi
|
||||
case EdgePanelKind::MEAN:
|
||||
SetLaneMeanFilter(lane, false);
|
||||
break;
|
||||
case EdgePanelKind::DIFF_AMP:
|
||||
SetLaneDiffAmp(lane, false);
|
||||
break;
|
||||
case EdgePanelKind::COUNT:
|
||||
default:
|
||||
SetLaneEdgeCount(lane, false);
|
||||
@@ -4810,6 +5028,16 @@ namespace jdi
|
||||
mix(static_cast<std::uint64_t>(item.points));
|
||||
mix(static_cast<std::uint64_t>(item.order));
|
||||
}
|
||||
// 「差异放大」的开关与每个被勾选信号各自的窗口点数 X / 放大倍数 N 同理。
|
||||
mix(Tab().Lanes()[laneIndex].diffAmp ? 1ull : 0ull);
|
||||
mix(static_cast<std::uint64_t>(Tab().Lanes()[laneIndex].diffAmpItems.size()));
|
||||
for (const Lane::DiffAmpItem& item : Tab().Lanes()[laneIndex].diffAmpItems)
|
||||
{
|
||||
mix(static_cast<std::uint64_t>(item.ref.fileIndex));
|
||||
mix(static_cast<std::uint64_t>(item.ref.signalIndex));
|
||||
mix(static_cast<std::uint64_t>(item.points));
|
||||
mix(static_cast<std::uint64_t>(item.order));
|
||||
}
|
||||
mix(signals.size());
|
||||
for (const SignalRef& ref : signals)
|
||||
{
|
||||
@@ -5622,6 +5850,9 @@ namespace jdi
|
||||
// 「均值滤波」开启时,为**被勾选**的信号各叠加一条多阶滑动平均曲线
|
||||
//(均值点数 X、阶数 N 由该信号自己的条目给出)。
|
||||
const bool meanFilterOn = Tab().Lanes()[static_cast<std::size_t>(laneIndex)].meanFilter;
|
||||
// 「差异放大」开启时同样只为**被勾选**的信号叠加曲线
|
||||
//(窗口点数 X、放大倍数 N 由该信号自己的条目给出)。
|
||||
const bool diffAmpOn = Tab().Lanes()[static_cast<std::size_t>(laneIndex)].diffAmp;
|
||||
|
||||
for (const SignalRef& ref : laneSignals)
|
||||
{
|
||||
@@ -5758,6 +5989,16 @@ namespace jdi
|
||||
PaintMeanFilterCurve(g, resolved, lane, item->points, item->order);
|
||||
}
|
||||
}
|
||||
// 差异放大曲线:同样是仅用于预览的叠加曲线,只在被勾选时绘制。
|
||||
if (diffAmpOn)
|
||||
{
|
||||
const Lane::DiffAmpItem* item =
|
||||
Tab().Lanes()[static_cast<std::size_t>(laneIndex)].FindDiffAmpItem(ref);
|
||||
if (item != nullptr)
|
||||
{
|
||||
PaintDiffAmpCurve(g, resolved, lane, item->points, item->order);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// g.Restore 也会还原绘制状态,此处直接恢复图形状态即可。
|
||||
@@ -6472,14 +6713,19 @@ namespace jdi
|
||||
DrawLabelOutline(g, labelRect, 3.0f * m_scale, ink.outline);
|
||||
UiText(g, text, m_fonts->monoSmall, labelRect, ink.text, TextAlign::CENTER);
|
||||
|
||||
// 「边沿计数(中心轴)」/「均值滤波」开启时,纵向光标还要额外标注**派生曲线**
|
||||
// 在该时刻的取值(它不是信号,因此单独标注,不并入上面的取值文案)。
|
||||
// 两条曲线的参数与语义完全一致,故共用同一段绘制逻辑。
|
||||
const auto paintDerivedCapsule = [&](int first, int second, const std::wstring& suffix)
|
||||
// 「边沿计数(中心轴)」/「均值滤波」/「差异放大」开启时,纵向光标还要额外标注
|
||||
// **派生曲线**在该时刻的取值(它不是信号,因此单独标注,不并入上面的取值文案)。
|
||||
// 三条曲线的参数与标注规则完全一致,故共用同一段绘制逻辑。
|
||||
const auto paintDerivedCapsule = [&](int first, int second, const std::wstring& suffix,
|
||||
bool diffAmp)
|
||||
{
|
||||
double derivedValue = 0.0;
|
||||
if (!MeanFilterValueAt(*resolved.file, *resolved.data, first, second, time,
|
||||
derivedValue))
|
||||
const bool ok =
|
||||
diffAmp ? DiffAmpValueAt(*resolved.file, *resolved.data, first, second, time,
|
||||
derivedValue)
|
||||
: MeanFilterValueAt(*resolved.file, *resolved.data, first, second, time,
|
||||
derivedValue);
|
||||
if (!ok)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -6498,8 +6744,12 @@ namespace jdi
|
||||
if (showDelta)
|
||||
{
|
||||
double otherDerived = 0.0;
|
||||
if (MeanFilterValueAt(*resolved.file, *resolved.data, first, second,
|
||||
otherTime, otherDerived))
|
||||
const bool otherOk =
|
||||
diffAmp ? DiffAmpValueAt(*resolved.file, *resolved.data, first, second,
|
||||
otherTime, otherDerived)
|
||||
: MeanFilterValueAt(*resolved.file, *resolved.data, first, second,
|
||||
otherTime, otherDerived);
|
||||
if (otherOk)
|
||||
{
|
||||
const double delta = derivedValue - otherDerived;
|
||||
centerText += (delta < 0.0) ? L"-" : L"+";
|
||||
@@ -6549,7 +6799,7 @@ namespace jdi
|
||||
m_tab->Lanes()[laneIndex].FindEdgeCenterItem(ref);
|
||||
if (centerItem != nullptr)
|
||||
{
|
||||
paintDerivedCapsule(centerItem->points, centerItem->order, L"中心轴");
|
||||
paintDerivedCapsule(centerItem->points, centerItem->order, L"中心轴", false);
|
||||
}
|
||||
}
|
||||
if (m_tab->Lanes()[laneIndex].meanFilter)
|
||||
@@ -6559,7 +6809,17 @@ namespace jdi
|
||||
m_tab->Lanes()[laneIndex].FindMeanFilterItem(ref);
|
||||
if (meanItem != nullptr)
|
||||
{
|
||||
paintDerivedCapsule(meanItem->points, meanItem->order, L"均值滤波");
|
||||
paintDerivedCapsule(meanItem->points, meanItem->order, L"均值滤波", false);
|
||||
}
|
||||
}
|
||||
if (m_tab->Lanes()[laneIndex].diffAmp)
|
||||
{
|
||||
// 差异放大曲线:同样是只标注被勾选的信号,尾缀为「差异化」。
|
||||
const Lane::DiffAmpItem* diffItem =
|
||||
m_tab->Lanes()[laneIndex].FindDiffAmpItem(ref);
|
||||
if (diffItem != nullptr)
|
||||
{
|
||||
paintDerivedCapsule(diffItem->points, diffItem->order, L"差异化", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6642,9 +6902,15 @@ namespace jdi
|
||||
const Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
const bool centerAxis = (kind == EdgePanelKind::CENTER);
|
||||
const bool meanFilter = (kind == EdgePanelKind::MEAN);
|
||||
const bool enabled = meanFilter ? lane.meanFilter : (centerAxis ? lane.edgeCenter : lane.edgeCount);
|
||||
const bool diffAmp = (kind == EdgePanelKind::DIFF_AMP);
|
||||
// 「均值滤波」与「差异放大」共用同一套「逐条目 + 底部按钮」排布。
|
||||
const bool itemized = (centerAxis || meanFilter || diffAmp);
|
||||
const bool hasButton = (meanFilter || diffAmp);
|
||||
const bool enabled =
|
||||
meanFilter ? lane.meanFilter : (diffAmp ? lane.diffAmp : (centerAxis ? lane.edgeCenter : lane.edgeCount));
|
||||
const bool open = meanFilter ? lane.meanFilterPanelOpen
|
||||
: (centerAxis ? lane.edgeCenterPanelOpen : lane.edgePanelOpen);
|
||||
: (diffAmp ? lane.diffAmpPanelOpen
|
||||
: (centerAxis ? lane.edgeCenterPanelOpen : lane.edgePanelOpen));
|
||||
if (!enabled || !open)
|
||||
{
|
||||
return layout;
|
||||
@@ -6669,11 +6935,10 @@ namespace jdi
|
||||
const float titleHeight = kEdgePanelTitleHeight * m_scale;
|
||||
const float rowHeight = kEdgePanelRowHeight * m_scale;
|
||||
const float pad = kEdgePanelPad * m_scale;
|
||||
// 「中心轴边沿计数」与「均值滤波」共用同一套「逐条目」排布(三排/两排),
|
||||
// 「中心轴边沿计数」「均值滤波」「差异放大」共用同一套「逐条目」排布(三排/两排),
|
||||
// 因此不再有独立的底部编辑行;只有「边沿计数」保留两行文本式排版。
|
||||
const float buttonHeight = meanFilter ? (kEdgePanelButtonHeight * m_scale) : 0.0f;
|
||||
const float buttonHeight = hasButton ? (kEdgePanelButtonHeight * m_scale) : 0.0f;
|
||||
const float rowsPerSignal = meanFilter ? 1.0f : 2.0f;
|
||||
const bool itemized = meanFilter || centerAxis;
|
||||
|
||||
// 逐条目面板:每个条目 = 第一排(信号名 + 复选框)+ 第二排(均值点数 X / 阶数 N)
|
||||
// + (中心轴)第三排(统计结果);后两排**只在该条目被勾选时才占位**,
|
||||
@@ -6692,7 +6957,9 @@ namespace jdi
|
||||
continue;
|
||||
}
|
||||
itemsHeight += meanRowHeight;
|
||||
const bool checked = meanFilter ? lane.MeanFilterEnabled(ref) : lane.EdgeCenterEnabled(ref);
|
||||
const bool checked = meanFilter ? lane.MeanFilterEnabled(ref)
|
||||
: (diffAmp ? lane.DiffAmpEnabled(ref)
|
||||
: lane.EdgeCenterEnabled(ref));
|
||||
if (checked)
|
||||
{
|
||||
itemsHeight += (kEdgePanelMeanEditHeight + kEdgePanelMeanRowGap) * m_scale;
|
||||
@@ -6702,7 +6969,10 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
}
|
||||
height = titleHeight + pad + itemsHeight + pad + buttonHeight;
|
||||
// 底部按钮行:上方与条目之间、下方与面板下边界之间各留一个 `pad`,
|
||||
// 与面板的左右内边距完全一致(原先按钮直接贴着下边框,看起来很不协调)。
|
||||
height = titleHeight + pad + itemsHeight + pad + buttonHeight +
|
||||
(hasButton ? pad : 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -6711,9 +6981,11 @@ namespace jdi
|
||||
|
||||
// 首次使用(位置为负)时自动放到绘图区右侧,并各网格依次错开,避免完全重叠。
|
||||
const double storedX = meanFilter ? lane.meanFilterPanelX
|
||||
: (centerAxis ? lane.edgeCenterPanelX : lane.edgePanelX);
|
||||
: (diffAmp ? lane.diffAmpPanelX
|
||||
: (centerAxis ? lane.edgeCenterPanelX : lane.edgePanelX));
|
||||
const double storedY = meanFilter ? lane.meanFilterPanelY
|
||||
: (centerAxis ? lane.edgeCenterPanelY : lane.edgePanelY);
|
||||
: (diffAmp ? lane.diffAmpPanelY
|
||||
: (centerAxis ? lane.edgeCenterPanelY : lane.edgePanelY));
|
||||
float left = (storedX >= 0.0)
|
||||
? static_cast<float>(storedX)
|
||||
: (m_plotRect.X + m_plotRect.Width - width - 16.0f * m_scale);
|
||||
@@ -6736,9 +7008,12 @@ namespace jdi
|
||||
if (itemized)
|
||||
{
|
||||
const float buttonRowHeight = kEdgePanelButtonHeight * m_scale;
|
||||
if (meanFilter)
|
||||
if (hasButton)
|
||||
{
|
||||
layout.button = gd::RectF(left + pad, top + height - buttonRowHeight,
|
||||
// 底部按钮与面板下边界之间留出与左右、与内容相同的 `pad` 间距
|
||||
//(原先按钮行直接贴着下边框,视觉上与其它元素不协调);
|
||||
// 面板高度已含这份 pad,因此这里只上移按钮行即可。
|
||||
layout.button = gd::RectF(left + pad, top + height - pad - buttonRowHeight,
|
||||
std::max(0.0f, width - pad * 2.0f), buttonRowHeight);
|
||||
}
|
||||
|
||||
@@ -6762,7 +7037,8 @@ namespace jdi
|
||||
left + width - pad - checkSize, rowY + (meanRowHeight - checkSize) * 0.5f, checkSize,
|
||||
checkSize));
|
||||
const bool checked = meanFilter ? lane.MeanFilterEnabled(ref)
|
||||
: lane.EdgeCenterEnabled(ref);
|
||||
: (diffAmp ? lane.DiffAmpEnabled(ref)
|
||||
: lane.EdgeCenterEnabled(ref));
|
||||
if (checked)
|
||||
{
|
||||
const float editTop = rowY + meanRowHeight;
|
||||
@@ -7132,6 +7408,17 @@ namespace jdi
|
||||
PaintDerivedCurve(g, resolved, lane, MeanFilterCurveFor(*resolved.data, points, order));
|
||||
}
|
||||
|
||||
void PlotView::PaintDiffAmpCurve(gd::Graphics& g, const ResolvedSignal& resolved,
|
||||
const LaneVisual& lane, int points, int gain) const
|
||||
{
|
||||
if (resolved.data == nullptr || resolved.file == nullptr || resolved.style == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 派生曲线一律使用同一段绘制逻辑与配色规则(差异放大曲线同样如此)。
|
||||
PaintDerivedCurve(g, resolved, lane, DiffAmpCurveFor(*resolved.data, points, gain));
|
||||
}
|
||||
|
||||
void PlotView::PaintEdgeCursor(gd::Graphics& g, const std::vector<LaneVisual>& lanes)
|
||||
{
|
||||
if (m_tab == nullptr || m_dataSet == nullptr)
|
||||
@@ -7236,13 +7523,17 @@ namespace jdi
|
||||
}
|
||||
|
||||
// ---- 统计浮动面板(最后绘制,覆盖在波形之上)----
|
||||
// 「边沿计数」「边沿计数(中心轴)」「均值滤波」共用同一套样式,
|
||||
// 仅标题、内容行与底部编辑行/按钮不同。
|
||||
for (int pass = 0; pass < 3; ++pass)
|
||||
// 「边沿计数」「边沿计数(中心轴)」「均值滤波」「差异放大」共用同一套样式,
|
||||
// 仅标题、内容行与底部按钮不同。
|
||||
for (int pass = 0; pass < kEdgePanelKindCount; ++pass)
|
||||
{
|
||||
const EdgePanelKind kind = kEdgePanelKinds[pass];
|
||||
const bool centerAxis = (kind == EdgePanelKind::CENTER);
|
||||
const bool meanFilter = (kind == EdgePanelKind::MEAN);
|
||||
const bool diffAmp = (kind == EdgePanelKind::DIFF_AMP);
|
||||
// 「均值滤波」与「差异放大」共用「逐条目 + 底部按钮」排布。
|
||||
const bool itemized = (centerAxis || meanFilter || diffAmp);
|
||||
const bool hasButton = (meanFilter || diffAmp);
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
const Lane& tabLane = tabLanes[index];
|
||||
@@ -7277,7 +7568,9 @@ namespace jdi
|
||||
m_theme->text, TextAlign::CENTER);
|
||||
const std::wstring title =
|
||||
meanFilter ? std::wstring(L"均值滤波")
|
||||
: ((centerAxis ? L"中心轴边沿计数" : L"边沿计数") + EdgeRangeSuffix());
|
||||
: (diffAmp ? std::wstring(L"差异放大")
|
||||
: ((centerAxis ? L"中心轴边沿计数" : L"边沿计数") +
|
||||
EdgeRangeSuffix()));
|
||||
UiText(g, title, m_fonts->uiSmall,
|
||||
gd::RectF(badgeRect.X + badgeRect.Width + 8.0f * m_scale, panel.Y,
|
||||
panel.Width - badgeRect.Width - 8.0f * m_scale - titleHeight,
|
||||
@@ -7305,10 +7598,10 @@ namespace jdi
|
||||
closeRect.X + crossPad, closeRect.Y + closeRect.Height - crossPad, crossColor,
|
||||
1.6f * m_scale);
|
||||
|
||||
// 「中心轴边沿计数」/「均值滤波」面板都采用「逐条目」排布:
|
||||
// 第一排 = 名称 + 复选框,第二排 = 均值点数 X / 阶数 N(仅勾选的条目),
|
||||
// 「中心轴边沿计数」/「均值滤波」/「差异放大」面板都采用「逐条目」排布:
|
||||
// 第一排 = 名称 + 复选框,第二排 = 两个参数输入框(仅勾选的条目),
|
||||
// 中心轴再多第三排 = 对应的统计结果(同样仅勾选的条目)。
|
||||
if (meanFilter || centerAxis)
|
||||
if (itemized)
|
||||
{
|
||||
for (std::size_t rowIndex = 0; rowIndex < layout.rowRefs.size(); ++rowIndex)
|
||||
{
|
||||
@@ -7321,7 +7614,8 @@ namespace jdi
|
||||
const std::uint32_t signalColor = StyleColor(*resolved.style);
|
||||
const gd::RectF& checkRect = layout.rowChecks[rowIndex];
|
||||
const bool checked = meanFilter ? tabLane.MeanFilterEnabled(ref)
|
||||
: tabLane.EdgeCenterEnabled(ref);
|
||||
: (diffAmp ? tabLane.DiffAmpEnabled(ref)
|
||||
: tabLane.EdgeCenterEnabled(ref));
|
||||
|
||||
// 名称:第一排左侧,避开右侧复选框。
|
||||
const float nameWidth =
|
||||
@@ -7338,15 +7632,26 @@ namespace jdi
|
||||
continue;
|
||||
}
|
||||
|
||||
// 第二排:两个输入框,左侧标“均值点数 X”,右侧标“阶数 N”。
|
||||
const Lane::MeanFilterItem* item = meanFilter ? tabLane.FindMeanFilterItem(ref)
|
||||
: tabLane.FindEdgeCenterItem(ref);
|
||||
const int shownPoints = (item != nullptr) ? item->points : 5;
|
||||
const int shownOrder = (item != nullptr) ? item->order : 1;
|
||||
const PlotEditTarget pointsTarget = meanFilter ? PlotEditTarget::MEAN_FILTER_POINTS
|
||||
: PlotEditTarget::EDGE_CENTER_POINTS;
|
||||
const PlotEditTarget orderTarget = meanFilter ? PlotEditTarget::MEAN_FILTER_ORDER
|
||||
: PlotEditTarget::EDGE_CENTER_ORDER;
|
||||
// 第二排:两个输入框。均值滤波是“均值点数 X / 阶数 N”,
|
||||
// 差异放大是“窗口点数 X / 放大倍数 N”(同一套排版)。
|
||||
const Lane::MeanFilterItem* item =
|
||||
meanFilter ? tabLane.FindMeanFilterItem(ref)
|
||||
: (diffAmp ? tabLane.FindDiffAmpItem(ref)
|
||||
: tabLane.FindEdgeCenterItem(ref));
|
||||
const int shownPoints =
|
||||
(item != nullptr) ? item->points : (diffAmp ? 64 : 5);
|
||||
const int shownOrder =
|
||||
(item != nullptr) ? item->order : (diffAmp ? 2 : 1);
|
||||
const wchar_t* pointsLabel = diffAmp ? L"窗口点数 X" : L"均值点数 X";
|
||||
const wchar_t* orderLabel = diffAmp ? L"放大倍数 N" : L"阶数 N";
|
||||
const PlotEditTarget pointsTarget =
|
||||
meanFilter ? PlotEditTarget::MEAN_FILTER_POINTS
|
||||
: (diffAmp ? PlotEditTarget::DIFF_AMP_POINTS
|
||||
: PlotEditTarget::EDGE_CENTER_POINTS);
|
||||
const PlotEditTarget orderTarget =
|
||||
meanFilter ? PlotEditTarget::MEAN_FILTER_ORDER
|
||||
: (diffAmp ? PlotEditTarget::DIFF_AMP_ORDER
|
||||
: PlotEditTarget::EDGE_CENTER_ORDER);
|
||||
const auto paintField = [&](const gd::RectF& rect, const wchar_t* label,
|
||||
int value, PlotEditTarget target)
|
||||
{
|
||||
@@ -7383,8 +7688,8 @@ namespace jdi
|
||||
m_theme->textStrong, TextAlign::CENTER);
|
||||
}
|
||||
};
|
||||
paintField(layout.rowPoints[rowIndex], L"均值点数 X", shownPoints, pointsTarget);
|
||||
paintField(layout.rowOrder[rowIndex], L"阶数 N", shownOrder, orderTarget);
|
||||
paintField(layout.rowPoints[rowIndex], pointsLabel, shownPoints, pointsTarget);
|
||||
paintField(layout.rowOrder[rowIndex], orderLabel, shownOrder, orderTarget);
|
||||
|
||||
// 第三排(中心轴):该信号在当前参数下的上升沿/下降沿统计结果。
|
||||
if (centerAxis && rowIndex < layout.rowStats.size() &&
|
||||
@@ -7414,7 +7719,7 @@ namespace jdi
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (meanFilter || centerAxis)
|
||||
if (itemized)
|
||||
{
|
||||
// 逐条目绘制已在上面完成。
|
||||
continue;
|
||||
@@ -7438,8 +7743,8 @@ namespace jdi
|
||||
rowY += rowHeight * 2.0f;
|
||||
}
|
||||
|
||||
// 均值滤波面板底部按钮行:「构建为新信号」(悬停高亮)。
|
||||
if (meanFilter)
|
||||
// 逐条目面板底部按钮行:「构建为新信号」(悬停高亮)。
|
||||
if (hasButton)
|
||||
{
|
||||
const gd::RectF& buttonRect = layout.button;
|
||||
if (buttonRect.Width > 0.0f)
|
||||
@@ -7453,7 +7758,7 @@ namespace jdi
|
||||
UiFillRoundRect(g, inner, 4.0f * m_scale,
|
||||
hot ? WithAlpha(m_theme->accent, 0x3A) : m_theme->cardBg);
|
||||
UiDrawRoundRect(g, inner, 4.0f * m_scale, m_theme->accent, 1.2f);
|
||||
UiText(g, kMeanFilterBuildLabel, m_fonts->uiSmall, inner, m_theme->accent,
|
||||
UiText(g, kBuildSignalLabel, m_fonts->uiSmall, inner, m_theme->accent,
|
||||
TextAlign::CENTER);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user