• QML 自定义折线图


    ChartView + LineSeries 虽然强大,但由于性能和效果显示上和期待的结果有偏差,仍无法满足需求,这种情况下,需要自定义绘图实现。

    本例模拟实现 CT 仪器上面显示的患者的心电图,先上效果图:

     通过本例,可以学习到,QML如何调用C++代码以及自定义绘图:

    1. 在 QML 中创建 C++ 类对象,需要在使用前注册,在 QML 中需要先导入:

    qmlRegisterType<DataPaintedItem>("com.diysoul", 1, 0, "DataPaintedItem");

           参数1~3声明命名空间与版本号,用于在 QML 中导入时使用。参数4指定QML中使用的类名称。

    import com.diysoul 1.0

    2. 需要在 QML 中调用 C++ 类成员,需要使用 Q_INVOKABLE 宏进行声明,如: Q_INVOKABLE void appendPoint(int y);

    关键代码:

    import QtQuick 2.0
    import QtQuick.Layouts 1.12
    import com.diysoul 1.0
    
    Rectangle {
    
        color: Qt.rgba(0, 0, 1, 0.1)
        anchors.fill: parent
        anchors.margins: 10
        radius: 5
    
        DataPaintedItem {
            id: paintItem
            pointCount: 800
            spaceCount: 5
            anchors.fill: parent
            title: qsTr("折线图")
            titleFont.bold: true
            titleFont.pixelSize: 35
            titleColor: Qt.rgba(0, 0, 1, 0.5)
            xTickCount: 10
            yTickCount: 6
            xLineVisible: true
            yLineVisible: true
            yMax: 8000
            yMin: 0
            labelsVisible: true
            labelsColor: Qt.rgba(0, 0, 0, 0.9)
            labelsFont.pixelSize: 15
            lineWidth: 2
            gridLineWidth: 1
            lineColor: Qt.rgba(1, 0, 0, 1)
            gridLineColor: Qt.rgba(0, 0, 1, 0.5)
        }
    
        // FIXME 2021-08-07 : timer for test
        Timer {
            running: true
            interval: 1
            repeat: true
    
            property int current: 0
            property var valueList: [
                2000, 2100, 2200, 2300, 2400, 2500, 2600, 2700, 2800, 2900,
                3000, 3100, 3200, 3300, 3400, 3500, 3600, 3700, 3800, 3900,
                4000, 4100, 4200, 4300, 4400, 4500, 4600, 4700, 4800, 4900,
                4950, 4960, 4970, 4980, 4990, 5000, 4990, 4980, 4970, 4960, 4950,
                4900, 4800, 4700, 4600, 4500, 4400, 4300, 4200, 4100, 4000,
                3900, 3800, 3700, 3600, 3500, 3400, 3300, 3200, 3100, 3000,
                2900, 2800, 2700, 2600, 2500, 2400, 2300, 2200, 2100,
            ]
    
            onTriggered: {
                var value = valueList[current]
                current = current + 1
                if (current >= valueList.length) {
                    current = 0
                }
    
                paintItem.appendPoint(value)
            }
        }
    }
    实际完成绘制图形功能的类 DataPaintedItem 定义如下:

    #pragma once
    #include <QQuickPaintedItem>
    #include <QValueAxis>
    
    class DataPaintedItem : public QQuickPaintedItem
    {
        Q_OBJECT
    public:
        Q_PROPERTY(QString title READ getTitle WRITE setTitle NOTIFY titleChanged)
        Q_PROPERTY(QFont titleFont READ getTitleFont WRITE setTitleFont NOTIFY titleFontChanged)
        Q_PROPERTY(QColor titleColor READ getTitleColor WRITE setTitleColor NOTIFY titleColorChanged)
        Q_PROPERTY(int pointCount READ getPointCount WRITE setPointCount NOTIFY pointCountChanged)
        Q_PROPERTY(int spaceCount READ getSpaceCount WRITE setSpaceCount NOTIFY spaceCountChanged)
        Q_PROPERTY(int xTickCount READ getXTickCount WRITE setXTickCount NOTIFY xTickCountChanged)
        Q_PROPERTY(int yTickCount READ getYTickCount WRITE setYTickCount NOTIFY yTickCountChanged)
        Q_PROPERTY(QColor lineColor READ getLineColor WRITE setLineColor NOTIFY lineColorChanged)
        Q_PROPERTY(QColor gridLineColor READ getGridLineColor WRITE setGridLineColor NOTIFY gridLineColorChanged)
        Q_PROPERTY(QFont lineFont READ getLineFont WRITE setLineFont NOTIFY lineFontChanged)
        Q_PROPERTY(QFont gridLineFont READ getGridLineFont WRITE setGridLineFont NOTIFY gridLineFontChanged)
        Q_PROPERTY(bool xLineVisible READ getXLineVisible WRITE setXLineVisible NOTIFY xLineVisibleChanged)
        Q_PROPERTY(bool yLineVisible READ getYLineVisible WRITE setYLineVisible NOTIFY yLineVisibleChanged)
        Q_PROPERTY(int lineWidth READ getLineWidth WRITE setLineWidth NOTIFY lineWidthChanged)
        Q_PROPERTY(int gridLineWidth READ getGridLineWidth WRITE setGridLineWidth NOTIFY gridLineWidthChanged)
        Q_PROPERTY(int yMin READ getYMin WRITE setYMin NOTIFY yMinChanged)
        Q_PROPERTY(int yMax READ getYMax WRITE setYMax NOTIFY yMaxChanged)
        Q_PROPERTY(QList<QPointF> points READ getPoints WRITE setPoints)
        Q_PROPERTY(bool labelsVisible READ getLabelsVisible WRITE setLabelsVisible NOTIFY labelsVisibleChanged)
        Q_PROPERTY(QColor labelsColor READ getLabelsColor WRITE setLabelsColor NOTIFY labelsColorChanged)
        Q_PROPERTY(QFont labelsFont READ getLabelsFont WRITE setLabelsFont NOTIFY labelsFontChanged)
    
    public:
        explicit DataPaintedItem(QQuickItem* parent = nullptr);
        ~DataPaintedItem() override;
    
        void paint(QPainter* painter) override;
    
        Q_INVOKABLE void appendPoint(int y);
    
        int getYMin() const;
        void setYMin(int value);
    
        int getYMax() const;
        void setYMax(int value);
    
        int getXTickCount() const;
        void setXTickCount(int tickCount);
    
        int getYTickCount() const;
        void setYTickCount(int tickCount);
    
        bool getXLineVisible() const;
        void setXLineVisible(bool newXLineVisible);
    
        bool getYLineVisible() const;
        void setYLineVisible(bool newYLineVisible);
    
        int getPointCount() const;
        void setPointCount(int pointCount);
    
        int getSpaceCount() const;
        void setSpaceCount(int spaceCount);
    
        const QList<QPointF>& getPoints() const;
        void setPoints(const QList<QPointF>& points);
    
        const QString& getTitle() const;
        void setTitle(const QString& title);
    
        const QFont& getTitleFont() const;
        void setTitleFont(const QFont& font);
    
        const QColor& getTitleColor() const;
        void setTitleColor(const QColor& color);
    
        const QColor& getLineColor() const;
        void setLineColor(const QColor& newLineColor);
    
        const QColor& getGridLineColor() const;
        void setGridLineColor(const QColor& newGridLineColor);
    
        const QFont& getLineFont() const;
        void setLineFont(const QFont& newLineFont);
    
        const QFont& getGridLineFont() const;
        void setGridLineFont(const QFont& newGridLineFont);
    
        int getLineWidth() const;
        void setLineWidth(int newLineWidth);
    
        int getGridLineWidth() const;
        void setGridLineWidth(int newGridLineWidth);
    
        bool getLabelsVisible() const;
        void setLabelsVisible(bool newLabelsVisible);
    
        const QColor& getLabelsColor() const;
        void setLabelsColor(const QColor& newLabelsColor);
    
        const QFont& getLabelsFont() const;
        void setLabelsFont(const QFont& newLabelsFont);
    
    signals:
        void titleChanged();
        void titleFontChanged();
        void titleColorChanged();
        void pointCountChanged();
        void spaceCountChanged();
        void yMinChanged();
        void yMaxChanged();
        void xTickCountChanged();
        void yTickCountChanged();
        void xLineVisibleChanged();
        void yLineVisibleChanged();
        void lineColorChanged();
        void gridLineColorChanged();
        void lineFontChanged();
        void gridLineFontChanged();
        void lineWidthChanged();
        void gridLineWidthChanged();
        void labelsVisibleChanged();
        void labelsColorChanged();
        void labelsFontChanged();
    
    private:
        void drawTitle(QPainter* painter);
        void drawLabels(QPainter* painter);
        void drawGridLine(QPainter* painter);
        void drawLine(QPainter* painter);
        QPointF transformPoint(const QPointF& pt) const;
    
    private:
        QString                   myTitle;
        QFont                     myTitleFont;
        QColor                    myTitleColor;
        int                       myPointCount{60};
        int                       mySpaceCount{10};
        int                       myXTickCount{1};
        int                       myYTickCount{1};
        bool                      myXLineVisible{};
        bool                      myYLineVisible{};
        QColor                    myLineColor;
        QColor                    myGridLineColor;
        QFont                     myLineFont;
        QFont                     myGridLineFont;
        int                       myLineWidth;
        int                       myGridLineWidth;
        bool                      myLabelsVisible{true};
        QColor                    myLabelsColor;
        QFont                     myLabelsFont;
        int                       myYMaxValue{100};
        int                       myYMinValue{};
        QList<QPointF>            myPoints;
        int                       myCurrentPointIndex{};
        int                       myXStart{};
        int                       myXEnd{};
        int                       myYStart{};
        int                       myYEnd{};
    };
    #include "DataPaintedItem.h"
    #include <QDebug>
    #include <QPainter>
    #include <QPainterPath>
    
    DataPaintedItem::DataPaintedItem(QQuickItem* parent)
        : QQuickPaintedItem(parent)
    {
    }
    
    DataPaintedItem::~DataPaintedItem()
    {
    }
    
    void DataPaintedItem::paint(QPainter* painter)
    {
        if (!painter) {
            return;
        }
    
        myXStart = myGridLineWidth;
        myXEnd = width() - myGridLineWidth;
        myYStart = myGridLineWidth;
        myYEnd = height() - myGridLineWidth;
    
        // The drawing order cannot be changed,
        // because there are dependencies between them
        drawTitle(painter);
        drawLabels(painter);
        drawGridLine(painter);
        drawLine(painter);
    }
    
    void DataPaintedItem::appendPoint(int y)
    {
        if (myPointCount <= 0) {
            return;
        }
        if (myPoints.size() < myPointCount) {
            myCurrentPointIndex = 0;
            myPoints.push_back(QPointF{static_cast<double>(myPoints.size()), static_cast<double>(y)});
        } else {
            myPoints[myCurrentPointIndex].setY(y);
            ++myCurrentPointIndex;
            myCurrentPointIndex = myCurrentPointIndex % myPoints.size();
        }
    
        update();
    }
    
    int DataPaintedItem::getYMin() const
    {
        return myYMinValue;
    }
    
    void DataPaintedItem::setYMin(int value)
    {
        if (myYMinValue == value) {
            return;
        }
        myYMinValue = value;
        emit yMinChanged();
        update();
    }
    
    int DataPaintedItem::getYMax() const
    {
        return myYMaxValue;
    }
    
    void DataPaintedItem::setYMax(int value)
    {
        if (myYMaxValue == value) {
            return;
        }
        myYMaxValue = value;
        emit yMaxChanged();
        update();
    }
    
    int DataPaintedItem::getYTickCount() const
    {
        return myYTickCount;
    }
    
    void DataPaintedItem::setYTickCount(int tickCount)
    {
        if (myYTickCount == tickCount) {
            return;
        }
        if (tickCount <= 0) {
            return;
        }
        myYTickCount = tickCount;
        emit yTickCountChanged();
    }
    
    bool DataPaintedItem::getYLineVisible() const
    {
        return myYLineVisible;
    }
    
    void DataPaintedItem::setYLineVisible(bool newYLineVisible)
    {
        if (myYLineVisible == newYLineVisible) {
            return;
        }
        myYLineVisible = newYLineVisible;
        emit yLineVisibleChanged();
    }
    
    bool DataPaintedItem::getXLineVisible() const
    {
        return myXLineVisible;
    }
    
    void DataPaintedItem::setXLineVisible(bool newXLineVisible)
    {
        if (myXLineVisible == newXLineVisible) {
            return;
        }
        myXLineVisible = newXLineVisible;
        emit xLineVisibleChanged();
    }
    
    int DataPaintedItem::getXTickCount() const
    {
        return myXTickCount;
    }
    
    void DataPaintedItem::setXTickCount(int tickCount)
    {
        if (myXTickCount == tickCount) {
            return;
        }
        if (tickCount <= 0) {
            return;
        }
        myXTickCount = tickCount;
        emit xTickCountChanged();
    }
    
    int DataPaintedItem::getPointCount() const
    {
        return myPointCount;
    }
    
    void DataPaintedItem::setPointCount(int pointCount)
    {
        if (myPointCount == pointCount) {
            return;
        }
        if (pointCount <= 0) {
            return;
        }
        myPointCount = pointCount;
    
        // remove old point(s)
        myCurrentPointIndex = 0;
        if (myPoints.size() > pointCount) {
            myPoints.erase(myPoints.begin(), myPoints.begin() + (myPoints.size() - pointCount));
        }
    
        emit pointCountChanged();
        update();
    }
    
    int DataPaintedItem::getSpaceCount() const
    {
        return mySpaceCount;
    }
    
    void DataPaintedItem::setSpaceCount(int spaceCount)
    {
        if (mySpaceCount == spaceCount) {
            return;
        }
        if (spaceCount < 0) {
            return;
        }
        mySpaceCount = spaceCount;
        emit spaceCountChanged();
    }
    
    const QList<QPointF>& DataPaintedItem::getPoints() const
    {
        return myPoints;
    }
    
    void DataPaintedItem::setPoints(const QList<QPointF>& points)
    {
        if (myPoints == points) {
            return;
        }
        myPoints = points;
        update();
    }
    
    const QString& DataPaintedItem::getTitle() const
    {
        return myTitle;
    }
    
    void DataPaintedItem::setTitle(const QString& title)
    {
        if (myTitle == title) {
            return;
        }
        myTitle = title;
        emit titleChanged();
        update();
    }
    
    const QFont& DataPaintedItem::getTitleFont() const
    {
        return myTitleFont;
    }
    
    void DataPaintedItem::setTitleFont(const QFont& font)
    {
        if (myTitleFont == font)
            return;
        myTitleFont = font;
        emit titleFontChanged();
    }
    
    const QColor& DataPaintedItem::getTitleColor() const
    {
        return myTitleColor;
    }
    
    void DataPaintedItem::setTitleColor(const QColor& color)
    {
        if (myTitleColor == color) {
            return;
        }
        myTitleColor = color;
        emit titleColorChanged();
        update();
    }
    
    const QColor& DataPaintedItem::getLineColor() const
    {
        return myLineColor;
    }
    
    void DataPaintedItem::setLineColor(const QColor& newLineColor)
    {
        if (myLineColor == newLineColor) {
            return;
        }
        myLineColor = newLineColor;
        emit lineColorChanged();
    }
    
    const QColor& DataPaintedItem::getGridLineColor() const
    {
        return myGridLineColor;
    }
    
    void DataPaintedItem::setGridLineColor(const QColor& newGridLineColor)
    {
        if (myGridLineColor == newGridLineColor)
            return;
        myGridLineColor = newGridLineColor;
        emit gridLineColorChanged();
    }
    
    const QFont& DataPaintedItem::getLineFont() const
    {
        return myLineFont;
    }
    
    void DataPaintedItem::setLineFont(const QFont& newLineFont)
    {
        if (myLineFont == newLineFont) {
            return;
        }
        myLineFont = newLineFont;
        emit lineFontChanged();
    }
    
    const QFont& DataPaintedItem::getGridLineFont() const
    {
        return myGridLineFont;
    }
    
    void DataPaintedItem::setGridLineFont(const QFont& newGridLineFont)
    {
        if (myGridLineFont == newGridLineFont) {
            return;
        }
        myGridLineFont = newGridLineFont;
        emit gridLineFontChanged();
    }
    
    int DataPaintedItem::getGridLineWidth() const
    {
        return myGridLineWidth;
    }
    
    void DataPaintedItem::setGridLineWidth(int newGridLineWidth)
    {
        if (myGridLineWidth == newGridLineWidth) {
            return;
        }
        myGridLineWidth = newGridLineWidth;
        emit gridLineWidthChanged();
    }
    
    int DataPaintedItem::getLineWidth() const
    {
        return myLineWidth;
    }
    
    void DataPaintedItem::setLineWidth(int newLineWidth)
    {
        if (myLineWidth == newLineWidth) {
            return;
        }
        myLineWidth = newLineWidth;
        emit lineWidthChanged();
    }
    
    
    bool DataPaintedItem::getLabelsVisible() const
    {
        return myLabelsVisible;
    }
    
    void DataPaintedItem::setLabelsVisible(bool newLabelsVisible)
    {
        if (myLabelsVisible == newLabelsVisible) {
            return;
        }
        myLabelsVisible = newLabelsVisible;
        emit labelsVisibleChanged();
    }
    
    const QFont& DataPaintedItem::getLabelsFont() const
    {
        return myLabelsFont;
    }
    
    void DataPaintedItem::setLabelsFont(const QFont& newLabelsFont)
    {
        if (myLabelsFont == newLabelsFont) {
            return;
        }
        myLabelsFont = newLabelsFont;
        emit labelsFontChanged();
    }
    
    const QColor& DataPaintedItem::getLabelsColor() const
    {
        return myLabelsColor;
    }
    
    void DataPaintedItem::setLabelsColor(const QColor& newLabelsColor)
    {
        if (myLabelsColor == newLabelsColor) {
            return;
        }
        myLabelsColor = newLabelsColor;
        emit labelsColorChanged();
    }
    
    void DataPaintedItem::drawTitle(QPainter* painter)
    {
        if (!myTitle.isEmpty()) {
            QFontMetrics metrics(myTitleFont, painter->device());
            int titleHeight = metrics.height() + 10;
            myYStart += titleHeight;
    
            painter->save();
            QPen pen = painter->pen();
            pen.setColor(myTitleColor);
            painter->setPen(pen);
            painter->setFont(myTitleFont);
            painter->drawText(0, 0, width(), titleHeight, Qt::AlignCenter, myTitle);
            painter->restore();
        }
    }
    
    void DataPaintedItem::drawLabels(QPainter* painter)
    {
        if (myLabelsVisible) {
            painter->save();
            QPen pen = painter->pen();
            pen.setColor(myLabelsColor);
            painter->setPen(pen);
            painter->setFont(myLabelsFont);
            const QRect rc = painter->boundingRect(myXStart, myYStart, myXEnd - myXStart, myYEnd - myYStart
                                                   , Qt::AlignLeft, QString::number(myYMaxValue) + "W");
    
            auto fnDrawLabel = [&](const QRect& rc, int val) {
                painter->drawText(rc, Qt::AlignCenter, QString::number(val));
            };
    
            QRect labelRect{myXStart, myYStart, rc.width(), rc.height()};
            fnDrawLabel(labelRect, myYMaxValue);
    
            int valueSpace = (myYMaxValue - myYMinValue) / myYTickCount;
            int curValue = myYMaxValue - valueSpace;
            int yLineSpace = (myYEnd - myYStart) / myYTickCount;
            for (int i = 1; i < myYTickCount; ++i) {
                labelRect.setY(myYStart + i * yLineSpace * 2 - rc.height());
                fnDrawLabel(labelRect, curValue);
                curValue -= valueSpace;
            }
    
            labelRect.setRect(myXStart, myYEnd - rc.height(), rc.width(), rc.height());
            fnDrawLabel(labelRect, myYMinValue);
    
            myXStart += rc.width();
            painter->restore();
        }
    }
    
    void DataPaintedItem::drawGridLine(QPainter* painter)
    {
        QPainterPath linePath;
        if (myXLineVisible) {
            int xLineSpace = (myXEnd - myXStart) / myXTickCount;
            for (int i = 1; i < myXTickCount; ++i) {
                int xPos = myXStart + xLineSpace * i;
                linePath.moveTo(xPos, myYStart);
                linePath.lineTo(xPos, myYEnd);
            }
        }
        if (myYLineVisible) {
            int yLineSpace = (myYEnd - myYStart) / myYTickCount;
            for (int i = 1; i < myYTickCount; ++i) {
                int yPos = myYStart + yLineSpace * i;
                linePath.moveTo(myXStart, yPos);
                linePath.lineTo(myXEnd, yPos);
            }
        }
        if (!linePath.isEmpty()) {
            painter->save();
            QPen pen = painter->pen();
            pen.setColor(myGridLineColor);
            pen.setStyle(Qt::CustomDashLine);
            pen.setDashPattern(QVector<qreal>{8, 10});
            pen.setWidth(myGridLineWidth);
            painter->setPen(pen);
            painter->drawPath(linePath);
            painter->setFont(myGridLineFont);
            painter->restore();
        }
    }
    
    void DataPaintedItem::drawLine(QPainter* painter)
    {
        if (!myPoints.isEmpty()) {
            QPainterPath ptPath;
            ptPath.moveTo(transformPoint(myPoints.at(0)));
    
            if (myCurrentPointIndex > 0) {
                int i = 0;
                for (; i < myCurrentPointIndex; ++i) {
                    ptPath.lineTo(transformPoint(myPoints.at(i)));
                }
    
                i = myCurrentPointIndex + mySpaceCount;
                if (i < myPoints.size()) {
                    ptPath.moveTo(transformPoint(myPoints.at(i)));
                    for (; i < myPoints.size(); ++i) {
                        ptPath.lineTo(transformPoint(myPoints.at(i)));
                    }
                }
            } else {
                for (int i = 1; i < myPoints.size(); ++i) {
                    ptPath.lineTo(transformPoint(myPoints.at(i)));
                }
            }
    
            painter->save();
            painter->setRenderHint(QPainter::Antialiasing, true);
            QPen pen = painter->pen();
            pen.setColor(myLineColor);
            pen.setWidth(myLineWidth);
            painter->setPen(pen);
            painter->drawPath(ptPath);
            painter->setFont(myLineFont);
            painter->restore();
        }
    }
    
    QPointF DataPaintedItem::transformPoint(const QPointF& pt) const
    {
        int pointCount = myPointCount > 0 ? myPointCount : 1;
        int w = myXEnd - myXStart;
        auto x = pt.x() * w / pointCount + myXStart;
    
        int h = myYEnd - myYStart;
        auto y = h - pt.y() * h / (myYMaxValue - myYMinValue) + myYStart;
        if (y < myYStart) {
            y = myYStart;
        } else if (y > myYEnd) {
            y = myYEnd;
        }
    
        return QPointF{x, y};
    }

    完整源码下载链接

    https://download.csdn.net/download/DIYsoul/20901645

    代码养活自己
  • 相关阅读:
    Oracle 19.3 RAC on Redhat 7.6 安装最佳实践
    宝宝换牙期 需要注意这几点
    人生与机会
    历届「Jolt Awards」获奖书籍
    语言包缩写
    用XMLTask操作XML
    clearcase命令(转)
    【ZT】我家宝宝不会哭分享在美国养孩子的妈妈经(必看)
    Ubuntu启动问题以及Grub Rescue修复方法
    几个远程桌面客户端
  • 原文地址:https://www.cnblogs.com/diysoul/p/15114350.html
Copyright © 2020-2023  润新知