There are a lot of different ways to create an areachart in Qt/Qml. But in this method I have tried to create an areachart using QPainter class.

Why I chose this method? There are 2 different reasons,

  • I’m working in a project that was already created as a QGuiApplication. When I explored the possibility are creating charts inside Qml, I found that I require QtCharts module and it works only with QApplication instead of QGuiApplication. For various reasons, I didn’t want to change it to QApplication. This is what the Qt documentation said,

Note: Projects created with Qt Creator’s Qt Quick Application wizard are based on the Qt Quick 2 template that uses QGuiApplication by default. All such QGuiApplication instances in the project must be replaced with QApplication as the module depends on Qt’s Graphics View Framework for rendering.”

  • This method doesn’t even require QtCharts module. Which means I don’t need the QtCharts library when I deploy my application.

QPainter is a simple class that provides a lot of functions to draw and paint shapes and lines. So here instead of plotting a chart, I’m just painting the chart. Does it make sense? If not, don’t worry. You will understand how, when looking at the code.

Here is a video on how it looks like,

Now let us look at the source code. For simplicity I have removed the x-y axis path plotting in paint() function of areachart.cpp and how the chart is refreshed every few milliseconds in main.cpp. Here is an idea of what is done.

  1. The Areachart class inherits QQuickPaintedItem class. We use the paint() function to define and draw the path based on the vector array points.
  2. The Trick behind: It is important to keep the path’s first y-point and last y-point to fall back to the x-axis (200 pixels in this example) or else it will no way look like an areachart !!!
  3. Then the areachart class is registered to the QML system using <qmlRegisterType> in main.cpp
  4. The registered class is instantiated inside main.qml as a QML element.
// areachart.h

#ifndef AREACHART_H
#define AREACHART_H
#include <QtQuick/QQuickPaintedItem>
class AreaChart : public QQuickPaintedItem
{
    Q_OBJECT
    Q_PROPERTY(qreal chdata READ chdata WRITE setchdata NOTIFY chdataChanged)
public:
    AreaChart(QQuickItem *parent = 0);

    qreal chdata() const;
    void setchdata(const qreal &chdata);

    void paint(QPainter *painter);
signals:
    void chdataChanged();
private:
    qreal m_chdata;
};
#endif 
// areachart.cpp

#include "areachart.h"
#include <QPainter>
using namespace std;

vector<int> xpos({0,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100,105,110,115,120,125,130,135,140,145,150,155,160,165,170,175,180,185,190,195});
vector<int> ypos({140,150,140,170,160,160,170,190,220,140,150,150,190,170,160,180,160,150,170,180,140,150,130,170,140,150,170,190,180,160,150,150,190,150,170,180,160,150,170,200});

#define X_SHIFT  20
#define X_AXIS_YCOR 200

AreaChart::AreaChart(QQuickItem *parent)
    : QQuickPaintedItem(parent)
{
    m_chdata = 0;
}

qreal AreaChart::chdata() const
{
    return m_chdata;
}

void AreaChart::setchdata(const qreal &chdata)
{
    if(chdata != m_chdata)
    {
        m_chdata = chdata;
        update();
        emit chdataChanged();
    }

}

void AreaChart::paint(QPainter *painter)
{
    std::rotate(ypos.begin(),ypos.begin()+1,ypos.end()-1); /*Rotating the plot points everytime when update() is called.*/

    QPainterPath areapath;

	/* Area chart path*/
    areapath.setFillRule(Qt::OddEvenFill);
    areapath.moveTo(X_SHIFT, X_AXIS_YCOR);	 /*Move to the bottom of the rectangle to start plotting. In qml, chart window is 200,200*/
    for(int i=0; i < xpos.size(); i++)
    {
        areapath.lineTo(xpos.at(i)+X_SHIFT,ypos.at(i));
    }

	/* Painting areachart path*/
    painter->setRenderHints(QPainter::Antialiasing, true);
    painter->setPen(QPen(QColor("pink"), 2, Qt::SolidLine,
                        Qt::RoundCap, Qt::RoundJoin));
    painter->setBrush(QColor("red"));
    painter->setOpacity(0.5);
    painter->drawPath(areapath);
}
// main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtCore>
#include "areachart.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);
    qmlRegisterType<AreaChart>("Charts",1,0,"AreaChart");
    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;
    return app.exec();
}
// main.qml

import QtQuick 2.9
import QtQuick.Window 2.2
import Charts 1.0

Window {
    visible: true
    width: 800
    height: 480
    title: qsTr("Charts")
    color: "black"

    AreaChart{
        id: chrt_areachart
        x:200
        y:140
        width: 220
        height: 230
        chdata: chrtval.value
    }
}

Note: The chart will be plotted only once with the initial data in the array. So even if the x,y points are changed during runtime, it won’t reflect on the chart. If runtime update of the chart is required, any one of the properties of the chart needs to be changed. In this example this is achieved by “m_chdata” which is defined as Q_PROPERTY item which emits a signal everytime its value is changed.

The runtime movement of the chart is done by creating a simple QTimer which simply changes a dummy value which is assigned to the “chdata” inside the QML. This causes the update() function to be triggered from setchdata() where the vector array is rotated and painted again.

And if all these seems to be of adding no value for your use-case, you can simply use QtCharts to plot your charts. 🙂