Galerie mit Oberflächen-Diagrammen
Galerie mit drei verschiedenen Anwendungsmöglichkeiten für ein „ Surface3D “-Diagramm.
Die „Surface Graph Gallery“ veranschaulicht drei verschiedene benutzerdefinierte Funktionen mit „ Surface3D “-Diagrammen. Die Funktionen verfügen in der Anwendung über eigene Registerkarten.
Die folgenden Abschnitte konzentrieren sich ausschließlich auf diese Funktionen und verzichten auf eine Erläuterung der Grundfunktionalität – 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 Ordner „ 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 einem „ 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 für „ 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 wird die Höhe übertrieben dargestellt.
Surface3DSeries {
id: heightSeries
shading: Surface3DSeries.Shading.Smooth
drawMode: Surface3DSeries.DrawSurface
HeightMapSurfaceDataProxy {
heightMapFile: "://qml/surfacegallery/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 „ main.qml “ das Element „ Surface3D “ ein, um die Daten anzuzeigen.
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 „Gradient“ fest, wobei zwei zusätzliche Farbstufen den Diagramm lebendiger machen:
Gradient {
id: surfaceGradient
GradientStop { position: 0.0; color: "darkgreen"}
GradientStop { position: 0.15; color: "darkslategray" }
GradientStop { position: 0.7; color: "peru" }
GradientStop { position: 1.0; color: "white" }
}Fügen Sie dieses Element in die Eigenschaft „ baseGradients “ des „ theme “ ein, das in „ Surface3D “ verwendet wird:
theme: GraphsTheme {
colorScheme: GraphsTheme.ColorScheme.Dark
labelFont.family: "STCaiyun"
labelFont.pointSize: 35
colorStyle: GraphsTheme.ColorStyle.ObjectGradient
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 Zeichenmodus lässt sich nicht vollständig löschen; solange die Oberfläche selbst sichtbar ist, kann das Oberflächenraster daher nicht ausgeblendet werden:
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 kann nicht vollständig gelöscht werden; 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.shading === Surface3DSeries.Shading.Flat) {
heightSeries.shading = Surface3DSeries.Shading.Smooth;
text = "Show\nFlat"
} else {
heightSeries.shading = Surface3DSeries.Shading.Flat;
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. In der Regel werden Spektrogramme als zweidimensionale Oberflächen dargestellt, die durch eine orthogonale Draufsicht auf das Diagramm simuliert werden. 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 Element vom Typ „ Surface3D “ mit den im „ Surface3DSeries “ angegebenen Daten und einem „ ItemModelSurfaceDataProxy “:
Surface3D {
id: surfaceGraph
anchors.fill: parent
// Don't show specular spotlight as we don't want it to distort the colors
lightStrength: 0.0
ambientLightStrength: 1.0
Surface3DSeries {
id: surfaceSeries
shading: Surface3DSeries.Shading.Smooth
drawMode: Surface3DSeries.DrawSurface
baseGradient: surfaceGradient
colorStyle: GraphsTheme.ColorStyle.RangeGradient
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 „ cameraPreset “. Entfernen Sie die Perspektive, indem Sie die orthografische 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
cameraPreset: Graphs3D.CameraPreset.DirectlyAboveDa bei dieser Ansicht das horizontale Achsenraster größtenteils von der Oberfläche verdeckt wird, kehren Sie das horizontale Raster um, damit 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 umzuschalten:
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 radialen Achsen werden standardmäßig außerhalb des Diagramms gezeichnet. Um sie direkt neben der 0-Grad-Winkelachse innerhalb des Diagramms darzustellen, legen Sie für sie nur einen geringen Versatz fest:
radialLabelOffset: 0.01Um den 2D-Effekt zu gewährleisten, 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:
rotationEnabled: !surfaceGraph.orthoProjectionOszilloskop
Kombinieren Sie auf der Registerkarte „ Oscilloscope “ C++ und QML in einer Anwendung und zeigen Sie Daten an, die sich dynamisch ändern.
Datenquelle in C++
Modellbasierte Proxies eignen sich gut für einfache oder statische Diagramme, doch sollten Sie für die Darstellung von Daten, die sich in Echtzeit ändern, Basis-Proxies verwenden, um die beste Leistung zu erzielen. Diese werden in QML nicht unterstützt, da die darin gespeicherten Datenelemente die Eigenschaft „ QObject “ nicht erben und daher nicht direkt über QML-Code 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
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 waveAngleMul = M_PI * M_PI * rowMod;
float waveMul = 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(waveAngleMul * colMod) + 1.0)))) * waveMul
+ QRandomGenerator::global()->bounded(0.15f) * yRangeMod;
int index = k + cacheIndexAdjustment;
if (index >= columnCount) {
// Wrap over
index -= columnCount;
x -= xRange;
}
row[index] = QSurfaceDataItem(x, y, z);
}
}
}Die zweite Methode, ` update()`, kopiert einen Satz der zwischengespeicherten Daten in ein anderes Array, das durch den Aufruf von ` QSurfaceDataProxy::resetArray()` der Serie zugewiesen wird. Um den Overhead zu minimieren, verwenden Sie dasselbe Array wieder, sofern sich dessen Dimensionen nicht geändert haben:
// Each iteration uses data from a different cached array
m_index++;
if (m_index > m_data.count() - 1)
m_index = 0;
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.isEmpty() || series->dataProxy()->rowCount() != newRowCount
|| series->dataProxy()->columnCount() != newColumnCount) {
m_resetArray.clear();
m_resetArray.reserve(newRowCount);
for (int i = 0; i < newRowCount; i++)
m_resetArray.append(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];
for (int j = 0; j < newColumnCount; j++)
row[j].setPosition(sourceRow.at(j).position());
}
// Notify the proxy that data has changed
series->dataProxy()->resetArray(m_resetArray);Auch wenn wir mit dem zuvor auf die Serie 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 öffentlich, 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(surfacegallery
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, muss der Metatyp registriert sein:
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 SurfaceGalleryExample
...
DataSource {
id: dataSource
}Definieren Sie einen „ Surface3D “-Graphen und weisen Sie ihm ein „ Surface3DSeries “ zu:
Surface3D {
id: surfaceGraph
anchors.fill: parent
Surface3DSeries {
id: surfaceSeries
drawMode: Surface3DSeries.DrawSurfaceAndWireframe
itemLabelFormat: "@xLabel, @zLabel: @yLabel"Geben Sie keinen Proxy für den „ Surface3DSeries “ an, den Sie an den Graphen anhängen. Dadurch nutzt 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 vollständig 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 in den Daten auszulösen, definieren Sie eine „ Timer “, die die Methode „ update() “ in „ DataSource “ in den gewünschten Intervallen aufruft:
Timer {
id: refreshTimer
interval: 1000 / frequencySlider.value
running: true
repeat: true
onTriggered: dataSource.update(surfaceSeries);
}Direktes Rendering aktivieren
Da diese Anwendung möglicherweise 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 “ verwendete Standardformat unterstützt kein Antialiasing. Verwenden Sie die bereitgestellte Hilfsfunktion, um das Oberflächenformat in „ main.cpp “ zu ändern:
#include <QtGraphs/qutils.h>
...
// Enable antialiasing in direct rendering mode
viewer.setFormat(QQuick3D::idealSurfaceFormat(8));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.