文章目录
前言
这是一段做作的前言,摘自网络。
在Qt5.7之前,Qt Charts 一直是商业版才有,所以在此之前的Qt开发人员若想绘制图表需采用第三方库,常见的是Qt Charts是Qt提供的图表模块,在Qt5.7之前只有商业版才有,但是从Qt5.7之后,社区版本也包含了Qt Charts。Qt Charts可以很方便的绘制常见的折线图、柱状图、饼图等图表。它基于Qt的Graphics View架构,核心组件是QChartView和QChart。其中QChartView正是继承于QGraphicsView类,因此它也可以作为Graphics View中的视图组件。另一个QChart则由QGraphicsWidget继承而来,继续向上追溯,发现他们都继承于QGraphicsItem,所以QChart是图形项。
准备工作
安装QChart组件
Qt
安装时,默认情况下不安装 QCharts组件
,所以如果需要使用 QCharts组件
,我们需要先更新一下 Qt组件
。
首先在我们的 Qt安装路径
下找到 MaintenanceTool
。
若在下一步出现 “要继续此操作,至少需要一个有效且已启用的储存库。”;则需要在设置中设置存储库。
Qt 存储库(戳我)
ps:我采用的是清华镜像,只需点击HTTP 进入镜像地址并移动至图示目录下,复制如上图所示的 HTTP地址(点击此处偷懒) 作为存储库,随后添加:
项目配置
.pro文件中添加
QT += charts
.h文件中添加
QT_CHARTS_USE_NAMESPACE
QCharts必须的头文件
#include <QChartView>
#include <QChart>
主要组成部分
Qt Charts
模块是一组易于使用的图标组件,基于 Qt 的 GrapHics View
架构,核心是 QChartView
和 QChart
。
QChartView
QChartView
是 QChart
的视图组件,用于显示。在 QtCreator
中使用 QChartView
可以放置一个 QWidget
,然后升级为 QChartView
。
QChartView
的内容很少,建议直接过一遍文档 QChartView
QChartView继承关系如下:
QChart
QChart
管理不同类型的序列和其他与图表相关的对象,例如坐标轴及图例。
QChart
继承关系如下:
序列
常见的序列如下:
-
QBarSeries
(柱状图) -
QHorizontalBarSeries
(水平柱状图) -
QHorizontalPercentBarSeries
(水平百分比柱状图) -
QHorizontalStackedBarSeries
(水平层叠图) -
QPercentBarSeries
百分比柱状图)QStackedBarSeries
(层叠图/堆叠的条形图) -
QAreaSeries
(面积图) -
QBoxPlotSeries
(形图/盒须图) -
QPieSeries
(饼图) -
QXYSeries
(线性图、曲线图、散点图的基类) -
QLineSeries
(折线图) -
QSplineSeries
(曲线图) -
QScatterSeries
(散点图)
序列继承关系如下:
本文仅介绍最常用的 QLineSeries
。
坐标轴
坐标轴封装了刻度,标签,网格线,标题等属性。
坐标轴有以下几种:
QBarCategoryAxis
类别坐标轴,用字符串作为坐标轴的可读,用于图表的非数值坐标轴QDateTimeAxis
时间坐标轴,用作时间数据的坐标轴QLogValueAxis
对数数值坐标轴,作为数值类数据的对数坐标轴QValueAxis
数值坐标轴,用作数值型数据的坐标轴QCategoryAxis
分组数值坐标轴,可以为数值范围设置标签
坐标轴继承关系如下:
图例
图例是对图表上序列的补充说明。我们可以设置序列颜色及其文字说明、并控制序列显示的位置。
此外,图例中还有一个 QLegendMarker
类,可为每个序列的图例生成一个类似QChrckedBox
的组件;单击序列的标记,可以控制序列是否显示。
下面是官方例程 chartthemes
的运行效果。
上述内容,建议通过官方例程配合类文档学习。
静态图表
源代码
chart = new QChart();
mAxY = new QValueAxis();
mAxX = new QValueAxis();
mLineSeries = new QLineSeries();
//y轴范围
mAxY->setRange(0, 10);
// Y轴分等份
mAxY->setTickCount(11);
mAxX->setRange(0,10);
mAxX->setTickCount(11);
// 将系列添加到图表
chart->addSeries(mLineSeries);
chart->setTheme(QtCharts::QChart::ChartThemeBrownSand);
mAxX->setTitleText(QString(tr("ImageNumber")));
mAxY->setTitleText(QString(tr("ReadRate(%)")));
chart->addAxis(mAxY, Qt::AlignLeft);
chart->addAxis(mAxX, Qt::AlignBottom);
mLineSeries->attachAxis(mAxY);
mLineSeries->attachAxis(mAxX);
//隐藏背景
chart->setBackgroundVisible(false);
//设置外边界全部为0
chart->setContentsMargins(0, 0, 0, 0);
//设置内边界全部为0
chart->setMargins(QMargins(0, 0, 0, 0));
//设置背景区域无圆角
chart->setBackgroundRoundness(0);
//突出曲线上的点
mLineSeries->setPointsVisible(true);
//图例
QLegend *mlegend = chart->legend();
mLineSeries->setName("testname");
mLineSeries->setColor(QColor(255,0,0));
//在底部显示
mlegend->setAlignment(Qt::AlignBottom);
mlegend->show();
// 将图表绑定到视图 wiget 为 QChartView
ui->widget->setChart(chart);
for(int i = 0 ;i < 10;i++){
mLineSeries->append(i+1, i);
}
动态图表
场景一
显示最近的 n
个数据。
思路
对于数值坐标轴,我们仅需要在加入数据时判断数据点是否超过 x轴
最大值。
若超过则取得旧的pointslist
,并将其所有点的 x轴
数值 -1
,再加入新点。
效果
核心实现
QList<QPointF> oldPoints = mLineSeries->points();
int pointCount =oldPoints.size();
if(pointCount > mAxX->max()){
QList<QPointF> newPoints;
for(int i = 0; i < pointCount; i++){
QPointF point;
point.setX(oldPoints.at(i).x()-1);
point.setY(oldPoints.at(i).y());
newPoints.append(point);
}
qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
QPointF point;
point.setX(oldPoints.at(pointCount -1).x());
point.setY(qrand()%10);
newPoints.append(point);
mLineSeries->clear();
mLineSeries->append(newPoints);
mLineSeries->setPointsVisible(true);
}
else{
qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
QPointF point;
point.setX(oldPoints.at(pointCount -1).x() +1);
point.setY(qrand()%10);
mLineSeries->append(point);
}
场景二
显示最近一段时间的数据。
思路
这里需要增加两个概念
- 定时间隔
TimerBreaks
- 最大采样数
MaxSize
首次添加时设置时间轴,每次添加数据点都先判断当前数据点是否超过最大采样数,若超过则删除线中第一个点,再添加数据。
效果
核心实现
// 最大采样数
const int MaxSize = 100;
// 定时时间间隔-ms
const int TimerBreaks = 10;
void MainWindow::slot_timeout()
{
static int cnt = 0;
static QDateTime BeginTime, EndTime;
if (cnt == 0)
{
BeginTime = QDateTime::currentDateTime();
EndTime = BeginTime.addMSecs(MaxSize*(TimerBreaks + 1));
mAxDateX->setMin(BeginTime);
mAxDateX->setMax(EndTime);
}
qint64 x1 = QDateTime::currentMSecsSinceEpoch();
qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
qreal y =qrand()%10;
cnt = cnt > (MaxSize - 1) ? 1 : (++cnt);
if (mLineSeries->count() > (MaxSize-1))
{
//删除第一个元素
mLineSeries->removePoints(0, 1);
EndTime = QDateTime::currentDateTime();
BeginTime = EndTime.addMSecs((qint64)MaxSize*(-1)*TimerBreaks);
mAxDateX->setMin(BeginTime);
mAxDateX->setMax(EndTime);
}
mLineSeries->append(x1, y);
}
交互
获取曲线在鼠标位置坐标
在 QXYSeries
中可以发现该信号:
-
void clicked(const QPointF &point)
鼠标点击序列时,触发该信号 -
void hovered(const QPointF &point, bool state)
鼠标在序列上时触发该信号
有了这两个信号,就可以很容易的实现以下功能:
以上效果来自 Qt
官方例程 - Callout
,核心是通过这两个信号实现控件的动态创建、隐藏和显示。
图例控制序列隐藏/显示
可参考官方例程 legendmarkers
。
以下为摘录核心:
const auto markers = m_chart->legend()->markers();
for (QLegendMarker *marker : markers) {
// Disconnect possible existing connection to avoid multiple connections
QObject::disconnect(marker, &QLegendMarker::clicked, this ,&MainWidget::handleMarkerClicked);
QObject::connect(marker, &QLegendMarker::clicked, this, &MainWidget::handleMarkerClicked);
//点击事件
void MainWidget::handleMarkerClicked()
{
QLegendMarker* marker = qobject_cast<QLegendMarker*> (sender());
//断言
Q_ASSERT(marker);
switch (marker->type())
{
case QLegendMarker::LegendMarkerTypeXY:
{
//控序列隐藏/显示
// Toggle visibility of series
marker->series()->setVisible(!marker->series()->isVisible());
// Turn legend marker back to visible, since hiding series also hides the marker
// and we don't want it to happen now.
marker->setVisible(true);
//修改图例
// Dim the marker, if series is not visible
qreal alpha = 1.0;
if (!marker->series()->isVisible())
alpha = 0.5;
QColor color;
QBrush brush = marker->labelBrush();
color = brush.color();
color.setAlphaF(alpha);
brush.setColor(color);
marker->setLabelBrush(brush);
brush = marker->brush();
color = brush.color();
color.setAlphaF(alpha);
brush.setColor(color);
marker->setBrush(brush);
QPen pen = marker->pen();
color = pen.color();
color.setAlphaF(alpha);
pen.setColor(color);
marker->setPen(pen);
break;
}
default:
{
qDebug() << "Unknown marker type";
break;
}
}
}
卡顿问题
在使用图表时,经常会遇到卡顿的问题。
目前总结的解决卡顿的方法主要有:
- 去掉动画、样式、标签形状等
m_chart->setAnimationOptions(QChart::NoAnimation);
- 开启
OPenGL
加速
line->setUseOpenGL();
值得注意的是:
QAreaSeries
面积和散点图无法使用。- 加速系列不支持系列动画。
- 加速序列不支持点标签。
- 对于加速系列,笔样式和标记形状被忽略。仅支持实线和普通散点。散点可以是圆形或矩形,这取决于底层图形硬件和驱动程序。
- 极坐标图不支持加速序列。
- 避免短时间内逐点添加,大数据刷新采用
append(const QList<QPointF> &points)
line->append(newPoints);