qtqmlqtquick2qtcharts

How do I correctly use QtCharts in a Qt Quick project? (QML works in qmlscene but fails in app)


I'm trying to use QtCharts (specifically ChartView, PieSeries, etc.) in a Qt Quick project using Qt 6.9 with MinGW on Windows.

Everything works perfectly when I run the QML with qmlscene, but as soon as I load a QML file with ChartView in my built app, it crashes at startup or fails to load components like PieSlice.

I’m using qt_add_qml_module() in CMake, I’ve tried windeployqt --qmldir qml, and I'm importing QtCharts in QML. Still, the app crashes or reports:

PieSlice was not found. Did you add all imports and dependencies?

I suspect the issue is related to QML import paths or missing plugin deployment, but I'm not sure what step I'm missing.

I am using qt_add_qml_module() in CMake and have run windeployqt --qmldir qml to make sure all dependencies are bundled. I’m importing QtCharts in my QML code.

I expected the app to launch and display the chart normally, just like it does when I run the QML file with qmlscene.

I double-checked my main.cpp and CMake setup, and everything seems correct. To isolate the issue, I even created a new minimal project with just a ChartView on the main window — it still crashes. But when I open the same QML in qmlscene, it works perfectly.

So I decided to create a brand-new project and just put a minimal QtCharts example directly in the main QML window — nothing else. It crashes again. I then ran the same QML file with qmlscene, and it displayed perfectly fine.

I am at a loss as to what to do next. It seems like QtCharts is available in the environment, but the actual application cannot find or load it at runtime. I'm not sure if this is a problem with import paths, plugin deployment, or something else entirely.

CMakeList.txt:

cmake_minimum_required(VERSION 3.16)

project(rtm_gui_test VERSION 0.1 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(QT_QML_GENERATE_QMLLS_INI ON)

find_package(Qt6 REQUIRED COMPONENTS Quick Charts)

qt_standard_project_setup(REQUIRES 6.8)

qt_add_executable(apprtm_gui_test
    main.cpp
)

qt_add_qml_module(apprtm_gui_test
    URI rtm_gui_test
    VERSION 1.0
    QML_FILES
        Main.qml
)

# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
set_target_properties(apprtm_gui_test PROPERTIES
#    MACOSX_BUNDLE_GUI_IDENTIFIER com.example.apprtm_gui_test
    MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
    MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
    MACOSX_BUNDLE TRUE
    WIN32_EXECUTABLE TRUE
)

target_link_libraries(apprtm_gui_test
    PRIVATE Qt6::Quick
    Qt6::Charts
)

include(GNUInstallDirs)
install(TARGETS apprtm_gui_test
    BUNDLE DESTINATION .
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

Main.qml:

import QtQuick
import QtQuick.Controls
import QtCharts

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    ChartView {
        title: "Line Chart"
        anchors.fill: parent
        antialiasing: true

        LineSeries {
            name: "Line"
            XYPoint { x: 0; y: 0 }
            XYPoint { x: 1.1; y: 2.1 }
            XYPoint { x: 1.9; y: 3.3 }
            XYPoint { x: 2.1; y: 2.1 }
            XYPoint { x: 2.9; y: 4.9 }
            XYPoint { x: 3.4; y: 3.0 }
            XYPoint { x: 4.1; y: 3.3 }
        }
    }
}

main.cpp:

#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    QObject::connect(
        &engine,
        &QQmlApplicationEngine::objectCreationFailed,
        &app,
        []() { QCoreApplication::exit(-1); },
        Qt::QueuedConnection);
    engine.loadFromModule("rtm_gui_test", "Main");

    return app.exec();
}


Solution

  • I'm still working on my project, but I've recently decided to pivot to the QtGraphs module. I'm figuring out the details as I go, but so far, I got a working (ish) solution (So far I am capable of displaying data that is being produced by a C++ module).

    Main.qml:

    import QtQuick 2.15
    import QtQuick.Controls 2.15
    import QtGraphs
    
    
    Window {
        width: 640
        height: 480
        visible: true
        title: qsTr("Graph Viewer")
    
        GraphsView {
            id: graphsView
            anchors.fill: parent
            anchors.margins: 16
    
            theme: GraphsTheme {
                readonly property color c1: "#DBEB00"
                readonly property color c2: "#373F26"
                readonly property color c3: Qt.lighter(c2, 1.5)
                colorScheme: GraphsTheme.ColorScheme.Dark
                seriesColors: ["#2CDE85", "#DBEB00"]
                grid.mainColor: c3
                grid.subColor: c2
                axisX.mainColor: c3
                axisY.mainColor: c3
                axisX.subColor: c2
                axisY.subColor: c2
                axisX.labelTextColor: c1
                axisY.labelTextColor: c1
            }
    
            axisX: ValueAxis {
                max: graphController.rangoSuperiorX
                min: graphController.rangoInferiorX
                tickInterval: 25
                subTickCount: 1
                labelDecimals: 1
            }
    
            axisY: ValueAxis {
                max: graphController.rangoSuperiorY
                min: graphController.rangoInferiorY
                tickInterval: 1
                subTickCount: 10
                labelDecimals: 1
            }
    
            Component {
                id: marker
                Rectangle {
                    width: 8
                    height: 8
                    color: "white"
                    radius: width / 2
                    border.width: 2
                    border.color: "black"
                }
            }
    
            LineSeries {
                id: lineSeries1
                width: 4
                pointDelegate: marker
            }
            
    
            Connections {
                target: graphController
                function onPointAdded(x, y) {
                    lineSeries1.append(x, y)
                }
            }
        }
    }
    

    graphcontroller.cpp:

    #include "graphcontroller.h"
    #include <QtGraphs/QLineSeries>
    #include <QDebug>
    
    
    // testing
    #include <QTimer>
    #include <QtGlobal>  // for qrand()
    #include <QDateTime> // optional: to seed qrand() with time
    
    
    
    /*-------------------------------------------------------------------------------------------------*/
    /* Constructor del controlador del gráfico                                                         */
    /*-------------------------------------------------------------------------------------------------*/
    GraphController::GraphController(QObject* parent,
                                     int max_rango_x,
                                     int min_rango_x,
                                     int max_rango_y,
                                     int min_rango_y)
        : QObject(parent),
        n_datos_almacenados(0),
        m_rango_superior_x(max_rango_x),
        m_rango_inferior_x(min_rango_x),
        m_rango_superior_y(max_rango_y),
        m_rango_inferior_y(min_rango_y),
        valor_maximo_x(min_rango_x),
        valor_minimo_x(min_rango_x),
        valor_maximo_y(min_rango_y),
        valor_minimo_y(min_rango_y)
    {
        // Inicialización opcional
        QTimer* timer = new QTimer(this);
        connect(timer, &QTimer::timeout, this, [this]() {
            static double x = 0;
            double y = rand() % 10;
            this->addPoint(x++, y);
        });
        timer->start(500); // every 500 ms
    
    }
    
    /*-------------------------------------------------------------------------------------------------*/
    /* Getter de la serie interna                                                                      */
    /* Nota: No se utiliza directamente en QML con QtGraphs                                            */
    /*-------------------------------------------------------------------------------------------------*/
    QLineSeries* GraphController::getSeries() {
        return &m_internalSeries;
    }
    
    /*-------------------------------------------------------------------------------------------------*/
    /* Getters y setters para los rangos                                                               */
    /*-------------------------------------------------------------------------------------------------*/
    int GraphController::getRangoSuperiorX()  { return m_rango_superior_x; }
    int GraphController::getRangoInferiorX()  { return m_rango_inferior_x; }
    int GraphController::getRangoSuperiorY()  { return m_rango_superior_y; }
    int GraphController::getRangoInferiorY()  { return m_rango_inferior_y; }
    
    void GraphController::setRangoSuperiorX(int x) {
        if (m_rango_superior_x != x) {
            m_rango_superior_x = x;
            emit rangoXChanged();
        }
    }
    
    void GraphController::setRangoInferiorX(int x) {
        if (m_rango_inferior_x != x) {
            m_rango_inferior_x = x;
            emit rangoXChanged();
        }
    }
    
    void GraphController::setRangoSuperiorY(int y) {
        if (m_rango_superior_y != y) {
            m_rango_superior_y = y;
            emit rangoYChanged();
        }
    }
    
    void GraphController::setRangoInferiorY(int y) {
        if (m_rango_inferior_y != y) {
            m_rango_inferior_y = y;
            emit rangoYChanged();
        }
    }
    
    /*-------------------------------------------------------------------------------------------------*/
    /* Agrega un nuevo punto a la serie interna                                                        */
    /* Emite la señal para que QML lo dibuje en su propio LineSeries                                   */
    /*-------------------------------------------------------------------------------------------------*/
    void GraphController::addPoint(double x, double y) {
        m_internalSeries.append(x, y);
        ++n_datos_almacenados;
    
        emit pointAdded(x, y);
    
        // Update value bounds
        valor_maximo_x = std::max(valor_maximo_x, static_cast<int>(x));
        valor_minimo_x = std::min(valor_minimo_x, static_cast<int>(x));
        valor_maximo_y = std::max(valor_maximo_y, static_cast<int>(y));
        valor_minimo_y = std::min(valor_minimo_y, static_cast<int>(y));
    
    
    }
    
    
    void GraphController::updateRanges(double x, double y) {
        bool rangoXChangedFlag = false;
        bool rangoYChangedFlag = false;
    
        // Update min/max X values
        if (x > valor_maximo_x) {
            valor_maximo_x = x;
            rangoXChangedFlag = true;
        }
        if (x < valor_minimo_x) {
            valor_minimo_x = x;
            rangoXChangedFlag = true;
        }
    
        // Update min/max Y values
        if (y > valor_maximo_y) {
            valor_maximo_y = y;
            rangoYChangedFlag = true;
        }
        if (y < valor_minimo_y) {
            valor_minimo_y = y;
            rangoYChangedFlag = true;
        }
    
        // Update control range if x exceeds current range
        if (x > m_rango_superior_x) {
            m_rango_superior_x = x;
            rangoXChangedFlag = true;
        }
        if (x < m_rango_inferior_x) {
            m_rango_inferior_x = x;
            rangoXChangedFlag = true;
        }
    
        // Emit signals only if needed
        if (rangoXChangedFlag)
            emit rangoXChanged();
        if (rangoYChangedFlag)
            emit rangoYChanged();
    }
    

    graphcontroller.h:

    #ifndef GRAPHCONTROLLER_H
    #define GRAPHCONTROLLER_H
    
    #include <QObject>
    #include <QtGraphs/QLineSeries>
    #include <QQmlEngine>
    
    /*--------------------------------------------------------------------------------------------------------------------*/
    /* Controlador que maneja una serie de datos para representar en QML usando QtGraphs                                  */
    /*--------------------------------------------------------------------------------------------------------------------*/
    
    class GraphController : public QObject {
        Q_OBJECT
    
        Q_PROPERTY(QLineSeries* series READ getSeries CONSTANT)  // Acceso opcional desde QML si se desea leer la serie
    
        Q_PROPERTY(int rangoSuperiorX READ getRangoSuperiorX WRITE setRangoSuperiorX NOTIFY rangoXChanged)
        Q_PROPERTY(int rangoInferiorX READ getRangoInferiorX WRITE setRangoInferiorX NOTIFY rangoXChanged)
        Q_PROPERTY(int rangoSuperiorY READ getRangoSuperiorY WRITE setRangoSuperiorY NOTIFY rangoYChanged)
        Q_PROPERTY(int rangoInferiorY READ getRangoInferiorY WRITE setRangoInferiorY NOTIFY rangoYChanged)
    
        QML_ELEMENT  // Permite usar GraphController directamente en QML (con import path correcto)
    
    public:
        explicit GraphController(QObject* parent = nullptr,
                                 int max_rango_x = 100,
                                 int min_rango_x = 0,
                                 int max_rango_y = 50,
                                 int min_rango_y = 0);
    
        // Métodos públicos
        void addPoint(double x, double y);          // Añade un nuevo punto y emite signal
        void updateRanges(double x, double y);       // Autoajuste de rango si se usa
        void showCompleteGraph();                   // Ajustar los rangos para mostrar toda la grafica
    
        // Getters/setters para Q_PROPERTY
        int getRangoSuperiorX();
        int getRangoInferiorX();
        int getRangoSuperiorY();
        int getRangoInferiorY();
        void setRangoSuperiorX(int x);
        void setRangoInferiorX(int x);
        void setRangoSuperiorY(int y);
        void setRangoInferiorY(int y);
    
        QLineSeries* getSeries();  // Acceso directo a la serie interna (para pruebas o lógica adicional)
    
    signals:
        void pointAdded(double x, double y);  // Señal para conectar a QML y añadir visualmente un nuevo punto
        void rangoXChanged();                 // Notifica a QML que el eje X debe cambiar
        void rangoYChanged();                 // Notifica a QML que el eje Y debe cambiar
    
    private:
        QLineSeries m_internalSeries;  // Serie interna mantenida solo en C++ (no compartida con QML)
        int n_datos_almacenados;
    
        // Rango de ejes (control y ajustes)
        int m_rango_superior_x;
        int m_rango_inferior_x;
        int m_rango_superior_y;
        int m_rango_inferior_y;
    
        // Seguimiento de valores extremos (opcional para autoajuste)
        int valor_maximo_x;
        int valor_minimo_x;
        int valor_maximo_y;
        int valor_minimo_y;
    };
    
    #endif // GRAPHCONTROLLER_H