目录
需要用到的头文件:
cpp
#include <QtCharts/QChart>
#include <QtCharts/QChartView>
#include <QtCharts/QValueAxis>
#include <QPoint>
#include <QtCharts/QLineSeries>
#include <QString>
#include <QGridLayout>
#include <QLegendMarker>
直白一点说明,QChart用来存放曲线坐标轴和曲线,QChartView用来存放QChart,QChartView是一个Widget,可以独立show或者和其他widget做布局。
QLineSeries是直线,上图可以算是一个折线图,Qt还有散点图、光滑的曲线图、条形图、饼状图等等。
QValueAxis是坐标轴。
头文件内容
cpp
/*!
* \file curve_charts.h
* \date 2023/11/18 23:08
*
* \author
* Contact:
*
* \brief
*
* 基于Qt的曲线图
*
* \note
*/
#pragma once
#include <unordered_map>
#include <QWidget>
#include <QtCharts/QChart>
#include <QtCharts/QChartView>
#include <QtCharts/QValueAxis>
#include <QPoint>
#include <QtCharts/QLineSeries>
#include <QString>
#include <QGridLayout>
#include <QLegendMarker>
#include <QScatterSeries>
#define QString2StdString(x) ((x).toStdString())
#define StdString2QString(x) (QString::fromLocal8Bit(std::string(x).c_str()))
QT_CHARTS_USE_NAMESPACE
class CurveCharts : public QWidget
{
Q_OBJECT
public:
CurveCharts(QWidget *parent = nullptr);
~CurveCharts();
/*!
* @func: void AddSeries(const std::string _series_name, QPen _series_pen);
*
* @date: 2023/11/18
*
* @author:叫牌
*
* @brief: 添加一条曲线,如果已经有这条曲线名称添加失败
*
* @param _series_name: 曲线名称
* @param _series_pen: 曲线颜色
*
* @return: true添加成功,false添加失败
*/
bool AddSeries(const std::string& _series_name, QPen _series_pen);
/*!
* @func: void UpdateSeries(const std::string _series_name, std::vector<QPoint> _charts_data);
*
* @date: 2023/11/18
*
* @author:叫牌
*
* @brief: 通过曲线名称更新曲线
*
* @param _series_name: 曲线名称
* @param _charts_data: 曲线数据
*
* @return: true更新成功,false更新失败
*/
bool UpdateSeries(const std::string& _series_name, std::vector<QPoint> _charts_data);
/*!
* @func: void AppendSeriesData(const std::string& _series_name, std::vector<QPoint> _chart_data);
*
* @date: 2023/11/18
*
* @author:叫牌
*
* @brief: 通过曲线名称追加曲线数据
*
* @param _series_name: 曲线名称
* @param _charts_data: 曲线数据
*
* @return: true更新成功,false更新失败
*/
bool AppendSeriesData(const std::string& _series_name, std::vector<QPoint> _chart_data);
/*!
* @func: void SetTitle(const std::string _title);
*
* @date: 2023/11/18
*
* @author:叫牌
*
* @brief: 设置曲线标题
*
* @param _title: 曲线标题
*
* @return: 无
*/
void SetTitle(const std::string& _title);
/*!
* @func: std::string GetTitle();
*
* @date: 2023/11/18
*
* @author:叫牌
*
* @brief: 获取曲线标题
*
* @return: 曲线标题
*/
std::string GetTitle();
/*!
* @func: void SetXAxisRange(qreal _x_min, qreal _x_max);
*
* @date: 2023/11/18
*
* @author:叫牌
*
* @brief: 设置X轴范围
*
* @return: 无
*/
void SetXAxisRange(qreal _x_min, qreal _x_max);
/*!
* @func: void SetYAxisRange(qreal _y_min, qreal _y_max);
*
* @date: 2023/11/18
*
* @author:叫牌
*
* @brief: 设置Y轴范围
*
* @return: 无
*/
void SetYAxisRange(qreal _y_min, qreal _y_max);
/*!
* @func: void SetTitle(const std::string& _XTitle, const std::string& _YTitle);
*
* @date: 2023/11/18
*
* @author:叫牌
*
* @brief: 设置Y轴范围
*
* @param _XTitle: X坐标轴标题
* @param _YTitle: Y坐标轴标题
*
* @return: 无
*/
void setAxisTitles(const std::string& _XTitle, const std::string& _YTitle);
/*!
* @func: void SetAxisRanges(bool _flag);
*
* @date: 2023/11/18
*
* @author:叫牌
*
* @brief: 设置坐标轴自适应
*
* @param _flag: true表示自适应,false表示不自适应
*
* @return: 无
*/
void SetAxisRanges(const bool& _flag);
/*!
* @func: void SetLegendVisiable(const bool& _flag);
*
* @date: 2023/11/18
*
* @author:叫牌
*
* @brief: 设置图例显隐
*
* @param _flag: true表示显示,false表示隐藏
*
* @return: 无
*/
void SetLegendVisiable(const bool& _flag);
/*!
* @func: void SetRubberBand(const bool& _hor, const bool& _ver);
*
* @date: 2023/11/18
*
* @author:叫牌
*
* @brief: 设置垂直或水平缩放
*
* @param _hor: true表示打开水平缩放,false表示关闭
* @param _ver: true表示打开垂直缩放,false表示关闭
*
* @return: 无
*
* @note: 垂直和水平缩放只能打开一个,都打开默认选择水平缩放
*/
void SetRubberBand(const bool& _hor, const bool& _ver);
private:
QChartView* chart_view_;
QChart* chart_;
// X轴范围
QValueAxis* x_axis_;
// Y轴范围
QValueAxis* y_axis_;
// 存放所有的曲线
// <曲线名称,对应的曲线指针>
std::unordered_map<std::string, QLineSeries*> charts_series_;
// 曲线数据的X和Y最小值
QPoint axis_min_;
// 曲线数据的X和Y轴最大值
QPoint axis_max_;
// 坐标轴是否自适应
bool is_adaption_;
// 图例是否显隐
bool is_legend_;
};
我封装了接口供外部直接调用。
构造函数
cpp
CurveCharts::CurveCharts(QWidget* parent)
: QWidget(parent)
, chart_view_(new QChartView)
, chart_(new QChart)
, x_axis_(new QValueAxis)
, y_axis_(new QValueAxis)
, is_adaption_(false)
, is_legend_(false)
{
// 布局,栅格布局,把QChartView对象之间放进去,在main函数实例化对象调用show方法。
QGridLayout* layout = new QGridLayout;
this->setLayout(layout);
layout->addWidget(chart_view_);
// 窗口标题
setWindowTitle(StdString2QString("曲线视图"));
// 设置QChart
chart_view_->setChart(chart_);
// 设置X和Y坐标轴标题的显示位置,X轴标题在最小面,Y轴标题在左侧
chart_->addAxis(x_axis_, Qt::AlignBottom);
chart_->addAxis(y_axis_, Qt::AlignLeft);
// 用于平滑图形的边缘和曲线,减少锯齿状的边缘
chart_view_->setRenderHint(QPainter::Antialiasing);
// 初始化最小最大值,方便记录X轴和Y轴所有曲线数据的最大最小值
axis_min_.setX(INT_MAX);
axis_min_.setY(INT_MAX);
axis_max_.setX(INT_MIN);
axis_max_.setY(INT_MIN);
}
构造函数里对各种成员变量初始化,进行布局,将QChartView对象通过栅格布局,设置X和Y轴的标题。
AddSeries方法
cpp
bool CurveCharts::AddSeries(const std::string& _series_name, QPen _series_pen)
{
// 如果找到了就说明这个曲线的名称已经添加过了,不能重复添加或者覆盖
if (charts_series_.find(_series_name) != charts_series_.end())
return false;
// 定义一个折线对象
QLineSeries* line_series = new QLineSeries;
charts_series_[_series_name] = line_series;
// 添加到QChart
chart_->addSeries(line_series);
// 给这条折线添加颜色
line_series->setPen(_series_pen);
// 设置X轴
line_series->attachAxis(x_axis_);
// 设置Y轴
line_series->attachAxis(y_axis_);
// 设置这条折线的名字
line_series->setName(StdString2QString(_series_name));
// 为true表示显示图例
if(is_legend_)
chart_->legend()->markers(line_series)[0]->setLabel(StdString2QString(_series_name));
return true;
}
这是一个添加曲线的函数,我们通过实例化该类的对象调用,参数就是你要添加曲线的名字,还有你想要它是什么颜色。
图例:
UpdateSeries方法
cpp
bool CurveCharts::UpdateSeries(const std::string& _series_name, std::vector<QPoint> _charts_data)
{
// 找不到说明没有这条曲线
if (charts_series_.find(_series_name) == charts_series_.end())
return false;
// 这是更新,所以得把之前这条曲线的数据清空
charts_series_[_series_name]->clear();
// 添加曲线数据,同时将最大最小值替换
for (const auto& point : _charts_data)
{
qreal y_min = point.y() < axis_min_.y() ? point.y() : axis_min_.y();
qreal y_max = point.y() > axis_max_.y() ? point.y() : axis_max_.y();
qreal x_min = point.x() < axis_min_.x() ? point.x() : axis_min_.x();
qreal x_max = point.x() > axis_max_.x() ? point.x() : axis_max_.x();
axis_min_.setX(x_min);
axis_min_.setY(y_min);
axis_max_.setX(x_max);
axis_max_.setY(y_max);
charts_series_[_series_name]->append(point.x(), point.y());
}
// 如果是坐标轴自适应数据,那就通过设置坐标轴范围进行设置
if (is_adaption_)
{
SetXAxisRange(axis_min_.x(), axis_max_.x());
SetYAxisRange(axis_min_.y(), axis_max_.y());
}
return true;
}
这是更新曲线数据,所谓更新就是把之前的清空,是新一份数据,参数是要更新的曲线名称和数据。
AppendSeriesData方法
cpp
bool CurveCharts::AppendSeriesData(const std::string& _series_name, std::vector<QPoint> _chart_data)
{
if (charts_series_.find(_series_name) == charts_series_.end())
return false;
for (const auto& point : _chart_data)
{
qreal y_min = point.y() < axis_min_.y() ? point.y() : axis_min_.y();
qreal y_max = point.y() > axis_max_.y() ? point.y() : axis_max_.y();
qreal x_min = point.x() < axis_min_.x() ? point.x() : axis_min_.x();
qreal x_max = point.x() > axis_max_.x() ? point.x() : axis_max_.x();
axis_min_.setX(x_min);
axis_min_.setY(y_min);
axis_max_.setX(x_max);
axis_max_.setY(y_max);
charts_series_[_series_name]->append(point.x(), point.y());
}
if (is_adaption_)
{
SetXAxisRange(axis_min_.x(), axis_max_.x());
SetYAxisRange(axis_min_.y(), axis_max_.y());
}
return true;
}
追加曲线数据,我们需要在外部给某条曲线数据追加时,和更新的区别在于更新需要清空,而追加不需要,沿用之前的数据。
SetLegendVisiable
cpp
void CurveCharts::SetLegendVisiable(const bool& _flag)
{
is_legend_ = _flag;
if (is_legend_)
{
for (auto it = charts_series_.begin(); it != charts_series_.end(); it++)
chart_->legend()->markers(it->second)[0]->setLabel(StdString2QString(it->first));
}
}
设置是否显示图例,true表示显示,false表示不显示
SetRubberBand
cpp
void CurveCharts::SetRubberBand(const bool& _hor, const bool& _ver)
{
if (_hor)
chart_view_->setRubberBand(QChartView::HorizontalRubberBand);
else if (_ver)
chart_view_->setRubberBand(QChartView::VerticalRubberBand);
}
设置垂直缩放和水平缩放,但是这两个只能生效一个,不能同时生效。
剩下的方法就很简单,不做说明,直接贴源码。
CPP内容
cpp
#include "curve_charts.h"
CurveCharts::CurveCharts(QWidget* parent)
: QWidget(parent)
, chart_view_(new QChartView)
, chart_(new QChart)
, x_axis_(new QValueAxis)
, y_axis_(new QValueAxis)
, is_adaption_(false)
, is_legend_(false)
{
// 布局,栅格布局,把QChartView对象之间放进去,在main函数实例化对象调用show方法。
QGridLayout* layout = new QGridLayout;
this->setLayout(layout);
layout->addWidget(chart_view_);
// 窗口标题
setWindowTitle(StdString2QString("曲线视图"));
// 设置QChart
chart_view_->setChart(chart_);
// 设置X和Y坐标轴标题的显示位置,X轴标题在最小面,Y轴标题在左侧
chart_->addAxis(x_axis_, Qt::AlignBottom);
chart_->addAxis(y_axis_, Qt::AlignLeft);
chart_view_->setRenderHint(QPainter::Antialiasing);
// 初始化最小最大值,方便记录X轴和Y轴所有曲线数据的最大最小值
axis_min_.setX(INT_MAX);
axis_min_.setY(INT_MAX);
axis_max_.setX(INT_MIN);
axis_max_.setY(INT_MIN);
}
CurveCharts::~CurveCharts()
{}
bool CurveCharts::AddSeries(const std::string& _series_name, QPen _series_pen)
{
// 如果找到了就说明这个曲线的名称已经添加过了,不能重复添加或者覆盖
if (charts_series_.find(_series_name) != charts_series_.end())
return false;
// 定义一个折线对象
QLineSeries* line_series = new QLineSeries;
charts_series_[_series_name] = line_series;
// 添加到QChart
chart_->addSeries(line_series);
// 给这条折线添加颜色
line_series->setPen(_series_pen);
// 设置X轴
line_series->attachAxis(x_axis_);
// 设置Y轴
line_series->attachAxis(y_axis_);
// 设置这条折线的名字
line_series->setName(StdString2QString(_series_name));
// 为true表示显示图例
if(is_legend_)
chart_->legend()->markers(line_series)[0]->setLabel(StdString2QString(_series_name));
return true;
}
bool CurveCharts::UpdateSeries(const std::string& _series_name, std::vector<QPoint> _charts_data)
{
// 找不到说明没有这条曲线
if (charts_series_.find(_series_name) == charts_series_.end())
return false;
// 这是更新,所以得把之前这条曲线的数据清空
charts_series_[_series_name]->clear();
// 添加曲线数据,同时将最大最小值替换
for (const auto& point : _charts_data)
{
qreal y_min = point.y() < axis_min_.y() ? point.y() : axis_min_.y();
qreal y_max = point.y() > axis_max_.y() ? point.y() : axis_max_.y();
qreal x_min = point.x() < axis_min_.x() ? point.x() : axis_min_.x();
qreal x_max = point.x() > axis_max_.x() ? point.x() : axis_max_.x();
axis_min_.setX(x_min);
axis_min_.setY(y_min);
axis_max_.setX(x_max);
axis_max_.setY(y_max);
charts_series_[_series_name]->append(point.x(), point.y());
}
// 如果是坐标轴自适应数据,那就通过设置坐标轴范围进行设置
if (is_adaption_)
{
SetXAxisRange(axis_min_.x(), axis_max_.x());
SetYAxisRange(axis_min_.y(), axis_max_.y());
}
return true;
}
bool CurveCharts::AppendSeriesData(const std::string& _series_name, std::vector<QPoint> _chart_data)
{
if (charts_series_.find(_series_name) == charts_series_.end())
return false;
for (const auto& point : _chart_data)
{
qreal y_min = point.y() < axis_min_.y() ? point.y() : axis_min_.y();
qreal y_max = point.y() > axis_max_.y() ? point.y() : axis_max_.y();
qreal x_min = point.x() < axis_min_.x() ? point.x() : axis_min_.x();
qreal x_max = point.x() > axis_max_.x() ? point.x() : axis_max_.x();
axis_min_.setX(x_min);
axis_min_.setY(y_min);
axis_max_.setX(x_max);
axis_max_.setY(y_max);
charts_series_[_series_name]->append(point.x(), point.y());
}
if (is_adaption_)
{
SetXAxisRange(axis_min_.x(), axis_max_.x());
SetYAxisRange(axis_min_.y(), axis_max_.y());
}
return true;
}
void CurveCharts::SetTitle(const std::string& _title)
{
chart_->setTitle(StdString2QString(_title));
}
std::string CurveCharts::GetTitle()
{
return QString2StdString(chart_->title());
}
void CurveCharts::SetXAxisRange(qreal _x_min, qreal _x_max)
{
x_axis_->setRange(_x_min, _x_max);
}
void CurveCharts::SetYAxisRange(qreal _y_min, qreal _y_max)
{
y_axis_->setRange(_y_min, _y_max);
}
void CurveCharts::setAxisTitles(const std::string& _XTitle, const std::string& _YTitle)
{
x_axis_->setTitleText(StdString2QString(_XTitle));
y_axis_->setTitleText(StdString2QString(_YTitle));
}
void CurveCharts::SetRubberBand(const bool& _hor, const bool& _ver)
{
if (_hor)
chart_view_->setRubberBand(QChartView::HorizontalRubberBand);
else if (_ver)
chart_view_->setRubberBand(QChartView::VerticalRubberBand);
}
void CurveCharts::SetAxisRanges(const bool& _flag)
{
is_adaption_ = _flag;
}
void CurveCharts::SetLegendVisiable(const bool& _flag)
{
is_legend_ = _flag;
if (is_legend_)
{
for (auto it = charts_series_.begin(); it != charts_series_.end(); it++)
chart_->legend()->markers(it->second)[0]->setLabel(StdString2QString(it->first));
}
}
测试函数
cpp
#include <QtWidgets/QApplication>
#include <QTimer>
#include <random>
#include "curve_charts.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
CurveCharts* cure_charts = new CurveCharts;
cure_charts->SetTitle("信号曲线");
cure_charts->AddSeries("时域", QPen(QColor(Qt::green)));
cure_charts->AddSeries("频域", QPen(QColor(Qt::red)));
cure_charts->SetAxisRanges(true);
cure_charts->SetLegendVisiable(true);
cure_charts->SetRubberBand(true, false);
cure_charts->setAxisTitles("时间", "幅度");
int x = 0;
QTimer timer;
timer.setInterval(1000); // 设置定时间隔
QObject::connect(&timer, &QTimer::timeout, [&]() {
std::random_device rd; // 随机设备,用于产生随机数种子
std::mt19937 gen(rd()); // 使用随机设备的随机数引擎
std::uniform_int_distribution<int> dist(0, 100); // 定义随机数分布范围为 [0, 100]
std::vector<QPoint> temp1;
std::vector<QPoint> temp2;
int randomNumber1 = dist(gen);
int randomNumber2 = dist(gen);
QPoint point;
point.setX(x);
point.setY(randomNumber1);
temp1.emplace_back(point);
point.setY(randomNumber2);
temp2.emplace_back(point);
cure_charts->AppendSeriesData("时域", temp1);
cure_charts->AppendSeriesData("频域", temp2);
x++;
});
timer.start();
cure_charts->show();
return a.exec();
}
在测试代码中,我们添加了两条曲线,定义了一个定时器,间隔为1秒,每次产生两个0~100的随机值代表两条曲线的Y值,X值是时间。
另外,QChart支持设置背景色、十字线之类的效果,可以自己div。