首先来张运行的效果截图:
在做这个绘制过程中有三个要点:
(1)坐标轴的定制:这个的关键点有两个方法:①通过继承QValue3DAxisFormatter类重新改写生成轴标生成规则(stringForValue(qreal value, const QString &format)这个虚函数),直接贴代码:
class For3DAxisValue: public QValue3DAxisFormatter
{
Q_OBJECT
public:
explicit For3DAxisValue(const QVector<long long> &vec = QVector<long long>()):m_tickersValVect(vec)
{
}
explicit For3DAxisValue(const For3DAxisValue &other)
{
m_tickersValVect = other.m_tickersValVect;
}
QString stringForValue(qreal value, const QString &format) const override
{
if(format == "X")
{
// 这里后绘制1~100轴标
if(value >=1 && value < m_tickersValVect.count())
return QDateTime::fromMSecsSinceEpoch(m_tickersValVect[value]).toString(QString::fromLatin1("HH:mm:ss zzz"));
else
return QString();
}
else if(format == "Y")
{
return QString("%1dBm").arg(QString::number(value,'f', 2));
}
else if(format == "Z")
{
return creatTickString(value, m_rangeVal);
}
else{
return QValue3DAxisFormatter::stringForValue(value, format);
}
}
void setTimeTickerVect(const QVector<long long> &vec)
{
m_tickersValVect = vec;
}
void setRangeVal(double rangeVal)
{
m_rangeVal = rangeVal;
}
QVector<long long> m_tickersValVect;
int m_oop;
private:
double m_rangeVal = 1e9;
};
下面是调用部分:
Q3DSurface *m_graph_ = new Q3DSurface;
For3DAxisValue *v3dAF = new For3DAxisValue;
m_graph_->axisY()->setFormatter(v3dAF);
m_graph_->axisZ()->setTitle(tr("频率"));
m_graph_->axisZ()->setTitleVisible(true);
m_graph_->axisZ()->setLabelFormat("Z");
m_graph_->axisZ()->setRange(0, 1e8);
m_graph_->axisZ()->setSegmentCount(20);
m_graph_->setHorizontalAspectRatio(1);
v3dAF->setRangeVal(1e8);
显而易见,这种方法简单粗暴,也更加灵活,这是我最喜欢的。②通过Q3DSurface::axisZ()->setLabelFormat(“”)这个接口设定生成轴标的表达式,由于本人不太会用lamda表达式,所以就不多赘述误人子弟了。
(2)鼠标操作:这个比较简单直接重写鼠标事件函数,直接贴代码:
class XQ3DSurface:public Q3DSurface
{
Q_OBJECT
public:
explicit XQ3DSurface()
{
connect(scene()->activeCamera(), &Q3DCamera::xRotationChanged, this, [=](float rotation){
m_vertical = rotation;
});
connect(scene()->activeCamera(), &Q3DCamera::yRotationChanged, this, [=](float rotation){
m_horizontal = rotation;
});
connect(scene()->activeCamera(), &Q3DCamera::zoomLevelChanged, this, [=](float zoomLevel){
m_zoomLevel = zoomLevel;
});
// connect(scene()->activeCamera(), &Q3DCamera::targetChanged, this, [=](const QVector3D &target){
// m_currentTargetPos3D = target;
// });
}
void keyPressEvent(QKeyEvent *event)
{
if(event->key() == Qt::Key_Control)
{
m_ctrlPressed = true;
}
else{
if(m_ctrlPressed)
{
QVector3D targt = scene()->activeCamera()->target();
if(event->key() == Qt::Key_Up)
{
targt.setY(targt.y() - 0.1);
scene()->activeCamera()->setTarget(targt);
}
else if(event->key() == Qt::Key_Down)
{
targt.setY(targt.y() + 0.1);
scene()->activeCamera()->setTarget(targt);
}
else if(event->key() == Qt::Key_Left)
{
targt.setX(targt.x() + 0.1);
scene()->activeCamera()->setTarget(targt);
}
else if(event->key() == Qt::Key_Right)
{
targt.setX(targt.x() - 0.1);
scene()->activeCamera()->setTarget(targt);
}
}
else
{
if(event->key() == Qt::Key_Up)
{
scene()->activeCamera()->setCameraPosition(m_vertical, m_horizontal - 1, m_zoomLevel);
}
else if(event->key() == Qt::Key_Down)
{
scene()->activeCamera()->setCameraPosition(m_vertical, m_horizontal + 1, m_zoomLevel);
}
else if(event->key() == Qt::Key_Left)
{
scene()->activeCamera()->setCameraPosition(m_vertical - 1, m_horizontal, m_zoomLevel);
}
else if(event->key() == Qt::Key_Right)
{
scene()->activeCamera()->setCameraPosition(m_vertical + 1, m_horizontal, m_zoomLevel);
}
}
}
Q3DSurface::keyPressEvent(event);
}
void keyReleaseEvent(QKeyEvent *event)
{
if(event->key() == Qt::Key_Control)
{
m_ctrlPressed = false;
}
Q3DSurface::keyReleaseEvent(event);
}
virtual void mouseMoveEvent(QMouseEvent *event)
{
Q3DSurface::mouseMoveEvent(event);
}
virtual void mousePressEvent(QMouseEvent *event)
{
QWindow *topLevelWidget = this->parent();
while (topLevelWidget->parent() != nullptr) {
topLevelWidget = topLevelWidget->parent();
}
QRect rct = topLevelWidget->geometry ().translated(0,0);
RECT mainWinRect ; //RECT在windef.h中被定义
mainWinRect. left = (LONG )this->geometry(). left ( ) + rct.left();
mainWinRect. right = (LONG )this->geometry(). right ( ) + rct.left() ;
mainWinRect. top = (LONG )this->geometry(). top ( ) + rct.top();
mainWinRect. bottom = (LONG )this->geometry(). bottom ( ) + rct.top() ;
ClipCursor ( &mainWinRect ) ; //这是Windows API
// qDebug()<<rct.left ( )<< " "<<rct. right ( )<< " "<<rct. top ( )<< " "<<rct. bottom ( );
// qDebug()<<this->position().x()<< " "<<this->position().y();
Q3DSurface::mousePressEvent(event);
}
virtual void mouseReleaseEvent(QMouseEvent *event)
{
ClipCursor(NULL);
Q3DSurface::mouseReleaseEvent(event);
}
private:
float m_horizontal = 0;
float m_vertical = 0;
bool m_ctrlPressed = false;
// QVector3D m_currentTargetPos3D;
float m_zoomLevel = 100;
};
本人是实现的限定鼠标拖动范围,含有ctrl+上下左右箭头时移动画面。
(3)遇到的一个小坑,在使用Q3DSurface的时候,本人的应用场景是实时绘制服务端传回来的数据,起初我是想直接想通过Q3DSurface::dataProxy()->resetArray(data)进行实时切换数据,但在应用中惊奇的发现有个bug,在频繁的数据切换中QSurface3DSeries内部每次都会对之前的数据进行清理析构,但是莫名其妙的会清理到野指针,当然这也可能是本人对数据清理工作逻辑没做好导致。于是本人进行了以下操作:
// 在下一次数据进入时进行数据清除
if(m_p3DwaterfallPlot1)
{
// 手动清空数据列表
m_p3DwaterfallPlot1->clear();
// 将数据设置为空
m_series_->dataProxy()->resetArray(nullptr);
}
// 记录导入数据指针地址
m_p3DwaterfallPlot1 = plot;
// 填充新数据
m_series_->dataProxy()->resetArray(plot);
尽管进行了手动清理,再填入数据,但依然在resetArray(plot)步骤中出现访问野指针的情况,于是笔者想了第二种办法,就是每次数据的填充都填入新的QSurface3DSeries中,再将数据代理移交给Q3DSurface,如下。
// 在下一次数据进入时进行数据清除
if(m_p3DwaterfallPlot1)
{
// 手动清空数据列表
m_p3DwaterfallPlot1->clear();
// 将数据设置为空
m_series_->dataProxy()->resetArray(nullptr);
}
// 记录导入数据指针地址
m_p3DwaterfallPlot1 = plot;
m_graph_->removeSeries(m_series_);
delete m_series_;
// 重新载入数据
m_series_ = new QSurface3DSeries();
m_series_->dataProxy()->resetArray(plot);
m_graph_->addSeries(m_series_);
这样确实解决了问题,但是性能却下降明显,后续在看看还有没有更好的方式解决,或者有如果你有方法能解决,希望指点一二。
许久没有更新博客了,一直在忙碌,最近想起了一路的经历总得记录下来好回首自身成长的过程,于是就想起了这个每次遇到问题比点开的优快云,希望以后能坚持更新下去。