1.三维柱状图
1.1 实例说明
通过一个三维柱状图的实例来说明使用 Data Visualization 的类绘制三维图形的基本原理。实例如图:
使用Q3DBars绘制一个三维柱状图,并在界面上对其一些常见属性和操作进行控制,效果如图1.1显示:
图 1.1
右侧是用Q3DBars绘制的三维柱状图;左侧是对三维柱状图的一些操作和属性的设置,包括预设视角的选择,水平、垂直方向的旋转以及缩放,属性设置包括图表主题、选择方式、棒图项的样式,以及一些显示效果的设置。
无需额外编程和设置,程序运行时在图表单击左键可以选择图表中的某个项、某个列或行(与选择方式的设置有关),按住鼠标右键时上下、左右移动可以进行水平和垂直方向的旋转,鼠标滚动可以进行缩放。
1.2 主窗口设计
改例是主窗口继承自QMainWindow的应用程序,可视化创建主窗体。
由于三维图形类Q3DBars、Q3DScatter、Q3DSurface都是从QWindow继承而来的,上一篇已经说过,不能简单使用QWidget组件作为Q3DBars组件的容器,也就是不能在主窗口上放置一个QWidget组件,然后作为Q3DBars组件的容器,而是使用QWidget::createWindowContaier()动态创建QWidget作为Q3DBars的容器。
在UI设计器中设计主窗体的界面如下图1.2所示,只设计用于三维图表属性控制的界面组件,用一个QGroupBox组件容纳所有这些三维属性控制界面组件,将这个QGroupBox组件命名为groupBox。为groupBox内的组件设计好布局,删除主窗口的工具栏、菜单栏和状态栏。窗口右侧区域是空白,是为动态创建Q3DBars的QWidget容器预留的空间。
主窗口类的头文件mainwindow.h中的定义如下:
#include <QMainWindow>
#include <QtDataVisualization>
using namespace QtDataVisualization;
class MainWindow : public QMainWindow
{
Q_OBJECT
private:
QWidget *graphContainer; //图表的容器
Q3DBars *graph3D; //图表
QBar3DSeries *series; //图表的序列
void iniGraph3D(); //创建图表
public:
explcit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
};
图 1.2
在MainWindow类中,定义了私有变量graphContainer作为三维图表的容器,它在MainWindow构造函数中创建。还定义三维图表和序列的私有变量graph3D和series,是为了在程序里便于直接引用。
iniGraph3D()函数用于初始化创建图表,在构造函数里调用。
1.2 三维柱状图的创建
在主窗口的构造函数里创建三维图表,MainWindow的构造函数代码如下:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
iniGraph3D(); // 创建三维图表
QSplitter *splitter = new QSplitter(Qt::Horizontal);
splitter->addWidget(ui->groupBox); //左侧控制面板
splitter->addWidget(graphContainer); //右侧图表
this->setCentralWidget(splitter);
}
构造函数里首先调用iniGraph3D()创建三维图标及其容器graphContainer,然后创建一个分割条组件splitter,将可视化设计的控制面板groupBox与动态创建的图标容器graphContainer添加到分割条里,作为主窗口的中心组件。
这样在运行时就获得如下图 1 的界面,界面具体制动填充效果,并且可以分割左侧的控制面板和右侧的图表。
iniGraph3D()函数创建图表,其实现代码如下:
void MainWindow::iniGraph3D()
{//创建图表
graph3D = new Q3DBars();
graphContainer = QWidget::createWindowContainer(graph3D);
//创建坐标轴
QStringList rowLabs;
QStringList colLabs;
rowLabs << "row1" << "row2" << "row3";
colLabs << "col1" << "col2" << "col3" << "col4" << "col5";
QValue3DAxis *axisV = new QValue3DAxis; //数值坐标轴
axisV->setTitle("value");
axisV->setTitleVisible(true);
QCategory3DAxis *axisRow = new QCategory3DAxis; //行坐标轴
axisRow->setTitle("row axis");
axisRow->setLabels(rowLabs);
axisRow->setTitleVisible(true);
QCategory3DAxis *axisCol = new QCategory3DAxis; //列坐标轴
axisCol->setTitle("column axis");
axisCol->setLabels(colLabs);
axisCol->setTitleVisible(true);
graph3D->setValueAxis(axisV);
graph3D->setRowAxis(axisRow);
graph3D->setColumnAxis(axisCol);
//创建序列
series = new QBar3DSeries;
series->setMesh(QAbstract3Dseries::MeshCylinder);//棒图形状
series->setItemLabelFormat("(@rowLabel,@colLabel): %。1f");//标签格式
graph3D->addSeries(series);
//添加数据
QBarDataArray *dataSet = new QBarDataArray; //数据数组
dataSet->reserve(rowLabs.count());
QBarDataRow *dataRow = new QBarDataRow;
*dataRow << 1 << 2 << 3 << 4 << 5;//第1行数据,有5列
dataSet->append(dataRow);
QBarDataRow *dataRow2 = new QBarDataRow;
*dataRow2 << 5 << 5 << 5 << 5 << 5;//第2行数据,有5列
dataSet->append(dataRow2);
QBarDataRow *dataRow3 = new QBarDataRow;
*dataRow3 << 1 << 5 << 9 << 5 << 1;//第3行数据,有5列
dataSet->append(dataRow3);
series->dataProxy()->resetArray(dataSet);
}
程序首先创建Q3DBars类的实例graph3D,然后创建一个QWidget实例graphContainer作为其容器。创建graphContainer时使用了QWidget的静态函数createWindowContainer(),这样创建的QWidget实例才可以作为QWindow及其子类组件的容器。
三维柱状图的水平面的两个轴采用QCategory3DAxis类型的坐标轴,是文字型标签坐标轴,分别为其设置轴标题和轴标签。垂直方向表示项的数值大小,所以使用QValue3DAxis类型的坐标轴。创建坐标轴后,分别设置为柱状图行坐标轴、列坐标轴和数值坐标轴,即:
graph3D->setValueAxis(axisV);
graph3D->setRowAxis(axisRow);
grapy3D->setColumnAxis(axisCol);
与Q3DBars对应使用的序列是QBar3DSeries类,创建序列实例对象series,设置棒图性质和标签格式后添加到图表。
然后创建QBarDataArray数组dataSet,其元素个数等于三维柱状图的行数,也就是dataSet的每个元素是一个行的数据。
一个行的数据是QBarDataRow类型,例如,创建第1行的数据并添加到柱状图的数据数组里的代码如下:
QBarDataRow *dataRow = new QBarDataRow;
*dataRow << 1 << 2 << 3 << 4 << 5;//第1行数据,有5列
dataSet->append(dataRow);
实际上,QBarDataRow 和 QBarDataArray都不是类,而是类型定义,在Qt的源代码中,他们的定义如下:
typedef QVector<QBarDataItem> QBarDataRow;
typedef QList<QBarDataRow *> QBarDataArray;
所以,QBarDataRow是QBarDataItem的向量,QBarDataArray是QBarDataRow的列表。数据的基本元素是QBarDataItem,也就是柱状图中的一个项,QBarDataItem只有value和rotation两个属性,用于存储项的数值和旋转角度。
在图1.1中,柱状图的数据有3行5列,添加数据时,每次添加一行的数据,但是属于一个序列。
设置完数据后,为序列的数据代理设置数据,语句如下:
series->dataProxy()->resetArray(dataSet);
程序里没有为序列先创建QBarDataProxy类型的数据代理,然后再添加到序列里。调用series->dataProxy()使用内部缺省的数据代理,resetArray(dataSet)将清除数据代理原有的数据,并使用dataSet作为数据源。
1.3 三维柱状图属性设置
图1.1左侧的控制面板用于图表的操作和一些常用属性的设置
1:预设视角
3种三维图表的父类QAbstract3DGraph 有一个函数 scene() 可以活得图表的场景,是一个Q3DScene类。Q3DScene 是三维图的场景,包含一个相机(camera)和一个光源,还提供一个3D主视口(viewport)和两个2D副视口(subviewport),其中次级2D副视口用于显示二维切片图。
场景的相机位置就是我们看图标的视角,当旋转一个图表时,实际上就是相机位置的变化,Qt为相机提供了一些预设的视角,由枚举类型Q3DCamera::CameraPrest定义,它有二十多种取值,其中几种是:
Q3DCamera::CameraPresetFrontLow,前下方;
Q3DCamera::CameraPresetFront, 正前方;
Q3DCamera::CameraPresetFrontHigh, 前上方;
Q3DCamera::CameraPresetLeft, 左侧。
在控制面版的“预设视角”的下拉列表框中列出了所有视角取值,为currentIndexChanged()信号编写槽函数代码如下:
void MainWindow::on_comboCamera_currentIndexChanged(int index)
{
//变换视角
Q3DCamera::CameraPreset cameraPos = Q3DCamera::CameraPreset(index);
graph3D->scene()->activeCamera()->ssetCameraPreset(cameraPos);
}
通过graph3D->scene()->activeCamera()获取图表视图当前的相机,是一个Q3DCamera类型的对象,然后调用其setCameraPreset函数设置预设的视角。因为列表框中的选项是完全按照枚举类型Q3DCamera::CameraPreset 的取值顺序排列的,所以,只需将其序号转换为Q3DCamera::CameraPreset类型即可。
2:旋转和缩放
在程序运行时,在图表上滚动鼠标滚轮就可以缩放,按住鼠标右键上下左右移动可以对图表进行三维旋转。图表的旋转和缩放是通过改变图表场景的相机位置和缩放系数来实现的。
控制面版上有3个QSlider组件分别用于水平旋转、垂直旋转和缩放。3个QSlider组件的valueChanged(int value)信号的响应代码相同,代码如下:
void MainWindow::on_slideH_valueChanged(int value)
{
Q_UNUSED(value);
int xRot = ui->sliderH->value(); //水平
int yRot = ui->sliderV->value(); //垂直
int zoom = ui->sliderZoom()->value(); //缩放
graph3D->scene()->activeCamera->setCameraPosition(xRot,yRot,zoom);
}
Q3DCamera类的setCameraPosition()函数用于设置相机的位置和缩放系数,setCameraPosition()函数的原型定义是:
void Q3DCamera::setCameraPosition(float horizontal,float vertical,float zoom = 100.0f)
其中,horizontal是水平旋转角度,在-180°至+180°之间取值,所以sliderH的取值范围设置为-180至+180;vertical是垂直方向旋转角度,在0至90°之间取值,所以sliderV的取值范围设置为0至90;zoom是缩放系数,缺省值为100,表示无缩放,sliderZoom的取值范围设置为10至500,小于100是缩小,大于100是放大。
setCameraPosition()函数同时定义了相机的位置和缩放,所以3个QSlider组件的valueChanged(int value)信号响应代码相同。
3:主题
与QChart一样,QAbstract3DGraph类可以设置主题,主题定义了图表的各种颜色和外观设置。通过activeTheme()可以获取图表当前设置的主题,通过setActiveTheme()函数可以设置新的主题。
用于三维图表的主题类是Q3DTheme,其type()属性表示主题类型。主题类型是一个枚举类型Q3DTheme::Theme,有多种取值,与QChart的主题的枚举类型的取值类似。
控制面板上的“主题”下拉列表框中列出了所有可选的主题,为其currentIndexChanged( int index)信号槽写响应槽函数,代码如下:
void MainWindow::on_cBoxTheme_currentIndexChanged(int index)
{
//设置主题
Q3DTheme *currentTheme = graph3D->activeTheme();
currentTheme->setType(Q3DTheme::Theme(index));
}
图表的一些显示属性的设置是通过设置activeTheme()主题的属性来实现的,例如控制面板上的“显示背景”复选框的代码如下:
void MainWindow::on_chkBoxGrid_clicked(bool checked)
{
//图表的背景
graph3D->activeTheme()->setBackgroundEnabled(checked);
}
"显示背景网格"复选框的代码如下:
void MainWindow::on_chkBoxGrid_clicked(bool checked)
{
//图表的网格
graph3D->activeTheme()->setGridEnabled(checked);
}
“坐标轴标签背景可见”的复选框的代码如下:
void MainWindow::on_chkBoxAxisBackground_clicked(bool checked)
{
//轴标签背景
graph3D->activeTheme()->setLabelBackgroundEnabled(checked);
}
4:选择模式
“选择模式”指的是鼠标在图表上单机时,项被选择的模式,缺省为选择一个项。QAbstract3DGraph类的setSelectionMode()函数用于设置选择模式,选择模式是枚举类型QAbstract3DGraph::SelectionFlag,其取值常量见下表:
QAbstract3DGraph::SelectionFlag 枚举类型取值
控制面板的“选择模式”下拉列表框中列出了除SelectionMultiSeries之外的其他所有选择模式,其currentIndexChanged(int index)信号的槽函数代码如下:
void MainWindow::on_cBoxSelectionMode_currentIndexChanged(int index)
{
//选择模式
graph3D->setSelectionMode(QAbstract3DGraph::SelectionFlags(index));
}
5.序列相关的设置
Q3DBars只能显示QBar3DSeries序列,QBar3DSeries、QScatter3DSeries 和 QSurface3DSeries都继承自QAbstract3DSeries(),如图所示:
序列的设置主要是对其一些显示属性的设置,QAbstract3DSeries类的主要函数见下表(省略函数参数中的const关键字)。一个设置函数一般对应一个读取函数,如setBaseColor()用于设置序列基本颜色,对应的读取序列基本颜色的函数是baseColor(),表中仅列出设置函数。
QAbstract3DSeries类的主要函数功能
窗口控制面版中的“棒图样式”下拉列表框列出了QAbstract3DSeries::Mesh枚举类型的各种取值,用于设置棒图的样式,如:
QAbstract3DSeries::MeshBar, 棱柱;
QAbstract3DSeries::MeshCylinder, 圆柱;
QAbstract3DSeries::MeshSphere, 椭圆。
还有一些其他取值,请详细的见Qt的帮助文件。
“棒图样式”下拉列表框的currentIndexChanged(int index)信号的槽函数代码如下:
void MainWindow::on_cBoxBarStyle_currentIndexChanged(int index)
{
//棒图样式
QAbstract3DSeries::Mesh aMesh;
aMesh = QAbstract3DSeries::Mesh(index+1);
series->setMesh(aMesh);
}
"项的标签可见"复选框的响应代码如下:
void MainWindow::on_chkBoxItemLabel_clicked(bool checked)
{
//项的标签是否可见
series->setItemLabelFormat("value at (@rowLabel,@colLabel): &.0f");
series->setItemLabelVisible(checked);
}
这里使用了setItemLabelFormat()函数设置项的标签显示文字的格式,在格式设置中,可以使用一些标记符号,见下表:
QBar3DSeries的setItemLabelFormat()函数可用的标记符号
6.坐标轴反向
在下图中的三维柱状图中,垂直方向是数值坐标轴,缺省的方向是向上为正。如果实际情况需要数值坐标轴反向,可以调用QValue3DAxis::setReversed()函数将坐标轴设置为反向。控制面板上的”数值坐标轴反向“复选框的槽函数代码如下:
void MainWindow::on_chkBoxReverse_clicked(bool checked)
{
//数值轴反向
graph3D->valueAxis()->setReversed(checked);
}