Files
Just-DataInspector/Components/PlotView/Inc/PlotView.hpp
T
DeepSeek V4.1 Flash 132b305544 feat: 搭建 Just DataInspector 波形数据查看器基础功能
基于 Win32 API + GDI+ 从零实现仿真数据检查器,用于替代 Simulink.sdi.view 查看带时间轴的 CSV 数据。

- 新增 Utilities 层:UTF-8/宽字符与数值格式化工具、CSV 解析器(自动识别分隔符与引号)、
  信号数据集模型(时间轴识别、信号名解析、取值采样)、Solarised Light 主题色板与绘图基础工具
- 新增 Toolbar 组件:打开 CSV、适应窗口、适应时间、放大、缩小、合并网格、每信号一格
- 新增 SignalPanel 组件:信号列表(可见性开关、颜色标识、所属网格标记)、样式编辑区
  (颜色、线宽、线段/散点/点线/阶梯显示方式、所属网格),支持右键上下文菜单与滚轮滚动
- 新增 PlotView 组件:多网格波形绘制、自适应时间轴与幅值轴、网格线、信号图例、
  时间光标与实时数值读数、纵向滚动条
- 新增 Application 组件:DPI 感知(Per-Monitor V2)、双缓冲绘制、区域布局与分隔条拖拽、
  鼠标与键盘消息分发、文件拖放、状态栏提示
- 交互能力:滚轮缩放、Shift+滚轮平移、Ctrl+滚轮缩放幅值、拖拽平移、幅值轴纵向缩放、
  右键框选缩放、单击固定/取消时间光标、双击自适应、方向键平移与滚动
- 补齐 Makefile 的源文件与头文件索引,并新增 gdiplus、comdlg32 链接库
2026-09-29 15:05:29 +08:00

397 lines
12 KiB
C++

#ifndef __PLOT_VIEW_HPP__
#define __PLOT_VIEW_HPP__
#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
#include <string>
#include <vector>
#include "PaintUtil.hpp"
#include "SignalSet.hpp"
#include "Theme.hpp"
namespace jdi
{
/**
* @brief 绘图区拖拽交互模式。
*/
enum class PlotDragMode : int
{
NONE = 0, /**< 未拖拽。 */
PAN_TIME, /**< 水平平移时间轴。 */
ZOOM_Y, /**< 纵向缩放某网格的幅值轴。 */
BOX_ZOOM, /**< 右键框选缩放。 */
SCROLL_LANES /**< 拖动网格滚动条。 */
};
/**
* @brief 波形绘图区:网格、时间轴、光标、缩放与自适应。
*/
class PlotView
{
public:
PlotView();
/**
* @brief 绑定字体与主题资源。
* @param [IN] fonts 字体集合,需在视图生命周期内保持有效。
* @param [IN] theme 主题配色,需在视图生命周期内保持有效。
*/
void Setup(const UiFonts* fonts, const Theme* theme);
/**
* @brief 绑定数据集。
* @param [IN] document 数据集指针,允许为空。
*/
void SetDocument(SignalSet* document);
/**
* @brief 设置 DPI 缩放比例。
* @param [IN] scale 缩放比例(1.0 对应 96 DPI)。
*/
void SetScale(float scale);
/**
* @brief 按给定区域重新排布绘图区。
* @param [IN] area 可用区域。
*/
void Layout(const gd::RectF& area);
/**
* @brief 绘制绘图区。
* @param [IN,OUT] g 绘图上下文。
*/
void Paint(gd::Graphics& g);
/**
* @brief 处理鼠标移动(含拖拽)。
* @param [IN] x 客户区横坐标。
* @param [IN] y 客户区纵坐标。
* @return 需要重绘返回 true。
*/
bool OnMouseMove(int x, int y);
/**
* @brief 处理鼠标左键按下。
* @param [IN] x 客户区横坐标。
* @param [IN] y 客户区纵坐标。
* @return 需要重绘返回 true。
*/
bool OnLButtonDown(int x, int y);
/**
* @brief 处理鼠标左键抬起。
* @param [IN] x 客户区横坐标。
* @param [IN] y 客户区纵坐标。
* @return 需要重绘返回 true。
*/
bool OnLButtonUp(int x, int y);
/**
* @brief 处理鼠标右键按下(开始框选缩放)。
* @param [IN] x 客户区横坐标。
* @param [IN] y 客户区纵坐标。
* @return 需要重绘返回 true。
*/
bool OnRButtonDown(int x, int y);
/**
* @brief 处理鼠标右键抬起(结束框选缩放或清除光标)。
* @param [IN] x 客户区横坐标。
* @param [IN] y 客户区纵坐标。
* @return 需要重绘返回 true。
*/
bool OnRButtonUp(int x, int y);
/**
* @brief 处理鼠标滚轮。
* @param [IN] x 客户区横坐标。
* @param [IN] y 客户区纵坐标。
* @param [IN] delta 滚轮增量。
* @param [IN] ctrl 是否按下 Ctrl(缩放当前网格幅值)。
* @param [IN] shift 是否按下 Shift(平移时间轴)。
* @param [IN] alt 是否按下 Alt(纵向滚动网格)。
* @return 需要重绘返回 true。
*/
bool OnMouseWheel(int x, int y, int delta, bool ctrl, bool shift, bool alt);
/**
* @brief 处理鼠标双击(恢复自适应视图)。
* @param [IN] x 客户区横坐标。
* @param [IN] y 客户区纵坐标。
* @return 需要重绘返回 true。
*/
bool OnDoubleClick(int x, int y);
/**
* @brief 处理鼠标离开绘图区。
* @return 需要重绘返回 true。
*/
bool OnMouseLeave();
/**
* @brief 判断坐标是否落在绘图区范围内。
* @param [IN] x 客户区横坐标。
* @param [IN] y 客户区纵坐标。
* @return 落在范围内返回 true。
*/
bool HitTest(int x, int y) const;
/**
* @brief 查询当前鼠标位置应使用的系统光标。
* @param [IN] x 客户区横坐标。
* @param [IN] y 客户区纵坐标。
* @return 系统光标句柄,未命中特殊区域返回 nullptr。
*/
HCURSOR QueryCursor(int x, int y) const;
/**
* @brief 时间轴自适应到全部数据范围。
*/
void FitTime();
/**
* @brief 时间轴与幅值轴同时自适应。
*/
void FitAll();
/**
* @brief 以给定倍率缩放时间轴(以视图中心为锚点)。
* @param [IN] factor 缩放倍率,大于 1 表示放大。
*/
void ZoomTimeStep(double factor);
/**
* @brief 按当前视图跨度的比例平移时间轴。
* @param [IN] fraction 平移比例,正值向右(时间增大)平移。
*/
void PanTimeByFraction(double fraction);
/**
* @brief 按网格内容高度的比例纵向滚动网格。
* @param [IN] fraction 滚动比例,正值向下滚动。
*/
void ScrollLanesByFraction(double fraction);
/**
* @brief 获取网格当前内容总高度。
* @return 内容高度(像素)。
*/
float LaneContentHeight() const;
/**
* @brief 清除已固定的时间光标。
*/
void ClearCursor();
/**
* @brief 判断当前是否存在激活的时间光标。
* @return 存在返回 true。
*/
bool HasCursor() const { return m_cursorVisible; }
/**
* @brief 获取当前激活光标对应的时间。
* @return 光标时间。
*/
double CursorTime() const { return m_cursorTime; }
/**
* @brief 获取当前视图左边界时间。
* @return 视图左边界时间。
*/
double ViewMinTime() const { return m_viewMin; }
/**
* @brief 获取当前视图右边界时间。
* @return 视图右边界时间。
*/
double ViewMaxTime() const { return m_viewMax; }
/**
* @brief 获取绘图区(波形区域)矩形。
* @return 波形区域矩形。
*/
const gd::RectF& WaveformRect() const { return m_plotRect; }
private:
/**
* @brief 单个网格的绘制信息。
*/
struct LaneVisual
{
gd::RectF rect; /**< 网格波形区域。 */
double yMin = 0.0; /**< 幅值下限。 */
double yMax = 1.0; /**< 幅值上限。 */
bool empty = true; /**< 是否没有可见信号。 */
};
/**
* @brief 重新计算各网格的布局与幅值范围。
*/
void BuildLaneVisuals();
/**
* @brief 限制网格滚动偏移量。
*/
void ClampLaneScroll();
/**
* @brief 将时间映射为绘图区横坐标。
* @param [IN] time 时间值。
* @return 横坐标。
*/
float TimeToX(double time) const;
/**
* @brief 将绘图区横坐标还原为时间。
* @param [IN] x 横坐标。
* @return 时间值。
*/
double XToTime(float x) const;
/**
* @brief 将幅值映射为纵坐标。
* @param [IN] value 幅值。
* @param [IN] lane 网格绘制信息。
* @return 纵坐标。
*/
float ValueToY(double value, const LaneVisual& lane) const;
/**
* @brief 将纵坐标还原为幅值。
* @param [IN] y 纵坐标。
* @param [IN] lane 网格绘制信息。
* @return 幅值。
*/
double YToValue(float y, const LaneVisual& lane) const;
/**
* @brief 查询指定纵坐标落在哪个网格内。
* @param [IN] y 纵坐标。
* @return 网格下标,未命中返回 -1。
*/
int LaneAtY(int y) const;
/**
* @brief 限制时间视图范围,避免过度平移到数据之外。
*/
void ClampTimeView();
/**
* @brief 以指定锚点缩放时间轴。
* @param [IN] factor 缩放倍率,大于 1 表示放大。
* @param [IN] anchorTime 锚点时间。
*/
void ZoomTimeAt(double factor, double anchorTime);
/**
* @brief 构建单条信号在当前视图下的折线点序列。
* @param [IN] signal 目标信号。
* @param [IN] lane 所属网格绘制信息。
* @param [IN] stepMode 是否使用阶梯保持连线。
* @param [OUT] points 输出点序列。
* @param [OUT] breaks 输出每段折线的起始下标(用于处理缺失值断线)。
*/
void BuildPolyline(const Signal& signal, const LaneVisual& lane, bool stepMode,
std::vector<gd::PointF>& points, std::vector<std::size_t>& breaks) const;
/**
* @brief 绘制单个网格。
* @param [IN,OUT] g 绘图上下文。
* @param [IN] laneIndex 网格下标。
* @param [IN] lane 网格绘制信息。
* @param [IN] timeTicks 时间刻度序列。
* @param [IN] timeStep 时间刻度步长。
*/
void PaintLane(gd::Graphics& g, int laneIndex, const LaneVisual& lane, const std::vector<double>& timeTicks,
double timeStep);
/**
* @brief 绘制底部时间轴。
* @param [IN,OUT] g 绘图上下文。
* @param [IN] timeTicks 时间刻度序列。
* @param [IN] timeStep 时间刻度步长。
*/
void PaintTimeAxis(gd::Graphics& g, const std::vector<double>& timeTicks, double timeStep);
/**
* @brief 绘制时间光标及其读数。
* @param [IN,OUT] g 绘图上下文。
* @param [IN] lanes 网格绘制信息集合。
*/
void PaintCursor(gd::Graphics& g, const std::vector<LaneVisual>& lanes);
/**
* @brief 绘制网格纵向滚动条。
* @param [IN,OUT] g 绘图上下文。
*/
void PaintScrollBar(gd::Graphics& g);
/**
* @brief 绘制网格标题(信号图例)。
* @param [IN,OUT] g 绘图上下文。
* @param [IN] laneIndex 网格下标。
* @param [IN] lane 网格绘制信息。
*/
void PaintLaneLegend(gd::Graphics& g, int laneIndex, const LaneVisual& lane);
/**
* @brief 在网格内绘制各信号的幅值读数。
* @param [IN,OUT] g 绘图上下文。
* @param [IN] laneIndex 网格下标。
* @param [IN] lane 网格绘制信息。
*/
void PaintLaneReadout(gd::Graphics& g, int laneIndex, const LaneVisual& lane);
const UiFonts* m_fonts = nullptr; /**< 字体资源。 */
const Theme* m_theme = nullptr; /**< 主题资源。 */
SignalSet* m_document = nullptr; /**< 数据集。 */
float m_scale = 1.0f; /**< DPI 缩放比例。 */
gd::RectF m_area; /**< 绘图区整体区域。 */
gd::RectF m_plotRect; /**< 波形绘制区域。 */
gd::RectF m_timeAxisRect; /**< 时间轴区域。 */
gd::RectF m_scrollBarRect; /**< 网格滚动条区域。 */
float m_yAxisWidth = 64.0f; /**< 纵轴标签列宽度(逻辑像素)。 */
float m_timeAxisHeight = 34.0f; /**< 时间轴高度(逻辑像素)。 */
float m_laneGap = 6.0f; /**< 网格间距(逻辑像素)。 */
float m_minLaneHeight = 46.0f; /**< 网格最小高度(逻辑像素)。 */
float m_laneScroll = 0.0f; /**< 网格纵向滚动偏移量。 */
float m_laneContentHeight = 0.0f; /**< 网格内容总高度。 */
bool m_scrollBarVisible = false; /**< 是否需要显示滚动条。 */
double m_viewMin = 0.0; /**< 视图左边界时间。 */
double m_viewMax = 1.0; /**< 视图右边界时间。 */
bool m_cursorVisible = false; /**< 是否存在激活光标。 */
bool m_cursorPinned = false; /**< 光标是否已被固定。 */
double m_cursorTime = 0.0; /**< 光标时间。 */
PlotDragMode m_dragMode = PlotDragMode::NONE; /**< 当前拖拽模式。 */
int m_dragLane = -1; /**< 拖拽所作用的网格下标。 */
bool m_dragMoved = false; /**< 拖拽过程中是否发生了位移。 */
int m_dragStartX = 0; /**< 拖拽起点横坐标。 */
int m_dragStartY = 0; /**< 拖拽起点纵坐标。 */
int m_dragLastX = 0; /**< 上一次拖拽横坐标。 */
int m_dragLastY = 0; /**< 上一次拖拽纵坐标。 */
double m_dragStartViewMin = 0.0; /**< 拖拽起始视图左边界。 */
double m_dragStartViewMax = 1.0; /**< 拖拽起始视图右边界。 */
double m_dragAnchorValue = 0.0; /**< 纵轴缩放锚点幅值。 */
double m_dragAnchorRatio = 0.5; /**< 纵轴缩放锚点相对位置。 */
double m_dragStartYMin = 0.0; /**< 拖拽起始幅值下限。 */
double m_dragStartYMax = 1.0; /**< 拖拽起始幅值上限。 */
gd::RectF m_boxRect; /**< 框选缩放矩形。 */
int m_hoverLane = -1; /**< 当前悬停网格下标。 */
std::vector<LaneVisual> m_lanes; /**< 当前网格绘制信息集合。 */
};
} // namespace jdi
#endif // __PLOT_VIEW_HPP__