基于 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 链接库
397 lines
12 KiB
C++
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__
|