Esc 退出缩放模式、修复拖拽时波形变模糊、加速超大文件的模式切换
Esc 行为(用户要求): - 网格内按 Esc 在取消拖拽选区的同时,退出全部缩放模式(时间轴缩放 / Y 轴缩放), 工具栏两个缩放按钮随之回到未选中态;时间光标按钮不受影响。 - 新增 PlotView::ExitZoomModes(),由 Application 的 VK_ESCAPE 分支与 CancelDrag 一起调用。 修复拖拽时波形变模糊(用户报告): - 上一轮为提升拖拽帧率引入了"交互降级":拖拽期间关闭抗锯齿 + 顶点预算减半 + 每列只扫 32 个采样点。实测这会让包络丢掉峰值、线条失去抗锯齿, 在选区放大 / 拖拽信号 / 拖拽光标时明显变"糊"。现已**全部移除**, 任何交互期间都按全质量绘制。 - 用"粗粒度包络层"替代降级来保证交互帧率: SignalData 新增 coarseMin/coarseMax/coarseStride(每 256 个采样一组存 min/max, 额外内存约为采样数据的 0.8%),在 BuildDataFile 统计 min/max 的同一个循环里顺带构建 (不额外扫数据)。折线包络改为读取"每列覆盖到的分组", 数据读取量从 O(可见采样数) 降到 O(像素列数 × 少量分组),**绘制结果与全量扫描完全一致**。 - 实测(VERY_BIG_DATA.CSV = 1.77GB、2580 万行 × 5 信号,全选显示): 视图变化时的包络构建由 176~196ms 降到 ≈0ms;平移拖拽整帧由约 336ms 降到 90~120ms。 - 注意:包络路径按分组判断无效值(整组无有效值才断线), 因此密集视图中孤立的 NaN 不再把波形切成两段(原先会),视觉上更干净。 加速超大文件的显示模式切换(用户报告): - 散点绘制由逐点 FillEllipse 改为把全部散点合并进一条 GraphicsPath 后一次 FillPath: 实测 2500 点由 63ms 降到 7ms(基准),程序内单条密集信号由约 51ms 降到约 22ms, 仍是抗锯齿圆形,观感与逐点绘制一致。 - 线段/散点/点线切换时不再有卡顿感;空闲 CPU 仍为 0.00%。 验证: - Esc:分别启用时间轴缩放 / Y 轴缩放后按 Esc,两个按钮均回到未选中(截图对比), 时间光标按钮保持选中;框选拖拽中按 Esc 时间轴保持 0.2~1.2 不变。 - 清晰度:光标拖拽中与静止时对同一区域做像素哈希,结果完全一致; 框选拖拽同样一致;信号拖拽中间态截图中源波形与虚影均为抗锯齿全质量。 - 超大文件:线段/散点/点线切换与拖拽的正常渲染(截图),1.77GB 文件加载过渡动画正常。 - 回归:菜单内联编辑 Y 轴上下限、选区 Y 轴自适应、图例点击选中、面板改名均正常。 - 内存:BIG_DATA 全选 122MB(粗粒度层约 +1MB);VERY_BIG 全选 1224MB(理论下限 1.25GB)。 - make clean && make all 零警告零错误。
This commit is contained in:
1 parent
45b44eae57
commit
04e98ee041
5 files changed
+169
-76
No files matched your search
@@ -109,6 +109,20 @@ namespace jdi
|
||||
double minValue = 0.0; /**< 有限采样值最小值。 */
|
||||
double maxValue = 0.0; /**< 有限采样值最大值。 */
|
||||
std::size_t finiteCount = 0; /**< 有限采样点数量。 */
|
||||
|
||||
/**
|
||||
* @brief 粗粒度包络层:每 coarseStride 个采样一组的 min/max。
|
||||
*
|
||||
* 大文件(千万级采样)的折线包络构建是 O(可见采样数) 的,每次视图变化都要重算,
|
||||
* 实测 1770 万行 × 5 信号需要约 200ms/帧,拖拽时严重掉帧。
|
||||
* 有了这一层(额外内存 ≈ 采样数的 0.8%),构建包络时只需读取“每列覆盖到的分组”,
|
||||
* 读取量降到约 1/5000,且**绘制结果与全量扫描完全一致**(分组内已存真实极值)。
|
||||
* 分组内没有有效值时存 ±inf,读取时跳过。
|
||||
*/
|
||||
std::vector<double> coarseMin; /**< 分组最小值。 */
|
||||
std::vector<double> coarseMax; /**< 分组最大值。 */
|
||||
std::size_t coarseStride = 0; /**< 分组的采样跨度;0 表示未构建。 */
|
||||
|
||||
SignalStyle style; /**< 显示外观(全局共享,跨标签页同步)。 */
|
||||
|
||||
/**
|
||||
|
||||
@@ -246,6 +246,16 @@ namespace jdi
|
||||
|
||||
// ============================ DataFile 构建 ============================
|
||||
|
||||
/**
|
||||
* @brief 粗粒度包络层每个分组覆盖的采样数。
|
||||
*
|
||||
* 16KB 的双精度分组可覆盖 1024 个采样(内存开销 = 采样数的 1.6%),
|
||||
* 但会让"每列采样数略多于分组跨度"的场景退化为逐点扫描。
|
||||
* 取 256 时:额外内存约为采样数的 0.8%,最坏情况的扫描量也能压低到
|
||||
* 每列 ≤256 个采样(2400 列 ≈ 0.6M 次读取,约 1ms)。
|
||||
*/
|
||||
const std::size_t kCoarseStride = 256;
|
||||
|
||||
/**
|
||||
* @brief 单条信号的取值范围统计结果。
|
||||
*/
|
||||
@@ -257,24 +267,53 @@ namespace jdi
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 统计一列采样的最小/最大值与有效点数量。
|
||||
* @brief 统计一列采样的最小/最大值与有效点数量,并顺带构建粗粒度包络层。
|
||||
*
|
||||
* 两者在同一个循环内完成,避免为粗粒度层多扫一遍数据(大文件下那是一整趟内存遍历)。
|
||||
*
|
||||
* @param [IN] values 采样列。
|
||||
* @param [OUT] coarseMin 粗粒度分组最小值输出。
|
||||
* @param [OUT] coarseMax 粗粒度分组最大值输出。
|
||||
* @return 统计结果。
|
||||
*/
|
||||
SignalStats ComputeStats(const std::vector<double>& values)
|
||||
SignalStats ComputeStats(const std::vector<double>& values, std::vector<double>& coarseMin,
|
||||
std::vector<double>& coarseMax)
|
||||
{
|
||||
SignalStats stats;
|
||||
double low = std::numeric_limits<double>::infinity();
|
||||
double high = -std::numeric_limits<double>::infinity();
|
||||
for (const double value : values)
|
||||
|
||||
const std::size_t count = values.size();
|
||||
const std::size_t buckets = (count + kCoarseStride - 1) / kCoarseStride;
|
||||
coarseMin.assign(buckets, std::numeric_limits<double>::infinity());
|
||||
coarseMax.assign(buckets, -std::numeric_limits<double>::infinity());
|
||||
|
||||
std::size_t bucket = 0;
|
||||
std::size_t bucketEnd = kCoarseStride;
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
if (index >= bucketEnd)
|
||||
{
|
||||
++bucket;
|
||||
bucketEnd += kCoarseStride;
|
||||
}
|
||||
const double value = values[index];
|
||||
if (std::isfinite(value))
|
||||
{
|
||||
++stats.finiteCount;
|
||||
low = std::min(low, value);
|
||||
high = std::max(high, value);
|
||||
if (value < coarseMin[bucket])
|
||||
{
|
||||
coarseMin[bucket] = value;
|
||||
}
|
||||
if (value > coarseMax[bucket])
|
||||
{
|
||||
coarseMax[bucket] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stats.finiteCount == 0)
|
||||
{
|
||||
stats.minValue = 0.0;
|
||||
@@ -377,7 +416,8 @@ namespace jdi
|
||||
}
|
||||
|
||||
signal.values = std::move(table.columns[column]);
|
||||
const SignalStats stats = ComputeStats(signal.values);
|
||||
const SignalStats stats = ComputeStats(signal.values, signal.coarseMin, signal.coarseMax);
|
||||
signal.coarseStride = kCoarseStride;
|
||||
signal.minValue = stats.minValue;
|
||||
signal.maxValue = stats.maxValue;
|
||||
signal.finiteCount = stats.finiteCount;
|
||||
|
||||
Reference in new issue
Block a user