全局 Y 轴自适应跟随网格结构 + 组名悬浮 + 稀疏信号默认点线 + 单点光标吸附 + 放大自动点线

一、全局「Y 轴自适应」跟随网格结构
 * 新勾选信号产生的新网格现在会**继承**该开关:RebuildLanes 重建前先算
   previousAllAutoFit(非空且每个网格都开着 = 全局开启的正式定义),LaneCarry 加
   matched 标记,新出现的信号用它作为 fitYToSelection 的默认值。
 * 新增 PlotView::ReapplyGlobalAutoFitIfEnabled(),在 Layout()(网格数量变化 →
   Relayout)、RefreshLanes()(信号数量变化)、SplitLane() 之后立即重算一次。
   ⇒ 新勾选(按当前视界自适应)、多信号网格内取消显示某信号(剩余信号重新收敛)、
     拆分网格(每份各自自适应)三项都覆盖。

二、信号列表「组名悬浮」
 * 分组行被滚出列表上边界、而该组仍有信号留在列表里时,把分组行钉在列表顶部
   (不透明底 + 底部边界线);整组翻过去后本组组名消失,改由下一组悬浮。
 * 新增 SignalPanel::StickyGroupRowIndex() / PaintStickyGroupRow();RowRect 增加
   m_rowYOverrideActive/m_rowYOverrideY 覆盖行顶部坐标(RowRect 是行内名称框 / ✕ /
   徽标的唯一几何来源)。常规行循环跳过被钉住的那一行,避免同一行被画两次。

三、「少数数据点」信号的默认样式
 * 有效采样点 1 个 → 「点线」+ 最大线宽(10px);2 … 16 个 → 「点线」+ 2px;
   其余保持「线段」+ 1px。只在新建信号时生效(BuildSignalGroup 与
   InsertDerivedSignal),不覆盖用户设置。
 * 线宽范围常量统一到 DataModel.hpp(kSignalLineWidthMin/Max),SignalPanel 的线宽
   滑块改为引用它们(原本两处各自硬编码,容易漂移)。

四、单点信号的光标吸附(吸附而非卡住)
 * 旧逻辑要求时间轴至少 2 个采样点才能算「采样间距」,单点信号因此永远不吸附。
 * 新增 SignalData::singlePointTime(加载时与 ComputeStats 同趟扫描缓存该点时刻),
   SnapTimeToSample 新增半径分支(kCursorSnapPointRadiusPixels = 8 逻辑像素):
   半径内吸附到该点,半径外自由移动(移开即自由,不是把光标吸住)。

五、放大到「采样点稀疏」时自动启用点线
 * SignalStyle 新增运行态三字段:autoScatterActive / autoScatterSavedMode /
   autoScatterUserOverride。
 * AxisSparseInView()(采样点间距 > 半个绘图区宽度,与光标吸附上门限同源;单点时间轴
   不算稀疏)+ UpdateAutoScatterStyles():进入阈值保存原绘制方式并切「点线」,
   退出阈值(如适应视图)恢复;平移不改变缩放等级 ⇒ 结果稳定,不会样式抖动。
 * 自动启用期间用户手动改了绘制方式则退出时**不恢复**(尊重用户选择);该判定必须在
   恢复判断之前完成 —— 用户改完样式可能紧接着就退出阈值,否则会被覆盖。
 * 钩子:SetTimeView(视界唯一写入口)与 Layout / RefreshLanes / SplitLane。

六、验证(离屏 .preflight/check:SW_HIDE 窗口、不注入真实鼠标键盘、进程/线程 BELOW_NORMAL)
 65/65 通过(连跑 3 次稳定):[A1]/[A2]/[A3] 全局自适应、[B] 组名悬浮(含钉住行与自然
 位置逐像素一致、整组翻过后组名消失)、[C] 稀疏默认样式边界(1/2/5/16/17/1000 点)、
 [D] 单点吸附(半径内吸附、半径外自由、无残留粘滞)、[E] 自动点线(进入/退出/平移稳定/
 用户手动改过后不覆盖/再次进入再启用)。
 A/B:临时禁用六个机制后重跑 ⇒ 21 项如实失败(覆盖全部六项),证明断言有判别力。
 make release 零警告(用户正运行 Target.exe,链接用临时 -o 名字验证通过)。

七、规则同步
 Documents/agent_mem.md 新增 §9.29、§11.2 第 55–58 条硬约束与变更日志。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-15 14:10:13 +08:00
1 parent 3bca3b1201
commit 9f6de3bba4
7 files changed
+559 -5

No files matched your search

+55 -1
View File
@@ -5,6 +5,7 @@
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <string>
#include <atomic>
#include <vector>
@@ -22,9 +23,37 @@ namespace jdi
{
LINE = 0, /**< 相邻采样点之间以插值直线连接。 */
SCATTER = 1, /**< 仅绘制采样点散点。 */
LINE_SCATTER = 2 /**< 同时绘制插值直线与采样点散点。 */
LINE_SCATTER = 2 /**< 同时绘制插值直线与采样点散点(界面文案「点线」)。 */
};
/**
* @brief 样式允许的线宽范围(逻辑像素)。
*
* 信号面板的线宽滑块与所有线宽写入路径都以此为唯一依据;
* 稀疏信号(见 `kSparseSignalSampleLimit`)的初始线宽取其中的较大值。
*/
constexpr float kSignalLineWidthMin = 0.5f;
constexpr float kSignalLineWidthMax = 10.0f;
/**
* @brief 「少数数据点信号」的有效采样点数量上限(含)。
*
* 有效采样点不超过该值的信号,初始样式直接使用「点线」(`LINE_SCATTER`)并加粗到
* `kSparseSignalLineWidth`:采样点这么少时线段模式几乎什么也看不见
*(只有一个点时甚至完全画不出来)。正常密度的信号保持「线段 + 1px」。
*/
constexpr std::size_t kSparseSignalSampleLimit = 16;
/** @brief 稀疏信号(有效采样点 2 … `kSparseSignalSampleLimit`)的初始线宽。 */
constexpr float kSparseSignalLineWidth = 2.0f;
/**
* @brief **只含一个有效采样点**的信号的初始线宽(取样式允许的最大线宽)。
*
* 单个数据点在「点线」模式下就是一个圆点,半径与线宽成正比;取最大值才能看清。
*/
constexpr float kSinglePointSignalLineWidth = kSignalLineWidthMax;
/**
* @brief 绘图区拖拽缩放模式(由工具栏按钮切换)。
*/
@@ -146,6 +175,22 @@ namespace jdi
{
return std::fabs(offsetY) > 1.0e-9f || std::fabs(offsetTime) > 1.0e-15;
}
/**
* @brief 「放大到采样点稀疏时自动启用点线」的运行时状态。
*
* 视界放大到采样点间距超过半个绘图区宽度时(此时光标也会吸附数据点),
* 线段模式会退化成「看不出数据点在哪」,因此自动切到「点线」给用户提示;
* 视界回退到阈值之外(如适应视图)时自动恢复**原来的**绘制方式。
*
* * `autoScatterActive` —— 当前绘制方式是否由该机制自动设置的;
* * `autoScatterSavedMode` —— 自动启用**之前**的绘制方式(退出时恢复它);
* * `autoScatterUserOverride` —— 自动启用期间用户**手动**改过绘制方式:
* 此时退出不再恢复(用户的选择优先,用户明确要求)。
*/
bool autoScatterActive = false;
DrawMode autoScatterSavedMode = DrawMode::LINE;
bool autoScatterUserOverride = false;
};
/**
@@ -308,6 +353,15 @@ namespace jdi
double maxValue = 0.0; /**< 有限采样值最大值。 */
std::size_t finiteCount = 0; /**< 有限采样点数量。 */
/**
* @brief 只含**一个**有效采样点时的该点时刻(不存在该情况时为 NaN)。
*
* 在加载/插入信号时与 `ComputeStats` 同一趟扫描就地填充,供光标吸附使用:
* 只有一个数据点的信号,光标在其它区域自由移动、靠近该点时吸附过去
*(吸附而非“卡住”)。没有这个缓存就只能在鼠标移动时线性扫描采样值,大信号下不可接受。
*/
double singlePointTime = std::numeric_limits<double>::quiet_NaN();
/**
* @brief 粗粒度包络层:每 coarseStride 个采样一组的 min/max。
*