QCustomplot2实战示例

发布时间:2024年01月12日

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.94
			QString 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为QList
	qlOut = 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;

			//只显示重复IP
			if (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

文章来源:https://blog.csdn.net/SNAKEpc12138/article/details/135546305
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。