当前位置: 首页 > news >正文

【Qt】 Data Visualization

三维数据可视化

    • 三维柱状图
      • 三维图的创建
      • 程序截图
      • 示例代码
    • 三维散点图
      • 三维图创建
      • 程序截图
      • 示例代码
    • 三维曲面图
      • 三维图创建
      • 程序截图
      • 示例代码

Data Visualization 是 Qt 中的一个三维数据可视化模块,可用于绘制三维柱状图、三维散点图和三维曲面。与 Charts 模块类似,Data Visualization 模块也是基于图形/视图架构的。DataVisualization 的功能虽然不能和一些专业的三维图形类库(如 VTK)的相提并论,但是它操作简单、易用,对于简单的三维数据显示是比较实用的。

三维柱状图

三维图的创建

  1. 创建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,所以必须用这个静态函数为其创建一个容器。

  1. 场景和相机。Q3DBars的父类QAbstract3DGraph定义了三维图的一些基本元素和属性,包括场景、主题、选择模式等。QAbstract3DGraph 的函数scene()返回一个 Q3DScene 对象,它是三维图的场景对象。在一个三维场景里必须有相机(camera)和光源(light),创建三维图时会自动创建默认的相机和光源。
  • 函数Q3DScene::activeCamera()返回场景当前的相机对象,是Q3DCamera类型。相机类似于人的眼睛,通过对相机位置的控制可以实现图形的旋转、缩放和平移。
  • 函数Q3DScene::activeLight()返回场景当前的光源对象,是Q3DLight类型。
  1. 坐标轴。Q3DBars用3个函数分别设置3个坐标轴,这3个函数原型定义如下:
void  Q3DBars::setValueAxis(QValue3DAxis *axis)        //设置数值坐标轴 
void  Q3DBars::setRowAxis(QCategory3DAxis *axis)       //设置行坐标轴    
void  Q3DBars::setColumnAxis(QCategory3DAxis *axis)      //设置列坐标轴 

函数setValueAxis()设置一个 QValue3DAxis 类型的对象作为数值坐标轴,也就是垂直方向的坐标轴。
函数setRowAxis()和 setColumnAxis()分别设置一个 QCategory3DAxis 类型的对象作为行坐标轴和列坐标轴,也就是水平方向的两个坐标轴。

  1. QBar3DSeries 序列。与Q3DBars 结合使用的序列类是QBar3DSeries,程序创建了序列对象series,设置棒柱形状和标签格式后添加到三维图中。QBar3DSeries的函数setItemLabelFormat()用于设置棒柱被点击后标签显示的格式,程序中的设置语句为:
series->setItemLabelFormat("(@rowLabel,@colLabel): %d"); 

  1. 数据代理。与 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_DATAVISUALIZATION

class 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里无法删除一列棒柱数据项或单独删除某个棒柱数据项。

  1. 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_NAMESPACE


class MainWindow : public QMainWindow
{
    Q_OBJECT

private:
    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继承自QWindow
    graph3D->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();   //创建三维柱状图,会创建一个容器对象graphContainer
    QSplitter   *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)
{ //显示背景网格 CheckBox
    graph3D->activeTheme()->setGridEnabled(checked);
}

void MainWindow::on_chkBoxSmooth_clicked(bool checked)
{ //光滑效果 CheckBox
    series->setMeshSmooth(checked);
}

void MainWindow::on_chkBoxReflection_clicked(bool checked)
{//显示倒影 CheckBox
    graph3D->setReflection(checked);
}

void MainWindow::on_chkBoxAxisTitle_clicked(bool checked)
{//显示轴标题 CheckBox
    graph3D->valueAxis()->setTitleVisible(checked);
    graph3D->rowAxis()->setTitleVisible(checked);
    graph3D->columnAxis()->setTitleVisible(checked);
}

void MainWindow::on_chkBoxAxisBackground_clicked(bool checked)
{//显示轴标签背景 CheckBox
    graph3D->activeTheme()->setLabelBackgroundEnabled(checked);
}

void MainWindow::on_chkBoxReverse_clicked(bool checked)
{//数值坐标轴反向 CheckBox
    graph3D->valueAxis()->setReversed(checked);
}

void MainWindow::on_chkBoxBackground_clicked(bool checked)
{//显示背景 CheckBox
    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)
{ //显示选中棒柱的标签 CheckBox
    series->setItemLabelFormat("value at (@rowLabel,@colLabel): %.1f");
    series->setItemLabelVisible(checked);
}

void MainWindow::on_cBoxBarStyle_currentIndexChanged(int index)
{ //棒柱样式 ComboBox
    QAbstract3DSeries::Mesh aMesh;
    aMesh=QAbstract3DSeries::Mesh(index+1);  //0=MeshUserDefined
    series->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_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

private:
    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~1

    int N=41;
    int itemCount=N*N;
    QScatterDataArray *dataArray = new QScatterDataArray();
    dataArray->resize(itemCount);   //设置数组大小
    QScatterDataItem *ptrToDataArray = &dataArray->first();     //首地址
    //墨西哥草帽,-10:0.5:10, N=41
    float 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;
            else
                z=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)
{ //光滑效果 CheckBox
    series->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)
{ //散点形状 ComboBox
    QAbstract3DSeries::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)
{//显示阴影 CheckBox
    if (checked)
        graph3D->setShadowQuality(QAbstract3DGraph::ShadowQualityMedium);
    else
        graph3D->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_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

private:
    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;
            else
                z=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)
{ //单点光滑效果 CheckBox
    series->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)
{ //显示单点标签 CheckBox
    series->setItemLabelVisible(checked);
}

void MainWindow::on_cBoxBarStyle_currentIndexChanged(int index)
{ //单点形状 ComboBox
    QAbstract3DSeries::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);
    else
        graph3D->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)
{//曲面样式 ComboBox
    if (index==0)
        series->setDrawMode(QSurface3DSeries::DrawWireframe);
    else if (index==1)
        series->setDrawMode(QSurface3DSeries::DrawSurface);
    else
        series->setDrawMode(QSurface3DSeries::DrawSurfaceAndWireframe);
}

void MainWindow::on_comboCamera_currentIndexChanged(int index)
{
    graph3D->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPreset(index));
}

void MainWindow::on_chkBoxFlatShading_clicked(bool checked)
{//平面着色 CheckBox
    series->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()
{//渐变颜色1
    QLinearGradient 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()
{//渐变颜色2
    QLinearGradient 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);
}


相关文章:

  • P8752 [蓝桥杯 2021 省 B2] 特殊年份——string提取索引转换为值
  • ARM系统源码编译OpenCV 4.10.0(包含opencv_contrib)
  • vue3和vue2的组件开发有什么区别
  • 3.10 企业级AI内容生成引擎:从策略到落地的全链路技术指南
  • 【大模型】Transformers基础组件 - Tokenizer
  • 2024年职高单招或高考计算机类投档线
  • Python基于Django的人脸识别上课考勤管理系统【附源码】
  • flink jobgraph详细介绍
  • Golang GORM系列:GORM并发与连接池
  • 未来游戏:当人工智能重构虚拟世界的底层逻辑
  • 【mysql】数据类型介绍-空间类型-空间索引
  • Docker换源加速(更换镜像源)详细教程(2025.2最新可用镜像,全网最详细)
  • 机械学习基础-10.从时间序列数据中学习-数据建模与机械智能课程自留
  • LabVIEW的吞雨测控系统
  • 探讨如何加快 C# 多层循环的速度效率
  • 软件测试:定义和实质
  • 观望=没有!
  • 利用websocket检测网络连接稳定性
  • MySQL 清空表的数据
  • CommonLang3-使用介绍
  • 著名连环画家庞邦本逝世
  • 比特币挖矿公司GRYP股价涨超171%:将与特朗普儿子创设的公司合并
  • 《淮水竹亭》:一手好牌,为何打成这样
  • 体坛联播|巴萨4比3打服皇马,利物浦2比2战平阿森纳
  • 韩国大选连发“五月惊奇”:在野党刚“摆脱”官司,执政党又生“内讧”
  • 退休夫妻月入1.2万负债1.2亿申请破产,律师:“诚实而不幸”系前置条件