备注:
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);
}
}
}
}