Qt开发技术:Q3D图表开发笔记(三):Q3DSurface三维曲面图介绍、Demo以及代码详解
前言
qt提供了q3d进行三维开发,虽然这个框架没有得到大量运用也不是那么成功,性能上也有很大的欠缺,但是普通的点到为止的应用展示还是可以的。
其中就包括华丽绚烂的三维图表,数据量不大的时候是可以使用的。
前面介绍了基础的q3d散点图、柱状图,本篇介绍基础的三维曲面图。
Demo:Q3DSurface散点图演示效果



Q3D提供的三维图表
依赖QtDataVisualization。在安装qt的时候要选择安装QtDataVisualization模块。
Q3DScatter散点图
Q3D的散点图,性能大约支撑1000个点可以不卡顿,具体依赖pc,1000个点是什么 概念,可以理解为:10x10x10的区域,每个区域一个数据点。

Q3DBars柱状图
Q3D的柱状图,性能跟散点图类似。

Q3DSurface平面凹凸图,平面纹理图,平面曲线图
Q3D的柱状图,性能跟散点图类似。

Q3DSurface平面曲线图
简介
Q3DSurface类提供了渲染3D曲面图的方法。该类使开发人员能够渲染3D表面图,并通过自由旋转场景来查看它们。可以通过QSurface3DSeries控制曲面的视觉财产,例如绘制模式和着色。
Q3DSurface通过在用户用鼠标左键点击的数据点上显示高亮显示的球(当使用默认输入处理程序时)或通过QSurface3DSeries进行选择来支持选择。选择指针附带一个标签,在默认情况下,该标签显示数据点的值和点的坐标。
轴上显示的值范围和标签格式可以通过QValue3DAxis进行控制。
要旋转图形,请按住鼠标右键并移动鼠标。缩放是使用鼠标滚轮完成的。两者都假设默认的输入处理程序正在使用中。
如果没有将任何轴明确设置为Q3DSurface,则会创建不带标签的临时默认轴。这些默认轴可以通过轴访问器进行修改,但只要明确设置了方向的任何轴,该方向的默认轴就会被破坏。
构造最小Q3D平面曲线图
首先,构造Q3D曲面。由于在本例中,我们将图形作为顶级窗口运行,因此需要清除Qt::FramelessWindowHint标志,该标志在默认情况下设置:
Q3DSurface surface;
surface.setFlags(surface.flags() ^ Qt::FramelessWindowHint);
现在Q3DSurface已准备好接收要渲染的数据。创建数据元素以接收值:
QSurfaceDataArray *data = new QSurfaceDataArray;
QSurfaceDataRow *dataRow1 = new QSurfaceDataRow;
QSurfaceDataRow *dataRow2 = new QSurfaceDataRow;
首先将数据喂给行元素,然后将它们的指针添加到数据元素:
*dataRow1 << QVector3D(0.0f, 0.1f, 0.5f) << QVector3D(1.0f, 0.5f, 0.5f);
*dataRow2 << QVector3D(0.0f, 1.8f, 1.0f) << QVector3D(1.0f, 1.2f, 1.0f);
*data << dataRow1 << dataRow2;、
创建新系列并为其设置数据:
QSurface3DSeries *series = new QSurface3DSeries;
series->dataProxy()->resetArray(data);
surface.addSeries(series);
最后,设置为可见:
surface.show();
创建和显示此图所需的完整代码为:
#include <QtDataVisualization>
using namespace QtDataVisualization;
int main(int argc, char **argv)
{
QGuiApplication app(argc, argv);
Q3DSurface surface;
surface.setFlags(surface.flags() ^ Qt::FramelessWindowHint);
QSurfaceDataArray *data = new QSurfaceDataArray;
QSurfaceDataRow *dataRow1 = new QSurfaceDataRow;
QSurfaceDataRow *dataRow2 = new QSurfaceDataRow;
*dataRow1 << QVector3D(0.0f, 0.1f, 0.5f) << QVector3D(1.0f, 0.5f, 0.5f);
*dataRow2 << QVector3D(0.0f, 1.8f, 1.0f) << QVector3D(1.0f, 1.2f, 1.0f);
*data << dataRow1 << dataRow2;
QSurface3DSeries *series = new QSurface3DSeries;
series->dataProxy()->resetArray(data);
surface.addSeries(series);
surface.show();
return app.exec();
}
运行效果:

场景可以被旋转、放大,并且可以选择一个项目来查看其位置,但在这个最小的代码示例中不包括其他交互。
Q3Ddemo构建流程解析
步骤一:确认安装QtDataVisualization模块
如何确认,则是在帮助文件中查看是否有Q3dscatter类。一般是安装了模块才会有对应的帮助文件。没有则重新安装qt或者单独安装该模块。

步骤二:工程配置文件中加入模块
Q3d是在数据可视化模块中,需要在pro或者pri配置文件中添加。
QT += datavisualization

步骤三:添加使用到的头文件
使用到Q3DBar相关类中添加头文件,主要使用到Q3DBar、QBar3DSeries、QBarDataRow等等。
#include <Q3DBars>
#include <Q3DTheme>
#include <QBar3DSeries>
#include <QVector3D>

步骤四:添加命名空间
这时候还是无法使用对应的类,需要添加命名空间才行:
using namespace QtDataVisualization;

步骤五:Q3D的图标基础构建框架
下面是包含注释的Q3DSurface基础构建流程(注意轴的显示,查看末尾“入坑一”,注意数据的成面规则,查看“入坑二”
_pQ3DSurface = new Q3DSurface();
_pContainer = QWidget::createWindowContainer(_pQ3DSurface, this);
// 设置轴文本
{
// 注意笛卡尔坐标
_pQ3DSurface->axisX()->setTitle("经度(°)");
_pQ3DSurface->axisX()->setTitleVisible(true);
_pQ3DSurface->axisY()->setTitle("高度(m)");
_pQ3DSurface->axisY()->setTitleVisible(true);
_pQ3DSurface->axisZ()->setTitle("纬度(°)");
_pQ3DSurface->axisZ()->setTitleVisible(true);
}
// 设置轴范围
{
// 注意笛卡尔坐标
_pQ3DSurface->axisX()->setRange(0, 359);
_pQ3DSurface->axisY()->setRange(0, 100);
_pQ3DSurface->axisZ()->setRange(0, 359);
}
// 生成一个曲线
_pSurface3DSeries = new QSurface3DSeries(_pQ3DSurface);
// 设置渲染平滑
_pSurface3DSeries->setMeshSmooth(true);
// 设置渲染模式
// DrawWireframe : 绘制栅格
// DrawSurface : 绘制表面
// DrawSurfaceAndWireframe : 绘制栅格和图表面
_pSurface3DSeries->setDrawMode(QSurface3DSeries::DrawSurface);
// 视图添加该曲线
_pQ3DSurface->addSeries(_pSurface3DSeries);
// 设置阴影质量
_pQ3DSurface->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftLow);
// 设置视角
_pQ3DSurface->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetIsometricLeft);
// 设置子网格
_pQ3DSurface->activeTheme()->setGridEnabled(true);
#if 1
// 添加模拟数据
QSurfaceDataArray *pSurfaceDataArray = new QSurfaceDataArray;
#if 1
#if 1
// 这是 z 纬度
for(int n = 0; n < 360; n++)
{
QSurfaceDataRow *pSurfaceDataRow = new QSurfaceDataRow;
// 这是 x 经度
for(int m = 0; m < 360; m++)
{
// 注意与笛卡尔坐标进行映射
*pSurfaceDataRow << QVector3D(m, n / 7 + m / 7, n);
}
*pSurfaceDataArray << pSurfaceDataRow;
}
#else
for(int n = 0; n < 360; n++)
{
QSurfaceDataRow *pSurfaceDataRow = new QSurfaceDataRow;
// 这是 x 经度
for(int m = 0; m < 360; m++)
{
// 注意与笛卡尔坐标进行映射
*pSurfaceDataRow << QVector3D(m, qrand() % 100, n);
LOG << n << m;
}
*pSurfaceDataArray << pSurfaceDataRow;
}
#endif
#else
QSurfaceDataRow *pSurfaceDataRow1 = new QSurfaceDataRow;
QSurfaceDataRow *pSurfaceDataRow2 = new QSurfaceDataRow;
QSurfaceDataRow *pSurfaceDataRow3 = new QSurfaceDataRow;
// 行与行之间,要形成一个四点成面
*pSurfaceDataRow1 << QVector3D(0, 0, 0) << QVector3D(359, 20, 0);
*pSurfaceDataRow2 << QVector3D(50, 20, 179) << QVector3D(359, 40, 179);
*pSurfaceDataRow3 << QVector3D(100, 80, 359) << QVector3D(359, 100, 359);
*pSurfaceDataArray << pSurfaceDataRow1 << pSurfaceDataRow2 << pSurfaceDataRow3;
#endif
// 添加数据(自动冲掉之前的数据)
_pSurface3DSeries->dataProxy()->resetArray(pSurfaceDataArray);
#endif
_pQ3DSurface->addSeries(_pSurface3DSeries);
_pQ3DSurface->show();
Demo源码
Q3dSurfaceWidget.h
#ifndef Q3DSURFACEWIDGET_H
#define Q3DSURFACEWIDGET_H
#include <QWidget>
#include <Q3DSurface>
#include <Q3DTheme>
#include <QSurface3DSeries>
#include <QVector3D>
using namespace QtDataVisualization;
namespace Ui {
class Q3dSurfaceWidget;
}
class Q3dSurfaceWidget : public QWidget
{
Q_OBJECT
public:
explicit Q3dSurfaceWidget(QWidget *parent = 0);
~Q3dSurfaceWidget();
protected:
void initControl();
protected:
void resizeEvent(QResizeEvent *event);
private:
Ui::Q3dSurfaceWidget *ui;
private:
Q3DSurface *_pQ3DSurface; // q3d平面曲线图
QWidget *_pContainer; // q3d窗口容器
QSurface3DSeries *_pSurface3DSeries ; // q3d柱状图数据
};
#endif // Q3DSURFACEWIDGET_H
Q3dSurfaceWidget.cpp
#include "Q3dSurfaceWidget.h"
#include "ui_Q3dSurfaceWidget.h"
#include <Q3DTheme>
#include <QDebug>
#include <QDateTime>
//#define LOG qDebug()<<__FILE__<<__LINE__
//#define LOG qDebug()<<__FILE__<<__LINE__<<__FUNCTION__
//#define LOG qDebug()<<__FILE__<<__LINE__<<QThread()::currentThread()
//#define LOG qDebug()<<__FILE__<<__LINE__<<QDateTime::currentDateTime().toString("yyyy-MM-dd")
#define LOG qDebug()<<__FILE__<<__LINE__<<QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz")
Q3dSurfaceWidget::Q3dSurfaceWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Q3dSurfaceWidget),
_pQ3DSurface(0),
_pContainer(0),
_pSurface3DSeries(0)
{
ui->setupUi(this);
QString version = "v1.0.0";
initControl();
}
Q3dSurfaceWidget::~Q3dSurfaceWidget()
{
delete ui;
}
void Q3dSurfaceWidget::initControl()
{
_pQ3DSurface = new Q3DSurface();
_pContainer = QWidget::createWindowContainer(_pQ3DSurface, this);
// 设置轴文本
{
// 注意笛卡尔坐标
_pQ3DSurface->axisX()->setTitle("经度(°)");
_pQ3DSurface->axisX()->setTitleVisible(true);
_pQ3DSurface->axisY()->setTitle("高度(m)");
_pQ3DSurface->axisY()->setTitleVisible(true);
_pQ3DSurface->axisZ()->setTitle("纬度(°)");
_pQ3DSurface->axisZ()->setTitleVisible(true);
}
// 设置轴范围
{
// 注意笛卡尔坐标
_pQ3DSurface->axisX()->setRange(0, 359);
_pQ3DSurface->axisY()->setRange(0, 100);
_pQ3DSurface->axisZ()->setRange(0, 359);
}
// 生成一个曲线
_pSurface3DSeries = new QSurface3DSeries(_pQ3DSurface);
// 设置渲染平滑
_pSurface3DSeries->setMeshSmooth(true);
// 设置渲染模式
// DrawWireframe : 绘制栅格
// DrawSurface : 绘制表面
// DrawSurfaceAndWireframe : 绘制栅格和图表面
_pSurface3DSeries->setDrawMode(QSurface3DSeries::DrawSurface);
// 视图添加该曲线
_pQ3DSurface->addSeries(_pSurface3DSeries);
// 设置阴影质量
_pQ3DSurface->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftLow);
// 设置视角
_pQ3DSurface->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetIsometricLeft);
// 设置子网格
_pQ3DSurface->activeTheme()->setGridEnabled(true);
#if 1
// 添加模拟数据
QSurfaceDataArray *pSurfaceDataArray = new QSurfaceDataArray;
#if 1
#if 1
// 这是 z 纬度
for(int n = 0; n < 360; n++)
{
QSurfaceDataRow *pSurfaceDataRow = new QSurfaceDataRow;
// 这是 x 经度
for(int m = 0; m < 360; m++)
{
// 注意与笛卡尔坐标进行映射
*pSurfaceDataRow << QVector3D(m, n / 7 + m / 7, n);
}
*pSurfaceDataArray << pSurfaceDataRow;
}
#else
for(int n = 0; n < 360; n++)
{
QSurfaceDataRow *pSurfaceDataRow = new QSurfaceDataRow;
// 这是 x 经度
for(int m = 0; m < 360; m++)
{
// 注意与笛卡尔坐标进行映射
*pSurfaceDataRow << QVector3D(m, qrand() % 100, n);
LOG << n << m;
}
*pSurfaceDataArray << pSurfaceDataRow;
}
#endif
#else
QSurfaceDataRow *pSurfaceDataRow1 = new QSurfaceDataRow;
QSurfaceDataRow *pSurfaceDataRow2 = new QSurfaceDataRow;
QSurfaceDataRow *pSurfaceDataRow3 = new QSurfaceDataRow;
// 行与行之间,要形成一个四点成面
*pSurfaceDataRow1 << QVector3D(0, 0, 0) << QVector3D(359, 20, 0);
*pSurfaceDataRow2 << QVector3D(50, 20, 179) << QVector3D(359, 40, 179);
*pSurfaceDataRow3 << QVector3D(100, 80, 359) << QVector3D(359, 100, 359);
*pSurfaceDataArray << pSurfaceDataRow1 << pSurfaceDataRow2 << pSurfaceDataRow3;
#endif
// 添加数据(自动冲掉之前的数据)
_pSurface3DSeries->dataProxy()->resetArray(pSurfaceDataArray);
#endif
_pQ3DSurface->addSeries(_pSurface3DSeries);
_pQ3DSurface->show();
}
void Q3dSurfaceWidget::resizeEvent(QResizeEvent *event)
{
if(_pContainer)
{
_pContainer->setGeometry(rect());
}
}
工程模板v1.2.0

入坑
入坑一:xyz坐标系不对
问题
x精度,y维度,z高度(海拔高度)映射错误

原因
x,y,z实际是遵循笛卡尔坐标集
解决
先理解坐标,然后z轴方向,数据也要替换(按照x,y,z来排列,改为x,z,y)
&emso;
入坑二:曲面显示不对
问题
数据显示映射错误
原因
点成面,需要遵循4点成面的规则,和opengl相关3点成面和4点成面的原理类似。


解决
相邻行与行之间,要形成面,修改后展示如下:


Qt开发技术:Q3D图表开发笔记(三):Q3DSurface三维曲面图介绍、Demo以及代码详解的更多相关文章
- Qt开发技术:QCharts(三)QCharts样条曲线图介绍、Demo以及代码详解
若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...
- Qt开发技术:图形视图框架(二)场景QGraphicsScene、QGraphicsItem与QGraphicsView详解
前话 Qt的图形视图框架,最核心的三个类为:QGraphicsScene.QGraphicsItem与QGraphicsView. 基于图形框架的高级白板软件Demo QGraphicsSce ...
- Qwt开发笔记(二):Qwt基础框架介绍、折线图介绍、折线图Demo以及代码详解
前言 QWT开发笔记系列整理集合,这是目前使用最为广泛的Qt图表类(Qt的QWidget代码方向只有QtCharts,Qwt,QCustomPlot),使用多年,系统性的整理,本系列旨在系统解说并 ...
- FFmpeg开发笔记(五):ffmpeg解码的基本流程详解(ffmpeg3新解码api)
若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...
- Android图表库MPAndroidChart(三)——双重轴线形图的实现,这次就so easy了
Android图表库MPAndroidChart(三)--双重轴线形图的实现,这次就so easy了 在学习本课程之前我建议先把我之前的博客看完,这样对整体的流程有一个大致的了解 Android图表库 ...
- Android群英传笔记——第十二章:Android5.X 新特性详解,Material Design UI的新体验
Android群英传笔记--第十二章:Android5.X 新特性详解,Material Design UI的新体验 第十一章为什么不写,因为我很早之前就已经写过了,有需要的可以去看 Android高 ...
- [转帖]技术扫盲:新一代基于UDP的低延时网络传输层协议——QUIC详解
技术扫盲:新一代基于UDP的低延时网络传输层协议——QUIC详解 http://www.52im.net/thread-1309-1-1.html 本文来自腾讯资深研发工程师罗成的技术分享, ...
- ARM Cortex-M底层技术(2)—启动代码详解
杂谈 工作了一天,脑袋比较乱.一直想把底层的知识写成一个系列,希望可以坚持下去.为什么要写底层的东西呢?首先,工作用到了这部分内容,最近和内部Flash打交道比较多,自然而然会接触到一些底层的东西:第 ...
- Flutter学习笔记(15)--MaterialApp应用组件及routes路由详解
如需转载,请注明出处:Flutter学习笔记(15)--MaterialApp应用组件及routes路由详解 最近一段时间生病了,整天往医院跑,也没状态学东西了,现在是好了不少了,也该继续学习啦!!! ...
- FFmpeg开发笔记(四):ffmpeg解码的基本流程详解
若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...
随机推荐
- Spyglass CDC工具使用(二)
最近一直在搞CDC (clock domain crossing) 方面的事情,现在就CDC的一些知识点进行总结. 做CDC检查使用的是Spyglass工具.以下内容转载自:Spyglass检查之CD ...
- Field userService in com.lin.hms.controller.LogController required a bean of type 'org.lin.hms.service.UserService' that could not be found.
需要一个bean但找不到 解决 我们在controller使用的service没有注入spring容器,那么我们可以在启动类上,加上包扫描注解,让这个bean所在的包能扫描到: @ComponentS ...
- SQL SERVER 2014 双机热备操作流程-数据库双向同步 (第二篇:订阅)
1.登录从服务器数据库,从数据库左侧菜单栏找到->复制->本地订阅->右击新建订阅->选择查找SQL Server 发布服务器,数据库服务器名称要是主服务器计算机名称,输入登录 ...
- 【picoCTF]cookies write up
顾名思义,这一挑战涉及对cookie的简单操作.登录页面会显示一个搜索框,其中包含一个输入字段,用于检查您为其提供的 Cookie 类型. 点击链接,页面如下: 随便在框里输入内容,显示如下: 输出返 ...
- iframe.contentWindows使用
一.在使用iframe的页面,要操作这个iframe里面的DOM元素可以用: contentWindow.contentDocument(测试的时候chrome浏览器,要在服务器环境下) 1.先获取i ...
- gorm操作sqlite3,高并发读写如何避免锁库?
1. 场景 这两天一直被这个sqlit3困扰,起因是项目中需要有这样一个中间,中间件承担着API角色和流量转发的角色,需要接收来自至少300个agent的请求数据,和健康检测的请求. 所以当即想到用g ...
- 前端程序员需要了解的MySQL
数据库的基本概念 数据库(database)是用来组织.存储和管理数据的仓库.对数据库中的数据可以进行增删改查操作.市面上常见的数据库有: MySQL(使用最广泛.流行度最高的开源免费数据库 Comm ...
- .NET/C#操作Redis的简单方法
本文属于Redis初级应用,只起初步引路作用,高手们可略过. 支持.Net Core(2.0及以上)/.Net Framework(4.5及以上),可以部署在Docker, Windows, Linu ...
- 如何使用 vue + intro 实现后台管理系统的引导
引言 为了让用户更好的适应新版,或更方便使用公司内部系统,可以加入新手指引功能.如果你也想在自己的网页加入用户指引,那就试试在 vue 中使用 Intro.js 吧,它能够很轻松的制作出新手指引的效果 ...
- Mybatis应用手册
重学mybatis框架,汇成应用手册,整理mybatis中用法且附相关实例,方便日常回顾 目录结构: ----------------- Github手册地址:https://github.com/x ...