欢迎光临
我们一直在努力

【Qt】《Qt 5.9 C++开发指南》笔记-Chapter9-Qt Charts

第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); //缩小
}

赞(0)
未经允许不得转载:171主机测评 » 【Qt】《Qt 5.9 C++开发指南》笔记-Chapter9-Qt Charts
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址