实验性功能完善:面板交互修正 + 新增「边沿计数(中心轴)」

一、实验性功能浮动面板的交互修正(「边沿计数」与「边沿计数(中心轴)」共用)
- 面板只有**标题栏**可以拖拽:鼠标在标题栏显示抓握光标(IDC_SIZEALL),
  在面板主体(含统计行与底部编辑行)保持原始箭头,按下主体也不再拖动面板(仅消费点击,
  不会穿透到底层的平移/选区/信号拖拽)。
- ✕ 关闭按钮:鼠标悬停时高亮(danger 色底 + danger 色叉),但**不改变鼠标光标样式**。
- 为此新增 `EdgePanelLayout`(panel / title / close / edit / rows)与
  `EdgePanelLayoutFor(laneIndex, centerAxis)`,`QueryCursor`、`OnMouseMove`、`OnLButtonDown`
  与绘制统一按该布局判定。

二、新增「实验性功能 → 边沿计数(中心轴)」(带 √ 的切换项,按网格独立)
- 开启后为网格内**每条信号**叠加绘制一条"中心平均值曲线":
  每个数据点 = 自身与左右各 N 个数据点(共 2N+1 点,边界处按实际窗口)的平均值。
  用滑动窗口求和构建,整体 O(n),不额外占用前缀和内存;结果按 (信号, N) 缓存
  (deque 保证引用有效),数据层变化或 N 改变时清空。
- 该曲线只是叠加显示,**不会**加入左侧信号列表;
  为与波形区分,颜色按背景明暗把信号色向黑/白方向拉远(浅色主题加深、深色主题提亮),
  线宽略粗于原信号。
- 纵向光标仍会标注这条曲线的取值:在原取值胶囊之外额外绘制一个「中 <值>」胶囊
  (与曲线同色、半透明),并复用统一的避让逻辑(PlaceCursorLabel,已抽成函数)。
- 统计规则:**信号从下往上越过中心平均值曲线 → 上升沿;从上往下穿过 → 下降沿**
  (比较 `signal[i] - center[i]` 的符号变化,NaN 视为断点)。
- 浮动面板标题为「中心轴边沿计数」,样式与「边沿计数」面板一致(GX 徽标 + 标题 + ✕ +
  每信号一行名称与「上升沿 N / 下降沿 M」,数字完整不省略、面板宽度按文本实测自适应);
  面板**底部固定一行「平滑点数 N」内联编辑设置**(新增 EditFilter::NUMBER 编辑目标
  PlotEditTarget::EDGE_CENTER_SMOOTH,夹在 0–1000000),始终位于面板最下方。
- 与双光标模式联动:双光标时标题追加「(光标区间内)」,只统计左右光标之间的区间;
  区间端点恒取两光标时间的 min/max,因此把任一光标拖到另一侧(左右交换)后依然正确。
- ✕ 关闭面板的同时关闭该网格的「边沿计数(中心轴)」功能(面板与曲线一并消失)。

验证(TestData/VERY_BIG_DATA.CSV,编码器A相,125%、1725x996):
- 面板光标形态探测:主体 = ARROW、标题栏 = SIZEALL、✕ = ARROW;
  从主体拖动面板不发生位移(截图核对)。
- ✕ 悬停高亮可见。
- 「边沿计数(中心轴)」:面板标题「中心轴边沿计数」、底部「平滑点数 N = 5」;
  中心平均值曲线(深蓝)清晰可见;把 N 改为 3000 后计数同步更新。
  用独立脚本 .preflight/centerprobe.ps1 按同一规则复算,与界面**逐位一致**:
  N=5 → 上升沿 2712920 / 下降沿 2712913;N=1000 → 2373176 / 2373176。
- 双光标联动:区间 [48.0211, 64.4051](面板标题带「(光标区间内)」)→ 349220 / 349220;
  把光标 1 的时间改到 100(越过光标 2 的 64.4051)后,区间变为 [64.4051, 100],
  计数 778085 / 778084,与按该区间复算的结果一致(±2 为光标时间显示值取整导致的边界差)。
- 纵向光标读数:右光标显示 `4.9417 (-0.071945)` + `中 4.94199`,
  左光标显示 `中 2.9165`(中心曲线取值单独标注)。
- 左侧信号列表仍只有原信号,未新增曲线条目。
- 回归:[1].CSV 上「边沿计数(光标区间内)」正常(Δt:151ms 200us);
  「边沿计数(中心轴)」在 [1].CSV 上曲线清晰可见(上升沿 199 / 下降沿 199)。
- make clean && make all 无警告无错误。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-09 11:23:15 +08:00
1 parent b884e7ecb4
commit 5182bd8aa3
5 files changed
+996 -206

No files matched your search

@@ -1451,6 +1451,12 @@ namespace jdi
edgeCount.id = PLOT_CMD_LANE_EDGE_COUNT;
edgeCount.checked = m_plot.LaneEdgeCount(laneIndex);
experimental.submenu.push_back(edgeCount);
MenuItem edgeCenter;
edgeCenter.label = L"边沿计数(中心轴)";
edgeCenter.id = PLOT_CMD_LANE_EDGE_CENTER;
edgeCenter.checked = m_plot.LaneEdgeCenterCount(laneIndex);
experimental.submenu.push_back(edgeCenter);
}
items.push_back(experimental);
@@ -1557,6 +1563,10 @@ namespace jdi
// 「实验性功能 → 边沿计数」:切换本网格的横向光标与统计面板。
m_plot.ToggleLaneEdgeCount(laneIndex);
break;
case PLOT_CMD_LANE_EDGE_CENTER:
// 「实验性功能 → 边沿计数(中心轴)」:切换中心平均值曲线与统计面板。
m_plot.ToggleLaneEdgeCenterCount(laneIndex);
break;
case PLOT_CMD_FIT_ALL:
m_plot.FitAll();
break;
+138 -4
View File
@@ -52,7 +52,8 @@ namespace jdi
CURSOR_SPAN, /**< 编辑双光标之间的时间差(前方光标不动,后方光标随之移动)。 */
LANE_Y_MAX, /**< 编辑某网格的 Y 轴最大值。 */
LANE_Y_MIN, /**< 编辑某网格的 Y 轴最小值。 */
SIGNAL_NAME /**< 编辑图例中某信号的显示名称。 */
SIGNAL_NAME, /**< 编辑图例中某信号的显示名称。 */
EDGE_CENTER_SMOOTH /**< 编辑「中心轴边沿计数」的平滑点数 N。 */
};
/**
@@ -66,7 +67,8 @@ namespace jdi
PLOT_CMD_FIT_ALL = 4, /**< 全部网格与时间轴自适应。 */
PLOT_CMD_LANE_FIT_TIME = 5, /**< 时间轴自适应到本网格信号范围(按网格时间轴展开)。 */
PLOT_CMD_LANE_AUTO_VIEW = 6, /**< 切换本网格的「选区 Y 轴自适应」开关。 */
PLOT_CMD_LANE_EDGE_COUNT = 7 /**< 切换本网格的「实验性功能 → 边沿计数」开关。 */
PLOT_CMD_LANE_EDGE_COUNT = 7, /**< 切换本网格的「实验性功能 → 边沿计数」开关。 */
PLOT_CMD_LANE_EDGE_CENTER = 8 /**< 切换本网格的「实验性功能 → 边沿计数(中心轴)」开关。 */
};
/**
@@ -407,6 +409,28 @@ namespace jdi
*/
bool ToggleLaneEdgeCount(int laneIndex);
/**
* @brief 判断某网格是否开启了「实验性功能 → 边沿计数(中心轴)」。
* @param [IN] laneIndex 网格下标。
* @return 开启返回 true。
*/
bool LaneEdgeCenterCount(int laneIndex) const;
/**
* @brief 设置某网格的「边沿计数(中心轴)」开关。
* @param [IN] laneIndex 网格下标。
* @param [IN] enabled 目标状态。
* @return 状态发生变化返回 true。
*/
bool SetLaneEdgeCenterCount(int laneIndex, bool enabled);
/**
* @brief 切换某网格的「边沿计数(中心轴)」开关。
* @param [IN] laneIndex 网格下标。
* @return 切换后的状态。
*/
bool ToggleLaneEdgeCenterCount(int laneIndex);
/**
* @brief 进入某网格 Y 轴上限/下限的内联输入状态。
* @param [IN] laneIndex 网格下标。
@@ -855,6 +879,90 @@ namespace jdi
*/
bool IsOnEdgeCursorLine(int x, int y, int& laneIndex) const;
/**
* @brief 实验性功能浮动面板的几何布局(边沿计数 / 中心轴边沿计数共用同一套样式)。
*/
struct EdgePanelLayout
{
gd::RectF panel; /**< 面板整体矩形。 */
gd::RectF title; /**< 标题栏(**唯一可拖拽区域**)。 */
gd::RectF close; /**< ✕ 关闭按钮。 */
gd::RectF edit; /**< 面板底部编辑行(无编辑内容时宽度为 0)。 */
int rows = 0; /**< 统计行数(每条可见信号一行)。 */
};
/**
* @brief 计算某个实验性功能面板的布局。
* @param [IN] laneIndex 网格下标。
* @param [IN] centerAxis 为 true 时取「边沿计数(中心轴)」面板,否则取「边沿计数」。
* @return 布局;面板未展开时各项均为空矩形。
*/
EdgePanelLayout EdgePanelLayoutFor(int laneIndex, bool centerAxis) const;
/**
* @brief 获取「边沿计数(中心轴)」浮动面板的矩形。
* @param [IN] laneIndex 网格下标。
* @return 面板矩形(未展开时为空矩形)。
*/
gd::RectF EdgeCenterPanelRect(int laneIndex) const;
/**
* @brief 获取中心平均值曲线(每个数据点 = 自身与左右各 N 点的平均值),结果带缓存。
* @param [IN] signal 目标信号。
* @param [IN] smooth 平滑半径 N(0 表示不平滑)。
* @return 曲线数值数组(长度与 `signal.values` 相同;缺失处为 NaN)。
*/
const std::vector<float>& CenterCurveFor(const SignalData& signal, int smooth) const;
/**
* @brief 取某个时刻的中心平均值曲线取值(用于纵向光标的取值标注)。
* @param [IN] file 信号所属文件。
* @param [IN] signal 目标信号。
* @param [IN] smooth 平滑半径 N。
* @param [IN] time 采样时刻。
* @param [OUT] value 输出取值。
* @return 取到有效值返回 true。
*/
bool CenterValueAt(const DataFile& file, const SignalData& signal, int smooth, double time,
double& value) const;
/**
* @brief 统计信号穿越“中心平均值曲线”的上/下边沿数量,结果带缓存。
* @param [IN] file 信号所属文件。
* @param [IN] signal 目标信号。
* @param [IN] smooth 平滑半径 N。
* @param [IN] hasRange 是否只统计某个时间区间(双光标模式下为光标区间)。
* @param [IN] rangeMin 区间左端点时间。
* @param [IN] rangeMax 区间右端点时间。
* @param [OUT] rising 上升沿数量(信号从下往上越过中心平均值曲线)。
* @param [OUT] falling 下降沿数量(信号从上往下穿过中心平均值曲线)。
*/
void EdgeCenterCountsFor(const DataFile& file, const SignalData& signal, int smooth,
bool hasRange, double rangeMin, double rangeMax, std::size_t& rising,
std::size_t& falling) const;
/**
* @brief 绘制某条信号的中心平均值曲线(叠加在原信号所在网格上)。
* @param [IN,OUT] g 绘图上下文。
* @param [IN] resolved 信号视图。
* @param [IN] lane 网格绘制信息。
* @param [IN] smooth 平滑半径 N。
*/
void PaintCenterCurve(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane,
int smooth) const;
/**
* @brief 放置一个光标取值胶囊:先夹到网格可见范围内,再向下(必要时向上)避让
* 已占用的标注矩形,最后登记到 `m_cursorLabelRects`。
* @param [IN,OUT] rect 胶囊矩形(Y 会被调整)。
* @param [IN] preferredTop 期望的顶边纵坐标(避让前)。
* @param [IN] textHeight 胶囊高度。
* @param [IN] clipTop 网格可见区上边界。
* @param [IN] clipBottom 网格可见区下边界。
*/
void PlaceCursorLabel(gd::RectF& rect, float preferredTop, float textHeight, float clipTop,
float clipBottom);
/**
* @brief 获取边沿统计面板的矩形(未展开时返回空矩形)。
* @param [IN] laneIndex 网格下标。
@@ -885,11 +993,12 @@ namespace jdi
std::size_t& falling) const;
/**
* @brief 计算边沿统计面板所需的宽度(排版与绘制共用,保证文字完整不被省略)。
* @brief 计算实验性功能面板所需的宽度(排版与绘制共用,保证文字完整不被省略)。
* @param [IN] laneIndex 网格下标。
* @param [IN] centerAxis 为 true 时取「边沿计数(中心轴)」面板。
* @return 面板宽度(像素)。
*/
float EdgePanelWidth(int laneIndex) const;
float EdgePanelWidth(int laneIndex, bool centerAxis) const;
/**
* @brief 判断边沿统计是否限定在双光标区间内(双光标模式)。
@@ -1050,9 +1159,34 @@ namespace jdi
};
mutable std::vector<EdgeCountEntry> m_edgeCountCache; /**< 边沿统计缓存(纯缓存,逻辑上不影响对象状态)。 */
/** 中心平均值曲线缓存项(按「信号指针 + 平滑半径」标识)。 */
struct CenterCurveEntry
{
const SignalData* signal = nullptr;
int smooth = 0;
std::vector<float> values; /**< 与 signal->values 等长;缺失处为 NaN。 */
};
/** 中心平均值曲线缓存:**deque** 保证 push_back 后已有元素引用仍然有效。 */
mutable std::deque<CenterCurveEntry> m_centerCurveCache;
/** 中心轴边沿统计缓存项。 */
struct EdgeCenterCountEntry
{
const DataFile* file = nullptr;
const SignalData* signal = nullptr;
int smooth = 0;
bool hasRange = false;
double rangeMin = 0.0;
double rangeMax = 0.0;
std::size_t rising = 0;
std::size_t falling = 0;
};
mutable std::vector<EdgeCenterCountEntry> m_edgeCenterCountCache; /**< 中心轴边沿统计缓存。 */
// 「边沿计数」横向光标 / 面板的拖拽状态。
int m_edgeDragLane = -1; /**< 正在拖动的横向光标所属网格。 */
int m_edgePanelDragLane = -1; /**< 正在拖动的统计面板所属网格。 */
bool m_edgePanelDragCenter = false; /**< 正在拖动的面板是否为「中心轴边沿计数」面板。 */
gd::PointF m_edgePanelDragOffset; /**< 抓取面板时的相对偏移(保持不跳变)。 */
/**
File diff suppressed because it is too large. Load diff
+21
View File
@@ -370,6 +370,27 @@ namespace jdi
/** 统计面板左上角纵坐标(<0 表示自动定位)。 */
double edgePanelY = -1.0;
/**
* @brief 「实验性功能 → 边沿计数(中心轴)」开关。
*
* 开启后为本网格内每条信号额外绘制一条"中心平均值曲线"(每个数据点取自身与左右各
* `edgeCenterSmooth` 个点的平均值),并按“信号穿越中心平均值曲线”统计上/下边沿。
* 这条曲线只是叠加显示,不会作为信号加入左侧列表。
*/
bool edgeCenter = false;
/** 中心平均值的平滑半径 N(左右各取 N 个数据点,窗口大小 2N+1)。 */
int edgeCenterSmooth = 5;
/** 中心轴统计浮动面板是否展开。 */
bool edgeCenterPanelOpen = true;
/** 中心轴统计面板左上角位置(客户区像素;<0 表示自动定位)。 */
double edgeCenterPanelX = -1.0;
/** 中心轴统计面板左上角纵坐标(<0 表示自动定位)。 */
double edgeCenterPanelY = -1.0;
};
/**
+15
View File
@@ -121,6 +121,11 @@ namespace jdi
bool edgePanelOpen = true;
double edgePanelX = -1.0;
double edgePanelY = -1.0;
bool edgeCenter = false;
int edgeCenterSmooth = 5;
bool edgeCenterPanelOpen = true;
double edgeCenterPanelX = -1.0;
double edgeCenterPanelY = -1.0;
};
// 先记下每个信号原来所在网格的网格自身状态:
@@ -136,6 +141,11 @@ namespace jdi
carry.edgePanelOpen = lane.edgePanelOpen;
carry.edgePanelX = lane.edgePanelX;
carry.edgePanelY = lane.edgePanelY;
carry.edgeCenter = lane.edgeCenter;
carry.edgeCenterSmooth = lane.edgeCenterSmooth;
carry.edgeCenterPanelOpen = lane.edgeCenterPanelOpen;
carry.edgeCenterPanelX = lane.edgeCenterPanelX;
carry.edgeCenterPanelY = lane.edgeCenterPanelY;
for (const SignalRef& ref : lane.signals)
{
previousCarry.emplace_back(ref, carry);
@@ -162,6 +172,11 @@ namespace jdi
lane.edgePanelOpen = carry.edgePanelOpen;
lane.edgePanelX = carry.edgePanelX;
lane.edgePanelY = carry.edgePanelY;
lane.edgeCenter = carry.edgeCenter;
lane.edgeCenterSmooth = carry.edgeCenterSmooth;
lane.edgeCenterPanelOpen = carry.edgeCenterPanelOpen;
lane.edgeCenterPanelX = carry.edgeCenterPanelX;
lane.edgeCenterPanelY = carry.edgeCenterPanelY;
};
// 收集全部可见信号引用。