Draw an arc/circle sector in QML?

23,179

Solution 1

Yes, using Canvas (and Context2D):

import QtQuick 2.3

Rectangle {
    width: 400
    height: 400

    Canvas {
        anchors.fill: parent
        onPaint: {
            var ctx = getContext("2d");
            ctx.reset();

            var centreX = width / 2;
            var centreY = height / 2;

            ctx.beginPath();
            ctx.fillStyle = "black";
            ctx.moveTo(centreX, centreY);
            ctx.arc(centreX, centreY, width / 4, 0, Math.PI * 0.5, false);
            ctx.lineTo(centreX, centreY);
            ctx.fill();

            ctx.beginPath();
            ctx.fillStyle = "red";
            ctx.moveTo(centreX, centreY);
            ctx.arc(centreX, centreY, width / 4, Math.PI * 0.5, Math.PI * 2, false);
            ctx.lineTo(centreX, centreY);
            ctx.fill();
        }
    }
}

I actually took the code for this from this answer, as Qt's Canvas implements the HTML5 Canvas API. This makes it really easy to find examples on the web; just search for "draw pie slice blah html5 canvas", for example.

For the mouse detection, you'll have to brush off your maths skills...

... or just steal the code from here. :)

Note that Canvas only repaints when it's resized, or when requestPaint() is called, so if you want to change the colour of a slice depending on the mouse position, you'll need to call that function to see the colour change.

Solution 2

Draw it using qml, you don't need the canvas. As a guideline I usually go through Qt's examples before deciding on an implementation. The code below can be found in "Shapes" example.

import QtQuick 2.11
import QtQuick.Shapes 1.15

Shape {
    width: 120
    height: 130
    anchors.bottom: parent.bottom
    anchors.right: parent.right
    // multisample, decide based on your scene settings
    layer.enabled: true
    layer.samples: 4

    ShapePath {
        fillColor: "black"
        strokeColor: "darkBlue"
        strokeWidth: 20
        capStyle: ShapePath.FlatCap

        PathAngleArc {
            centerX: 65; centerY: 95
            radiusX: 45; radiusY: 45
            startAngle: -180
            sweepAngle: 180
        }
    }
}

Solution 3

Use charts http://doc.qt.io/QtCharts/qtcharts-qmlmodule.html

import QtQuick 2.0
import QtCharts 2.0

ChartView {
width: 400 
height: 300
theme: ChartView.ChartThemeBrownSand
antialiasing: true

PieSeries {
    id: pieSeries
    PieSlice { label: "eaten"; value: 94.9 }
    PieSlice { label: "not yet eaten"; value: 5.1 }
}
}

Solution 4

Since the question was about drawing "pizza slices" and making each of them clickable, an important detail is mapping clicks to the right segment (aka the right "pizza slice").

None of the prior answers seem to contain any onClicked handling, so I offer yet another possibility. (All the prior answers are also valid, they just don't make it immediately clear where to intercept clicks.)

I had a similar case wherein I needed to slice a rectangle on a 45 degree diagonal, and detect whether clicks fall above or below the diagonal line.

Thankfully, it turns out that QML allows you to:

  • create a grid
  • apply a transform: Rotation to that grid
  • ... and then automatically your clicks "just work"
    • (meaning: clicking on a 45-degree rotated rectangle maps as you would wish)

The demo code renders a rotated grid like the following, and outputs (via console.log) the color that you click on:

rotated grid of QML rectangles

The following has been tested on Ubuntu using Qt 5.14

import QtGraphicalEffects 1.12
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Controls.Universal 2.2
import QtQuick.Layouts 1.14

Item {
  height: 1000
  width: 2000

  GridLayout {
    id: grid
    columnSpacing: 0 // the default is non-zero!
    rowSpacing: 0
    anchors.centerIn: parent
    rows: 2
    columns: 2

    Rectangle {
      height: 200
      width: 200
      color: 'red'
      MouseArea {
        anchors.fill: parent
        onClicked: {
          console.log('red')
        }
      }
    }
    Rectangle {
      height: 200
      width: 200
      color: 'yellow'
      MouseArea {
        anchors.fill: parent
        onClicked: {
          console.log('yellow')
        }
      }
    }
    Rectangle {
      height: 200
      width: 200
      color: 'green'
      MouseArea {
        anchors.fill: parent
        onClicked: {
          console.log('green')
        }
      }
    }
    Rectangle {
      height: 200
      width: 200
      color: 'blue'
      MouseArea {
        anchors.fill: parent
        onClicked: {
          console.log('blue')
        }
      }
    }
    transform: Rotation {
      origin.x: grid.width * 0.5
      origin.y: grid.height * 0.5
      axis {
        x: 0
        y: 0
        z: 1
      }
      angle: 45
    }
  }
}

Share:
23,179
RafaelTSCS
Author by

RafaelTSCS

Brazilian. Java, web, and Scala developer.

Updated on January 15, 2022

Comments

  • RafaelTSCS
    RafaelTSCS over 2 years

    I know that it is possible to draw a circle in QML using the following code:

    Rectangle {
         width: 150
         height: 150
         anchors.horizontalCenter: parent.horizontalCenter
         anchors.top: parent.top
         color: "#095e7b"
         border.color: "#0a2f4a"
         border.width: 2
         radius: width*0.5
    }
    

    My question is: what if I need to draw a sector of a circle. (Pizza Slices) and make each of these slices clickable? Can I do this using QML only?

  • Erkki Nokso-Koivisto
    Erkki Nokso-Koivisto about 4 years
    Licensing alert! Those of us writing commercial code need to be aware: unlike most of Qt, QtCharts is not licensed under LGPL. If your product is closed source, you would need a commercial Qt license to use QtCharts.