fix(plot): 默认线宽 1.0px、菜单文本左对齐、散点白边修复与大文件拖拽性能优化

1) 默认线宽改为 1.0px
- SignalStyle::lineWidth 初值 1.6f -> 1.0f (DataModel.hpp / DataModel.cpp)
- 新增 LaneScroll() const 重载,供 const 的 MakeSceneCacheKey() 读取

2) 网格右键菜单文本左对齐
- PopupMenu 的 HEADER / NORMAL / EDIT 三类条目统一使用 kMarkerColumn(22.0f)
  作为文本列,替换原先写死的 8.0f,使 "网格 N"、"Y 轴最大值"、
  "Y 轴最小值"、"选区 Y 轴自适应" 等所有条目左边缘严格对齐

3) 修复散点/点线模式下数据点重叠处的白色缝隙
- 根因: GDI+ GraphicsPath 默认 FillModeAlternate(奇偶填充),重叠圆的交叠区域
  被反向填充,视觉上表现为白色缺口/白边
- 主绘制与拖拽 ghost 的 GraphicsPath 均显式 SetFillMode(FillModeWinding)
- ghost 由逐点 FillEllipse 改为单个 GraphicsPath 一次 FillPath(批量绘制)

4) 大文件拖拽流畅性优化 (VERY_BIG_DATA.CSV 1.77GB / 5 信号)
- 新增拖拽静态场景缓存: 在 MOVE_SIGNAL / CURSOR_MOVE 期间把整个客户区
  (绘图区 + 标签页 + 工具栏 + 信号面板) 捕快到 m_sceneDc 位图,后续帧直接
  BitBlt,仅在 SceneCacheKey(dragMode/viewMin/viewMax/laneScroll/laneCount/
  区域/scale) 变化时重绘。Application::Render 在复用场景时跳过窗口背景与
  各子控件绘制,缓存位图由 ~PlotView() 释放
- 失效点: SetContext / SetScale / Layout / InvalidatePolylineCache
- 光标数值读数从 PaintLane 抽出为 PaintLaneReadouts(),在缓存 blit 之后绘制,
  避免读数被缓存导致拖动时显示旧值
- 拖拽 ghost 复用已有 PolylineCacheEntry,改用 TranslateTransform 偏移,
  不再重复构造折线顶点
- 实测: 拖拽帧耗时 134-146ms -> 22-25ms;散点模式拖拽约 24.6ms;
  非拖拽重绘维持约 118ms;空闲 CPU 0.00%

构建: make clean; make all 无警告无错误
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-07 10:22:57 +08:00
1 parent 04e98ee041
commit a3e638fa58
6 files changed
+407 -29

No files matched your search

+3 -1
View File
@@ -92,7 +92,7 @@ namespace jdi
std::uint32_t color = 0xFF268BD2; /**< 波形颜色,格式 0xAARRGGBB。 */
std::uint32_t customColor = 0xFF6C71C4; /**< 自定义颜色记忆值(切换预设色后仍保留)。 */
float opacity = 1.0f; /**< 不透明度,取值 0.0-1.0,默认 1.0。 */
float lineWidth = 1.6f; /**< 线宽(逻辑像素,未乘 DPI 缩放)。 */
float lineWidth = 1.0f; /**< 线宽(逻辑像素,未乘 DPI 缩放)。 */
DrawMode mode = DrawMode::LINE; /**< 绘制方式。 */
};
@@ -549,6 +549,8 @@ namespace jdi
bool& ViewInitialized() { return m_viewInitialized; }
/// @brief 网格纵向滚动偏移量(可读写)。
float& LaneScroll() { return m_laneScroll; }
/// @brief 网格纵向滚动偏移量(只读)。
float LaneScroll() const { return m_laneScroll; }
/// @brief 时间光标是否启用(可读写)。
bool& CursorEnabled() { return m_cursorEnabled; }
/// @brief 时间光标是否启用(只读)。
+1 -1
View File
@@ -432,7 +432,7 @@ namespace jdi
: 0xFF268BD2u;
signal.style.customColor = signal.style.color;
signal.style.opacity = 1.0f;
signal.style.lineWidth = 1.6f;
signal.style.lineWidth = 1.0f;
signal.style.mode = DrawMode::LINE;
}
+5 -4
View File
@@ -86,7 +86,7 @@ namespace jdi
}
else if (item.kind == MenuItemKind::HEADER)
{
const float width = (kPadH + 8.0f) * m_scale +
const float width = (kPadH + kMarkerColumn) * m_scale +
UiMeasureTextWidth(m_fonts->uiSmallBold, item.label) + kRightPad * m_scale;
contentWidth = std::max(contentWidth, width);
}
@@ -102,7 +102,7 @@ namespace jdi
const float fieldWidth =
std::max(kEditFieldWidth * m_scale,
UiMeasureTextWidth(m_fonts->uiSmall, item.value) + 20.0f * m_scale);
const float width = (kPadH + 8.0f) * m_scale +
const float width = (kPadH + kMarkerColumn) * m_scale +
UiMeasureTextWidth(m_fonts->uiSmall, item.label) + kEditFieldGap * m_scale +
fieldWidth + kRightPad * m_scale;
contentWidth = std::max(contentWidth, width);
@@ -753,7 +753,7 @@ namespace jdi
if (item.kind == MenuItemKind::HEADER)
{
UiText(g, item.label, m_fonts->uiSmallBold,
gd::RectF(rect.X + 8.0f * m_scale, rect.Y, rect.Width, rect.Height),
gd::RectF(rect.X + kMarkerColumn * m_scale, rect.Y, rect.Width, rect.Height),
m_theme->textMuted, TextAlign::LEFT);
continue;
}
@@ -771,7 +771,8 @@ namespace jdi
}
const std::uint32_t labelColor = item.enabled ? m_theme->text : m_theme->textMuted;
const float labelLeft = rect.X + 8.0f * m_scale;
// 说明文字与普通项使用同一文字列(kMarkerColumn),保证菜单内所有文本左对齐。
const float labelLeft = rect.X + kMarkerColumn * m_scale;
const gd::RectF fieldRect = EditFieldRect(static_cast<int>(index));
const float labelWidth = std::max(8.0f, fieldRect.X - labelLeft - kEditFieldGap * m_scale);
UiText(g, UiEllipsize(g, item.label, m_fonts->uiSmall, labelWidth), m_fonts->uiSmall,