This is a text-only version of the following page on https://raymii.org:
---
Title       : 	Drawing a Circle in Qt QML three different ways
Author      : 	Remy van Elst
Date        : 	05-07-2023 23:59
URL         : 	https://raymii.org/s/articles/Drawing_a_Circle_in_Qt_QML_three_different_ways.html
Format      : 	Markdown/HTML
---



Qt has no `Circle` built in to QML as a basic type, as for example the `Rectangle` or the `Button` control. This post shows you how to get a `Circle` in QML, from the most basic method (a `Rectangle` with a `radius` of 180) to more advanced methods, using the `Canvas` JavaScript API (which allows us to draw a partially filled Circle, for a Pie Chart) and a `c++` control based on `QQuickPaintedItem`. I wanted to experiment with the `Canvas` QML control and the `QQuickPaintedItem` C++ interface to get a better understanding of Qt and QML drawing interfaces, this post reflects that journey including showing your grouped QML properties exposed from C++.

<p class="ad"> <b>Recently I removed all Google Ads from this site due to their invasive tracking, as well as Google Analytics. Please, if you found this content useful, consider a small donation using any of the options below:</b><br><br> <a href="https://leafnode.nl">I'm developing an open source monitoring app called  Leaf Node Monitoring, for windows, linux & android. Go check it out!</a><br><br> <a href="https://github.com/sponsors/RaymiiOrg/">Consider sponsoring me on Github. It means the world to me if you show your appreciation and you'll help pay the server costs.</a><br><br> <a href="https://www.digitalocean.com/?refcode=7435ae6b8212">You can also sponsor me by getting a Digital Ocean VPS. With this referral link you'll get $100 credit for 60 days. </a><br><br> </p>



With all the controls in it, the program looks like:


![qml_circle_all.gif][1]


In this post I'm going to show you QML Profiler screenshots. These are shown as
a comparison between the 3 methods on my specific machine. Don't treat them as
a benchmark, do that yourself if you experience performance issues.

The QML program has a grid with all the controls in it. Per section I'm
commenting out all but one and then taking a screenshot. Without any controls,
so just the grid, the QML profiler looks like this:

![QML profiler empty][3]



This article also shows how to expose a [Grouped QML Property][4]. This allows
you to set  `border.width` and `border.color` in your custom C++ exposed QML
control.

For this example I'm using Qt 5.15, the code also works with Qt 6.4. 

### QML Rectangle Circle

The simplest and most basic method to get a `Circle` in QML is a `Rectangle`
with a `radius` property set to `width / 2` or `180`:


    Rectangle {
       width: 150
       height: 150
       color: "dodgerblue"
       radius: 180
    }


Which looks like:

![rectangle circle][6]


The QML Profiler shows that this is quite fast:

![rectangle circle profiler][7]


The rectangle drawing method has a few advantages, namely that you get a
`border`, a fill color and all the other advantages that this QML Control gives
you. Later on in the `C++` example you'll see that we need to provide that all
ourselves.



### QML Canvas Circle
  
Most of the examples you'll find online regarding a Circle in QML refer to the
`Canvas` or `Shapes` API. The `Canvas` API is a JavaScript way to draw stuff on
screen. The disadvantage is that this requires a lot more memory and resources,
but you do get a lot of flexibility.

My `QMLCircle.qml` file is pasted at the end of this post and supports being
used as a Pie chart. One section is filled in with one color and a different
section is filled with another color, including an `Animation` so that it looks
nice. Useful for a `Chart`-like graphics.

The basic circle looks the same as the `Rectangle`-with-radius circle:


    QMLCircle {
      primaryColor: "skyblue"
    }


![qml circle][8]

The QML profiler shows that a lot more is happening:

![qml circle profiler][9]

Is this slower? Probably, and almost certainly on embedded deviced

When using the 'pie-chart' feature it looks like this:

    QMLCircle {
        primaryColor: "skyblue"
        secondaryColor: "tomato"
        value: 0.87
    }


![qml circle filled][10]


Using it combined with the `Animation` is also quite cool:

![qml_circle filled.gif][11]


    property int timerDuration: 10
    property int timerSecDone: 0  

    QMLCircle {
      value: (timerSecDone * 100 / timerDuration) / 100
      primaryColor: "skyblue"
      secondaryColor: "tomato"
    }

    Timer {
      interval: 1000
      running: true
      triggeredOnStart: true
      repeat: true
      onTriggered: {
        timerSecDone++
        if (timerSecDone > timerDuration+1) {
          timerSecDone = 0;
        }
      }
    }


Using `Canvas` gives you a bunch of flexibility, this `Animation` was really
easy and quick to add. (Especially in comparison to the C++ style in the next
section).  In one of the Coffee Machines we make at work almost this exact code
is used in one of the UI's to show a progress bar of the consumption status. It
includes a bit more 'fancyness', styling, animation, but it boils down to the
same code. I know because I wrote it.

### C++ QML Circle

The last example I want to show is a C++ based `Circle`. It uses the Qt
[Drawing API][5] 

I've coded up a basic `QQuickPaintedItem` which draws a Circle. It has two
properties, `color` and `antialiassing`. The latter is to make it look smooth,
the first is for the fill color. It looks like this:

    CppCircle {
      width: 150
      height: 150
      color: "greenyellow"
      antialiasing: true
      border.color: "black"
      border.width: 1
    }

    CppCircle {
      width: 150
      height: 150
      color: "hotpink"
      antialiasing: false
      border.color: "black"
      border.width: 5
    }

![cpp circle][12]

The second (`hotpink`) circle is not anti-aliassed. The basic code is a class derived from `QQuickPaintedItem`. The most important method is the `paint` method:


    void CppCircle::paint(QPainter *painter)
    {
        // make it smooth
        if(antialiasing())
            painter->setRenderHint(QPainter::Antialiasing);

        // create rect which will be used to draw circle in
        QRectF rect(0 + border()->width(), 0 + border()->width(), width() - 1 - (border()->width()*2), height() - 1 - (border()->width()*2));

        // create brush based on QML color property
        QBrush brush(m_color);
        // use brush to fill figures
        painter->setBrush(brush);


        // create pen
        QPen pen;
        if(border()->width() > 0) {
            pen.setBrush(border()->color());
            pen.setWidth(border()->width());
            pen.setStyle(Qt::SolidLine);
        }
        else {
            pen.setStyle(Qt::NoPen);
        }
        painter->setPen(pen);


        // Draw the circle
        painter->drawEllipse(rect);
    }


The full code is at the end of the article but the gist should be clear. A `Pen`
is used to draw lines and outlines, a `Brush` is used to fill figures. The full
code  at the end of this page shows `border` as a [QML Property Group][4] so
that I can set `border.width` and `border.color`. 

The QML profiler seems to show that this custom control is even faster than the
`Rectangle` (which was  `28 microseconds` compared to `21`): 

![cpp circle profiler][13]


The C++ method is the fastest of the bunch, but it is also the most limited.
Adding the `border` property took way more time than the `Animation` in the 
Javascript QML Control, so this is a tradeoff you need to make for yourself.



### Full source code


Here below you'll find the full source code for the program. First an animated
gif of the full program. Any artifacts or stuttering are caused by the
recording software. It's super smooth on my local machine.


![qml_circle_all.gif][1]



#### main.qml

    /*
     * Copyright (c) 2023 Remy van Elst
     *
     * This program is free software: you can redistribute it and/or modify
     * it under the terms of the GNU General Public License as published by
     * the Free Software Foundation, version 3.
     *
     * This program is distributed in the hope that it will be useful, but
     * WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
     * General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with this program. If not, see <http://www.gnu.org/licenses/>.
     *
     */


    import QtQuick 2.15
    import QtQuick.Controls 2.15
    import QtQuick.Window 2.15
    import org.raymii.shapes 1.0

    Window {
        width: 640
        height: 480
        visible: true
        title: qsTr("QML Circles demo by Raymii.org")
        
        property int timerDuration: 10
        property int timerSecDone: 0
        
        
        Grid {
            anchors.fill: parent
            anchors.margins: 10
            spacing: 10
            columns: 3
            rows: 3
            
            Rectangle {
                id: rectCircle
                width: 150
                height: 150
                color: "dodgerblue"
                radius: 180
            }
            
            QMLCircle {
                primaryColor: "skyblue"
                secondaryColor: "tomato"
                value: 0.87
            }
            
            CppCircle {
                width: 150
                height: 150
                color: "greenyellow"
                antialiasing: true
                border.color: "black"
                border.width: 1
            }
            
            CppCircle {
                width: 150
                height: 150
                color: "hotpink"
                antialiasing: false
                border.color: "black"
                border.width: 5
            }
            
            
            
            QMLCircle {
                value: (timerSecDone * 100 / timerDuration) / 100
                primaryColor: "skyblue"
                secondaryColor: "tomato"
            }
            
            QMLCircle {
                value: 0.15
            }
        }
        
        
        Timer {
            interval: 1000
            running: true
            triggeredOnStart: true
            repeat: true
            onTriggered: {
                timerSecDone++
                if (timerSecDone > timerDuration+1) {
                    timerSecDone = 0;
                }
            }
        }   
    }


#### main.cpp


    /*
     * Copyright (c) 2023 Remy van Elst
     *
     * This program is free software: you can redistribute it and/or modify
     * it under the terms of the GNU General Public License as published by
     * the Free Software Foundation, version 3.
     *
     * This program is distributed in the hope that it will be useful, but
     * WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
     * General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with this program. If not, see <http://www.gnu.org/licenses/>.
     *
     */
    #include "cppcircle.h"

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>


    int main(int argc, char *argv[])
    {
    #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    #endif
        QGuiApplication app(argc, argv);

        QQmlApplicationEngine engine;

        qmlRegisterType<CppCircle>("org.raymii.shapes", 1, 0, "CppCircle");
        // You MUST make this type know to QML otherwise you'll receive an error:
        // Invalid grouped property access: Property "border" with type "BorderGroupedProperty*", which is not a value type
        qmlRegisterType<BorderGroupedProperty>("org.raymii.shapes", 1, 0, "BorderGroupedProperty");

        const QUrl url(QStringLiteral("qrc:/main.qml"));
        QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
            &app, [url](QObject *obj, const QUrl &objUrl) {
                if (!obj && url == objUrl)
                    QCoreApplication::exit(-1);
            }, Qt::QueuedConnection);
        engine.load(url);
        return app.exec();
    }



#### cppCircle.h


    /*
     * Copyright (c) 2023 Remy van Elst
     *
     * This program is free software: you can redistribute it and/or modify
     * it under the terms of the GNU General Public License as published by
     * the Free Software Foundation, version 3.
     *
     * This program is distributed in the hope that it will be useful, but
     * WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
     * General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with this program. If not, see <http://www.gnu.org/licenses/>.
     *
     */

    #pragma once

    #include <QObject>
    #include <QQuickPaintedItem>
    #include <QPainter>


    class BorderGroupedProperty : public QObject
    {
        Q_OBJECT
        Q_PROPERTY(int width READ width WRITE setWidth NOTIFY widthChanged)
        Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
    public:
        BorderGroupedProperty(QObject* parent = nullptr);
        int width() const;
        void setWidth(int newWidth);
        QColor color() const;
        void setColor(const QColor &newColor);

    signals:
        void widthChanged();
        void colorChanged();

    private:
        int m_width = 0;
        QColor m_color = QColor(0,0,0);
    };



    class CppCircle : public QQuickPaintedItem
    {
        Q_OBJECT
        Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
        Q_PROPERTY(bool antialiasing READ antialiasing WRITE setAntialiasing NOTIFY antialiasingChanged)
        Q_PROPERTY(BorderGroupedProperty* border READ border)

    public:
        explicit CppCircle(QQuickItem *parent = nullptr);
        virtual void paint(QPainter *painter);
        QColor color() const;
        void setColor(const QColor &newColor);
        bool antialiasing() const;
        void setAntialiasing(bool newAntialiasing);
        BorderGroupedProperty *border() const;

    signals:
        void colorChanged();
        void antialiasingChanged();

    private:
        QColor m_color;
        bool m_antialiasing;
        BorderGroupedProperty *m_border = nullptr;
    };


#### cppCircle.cpp

    
    /*
     * Copyright (c) 2023 Remy van Elst
     *
     * This program is free software: you can redistribute it and/or modify
     * it under the terms of the GNU General Public License as published by
     * the Free Software Foundation, version 3.
     *
     * This program is distributed in the hope that it will be useful, but
     * WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
     * General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with this program. If not, see <http://www.gnu.org/licenses/>.
     *
     */

    #include "cppcircle.h"
    #include <QPen>

    CppCircle::CppCircle(QQuickItem *parent)
        : QQuickPaintedItem{parent}, m_border(new BorderGroupedProperty(this))
    {

    }


    QColor CppCircle::color() const
    {
        return m_color;
    }

    void CppCircle::setColor(const QColor &newColor)
    {
        if (m_color == newColor)
            return;
        m_color = newColor;
        emit colorChanged();
    }


    bool CppCircle::antialiasing() const
    {
        return m_antialiasing;
    }

    void CppCircle::setAntialiasing(bool newAntialiasing)
    {
        if (m_antialiasing == newAntialiasing)
            return;
        m_antialiasing = newAntialiasing;
        emit antialiasingChanged();
    }


    void CppCircle::paint(QPainter *painter)
    {
        // make it smooth
        if(antialiasing())
            painter->setRenderHint(QPainter::Antialiasing);

        // create rect which will be used to draw circle in
        QRectF rect(0 + border()->width(), 0 + border()->width(), width() - 1 - (border()->width()*2), height() - 1 - (border()->width()*2));

        // create brush based on QML color property
        QBrush brush(m_color);
        // use brush to fill figures
        painter->setBrush(brush);


        // create pen
        QPen pen;
        if(border()->width() > 0) {
            pen.setBrush(border()->color());
            pen.setWidth(border()->width());
            pen.setStyle(Qt::SolidLine);
        }
        else {
            pen.setStyle(Qt::NoPen);
        }
        painter->setPen(pen);


        // Draw the circle
        painter->drawEllipse(rect);
    }



    BorderGroupedProperty::BorderGroupedProperty(QObject *parent) : QObject(parent)
    {

    }

    int BorderGroupedProperty::width() const
    {
        return m_width;
    }

    void BorderGroupedProperty::setWidth(int newWidth)
    {
        if (m_width == newWidth)
            return;
        m_width = newWidth;
        emit widthChanged();
    }

    QColor BorderGroupedProperty::color() const
    {
        return m_color;
    }

    void BorderGroupedProperty::setColor(const QColor &newColor)
    {
        if (m_color == newColor)
            return;
        m_color = newColor;
        emit colorChanged();
    }

    BorderGroupedProperty *CppCircle::border() const
    {
        return m_border;
    }



#### QMLCircle.qml 


    
    /*
     * Copyright (c) 2023 Remy van Elst
     *
     * This program is free software: you can redistribute it and/or modify
     * it under the terms of the GNU General Public License as published by
     * the Free Software Foundation, version 3.
     *
     * This program is distributed in the hope that it will be useful, but
     * WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
     * General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with this program. If not, see <http://www.gnu.org/licenses/>.
     *
     */

    import QtQuick 2.15

    Item {
        id: root
        property int size: 150
        property real value: 0
        property color primaryColor: "#ff6725"
        property color secondaryColor: "#52adff"
        property int animationTime: 1000
        width: size
        height: size

        onValueChanged: c.degree = value * 360

        Canvas {
            id: c
            property real degree: 0

            anchors.fill: parent
            antialiasing: true
            onDegreeChanged: requestPaint()

            onPaint: {
                var ctx = getContext("2d");

                var x = root.width / 2;
                var y = root.height / 2;

                var radius = root.size / 2
                var startAngle = (Math.PI / 180) * 270;
                var fullAngle = (Math.PI / 180) * (270 + 360);
                var progressAngle = (Math.PI / 180) * (270 + degree);

                ctx.reset()

                ctx.fillStyle = root.secondaryColor;
                ctx.beginPath();
                ctx.moveTo(x,y);
                ctx.arc(x, y, radius-1, startAngle, fullAngle);
                ctx.lineTo(x, y)
                ctx.fill();

                ctx.fillStyle = root.primaryColor;
                ctx.beginPath();
                ctx.moveTo(x,y);
                ctx.arc(x, y, radius, startAngle, progressAngle);
                ctx.lineTo(x, y)
                ctx.fill();
            }

            Behavior on degree {
                NumberAnimation {
                    duration: root.animationTime
                }
            }
        }
    }




[1]: /s/inc/img/qml-circle-1.gif
[2]: /s/inc/img/qml_circle_2.png
[3]: /s/inc/img/qml_circle_3.png
[4]: https://web.archive.org/web/20230705195243/https://doc.qt.io/qt-5/qtqml-cppintegration-exposecppattributes.html#grouped-properties
[5]: https://web.archive.org/web/20230705193742/https://doc.qt.io/qt-5/paintsystem-drawing.html
[6]: /s/inc/img/qml_circle_4.png
[7]: /s/inc/img/qml_circle_5.png
[8]: /s/inc/img/qml_circle_6.png
[9]: /s/inc/img/qml_circle_7.png
[10]: /s/inc/img/qml_circle_8.png
[11]: /s/inc/img/qml_circle_9.gif
[12]: /s/inc/img/qml_circle_10.png
[13]: /s/inc/img/qml_circle_11.png

---

License:
All the text on this website is free as in freedom unless stated otherwise. 
This means you can use it in any way you want, you can copy it, change it 
the way you like and republish it, as long as you release the (modified) 
content under the same license to give others the same freedoms you've got 
and place my name and a link to this site with the article as source.

This site uses Google Analytics for statistics and Google Adwords for 
advertisements. You are tracked and Google knows everything about you. 
Use an adblocker like ublock-origin if you don't want it.

All the code on this website is licensed under the GNU GPL v3 license 
unless already licensed under a license which does not allows this form 
of licensing or if another license is stated on that page / in that software:

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

Just to be clear, the information on this website is for meant for educational 
purposes and you use it at your own risk. I do not take responsibility if you 
screw something up. Use common sense, do not 'rm -rf /' as root for example. 
If you have any questions then do not hesitate to contact me.

See https://raymii.org/s/static/About.html for details.