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 Q3DBars et combinez l’utilisation de widgets pour ajuster diverses propriétés du graphique. L’exemple montre comment :
- Créer une application avec Q3DBars et quelques widgets
- Utiliser QBar3DSeries et QBarDataProxy pour définir les données du 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 Q3DBars
Pour plus d'informations sur l'interaction avec le graphique, consultez cette page.
Création de l'application
Commencez par instancier Q3DBars dans bargraph.cpp:
m_barsGraph = new Q3DBars();Ensuite, créez le widget, ainsi que les mises en page horizontale et verticale.
Le graphique est intégré dans un conteneur de fenêtre à l’aide de ` QWidget::createWindowContainer()`. Cette étape est obligatoire car toutes les classes de graphiques de visualisation de données (Q3DBars, Q3DScatter, Q3DSurface) héritent de ` QWindow`. C’est la seule façon d’utiliser une classe héritant de ` QWindow ` en tant que widget.
Ajoutez le graphique et la disposition verticale à la disposition horizontale :
m_barsWidget = new QWidget;
auto *hLayout = new QHBoxLayout(m_barsWidget);
m_container = QWidget::createWindowContainer(m_barsGraph, m_barsWidget);
m_barsGraph->resize(minimumGraphSize);
m_container->setMinimumSize(minimumGraphSize);
m_container->setMaximumSize(maximumGraphSize);
m_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_container->setFocusPolicy(Qt::StrongFocus);
hLayout->addWidget(m_container, 1);
auto *vLayout = new QVBoxLayout();
hLayout->addLayout(vLayout);Ensuite, créez une autre classe pour gérer l'ajout de données et les autres interactions avec le graphique :
auto *modifier = new GraphModifier(m_barsGraph, this);Configuration du graphique à barres
Configurez le graphique dans le constructeur de la classe « GraphModifier » :
GraphModifier::GraphModifier(Q3DBars *bargraph, QObject *parent) :
QObject(parent),
m_graph(bargraph),Commencez par créer les axes et les séries sous forme de variables membres afin de pouvoir les modifier facilement :
m_temperatureAxis(new QValue3DAxis),
m_yearAxis(new QCategory3DAxis),
m_monthAxis(new QCategory3DAxis),
m_primarySeries(new QBar3DSeries),
m_secondarySeries(new QBar3DSeries),
m_celsiusString(u"°C"_s)Définissez ensuite certaines propriétés visuelles du graphique :
m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftMedium);
m_graph->activeTheme()->setBackgroundEnabled(false);
m_graph->activeTheme()->setFont(QFont("Times New Roman", m_fontSize));
m_graph->activeTheme()->setLabelBackgroundEnabled(true);
m_graph->setMultiSeriesUniform(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->setLabelAutoRotation(30.0f);
m_temperatureAxis->setTitleVisible(true);
m_yearAxis->setTitle("Year");
m_yearAxis->setLabelAutoRotation(30.0f);
m_yearAxis->setTitleVisible(true);
m_monthAxis->setTitle("Month");
m_monthAxis->setLabelAutoRotation(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 d’axe à l’aide de la méthode setLabelAutoRotation() afin qu’elles s’orientent légèrement vers la caméra. Cela améliore la lisibilité des étiquettes d’axe lorsque la caméra est placée sous des angles extrêmes.
Ensuite, initialisez 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::MeshBevelBar);
m_primarySeries->setMeshSmooth(false);
m_secondarySeries->setItemLabelFormat(u"Helsinki - @colLabel @rowLabel: @valueLabel"_s);
m_secondarySeries->setMesh(QAbstract3DSeries::MeshBevelBar);
m_secondarySeries->setMeshSmooth(false);
m_secondarySeries->setVisible(false);Ajoutez les séries au graphique :
m_graph->addSeries(m_primarySeries);
m_graph->addSeries(m_secondarySeries);Enfin, définissez 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 faire défiler les différents angles de caméra :
changePresetCamera();La caméra est contrôlée via l’objet « scène » du graphique :
static int preset = Q3DCamera::CameraPresetFront;
m_graph->scene()->activeCamera()->setCameraPreset((Q3DCamera::CameraPreset)preset);
if (++preset > Q3DCamera::CameraPresetDirectlyBelow)
preset = Q3DCamera::CameraPresetFrontLow;Pour plus d’informations sur l’utilisation de la scène et des caméras, consultez les sections Q3DScene et Q3DCamera.
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 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
...
auto *dataSet = new QBarDataArray;
auto *dataSet2 = new QBarDataArray;
dataSet->reserve(m_years.size());
for (qsizetype year = 0; year < m_years.size(); ++year) {
// Create a data row
auto *dataRow = new QBarDataRow(m_months.size());
auto *dataRow2 = new QBarDataRow(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 :
auto *rotationSliderX = new QSlider(Qt::Horizontal, m_barsWidget);
rotationSliderX->setTickInterval(30);
rotationSliderX->setTickPosition(QSlider::TicksBelow);
rotationSliderX->setMinimum(-180);
rotationSliderX->setValue(0);
rotationSliderX->setMaximum(180);Utilisez le curseur pour faire pivoter le graphique au lieu d'utiliser uniquement la souris ou le toucher. Ajoutez-le à la disposition verticale :
vLayout->addWidget(new QLabel(u"Rotate horizontally"_s));
vLayout->addWidget(rotationSliderX, 0, Qt::AlignTop);Ensuite, connectez-le à une méthode dans ` GraphModifier` :
Créez un slot dans ` GraphModifier ` pour la connexion du signal. La caméra est contrôlée via l’objet scène. Cette fois-ci, 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 rotation)
{
m_xRotation = rotation;
m_graph->scene()->activeCamera()->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
- Le 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. À titre d’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
SelectionRow - 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 « SelectionSlice » et « SelectionItem », à condition que l’option « SelectionRow » ou « SelectionColumn » 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 s’effectuent dans le constructeur :
Q3DCamera *camera = m_graph->scene()->activeCamera();
m_defaultAngleX = camera->xRotation();
m_defaultAngleY = camera->yRotation();
m_defaultZoom = camera->zoomLevel();
m_defaultTarget = camera->target();
m_animationCameraX.setTargetObject(camera);
m_animationCameraY.setTargetObject(camera);
m_animationCameraZoom.setTargetObject(camera);
m_animationCameraTarget.setTargetObject(camera);
m_animationCameraX.setPropertyName("xRotation");
m_animationCameraY.setPropertyName("yRotation");
m_animationCameraZoom.setPropertyName("zoomLevel");
m_animationCameraTarget.setPropertyName("target");
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. La propriété ` QPropertyAnimation ` ( m_animationCameraTarget ) cible la propriété ` Q3DCamera::target `, 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 personnalisé pour les données
En activant le mode de données « Custom Proxy Data », un ensemble de données personnalisé et le proxy correspondant sont pris en charge.
Définissons un ensemble de données simple et flexible, VariantDataSet, dans lequel chaque élément de données est une liste de variantes. Chaque élément peut comporter plusieurs valeurs, identifiées par leur index dans la liste. Dans ce cas, l'ensemble de données stocke les précipitations mensuelles, la valeur à l'index zéro correspondant à l'année, celle à l'index un au mois et celle à l'index deux au volume des précipitations pour ce mois.
Le proxy personnalisé est similaire aux proxys basés sur le modèle d’élément fournis par Qt Data Visualization, et il nécessite un mappage pour interpréter les données.
VariantDataSet
Définissez les éléments de données en tant qu’objets QVariantList. Ajoutez une fonctionnalité permettant de vider l’ensemble de données et d’interroger une référence aux données contenues dans cet ensemble. Ajoutez également des signaux à émettre lorsque des données sont ajoutées ou lorsque l’ensemble est vidé :
using VariantDataItem = QVariantList;
using VariantDataItemList = QList<VariantDataItem *>;
...
void clear();
int addItem(VariantDataItem *item);
int addItems(VariantDataItemList *itemList);
const VariantDataItemList &itemList() const;
Q_SIGNALS:
void itemsAdded(int index, int count);
void dataCleared();VariantBarDataProxy
Sous-classez VariantBarDataProxy à partir de QBarDataProxy et fournissez une API simple composée de getters et de setters pour l'ensemble de données et le mappage :
class VariantBarDataProxy : public QBarDataProxy
...
// Doesn't gain ownership of the dataset, but does connect to it to listen for data changes.
void setDataSet(VariantDataSet *newSet);
VariantDataSet *dataSet();
// Map key (row, column, value) to value index in data item (VariantItem).
// Doesn't gain ownership of mapping, but does connect to it to listen for mapping changes.
// Modifying mapping that is set to proxy will trigger dataset re-resolving.
void setMapping(VariantBarDataMapping *mapping);
VariantBarDataMapping *mapping();Le proxy surveille les modifications apportées au jeu de données et au mappage, et recalcule le jeu de données si des modifications sont détectées. Il ne s'agit pas d'une implémentation particulièrement efficace, car toute modification entraîne le recalcul de l'intégralité du jeu de données, mais cela ne pose pas de problème dans le cadre de cet exemple.
Dans la méthode ` resolveDataSet() `, triez les valeurs de données de type `variant` en lignes et en colonnes en fonction du mappage. Ce processus est très similaire à la manière dont `QItemModelBarDataProxy` gère le mappage, sauf qu’ici vous utilisez des index de liste au lieu des rôles du modèle d’élément. Une fois les valeurs triées, générez à partir de celles-ci une instance de ` QBarDataArray `, puis appelez la méthode ` resetArray() ` de la classe parente :
void VariantBarDataProxy::resolveDataSet()
{
// If we have no data or mapping, or the categories are not defined, simply clear the array
if (m_dataSet.isNull() || m_mapping.isNull() || !m_mapping->rowCategories().size()
|| !m_mapping->columnCategories().size()) {
resetArray(nullptr);
return;
}
const VariantDataItemList &itemList = m_dataSet->itemList();
int rowIndex = m_mapping->rowIndex();
int columnIndex = m_mapping->columnIndex();
int valueIndex = m_mapping->valueIndex();
const QStringList &rowList = m_mapping->rowCategories();
const QStringList &columnList = m_mapping->columnCategories();
// Sort values into rows and columns
using ColumnValueMap = QHash<QString, float>;
QHash <QString, ColumnValueMap> itemValueMap;
for (const VariantDataItem *item : itemList) {
itemValueMap[item->at(rowIndex).toString()][item->at(columnIndex).toString()]
= item->at(valueIndex).toReal();
}
// Create a new data array in format the parent class understands
auto *newProxyArray = new QBarDataArray;
for (const QString &rowKey : rowList) {
auto *newProxyRow = new QBarDataRow(columnList.size());
for (qsizetype i = 0; i < columnList.size(); ++i)
(*newProxyRow)[i].setValue(itemValueMap[rowKey][columnList.at(i)]);
newProxyArray->append(newProxyRow);
}
// Finally, reset the data array in the parent class
resetArray(newProxyArray);
}VariantBarDataMapping
Stockez les informations de mappage entre les index des éléments de données d’ VariantDataSet et les lignes, colonnes et valeurs d’ QBarDataArray dans VariantBarDataMapping. Ce dernier contient les listes de lignes et de colonnes à inclure dans les données résolues :
Q_PROPERTY(int rowIndex READ rowIndex WRITE setRowIndex NOTIFY rowIndexChanged)
Q_PROPERTY(int columnIndex READ columnIndex WRITE setColumnIndex NOTIFY columnIndexChanged)
Q_PROPERTY(int valueIndex READ valueIndex WRITE setValueIndex NOTIFY valueIndexChanged)
Q_PROPERTY(QStringList rowCategories READ rowCategories WRITE setRowCategories NOTIFY rowCategoriesChanged)
Q_PROPERTY(QStringList columnCategories READ columnCategories WRITE setColumnCategories NOTIFY columnCategoriesChanged)
...
explicit VariantBarDataMapping(int rowIndex, int columnIndex, int valueIndex,
const QStringList &rowCategories,
const QStringList &columnCategories);
...
void remap(int rowIndex, int columnIndex, int valueIndex,
const QStringList &rowCategories,
const QStringList &columnCategories);
...
void mappingChanged();La principale façon d’utiliser un objet ` VariantBarDataMapping ` consiste à fournir les mappages dans le constructeur, bien que vous puissiez utiliser la méthode ` remap() ` pour les définir ultérieurement, soit individuellement, soit tous ensemble. Émettez un signal si le mappage change. Le résultat est une version simplifiée de la fonctionnalité de mappage de `QItemModelBarDataProxy`, adaptée pour fonctionner avec des listes de variants plutôt qu’avec des modèles d’éléments.
RainfallData
Gérez la configuration de QBar3DSeries avec le proxy personnalisé dans la classe ` RainfallData ` :
m_proxy = new VariantBarDataProxy;
m_series = new QBar3DSeries(m_proxy);Remplir l’ensemble de données de la variante dans la méthode ` addDataSet() ` :
void RainfallData::addDataSet()
{
// Créer un nouvel ensemble de données de type « variant » et une liste d’éléments de données
m_dataSet = new VariantDataSet;
auto *itemList = new VariantDataItemList;
// Lire les données d’un fichier dans la liste d’éléments de données
QFile dataFile(":/data/raindata.txt");
if (dataFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream stream(&dataFile);
while (!stream.atEnd()) {
QString line = stream.readLine();
if (line.startsWith('#')) // 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 de chaînes de caractères, et la valeur des précipitations sous forme de nombre à virgule flottante
// dans un élément de données de type Variant, puis ajouter cet élément à la liste d'éléments.
auto *newItem = new VariantDataItem ;
for (int i = 0; i < 2;++i)
newItem->append(strList.at(i).trimmed().toString());
newItem->append(strList.at(2).trimmed().toDouble());
itemList->append(newItem);
}
} else {
qWarning() << "Unable to open data file:" << dataFile.fileName();
}
...Ajoutez l'ensemble de données au proxy personnalisé et définissez le mappage :
// Add items to the data set and set it to the proxy
m_dataSet->addItems(itemList);
m_proxy->setDataSet(m_dataSet);
// Create new mapping for the data and set it to the proxy
m_mapping = new VariantBarDataMapping(0, 1, 2, m_years, m_numericMonths);
m_proxy->setMapping(m_mapping);Enfin, ajoutez une fonction permettant de récupérer la série créée afin de l'afficher :
QBar3DSeries *customSeries() { return m_series; }Nuage de points
Dans l'onglet « Scatter Graph », créez un nuage de points en 3D à l'aide de Q3DScatter. L'exemple montre comment :
- Configurer le graphique Q3DScatter
- Utiliser QScatterDataProxy pour attribuer des données au graphique
- Créer un gestionnaire d'entrée personnalisé en étendant Q3DInputHandler
Pour la création d'une application de base, consultez la section « Graphique à barres ».
Configuration du graphique en nuage de points
Commencez par définir certaines propriétés visuelles du graphique dans le constructeur de l'ScatterDataModifier:
m_graph->activeTheme()->setType(Q3DTheme::ThemeStoneMoss);
m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftHigh);
m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFront);
m_graph->scene()->activeCamera()->setZoomLevel(80.f);Aucune de ces options n’est obligatoire, mais elles permettent de remplacer les valeurs par défaut du graphique. Vous pouvez voir à quoi cela ressemble avec les valeurs par défaut en commentant le bloc ci-dessus.
Ensuite, créez un `QScatterDataProxy` et la `QScatter3DSeries` associée. 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
La dernière étape à effectuer dans le constructeur d'ScatterDataModifier consiste à ajouter 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 procédant ainsi, le constructeur reste plus simple et la configuration des axes est placée à proximité des données.
Ensuite, créez un tableau de données et remplissez-le :
auto *dataArray = new QScatterDataArray;
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 des widgets pour contrôler le graphique ».
Remplacer la gestion par défaut des entrées
Initialisez ` m_inputHandler ` dans le constructeur avec un pointeur vers l'instance du graphique de dispersion :
m_inputHandler(new AxesInputHandler(scatter))Remplacez le mécanisme de gestion des entrées par défaut en définissant le gestionnaire d'entrées actif de Q3DScatter sur AxesInputHandler, qui implémente le comportement personnalisé :
// Give ownership of the handler to the graph and make it the active handler
m_graph->setActiveInputHandler(m_inputHandler);Le gestionnaire d’entrée doit pouvoir accéder aux axes du graphique ; transmettez-les-lui donc :
// Give our axes to the input handler
m_inputHandler->setAxes(m_graph->axisX(), m_graph->axisZ(), m_graph->axisY());Extension de la gestion des événements de souris
Tout d'abord, faites hériter le gestionnaire d'entrée personnalisé de `Q3DInputHandler` plutôt que de `QAbstract3DInputHandler` afin de conserver toutes les fonctionnalités de la gestion d'entrée par défaut et d'y ajouter les fonctionnalités personnalisées :
class AxesInputHandler : public Q3DInputHandlerCommencez par étendre les fonctionnalités par défaut en réimplémentant certains événements de souris. Commencez par étendre la classe ` mousePressEvent`. Ajoutez-y un indicateur « m_mousePressed » pour le bouton gauche de la souris, tout en conservant le reste des fonctionnalités par défaut :
void AxesInputHandler::mousePressEvent(QMouseEvent *event, const QPoint &mousePos)
{
Q3DInputHandler::mousePressEvent(event, mousePos);
if (Qt::LeftButton == event->button())
m_mousePressed = true;
}Ensuite, modifiez ` mouseReleaseEvent ` pour effacer le drapeau et réinitialiser l'état interne :
void AxesInputHandler::mouseReleaseEvent(QMouseEvent *event, const QPoint &mousePos)
{
Q3DInputHandler::mouseReleaseEvent(event, mousePos);
m_mousePressed = false;
m_state = StateNormal;
}Ensuite, modifiez mouseMoveEvent. Vérifiez si le drapeau « m_mousePressed » est à « true » et si l’état interne est différent de « StateNormal ». Si c’est le cas, définissez les positions d’entrée pour le calcul de la distance de déplacement de la souris, puis appelez la fonction de glissement des axes (voir la section « Implémentation du glissement des axes » pour plus de détails) :
void AxesInputHandler::mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos)
{
// Check if we're trying to drag axis label
if (m_mousePressed && m_state != StateNormal) {
setPreviousInputPos(inputPosition());
setInputPosition(mousePos);
handleAxisDragging();
} else {
Q3DInputHandler::mouseMoveEvent(event, mousePos);
}
}Implémentation du glissement d’axe
Commencez par écouter le signal de sélection provenant du graphique. Effectuez cette opération dans le constructeur, puis reliez-la à la méthode ` handleElementSelected ` :
// Connect to the item selection signal from graph
connect(graph, &QAbstract3DGraph::selectedElementChanged, this,
&AxesInputHandler::handleElementSelected);Dans handleElementSelected, vérifiez le type de sélection et définissez l'état interne en fonction de celui-ci :
switch (type) {
case QAbstract3DGraph::ElementAxisXLabel:
m_state = StateDraggingX;
break;
case QAbstract3DGraph::ElementAxisYLabel:
m_state = StateDraggingY;
break;
case QAbstract3DGraph::ElementAxisZLabel:
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 depuis ` mouseMoveEvent` si les conditions requises sont remplies :
// Check if we're trying to drag axis label
if (m_mousePressed && m_state != StateNormal) {Dans ` handleAxisDragging`, on récupère d’abord l’orientation de la scène à partir de la caméra active :
// Get scene orientation from active camera
float xRotation = scene()->activeCamera()->xRotation();
float yRotation = scene()->activeCamera()->yRotation();Ensuite, calculez les modificateurs pour 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));Ensuite, calculez le déplacement de la souris et modifiez-le en fonction de la rotation en y de la caméra :
// Get the drag amount
QPoint move = inputPosition() - previousInputPos();
// Flip the effect of y movement if we're viewing from below
float yMove = (yRotation < 0) ? -move.y() : move.y();Ensuite, appliquez la distance parcourue à l'axe correspondant :
// Adjust axes
switch (m_state) {
case StateDraggingX:
distance = (move.x() * xMulX - yMove * xMulY) / m_speedModifier;
m_axisX->setRange(m_axisX->min() - distance, m_axisX->max() - distance);
break;
case StateDraggingZ:
distance = (move.x() * zMulX + yMove * zMulY) / m_speedModifier;
m_axisZ->setRange(m_axisZ->min() + distance, m_axisZ->max() + distance);
break;
case StateDraggingY:
distance = move.y() / m_speedModifier; // No need to use adjusted y move here
m_axisY->setRange(m_axisY->min() + distance, m_axisY->max() + distance);
break;
default:
break;
}Enfin, ajoutez une fonction permettant de définir la vitesse de glissement :
inline void setDragSpeedModifier(float modifier) { m_speedModifier = modifier; }Cela est nécessaire, car la distance de déplacement de la souris est exprimée en coordonnées absolues à l'écran, et vous devez l'adapter à la plage de l'axe. Plus la valeur est élevée, plus le glissement sera lent. Notez que dans cet exemple, le niveau de zoom de la scène n’est pas pris en compte lors du calcul de la vitesse de glissement ; vous remarquerez donc des changements dans l’ajustement de la plage lorsque vous modifiez le niveau de zoom.
Vous pouvez également ajuster automatiquement ce modificateur en fonction de la plage de l'axe et du niveau de zoom de la caméra.
Graphique de surface
Dans l'onglet « Surface Graph », créez un graphique de surface 3D à l'aide de Q3DSurface. L'exemple montre comment :
- Configurer un QSurfaceDataProxy de base et lui attribuer des données.
- 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 les plages d'axes pour afficher les portions sélectionnées du graphique.
- Définissez un dégradé de surface personnalisé.
- Ajoutez des éléments et des étiquettes personnalisés à l'aide de QCustom3DItem et QCustom3DLabel.
- Utilisez un gestionnaire d'entrée 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 à une nouvelle instance de QSurface3DSeries :
m_sqrtSinProxy = new QSurfaceDataProxy();
m_sqrtSinSeries = new QSurface3DSeries(m_sqrtSinProxy);Ensuite, remplissez le proxy avec des données simples correspondant à une racine carrée et à une onde sinusoïdale. Créez une nouvelle instance de ` QSurfaceDataArray ` et ajoutez-y des éléments de type ` QSurfaceDataRow `. Définissez l'objet ` QSurfaceDataArray ` ainsi créé comme tableau de données pour le `QSurfaceDataProxy` en appelant la méthode ` resetArray()`.
auto *dataArray = new QSurfaceDataArray;
dataArray->reserve(sampleCountZ);
for (int i = 0 ; i < sampleCountZ ; ++i) {
auto *newRow = new QSurfaceDataRow;
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 à séries multiples
Créez la carte d’altitude en instanciant un `QHeightMapSurfaceDataProxy` avec un ` QImage ` contenant les données d’altitude. Utilisez ` QHeightMapSurfaceDataProxy::setValueRanges()` pour définir la plage de valeurs de la carte. Dans l’exemple, la carte couvre une zone imaginaire comprise entre 34,0° N et 40,0° N de latitude et entre 18,0° E et 24,0° E de longitude. 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’entre elles à partir d’images de cartes d’altitude.
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 concernent le 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, que vous verrez plus loin dans un extrait de code. Ce multiplicateur est calculé en divisant la plus grande valeur 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 du Service national du cadastre de Finlande, lisez les données du fichier PNG et décodez-les en QSurface3DSeries.
Commencez par définir le multiplicateur d’encodage :
// Value used to encode height data as RGB value on PNG file
const float packingFactor = 11983.f;Ensuite, 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);
auto *dataArray = new QSurfaceDataArray;
dataArray->reserve(imageHeight);
for (int i = 0; i < imageHeight; ++i) {
int p = i * widthBits;
float z = height - float(i) * stepZ;
auto *newRow = new QSurfaceDataRow;
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);Les données sont désormais exploitables par le proxy.
Sélection de l'ensemble de données
Pour présenter différents proxys, Surface Graph propose trois boutons radio permettant de basculer d’une série à l’autre.
Avec « Sqrt & Sin », la série générée par défaut est activée. Commencez par définir 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 ensuite 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. Enfin, 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->setFlatShadingEnabled(true);
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()->setLabelAutoRotation(30.f);
m_graph->axisY()->setLabelAutoRotation(90.f);
m_graph->axisZ()->setLabelAutoRotation(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 désactivées. L’ajustement automatique de la plage de l’axe Y fonctionne bien pour la surface de la carte d’altitude ; veillez donc à ce qu’il soit 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 en évidence des zones sur celle-ci :
m_graph->setActiveInputHandler(m_customInputHandler);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 Q3DSurface peuvent être utilisés avec des boutons radio. Pour activer ou désactiver le mode sélectionné, ajoutez les méthodes inline suivantes :
void toggleModeNone() { m_graph->setSelectionMode(QAbstract3DGraph::SelectionNone); }
void toggleModeItem() { m_graph->setSelectionMode(QAbstract3DGraph::SelectionItem); }
void toggleModeSliceRow() { m_graph->setSelectionMode(QAbstract3DGraph::SelectionItemAndRow
| QAbstract3DGraph::SelectionSlice
| QAbstract3DGraph::SelectionMultiSeries); }
void toggleModeSliceColumn() { m_graph->setSelectionMode(QAbstract3DGraph::SelectionItemAndColumn
| QAbstract3DGraph::SelectionSlice
| QAbstract3DGraph::SelectionMultiSeries); }Ajoutez les indicateurs « QAbstract3DGraph::SelectionSlice » et « QAbstract3DGraph::SelectionMultiSeries » pour les modes de sélection par ligne et par colonne afin de permettre la sélection simultanée de tranches sur toutes les séries visibles du graphique.
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);Ajoutez la prise en charge du réglage de la plage X à partir des contrôles du widget vers le graphique :
void SurfaceGraphModifier::setAxisXRange(float min, float max)
{
m_graph->axisX()->setRange(min, max);
}Ajouter 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 », il est possible d’utiliser des dégradés de surface personnalisés à l’aide de deux boutons. Définissez le dégradé à l’adresse QLinearGradient, où vous pouvez définir les couleurs souhaitées. Modifiez également le style de couleur en accédant à Q3DTheme::ColorStyleRangeGradient 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(Q3DTheme::ColorStyleRangeGradient);Ajout de maillages personnalisés à l’application
Ajoutez les fichiers de maillage à CMakeLists.txt pour la compilation avec CMake :
set(graphgallery_resource_files
...
"data/oilrig.obj"
"data/pipe.obj"
"data/refinery.obj"
...
)
qt6_add_resources(graphgallery "graphgallery"
PREFIX
"/"
FILES
${graphgallery_resource_files}
)Ajoutez-les également dans le fichier de ressources qrc pour une utilisation avec qmake :
<RCC>
<qresource prefix="/">
...
<file>data/refinery.obj</file>
<file>data/oilrig.obj</file>
<file>data/pipe.obj</file>
...
</qresource>
</RCC>Ajout d’un élément personnalisé à un graphique
Grâce à 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 propriétés 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.
Commencez par créer un petit QImage. Remplissez-le d’une seule couleur qui servira de couleur à l’objet personnalisé :
Spécifiez ensuite la position de l’élément dans une variable. Cette position pourra ensuite être utilisée pour retirer le bon élément du graphique :
QVector3D positionOne = QVector3D(39.f, 77.f, 19.2f);Créez ensuite un nouvel objet `QCustom3DItem` en spécifiant tous les paramètres :
auto *item = new QCustom3DItem(":/data/oilrig.obj", positionOne,
QVector3D(0.025f, 0.025f, 0.025f),
QQuaternion::fromAxisAndAngle(0.f, 1.f, 0.f, 45.f),
color);Enfin, ajoutez l’élément au graphique :
m_graph->addCustomItem(item);Ajout d’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, aucun maillage personnalisé n'est nécessaire, mais simplement une instance de 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 graphe, 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. Si vous souhaitez 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 la méthode ` 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 cette case :
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 panoramique 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_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 disponible 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 dans lequel la hauteur est exagérée :
void CustomInputHandler::wheelEvent(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_axisX->setRange(m_axisXMinValue, m_axisXMaxValue);
m_axisY->setRange(100.f, y);
m_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->dataProxy()->array()->at(0)->size();
m_srcHeight = m_topographicSeries->dataProxy()->array()->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.y() - halfWidth;
if (startX < 0 )
startX = 0;
int endX = position.y() + halfWidth;
if (endX > (m_srcWidth - 1))
endX = m_srcWidth - 1;
int startZ = position.x() - halfHeight;
if (startZ < 0 )
startZ = 0;
int endZ = position.x() + halfHeight;
if (endZ > (m_srcHeight - 1))
endZ = m_srcHeight - 1;
QSurfaceDataProxy *srcProxy = m_topographicSeries->dataProxy();
const QSurfaceDataArray &srcArray = *srcProxy->array();
auto *dataArray = new QSurfaceDataArray;
dataArray->reserve(endZ - startZ);
for (int i = startZ; i < endZ; ++i) {
auto *newRow = new QSurfaceDataRow;
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() + 0.1f);
newRow->append(QSurfaceDataItem(pos));
}
dataArray->append(newRow);
}
dataProxy()->resetArray(dataArray);
setVisible(true);
}Une série « Gradient to the Highlight »
Comme l’ HighlightSeries e est de type QSurface3DSeries, toutes les méthodes de décoration 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(Q3DTheme::ColorStyleRangeGradient);
}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.