QCustomplot2实战示例

QCustomplot

简介

QCustomPlot是一个用于绘制交互式图表和图形的开源C++库。它为Qt应用程序提供了强大的绘图功能,可用于创建各种类型的图表,如线图、柱状图、散点图、饼图等。

QCustomPlot具有灵活的配置选项,可以自定义图表的外观和行为。该库易于使用且功能强大,适用于需要在Qt应用程序中显示和操作图表数据的开发项目。

官网下载:

Qt Plotting Widget QCustomPlot - Download

使用

源码使用

进入上面官网链接:
1

下载后得到qcustomplot.h与qcustomplot.cpp:
2

拷贝到工程目录下,右键 -> 添加现有文件…,将这两个文件添加至工程,即可在项目中使用:
3

.pro文件中需添加printsupport模块:
4

编译动态库使用

进入上面官网链接:

5

下载找到sharedlib-compilation目录:
6

使用和项目相同的编译器编译生成动态库即可:
7

例: 我项目使用的是Qt5.15.2_msvc2019, 因此这里也用Qt5.15.2_msvc2019编译生成动态库:
8

9

将动态库文件和头文件添加到项目中:
10

不知如何将动态链接库添加到项目中可参考:

动态链接库(三)–动态链接库的使用_动态链接库怎么调用-CSDN博客

同样在.pro中添加printsupport模块:

11

最后包含头文件即可使用:

12

需求

因项目需要展示以时间为x轴、以IP值为Y轴的二维坐标系统,而Qt Charts并没有提供专门的对于IP(Internet Protocol)值这样的特殊需求的坐标轴类型,因此考虑使用QCustomPlot。

具体需求:

现有一个Excel文件,内含两张表格,每个表格都有相应的IP及时间记录。需要分析,两种表格中是否存在同一IP,在同一时间段使用的记录。存在则找出这段使用同一IP的时间交集。

需求实现

获取Excel表格数据

这里使用QAxObject 类来获取表格数据。

typedef struct
{QString qsTime;QString qsIP;
}IP_TIME, *PIP_TIME;bool Widget::readExcel()
{// 打开文件对话框,选择文件QString fileName = QFileDialog::getOpenFileName(nullptr, "Open Excel", QDir::currentPath(), "Excel Files (*.xls *.xlsx)");if (fileName.isEmpty()){return false;}//QVector<QList<IP_TIME>> vlIP_Time;	//保存每张表,每行数据的信息vlIP_Time.clear();// 创建连接到Excel的对象QAxObject* excel = new QAxObject("Excel.Application");// 打开工作簿QAxObject* workbooks = excel->querySubObject("WorkBooks");//打开文件QAxObject* workbook = workbooks->querySubObject("Open(QString, QVariant)", fileName, 0);// 获取表格对象集合QAxObject* worksheets = workbook->querySubObject("Worksheets");// 计算工作表数量int worksheetCount = worksheets->dynamicCall("Count()").toInt();// 遍历工作表集合for (int i = 1; i <= worksheetCount; i++){qDebug() << u8"\n =============" << "《 sheet" << i << "》============== \n\n";QList<IP_TIME> qlTmp;// 获取工作表QAxObject* worksheet = worksheets->querySubObject("Item(int)", i);// 获取行数QAxObject* usedRange = worksheet->querySubObject("UsedRange");int rowCount = usedRange->querySubObject("Rows")->property("Count").toInt();// 获取列数int columnCount = usedRange->querySubObject("Columns")->property("Count").toInt();qDebug() << "rowCount: " << rowCount << ", columnCount: " << columnCount << "\n";// 遍历工作表的所有行for (int row = 1; row <= rowCount; row++){//这里有多余数据,强制排除一下if (i == 1 && row >= ui->lineEdit_sheet1Max->text().toInt()){continue;}else if (i == 2 && row >= ui->lineEdit_sheet2Max->text().toInt()){continue;}// 遍历工作表的所有列//表格内容固定:第一列是日期    第二列是时间,            第三列是IP//示例:        2023/7/2   18:07:17 +0800 CST	 223.104.68.94QString qsTime;for (int column = 1; column <= columnCount; column++){// 读取单元格内容QAxObject* cell = worksheet->querySubObject("Cells(int,int)", row, column);QString cellValue;if (column == 1){QDateTime cellDateTime = cell->dynamicCall("Value()").toDateTime();cellValue = cellDateTime.toString("yyyy/MM/dd");}else{cellValue = cell->dynamicCall("Value()").toString();}if (cellValue.isEmpty() || cellValue == "IP" || cellValue == "时间"){qDebug() << "cellValue: " << cellValue;continue;}if (column == 3){if (cellValue.isEmpty()){//qDebug() << "Row:" << row << "Column:" << column << "value :" << cellValue;continue;}qsTime = qsTime.left(qsTime.size() - 10);IP_TIME ipTime;ipTime.qsTime = qsTime;ipTime.qsIP = cellValue;qlTmp.append(ipTime);}else{qsTime += cellValue;}// 输出单元格内容//qDebug() << "Row:" << row << "Column:" << column << "value :" << cellValue;}}//end for(int row = 1; row <= rowCount; row++)vlIP_Time.append(qlTmp);}// 关闭工作簿并关闭Excel应用workbook->dynamicCall("Close()");excel->dynamicCall("Quit()");delete excel;return true;
}

上面这个函数让用户选择Excel文件,然后获取每个表格内容到成员变量vlIP_Time中。

注意:

表格的格式需固定为:

第一列是日期 第二列是时间 第三列是IP

2023/7/2 18:07:17 +0800 CST 223.104.68.94

使用QCustomPlot展示数据

代码如下:

void Widget::getAllIP(const QList<IP_TIME>& ql, const QList<IP_TIME>& ql2, QList<QString>& qlOut)
{qlOut.clear();QSet<QString> commonSet;  // 使用QSet进行去重for (int i = 0; i < ql.size(); i++){commonSet.insert(ql.at(i).qsIP);}for (int j = 0; j < ql2.size(); j++){commonSet.insert(ql2.at(j).qsIP);}// 转换QSet为QListqlOut = commonSet.values();
}void Widget::dataShow()
{qDebug() << "\n dataShoww \n";plot = new QCustomPlot();mainHLayout->addWidget(plot);plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);//悬浮显示各节点信息//plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables | QCP::iSelectAxes);//plot->setSelectionTolerance(5); // 根据需要调整选择公差,此处值为15像素//connect(plot, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(showPointToolTip(QMouseEvent*)));//设置时间x轴QSharedPointer<QCPAxisTickerDateTime> dateTicker(new QCPAxisTickerDateTime);dateTicker->setDateTimeFormat("yyyy/MM/dd hh:mm:ss");plot->xAxis->setTicker(dateTicker);plot->xAxis->setLabel("Time");plot->yAxis->setLabel("IP Address");//QList<QString> qlLabels;	//保存vlIP_Time中,所有使用到的IP列表,用于IP值y轴构建qlLabels.clear();for (int i = 0; i < vlIP_Time.size(); i++){if (i + 1 >= vlIP_Time.size()){break;}QList<IP_TIME> ql = vlIP_Time[i];QList<IP_TIME> ql2 = vlIP_Time[i + 1];getAllIP(ql, ql2, qlLabels);}// 使用 'qSort' 函数对 QList 进行排序std::sort(qlLabels.begin(), qlLabels.end());qDebug() << "\n qlLabels size: " << qlLabels.size() << "\n";qDebug() << qlLabels << "\n";QVector<double> ticks;QVector<QString> labels;int nIndex = 0;foreach(const QString & ip, qlLabels){ticks << nIndex;labels << ip;//qDebug() << "ip: " << ip;++nIndex;}QSharedPointer<QCPAxisTickerText> textTicker(new QCPAxisTickerText);textTicker->addTicks(ticks, labels);plot->yAxis->setTicker(textTicker);for (int i = 0; i < vlIP_Time.size(); i++){plot->addGraph();//各节点连接成线//plot->graph(i)->setLineStyle(QCPGraph::lsNone);plot->graph(i)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc));if (i == 0){plot->graph(i)->setPen(QPen(Qt::blue, 3));plot->graph(i)->setName("zhang");}else if (i == 1){plot->graph(i)->setPen(QPen(Qt::red, 1));plot->graph(i)->setName("ma");}}//添加数据qDebug() << "\n add data \n";for (int i = 0; i < vlIP_Time.size(); i++){QList<IP_TIME> qlIP_TIME = vlIP_Time[i];QString qsOldIP = qlIP_TIME.first().qsIP;QString qsOldTime;for (int j = 0; j < qlIP_TIME.size(); j++){QString qsIP = qlIP_TIME[j].qsIP;QString qsTime = qlIP_TIME[j].qsTime;//只显示重复IPif (qlLabels.indexOf(qsIP) == -1){continue;}QDateTime dateTime = QDateTime::fromString(qsTime, "yyyy/MM/dd HH:mm:ss");double time = dateTime.toMSecsSinceEpoch() / 1000.0;//qDebug() << "qlTime: " << qsTime << ",qsIP: " << qsIP << ", index: " << qlLabels.indexOf(qsIP);if (qsIP != qsOldIP){//添加辅助点,以更好观察plot->graph(i)->addData(time, qlLabels.indexOf(qsOldIP));}qsOldIP = qsIP;qsOldTime = qsTime;plot->graph(i)->addData(time, qlLabels.indexOf(qsIP));}}plot->rescaleAxes();plot->replot();plot->show();
}

效果如下:
13

分析相同IP使用时间交集

核心思路是:

将每个IP及其对应的使用时间段保存在一个数据结构里,然后对每一个IP,我们比对所有的使用时间段,检查是否存在交集。

存在4种交集情况:


14


15

16

④:
17

构造hash:

void Widget::createHashMap()
{//QMap<QString, QList<QPair<QDateTime, QDateTime>>> hashMap;	//保存重复IP的每个使用时间段//例:(103.116.122.114, {[2023/07/02 12:01:34, 2023/07/02 15:34:55], [2023/09/12 08:37:22, 2023/09/14 22:30:12]})hashMap.clear();//QList<QString> qlLabels;	//保存vlIP_Time中,所有使用到的IP列表qDebug() << "createHashMap--qlLabels size: " << qlLabels.size();for (int i = 0; i < vlIP_Time.size(); i++){QList<IP_TIME> qlTime = vlIP_Time[i];QString qsOldIP = qlTime.first().qsIP;QString qsOldTime = qlTime.first().qsTime;for (int j = 0; j < qlTime.count(); j++){IP_TIME ip_time = qlTime[j];QString qsIP = ip_time.qsIP;QString qsTime = ip_time.qsTime;if (qsIP != qsOldIP){QDateTime dateTime_begin = QDateTime::fromString(qsOldTime, "yyyy/MM/dd HH:mm:ss");QDateTime dateTime_end = QDateTime::fromString(qsTime, "yyyy/MM/dd HH:mm:ss");//qDebug() << "IP: " << qsOldIP << ", begin: " << qsOldTime << ", end: " << qsTime;QPair<QDateTime, QDateTime> pair(dateTime_begin, dateTime_end);if (!qlLabels.contains(qsOldIP)){qsOldIP = qsIP;qsOldTime = qsTime;continue;}//IP变化if (hashMap.contains(qsOldIP)){hashMap[qsOldIP].append(pair);}else{QList<QPair<QDateTime, QDateTime>> qlDate;qlDate.append(pair);hashMap.insert(qsOldIP, qlDate);}qsOldIP = qsIP;qsOldTime = qsTime;}else{}}}
}

计算交集:

QMap<QString, QList<QPair<QDateTime, QDateTime>>> Widget::getOverlappingTimePeriods(QMap<QString, QList<QPair<QDateTime, QDateTime>>>& hashMap)
{QMap<QString, QList<QPair<QDateTime, QDateTime>>> resultMap;QList<QString> keys = hashMap.keys();Logger::writeLog(QString(u8"两个表中都有用到的IP数量为:%1").arg(QString::number(keys.count())));Logger::writeLog(QString(u8"具体如下:"));for (int i = 0; i < keys.count(); i++){QString qsLog = QString(u8"%1: %2").arg(QString::number(i + 1)).arg(keys.at(i));Logger::writeLog(qsLog);}//所有使用到的IP列表qDebug() << "qlLabels: " << qlLabels.size();Logger::writeLog(QString(u8"\n\n"));bool bRet = false;for (QString key : keys){QList < QPair<QDateTime, QDateTime> > qlTime = hashMap[key];for (int i = 0; i < qlTime.size(); i++){QPair<QDateTime, QDateTime> pair = qlTime[i];for (int j = i + 1; j < qlTime.size(); j++){QPair<QDateTime, QDateTime> pair2 = qlTime[j];bool bIn = false;//有时间交集, 计算交集区间QPair<QDateTime, QDateTime> pairSame;if (pair.first > pair2.first && pair.second < pair2.second){/*如下:<-----><------------------->*/qDebug() << u8"交集情况①";Logger::writeLog(u8"交集情况①");pairSame = pair;bIn = true;}else if (pair2.first > pair.first && pair2.second < pair.second){/*如下:<----------------><------>*/qDebug() << u8"交集情况②";Logger::writeLog(u8"交集情况②");pairSame = pair2;bIn = true;}else if (pair2.first > pair.first && pair2.first < pair.second){/* 如下:<----><----->*/qDebug() << u8"交集情况③";Logger::writeLog(u8"交集情况③");pairSame.first = pair2.first;pairSame.second = pair.second;bIn = true;}else if (pair.first > pair2.first && pair.first < pair2.second){/* 如下:<-------><------->*/qDebug() << u8"交集情况④";Logger::writeLog(u8"交集情况④");pairSame.first = pair.first;pairSame.second = pair2.second;bIn = true;}bRet |= bIn;if (bIn){qDebug() << u8"当前比较IP: " << key;qDebug() << u8"表1 begin: " << pair.first << ", pair end: " << pair.second;qDebug() << u8"表2 begin: " << pair2.first << ", pair end: " << pair2.second;qDebug() << u8"===时间交集 begin: " << pairSame.first << ", end: " << pairSame.second << " ===\n";//确保交集必记录Logger::writeLog(QString(u8"当前比较IP: %1").arg(key));Logger::writeLog(QString(u8"表1 IP使用时间区间[%1, %2]").arg(pair.first.toString("yyyy/MM/dd hh:mm:ss")).arg(pair.second.toString("yyyy/MM/dd hh:mm:ss")));Logger::writeLog(QString(u8"表2 IP使用时间区间[%1, %2]").arg(pair2.first.toString("yyyy/MM/dd hh:mm:ss")).arg(pair2.second.toString("yyyy/MM/dd hh:mm:ss")));Logger::writeLog(QString(u8"存在时间交集 [%1, %2]").arg(pairSame.first.toString("yyyy/MM/dd hh:mm:ss")).arg(pairSame.second.toString("yyyy/MM/dd hh:mm:ss")));if (!ui->checkBox_logDetail->isChecked()){Logger::writeLog(u8"\n\n");}if (resultMap.contains(key)){resultMap[key].append(pairSame);}else{QList<QPair<QDateTime, QDateTime>> qlTime;qlTime.append(pairSame);resultMap.insert(key, qlTime);}}else{//qDebug() << u8"=====无时间交集=====";if (ui->checkBox_logDetail->isChecked()){Logger::writeLog(QString(u8"当前比较IP: %1").arg(key));Logger::writeLog(QString(u8"表1 IP使用时间区间[%1, %2]").arg(pair.first.toString("yyyy/MM/dd hh:mm:ss")).arg(pair.second.toString("yyyy/MM/dd hh:mm:ss")));Logger::writeLog(QString(u8"表2 IP使用时间区间[%1, %2]").arg(pair2.first.toString("yyyy/MM/dd hh:mm:ss")).arg(pair2.second.toString("yyyy/MM/dd hh:mm:ss")));Logger::writeLog(u8"=====无时间交集=====");}}if (ui->checkBox_logDetail->isChecked()){Logger::writeLog(u8"\n\n");}}//end for (int j = i + 1; j < qlTime.size(); j++)}}//end for (QString key : keys)if (!bRet){Logger::writeLog(u8"\n=====所有IP均无时间交集=====\n");}return resultMap;
}

详细比对信息记录在log文件中。

测试

测试文件:1.xlsx

ps: 测试文件及说明均放在项目输出目录中,项目完整代码见附录下的仓库链接

构造四段(四中情况)交集:

测试文件:1.xlsx

构造4种交集情况:

20
符合IP:223.104.68.233
sheet1: 2023/07/04 14:13:33 到 2023/07/04 15:01:56
sheet2: 2023/07/04 12:08:04 到 2023/07/04 20:35:45
时间交集:2023/07/04 14:13:33 到 2023/07/04 15:01:56


21
符合IP:223.104.68.66
sheet1:2023/07/03 08:37:41 到 2023/07/04 12:05:08
sheet2: 2023/07/03 16:30:04 到 2023/07/03 16:59:43
时间交集:2023/07/03 16:30:04 到 2023/07/03 16:59:43


22

符合IP:103.116.122.50
sheet1: 2023/07/04 21:12:12 到 2023/07/04 22:32:19
sheet2: 2023/07/04 21:52:32 到 2023/07/04 23:45:31
交集时间:2023/07/04 21:52:32 到 2023/07/04 22:32:19


23

符合IP: 121.35.185.77
sheet1: 2023/07/02 18:07:17 到 2023/07/03 08:37:41
sheet2: 2023/07/02 08:55:56 到 2023/07/02 19:58:16
交集时间:2023/07/02 18:07:17 到 2023/07/02 19:58:16

显示比对结果:

18

详细比对信息记录:
19

自测基本满足上述需求。

附录

QCustomPlot官网:

Qt Plotting Widget QCustomPlot - Introduction

QCustomPlot官网下载地址:

Qt Plotting Widget QCustomPlot - Download

动态库使用参考:

动态链接库(三)–动态链接库的使用_动态链接库怎么调用-CSDN博客

完整代码及测试文件:

GitHub - SNAKEpg12138/CoordinateWidget

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/342968.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Maven的pom文件引用以及下载失败

背景&#xff1a;项目中使用到新版本的aspose-words组件&#xff0c;但是引用失败&#xff1b; pom.xml中引用,但是maven 仓库没有aspose-words 后续版本&#xff0c;所以需要在pom中配置 aspose-words的官网。 <dependencies><dependency><groupId>com.as…

K8S--安装MySQL8(单机)

原文网址&#xff1a;K8S--安装MySQL8&#xff08;单机&#xff09;-CSDN博客 简介 本文介绍K8S部署MySQL8&#xff08;单机&#xff09;的方法。 ----------------------------------------------------------------------------------------------- 分享Java真实高频面试题…

MySQL-外键等信息

38. 基础-多表查询-概述_哔哩哔哩_bilibili 1、流程函数 2、约束字段 删除外键 &#xff1a; alter table emp2 drop foreign key 外键名 //外键可以保持数据的一致性和完整性&#xff0c;外键的话&#xff0c;就是类似一个主表&#xff0c;一个从表&#xff0c;从表的其中一…

js 数据回调 异步 Promise

回调顺序 JavaScript 函数按照它们被调用的顺序执行。而不是以它们被定义的顺序。 js数据顺序问题 <!DOCTYPE html> <html> <body><h2>JavaScript 函数序列</h2><p>JavaScript 函数按照它们被调用的顺序执行。</p><p id"de…

Android14之解决Pixel手机联网出现感叹号(一百八十)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

【一文搞懂JVM的内存屏障】

要命的问题&#xff1a; 什么是线程的安全性&#xff1f;怎么保证&#xff1f;jvm什么是的内存屏障&#xff1f;他有什么作用&#xff1f; **线程的安全性是指&#xff1a;**指在多线程环境下&#xff0c;多个线程同时访问同一资源时不会产生意外结果或导致数据出错的状态。其…

基于python的室内老人实时摔倒智能监测系统(康复训练检测+代码)

概述 导入所需的库&#xff0c;包括cv2、和numpy。 定义了一个用于计算角度的函数calculate_angle(a, b, c)&#xff0c;其中a、b和c是三个关键点的坐标。 初始化姿态检测和绘图工具。 打开并读取视频文件。 -摔倒检测&#xff08;fallen&#xff09; 循环遍历视频的每一帧…

“消费新纪元:从传统消费到消费增值的跨越!“

你是否已经厌倦了传统消费模式&#xff0c;感觉每一次购物只是让钱从左手流到右手&#xff1f;现在&#xff0c;一个新的消费模式正在兴起&#xff0c;它让你的消费变得更有价值&#xff01; 消费增值是一种创新的消费理念&#xff0c;它让你在享受优质商品和服务的同时&#x…

Java并发之同步三:Condition条件队列

一、总览 二、源码分析 2.1 人口 public Condition newCondition() {return sync.newCondition();}final ConditionObject newCondition() {return new ConditionObject();}public class ConditionObject implements Condition, java.io.Serializable {private static final lo…

JVM运行时数据区(上篇)

JVM运行时数据区可分为线程共享的堆&#xff0c;方法区和线程独享的虚拟机栈、本地方法栈、程序计时器此外还有一个单独的直接内存&#xff0c;如下图所述&#xff1a; 程序计数器 程序计数器&#xff08;Program Counter Register&#xff09;也叫PC寄存器&#xff0c;每个线…

Windows安装Rust环境(详细教程)

一、 安装mingw64(C语言环境) Rust默认使用的C语言依赖Visual Studio&#xff0c;但该工具占用空间大安装也较为麻烦&#xff0c;可以选用轻便的mingw64包。 1.1 安装地址 (1) 下载地址1-GitHub&#xff1a;Releases niXman/mingw-builds-binaries GitHub (2) 下载地址2-W…

JRT界面打开器

开发BS界面时候有个问题&#xff0c;如果新做页面还没挂菜单&#xff0c;那么测试新页面有两个办法&#xff0c;一是把菜单挂上用&#xff0c;一是手输URL。而我在开发阶段两个事都不想干&#xff0c;那么怎么解决呢&#xff1f; 以前WebLoader启动时候会启动C#写的URL辅助器 …