关于Q3DSurface绘制3D瀑布图的一点点心得记录

首先来张运行的效果截图:
3D瀑布图
在做这个绘制过程中有三个要点:
(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_);

这样确实解决了问题,但是性能却下降明显,后续在看看还有没有更好的方式解决,或者有如果你有方法能解决,希望指点一二。

许久没有更新博客了,一直在忙碌,最近想起了一路的经历总得记录下来好回首自身成长的过程,于是就想起了这个每次遇到问题比点开的优快云,希望以后能坚持更新下去。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值