Sur cette page

Galerie de graphiques de surface

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

La galerie de graphiques en surface présente trois fonctionnalités personnalisées différentes avec les graphiques Surface3D. Ces fonctionnalités disposent de leurs propres onglets dans l'application.

Les sections suivantes se concentrent uniquement sur ces fonctionnalités et ne traitent pas des fonctionnalités de base. Pour une documentation plus détaillée avec des exemples en QML, consultez la page « Graphique de dispersion simple ».

Représentation graphique en 3D d'une surface représentant un spectrogramme sous la forme d'une ondulation en pics aux couleurs de l'arc-en-ciel

Exécution de l'exemple

Vous pouvez exécuter l'exemple à partir de :

Carte d’altitude

Dans l’onglet « Height Map », générez un graphique de surface à partir de données d’altitude. Les données utilisées sont une carte d’altitude du mont Ruapehu et du mont 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 `Surface3DSeries`. Au sein du HeightMapSurfaceDataProxy, la propriété heightMapFile spécifie le fichier image contenant les données d’altitude. Les propriétés de valeur du proxy définissent les valeurs minimales et maximales de la largeur, de la profondeur et de la hauteur de la surface. Les valeurs z et x correspondent à la latitude et à la longitude, approximativement à la position réelle, et la y est exprimée en mètres.

Remarque : le rapport d’aspect du graphique n’est pas défini à l’échelle réelle ; c’est la hauteur qui est exagérée.

Surface3DSeries {
    id: heightSeries
    flatShadingEnabled: false
    drawMode: Surface3DSeries.DrawSurface

    HeightMapSurfaceDataProxy {
        heightMapFile: "://qml/qmlsurfacegallery/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 le fichier « main.qml », configurez l’élément Surface3D pour afficher les données.

Commencez par définir le dégradé personnalisé à utiliser pour la surface. Définissez les couleurs de la position 0,0 à 1,0 à l’aide de ColorGradient, en ajoutant deux arrêts supplémentaires pour rendre le graphique plus vivant :

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

Définissez cet élément dans la propriété « baseGradients » de l’ theme e utilisé dans Surface3D :

theme: Theme3D {
    type: Theme3D.ThemeStoneMoss
    font.family: "STCaiyun"
    font.pointSize: 35
    colorStyle: Theme3D.ColorStyleRangeGradient
    baseGradients: [surfaceGradient] // Use the custom gradient
}

Utilisez les boutons pour contrôler les autres fonctionnalités de Surface3D.

Le premier bouton active ou désactive la grille de la surface. Le mode de dessin ne peut pas être entièrement effacé ; par conséquent, à moins que la surface elle-même ne soit visible, la grille de la surface ne peut pas être masquée :

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

Le deuxième permet de définir 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 active ou désactive la surface en mode de dessin de surface. Le mode de dessin ne pouvant pas être entièrement effacé, la surface elle-même ne peut pas être masquée tant que la grille de surface est visible :

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

Le quatrième bouton permet de définir 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.flatShadingEnabled) {
        heightSeries.flatShadingEnabled = false;
        text = "Show\nFlat"
    } else {
        heightSeries.flatShadingEnabled = true;
        text = "Show\nSmooth"
    }
}

Les boutons restants contrôlent les caractéristiques de l’arrière-plan du graphique.

Spectrogramme

Dans l'onglet « Spectrogram », affichez des spectrogrammes polaires et cartésiens et utilisez une projection orthographique pour les représenter en 2D.

Un spectrogramme est un graphique de surface présentant un dégradé de valeurs utilisé pour mettre en évidence les différentes valeurs. Généralement, les spectrogrammes sont représentés sous forme de surfaces bidimensionnelles, ce qui est simulé par une vue orthographique de haut en 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 la Surface3DSeries à l’aide d’un ItemModelSurfaceDataProxy :

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

Les propriétés clés permettant d’activer l’effet 2D sont orthoProjection et scene.activeCamera.cameraPreset. Supprimez la perspective en activant la projection orthographique pour le graphique, et la dimension Y en visualisant le graphique directement depuis le haut :

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

Comme ce point de vue masque en grande partie la grille de l’axe horizontal derrière la surface, inversez la grille horizontale pour qu’elle soit dessinée par-dessus le graphique :

flipHorizontalGrid: true
Spectrogramme polaire

Selon les données, il est parfois plus naturel d’utiliser un graphique polaire plutôt qu’un graphique cartésien. Cette fonctionnalité est prise en charge via la propriété polar.

Ajoutez un bouton permettant de 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 en axe polaire radial. Les points de la surface sont recalculés en fonction des nouveaux axes.

Par défaut, les étiquettes de l’axe radial sont dessinées à l’extérieur du graphique. Pour les dessiner juste à côté de l’axe angulaire à 0 degré à l’intérieur du graphique, définissez-leur un décalage minime :

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 ou désactive automatiquement la propriété « rotationEnabled » en fonction du mode de projection :

inputHandler: TouchInputHandler3D {
    rotationEnabled: !surfaceGraph.orthoProjection
}

Oscilloscope

Dans l’onglet « Oscilloscope », combinez C++ et QML au sein d’une application et affichez des données qui évoluent dynamiquement.

Source de données en C++

Les proxys basés sur le modèle d’éléments conviennent aux graphiques simples ou statiques, mais utilisez des proxys de base pour obtenir les meilleures performances lors de l’affichage de données évoluant en temps réel. Ceux-ci ne sont pas pris en charge en QML, car les éléments de données qu’ils stockent n’héritent pas de la propriété ` QObject ` et ne peuvent donc pas être manipulés directement à partir du code QML. Pour contourner cette limitation, implémentez une classe « DataSource » simple en C++ afin de remplir le proxy de données de la série.

Créez une classe ` DataSource ` fournissant deux méthodes pouvant être appelées depuis 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ées à afficher. Les données sont mises en cache dans un format accepté par `QSurfaceDataProxy` :

// Populate caches
auto *generator = QRandomGenerator::global();
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 rowColWaveAngleMul = M_PI * M_PI * rowMod;
        float rowColWaveMul = 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(rowColWaveAngleMul * colMod) + 1.0))))
                    * rowColWaveMul
                    + generator->bounded(0.15f) * yRangeMod;

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

La deuxième méthode, ` update()`, copie un ensemble de données mises en cache dans un autre tableau, qui est attribué au proxy de données de la série en appelant ` QSurfaceDataProxy::resetArray()`. Pour minimiser la surcharge, réutilisez le même tableau si ses dimensions n’ont pas changé :

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

const 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 || series->dataProxy()->rowCount() != newRowCount
        || series->dataProxy()->columnCount() != newColumnCount) {
    m_resetArray = new QSurfaceDataArray();
    m_resetArray->reserve(newRowCount);
    for (int i = 0; i < newRowCount; ++i)
        m_resetArray->append(new 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];
    std::copy(sourceRow.cbegin(), sourceRow.cend(), row.begin());
}

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

Même si nous opérons sur le pointeur de tableau précédemment défini comme proxy, la méthode ` QSurfaceDataProxy::resetArray()` doit tout de même être appelée après avoir modifié les données qu’il contient afin de déclencher le rendu des données par le graphique.

Pour pouvoir accéder aux méthodes d’ DataSource depuis QML, exposez la source de données en définissant DataSource comme un QML_ELEMENT:

class DataSource : public QObject
{
    Q_OBJECT
    QML_ELEMENT

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

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

Pour utiliser des 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 *>();
Application QML

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

import SurfaceGallery
...
DataSource {
    id: dataSource
}

Définissez un graphe Surface3D et associez-lui une Surface3DSeries :

Surface3D {
    id: surfaceGraph
    anchors.fill: parent

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

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

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

itemLabelVisible: false

Vous pouvez afficher les informations relatives à l’élément sélectionné dans un élément ` Text ` au lieu de l’étiquette flottante par défaut située au-dessus du pointeur de sélection :

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

Initialisez le cache de l’ DataSource une fois le graphique terminé en appelant la 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 un ` Timer`, qui appelle la méthode ` update() ` dans ` DataSource ` aux intervalles souhaités :

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

Comme cette application est susceptible de traiter un grand nombre de données évoluant rapidement, elle utilise le mode de rendu direct pour optimiser ses performances. 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 <QtDataVisualization/qutils.h>
...
// Enable antialiasing in direct rendering mode
viewer.setFormat(qDefaultSurfaceFormat(true));

Exemple de contenu

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.