QCustomPlot多个y轴一个x轴、实时绘制多条曲线

备注:
1、动态增加/移除坐标系;
2、多段y轴,共用同一个x轴;
3、x轴y轴数据同步,当放大缩小表格时;
4、通过定时器0.5s更新一次数据;

源码下载地址:
https://download.csdn.net/download/weixin_45074487/90592500

也可参考下面文档,大数据量刷新曲线一秒500000个数据点
https://blog.csdn.net/weixin_45074487/article/details/144985133

****亲,感觉不错的话点个赞哦****

在这里插入图片描述

话不多说上源码:亲测可用

1、左侧通用树形目录参考: https://blog.csdn.net/weixin_45074487/article/details/137081375

一、项目中结合树形目录勾选框,进行动态增加和删除勾选框,通过定时器模拟数据进行显示

connect(m_treeWidget, &TreeDirectoryWidget::sig_itemCheckChange, this, &WdtCustomChart::SlotItemChange);
connect(m_data_timer, SIGNAL(timeout()), this, SLOT(SlotDataTimeOut()));
void WdtCustomChart::SlotItemChange(QString id, bool check)
{
    //根据树形目录勾选框是否勾选进行动态增加/移除坐标系
	if (check)
	{
		auto rows = m_plot->plotLayout()->rowCount();
		QCPAxisRect* axisRect = CreateQCPAxisRect(id);
		m_plot->plotLayout()->addElement(rows++, 0, axisRect);
		UpdateSettingCommonXaXisx();
		ConnectAllAxisx(true);	
	}
	else
	{
		//移除该通道坐标及曲线
		RemoveQCPAxisRect(id);
		UpdateSettingCommonXaXisx();
		max = 50;
		min = 0;
	}
	m_plot->replot();
}

void WdtCustomChart::SlotDataTimeOut()
{
	static double key = 0;; // 开始到现在的时间,单位
	QCPAxis* poAxisY = NULL;
	for (auto& pair : m_channel_plots.toStdMap())
	{
		int value = qrand() % 20;
		auto channel = pair.first;
		auto graph = pair.second;
		graph->addData(key,value);
		if (m_channel_axises.contains(channel))
		{
			auto AxisRec = m_channel_axises.value(channel);
			//QCPAxis* poAxisY = AxisRec->axis(QCPAxis::atLeft);		//y轴
			//int upper = (int)(poAxisY->range().upper);

			//if (upper < value)
			//	poAxisY->setRangeUpper(value + 10);

			if (key > 20)
				AxisRec->axis(QCPAxis::atBottom)->setRange(key, 20, Qt::AlignRight);
		}
	}
	key+=0.5;
	//绘图
	m_plot->replot();
}

static const QColor colors[] =
{
	Qt::red,
	Qt::green,
	Qt::blue,
	Qt::cyan,
	Qt::magenta,
	Qt::gray,
	Qt::darkRed,
	Qt::darkGreen,
	Qt::darkBlue,
	Qt::darkCyan,
};

二、核心代码:

1、动态增加channel所对应的坐标系

QCPAxisRect* WdtCustomChart::CreateQCPAxisRect(QString channel)
{
    //channel代表坐标系id
	QCPAxisRect* axisRect = new QCPAxisRect(m_plot, true);		
	axisRect->setupFullAxesBox(true);
	axisRect->setRangeZoom(Qt::Horizontal | Qt::Vertical); 
	QCPAxis* poAxisX = axisRect->axis(QCPAxis::atBottom);	//x轴
	QCPAxis* poAxisY = axisRect->axis(QCPAxis::atLeft);		//y轴
	poAxisX->setRange(0, 20);
	poAxisY->setRange(0, 50);
	axisRect->axis(QCPAxis::atTop)->setVisible(false);
	axisRect->axis(QCPAxis::atRight)->setVisible(false);
	poAxisX->grid()->setSubGridVisible(true);
	poAxisY->grid()->setSubGridVisible(true);
	axisRect->setAutoMargins(QCP::msRight | QCP::msLeft);
	axisRect->setMargins(QMargins(100, 0, 0, 0));
	poAxisY->setPadding(5); // 这里的5是你想要的额外填充
	poAxisY->setTickLabelPadding(10); // 这里的5是你想要的额外填充
	poAxisY->setLabelPadding(10); // 这里的5是你想要的额外填充
	poAxisX->setVisible(true);
	poAxisX->setLabel(Trans("时间(s)"));
	poAxisY->rescale(true);			//y轴自适应
	//设置x轴y轴自由缩放
	QList<QCPAxis*> aaxisField;
	aaxisField << poAxisX;
	aaxisField << poAxisY;
	axisRect->setRangeZoomAxes(aaxisField);
	
	poAxisY->setVisible(true);
	poAxisY->setLabel(Trans("通道") + channel);
	auto poGraph = m_plot->addGraph(poAxisX, poAxisY);
	QPen pen;
	pen.setColor(colors[qrand() % 10]);
	poGraph->setPen(pen);

	//下面这两行使得Y轴轴线总是对齐
	axisRect->setAutoMargins(QCP::MarginSide::msLeft | QCP::MarginSide::msRight);
	m_channel_axises.insert(channel.toInt(), axisRect);
	m_channel_plots.insert(channel.toInt(), poGraph);
	return axisRect;
}

2、动态删除channel所对应的坐标系

void WdtCustomChart::RemoveQCPAxisRect(QString channel)
{
	if (m_channel_axises.contains(channel.toInt()) && m_channel_plots.contains(channel.toInt()))
	{
		auto axes_rect = m_channel_axises.value(channel.toInt());
		auto graph_plot = m_channel_plots.value(channel.toInt());
		m_plot->plotLayout()->remove(axes_rect);
		m_plot->plotLayout()->simplify();
		m_channel_axises.remove(channel.toInt());
		m_channel_plots.remove(channel.toInt());
	}
}

3、//多段y轴共用同一个x轴

void WdtCustomChart::UpdateSettingCommonXaXisx()
{
	auto elements = m_plot->plotLayout()->elementCount();
	for (int row = 0; row < elements; row++)
	{
		QCPAxisRect* poRecti = (QCPAxisRect*)m_plot->plotLayout()->element(row, 0);
		if (poRecti)
		{
			//布局中最后一个元素进行设置
			if (row == (elements - 1))
			{
				poRecti->setMargins(QMargins(100, 0, 0, 40));
				poRecti->axis(QCPAxis::atBottom)->setVisible(true);
			}
			else
			{
				poRecti->setMargins(QMargins(100, 0, 0, 0));
				poRecti->axis(QCPAxis::atBottom)->setVisible(false);
			}
		}
	}
}

4、//当鼠标放大缩小上下移动时 ,多段y轴x轴同步

void WdtCustomChart::ConnectAllAxisx(bool on)
{
	auto elements = m_plot->plotLayout()->elementCount();
	for (int i = 0; i < elements; i++)
	{
		QCPAxisRect* poRecti = (QCPAxisRect*)m_plot->plotLayout()->element(i, 0);
		for (int j = 0; j < elements; j++)
		{
			QCPAxisRect* poRectj = (QCPAxisRect*)m_plot->plotLayout()->element(j, 0);
			if (poRectj && poRecti && i!=j)
			{
				connect(poRecti->axis(QCPAxis::atBottom), QOverload<const QCPRange&>::of(&QCPAxis::rangeChanged),
					poRectj->axis(QCPAxis::atBottom), QOverload<const QCPRange&>::of(&QCPAxis::setRange));
				connect(poRecti->axis(QCPAxis::atLeft), QOverload<const QCPRange&>::of(&QCPAxis::rangeChanged),
					poRectj->axis(QCPAxis::atLeft), QOverload<const QCPRange&>::of(&QCPAxis::setRange));
			}
		}
	}
}

5、初始化QCustomPlot

void WdtCustomChart::initPlot()
{

	m_plot = new QCustomPlot(this);
	m_plot->plotLayout()->clear();
	//// 允许用户用鼠标拖动轴范围,以鼠标为中心滚轮缩放,点击选择图形:
	m_plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);
}

WdtCustomChart .h

#pragma once

#include <qwidget.h>
#include "qcustomplot.h"
#include <QTimer>

#include "datastruct.h"
#include "WidgetEx.h"
#include "DataDefine.h"
#include "IntToString.h"
#include "TreeDirectoryWidget.h"

class WdtCustomChart : public WidgetEx
{
	Q_OBJECT

public:
	WdtCustomChart(QWidget* parent);
	~WdtCustomChart();

	void init();

private:
	void initTreeData(QMap<int, DevStationData* >& devMap);
	void initPlot();
	void UpdateGraphPlot(QString id,bool check);
	//曲线号
	void UpdateGraphPlotData(QString id, int key, double value);
	void UpdateGraphPlotData(QString id,int key);

	//多段y轴、共用x轴坐标系
	//根据勾选的通道号创建坐标系
	QCPAxisRect* CreateQCPAxisRect(QString channel);
	//删除坐标系
	void RemoveQCPAxisRect(QString channel);
	//x轴y轴同步
	void ConnectAllAxisx(bool on);
	//更新设置共用一个x轴
	void UpdateSettingCommonXaXisx();

public slots:
	void SlotItemChange(QString id,bool check);
	void SlotDataTimeOut();

private:

	TreeDirectoryWidget* m_treeWidget = nullptr;
	QCustomPlot* m_plot;
	QCPRange m_originXRange;
	QMap<int, DevStationData* > m_devMap;
	QMap<QString, bool> m_select_checkbox;
	QMap<QString,int> m_ploy_index_channel;	//通道号 绑定 曲线号

	//动态增加删除通道所对应的曲线图
	QMap<int, QCPAxisRect*> m_channel_axises;	//每一个通道相对应的纵坐标
	QMap<int, QCPGraph*> m_channel_plots;		//每一个通道所对应的曲线
	QTimer* m_data_timer;
	//y轴动态变化值
	int max = 50;	
	int min = 0;
};

WdtCustomChart .cpp
	
	#include "WdtCustomChart.h"
#include "Language.h"

WdtCustomChart::WdtCustomChart(QWidget* parent):WidgetEx(parent)
{

}


WdtCustomChart::~WdtCustomChart()
{
	if (m_data_timer)
	{
		m_data_timer->stop();
		delete m_data_timer;
		m_data_timer = NULL;
	}
}

void WdtCustomChart::init()
{
	if (m_treeWidget == nullptr)
	{
		m_treeWidget = new TreeDirectoryWidget(this);
	}
	initPlot();
	initTreeData(m_devMap);
	QHBoxLayout* layout = new QHBoxLayout;
	layout->addWidget(m_treeWidget,0);
	layout->addWidget(m_plot,1);

	setLayout(layout);
	connect(m_treeWidget, &TreeDirectoryWidget::sig_itemCheckChange, this, &WdtCustomChart::SlotItemChange);
	m_data_timer = new QTimer;
	connect(m_data_timer, SIGNAL(timeout()), this, SLOT(SlotDataTimeOut()));
	m_data_timer->start(1000);

}

static const QColor colors[] =
{
	Qt::red,
	Qt::green,
	Qt::blue,
	Qt::cyan,
	Qt::magenta,
	Qt::gray,
	Qt::darkRed,
	Qt::darkGreen,
	Qt::darkBlue,
	Qt::darkCyan,
};

void WdtCustomChart::initPlot()
{
	m_plot = new QCustomPlot(this);
	m_plot->plotLayout()->clear();
	//// 允许用户用鼠标拖动轴范围,以鼠标为中心滚轮缩放,点击选择图形:
	m_plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);
}

void WdtCustomChart::initTreeData(QMap<int, DevStationData* >& devMap)
{
	for (int i = 1; i <= 100; i++)
	{
		DevStationData* data = new DevStationData();
		if (i <=20)
		{
			data->setType(Trans("通道1-20"));
			data->setDevName(Trans("通道") + QString::number(i));
		}
		else if (20<i && i<=40)
		{
			data->setType(Trans("通道20-40"));
			data->setDevName(Trans("通道") + QString::number(i));
		}
		else if (40 < i && i <= 60)
		{
			data->setType(Trans("通道40-60"));
			data->setDevName(Trans("通道") + QString::number(i));
		}
		else if (60 < i && i <= 80)
		{
			data->setType(Trans("通道60-80"));
			data->setDevName(Trans("通道") + QString::number(i));
		}
		else if (80 < i && i <= 100)
		{
			data->setType(Trans("通道80-100"));
			data->setDevName(Trans("通道") + QString::number(i));
		}
		//data->setStatus(DevStatus(status));
		data->setDevCode(QString::number(i));
		devMap.insert(i, data);
	}
	QMap<QString, QString> treeIdmap;    //type和id

	if (m_treeWidget)
	{
		for (auto& pair : devMap.toStdMap())
		{
			auto channel = pair.first;
			auto data = pair.second;
			QString code = data->getDevCode();
			QString type = data->getType();
			if (treeIdmap.contains(type))
			{
				QString Treeid = treeIdmap.value(type);
				m_treeWidget->CreateChildLeafNode((treeItemInfo*)data, Treeid);
				//_treeWidget->setLeafNodeCheck(code,true);
			}
			else
			{
				QString treeId = m_treeWidget->CreateRootLeafNode(type);
				m_treeWidget->CreateChildLeafNode((treeItemInfo*)data, treeId);
				treeIdmap.insert(type, treeId);
			}
		}
	}
}

void WdtCustomChart::UpdateGraphPlot(QString id, bool check)
{
	static int i = 0;
	QPen pen;

	if (check)
	{
		m_plot->addGraph();
		pen.setColor(colors[qrand() % 10]);
		m_plot->graph(i)->setPen(pen/*QPen(Qt::red)*/);
		//通道号对应的曲线号
		m_ploy_index_channel.insert(id, i);
		i++;
	}
	else
	{
		if (m_ploy_index_channel.contains(id))
		{
			auto index = m_ploy_index_channel.value(id);
			m_plot->removeGraph(index);
			m_ploy_index_channel.remove(id);
		}
		i--;
	}
}

void WdtCustomChart::UpdateGraphPlotData(QString id, int key)
{
	ShowData showData;
	QVector<double> keys(1000), values(1000);
	for (int i = 1; i < 1000; i++)
	{
		keys[i] = key;
		values[i] = qrand() % 20+30;
	}
	
	m_plot->graph(id.toInt())->setData(keys, values);
}

void WdtCustomChart::UpdateGraphPlotData(QString id, int key,double value)
{
	m_plot->graph(id.toInt())->addData(key, value);
}

void WdtCustomChart::SlotDataTimeOut()
{
	QCPAxis* poAxisY = NULL;
	static double key = 0;; // 开始到现在的时间,单位
	for (auto& pair : m_channel_plots.toStdMap())
	{
		int value = (qrand() % 10 )* (qrand() % 10);
		auto channel = pair.first;
		auto graph = pair.second;
		graph->addData(key,value);
		if (m_channel_axises.contains(channel))
		{
			auto AxisRec = m_channel_axises.value(channel);
			QCPAxis* poAxisY = AxisRec->axis(QCPAxis::atLeft);		//y轴
			poAxisY->rescale(true);
		int upper = (int)(poAxisY->range().upper);
			int lower = (int)(poAxisY->range().lower);

			if (value > max)
			{
				max = value+20;
				//poAxisY->setRangeUpper(value + 50);
			}
			else if (value < min)
			{
				min = value-20;
				//poAxisY->setRangeLower(value - 50);
			}

			if (key > 20)
				AxisRec->axis(QCPAxis::atBottom)->setRange(key, 20, Qt::AlignRight);
		}
	}
	if (poAxisY && (max!=0))
	{
		poAxisY->setRangeUpper(max);
		poAxisY->setRangeLower(min);
	}
	key+=0.5;
	//绘图
	m_plot->replot();
}

void WdtCustomChart::SlotItemChange(QString id, bool check)
{
	if (check)
	{
		auto rows = m_plot->plotLayout()->rowCount();
		QCPAxisRect* axisRect = CreateQCPAxisRect(id);
		m_plot->plotLayout()->addElement(rows++, 0, axisRect);
		UpdateSettingCommonXaXisx();
		ConnectAllAxisx(true);	
	}
	else
	{
		//移除该通道坐标及曲线
		RemoveQCPAxisRect(id);
		UpdateSettingCommonXaXisx();
		max = 50;
		min = 0;
	}
	m_plot->replot();
}

QCPAxisRect* WdtCustomChart::CreateQCPAxisRect(QString channel)
{
	QCPAxisRect* axisRect = new QCPAxisRect(m_plot, true);		//坐标轴围成的矩形区域
	axisRect->setupFullAxesBox(true);
	axisRect->setRangeZoom(Qt::Horizontal | Qt::Vertical); //水平方向缩

	QCPAxis* poAxisX = axisRect->axis(QCPAxis::atBottom);	//x轴
	QCPAxis* poAxisY = axisRect->axis(QCPAxis::atLeft);		//y轴
	poAxisX->setRange(0, 20);
	poAxisY->setRange(0, 50);
	axisRect->axis(QCPAxis::atTop)->setVisible(false);
	axisRect->axis(QCPAxis::atRight)->setVisible(false);

	//Field
	poAxisX->grid()->setSubGridVisible(true);
	poAxisY->grid()->setSubGridVisible(true);

	axisRect->setAutoMargins(QCP::msRight | QCP::msLeft);
	axisRect->setMargins(QMargins(100, 0, 0, 0));

	poAxisY->setPadding(5); // 这里的5是你想要的额外填充
	poAxisY->setTickLabelPadding(10); // 这里的5是你想要的额外填充
	poAxisY->setLabelPadding(10); // 这里的5是你想要的额外填充

	poAxisX->setVisible(true);
	poAxisX->setLabel(Trans("时间(s)"));

	//设置x轴y轴自由缩放
	QList<QCPAxis*> aaxisField;
	aaxisField << poAxisX;
	aaxisField << poAxisY;
	axisRect->setRangeZoomAxes(aaxisField);

	poAxisY->setVisible(true);
	poAxisY->setLabel(Trans("通道") + channel);
	poAxisY->rescale(true);			//y轴自适应

	auto poGraph = m_plot->addGraph(poAxisX, poAxisY);
	QPen pen;
	pen.setColor(colors[qrand() % 10]);
	poGraph->setPen(pen);

	//下面这两行使得Y轴轴线总是对齐
	axisRect->setAutoMargins(QCP::MarginSide::msLeft | QCP::MarginSide::msRight);
	m_channel_axises.insert(channel.toInt(), axisRect);
	m_channel_plots.insert(channel.toInt(), poGraph);

	return axisRect;
}

void WdtCustomChart::RemoveQCPAxisRect(QString channel)
{
	if (m_channel_axises.contains(channel.toInt()) && m_channel_plots.contains(channel.toInt()))
	{
		auto axes_rect = m_channel_axises.value(channel.toInt());
		auto graph_plot = m_channel_plots.value(channel.toInt());
		m_plot->plotLayout()->remove(axes_rect);
		m_plot->plotLayout()->simplify();
		m_channel_axises.remove(channel.toInt());
		m_channel_plots.remove(channel.toInt());
	}
}

void WdtCustomChart::ConnectAllAxisx(bool on)
{
	auto elements = m_plot->plotLayout()->elementCount();
	for (int i = 0; i < elements; i++)
	{
		QCPAxisRect* poRecti = (QCPAxisRect*)m_plot->plotLayout()->element(i, 0);
		for (int j = 0; j < elements; j++)
		{
			QCPAxisRect* poRectj = (QCPAxisRect*)m_plot->plotLayout()->element(j, 0);
			if (poRectj && poRecti && i!=j)
			{
				connect(poRecti->axis(QCPAxis::atBottom), QOverload<const QCPRange&>::of(&QCPAxis::rangeChanged),
					poRectj->axis(QCPAxis::atBottom), QOverload<const QCPRange&>::of(&QCPAxis::setRange));
				connect(poRecti->axis(QCPAxis::atLeft), QOverload<const QCPRange&>::of(&QCPAxis::rangeChanged),
					poRectj->axis(QCPAxis::atLeft), QOverload<const QCPRange&>::of(&QCPAxis::setRange));
			}
		}
	}
}

void WdtCustomChart::UpdateSettingCommonXaXisx()
{
	auto elements = m_plot->plotLayout()->elementCount();
	for (int row = 0; row < elements; row++)
	{
		QCPAxisRect* poRecti = (QCPAxisRect*)m_plot->plotLayout()->element(row, 0);
		if (poRecti)
		{
			//布局中最后一个元素进行设置
			if (row == (elements - 1))
			{
				poRecti->setMargins(QMargins(100, 0, 0, 40));
				poRecti->axis(QCPAxis::atBottom)->setVisible(true);
			}
			else
			{
				poRecti->setMargins(QMargins(100, 0, 0, 0));
				poRecti->axis(QCPAxis::atBottom)->setVisible(false);
			}
		}
	}
}
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星空上的鲸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值