Galerie de graphiques
Galerie de graphiques à barres, de nuages de points et de surfaces.
La galerie de graphiques présente ces trois types de graphiques ainsi que certaines de leurs fonctionnalités spécifiques. Les graphiques disposent de leurs propres onglets dans l'application.

Exécution de l'exemple
Vous pouvez exécuter l'exemple à partir de :
- Qt Creator
Ouvrez le mode « Welcome » et sélectionnez l'exemple dans « Examples ». Pour plus d'informations, consultez Qt Creator: Tutoriel : Compiler et exécuter.
- Qt Extension for Visual Studio Code
Exécutez la commande « Qt: Open Qt examples » à partir de Command Palette, puis sélectionnez l’exemple dans la liste. Pour plus d’informations, consultez Qt Extension for Visual Studio Code: Tutoriel : Compiler 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 diverses propriétés du graphique. L’exemple montre comment :
- Créer une application avec Q3DBarWidgetItem et quelques widgets de contrôle
- Utiliser QBar3DSeries et QBarDataProxy pour attribuer des données au graphique
- Régler certaines propriétés du graphique et des séries à l'aide des commandes des widgets
- Sélectionner une ligne ou une colonne en cliquant sur une étiquette d’axe
- Créer un proxy personnalisé à utiliser avec Q3DBarWidgetItem
Pour plus d'informations sur l'interaction avec le graphique, consultez cette page.
Création de l'application
- Dans `
bargraph.cpp`, instanciez ` QQuickWidget ` et ` Q3DBarsWidgetItem`, puis définissez l'instance ` QQuickWidget ` comme widget pour ` Q3DBarsWidgetItem` :m_quickWidget = new QQuickWidget(); m_barGraph = new Q3DBarsWidgetItem(this); m_barGraph->setWidget(m_quickWidget); - Créez un widget conteneur, ainsi que des mises en page horizontale et verticale. Ajoutez le graphique et la mise en page verticale à la mise en page 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); - Créez une autre classe pour gérer l’ajout de données et les autres interactions avec le graphique :
m_modifier = new GraphModifier(m_barGraph, this);
Configuration du graphique à barres
- Configurez le graphique dans le constructeur de la classe `
GraphModifier` :GraphModifier::GraphModifier(Q3DBarsWidgetItem *bargraph, QObject *parent) : QObject(parent) , m_graph(bargraph) - Créez les axes et les séries sous forme de 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) - Définissez certaines propriétés visuelles du 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); - Configurez les axes et définissez-les comme 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); - Appliquez un léger angle de rotation automatique aux étiquettes des axes :
m_yearAxis->setLabelAutoAngle(30.0f);Cela permet de les orienter légèrement vers la caméra, ce qui améliore la lisibilité des étiquettes d'axe lorsque l'angle de la caméra est extrême.
- Initialiser les propriétés visuelles des séries. 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); - Ajoutez les séries au graphique :
m_graph->addSeries(m_primarySeries); m_graph->addSeries(m_secondarySeries); - Définissez l'angle de vue en appelant la même méthode que celle utilisée par le bouton de changement d'angle de vue de l'interface utilisateur pour faire défiler les différents angles de vue :
changePresetCamera(); - Le nouveau préréglage de caméra est appliqué 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 à l'aide 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);Utilisation de widgets pour contrôler le graphique
Poursuivez en ajoutant quelques widgets dans bargraph.cpp.
- Ajoutez un curseur :
- Utilisez le curseur pour faire pivoter le graphique au lieu d'utiliser uniquement la souris ou le toucher. Ajoutez-le à la disposition verticale :
- Reliez-le à une méthode dans
GraphModifier: - Créez un slot dans `
GraphModifier` pour la connexion du signal. Spécifiez 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 désormais utiliser le curseur pour faire pivoter le graphique.
Ajoutez d'autres widgets à la disposition verticale pour contrôler :
- La rotation du graphique
- Le style des étiquettes
- Préréglage de la caméra
- La visibilité de l'arrière-plan
- La 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 des étiquettes d'axe
- Mode de données
Certains contrôles du widget sont intentionnellement désactivés en mode de 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 des graphiques à barres. Par exemple, vous pouvez sélectionner des lignes en cliquant sur une étiquette d’axe de la manière suivante :
- Modifier le mode de sélection en
Row - Cliquez sur une étiquette d'année
- La ligne correspondant à l’année sur laquelle vous avez cliqué est sélectionnée
La même méthode fonctionne avec les indicateurs « Slice » et « Item », à condition que l’option « Row » ou « Column » soit également activée.
Zoom sur la sélection
À titre d’exemple de réglage de la cible de la caméra, implémentez une animation de zoom sur la sélection via l’appui sur un bouton. Les initialisations de l’animation sont effectuées 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 sur l’intervalle (-1, 1).
Déterminez la position de la barre sélectionnée par rapport aux axes, et utilisez cette valeur 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 soit toujours orientée 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;Proxy de modèle 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.
RainfallData
La classe ` RainfallData ` configure une ` QItemModelBarDataProxy ` ainsi que les séries et les axes.
- Nous utilisons une structure «
std::array<double, 12>» pour stocker les valeurs d’une année indexées par mois. La fonction d’aide statique `readData()` lit les données et renvoie un tableau ` 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; // Lecture des données d’un fichier dans la liste d’é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(&fichierDeDonnées); int dernièreAnnée =-1; while (!stream.atEnd()) { QString line = stream.readLine(); if (line.startsWith(u'#')) // Ignorer les commentaires continue; const auto strList = QStringView{line}.split(',', Qt::SkipEmptyParts); // Chaque ligne contient trois éléments : l'année, le mois et la valeur des précipitations if (strList.size() < 3) { qWarning() << "Invalid row read from data:" << line; continue; } // Enregistrer l'année et le mois sous forme d'entiers, et la valeur des précipitations sous forme de nombre à virgule flottante bool yearOk{}; bool moisOk{}; bool valeurOk{}; const int année = strList.at(0).trimmed().toInt(&annéeOk); const int mois = strList.at(1).trimmed().toInt(&moisOk); const double value = strList.at(2).trimmed().toDouble(&valueOk); if (!yearOk|| !monthOk|| month < 1|| month > 12|| !valueOk) { qWarning() << "Invalid row values:" << line; continuer; } if (année != dernièreAnnée) { if (dernièreAnnée==-1) { *premièreAnnée = année ; } else if (année != dernièreAnnée + 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; } ... - L'QList représente un tableau bidimensionnel indexé respectivement par l'année et le mois. À partir de celui-ci, 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); - Enfin, ajoutez une fonction permettant de récupérer la série créée pour l'afficher :
QBar3DSeries *customSeries() { return m_series; }
Graphique en nuage de points
Dans l’onglet « Scatter Graph », créez un nuage de points en 3D à l’aide de Q3DScatterWidgetItem. L’exemple montre comment :
- Configurer un graphique « Q3DScatterWidgetItem »
- Utiliser QScatterDataProxy pour définir les données du graphique
- Créer et ajouter un gestionnaire d'entrée personnalisé
Pour la création d'une application de base, consultez la section « Graphique à barres ».
Configuration du graphique en nuage de points
- Définissez certaines propriétés visuelles du graphique dans le constructeur de l'
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 de remplacer les valeurs par défaut du graphique. Pour observer l’apparence avec les valeurs par défaut, le bloc ci-dessus peut être commenté.
- Créez un objet ` QScatterDataProxy ` et l'QScatter3DSeries` associé. Définissez un format d'étiquette personnalisé et un lissage de maillage pour la série, puis 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 nuage de points
- Dans le constructeur de `
ScatterDataModifier`, ajoutez des données au graphique :addData(); - L'ajout des données proprement dit s'effectue 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 pourriez également le faire dans le constructeur de `
ScatterDataModifier`. En le faisant ici, le constructeur reste plus simple et la configuration des axes est plus proche des données. - 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)); } } - 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 contient désormais les données et est prêt à être utilisé. Pour plus d’informations sur l’ajout de widgets permettant de contrôler le graphique, consultez la section « Utilisation de widgets pour contrôler le graphique ».
Remplacer la gestion par défaut des entrées
Pour remplacer le mécanisme de gestion des entrées par défaut, définissez les nouveaux gestionnaires d’entrées 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 souris
Implémentez un nouveau gestionnaire d’événements ` drag `. Il fournit une distance de déplacement de la souris pour le calcul du glissement des axes (voir Implémentation du glissement des axes 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);Implémentation du glissement d'axe
- Commencez par écouter le signal de sélection émis par le graphique. Effectuez cette opération dans le constructeur, puis reliez-la à 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); - Dans
handleElementSelected, vérifiez le type de 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; } - La logique de glissement proprement dite est implémentée dans la méthode `
handleAxisDragging`, qui est appelée à partir de l'événement `drag` :void ScatterDataModifier::handleAxisDragging(QVector2D delta) - Dans `
handleAxisDragging`, récupérez d'abord 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(); - Calculez les modificateurs de la direction du mouvement de la souris en fonction de cette 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)); - Calculez le mouvement de la souris et modifiez-le 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(); - Appliquer la distance parcourue à l'axe correspondant :
// 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 3D à l'aide de Q3DSurfaceWidgetItem. L'exemple montre comment :
- QSurfaceDataProxy Configurer un graphique de surface de base et définir les données correspondantes.
- Utiliser l’ 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 les plages d'axes pour afficher les parties sélectionnées du graphique.
- Définir un dégradé de surface personnalisé.
- Ajoutez des éléments et des étiquettes personnalisés à l'aide de QCustom3DItem et QCustom3DLabel.
- Utilisez un gestionnaire de saisie personnalisé pour activer le zoom et le panoramique.
- Mettez en surbrillance une zone de la surface.
Pour la création d'une application de base, consultez la section « Graphique à barres ».
Surface simple avec données générées
- Commencez par instancier un nouvel objet « QSurfaceDataProxy » et associez-le à un nouvel objet « QSurface3DSeries » :
m_sqrtSinProxy = new QSurfaceDataProxy(); m_sqrtSinSeries = new QSurface3DSeries(m_sqrtSinProxy); - Remplissez le proxy avec des données simples de racine carrée et d’onde sinusoïdale. Créez une instance de `
QSurfaceDataArray` et ajoutez-y des éléments `QSurfaceDataRow`. Définissez l’`QSurfaceDataArray` ainsi créé comme tableau de données pour la ` 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 carte d’altitude multiséries
Créez la carte d’altitude en instanciant un objet ` QHeightMapSurfaceDataProxy ` avec un ` QImage ` contenant les données d’altitude. Utilisez la méthode ` QHeightMapSurfaceDataProxy::setValueRanges()` pour définir la plage de valeurs de la carte. Dans l’exemple, la carte correspond à une position imaginaire comprise entre 34,0° N et 40,0° N et entre 18,0° E et 24,0° E. Ces valeurs servent à 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 chacune d’elles à partir d’images de cartes altimétriques. -
Données cartographiques topographiques
Les données topographiques proviennent du Service national de géodésie de Finlande. Celui-ci propose un produit appelé « Elevation Model 2 m », qui convient parfaitement à cet exemple.
Les données topographiques proviennent du massif de Levi. La précision de ces données étant bien supérieure aux besoins, elles sont compressées et encodées dans un fichier PNG. La valeur d'altitude 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. Ce multiplicateur est calculé en divisant la plus grande valeur sur 24 bits par l’altitude du point le plus élevé de Finlande.
QHeightMapSurfaceDataProxy ne convertit que les valeurs d’un octet. Pour tirer parti de la plus grande précision des données fournies par le Service national du cadastre de Finlande, lisez les données du fichier PNG et décodez-les au format « QSurface3DSeries ».
- Définissez le multiplicateur d’encodage :
// Value used to encode height data as RGB value on PNG file const float packingFactor = 11983.f; - 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 Surface Graph peut exploiter les données via le proxy.
Sélection de l'ensemble de données
Pour illustrer les différents proxys, l'Surface Graph dispose de trois boutons radio permettant de basculer d'une série à l'autre.
Avec « Sqrt & Sin », la série simple générée est activée.
- Définissez les options d’affichage, telles que l’activation de la grille pour la surface et la sélection du mode d’ombrage plat.
- Définissez le format des étiquettes d’axe et les plages de valeurs. Activez la rotation automatique des étiquettes pour améliorer leur lisibilité lorsque l’angle de vue est faible.
- 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 cartes 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 l’option « 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 en évidence des zones sur celle-ci :
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);Consultez la section « Utiliser un gestionnaire d’entrée personnalisé pour activer le zoom et le panoramique » pour plus d’informations sur le gestionnaire d’entrée personnalisé pour cet ensemble 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 pour le désactiver, ajoutez les méthodes inline 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 la sélection simultanée de tranches sur toutes les séries visibles du graphique, ajoutez les indicateurs « 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 de régler les valeurs minimales et maximales des axes X et Z. Lors de la sélection du proxy, ces curseurs sont ajustés pour correspondre aux plages des 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 permettre le réglage de la plage X du graphique à partir des contrôles du widget, ajoutez :
void SurfaceGraphModifier::setAxisXRange(float min, float max)
{
m_graph->axisX()->setRange(min, max);
}Ajoutez la prise en charge de la plage Z de la même manière.
Dégradés de surface personnalisés
Avec l'ensemble de données « Sqrt & Sin », des dégradés de surface personnalisés peuvent être utilisés à l'aide de deux boutons. Définissez le dégradé à l'aide de ` QLinearGradient`, où les couleurs souhaitées sont définies. Modifiez é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, ajoutez 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 graphe
Avec l'ensemble de données « Multiseries Height Map », des é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'un autre ensemble de cases à cocher, notamment la transparence des deux couches supérieures et la mise en surbrillance de la couche inférieure.
- Créer un petit fichier « QImage ». Remplissez-le d’une seule couleur qui servira de couleur à l’objet personnalisé :
- Spécifiez la position de l’élément dans une variable. Cette position peut ensuite être utilisée pour supprimer le bon élément du graphique :
QVector3D positionOne = QVector3D(39.f, 77.f, 19.2f); - Créez un nouvel « 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); - Ajoutez l'élément au graphique :
m_graph->addCustomItem(item);
Ajouter une étiquette personnalisée à un graphique
L'ajout d'une étiquette personnalisée s'effectue de manière très similaire à l'ajout d'un élément personnalisé. Pour l'étiquette, aucun maillage personnalisé n'est nécessaire, mais simplement une instance d'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);Supprimer un élément personnalisé d'un graphe
Pour supprimer un élément spécifique du graphique, appelez la méthode ` removeCustomItemAt() ` en indiquant 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 surfaces
À l’aide de l’ensemble 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 à l’aide de QSurface3DSeries::setTextureFile(). Ajoutez une case à cocher pour contrôler si la texture est définie ou non, ainsi qu’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 à l’aide de cette méthode efface la texture, et la surface utilise alors les dégradés ou les couleurs du thème.
Utiliser un gestionnaire d’entrée personnalisé pour activer le zoom et le panoramique
À l’aide de l’ensemble de données ` Textured Topography `, créez un gestionnaire d’entrée personnalisé pour mettre en surbrillance la sélection sur le graphique et permettre le défilement horizontal du graphique.
La mise en œuvre du défilement est similaire à celle présentée dans la section « Mise en œuvre du glissement des axes ». La différence réside dans le fait que, dans cet exemple, vous ne suivez que les axes X et Z et n’autorisez pas le glissement de la surface en dehors du graphique. Pour limiter le glissement, respectez les limites des axes et ne faites rien si le glissement sort 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, interceptez l’ wheelEvent e 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 évite d’obtenir un graphique dont 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);
}Ajoutez ensuite des limites au niveau de zoom, afin qu’il ne se rapproche pas trop de la surface ni ne s’en éloigne trop. Par exemple, si la valeur de l’axe X passe en dessous de la limite autorisée, c’est-à-dire si le zoom s’éloigne trop, la valeur est fixée à la valeur minimale autorisée. Si la plage descend en dessous du minimum de la plage, 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;
}Mettre en évidence une zone de la surface
Pour implémenter 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.
Commencez par passer à ` HighlightSeries ` le pointeur vers la série d’origine, 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 issue du tableau de données de la série « topography » :
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);
}Une série « Gradient to the Highlight »
Comme la série « HighlightSeries » est une série de type « QSurface3DSeries », toutes les méthodes de mise en forme disponibles pour une série sont accessibles. Dans cet exemple, ajoutez un dégradé pour mettre en valeur l’altitude. Étant donné que le style de dégradé approprié dépend de la plage de l’axe Y et que nous modifions cette plage lors du zoom, les positions des couleurs du dégradé doivent être ajustées à mesure que la plage change. 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é s’effectue dans la méthode ` handleGradientChange ` ; il faut donc la connecter pour qu’elle réagisse aux changements sur l’axe Y :
QObject::connect(m_graph->axisY(),
&QValue3DAxis::maxChanged,
m_highlight,
&HighlightSeries::handleGradientChange);Lorsqu’un changement de la valeur maximale de l’axe Y se produit, calculez les nouvelles positions de couleur 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);
}Exemple de contenu
© 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.