自定义封装Qt柱状图类,实现数据动态更新和鼠标数值显示

最近有个使用Qt动态创建柱状图的需求,使用Qt中的图表类Qchart封装一个自定义柱状图类,该类需要完成三点需求:

1、支持X轴和Y轴的同时动态输入,即表中X轴条柱个数和Y轴数值动态变化;

2、X轴条柱个数不变,动态刷新Y轴对应各个条柱数值。

3、在柱状图显示时,支持鼠标悬停时显示条柱数值。

【思路】

1、考虑自定义这个类如何在主窗口中的展示,在类似使用QHBoxLayout创建一个固定的水平布局对象,然后将创建好的QChartView图表对象放置在这个布局中。当出现图表重新绘制时,将原来的图表对象从布局中移除,之后将新图表对象添加到原来的布局对象中,这样布局对象固定的只有一个图表。在主窗口上的显示,同样的创建一个水平布局的空间(通过ui设计师界面添加),在主窗帘对象的初始化过程中,将创建好了的自定义图表类对象中的布局成员添加到ui界面中,这样自定义类中图表的改变,就可实时动态显示在上面了。

2、考虑到支持X轴和Y轴的动态刷新,封装好的类提供一个接口接受数据,根据输入数据,如果发现新数据需要X轴条柱个数,则需要销毁原来图表,根据数据重新创建;如果新数据内容所需X轴条柱个数与当前图表X轴条柱个数一致,则仅仅需要更新当前图表的Y轴数据即可。这样一来,提供给外面的接口尽量的简单,将第一点和第二点需求内容的判断由接口内部判断。

3、鼠标悬停时显示条柱Y轴数值,这个涉及到鼠标事件,这个功能由主窗口实现,而鼠标悬停位置参数需要图表中的QBarSerises对象提供,因此自定义封装的图表需要同时对外更新提供QBarSeries成员,用于主窗口信号的触发。

4、平台要求:Qt Creator 4.5.1,支持Qtchart(需安装有对应的插件)

【实现】

基于上面的思路,数据更新和柱状图对象创建封装在自定义柱状图类中,自定义封装好的类主要提供接口方法和内部成员对象如下:

public:
    //清空当前柱状图Y轴数据
    void ClearData();
    //数据更新接口:内部判断是重置原柱状图还是更新Y轴数据
    void UpdateDate(std::map<QString, int> mapData);
    //数据更新接口:仅更新柱状图Y轴数据,多于X轴条柱个数不做新增处理
    void UpdateDate(std::vector<int> vecDate);

private:
    //根据输入数据创建对应柱状图
    QChartView * CreaterNewChartView(std::map<QString, int> mapData);

public:
    QChartView      *m_Chartview;
    QHBoxLayout     *m_Layout;
    QBarSeries      *m_Series;
    QBarSeries      *m_SeriesNew;

    unsigned int    m_uiCnt;        //当前柱状图X轴条柱个数
    QBarSet         *m_Barset;

数据更新接口主要有两个,一个是外部已知的不改变X轴条柱个数,仅更新Y轴数据,另一个是X轴与Y轴需同步更新。两个数据更新接口的实现如下:

//不改变当前柱状图,仅更新Y轴数据
void BarChartBuilder::UpdateDate(std::vector<int> vecDate)
{
    if(m_uiCnt == vecDate.size())
    {
        //已有图标条柱个数与新数据一致,不需要重置
        for(unsigned int idx = 0; idx < vecDate.size(); idx++)
            m_Barset->replace(idx, vecDate[idx]);
    }
}

//改变当前柱状图重新创建
void BarChartBuilder::UpdateDate(std::map<QString, int> mapData)
{
    if(0 == mapData.size())
        return;

    if(m_uiCnt == mapData.size())
    {
        std::vector<int> vecDate;
        for(std::map<QString, int>::iterator it = mapData.begin(); it != mapData.end(); it++)
        {
            vecDate.push_back(it->second);
        }
        UpdateDate(vecDate);
        return;
    }


    QChartView *pchartView = CreaterNewChartView(mapData);
    m_Layout->removeWidget(m_Chartview);
    m_Layout->addWidget(pchartView);
    m_Chartview = pchartView;
    m_Series = m_SeriesNew;

    m_uiCnt = mapData.size();
}

其中,将柱状图创建功能单独作为一个private的方法实现,柱状图的创建方法返回一个QChartView对象,提供给数据更新接口重置布局中的图表对象。需要说明的是,我这里X轴上的每个条柱仅需要支持一个数据,如果需要支持多个,可参考官方案例,仅需要多创建对应的QBarSet即可,以此类推。

QChartView * BarChartBuilder::CreaterNewChartView(std::map<QString, int> mapData)
{
    m_Barset = new QBarSet("");
    m_SeriesNew = new QBarSeries();
    m_SeriesNew->append(m_Barset);

    QChart *pChart = new QChart();
    pChart->addSeries(m_SeriesNew);
    pChart->setTitle("Simple barchart example");                // 设置图表的标题
    pChart->setAnimationOptions(QChart::SeriesAnimations);      // 动画效果

    //pseries->setLabelsPosition(QAbstractBarSeries::LabelsInsideEnd);//单条数据数值
    //pseries->setLabelsVisible(true);                                //数值可视化

    QStringList pcategories;
    int iMax = 0;
    for(std::map<QString, int>::iterator it = mapData.begin(); it != mapData.end(); it++)
    {
        m_Barset->append(it->second);
        pcategories.append(it->first);

        if(iMax < it->second)
            iMax = it->second;
    }

    QBarCategoryAxis *paxis = new QBarCategoryAxis(); //X轴
    paxis->append(pcategories);
    pChart->setAxisX(paxis, m_SeriesNew);

    QValueAxis *mAyis= new QValueAxis;      //Y轴
    pChart->addAxis(mAyis, Qt::AlignLeft);
    m_SeriesNew->attachAxis(mAyis);
    mAyis->setRange(0, iMax + 100);            //Y轴大小值
    mAyis->setLabelFormat("%u");            //Y轴数据格式

    pChart->legend()->setVisible(false);            //数据说明不可见
    //pChart->legend()->setAlignment(Qt::AlignBottom);//数值说明放底部

    QChartView *pchartView = new QChartView(pChart);
    pchartView->setRenderHint(QPainter::Antialiasing);

    return pchartView;
}

另外,为了支持柱状图数据的清空,提供ClearData接口用于清空Y轴数据,但不重置已有柱状图

void BarChartBuilder::ClearData()
{
    if(nullptr != m_Series)
    {
        m_Series->clear();
    }
}

针对鼠标在柱状图上悬停时显示对应Y轴数据的实现,需要在数据更新给自定义柱状图类对象后,将新的柱状图对象中的QBarSeries成员通过信号槽连接的方式连接到主窗口的槽函数中,这样主窗口可获知当前鼠标所处位置属于哪个条柱。鼠标悬停时数值内容的显示以标签的形式展示,显示的标签通过new出来的QLabel对象存放。为了方便管理和避免内存泄漏,主窗口用一个vector容器存放新数据更新好了之后新创建的QLabel标签对象。在每次新数据更新后,该vector容器先delete清空原来的标签对象,之后放入对应X轴条柱个数的QLabel对象。标签对象清理和新柱状图信号槽连接的实现如下:

//重置鼠标悬停时的标签对象,连接新柱状图的信号槽函数
void MainWindow::ResetLabel()
{
    connect(m_BarChart->m_Series, SIGNAL(hovered(bool,int,QBarSet*)), this, SLOT(sltTooltip(bool,int,QBarSet*)));
    for(unsigned int index = 0; index < m_vecToolTips.size(); index++)
    {
        delete m_vecToolTips[index];
    }
    m_vecToolTips.clear();
    for(unsigned int index = 0; index < m_BarChart->m_uiCnt; index++)
    {
        QLabel *label = nullptr;
        m_vecToolTips.push_back(label);
    }
}

//鼠标悬停时标签创建和显示
void MainWindow::sltTooltip(bool status, int index, QBarSet *barset)
{
    if(nullptr != m_BarChart->m_Series && nullptr != m_BarChart->m_Chartview)
    {
        QChart* pchart = m_BarChart->m_Chartview->chart();
        QLabel *m_tooltip = nullptr;

        if(nullptr != m_vecToolTips[index])
        {
            m_tooltip = m_vecToolTips[index];
        }
        else
        {
            m_tooltip = new QLabel(m_BarChart->m_Chartview);    //头文件中的定义 QLabel*   m_tooltip = nullptr;
            m_tooltip->setStyleSheet("background: rgba(240, 128, 128,185);color: rgb(248, 248, 255);"
                                     "border:0px groove gray;border-radius:10px;padding:2px 4px;"
                                     "border:2px groove gray;border-radius:10px;padding:2px 4px");
            m_tooltip->setVisible(false);
            m_vecToolTips[index] = m_tooltip;
        }

        if (status)
        {
            double val = barset->at(index);
            QPointF point(index, barset->at(index));
            QPointF pointLabel = pchart->mapToPosition(point);

            m_tooltip->setText(QString::number(val));
            m_tooltip->move(pointLabel.x(), pointLabel.y() - m_tooltip->height()*1.5);
            m_tooltip->show();
        }
        else
        {
            m_tooltip->hide();
        }
    }
}

【效果】

通过上述实现,在demo中的实现效果如下图所示

完整demo代码资源:定义封装Qt柱状图类,实现数据动态更新和鼠标数值显示

  • 11
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
好的,以下是一个简单的Qt widget封装的YUV数据显示类: ```cpp // yuvwidget.h #ifndef YUVWIDGET_H #define YUVWIDGET_H #include <QWidget> #include <QImage> class YUVWidget : public QWidget { Q_OBJECT public: explicit YUVWidget(QWidget *parent = nullptr); virtual ~YUVWidget(); void setYUVData(unsigned char *yuvData, int width, int height); protected: virtual void paintEvent(QPaintEvent *event); private: QImage m_image; unsigned char *m_yuvData; int m_width; int m_height; }; #endif // YUVWIDGET_H // yuvwidget.cpp #include "yuvwidget.h" #include <QPainter> YUVWidget::YUVWidget(QWidget *parent) : QWidget(parent), m_yuvData(nullptr), m_width(0), m_height(0) { } YUVWidget::~YUVWidget() { if (m_yuvData != nullptr) { delete [] m_yuvData; } } void YUVWidget::setYUVData(unsigned char *yuvData, int width, int height) { if (m_yuvData != nullptr) { delete [] m_yuvData; } m_yuvData = new unsigned char[width * height * 3 / 2]; memcpy(m_yuvData, yuvData, width * height * 3 / 2); m_width = width; m_height = height; m_image = QImage(m_yuvData, m_width, m_height, QImage::Format_YUV420P); m_image = m_image.rgbSwapped(); update(); } void YUVWidget::paintEvent(QPaintEvent *event) { QPainter painter(this); painter.drawImage(QPoint(0, 0), m_image); } ``` 在这个类中,我们使用QImage来管理YUV数据的内存并显示。在setYUVData函数中,我们首先使用new来分配足够的内存来存储YUV数据,并将其复制到新分配的内存中。然后使用QImage的构造函数来创建一个QImage对象,该对象指向我们刚刚分配的内存,格式为QImage::Format_YUV420P(这是默认格式,也可以根据需要选择其他格式)。最后,我们调用rgbSwapped函数来将像颜色空间从YUV转换为RGB,并更新窗口以显示像。 在paintEvent函数中,我们使用QPainter来在窗口中绘制像。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值