Surface Graph Gallery

// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick
import QtQuick.Controls
import QtGraphs

Rectangle {
    id: spectrogramView
    color: surfaceGraph.theme.windowColor

    required property bool portraitMode

    property real buttonWidth: spectrogramView.portraitMode ? (spectrogramView.width - 35) / 2
                                                            : (spectrogramView.width - 50) / 5

    SpectrogramData {
        id: surfaceData
    }

    Item {
        id: surfaceView
        anchors.top: buttons.bottom
        anchors.left: parent.left
        anchors.right: legend.left
        anchors.bottom: parent.bottom

        ColorGradient {
            id: surfaceGradient
            ColorGradientStop { position: 0.0; color: "black" }
            ColorGradientStop { position: 0.2; color: "red" }
            ColorGradientStop { position: 0.5; color: "blue" }
            ColorGradientStop { position: 0.8; color: "yellow" }
            ColorGradientStop { position: 1.0; color: "white" }
        }

        ValueAxis3D {
            id: xAxis
            segmentCount: 8
            labelFormat: "%i\u00B0"
            title: "Angle"
            titleVisible: true
            titleFixed: false
        }

        ValueAxis3D {
            id: yAxis
            segmentCount: 8
            labelFormat: "%i \%"
            title: "Value"
            titleVisible: true
            labelAutoRotation: 0
            titleFixed: false
        }

        ValueAxis3D {
            id: zAxis
            segmentCount: 5
            labelFormat: "%i nm"
            title: "Radius"
            titleVisible: true
            titleFixed: false
        }

        Theme3D {
            id: customTheme
            type: Theme3D.ThemeQt
            // Don't show specular spotlight as we don't want it to distort the colors
            lightStrength: 0.0
            ambientLightStrength: 1.0
            backgroundEnabled: false
            gridLineColor: "#AAAAAA"
            windowColor: "#EEEEEE"
        }

        Surface3D {
            id: surfaceGraph
            anchors.fill: parent

            Surface3DSeries {
                id: surfaceSeries
                flatShadingEnabled: false
                drawMode: Surface3DSeries.DrawSurface
                baseGradient: surfaceGradient
                colorStyle: Theme3D.ColorStyleRangeGradient
                itemLabelFormat: "(@xLabel, @zLabel): @yLabel"

                ItemModelSurfaceDataProxy {
                    itemModel: surfaceData.model
                    rowRole: "radius"
                    columnRole: "angle"
                    yPosRole: "value"
                }
            }

            // Remove the perspective and view the graph from top down to achieve 2D effect
            orthoProjection: true
            scene.activeCamera.cameraPreset: Camera3D.CameraPresetDirectlyAbove

            flipHorizontalGrid: true

            radialLabelOffset: 0.01

            inputHandler: TouchInputHandler3D {
                rotationEnabled: !surfaceGraph.orthoProjection
            }

            theme: customTheme
            shadowQuality: AbstractGraph3D.ShadowQualityNone
            selectionMode: AbstractGraph3D.SelectionSlice | AbstractGraph3D.SelectionItemAndColumn
            axisX: xAxis
            axisY: yAxis
            axisZ: zAxis

            aspectRatio: 1.0
            horizontalAspectRatio: 1.0
            scene.activeCamera.zoomLevel: 140
        }
    }

    Item {
        id: buttons
        anchors.top: parent.top
        anchors.left: parent.left
        anchors.right: parent.right
        height: spectrogramView.portraitMode ? (polarToggle.height + 10) * 3
                                             : polarToggle.height + 30
        anchors.margins: 10

        Button {
            id: polarToggle
            anchors.margins: 5
            anchors.left: parent.left
            anchors.top: parent.top
            width: spectrogramView.buttonWidth // Calculated elsewhere based on screen orientation
            text: "Switch to\n" + (surfaceGraph.polar ? "cartesian" : "polar")
            onClicked: surfaceGraph.polar = !surfaceGraph.polar;
        }

        Button {
            id: orthoToggle
            anchors.margins: 5
            anchors.left: polarToggle.right
            anchors.top: parent.top
            width: spectrogramView.buttonWidth
            text: "Switch to\n" + (surfaceGraph.orthoProjection ? "perspective" : "orthographic")
            onClicked: {
                if (surfaceGraph.orthoProjection) {
                    surfaceGraph.orthoProjection = false;
                    xAxis.labelAutoRotation = 30;
                    yAxis.labelAutoRotation = 30;
                    zAxis.labelAutoRotation = 30;
                } else {
                    surfaceGraph.orthoProjection = true;
                    surfaceGraph.scene.activeCamera.cameraPreset
                            = Camera3D.CameraPresetDirectlyAbove;
                    surfaceSeries.drawMode &= ~Surface3DSeries.DrawWireframe;
                    xAxis.labelAutoRotation = 0;
                    yAxis.labelAutoRotation = 0;
                    zAxis.labelAutoRotation = 0;
                }
            }
        }

        Button {
            id: flipGridToggle
            anchors.margins: 5
            anchors.left: spectrogramView.portraitMode ? parent.left : orthoToggle.right
            anchors.top: spectrogramView.portraitMode ? orthoToggle.bottom : parent.top
            width: spectrogramView.buttonWidth
            text: "Toggle axis\ngrid on top"
            onClicked: surfaceGraph.flipHorizontalGrid = !surfaceGraph.flipHorizontalGrid;
        }

        Button {
            id: labelOffsetToggle
            anchors.margins: 5
            anchors.left: flipGridToggle.right
            anchors.top: spectrogramView.portraitMode ? orthoToggle.bottom : parent.top
            width: spectrogramView.buttonWidth
            text: "Toggle radial\nlabel position"
            visible: surfaceGraph.polar
            onClicked: {
                if (surfaceGraph.radialLabelOffset >= 1.0)
                    surfaceGraph.radialLabelOffset = 0.01;
                else
                    surfaceGraph.radialLabelOffset = 1.0;
            }
        }

        Button {
            id: surfaceGridToggle
            anchors.margins: 5
            anchors.left: spectrogramView.portraitMode ? (labelOffsetToggle.visible ? parent.left
                                                                                    : flipGridToggle.right)
                                                       : (labelOffsetToggle.visible ? labelOffsetToggle.right
                                                                                    : flipGridToggle.right)
            anchors.top: spectrogramView.portraitMode ? (labelOffsetToggle.visible ? labelOffsetToggle.bottom
                                                                                   : orthoToggle.bottom)
                                                      : parent.top
            width: spectrogramView.buttonWidth
            text: "Toggle\nsurface grid"
            visible: !surfaceGraph.orthoProjection
            onClicked: {
                if (surfaceSeries.drawMode & Surface3DSeries.DrawWireframe)
                    surfaceSeries.drawMode &= ~Surface3DSeries.DrawWireframe;
                else
                    surfaceSeries.drawMode |= Surface3DSeries.DrawWireframe;
            }
        }
    }

    Item {
        id: legend
        anchors.bottom: parent.bottom
        anchors.top: buttons.bottom
        anchors.right: parent.right
        width: spectrogramView.portraitMode ? 100 : 125

        Rectangle {
            id: gradient
            anchors.margins: 20
            anchors.bottom: legend.bottom
            anchors.top: legend.top
            anchors.right: legend.right
            border.color: "black"
            border.width: 1
            width: spectrogramView.portraitMode ? 25 : 50
            rotation: 180
            gradient: Gradient {
                GradientStop { position: 0.0; color: "black" }
                GradientStop { position: 0.2; color: "red" }
                GradientStop { position: 0.5; color: "blue" }
                GradientStop { position: 0.8; color: "yellow" }
                GradientStop { position: 1.0; color: "white" }
            }
        }

        Text {
            anchors.verticalCenter: gradient.bottom
            anchors.right: gradient.left
            anchors.margins: 2
            text: surfaceGraph.axisY.min  + "%"
        }

        Text {
            anchors.verticalCenter: gradient.verticalCenter
            anchors.right: gradient.left
            anchors.margins: 2
            text: (surfaceGraph.axisY.max + surfaceGraph.axisY.min) / 2  + "%"
        }

        Text {
            anchors.verticalCenter: gradient.top
            anchors.right: gradient.left
            anchors.margins: 2
            text: surfaceGraph.axisY.max + "%"
        }
    }
}