Sur cette page

Galerie de graphiques

Galerie de graphiques en barres, en nuage de points et en surface.

Lagalerie de graphiques présente les trois types de graphiques et certaines de leurs caractéristiques particulières. Les graphiques ont leurs propres onglets dans l'application.

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.

Graphique à barres

Dans l'onglet Bar Graph, créez un graphique à barres en 3D à l'aide de Q3DBarWidgetItem et combinez l'utilisation de widgets pour ajuster les différentes qualités du graphique à barres. L'exemple montre comment :

  • Créer une application avec Q3DBarWidgetItem et quelques widgets de contrôle
  • utiliser QBar3DSeries et QBarDataProxy pour définir les données du graphique
  • Ajuster certaines propriétés du graphique et de la série à l'aide des contrôles du widget
  • Sélectionner une ligne ou une colonne en cliquant sur l'étiquette d'un axe
  • Créer un proxy personnalisé à utiliser avec Q3DBarWidgetItem

Pour plus d'informations sur l'interaction avec le graphique, voir cette page.

Création de l'application
  1. Dans bargraph.cpp, instanciez QQuickWidget et Q3DBarsWidgetItem, et définissez l'instance QQuickWidget comme widget pour Q3DBarsWidgetItem:
    m_quickWidget = new QQuickWidget();
    m_barGraph = new Q3DBarsWidgetItem(this);
    m_barGraph->setWidget(m_quickWidget);
  2. Créez un widget conteneur, ainsi que des présentations horizontale et verticale. Ajoutez le graphique et la disposition verticale à la disposition horizontale :
    m_container = new QWidget();
    auto *hLayout = new QHBoxLayout(m_container);
    QSize screenSize = m_quickWidget->screen()->size();
    m_quickWidget->setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 1.75));
    m_quickWidget->setMaximumSize(screenSize);
    m_quickWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    m_quickWidget->setFocusPolicy(Qt::StrongFocus);
    hLayout->addWidget(m_quickWidget, 1);
    
    auto *vLayout = new QVBoxLayout();
    hLayout->addLayout(vLayout);
  3. Créez une autre classe pour gérer l'ajout de données et d'autres interactions avec le graphique :
    m_modifier = new GraphModifier(m_barGraph, this);
Mise en place du graphique à barres
  1. Configurez le graphique dans le constructeur de la classe GraphModifier:
    GraphModifier::GraphModifier(Q3DBarsWidgetItem *bargraph, QObject *parent)
        : QObject(parent)
        , m_graph(bargraph)
  2. Créez les axes et les séries dans des variables membres afin de pouvoir les modifier :
    , m_temperatureAxis(new QValue3DAxis)
    , m_yearAxis(new QCategory3DAxis)
    , m_monthAxis(new QCategory3DAxis)
    , m_primarySeries(new QBar3DSeries)
    , m_secondarySeries(new QBar3DSeries)
  3. Définir certaines qualités visuelles pour le graphique :
    m_graph->setShadowQuality(QtGraphs3D::ShadowQuality::SoftMedium);
    m_graph->setMultiSeriesUniform(true);
    // These are set through the active theme
    m_graph->activeTheme()->setPlotAreaBackgroundVisible(false);
    m_graph->activeTheme()->setLabelFont(QFont("Times New Roman", m_fontSize));
    m_graph->activeTheme()->setLabelBackgroundVisible(true);
  4. Définir les axes et en faire les axes actifs du graphique :
    m_temperatureAxis->setTitle("Average temperature");
    m_temperatureAxis->setSegmentCount(m_segments);
    m_temperatureAxis->setSubSegmentCount(m_subSegments);
    m_temperatureAxis->setRange(m_minval, m_maxval);
    m_temperatureAxis->setLabelFormat(u"%.1f "_s + m_celsiusString);
    m_temperatureAxis->setLabelAutoAngle(30.0f);
    m_temperatureAxis->setTitleVisible(true);
    
    m_yearAxis->setTitle("Year");
    m_yearAxis->setLabelAutoAngle(30.0f);
    m_yearAxis->setTitleVisible(true);
    
    m_monthAxis->setTitle("Month");
    m_monthAxis->setLabelAutoAngle(30.0f);
    m_monthAxis->setTitleVisible(true);
    
    m_graph->setValueAxis(m_temperatureAxis);
    m_graph->setRowAxis(m_yearAxis);
    m_graph->setColumnAxis(m_monthAxis);
  5. Donnez aux étiquettes des axes un petit angle d'autorotation :
    m_yearAxis->setLabelAutoAngle(30.0f);

    Cela permet de les orienter légèrement vers la caméra, ce qui améliore la lisibilité de l'étiquette de l'axe dans les angles extrêmes de la caméra.

  6. Initialiser les propriétés visuelles de la série. Notez que la deuxième série n'est pas visible au départ :
    m_primarySeries->setItemLabelFormat(u"Oulu - @colLabel @rowLabel: @valueLabel"_s);
    m_primarySeries->setMesh(QAbstract3DSeries::Mesh::BevelBar);
    m_primarySeries->setMeshSmooth(false);
    
    m_secondarySeries->setItemLabelFormat(u"Helsinki - @colLabel @rowLabel: @valueLabel"_s);
    m_secondarySeries->setMesh(QAbstract3DSeries::Mesh::BevelBar);
    m_secondarySeries->setMeshSmooth(false);
    m_secondarySeries->setVisible(false);
  7. Ajouter la série au graphique :
    m_graph->addSeries(m_primarySeries);
    m_graph->addSeries(m_secondarySeries);
  8. Définir l'angle de la caméra en appelant la même méthode que celle utilisée par le bouton de changement d'angle de la caméra dans l'interface utilisateur pour passer d'un angle de caméra à l'autre :
    changePresetCamera();
  9. Le nouveau préréglage de la caméra est ajouté au graphique :
    static int preset = int(QtGraphs3D::CameraPreset::Front);
    
    m_graph->setCameraPreset((QtGraphs3D::CameraPreset) preset);
    
    if (++preset > int(QtGraphs3D::CameraPreset::DirectlyBelow))
        preset = int(QtGraphs3D::CameraPreset::FrontLow);
Ajout de données au graphique

À la fin du constructeur, appelez une méthode qui configure les données :

resetTemperatureData();

Cette méthode ajoute des données aux séries concernées en utilisant des proxys des deux séries :

// Set up data
static const float tempOulu[8][12] = {
    {-7.4f, -2.4f, 0.0f, 3.0f, 8.2f, 11.6f, 14.7f, 15.4f, 11.4f, 4.2f, 2.1f, -2.3f},     // 2015
    {-13.4f, -3.9f, -1.8f, 3.1f, 10.6f, 13.7f, 17.8f, 13.6f, 10.7f, 3.5f, -3.1f, -4.2f}, // 2016
...
QBarDataArray dataSet;
QBarDataArray dataSet2;

dataSet.reserve(m_years.size());
for (qsizetype year = 0; year < m_years.size(); ++year) {
    // Create a data row
    QBarDataRow dataRow(m_months.size());
    QBarDataRow dataRow2(m_months.size());
    for (qsizetype month = 0; month < m_months.size(); ++month) {
        // Add data to the row
        dataRow[month].setValue(tempOulu[year][month]);
        dataRow2[month].setValue(tempHelsinki[year][month]);
    }
    // Add the row to the set
    dataSet.append(dataRow);
    dataSet2.append(dataRow2);
}

// Add data to the data proxy (the data proxy assumes ownership of it)
m_primarySeries->dataProxy()->resetArray(dataSet, m_years, m_months);
m_secondarySeries->dataProxy()->resetArray(dataSet2, m_years, m_months);
Utiliser des widgets pour contrôler le graphique

Poursuivez en ajoutant quelques widgets à l'adresse bargraph.cpp.

  1. Ajoutez un curseur :
    auto *rotationSliderX = new QSlider(Qt::Horizontal, m_container);
    rotationSliderX->setTickInterval(30);
    rotationSliderX->setTickPosition(QSlider::TicksBelow);
    rotationSliderX->setMinimum(-180);
    rotationSliderX->setValue(0);
    rotationSliderX->setMaximum(180);
  2. Utilisez le curseur pour faire pivoter le graphique au lieu d'utiliser simplement la souris ou le toucher. Ajoutez-le à la disposition verticale :
    vLayout->addWidget(new QLabel(u"Rotate horizontally"_s));
    vLayout->addWidget(rotationSliderX, 0, Qt::AlignTop);
  3. Connectez-le à une méthode dans GraphModifier:
    QObject::connect(rotationSliderX, &QSlider::valueChanged, m_modifier, &GraphModifier::rotateX);
  4. Créez un emplacement dans GraphModifier pour la connexion du signal. Spécifier la position réelle de la caméra le long de l'orbite autour du point central, au lieu de spécifier un angle de caméra prédéfini :
    void GraphModifier::rotateX(int angle)
    {
        m_xRotation = angle;
        m_graph->setCameraPosition(m_xRotation, m_yRotation);
    }

Vous pouvez maintenant utiliser le curseur pour faire pivoter le graphique.

Ajoutez d'autres widgets à la disposition verticale pour contrôler :

  • la rotation du graphique
  • Style d'étiquette
  • Préréglage de la caméra
  • Visibilité de l'arrière-plan
  • Visibilité de la grille
  • Lissage de l'ombrage des barres
  • Visibilité de la deuxième série de barres
  • Direction de l'axe des valeurs
  • Visibilité et rotation du titre de l'axe
  • Plage de données à afficher
  • Style des barres
  • Mode de sélection
  • Thème
  • Qualité de l'ombre
  • Police
  • Taille de la police
  • Rotation de l'étiquette de l'axe
  • Mode données

Certains contrôles de widgets sont intentionnellement désactivés lorsqu'ils sont en mode données ( Custom Proxy Data ).

Sélection d'une ligne ou d'une colonne en cliquant sur une étiquette d'axe

La sélection par étiquette d'axe est une fonctionnalité par défaut pour les graphiques à barres. Par exemple, vous pouvez sélectionner des lignes en cliquant sur une étiquette d'axe de la manière suivante :

  1. Changez le mode de sélection en Row
  2. Cliquez sur une étiquette d'année
  3. La ligne correspondant à l'année cliquée est sélectionnée.

La même méthode fonctionne avec les drapeaux Slice et Item, à condition que Row ou Column soit également activé.

Zoom sur la sélection

Pour illustrer l'ajustement de la cible de la caméra, implémentez une animation de zoom jusqu'à la sélection via une pression sur un bouton. L'initialisation de l'animation se fait dans le constructeur :

m_defaultAngleX = m_graph->cameraXRotation();
m_defaultAngleY = m_graph->cameraYRotation();
m_defaultZoom = m_graph->cameraZoomLevel();
m_defaultTarget = m_graph->cameraTargetPosition();

m_animationCameraX.setTargetObject(m_graph);
m_animationCameraY.setTargetObject(m_graph);
m_animationCameraZoom.setTargetObject(m_graph);
m_animationCameraTarget.setTargetObject(m_graph);

m_animationCameraX.setPropertyName("cameraXRotation");
m_animationCameraY.setPropertyName("cameraYRotation");
m_animationCameraZoom.setPropertyName("cameraZoomLevel");
m_animationCameraTarget.setPropertyName("cameraTargetPosition");

int duration = 1700;
m_animationCameraX.setDuration(duration);
m_animationCameraY.setDuration(duration);
m_animationCameraZoom.setDuration(duration);
m_animationCameraTarget.setDuration(duration);

// The zoom always first zooms out above the graph and then zooms in
qreal zoomOutFraction = 0.3;
m_animationCameraX.setKeyValueAt(zoomOutFraction, QVariant::fromValue(0.0f));
m_animationCameraY.setKeyValueAt(zoomOutFraction, QVariant::fromValue(90.0f));
m_animationCameraZoom.setKeyValueAt(zoomOutFraction, QVariant::fromValue(50.0f));
m_animationCameraTarget.setKeyValueAt(zoomOutFraction,
                                      QVariant::fromValue(QVector3D(0.0f, 0.0f, 0.0f)));

La fonction GraphModifier::zoomToSelectedBar() contient la fonctionnalité de zoom. QPropertyAnimation m_animationCameraTarget cible la propriété cameraTargetPosition, qui prend une valeur normalisée dans l'intervalle (-1, 1).

Déterminez la position de la barre sélectionnée par rapport aux axes et utilisez-la comme valeur finale pour m_animationCameraTarget:

QVector3D endTarget;
float xMin = m_graph->columnAxis()->min();
float xRange = m_graph->columnAxis()->max() - xMin;
float zMin = m_graph->rowAxis()->min();
float zRange = m_graph->rowAxis()->max() - zMin;
endTarget.setX((selectedBar.y() - xMin) / xRange * 2.0f - 1.0f);
endTarget.setZ((selectedBar.x() - zMin) / zRange * 2.0f - 1.0f);
...
m_animationCameraTarget.setEndValue(QVariant::fromValue(endTarget));

Ensuite, faites pivoter la caméra de manière à ce qu'elle pointe toujours approximativement vers le centre du graphique à la fin de l'animation :

qreal endAngleX = 90.0 - qRadiansToDegrees(qAtan(qreal(endTarget.z() / endTarget.x())));
if (endTarget.x() > 0.0f)
    endAngleX -= 180.0f;
float barValue = m_graph->selectedSeries()
                     ->dataProxy()
                     ->itemAt(selectedBar.x(), selectedBar.y())
                     .value();
float endAngleY = barValue >= 0.0f ? 30.0f : -30.0f;
if (m_graph->valueAxis()->reversed())
    endAngleY *= -1.0f;
Modèle Proxy pour les données

Lorsque vous activez le mode de données Model Proxy Data, le graphique de l'exemple utilise un ensemble de données basé sur un modèle d'élément et le proxy correspondant.

Données pluviométriques

La classe RainfallData met en place un QItemModelBarDataProxy ainsi que les séries et les axes.

  1. Nous utilisons un std::array<double, 12> pour stocker les valeurs d'une année indexée par mois. La fonction d'aide statique readData() lit les données et renvoie un QList indexé par année :
    using YearlyData = std::array<double, 12>;using ModelData = QList<YearlyData>;static ModelData readData(const QString &fileName, int *firstYear) { ModelData result ; *firstYear =-1; // Lit les données d'un fichier de données dans la liste des éléments de données   QFile dataFile(fileName) ; if (!dataFile.open(QIODevice::ReadOnly | QIODevice::Text)) {        qWarning() << "Unable to open data file:" << dataFile.fileName() << dataFile.errorString();
           return result ; } QTextStream stream(&dataFile) ; int lastYear =-1; while (!stream.atEnd()) { QString line = stream.readLine() ; if (line.startsWith(u'#')) // Ignore les commentaires continue; const auto strList = QStringView{line}.split(',', Qt::SkipEmptyParts) ; // Chaque ligne contient trois données : Année, mois et valeur des précipitations if (strList.size() < 3) {            qWarning() << "Invalid row read from data:" << line;
               continue; } // Stocker l'année et le mois en tant qu'int, et la valeur de la pluie en tant que double bool yearOk{} ; bool monthOk{} ; bool valueOk{} ; const int year = strList.at(0).trimmed().toInt(&yearOk) ; const int month = strList.at(1).trimmed().toInt(&monthOk) ; const double value = strList.at(2).trimmed().toDouble(&valueOk) ; if (!yearOk || !monthOk || month < 1 || month > 12 ||  !valueOk) {            qWarning() << "Invalid row values:" << line;
               continue; } if (year != lastYear) { if (lastYear ==-1) { *firstYear = year ; } else if (year != lastYear + 1) {                qWarning() << "Non-consecutive years" << year << lastYear;
                   return {} ; } lastYear = year ; result.emplace_back(YearlyData{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) ; } result.back()[month - 1] = value ; } return result ; } ...
  2. QList représente un tableau à deux dimensions indexé par l'année et le mois, respectivement. À partir de là, nous remplissons un QRangeModel et le transmettons à un QItemModelBarDataProxy() dans la classe RainfallData:
    int firstYear{};
    auto data = readData(":/data/raindata.txt"_L1, &firstYear);
    Q_ASSERT(!data.isEmpty());
    updateYearsList(firstYear, firstYear + int(data.size()) - 1);
    auto *model = new QRangeModel(data, this);
    
    m_proxy = new QItemModelBarDataProxy(model);
    m_proxy->setUseModelCategories(true);
    m_series = new QBar3DSeries(m_proxy);
  3. Enfin, nous ajoutons une fonction permettant d'obtenir la série créée pour l'afficher :
    QBar3DSeries *customSeries() { return m_series; }

Graphique de dispersion

Dans l'onglet Scatter Graph, créez un graphique de dispersion en 3D à l'aide de Q3DScatterWidgetItem. L'exemple montre comment :

Pour la création d'une application de base, voir Bar Graph.

Configuration du graphique en nuage de points
  1. Définissez certaines qualités visuelles du graphique dans le constructeur de l'application ScatterDataModifier:
    m_graph->setShadowQuality(QtGraphs3D::ShadowQuality::SoftHigh);
    m_graph->setCameraPreset(QtGraphs3D::CameraPreset::Front);
    m_graph->setCameraZoomLevel(80.f);
    // These are set through active theme
    m_graph->activeTheme()->setTheme(QGraphsTheme::Theme::MixSeries);
    m_graph->activeTheme()->setColorScheme(QGraphsTheme::ColorScheme::Dark);

    Aucun de ces paramètres n'est obligatoire, mais ils permettent d'ignorer les valeurs par défaut du graphique. Pour observer l'apparence avec les valeurs par défaut prédéfinies, le bloc ci-dessus peut être commenté.

  2. Créez un QScatterDataProxy et le QScatter3DSeries associé. Définissez un format d'étiquette personnalisé et un lissage de maille pour la série et ajoutez-la au graphique :
    auto *proxy = new QScatterDataProxy;
    auto *series = new QScatter3DSeries(proxy);
    series->setItemLabelFormat(u"@xTitle: @xLabel @yTitle: @yLabel @zTitle: @zLabel"_s);
    series->setMeshSmooth(m_smooth);
    m_graph->addSeries(series);
Ajout de données de dispersion
  1. Dans le constructeur de ScatterDataModifier, ajoutez des données au graphique :
    addData();
  2. L'ajout des données est effectué dans la méthode addData(). Commencez par configurer les axes :
    m_graph->axisX()->setTitle("X");
    m_graph->axisY()->setTitle("Y");
    m_graph->axisZ()->setTitle("Z");

    Vous pouvez également le faire dans le constructeur de ScatterDataModifier. En le faisant ici, le constructeur reste plus simple et la configuration des axes est proche des données.

  3. Créez un tableau de données et remplissez-le :
    QScatterDataArray dataArray;
    dataArray.reserve(m_itemCount);
        ...
    const float limit = qSqrt(m_itemCount) / 2.0f;
    for (int i = -limit; i < limit; ++i) {
        for (int j = -limit; j < limit; ++j) {
            const float x = float(i) + 0.5f;
            const float y = qCos(qDegreesToRadians(float(i * j) / m_curveDivider));
            const float z = float(j) + 0.5f;
            dataArray.append(QScatterDataItem(x, y, z));
        }
    }
  4. Enfin, indiquez au proxy de commencer à utiliser les données que nous lui avons fournies :
    m_graph->seriesList().at(0)->dataProxy()->resetArray(dataArray);

Le graphique dispose désormais des données et est prêt à être utilisé. Pour plus d'informations sur l'ajout de widgets pour contrôler le graphique, voir Utiliser des widgets pour contrôler le graphique.

Remplacement de la gestion des entrées par défaut

Pour remplacer le mécanisme de gestion des entrées par défaut, définissez les nouveaux gestionnaires d'entrée de Q3DScatterWidgetItem, qui implémente le comportement personnalisé :

connect(m_graph,
        &Q3DGraphsWidgetItem::selectedElementChanged,
        this,
        &ScatterDataModifier::handleElementSelected);
connect(m_graph, &Q3DGraphsWidgetItem::dragged, this, &ScatterDataModifier::handleAxisDragging);
m_graph->setDragButton(Qt::LeftButton);
Extension de la gestion des événements de la souris

Implémentez un nouveau gestionnaire d'événements drag. Il fournit une distance de déplacement de la souris pour le calcul du déplacement de l'axe (voir Mise en œuvre du déplacement de l'axe pour plus de détails) :

connect(m_graph,
        &Q3DGraphsWidgetItem::selectedElementChanged,
        this,
        &ScatterDataModifier::handleElementSelected);
connect(m_graph, &Q3DGraphsWidgetItem::dragged, this, &ScatterDataModifier::handleAxisDragging);
m_graph->setDragButton(Qt::LeftButton);
Mise en œuvre du déplacement de l'axe
  1. Commencer à écouter le signal de sélection du graphique. Faites-le dans le constructeur et connectez-le à la méthode handleElementSelected:
    connect(m_graph,
            &Q3DGraphsWidgetItem::selectedElementChanged,
            this,
            &ScatterDataModifier::handleElementSelected);
    connect(m_graph, &Q3DGraphsWidgetItem::dragged, this, &ScatterDataModifier::handleAxisDragging);
    m_graph->setDragButton(Qt::LeftButton);
  2. Dans handleElementSelected, vérifiez le type de la sélection et définissez l'état interne en fonction de celui-ci :
    switch (type) {
    case QtGraphs3D::ElementType::AxisXLabel:
        m_state = StateDraggingX;
        break;
    case QtGraphs3D::ElementType::AxisYLabel:
        m_state = StateDraggingY;
        break;
    case QtGraphs3D::ElementType::AxisZLabel:
        m_state = StateDraggingZ;
        break;
    default:
        m_state = StateNormal;
        break;
    }
  3. La logique de déplacement proprement dite est mise en œuvre dans la méthode handleAxisDragging, qui est appelée à partir de l'événement drag:
    void ScatterDataModifier::handleAxisDragging(QVector2D delta)
  4. Dans handleAxisDragging, il faut d'abord obtenir l'orientation de la scène à partir de la caméra active :
    // Get scene orientation from active camera
    float xRotation = m_graph->cameraXRotation();
    float yRotation = m_graph->cameraYRotation();
  5. Calculer les modificateurs pour la direction du mouvement de la souris en fonction de l'orientation :
    // Calculate directional drag multipliers based on rotation
    float xMulX = qCos(qDegreesToRadians(xRotation));
    float xMulY = qSin(qDegreesToRadians(xRotation));
    float zMulX = qSin(qDegreesToRadians(xRotation));
    float zMulY = qCos(qDegreesToRadians(xRotation));
  6. Calculer le mouvement de la souris et le modifier en fonction de la rotation en y de la caméra :
    // Get the drag amount
    QPoint move = delta.toPoint();
    
    // Flip the effect of y movement if we're viewing from below
    float yMove = (yRotation < 0) ? -move.y() : move.y();
  7. Appliquer la distance déplacée au bon axe :
    // Adjust axes
    QValue3DAxis *axis = nullptr;
    switch (m_state) {
    case StateDraggingX:
        axis = m_graph->axisX();
        distance = (move.x() * xMulX - yMove * xMulY) / m_dragSpeedModifier;
        axis->setRange(axis->min() - distance, axis->max() - distance);
        break;
    case StateDraggingZ:
        axis = m_graph->axisZ();
        distance = (move.x() * zMulX + yMove * zMulY) / m_dragSpeedModifier;
        axis->setRange(axis->min() + distance, axis->max() + distance);
        break;
    case StateDraggingY:
        axis = m_graph->axisY();
        distance = move.y() / m_dragSpeedModifier; // No need to use adjusted y move here
        axis->setRange(axis->min() + distance, axis->max() + distance);
        break;
    default:
        break;
    }

Graphique de surface

Dans l'onglet Surface Graph, créez un graphique de surface en 3D à l'aide de Q3DSurfaceWidgetItem. L'exemple montre comment :

  • Configurer un site QSurfaceDataProxy de base et définir les données correspondantes.
  • Utiliser QHeightMapSurfaceDataProxy pour afficher des cartes d'altitude en 3D.
  • Utiliser des données topographiques pour créer des cartes d'altitude en 3D.
  • Utiliser trois modes de sélection différents pour étudier le graphique.
  • Utilisez des plages d'axes pour afficher des parties sélectionnées du graphique.
  • Définir un gradient de surface personnalisé.
  • Ajouter des éléments et des étiquettes personnalisés avec QCustom3DItem et QCustom3DLabel.
  • Utiliser un gestionnaire d'entrée personnalisé pour activer le zoom et le panoramique.
  • Mettre en évidence une zone de la surface.

Pour la création d'une application de base, voir Bar Graph.

Surface simple avec données générées
  1. Tout d'abord, instanciez un nouveau QSurfaceDataProxy et attachez-le à un nouveau QSurface3DSeries:
    m_sqrtSinProxy = new QSurfaceDataProxy();
    m_sqrtSinSeries = new QSurface3DSeries(m_sqrtSinProxy);
  2. Remplissez le proxy avec une simple racine carrée et des données sinusoïdales. Créez une instance QSurfaceDataArray et ajoutez-y des éléments QSurfaceDataRow. Définissez l'instance QSurfaceDataArray créée comme tableau de données pour l'instance QSurfaceDataProxy en appelant resetArray().
    QSurfaceDataArray dataArray;
    dataArray.reserve(sampleCountZ);
    for (int i = 0; i < sampleCountZ; ++i) {
        QSurfaceDataRow newRow;
        newRow.reserve(sampleCountX);
        // Keep values within range bounds, since just adding step can cause minor
        // drift due to the rounding errors.
        float z = qMin(sampleMax, (i * stepZ + sampleMin));
        for (int j = 0; j < sampleCountX; ++j) {
            float x = qMin(sampleMax, (j * stepX + sampleMin));
            float R = qSqrt(z * z + x * x) + 0.01f;
            float y = (qSin(R) / R + 0.24f) * 1.61f;
            newRow.append(QSurfaceDataItem(x, y, z));
        }
        dataArray.append(newRow);
    }
    
    m_sqrtSinProxy->resetArray(dataArray);
Données de la carte d'altitude multisérie

Créez la carte des hauteurs en instanciant un QHeightMapSurfaceDataProxy avec un QImage contenant les données sur les hauteurs. Utilisez QHeightMapSurfaceDataProxy::setValueRanges() pour définir la plage de valeurs de la carte. Dans l'exemple, la carte provient d'une position imaginaire de 34,0° N - 40,0° N et 18,0° E - 24,0° E. Ces valeurs sont utilisées pour positionner la carte sur les axes.

// Create the first surface layer
QImage heightMapImageOne(":/data/layer_1.png");
m_heightMapProxyOne = new QHeightMapSurfaceDataProxy(heightMapImageOne);
m_heightMapSeriesOne = new QSurface3DSeries(m_heightMapProxyOne);
m_heightMapSeriesOne->setItemLabelFormat(u"(@xLabel, @zLabel): @yLabel"_s);
m_heightMapProxyOne->setValueRanges(34.f, 40.f, 18.f, 24.f);

Ajoutez les autres couches de surface de la même manière, en créant un proxy et une série pour eux en utilisant les images de la carte des hauteurs. -

Données de la carte topographique

Les données topographiques sont obtenues auprès du National Land Survey de Finlande. Il fournit un produit appelé Elevation Model 2 m, qui convient à cet exemple.

Les données topographiques proviennent de Levi fell. La précision des données dépasse largement les besoins, c'est pourquoi elles sont compressées et encodées dans un fichier PNG. La valeur de hauteur des données ASCII d'origine est encodée au format RVB à l'aide d'un multiplicateur, comme le montre l'exemple de code ci-dessous. Le multiplicateur est calculé en divisant la plus grande valeur de 24 bits par le point le plus élevé en Finlande.

QHeightMapSurfaceDataProxy ne convertit que les valeurs d'un octet. Pour utiliser la plus grande précision des données du National Land Survey of Finland, lisez les données du fichier PNG et décodez-les dans un fichier QSurface3DSeries.

  1. Définissez le multiplicateur d'encodage :
    // Value used to encode height data as RGB value on PNG file
    const float packingFactor = 11983.f;
  2. Effectuez le décodage proprement dit :
    QImage heightMapImage(file);
    uchar *bits = heightMapImage.bits();
    int imageHeight = heightMapImage.height();
    int imageWidth = heightMapImage.width();
    int widthBits = imageWidth * 4;
    float stepX = width / float(imageWidth);
    float stepZ = height / float(imageHeight);
    
    QSurfaceDataArray dataArray;
    dataArray.reserve(imageHeight);
    for (int i = 0; i < imageHeight; ++i) {
        int p = i * widthBits;
        float z = height - float(i) * stepZ;
        QSurfaceDataRow newRow;
        newRow.reserve(imageWidth);
        for (int j = 0; j < imageWidth; ++j) {
            uchar aa = bits[p + 0];
            uchar rr = bits[p + 1];
            uchar gg = bits[p + 2];
            uint color = uint((gg << 16) + (rr << 8) + aa);
            float y = float(color) / packingFactor;
            newRow.append(QSurfaceDataItem(float(j) * stepX, y, z));
            p += 4;
        }
        dataArray.append(newRow);
    }
    
    dataProxy()->resetArray(dataArray);

Désormais, un graphique de surface peut consommer les données via le proxy.

Sélection de la série de données

Pour illustrer les différents proxy, le site Surface Graph comporte trois boutons radio permettant de passer d'une série à l'autre.

Avec Sqrt & Sin, la série générée simple est activée.

  1. Définissez les caractéristiques décoratives, telles que l'activation de la grille pour la surface et la sélection du mode d'ombrage plat.
  2. Définissez le format de l'étiquette de l'axe et les plages de valeurs. Réglez la rotation automatique de l'étiquette pour améliorer la lisibilité de l'étiquette lorsque l'angle de la caméra est faible.
  3. Assurez-vous que la bonne série est ajoutée au graphique et que les autres ne le sont pas.
m_sqrtSinSeries->setDrawMode(QSurface3DSeries::DrawSurfaceAndWireframe);
m_sqrtSinSeries->setShading(QSurface3DSeries::Shading::Flat);

m_graph->axisX()->setLabelFormat("%.2f");
m_graph->axisZ()->setLabelFormat("%.2f");
m_graph->axisX()->setRange(sampleMin, sampleMax);
m_graph->axisY()->setRange(0.f, 2.f);
m_graph->axisZ()->setRange(sampleMin, sampleMax);
m_graph->axisX()->setLabelAutoAngle(30.f);
m_graph->axisY()->setLabelAutoAngle(90.f);
m_graph->axisZ()->setLabelAutoAngle(30.f);

m_graph->removeSeries(m_heightMapSeriesOne);
m_graph->removeSeries(m_heightMapSeriesTwo);
m_graph->removeSeries(m_heightMapSeriesThree);
m_graph->removeSeries(m_topography);
m_graph->removeSeries(m_highlight);

m_graph->addSeries(m_sqrtSinSeries);

Avec Multiseries Height Map, les séries de la carte d'altitude sont activées et les autres sont désactivées. L'ajustement automatique de la plage de l'axe Y fonctionne bien pour la surface de la carte d'altitude, assurez-vous donc qu'il est activé.

m_graph->axisY()->setAutoAdjustRange(true);

Avec Textured Topography, la série topographique est activée et les autres sont désactivées. Activez un gestionnaire d'entrée personnalisé pour cette série, afin de pouvoir mettre des zones en évidence :

m_graph->setDragButton(Qt::LeftButton);
QObject::connect(m_graph,
                 &Q3DGraphsWidgetItem::dragged,
                 this,
                 &SurfaceGraphModifier::handleAxisDragging);

QObject::connect(m_graph,
                 &Q3DGraphsWidgetItem::wheel,
                 this,
                 &SurfaceGraphModifier::onWheel);
m_graph->setZoomEnabled(false);

Voir Use Custom Input Handler to Enable Zooming and Panning (Utiliser un gestionnaire d'entrée personnalisé pour activer le zoom et le panoramique) pour obtenir des informations sur le gestionnaire d'entrée personnalisé pour cette série de données.

Modes de sélection

Les trois modes de sélection pris en charge par Q3DSurfaceWidgetItem peuvent être utilisés avec des boutons radio. Pour activer le mode sélectionné ou l'effacer, ajoutez les méthodes en ligne suivantes :

void toggleModeNone() { m_graph->setSelectionMode(QtGraphs3D::SelectionFlag::None); }
void toggleModeItem() { m_graph->setSelectionMode(QtGraphs3D::SelectionFlag::Item); }
void toggleModeSliceRow()
{
    m_graph->setSelectionMode(QtGraphs3D::SelectionFlag::ItemAndRow
                              | QtGraphs3D::SelectionFlag::Slice
                              | QtGraphs3D::SelectionFlag::MultiSeries);
}
void toggleModeSliceColumn()
{
    m_graph->setSelectionMode(QtGraphs3D::SelectionFlag::ItemAndColumn
                              | QtGraphs3D::SelectionFlag::Slice
                              | QtGraphs3D::SelectionFlag::MultiSeries);
}

Pour permettre d'effectuer une sélection par tranche sur toutes les séries visibles du graphique simultanément, ajoutez les drapeaux QtGraphs3D::SelectionFlag::Slice et QtGraphs3D::SelectionFlag::MultiSeries pour les modes de sélection par ligne et par colonne.

Plages d'axes pour l'étude du graphique

L'exemple comporte quatre curseurs permettant d'ajuster les valeurs minimales et maximales des axes X et Z. Lors de la sélection du proxy, ces curseurs sont activés. Lors de la sélection du proxy, ces curseurs sont ajustés pour correspondre aux plages d'axes de l'ensemble de données actuel :

// Reset range sliders for Sqrt & Sin
m_rangeMinX = sampleMin;
m_rangeMinZ = sampleMin;
m_stepX = (sampleMax - sampleMin) / float(sampleCountX - 1);
m_stepZ = (sampleMax - sampleMin) / float(sampleCountZ - 1);
m_axisMinSliderX->setMinimum(0);
m_axisMinSliderX->setMaximum(sampleCountX - 2);
m_axisMinSliderX->setValue(0);
m_axisMaxSliderX->setMinimum(1);
m_axisMaxSliderX->setMaximum(sampleCountX - 1);
m_axisMaxSliderX->setValue(sampleCountX - 1);
m_axisMinSliderZ->setMinimum(0);
m_axisMinSliderZ->setMaximum(sampleCountZ - 2);
m_axisMinSliderZ->setValue(0);
m_axisMaxSliderZ->setMinimum(1);
m_axisMaxSliderZ->setMaximum(sampleCountZ - 1);
m_axisMaxSliderZ->setValue(sampleCountZ - 1);

Pour ajouter au graphique la prise en charge de la définition de la plage X à partir des commandes du widget, ajoutez :

void SurfaceGraphModifier::setAxisXRange(float min, float max)
{
    m_graph->axisX()->setRange(min, max);
}

Pour ajouter la prise en charge de la plage Z, procédez de la même manière.

Gradients de surface personnalisés

Avec l'ensemble de données Sqrt & Sin, il est possible d'utiliser des gradients de surface personnalisés à l'aide de deux boutons-poussoirs. Définissez le dégradé avec QLinearGradient, où les couleurs souhaitées sont définies. Changez également le style de couleur en Q3DTheme::ColorStyle::RangeGradient pour utiliser le dégradé.

QLinearGradient gr;
gr.setColorAt(0.f, Qt::black);
gr.setColorAt(0.33f, Qt::blue);
gr.setColorAt(0.67f, Qt::red);
gr.setColorAt(1.f, Qt::yellow);

m_sqrtSinSeries->setBaseGradient(gr);
m_sqrtSinSeries->setColorStyle(QGraphsTheme::ColorStyle::RangeGradient);
Ajout de maillages personnalisés à l'application

Pour ajouter des maillages personnalisés à l'application :

  • Pour une compilation cmake. Ajoutez les fichiers de maillage à CMakeLists.txt:
    set(graphgallery_resource_files
        ...
        "data/oilrig.mesh"
        "data/pipe.mesh"
        "data/refinery.mesh"
        ...
    )
    
    qt6_add_resources(widgetgraphgallery "widgetgraphgallery"
        PREFIX
            "/"
        FILES
            ${graphgallery_resource_files}
    )
  • Pour une compilation qmake. Ajouter les fichiers de maillage dans le fichier de ressources qrc :
    <RCC>
        <qresource prefix="/">
            ...
            <file>data/refinery.mesh</file>
            <file>data/oilrig.mesh</file>
            <file>data/pipe.mesh</file>
            ...
        </qresource>
    </RCC>
Ajout d'un élément personnalisé à un graphique

Avec l'ensemble de données Multiseries Height Map, les éléments personnalisés sont insérés dans le graphique et peuvent être activés ou désactivés à l'aide de cases à cocher. D'autres modifications visuelles peuvent également être contrôlées à l'aide d'une autre série de cases à cocher, notamment la transparence pour les deux couches supérieures et la surbrillance pour la couche inférieure.

  • Création d'un petit QImage. Remplissez-le d'une seule couleur qui servira de couleur à l'objet personnalisé :
    QImage color = QImage(2, 2, QImage::Format_RGB32);
    color.fill(Qt::red);
  • Spécifier la position de l'objet dans une variable. La position peut ensuite être utilisée pour retirer le bon élément du graphique :
    QVector3D positionOne = QVector3D(39.f, 77.f, 19.2f);
  • Créer un nouveau site QCustom3DItem avec tous les paramètres :
    auto *item = new QCustom3DItem(":/data/oilrig.mesh",
                                   positionOne,
                                   QVector3D(0.0125f, 0.0125f, 0.0125f),
                                   QQuaternion::fromAxisAndAngle(0.f, 1.f, 0.f, 45.f),
                                   color);
  • Ajouter l'élément au graphique :
    m_graph->addCustomItem(item);
Ajouter une étiquette personnalisée à un graphique

L'ajout d'une étiquette personnalisée est très similaire à l'ajout d'un élément personnalisé. Pour l'étiquette, il n'est pas nécessaire d'avoir un maillage personnalisé, mais simplement une instance QCustom3DLabel:

auto *label = new QCustom3DLabel();
label->setText("Oil Rig One");
label->setPosition(positionOneLabel);
label->setScaling(QVector3D(1.f, 1.f, 1.f));
m_graph->addCustomItem(label);
Suppression d'un élément personnalisé d'un graphique

Pour supprimer un élément spécifique du graphique, appelez removeCustomItemAt() avec la position de l'élément :

m_graph->removeCustomItemAt(positionOne);

Remarque : la suppression d'un élément personnalisé du graphique entraîne également la suppression de l'objet. Pour conserver l'élément, utilisez plutôt la méthode releaseCustomItem().

Texture d'une série de surface

Avec le jeu de données Textured Topography, créez une texture de carte à utiliser avec la carte d'altitude topographique.

Définissez une image à utiliser comme texture sur une surface avec QSurface3DSeries::setTextureFile(). Ajoutez une case à cocher pour contrôler si la texture est définie ou non, et un gestionnaire pour réagir à l'état de la case à cocher :

void SurfaceGraphModifier::toggleSurfaceTexture(bool enable)
{
    if (enable)
        m_topography->setTextureFile(":/data/maptexture.jpg");
    else
        m_topography->setTextureFile("");
}

Dans cet exemple, l'image est lue à partir d'un fichier JPG. La définition d'un fichier vide avec la méthode efface la texture, et la surface utilise les dégradés ou les couleurs du thème.

Utilisation d'un gestionnaire d'entrée personnalisé pour activer le zoom et le panoramique

Avec l'ensemble de données Textured Topography, créez un gestionnaire d'entrée personnalisé pour mettre en évidence la sélection sur le graphique et permettre un panoramique sur le graphique.

La mise en œuvre du panoramique est similaire à celle présentée dans la section Mise en œuvre du glissement d'axe. La différence est que, dans cet exemple, vous ne suivez que les axes X et Z et ne permettez pas de faire glisser la surface en dehors du graphique. Pour limiter le déplacement, suivez les limites des axes et ne faites rien si vous sortez du graphique :

case StateDraggingX:
    distance = (move.x() * xMulX - move.y() * xMulY) * m_speedModifier;
    m_axisXMinValue -= distance;
    m_axisXMaxValue -= distance;
    if (m_axisXMinValue < m_areaMinValue) {
        float dist = m_axisXMaxValue - m_axisXMinValue;
        m_axisXMinValue = m_areaMinValue;
        m_axisXMaxValue = m_axisXMinValue + dist;
    }
    if (m_axisXMaxValue > m_areaMaxValue) {
        float dist = m_axisXMaxValue - m_axisXMinValue;
        m_axisXMaxValue = m_areaMaxValue;
        m_axisXMinValue = m_axisXMaxValue - dist;
    }
    m_graph->axisX()->setRange(m_axisXMinValue, m_axisXMaxValue);
    break;

Pour le zoom, attrapez le site wheelEvent et ajustez les plages des axes X et Y en fonction de la valeur delta sur QWheelEvent. Ajustez l'axe Y de manière à ce que le rapport d'aspect entre l'axe Y et le plan XZ reste le même. Cela permet d'éviter d'obtenir un graphique dans lequel la hauteur est exagérée :

void SurfaceGraphModifier::onWheel(QWheelEvent *event)
{
    float delta = float(event->angleDelta().y());

    m_axisXMinValue += delta;
    m_axisXMaxValue -= delta;
    m_axisZMinValue += delta;
    m_axisZMaxValue -= delta;
    checkConstraints();

    float y = (m_axisXMaxValue - m_axisXMinValue) * m_aspectRatio;

    m_graph->axisX()->setRange(m_axisXMinValue, m_axisXMaxValue);
    m_graph->axisY()->setRange(100.f, y);
    m_graph->axisZ()->setRange(m_axisZMinValue, m_axisZMaxValue);
}

Ensuite, ajoutez quelques limites au niveau de zoom, afin qu'il ne s'approche pas trop de la surface ou qu'il ne s'en éloigne pas trop. Par exemple, si la valeur de l'axe X est inférieure à la limite autorisée, c'est-à-dire si le zoom est trop important, la valeur est fixée à la valeur minimale autorisée. Si la plage passe en dessous du minimum autorisé, les deux extrémités de l'axe sont ajustées de manière à ce que la plage reste à la limite :

if (m_axisXMinValue < m_areaMinValue)
    m_axisXMinValue = m_areaMinValue;
if (m_axisXMaxValue > m_areaMaxValue)
    m_axisXMaxValue = m_areaMaxValue;
// Don't allow too much zoom in
if ((m_axisXMaxValue - m_axisXMinValue) < m_axisXMinRange) {
    float adjust = (m_axisXMinRange - (m_axisXMaxValue - m_axisXMinValue)) / 2.f;
    m_axisXMinValue -= adjust;
    m_axisXMaxValue += adjust;
}
Mise en évidence d'une zone de la surface

Pour mettre en œuvre une mise en évidence à afficher sur la surface, créez une copie de la série et ajoutez un décalage à la valeur y. Dans cet exemple, la classe HighlightSeries implémente la création de la copie dans sa méthode handlePositionChange.

Tout d'abord, donnez à HighlightSeries le pointeur vers la série originale, puis commencez à écouter le signal QSurface3DSeries::selectedPointChanged:

void HighlightSeries::setTopographicSeries(TopographicSeries *series)
{
    m_topographicSeries = series;
    m_srcWidth = m_topographicSeries->dataArray().at(0).size();
    m_srcHeight = m_topographicSeries->dataArray().size();

    QObject::connect(m_topographicSeries,
                     &QSurface3DSeries::selectedPointChanged,
                     this,
                     &HighlightSeries::handlePositionChange);
}

Lorsque le signal se déclenche, vérifiez que la position est valide. Ensuite, calculez les plages pour la zone copiée et vérifiez qu'elles restent dans les limites. Enfin, remplissez le tableau de données de la série "highlight" avec la plage du tableau de données de la série "topographie" :

void HighlightSeries::handlePositionChange(const QPoint &position)
{
    m_position = position;

    if (position == invalidSelectionPosition()) {
        setVisible(false);
        return;
    }

    int halfWidth = m_width / 2;
    int halfHeight = m_height / 2;

    int startX = position.x() - halfWidth;
    if (startX < 0)
        startX = 0;
    int endX = position.x() + halfWidth;
    if (endX > (m_srcWidth - 1))
        endX = m_srcWidth - 1;
    int startZ = position.y() - halfHeight;
    if (startZ < 0)
        startZ = 0;
    int endZ = position.y() + halfHeight;
    if (endZ > (m_srcHeight - 1))
        endZ = m_srcHeight - 1;

    const QSurfaceDataArray &srcArray = m_topographicSeries->dataArray();

    QSurfaceDataArray dataArray;
    dataArray.reserve(endZ - startZ);
    for (int i = startZ; i < endZ; ++i) {
        QSurfaceDataRow newRow;
        newRow.reserve(endX - startX);
        QSurfaceDataRow srcRow = srcArray.at(i);
        for (int j = startX; j < endX; ++j) {
            QVector3D pos = srcRow.at(j).position();
            pos.setY(pos.y() + m_heightAdjustment);
            newRow.append(QSurfaceDataItem(pos));
        }
        dataArray.append(newRow);
    }

    dataProxy()->resetArray(dataArray);
    setVisible(true);
}
Un dégradé pour la série Highlight

Comme le site HighlightSeries est un site QSurface3DSeries, toutes les méthodes de décoration d'une série sont disponibles. Dans cet exemple, ajoutez un dégradé pour mettre en évidence l'élévation. Étant donné que le style de dégradé approprié dépend de la plage de l'axe Y et que nous modifions la plage lorsque nous effectuons un zoom, les positions des couleurs du dégradé doivent être ajustées en fonction de la modification de la plage. Pour ce faire, définissez des valeurs proportionnelles pour les positions des couleurs du dégradé :

const float darkRedPos = 1.f;
const float redPos = 0.8f;
const float yellowPos = 0.6f;
const float greenPos = 0.4f;
const float darkGreenPos = 0.2f;

La modification du dégradé est effectuée dans la méthode handleGradientChange, il faut donc la connecter pour qu'elle réagisse aux changements de l'axe Y :

QObject::connect(m_graph->axisY(),
                 &QValue3DAxis::maxChanged,
                 m_highlight,
                 &HighlightSeries::handleGradientChange);

Lorsqu'une modification de la valeur maximale de l'axe Y se produit, calculez les nouvelles positions des couleurs du dégradé :

void HighlightSeries::handleGradientChange(float value)
{
    float ratio = m_minHeight / value;

    QLinearGradient gr;
    gr.setColorAt(0.f, Qt::black);
    gr.setColorAt(darkGreenPos * ratio, Qt::darkGreen);
    gr.setColorAt(greenPos * ratio, Qt::green);
    gr.setColorAt(yellowPos * ratio, Qt::yellow);
    gr.setColorAt(redPos * ratio, Qt::red);
    gr.setColorAt(darkRedPos * ratio, Qt::darkRed);

    setBaseGradient(gr);
    setColorStyle(QGraphsTheme::ColorStyle::RangeGradient);

    handleZoomChange(ratio);
}

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.