Traitement des axes
Mise en œuvre du glissement d'axe avec un gestionnaire d'entrée personnalisé en QML, et création d'un formateur d'axe personnalisé.
Axis Handling présente deux fonctionnalités personnalisées différentes avec des axes. Ces fonctionnalités ont leurs propres onglets dans l'application.
Les sections suivantes se concentrent uniquement sur ces fonctionnalités et omettent d'expliquer les fonctionnalités de base - pour une documentation d'exemple QML plus détaillée, voir Simple Scatter Graph (Graphique de dispersion simple).

Exécution de l'exemple
Pour exécuter l'exemple à partir de Qt CreatorOuvrez le mode Welcome et sélectionnez l'exemple à partir de Examples. Pour plus d'informations, voir Qt Creator: Tutorial : Construire et exécuter.
Déplacement de l'axe
Dans l'onglet Axis Dragging, mettez en œuvre un gestionnaire d'entrée personnalisé en QML qui vous permet de faire glisser les étiquettes d'axe pour modifier les plages d'axe. De plus, utilisez la projection orthographique et mettez à jour dynamiquement les propriétés d'un élément personnalisé.
Remplacer la gestion des entrées par défaut
Pour désactiver le mécanisme de gestion des entrées par défaut, désactivez le gestionnaire d'entrée par défaut de Scatter3D:
unsetDefaultInputHandler();
...Ensuite, ajoutez un MouseArea et configurez-le pour qu'il remplisse le parent, qui est le même Item dans lequel notre scatterGraph est contenu. Réglez-le également pour qu'il n'accepte que les pressions sur le bouton gauche de la souris, car dans cet exemple, les autres boutons ne sont pas nécessaires :
MouseArea { anchors.fill: parent hoverEnabled: true acceptedButtons: Qt.LeftButton ...
Ensuite, écoutez les pressions de la souris et, lorsqu'elles sont détectées, envoyez une requête de sélection au graphique :
onPressed: (mouse)=> { scatterGraph.doPicking(Qt.point(mouse.x, mouse.y)); }
Le gestionnaire de signal onPositionChanged capture la position actuelle de la souris qui sera nécessaire pour le calcul de la distance de déplacement :
onPositionChanged: (mouse)=> { currentMouseX = mouse.x; currentMouseY = mouse.y; ...
À la fin de onPositionChanged, enregistrez la position précédente de la souris pour le calcul de la distance de déplacement qui sera présenté ultérieurement :
...
previousMouseX = currentMouseX;
previousMouseY = currentMouseY;
}Traduire le mouvement de la souris en changement de plage d'axe
Dans scatterGraph, écoutez onSelectedElementChanged. Le signal est émis après que la requête de sélection a été effectuée dans onPressed de inputArea. Définissez le type d'élément dans une propriété que vous avez définie (property int selectedAxisLabel: -1) dans le composant principal, puisqu'il s'agit d'un type qui vous intéresse :
onSelectedElementChanged: { if (selectedElement >= Graphs3D.ElementType.AxisXLabel && selectedElement <= Graphs3D.ElementType.AxisZLabel) { selectedAxisLabel = selectedElement; } else { selectedAxisLabel = -1; } }
Ensuite, de retour à onPositionChanged de inputArea, vérifiez si un bouton de la souris est enfoncé et si vous avez une sélection d'étiquette d'axe en cours. Si ces conditions sont remplies, appelez la fonction qui effectue la conversion entre le mouvement de la souris et la mise à jour de la plage d'axes :
... if (pressed && selectedAxisLabel != -1) axisDragView.dragAxis(); ...
La conversion est facile dans ce cas, car la rotation de la caméra est fixe. Vous pouvez utiliser des valeurs précalculées, calculer la distance de déplacement de la souris et appliquer les valeurs à la plage d'axes sélectionnée :
function dragAxis() { // Do nothing if previous mouse position is uninitialized if (previousMouseX === -1) return; // Directional drag multipliers based on rotation. Camera is locked to 45 degrees, so we // can use one precalculated value instead of calculating xx, xy, zx and zy individually var cameraMultiplier = 0.70710678; // Calculate the mouse move amount var moveX = currentMouseX - previousMouseX; var moveY = currentMouseY - previousMouseY; // Adjust axes switch (selectedAxisLabel) { case Graphs3D.ElementType.AxisXLabel: var distance = ((moveX - moveY) * cameraMultiplier) / dragSpeedModifier; // Check if we need to change min or max first to avoid invalid ranges if (distance > 0) { scatterGraph.axisX.min -= distance; scatterGraph.axisX.max -= distance; } else { scatterGraph.axisX.max -= distance; scatterGraph.axisX.min -= distance; } break; case Graphs3D.ElementType.AxisYLabel: distance = moveY / dragSpeedModifier; // Check if we need to change min or max first to avoid invalid ranges if (distance > 0) { scatterGraph.axisY.max += distance; scatterGraph.axisY.min += distance; } else { scatterGraph.axisY.min += distance; scatterGraph.axisY.max += distance; } break; case Graphs3D.ElementType.AxisZLabel: distance = ((moveX + moveY) * cameraMultiplier) / dragSpeedModifier; // Check if we need to change min or max first to avoid invalid ranges if (distance > 0) { scatterGraph.axisZ.max += distance; scatterGraph.axisZ.min += distance; } else { scatterGraph.axisZ.min += distance; scatterGraph.axisZ.max += distance; } break; } }
Pour une conversion plus sophistiquée du mouvement de la souris à la mise à jour de la plage d'axes, voir Galerie de graphiques.
Autres fonctionnalités
L'exemple montre également comment utiliser la projection orthographique et comment mettre à jour les propriétés d'un élément personnalisé à la volée.
La projection orthographique est très simple. Il vous suffit de modifier la propriété orthoProjection de scatterGraph. L'exemple comporte un bouton permettant de l'activer ou de la désactiver :
Button { id: orthoToggle width: axisDragView.portraitMode ? parent.width : parent.width / 3 text: "Display Orthographic" anchors.left: axisDragView.portraitMode ? parent.left : rangeToggle.right anchors.top: axisDragView.portraitMode ? rangeToggle.bottom : parent.top onClicked: { if (scatterGraph.orthoProjection) { text = "Display Orthographic"; scatterGraph.orthoProjection = false; // Orthographic projection disables shadows, so we need to switch them back on scatterGraph.shadowQuality = Graphs3D.ShadowQuality.Medium } else { text = "Display Perspective"; scatterGraph.orthoProjection = true; } } }
Pour les éléments personnalisés, ajoutez-en un à la propriété customItemList de scatterGraph:
customItemList: [ Custom3DItem { id: qtCube meshFile: ":/qml/axishandling/cube.mesh" textureFile: ":/qml/axishandling/cubetexture.png" position: Qt.vector3d(0.65, 0.35, 0.65) scaling: Qt.vector3d(0.3, 0.3, 0.3) } ]
Vous implémentez une minuterie pour ajouter, supprimer et faire pivoter tous les éléments du graphique, et vous utilisez la même minuterie pour faire pivoter l'élément personnalisé :
onTriggered: { rotationAngle = rotationAngle + 1; qtCube.setRotationAxisAndAngle(Qt.vector3d(1, 0, 1), rotationAngle); ...
Formateurs d'axes
Dans l'onglet Axis Formatter, créez un formateur d'axe personnalisé. Il illustre également comment utiliser les formateurs d'axes prédéfinis.
Formateur d'axe personnalisé
La personnalisation des formateurs d'axes nécessite la sous-classification de QValue3DAxisFormatter, ce qui ne peut pas être fait uniquement dans le code QML. Dans cet exemple, l'axe interprète les valeurs flottantes comme un horodatage et affiche la date dans les étiquettes de l'axe. Pour ce faire, introduisez une nouvelle classe appelée CustomFormatter, qui sous-classe la classe QValue3DAxisFormatter:
class CustomFormatter : public QValue3DAxisFormatter { ...
Étant donné que les valeurs flottantes d'une classe QScatter3DSeries ne peuvent pas être directement converties en valeurs QDateTime en raison d'une différence de largeur de données, il est nécessaire d'établir une sorte de correspondance entre les deux. Pour ce faire, il faut spécifier une date d'origine pour le formateur et interpréter les valeurs flottantes de QScatter3DSeries comme des décalages de date par rapport à cette valeur d'origine. La date d'origine est donnée comme une propriété :
Q_PROPERTY(QDate originDate READ originDate WRITE setOriginDate NOTIFY originDateChanged)Pour la conversion d'une valeur en QDateTime, utilisez la méthode valueToDateTime():
QDateTime CustomFormatter::valueToDateTime(qreal value) const { return m_originDate.startOfDay().addMSecs(qint64(oneDayMs * value)); }
Pour fonctionner en tant que formateur d'axes, CustomFormatter doit réimplémenter certaines méthodes virtuelles :
QValue3DAxisFormatter *createNewInstance() const override; void populateCopy(QValue3DAxisFormatter ©) override; void recalculate() override; QString stringForValue(qreal value, const QString &format) override;
Les deux premières méthodes sont simples : il suffit de créer une nouvelle instance de CustomFormatter et d'y copier les données nécessaires. Utilisez ces deux méthodes pour créer et mettre à jour un cache de formateurs à des fins de rendu. N'oubliez pas d'appeler l'implémentation de la superclasse de populateCopy():
QValue3DAxisFormatter *CustomFormatter::createNewInstance() const { return new CustomFormatter(); } void CustomFormatter::populateCopy(QValue3DAxisFormatter ©) { QValue3DAxisFormatter::populateCopy(copy); CustomFormatter *customFormatter = static_cast<CustomFormatter *>(©); customFormatter->m_originDate = m_originDate; customFormatter->m_selectionFormat = m_selectionFormat; }
CustomFormatter Le formateur personnalisé effectue l'essentiel de son travail dans la méthode recalculate(), où il calcule les positions de la grille, de la sous-grille et de l'étiquette, et formate les chaînes de l'étiquette. Dans le formateur personnalisé, ignorez le nombre de segments de l'axe et dessinez une ligne de grille toujours à minuit. Le nombre de sous-segments et le positionnement des étiquettes sont gérés normalement :
void CustomFormatter::recalculate() { // We want our axis to always have gridlines at date breaks // Convert range into QDateTimes QDateTime minTime = valueToDateTime(qreal(axis()->min())); QDateTime maxTime = valueToDateTime(qreal(axis()->max())); // Find out the grid counts QTime midnight(0, 0); QDateTime minFullDate(minTime.date(), midnight); int gridCount = 0; if (minFullDate != minTime) minFullDate = minFullDate.addDays(1); QDateTime maxFullDate(maxTime.date(), midnight); gridCount += minFullDate.daysTo(maxFullDate) + 1; int subGridCount = axis()->subSegmentCount() - 1; QList<float> gridPositions; QList<float> subGridPositions; QList<float> labelPositions; QStringList labelStrings; // Reserve space for position arrays and label strings gridPositions.resize(gridCount); subGridPositions.resize((gridCount + 1) * subGridCount); labelPositions.resize(gridCount); labelStrings.reserve(gridCount); // Calculate positions and format labels qint64 startMs = minTime.toMSecsSinceEpoch(); qint64 endMs = maxTime.toMSecsSinceEpoch(); qreal dateNormalizer = endMs - startMs; qreal firstLineOffset = (minFullDate.toMSecsSinceEpoch() - startMs) / dateNormalizer; qreal segmentStep = oneDayMs / dateNormalizer; qreal subSegmentStep = 0; if (subGridCount > 0) subSegmentStep = segmentStep / qreal(subGridCount + 1); for (int i = 0; i < gridCount; i++) { qreal gridValue = firstLineOffset + (segmentStep * qreal(i)); gridPositions[i] = float(gridValue); labelPositions[i] = float(gridValue); labelStrings << minFullDate.addDays(i).toString(axis()->labelFormat()); } for (int i = 0; i <= gridCount; i++) { if (subGridPositions.size()) { for (int j = 0; j < subGridCount; j++) { float position; if (i) position = gridPositions.at(i - 1) + subSegmentStep * (j + 1); else position = gridPositions.at(0) - segmentStep + subSegmentStep * (j + 1); if (position > 1.0f || position < 0.0f) position = gridPositions.at(0); subGridPositions[i * subGridCount + j] = position; } } } setGridPoitions(gridPositions); setSubGridPositions(subGridPositions); setlabelPositions(labelPositions); setLabelStrings(labelStrings); }
Les étiquettes de l'axe sont formatées de manière à n'afficher que la date. Toutefois, pour augmenter la résolution de l'horodatage de l'étiquette de sélection, spécifiez une autre propriété pour le formateur personnalisé afin de permettre à l'utilisateur de le personnaliser :
Q_PROPERTY(QString selectionFormat READ selectionFormat WRITE setSelectionFormat NOTIFY
selectionFormatChanged)Cette propriété de format de sélection est utilisée dans la méthode réimplémentée stringToValue, où le format soumis est ignoré et remplacé par le format de sélection personnalisé :
QString CustomFormatter::stringForValue(qreal value, const QString &format) { Q_UNUSED(format); return valueToDateTime(value).toString(m_selectionFormat); }
Pour exposer notre nouveau formateur personnalisé au QML, il faut le déclarer et en faire un module QML. Pour plus d'informations sur la manière de procéder, voir Surface Graph Gallery.
QML
Dans le code QML, définissez un axe différent pour chaque dimension :
axisZ: valueAxis axisY: logAxis axisX: dateAxis
L'axe Z est un simple Value3DAxis:
Value3DAxis { id: valueAxis segmentCount: 5 subSegmentCount: 2 labelFormat: "%.2f" min: 0 max: 10 }
Pour l'axe Y, définissez un axe logarithmique. Pour que Value3DAxis affiche une échelle logarithmique, spécifiez LogValue3DAxisFormatter pour la propriété formatter de l'axe :
Value3DAxis { id: logAxis formatter: LogValue3DAxisFormatter { id: logAxisFormatter base: 10 autoSubGrid: true edgeLabelsVisible: true } labelFormat: "%.2f" }
Enfin, pour l'axe des X, utilisez le nouveau CustomFormatter:
Value3DAxis { id: dateAxis formatter: CustomFormatter { originDate: "2023-01-01" selectionFormat: "yyyy-MM-dd HH:mm:ss" } subSegmentCount: 2 labelFormat: "yyyy-MM-dd" min: 0 max: 14 }
Le reste de l'application consiste en une logique assez explicite pour modifier les axes et afficher le graphique.
Contenu de l'exemple
© 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.