Galerie mit Oberflächen-Diagrammen
Galerie mit drei verschiedenen Anwendungsmöglichkeiten für ein Surface3D-Diagramm.
Die Surface-Graph-Galerie demonstriert drei verschiedene benutzerdefinierte Funktionen mit Surface3D-Diagrammen. Die Funktionen verfügen über eigene Registerkarten in der Anwendung.
Die folgenden Abschnitte konzentrieren sich ausschließlich auf diese Funktionen und verzichten auf eine Erläuterung der Grundfunktionen – eine ausführlichere Dokumentation mit QML-Beispielen finden Sie unter „Einfaches Streudiagramm“.

Ausführen des Beispiels
Sie können das Beispiel wie folgt ausführen:
- Qt Creator
Öffnen Sie den Modus „ Welcome “ und wählen Sie das Beispiel unter „ Examples “ aus. Weitere Informationen finden Sie unter „Qt Creator “: Tutorial: Erstellen und Ausführen.
- Qt Extension for Visual Studio Code
Führen Sie den Befehl „ Qt: Open Qt examples “ aus dem Verzeichnis „ Command Palette “ aus und wählen Sie das Beispiel aus der Liste aus. Weitere Informationen finden Sie unter Qt Extension for Visual Studio Code: Tutorial: Erstellen und Ausführen.
Höhenkarte
Erstellen Sie auf der Registerkarte „ Height Map “ ein Oberflächendiagramm aus Höhendaten. Als Daten wird eine Höhenkarte des Mount Ruapehu und des Mount Ngauruhoe in Neuseeland verwendet.
Hinzufügen von Daten zum Diagramm
Die Daten werden mithilfe von `HeightMapSurfaceDataProxy` festgelegt, das Höheninformationen aus einem Höhenkartenbild ausliest. Der Proxy selbst ist in einer `Surface3DSeries` enthalten. Innerhalb des „HeightMapSurfaceDataProxy“ gibt die Eigenschaft „ heightMapFile “ die Bilddatei an, die die Höhendaten enthält. Die Wert-Eigenschaften im Proxy definieren die Minimal- und Maximalwerte für Breite, Tiefe und Höhe der Oberfläche. Die Werte „ z “ und „ x “ sind in Breiten- und Längengraden angegeben und entsprechen in etwa der realen Position, während die „ y “ in Metern angegeben ist.
Hinweis: Das Seitenverhältnis des Diagramms entspricht nicht dem realen Maßstab, stattdessen ist die Höhe übertrieben dargestellt.
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()
}Anzeigen der Daten
Richten Sie in der Datei „ main.qml “ das „Surface3D“-Element ein, um die Daten darzustellen.
Definieren Sie zunächst den benutzerdefinierten Farbverlauf, der für die Oberfläche verwendet werden soll. Legen Sie die Farben von Position 0,0 bis 1,0 mit „ColorGradient“ fest, wobei zwei zusätzliche Farbstufen hinzugefügt werden, um das Diagramm lebendiger zu gestalten:
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" }
}Fügen Sie dieses Element in die Eigenschaft „ baseGradients “ des in Surface3D verwendeten „ theme “ ein:
theme: Theme3D {
type: Theme3D.ThemeStoneMoss
font.family: "STCaiyun"
font.pointSize: 35
colorStyle: Theme3D.ColorStyleRangeGradient
baseGradients: [surfaceGradient] // Use the custom gradient
}Verwenden Sie die Schaltflächen, um weitere Funktionen von Surface3D zu steuern.
Die erste Schaltfläche schaltet das Oberflächenraster ein und aus. Der Zeichnungsmodus lässt sich nicht vollständig löschen; daher kann das Oberflächenraster nur ausgeblendet werden, wenn die Oberfläche selbst sichtbar ist:
onClicked: {
if (heightSeries.drawMode & Surface3DSeries.DrawWireframe)
heightSeries.drawMode &= ~Surface3DSeries.DrawWireframe;
else
heightSeries.drawMode |= Surface3DSeries.DrawWireframe;
}Die zweite Schaltfläche legt die Farbe des Oberflächenrasters fest:
onClicked: {
if (Qt.colorEqual(heightSeries.wireframeColor, "#000000")) {
heightSeries.wireframeColor = "red";
text = "Black surface\ngrid color";
} else {
heightSeries.wireframeColor = "black";
text = "Red surface\ngrid color";
}
}Die dritte Schaltfläche schaltet die Oberfläche im Oberflächen-Zeichenmodus ein oder aus. Der Zeichenmodus lässt sich nicht vollständig zurücksetzen; solange das Oberflächenraster sichtbar ist, kann die Oberfläche selbst daher nicht ausgeblendet werden:
onClicked: {
if (heightSeries.drawMode & Surface3DSeries.DrawSurface)
heightSeries.drawMode &= ~Surface3DSeries.DrawSurface;
else
heightSeries.drawMode |= Surface3DSeries.DrawSurface;
}Die vierte Schaltfläche legt den Schattierungsmodus fest. Wenn Sie das Beispiel auf einem OpenGL-ES-System ausführen, ist „Flat Shading“ nicht verfügbar:
onClicked: {
if (heightSeries.flatShadingEnabled) {
heightSeries.flatShadingEnabled = false;
text = "Show\nFlat"
} else {
heightSeries.flatShadingEnabled = true;
text = "Show\nSmooth"
}
}Die übrigen Schaltflächen steuern die Hintergrundfunktionen des Diagramms.
Spektrogramm
Auf der Registerkarte „ Spectrogram “ können Sie polare und kartesische Spektrogramme anzeigen und diese mithilfe einer orthogonalen Projektion in 2D darstellen.
Ein Spektrogramm ist ein Oberflächendiagramm mit einem Bereichsgradienten, der dazu dient, die unterschiedlichen Werte hervorzuheben. Typischerweise werden Spektrogramme als zweidimensionale Oberflächen dargestellt, was durch eine orthogonale Draufsicht auf das Diagramm simuliert wird. Um den 2D-Effekt zu gewährleisten, deaktivieren Sie im orthogonalen Modus die Diagrammrotation per Maus oder Touch.
Erstellen eines Spektrogramms
Um ein 2D-Spektrogramm zu erstellen, definieren Sie ein „Surface3D“-Element mit den in der „Surface3DSeries“ angegebenen Daten unter Verwendung eines „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"
}
}Die wichtigsten Eigenschaften zur Aktivierung des 2D-Effekts sind „ orthoProjection “ und „ scene.activeCamera.cameraPreset “. Entfernen Sie die Perspektive, indem Sie die orthogonale Projektion für das Diagramm aktivieren, und die Y-Dimension, indem Sie das Diagramm direkt von oben betrachten:
// Remove the perspective and view the graph from top down to achieve 2D effect
orthoProjection: true
scene.activeCamera.cameraPreset: Camera3D.CameraPresetDirectlyAboveDa bei dieser Ansicht das Raster der horizontalen Achse größtenteils von der Oberfläche verdeckt wird, kehren Sie die Ausrichtung des horizontalen Rasters um, sodass es über dem Diagramm gezeichnet wird:
flipHorizontalGrid: truePolares Spektrogramm
Je nach Daten ist es manchmal naheliegender, ein polares Diagramm anstelle eines kartesischen zu verwenden. Dies wird über die Eigenschaft „ polar “ unterstützt.
Fügen Sie eine Schaltfläche hinzu, um zwischen dem polaren und dem kartesischen Modus zu wechseln:
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;
}Im polaren Modus wird die X-Achse in die polare Winkelachse und die Z-Achse in eine polare Radialachse umgewandelt. Die Oberflächenpunkte werden entsprechend den neuen Achsen neu berechnet.
Die Beschriftungen der Radialachse werden standardmäßig außerhalb des Diagramms gezeichnet. Um sie direkt neben der 0-Grad-Winkelachse innerhalb des Diagramms darzustellen, definieren Sie für sie nur einen kleinen Versatz:
radialLabelOffset: 0.01Um den 2D-Effekt zu verstärken, deaktivieren Sie die Diagrammrotation im orthografischen Modus, indem Sie den Standard-Eingabehandler durch einen benutzerdefinierten überschreiben, der die Eigenschaft „ rotationEnabled “ je nach Projektionsmodus automatisch umschaltet:
inputHandler: TouchInputHandler3D {
rotationEnabled: !surfaceGraph.orthoProjection
}Oszilloskop
Kombinieren Sie auf der Registerkarte „ Oscilloscope “ C++ und QML in einer Anwendung und zeigen Sie Daten an, die sich dynamisch ändern.
Datenquelle in C++
Die auf dem Elementmodell basierenden Proxies eignen sich gut für einfache oder statische Diagramme, verwenden Sie jedoch einfache Proxies, um die beste Leistung bei der Darstellung von Daten zu erzielen, die sich in Echtzeit ändern. Diese werden in QML nicht unterstützt, da die von ihnen gespeicherten Datenelemente die Eigenschaft „ QObject “ nicht erben und daher nicht direkt aus dem QML-Code heraus manipuliert werden können. Um diese Einschränkung zu umgehen, implementieren Sie eine einfache „ DataSource “-Klasse in C++, um den Daten-Proxy der Reihe zu füllen.
Erstellen Sie eine Klasse „ DataSource “, die zwei Methoden bereitstellt, die aus QML aufgerufen werden können:
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);Die erste Methode, ` generateData()`, erstellt einen Cache mit simulierten Oszilloskopdaten zur Anzeige. Die Daten werden in einem Format zwischengespeichert, das von `QSurfaceDataProxy` akzeptiert wird:
// 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);
}
}
}Die zweite Methode, ` update()`, kopiert einen Satz der zwischengespeicherten Daten in ein anderes Array, das durch den Aufruf von ` QSurfaceDataProxy::resetArray()` als Datenproxy der Serie festgelegt wird. Um den Overhead zu minimieren, wird dasselbe Array wiederverwendet, sofern sich die Array-Dimensionen nicht geändert haben:
// 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);Auch wenn wir mit dem zuvor auf den Proxy gesetzten Array-Zeiger arbeiten, muss ` QSurfaceDataProxy::resetArray()` nach einer Änderung der darin enthaltenen Daten dennoch aufgerufen werden, damit das Diagramm die Daten rendert.
Um von QML aus auf die Methoden von ` DataSource ` zugreifen zu können, machen Sie die Datenquelle zugänglich, indem Sie die `DataSource` zu einer ` QML_ELEMENT` machen:
class DataSource : public QObject
{
Q_OBJECT
QML_ELEMENTDeklarieren Sie sie außerdem in der Datei `CMakeLists.txt` als QML-Modul:
qt6_add_qml_module(qmlsurfacegallery
URI SurfaceGallery
VERSION 1.0
NO_RESOURCE_TARGET_PATH
SOURCES
datasource.cpp datasource.h
...
)Um QSurface3DSeries-Zeiger in allen Umgebungen und Builds als Parameter für die Methoden der Klasse „ DataSource “ verwenden zu können, stellen Sie sicher, dass der Metatyp registriert ist:
qRegisterMetaType<QSurface3DSeries *>();QML-Anwendung
Um die ` DataSource` zu verwenden, importieren Sie das QML-Modul und erstellen Sie eine Instanz von ` DataSource `, die verwendet werden soll:
import SurfaceGallery
...
DataSource {
id: dataSource
}Definieren Sie einen `Surface3D`-Graphen und weisen Sie ihm eine `Surface3DSeries` zu:
Surface3D {
id: surfaceGraph
anchors.fill: parent
Surface3DSeries {
id: surfaceSeries
drawMode: Surface3DSeries.DrawSurfaceAndWireframe
itemLabelFormat: "@xLabel, @zLabel: @yLabel"Geben Sie keinen Proxy für die „Surface3DSeries“ an, die Sie an das Diagramm anhängen. Dadurch verwendet die Reihe den Standard-„QSurfaceDataProxy“.
Blenden Sie die Elementbeschriftung mit ` itemLabelVisible` aus. Bei dynamischen, sich schnell ändernden Daten würde eine schwebende Auswahlbeschriftung ablenken und die Lesbarkeit beeinträchtigen.
itemLabelVisible: falseSie können die Informationen zum ausgewählten Element in einem ` Text `-Element anzeigen, anstatt die standardmäßige schwebende Beschriftung über dem Auswahlzeiger zu verwenden:
onItemLabelChanged: {
if (surfaceSeries.selectedPoint == surfaceSeries.invalidSelectionPosition)
selectionText.text = "No selection";
else
selectionText.text = surfaceSeries.itemLabel;
}Initialisieren Sie den „ DataSource “-Cache, sobald das Diagramm fertiggestellt ist, indem Sie die Hilfsfunktion „ generateData() “ aufrufen, die die gleichnamige Methode in „ DataSource “ aufruft:
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);
}Um die Aktualisierungen der Daten auszulösen, definieren Sie eine ` Timer`, die in festgelegten Intervallen die Methode ` update() ` in ` DataSource ` aufruft:
Timer {
id: refreshTimer
interval: 1000 / frequencySlider.value
running: true
repeat: true
onTriggered: dataSource.update(surfaceSeries);
}Direktes Rendering aktivieren
Da diese Anwendung potenziell mit vielen sich schnell ändernden Daten arbeitet, nutzt sie aus Leistungsgründen den Direkt-Rendering-Modus. Um Antialiasing in diesem Modus zu aktivieren, ändern Sie das Oberflächenformat des Anwendungsfensters. Das von „ QQuickView “ standardmäßig verwendete Format unterstützt kein Antialiasing. Verwenden Sie die bereitgestellte Hilfsfunktion, um das Oberflächenformat in „ main.cpp “ zu ändern:
#include <QtDataVisualization/qutils.h>
...
// Enable antialiasing in direct rendering mode
viewer.setFormat(qDefaultSurfaceFormat(true));Beispielinhalt
© 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.