三维数据可视化
- 三维柱状图
- 三维图的创建
- 程序截图
- 示例代码
- 三维散点图
- 三维图创建
- 程序截图
- 示例代码
- 三维曲面图
- 三维图创建
- 程序截图
- 示例代码
Data Visualization 是 Qt 中的一个三维数据可视化模块,可用于绘制三维柱状图、三维散点图和三维曲面。与 Charts 模块类似,Data Visualization 模块也是基于图形/视图架构的。DataVisualization 的功能虽然不能和一些专业的三维图形类库(如 VTK)的相提并论,但是它操作简单、易用,对于简单的三维数据显示是比较实用的。
三维柱状图
三维图的创建
- 创建Q3DBars对象后,必须为其创建一个QWidget对象作为容器,程序中的代码是:
graph3D = new Q3DBars();
graphContainer = QWidget::createWindowContainer(graph3D, this); //创建三维图的容器
这里使用了QWidget的静态函数createWindowContainer(),这个函数的原型定义如下:
QWidget *QWidget::createWindowContainer(QWindow *window, QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags())
window 是一个QWindow类对象,parent 是父容器对象,flags是窗口标志。函数的返回值是一个QWidget对象指针。
这个函数的功能是为 QWindow 类型的对象创建特殊的 QWidget 对象来作为容器,因为
QWindow 类型的对象不能直接放在普通的QWidget对象上。Q3DBars 的上层父类是QWindow,所以必须用这个静态函数为其创建一个容器。
- 场景和相机。Q3DBars的父类QAbstract3DGraph定义了三维图的一些基本元素和属性,包括场景、主题、选择模式等。QAbstract3DGraph 的函数scene()返回一个 Q3DScene 对象,它是三维图的场景对象。在一个三维场景里必须有相机(camera)和光源(light),创建三维图时会自动创建默认的相机和光源。
- 函数Q3DScene::activeCamera()返回场景当前的相机对象,是Q3DCamera类型。相机类似于人的眼睛,通过对相机位置的控制可以实现图形的旋转、缩放和平移。
- 函数Q3DScene::activeLight()返回场景当前的光源对象,是Q3DLight类型。
- 坐标轴。Q3DBars用3个函数分别设置3个坐标轴,这3个函数原型定义如下:
void Q3DBars::setValueAxis(QValue3DAxis *axis) //设置数值坐标轴
void Q3DBars::setRowAxis(QCategory3DAxis *axis) //设置行坐标轴
void Q3DBars::setColumnAxis(QCategory3DAxis *axis) //设置列坐标轴
函数setValueAxis()设置一个 QValue3DAxis 类型的对象作为数值坐标轴,也就是垂直方向的坐标轴。
函数setRowAxis()和 setColumnAxis()分别设置一个 QCategory3DAxis 类型的对象作为行坐标轴和列坐标轴,也就是水平方向的两个坐标轴。
- QBar3DSeries 序列。与Q3DBars 结合使用的序列类是QBar3DSeries,程序创建了序列对象series,设置棒柱形状和标签格式后添加到三维图中。QBar3DSeries的函数setItemLabelFormat()用于设置棒柱被点击后标签显示的格式,程序中的设置语句为:
series->setItemLabelFormat("(@rowLabel,@colLabel): %d");
- 数据代理。与 QBar3DSeries 配套的数据代理类是 QBarDataProxy,它用于存储和管理在QBar3DSeries 序列中显示的数据。在三维柱状图中,每一个棒柱都要用一个QBarDataItem对象表示。
QBarDataProxy 在添加、插入或删除棒柱时是以行为单位管理的。一行的棒柱数据用一个QBarDataRow 类型管理,所有行的棒柱数据用QBarDataArray类型管理。这两个类型其实不是类,而是列表类型,在Qt C++的源代码中,它们的定义如下:
typedef QList<QBarDataItem> QBarDataRow;
typedef QList<QBarDataRow *> QBarDataArray;
QBarDataRow 就是 QBarDataItem 对象的列表,QBarDataArray 就是 QBarDataRow 对象指针列表。程序中每创建一个QBarDataItem类型的棒柱数据项dataItem,就将dataItem先添加到棒柱数据行dataRow里;创建完一行棒柱数据后,再将dataRow添加到棒柱数据数组dataArray里。
- QBarDataItem 代码
#ifndef QBARDATAITEM_H
#define QBARDATAITEM_H#include <QtDataVisualization/qdatavisualizationglobal.h>QT_BEGIN_NAMESPACE_DATAVISUALIZATIONclass QBarDataItemPrivate;class QT_DATAVISUALIZATION_EXPORT QBarDataItem
{
public:QBarDataItem();QBarDataItem(float value);QBarDataItem(float value, float angle);QBarDataItem(const QBarDataItem &other);~QBarDataItem();QBarDataItem &operator=(const QBarDataItem &other);inline void setValue(float val) { m_value = val; }inline float value() const { return m_value; }inline void setRotation(float angle) { m_angle = angle; }inline float rotation() const { return m_angle; }protected:void createExtraData();QBarDataItemPrivate *d_ptr;private:float m_value;float m_angle;
};QT_END_NAMESPACE_DATAVISUALIZATION#endif
QBar3DSeries 序列被创建后,就会自动创建数据代理,QBar3DSeries::dataProxy()返回当前的QBarDataProxy 数据代理对象。QBarDataProxy 有接口函数用于设置行坐标标签、列坐标标签和棒柱数据数组,这3个函数的原型定义如下:
void QBarDataProxy::setRowLabels(const QStringList &labels) //设置行坐标标签
void QBarDataProxy::setColumnLabels(const QStringList &labels) //设置列坐标标签
void QBarDataProxy::resetArray(QBarDataArray *newArray) //设置棒柱数据数组
在数据代理里设置的行坐标标签与每一行数据是对应的,如果删除了某一行的数据,这个行坐标标签也会被删除。在QBarDataProxy里无法删除一列棒柱数据项或单独删除某个棒柱数据项。
- QBar3DSeries 的 selectedBarChanged()信号。在三维柱状图上选择的当前棒柱变化时,序列对象会发射selectedBarChanged()信号,其函数原型定义如下:
void QBar3DSeries::selectedBarChanged(const QPoint &position)
参数position 表示当前选中棒柱的坐标,position.x()表示棒柱的行号,position.y()表示列号。 为此信号关联自定义槽函数do_barSelected()后,当点击一个棒柱时会在状态栏里显示其信息。有棒柱被选中时,通过QBarDataProxy 的itemAt()函数可以获取这个棒柱关联的QBarDataItem 对象,再通过函数QBarDataItem::value()可以获取棒柱的数值
程序截图
示例代码
- h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>#include <QtDataVisualization>
#include "q3dbars.h"
#include "qbar3dseries.h"using namespace QtDataVisualization;QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTprivate:QWidget *graphContainer; //三维图的容器Q3DBars *graph3D; //三维图QBar3DSeries *series; //序列void iniGraph3D(); //创建三维图public:MainWindow(QWidget *parent = nullptr);~MainWindow();private slots:void do_barSelected(const QPoint &position);//void on_chkBoxGrid_clicked(bool checked);void on_chkBoxSmooth_clicked(bool checked);void on_chkBoxReflection_clicked(bool checked);void on_chkBoxAxisTitle_clicked(bool checked);void on_chkBoxAxisBackground_clicked(bool checked);void on_chkBoxReverse_clicked(bool checked);void on_chkBoxBackground_clicked(bool checked);void on_spinFontSize_valueChanged(int arg1);void on_cBoxTheme_currentIndexChanged(int index);// void on_btnBarColor_clicked();void on_chkBoxItemLabel_clicked(bool checked);void on_cBoxBarStyle_currentIndexChanged(int index);void on_cBoxSelectionMode_currentIndexChanged(int index);void on_comboCamera_currentIndexChanged(int index);void on_sliderH_valueChanged(int value);void on_sliderV_valueChanged(int value);void on_sliderZoom_valueChanged(int value);void on_actBar_ChangeValue_triggered();void on_actSeries_BaseColor_triggered();void on_actData_Add_triggered();void on_actData_Insert_triggered();void on_actData_Delete_triggered();void on_btnMoveDown_clicked();void on_btnMoveUp_clicked();void on_btnMoveLeft_clicked();void on_btnMoveRight_clicked();void on_btnResetCamera_clicked();void on_actRedraw_triggered();private:Ui::MainWindow *ui;
};#endif // MAINWINDOW_H
- cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"#include <QSplitter>#include <QColorDialog>
#include <QInputDialog>
#include <QVector3D>void MainWindow::iniGraph3D()
{graph3D = new Q3DBars();graphContainer = QWidget::createWindowContainer(graph3D, this); //Q3DBars继承自QWindowgraph3D->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFrontHigh); //设置视角//创建坐标轴QValue3DAxis *axisV=new QValue3DAxis; //数值坐标axisV->setTitle("销量");axisV->setTitleVisible(true);axisV->setLabelFormat("%d");graph3D->setValueAxis(axisV); //设置数值坐标轴QCategory3DAxis *axisRow=new QCategory3DAxis;axisRow->setTitle("row axis");// QStringList rowLabs; //行坐标标签// rowLabs << "Week1" << "Week2"<<"Week3";// axisRow->setLabels(rowLabs);axisRow->setTitleVisible(true);graph3D->setRowAxis(axisRow); //设置行坐标轴QCategory3DAxis *axisCol=new QCategory3DAxis;axisCol->setTitle("column axis");// QStringList colLabs; //列坐标标签// colLabs << "Mon" << "Tue" << "Wed" << "Thur"<<"Fri";// axisCol->setLabels(colLabs);axisCol->setTitleVisible(true);graph3D->setColumnAxis(axisCol); //设置列坐标轴//创建序列series = new QBar3DSeries;series->setMesh(QAbstract3DSeries::MeshCylinder); //棒柱形状series->setItemLabelFormat("(@rowLabel,@colLabel): %d"); //项的标签显示格式graph3D->addSeries(series);//设置数据代理的数据QBarDataArray *dataArray = new QBarDataArray; //棒柱数据数组,typedef QList<QBarDataRow *> QBarDataArray;for (int i=0; i<3; i++) //行{QBarDataRow *dataRow= new QBarDataRow; //棒柱数据行, typedef QList<QBarDataItem> QBarDataRow;for(int j=1; j<=5; j++) //列{quint32 value=QRandomGenerator::global()->bounded(5,15); //随机整数[5,15)QBarDataItem *dataItem= new QBarDataItem; //创建棒柱数据对象dataItem->setValue(value); //设置棒柱的数据dataRow->append(*dataItem); //添加到棒柱数据行}dataArray->append(dataRow); //棒柱数据数组添加一个棒柱数据行}QStringList rowLabs; //行坐标标签rowLabs << "Week1" << "Week2"<<"Week3";series->dataProxy()->setRowLabels(rowLabs); //设置数据代理的行标签QStringList colLabs; //列坐标标签colLabs << "Mon" << "Tue" << "Wed" << "Thur"<<"Fri";series->dataProxy()->setColumnLabels(colLabs);//设置数据代理的列标签series->dataProxy()->resetArray(dataArray); //重设数据代理的数据connect(series,&QBar3DSeries::selectedBarChanged,this,&MainWindow::do_barSelected);
}//void MainWindow::resetData()
//{
// QBarDataProxy *dataProxy=new QBarDataProxy; //新建数据代理
// int rowCount= series->dataProxy()->rowCount(); //数据代理的行数
// for (int i=0; i<rowCount; i++) //行
// {
// QBarDataRow *dataRow= new QBarDataRow; //typedef QList<QBarDataItem> QBarDataRow;
// for(int j=1; j<=5; j++) //列
// {
// quint32 value=QRandomGenerator::global()->bounded(5,15);
// QBarDataItem *dataItem= new QBarDataItem; //数据项
// dataItem->setValue(value);
// dataRow->append(*dataItem);
// }
// QString rowStr=QString("第%1周").arg(i+1);
// dataProxy->addRow(dataRow, rowStr); //添加行数据和标签
// }// QStringList colLabs=series->dataProxy()->columnLabels(); //原来的列坐标轴标签
// dataProxy->setColumnLabels(colLabs);// series->dataProxy()->resetArray(); //清除数据代理和坐标轴标签,必须清除,否则不能重新设置坐标轴标签
// series->setDataProxy(dataProxy); //重新设置数据代理,会删除之前的数据代理
//}//void MainWindow::redrawGraph3D()
//{
// QBarDataProxy *dataProxy=series->dataProxy();
// int rowCount=dataProxy->rowCount();
// int colCount=dataProxy->columnLabels().count();
// for (int i=0; i<rowCount; i++) //行
// {
// for(int j=0; j<colCount; j++) //列
// {
// QBarDataItem dataItem= *(dataProxy->itemAt(i,j));
// quint32 value=QRandomGenerator::global()->bounded(5,15);
// dataItem.setValue(value);
// dataProxy->setItem(i,j,dataItem);
// }
// }
//}MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);ui->sliderZoom->setRange(10,500); //设置slider的数据范围ui->sliderH->setRange(-180,180); //水平旋转角度范围ui->sliderV->setRange(-180,180); //垂直旋转角度范围iniGraph3D(); //创建三维柱状图,会创建一个容器对象graphContainerQSplitter *splitter=new QSplitter(Qt::Horizontal);splitter->addWidget(ui->frameSetup); //左侧控制面板splitter->addWidget(graphContainer); //右侧三维图this->setCentralWidget(splitter); //设置主窗口中心组建
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::do_barSelected(const QPoint &position)
{if (position.x()<0 || position.y()<0) //必须加此判断{ui->actBar_ChangeValue->setEnabled(false);return;}ui->actBar_ChangeValue->setEnabled(true);const QBarDataItem *bar =series->dataProxy()->itemAt(position);QString info=QString::asprintf("选中的棒柱,Row=%d, Column=%d, Value=%.1f",position.x(),position.y(),bar->value());ui->statusBar->showMessage(info);
}void MainWindow::on_chkBoxGrid_clicked(bool checked)
{ //显示背景网格 CheckBoxgraph3D->activeTheme()->setGridEnabled(checked);
}void MainWindow::on_chkBoxSmooth_clicked(bool checked)
{ //光滑效果 CheckBoxseries->setMeshSmooth(checked);
}void MainWindow::on_chkBoxReflection_clicked(bool checked)
{//显示倒影 CheckBoxgraph3D->setReflection(checked);
}void MainWindow::on_chkBoxAxisTitle_clicked(bool checked)
{//显示轴标题 CheckBoxgraph3D->valueAxis()->setTitleVisible(checked);graph3D->rowAxis()->setTitleVisible(checked);graph3D->columnAxis()->setTitleVisible(checked);
}void MainWindow::on_chkBoxAxisBackground_clicked(bool checked)
{//显示轴标签背景 CheckBoxgraph3D->activeTheme()->setLabelBackgroundEnabled(checked);
}void MainWindow::on_chkBoxReverse_clicked(bool checked)
{//数值坐标轴反向 CheckBoxgraph3D->valueAxis()->setReversed(checked);
}void MainWindow::on_chkBoxBackground_clicked(bool checked)
{//显示背景 CheckBoxgraph3D->activeTheme()->setBackgroundEnabled(checked);
}void MainWindow::on_spinFontSize_valueChanged(int arg1)
{//轴标签字体大小QFont font = graph3D->activeTheme()->font();font.setPointSize(arg1);graph3D->activeTheme()->setFont(font);
}void MainWindow::on_cBoxTheme_currentIndexChanged(int index)
{//三维图主题Q3DTheme *currentTheme = graph3D->activeTheme();currentTheme->setType(Q3DTheme::Theme(index));
}void MainWindow::on_chkBoxItemLabel_clicked(bool checked)
{ //显示选中棒柱的标签 CheckBoxseries->setItemLabelFormat("value at (@rowLabel,@colLabel): %.1f");series->setItemLabelVisible(checked);
}void MainWindow::on_cBoxBarStyle_currentIndexChanged(int index)
{ //棒柱样式 ComboBoxQAbstract3DSeries::Mesh aMesh;aMesh=QAbstract3DSeries::Mesh(index+1); //0=MeshUserDefinedseries->setMesh(aMesh);
}void MainWindow::on_cBoxSelectionMode_currentIndexChanged(int index)
{//选择模式graph3D->setSelectionMode(QAbstract3DGraph::SelectionFlags(index));
}void MainWindow::on_comboCamera_currentIndexChanged(int index)
{ //预设视角Q3DCamera::CameraPreset cameraPos=Q3DCamera::CameraPreset(index);graph3D->scene()->activeCamera()->setCameraPreset(cameraPos);
}void MainWindow::on_sliderH_valueChanged(int value)
{//水平旋转graph3D->scene()->activeCamera()->setXRotation(value);
}void MainWindow::on_sliderV_valueChanged(int value)
{//垂直旋转graph3D->scene()->activeCamera()->setYRotation(value);
}void MainWindow::on_sliderZoom_valueChanged(int value)
{//缩放graph3D->scene()->activeCamera()->setZoomLevel(value);
}void MainWindow::on_actBar_ChangeValue_triggered()
{QPoint position=series->selectedBar();if (position.x()<0 || position.y()<0) //必须加此判断return;QBarDataItem bar=*(series->dataProxy()->itemAt(position));qreal value=bar.value(); //原来的值bool ok;value = QInputDialog::getInt(this, "输入数值","更改棒柱数值" , value, 0, 50, 1, &ok);if (ok){bar.setValue(value);series->dataProxy()->setItem(position, bar);}
}void MainWindow::on_actSeries_BaseColor_triggered()
{//设置序列基本颜色QColor color=series->baseColor();color=QColorDialog::getColor(color);if (color.isValid())series->setBaseColor(color);
}void MainWindow::on_actData_Add_triggered()
{//添加行QString rowLabel = QInputDialog::getText(this, "输入字符串","请输入行标签");if (rowLabel.isEmpty())return;QBarDataRow *dataRow= new QBarDataRow; //棒柱数据行for(int j=1; j<=5; j++) //固定5列{quint32 value=QRandomGenerator::global()->bounded(15,25);QBarDataItem *dataItem= new QBarDataItem;dataItem->setValue(value);dataRow->append(*dataItem);}series->dataProxy()->addRow(dataRow, rowLabel); //添加棒柱数据行和标签
}void MainWindow::on_actData_Insert_triggered()
{ //插入行QString rowLabel = QInputDialog::getText(this, "输入字符串","请输入行标签");if (rowLabel.isEmpty())return;QPoint position=series->selectedBar();int index=position.x(); //当前行号if (index<0)index=0;QBarDataRow *dataRow= new QBarDataRow; //棒柱数据行for(int j=1; j<=5; j++) //固定5列{quint32 value=QRandomGenerator::global()->bounded(20,30);QBarDataItem *dataItem= new QBarDataItem;dataItem->setValue(value);dataRow->append(*dataItem);}series->dataProxy()->insertRow(index, dataRow, rowLabel); //插入棒柱数据行和标签
}//删除行
void MainWindow::on_actData_Delete_triggered()
{QPoint position=series->selectedBar();if (position.x()<0 || position.y()<0)return;int rowIndex=position.x(); //当前行号int removeCount=1; //删除的行数int removeLabels=true; //是否删除行标签series->dataProxy()->removeRows(rowIndex, removeCount, removeLabels);
}void MainWindow::on_btnMoveDown_clicked()
{//下移QVector3D target3D=graph3D->scene()->activeCamera()->target();qreal z=target3D.z();target3D.setZ(z+0.1);graph3D->scene()->activeCamera()->setTarget(target3D);
}void MainWindow::on_btnMoveUp_clicked()
{//上移QVector3D target3D=graph3D->scene()->activeCamera()->target();qreal z=target3D.z();target3D.setZ(z-0.1);graph3D->scene()->activeCamera()->setTarget(target3D);
}void MainWindow::on_btnMoveLeft_clicked()
{//左移QVector3D target3D=graph3D->scene()->activeCamera()->target();qreal x=target3D.x();target3D.setX(x+0.1);graph3D->scene()->activeCamera()->setTarget(target3D);
}void MainWindow::on_btnMoveRight_clicked()
{//右移QVector3D target3D=graph3D->scene()->activeCamera()->target();qreal x=target3D.x();target3D.setX(x-0.1);graph3D->scene()->activeCamera()->setTarget(target3D);
}// 复位到FrontHigh视角
void MainWindow::on_btnResetCamera_clicked()
{graph3D->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFrontHigh);
}void MainWindow::on_actRedraw_triggered()
{QBarDataProxy *dataProxy=new QBarDataProxy; //新建数据代理int rowCount= series->dataProxy()->rowCount(); //数据代理的行数for (int i=0; i<rowCount; i++) //行{QBarDataRow *dataRow= new QBarDataRow; //棒柱数据行for(int j=1; j<=5; j++) //列{quint32 value=QRandomGenerator::global()->bounded(5,15);QBarDataItem *dataItem= new QBarDataItem; //数据项dataItem->setValue(value);dataRow->append(*dataItem);}QString rowStr=QString("第%1周").arg(i+1);dataProxy->addRow(dataRow, rowStr); //添加行数据和标签}QStringList colLabs=series->dataProxy()->columnLabels(); //原来的列坐标轴标签dataProxy->setColumnLabels(colLabs);series->dataProxy()->resetArray(); //清除数据代理和坐标轴标签,必须清除,否则不能重新设置坐标轴标签series->setDataProxy(dataProxy); //重新设置数据代理,会删除之前的数据代理
}
三维散点图
三维图创建
数据代理类 QScatterDataProxy。与三维散点图序列 QScatter3DSeries 配套的数据代理类是QScatterDataProxy,它存储和管理的基本元素是QScatterDataItem对象。序列中每个散点是一个QScatterDataItem 对象,它存储了空间点的三维坐标和旋转角度。
QScatterDataProxy 的函数 resetArray()用于重设数据内容,其函数原型定义如下:
void QScatterDataProxy::resetArray(QScatterDataArray *newArray)
参数newArray 是 QScatterDataArray 类型指针,而 QScatterDataArray 就是 QScatterDataItem对象的列表,Qt源程序中对其的定义如下:
typedef QList<QScatterDataItem> QScatterDataArray;
所以,QScatterDataProxy 存储的就是一系列QScatterDataItem 散点对象。在本示例中,“墨西哥草帽”图的数据点在水平面上是均匀分布的,相当于在水平面上做了网格划分,每个网格里面都有一个数据点。但是散点图并不要求数据点规则分布,只是根据每个散点的三维坐标和旋转方向绘图。
程序截图
示例代码
- .h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>#include <QtDataVisualization>
using namespace QtDataVisualization;QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTprivate:QWidget *graphContainer; //图表的容器Q3DScatter *graph3D; //散点图QScatter3DSeries *series; //散点序列void iniGraph3D(); //初始化绘图public:MainWindow(QWidget *parent = nullptr);~MainWindow();private slots:void do_itemSelected(int index); //与 series 的selectedItemChanged信号关联void on_chkBoxGrid_clicked(bool checked);void on_chkBoxSmooth_clicked(bool checked);void on_chkBoxAxisTitle_clicked(bool checked);void on_chkBoxAxisBackground_clicked(bool checked);void on_chkBoxReverse_clicked(bool checked);void on_chkBoxBackground_clicked(bool checked);void on_spinFontSize_valueChanged(int arg1);void on_cBoxTheme_currentIndexChanged(int index);// void on_btnBarColor_clicked();void on_chkBoxItemLabel_clicked(bool checked);void on_cBoxBarStyle_currentIndexChanged(int index);void on_cBoxSelectionMode_currentIndexChanged(int index);void on_spinItemSize_valueChanged(double arg1);void on_chkBoxShadow_clicked(bool checked);// void on_comboBox_currentIndexChanged(int index);void on_sliderH_valueChanged(int value);void on_sliderV_valueChanged(int value);// void on_btnUp_clicked();void on_sliderZoom_valueChanged(int value);void on_comboCamera_currentIndexChanged(int index);void on_actSeries_BaseColor_triggered();void on_actPoint_ChangeValue_triggered();void on_actData_Delete_triggered();void on_actData_Add_triggered();void on_btnResetCamera_clicked();void on_btnMoveLeft_clicked();void on_btnMoveRight_clicked();void on_btnMoveUp_clicked();void on_btnMoveDown_clicked();private:Ui::MainWindow *ui;
};#endif // MAINWINDOW_H
- .cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"#include <QSplitter>
#include <QInputDialog>
#include <QColorDialog>
#include <QMessageBox>void MainWindow::iniGraph3D()
{graph3D = new Q3DScatter();graphContainer = QWidget::createWindowContainer(graph3D,this); //Q3DBars继承自QWindow,必须如此创建QScatterDataProxy *proxy = new QScatterDataProxy(); //数据代理series = new QScatter3DSeries(proxy); //创建序列series->setItemLabelFormat("(@xLabel, @zLabel, @yLabel)"); //散点标签的格式series->setMeshSmooth(true);series->setBaseColor(Qt::yellow);graph3D->addSeries(series);//使用内置的坐标轴graph3D->axisX()->setTitle("axis X");graph3D->axisX()->setLabelFormat("%.2f");graph3D->axisX()->setTitleVisible(true);graph3D->axisY()->setTitle("axis Y");graph3D->axisY()->setLabelFormat("%.2f");graph3D->axisY()->setTitleVisible(true);graph3D->axisZ()->setTitle("axis Z");graph3D->axisZ()->setLabelFormat("%.2f");graph3D->axisZ()->setTitleVisible(true);graph3D->activeTheme()->setLabelBackgroundEnabled(false); //不显示轴标签背景series->setMesh(QAbstract3DSeries::MeshSphere); //设置散点形状series->setItemSize(0.2); //设置散点大小,默认值为0,取值范围 0~1int N=41;int itemCount=N*N;QScatterDataArray *dataArray = new QScatterDataArray();dataArray->resize(itemCount); //设置数组大小QScatterDataItem *ptrToDataArray = &dataArray->first(); //首地址//墨西哥草帽,-10:0.5:10, N=41float x,y,z;x=-10;for (int i=1 ; i <=N; i++){y=-10;for ( int j =1; j <=N; j++){z=qSqrt(x*x+y*y);if (z!=0)z=10*qSin(z)/z;elsez=10;ptrToDataArray->setPosition(QVector3D(x,z,y));ptrToDataArray++;y+=0.5;}x+=0.5;}series->dataProxy()->resetArray(dataArray);connect(series, &QScatter3DSeries::selectedItemChanged,this, &MainWindow::do_itemSelected);
}MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);ui->sliderZoom->setRange(10,500); //设置slider的数据范围ui->sliderH->setRange(-180,180);ui->sliderV->setRange(-180,180);iniGraph3D(); //创建图表QSplitter *splitter=new QSplitter(Qt::Horizontal);splitter->addWidget(ui->frameSetup);splitter->addWidget(graphContainer);this->setCentralWidget(splitter);
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::do_itemSelected(int index)
{ui->actPoint_ChangeValue->setEnabled(index>=0);ui->actData_Delete->setEnabled(index>=0);if(index>=0){QScatterDataItem item= *(series->dataProxy()->itemAt(index));QString str=QString::asprintf("选中点的坐标(x,z,y)=(%.2f, %.2f, %.2f)",item.x(), item.z(), item.y());ui->statusBar->showMessage(str);}
}void MainWindow::on_chkBoxGrid_clicked(bool checked)
{ //图表的网格graph3D->activeTheme()->setGridEnabled(checked);
}void MainWindow::on_chkBoxSmooth_clicked(bool checked)
{ //光滑效果 CheckBoxseries->setMeshSmooth(checked);
}void MainWindow::on_chkBoxAxisTitle_clicked(bool checked)
{//轴标题graph3D->axisX()->setTitleVisible(checked);graph3D->axisY()->setTitleVisible(checked);graph3D->axisZ()->setTitleVisible(checked);
}void MainWindow::on_chkBoxAxisBackground_clicked(bool checked)
{//轴标题背景graph3D->activeTheme()->setLabelBackgroundEnabled(checked);
}void MainWindow::on_chkBoxReverse_clicked(bool checked)
{//Z轴反向graph3D->axisY()->setReversed(checked);
}void MainWindow::on_chkBoxBackground_clicked(bool checked)
{//图表的背景graph3D->activeTheme()->setBackgroundEnabled(checked);
}void MainWindow::on_spinFontSize_valueChanged(int arg1)
{//轴标签字体大小QFont font = graph3D->activeTheme()->font();font.setPointSize(arg1);graph3D->activeTheme()->setFont(font);
}void MainWindow::on_cBoxTheme_currentIndexChanged(int index)
{//设置主题Q3DTheme *currentTheme = graph3D->activeTheme();currentTheme->setType(Q3DTheme::Theme(index));
}void MainWindow::on_chkBoxItemLabel_clicked(bool checked)
{ //显示选中散点的标签// series->setItemLabelFormat("value at (@rowLabel,@colLabel): %.1f");series->setItemLabelVisible(checked);
}void MainWindow::on_cBoxBarStyle_currentIndexChanged(int index)
{ //散点形状 ComboBoxQAbstract3DSeries::Mesh aMesh;aMesh=QAbstract3DSeries::Mesh(index+1);series->setMesh(aMesh);
}void MainWindow::on_cBoxSelectionMode_currentIndexChanged(int index)
{//选择模式graph3D->setSelectionMode(QAbstract3DGraph::SelectionFlags(index));
}void MainWindow::on_spinItemSize_valueChanged(double arg1)
{//散点大小series->setItemSize(arg1);//default 0. value 0~1
}void MainWindow::on_chkBoxShadow_clicked(bool checked)
{//显示阴影 CheckBoxif (checked)graph3D->setShadowQuality(QAbstract3DGraph::ShadowQualityMedium);elsegraph3D->setShadowQuality(QAbstract3DGraph::ShadowQualityNone);
}void MainWindow::on_sliderH_valueChanged(int value)
{//水平旋转graph3D->scene()->activeCamera()->setXRotation(value);
}void MainWindow::on_sliderV_valueChanged(int value)
{//垂直旋转graph3D->scene()->activeCamera()->setYRotation(value);
}void MainWindow::on_sliderZoom_valueChanged(int value)
{//缩放graph3D->scene()->activeCamera()->setZoomLevel(value);
}void MainWindow::on_comboCamera_currentIndexChanged(int index)
{//预设视角Q3DCamera::CameraPreset cameraPos=Q3DCamera::CameraPreset(index);graph3D->scene()->activeCamera()->setCameraPreset(cameraPos);
}void MainWindow::on_actSeries_BaseColor_triggered()
{QColor color=series->baseColor();color=QColorDialog::getColor(color);if (color.isValid())series->setBaseColor(color);
}void MainWindow::on_actPoint_ChangeValue_triggered()
{//修改当前散点的坐标int index=series->selectedItem(); //当前选中点的序号if (index<0)return;QScatterDataItem item =*(series->dataProxy()->itemAt(index));QString coord=QString::asprintf("%.2f, %.2f, %.2f",item.x(),item.z(),item.y());bool ok=false;QString newText = QInputDialog::getText(this,"修改点坐标","按格式输入点的坐标(x,z,y)",QLineEdit::Normal,coord,&ok);if (!ok)return;newText =newText.simplified(); //去除前后和中间的空格QStringList xzy=newText.split(QLatin1Char(','),Qt::SkipEmptyParts); //按逗号分割if(xzy.size() != 3){QMessageBox::critical(this,"错误","输入坐标数据格式错误");return;}item.setX(xzy[0].toFloat()); //设置散点坐标item.setZ(xzy[1].toFloat());item.setY(xzy[2].toFloat());series->dataProxy()->setItem(index,item); //重新设置散点数据项
}void MainWindow::on_actData_Delete_triggered()
{//删除当前点int index=series->selectedItem(); //当前选中点的序号if (index<0)return;int removeCount=1; //删除点个数series->dataProxy()->removeItems(index,removeCount);
}void MainWindow::on_actData_Add_triggered()
{//添加随机点int x=QRandomGenerator::global()->bounded(-10,10);int z=QRandomGenerator::global()->bounded(-10,10);int y=QRandomGenerator::global()->bounded(5,10);QScatterDataItem item;item.setX(x);item.setY(y);item.setZ(z);series->dataProxy()->addItem(item);
}void MainWindow::on_btnResetCamera_clicked()
{//复位到FrontHigh视角graph3D->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFrontHigh);
}void MainWindow::on_btnMoveLeft_clicked()
{//左移QVector3D target3D=graph3D->scene()->activeCamera()->target();qreal x=target3D.x();target3D.setX(x+0.1);graph3D->scene()->activeCamera()->setTarget(target3D);
}void MainWindow::on_btnMoveRight_clicked()
{//右移QVector3D target3D=graph3D->scene()->activeCamera()->target();qreal x=target3D.x();target3D.setX(x-0.1);graph3D->scene()->activeCamera()->setTarget(target3D);
}void MainWindow::on_btnMoveUp_clicked()
{//上移QVector3D target3D=graph3D->scene()->activeCamera()->target();qreal z=target3D.z();target3D.setZ(z-0.1);graph3D->scene()->activeCamera()->setTarget(target3D);
}void MainWindow::on_btnMoveDown_clicked()
{//下移QVector3D target3D=graph3D->scene()->activeCamera()->target();qreal z=target3D.z();target3D.setZ(z+0.1);graph3D->scene()->activeCamera()->setTarget(target3D);
}
三维曲面图
三维图创建
绘制三维曲面图需要使用Q3DSurface图形类和QSurface3DSeries序列类,根据使用的数据代理类不同,可以绘制两种三维曲面图。
- QHeightMapSurfaceDataProxy 数据代理类,根据一张图片的数据绘制三维曲面,典型的如三维地形图
- QSurfaceDataProxy 数据代理类,根据空间点的三维坐标绘制曲面,例如一般的三维函数曲面
三维曲面实际上是由三维空间中的点确定的,曲面的基本数据是空间中的点坐标数据,然后由图形类的底层将这些点连线、划分为基本的三角形,再渲染成曲面。在确定三维曲面的数据点时,一般有两个轴的坐标是均匀划分的。 与三维曲面序列QSurface3DSeries配套的数据代理类是QSurfaceDataProxy,它用二维索引管理数据,与三维柱状图的数据代理类QBarDataProxy的管理方式类似。
空间中每个点是一个QSurfaceDataItem对象,我们称之为点数据项,它存储了点的空间坐标。QSurfaceDataRow 是 QSurfaceDataItem 对象的列表,它存储了一行点的数据;QSurfaceDataArray 是QSurfaceDataRow 对象指针的列表,它按行存储了所有点的数据。它们在Qt源代码中的定义如下:
typedef QList<QSurfaceDataItem> QSurfaceDataRow;
typedef QList<QSurfaceDataRow *> QSurfaceDataArray;
程序截图
示例代码
- .h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>#include <QtDataVisualization>
using namespace QtDataVisualization;
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTprivate:QWidget *graphContainer; //图表的容器Q3DSurface *graph3D; //三维图表QSurface3DSeries *series; //序列// QSurfaceDataProxy *dataProxy; //数据代理void iniGraph3D(); //初始化绘制三维图表public:MainWindow(QWidget *parent = nullptr);~MainWindow();private slots:void do_pointSelected(const QPoint &position);void on_chkBoxGrid_clicked(bool checked);void on_chkBoxSmooth_clicked(bool checked);void on_chkBoxReflection_clicked(bool checked);void on_chkBoxAxisTitle_clicked(bool checked);void on_chkBoxAxisBackground_clicked(bool checked);void on_chkBoxReverse_clicked(bool checked);void on_chkBoxBackground_clicked(bool checked);void on_spinFontSize_valueChanged(int arg1);void on_cBoxTheme_currentIndexChanged(int index);// void on_btnBarColor_clicked();void on_chkBoxItemLabel_clicked(bool checked);void on_cBoxBarStyle_currentIndexChanged(int index);void on_cBoxSelectionMode_currentIndexChanged(int index);// void on_spinItemSize_valueChanged(double arg1);void on_chkBoxShadow_clicked(bool checked);// void on_comboBox_currentIndexChanged(int index);void on_sliderH_valueChanged(int value);void on_sliderV_valueChanged(int value);void on_comboDrawMode_currentIndexChanged(int index);// void on_btnGrad1_clicked();// void on_btnGrad2_clicked();// void on_pushButton_clicked();// void on_pushButton_clicked();void on_sliderZoom_valueChanged(int value);void on_comboCamera_currentIndexChanged(int index);void on_chkBoxFlatShading_clicked(bool checked);void on_actSurf_Color_triggered();void on_actSurf_GradColor1_triggered();void on_actSurf_GradColor2_triggered();// void on_actSurf_Texture_triggered();void on_actPoint_Modify_triggered();void on_actPoint_DeleteRow_triggered();// void on_cBoxSelectionMode_2_currentIndexChanged(int index);void on_btnResetCamera_clicked();void on_btnMoveUp_clicked();void on_btnMoveLeft_clicked();void on_btnMoveRight_clicked();void on_btnMoveDown_clicked();private:Ui::MainWindow *ui;
};#endif // MAINWINDOW_H
- .cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"#include <QSplitter>#include <QColorDialog>
#include <QFileDialog>
#include <QInputDialog>
#include <QMessageBox>void MainWindow::iniGraph3D()
{graph3D = new Q3DSurface();graphContainer = QWidget::createWindowContainer(graph3D,this); //Q3DBars继承自QWindow,必须如此创建//创建坐标轴QValue3DAxis *axisX=new QValue3DAxis;axisX->setTitle("Axis X");axisX->setTitleVisible(true);axisX->setLabelFormat("%.2f");axisX->setRange(-11,11);// axisX->setAutoAdjustRange(true);graph3D->setAxisX(axisX);QValue3DAxis *axisY=new QValue3DAxis;axisY->setTitle("Axis Y");axisY->setTitleVisible(true);axisY->setLabelFormat("%.2f");// axisY->setRange(-10,10);axisY->setAutoAdjustRange(true); //自动调整范围graph3D->setAxisY(axisY);QValue3DAxis *axisZ=new QValue3DAxis;axisZ->setTitle("Axis Z");axisZ->setTitleVisible(true);axisZ->setLabelFormat("%.2f");axisZ->setRange(-11,11);// axisZ->setAutoAdjustRange(true);graph3D->setAxisZ(axisZ);//创建数据代理QSurfaceDataProxy *dataProxy = new QSurfaceDataProxy();series = new QSurface3DSeries(dataProxy);series->setItemLabelFormat("(@xLabel,@zLabel,@yLabel)");series->setMeshSmooth(true);series->setBaseColor(Qt::cyan);series->setDrawMode(QSurface3DSeries::DrawSurfaceAndWireframe);series->setFlatShadingEnabled(false);graph3D->addSeries(series);graph3D->activeTheme()->setLabelBackgroundEnabled(false);//创建数据, 墨西哥草帽int N=41; //-10:0.5:10, N个数据点QSurfaceDataArray *dataArray = new QSurfaceDataArray; //数组dataArray->reserve(N);float x=-10,y,z;for (int i =1 ; i <=N; i++){QSurfaceDataRow *newRow = new QSurfaceDataRow(N); //一行的数据y=-10;int index=0;for (int j = 1; j <=N; j++){z=qSqrt(x*x+y*y);if (z!=0)z=10*qSin(z)/z;elsez=10;(*newRow)[index++].setPosition(QVector3D(x, z, y));y += 0.5;}x += 0.5;// *dataArray << newRow;dataArray->append(newRow); //添加一行数据}dataProxy->resetArray(dataArray);connect(series, &QSurface3DSeries::selectedPointChanged,this, &MainWindow::do_pointSelected);
}MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);ui->sliderZoom->setRange(10,500); //设置slider的数据范围ui->sliderH->setRange(-180,180);ui->sliderV->setRange(-180,180);iniGraph3D();QSplitter *splitter=new QSplitter(Qt::Horizontal);splitter->addWidget(ui->frameSetup);splitter->addWidget(graphContainer);this->setCentralWidget(splitter);
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::do_pointSelected(const QPoint &position)
{if((position.x()<0) || (position.y()<0)){ui->actPoint_Modify->setEnabled(false);ui->actPoint_DeleteRow->setEnabled(false);return;}ui->actPoint_Modify->setEnabled(true);ui->actPoint_DeleteRow->setEnabled(true);QSurfaceDataItem item= *(series->dataProxy()->itemAt(position));QString str=QString::asprintf("选中点的坐标(x,z,y)=(%.2f, %.2f, %.2f)",item.x(), item.z(), item.y());ui->statusBar->showMessage(str);
}void MainWindow::on_chkBoxGrid_clicked(bool checked)
{ //显示背景网格graph3D->activeTheme()->setGridEnabled(checked);
}void MainWindow::on_chkBoxSmooth_clicked(bool checked)
{ //单点光滑效果 CheckBoxseries->setMeshSmooth(checked);
}void MainWindow::on_chkBoxReflection_clicked(bool checked)
{//反射graph3D->setReflection(checked);
}void MainWindow::on_chkBoxAxisTitle_clicked(bool checked)
{//显示轴标题graph3D->axisX()->setTitleVisible(checked);graph3D->axisY()->setTitleVisible(checked);graph3D->axisZ()->setTitleVisible(checked);
}void MainWindow::on_chkBoxAxisBackground_clicked(bool checked)
{//轴标题背景graph3D->activeTheme()->setLabelBackgroundEnabled(checked);
}void MainWindow::on_chkBoxReverse_clicked(bool checked)
{//Y轴(垂直)反向graph3D->axisY()->setReversed(checked);
}void MainWindow::on_chkBoxBackground_clicked(bool checked)
{//图表的背景graph3D->activeTheme()->setBackgroundEnabled(checked);
}void MainWindow::on_spinFontSize_valueChanged(int arg1)
{//轴标签字体大小QFont font = graph3D->activeTheme()->font();font.setPointSize(arg1);graph3D->activeTheme()->setFont(font);
}void MainWindow::on_cBoxTheme_currentIndexChanged(int index)
{//设置主题Q3DTheme *currentTheme = graph3D->activeTheme();currentTheme->setType(Q3DTheme::Theme(index));
}void MainWindow::on_chkBoxItemLabel_clicked(bool checked)
{ //显示单点标签 CheckBoxseries->setItemLabelVisible(checked);
}void MainWindow::on_cBoxBarStyle_currentIndexChanged(int index)
{ //单点形状 ComboBoxQAbstract3DSeries::Mesh aMesh;aMesh=QAbstract3DSeries::Mesh(index+1);series->setMesh(aMesh);
}void MainWindow::on_cBoxSelectionMode_currentIndexChanged(int index)
{//选择模式switch(index){case 0:graph3D->setSelectionMode(QAbstract3DGraph::SelectionNone);break;case 1:graph3D->setSelectionMode(QAbstract3DGraph::SelectionItem);break;case 2:graph3D->setSelectionMode(QAbstract3DGraph::SelectionItemAndRow| QAbstract3DGraph::SelectionSlice);break;case 3:graph3D->setSelectionMode(QAbstract3DGraph::SelectionItemAndColumn| QAbstract3DGraph::SelectionSlice);default:break;}
}void MainWindow::on_chkBoxShadow_clicked(bool checked)
{//显示阴影if (checked)graph3D->setShadowQuality(QAbstract3DGraph::ShadowQualityMedium);elsegraph3D->setShadowQuality(QAbstract3DGraph::ShadowQualityNone);
}void MainWindow::on_sliderH_valueChanged(int value)
{//水平旋转graph3D->scene()->activeCamera()->setXRotation(value);
}void MainWindow::on_sliderV_valueChanged(int value)
{//垂直旋转graph3D->scene()->activeCamera()->setYRotation(value);
}void MainWindow::on_sliderZoom_valueChanged(int value)
{//缩放graph3D->scene()->activeCamera()->setZoomLevel(value);
}void MainWindow::on_comboDrawMode_currentIndexChanged(int index)
{//曲面样式 ComboBoxif (index==0)series->setDrawMode(QSurface3DSeries::DrawWireframe);else if (index==1)series->setDrawMode(QSurface3DSeries::DrawSurface);elseseries->setDrawMode(QSurface3DSeries::DrawSurfaceAndWireframe);
}void MainWindow::on_comboCamera_currentIndexChanged(int index)
{graph3D->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPreset(index));
}void MainWindow::on_chkBoxFlatShading_clicked(bool checked)
{//平面着色 CheckBoxseries->setFlatShadingEnabled(checked);
}void MainWindow::on_actSurf_Color_triggered()
{//设置曲面颜色QColor color=series->baseColor();color=QColorDialog::getColor(color);if (color.isValid()){series->setBaseColor(color);series->setColorStyle(Q3DTheme::ColorStyleUniform); //单一颜色}
}void MainWindow::on_actSurf_GradColor1_triggered()
{//渐变颜色1QLinearGradient gr;gr.setColorAt(0.0, Qt::blue);// gr.setColorAt(0.33, Qt::blue);// gr.setColorAt(0.67, Qt::red);gr.setColorAt(1.0, Qt::yellow);// gr.setColorAt(0.0, Qt::black);// gr.setColorAt(0.33, Qt::blue);// gr.setColorAt(0.67, Qt::red);// gr.setColorAt(1.0, Qt::yellow);series->setBaseGradient(gr);series->setColorStyle(Q3DTheme::ColorStyleRangeGradient);
}void MainWindow::on_actSurf_GradColor2_triggered()
{//渐变颜色2QLinearGradient grGtoR;// grGtoR.setColorAt(0.5, Qt::yellow);// grGtoR.setColorAt(0.2, Qt::red);grGtoR.setColorAt(0.0, Qt::cyan);grGtoR.setColorAt(1.0, Qt::red);// grGtoR.setColorAt(1.0, Qt::darkGreen);// grGtoR.setColorAt(0.5, Qt::yellow);// grGtoR.setColorAt(0.2, Qt::red);// grGtoR.setColorAt(0.0, Qt::darkRed);series->setBaseGradient(grGtoR);series->setColorStyle(Q3DTheme::ColorStyleRangeGradient);
}void MainWindow::on_actPoint_Modify_triggered()
{//修改点坐标QPoint point=series->selectedPoint();if ((point.x()<0) || point.y()<0)return;QSurfaceDataItem item =*(series->dataProxy()->itemAt(point));QString coord=QString::asprintf("%.2f, %.2f, %.2f",item.x(),item.z(),item.y());bool ok=false;QString newText = QInputDialog::getText(this,"修改点坐标","按格式输入点的坐标(x,z,y)",QLineEdit::Normal,coord,&ok);if (!ok)return;newText =newText.simplified(); //去除前后和中间的空格QStringList xzy=newText.split(QLatin1Char(','),Qt::SkipEmptyParts); //按逗号分割if(xzy.size() != 3){QMessageBox::critical(this,"错误","输入坐标数据格式错误");return;}item.setX(xzy[0].toFloat());item.setZ(xzy[1].toFloat());item.setY(xzy[2].toFloat());series->dataProxy()->setItem(point,item);
}void MainWindow::on_actPoint_DeleteRow_triggered()
{//删除行QPoint point=series->selectedPoint();if ((point.x()<0) || point.y()<0)return;int removeCount=1; //删除行的个数series->dataProxy()->removeRows(point.x(),removeCount);
}void MainWindow::on_btnResetCamera_clicked()
{//复位到FrontHigh视角graph3D->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFrontHigh);
}void MainWindow::on_btnMoveUp_clicked()
{//上移QVector3D target3D=graph3D->scene()->activeCamera()->target();qreal z=target3D.z();target3D.setZ(z-0.1);graph3D->scene()->activeCamera()->setTarget(target3D);
}void MainWindow::on_btnMoveLeft_clicked()
{//左移QVector3D target3D=graph3D->scene()->activeCamera()->target();qreal x=target3D.x();target3D.setX(x+0.1);graph3D->scene()->activeCamera()->setTarget(target3D);
}void MainWindow::on_btnMoveRight_clicked()
{//右移QVector3D target3D=graph3D->scene()->activeCamera()->target();qreal x=target3D.x();target3D.setX(x-0.1);graph3D->scene()->activeCamera()->setTarget(target3D);
}void MainWindow::on_btnMoveDown_clicked()
{//下移QVector3D target3D=graph3D->scene()->activeCamera()->target();qreal z=target3D.z();target3D.setZ(z+0.1);graph3D->scene()->activeCamera()->setTarget(target3D);
}