[[Qt] Chart display control developed based on QChartView, supports real-time display, dynamic update, mouse interaction and other operations]

foreword

这是一个Qt平台的基于QChartView类的图像显示控件,支持鼠标交互,支持数据实时显示,动态更新,坐标轴自适应点集的值,鼠标实时点显示。
实现平台:Windows 10 x64 + Qt 6.2.3 + MSVC 2019 

Watch the demo video first

demo video

Control class key code description

Crosshair and display coordinates implementation

Define the crosshair lineitem variable and the coordinate textitem variable in the .h file

	QGraphicsLineItem *m_xLine;
    QGraphicsLineItem *m_yLine;
    QGraphicsTextItem *m_txtPos;

Initialize in Cpp file

    m_xLine = new QGraphicsLineItem();
    m_xLine->setPen(QPen(QColor( 100, 100, 100 )));
    m_xLine->setZValue(2);

    m_yLine = new QGraphicsLineItem();
    m_yLine->setPen(QPen(QColor( 100, 100, 100 )));
    m_yLine->setZValue(2);

    scene()->addItem(m_xLine);
    scene()->addItem(m_yLine);

    m_txtPos = new QGraphicsTextItem();
    m_txtPos->setFont(QFont("宋体", 15, QFont::Bold));
    m_txtPos->setVisible(false);
    scene()->addItem(m_txtPos);

Then define the mouse event, display when the mouse enters, hide when it moves out, and display when it moves.

void ChartDrawer::mouseMoveEvent(QMouseEvent *pEvent)
{
    
    
    QPoint point = pEvent->pos();
    m_xLine->setLine(point.x(),0,point.x(),this->height());
    m_yLine->setLine(0,point.y(),this->width(),point.y());
    QPointF chartPoint = m_chart->mapToValue(point);
    m_txtPos->setPlainText(QString("%1,%2").arg(QString::number(chartPoint.x(),'f',3),
                                                QString::number(chartPoint.y(),'f',3)));
    m_txtPos->setPos(point.x(), point.y());
    QChartView::mouseMoveEvent(pEvent);
}
void ChartDrawer::enterEvent(QEnterEvent *pEvent)
{
    
    
    m_txtPos->setVisible(true);
    m_xLine->setVisible(true);
    m_yLine->setVisible(true);
    QChartView::leaveEvent(pEvent);
}

void ChartDrawer::leaveEvent(QEvent *pEvent)
{
    
    
    m_txtPos->setVisible(false);
    m_xLine->setVisible(false);
    m_yLine->setVisible(false);
    QChartView::leaveEvent(pEvent);
}

For other implementations, please refer to the specific code

Control class implementation specific code

ChartDrawer.h file

#ifndef CHARTDRAWER_H
#define CHARTDRAWER_H

#include <QtCharts>
#include <QChartView>
#include <QWidget>
#include <QGraphicsLineItem>
#include <QLabel>
#include <QGraphicsTextItem>

#define UPDATE_DIS 0.001
#define MINOR_TICK_COUNT 10
#define TICK_COUNT_MIN 5
#define TICK_COUNT_MAX 20
#define TICK_DIS_DEFAULT 20
#define DEFAULT_X_MIN 0
#define DEFAULT_X_MAX 10
#define DEFAULT_Y_MIN 0
#define DEFAULT_Y_MAX 10

class ChartDrawer : public QChartView
{
    
    
    Q_OBJECT
public:
    explicit ChartDrawer(QWidget *parent = nullptr);
    ~ChartDrawer();

    enum AxisType{
    
    
        AxisType_Static = 0,
        AxisType_Slide,
        AxisType_Dynamic
    };

    QAbstractSeries::SeriesType getSeriesType();
    void addSeries(QAbstractSeries::SeriesType type = QAbstractSeries::SeriesTypeLine,QString seriesName = "");
    void setSeriesStyle(QString seriesName = "", Qt::GlobalColor color = Qt::red, int width = 2);
    void setAxisType(AxisType xType = AxisType_Dynamic);
    void setAxisRange(double xMin, double xMax);
    void setAxisRange(double slideDis);
    void setAxisRange();
    double getXAxisRange();
    double getYAxisRange();
    void setAxisTitle(QString xTitle, QString yTitle);
    void setGridVisible(bool isShow);
    void setLegendVisible(bool isShow);
    void setOriginPointFs(QString seriesName, QList<QPointF>);
    void addPointF(QString seriesName, QPointF);
    void clearAllPointF();
    static bool compareX(QPointF a, QPointF b);
    static bool compareY(QPointF a, QPointF b);


    virtual void mousePressEvent(QMouseEvent *pEvent) override;
    virtual void mouseReleaseEvent(QMouseEvent *pEvent) override;
    virtual void wheelEvent(QWheelEvent *pEvent) override;
    virtual void mouseMoveEvent(QMouseEvent *e) override;
    virtual void enterEvent(QEnterEvent *pEvent) override;
    virtual void leaveEvent(QEvent *pEvent) override;

    QChart          *m_chart;
    QValueAxis      *m_xAxis;
    QValueAxis      *m_yAxis;
    QGraphicsLineItem *m_xLine;
    QGraphicsLineItem *m_yLine;
    QGraphicsTextItem *m_txtPos;

    QMap<QString, QXYSeries*>  m_mapSeries;
    QAbstractSeries::SeriesType m_seriesType;
    AxisType m_xAxisType = AxisType_Dynamic;
    double m_xMin, m_xMax, m_yMin, m_yMax;
    double m_slideDis;
    bool m_middleButtonPressed = false;
    QPoint m_oPrePos;

};
#endif // CHARTDRAWER_H

ChartDrawer.cpp file

#include "chartdrawer.h"

ChartDrawer::ChartDrawer(QWidget *parent)
    : QChartView{
    
    parent},
     m_xMin(0.0),
     m_xMax(10),
     m_yMin(0.0),
     m_yMax(10)
{
    
    
    m_chart = new QChart();
    this->setChart(m_chart);
    this->setRubberBand(QChartView::NoRubberBand);
    this->setRenderHint(QPainter::Antialiasing);
    this->setContentsMargins(0,0,0,0);
    //m_chart->setTheme(QChart::ChartThemeDark);

    m_xAxis = new QValueAxis;
    m_xAxis->setLabelFormat("%.3f");
    m_xAxis->setRange(m_xMin,m_xMax);

    m_yAxis = new QValueAxis;
    m_yAxis->setLabelFormat("%.3f");
    m_yAxis->setRange(m_yMin,m_yMax);

    m_chart->addAxis(m_xAxis, Qt::AlignBottom);
    m_chart->addAxis(m_yAxis, Qt::AlignLeft);

    m_xLine = new QGraphicsLineItem();
    m_xLine->setPen(QPen(QColor( 100, 100, 100 )));
    m_xLine->setZValue(2);

    m_yLine = new QGraphicsLineItem();
    m_yLine->setPen(QPen(QColor( 100, 100, 100 )));
    m_yLine->setZValue(2);

    scene()->addItem(m_xLine);
    scene()->addItem(m_yLine);

    m_txtPos = new QGraphicsTextItem();
    m_txtPos->setFont(QFont("宋体", 15, QFont::Bold));
    m_txtPos->setVisible(false);
    scene()->addItem(m_txtPos);

}

ChartDrawer::~ChartDrawer()
{
    
    
    deleteLater();
    delete m_xLine;
    delete m_yLine;
    delete m_txtPos;
    m_xLine = nullptr;
    m_yLine = nullptr;
    m_txtPos = nullptr;
}
QAbstractSeries::SeriesType ChartDrawer::getSeriesType()
{
    
    
    return m_seriesType;
}
void ChartDrawer::addSeries(QAbstractSeries::SeriesType type,QString seriesName)
{
    
    
    m_seriesType = type;
    QXYSeries *series;
    switch(type)
    {
    
    
    case QAbstractSeries::SeriesType::SeriesTypeLine:
        series = new QLineSeries(this);
        break;
    case QAbstractSeries::SeriesType::SeriesTypeSpline:
        series = new QSplineSeries(this);
        break;
    case QAbstractSeries::SeriesType::SeriesTypeScatter:
        series = new QScatterSeries(this);
        break;
    default:
        break;
    }

    m_chart->addSeries(series);
    series->setName(seriesName);
    series->attachAxis(m_xAxis);
    series->attachAxis(m_yAxis);
    // 隐藏点标签
    series->setPointLabelsVisible(false);

    m_mapSeries.insert(seriesName,series);
}
void ChartDrawer::setSeriesStyle(QString seriesName, Qt::GlobalColor color, int width)
{
    
    
    if(m_mapSeries.find(seriesName).value() == nullptr){
    
    
        qDebug() << QStringLiteral("曲线不存在");
        return;
    }
    QPen splinePen;
    splinePen.setBrush(color);
    splinePen.setColor(color);
    splinePen.setWidth(width);
    m_mapSeries[seriesName]->setPen(splinePen);
}

void ChartDrawer::setAxisType(AxisType xType)
{
    
    
    m_xAxisType = xType;
}

inline bool ChartDrawer::compareX(QPointF a, QPointF b)
{
    
    
    return a.x() > b.x();
}
inline bool ChartDrawer::compareY(QPointF a, QPointF b)
{
    
    
    return a.y() > b.y();
}

void ChartDrawer::setAxisRange(double xMin, double xMax)
{
    
    
    m_xMin = xMin;
    m_xMax = xMax;
}
void ChartDrawer::setAxisRange(double slideDis)
{
    
    
    m_slideDis = slideDis;
}
void ChartDrawer::setAxisRange()
{
    
    
    QVector<QPointF> olddata;
    foreach(QXYSeries *series, m_mapSeries){
    
    
        olddata << series->points();
    }
    QVector<QPointF> sortData = olddata;

    std::sort(sortData.begin(),sortData.end(),compareY);
    m_yMin = sortData.last().y();
    m_yMax = sortData.first().y();

    m_yAxis->setRange(m_yMin, m_yMax);

    if(m_xAxisType == AxisType_Static){
    
    

    } else {
    
    
        std::sort(sortData.begin(),sortData.end(),compareX);
        m_xMax = sortData.first().x();
        if(m_xAxisType == AxisType_Slide){
    
    
            m_xMin = m_xMax - m_slideDis;
        } else if(m_xAxisType == AxisType_Dynamic) {
    
    
            m_xMin = sortData.last().x();
        }
    }
    m_xAxis->setRange(m_xMin, m_xMax);


    //qDebug() << m_xMin << m_xMax << m_yMin << m_yMax;
}
double ChartDrawer::getXAxisRange()
{
    
    
    return double(m_xAxis->max() - m_xAxis->min());
}
double ChartDrawer::getYAxisRange()
{
    
    
    return double(m_yAxis->max() - m_yAxis->min());
}
void ChartDrawer::setAxisTitle(QString xTitle, QString yTitle)
{
    
    
    m_xAxis->setTitleText(xTitle);
    m_yAxis->setTitleText(yTitle);
}

void ChartDrawer::setGridVisible(bool isShow)
{
    
    
    int count = double(m_xAxis->max() - m_xAxis->min()) / 20;
    if(count < TICK_COUNT_MIN){
    
    
        count = TICK_COUNT_MIN;
    } else if(count > TICK_COUNT_MAX){
    
    
        count = TICK_COUNT_MAX;
    }
    m_xAxis->setGridLineVisible(isShow);
    m_xAxis->setTickCount(count);
    m_xAxis->setMinorTickCount(MINOR_TICK_COUNT);

    count = double(m_yAxis->max() - m_yAxis->min()) / 20;
    if(count < TICK_COUNT_MIN){
    
    
        count = TICK_COUNT_MIN;
    } else if(count > TICK_COUNT_MAX){
    
    
        count = TICK_COUNT_MAX;
    }
    m_yAxis->setGridLineVisible(isShow);
    m_yAxis->setTickCount(count);
    m_yAxis->setMinorTickCount(MINOR_TICK_COUNT);
}

void ChartDrawer::setLegendVisible(bool isShow)
{
    
    
    m_chart->legend()->setVisible(isShow);
    /*m_chart->legend()->setLayoutDirection(Qt::LeftToRight);
    m_chart->legend()->setAlignment(Qt::AlignBottom);*/
}
void ChartDrawer::setOriginPointFs(QString seriesName, QList<QPointF> data)
{
    
    
    if(m_mapSeries.find(seriesName).value() == nullptr){
    
    
        qDebug() << QStringLiteral("曲线不存在");
        return;
    }
    m_mapSeries[seriesName]->append(data);
}
void ChartDrawer::addPointF(QString seriesName, QPointF pointf)
{
    
    
    if(m_mapSeries.find(seriesName).value() == nullptr){
    
    
        qDebug() << QStringLiteral("曲线不存在");
        return;
    }
    //qDebug() << pointf;
    QVector<QPointF> olddata = m_mapSeries[seriesName]->points();
    if(olddata.size() > 0){
    
    
        double xdis = abs(olddata[olddata.size()-1].x() - pointf.x());
        double ydis = abs(olddata[olddata.size()-1].y() - pointf.y());
        //qDebug() << "x:" << xdis << "," << "y:" << ydis;
        if(xdis < UPDATE_DIS && ydis < UPDATE_DIS)
            return;
    }
    olddata.append(pointf);
    m_mapSeries[seriesName]->replace(olddata);
    setAxisRange();
}
void ChartDrawer::clearAllPointF()
{
    
    
    foreach(QXYSeries *series, m_mapSeries){
    
    
        series->replace(QVector<QPointF>());
    }
}
void ChartDrawer::mousePressEvent(QMouseEvent *pEvent)
{
    
    
    if (pEvent->button() == Qt::MiddleButton) {
    
    
        m_middleButtonPressed = true;
        m_oPrePos = pEvent->pos();
        this->setCursor(Qt::OpenHandCursor);
    } else if(pEvent->button() == Qt::RightButton) {
    
    
        setAxisRange();
    }
    QChartView::mousePressEvent(pEvent);
}
void ChartDrawer::mouseReleaseEvent(QMouseEvent *pEvent)
{
    
    
    if (pEvent->button() == Qt::MiddleButton) {
    
    
        m_middleButtonPressed = false;
        this->setCursor(Qt::ArrowCursor);
    }
    QChartView::mouseReleaseEvent(pEvent);
}
void ChartDrawer::wheelEvent(QWheelEvent *pEvent)
{
    
    

    QPointF point = pEvent->position();
    double rVal = std::pow(0.999, pEvent->angleDelta().y());
    QPointF chartPoint = m_chart->mapToValue(point);

    //计算当前点到最小点和最大点的距离,x轴和y轴
    double oldLeftDis = chartPoint.x() - m_xAxis->min();
    double oldRightDis = m_xAxis->max() - chartPoint.x();
    double oldDownDis = chartPoint.y() - m_yAxis->min();
    double oldUpDis = m_yAxis->max() - chartPoint.y();

    //计算当前点到缩放后的最小点和最大点的距离,x轴和y轴
    double newLeftDis = oldLeftDis * rVal;
    double newRightDis = oldRightDis * rVal;
    double newDownDis = oldDownDis * rVal;
    double newUpDis = oldUpDis * rVal;

    //计算新的坐标最小点和最大点
    double newXMin = chartPoint.x() - newLeftDis;
    double newXMax = chartPoint.x() + newRightDis;
    double newYMin = chartPoint.y() - newDownDis;
    double newYMax = chartPoint.y() + newUpDis;
    //重新设置坐标轴的显示范围
    m_xAxis->setRange(newXMin, newXMax);
    m_yAxis->setRange(newYMin, newYMax);

    QChartView::wheelEvent(pEvent);
}
void ChartDrawer::mouseMoveEvent(QMouseEvent *pEvent)
{
    
    
    QPoint point = pEvent->pos();
    if (m_middleButtonPressed) {
    
    
     QPoint oDeltaPos = point - m_oPrePos;
     m_chart->scroll(-oDeltaPos.x(), oDeltaPos.y());
     m_oPrePos = point;
    }
    m_xLine->setLine(point.x(),0,point.x(),this->height());
    m_yLine->setLine(0,point.y(),this->width(),point.y());
    QPointF chartPoint = m_chart->mapToValue(point);
    m_txtPos->setPlainText(QString("%1,%2").arg(QString::number(chartPoint.x(),'f',3),
                                                QString::number(chartPoint.y(),'f',3)));
    m_txtPos->setPos(point.x(), point.y());
    QChartView::mouseMoveEvent(pEvent);
}
void ChartDrawer::enterEvent(QEnterEvent *pEvent)
{
    
    
    m_txtPos->setVisible(true);
    m_xLine->setVisible(true);
    m_yLine->setVisible(true);
    QChartView::leaveEvent(pEvent);
}

void ChartDrawer::leaveEvent(QEvent *pEvent)
{
    
    
    m_txtPos->setVisible(false);
    m_xLine->setVisible(false);
    m_yLine->setVisible(false);
    QChartView::leaveEvent(pEvent);
}

Use of control classes

The specific use code is as follows
1. Initialize the class object and add it to the interface layout

	m_chartDrawer = new ChartDrawer(this);
    vLayout->addWidget(m_chartDrawer);
    
	m_chartDrawer->setAxisTitle("时间/(s)","路程/(m)");
	m_chartDrawer->setGridVisible(true);
	m_chartDrawer->setLegendVisible(false);
	//    m_chartDrawer->setAxisType(ChartDrawer::AxisType::AxisType_Slide);
	//m_chartDrawer->setAxisRange(0,10);
	
	m_chartDrawer->addSeries(QAbstractSeries::SeriesTypeLine,"line0");
	m_chartDrawer->setSeriesStyle("line0", Qt::red, 3);
	m_chartDrawer->setOriginPointFs("line0", QList<QPointF>());
	
	m_chartDrawer->addSeries(QAbstractSeries::SeriesTypeSpline,"line1");
	m_chartDrawer->setSeriesStyle("line1", Qt::green, 3);
	m_chartDrawer->setOriginPointFs("line1", QList<QPointF>());

2. Add points to the chart control.
I define a timer here, and add a point to the two curves every 1s.

void MainWindow::timerEvent(QTimerEvent *event)
{
    
    
    //qDebug() << "aa";
    m_time += 1;
    srand(QTime(0, 0, 0).secsTo(QTime::currentTime()));
    double  m_distance = QRandomGenerator::global()->bounded(0,20);
    if(m_preLine == "line0")
    {
    
    
        m_chartDrawer->addPointF("line1", QPointF(m_time,m_distance));
        m_preLine = "line1";
    }
    else
    {
    
    
        m_chartDrawer->addPointF("line0", QPointF(m_time,m_distance));
        m_preLine = "line0";
    }
}

3. Remove all points from the control

m_chartDrawer->clearAllPointF();

If you still don't understand, it is recommended to download the source code directly

Source link: https://download.csdn.net/download/xiaohuihuihuige/87264001

Please like it if it helps. Follow me for more custom controls

Guess you like

Origin blog.csdn.net/xiaohuihuihuige/article/details/128265056