diff --git a/Components/PlotView/Src/PlotView.cpp b/Components/PlotView/Src/PlotView.cpp index 600e7ba..46a300e 100644 --- a/Components/PlotView/Src/PlotView.cpp +++ b/Components/PlotView/Src/PlotView.cpp @@ -59,6 +59,51 @@ namespace jdi const wchar_t* const kMeanFilterBuildLabel = L"构建为新信号"; /**< 均值滤波面板底部按钮文案。 */ const std::size_t kEdgeCountCacheLimit = 48; /**< 边沿统计缓存条目上限(超出时淘汰最早的一条)。 */ + /** + * @brief 取折线(相邻采样点之间线性连接)在给定时刻的取值。 + * + * 包络是按像素列聚合的,因此某一段折线可能**跨越列边界**:跨越边界的那一小段 + * 在两个列里都不属于"落在本列内的采样点",只看列内采样点就会把它整段丢掉。 + * 该函数用于在列边界处取回这段折线的取值,使相邻两列的包络严丝合缝。 + * + * @param [IN] time 时间轴。 + * @param [IN] values 采样值。 + * @param [IN] count 可用采样点数量。 + * @param [IN] firstIndex 第一个时间不小于查询时刻的采样下标(相交线段为 firstIndex-1 → firstIndex)。 + * @param [IN] t 查询时刻。 + * @param [OUT] out 输出取值。 + * @return 折线覆盖该时刻且取到有效值返回 true。 + */ + bool PolylineValueAt(const std::vector& time, const std::vector& values, + std::size_t count, std::size_t firstIndex, double t, double& out) + { + if (firstIndex == 0 || firstIndex >= count) + { + // 该时刻之前(或之后)没有采样点,折线并未延伸到这里。 + return false; + } + const double t0 = time[firstIndex - 1]; + const double t1 = time[firstIndex]; + const double v0 = values[firstIndex - 1]; + const double v1 = values[firstIndex]; + if (!std::isfinite(t0) || !std::isfinite(t1) || !std::isfinite(v0) || !std::isfinite(v1) || + !(t1 > t0)) + { + return false; + } + double ratio = (t - t0) / (t1 - t0); + if (ratio < 0.0) + { + ratio = 0.0; + } + else if (ratio > 1.0) + { + ratio = 1.0; + } + out = v0 + (v1 - v0) * ratio; + return true; + } + /** * @brief 统计相邻采样对 (k-1, k) 中穿越判定基准的边沿数量。 * @@ -3929,13 +3974,6 @@ namespace jdi std::lower_bound(time.begin() + static_cast(i0), rangeEnd, t1) - time.begin()); - if (i1 <= i0) - { - // 该列没有采样点:说明数据在此处存在时间间隙,断开当前折线。 - inSegment = false; - flushBandSegment(); - continue; - } double low = std::numeric_limits::infinity(); double high = -std::numeric_limits::infinity(); @@ -3943,8 +3981,8 @@ namespace jdi std::size_t highOrder = 0; bool broken = false; - // 一列至少覆盖两个分组时才用分组层(否则直接扫原始采样更准且更省)。 - if (hasCoarse && (i1 - i0) >= 2 * coarseStride) + // (一)落在本列区间内的采样点。 + if (i1 > i0 && hasCoarse && (i1 - i0) >= 2 * coarseStride) { const std::size_t firstBucket = i0 / coarseStride; const std::size_t lastBucket = @@ -3971,7 +4009,7 @@ namespace jdi } } } - else + else if (i1 > i0) { for (std::size_t index = i0; index < i1; ++index) { @@ -3995,9 +4033,48 @@ namespace jdi } } + // (二)跨越列边界的两段折线在边界处的取值。 + // + // 波形按相邻采样点之间的**直线**绘制,而包络是按像素列聚合的。采样间隔 + // 可能大于一个像素列(本仓库的 Problem_Data.csv 相邻采样间隔约 27µs 且不固定, + // 放大到 10ms/大格时一个像素列约 60µs),此时一次跃变的两个端点会分别落在 + // 相邻两列里:对每一列来说"落在本列内的采样点"都在同一边,于是这段竖向折线 + // 在两侧的包络里都不出现 → 波形在这些跃变处被整段丢掉,看上去就是"断开"。 + // + // 把列边界处的折线取值计入本列包络即可消除该现象:折线在列内是线性的, + // 其极值必然出现在采样点或列边界上,因此「列内采样点 + 两侧边界取值」就是 + // 该列包络的精确解,且相邻两列在边界处取到同一个值,包络带严丝合缝。 + double boundary = 0.0; + if (PolylineValueAt(time, values, count, i0, t0, boundary)) + { + if (boundary < low) + { + low = boundary; + lowOrder = (i0 > 0) ? (i0 - 1) : 0; + } + if (boundary > high) + { + high = boundary; + highOrder = (i0 > 0) ? (i0 - 1) : 0; + } + } + if (PolylineValueAt(time, values, count, i1, t1, boundary)) + { + if (boundary < low) + { + low = boundary; + lowOrder = i1; + } + if (boundary > high) + { + high = boundary; + highOrder = i1; + } + } + if (!(low <= high)) { - // 本列全是无效值。 + // 本列没有任何有效取值:折线尚未开始、已经结束,或该处数据缺失。 inSegment = false; flushBandSegment(); continue;