En esta página

Galería de gráficos de superficie

Galería con tres formas diferentes de utilizar un gráfico Surface3D.

LaGalería de Gráficos deSuperficie muestra tres funciones personalizadas diferentes con los gráficos de Surface3D. 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, ver 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.

Mapa de alturas

En la pestaña Height Map, genere un gráfico de superficie a partir de los datos de altura. Los datos utilizados son un mapa de alturas del Monte Ruapehu y del Monte Ngauruhoe en Nueva Zelanda.

Añadir datos al gráfico

Los datos se configuran utilizando HeightMapSurfaceDataProxy, que lee la información de altura de una imagen de mapa de alturas. El proxy en sí está contenido en Surface3DSeries. Dentro de HeightMapSurfaceDataProxy, la propiedad heightMapFile especifica el archivo de imagen que contiene los datos de altura. Las propiedades de valor en el proxy definen los valores mínimo y máximo para la anchura, profundidad y altura de la superficie. Los valores z y x están en latitud y longitud, aproximadamente en la posición del mundo real, y el y está en metros.

Nota: la relación de aspecto del gráfico no se ajusta a la escala del mundo real, sino que se exagera la altura.

Surface3DSeries {
    id: heightSeries
    flatShadingEnabled: false
    drawMode: Surface3DSeries.DrawSurface

    HeightMapSurfaceDataProxy {
        heightMapFile: "://qml/qmlsurfacegallery/heightmap.png"
        // We don't want the default data values set by heightmap proxy, but use
        // actual coordinate and height values instead
        autoScaleY: true
        minYValue: 740
        maxYValue: 2787
        minZValue: -374 // ~ -39.374411"N
        maxZValue: -116 // ~ -39.115971"N
        minXValue: 472  // ~ 175.471767"E
        maxXValue: 781  // ~ 175.780758"E
    }

    onDrawModeChanged: heightMapView.checkState()
}
Visualización de los datos

En main.qml, configure el elemento Surface3D para mostrar los datos.

En primer lugar, define el gradiente personalizado que se utilizará para la superficie. Establece los colores de la posición 0.0 a 1.0 con ColorGradient, con dos paradas extra para hacer el gráfico más vivo:

ColorGradient {
    id: surfaceGradient
    ColorGradientStop { position: 0.0; color: "darkgreen"}
    ColorGradientStop { position: 0.15; color: "darkslategray" }
    ColorGradientStop { position: 0.7; color: "peru" }
    ColorGradientStop { position: 1.0; color: "white" }
}

Establezca este elemento en la propiedad baseGradients en el theme utilizado en Surface3D:

theme: Theme3D {
    type: Theme3D.ThemeStoneMoss
    font.family: "STCaiyun"
    font.pointSize: 35
    colorStyle: Theme3D.ColorStyleRangeGradient
    baseGradients: [surfaceGradient] // Use the custom gradient
}

Utilice los botones para controlar otras funciones de Surface3D.

El primer botón activa y desactiva la cuadrícula de superficie. El modo de dibujo no se puede borrar completamente, por lo que a menos que la propia superficie sea visible, la rejilla de superficie no se puede ocultar:

onClicked: {
    if (heightSeries.drawMode & Surface3DSeries.DrawWireframe)
        heightSeries.drawMode &= ~Surface3DSeries.DrawWireframe;
    else
        heightSeries.drawMode |= Surface3DSeries.DrawWireframe;
}

El segundo establece el color de la rejilla de superficie:

onClicked: {
    if (Qt.colorEqual(heightSeries.wireframeColor, "#000000")) {
        heightSeries.wireframeColor = "red";
        text = "Black surface\ngrid color";
    } else {
        heightSeries.wireframeColor = "black";
        text = "Red surface\ngrid color";
    }
}

El tercero activa o desactiva la superficie en el modo de dibujo. El modo de dibujo no se puede desactivar completamente, así que a menos que la rejilla de la superficie sea visible, la propia superficie no se puede ocultar:

onClicked: {
    if (heightSeries.drawMode & Surface3DSeries.DrawSurface)
        heightSeries.drawMode &= ~Surface3DSeries.DrawSurface;
    else
        heightSeries.drawMode |= Surface3DSeries.DrawSurface;
}

El cuarto establece el modo de sombreado. Si está ejecutando el ejemplo en un sistema OpenGL ES, el sombreado plano no está disponible:

onClicked: {
    if (heightSeries.flatShadingEnabled) {
        heightSeries.flatShadingEnabled = false;
        text = "Show\nFlat"
    } else {
        heightSeries.flatShadingEnabled = true;
        text = "Show\nSmooth"
    }
}

Los botones restantes controlan las características del fondo del gráfico.

Espectrograma

En la pestaña Spectrogram, muestre espectrogramas polares y cartesianos y utilice la proyección ortográfica para mostrarlos en 2D.

Un espectrograma es un gráfico de superficie con un gradiente de rango utilizado para resaltar los diferentes valores. Normalmente, los espectrogramas se muestran con superficies bidimensionales, lo que se simula con una vista ortográfica descendente del gráfico. Para reforzar el efecto 2D, desactive la rotación del gráfico mediante el ratón o el tacto cuando se encuentre en el modo ortográfico.

Creación de un espectrograma

Para crear un espectrograma 2D, defina un elemento Surface3D con los datos indicados en Surface3DSeries con un ItemModelSurfaceDataProxy:

Surface3D {
    id: surfaceGraph
    anchors.fill: parent

    Surface3DSeries {
        id: surfaceSeries
        flatShadingEnabled: false
        drawMode: Surface3DSeries.DrawSurface
        baseGradient: surfaceGradient
        colorStyle: Theme3D.ColorStyleRangeGradient
        itemLabelFormat: "(@xLabel, @zLabel): @yLabel"

        ItemModelSurfaceDataProxy {
            itemModel: surfaceData.model
            rowRole: "radius"
            columnRole: "angle"
            yPosRole: "value"
        }
    }

Las propiedades clave para activar el efecto 2D son orthoProjection y scene.activeCamera.cameraPreset. Elimine la perspectiva activando la proyección ortográfica para el gráfico, y la dimensión Y visualizando el gráfico directamente desde arriba:

// Remove the perspective and view the graph from top down to achieve 2D effect
orthoProjection: true
scene.activeCamera.cameraPreset: Camera3D.CameraPresetDirectlyAbove

Dado que este punto de vista hace que la rejilla del eje horizontal quede oculta en su mayor parte por la superficie, voltee la rejilla horizontal para que se dibuje encima del gráfico:

flipHorizontalGrid: true
Espectrograma polar

Dependiendo de los datos, a veces es más natural utilizar un gráfico polar en lugar de uno cartesiano. Esto es posible gracias a la propiedad polar.

Añade un botón para cambiar entre los modos polar y cartesiano:

Button {
    id: polarToggle
    anchors.margins: 5
    anchors.left: parent.left
    anchors.top: parent.top
    width: spectrogramView.buttonWidth // Calculated elsewhere based on screen orientation
    text: "Switch to\n" + (surfaceGraph.polar ? "cartesian" : "polar")
    onClicked: surfaceGraph.polar = !surfaceGraph.polar;
}

En el modo polar, el eje X se convierte en el eje polar angular, y el eje Z se convierte en un eje polar radial. Los puntos de la superficie se recalculan según los nuevos ejes.

Las etiquetas de los ejes radiales se dibujan fuera del gráfico por defecto. Para dibujarlas justo al lado del eje angular de 0 grados dentro del gráfico, defina sólo un pequeño desplazamiento para ellas:

radialLabelOffset: 0.01

Para reforzar el efecto 2D, desactive la rotación de la gráfica en modo ortográfico sustituyendo el manejador de entrada por defecto por uno personalizado, que active automáticamente la propiedad rotationEnabled en función del modo de proyección:

inputHandler: TouchInputHandler3D {
    rotationEnabled: !surfaceGraph.orthoProjection
}

Osciloscopio

En la pestaña Oscilloscope, combina C++ y QML en una aplicación, y muestra datos que cambian dinámicamente.

Fuente de datos en C

Los proxies basados en modelos de elementos son buenos para gráficos simples o estáticos, pero utiliza proxies básicos para conseguir el mejor rendimiento cuando muestres datos que cambian en tiempo real. No son compatibles con QML, ya que los elementos de datos que almacenan no heredan QObject y, por tanto, no pueden manipularse directamente desde código QML. Para superar esta limitación, implemente una clase DataSource sencilla en C++ para rellenar el proxy de datos de la serie.

Cree una clase DataSource para proporcionar dos métodos que puedan invocarse desde QML:

class DataSource : public QObject
{
    Q_OBJECT
    ...
Q_INVOKABLE void generateData(int cacheCount, int rowCount, int columnCount,
                              float xMin, float xMax,
                              float yMin, float yMax,
                              float zMin, float zMax);

Q_INVOKABLE void update(QSurface3DSeries *series);

El primer método, generateData(), crea una caché de datos de osciloscopio simulados para mostrar. Los datos se almacenan en caché en un formato que QSurfaceDataProxy acepta:

// Populate caches
auto *generator = QRandomGenerator::global();
for (int i = 0; i < cacheCount; ++i) {
    QSurfaceDataArray &cache = m_data[i];
    float cacheXAdjustment = cacheStep * i;
    float cacheIndexAdjustment = cacheIndexStep * i;
    for (int j = 0; j < rowCount; ++j) {
        QSurfaceDataRow &row = *(cache[j]);
        float rowMod = (float(j)) / float(rowCount);
        float yRangeMod = yRange * rowMod;
        float zRangeMod = zRange * rowMod;
        float z = zRangeMod + zMin;
        qreal rowColWaveAngleMul = M_PI * M_PI * rowMod;
        float rowColWaveMul = yRangeMod * 0.2f;
        for (int k = 0; k < columnCount; k++) {
            float colMod = (float(k)) / float(columnCount);
            float xRangeMod = xRange * colMod;
            float x = xRangeMod + xMin + cacheXAdjustment;
            float colWave = float(qSin((2.0 * M_PI * colMod) - (1.0 / 2.0 * M_PI)) + 1.0);
            float y = (colWave * ((float(qSin(rowColWaveAngleMul * colMod) + 1.0))))
                    * rowColWaveMul
                    + generator->bounded(0.15f) * yRangeMod;

            int index = k + cacheIndexAdjustment;
            if (index >= columnCount) {
                // Wrap over
                index -= columnCount;
                x -= xRange;
            }
            row[index] = QVector3D(x, y, z);
        }
    }
}

El segundo método, update(), copia un conjunto de los datos almacenados en caché en otra matriz, que se establece en el proxy de datos de la serie llamando a QSurfaceDataProxy::resetArray(). Para minimizar la sobrecarga, reutiliza el mismo array si las dimensiones del array no han cambiado:

// Each iteration uses data from a different cached array
if (++m_index >= m_data.size())
    m_index = 0;

const QSurfaceDataArray &array = m_data.at(m_index);
int newRowCount = array.size();
int newColumnCount = array.at(0)->size();

// If the first time or the dimensions of the cache array have changed,
// reconstruct the reset array
if (!m_resetArray || series->dataProxy()->rowCount() != newRowCount
        || series->dataProxy()->columnCount() != newColumnCount) {
    m_resetArray = new QSurfaceDataArray();
    m_resetArray->reserve(newRowCount);
    for (int i = 0; i < newRowCount; ++i)
        m_resetArray->append(new QSurfaceDataRow(newColumnCount));
}

// Copy items from our cache to the reset array
for (int i = 0; i < newRowCount; ++i) {
    const QSurfaceDataRow &sourceRow = *(array.at(i));
    QSurfaceDataRow &row = *(*m_resetArray)[i];
    std::copy(sourceRow.cbegin(), sourceRow.cend(), row.begin());
}

// Notify the proxy that data has changed
series->dataProxy()->resetArray(m_resetArray);

Aunque estemos operando sobre el puntero del array previamente establecido al proxy, QSurfaceDataProxy::resetArray() todavía necesita ser llamado después de cambiar los datos en él para que el gráfico renderice los datos.

Para poder acceder a los métodos de DataSource desde QML, exponga la fuente de datos convirtiendo el DataSource en QML_ELEMENT:

class DataSource : public QObject
{
    Q_OBJECT
    QML_ELEMENT

Además, declararlo como un módulo QML en el CMakeLists.txt:

qt6_add_qml_module(qmlsurfacegallery
    URI SurfaceGallery
    VERSION 1.0
    NO_RESOURCE_TARGET_PATH
    SOURCES
        datasource.cpp datasource.h
    ...
)

Para utilizar punteros QSurface3DSeries como parámetros de los métodos de la clase DataSource en todos los entornos y versiones, asegúrese de que el metatipo está registrado:

qRegisterMetaType<QSurface3DSeries *>();
Aplicación QML

Para utilizar DataSource, importa el módulo QML y crea una instancia de DataSource para ser utilizada:

import SurfaceGallery
...
DataSource {
    id: dataSource
}

Define un gráfico Surface3D y dale un Surface3DSeries:

Surface3D {
    id: surfaceGraph
    anchors.fill: parent

    Surface3DSeries {
        id: surfaceSeries
        drawMode: Surface3DSeries.DrawSurfaceAndWireframe
        itemLabelFormat: "@xLabel, @zLabel: @yLabel"

No especifique un proxy para el Surface3DSeries que adjunta al gráfico. Esto hace que la serie utilice el valor por defecto QSurfaceDataProxy.

Oculte la etiqueta de selección con itemLabelVisible. Con datos dinámicos que cambian rápidamente, una etiqueta de selección flotante distraería y sería difícil de leer.

itemLabelVisible: false

Puede mostrar la información del elemento seleccionado en un elemento Text en lugar de la etiqueta flotante por defecto sobre el puntero de selección:

onItemLabelChanged: {
    if (surfaceSeries.selectedPoint == surfaceSeries.invalidSelectionPosition)
        selectionText.text = "No selection";
    else
        selectionText.text = surfaceSeries.itemLabel;
}

Inicialice la caché DataSource cuando el gráfico esté completo llamando a una función de ayuda generateData(), que llama al método con el mismo nombre en DataSource:

Component.onCompleted: oscilloscopeView.generateData();
...
function generateData() {
    dataSource.generateData(oscilloscopeView.sampleCache, oscilloscopeView.sampleRows,
                            oscilloscopeView.sampleColumns,
                            surfaceGraph.axisX.min, surfaceGraph.axisX.max,
                            surfaceGraph.axisY.min, surfaceGraph.axisY.max,
                            surfaceGraph.axisZ.min, surfaceGraph.axisZ.max);
}

Para activar las actualizaciones de datos, defina una función Timer, que llame al método update() en DataSource a intervalos solicitados:

Timer {
    id: refreshTimer
    interval: 1000 / frequencySlider.value
    running: true
    repeat: true
    onTriggered: dataSource.update(surfaceSeries);
}
Activación de la renderización directa

Dado que esta aplicación maneja potencialmente una gran cantidad de datos que cambian rápidamente, utiliza el modo de renderizado directo para mejorar el rendimiento. Para habilitar el antialiasing en este modo, cambie el formato de superficie de la ventana de la aplicación. El formato por defecto utilizado por QQuickView no soporta antialiasing. Utilice la función de utilidad proporcionada para cambiar el formato de superficie en main.cpp:

#include <QtDataVisualization/qutils.h>
...
// Enable antialiasing in direct rendering mode
viewer.setFormat(qDefaultSurfaceFormat(true));

Contenido del ejemplo

Proyecto de ejemplo @ code.qt.io

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.