How to center Text in a PathAngleArc Shape - QML

Viewed 49

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.

1 Answers

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 }
        }
    }
}
Related