=一、 前言=
由于Qt本身的model、delegate结构,我们在遇到图表等内容时也通常使用该方式直接绘制,优点是可以随心所欲的绘制任何图形,但是缺点是代码量较大,影响开发效率。
=二、 简介=
其中Qt本身也为我们提供了绘制图表的QChart模块,且该模块功能强大,能够绘制包含折线、曲线、饼图、棒图、散点图、雷达图等各种常用的图表,在QT5.7版本以前该模块一直属于收费项目,只有商业版本才能使用,但是5.7版本后开放了Qt Charts(二维图表)Qt Data Visualization(三维图表)的权限。但是QChart一般只能处理2000个点以内的数据,如果数据过多就会存在刷新卡顿的问题。
而QT中常用的第三方工具为QCustomPlot和Qwt,其中Qwt功能比较全面但配置比较麻烦,而qcustomplot小巧玲珑,简单易用,上手快,只有两个源文件可以直接添加进工程,修改源代码也比较方便。本文仅介绍QCustomPlot工具,QCustomPlot专注于制作美观、高品质的2D图表,且可以导出各种格式的图片,如矢量化的PDF文件和PNG/JPG/BMP等文件。
=三、 安装与使用=
<1> QChart为Qt本身自带的模块,因此在创建项目时勾选Charts模块,在需要使用该模块的类UI文件中添加Widget控件,将Widget提升为QChartView类。
{F2291160}
但是UI文件中需要增加命名空间,不然会报编译错误,而由于在UI文件中添加内容每次构建都需要修改,因此在头文件中增加,在头文件的#include “ui_xxxx.h”前增加
命名空间:
#include <QtCharts>
QT_CHARTS_USE_NAMESPACE /* 增加QCharts命名空间 */
#include “ui_xxxx.h”
<2> QCustomPlot虽然为开源第三方库,但是由于其专注与2D的图表绘制,因此其内容小巧(头文件300k,源文件1300k),仅需下载qcustomplot的头文件与源文件,加载到项目中。(Ps:因为qcustomplot支持文件输出与打印,因此如果要使用该功能则需要在项目中勾选 printf support模块,如果不需要或不想引入printf support模块则可以在源码中删除相关内容)。在所需要使用qcustomplot中的类中包含qcustomplot头文件即可.
下载地址:https://www.qcustomplot.com/index.php/introduction
=四、 绘图示例=
以项目中设备设置中的的太阳能设备数据绘制为例,展示QCharts和QCustomPlot的使用方法,绘制一个月的电池电量变化数据。
<1>使用QCharts绘制
```
lang=C,
#include "QtChartApplicatin.h"
#include <QChartView>
#include <QLineSeries>
#include <QtCharts>
using namespace QtCharts;
QtChartApplicatin::QtChartApplicatin(QWidget *parent) : QMainWindow(parent) {
ui.setupUi(this);
/* 绘制前时间 */
QTime startTime = QTime::currentTime();
/* 绘制图表 */
QChart *chart = new QChart();
/* 设置自定义坐标轴 */
QValueAxis *axisX = new QValueAxis(); /* X轴 */
QValueAxis *axisY = new QValueAxis(); /* Y轴 */
/* 设置坐标轴范围 */
axisX->setRange(0, 24);
axisY->setRange(0, 2);
/* 设置坐标轴刻度线(可以设置主要和次要刻度线) 可以按固定值(配合间隔)、份设置刻度线 */
axisX->setTickType(QValueAxis::TickType::TicksFixed);
axisX->setTickCount(7);
axisY->setTickType(QValueAxis::TickType::TicksDynamic);
axisY->setTickInterval(0.4);
/* 设置刻度线颜色 */
axisY->setGridLineColor(QColor("#D9D9D9"));
axisX->setLinePenColor(QColor("#666666"));
/* 隐藏Y轴和X轴刻度线 */
axisX->setGridLineVisible(false);
axisY->setLineVisible(false);
/* QSplineSeries 平滑曲线 QLineSeries折线 */
QLineSeries *LineSeries1 = new QLineSeries();
QLineSeries *LineSeries2 = new QLineSeries();
/* 添加数据 */
for (double x = 0; x < 24; x += 1) {
LineSeries1->append(x, sin(x));
LineSeries2->append(x, cos(x));
}
/* 绘制数据点与线 */
LineSeries1->setPointsVisible(true);
LineSeries2->setPointsVisible(true);
LineSeries1->setPen(QPen(QColor("#24B354"), 2, Qt::SolidLine));
LineSeries2->setPen(QPen(QColor("#1786E6"), 2, Qt::SolidLine));
/* 图例 */
LineSeries1->setName("consumption");
LineSeries2->setName("generation");
chart->legend()->hide();
/* 将坐标轴绑定图表 */
chart->setAxisX(axisX, LineSeries1);
chart->setAxisY(axisY, LineSeries1);
/* 将系列绑定图表 */
chart->addSeries(LineSeries1);
chart->addSeries(LineSeries2);
/* 去锯齿 试线条显示更简洁美观 */
ui.widget->setRenderHint(QPainter::RenderHint::Antialiasing);
/* 将图表设置到控件(chartview)内 */
ui.widget->setChart(chart);
QTime endTime = QTime::currentTime();
auto totalTime = startTime.msecsTo(endTime);
}
```
{F2291162}
<2>使用qcustomplot绘制
Qcustomplot类管理着所有的图层,默认自带了六个图层,分别是:
背景层background
网格层grid
绘图层main
坐标轴层axes
图例层legend
overlay层overlay(交互层)
各层按顺序绘制,其中前五个图层共享一个绘图缓存,overlay为单独绘制机制,可以对overlay图层进行单独的更新,减少刷新时间。
```
lang=C,
#include "QtCustomPlotApplication1.h"
#include "qcustomplot.h"
QtCustomPlotApplication1::QtCustomPlotApplication1(QWidget *parent)
: QMainWindow(parent) {
ui.setupUi(this);
/* 连接鼠标事件 */
connect(ui.customPlot, SIGNAL(mouseMove(QMouseEvent *)), this,
SLOT(mouseMove(QMouseEvent *)));
/* 生成数据,画出的是抛物线 */
QVector<double> x, y0, y1;
for (double i = 0; i < 24; i += 0.1) {
x.append(i);
y0.append(1 + sin(i));
y1.append(1 + cos(i));
}
/* 添加数据曲线(一个图像可以有多个数据曲线) */
ui.customPlot->addGraph();
/* 一个曲线对应一个graph, 每添加一条曲线可以通过addgraph添加
* 可以将每一条曲线理解为一个图层
* qcustomplot一共有四个坐标轴,xAxis/xAxis2/yAxis/yAxis2
*/
ui.customPlot->addGraph();
/* 边框右侧和上侧均显示刻度线,但不显示刻度值 */
ui.customPlot->xAxis2->setVisible(false);
ui.customPlot->xAxis2->setTickLabels(false);
ui.customPlot->yAxis2->setVisible(false);
ui.customPlot->yAxis2->setTickLabels(false);
/* graph(0);可以获取某个数据曲线(按添加先后排序) */
/* setData();为数据曲线关联数据 */
ui.customPlot->graph(0)->setData(x, y0);
ui.customPlot->graph(0)->setName("consumption");
ui.customPlot->graph(1)->setData(x, y1);
ui.customPlot->graph(1)->setName("generation");
/* 设置曲线颜色 */
ui.customPlot->graph(0)->setPen(QPen(QBrush(QColor("#24B354")), 2));
ui.customPlot->graph(1)->setPen(QPen(QBrush(QColor("#1786E6")), 2));
ui.customPlot->graph(0)->setScatterStyle(
QCPScatterStyle(QCPScatterStyle::ssDisc, 5)); //设置散点形状
ui.customPlot->graph(1)->setScatterStyle(
QCPScatterStyle(QCPScatterStyle::ssDisc, 5)); //设置散点形状
/* 线段风格 */
// ui.customPlot->graph(0)->setLineStyle(QCPGraph::LineStyle::lsLine);
/* 为坐标轴添加标签 */
//ui.customPlot->xAxis->setLabel("x");
//ui.customPlot->yAxis->setLabel("y");
/* 设置X轴不显示刻度以及网格 */
ui.customPlot->xAxis->setTicker(false);
ui.customPlot->xAxis->setSubTicks(false);
ui.customPlot->xAxis->grid()->setVisible(false);
ui.customPlot->xAxis->setBasePen(QPen(QColor("#666666"), 1));//设置下轴颜色
/* 设置Y轴仅显示网格线 */
ui.customPlot->yAxis->setBasePen(QPen(QColor(0,0,0,0), 1));//隐藏左轴
ui.customPlot->yAxis->setTickLength(0);
ui.customPlot->yAxis->setSubTicks(false);
ui.customPlot->yAxis->grid()->setPen(QPen(QColor("#D9D9D9"), 1));
/*设置坐标轴的范围,以看到所有数据 */
ui.customPlot->xAxis->setRange(0, 25);
ui.customPlot->xAxis->ticker()->setTickStepStrategy(QCPAxisTicker::tssMeetTickCount);
ui.customPlot->xAxis->ticker()->setTickCount(6);
ui.customPlot->yAxis->setRange(0, 2);
ui.customPlot->legend->setVisible(false);
/* 支持鼠标拖拽轴的范围、滚动缩放轴的范围,左键点选图层(每条曲线独占一个图层)
*/
ui.customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom |
QCP::iSelectPlottables);
/* 生成游标 */
tracer_ = new QCPItemTracer(ui.customPlot);
tracer_->setPen(QPen(Qt::red)); //圆圈轮廓颜色
tracer_->setBrush(QBrush(Qt::red)); //圆圈圈内颜色
tracer_->setStyle(QCPItemTracer::tsCircle); //圆圈
tracer_->setSize(5); //设置大小
/* 生成游标说明 */
tracerLabel_ = new QCPItemText(ui.customPlot);
tracerLabel_->setLayer("overlay"); //设置图层为overlay,因为需要频繁刷新
tracerLabel_->setPen(QPen(Qt::black)); //设置游标说明颜色
tracerLabel_->setPositionAlignment(Qt::AlignLeft | Qt::AlignTop); //设置位置
tracerLabel_->position->setParentAnchor(
tracer_->position); //将游标说明锚固在tracer位置处,实现自动跟随
tracerLabel_->setVisible(false);
/* 重画图像 */
ui.customPlot->replot();
}
void QtCustomPlotApplication1::mouseMove(QMouseEvent *e) {
QCPGraph *mGraph = ui.customPlot->graph(0);
//将像素点转换成qcustomplot中的坐标值,并通过setGraphKey将锚点值设为真实数据值。tracer->setGraphKey(xAxis->pixelToCoord(event->pos().x()));
int graphCount = ui.customPlot->graphCount();
/* 获得鼠标位置处对应的横坐标数据x */
double x = ui.customPlot->xAxis->pixelToCoord(e->pos().x());
/* 遍历曲线 */
for (int i = 0; i < graphCount; ++i) {
/* 判断哪一条曲线被选中 */
if (ui.customPlot->graph(i)->selected()) {
/* 显示锚点 */
tracer_->setVisible(true);
mGraph = ui.customPlot->graph(i);
tracer_->setGraph(mGraph); //将锚点设置到被选中的曲线上
tracer_->setGraphKey(x); //将游标横坐标设置成刚获得的横坐标数据x
tracer_->setInterpolating(
true); //游标的纵坐标可以通过曲线数据线性插值自动获得
tracer_->updatePosition(); //使得刚设置游标的横纵坐标位置生效
double xValue = tracer_->position->key();
double yValue = tracer_->position->value();
/* 显示游标说明框 */
tracerLabel_->setText(QString("%1: x = %2, y = %3")
.arg(mGraph->name())
.arg(QString::number(xValue))
.arg(QString::number(yValue)));
break;
} else {
/* 没有曲线被选中,不显示锚点 */
tracer_->setVisible(false);
}
}
/* 重绘 */
ui.customPlot->layout(5)->replot();
}
```
{F2291163}
其中QCharts(QDateTimeAxis)和Qcustomplot(QCPAxisTickerDateTime)都支持日期格式的坐标轴 ,均是通过时间戳与日期之间的转换,使得坐标轴以日期、时间的格式显示。还可以设置对数、科学计数等显示方式。
QChart和QCustomPlot都可以通过重写鼠标事件实现与数据图表的交互,但是Qcustomplot可以通过设置setInteractions() 直接实现缩放、放大、拖动等简单的交互,并且提供了QCPItemTracer、QCPItemText简单控件实现数据显示等功能。
{F2291171}
=五、 绘图效率与质量=
表中是测试时长为代码中加载完成图表所需时间,在将图表设置到UI端的view控件内的时间未计算。
|数据点/耗时(ms)| QCharts| Qcustomplot |Qpaint|
|24 |240 |201| 4|
|240 |268 |207 |17|
|2400| 388 |221| 102|
|24000 |1631(开始卡顿)| 254| 974|
|240000| 14009(UI加载不出来)| 368 |9596|
|2400000 |144949 |1829(略微卡顿)| 93144|
|源代码行数 |78| 145 |558|
QCharts:
{F2291165}
QCustomplot:
{F2291166}
=六、 总结=
当数据量较小时:QPaint > QCustomPlot > QChart
当数据量较大时:QCustomPlot > QPaint > QChart
图表美观度:QCustomPlot > QChart
交互功能:QCustomPlot > QChart
因此如果绘制比较简单的图形或内容和常见图表差异较大的情况可以使用paint直接绘制,如果绘制常见的图表,并且具有一定数据量的情况下,可以优先考虑使用qcustomplot。