Sur cette page

Galerie de graphiques de surface

Galerie présentant trois façons différentes d'utiliser un graphique Surface3D.

Lagalerie des graphiques de surface présente trois fonctions personnalisées différentes avec les graphiques Surface3D. Ces fonctions ont leurs propres onglets dans l'application.

Les sections suivantes se concentrent uniquement sur ces fonctionnalités et omettent d'expliquer la fonctionnalité de base - pour une documentation d'exemple QML plus détaillée, voir Simple Scatter Graph (graphique de dispersion simple).

Exécution de l'exemple

Pour exécuter l'exemple à partir de Qt CreatorOuvrez le mode Welcome et sélectionnez l'exemple à partir de Examples. Pour plus d'informations, voir Qt Creator: Tutorial : Construire et exécuter.

Carte des hauteurs

Dans l'onglet Height Map, générez un graphique de surface à partir des données d'altitude. Les données utilisées sont une carte d'altitude des monts Ruapehu et Ngauruhoe en Nouvelle-Zélande.

Ajout de données au graphique

Les données sont définies à l'aide de HeightMapSurfaceDataProxy, qui lit les informations d'altitude à partir d'une image de carte d'altitude. Le proxy lui-même est contenu dans un fichier Surface3DSeries. À l'intérieur de HeightMapSurfaceDataProxy, la propriété heightMapFile spécifie le fichier image contenant les données d'altitude. Les propriétés de valeur dans le proxy définissent les valeurs minimales et maximales pour la largeur, la profondeur et la hauteur de la surface. Les valeurs z et x sont exprimées en latitude et en longitude, approximativement à la position réelle, et la valeur y est exprimée en mètres.

Remarque : le rapport d'aspect du graphique n'est pas défini à l'échelle réelle, mais la hauteur est exagérée.

Surface3DSeries {
    id: heightSeries
    shading: Surface3DSeries.Shading.Smooth
    drawMode: Surface3DSeries.DrawSurface

    HeightMapSurfaceDataProxy {
        heightMapFile: "://qml/surfacegallery/heightmap.png"
        // We don't want the default data values set by heightmap proxy, but use
        // actual coordinate and height values instead
        autoScaleY: true
        minYValue: 740
        maxYValue: 2787
        minZValue: -374 // ~ -39.374411"N
        maxZValue: -116 // ~ -39.115971"N
        minXValue: 472  // ~ 175.471767"E
        maxXValue: 781  // ~ 175.780758"E
    }

    onDrawModeChanged: heightMapView.checkState()
}
Affichage des données

Dans main.qml, configurez l'élément Surface3D pour afficher les données.

Commencez par définir le gradient personnalisé à utiliser pour la surface. Définissez les couleurs de la position 0.0 à 1.0 avec Gradient, avec deux arrêts supplémentaires pour rendre le graphique plus vivant :

Gradient {
    id: surfaceGradient
    GradientStop { position: 0.0; color: "darkgreen"}
    GradientStop { position: 0.15; color: "darkslategray" }
    GradientStop { position: 0.7; color: "peru" }
    GradientStop { position: 1.0; color: "white" }
}

Placez cet élément dans la propriété baseGradients dans la propriété theme utilisée dans Surface3D:

theme: GraphsTheme {
    colorScheme: GraphsTheme.ColorScheme.Dark
    labelFont.family: "STCaiyun"
    labelFont.pointSize: 35
    colorStyle: GraphsTheme.ColorStyle.ObjectGradient
    baseGradients: [surfaceGradient] // Use the custom gradient
}

Utilisez les boutons pour contrôler d'autres fonctions de Surface3D.

Le premier bouton permet d'activer ou de désactiver la grille de surface. Le mode de dessin ne peut pas être complètement supprimé ; par conséquent, à moins que la surface elle-même ne soit visible, la grille de surface ne peut pas être cachée :

onClicked: {
    if (heightSeries.drawMode & Surface3DSeries.DrawWireframe)
        heightSeries.drawMode &= ~Surface3DSeries.DrawWireframe;
    else
        heightSeries.drawMode |= Surface3DSeries.DrawWireframe;
}

Le deuxième bouton définit la couleur de la grille de surface :

onClicked: {
    if (Qt.colorEqual(heightSeries.wireframeColor, "#000000")) {
        heightSeries.wireframeColor = "red";
        text = "Black surface\ngrid color";
    } else {
        heightSeries.wireframeColor = "black";
        text = "Red surface\ngrid color";
    }
}

Le troisième permet d'activer ou de désactiver la surface dans le mode de dessin de la surface. Le mode de dessin ne peut pas être complètement désactivé, donc à moins que la grille de la surface soit visible, la surface elle-même ne peut pas être cachée :

onClicked: {
    if (heightSeries.drawMode & Surface3DSeries.DrawSurface)
        heightSeries.drawMode &= ~Surface3DSeries.DrawSurface;
    else
        heightSeries.drawMode |= Surface3DSeries.DrawSurface;
}

Le quatrième définit le mode d'ombrage. Si vous exécutez l'exemple sur un système OpenGL ES, l'ombrage plat n'est pas disponible :

onClicked: {
    if (heightSeries.shading === Surface3DSeries.Shading.Flat) {
        heightSeries.shading = Surface3DSeries.Shading.Smooth;
        text = "Show\nFlat"
    } else {
        heightSeries.shading = Surface3DSeries.Shading.Flat;
        text = "Show\nSmooth"
    }
}

Les boutons restants permettent de contrôler les caractéristiques de l'arrière-plan du graphique.

Spectrogramme

Dans l'onglet Spectrogram, affichez des spectrogrammes polaires et cartésiens et utilisez la projection orthographique pour les afficher en 2D.

Un spectrogramme est un graphique de surface avec un gradient de gamme utilisé pour mettre en évidence les différentes valeurs. Généralement, les spectrogrammes sont affichés avec des surfaces bidimensionnelles, qui sont simulées par une vue orthographique du haut vers le bas du graphique. Pour renforcer l'effet 2D, désactivez la rotation du graphique à l'aide de la souris ou du toucher lorsque vous êtes en mode orthographique.

Création d'un spectrogramme

Pour créer un spectrogramme en 2D, définissez un élément Surface3D avec les données fournies dans le document Surface3DSeries et ItemModelSurfaceDataProxy:

Surface3D {
    id: surfaceGraph
    anchors.fill: parent

    // Don't show specular spotlight as we don't want it to distort the colors
    lightStrength: 0.0
    ambientLightStrength: 1.0

    Surface3DSeries {
        id: surfaceSeries
        shading: Surface3DSeries.Shading.Smooth
        drawMode: Surface3DSeries.DrawSurface
        baseGradient: surfaceGradient
        colorStyle: GraphsTheme.ColorStyle.RangeGradient
        itemLabelFormat: "(@xLabel, @zLabel): @yLabel"

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

Les propriétés clés pour activer l'effet 2D sont orthoProjection et cameraPreset. Supprimez la perspective en activant la projection orthographique pour le graphique, et la dimension Y en regardant le graphique directement du dessus :

// Remove the perspective and view the graph from top down to achieve 2D effect
orthoProjection: true
cameraPreset: Graphs3D.CameraPreset.DirectlyAbove

Comme ce point de vue fait en sorte que la grille de l'axe horizontal est en grande partie masquée par la surface, inversez la grille horizontale pour qu'elle soit dessinée sur le dessus du graphique :

flipHorizontalGrid: true
Spectrogramme polaire

En fonction des données, il est parfois plus naturel d'utiliser un graphique polaire qu'un graphique cartésien. Cela est possible grâce à la propriété polar.

Ajouter un bouton pour basculer entre les modes polaire et cartésien :

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

En mode polaire, l'axe X est converti en axe polaire angulaire et l'axe Z est converti en axe polaire radial. Les points de la surface sont recalculés en fonction des nouveaux axes.

Par défaut, les étiquettes des axes radiaux sont dessinées à l'extérieur du graphique. Pour les dessiner juste à côté de l'axe angulaire de 0 degré à l'intérieur du graphique, définissez seulement un petit décalage pour eux :

radialLabelOffset: 0.01

Pour renforcer l'effet 2D, désactivez la rotation du graphique en mode orthographique en remplaçant le gestionnaire d'entrée par défaut par un gestionnaire personnalisé, qui active automatiquement la propriété rotationEnabled en fonction du mode de projection :

rotationEnabled: !surfaceGraph.orthoProjection

Oscilloscope

Dans l'onglet Oscilloscope, combinez C++ et QML dans une application et affichez des données qui changent dynamiquement.

Source de données en C++

Les proxys basés sur les modèles d'éléments conviennent aux graphiques simples ou statiques, mais il faut utiliser des proxys de base pour obtenir les meilleures performances lors de l'affichage de données changeant en temps réel. Elles ne sont pas prises en charge par QML, car les éléments de données qu'elles stockent n'héritent pas de QObject et ne peuvent donc pas être manipulées directement à partir du code QML. Pour surmonter cette limitation, mettez en œuvre une simple classe DataSource en C++ pour remplir le proxy de données de la série.

Créez une classe DataSource pour fournir deux méthodes qui peuvent être invoquées à partir de QML :

class DataSource : public QObject
{
    Q_OBJECT
    ...
Q_INVOKABLE void generateData(int cacheCount,
                              int rowCount,
                              int columnCount,
                              float xMin,
                              float xMax,
                              float yMin,
                              float yMax,
                              float zMin,
                              float zMax);

Q_INVOKABLE void update(QSurface3DSeries *series);

La première méthode, generateData(), crée un cache de données d'oscilloscope simulé à afficher. Les données sont mises en cache dans un format accepté par QSurfaceDataProxy:

// Populate caches
for (int i = 0; i < cacheCount; i++) {
    QSurfaceDataArray &cache = m_data[i];
    float cacheXAdjustment = cacheStep * i;
    float cacheIndexAdjustment = cacheIndexStep * i;
    for (int j = 0; j < rowCount; j++) {
        QSurfaceDataRow &row = cache[j];
        float rowMod = (float(j)) / float(rowCount);
        float yRangeMod = yRange * rowMod;
        float zRangeMod = zRange * rowMod;
        float z = zRangeMod + zMin;
        qreal waveAngleMul = M_PI * M_PI * rowMod;
        float waveMul = yRangeMod * 0.2f;
        for (int k = 0; k < columnCount; k++) {
            float colMod = (float(k)) / float(columnCount);
            float xRangeMod = xRange * colMod;
            float x = xRangeMod + xMin + cacheXAdjustment;
            float colWave = float(qSin((2.0 * M_PI * colMod) - (1.0 / 2.0 * M_PI)) + 1.0);
            float y = (colWave * ((float(qSin(waveAngleMul * colMod) + 1.0)))) * waveMul
                      + QRandomGenerator::global()->bounded(0.15f) * yRangeMod;

            int index = k + cacheIndexAdjustment;
            if (index >= columnCount) {
                // Wrap over
                index -= columnCount;
                x -= xRange;
            }
            row[index] = QSurfaceDataItem(x, y, z);
        }
    }
}

La seconde méthode, update(), copie un ensemble de données mises en cache dans un autre tableau, qui est défini sur la série en appelant QSurfaceDataProxy::resetArray(). Pour minimiser les frais généraux, il convient de réutiliser le même tableau si ses dimensions n'ont pas changé :

// Each iteration uses data from a different cached array
m_index++;
if (m_index > m_data.count() - 1)
    m_index = 0;

QSurfaceDataArray array = m_data.at(m_index);
int newRowCount = array.size();
int newColumnCount = array.at(0).size();

// If the first time or the dimensions of the cache array have changed,
// reconstruct the reset array
if (m_resetArray.isEmpty() || series->dataProxy()->rowCount() != newRowCount
    || series->dataProxy()->columnCount() != newColumnCount) {
    m_resetArray.clear();
    m_resetArray.reserve(newRowCount);
    for (int i = 0; i < newRowCount; i++)
        m_resetArray.append(QSurfaceDataRow(newColumnCount));
}

// Copy items from our cache to the reset array
for (int i = 0; i < newRowCount; i++) {
    const QSurfaceDataRow &sourceRow = array.at(i);
    QSurfaceDataRow &row = m_resetArray[i];
    for (int j = 0; j < newColumnCount; j++)
        row[j].setPosition(sourceRow.at(j).position());
}

// Notify the proxy that data has changed
series->dataProxy()->resetArray(m_resetArray);

Même si nous utilisons le pointeur de tableau précédemment défini pour la série, QSurfaceDataProxy::resetArray() doit toujours être appelé après avoir modifié les données qu'il contient afin d'inviter le graphique à rendre les données.

Pour pouvoir accéder aux méthodes DataSource à partir de QML, exposez la source de données en faisant de DataSource un QML_ELEMENT:

class DataSource : public QObject
{
    Q_OBJECT
    QML_ELEMENT

De plus, déclarez-la comme module QML dans le fichier CMakeLists.txt :

qt6_add_qml_module(surfacegallery
    URI SurfaceGallery
    VERSION 1.0
    NO_RESOURCE_TARGET_PATH
    SOURCES
        datasource.cpp datasource.h
    ...
)

Pour utiliser les pointeurs QSurface3DSeries comme paramètres des méthodes de la classe DataSource dans tous les environnements et toutes les versions, assurez-vous que le méta type est enregistré :

qRegisterMetaType<QSurface3DSeries *>();
QML Application

Pour utiliser DataSource, importez le module QML et créez une instance de DataSource à utiliser :

import SurfaceGalleryExample
...
DataSource {
    id: dataSource
}

Définissez un graphe Surface3D et donnez-lui un Surface3DSeries:

Surface3D {
    id: surfaceGraph
    anchors.fill: parent

    Surface3DSeries {
        id: surfaceSeries
        drawMode: Surface3DSeries.DrawSurfaceAndWireframe
        itemLabelFormat: "@xLabel, @zLabel: @yLabel"

Ne spécifiez pas de proxy pour le Surface3DSeries que vous attachez au graphique. La série utilise ainsi la valeur par défaut QSurfaceDataProxy.

Masquez l'étiquette de l'élément avec itemLabelVisible. Avec des données dynamiques et changeant rapidement, une étiquette de sélection flottante serait gênante et difficile à lire.

itemLabelVisible: false

Vous pouvez afficher les informations sur l'élément sélectionné dans un élément Text au lieu de l'étiquette flottante par défaut au-dessus du pointeur de sélection :

onItemLabelChanged: {
    if (surfaceSeries.selectedPoint == surfaceSeries.invalidSelectionPosition)
        selectionText.text = "No selection";
    else
        selectionText.text = surfaceSeries.itemLabel;
}

Initialiser le cache DataSource lorsque le graphique est terminé en appelant une fonction d'aide generateData(), qui appelle la méthode du même nom dans DataSource:

Component.onCompleted: oscilloscopeView.generateData();
...
function generateData() {
    dataSource.generateData(oscilloscopeView.sampleCache, oscilloscopeView.sampleRows,
                            oscilloscopeView.sampleColumns,
                            surfaceGraph.axisX.min, surfaceGraph.axisX.max,
                            surfaceGraph.axisY.min, surfaceGraph.axisY.max,
                            surfaceGraph.axisZ.min, surfaceGraph.axisZ.max);
}

Pour déclencher les mises à jour des données, définissez une fonction Timer, qui appelle la méthode update() dans DataSource aux intervalles demandés :

Timer {
    id: refreshTimer
    interval: 1000 / frequencySlider.value
    running: true
    repeat: true
    onTriggered: dataSource.update(surfaceSeries);
}
Activation du rendu direct

Étant donné que cette application traite potentiellement un grand nombre de données changeant rapidement, elle utilise le mode de rendu direct pour des raisons de performance. Pour activer l'anticrénelage dans ce mode, modifiez le format de surface de la fenêtre de l'application. Le format par défaut utilisé par QQuickView ne prend pas en charge l'anticrénelage. Utilisez la fonction utilitaire fournie pour modifier le format de surface dans main.cpp:

#include <QtGraphs/qutils.h>
...
// Enable antialiasing in direct rendering mode
viewer.setFormat(QQuick3D::idealSurfaceFormat(8));

Contenu de l'exemple

Exemple de projet @ code.qt.io

© 2026 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.