Qt使用QChart制作多路虚拟示波器


使用QT做虚拟示波器,共16通道,波形是重叠在一起(不同颜色区分),想用写好的TCP传输来让单片机与电脑通信,解刨数据,放入到示波器中进行显示。


准备工作:首先我缺一个绘图的控件,于是在网上找了找,网上给我介绍的有三种。分别是qcustomplot、qwt以及qchart。我第一次使用的是qcustomplot,我发现使用qcustomplot如果不生成静态库再使用的话,运行起来特别特别慢,这就对编程带来了不便。这其实也不能怪人家,它毕竟大小就有1M多,我们一般写代码一个文件才几十K,所以想呀,qcustomplot编译起来肯定很花时间。然后我就使用了QChart类,我听说会玩这个的话,要比另外两种强。qwt我还没碰过。声明一下,我只是谈谈我个人的想法,不喜勿喷!

  1. QChartView是一个窗口,提供显示的地方。而QChart则像是一张图纸,QLineSeries直线或者QSplineSeries平滑线就相当于是画笔了。我使用继承QChartView来做一个方便我对外使用打开通道、关闭通道、添加数据等功能的接口。

    class WaveDisplay : public QChartView
  2. 使用枚举定义16个通道

    //设置通道数
    typedef enum
    {
        Channel1,  Channel2,  Channel3,  Channel4,
        Channel5,  Channel6,  Channel7,  Channel8,
        Channel9,  Channel10, Channel11, Channel12,
        Channel13, Channel14, Channel15, Channel16
    }Channel;

     

  3. 曲线选择类型,方便后面更改

    typedef QLineSeries LineType;//QLineSeries 直线     QSplineSeries 平滑线
    

     

  4. 整个控件需要用到一些共有变量,每一次打开一个通道的新建一只画笔(QSplineSeries),然后这只画笔还得记录下来,为我后续添加数据使用,我在这里使用的是QHash来保存的,个人感觉这个还是挺好用的,推荐!后面三行用于自动控制刻度范围。

    QChart *chart;                                              //图纸
    QValueAxis *axisX, *axisY;                                  //坐标轴
    QHash seriesHash;                       //将多条线放在图纸上
    QHash minALL;                              //记录所有曲线的最小值
    QHash maxALL;                              //记录所有曲线的最大值
    double Xmax = 0, Ymax = -350;
    double Xmin = 0, Ymin = 350;
    double Xrang = 3;

     

  5. 初始化工作

    WaveDisplay::WaveDisplay(QWidget *parent) : QChartView(parent)
    {
        mainLayout = new QGridLayout(parent);             //居中显示
        mainLayout->addWidget(this, 0, 1, 3, 1);
        chart = new QChart;                               //画图纸
        chart->legend()->setVisible(false);               //曲线文本提示
        setChart(chart);                                  //添加图纸
        axisX = new QValueAxis;                           //设置坐标轴
        axisY = new QValueAxis;
        chart->addAxis(axisX, Qt::AlignBottom);           //将坐标轴加到chart上,居下
        chart->addAxis(axisY, Qt::AlignLeft);             //居左
        setRenderHint(QPainter::Antialiasing, true);      //反走样
        /*特效*/
        //setRubberBand(QChartView::RectangleRubberBand); //矩形缩放
        //chart->setAnimationOptions(QChart::SeriesAnimations); //曲线动画模式,不能启用这一项或是选择这个选项,这个会导致曲线闪烁
    }
    

     

  6. 打开一个通道

    void WaveDisplay::openChannel(Channel channel)//添加一条平滑线并添加到图纸中
    {
        minALL.insert(channel, 100000);
        maxALL.insert(channel, -100000);
        if(seriesHash.contains(channel))                 //如果重复打开同一个通道就会执行这条语句,主要是防止内存泄漏
        {
            chart->addSeries(seriesHash[channel]);
            chart->setAxisX(axisX, seriesHash[channel]);  //将x和y坐标轴与第一条曲线连接
            chart->setAxisY(axisY, seriesHash[channel]);
            return;
        }
        LineType *series = new LineType();
        series->setPen(Color[channel]);
        chart->addSeries(series);
        chart->setAxisX(axisX, series);                   //将x和y坐标轴与第一条曲线连接
        chart->setAxisY(axisY, series);
        seriesHash.insert(channel, series);               //记录到表中
    }

     

  7.  颜色如果自己不想配的话可以直接使用我自己配的,配16中人眼看起来完全不同颜色还是挺难受的

    namespace ChannelColor{
    static QColor Color[16] = {
        QColor(165, 42, 42),    //Channel1Color
        QColor (255, 127, 80),  //Channel2Color
        QColor (30, 144, 255),  //Channel3Color
        QColor (218, 165, 32),  //Channel4Color
        QColor (255, 0, 255),   //Channel5Color
        QColor (147, 112, 219), //Channel6Color
        QColor (0, 255, 255),   //Channel7Color
        QColor (0, 0, 128),     //Channel8Color
        QColor (128, 0, 128),   //Channel9Color
        QColor (46, 139, 87),   //Channel10Color
        QColor (152, 251, 152), //Channel11Color
        QColor (0, 255, 127),   //Channel12Color
        QColor (0, 0, 255),     //Channel13Color
        QColor (135, 206, 250), //Channel14Color
        QColor (255, 192, 203), //Channel15Color
        QColor (127, 255, 0)    //Channel16Color
    };
    }
    

     

  8. 关闭通道

    void WaveDisplay::closeChannel(Channel channel)
    {
        if(seriesHash.contains(channel))                //如果先前没有打开这通道,程序就不会执行这条复合语句,主要防止用户点错
        {
            chart->removeSeries(seriesHash[channel]);   //先删去图纸上的线
            minALL.remove(channel);
            maxALL.remove(channel);
        }
    }
  9. 可以在设置数据和追加数据的时候添加判断,这样就可以自动刻度了

    void WaveDisplay::setData(Channel channel, const QList& data)
    { 
        if(seriesHash.contains(channel) && chart->series().contains(seriesHash[channel]))
        {
            seriesHash[channel]->clear();
            seriesHash[channel]->append(data);
            Xmin = data[0].x() < Xmin ? data[0].x() : Xmin;
            Xmax = data[data.count() - 1].x() > Xmax ? data[data.count() - 1].x() : Xmax;
            for(int i = 0; i < data.count(); i++)
            {
                maxALL[channel] = data[i].y() > maxALL[channel] ? data[i].y() : maxALL[channel];
                minALL[channel] = data[i].y() < minALL[channel] ? data[i].y() : minALL[channel];
            }
            updateChart();
        }
    }
    

     

  10. 追加数据

    void WaveDisplay::appendData(Channel channel, const QPointF& data)
    {
        if(seriesHash.contains(channel) && chart->series().contains(seriesHash[channel]))
        {
            seriesHash[channel]->append(data);
            if(data.x() > Xrang)
            {
                Xmax = data.x();
                Xmin = Xmax - Xrang;
            }
    
            maxALL[channel] = data.y() > maxALL[channel] ? data.y() : maxALL[channel];
            minALL[channel] = data.y() < minALL[channel] ? data.y() : minALL[channel];
            updateChart();
        }
    }
    void WaveDisplay::appendData(Channel channel, const QList& data)
    {
        if(seriesHash.contains(channel) && chart->series().contains(seriesHash[channel]))
        {
            seriesHash[channel]->append(data);
            Xmin = data[0].x() < Xmin ? data[0].x() : Xmin;
            Xmax = data[data.count() - 1].x() > Xmax ? data[data.count() - 1].x() : Xmax;
            for(int i = 0; i < data.count(); i++)
            {
                maxALL[channel] = data[i].y() > maxALL[channel] ? data[i].y() : maxALL[channel];
                minALL[channel] = data[i].y() < minALL[channel] ? data[i].y() : minALL[channel];
            }
            updateChart();
        }
    }

     

  11. 按键点击事件:ESC——恢复默认显示;TAB——支持鼠标取点

    void WaveDisplay::keyPressEvent(QKeyEvent *event)
    {
        switch (event->key())
        {
        case Qt::Key_Escape:
            updateChart();
            break;
        case Qt::Key_Tab://Tab键被按下
            showPoint = true;
            break;
        default:
            break;
        }
    }

     

  12. 按键释放事件:TAB——不支持鼠标取点

    void WaveDisplay::keyReleaseEvent(QKeyEvent *event)
    {
        if(event->key() == Qt::Key_Tab)
            showPoint = false;
    }

     

  13. 鼠标点击事件:左键——取点;右键——配合曲线移动

    void WaveDisplay::mouseMoveEvent(QMouseEvent *event)//参考:https://blog.csdn.net/qq_31073871/article/details/83019943
    {
        QPoint temp = event->pos();//获取当前坐标的位置
        if(showPoint)
        {
            QPointF f = chart->mapToValue(temp);
            QString str = '(' + QString::number(f.x()) + ',' + QString::number(f.y()) + ')';
            QToolTip::showText(event->globalPos(), str);
        }else{
            QToolTip::hideText();
        }
        //    getPos(event);
        if(event->buttons() == Qt::RightButton){      // 这里必须使用buttons(),因为鼠标移动过程中会检测所有按下的键,而这个时候button()是无法检测那个按键被按下,所以必须使用buttons()函数,看清楚,是buttons()不是button()
            QPointF f = chart->mapToValue(temp) - chart->mapToValue(lastPos);
            axisX->setRange(axisX->min() - f.x(), axisX->max() - f.x());
            axisY->setRange(axisY->min() - f.y(), axisY->max() - f.y());
            chart->update();
            lastPos = temp;
            update();
        }
    }
  14. 鼠标点击事件:左键——取点;右键——移动曲线

    void WaveDisplay::mousePressEvent(QMouseEvent *event)
    {
        if(event->button() == Qt::RightButton){      // 如果是鼠标中键按下
            QCursor cursor;
            cursor.setShape(Qt::ClosedHandCursor);
            QApplication::setOverrideCursor(cursor); // 使鼠标指针暂时改变形状
            lastPos = event->pos();                  // 获取指针位置和窗口位置的差值,这个globalPos()获取的是鼠标在桌面上的位置,也可以使用pos()函数获取指针在窗口的位置。
        }
        else if(event->button() == Qt::LeftButton){
            getPos(event);
        }
    }

     

  15. 鼠标移动事件:如果TAB按下则显示鼠标位置;右键:移动曲线

    void WaveDisplay::mouseMoveEvent(QMouseEvent *event)
    {
        QPoint temp = event->pos();//获取当前坐标的位置
        if(showPoint)
        {
            QPointF f = chart->mapToValue(temp);
            QString str = '(' + QString::number(f.x()) + ',' + QString::number(f.y()) + ')';
            QToolTip::showText(event->globalPos(), str);
        }else{
            QToolTip::hideText();
        }
        if(event->buttons() == Qt::RightButton){      // 这里必须使用buttons(),因为鼠标移动过程中会检测所有按下的键,而这个时候button()是无法检测那个按键被按下,所以必须使用buttons()函数,看清楚,是buttons()不是button()
            QPointF f = chart->mapToValue(temp) - chart->mapToValue(lastPos);
            axisX->setRange(axisX->min() - f.x(), axisX->max() - f.x());
            axisY->setRange(axisY->min() - f.y(), axisY->max() - f.y());
            chart->update();
            lastPos = temp;
            update();
        }
    }

     

  16. 鼠标释放事件:配合曲线移动

    void WaveDisplay::mouseReleaseEvent(QMouseEvent *event)
    {
        Q_UNUSED(event);
        QApplication::restoreOverrideCursor();         // 恢复鼠标指针形状
    }

     

  17. 滚轮事件:参考http://blog.csdn.net/smarterr/article/details/80781368

    void WaveDisplay::wheelEvent(QWheelEvent *event)
    {
        if(event->delta() > 0){                    // 当滚轮远离使用者时
            chart->zoomIn();                       // 进行放大
            Xrang /= 1.5;
        }else{                                     // 当滚轮向使用者方向旋转时
            chart->zoomOut();                      // 进行缩小
            Xrang *= 1.5;
        }
    }

     

  18. 获取鼠标坐标函数,自己简单写了下思路,欢迎提问

    void WaveDisplay::getPos(QMouseEvent *event)
    {
        QPoint temp = event->pos();//获取当前坐标的位置
        if(showPoint)
        {
            QPointF f = chart->mapToValue(temp);
            QString str = '(' + QString::number(f.x()) + ',' + QString::number(f.y()) + ')';
            QToolTip::showText(event->globalPos(), str);
    
    
            QHash::iterator seriesTemp;
            for(seriesTemp = seriesHash.begin(); seriesTemp != seriesHash.end(); ++seriesTemp)//遍历每条曲线
            {
                QList pointfTemp = seriesTemp.value()->points();//线上所有点对应的屏幕坐标
                int low = 0;
                int high = pointfTemp.count() - 1;
                if(pointfTemp[low].x() > f.x() || pointfTemp[high].x() < f.x())
                {
                    posList[seriesTemp.key()].clear();
                    continue;
                }
                while(low < high - 1)//从某条线找到这个点的附近
                {
                    int middle = (low + high) / 2;
                    if(pointfTemp[middle].x() < f.x())
                    {
                        low = middle;
                    }
                    if(pointfTemp[middle].x() > f.x())
                    {
                        high = middle;
                    }
                }
                if(low == high)
                    posList[seriesTemp.key()] = '(' + QString::number(f.x()) + ',' + QString::number(pointfTemp[high].y()) + ')';
                else{
                    posList[seriesTemp.key()] = '(' + QString::number(f.x()) + ',' + QString::number\
                            (pointfTemp[low].y() + (pointfTemp[high].y() - pointfTemp[low].y()) / (pointfTemp[high].x() - pointfTemp[low].x()) * (f.x() - pointfTemp[low].x()))\
                            + ')';
                }
            }
        }else{
            QToolTip::hideText();
        }
    }
    

     

  19. 恢复坐标轴

    void WaveDisplay::updateChart()
    {
        double num;
        QHash::iterator rangTemp;
    
        //找最小值
        if(minALL.size())
        {
            rangTemp = minALL.begin();
            num = rangTemp.value();
            ++rangTemp;
            for(; rangTemp != minALL.end(); ++rangTemp)//遍历每条曲线
            {
                num = rangTemp.value() < num ? rangTemp.value() : num;
            }
            Ymin = num;
        }
    
        //找最大值
        if(maxALL.size())
        {
            rangTemp = maxALL.begin();
            num = rangTemp.value();
            ++rangTemp;
            for(; rangTemp != maxALL.end(); ++rangTemp)//遍历每条曲线
            {
                num = rangTemp.value() > num ? rangTemp.value() : num;
            }
            Ymax = num;
        }
    
        axisX->setRange(Xmin, Xmax);//设置图表坐标轴的范围,可以不设置,自动调节的
        double range = Ymax - Ymin;
        axisY->setRange(Ymin - range * 0.1, Ymax + range * 0.1);
    }
    

     

你可能感兴趣的:(Qt,C++)