Browse Source

单导联支持波形显示

master
zhaohe 1 year ago
parent
commit
2560fda1a7
  1. 8
      CMakeLists.txt
  2. 35496
      libzqt/qcustomplot.cpp
  3. 7737
      libzqt/qcustomplot.h
  4. 446
      libzqt/widgetplot2d.cpp
  5. 54
      libzqt/widgetplot2d.h
  6. 351
      libzqt/widgetplot2d.ui
  7. 126
      mainwindow.cpp
  8. 30
      mainwindow.ui
  9. 6
      src/main.cpp

8
CMakeLists.txt

@ -13,7 +13,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED) find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets SerialPort Charts Concurrent REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets PrintSupport SerialPort Charts Concurrent REQUIRED)
include_directories(libxsync/include) include_directories(libxsync/include)
include_directories(src) include_directories(src)
@ -33,6 +33,10 @@ set(PROJECT_SOURCES
src/qt_serial_datachannel.cpp src/qt_serial_datachannel.cpp
src/electrocardiograph_tester.cpp src/electrocardiograph_tester.cpp
libzqt/widgetplot2d.cpp
libzqt/widgetplot2d.ui
libzqt/qcustomplot.cpp
) )
@ -41,7 +45,7 @@ add_executable(electrocardiograph_upper WIN32
) )
target_link_options(electrocardiograph_upper PRIVATE -static -static-libgcc -static-libstdc++) target_link_options(electrocardiograph_upper PRIVATE -static -static-libgcc -static-libstdc++)
target_link_libraries(electrocardiograph_upper PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::SerialPort Qt${QT_VERSION_MAJOR}::Charts Qt${QT_VERSION_MAJOR}::Concurrent wsock32)
target_link_libraries(electrocardiograph_upper PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::PrintSupport Qt${QT_VERSION_MAJOR}::SerialPort Qt${QT_VERSION_MAJOR}::Charts Qt${QT_VERSION_MAJOR}::Concurrent wsock32)
set_target_properties(electrocardiograph_upper PROPERTIES set_target_properties(electrocardiograph_upper PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com

35496
libzqt/qcustomplot.cpp
File diff suppressed because it is too large
View File

7737
libzqt/qcustomplot.h
File diff suppressed because it is too large
View File

446
libzqt/widgetplot2d.cpp

@ -0,0 +1,446 @@
#include "widgetplot2d.h"
#include <QDebug>
#include "ui_widgetplot2d.h"
WidgetPlot2D::WidgetPlot2D(QWidget* parent) : QWidget(parent), ui(new Ui::WidgetPlot2D), time(QTime::currentTime()) {
ui->setupUi(this);
// QCP绘图初始化
initQCP();
// 初始化控件
initWidget();
// 上下分裂器
ui->splitter->setStretchFactor(0, 20);
ui->splitter->setStretchFactor(1, 1);
// 左右分裂器
ui->splitter1->setStretchFactor(0, 10);
ui->splitter1->setStretchFactor(1, 3);
// 滚动条设置
ui->horizontalScrollBar->setRange(0, 0);
// 拖动水平滚动条,图幅跟随变化
connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(horzScrollBarChanged(int)));
// 默认坐标范围
ui->customPlot->yAxis->setRange(-20, 20);
ui->customPlot->replot();
}
WidgetPlot2D::~WidgetPlot2D() { delete ui; }
/* 初始化曲线名称 */
void WidgetPlot2D::initGraphName(QStringList name) {
//----------------------------------------------------------------------------------------//
// 设置表头
ui->treeWidget->setHeaderLabels(QStringList() << "名称"
<< ""
<< "颜色");
ui->treeWidget->header()->setVisible(true);
// 表头文字中间对齐
QTreeWidgetItem* header = ui->treeWidget->headerItem();
header->setTextAlignment(0, Qt::AlignLeft);
header->setTextAlignment(1, Qt::AlignLeft);
header->setTextAlignment(2, Qt::AlignLeft);
// 设置列宽
ui->treeWidget->setColumnWidth(0, 100);
ui->treeWidget->setColumnWidth(1, 90);
ui->treeWidget->setColumnWidth(2, 30);
// 表头顺序是否可以拖动改变
ui->treeWidget->header()->setCascadingSectionResizes(true);
// 被选部分是否高亮显示
ui->treeWidget->header()->setHighlightSections(false);
// 最后一个区域是否占满表格余下的所有部分
ui->treeWidget->header()->setStretchLastSection(true);
// 列表的列数
ui->treeWidget->setColumnCount(3);
//----------------------------------------------------------------------------------------//
for (int i = 0; i < name.length(); i++) {
// 添加曲线列表项
QTreeWidgetItem* item = new QTreeWidgetItem;
ui->treeWidget->addTopLevelItem(item);
//----------------------------------------------------------------------------------------//
// 向曲线列表项第一列添加曲线是否可见复选框
QCheckBox* isShowCheckBox = new QCheckBox(name.at(i));
isShowCheckBox->setChecked(true);
connect(isShowCheckBox, SIGNAL(stateChanged(int)), this, SLOT(changeGraphVisible()));
ui->treeWidget->setItemWidget(item, 0, isShowCheckBox);
isShowCheckBoxVector.append(isShowCheckBox);
//----------------------------------------------------------------------------------------//
// 向曲线列表项第二列添加曲线数值标签
QLabel* valueLabel = new QLabel("0");
QFont font("Courier");
valueLabel->setFont(font);
// valueLabel->setAlignment(Qt::AlignHCenter);
ui->treeWidget->setItemWidget(item, 1, valueLabel);
valueLabelVector.append(valueLabel);
//----------------------------------------------------------------------------------------//
// 向曲线列表项第三列添加曲线颜色按钮,点击该按钮弹出颜色选择框,选择曲线颜色
QPushButton* GraphColorPushButton = new QPushButton("");
// 随机分配初始按钮背景色
static int r = 0, g = 0, b = 0;
r += 150;
g += 50;
b += 100;
if (r > 255) r -= 255;
if (g > 255) g -= 255;
if (b > 255) b -= 255;
QPalette pal;
pal.setColor(QPalette::Button, QColor(r, g, b));
GraphColorPushButton->setPalette(pal);
GraphColorPushButton->setAutoFillBackground(true); // 该句不能缺少,否则背景颜色无法改变
GraphColorPushButton->setFlat(true);
// 固定颜色按钮大小
GraphColorPushButton->setMinimumHeight(18); // 固定高度
GraphColorPushButton->setMaximumHeight(18);
GraphColorPushButton->setMinimumWidth(30); // 固定宽度
GraphColorPushButton->setMaximumWidth(30);
connect(GraphColorPushButton, SIGNAL(clicked()), this, SLOT(changeGraphColor()));
ui->treeWidget->setItemWidget(item, 2, GraphColorPushButton);
GraphColorPushButtonVector.append(GraphColorPushButton);
//----------------------------------------------------------------------------------------//
// 添加曲线
ui->customPlot->addGraph();
ui->customPlot->graph(ui->customPlot->graphCount() - 1)->setPen(QPen(QColor(r, g, b)));
ui->customPlot->graph(ui->customPlot->graphCount() - 1)->setVisible(true);
// 将曲线名称与曲线序号一一对应,之后添加数据就可以一一对应
nameToGraphMap[name.at(i)] = i;
// 初始化数据容器的值
for (int t = 0; t < name.length(); t++) {
valueVector.append(0);
}
}
}
/* 添加数据 */
void WidgetPlot2D::addData(QString name, double value, int offms) {
// 如果点击了“暂停”按钮,则不绘制图形
if (ui->pausePBtn->text() == "开始") return;
// 系统当前时间 = 系统运行初始时间 + 系统运行时间
static double start = time.hour() * 60 * 60 + time.minute() * 60 + time.second() + time.msec() / 1000.0;
double key = start + time.elapsed() / 1000.0 + offms / 1000;
// double start = 0;
// static int key = 0;
// key++;
// qDebug() << "start" << start;
// qDebug() << "key" << key;
// 子网格显示
ui->customPlot->xAxis->grid()->setSubGridVisible(ui->subGridCheck->isChecked());
ui->customPlot->yAxis->grid()->setSubGridVisible(ui->subGridCheck->isChecked());
// 自适应量程
if (ui->autoRangeCheck->isChecked()) {
ui->customPlot->rescaleAxes();
}
// 设置时间轴
int timeAxis = ui->timeAxisSpin->value();
ui->customPlot->xAxis->setRange(key, timeAxis, Qt::AlignRight);
// x轴和y轴全程显示
if (ui->fullShowCheck->isChecked()) {
ui->customPlot->rescaleAxes();
}
// 刷新绘图水平滚动条
ui->horizontalScrollBar->setRange(int(start), int(key)); // 刷新滚动条的范围
ui->horizontalScrollBar->setPageStep(1); // 设置翻页步长为 1s 的宽度
ui->horizontalScrollBar->setValue(int(key)); // 调整滑块位置到最右边
// 更新曲线绘图
ui->customPlot->graph(nameToGraphMap[name])->addData(key, value);
// ui->customPlot->replot();
// 使用rpQueuedReplot参数可以加快绘图速度,避免不必要的重复绘制
ui->customPlot->replot(QCustomPlot::rpQueuedReplot);
// 存储曲线的当前值
valueVector[nameToGraphMap[name]] = value;
//----------------------------------------------------------------------------------------//
// 计算每秒的帧率
static double lastFpsKey = key;
static int frameCount = 0;
frameCount++;
// 每2秒显示一次帧率
if (key - lastFpsKey > 0.5) {
uint64_t sum = 0;
for (int i = 0; i < ui->customPlot->plottableCount(); i++) {
sum += uint64_t(ui->customPlot->graph(i)->data()->size());
}
ui->statusLabel->setText(QString("%1 FPS, Total Data points: %2").arg(frameCount / (key - lastFpsKey), 0, 'f', 0).arg(sum));
lastFpsKey = key;
frameCount = 0;
// 更新数据标签
for (int t = 0; t < ui->customPlot->plottableCount(); t++) {
valueLabelVector[t]->setText(QString::number(valueVector[t]));
}
}
//----------------------------------------------------------------------------------------//
}
/* 曲线是否显示 */
void WidgetPlot2D::changeGraphVisible() {
for (int i = 0; i < isShowCheckBoxVector.length(); i++) {
ui->customPlot->graph(i)->setVisible(isShowCheckBoxVector[i]->isChecked());
}
}
/* 改变曲线颜色 */
void WidgetPlot2D::changeGraphColor() {
/* 在槽(SLOT)中sender()函数会返回一个指向 QObject 的指针来指向信号的发送者。
* C++ RTTI(Run-Time Type Identification)dynamic_cast运算符
* sender()QPushButton类sender()QObject指针转换为QPushButton指针
* if中的语句就会执行sender()QPushButton类型的指针dynamic_cast就会返回0if中的语句就不会执行了
* */
if (QPushButton* btn = dynamic_cast<QPushButton*>(sender())) {
QColor color = QColorDialog::getColor(Qt::white, this);
// 用户取消颜色对话框
if (color.isValid() == false) return;
// 设置按钮背景色
QPalette pal; // = btn->palette();
pal.setColor(QPalette::Button, color);
btn->setPalette(pal);
btn->setAutoFillBackground(true); // 该句不能缺少,否则背景颜色无法改变
btn->setFlat(true); // 该句不能缺少,否则背景颜色无法改变
}
// 改变对应的曲线颜色
for (int i = 0; i < GraphColorPushButtonVector.length(); i++) {
// 获取按钮的颜色
QPalette pal = GraphColorPushButtonVector[i]->palette();
QColor color = pal.color(QPalette::Button);
// 曲线颜色对应按钮颜色
ui->customPlot->graph(i)->setPen(QPen(color));
}
ui->customPlot->replot();
}
/* QCP绘图初始化 */
void WidgetPlot2D::initQCP() {
// 刻度显示
ui->customPlot->xAxis->setTicks(true);
ui->customPlot->yAxis->setTicks(true);
// 刻度值显示
ui->customPlot->xAxis->setTickLabels(true);
ui->customPlot->yAxis->setTickLabels(true);
// 网格显示
ui->customPlot->xAxis->grid()->setVisible(true);
ui->customPlot->yAxis->grid()->setVisible(true);
// 子网格显示
ui->customPlot->xAxis->grid()->setSubGridVisible(false);
ui->customPlot->yAxis->grid()->setSubGridVisible(false);
// 右和上坐标轴、刻度值显示
ui->customPlot->xAxis2->setVisible(true);
ui->customPlot->yAxis2->setVisible(true);
ui->customPlot->yAxis2->setTicks(true);
ui->customPlot->yAxis2->setTickLabels(true);
// make top right axes clones of bottom left axes. Looks prettier:
// ui->customPlot->axisRect()->setupFullAxesBox();
// make left and bottom axes always transfer their ranges to right and top axes:
connect(ui->customPlot->xAxis, SIGNAL(rangeChanged(QCPRange)), ui->customPlot->xAxis2, SLOT(setRange(QCPRange)));
connect(ui->customPlot->yAxis, SIGNAL(rangeChanged(QCPRange)), ui->customPlot->yAxis2, SLOT(setRange(QCPRange)));
// 暗色主题
// setPlotTheme(Qt::white, Qt::black);
// 亮色主题
setTheme(Qt::black, Qt::white);
// 可放大缩小和移动
ui->customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);
// x轴以时间形式显示
QSharedPointer<QCPAxisTickerTime> timeTicker(new QCPAxisTickerTime);
timeTicker->setTimeFormat("%h:%m:%s");
ui->customPlot->xAxis->setTicker(timeTicker);
ui->customPlot->axisRect()->setupFullAxesBox();
ui->customPlot->replot();
}
/* 设置绘图主题 */
void WidgetPlot2D::setTheme(QColor axis, QColor background) {
// 坐标颜色按钮
QPalette pal = ui->axisColorPBtn->palette();
pal.setColor(QPalette::Button, axis);
ui->axisColorPBtn->setPalette(pal);
ui->axisColorPBtn->setAutoFillBackground(true); // 该句不能缺少,否则背景颜色无法改变
ui->axisColorPBtn->setFlat(true); // 该句不能缺少,否则背景颜色无法改变
// 背景颜色按钮
pal = ui->backgroundColorPBtn->palette();
pal.setColor(QPalette::Button, background);
ui->backgroundColorPBtn->setPalette(pal);
ui->backgroundColorPBtn->setAutoFillBackground(true); // 该句不能缺少,否则背景颜色无法改变
ui->backgroundColorPBtn->setFlat(true); // 该句不能缺少,否则背景颜色无法改变
//----------------------------------------------------------------------------------------//
// 坐标标注颜色
ui->customPlot->xAxis->setLabelColor(axis);
ui->customPlot->yAxis->setLabelColor(axis);
// 坐标刻度值颜色
ui->customPlot->xAxis->setTickLabelColor(axis);
ui->customPlot->yAxis->setTickLabelColor(axis);
// 坐标基线颜色和宽度
ui->customPlot->xAxis->setBasePen(QPen(axis, 1));
ui->customPlot->yAxis->setBasePen(QPen(axis, 1));
// 坐标主刻度颜色和宽度
ui->customPlot->xAxis->setTickPen(QPen(axis, 1));
ui->customPlot->yAxis->setTickPen(QPen(axis, 1));
// 坐标子刻度颜色和宽度
ui->customPlot->xAxis->setSubTickPen(QPen(axis, 1));
ui->customPlot->yAxis->setSubTickPen(QPen(axis, 1));
// 坐标标注颜色
ui->customPlot->xAxis2->setLabelColor(axis);
ui->customPlot->yAxis2->setLabelColor(axis);
// 坐标刻度值颜色
ui->customPlot->xAxis2->setTickLabelColor(axis);
ui->customPlot->yAxis2->setTickLabelColor(axis);
// 坐标基线颜色和宽度
ui->customPlot->xAxis2->setBasePen(QPen(axis, 1));
ui->customPlot->yAxis2->setBasePen(QPen(axis, 1));
// 坐标主刻度颜色和宽度
ui->customPlot->xAxis2->setTickPen(QPen(axis, 1));
ui->customPlot->yAxis2->setTickPen(QPen(axis, 1));
// 坐标子刻度颜色和宽度
ui->customPlot->xAxis2->setSubTickPen(QPen(axis, 1));
ui->customPlot->yAxis2->setSubTickPen(QPen(axis, 1));
// 整个画布背景色
ui->customPlot->setBackground(background);
// 绘图区域背景色
ui->customPlot->axisRect()->setBackground(background);
// 刷新绘图
ui->customPlot->replot();
}
/* 初始化控件 */
void WidgetPlot2D::initWidget() {
// 时间轴默认时间宽度
ui->timeAxisSpin->setValue(20);
// 主题
QStringList theme;
theme << "亮色"
<< "暗色"
<< "自定义";
ui->themeCombo->addItems(theme);
connect(ui->themeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(changePlotTheme()));
//----------------------------------------------------------------------------------------//
// 坐标颜色按钮
QPalette pal = ui->axisColorPBtn->palette();
pal.setColor(QPalette::Button, Qt::black);
ui->axisColorPBtn->setPalette(pal);
ui->axisColorPBtn->setAutoFillBackground(true); // 该句不能缺少,否则背景颜色无法改变
ui->axisColorPBtn->setFlat(true); // 该句不能缺少,否则背景颜色无法改变
connect(ui->axisColorPBtn, SIGNAL(clicked()), this, SLOT(changePlotTheme()));
// 背景颜色按钮
pal = ui->backgroundColorPBtn->palette();
pal.setColor(QPalette::Button, Qt::white);
ui->backgroundColorPBtn->setPalette(pal);
ui->backgroundColorPBtn->setAutoFillBackground(true); // 该句不能缺少,否则背景颜色无法改变
ui->backgroundColorPBtn->setFlat(true); // 该句不能缺少,否则背景颜色无法改变
connect(ui->backgroundColorPBtn, SIGNAL(clicked()), this, SLOT(changePlotTheme()));
// 操作按钮
connect(ui->clearPBtn, SIGNAL(clicked()), this, SLOT(plotOperation()));
connect(ui->fullShowPBtn, SIGNAL(clicked()), this, SLOT(plotOperation()));
connect(ui->savePBtn, SIGNAL(clicked()), this, SLOT(plotOperation()));
connect(ui->pausePBtn, SIGNAL(clicked()), this, SLOT(plotOperation()));
//----------------------------------------------------------------------------------------//
}
/* 改变画图主题 */
void WidgetPlot2D::changePlotTheme() {
// 主题选择
if (QComboBox* combo = dynamic_cast<QComboBox*>(sender())) {
if (combo->currentText() == "亮色") {
setTheme(Qt::black, Qt::white); // 亮色主题
} else if (combo->currentText() == "暗色") {
setTheme(Qt::white, Qt::black); // 暗色主题
} else if (combo->currentText() == "自定义") {
// 绘图坐标颜色
QPalette axisPal = ui->axisColorPBtn->palette();
QColor axisColor = axisPal.color(QPalette::Button);
// 绘图背景颜色
QPalette backgroundPal = ui->backgroundColorPBtn->palette();
QColor backgroundColor = backgroundPal.color(QPalette::Button);
setTheme(axisColor, backgroundColor);
}
}
// 用户自定义主题
if (QPushButton* btn = dynamic_cast<QPushButton*>(sender())) {
QColor color = QColorDialog::getColor(Qt::white, this);
// 用户取消颜色对话框
if (color.isValid() == false) return;
// 设置按钮背景色
QPalette pal = btn->palette();
pal.setColor(QPalette::Button, color);
btn->setPalette(pal);
btn->setAutoFillBackground(true); // 该句不能缺少,否则背景颜色无法改变
btn->setFlat(true); // 该句不能缺少,否则背景颜色无法改变
// 绘图坐标颜色
QPalette axisPal = ui->axisColorPBtn->palette();
QColor axisColor = axisPal.color(QPalette::Button);
// 绘图背景颜色
QPalette backgroundPal = ui->backgroundColorPBtn->palette();
QColor backgroundColor = backgroundPal.color(QPalette::Button);
// 改变主题颜色
setTheme(axisColor, backgroundColor);
ui->themeCombo->setCurrentText("自定义");
}
}
/* 绘图操作 */
void WidgetPlot2D::plotOperation() {
if (QPushButton* btn = dynamic_cast<QPushButton*>(sender())) {
if (btn->text() == "清除") {
for (int i = 0; i < ui->customPlot->graphCount(); i++) {
// 先获得每条曲线的数据指针,然后删除数据
ui->customPlot->graph(i)->data()->clear();
ui->customPlot->replot();
time = QTime::currentTime();
}
}
if (btn->text() == "整图") {
ui->customPlot->rescaleAxes();
ui->customPlot->replot();
}
if (btn->text() == "保存") {
savePlotPng();
}
if (btn->text() == "暂停") {
btn->setText("开始");
QIcon icon(":/image/player.png");
btn->setIcon(icon);
} else if (btn->text() == "开始") {
btn->setText("暂停");
QIcon icon(":/image/pause.png");
btn->setIcon(icon);
}
}
}
/* 判断路径是否存在,不存在则新建,只能创建一级子目录,必须保证上级目录存在 */
bool WidgetPlot2D::isDirExist(QString fullPath) {
QDir dir(fullPath);
if (dir.exists()) {
return true;
} else {
// 创建一级子目录,必须保证上级目录存在
bool ok = dir.mkdir(fullPath);
return ok;
}
}
/* 保存绘图成图片 */
void WidgetPlot2D::savePlotPng() {
// 获取程序运行路径
QString savePath = QCoreApplication::applicationDirPath() + "/output";
// 判断文件路径
if (isDirExist(savePath)) {
} else {
qDebug() << "保存文件目录未找到!";
QMessageBox::warning(this, "warning", "保存文件目录未找到!", QMessageBox::Yes, QMessageBox::Yes);
}
QString fileName = QFileDialog::getSaveFileName(this,
"保存波形数据", // 对话框的标题
savePath, // 保存的默认路径为程序运行路径
"Save Picture (*.png *jpg)"); // 打开文件的类型,;隔开
// 如果用户点击了“取消”按钮
if (fileName.isNull()) return;
// 保存图片
ui->customPlot->savePng(fileName, 1280, 800, 1.0, -1, 255);
}
/* 水平滚动条移动 */
void WidgetPlot2D::horzScrollBarChanged(int value) {
// 若全程显示,则不移动绘图坐标
if (ui->fullShowCheck->isChecked()) return;
int timeAxis = ui->timeAxisSpin->value();
ui->customPlot->xAxis->setRange(value, timeAxis, Qt::AlignRight);
ui->customPlot->replot();
}

54
libzqt/widgetplot2d.h

@ -0,0 +1,54 @@
#ifndef WIDGETPLOT2D_H
#define WIDGETPLOT2D_H
#include <QWidget>
#include <QCheckBox>
#include <QLabel>
#include <QPushButton>
#include "qcustomplot.h"
namespace Ui {
class WidgetPlot2D;
}
class WidgetPlot2D : public QWidget
{
Q_OBJECT
public:
explicit WidgetPlot2D(QWidget *parent = 0);
~WidgetPlot2D();
void initGraphName(QStringList name);
private:
Ui::WidgetPlot2D *ui;
QVector<QCheckBox*> isShowCheckBoxVector; //
QVector<QLabel*> valueLabelVector; // label数值显示
QVector<QPushButton*> GraphColorPushButtonVector; // 线
QMap<QString, int> nameToGraphMap; // 线线
QVector<double> valueVector; // 线
QTime time;
int pointNum = 0;
void initQCP();
void initWidget();
void setTheme(QColor axis, QColor background);
bool isDirExist(QString fullPath);
void savePlotPng();
private slots:
void changeGraphVisible();
void changeGraphColor();
void plotOperation();
void horzScrollBarChanged(int value);
void changePlotTheme();
public slots:
void addData(QString name, double value,int offms);
};
#endif // WIDGETPLOT2D_H

351
libzqt/widgetplot2d.ui

@ -0,0 +1,351 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WidgetPlot2D</class>
<widget class="QWidget" name="WidgetPlot2D">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>904</width>
<height>531</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<property name="styleSheet">
<string notr="true">/******************** QGroupBox ***********************/
QGroupBox#plotUiGroup, #plotShowGroup, #plotOperGroup, #plotSetupGroup
{
min-width: 150px;
max-width: 150px;
min-height: 100px;
max-height: 100px;
border: 1px solid darkGreen;
border-radius: 8px;
background: rgba(250, 240, 230, 0.7); /*背景半透明 */
margin: 5px 0px 0px 0px;
}
QGroupBox#plotGroup
{
border: 1px solid darkGreen;
border-radius: 3px;
margin: 5px 0px 0px 0px;
}
QGroupBox::title
{
subcontrol-origin: margin;
subcontrol-position: top;
}
/****************** QPushButton *******************/
QPushButton#clearPBtn, #fullShowPBtn, #savePBtn, #openPBtn, #pausePBtn
{
border: 1px solid black;
background-color: lightGray;
width: 25px;
height: 20px;
border-radius: 5px; /* 边框圆角半径 */
}
QPushButton#clearPBtn:hover, #fullShowPBtn:hover, #savePBtn:hover, #openPBtn:hover, #pausePBtn:hover
{
border: 1px solid darkCyan;
border-radius: 5px; /* 边框圆角半径 */
}
QPushButton#clearPBtn:pressed, #fullShowPBtn:pressed, #savePBtn:pressed, #openPBtn:pressed, #pausePBtn:pressed
{
color: white;
border: 1px solid darkCyan;
background-color: darkCyan; /* 背景 */
border-radius: 5px; /* 边框圆角半径 */
}
/***********************QScrollBar****************************/
</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSplitter" name="splitter1">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QGroupBox" name="plotGroup">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string/>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>1</number>
</property>
<property name="leftMargin">
<number>1</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>1</number>
</property>
<property name="bottomMargin">
<number>1</number>
</property>
<item>
<widget class="QCustomPlot" name="customPlot" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QScrollBar" name="horizontalScrollBar">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QGroupBox" name="plotUiGroup">
<property name="title">
<string>颜色</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QComboBox" name="themeCombo"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>坐标颜色</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>主题</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>背景颜色</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="backgroundColorPBtn">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="axisColorPBtn">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="plotShowGroup">
<property name="title">
<string>显示</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="autoRangeCheck">
<property name="text">
<string>自适应量程</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="subGridCheck">
<property name="text">
<string>显示子网格</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>时间轴:</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QSpinBox" name="timeAxisSpin">
<property name="suffix">
<string> s</string>
</property>
<property name="prefix">
<string/>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>999999999</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QCheckBox" name="fullShowCheck">
<property name="text">
<string>全程</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="plotOperGroup">
<property name="minimumSize">
<size>
<width>152</width>
<height>107</height>
</size>
</property>
<property name="title">
<string>操作</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="2">
<widget class="QPushButton" name="fullShowPBtn">
<property name="text">
<string>整图</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QPushButton" name="clearPBtn">
<property name="text">
<string>清除</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="savePBtn">
<property name="text">
<string>保存</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="pausePBtn">
<property name="text">
<string>暂停</string>
</property>
<property name="icon">
<iconset resource="image.qrc">
<normaloff>:/image/pause.png</normaloff>:/image/pause.png</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="statusLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>绘图状态</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QTreeWidget" name="treeWidget">
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QCustomPlot</class>
<extends>QWidget</extends>
<header>qcustomplot.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="image.qrc"/>
</resources>
<connections/>
</ui>

126
mainwindow.cpp

@ -10,8 +10,11 @@
#include "electrocardiograph_tester.hpp" #include "electrocardiograph_tester.hpp"
#include "logger.hpp" #include "logger.hpp"
#include "qt_serial_datachannel.hpp" #include "qt_serial_datachannel.hpp"
#include "widgetplot2d.h"
#include "zexception.hpp" #include "zexception.hpp"
WidgetPlot2D *wp2d;
using namespace std; using namespace std;
using namespace iflytop; using namespace iflytop;
typedef enum { typedef enum {
@ -21,7 +24,6 @@ typedef enum {
static MainWindow *m_mainWindow; static MainWindow *m_mainWindow;
static QTDataChannel G_QTDataChannel; static QTDataChannel G_QTDataChannel;
static QTDataChannel G_WaveDataChannel;
static device_type_t m_devicetype; static device_type_t m_devicetype;
#define TAG "MainWindow" #define TAG "MainWindow"
@ -127,27 +129,6 @@ typedef struct {
} Wave_t; } Wave_t;
#pragma pack(pop) #pragma pack(pop)
void MainWindow::displayWave(int16_t wave1, int16_t wave2, int16_t wave3) {
// 1. 生成波形数据
Wave_t wave;
wave.header = 0xAAAA;
wave.wave1 = wave1;
wave.wave2 = wave2;
wave.wave3 = wave3;
wave.wave4 = 0;
wave.wave5 = 0;
wave.tail = 0x5555;
wave.check = 0;
uint8_t check = 0;
for (size_t i = 2; i < sizeof(wave) - 2; i++) {
check += ((uint8_t *)&wave)[i];
}
wave.check = check;
// 2. 发送波形数据
G_WaveDataChannel.send((uint8_t *)&wave, sizeof(wave));
}
void MainWindow::displayInfo(bool suc, QString info) {} void MainWindow::displayInfo(bool suc, QString info) {}
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
@ -163,6 +144,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
qRegisterMetaType<QFunction>("QFunction"); qRegisterMetaType<QFunction>("QFunction");
connect(this, SIGNAL(doinui_signal(QFunction)), this, SLOT(doinui_slot(QFunction))); connect(this, SIGNAL(doinui_signal(QFunction)), this, SLOT(doinui_slot(QFunction)));
// qInstallMessageHandler(log_output); // qInstallMessageHandler(log_output);
wp2d = new WidgetPlot2D();
wp2d->initGraphName(QStringList() << "心电");
/******************************************************************************* /*******************************************************************************
* * * *
@ -173,7 +156,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
* * * *
*******************************************************************************/ *******************************************************************************/
G_QTDataChannel.init(); G_QTDataChannel.init();
G_WaveDataChannel.init();
ElectrocardiographTester::ins()->initialize(&G_QTDataChannel); ElectrocardiographTester::ins()->initialize(&G_QTDataChannel);
} }
MainWindow::~MainWindow() { delete ui; } MainWindow::~MainWindow() { delete ui; }
@ -242,57 +224,17 @@ void MainWindow::constructUI() {
* UI * * UI *
*******************************************************************************/ *******************************************************************************/
{ {
const auto infos = QSerialPortInfo::availablePorts();
for (const QSerialPortInfo &info : infos) {
ui->waveSerialPortCB->addItem(info.portName());
}
}
ui->waveSerialBaudrateCB->addItem("9600");
ui->waveSerialBaudrateCB->addItem("14400");
ui->waveSerialBaudrateCB->addItem("19200");
ui->waveSerialBaudrateCB->addItem("38400");
ui->waveSerialBaudrateCB->addItem("57600");
ui->waveSerialBaudrateCB->addItem("115200");
ui->waveSerialBaudrateCB->addItem("460800");
ui->waveSerialBaudrateCB->addItem("500000");
ui->waveSerialBaudrateCB->setCurrentIndex(6);
connect(ui->waveSerialPortRefreshKey, &QPushButton::clicked, this, [this](bool check) {
ui->waveSerialPortCB->clear();
const auto infos = QSerialPortInfo::availablePorts();
for (const QSerialPortInfo &info : infos) {
ui->waveSerialPortCB->addItem(info.portName());
ui->deviceType->addItem("单导联一代(M1001)");
ui->deviceType->addItem("三导联一代(M1002)");
ui->deviceType->setCurrentIndex(0);
connect(ui->deviceType, &QComboBox::currentTextChanged, this, [this](const QString &text) {
if (text == "单导联一代(M1001)") {
m_devicetype = kone_lead_ecg;
} else if (text == "三导联一代(M1002)") {
m_devicetype = kthree_lead_ecg;
} }
}); });
connect(ui->waveSerialOpenKey, &QPushButton::clicked, this, [=](bool check) {
// 打开串口
if (ui->waveSerialOpenKey->text() == "打开") {
G_WaveDataChannel.setPortName(ui->waveSerialPortCB->currentText().toStdString());
G_WaveDataChannel.setBaudRate(ui->waveSerialBaudrateCB->currentText().toInt());
G_WaveDataChannel.setDataBits(QSerialPort::Data8);
G_WaveDataChannel.setParity(QSerialPort::NoParity);
G_WaveDataChannel.setFlowControl(QSerialPort::NoFlowControl);
G_WaveDataChannel.setStopBits(QSerialPort::OneStop);
if (!G_WaveDataChannel.open()) {
QMessageBox::about(NULL, "提示", "串口无法打开,串口不存在或已被占??");
return;
}
ui->waveSerialOpenKey->setText("关闭");
// 下拉菜单控件使能
ui->waveSerialBaudrateCB->setEnabled(false);
ui->waveSerialPortCB->setEnabled(false);
ui->waveSerialPortRefreshKey->setEnabled(false);
} else {
G_WaveDataChannel.close();
ui->waveSerialOpenKey->setText("打开");
ui->waveSerialBaudrateCB->setEnabled(true);
ui->waveSerialPortCB->setEnabled(true);
ui->waveSerialPortRefreshKey->setEnabled(true);
} }
});
// 事件填充 // 事件填充
ElectrocardiographTester::ins()->regReportCB([this](bool checkok, ify_hrs_packet_t *report_packet, size_t len) { ElectrocardiographTester::ins()->regReportCB([this](bool checkok, ify_hrs_packet_t *report_packet, size_t len) {
@ -300,21 +242,43 @@ void MainWindow::constructUI() {
switch (reportType) { switch (reportType) {
case ify_hrs_report_heartrate_data: { case ify_hrs_report_heartrate_data: {
heartrate_report_packet_t *heartrate_report = (heartrate_report_packet_t *)report_packet; heartrate_report_packet_t *heartrate_report = (heartrate_report_packet_t *)report_packet;
// reportPreviewShow("[preview data ] index %llu", heartrate_report->sample_data_index);
if (m_devicetype == kone_lead_ecg) {
if (checkok) {
uint16_t data0 = (uint16_t)(heartrate_report->data[0]) + ((uint16_t)heartrate_report->data[1] << 8);
uint16_t data1 = (uint16_t)(heartrate_report->data[2]) + ((uint16_t)heartrate_report->data[3] << 8);
uint16_t data2 = (uint16_t)(heartrate_report->data[4]) + ((uint16_t)heartrate_report->data[5] << 8);
uint16_t data3 = (uint16_t)(heartrate_report->data[6]) + ((uint16_t)heartrate_report->data[7] << 8);
uint16_t data4 = (uint16_t)(heartrate_report->data[8]) + ((uint16_t)heartrate_report->data[9] << 8);
emit doinui_signal(QFunction([this, data0, data1, data2, data3, data4]() { //
reportPreviewShow("[preview data ] index %llu %d", data0);
wp2d->addData("心电", data0, 0);
wp2d->addData("心电", data1, 2);
wp2d->addData("心电", data2, 4);
wp2d->addData("心电", data3, 6);
wp2d->addData("心电", data4, 8);
// wp2d->addData("心电", data2);
// wp2d->addData("心电", data3);
// wp2d->addData("心电", data4);
}));
}
} else if (m_devicetype == kthree_lead_ecg) {
}
uint16_t header = 0xA55A;
uint16_t tail = 0xB55B;
G_WaveDataChannel.send((uint8_t *)&header, sizeof(header));
G_WaveDataChannel.send((uint8_t *)report_packet, len);
G_WaveDataChannel.send((uint8_t *)&tail, sizeof(tail));
// heartrate_report->data[]
// uin
// uint16_t header = 0xA55A;
// uint16_t tail = 0xB55B;
// G_WaveDataChannel.send((uint8_t *)&header, sizeof(header));
// G_WaveDataChannel.send((uint8_t *)report_packet, len);
// G_WaveDataChannel.send((uint8_t *)&tail, sizeof(tail));
// if (m_devicetype == kone_lead_ecg) { // if (m_devicetype == kone_lead_ecg) {
reportPreviewShow("[preview data ] checksum_ok:%d index %llu", checkok, heartrate_report->sample_data_index);
// reportPreviewShow("[preview data ] checksum_ok:%d index %llu", checkok, heartrate_report->sample_data_index);
// } else if (m_devicetype == kthree_lead_ecg) { // } else if (m_devicetype == kthree_lead_ecg) {
// displayWave(heartrate_report->data[0], heartrate_report->data[1], heartrate_report->data[2]); // displayWave(heartrate_report->data[0], heartrate_report->data[1], heartrate_report->data[2]);
// } // }
break;
// break;
} }
case ify_hrs_report_battery_level: { case ify_hrs_report_battery_level: {
reportPreviewShow("[battery_level ]"); reportPreviewShow("[battery_level ]");
@ -536,6 +500,7 @@ void MainWindow::on_startRealtimeReport_clicked() {
try { try {
ElectrocardiographTester::ins()->startRealtimeReport(); ElectrocardiographTester::ins()->startRealtimeReport();
instructionPreviewShow("start realtime report success"); instructionPreviewShow("start realtime report success");
wp2d->show();
} catch (zexception &exception) { } catch (zexception &exception) {
processException(exception); processException(exception);
} }
@ -546,6 +511,7 @@ void MainWindow::on_stopRealtimeReport_clicked() {
try { try {
ElectrocardiographTester::ins()->stopRealtimeReport(); ElectrocardiographTester::ins()->stopRealtimeReport();
instructionPreviewShow("stop realtime report success"); instructionPreviewShow("stop realtime report success");
wp2d->hide();
} catch (zexception &exception) { } catch (zexception &exception) {
processException(exception); processException(exception);
} }

30
mainwindow.ui

@ -667,40 +667,16 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
</size> </size>
</property> </property>
<property name="title"> <property name="title">
<string>波形串口设置</string>
<string>设备配置</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_6"> <layout class="QGridLayout" name="gridLayout_6">
<item row="0" column="1"> <item row="0" column="1">
<widget class="QComboBox" name="waveSerialPortCB"/>
</item>
<item row="3" column="0" colspan="2">
<widget class="QPushButton" name="waveSerialOpenKey">
<property name="text">
<string>打开</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="waveSerialBaudrateCB"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="serialBaudrateLable_2">
<property name="text">
<string>波特率</string>
</property>
</widget>
<widget class="QComboBox" name="deviceType"/>
</item> </item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="serialPortLable_2"> <widget class="QLabel" name="serialPortLable_2">
<property name="text"> <property name="text">
<string>串口</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QPushButton" name="waveSerialPortRefreshKey">
<property name="text">
<string>刷新</string>
<string>型号</string>
</property> </property>
</widget> </widget>
</item> </item>

6
src/main.cpp

@ -22,6 +22,8 @@
#include "mainwindow.h" #include "mainwindow.h"
// //
#include <winsock2.h> #include <winsock2.h>
#include "widgetplot2d.h"
// //
#include <Windows.h> #include <Windows.h>
// //
@ -30,6 +32,7 @@
using namespace std; using namespace std;
QT_CHARTS_USE_NAMESPACE QT_CHARTS_USE_NAMESPACE
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
WSADATA wsaData; WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData); WSAStartup(MAKEWORD(2, 2), &wsaData);
@ -37,6 +40,9 @@ int main(int argc, char *argv[]) {
QApplication a(argc, argv); QApplication a(argc, argv);
MainWindow w; MainWindow w;
w.show(); w.show();
ZLOGI(TAG, "camera_light_src_timing_controller"); ZLOGI(TAG, "camera_light_src_timing_controller");
ZLOGI(TAG, "maufacturer : %s", MAUFACTURER); ZLOGI(TAG, "maufacturer : %s", MAUFACTURER);
ZLOGI(TAG, "version : %d.0.0", VERSION); ZLOGI(TAG, "version : %d.0.0", VERSION);

Loading…
Cancel
Save