Manejo de ejes
Implementación del arrastre de ejes con un manejador de entrada personalizado en QML, y creación de un formateador de ejes personalizado.
Manejo de Ejes demuestra dos características personalizadas diferentes con ejes. Las funciones tienen sus propias pestañas en la aplicación.
Las siguientes secciones se concentran sólo en esas características y omiten la explicación de la funcionalidad básica - para una documentación más detallada del ejemplo QML, vea Simple Scatter Graph.

Ejecutar el ejemplo
Para ejecutar el ejemplo desde Qt Creatorabra el modo Welcome y seleccione el ejemplo de Examples. Para más información, consulte Qt Creator: Tutorial: Construir y ejecutar.
Arrastre de Ejes
En la pestaña Axis Dragging, implemente un manejador de entrada personalizado en QML que le permita arrastrar las etiquetas de los ejes para cambiar los rangos de los ejes. Además, utilice la proyección ortográfica y actualice dinámicamente las propiedades de un elemento personalizado.
Anulación del manejo de entrada por defecto
Para desactivar el mecanismo de manejo de entrada por defecto, establezca el manejador de entrada activo del gráfico Scatter3D en null:
Scatter3D { id: scatterGraph inputHandler: null ...
Después, añade un MouseArea y configúralo para que rellene el padre, que es el mismo Item que contiene nuestro scatterGraph. También, configúralo para que sólo acepte pulsaciones del botón izquierdo del ratón, ya que en este ejemplo los otros botones no son necesarios:
MouseArea { anchors.fill: parent hoverEnabled: true acceptedButtons: Qt.LeftButton ...
Entonces, escucha las pulsaciones del ratón, y cuando las captura, envía una consulta de selección al gráfico:
onPressed: (mouse)=> { scatterGraph.scene.selectionQueryPosition = Qt.point(mouse.x, mouse.y); }
El manejador de señales onPositionChanged captura la posición actual del ratón que será necesaria para el cálculo de la distancia de movimiento:
onPositionChanged: (mouse)=> { currentMouseX = mouse.x; currentMouseY = mouse.y; ...
Al final de onPositionChanged, guarda la posición anterior del ratón para el cálculo de la distancia de movimiento que se introducirá más adelante:
...
previousMouseX = currentMouseX;
previousMouseY = currentMouseY;
}Traduciendo el Movimiento del Ratón al Cambio de Rango del Eje
En scatterGraph, escuche onSelectedElementChanged. La señal se emite después de que se haya realizado la consulta de selección en onPressed del inputArea. Establezca el tipo de elemento en una propiedad que haya definido (property int selectedAxisLabel: -1) en el componente principal, ya que es de un tipo que le interesa:
onSelectedElementChanged: { if (selectedElement >= AbstractGraph3D.ElementAxisXLabel && selectedElement <= AbstractGraph3D.ElementAxisZLabel) { selectedAxisLabel = selectedElement; } else { selectedAxisLabel = -1; } }
A continuación, de vuelta en el onPositionChanged de inputArea, compruebe si se pulsa un botón del ratón y si tiene una selección de etiqueta de eje actual. Si se cumplen las condiciones, llama a la función que hace la conversión de movimiento del ratón a actualización del rango de ejes:
... if (pressed && selectedAxisLabel != -1) axisDragView.dragAxis(); ...
La conversión es fácil en este caso, ya que la rotación de la cámara es fija. Puede utilizar algunos valores precalculados, calcular la distancia del movimiento del ratón y aplicar los valores al rango de ejes seleccionado:
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 AbstractGraph3D.ElementAxisXLabel: 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 AbstractGraph3D.ElementAxisYLabel: 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 AbstractGraph3D.ElementAxisZLabel: 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; } }
Para una conversión más sofisticada del movimiento del ratón a la actualización del rango de ejes, consulte Galería de gráficos.
Otras funciones
El ejemplo también muestra cómo utilizar la proyección ortográfica y cómo actualizar las propiedades de un elemento personalizado sobre la marcha.
La proyección ortográfica es muy sencilla. Sólo tendrá que cambiar la propiedad orthoProjection de scatterGraph. El ejemplo tiene un botón para activarla y desactivarla:
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 = AbstractGraph3D.ShadowQualityMedium } else { text = "Display Perspective"; scatterGraph.orthoProjection = true; } } }
Para elementos personalizados, añade uno a la customItemList de scatterGraph:
customItemList: [ Custom3DItem { id: qtCube meshFile: ":/qml/qmlaxishandling/cube.obj" textureFile: ":/qml/qmlaxishandling/cubetexture.png" position: Qt.vector3d(0.65, 0.35, 0.65) scaling: Qt.vector3d(0.3, 0.3, 0.3) } ]
Implementa un temporizador para añadir, eliminar y rotar todos los elementos del gráfico, y utiliza el mismo temporizador para rotar el elemento personalizado:
onTriggered: { rotationAngle = rotationAngle + 1; qtCube.setRotationAxisAndAngle(Qt.vector3d(1, 0, 1), rotationAngle); ...
Formateadores de ejes
En la pestaña Axis Formatter, crea un formateador de ejes personalizado. También ilustra cómo utilizar los formateadores de ejes predefinidos.
Formateador de Ejes Personalizado
Personalizar los formateadores de eje requiere subclasificar el QValue3DAxisFormatter, lo que no puede hacerse sólo en código QML. En este ejemplo, el eje interpreta los valores flotantes como una marca de tiempo y muestra la fecha en las etiquetas del eje. Para conseguirlo, introduzca una nueva clase llamada CustomFormatter, que subclasea a la QValue3DAxisFormatter:
class CustomFormatter : public QValue3DAxisFormatter { ...
Dado que los valores flotantes de QScatter3DSeries no pueden convertirse directamente en valores de QDateTime debido a la diferencia en la anchura de los datos, se necesita algún tipo de correspondencia entre ambos. Para ello, especifique una fecha de origen para el formateador e interprete los valores flotantes de QScatter3DSeries como desplazamientos de fecha respecto a ese valor de origen. La fecha de origen se da como una propiedad:
Q_PROPERTY(QDate originDate READ originDate WRITE setOriginDate NOTIFY originDateChanged)Para el mapeo de valor a QDateTime, utilice el método valueToDateTime():
QDateTime CustomFormatter::valueToDateTime(qreal value) const { return m_originDate.startOfDay().addMSecs(qint64(oneDayMs * value)); }
Para funcionar como formateador de ejes, CustomFormatter necesita reimplementar algunos métodos virtuales:
virtual QValue3DAxisFormatter *createNewInstance() const; virtual void populateCopy(QValue3DAxisFormatter ©) const; virtual void recalculate(); virtual QString stringForValue(qreal value, const QString &format) const;
Los dos primeros son sencillos, basta con crear una nueva instancia de CustomFormatter y copiarle los datos necesarios. Utiliza estos dos métodos para crear y actualizar una caché del formateador para propósitos de renderizado. Recuerda llamar a la implementación de la superclase populateCopy():
QValue3DAxisFormatter *CustomFormatter::createNewInstance() const { return new CustomFormatter(); } void CustomFormatter::populateCopy(QValue3DAxisFormatter ©) const { QValue3DAxisFormatter::populateCopy(copy); CustomFormatter *customFormatter = static_cast<CustomFormatter *>(©); customFormatter->m_originDate = m_originDate; customFormatter->m_selectionFormat = m_selectionFormat; }
CustomFormatter hace la mayor parte de su trabajo en el método recalculate(), donde nuestro formateador calcula la rejilla, la sub-rejilla y las posiciones de las etiquetas, así como formatea las cadenas de las etiquetas. En el formateador personalizado, ignora el recuento de segmentos del eje y dibuja una línea de rejilla siempre a medianoche. El recuento de subsegmentos y la posición de las etiquetas se manejan normalmente:
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; // 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; } } } }
Las etiquetas del eje se formatean para mostrar sólo la fecha. Sin embargo, para aumentar la resolución de la marca de tiempo de la etiqueta de selección, especifique otra propiedad para el formateador personalizado que permita al usuario personalizarlo:
Q_PROPERTY(QString selectionFormat READ selectionFormat WRITE setSelectionFormat NOTIFY selectionFormatChanged)Esta propiedad de formato de selección se utiliza en el método reimplementado stringToValue, donde se ignora el formato enviado y se sustituye por el formato de selección personalizado:
QString CustomFormatter::stringForValue(qreal value, const QString &format) const { Q_UNUSED(format); return valueToDateTime(value).toString(m_selectionFormat); }
Para exponer nuestro nuevo formateador personalizado al QML, declárelo y conviértalo en un módulo QML. Para obtener información sobre cómo hacerlo, consulte Galería de gráficos de superficie.
QML
En el código QML, defina un eje diferente para cada dimensión:
axisZ: valueAxis axisY: logAxis axisX: dateAxis
El eje Z es simplemente un ValueAxis3D normal:
ValueAxis3D { id: valueAxis segmentCount: 5 subSegmentCount: 2 labelFormat: "%.2f" min: 0 max: 10 }
Para el eje Y, defina un eje logarítmico. Para hacer que ValueAxis3D muestre una escala logarítmica, especifique LogValueAxis3DFormatter para la propiedad formatter del eje:
ValueAxis3D { id: logAxis formatter: LogValueAxis3DFormatter { id: logAxisFormatter base: 10 autoSubGrid: true showEdgeLabels: true } labelFormat: "%.2f" }
Y finalmente, para el eje X utilice el nuevo CustomFormatter:
ValueAxis3D { 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 }
El resto de la aplicación consiste en una lógica bastante autoexplicativa para modificar los ejes y mostrar el gráfico.
Contenido del ejemplo
© 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.