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, visit Building and Running an Example.

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