qmlpaintshapes

How to center Text in a PathAngleArc Shape - QML


I have a shape I'm creating to draw onto my circle. I need to center text which can tell me what angle the wedge I'm drawing on the circle is at. I think the easiest was to explain this will be to show it.

What I need the text to do: What I need the text to do.

vs

What the text is currently doing: What the text is currently doing.

My code so far in QML:

    Item {
    id: root

    width: size
    height: size

    layer.enabled: true
    layer.samples: 8

    property int size: 600
    property real arcOffset: 0
    property real arcBegin: 90            // start arc angle in degree
    property real arcEnd: 105            // end arc angle in degree
    property real lineWidth: 80

    property string colorCircle: "#CC3333"
    property string colorBackground: "#779933"

    onArcBeginChanged: canvas.requestPaint()
    onArcEndChanged: canvas.requestPaint()

    Behavior on arcBegin {
       id: animationArcBegin
       enabled: true
       NumberAnimation {
           duration: root.animationDuration
           easing.type: Easing.InOutCubic
       }
    }

    Behavior on arcEnd {
       id: animationArcEnd
       enabled: true
       NumberAnimation {
           duration: root.animationDuration
           easing.type: Easing.InOutCubic
       }
    }

    Canvas {
        id: canvas
        anchors.fill: parent
        rotation: -90 + parent.arcOffset

        onPaint: {
            var context = getContext("2d")
            var x = width /2
            var y = height / 2
            var start = Math.PI * (parent.arcBegin / 180)
            var end = Math.PI * (parent.arcEnd / 180)

            context.reset()

            context.beginPath();
            context.arc(x, y, (width / 2) - parent.lineWidth / 2, 0, Math.PI * 2, false)
            context.lineWidth = root.lineWidth
            context.strokeStyle = Material.backgroundDimColor
            context.stroke()

//            context.beginPath();
//            context.arc(x, y, (width / 2) - parent.lineWidth / 2, start, end, false)
//            context.lineWidth = root.lineWidth
//            context.strokeStyle = root.colorCircle
//            context.stroke()
        }

        Shape {
            width: 100
            height: 100
            ShapePath {
                strokeWidth: 0
                strokeColor: "red"
                fillColor: "red"

                PathAngleArc {
                    id: firstArc
                    centerX: root.width/2
                    centerY: root.height/2
                    radiusX: root.width/2
                    radiusY: root.height/2
                    startAngle: root.arcBegin
                    sweepAngle: 15
                }

                PathAngleArc {
                    centerX: root.width/2
                    centerY: root.height/2
                    radiusX: root.width/2 - root.lineWidth
                    radiusY: root.height/2 - root.lineWidth
                    startAngle: firstArc.startAngle + firstArc.sweepAngle -1
                    sweepAngle: -13
                    moveToStart: false
                }


            }

            Text {
                anchors.centerIn: parent
                font.pointSize: 12

                text: (root.arcBegin + root.arcEnd)/2 + "\xB0"
            }
        }
    }
}

Any help on this would be appreciated. I know the issue is that the shape has no position. But I don't know how to make the shape's position match the AngleArc's drawn shape. Every anchor I've tried sends the text on a mighty adventure across the world to weird places. I should mention that the location of the 3 wedges won't be fixed. it's based on user input and could be any angle. The the center of the wedge needs to be at the angle specified by the user.


Solution

  • You can use an Item (or Text) to fill the circle and then rotate the Item (or Text), like the code below.

    Item { 
        anchors.fill: parent
        rotation: (root.arcBegin + root.arcEnd)/2
        Text {
            y: (root.lineWidth - height)/2
            x: (parent.width - width)/2
            rotation: -parent.rotation // I suggest not rotating the text
            text: parent.rotation.toFixed(1) + '\u00B0'
        }
    }
    

    You may also just set Text x and y directly using cos and sin.

    Text {
        property real angle: (root.arcBegin + root.arcEnd)/2
        property real rad: (root.width - root.lineWidth)/2
        x: rad * Math.cos(angle * 0.01746 - 1.57) + root.width/2 - width/2
        y: rad * Math.sin(angle * 0.01746 - 1.57) + root.height/2 - height/2
        text: angle.toFixed(1) + '\u00B0'
    }
    

    However, I'm sure there is a better way to implement this.
    So I created one arc using the code below, but you can reuse the Shape component to create more arcs.

    In the Shape component, the angle of the arc is defined as property real angle.

    Preview

    Code

    Rectangle {
        id: root
    
        width: parent.width
        height: width
    
        radius: width
        color: 'transparent'
        border { color: '#777'; width: 25 }
    
        Rectangle { // This rect's border simply uses a lighter hue to fill the border of its parent rect.
            anchors { fill: parent; margins: 1 }
            radius: width
            color: 'transparent'
            border { color: '#f0f0f0'; width: root.border.width - 2 }
        }
    
        Shape {
            // The arc in green is this (wedge).
            // It can be separated into an outside component and then reused. (recommended)
            id: shape
            anchors.fill: parent
    
            property real angle: 0
    
            Item { // Text goes here; all that is required is a simple rotation of the parent item.
                anchors.fill: parent
                rotation: shape.angle
                Text {
                    y: (root.border.width - height)/2
                    x: (parent.width - width)/2
                    rotation: -shape.angle
                    text: shape.angle.toFixed(1) + '\u00B0'
                }
            }
    
            component Arc: PathAngleArc {
                centerX: root.width/2; centerY: root.height/2
                radiusX: root.width/2 - shapePath.strokeWidth/2 - 1
                radiusY: radiusX
                startAngle: -90 - sweepAngle/2 + shape.angle
                sweepAngle: 30
            }
    
            ShapePath { // This is the dotted line
                strokeWidth: 1
                strokeColor: "#777"
                fillColor: 'transparent'
                strokeStyle: ShapePath.DashLine
                dashPattern: [ 2, 2 ]
    
                startX: 20 * Math.cos(shape.angle * 0.01746 - 1.57) + root.width/2
                startY: 20 * Math.sin(shape.angle * 0.01746 - 1.57) + root.width/2
    
                PathLine {
                    x: (root.width/2 - shapePath.strokeWidth - 4) * Math.cos(shape.angle * 0.01746 - 1.57) + root.width/2
                    y: (root.width/2 - shapePath.strokeWidth - 4) * Math.sin(shape.angle * 0.01746 - 1.57) + root.height/2
                }
            }
    
            ShapePath { // This is the arc border (actually background)
                strokeWidth: root.border.width
                strokeColor: "#777"
                fillColor: 'transparent'
                capStyle: ShapePath.FlatCap
                Arc { 
                    startAngle: arc.startAngle-0.5
                    sweepAngle: arc.sweepAngle+1
                }
            }
    
            ShapePath { // And this is arc foreground
                id: shapePath
                strokeWidth: root.border.width - 2
                strokeColor: "#8fbf8f"
                fillColor: 'transparent'
                capStyle: ShapePath.FlatCap
                Arc { id: arc }
            }
        }
    }