第9章 Qt Charts
Qt Charts是Qt提供的图表模块,在Qt 5.7以前只有商业版才有Qt Charts,但是从Qt 5.7开始,社区版本也包含了Qt Charts。Qt Charts可以很方便地绘制常见的折线图、柱状图、饼图等图表,不用自己耗费时间和精力开发绘图组件或使用第三方组件了。
本章首先介绍Qt Charts的基本特点和功能,以画折线图为例详细说明Qt Charts各主要部件的操作方法,再介绍各种常用图表的绘图方法,最后介绍鼠标操作图形缩放等功能的实现。
9.1 Qt Charts概述
9.1.1 Qt Charts模块
Qt Charts模块是一组易于使用的图表组件,它基于Qt的Graphics View架构,其核心组件是QChartView和QChart。
QChartView的父类是QGraphicsView,就是Graphics View架构中的视图组件,所以,QChart View是用于显示图表的视图。
QChart的继承关系如原书的图9-1所示,可以看到,QChart是从QGraphicsItem继承而来的,所以,QChart是一种图形项。
QPolarChart是用于绘制极坐标图的图表类,它从QChart继承而来。
要在项目中使用Qt Charts模块,必须在项目的配置文件(.pro文件)中增加下面的一行语句:
Qt += charts
在需要使用QtCharts的类的头文件或源程序文件中,要使用如下的包含语句:
#include <QtCharts>
using namespace QtCharts;
也可以使用宏定义:
#include <QtCharts>
Qt_CHARTS_USE_NAMESPACE
9.1.2 一个简单的QChart绘图程序
先用一个简单实例程序说明QChart绘图的基本原理。创建一个基于QMainWindow的应用程序samp9_1,主窗口上不放置任何组件。在主窗口类中只定义一个createChart()函数,在主窗口的构造函数中调用此函数,即:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
createChart();
}
createChart()函数用于创建图表,其代码如下:
void MainWindow::createChart()
{ //创建图表
QChartView *chartView=new QChartView(this); //创建 ChartView
QChart *chart = new QChart(); //创建 Chart
chart->setTitle("简单函数曲线");
chartView->setChart(chart); //Chart添加到ChartView
this->setCentralWidget(chartView);
//创建折线序列
QLineSeries *series0 = new QLineSeries();
QLineSeries *series1 = new QLineSeries();
series0->setName("Sin曲线");
series1->setName("Cos曲线");
chart->addSeries(series0); //序列添加到图表
chart->addSeries(series1);
//序列添加数值
qreal t=0,y1,y2,intv=0.1;
int cnt=100;
for(int i=0;i<cnt;i++)
{
y1=qSin(t);//+qrand();
series0->append(t,y1);
y2=qSin(t+20);
series1->append(t,y2);
t+=intv;
}
//创建坐标轴
QValueAxis *axisX = new QValueAxis; //X 轴
axisX->setRange(0, 10); //设置坐标轴范围
axisX->setTitleText("time(secs)"); //标题
QValueAxis *axisY = new QValueAxis; //Y 轴
axisY->setRange(-2, 2);
axisY->setTitleText("value");
chart->setAxisX(axisX, series0); //为序列设置坐标轴
chart->setAxisY(axisY, series0);
chart->setAxisX(axisX, series1); //为序列设置坐标轴
chart->setAxisY(axisY, series1);
}
程序运行后界面如原书的图9-2所示。
在createChart()函数里,首先创建一个QChartView对象chartView,再创建一个QChart对象chart,将chart在chartView里显示,使用下面一行语句:
chartView->setChart(chart);
图表上用于显示数据的称为序列(series),这里使用折线序列QLineSeries,创建了两个QLine Series类型的序列,并且将序列添加到chart中。
chart->addSeries(series0);
chart->addSeries(series1);
序列存储用于显示的数据,所以需要为直线序列添加平面数据点的坐标数据。程序将生成正弦和余弦函数的数据作为序列的数据。
序列还需要坐标轴,创建QValueAxis类型的坐标轴作为图表的X轴和Y轴,调用QChart的setAxisX()和setAxisY()函数为两个序列分别设置X轴和Y轴。
9.1.3 图表的主要组成部分
观察createChart()函数的内容和程序运行后的界面(原书的图9-2),可知QChartView是QChart的视图组件,而一个QChart一般包括序列、坐标轴、图例、图表标题等部分。
1.QChartView 的功能
QChartView是QChart的视图组件,类似于Graphics View架构中的QGraphicsView。实际上,在窗口设计界面上使用QChartView时,就是先放置一个QGraphicsView组件,然后升级为QChartView。
QChartView类定义的函数很少,只有以下几个。
-
void setChart(QChart *chart),设置一个QChart对象作为显示的图表。
-
QChart * chart(),返回QChartView当前设置的QChart类对象。
-
void setRubberBand(RubberBands &rubberBand),设置选择框的类型,即鼠标在视图组件上拖动选择范围的方式,是一个QChartView::RubberBand枚举类型的组合,QChartView:: RubberBand枚举类型有以下几种取值:
-
QChartView::NoRubberBand——无选择框;
-
QChartView::VerticalRubberBand——垂向选择;
-
QChartView::HorizontalRubberBand——水平选择;
-
QChartView::RectangleRubberBand——矩形框选择。
-
-
RubberBands rubberBand(),返回设置的选择框类型。
2.序列
序列是数据的表现形式,如原书的图9-2中的两条曲线就是两个QLineSeries类型的序列。
图表的类型主要就是由序列的类型决定的,常见的图表类型有折线图、柱状图、饼图、散点图等,Qt Charts能实现的常见图表示例及用到的序列类见表9-1。
表9-1 Qt Charts常见图表及用到的序列类(图片来自Qt帮助文件)
| 柱状图QBarSeries | 水平柱状图QHorizontalBarSeries |
| 百分比柱状图QPercentBarSeries | 水平百分比柱状图QHorizontalPercentBarSeries |
| 堆叠柱状图QStackedBarSeries | 水平堆叠柱状图QHorizontalStackedBarSeries |
| 火柴盒图QBoxPlotSeries | 饼图QPieSeries |
| 折线图QLineSeries | 光滑曲线图QSplineSeries |
| 散点图QScatterSeries | 面积图QAreaSeries |
原书的图9-3是这些序列类的继承关系,可见它们都是从QAbstractSeries类继承而来的。折线、光滑曲线和散点的序列是从QXYSeries继承而来,用于绘制二维平面的数据;QAbstractBarSeries派生出柱状图、百分比柱状图和堆叠图等图表的序列;面积图、火柴盒图、饼图的序列都直接继承于QAbstractSeries。
3.坐标轴
一般的图表都有横轴和纵轴两个坐标轴,如折线图一般表示数据,坐标轴用QValueAxis类的数值坐标轴,如果用对数坐标,就可以使用QLogValueAxis类的坐标轴;柱状图的横坐标通常是文字,可以用QBarCategoryAxis作为横轴,而饼图一般没有坐标轴。
Qt Charts的坐标轴类、特点及其适用情况见表9-2,类的继承关系如原书的图9-4所示。
表9-2 Qt提供的坐标轴类
| QValueAxis | 数值坐标轴 | 作为数值型数据的坐标轴 |
| QCategoryAxis | 分组数值坐标轴 | 可以为数值范围设置文字标签 |
| QLogValueAxis | 对数数值坐标轴 | 作为数值型数据的对数坐标轴,可以设置对数的基 |
| QBarCategoryAxis | 类别坐标轴 | 用字符串作为坐标轴的刻度,用于图表的非数值坐标轴 |
| QDateTimeAxis | 日期时间坐标轴 | 作为日期时间数据的坐标轴 |
坐标轴类封装了坐标轴的刻度、标签、网格线、标题等属性。
4.图例
图例(Legend)是对图表上显示的序列的示例说明,如图9-2中为两条曲线显示的图例,有线条颜色和文字说明。QLegend是封装了图例控制功能的类,可以为每个序列设置图例中的文字,可以控制图例显示在图表的上、下、左、右不同位置。
对于图例还有一个类QLegendMarker,可以为每个序列的图例生成一个类似于QCheckBox的组件,在图例上单击序列的标记,可以控制序列是否显示。
9.2 QChart绘制折线图
9.2.1 实例功能
实例samp9_2以绘制折线图为例,详细介绍图表各个部分的设置和操作,包括图表的标题、图例、边距等属性设置,QLineSeries序列的属性设置,QValueAxis坐标轴的属性设置,以及图表的缩放。实例运行时界面如原书的图9-5所示。
实例samp9_2是一个主窗口继承自QMainWindow类的应用程序,界面的设计主要分为以下几个部分。
-
工具栏:创建几个Action,并创建工具栏,实现图表数据刷新和缩放功能。
-
主工作区图表视图:从组件面板放置一个QGraphicsView组件作为视图组件,并用Promote方法升级为QChartView组件,命名为chartView。
-
图表属性设置面板:左侧是一个QToolBox组件,分为3个操作面板,用于进行图表设置、曲线设置和坐标轴设置。
9.2.2 主窗口类定义和初始化
下面是主窗口类MainWindow的类定义(省略了Action和界面组件的槽函数定义)。在mainwindow.h文件中需要包含QtChart,并使用宏Qt_CHARTS_USE_NAMESPACE导入命名空间。
#include <QtCharts>
Qt_CHARTS_USE_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
private:
QLineSeries *curSeries; //当前序列
QValueAxis *curAxis; //当前坐标轴
void createChart(); //创建图表
void prepareData(); //更新数据
void updateFromChart(); //从图表更新到界面
public:
explicit MainWindow(QWidget *parent = 0);
private:
Ui::MainWindow *ui;
};
在MainWindow类中定义了两个私有变量,curSeries用于指向当前的QLineSeries序列,界面上对序列的设置操作都是针对选择的当前序列;curAxis用于指向当前的QValueAxis坐标轴,对坐标轴进行设置时就是针对当前坐标轴进行设置。
createChart()函数用于创建图表的各个基本部件,在构造函数里调用,prepareData()用于更新序列的数据,updateFromChart()用于读取图表的一些属性,并刷新界面显示。下面是主窗口构造函数,以及这3个函数的代码。
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
createChart();//创建图表
prepareData();//生成数据
updateFromChart(); //从图表获得属性值,刷新界面显示
}
void MainWindow::createChart()
{//创建图表的各个部件
QChart *chart = new QChart();
chart->setTitle("简单函数曲线");
ui->chartView->setChart(chart);
ui->chartView->setRenderHint(QPainter::Antialiasing);
QLineSeries *series0 = new QLineSeries();
QLineSeries *series1 = new QLineSeries();
series0->setName("Sin曲线");
series1->setName("Cos曲线");
curSeries=series0; //当前序列
QPen pen;
pen.setStyle(Qt::DotLine);
pen.setWidth(2);
pen.setColor(Qt::red);
series0->setPen(pen); //折线序列的线条设置
pen.setStyle(Qt::SolidLine);
pen.setColor(Qt::blue);
series1->setPen(pen);//折线序列的线条设置
chart->addSeries(series0);
chart->addSeries(series1);
QValueAxis *axisX = new QValueAxis;
curAxis=axisX; //当前坐标轴
axisX->setRange(0, 10); //设置坐标轴范围
axisX->setLabelFormat("%.1f"); //标签格式
axisX->setTickCount(11); //主分隔个数
axisX->setMinorTickCount(4);
axisX->setTitleText("time(secs)"); //标题
QValueAxis *axisY = new QValueAxis;
axisY->setRange(-2, 2);
axisY->setTitleText("value");
axisY->setTickCount(5);
axisY->setLabelFormat("%.2f"); //标签格式
axisY->setMinorTickCount(4);
chart->setAxisX(axisX, series0); //设置X坐标轴
chart->setAxisX(axisX, series1); //设置X坐标轴
chart->setAxisY(axisY, series0); //设置Y坐标轴
chart->setAxisY(axisY, series1); //设置Y坐标轴
}
void MainWindow::prepareData()
{//为序列生成数据
QLineSeries *series0=(QLineSeries *)ui->chartView->chart()->series().at(0);
QLineSeries *series1=(QLineSeries *)ui->chartView->chart()->series().at(1);
series0->clear(); //清除数据
series1->clear();
qsrand(QTime::currentTime().second());//随机数初始化
qreal t=0,y1,y2,intv=0.1;
qreal rd;
int cnt=100;
for(int i=0;iappend(t,y1); //序列添加数据点
rd=(qrand() % 10)-5; //随机数,-5~+5
y2=qCos(t)+rd/50;
series1->append(t,y2); //序列添加数据点
t+=intv;
}
}
void MainWindow::updateFromChart()
{ //从图表上获取数据更新界面显示
QChart* aChart=ui->chartView->chart(); //获取chart
ui->editTitle->setText(aChart->title()); //图表标题
QMargins mg=aChart->margins(); //边距
ui->spinMarginLeft->setValue(mg.left());
ui->spinMarginRight->setValue(mg.right());
ui->spinMarginTop->setValue(mg.top());
ui->spinMarginBottom->setValue(mg.bottom());
}
MainWindow类的构造函数调用3个私有函数进行图表和界面的初始化。
createChart()函数用于创建QChart对象,创建数据序列和坐标轴,并将这些部件组合成一个完整的图表。createChart()函数的功能与实例samp9_1中的函数功能类似,只是没有为序列添加数据点。
注意:在MainWindow类中并没有定义QChart类的成员变量,后面要使用创建的图表对象,都通过ui->chartView->chart()来获得QChart对象。
prepareData()函数用于为图表中的两个序列生成数据,其中使用了随机数,以便使得每次生成的数据稍有不同。
updateFromChart()函数用于将图表的标题和边距信息显示到窗口界面上。
9.2.3 画笔设置对话框QWDialogPen
在本实例中,经常需要设置一些对象的pen属性,如折线序列的pen属性,网格线的pen属性等。pen属性就是一个QPen对象,设置内容主要包括线型、线宽和颜色。为使用方便,设计一个自定义对话框QWDialogPen,专门用于QPen对象的属性设置。
QWDialogPen是一个可视化设计的对话框,其类定义如下:
class QWDialogPen : public QDialog
{ //QPen属性设置对话框
Q_OBJECT
private:
QPen m_pen; //成员变量
public:
explicit QWDialogPen(QWidget *parent = 0);
~QWDialogPen();
void setPen(QPen pen); //设置QPen,用于对话框的界面显示
QPen getPen(); //获取对话框设置的QPen的属性
static QPen getPen(QPen iniPen, bool &ok); //静态函数
private slots:
void on_btnColor_clicked();
private:
Ui::QWDialogPen *ui;
};
自定义对话框的设计在第6章已经介绍过,这个QWDialogPen的特别之处在于定义了一个静态函数getPen(),在类中的定义如下:
static QPen getPen(QPen iniPen, bool &ok); //静态函数
Qt的标准对话框一般都有静态函数,使用静态函数无需管理对话框的创建与删除,使用起来比较方便。
QWDialogPen类的静态函数getPen()以及相关函数的实现代码如下:
QPen QWDialogPen::getPen(QPen iniPen,bool &ok)
{ //静态函数,获取QPen
QWDialogPen *Dlg=new QWDialogPen; //创建一个对话框
Dlg->setPen(iniPen); //设置初始化QPen
QPen pen;
int ret=Dlg->exec(); //弹出对话框
if (ret==QDialog::Accepted)
{
pen=Dlg->getPen(); //获取
ok=true; }
else
{
pen=iniPen;
ok=false; }
delete Dlg; //删除对话框对象
return pen; //返回设置的QPen对象
}
void QWDialogPen::setPen(QPen pen)
{ //设置QPen,并刷新显示界面
m_pen=pen;
ui->spinWidth->setValue(pen.width()); //线宽
int i=static_cast<int>(pen.style()); //枚举类型转换为整型
ui->comboPenStyle->setCurrentIndex(i);
QColor color=pen.color();
ui->btnColor->setAutoFillBackground(true); //设置颜色按钮的背景色
QString str=QString::asprintf("background-color: rgb(%d, %d, %d);",
color.red(),color.green(),color.blue());
ui->btnColor->setStyleSheet(str);
}
QPen QWDialogPen::getPen()
{//获得设置的属性
m_pen.setStyle(Qt::PenStyle(ui->comboPenStyle->currentIndex())); //线型
m_pen.setWidth(ui->spinWidth->value()); //线宽
QColor color=ui->btnColor->palette().color(QPalette::Button);
m_pen.setColor(color); //颜色
return m_pen;
}
静态函数getPen()里创建了一个QWDialogPen类的实例Dlg,然后调用Dlg->setPen(iniPen)进行初始化设置,运行对话框并获取返回状态,若返回类型为QDialog::Accepted,就调用Dlg->getPen()获取设置属性后的QPen的对象,最后删除对话框对象并返回设置的QPen对象。所以,静态函数getPen()就是集成了普通方法调用对话框时的创建对话框、设置初始值、获取对话框返回状态、获取返回值、删除对话框的过程,简化了调用代码。
在设置颜色按钮的背景色时用到了StyleSheet的功能,这一点在16.2节有详细介绍。
原书的图9-6是QWDialogPen对话框运行界面,可以选择线型、设置线宽和颜色。
9.2.4 QChart的设置
QChart是组合图表各部件、显示各种数据序列的绘图组件。QChart接口函数众多,其主要接口函数分类整理后见表9-3。对于一个属性,通常有一个设置函数和一个对应的读取函数,如setTitle()用于设置图表标题,对应的读取图表标题的函数为title()。表9-3仅列出设置函数或单独地读取函数,仅列出函数的返回数据类型,省略了输入参数,函数详细定义请参考Qt帮助文件。
表9-3 QChart类的主要函数
| 图表 外观 | void setTitle() | 设置图表标题,显示在图表上方,支持HTML格式 |
| void setTitleFont() | 设置图表标题字体 | |
| void setTitleBrush() | 设置图表标题画刷 | |
| void setTheme() | 设置主题,主题是内置的UI设置,定义了图表的配色 | |
| void setMargins() | 设置绘图区与图表边界的4个边距 | |
| QLegend * legend() | 返回图表的图例,是一个QLegend类的对象 | |
| void setAnimationOptions() | 设置序列或坐标轴的动画效果 | |
| 数据 序列 | void addSeries() | 添加序列 |
| QList<QAbstractSeries *> series() | 返回图表拥有的序列的列表 | |
| void removeSeries() | 移除一个序列,但并不删除序列对象 | |
| void removeAllSeries() | 移除并删除图表的所有序列 | |
| 坐标轴 | void addAxis() | 为图表的某个方向添加坐标轴 |
| QList<QAbstractAxis *> axes() | 返回某个方向的坐标轴列表 | |
| void setAxisX() | 设置某个序列的水平方向的坐标轴 | |
| void setAxisY() | 设置某个序列的垂直方向的坐标轴 | |
| void removeAxis() | 移除一个坐标轴 | |
| void createDefaultAxes() | 根据已添加的序列的类型,创建缺省的坐标轴,前面已有的坐标轴会被删除 |
原书的图9-7是进行图表设置的界面,通过界面可以设置图表标题的文字内容和字体,可以设置图例的位置、是否显示、字体和颜色,可以设置4个边距的值,可以设置动画效果以及主题。原书的图9-8是进行曲线序列设置的界面,可操作内容如下。
setAnimationOptions(AnimationOptions options)函数设置图表的动画效果,输入参数是QChart:: AnimationOption枚举类型,有以下几种取值:
-
QChart::NoAnimation——无动画效果;
-
QChart::GridAxisAnimations——背景网格有动画效果;
-
QChart::SeriesAnimations——序列有动画效果;
-
QChart::AllAnimations——都有动画效果。
主题是预定义的图表配色样式,是QChart::ChartTheme枚举类型,有多种取值,使图表具有不同的配色效果。
图例是一个QLegend类的对象,通过QChart::legend()可以获得图表的图例。图例是根据添加的序列自动生成的,但是可以修改图例的一些属性,如在图表中的显示位置、图例文字的字体等。例如,设置图例显示在图表的底部可用下面的语句:
ui->chartView->chart()->legend()->setAlignment(Qt::AlignBottom);
设置图例文字的字体的代码如下:
void MainWindow::on_btnLegendFont_clicked()
{ //图例的字体设置
QFont font=ui->chartView->chart()->legend()->font();
bool ok=false;
font=QFontDialog::getFont(&ok,font);
if (ok)
ui->chartView->chart()->legend()->setFont(font);
}
9.2.5 QLineSeries序列的设置
本实例图表的序列使用的是QLineSeries,它是QXYSeries的子类,用于绘制二维数据点的折线图。QLineSeries的主要函数见表9-4(包括从父类继承的函数,仅列出函数的返回数据类型,省略了输入参数)。
表9-4 QLineSeries类的主要函数
| 序列名称 | void setName() | 设置序列的名称,这个名称会显示在图例里,支持HTML格式 |
| 图表 | QChart* chart() | 返回序列所属的图表对象 |
| 序列外观 | void setVisible() | 设置序列可见性 |
| void show() | 显示序列,使序列可见 | |
| void hide() | 隐藏序列,使其不可见 | |
| void setColor() | 设置序列线条的颜色 | |
| void setPen() | 设置绘制线条的画笔 | |
| void setBrush() | 设置绘制数据点的画刷 | |
| void setOpacity() | 设置序列的透明度,0表示完全透明,1表示不透明 | |
| 数据点 | void setPointsVisible() | 设置数据点的可见性 |
| void append() | 添加一个数据点到序列 | |
| void insert() | 在某个位置插入一个数据点 | |
| void replace() | 替换某个数据点 | |
| void clear() | 清除所有数据点 | |
| void remove() | 删除某个数据点 | |
| void removePoints() | 从某个位置开始,删除指定个数的数据点 | |
| int count() | 数据点的个数 | |
| 数据点 | QPointF& at() | 返回某个位置的数据点 |
| QList points() | 返回数据点的列表 | |
| QVector pointsVector() | 返回数据点的,效率更高 | |
| 数据点标签 | void setPointLabelsVisible() | 设置数据点标签的可见性 |
| void setPointLabelsColor() | 设置数据点标签的文字颜色 | |
| void setPointLabelsFont() | 设置数据点标签字体 | |
| void setPointLabelsFormat() | 设置数据点标签格式 | |
| void setPointLabelsClipping() | 设置标签的裁剪属性,缺省为True,即绘图区外的标签被裁剪掉 | |
| 坐标轴 | bool attachAxis() | 为序列附加一个坐标轴,通常需要一个X轴和一个Y轴 |
| bool detachAxis() | 解除一个附加的坐标轴 | |
| QList<QAbstractAxis *> attachedAxes() | 返回附加的坐标轴的列表 |
实例中对曲线序列进行属性设置的界面如原书的图9-8所示。首先通过上方的“选择操作序列”里的两个RadioButton按钮选择当前操作序列,两个按钮的代码相同,代码如下:
void MainWindow::on_radioSeries0_clicked()
{//获取当前数据序列
if (ui->radioSeries0->isChecked())
curSeries=(QLineSeries *)ui->chartView->chart()->series().at(0);
else
curSeries=(QLineSeries *)ui->chartView->chart()->series().at(1);
//获取序列的属性值,并显示到界面上
ui->editSeriesName->setText(curSeries->name());
ui->chkSeriesVisible->setChecked(curSeries->isVisible());
ui->chkPointVisible->setChecked(curSeries->pointsVisible());
ui->sliderSeriesOpacity->setValue(curSeries->opacity()*10);
ui->chkPointLabelVisible->setChecked(curSeries->pointLabelsVisible());
}
curSeries是在MainWindow类里定义的私有变量,用于指向当前操作的序列。选择序列后,会将当前序列的一些属性值显示到界面上,如序列名称、序列可见性、序列数据点可见性等。
序列的一些常规属性的设置都很简单,例如序列名称设置、序列可见性设置、曲线pen属性设置等,调用相应函数即可。
数据点标签的格式设置使用函数setPointLabelsFormat(),有两种数据可以在数据点标签中显示,有固定的标签:
@xPoint,数据点的X值;
@yPoint,数据点的Y值。
例如,使数据点标签只显示Y值,设置语句为:
curSeries->setPointLabelsFormat("@yPoint");
如果使数据点标签显示(X, Y)值,设置语句为:
curSeries->setPointLabelsFormat("(@xPoint,@yPoint)");
为一个序列添加数据点,可以使用append()函数,也可以使用流操作符“<<”,如prepareData()函数中添加数据点的部分也可以改写为:
*series0<<QPointF(t,y1); //序列添加数据点
为序列指定坐标轴,在前面的createChart()函数中,使用QChart的函数为序列设置坐标轴:
chart->setAxisX(axisX, series0); //添加X坐标轴
chart->setAxisX(axisX, series1); //添加X坐标轴
chart->setAxisY(axisY, series0); //添加Y坐标轴
chart->setAxisY(axisY, series1); //添加Y坐标轴
QChart:: setAxisX()函数为序列指定X坐标轴,并将坐标轴添加到图表里;QChart::setAxisY()函数为序列指定Y坐标轴,并将坐标轴添加到图表里。无需再调用序列的attachAxis()函数。若要使用序列的attachAxis()函数,则实现上述功能的代码如下:
chart->addAxis(axisX,Qt::AlignBottom); //坐标轴添加到图表,并指定方向
chart->addAxis(axisY,Qt::AlignLeft);
series0->attachAxis(axisX); //序列 series0 附加坐标轴
series0->attachAxis(axisY);
series1->attachAxis(axisX);//序列 series1 附加坐标轴
series1->attachAxis(axisY);
即先用QChart::addAxis()函数添加一个坐标轴到图表,并指定坐标轴的方向,然后用序列的attachAxis()函数附加坐标轴对象。
9.2.6 QValueAxis坐标轴的设置
本例中使用QValueAxis类的坐标轴,这是数值型坐标轴,与QLineSeries正好配合使用。Qt提供了好几种坐标轴类(见原书的图9-4),都是从QAbstractAxis类继承而来的。QValueAxis类的主要函数见表9-5(包括从QAbstractAxis继承的函数,仅列出函数的返回数据类型,省略了输入参数)。
表9-5 QValueAxis类的主要函数
| 坐标轴整体 | void setVisible() | 设置坐标轴可见性 |
| Qt::Orientation orientation() | 返回坐标轴方向 | |
| void setMin() | 设置坐标轴最小值 | |
| void setMax() | 设置坐标轴最大值 | |
| void setRange() | 设置坐标轴最小、最大值表示的范围 | |
| 轴标题 | void setTitleVisible() | 设置轴标题的可见性 |
| void setTitleText() | 设置轴标题的文字 | |
| void setTitleFont() | 设置轴标题的字体 | |
| void setTitleBrush() | 设置轴标题的画刷 | |
| 轴标签 | void setLabelFormat() | 设置标签格式,例如可以设置显示的小数点位数 |
| void setLabelsAngle() | 设置标签的角度,单位为度 | |
| void setLabelsBrush() | 设置标签的画刷 | |
| void setLabelsColor() | 设置标签文字颜色 | |
| void setLabelsFont() | 设置标签文字字体 | |
| void setLabelsVisible() | 设置轴标签文字是否可见 | |
| 轴线和刻度线 | void setTickCount() | 设置坐标轴主刻度的个数 |
| void setLineVisible() | 设置轴线和刻度线的可见性 | |
| void setLinePen() | 设置轴线和刻度线的画笔 | |
| void setLinePenColor() | 设置轴线和刻度线的颜色 | |
| 主网格线 | void setGridLineColor() | 设置网格线的颜色 |
| void setGridLinePen() | 设置网格线的画笔 | |
| void setGridLineVisible() | 设置网格线的可见性 | |
| 次刻度和次网格线 | void setMinorTickCount() | 设置两个主刻度之间的次刻度的个数 |
| void setMinorGridLineColor() | 设置次网格线的颜色 | |
| void setMinorGridLinePen() | 设置次网格线的画笔 | |
| void setMinorGridLineVisible() | 设置次网格线的可见性 |
参看图9-5图表的X坐标轴,QValueAxis坐标轴有以下几个组成部分。
-
坐标轴标题:是在坐标轴下方显示的文字,表示坐标轴的名称,图中X轴坐标轴的标题是“time(secs)”。坐标轴标题除了可以设置文字内容,还可以设置字体、画刷和可见性。
-
轴线和刻度线:轴线是图中从左到右的表示坐标轴的直线,刻度线是垂直于轴线的短线,包括主刻度线和次刻度线,主刻度个数是tickCount(),每两个主刻度之间的次刻度的个数是minorTickCount()。
-
轴标签:在主刻度处显示的数值标签文字,可以控制其数值格式、文字颜色和字体等。
-
主网格线:在绘图区与主刻度对应的网格线,可以设置其颜色、线条的pen属性、可见性等。
-
次网格线:在绘图区与次刻度对应的网格线,可以设置其颜色、线条的pen属性、可见性等。
搞清楚坐标轴的这些组成部分后,对其进行属性读取或设置就只需调用相应的函数即可。原书的图9-9是实例中窗口左侧“坐标轴设置”的界面内容,可以对坐标轴的各种属性进行设置。
在图9-9的界面上首先选择需要操作的坐标轴对象,两个RadioButton按钮的响应代码相同,为radioX按钮编写事件槽函数,另一个按钮的槽函数里只需调用这个函数即可,代码如下:
void MainWindow::on_radioX_clicked()
{ //获取当前坐标轴
if (ui->radioX->isChecked())
curAxis=(QValueAxis*)ui->chartView->chart()->axisX();
else
curAxis=(QValueAxis*)ui->chartView->chart()->axisY();
//获取坐标轴的各种属性,显示到界面上
ui->spinAxisMin->setValue(curAxis->min());
ui->spinAxisMax->setValue(curAxis->max());
ui->editAxisTitle->setText(curAxis->titleText());
ui->chkBoxAxisTitle->setChecked(curAxis->isTitleVisible());
ui->editAxisLabelFormat->setText(curAxis->labelFormat());
ui->chkBoxLabelsVisible->setChecked(curAxis->labelsVisible());
ui->chkGridLineVisible->setChecked(curAxis->isGridLineVisible());
ui->chkAxisLineVisible->setChecked(curAxis->isLineVisible());
ui->spinTickCount->setValue(curAxis->tickCount());
ui->chkAxisLineVisible->setChecked(curAxis->isLineVisible());
ui->spinMinorTickCount->setValue(curAxis->minorTickCount());
ui->chkMinorTickVisible->setChecked(curAxis->isMinorGridLineVisible());
}
程序中curAxis是在MainWindow类中定义的私有变量,用于表示当前操作的坐标轴对象。获取对象后,首先获得轴对象的各种属性并显示在界面上。
坐标轴各种属性的设置只需调用QValueAxis的相应函数即可,各种接口函数可参考表9-5。例如,“标签格式”按钮的槽函数代码如下:
void MainWindow::on_pushButton_clicked()
{//设置坐标轴刻度标签的文字格式
curAxis->setLabelFormat(ui->editAxisLabelFormat->text());
}
在编辑框editAxisLabelFormat里设置格式字符串,例如“%.2f”,作为QValueAxis::setLabelFormat()的输入参数。格式字符串的定义与printf()函数的格式字符串定义一样。
9.3 各种常见图表的绘制
9.3.1 实例功能概述
前一节通过折线图的绘制,介绍了Qt Charts绘图的基本原理。图表的类型由数据序列决定,除了折线图,Qt Charts还提供柱状图、饼图、百分比柱状图等常见图表。
本节通过实例samp9_3介绍这些常见图表的绘制方法,原书的图9-10是实例samp9_3的运行界面。左上方是随机生成的若干个学生的数学、语文、英语分数,平均分是自动计算的,左下方是根据分数统计的结果,右方是各种图表的页面。
该实例的主要目的是演示各种常见图表的绘制方法,图表的基本设置在程序里完成,不再如实例samp9_2那样提供丰富的设置功能。
窗口左上方是一个QTableView组件,使用Model/View结构提供数据编辑功能;左下角是一个QTreeWidget组件,用于显示统计数据结果;窗口右方是一个QTabWidget组件,共有5个页面,分别用于显示柱状图、饼图、堆叠图、百分比柱状图和散点图,每个图表的显示用一个Qgraphics View组件升级为QChartView。
9.3.2 数据准备
图9-10左上方的数据采用Model/View结构,在主窗口类MainWindow里定义了数据模型,并在主窗口的构造函数里初始化数据。下面是MainWindow的类定义(省略了部分窗口界面组件的槽函数定义)。
#define fixedColumnCount 5 //数据模型的列数
#define iniDataRowCount 6 //学生个数
#define colNoName 0 //姓名的列编号
#define colNoMath 1 //数学的列编号
#define colNoChinese 2 //语文的列编号
#define colNoEnglish 3 //英语的列编号
#define colNoAverage 4 //平均分的列编号
class MainWindow : public QMainWindow
{
Q_OBJECT
private:
QStandardItemModel *theModel; //数据模型
void iniData(); //初始化数据
void surveyData(); //统计数据
void iniBarChart(); //柱状图初始化
void buildBarChart(); //构建柱状图
void iniPiewChart(); //饼图
void buildPieChart();
void iniStackedBar(); //堆叠图
void buildStackedBar();
void iniPercentBar(); //百分比柱状图
void buildPercentBar();
void iniScatterChart(); //散点图
void buildScatterChart();
private slots:
//三个分数列的数据发生变化,重新计算平均分
void on_itemChanged(QStandardItem *item);
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
};
在头文件中首先定义了一些常量,包括数据模型的列数fixedColumnCount和学生人数iniData RowCount。定义为常量后便于修改,例如现在学生人数定义为6个,后续为了增大数据样本,可以将学生人数定义为30人。头文件里还定义了数据模型中5个列的编号,在后续的程序里使用常量。
MainWindow类中定义了数据模型类变量theModel,函数iniData()用于随机初始化数据,surveyData()用于对数据进行统计。
每个图表有两个函数,一个用于初始化,一个用于根据数据构建图表。如iniBarChart()用于初始化柱状图,buildBarChart()用于根据数据构建柱状图。
数据模型中每名学生的平均分根据3门课的成绩自动计算,希望在界面修改一个分数时,自动计算其平均分。这里可以利用QStandardItemModel的itemChanged()信号,在某个数据项被修改后会发射此信号,定义了一个私有槽函数on_itemChanged()用于响应此信号,实现平均分的自动计算。
下面是MainWindow的构造函数的实现代码。
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
theModel = new QStandardItemModel(iniDataRowCount,fixedColumnCount,this);
iniData(); //初始化数据
surveyData();//数据统计
//数据模块的 itemChanged信号与自定义的槽函数关联,用于自动计算平均分
connect(theModel,SIGNAL(itemChanged(QStandardItem *)),
this,SLOT(on_itemChanged(QStandardItem *)));
ui->tableView->setModel(theModel); //设置数据模型
iniBarChart(); //柱状图初始化
iniPiewChart();//饼图初始化
iniStackedBar();//堆叠图初始化
iniPercentBar();//百分比柱状图初始化
iniScatterChart(); //散点图初始化
}
在构造函数中,创建数据模型theModel,然后初始化数据、统计数据,将数据模型的itemChanged()信号与自定义槽函数on_itemChanged()关联起来。然后对5个图表调用函数进行初始化。
下面是iniData()函数的实现代码,用于对数据模型进行初始化。
void MainWindow::iniData()
{ //数据初始化
QStringList headerList;
headerList<<"姓名"<<"数学"<<"语文"<<"英语"<<"平均分";
theModel->setHorizontalHeaderLabels(headerList); //设置表头文字
qsrand(QTime::currentTime().second());//随机数种子
for (int i=0;i<theModel->rowCount();i++)
{
QString studName=QString::asprintf("学生%2d",i+1);
QStandardItem* aItem=new QStandardItem(studName);//创建item
aItem->setTextAlignment(Qt::AlignHCenter);
theModel->setItem(i,colNoName,aItem); //学生列,设置item
qreal avgScore=0;
for (int j=colNoMath;j<=colNoEnglish;j++) //数学,语文,英语
{ //不包含最后一列
qreal score=50.0+(qrand() % 50);//随机数
avgScore+=score;
aItem=new QStandardItem(QString::asprintf("%.0f",score));
aItem->setTextAlignment(Qt::AlignHCenter);
theModel->setItem(i,j,aItem); //设置Item
}
aItem=new QStandardItem(QString::asprintf("%.1f",avgScore/3));//平均分
aItem->setTextAlignment(Qt::AlignHCenter);
aItem->setFlags(aItem->flags() & (!Qt::ItemIsEditable));//不允许编辑
theModel->setItem(i,colNoAverage,aItem);
}
}
数据初始化会设置表头标题,数学、语文、英语分数采用随机数生成,然后计算平均分。其中,平均分一列是不允许编辑的。
数据统计函数surveyData()用于对数据模型里的数据按照分数段进行统计,然后显示在QTreeWidget组件里。数据统计是简单的数学计算,对QTreeWidget的操作在4.7节有介绍,所以surveyData()函数的代码不再具体显示。
自定义槽函数on_itemChanged()用于在分数修改后自动计算平均分,下面是槽函数on_itemChanged()的代码。
void MainWindow::on_itemChanged(QStandardItem *item)
{ //自动计算平均分
if ((item->column()<colNoMath) || (item->column()>colNoEnglish))
return; //如果被修改的item不是数学、语文、英语,就退出
int rowNo=item->row(); //获取数据的行编号
qreal avg=0;
QStandardItem *aItem;
for (int i=colNoMath;i<=colNoEnglish;i++)
{ //获取三个分数的和
aItem=theModel->item(rowNo,i);
avg+=aItem->text().toDouble();
}
avg=avg/3; //计算平均分
aItem=theModel->item(rowNo,colNoAverage); //获取平均分数据的item
aItem->setText(QString::asprintf("%.1f",avg));
}
传递的参数item是被修改的数据项。程序首先判断该数据项是不是3个基本分数,如果不是就退出。通过item->row()获取当前数据的行号,然后提取3个基本分数计算平均分,再更新数据模型中的平均分数据项。
9.3.3 柱状图
1.柱状图的绘制
MainWindow的构造函数里调用iniBarChart()函数对绘制柱状图进行初始化,单击原书的图9-10的“BarChart”页面的“刷新柱状图”按钮,会调用buildBarChart()绘制柱状图。绘制的柱状图如图9-10所示,柱状图由3个数据集组成,分别是数学、语文和英语,平均分用折线序列显示。图表的横坐标是学生姓名,纵坐标是数值。
iniBarChart()函数的代码如下:
void MainWindow::iniBarChart()
{ //柱状图初始化
QChart *chart = new QChart();
chart->setTitle("Barchart演示");
chart->setAnimationOptions(QChart::SeriesAnimations);
ui->chartViewBar->setChart(chart);
ui->chartViewBar->setRenderHint(QPainter::Antialiasing);
}
在可视化设计界面时,放置了一个QGraphicsView组件,然后升级为QChartView,并命名为chartViewBar。在iniBarChart()函数的代码里,创建了一个QChart对象,然后设置为QChartView组件显示的图表。
buildBarChart()函数用于绘制柱状图和平均分折线图,代码如下:
void MainWindow::buildBarChart()
{ //构造柱状图
QChart *chart =ui->chartViewBar->chart(); //获取ChartView关联的chart
chart->removeAllSeries(); //删除所有序列
chart->removeAxis(chart->axisX()); //删除坐标轴
chart->removeAxis(chart->axisY()); //删除坐标轴
//创建三个QBarSet数据集,从数据模型的表头获取Name
QBarSet *setMath = new QBarSet(theModel->horizontalHeaderItem(colNoMath)->text());
QBarSet *setChinese = new QBarSet(theModel->horizontalHeaderItem(colNoChinese)->text());
QBarSet *setEnglish= new QBarSet(theModel->horizontalHeaderItem(colNoEnglish)->text());
QLineSeries *Line = new QLineSeries(); //用于显示平均分
Line ->setName(theModel->horizontalHeaderItem(colNoAverage)->text());
QPen pen;
pen.setColor(Qt::red);
pen.setWidth(2);
Line ->setPen(pen);
for(int i=0;i<theModel->rowCount();i++)
{//从数据模型获取数据
setMath->append(theModel->item(i,colNoMath)->text().toInt());
setChinese->append(theModel->item(i,colNoChinese)->text().toInt());
setEnglish->append(theModel->item(i,colNoEnglish)->text().toInt());
Line->append(QPointF(i,theModel->item(i, colNoAverage)->text().toFloat()));
}
//创建一个柱状图序列 QBarSeries, 并添加三个数据集
QBarSeries *series = new QBarSeries();
series->append(setMath);
series->append(setChinese);
series->append(setEnglish);
chart->addSeries(series); //添加柱状图序列
chart->addSeries(Line); //添加折线图序列
//用于横坐标的字符串列表,即学生姓名
QStringList categories;
for (int i=0;i<theModel->rowCount();i++)
categories <<theModel->item(i,colNoName)->text();
//用于柱状图的横坐标轴
QBarCategoryAxis *axisX = new QBarCategoryAxis();
axisX->append(categories); //添加横坐标文字列表
chart->setAxisX(axisX, series); //设置横坐标
chart->setAxisX(axisX, Line);//设置横坐标
axisX->setRange(categories.at(0), categories.at(categories.count()-1));
//数值型坐标作为纵坐标轴
QValueAxis *axisY = new QValueAxis;
axisY->setRange(0, 100);
axisY->setTitleText("分数");
axisY->setTickCount(6);
axisY->setLabelFormat("%.0f"); //标签格式
chart->setAxisY(axisY, series);
chart->setAxisY(axisY, Line);
chart->legend()->setVisible(true); //显示图例
chart->legend()->setAlignment(Qt::AlignBottom); //图例显示在下方
}
代码里首先通过下面的一行语句获取chartViewBar关联的QChart组件,然后对Chart的操作就都可以使用此变量。
QChart *chart =ui->chartViewBar->chart();
获得chart之后,先删除chart原有的所有序列和坐标轴。
一个柱状图由多个数据集组成,在此创建了3个QBarSet数据集,分别对应数学、语文、英语3门课的分数。
柱状图的数据集类是QBarSet,创建对象时使用了数据模型的列标题作为数据集的名称,这个名称会显示在图例里。
显示平均分使用QLineSeries序列,即折线序列。
创建3个QBarSet数据集和折线序列后,遍历数据模型将数据添加到数据集或折线序列。QBarSet::append(const qreal value)只需添加一个数值,如:
setMath->append(theModel->item(i,colNoMath)->text().toInt()); //数学
QLineSeries::append(const QPointF &point)则是添加一个数据点。
为3个QBarSet数据集添加完数据后,创建一个QBarSeries序列,并将3个数据集添加到这个QBarSeries序列,即:
QBarSeries *series = new QBarSeries();
series->append(setMath);
series->append(setChinese);
series->append(setEnglish);
所以,虽然是有3个数据集,但是只有一个柱状图序列。然后将QBarSeries序列和QLineSeries序列添加到图表。
添加完序列后要创建坐标轴。横坐标是QBarCategoryAxis类的坐标,是每个学生的姓名;纵坐标是QValueAxis类型坐标,是数值型坐标,为其设置范围和刻度等属性后,设置为两个序列的Y轴坐标。
2.柱状图相关的主要类
从以上创建柱状图的程序可以看出,与柱状图相关的主要类包括以下3个。
-
QBarSet:用于创建柱状图的数据集。
-
QBarSeries:柱状图序列,一个柱状图序列一般包含多个QBarSet数据集。
-
QBarCategoryAxis:柱状图分类坐标,以文字标签形式表示的坐标。
QBarSet是直接从QObject继承而来的类,其主要的函数功能见表9-6(仅列出函数的返回数据类型,省略了输入参数)。
表9-6 QBarSet类的主要函数功能
| 标签 | void setLabel() | 设置数据集的标签,用于图例显示的文字 |
| void setLabelBrush() | 设置标签的画刷 | |
| void setLabelColor() | 设置标签的文字颜色 | |
| void setLabelFont() | 设置标签的字体 | |
| 数据棒 | void setBorderColor() | 设置数据集的棒图的边框颜色 |
| void setBrush() | 设置数据集的棒图的画刷 | |
| void setColor() | 设置数据集的棒图填充颜色 | |
| void setPen() | 设置数据集的棒图的边框画笔 | |
| 数据点 | void append() | 添加一个数据到数据集 |
| void insert() | 在某个位置插入一个数据到数据集 | |
| void remove() | 从某个位置开始删除一定数量的数据 | |
| void replace() | 替换某个位置的数据 | |
| qreal at() | 返回某个位置的数据 | |
| int count() | 返回数据的个数 | |
| qreal sum() | 返回数据集内所有数据的和 |
QBarSeries是绘制柱状图序列的类,从QAbstractBarSeries类继承而来。QBarSeries类主要实现对QBarSet数据集的操作,一个柱状图序列可以有多个数据集。QBarSeries类的主要函数功能见表9-7(包括从QAbstractBarSeries继承的函数,仅列出函数的返回数据类型,省略了输入参数)。
表9-7 QBarSeries类的主要函数功能
| 外观 | void setBarWidth() | 设置数据棒的宽度 |
| void setLabelsVisible() | 设置数据棒的标签可见性 | |
| void setLabelsFormat() | 设置数据棒的标签的格式,只支持一种:@value | |
| void setLabelsPosition() | 数据棒标签的位置,可在数据棒的中间、顶端、底端、外部 | |
| void setLabelsAngle() | 设置标签的角度 | |
| 数据集 | bool append() | 添加一个QBarSet数据集到序列 |
| bool insert() | 在某个位置插入一个QBarSet数据集到序列 | |
| bool remove() | 移除一个数据集,解除所属关系,并删除数据集对象 | |
| bool take() | 移除出一个数据集,但是不删除数据集对象 | |
| void clear() | 清除全部数据集,并删除数据集对象 | |
| QList<QBarSet *> barSets() | 返回数据集对象的列表 | |
| int count() | 返回数据集的个数 |
柱状图的横坐标一般是文字表示的类别,所以用于柱状图的横坐标类是QBarCategoryAxis,表9-8是QBarCategoryAxis的主要函数的功能(仅列出函数的返回数据类型,省略了输入参数)。
表9-8 QBarCategoryAxis主要函数功能
| 坐标内容 | void append() | 添加一个类别(category)到坐标轴 |
| void insert() | 在某个位置插入一个类别到坐标轴 | |
| void replace() | 替换某个类别 | |
| void remove() | 移除某个类别 | |
| void clear() | 删除所有类别 | |
| QString at() | 返回某个索引位置的类别文字 | |
| int count() | 返回类别的个数 | |
| void setCategories() | 设置一个QStringList字符串列表作为坐标轴的类别文字,删除原来所有类别文字 | |
| 坐标范围 | void setMin() | 设置坐标轴最小值 |
| void setMax() | 设置坐标轴最大值 | |
| void setRange() | 设置坐标轴范围 |
QBarCategoryAxis坐标轴的内容是一系列文字表示的类别,所以,其函数基本都是针对字符串的操作。
9.3.4 饼图
1.饼图的绘制
原书的图9-11是绘制饼图的页面。饼图一般用于对一个数据进行分段统计后分析显示,可以直观地表示某个数据所占的百分比。在图9-11中,绘制饼图需在上方的“分析数据”下拉列表框中选择分析数据对象,列表框中有数学、英语、语文、平均分4个数据对象可供选择,在下拉列表框中选择一个项,或单击“刷新饼图”按钮就可以刷新饼图。
饼图数据来源于分数的统计数据,即每个分数段的人数。为使每个分数段都有一定的统计人数,将mainwindow.h中的常量iniDataRowCount更改为较大值,如40。
界面上方的HoleSize可以设置饼图中心空心圆的大小,PieSize可以设置饼图的大小,“主题”下拉列表框可以为图表选择不同的配色主题。
MainWindow类中绘制饼图相关的函数有两个,iniPiewChart()用于初始化,在MainWindow的构造函数里调用,buildPieChart()用于绘制饼图。
iniPiewChart()函数用于创建QChart对象,并设置为QChartView类对象chartViewPie的显示图表,其代码与iniBarChart()类似,不再赘述。
在图9-11的“分析数据”下拉列表框的currentIndexChanged()信号的响应槽函数和“刷新饼图”按钮的响应代码里都调用buildPieChart()函数绘制饼图。buildPieChart()函数的代码如下:
void MainWindow::buildPieChart()
{ //绘制饼图
QChart *chart =ui->chartViewPie->chart(); //获取chart对象
chart->removeAllSeries();
int colNo=1+ui->cBoxCourse->currentIndex(); //获取分析对象
QPieSeries *series = new QPieSeries(); //创建饼图序列
series->setHoleSize(ui->spinHoleSize->value()); //饼图中间空心的大小
for (int i=0;i<=4;i++) //添加分块数据
{
QtreeWidgetItem* item=ui->treeWidget->topLevelItem(i);
series->append(item->text(0),item->text(colNo).toFloat());
}
QPieSlice *slice; //饼图分块
for(int i=0;i<=4;i++) //设置每个分块的标签文字
{
slice =series->slices().at(i); //获取分块
slice->setLabel(slice->label()+QString::asprintf(": %.0f人, %.1f%%",
slice->value(),slice->percentage()*100));
connect(slice, SIGNAL(hovered(bool)),
this, SLOT(on_PieSliceHighlight(bool)));
}
slice->setExploded(true); //最后一个设置为exploded
series->setLabelsVisible(true); //必须添加完slice之后再设置
chart->addSeries(series); //添加饼图序列
chart->setTitle("Piechart—-"+ui->cBoxCourse->currentText());
chart->legend()->setVisible(true); //图例
chart->legend()->setAlignment(Qt::AlignRight);
}
用于绘制饼图的序列类是QPieSeries,一般一个图表只有一个QPieSeries序列。
使用QPieSeries::append()添加一个数据,实际上就是为QPieSeries增加了一个QPieSlice对象,就是饼图的一个分块。
QPieSeries::slices()返回已经添加的饼图分块,是一个QPieSlice类型的列表。获取某个分块slice后,用slice->setLabel()函数设置其标签内容,slice->value()是数据块存储的数值,slice->percentage()是数据块在饼图中所占的百分比,是0到1之间的数。
另外,为每个分块对象slice的hovered(bool)信号关联了一个槽函数on_PieSliceHighlight()。hovered(bool)信号在鼠标移到和移出对象上时发射,此槽函数用以实现某个数据块的动态弹出效果。
下面是on_PieSliceHighlight()槽函数的代码:
void MainWindow::on_PieSliceHighlight(bool show)
{ //鼠标移入、移出时发射hovered()信号,动态设置exploded效果
QPieSlice* slice=(QPieSlice *)sender();
slice->setExploded(show);
}
函数的输入参数show表示hovered()信号发射时,鼠标是移入还是移出。show为true表示鼠标移入,否则为移出。代码首先通过sender()获取对象slice,然后调用其setExploded()函数实现效果。若setExploded(true),则数据块在饼图中是弹出的,如原书的图9-11中的“90分以上”这个数据块就是处于弹出的状态;若setExploded(false),则数据块就是正常的状态。
注意:饼图没有坐标轴,所以无需设置坐标轴。
2.饼图相关的主要类
饼图没有坐标轴,创建饼图主要涉及两个类。
-
QPieSeries:饼图序列,一个图表一般只有一个饼图序列。
-
QPieSlice:一个饼图的分块,一个饼图由多个分块组成。
QPieSeries类是饼图序列,主要功能是对数据块的操作和饼图外观的设置,主要的函数功能见表9-9(仅列出函数的返回数据类型,省略了输入参数)。
表9-9 QPieSeries类的主要函数功能
| 分块 | ||
| 操作 | bool append() | 添加一个分块到饼图 |
| bool insert() | 在某个位置插入一个分块 | |
| bool remove() | 移除并删除一个分块 | |
| bool take() | 移除一个分块,但是并不删除数据块对象 | |
| void clear() | 清除序列所有的分块 | |
| QList<QPieSlice *> slices() | 返回序列的所有分块的列表 | |
| int count() | 返回序列分块的个数 | |
| bool isEmpty() | 如果序列是空的,返回true,否则返回false | |
| qreal sum() | 返回序列各分块的数值的和 | |
| 外观 | void setHoleSize() | 设置饼图中心的空心圆的大小,在0至1之间 |
| void setPieSize() | 设置饼图占图表矩形区的相对大小,0是最小,1是最大 | |
| void setLabelsVisible() | 设置分块的标签的可见性 |
QPieSlice类是饼图上的一个分块,主要用于存储分块的数据,并决定分块的显示特性。QPieSlice类的主要函数功能见表9-10(仅列出函数的返回数据类型,省略了输入参数)。
表9-10 QPieSlice类的主要函数功能
| 数据 | QPieSeries * series() | 返回分块所属的QPieSeries序列对象 |
| void setValue() | 设置分块的数值,必须是正数 | |
| qreal percentage() | 返回本数据块的值在饼图中所有数据块的值的和中所占的百分比,数值在0到1之间。当饼图的数据块变化后自动更新 | |
| 标签 | void setLabelVisible() | 设置标签的可见性 |
| void setLabel() | 设置分块的标签文字 | |
| 标签 | void setLabelBrush() | 设置标签的画刷 |
| void setLabelColor() | 设置标签的颜色 | |
| void setLabelFont() | 设置标签的字体 | |
| void setLabelPosition() | 设置标签的位置,输入参数是QPieSlice::LabelPosition枚举类型 | |
| 外观 | void setExploded() | 设置分块是否弹出,如果设置为true,分块具有弹出效果 |
| void setPen() | 设置绘制分块的边框的画笔 | |
| void setBorderColor() | 设置边框的颜色,是画笔颜色的便捷调用方式 | |
| void setBorderWidth() | 设置边框的线宽,是画笔线宽的便捷调用方式 | |
| void setBrush() | 设置绘制分块的画刷 | |
| void setColor() | 设置分块的填充颜色,是画刷颜色的便捷调用方式 |
9.3.5 堆叠柱状图
堆叠柱状图(StackBar)的绘制效果如原书的图9-12所示,它是柱状图的一种变体。图中有数学、语文、英语这3个数据集,堆叠柱状图将3个数据集叠加成一个柱子来显示,一个柱子中每个小段是一门课的分数,柱子的高度表现了总分的大小。
iniStackedBar()用于图表的初始化,其代码与iniBarChart()相似,这里不再赘述。
buildStackedBar()函数用于根据数据模型里的所有学生的3门课的分数绘制叠加柱状图,实现代码如下:
void MainWindow::buildStackedBar()
{//绘制叠加柱状图
QChart *chart =ui->chartViewStackedBar->chart(); //获取QChart对象
chart->removeAllSeries();
chart->removeAxis(chart->axisX());
chart->removeAxis(chart->axisY());
//创建三门课程的数据集
QBarSet *setMath = new QBarSet(theModel->horizontalHeaderItem(colNoMath)->text());
QBarSet *setChinese = new QBarSet(theModel->horizontalHeaderItem(colNoChinese)->text());
QBarSet *setEnglish= new QBarSet(theModel->horizontalHeaderItem(colNoEnglish)->text());
for(int i=0;i<theModel->rowCount();i++)
{ //添加分数数据到数据集
setMath->append(theModel->item(i,colNoMath)->text().toInt());
setChinese->append(theModel->item(i,colNoChinese)->text().toInt());
setEnglish->append(theModel->item(i,colNoEnglish)->text().toInt());
}
//创建 QStackedBarSeries对象并添加数据集
QStackedBarSeries *series = new QStackedBarSeries();
series->append(setMath);
series->append(setChinese);
series->append(setEnglish);
series->setLabelsVisible(true);//显示每段的标签
chart->addSeries(series); //添加序列到图表
//创建横轴
QStringList categories;
for (int i=0;i<theModel->rowCount();i++)
categories <<theModel->item(i,colNoName)->text();
QBarCategoryAxis *axisX = new QBarCategoryAxis(); //类别坐标轴,作为横轴
axisX->append(categories);
chart->setAxisX(axisX, series);
axisX->setRange(categories.at(0), categories.at(categories.count()-1));
//数值坐标轴,作为纵轴
QValueAxis *axisY = new QValueAxis; //数值坐标轴,作为纵轴
axisY->setRange(0, 300);
axisY->setTitleText("总分");
axisY->setTickCount(6);
axisY->setLabelFormat("%.0f"); //标签格式
chart->setAxisY(axisY, series);
chart->legend()->setVisible(true);
chart->legend()->setAlignment(Qt::AlignBottom);
}
创建堆叠柱状图与创建柱状图类似,都需要先创建QBarSet数据集并添加数据,然后创建QStackedBarSeries序列,将3个数据集添加到这个序列。横轴采用QBarCategoryAxis类型的坐标轴,纵轴采用QValueAxis类型的坐标轴。除了使用QStackedBarSeries类作为序列,其他用到的类与buildBarChart()函数里的都相同。
9.3.6 百分比柱状图
绘制百分比柱状图的界面如原书的图9-13所示。图中有数学、语文、英语3个数据集,3个数据集的数据来源是按分数统计的结果,3个数据集叠加显示,显示效果类似于叠加柱状图,但是每个柱子的3个数据的和是相同的,等于学生总人数,所以,所有的柱子是等高度的。显示标签时,显示的是每段所占的百分比。
为避免某个分数段统计人数为0,将学生人数设置为40人,就是修改mianwindow.h文件中定义的符号iniDataRowCount为40。
iniPercentBar()用于图表的初始化,与iniBarChart()代码相似,这里不再赘述。
buildPercentBar()函数用于根据统计数据绘制百分比柱状图,其代码如下:
void MainWindow::buildPercentBar()
{//绘制百分比柱状图
QChart *chart =ui->chartViewPercentBar->chart();
chart->removeAllSeries();
chart->removeAxis(chart->axisX());
chart->removeAxis(chart->axisY());
//创建数据集
QBarSet *setMath = new QBarSet(theModel->horizontalHeaderItem(colNoMath)->text());
QBarSet *setChinese = new QBarSet(theModel->horizontalHeaderItem(colNoChinese)->text());
QBarSet *setEnglish= new QBarSet(theModel->horizontalHeaderItem(colNoEnglish)->text());
QTreeWidgetItem *item; //节点
QStringList categories;
for (int i=0;i<=4;i++)
{//从分数段统计数据表里获取数据,添加到数据集
item=ui->treeWidget->topLevelItem(i);
categories<<item->text(0);//用作横坐标的标签
setMath->append(item->text(colNoMath).toFloat());
setChinese->append(item->text(colNoChinese).toFloat());
setEnglish->append(item->text(colNoEnglish).toFloat());
}
QPercentBarSeries *series = new QPercentBarSeries(); //序列
series->append(setMath);
series->append(setChinese);
series->append(setEnglish);
series->setLabelsVisible(true);//显示百分比
chart->addSeries(series);
QBarCategoryAxis *axisX = new QBarCategoryAxis(); //横坐标
axisX->append(categories);
chart->setAxisX(axisX, series);
axisX->setRange(categories.at(0), categories.at(categories.count()-1));
QValueAxis *axisY = new QValueAxis;//纵坐标
axisY->setRange(0, 100);
axisY->setTitleText("百分比");
axisY->setTickCount(6);
axisY->setLabelFormat("%.1f"); //标签格式
chart->setAxisY(axisY, series);
chart->legend()->setVisible(true);
chart->legend()->setAlignment(Qt::AlignBottom);
}
绘制百分比柱状图也需要先创建QBarSet数据集,这里创建了3个数据集,从统计数据表里分别获取3门课各个分数段的人数。
绘制百分比柱状图使用QPercentBarSeries类的序列,添加数据集,设置X和Y轴的坐标轴即可。若显示数据标签,显示的是某个分数段内某门课的人数的百分比。
9.3.7 散点图和光滑曲线图
在窗口绘图区的“ScatterChart”页面可以绘制散点图和光滑曲线图,绘图效果如原书的图9-14所示。
绘制散点图使用QScatterSeries序列类。它在添加数据点后,将数据点以散点的形式显示,如图9-14中的“散点”序列。可以控制散点的形状、大小和颜色等属性。
绘制光滑曲线使用QSplineSeries序列类,它在两点之间连线时采用差值算法,从而形成光滑的曲线,而绘制折线图采用的是QLineSeries序列类,两点间只是简单的用直线连接。
实例中绘制散点和光滑曲线图使用随机数据,与界面左侧的学生分数数据无关。
iniScatterChart()用于图表的初始化,与iniBarChart()的代码相似,这里不再赘述。
单击绘图页面上方的“刷新图表”按钮,将调用buildScatterChart()函数绘制散点图和spline曲线,buildScatterChart()函数的代码如下:
void MainWindow::buildScatterChart()
{ //绘制 QScatterSeries和QSplineSeries图
QChart *chart =ui->chartViewScatter->chart();
chart->removeAllSeries();
chart->removeAxis(chart->axisX());
chart->removeAxis(chart->axisY());
QSplineSeries *seriesLine = new QSplineSeries(); //光滑曲线序列
seriesLine->setName("spline");
QPen pen;
pen.setColor(Qt::blue);
pen.setWidth(2);
seriesLine->setPen(pen);
QScatterSeries *series0 = new QScatterSeries(); //散点序列
series0->setName("散点");
series0->setMarkerShape(QScatterSeries::MarkerShapeCircle);
series0->setBorderColor(Qt::black);
series0->setBrush(QBrush(Qt::red));
series0->setMarkerSize(12);
qsrand(QTime::currentTime().second());//随机数种子
for (int i=0;iappend(x,y); //散点序列
seriesLine->append(x,y); //光滑曲线序列
}
chart->addSeries(series0);
chart->addSeries(seriesLine);
chart->createDefaultAxes(); //创建缺省的坐标轴
chart->axisX()->setTitleText("X 轴");
chart->axisX()->setRange(-2,22);
chart->axisY()->setTitleText("Y 轴");
chart->axisY()->setRange(-2,22);
chart->legend()->setVisible(true);
chart->legend()->setAlignment(Qt::AlignRight);
}
函数创建了一个QSplineSeries序列对象seriesLine,一个QScatterSeries序列对象series0。
散点序列的数据点形状采用函数setMarkerShape()进行设置,传递的参数是一个QScatterSeries:: MarkerShape枚举类型的值,这个枚举类型只有两种取值:
-
QScatterSeries::MarkerShapeCircle,圆形:
-
QScatterSeries::MarkerShapeRectangle,矩形。
setBorderColor()函数用于设置数据点形状边框的颜色,setBrush()函数可以设置填充颜色,setMarkerSize()函数设置数据点形状的大小。
两个序列创建后,采用随机函数生成数据点,两个序列添加相同的数据点。
创建坐标轴时采用了QChart::createDefaultAxes()函数生成缺省的坐标轴,创建缺省的坐标轴之后,还是可以通过QChart:: axisX()或QChart:: axisY()获取图表的X坐标轴或Y坐标轴进行坐标轴的属性设置,例如设置坐标轴标题和数值范围。
9.4 图表的其他操作
9.4.1 实例功能概述
前面介绍了QChart绘图的基本功能,以及几种常见图表的绘制方法,本节介绍QChart绘图的一些高级的用法,也是经常需要实现的一些功能。实例samp9_4用于演示这些功能的实现,原书的图9-15是实例运行界面。
该实例具有如下功能:
-
鼠标在图表上移动时,在状态栏里显示鼠标当前位置的坐标数值,这个功能在一般的数据曲线显示图表中是经常需要实现的;
-
图例具有类似于QCheckBox的功能,单击图例的图标可以显示或隐藏对应的序列;
-
通过工具栏按钮可以进行图表缩放操作;
-
鼠标在图表上可以选择矩形区域进行放大,单击鼠标右键则恢复图表大小;
-
使用按键可以实现图表的快捷操作,如“+”键进行放大、“-”键进行缩小、Home键恢复原始大小,使用上、下、左、右光标键可以移动序列的显示位置等。
9.4.2 自定义QWChartView类
QChart和QChartView是基于Graphics View结构的绘图类,要对一个QChart图表进行鼠标和按键操作,需要在QChartView组件里对鼠标和按键事件进行处理,这就需要自定义一个从QChartView继承的类,这与实例samp8_5中从QGraphicsView继承一个自定义图形视图类,实现鼠标和按键操作的原理类似。
自定义一个QWChartView类,它从QChartView继承而来,对鼠标和按键事件进行处理。QWChartView类的定义如下:
class QWChartView : public QChartView
{
Q_OBJECT
private:
QPoint beginPoint; //选择矩形区的起点
QPoint endPoint; //选择矩形区的终点
protected:
void mousePressEvent(QMouseEvent *event); //鼠标左键按下
void mouseMoveEvent(QMouseEvent *event); //鼠标移动
void mouseReleaseEvent(QMouseEvent *event); //鼠标释放左键
void keyPressEvent(QKeyEvent *event); //按键事件
public:
explicit QWChartView(QWidget *parent = 0);
~QWChartView();
signals:
void mouseMovePoint(QPoint point); //鼠标移动信号
};
QWChartView类中定义了两个私有变量beginPoint和endPoint用于鼠标框选矩形区域的起点和终点。QWChartView重定义了鼠标的3个事件函数和键盘按键事件的函数,定义了一个信号mouseMovePoint(QPoint point),在mouseMoveEvent()事件里发射此信号并传递鼠标光标处的屏幕坐标,用于在主窗口里实现鼠标在图表上移动时显示当前位置的坐标。
下面是QWChartView类各个函数的实现。
QWChartView::QWChartView(QWidget *parent):QChartView(parent)
{//构造函数
this->setDragMode(QGraphicsView::RubberBandDrag);
this->setMouseTracking(true);//必须开启此功能,缺省为false
}
void QWChartView::mousePressEvent(QMouseEvent *event)
{//鼠标左键按下,记录beginPoint
if (event->button()==Qt::LeftButton)
beginPoint=event->pos();
QChartView::mousePressEvent(event);
}
void QWChartView::mouseMoveEvent(QMouseEvent *event)
{//鼠标移动事件
QPoint point=event->pos();
emit mouseMovePoint(point);
QChartView::mouseMoveEvent(event);
}
void QWChartView::mouseReleaseEvent(QMouseEvent *event)
{//鼠标左键释放事件
if (event->button()==Qt::LeftButton)
{ //鼠标左键释放,获取矩形框的endPoint,进行缩放
endPoint=event->pos();
QRectF rectF;
rectF.setTopLeft(this->beginPoint);
rectF.setBottomRight(this->endPoint);
this->chart()->zoomIn(rectF);
}
else if (event->button()==Qt::RightButton)
this->chart()->zoomReset(); //鼠标右键释放,resetZoom
QChartView::mouseReleaseEvent(event);
}
void QWChartView::keyPressEvent(QKeyEvent *event)
{//按键控制
switch (event->key()) {
case Qt::Key_Plus:
chart()->zoom(1.2); break;
case Qt::Key_Minus:
chart()->zoom(0.8); break;
case Qt::Key_Left:
chart()->scroll(10, 0); break;
case Qt::Key_Right:
chart()->scroll(-10, 0); break;
case Qt::Key_Up:
chart()->scroll(0, -10); break;
case Qt::Key_Down:
chart()->scroll(0, 10); break;
case Qt::Key_PageUp:
chart()->scroll(0, 50); break;
case Qt::Key_PageDown:
chart()->scroll(0, -50); break;
case Qt::Key_Home:
chart()->zoomReset(); break;
default:
QGraphicsView::keyPressEvent(event);
}
}
在构造函数里,setDragMode(QGraphicsView::RubberBandDrag)将视图组件鼠标拖动选择方式设置为“橡皮框”形式,即鼠标左键按下时,随着鼠标拖动显示一个矩形选择框。
setMouseTracking(true)将鼠标跟踪设置为true(缺省为false)。如果不设置为true,窗口组件只在某个鼠标按键按下时才接收鼠标移动事件,设置为true之后,只要鼠标移动就会发射mouseMoveEvent()事件。
mousePressEvent(QMouseEvent *event)是在鼠标左键或右键按下时发生的事件,在响应程序里先判断鼠标左键是否按下,如果是鼠标左键按下了,就用变量beginPoint记录鼠标在视图组件中的位置。
mouseReleaseEvent(QMouseEvent *event)是在鼠标左键或右键释放时发生的事件,若是鼠标左键释放,则用变量endPoint记录鼠标位置坐标,beginPoint和endPoint就定义了鼠标框选的矩形区域,用关联的chart组件的zoomIn()函数显示这个矩形区域实现放大。
mouseMoveEvent(QMouseEvent *event)是鼠标在图表上移动时发生的事件,通过event->pos()获取鼠标在视图组件中的坐标point,然后发射信号mouseMovePoint(point)。在使用QWChartView类组件的主窗口里,可以定义槽函数对此信号作出响应,通过传递的参数将视图坐标转换为图表的坐标,从而实现鼠标位置的数值显示。
keyPressEvent(QKeyEvent *event)是键盘按键按下时发生的事件,从event->key()可以获得按下按键的名称,判断按键,然后做出缩放、移动等操作。
QChart::zoom(qreal factor)函数对图表显示区的内容进行缩放,factor大于1是放大,小于1是缩小,缩放后坐标轴的范围会自动变化。
QChart::scroll(qreal dx, qreal dy)函数将图表内容在平面上平移,平移后坐标轴会自动变化。
QChart::zoomReset()函数将取消所有缩放变化,恢复原始的大小。
9.4.3 主窗口类的设计
创建一个Widget Application项目,主窗口继承自QMainWindow,按照原书的图9-15进行窗口可视化设计。主窗口上放置一个QGraphicsView组件后提升为QWChartView类。
主窗口类MainWindow的定义如下:
class MainWindow : public QMainWindow
{
Q_OBJECT
private:
QLabel *labXYValue; //状态栏显示文字标签
void createChart(); //创建图表
void prepareData(); //准备数据
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void on_LegendMarkerClicked(); //图例单击槽函数,自定义槽函数
void on_mouseMovePoint(QPoint point); //鼠标移动事件,自定义槽函数
void on_actZoomReset_triggered(); //工具栏按钮,原始大小
void on_actZoomIn_triggered(); //工具栏按钮,放大
void on_actZoomOut_triggered(); //工具栏按钮,缩小
private:
Ui::MainWindow *ui;
};
QWChartView类已经实现了鼠标拖动区域放大功能和按键快捷操作功能,在主窗口类MainWindow中无需再实现这些功能。主窗口需要实现的功能包括:
-
绘制图表,createChart()函数用于初始化图表组件,prepareData()函数用于为图表创建序列并添加数据。
-
定义一个槽函数on_mouseMovePoint(QPoint),与QWChartView的mouseMovePoint(QPoint)信号相关联,用于实时显示鼠标光标处的坐标值。
-
定义一个槽函数on_LegendMarkerClicked(),与图表的图例marker的clicked()信号相关联,实现单击图例时显示或隐藏相关序列。
9.4.4 实时显示光标处的数值
主窗口类MainWindow的构造函数实现如下:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->setCentralWidget(ui->chartView);
labXYValue = new QLabel("X=, Y= "); //状态栏显示鼠标点的坐标
labXYValue->setMinimumWidth(200);
ui->statusBar->addWidget(labXYValue);
createChart();//创建图表
prepareData();//生成数据
QObject::connect(ui->chartView,SIGNAL(mouseMovePoint(QPoint)),
this, SLOT(on_mouseMovePoint(QPoint))); //鼠标移动事件
}
构造函数调用createChart()创建图表,调用prepareData()为图表添加数据。最后,将窗口上的chartView组件的mouseMovePoint(QPoint)信号与自定义槽函数on_mouseMovePoint(QPoint)关联起来,用于实现鼠标在图表上移动时在状态栏里显示光标处的数值。
createChart()和prepareData()的代码不详细介绍了,它们的作用就是创建两个QLineSeries序列,显示正弦、余弦曲线,其实现与实例samp9_1类似。
槽函数on_mouseMovePoint(QPoint)的实现代码如下:
void MainWindow::on_mouseMovePoint(QPoint point)
{ //鼠标移动响应
QPointF pt=ui->chartView->chart()->mapToValue(point); //转换为图表的数值
labXYValue->setText(QString::asprintf("X=%.1f,Y=%.2f",pt.x(),pt.y()));
}
传递的参数point是chartView的坐标,用QChart:: mapToValue()将视图的坐标转换为图表的坐标,这与Graphics View里视图坐标到场景坐标的转换类似。转换后得到的坐标pt是图表的数值坐标,即显示区在两个坐标轴定义下的坐标,在状态栏上显示此数值坐标的内容,就实现了实时显示鼠标光标处的数值的功能。
9.4.5 QLegendMarker的使用
图表创建序列后会自动创建图例,QLegend有一个只读属性markers,是一个列表类型,存储了每个序列的QLegendMarker对象。markers函数的原型如下:
QList<QLegendMarker *> QLegend::markers(QAbstractSeries *series = Q_NULLPTR)
如果不指定序列,则返回图表所有序列的LegendMarker对象列表。
QLegendMarker是直接继承自QObject的类,用于在图例中对关联的序列进行操作。QLegendMarker有两个部分,一个反映了序列颜色的颜色框,一个是反映序列名称的标签。QLegendMarker类的主要函数见表9-11(省略了函数参数中的const关键字)。
表9-11 QLegendMarker类的主要函数
| void setVisible(bool visible) | 设置图例标记的可见性 |
| void setLabel(QString &label) | 设置标签,即图例中的序列的名称 |
| void setFont(QFont &font) | 设置标签的字体 |
| QAbstractSeries * series() | 返回关联的序列 |
| LegendMarkerType type() | 返回图例标记的类型,取决于关联的序列的类型,有:QLegendMarker::LegendMarkerTypeArea、QLegendMarker::LegendMarkerTypeBar、QLegendMarker::LegendMarkerTypePie、QLegendMarker::LegendMarkerTypeXY、QLegendMarker::LegendMarkerTypeBoxPlot。LegendMarkerTypeXY用于所有从QXYSeries继承的序列类 |
QLegendMarker::visible属性控制图例标记的可见性,如果设置为false,在图例中就不会显示这个图例标记。可以将QLegendMarker当作一个QCheckBox使用,单击图例中相应的LegendMarker时,显示或隐藏相关联的序列,这只需要设计一个槽函数响应QLegendMarker的clicked()信号即可。
在createChart()函数中为图表创建两个QLineSeries序列后,在最后添加如下代码:
foreach (QLegendMarker* marker, chart->legend()->markers())
connect(marker, SIGNAL(clicked()),
this, SLOT(on_LegendMarkerClicked()));
该语句通过图例的markers()获取其QLegendMarker列表,然后为每个marker的clicked()信号关联自定义的槽函数on_LegendMarkerClicked()。
下面是槽函数on_LegendMarkerClicked()的代码:
void MainWindow::on_LegendMarkerClicked()
{//单击图例的marker的响应
QLegendMarker* marker = qobject_cast<QLegendMarker*> (sender());
switch (marker->type()) //marker的类型
{ case QLegendMarker::LegendMarkerTypeXY: //QLineSeries序列
{ marker->series()->setVisible(!marker->series()->isVisible());
marker->setVisible(true);
}
}
}
这里,首先将产生信号的对象sender()转换为QLegendMarker类对象marker,然后根据marker->type()判断图例标记的类型,本实例里创建的是QLineSeries序列,所以图例标记的类型是QLegendMarker::LegendMarkerTypeXY。
通过marker->series()获取关联的序列,然后交替设置其可见性,即:
marker->series()->setVisible(!marker->series()->isVisible());
marker的可见性总是设置为true,因为若设置为false,图例标记也会被隐藏,就无法再用鼠标单击操作了。
9.4.6 图表的缩放
在QWChartView类里,对鼠标事件进行了处理,用鼠标拖动一个矩形框时,图表放大显示所选的矩形区域,而且可以连续多次放大,单击鼠标右键时,又恢复原始大小。
在QWChartView类里还定义了键盘按键操作,运行时按相应的按键就可以进行放大、缩小、移动等操作。
主窗口工具栏上的“放大”“缩小”“原始大小”按钮可以实现图表的缩放操作,3个按钮的响应代码如下:
void MainWindow::on_actZoomReset_triggered()
{ ui->chartView->chart()->zoomReset();//恢复原始大小
}
void MainWindow::on_actZoomIn_triggered()
{ ui->chartView->chart()->zoom(1.2); //放大
}
void MainWindow::on_actZoomOut_triggered()
{ ui->chartView->chart()->zoom(0.8); //缩小
}


