You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

458 lines
19 KiB

1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
  1. #include "widgetplot2d.h"
  2. #include <QDebug>
  3. #include "ui_widgetplot2d.h"
  4. #include "zcsv.hpp"
  5. using namespace iflytop;
  6. WidgetPlot2D::WidgetPlot2D(QWidget* parent) : QWidget(parent), ui(new Ui::WidgetPlot2D) {
  7. ui->setupUi(this);
  8. // QCP绘图初始化
  9. initQCP();
  10. // 初始化控件
  11. initWidget();
  12. // 上下分裂器
  13. ui->splitter->setStretchFactor(0, 20);
  14. ui->splitter->setStretchFactor(1, 1);
  15. // 左右分裂器
  16. ui->splitter1->setStretchFactor(0, 10);
  17. ui->splitter1->setStretchFactor(1, 3);
  18. // 滚动条设置
  19. ui->horizontalScrollBar->setRange(0, 0);
  20. // 拖动水平滚动条,图幅跟随变化
  21. // connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(horzScrollBarChanged(int)));
  22. // 默认坐标范围
  23. ui->customPlot->yAxis->setRange(-20, 20);
  24. ui->customPlot->replot();
  25. }
  26. WidgetPlot2D::~WidgetPlot2D() { delete ui; }
  27. /* 初始化曲线名称 */
  28. void WidgetPlot2D::initGraphName(QStringList name) {
  29. //----------------------------------------------------------------------------------------//
  30. // 设置表头
  31. ui->treeWidget->setHeaderLabels(QStringList() << "名称"
  32. << ""
  33. << "颜色");
  34. ui->treeWidget->header()->setVisible(true);
  35. // 表头文字中间对齐
  36. QTreeWidgetItem* header = ui->treeWidget->headerItem();
  37. header->setTextAlignment(0, Qt::AlignLeft);
  38. header->setTextAlignment(1, Qt::AlignLeft);
  39. header->setTextAlignment(2, Qt::AlignLeft);
  40. // 设置列宽
  41. ui->treeWidget->setColumnWidth(0, 100);
  42. ui->treeWidget->setColumnWidth(1, 90);
  43. ui->treeWidget->setColumnWidth(2, 30);
  44. // 表头顺序是否可以拖动改变
  45. ui->treeWidget->header()->setCascadingSectionResizes(true);
  46. // 被选部分是否高亮显示
  47. ui->treeWidget->header()->setHighlightSections(false);
  48. // 最后一个区域是否占满表格余下的所有部分
  49. ui->treeWidget->header()->setStretchLastSection(true);
  50. // 列表的列数
  51. ui->treeWidget->setColumnCount(3);
  52. //----------------------------------------------------------------------------------------//
  53. for (int i = 0; i < name.length(); i++) {
  54. // 添加曲线列表项
  55. QTreeWidgetItem* item = new QTreeWidgetItem;
  56. ui->treeWidget->addTopLevelItem(item);
  57. //----------------------------------------------------------------------------------------//
  58. // 向曲线列表项第一列添加曲线是否可见复选框
  59. QCheckBox* isShowCheckBox = new QCheckBox(name.at(i));
  60. isShowCheckBox->setChecked(true);
  61. connect(isShowCheckBox, SIGNAL(stateChanged(int)), this, SLOT(changeGraphVisible()));
  62. ui->treeWidget->setItemWidget(item, 0, isShowCheckBox);
  63. isShowCheckBoxVector.append(isShowCheckBox);
  64. //----------------------------------------------------------------------------------------//
  65. // 向曲线列表项第二列添加曲线数值标签
  66. QLabel* valueLabel = new QLabel("0");
  67. QFont font("Courier");
  68. valueLabel->setFont(font);
  69. // valueLabel->setAlignment(Qt::AlignHCenter);
  70. ui->treeWidget->setItemWidget(item, 1, valueLabel);
  71. valueLabelVector.append(valueLabel);
  72. //----------------------------------------------------------------------------------------//
  73. // 向曲线列表项第三列添加曲线颜色按钮,点击该按钮弹出颜色选择框,选择曲线颜色
  74. QPushButton* GraphColorPushButton = new QPushButton("");
  75. // 随机分配初始按钮背景色
  76. static int r = 0, g = 0, b = 0;
  77. r += 150;
  78. g += 50;
  79. b += 100;
  80. if (r > 255) r -= 255;
  81. if (g > 255) g -= 255;
  82. if (b > 255) b -= 255;
  83. QPalette pal;
  84. pal.setColor(QPalette::Button, QColor(r, g, b));
  85. GraphColorPushButton->setPalette(pal);
  86. GraphColorPushButton->setAutoFillBackground(true); // 该句不能缺少,否则背景颜色无法改变
  87. GraphColorPushButton->setFlat(true);
  88. // 固定颜色按钮大小
  89. GraphColorPushButton->setMinimumHeight(18); // 固定高度
  90. GraphColorPushButton->setMaximumHeight(18);
  91. GraphColorPushButton->setMinimumWidth(30); // 固定宽度
  92. GraphColorPushButton->setMaximumWidth(30);
  93. connect(GraphColorPushButton, SIGNAL(clicked()), this, SLOT(changeGraphColor()));
  94. ui->treeWidget->setItemWidget(item, 2, GraphColorPushButton);
  95. GraphColorPushButtonVector.append(GraphColorPushButton);
  96. //----------------------------------------------------------------------------------------//
  97. // 添加曲线
  98. ui->customPlot->addGraph();
  99. ui->customPlot->graph(ui->customPlot->graphCount() - 1)->setPen(QPen(QColor(r, g, b)));
  100. ui->customPlot->graph(ui->customPlot->graphCount() - 1)->setVisible(true);
  101. // 将曲线名称与曲线序号一一对应,之后添加数据就可以一一对应
  102. nameToGraphMap[name.at(i)] = i;
  103. // 初始化数据容器的值
  104. for (int t = 0; t < name.length(); t++) {
  105. valueVector.append(0);
  106. }
  107. }
  108. }
  109. /* 添加数据 */
  110. void WidgetPlot2D::addData(QString name, double value, int offms) {
  111. // 如果点击了“暂停”按钮,则不绘制图形
  112. if (ui->pausePBtn->text() == "开始") return;
  113. double key = 0;
  114. double val = value;
  115. pointCnt[nameToGraphMap[name]]++;
  116. key = pointCnt[nameToGraphMap[name]];
  117. if (ui->autoRangeCheck->isChecked()) {
  118. ui->customPlot->rescaleAxes();
  119. }
  120. ui->customPlot->graph(nameToGraphMap[name])->addData(key, value);
  121. ui->customPlot->xAxis->setRange(key, ui->customPlot->xAxis->range().size(), Qt::AlignRight);
  122. ui->customPlot->replot(QCustomPlot::rpQueuedReplot);
  123. #if 0
  124. // 系统当前时间 = 系统运行初始时间 + 系统运行时间
  125. static double start = time.hour() * 60 * 60 + time.minute() * 60 + time.second() + time.msec() / 1000.0;
  126. double key = start + time.elapsed() / 1000.0 + offms / 1000;
  127. // 子网格显示
  128. ui->customPlot->xAxis->grid()->setSubGridVisible(ui->subGridCheck->isChecked());
  129. ui->customPlot->yAxis->grid()->setSubGridVisible(ui->subGridCheck->isChecked());
  130. // 自适应量程
  131. if (ui->autoRangeCheck->isChecked()) {
  132. ui->customPlot->rescaleAxes();
  133. }
  134. // 设置时间轴
  135. int timeAxis = ui->timeAxisSpin->value();
  136. ui->customPlot->xAxis->setRange(key, timeAxis, Qt::AlignRight);
  137. // x轴和y轴全程显示
  138. if (ui->fullShowCheck->isChecked()) {
  139. ui->customPlot->rescaleAxes();
  140. }
  141. // 刷新绘图水平滚动条
  142. ui->horizontalScrollBar->setRange(int(start), int(key)); // 刷新滚动条的范围
  143. ui->horizontalScrollBar->setPageStep(1); // 设置翻页步长为 1s 的宽度
  144. ui->horizontalScrollBar->setValue(int(key)); // 调整滑块位置到最右边
  145. // 更新曲线绘图
  146. ui->customPlot->graph(nameToGraphMap[name])->addData(key, value);
  147. // ui->customPlot->replot();
  148. // 使用rpQueuedReplot参数可以加快绘图速度,避免不必要的重复绘制
  149. ui->customPlot->replot(QCustomPlot::rpQueuedReplot);
  150. // 存储曲线的当前值
  151. valueVector[nameToGraphMap[name]] = value;
  152. //----------------------------------------------------------------------------------------//
  153. // 计算每秒的帧率
  154. static double lastFpsKey = key;
  155. static int frameCount = 0;
  156. frameCount++;
  157. // 每2秒显示一次帧率
  158. if (key - lastFpsKey > 0.5) {
  159. uint64_t sum = 0;
  160. for (int i = 0; i < ui->customPlot->plottableCount(); i++) {
  161. sum += uint64_t(ui->customPlot->graph(i)->data()->size());
  162. }
  163. ui->statusLabel->setText(QString("%1 FPS, Total Data points: %2").arg(frameCount / (key - lastFpsKey), 0, 'f', 0).arg(sum));
  164. lastFpsKey = key;
  165. frameCount = 0;
  166. // 更新数据标签
  167. for (int t = 0; t < ui->customPlot->plottableCount(); t++) {
  168. valueLabelVector[t]->setText(QString::number(valueVector[t]));
  169. }
  170. }
  171. #endif
  172. //----------------------------------------------------------------------------------------//
  173. }
  174. /* 曲线是否显示 */
  175. void WidgetPlot2D::changeGraphVisible() {
  176. for (int i = 0; i < isShowCheckBoxVector.length(); i++) {
  177. ui->customPlot->graph(i)->setVisible(isShowCheckBoxVector[i]->isChecked());
  178. }
  179. }
  180. /* 改变曲线颜色 */
  181. void WidgetPlot2D::changeGraphColor() {
  182. /* 在槽(SLOT)中sender()函数会返回一个指向 QObject 的指针来指向信号的发送者。
  183. * C++ RTTI(Run-Time Type Identification)dynamic_cast运算符
  184. * sender()QPushButton类sender()QObject指针转换为QPushButton指针
  185. * if中的语句就会执行sender()QPushButton类型的指针dynamic_cast就会返回0if中的语句就不会执行了
  186. * */
  187. if (QPushButton* btn = dynamic_cast<QPushButton*>(sender())) {
  188. QColor color = QColorDialog::getColor(Qt::white, this);
  189. // 用户取消颜色对话框
  190. if (color.isValid() == false) return;
  191. // 设置按钮背景色
  192. QPalette pal; // = btn->palette();
  193. pal.setColor(QPalette::Button, color);
  194. btn->setPalette(pal);
  195. btn->setAutoFillBackground(true); // 该句不能缺少,否则背景颜色无法改变
  196. btn->setFlat(true); // 该句不能缺少,否则背景颜色无法改变
  197. }
  198. // 改变对应的曲线颜色
  199. for (int i = 0; i < GraphColorPushButtonVector.length(); i++) {
  200. // 获取按钮的颜色
  201. QPalette pal = GraphColorPushButtonVector[i]->palette();
  202. QColor color = pal.color(QPalette::Button);
  203. // 曲线颜色对应按钮颜色
  204. ui->customPlot->graph(i)->setPen(QPen(color));
  205. }
  206. ui->customPlot->replot();
  207. }
  208. /* QCP绘图初始化 */
  209. void WidgetPlot2D::initQCP() {
  210. // 刻度显示
  211. ui->customPlot->xAxis->setTicks(true);
  212. ui->customPlot->yAxis->setTicks(true);
  213. // 刻度值显示
  214. ui->customPlot->xAxis->setTickLabels(true);
  215. ui->customPlot->yAxis->setTickLabels(true);
  216. // 网格显示
  217. ui->customPlot->xAxis->grid()->setVisible(true);
  218. ui->customPlot->yAxis->grid()->setVisible(true);
  219. // 子网格显示
  220. ui->customPlot->xAxis->grid()->setSubGridVisible(false);
  221. ui->customPlot->yAxis->grid()->setSubGridVisible(false);
  222. // 右和上坐标轴、刻度值显示
  223. ui->customPlot->xAxis2->setVisible(true);
  224. ui->customPlot->yAxis2->setVisible(true);
  225. ui->customPlot->yAxis2->setTicks(true);
  226. ui->customPlot->yAxis2->setTickLabels(true);
  227. // make top right axes clones of bottom left axes. Looks prettier:
  228. // ui->customPlot->axisRect()->setupFullAxesBox();
  229. // make left and bottom axes always transfer their ranges to right and top axes:
  230. connect(ui->customPlot->xAxis, SIGNAL(rangeChanged(QCPRange)), ui->customPlot->xAxis2, SLOT(setRange(QCPRange)));
  231. connect(ui->customPlot->yAxis, SIGNAL(rangeChanged(QCPRange)), ui->customPlot->yAxis2, SLOT(setRange(QCPRange)));
  232. // 暗色主题
  233. // setPlotTheme(Qt::white, Qt::black);
  234. // 亮色主题
  235. setTheme(Qt::black, Qt::white);
  236. // 可放大缩小和移动
  237. ui->customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);
  238. // x轴以时间形式显示
  239. // QSharedPointer<QCPAxisTickerTime> timeTicker(new QCPAxisTickerTime);
  240. // timeTicker->setTimeFormat("%h:%m:%s");
  241. // ui->customPlot->xAxis->setTicker(timeTicker);
  242. ui->customPlot->axisRect()->setupFullAxesBox();
  243. ui->customPlot->replot();
  244. }
  245. /* 设置绘图主题 */
  246. void WidgetPlot2D::setTheme(QColor axis, QColor background) {
  247. // 坐标颜色按钮
  248. QPalette pal = ui->axisColorPBtn->palette();
  249. pal.setColor(QPalette::Button, axis);
  250. ui->axisColorPBtn->setPalette(pal);
  251. ui->axisColorPBtn->setAutoFillBackground(true); // 该句不能缺少,否则背景颜色无法改变
  252. ui->axisColorPBtn->setFlat(true); // 该句不能缺少,否则背景颜色无法改变
  253. // 背景颜色按钮
  254. pal = ui->backgroundColorPBtn->palette();
  255. pal.setColor(QPalette::Button, background);
  256. ui->backgroundColorPBtn->setPalette(pal);
  257. ui->backgroundColorPBtn->setAutoFillBackground(true); // 该句不能缺少,否则背景颜色无法改变
  258. ui->backgroundColorPBtn->setFlat(true); // 该句不能缺少,否则背景颜色无法改变
  259. //----------------------------------------------------------------------------------------//
  260. // 坐标标注颜色
  261. ui->customPlot->xAxis->setLabelColor(axis);
  262. ui->customPlot->yAxis->setLabelColor(axis);
  263. // 坐标刻度值颜色
  264. ui->customPlot->xAxis->setTickLabelColor(axis);
  265. ui->customPlot->yAxis->setTickLabelColor(axis);
  266. // 坐标基线颜色和宽度
  267. ui->customPlot->xAxis->setBasePen(QPen(axis, 1));
  268. ui->customPlot->yAxis->setBasePen(QPen(axis, 1));
  269. // 坐标主刻度颜色和宽度
  270. ui->customPlot->xAxis->setTickPen(QPen(axis, 1));
  271. ui->customPlot->yAxis->setTickPen(QPen(axis, 1));
  272. // 坐标子刻度颜色和宽度
  273. ui->customPlot->xAxis->setSubTickPen(QPen(axis, 1));
  274. ui->customPlot->yAxis->setSubTickPen(QPen(axis, 1));
  275. // 坐标标注颜色
  276. ui->customPlot->xAxis2->setLabelColor(axis);
  277. ui->customPlot->yAxis2->setLabelColor(axis);
  278. // 坐标刻度值颜色
  279. ui->customPlot->xAxis2->setTickLabelColor(axis);
  280. ui->customPlot->yAxis2->setTickLabelColor(axis);
  281. // 坐标基线颜色和宽度
  282. ui->customPlot->xAxis2->setBasePen(QPen(axis, 1));
  283. ui->customPlot->yAxis2->setBasePen(QPen(axis, 1));
  284. // 坐标主刻度颜色和宽度
  285. ui->customPlot->xAxis2->setTickPen(QPen(axis, 1));
  286. ui->customPlot->yAxis2->setTickPen(QPen(axis, 1));
  287. // 坐标子刻度颜色和宽度
  288. ui->customPlot->xAxis2->setSubTickPen(QPen(axis, 1));
  289. ui->customPlot->yAxis2->setSubTickPen(QPen(axis, 1));
  290. // 整个画布背景色
  291. ui->customPlot->setBackground(background);
  292. // 绘图区域背景色
  293. ui->customPlot->axisRect()->setBackground(background);
  294. // 刷新绘图
  295. ui->customPlot->replot();
  296. }
  297. /* 初始化控件 */
  298. void WidgetPlot2D::initWidget() {
  299. // 时间轴默认时间宽度
  300. ui->timeAxisSpin->setValue(20);
  301. // 主题
  302. QStringList theme;
  303. theme << "亮色"
  304. << "暗色"
  305. << "自定义";
  306. ui->themeCombo->addItems(theme);
  307. connect(ui->themeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(changePlotTheme()));
  308. //----------------------------------------------------------------------------------------//
  309. // 坐标颜色按钮
  310. QPalette pal = ui->axisColorPBtn->palette();
  311. pal.setColor(QPalette::Button, Qt::black);
  312. ui->axisColorPBtn->setPalette(pal);
  313. ui->axisColorPBtn->setAutoFillBackground(true); // 该句不能缺少,否则背景颜色无法改变
  314. ui->axisColorPBtn->setFlat(true); // 该句不能缺少,否则背景颜色无法改变
  315. connect(ui->axisColorPBtn, SIGNAL(clicked()), this, SLOT(changePlotTheme()));
  316. // 背景颜色按钮
  317. pal = ui->backgroundColorPBtn->palette();
  318. pal.setColor(QPalette::Button, Qt::white);
  319. ui->backgroundColorPBtn->setPalette(pal);
  320. ui->backgroundColorPBtn->setAutoFillBackground(true); // 该句不能缺少,否则背景颜色无法改变
  321. ui->backgroundColorPBtn->setFlat(true); // 该句不能缺少,否则背景颜色无法改变
  322. connect(ui->backgroundColorPBtn, SIGNAL(clicked()), this, SLOT(changePlotTheme()));
  323. // 操作按钮
  324. connect(ui->clearPBtn, SIGNAL(clicked()), this, SLOT(plotOperation()));
  325. connect(ui->fullShowPBtn, SIGNAL(clicked()), this, SLOT(plotOperation()));
  326. connect(ui->savePBtn, SIGNAL(clicked()), this, SLOT(plotOperation()));
  327. connect(ui->pausePBtn, SIGNAL(clicked()), this, SLOT(plotOperation()));
  328. //----------------------------------------------------------------------------------------//
  329. }
  330. /* 改变画图主题 */
  331. void WidgetPlot2D::changePlotTheme() {
  332. // 主题选择
  333. if (QComboBox* combo = dynamic_cast<QComboBox*>(sender())) {
  334. if (combo->currentText() == "亮色") {
  335. setTheme(Qt::black, Qt::white); // 亮色主题
  336. } else if (combo->currentText() == "暗色") {
  337. setTheme(Qt::white, Qt::black); // 暗色主题
  338. } else if (combo->currentText() == "自定义") {
  339. // 绘图坐标颜色
  340. QPalette axisPal = ui->axisColorPBtn->palette();
  341. QColor axisColor = axisPal.color(QPalette::Button);
  342. // 绘图背景颜色
  343. QPalette backgroundPal = ui->backgroundColorPBtn->palette();
  344. QColor backgroundColor = backgroundPal.color(QPalette::Button);
  345. setTheme(axisColor, backgroundColor);
  346. }
  347. }
  348. // 用户自定义主题
  349. if (QPushButton* btn = dynamic_cast<QPushButton*>(sender())) {
  350. QColor color = QColorDialog::getColor(Qt::white, this);
  351. // 用户取消颜色对话框
  352. if (color.isValid() == false) return;
  353. // 设置按钮背景色
  354. QPalette pal = btn->palette();
  355. pal.setColor(QPalette::Button, color);
  356. btn->setPalette(pal);
  357. btn->setAutoFillBackground(true); // 该句不能缺少,否则背景颜色无法改变
  358. btn->setFlat(true); // 该句不能缺少,否则背景颜色无法改变
  359. // 绘图坐标颜色
  360. QPalette axisPal = ui->axisColorPBtn->palette();
  361. QColor axisColor = axisPal.color(QPalette::Button);
  362. // 绘图背景颜色
  363. QPalette backgroundPal = ui->backgroundColorPBtn->palette();
  364. QColor backgroundColor = backgroundPal.color(QPalette::Button);
  365. // 改变主题颜色
  366. setTheme(axisColor, backgroundColor);
  367. ui->themeCombo->setCurrentText("自定义");
  368. }
  369. }
  370. /* 绘图操作 */
  371. void WidgetPlot2D::plotOperation() {
  372. if (QPushButton* btn = dynamic_cast<QPushButton*>(sender())) {
  373. if (btn->text() == "清除") {
  374. for (int i = 0; i < ui->customPlot->graphCount(); i++) {
  375. // 先获得每条曲线的数据指针,然后删除数据
  376. ui->customPlot->graph(i)->data()->clear();
  377. ui->customPlot->replot();
  378. for (size_t i = 0; i < 100; i++) {
  379. pointCnt[i] = 0;
  380. }
  381. }
  382. }
  383. if (btn->text() == "整图") {
  384. ui->customPlot->rescaleAxes();
  385. ui->customPlot->replot();
  386. }
  387. if (btn->text() == "保存") {
  388. savePlotPng();
  389. }
  390. if (btn->text() == "暂停") {
  391. btn->setText("开始");
  392. QIcon icon(":/image/player.png");
  393. btn->setIcon(icon);
  394. } else if (btn->text() == "开始") {
  395. btn->setText("暂停");
  396. QIcon icon(":/image/pause.png");
  397. btn->setIcon(icon);
  398. }
  399. }
  400. }
  401. /* 判断路径是否存在,不存在则新建,只能创建一级子目录,必须保证上级目录存在 */
  402. bool WidgetPlot2D::isDirExist(QString fullPath) {
  403. QDir dir(fullPath);
  404. if (dir.exists()) {
  405. return true;
  406. } else {
  407. // 创建一级子目录,必须保证上级目录存在
  408. bool ok = dir.mkdir(fullPath);
  409. return ok;
  410. }
  411. }
  412. /* 保存绘图成图片 */
  413. void WidgetPlot2D::savePlotPng() {
  414. QString savePath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
  415. QString fileName = QFileDialog::getSaveFileName(this,
  416. "保存波形数据", // 对话框的标题
  417. savePath, // 保存的默认路径为程序运行路径
  418. "Save Picture (*.csv)"); // 打开文件的类型,;隔开
  419. if (fileName.isNull()) return;
  420. ZCSV csv;
  421. int datacnt = ui->customPlot->graph(0)->dataCount();
  422. csv.setdata(1, 1, "pointIndex");
  423. csv.setdata(1, 2, "value");
  424. for (size_t i = 0; i < datacnt; i++) {
  425. csv.setdata(i + 1, 1, QString::number(ui->customPlot->graph(0)->data()->at(i)->key).toStdString());
  426. csv.setdata(i + 1, 2, QString::number(ui->customPlot->graph(0)->data()->at(i)->value).toStdString());
  427. }
  428. csv.dumpCSV(fileName.toStdString());
  429. }
  430. /* 水平滚动条移动 */
  431. void WidgetPlot2D::horzScrollBarChanged(int value) {}