修复色域拖拽回弹、网格 GX 拖拽重排、均值滤波多阶与逐信号条目

1. 自定义颜色面板:拖动时鼠标移出控件不再"弹回去"
   - 根因是选点位置每帧都从 RGB 反推:色相 1 与 0 是同一个颜色(红),拖到色相条底部
     下一帧反推回 0,指示条弹到顶部;拖到色域方块右下角之外时明度为 0(黑色),
     黑色的饱和度无定义,反推得 0,选点弹到最左边。
   - 现在把"选点位置(饱和度/明度)与色相"作为独立状态保存,只由对应控件与外部改色维护;
     越界一律夹取到最近边界,因此鼠标离开控件后选点停在边界上。
   - 统一色域方块、色相条、预览色块与色号框的圆角;RGB 滑块适当加粗;
     色域选点去掉外圈深色描边(只保留白色描边),色相指示器改为白色描边的圆角长方条、
     底色取该位置的颜色;色相条改用分段线性渐变绘制。

2. 网格 GX 编号支持拖拽重排
   - 按住网格图例左侧的 GX 徽标即可上下拖动,整块网格(连同波形与标尺)跟随鼠标移动;
     途经的网格保持自身大小、整体让出一格,目标槽位有吸附高亮提示。
   - 松开后真正重排并从下到上/从上到下重新编号,信号列表徽标、图例徽标、菜单与浮动面板
     中所有 GX 编号同步更新;手动排布后切换为自定义划分,后续重建网格不会打乱次序。
   - 拖动期间复用静态场景缓存,按网格条带平移贴图,因此大文件下依然流畅。

3. 均值滤波升级为"均值点数 X + 阶数 N"
   - 原"均值点数 N"更名"均值点数 X",新增"阶数 N":预览曲线以 X 点均值滤波的结果为蓝本,
     再重复同样的滤波共 N 阶(1 阶即与原来的单次滤波逐位一致)。
   - 浮动面板改为逐信号条目:第一排是信号名与复选框,第二排是均值点数与阶数输入;
     只有勾选的信号才绘制预览曲线,未勾选时不显示第二排;启用功能时默认只勾选第一个信号。
   - 参数按信号保存(同一网格内不同信号可用不同参数),「构建为新信号」按各自参数构建。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-10 15:33:45 +08:00
1 parent 2d029fb09b
commit bc8db72f18
8 files changed
+1138 -181

No files matched your search

+29 -25
View File
@@ -1436,7 +1436,8 @@ namespace jdi
return true;
}
bool Application::BuildMeanFilterSignal(int fileIndex, int signalIndex, int radius, bool makeVisible)
bool Application::BuildMeanFilterSignal(int fileIndex, int signalIndex, int points, int order,
bool makeVisible)
{
const DataFile* file = m_dataSet.FileAt(fileIndex);
if (file == nullptr || signalIndex < 0 || signalIndex >= static_cast<int>(file->signals.size()))
@@ -1446,13 +1447,18 @@ namespace jdi
const SignalData& source = file->signals[static_cast<std::size_t>(signalIndex)];
const std::wstring baseName = source.Title();
// 1. 计算均值滤波采样值(与预览曲线共用同一实现)。
// 1. 计算多阶均值滤波采样值(与预览曲线共用同一实现)。
std::vector<float> filtered;
ComputeMovingAverage(source.values, radius, filtered);
ComputeMovingAverageIterated(source.values, points, order, filtered);
// 2. 组装新信号:名称形如「<原信号名称> (N点均值)」。
// 2. 组装新信号:名称形如「<原信号名称> (N点均值x阶)」。
SignalData derived;
derived.name = baseName + L" (" + std::to_wstring(radius) + L"点均值)";
derived.name = baseName + L" (" + std::to_wstring(points) + L"点均值";
if (order > 1)
{
derived.name += L"×" + std::to_wstring(order) + L"阶";
}
derived.name += L")";
derived.rawHeader = derived.name;
derived.unit = source.unit;
derived.values.assign(filtered.begin(), filtered.end());
@@ -1490,8 +1496,8 @@ namespace jdi
bool Application::ApplyPendingMeanFilterBuild()
{
int laneIndex = -1;
int radius = 0;
if (!m_plot.TakePendingMeanFilterBuild(laneIndex, radius))
std::vector<PlotView::MeanFilterBuild> items;
if (!m_plot.TakePendingMeanFilterBuild(laneIndex, items))
{
return false;
}
@@ -1501,21 +1507,10 @@ namespace jdi
return false;
}
// 先收集该网格内的全部可见信号(插入过程中信号下标会变化,因此先取快照)。
std::vector<SignalRef> targets;
for (const SignalRef& ref : ActiveTab().Lanes()[static_cast<std::size_t>(laneIndex)].signals)
{
if (m_dataSet.Resolve(ref) == nullptr || !ActiveTab().IsVisible(ref))
{
continue;
}
targets.push_back(ref);
}
// **同一文件内必须按下标降序处理**:每次插入只让插入点之后的信号下标 +1,
// 降序处理可保证尚未处理的信号下标始终有效(升序会把新插入的信号当成原信号)。
std::sort(targets.begin(), targets.end(),
[](const SignalRef& a, const SignalRef& b)
std::sort(items.begin(), items.end(),
[](const PlotView::MeanFilterBuild& a, const PlotView::MeanFilterBuild& b)
{
if (a.fileIndex != b.fileIndex)
{
@@ -1525,19 +1520,28 @@ namespace jdi
});
int built = 0;
for (const SignalRef& ref : targets)
int lastPoints = 0;
int lastOrder = 1;
for (const PlotView::MeanFilterBuild& item : items)
{
if (BuildMeanFilterSignal(ref.fileIndex, ref.signalIndex, radius, true))
if (BuildMeanFilterSignal(item.fileIndex, item.signalIndex, item.points, item.order, true))
{
++built;
lastPoints = item.points;
lastOrder = item.order;
}
}
if (built > 0)
{
SetStatusMessage(L"已构建 " + std::to_wstring(built) + L" 条 " + std::to_wstring(radius) +
L"点均值滤波信号。",
false);
std::wstring text = L"已构建 " + std::to_wstring(built) + L" 条 " +
std::to_wstring(lastPoints) + L"点均值";
if (lastOrder > 1)
{
text += L"×" + std::to_wstring(lastOrder) + L"阶";
}
text += L"滤波信号。";
SetStatusMessage(text, false);
}
return built > 0;
}