「调整」行:系数/偏移 按钮 + 偏移调整(Y 轴/时间轴)二级面板;结算顺序加入偏移;派生信号继承调整变换
按钮与面板: - 「系数」按钮改名为「系数/偏移」;点击展开的二级面板在原「系数调整」输入框下方新增「偏移调整」: 与「系数调整」同一套绘制(标题 + 标题栏右侧的还原图标),下面两行分别是「Y轴」「时间轴」, 每行左侧说明文字、右侧内联输入框(整行数值框)。 - 「Y轴」默认 0.00,非编辑态**始终带正负号**(+0.00 / -12.50),小数不足两位时补两位、更高精度保留更多位。 - 「时间轴」默认 0.00,非编辑态显示与双光标差值同一套复合单位文本(`Δt:500ms`、`Δt:1h 1min 2s`); 进入编辑态时显示以**秒**为单位的数值,并在输入框最右侧提示单位 `s`。 - 「偏移调整」的还原图标把两个偏移恢复为默认值 0;「系数调整」的还原图标仍只重置系数。 - 面板/输入框的编辑初值与显示文本分离(`MenuItem::editValue` / `editSuffix`), 非编辑态(`Δt:…`)与编辑态(`3662`)不一致时也能正确回显。 语义与结算顺序: - 结算顺序变为 **系数 → 绝对值 → 镜像 → 偏移**(前三步不变)。 - Y 偏移:给绘制值加常数(波形上下移动);时间偏移:采样绘制在 `t + offsetTime` 处 (例如 t=1s、偏移 -0.5s → 画在 0.5s)。 - ⚠️ Y 偏移以**未镜像空间**为准:垂直镜像开关变化时同步取反偏移量, 使绘制等价于「先加上偏移再镜像」(开关来回切换可逆)。 - 绘制链路全部跟随偏移:`BuildPolyline`(逐点描边 / 像素列包络 / 跨列边界插值 / 粗粒度分组层, 保序的 Y 偏移直接加在分组极值上)、派生预览曲线、光标取值读数(含 Δt)、 可见区间 Y 轴自适应、整段自适应的区间映射;折线缓存键、场景指纹、样式快照与图层指纹都纳入两个偏移。 派生信号(实验性功能「构建为新信号」): - 修复「预览是调整后的波形、构建出来却按原始状态绘制」:新信号**继承源信号的调整变换** (系数/绝对值/两个镜像/两个偏移)——网格预览绘制的是 `变换(滤波结果)`, 而构建出的信号只存了滤波结果,因此必须把变换一并继承; 时间轴偏移**只能**靠继承(派生信号与源信号共用同一文件时间轴,无法把时间平移烘进数据)。 离屏验证(不干扰用户): - ⚠️ harness 现在在每次注入鼠标消息后**立即释放本窗口的鼠标捕获** (宿主 WM_LBUTTONDOWN 会 SetCapture,隐藏窗口同样会把用户的真实鼠标输入吸走); 后续消息本来就是直接投递,无需捕获。 - 新增 [AP]:按钮标签不裁切、面板 5 行顺序与「偏移调整」位于系数输入框下方、 Y轴/时间轴行的标签与输入框同行且默认值/单位提示正确、时间轴编辑态显示秒值并在最右侧提示 `s`、 Y 偏移提交/显示正负号/波形整体平移量、时间偏移提交/Δt 复合单位(含 3662 → Δt:1h 1min 2s)/ 编辑态为秒、**按"偏移若干个采样点"构造的精确几何验证**(采样被画到移位后的像素位置、且不再画在原处)、 读数跟随、两个还原图标、垂直镜像对 Y 偏移取反(可逆)、 以及派生信号继承变换;[AL] 补充「系数→绝对值→镜像→偏移」的顺序与区间映射(含 Y 偏移)。 - 合计 645/645 通过;make clean && make all 零警告。
This commit is contained in:
1 parent
2b02518f85
commit
fc77b0262f
11 files changed
+567
-252
No files matched your search
@@ -1443,9 +1443,11 @@ namespace jdi
|
||||
* @param [IN] count 可用采样点数。
|
||||
* @param [OUT] begin 区间起点(含)。
|
||||
* @param [OUT] end 区间终点(不含)。
|
||||
* @param [IN] timeShift 信号的时间偏移:采样画在 `t + timeShift` 处,因此可见采样对应的
|
||||
* 源时刻区间为 `[ViewMin - timeShift, ViewMax - timeShift]`(偏移 0 时同视界)。
|
||||
*/
|
||||
void VisibleIndexRange(const DataFile& file, std::size_t count, std::size_t& begin,
|
||||
std::size_t& end) const;
|
||||
std::size_t& end, double timeShift = 0.0) const;
|
||||
|
||||
/**
|
||||
* @brief 绘制某条信号的多次迭代均值滤波曲线(与中心平均值曲线同一配色派生规则)。
|
||||
@@ -1842,6 +1844,8 @@ namespace jdi
|
||||
bool absolute = false; /**< 绘制前是否取绝对值。 */
|
||||
bool mirrorVertical = false; /**< 绘制前是否按 Y=0 上下镜像。 */
|
||||
bool mirrorHorizontal = false; /**< 是否水平镜像(取值下标反序)。 */
|
||||
float offsetY = 0.0f; /**< Y 轴偏移。 */
|
||||
double offsetTime = 0.0; /**< 时间轴偏移(秒)。 */
|
||||
std::vector<gd::PointF> points; /**< 折线顶点。 */
|
||||
std::vector<std::size_t> breaks; /**< 每段折线在 points 中的起始下标。 */
|
||||
|
||||
@@ -2009,6 +2013,8 @@ namespace jdi
|
||||
bool absolute = false; /**< 是否取绝对值。 */
|
||||
bool mirrorVertical = false; /**< 是否按 Y=0 上下镜像。 */
|
||||
bool mirrorHorizontal = false; /**< 是否水平镜像(数据反序)。 */
|
||||
float offsetY = 0.0f; /**< Y 轴偏移。 */
|
||||
double offsetTime = 0.0; /**< 时间轴偏移(秒)。 */
|
||||
|
||||
/** @brief 相等比较(用于判断"逐信号外观样式是否变化")。 */
|
||||
bool operator==(const SceneStyleEntry& other) const
|
||||
@@ -2017,7 +2023,8 @@ namespace jdi
|
||||
lineWidth == other.lineWidth && mode == other.mode &&
|
||||
gain == other.gain && absolute == other.absolute &&
|
||||
mirrorVertical == other.mirrorVertical &&
|
||||
mirrorHorizontal == other.mirrorHorizontal;
|
||||
mirrorHorizontal == other.mirrorHorizontal && offsetY == other.offsetY &&
|
||||
offsetTime == other.offsetTime;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -501,143 +501,6 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 时间间隔的显示单位。
|
||||
*/
|
||||
struct DurationUnit
|
||||
{
|
||||
double scale = 1.0; /**< 1 个该单位等于多少秒。 */
|
||||
const wchar_t* name = L"s"; /**< 单位名称。 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 把时间间隔格式化为"Δt 语义"的复合单位文本。
|
||||
*
|
||||
* 规则(用户明确要求):
|
||||
* - 顶级单位高于 s(即 h / min)时**一路整合到 s**,例如 3662s → "1h 1min 2s";
|
||||
* - 顶级单位为 s 及以下时,若不是该单位的整数,则**向下一级递进**,
|
||||
* 例如 5.06s → "5s 60ms";整秒数不加下级单位,例如 5s → "5s";
|
||||
* - 顶级单位为 ps 时**不再递进次级单位**,例如 5.123ps → "5.123 ps"。
|
||||
*
|
||||
* @param [IN] seconds 时间间隔(秒)。
|
||||
* @return 不含 "Δt" 前缀的间隔文本。
|
||||
*/
|
||||
std::wstring FormatDurationCompound(double seconds)
|
||||
{
|
||||
if (!std::isfinite(seconds))
|
||||
{
|
||||
return L"—";
|
||||
}
|
||||
const double value = std::fabs(seconds);
|
||||
if (value <= 0.0)
|
||||
{
|
||||
return L"0s";
|
||||
}
|
||||
|
||||
// ---- 顶级单位高于 s(h / min):整合为 h/min/s(整数秒)----
|
||||
// 注意条件必须是 >= 60s:1~60s 属于"顶级单位为 s"的情况,需按下一条规则处理
|
||||
// (非整数秒要递进到 ms,例如 5.06s → "5s 60ms")。
|
||||
if (value >= 60.0)
|
||||
{
|
||||
const long long totalSeconds = static_cast<long long>(std::llround(value));
|
||||
long long hours = 0;
|
||||
long long minutes = 0;
|
||||
long long secs = totalSeconds;
|
||||
if (totalSeconds >= 3600)
|
||||
{
|
||||
hours = totalSeconds / 3600;
|
||||
secs = totalSeconds % 3600;
|
||||
}
|
||||
if (secs >= 60)
|
||||
{
|
||||
minutes = secs / 60;
|
||||
secs = secs % 60;
|
||||
}
|
||||
|
||||
std::wstring text;
|
||||
const auto append = [&text](long long amount, const wchar_t* unit)
|
||||
{
|
||||
if (amount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!text.empty())
|
||||
{
|
||||
text += L" ";
|
||||
}
|
||||
text += std::to_wstring(amount);
|
||||
text += unit;
|
||||
};
|
||||
append(hours, L"h");
|
||||
append(minutes, L"min");
|
||||
append(secs, L"s");
|
||||
if (!text.empty())
|
||||
{
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 顶级单位为 s:整数秒 + 可选 ms ----
|
||||
if (value >= 1.0)
|
||||
{
|
||||
const double whole = std::floor(value);
|
||||
const double fraction = value - whole;
|
||||
std::wstring text = FormatValue(whole) + L"s";
|
||||
const double remainderMs = fraction * 1000.0;
|
||||
if (remainderMs >= 1.0e-6)
|
||||
{
|
||||
text += L" ";
|
||||
text += FormatValue(remainderMs);
|
||||
text += L"ms";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
// ---- 顶级单位低于 s:ms / us / ns / ps ----
|
||||
const DurationUnit subUnits[] = {
|
||||
{1.0e-3, L"ms"}, {1.0e-6, L"us"}, {1.0e-9, L"ns"}, {1.0e-12, L"ps"},
|
||||
};
|
||||
const std::size_t subCount = sizeof(subUnits) / sizeof(subUnits[0]);
|
||||
std::size_t index = subCount - 1;
|
||||
for (std::size_t i = 0; i < subCount; ++i)
|
||||
{
|
||||
if (value >= subUnits[i].scale)
|
||||
{
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 顶级单位为 ps:不再递进次级单位(保留小数)。
|
||||
if (index + 1 >= subCount)
|
||||
{
|
||||
return FormatValue(value / subUnits[index].scale) + L" " + subUnits[index].name;
|
||||
}
|
||||
|
||||
const double scaled = value / subUnits[index].scale;
|
||||
double whole = std::floor(scaled);
|
||||
double fraction = scaled - whole;
|
||||
// 浮点误差可能导致 fraction 极接近 1(例如 4.9999999),归一化一下。
|
||||
if (fraction >= 1.0 - 1.0e-9)
|
||||
{
|
||||
whole += 1.0;
|
||||
fraction = 0.0;
|
||||
}
|
||||
|
||||
std::wstring text = FormatValue(whole);
|
||||
text += subUnits[index].name;
|
||||
// 仍是该单位的整数 → 不显示下级单位。
|
||||
const double remainder = fraction * subUnits[index].scale;
|
||||
const double inNext = remainder / subUnits[index + 1].scale;
|
||||
if (inNext >= 1.0e-6)
|
||||
{
|
||||
text += L" ";
|
||||
text += FormatValue(inNext);
|
||||
text += subUnits[index + 1].name;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 双光标差值的完整显示文本("Δt:" + 复合单位间隔)。
|
||||
* @param [IN] seconds 时间间隔(秒)。
|
||||
@@ -662,23 +525,6 @@ namespace jdi
|
||||
return FormatValue(seconds) + L"s";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 把时间间隔格式化为"秒"数值文本(不含单位),用于内联编辑。
|
||||
*
|
||||
* 编辑时的量级**始终是秒**:即使显示为 "1.5min",双击编辑的内容也是 90。
|
||||
*
|
||||
* @param [IN] seconds 时间间隔(秒)。
|
||||
* @return 形如 "60" 的文本。
|
||||
*/
|
||||
std::wstring FormatDurationSeconds(double seconds)
|
||||
{
|
||||
if (!std::isfinite(seconds))
|
||||
{
|
||||
return L"0";
|
||||
}
|
||||
return FormatValue(std::fabs(seconds));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按比例缩放颜色的 alpha 通道(用于提示浮层淡出)。
|
||||
* @param [IN] argb 原始 0xAARRGGBB 颜色。
|
||||
@@ -2204,7 +2050,7 @@ namespace jdi
|
||||
}
|
||||
|
||||
void PlotView::VisibleIndexRange(const DataFile& file, std::size_t count, std::size_t& begin,
|
||||
std::size_t& end) const
|
||||
std::size_t& end, double timeShift) const
|
||||
{
|
||||
begin = 0;
|
||||
end = count;
|
||||
@@ -2212,15 +2058,16 @@ namespace jdi
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 时间偏移:采样画在 `t + timeShift` 处,因此落在当前视界内的采样其源时刻区间要整体左移。
|
||||
const std::vector<double>& time = file.time;
|
||||
begin = static_cast<std::size_t>(
|
||||
std::lower_bound(time.begin(), time.end(), Tab().ViewMin()) - time.begin());
|
||||
std::lower_bound(time.begin(), time.end(), Tab().ViewMin() - timeShift) - time.begin());
|
||||
if (begin > 0)
|
||||
{
|
||||
--begin;
|
||||
}
|
||||
end = static_cast<std::size_t>(
|
||||
std::lower_bound(time.begin(), time.end(), Tab().ViewMax()) - time.begin());
|
||||
std::lower_bound(time.begin(), time.end(), Tab().ViewMax() - timeShift) - time.begin());
|
||||
if (end < count)
|
||||
{
|
||||
++end;
|
||||
@@ -3202,19 +3049,24 @@ namespace jdi
|
||||
}
|
||||
|
||||
const std::size_t count = std::min(file->time.size(), signal->values.size());
|
||||
const SignalStyle* style = m_dataSet->Style(ref);
|
||||
// 时间偏移:可见采样对应的源时刻区间要整体左移(画在 t + shift 处)。
|
||||
const double timeShift = (style != nullptr) ? style->offsetTime : 0.0;
|
||||
const double sourceMin = viewMin - timeShift;
|
||||
const double sourceMax = viewMax - timeShift;
|
||||
std::size_t begin = 0;
|
||||
std::size_t end = count;
|
||||
if (file->timeMonotonic)
|
||||
{
|
||||
// 时间单调时直接二分定位区间,百万级采样也只需扫过可见段。
|
||||
begin = static_cast<std::size_t>(
|
||||
std::lower_bound(file->time.begin(), file->time.end(), viewMin) - file->time.begin());
|
||||
std::lower_bound(file->time.begin(), file->time.end(), sourceMin) - file->time.begin());
|
||||
if (begin > 0)
|
||||
{
|
||||
--begin;
|
||||
}
|
||||
end = static_cast<std::size_t>(
|
||||
std::lower_bound(file->time.begin(), file->time.end(), viewMax) - file->time.begin());
|
||||
std::lower_bound(file->time.begin(), file->time.end(), sourceMax) - file->time.begin());
|
||||
if (end < count)
|
||||
{
|
||||
++end;
|
||||
@@ -3226,7 +3078,7 @@ namespace jdi
|
||||
if (!file->timeMonotonic)
|
||||
{
|
||||
const double t = file->time[index];
|
||||
if (!std::isfinite(t) || t < viewMin || t > viewMax)
|
||||
if (!std::isfinite(t) || t < sourceMin || t > sourceMax)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -5355,11 +5207,13 @@ namespace jdi
|
||||
const std::size_t sampleCount = std::min(resolved.file->time.size(), resolved.data->values.size());
|
||||
const double viewMin = Tab().ViewMin();
|
||||
const double viewMax = Tab().ViewMax();
|
||||
// 绘制前的数值变换(系数/绝对值/镜像)会改变顶点,必须进缓存键。
|
||||
// 绘制前的数值变换(系数/绝对值/镜像/偏移)会改变顶点,必须进缓存键。
|
||||
const float gain = (resolved.style != nullptr) ? resolved.style->gain : 1.0f;
|
||||
const bool absolute = (resolved.style != nullptr) && resolved.style->absolute;
|
||||
const bool mirrorVertical = (resolved.style != nullptr) && resolved.style->mirrorVertical;
|
||||
const bool mirrorHorizontal = (resolved.style != nullptr) && resolved.style->mirrorHorizontal;
|
||||
const float offsetY = (resolved.style != nullptr) ? resolved.style->offsetY : 0.0f;
|
||||
const double offsetTime = (resolved.style != nullptr) ? resolved.style->offsetTime : 0.0;
|
||||
|
||||
for (const PolylineCacheEntry& entry : m_polylineCache)
|
||||
{
|
||||
@@ -5368,7 +5222,8 @@ namespace jdi
|
||||
entry.plotWidth == m_plotRect.Width && entry.laneY == lane.rect.Y &&
|
||||
entry.laneHeight == lane.rect.Height && entry.laneYMin == lane.yMin &&
|
||||
entry.laneYMax == lane.yMax && entry.gain == gain && entry.absolute == absolute &&
|
||||
entry.mirrorVertical == mirrorVertical && entry.mirrorHorizontal == mirrorHorizontal)
|
||||
entry.mirrorVertical == mirrorVertical && entry.mirrorHorizontal == mirrorHorizontal &&
|
||||
entry.offsetY == offsetY && entry.offsetTime == offsetTime)
|
||||
{
|
||||
return &entry;
|
||||
}
|
||||
@@ -5397,6 +5252,8 @@ namespace jdi
|
||||
entry.absolute = absolute;
|
||||
entry.mirrorVertical = mirrorVertical;
|
||||
entry.mirrorHorizontal = mirrorHorizontal;
|
||||
entry.offsetY = offsetY;
|
||||
entry.offsetTime = offsetTime;
|
||||
|
||||
BuildPolyline(resolved, lane, entry.points, entry.breaks, entry.band, entry.bandBreaks,
|
||||
entry.bandMode, entry.bandSpiky);
|
||||
@@ -5432,18 +5289,24 @@ namespace jdi
|
||||
const double viewMin = Tab().ViewMin();
|
||||
const double viewMax = Tab().ViewMax();
|
||||
|
||||
// 时间偏移:采样画在 `t + offsetTime` 处,因此“落在当前视界内的采样”
|
||||
// 其源时刻位于 `[viewMin - offsetTime, viewMax - offsetTime]`。
|
||||
const double timeShift = resolved.style->offsetTime;
|
||||
const double sourceMin = viewMin - timeShift;
|
||||
const double sourceMax = viewMax - timeShift;
|
||||
|
||||
std::size_t begin = 0;
|
||||
std::size_t end = count;
|
||||
|
||||
if (resolved.file->timeMonotonic)
|
||||
{
|
||||
const std::vector<double>::const_iterator first = std::lower_bound(time.begin(), time.end(), viewMin);
|
||||
const std::vector<double>::const_iterator first = std::lower_bound(time.begin(), time.end(), sourceMin);
|
||||
begin = static_cast<std::size_t>(first - time.begin());
|
||||
if (begin > 0)
|
||||
{
|
||||
--begin;
|
||||
}
|
||||
const std::vector<double>::const_iterator last = std::lower_bound(time.begin(), time.end(), viewMax);
|
||||
const std::vector<double>::const_iterator last = std::lower_bound(time.begin(), time.end(), sourceMax);
|
||||
end = static_cast<std::size_t>(last - time.begin());
|
||||
if (end < count)
|
||||
{
|
||||
@@ -5497,8 +5360,9 @@ namespace jdi
|
||||
{
|
||||
const float marginPx =
|
||||
std::max(4.0f, std::max(1.0f, resolved.style->lineWidth) * m_scale + 3.0f);
|
||||
const double ta = XToTime(m_stripX0 - marginPx);
|
||||
const double tb = XToTime(m_stripX1 + marginPx);
|
||||
// 条带边界是**绘制时刻**,换算成源时刻要减去时间偏移。
|
||||
const double ta = XToTime(m_stripX0 - marginPx) - timeShift;
|
||||
const double tb = XToTime(m_stripX1 + marginPx) - timeShift;
|
||||
const double clipLo = std::min(ta, tb);
|
||||
const double clipHi = std::max(ta, tb);
|
||||
std::size_t sBegin = static_cast<std::size_t>(
|
||||
@@ -5546,13 +5410,16 @@ namespace jdi
|
||||
const std::vector<double>& coarseMin = resolved.data->coarseMin;
|
||||
const std::vector<double>& coarseMax = resolved.data->coarseMax;
|
||||
const std::size_t coarseStride = resolved.data->coarseStride;
|
||||
// ⚠️ 粗粒度分组只存了逐组的 min/max:取绝对值 / 中心反转会把两者互换,
|
||||
// ⚠️ 粗粒度分组只存了逐组的 min/max:取绝对值 / 垂直镜像会把两者互换,
|
||||
// 此时分组层无法再给出正确的包络,必须回退到逐采样点变换(见 `DrawTransformOrderPreserving`)。
|
||||
// Y 偏移是保序的(整体加常数),因此仍可用分组层 —— 只需把偏移一并加上。
|
||||
const bool hasCoarse =
|
||||
(coarseStride > 0) && !coarseMin.empty() && (coarseMin.size() == coarseMax.size()) &&
|
||||
DrawTransformOrderPreserving(*resolved.style);
|
||||
// 保序变换(正增益)下,分组极值直接乘增益即为变换后的极值(gain==1 时逐位不变)。
|
||||
// 保序变换(正系数 + Y 偏移)下:分组极值 × 系数 + 偏移 即为变换后的极值
|
||||
//(系数 1、偏移 0 时逐位不变)。
|
||||
const double coarseScale = static_cast<double>(resolved.style->gain);
|
||||
const double coarseOffset = static_cast<double>(resolved.style->offsetY);
|
||||
|
||||
bool inSegment = false;
|
||||
|
||||
@@ -5575,21 +5442,24 @@ namespace jdi
|
||||
std::size_t searchFrom = begin;
|
||||
for (std::size_t column = 0; column < columns; ++column)
|
||||
{
|
||||
// `t0/t1` 是**绘制时刻**的列边界;源时刻要减去时间偏移(偏移为 0 时二者相同)。
|
||||
const double t0 = viewMin + viewSpan * (static_cast<double>(column) / static_cast<double>(columns));
|
||||
const double t1 =
|
||||
viewMin + viewSpan * (static_cast<double>(column + 1) / static_cast<double>(columns));
|
||||
const double s0 = t0 - timeShift;
|
||||
const double s1 = t1 - timeShift;
|
||||
|
||||
// 列边界随 column 单调递增,从上一列的位置继续二分,
|
||||
// 把每次搜索的范围压到剩余区间,避免 2400 次全区间二分带来的缓存缺失。
|
||||
const std::size_t i0 = static_cast<std::size_t>(
|
||||
std::lower_bound(time.begin() + static_cast<std::ptrdiff_t>(searchFrom), rangeEnd, t0) -
|
||||
std::lower_bound(time.begin() + static_cast<std::ptrdiff_t>(searchFrom), rangeEnd, s0) -
|
||||
time.begin());
|
||||
searchFrom = i0;
|
||||
const std::size_t i1 = (column + 1 == columns)
|
||||
? end
|
||||
: static_cast<std::size_t>(
|
||||
std::lower_bound(time.begin() + static_cast<std::ptrdiff_t>(i0),
|
||||
rangeEnd, t1) -
|
||||
rangeEnd, s1) -
|
||||
time.begin());
|
||||
|
||||
double low = std::numeric_limits<double>::infinity();
|
||||
@@ -5611,8 +5481,8 @@ namespace jdi
|
||||
std::min(srcB / coarseStride, coarseMin.size() - 1);
|
||||
for (std::size_t bucket = firstBucket; bucket <= lastBucket; ++bucket)
|
||||
{
|
||||
const double bucketLow = coarseMin[bucket] * coarseScale;
|
||||
const double bucketHigh = coarseMax[bucket] * coarseScale;
|
||||
const double bucketLow = coarseMin[bucket] * coarseScale + coarseOffset;
|
||||
const double bucketHigh = coarseMax[bucket] * coarseScale + coarseOffset;
|
||||
if (!std::isfinite(bucketLow))
|
||||
{
|
||||
// 该分组全部是无效值:包络到此断开,后续另起一段。
|
||||
@@ -5667,7 +5537,7 @@ namespace jdi
|
||||
// 其极值必然出现在采样点或列边界上,因此「列内采样点 + 两侧边界取值」就是
|
||||
// 该列包络的精确解,且相邻两列在边界处取到同一个值,包络带严丝合缝。
|
||||
double boundary = 0.0;
|
||||
if (PolylineValueAt(time, values, count, *resolved.style, i0, t0, boundary))
|
||||
if (PolylineValueAt(time, values, count, *resolved.style, i0, s0, boundary))
|
||||
{
|
||||
boundary = ApplyDrawTransform(*resolved.style, boundary);
|
||||
if (boundary < low)
|
||||
@@ -5681,7 +5551,7 @@ namespace jdi
|
||||
highOrder = (i0 > 0) ? SourceIndexFor(*resolved.style, count, i0 - 1) : 0;
|
||||
}
|
||||
}
|
||||
if (PolylineValueAt(time, values, count, *resolved.style, i1, t1, boundary))
|
||||
if (PolylineValueAt(time, values, count, *resolved.style, i1, s1, boundary))
|
||||
{
|
||||
boundary = ApplyDrawTransform(*resolved.style, boundary);
|
||||
const std::size_t order = SourceIndexFor(*resolved.style, count, (i1 > 0) ? (i1 - 1) : 0);
|
||||
@@ -5779,7 +5649,8 @@ namespace jdi
|
||||
continue;
|
||||
}
|
||||
|
||||
const float x = TimeToX(t);
|
||||
// 时间偏移:该采样画在 `t + offsetTime` 处(例如 t=1s、偏移 -0.5s → 画在 0.5s)。
|
||||
const float x = TimeToX(t + timeShift);
|
||||
float y = ValueToY(value, lane);
|
||||
y = static_cast<float>(ClampDouble(y, limitTop, limitBottom));
|
||||
|
||||
@@ -6441,6 +6312,9 @@ namespace jdi
|
||||
mix(resolved.style->absolute ? 1ull : 0ull);
|
||||
mix(resolved.style->mirrorVertical ? 1ull : 0ull);
|
||||
mix(resolved.style->mirrorHorizontal ? 1ull : 0ull);
|
||||
// 偏移(Y 平移 + 时间平移)同样会改变波形。
|
||||
mixFloat(resolved.style->offsetY);
|
||||
mixFloat(static_cast<float>(resolved.style->offsetTime));
|
||||
}
|
||||
// 图例文案参与绘制,重命名必须让缓存失效。
|
||||
mixText(resolved.data->Title());
|
||||
@@ -6490,6 +6364,8 @@ namespace jdi
|
||||
entry.absolute = resolved.style->absolute;
|
||||
entry.mirrorVertical = resolved.style->mirrorVertical;
|
||||
entry.mirrorHorizontal = resolved.style->mirrorHorizontal;
|
||||
entry.offsetY = resolved.style->offsetY;
|
||||
entry.offsetTime = resolved.style->offsetTime;
|
||||
}
|
||||
lane.push_back(entry);
|
||||
}
|
||||
@@ -7348,6 +7224,14 @@ namespace jdi
|
||||
mixFloat(resolved.style->opacity);
|
||||
mixFloat(resolved.style->lineWidth);
|
||||
mix(static_cast<std::uint64_t>(resolved.style->mode));
|
||||
// 「调整」变换会改变图层里的波形,也必须让图层失效
|
||||
//(否则贴回的旧像素会与当前样式不一致)。
|
||||
mixFloat(resolved.style->gain);
|
||||
mix(resolved.style->absolute ? 1ull : 0ull);
|
||||
mix(resolved.style->mirrorVertical ? 1ull : 0ull);
|
||||
mix(resolved.style->mirrorHorizontal ? 1ull : 0ull);
|
||||
mixFloat(resolved.style->offsetY);
|
||||
mixFloat(static_cast<float>(resolved.style->offsetTime));
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
@@ -10331,7 +10215,7 @@ namespace jdi
|
||||
|
||||
std::size_t begin = 0;
|
||||
std::size_t end = 0;
|
||||
VisibleIndexRange(*resolved.file, count, begin, end);
|
||||
VisibleIndexRange(*resolved.file, count, begin, end, resolved.style->offsetTime);
|
||||
if (end <= begin)
|
||||
{
|
||||
return;
|
||||
@@ -10387,7 +10271,8 @@ namespace jdi
|
||||
}
|
||||
float y = ValueToY(ApplyDrawTransform(*resolved.style, static_cast<double>(sampled)), lane);
|
||||
y = static_cast<float>(ClampDouble(y, limitTop, limitBottom));
|
||||
const float x = TimeToX(time[index]);
|
||||
// 时间偏移:该采样画在 `t + offsetTime` 处。
|
||||
const float x = TimeToX(time[index] + resolved.style->offsetTime);
|
||||
if (!inSegment)
|
||||
{
|
||||
breaks.push_back(points.size());
|
||||
|
||||
Reference in new issue
Block a user