Cockpit

Example application imitating an airplane cockpit using Graphs.

The cockpit example features a number of different graph types to draw fictional airplane control screens. The graphs used in the example are:

Running the Example

To run the example from Qt Creator, open the Welcome mode and select the example from Examples. For more information, see Qt Creator: Tutorial: Build and run.

Speedometer

The speedometer view consists of 3 PieSeries inside a single GraphsView with QML Rectangles as needles for both of the dials.

The backgroundVisible property for the GraphsTheme is set to false. All the PieSlices have their own custom colors. To display a circle scale with a hollow center, the example uses PieSeries with different holeSize.

GraphsView {
    id: chart
    anchors.fill: parent
    anchors.margins: 20

    theme: GraphsTheme {
        backgroundVisible: false
        borderColors: ["#252525"]
    }

    PieSeries {
        id: pieOuter
        pieSize: 1
        holeSize: 0.8
        startAngle: -120
        endAngle: 120

        PieSlice { label: "Stall"; value: 1; color: "#ff0000"; labelVisible: false }
        PieSlice { label: "Optimal"; value: 4; color: "#00ff00"; labelVisible: false }
        PieSlice { label: "Overspeed"; value: 1; color: "#ffaa22"; labelVisible: false }
    }

    PieSeries {
        pieSize: 0.8
        holeSize: 0.6
        startAngle: -120
        endAngle: 120

        PieSlice { value: 1; color: "#ffffff"; }
        PieSlice { value: 4; color: "#252525"; }
        PieSlice { value: 1; color: "#ffffff"; }
        PieSlice { value: 4; color: "#252525"; }
        PieSlice { value: 1; color: "#ffffff"; }
        PieSlice { value: 4; color: "#252525"; }
        PieSlice { value: 1; color: "#ffffff"; }
        PieSlice { value: 4; color: "#252525"; }
        PieSlice { value: 1; color: "#ffffff"; }
        PieSlice { value: 4; color: "#252525"; }
        PieSlice { value: 1; color: "#ffffff"; }
        PieSlice { value: 4; color: "#252525"; }
        PieSlice { value: 1; color: "#ffffff"; }
        PieSlice { value: 4; color: "#252525"; }
        PieSlice { value: 1; color: "#ffffff"; }
        PieSlice { value: 4; color: "#252525"; }
        PieSlice { value: 1; color: "#ffffff"; }
        PieSlice { value: 4; color: "#252525"; }
        PieSlice { value: 1; color: "#ffffff"; }
    }

    PieSeries {
        pieSize: 0.6
        holeSize: 1.0
        startAngle: -120
        endAngle: 120
        verticalPosition: 1

        PieSlice { label: "Stall"; value: 1; color: "#ff0000"; labelVisible: false }
        PieSlice { label: "Optimal"; value: 4; color: "#00ff00"; labelVisible: false  }
        PieSlice { label: "Overspeed"; value: 1; color: "#ffaa22"; labelVisible: false  }
    }
}

Vibration Panel

The Vibration Panel contains a LineSeries which is dynamically updated at runtime. After launch, a set of points is added to the series using the QML invoked append function. Then on each frame, the values are replaced using the replace function with calculated values. Users can change these values through the Vibration Graph Panel.

LineSeries {
    id: line
    property int divisions: 500
    property real amplitude: 0.5
    property real resolution: 0.5

    FrameAnimation {
        running: true

        onTriggered: {
            for (let i = 0; i < line.divisions; ++i) {
                let y = Math.sin(line.resolution*i)
                y *= Math.cos(i)
                y *= Math.sin(i / line.divisions * 3.2) * 3 * line.amplitude * Math.random()

               line.replace(i, (i/line.divisions) * 8.0, y + 4)
            }
        }
    }

    Component.onCompleted: {
        for (let i = 1; i <= divisions; ++i) {
            append((i/divisions) * 8.0, 4.0)
        }
    }

    function change(newDivs) {
        let delta = newDivs - divisions

        if (delta < 0) {
            delta = Math.abs(delta)
            removeMultiple(count - 1 - delta, delta)
        } else {
            for (let i = 0; i < delta; ++i) {
                append(((count + i)/divisions) * 8.0, 4.0)
            }
        }

        divisions = newDivs
    }
}

Vibration Graph Panel

This panel contains control knobs to adjust values that affect the visualization displayed in the Vibration Panel.

The Navigation Map has two main sections:

  • The left panel contains a number of BarSeries with a custom shader that utilizes the BarSets color to shade each of the bars.
    BarSeries {
        property real barOpacity: 0.
    
        id: barSeries
        barsType: BarSeries.BarsType.Stacked
        barWidth: 0.2
    
        barDelegate: Item {
            id: delegate
            antialiasing: true
            property real barOpacity: 0.5
            property color barColor
            property string barLabel
    
            FrameAnimation {
                running: true
                onTriggered: {
                    delegate.barOpacity = Math.abs(Math.sin(elapsedTime))
                }
            }
            ShaderEffect {
                id: effect
                readonly property alias iTime: delegate.barOpacity
                readonly property alias iColor: delegate.barColor
                readonly property vector3d iResolution: Qt.vector3d(width, height, 1.0)
    
                blending: true
                fragmentShader: 'bar.frag.qsb'
                anchors.fill: parent
            }
        }
    
        BarSet { id: set1; label: "Low"; values: [1, 2, 3, 1]; color: "red" }
        BarSet { id: set2; label: "Medium"; values: [2, 2, 0, 4]; color: "yellow"}
        BarSet { id: set3; label: "High"; values: [3, 2, 3, 1]; color: "green"}
    }
  • The right panel contains:
    • An AreaSeries with two LineSeries visualizes a fictional lake.
    • A ScatterSeries on top of that displays custom icons, for example, airports, on a map.
    • The ADD button adds a LineSeries whose points can be moved around to create a path. This LineSeries uses a custom shader for the pointDelegate and the ScatterSeries uses an image.
    AreaSeries {
        property double x: 0
        property double y: 0
    
        id: lake1
        color: "blue"
        upperSeries: LineSeries {
            id: s1
            XYPoint { x: 0.0; y: -3.5 }
            XYPoint { x: 1.0; y: -5.0 }
            XYPoint { x: 2.0; y: -2.5 }
            XYPoint { x: 2.5; y: -4.0 }
            XYPoint { x: 3.0; y: -4.2 }
        }
    
        lowerSeries: LineSeries {
            id: s2
            XYPoint { x: 0.0; y: -7.2 }
            XYPoint { x: 1.0; y: -7.0 }
            XYPoint { x: 2.0; y: -8.5 }
            XYPoint { x: 2.5; y: -8.0 }
            XYPoint { x: 3.0; y: -9.0 }
            XYPoint { x: 4.0; y: -6.5 }
        }
    }
    
    AreaSeries {
        property double x: 0
        property double y: 0
    
        id: lake2
        color: "blue"
        upperSeries: LineSeries {
            id: s3
            XYPoint { x: 0.0; y: 1.5 }
            XYPoint { x: 1.0; y: 3.0 }
            XYPoint { x: 2.0; y: 4.5 }
            XYPoint { x: 2.5; y: 4.8 }
            XYPoint { x: 3.0; y: 4.0 }
        }
    
        lowerSeries: LineSeries {
            id: s4
            XYPoint { x: 0.0; y: 0.0 }
            XYPoint { x: 1.0; y: 0.5 }
            XYPoint { x: 2.0; y: 0.2 }
            XYPoint { x: 2.5; y: 1.5 }
            XYPoint { x: 3.0; y: 1.0 }
            XYPoint { x: 4.0; y: 0.6 }
        }
    }
    
    // POI
    ScatterSeries {
        name: "Airport"
    
        pointDelegate: Image {
                source: "airplane-ico.png"
                mipmap: true
                width: 30
                height: 30
        }
    
        XYPoint{x: 4.0; y: 5.7}
        XYPoint{x: 2.2; y: 8.2}
        XYPoint{x: 6.4; y: 1.2}
        XYPoint{x: 7.4; y: 7.8}
    }
    
    LineSeries {
        id: linePath
        selectable: true
        draggable: true
        color: "white"
        pointDelegate: Item {
            width: 50
            height: 50
            property real pointValueX
            property real pointValueY
    
            FrameAnimation {
                id: scatterAnim
                running: true
            }
    
            ShaderEffect {
                readonly property vector3d iResolution: Qt.vector3d(width, height, 1.0)
                readonly property alias iTime: scatterAnim.elapsedTime
    
                blending: true
                fragmentShader: 'circleMarker.frag.qsb'
                anchors.fill: parent
            }
    
            Text {
                color: "white"
                font.pointSize: 4
                text: "LAT: " + pointValueX.toFixed(1) + ", " + "LON: " + pointValueY.toFixed(1)
            }
        }
    }

Primary Flight Display

The Primary Flight Display features an imitation of the "3D" view of the terrain. It is implemented with two AreaSeries to represent the ground and the sky each with custom colors. A FrameAnimation updates the values of these series.

AreaSeries {
    id: upperArea
    color: "cyan"

    upperSeries: LineSeries {
        XYPoint {x: 0; y: 10}
        XYPoint {x: 10; y: 10}
    }

    lowerSeries: LineSeries {
        id: upperLine
        XYPoint {x: 0; y: 3}
        XYPoint {x: 10; y: 4}
    }
}

FrameAnimation {
    running: true
    onTriggered: {
        upperLine.replace(0, upperLine.at(0).x, Math.sin(elapsedTime) + 6)
        upperLine.replace(1, upperLine.at(1).x, Math.cos(elapsedTime) + 6)
        lowerLine.replace(0, lowerLine.at(0).x, Math.sin(elapsedTime) + 6)
        lowerLine.replace(1, lowerLine.at(1).x, Math.cos(elapsedTime) + 6)
        barSet.values = [Math.sin(elapsedTime) + 5]
    }
}

At the left side, a themed BarSeries shows the y-axis labels. This BarSeries also uses a custom shader for the bar.

BarSeries {
    id: barSeries
    selectable: true
    barDelegate: Item {
        id: delegate
        antialiasing: true
        property real barOpacity: 0.5
        property color barColor
        property string barLabel

        ShaderEffect {
            id: effect
            readonly property vector3d iResolution: Qt.vector3d(width, height, 1.0)

            blending: true
            fragmentShader: 'pitchbar.frag.qsb'
            anchors.fill: parent
        }
    }

    BarSet { id: barSet; values: []; selectedColor: "red" }
}

At the bottom of the view, a SplineSeries visualizes the terrain height under the plane. The SplineSeries is updated at each frame. The code that updates the series appends to the back and removes them from the front a continuous series of points. On hover, the SplineSeries shows a tooltip which displays the y-axis value (Altitude).

GraphsView {
    anchors.fill: parent
    anchors.leftMargin: -90
    anchors.rightMargin: -80
    anchors.bottomMargin: -30

    theme: GraphsTheme {
        backgroundVisible: false
        plotAreaBackgroundColor: "#11000000"
    }

    axisX: ValueAxis {
        max: 10
        subTickCount: 9
        lineVisible: false
        gridVisible: false
        subGridVisible: false
        labelsVisible: false
        visible: false
    }

    axisY: ValueAxis {
        max: 10
        subTickCount: 9
        lineVisible: false
        gridVisible: false
        subGridVisible: false
        labelsVisible: false
        visible: false
    }

    ToolTip {
        id: tooltip
    }

    onHoverEnter: {
        tooltip.visible = true;
    }

    onHoverExit: {
        tooltip.visible = false;
    }

    onHover: (seriesName, position, value) => {
                 tooltip.x = position.x + 1;
                 tooltip.y = position.y + 1;
                 tooltip.text = "Altitude: " + (value.y * 1000).toFixed(1) + "m";
             }

    FrameAnimation {
        property var points: []

        Component.onCompleted: {
            for (let i = 0; i < altitudeLine.count; ++i) {
                points[i] = altitudeLine.at(i)
            }
        }

        running: true
        onTriggered: {
            for (let i = 0; i < points.length; ++i) {
                points[i].x -= frameTime

                if (points[1].x <= -2) {
                    let p = points[0]
                    p.x = points[points.length - 1].x + 1
                    altitudeLine.append(p)
                    altitudeLine.remove(0)

                    points.length = 0

                    for (let i = 0; i < altitudeLine.count; ++i) {
                        points[i] = altitudeLine.at(i)
                    }
                }
            }
            altitudeLine.replace(points)
            altitudeLine.update()
        }
    }

    SplineSeries {
        id: altitudeLine
        hoverable: true
        width: 3

        XYPoint {x: 0; y: 5}
        XYPoint {x: 1; y: 2}
        XYPoint {x: 2; y: 5}
        XYPoint {x: 3; y: 4}
        XYPoint {x: 4; y: 6}
        XYPoint {x: 5; y: 7}
        XYPoint {x: 6; y: 9}
        XYPoint {x: 7; y: 8}
        XYPoint {x: 8; y: 9}
        XYPoint {x: 9; y: 6}
        XYPoint {x: 10; y: 6}
        XYPoint {x: 11; y: 6}
        XYPoint {x: 12; y: 1}
        XYPoint {x: 13; y: 9}
        XYPoint {x: 14; y: 1}
    }

}

Example project @ code.qt.io

© 2024 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.