Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. [QCharts] How to improve the performance of a line chart with a range-changing x-axis?

[QCharts] How to improve the performance of a line chart with a range-changing x-axis?

Scheduled Pinned Locked Moved Unsolved QML and Qt Quick
1 Posts 1 Posters 369 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • M Offline
    M Offline
    mjs225
    wrote on last edited by mjs225
    #1

    I need to display about 10 lines in one chart. Data comes about every 50ms. The x-axis is time, shared by all data. I've followed the oscilloscope example and found the chart very performant with OpenGL enabled. But after some experiments, I find that if the x-axis' range changes when new data is appended, the performance drops a lot. Is there a way to improve the performance of a line chart with a range-changing x-axis?

    The following is an example project based on the oscilloscope example. After pressing the Start button, the line series' data will change every 50ms. On my Windows 10 laptop, the CPU usage increases from 0.8% to 9%, and the power consumption changes from low to high when the fixXAxis box is unchecked.

    # CMakeLists.txt
    cmake_minimum_required(VERSION 3.16)
    project(chart LANGUAGES CXX)
    find_package(Qt6 REQUIRED COMPONENTS Charts Core Gui Qml Quick)
    
    qt_standard_project_setup()
    qt_add_executable(${PROJECT_NAME}
        main.cpp
    )
    target_link_libraries(${PROJECT_NAME} PRIVATE
        Qt::Charts
        Qt::Core
        Qt::Gui
        Qt::Qml
        Qt::Quick
    )
    set(resources_resource_files
        "qml/Main.qml"
        "qml/Chart.qml"
    )
    qt6_add_resources(${PROJECT_NAME} "resources"
        PREFIX
            "/"
        FILES
            ${resources_resource_files}
    )
    
    // main.cpp
    #include <QApplication>
    #include <QDir>
    #include <QLineSeries>
    #include <QQmlContext>
    #include <QQmlEngine>
    #include <QQuickView>
    #include <QTimer>
    #include <QValueAxis>
    #include <memory>
    #include <vector>
    #include <assert.h>
    
    Q_DECLARE_METATYPE(QAbstractSeries *)
    
    struct Backend : QObject {
      Q_OBJECT
      Q_PROPERTY(bool fixXAxis MEMBER fixXAxis NOTIFY fixXAxisChanged)
      // 2 LineSeries in qml ChartView
      std::vector<QXYSeries *> series;
      // common xAxis shared by all series
      QValueAxis *xAxis = nullptr;
    
      std::unique_ptr<QTimer> timer;
    
      bool fixXAxis = false;
      // use a fix-length list if fixXAxis==true
      static constexpr qsizetype listLen = 101;
      double tick = 0;
    signals:
      void fixXAxisChanged(bool);
    
    public:
      Backend() {
        qRegisterMetaType<QAbstractSeries *>();
        connect(this, &Backend::fixXAxisChanged, [this](bool newFixXAxis){
          if(newFixXAxis){
            if(xAxis)
              xAxis->setRange(0, 100);
          }else{
            tick = listLen;
          }
        });
      }
    private:
      void updateSeries() {
        assert(series.size() == 2);
        if (fixXAxis) {
          regenerateData(tick);
        } else {
          series[0]->append(QPointF{tick, sin(tick / 5.0)});
          series[1]->append(QPointF{tick, cos(tick / 5.0)});
          xAxis->setRange(tick - 90, tick + 10); // performance
        }
        tick++;
      }
      void regenerateData(double startPoint) {
        QList<QPointF> data0;
        QList<QPointF> data1;
        data0.reserve(listLen);
        data1.reserve(listLen);
        for (double x = 0; x < listLen; ++x) {
          data0.push_back(QPointF{x, sin((startPoint + x) / 5.0)});
          data1.push_back(QPointF{x, cos((startPoint + x) / 5.0)});
        }
        series[0]->replace(data0);
        series[1]->replace(data1);
      }
    
    public slots:
      void registerSeries(QAbstractSeries *series) {
        auto xySeries = static_cast<QXYSeries *>(series);
        this->series.push_back(xySeries);
        // all registered line series share a common xAxis
        if (xAxis == nullptr)
          xAxis = static_cast<QValueAxis *>(xySeries->attachedAxes()[0]);
      }
      void startTimer() {
        timer = std::make_unique<QTimer>();
        connect(timer.get(), &QTimer::timeout, [this] { updateSeries(); });
        timer->start(50);
      }
    };
    
    int main(int argc, char *argv[]) {
      QApplication app(argc, argv);
      // Set QSG_RHI_BACKEND=opengl environment variable to force the OpenGL backend to be used
      const bool openGLSupported =
          QQuickWindow::graphicsApi() == QSGRendererInterface::OpenGLRhi;
      assert(openGLSupported);
      Backend backend;
      QQuickView viewer;
    #ifdef Q_OS_WIN
      QString extraImportPath(QStringLiteral("%1/../../../../%2"));
    #else
      QString extraImportPath(QStringLiteral("%1/../../../%2"));
    #endif
      viewer.engine()->addImportPath(extraImportPath.arg(
          QGuiApplication::applicationDirPath(), QString::fromLatin1("qml")));
      QObject::connect(viewer.engine(), &QQmlEngine::quit, &viewer,
                       &QWindow::close);
    
      viewer.rootContext()->setContextProperty("backend", &backend);
    
      viewer.setSource(QUrl("qrc:/qml/Main.qml"));
      viewer.setResizeMode(QQuickView::SizeRootObjectToView);
      viewer.show();
      return app.exec();
    }
    #include "main.moc"
    
    // qml/Main.qml
    import QtQuick
    import QtQuick.Controls
    Item{
        id: main
        width: 600
        height: 400
    
        Button {
            id: btn
            text: "Start"
            onClicked: chart.registerAndStart()
        }
        CheckBox {
            id: ckBox
            text: "FixXAxis"
            checked: backend.fixXAxis
            anchors.top: btn.bottom
            onClicked: backend.fixXAxis = !backend.fixXAxis
        }
        Text {
            text: "opengl: "+ (chart.openGL ? "y" : "n")
            anchors.top: ckBox.bottom
        }
        Chart {
            id: chart
            anchors.top: parent.top
            anchors.bottom: parent.bottom
            anchors.right: parent.right
            anchors.left: ckBox.right
            height: main.height
        }
    }
    
    // qml/Chart.qml
    import QtQuick
    import QtCharts
    
    ChartView {
        id: chartView
        property bool openGL: true
        animationOptions: ChartView.NoAnimation
        theme: ChartView.ChartThemeDark
    
        ValueAxis {
            id: axisY1
            min: -2
            max: 4
        }
        ValueAxis {
            id: axisY2
            min: -4
            max: 2
        }
        ValueAxis {
            id: axisX
            min: 0
            max: 100
        }
        LineSeries {
            id: lineSeries1
            name: "signal 1"
            axisX: axisX
            axisY: axisY1
            useOpenGL: chartView.openGL
        }
        LineSeries {
            id: lineSeries2
            name: "signal 2"
            axisX: axisX
            axisYRight: axisY2
            useOpenGL: chartView.openGL
        }
        function registerAndStart(){
            backend.registerSeries(chartView.series(0))
            backend.registerSeries(chartView.series(1))
            backend.startTimer()
        }
    }
    
    1 Reply Last reply
    0

    • Login

    • Login or register to search.
    • First post
      Last post
    0
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    • Get Qt Extensions
    • Unsolved