边沿计数改用「绘制值」+ 派生信号烘入时间偏移 + 图例托底统一为 GX 徽标款 + 调整二级面板向上展开
一、两个「边沿计数」按「画出来的曲线」判定(原先按原始采样值 → 应用过「调整」的信号恒为 0)
- 用户复述的权威定义:设定一个横向(Y 轴)光标,信号绘制出来的曲线从下往上穿过该幅值 = 上升沿,
从上往下穿过 = 下降沿。
- 症状:VERY_BIG_DATA 的「编码器B相」设系数 50 + 时间轴偏移 -20s,把光标拖到画面中间,
很规整的方波却统计出 0/0。
- 根因:EdgeCountsFor / EdgeCenterCountsFor 只比较 signal.values 的原始采样值,而横向光标读的是
屏幕上的值(已乘系数、已加偏移)→ 原始值域与阈值完全不相交。
- 修法:判定值一律取 ApplyDrawTransform(style, values[SourceIndexFor(style, count, index)]);
* 中心轴两侧必须套用同一套变换(drawn - drawnCenter);
* 水平镜像时曲线数组按源下标存储、而统计区间是显示下标 → 请求曲线窗口前必须把
[needFirst, needEnd) 映射为 [count-needEnd, count-needFirst);
* 缓存必须带样式指纹:EdgeCountEntry / EdgeCenterCountEntry 新增 styleKey
(EdgeStyleKey():gain/offsetY 按 1e6 定点 + absolute/mirrorVertical/mirrorHorizontal 位),
否则改完「调整」仍会命中变换前的旧计数。
二、时间区间之外的采样不得计入(区间边界点)
- 症状:A 在阈值以下、B 超过阈值;双光标下一个光标正好在 B 点,旧实现把 B 算成一个上升沿。
- 根因:EdgeIndexRange 在左端多取了一个采样点(if (begin > 0) --begin;)。
- 修法:删掉该外扩,收紧为「只有两个端点都落在 [begin, end) 内的采样对才计入」
(begin = lower_bound(rangeMin)、end = upper_bound(rangeMax),调用方从 begin+1 枚举到 end-1)。
- 结果:把区间起点从 times[crossing-1] 移到 times[crossing] 恰好少一个上升沿。
三、派生信号把 offsetTime 也烘进采样值(推翻 §9.19 的「唯一例外」)
- 用户要求:新信号默认不启用任何「调整」时就必须与网格里的预览逐点重合。
- 修法(Application::BuildDerivedSignal,在「值变换 + 水平镜像反序」之后追加一步):
新信号第 i 个采样 = 已变换的曲线在 time[i] - offsetTime 处的取值(取最近的采样点);
单调时间轴用只前进的游标(整段 O(n));平移后越界的采样置 NaN(与预览一致);
新信号样式全部保持默认(gain=1 / absolute=false / 两个镜像=false / offsetY=0 / offsetTime=0)。
- 退化路径:时间轴非单调或时间列缺失时无法按下标推进取样,退回把 offsetTime 带在样式上
(两条路径的显示效果相同)。
四、图例托底统一为 GX 编号徽标同款 + 新增更小字体 uiTiny
- 托底高度 = kLaneBadgeHeight、上下边界与 LaneBadgeRect() 完全一致、圆角 = 高度的一半(胶囊形)、
配色与描边与徽标相同(选中态 accentSoft/accent)、水平内边距 = 胶囊半径。
- 文本改用新增字体 UiFonts::uiTiny(10 逻辑 px):原 uiSmall(12px) 几乎顶满 17px 高的胶囊。
- LegendItem 拆出 plateWidth(托底宽 = 内容宽 + 2×内边距),条目步进 = plateWidth + kLegendItemGap;
kLegendPlateRadius 已删除(半径一律由徽标高度推出)。
五、「系数/偏移」「镜像」二级面板改为向上展开
- PopupMenu::Open(..., bool expandUp = false):true 时 (x, y) 是菜单的左下角、菜单向上展开;
BuildLayout 增加同参数并记录 m_buildExpandUp(RebuildLayout 必须复用,否则数值回写触发的重排
会让面板跳回向下展开)。
- SignalPanel::OpenGainMenu / OpenMirrorMenu:锚点 = (button.X, button.Y - kAdjustMenuGap),
即「面板左下角对准按钮左上角、再略微上去一点」(kAdjustMenuGap = 4 逻辑像素)。
六、验证(全部离屏,不占用用户的鼠标 / 键盘 / 屏幕)
- .preflight/uitest 新增 [AR]:
* 边沿计数 plain / gain=50 / offsetY / mirrorVertical / mirrorHorizontal 逐个与「独立参考实现」
逐位一致;offsetY 用例给出 A/B:按原始采样值算会得到 7/6 个边沿(正是用户报告的缺陷);
* 区间边界:起点从 crossing-1 移到 crossing 恰好少一个上升沿;
* 中心轴:absolute + offsetY 的样式下与参考实现逐位一致(32234 个边沿量级);
* 派生信号:无偏移时全部 128 万采样逐点等于 transform(滤波);带 +10 个平均采样周期的偏移时,
抽样点等于 transform(滤波) 在 time[i] - offsetTime(最近采样)处的取值;样式全默认;
* 图例托底:height == 徽标高度、Y == 徽标 Y、radius == height/2、色块与文本都在托底之内;
* 调整面板:菜单左边界 == 按钮左边界、菜单底 == 按钮顶 - 4*scale、且不覆盖按钮(两个按钮各测一次);
* GX 拖拽让位:±0.6H 各让位 1 格(目标 0 / 3),±0.4H 与恰好 ±0.5H 都不让位(严格「超过」)。
- 同步修正 harness 里两处「旧规则」断言(否则必然 FAIL 并误导后来者):
[AP] 「派生信号继承调整变换」→ 「样式默认 + 采样值按 time[i]-offsetTime 烘焙」;
[AN] 「图例带无托底(网格线可透出)」→ 「装饰矩形逐块、无一块跨越整条带、未被托底覆盖的列
仍能看到底层内容」。
- 合计 722/722 通过;make release 零警告(本机 GCC 15.2.0 UCRT;Target.exe 被占用时链接会报
Permission denied,已用临时 -o 名字验证链接正确性)。
规则同步:Documents/agent_mem.md 新增 §9.21,§9.19 的「offsetTime 仍需继承」标注为已作废,
新增 §11.2 第 21–25 条硬约束,并补变更日志一行。
This commit is contained in:
1 parent
1b3ab4ac09
commit
a2a38236fe
9 files changed
+434
-118
No files matched your search
@@ -1535,9 +1535,10 @@ namespace jdi
|
||||
// * 数值域变换(系数 → 绝对值 → 垂直镜像 → Y 偏移)**与**水平镜像(数据反序)
|
||||
// 全部作用在派生采样值上;
|
||||
// * 于是新信号**用默认外观**绘制出来就与预览曲线逐位一致。
|
||||
// * ⚠️ **时间轴偏移是唯一的例外**:它改变的是"绘制在哪个时刻",而派生信号与源信号
|
||||
// 共用同一个文件时间轴,无法把时间平移烘进采样数据,因此仍由新信号的样式携带
|
||||
// (否则新信号会相对预览整体横向错位)。
|
||||
// * ⚠️ **时间轴偏移也一并烘进数据**(见 2c:按 `time[i] - offsetTime` 重采样)。
|
||||
// 新信号与源信号共用同一个文件时间轴,因此时间平移只能写进采样值 —— 这样
|
||||
// 新信号**不启用任何「调整」**就与预览逐点重合(用户明确要求)。
|
||||
// 只有"无法烘值"的退化情形才退回把 `offsetTime` 带在样式上(见 3b)。
|
||||
SignalStyle sourceTransform;
|
||||
bool hasSourceStyle = false;
|
||||
{
|
||||
@@ -1597,6 +1598,55 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
|
||||
// 2c. 时间轴偏移:把 `offsetTime` 也**重采样烘进采样值**。
|
||||
//
|
||||
// 预览曲线是按「源信号样式」绘制的,它的时间轴偏移会把曲线整体横向平移。
|
||||
// 派生信号与源信号**共用同一个文件时间轴**,所以时间平移本无法写进"时间列";
|
||||
// 但用户明确要求:**新信号不启用任何「调整」也要与预览逐点重合**
|
||||
//(新信号的「调整」面板必须是默认值,不允许靠"继承 offsetTime"来表达)。
|
||||
// 因此这里把平移直接写进数据:
|
||||
// * 新信号第 i 个采样 = 预览曲线在 `time[i]` 时刻显示的值
|
||||
// = 已变换的曲线在 `time[i] - offsetTime` 处的取值(取最近的采样点);
|
||||
// * 平移后落在数据范围之外的采样置 NaN —— 与预览"该处没有曲线"完全一致。
|
||||
// ⚠️ 一次整段重采样需要一份等长的临时缓冲(千万级采样 ≈ 数十 MB),用完立即释放;
|
||||
// 这是"构建为新信号"的一次性操作,不在交互路径上。
|
||||
// ⚠️ 时间轴非单调时无法按下标单调推进取样(只能整表扫描),此时退回"继承
|
||||
// `offsetTime`"(见 3b):两条路径的**显示效果相同**,只是偏移留在样式里。
|
||||
bool timeShiftBaked = false;
|
||||
if (hasSourceStyle && sourceTransform.offsetTime != 0.0 && file->timeMonotonic)
|
||||
{
|
||||
const std::vector<double>& times = file->time;
|
||||
const std::size_t total = derived.values.size();
|
||||
if (total > 1 && times.size() >= total)
|
||||
{
|
||||
const double shift = sourceTransform.offsetTime;
|
||||
std::vector<double> shifted(total, std::numeric_limits<double>::quiet_NaN());
|
||||
// 目标时刻 `times[index] - shift` 随 index 单调不减 → 游标只前进,整体 O(n)。
|
||||
std::size_t probe = 0;
|
||||
for (std::size_t index = 0; index < total; ++index)
|
||||
{
|
||||
const double target = times[index] - shift;
|
||||
if (!std::isfinite(target) || target < times[0] || target > times[total - 1])
|
||||
{
|
||||
continue; // 平移后越界:该处没有曲线
|
||||
}
|
||||
while (probe + 1 < total && times[probe + 1] < target)
|
||||
{
|
||||
++probe;
|
||||
}
|
||||
std::size_t nearest = probe;
|
||||
if (probe + 1 < total &&
|
||||
std::fabs(times[probe + 1] - target) < std::fabs(times[probe] - target))
|
||||
{
|
||||
nearest = probe + 1;
|
||||
}
|
||||
shifted[index] = derived.values[nearest];
|
||||
}
|
||||
derived.values.swap(shifted);
|
||||
timeShiftBaked = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 插入到原信号的下方(数据层 + 各标签页引用重映射)。
|
||||
int newIndex = -1;
|
||||
// 插入派生信号会移动该文件的信号存储:先暂停后台计算,避免其读取已移动的数据。
|
||||
@@ -1611,10 +1661,10 @@ namespace jdi
|
||||
tab.RemapAfterSignalInsert(request.fileIndex, newIndex);
|
||||
}
|
||||
|
||||
// 3b. 仅把**时间轴偏移**带到新信号上(唯一无法烘进数据的一项,见上面第 0 步说明)。
|
||||
// 其余「调整」项(系数 / 绝对值 / 镜像 / Y 偏移)保持在默认值 —— 它们已经
|
||||
// 烘进了采样数据,因此新信号不经过任何"调整"就与预览曲线完全一致。
|
||||
if (hasSourceStyle && sourceTransform.offsetTime != 0.0)
|
||||
// 3b. 时间轴偏移正常情况下已烘进采样值(见 2c),新信号的「调整」全部保持默认。
|
||||
// ⚠️ 只有"无法烘值"的退化情形(时间轴非单调 / 时间列缺失)才退回到把
|
||||
// `offsetTime` 带在新信号样式上 —— 无论如何都要让**显示**与预览一致。
|
||||
if (hasSourceStyle && sourceTransform.offsetTime != 0.0 && !timeShiftBaked)
|
||||
{
|
||||
if (SignalStyle* derivedStyle = m_dataSet.Style(SignalRef{request.fileIndex, newIndex}))
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user