Galerie der Oberflächendiagramme
Galerie mit drei verschiedenen Möglichkeiten, ein Surface3D Diagramm zu verwenden.
DieOberflächengrafik-Galerie demonstriert drei verschiedene benutzerdefinierte Funktionen mit Surface3D Graphen. Die Funktionen haben ihre eigenen Registerkarten in der Anwendung.
Die folgenden Abschnitte konzentrieren sich nur auf diese Funktionen und übergehen die Erläuterung der grundlegenden Funktionalität - eine ausführlichere QML-Beispieldokumentation finden Sie unter Simple Scatter Graph.
Ausführen des Beispiels
Um das Beispiel auszuführen Qt Creatorzu starten, öffnen Sie den Modus Welcome und wählen Sie das Beispiel unter Examples aus. Weitere Informationen finden Sie unter Erstellen und Ausführen eines Beispiels.
Höhenkarte
Erstellen Sie auf der Registerkarte Height Map ein Oberflächendiagramm aus Höhendaten. Die verwendeten Daten sind eine Höhenkarte von Mount Ruapehu und Mount Ngauruhoe in Neuseeland.
Hinzufügen von Daten zum Diagramm
Die Daten werden mit HeightMapSurfaceDataProxy gesetzt, das Höheninformationen aus einem Höhenkartenbild liest. Der Proxy selbst ist in einer Surface3DSeries enthalten. Innerhalb der HeightMapSurfaceDataProxy gibt die Eigenschaft heightMapFile
die Bilddatei an, die die Höhendaten enthält. Die Werteigenschaften im Proxy definieren die minimalen und maximalen Werte für die Breite, Tiefe und Höhe des Oberflächenbereichs. Die Werte z
und x
sind in Breiten- und Längengraden, ungefähr an der realen Position, und y
ist in Metern angegeben.
Hinweis: Das Seitenverhältnis des Diagramms wird nicht auf den realen Maßstab eingestellt, sondern die Höhe wird ü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() }
Anzeige der Daten
Richten Sie in main.qml
das Element Surface3D für die Anzeige der Daten ein.
Definieren Sie zunächst den benutzerdefinierten Farbverlauf, der für die Oberfläche verwendet werden soll. Stellen Sie die Farben von Position 0.0 bis 1.0 mit ColorGradient ein, mit zwei zusätzlichen Stopps, um die Grafik lebendiger zu machen:
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" } }
Setzen Sie dieses Element in die Eigenschaft baseGradients
in dem in Surface3D verwendeten theme
:
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 andere Funktionen von Surface3D zu steuern.
Die erste Schaltfläche schaltet das Oberflächenraster ein und aus. Der Zeichenmodus kann nicht vollständig deaktiviert werden, d.h. wenn die Oberfläche selbst nicht sichtbar ist, kann das Oberflächenraster nicht ausgeblendet werden:
onClicked: { if (heightSeries.drawMode & Surface3DSeries.DrawWireframe) heightSeries.drawMode &= ~Surface3DSeries.DrawWireframe; else heightSeries.drawMode |= Surface3DSeries.DrawWireframe; }
Mit der zweiten Schaltfläche wird die Farbe des Oberflächengitters eingestellt:
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 schaltet die Oberfläche im Oberflächen-Zeichenmodus ein oder aus. Der Zeichenmodus kann nicht vollständig deaktiviert werden, d.h. wenn das Oberflächengitter nicht sichtbar ist, kann die Oberfläche selbst nicht ausgeblendet werden:
onClicked: { if (heightSeries.drawMode & Surface3DSeries.DrawSurface) heightSeries.drawMode &= ~Surface3DSeries.DrawSurface; else heightSeries.drawMode |= Surface3DSeries.DrawSurface; }
Mit dem vierten wird der Schattierungsmodus eingestellt. 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" } }
Mit den übrigen Schaltflächen werden die Hintergrundfunktionen des Diagramms gesteuert.
Spektrogramm
Auf der Registerkarte Spectrogram können Sie polare und kartesische Spektrogramme anzeigen und die orthografische Projektion verwenden, um sie in 2D darzustellen.
Ein Spektrogramm ist ein Oberflächendiagramm mit einem Bereichsgradienten, der zur Hervorhebung der verschiedenen Werte verwendet wird. Normalerweise werden Spektrogramme mit zweidimensionalen Oberflächen dargestellt, was durch eine orthografische Ansicht des Graphen von oben nach unten simuliert wird. Um den 2D-Effekt zu verstärken, deaktivieren Sie die Drehung des Diagramms mit der Maus oder durch Berühren, wenn Sie sich im orthografischen Modus befinden.
Erstellen eines Spektrogramms
Um ein 2D-Spektrogramm zu erstellen, definieren Sie ein Element Surface3D mit den in Surface3DSeries angegebenen Daten mit einem 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 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 scene.activeCamera.cameraPreset: Camera3D.CameraPresetDirectlyAbove
Da bei dieser Betrachtungsweise das Gitter der horizontalen Achse größtenteils von der Oberfläche verdeckt wird, kippen Sie das horizontale Gitter, damit es über dem Diagramm gezeichnet wird:
flipHorizontalGrid: true
Polares Spektrogramm
Je nach Datenlage ist es manchmal sinnvoller, ein Polardiagramm anstelle eines kartesischen Diagramms zu verwenden. Dies wird durch 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 Winkelpolarachse und die Z-Achse in eine radiale Polarachse umgewandelt. Die Oberflächenpunkte werden entsprechend den neuen Achsen neu berechnet.
Die Radialachsenbeschriftungen werden standardmäßig außerhalb des Diagramms gezeichnet. Um sie direkt neben der 0-Grad-Winkelachse innerhalb des Diagramms zu zeichnen, definieren Sie nur einen kleinen Versatz für sie:
radialLabelOffset: 0.01
Um den 2D-Effekt zu erzwingen, deaktivieren Sie die Drehung des Diagramms im orthografischen Modus, indem Sie die Standardeingabe mit einer benutzerdefinierten Eingabehandlung überschreiben, die die Eigenschaft rotationEnabled automatisch auf der Grundlage des Projektionsmodus umschaltet:
inputHandler: TouchInputHandler3D { rotationEnabled: !surfaceGraph.orthoProjection }
Oscilloscope
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, aber verwenden Sie grundlegende Proxies, um die beste Leistung bei der Anzeige von sich in Echtzeit ändernden Daten zu erzielen. Diese werden in QML nicht unterstützt, da die von ihnen gespeicherten Datenelemente nicht QObject erben und daher nicht direkt von QML-Code aus manipuliert werden können. Um diese Einschränkung zu überwinden, implementieren Sie eine einfache DataSource
Klasse in C++, um den Daten-Proxy der Serie zu füllen.
Erstellen Sie eine Klasse DataSource
, um zwei Methoden bereitzustellen, die von QML aus 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, die angezeigt werden sollen. 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() auf den Daten-Proxy der Serie gesetzt wird. Um den Overhead zu minimieren, sollten Sie dasselbe Array wiederverwenden, wenn sich die Abmessungen des Arrays 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 Array-Zeiger arbeiten, der zuvor auf den Proxy gesetzt wurde, muss QSurfaceDataProxy::resetArray() immer noch aufgerufen werden, nachdem die Daten im Array geändert wurden, um das Diagramm zum Rendern der Daten aufzufordern.
Um von QML aus auf die Methoden von DataSource
zugreifen zu können, legen Sie die Datenquelle offen, indem Sie aus der DataSource eine QML_ELEMENT machen:
class DataSource : public QObject { Q_OBJECT QML_ELEMENT
Deklarieren Sie sie außerdem als QML-Modul in der Datei CMakeLists.txt:
qt6_add_qml_module(qmlsurfacegallery URI SurfaceGallery VERSION 1.0 NO_RESOURCE_TARGET_PATH SOURCES datasource.cpp datasource.h ... )
Um QSurface3DSeries Zeiger als Parameter für die Methoden der Klasse DataSource
in allen Umgebungen und Builds zu verwenden, stellen Sie sicher, dass der Metatyp registriert ist:
qRegisterMetaType<QSurface3DSeries *>();
QML-Anwendung
Um 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 geben Sie ihm einen Surface3DSeries:
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 wird für die Reihe die Standardeinstellung QSurfaceDataProxy verwendet.
Blenden Sie die Elementbeschriftung mit itemLabelVisible aus. Bei dynamischen, sich schnell ändernden Daten wäre eine schwebende Auswahlbeschriftung ablenkend und schwer zu lesen.
itemLabelVisible: false
Sie können die Informationen zum ausgewählten Element in einem Text
-Element anstelle der standardmäßigen schwebenden Beschriftung über dem Auswahlzeiger anzeigen:
onItemLabelChanged: { if (surfaceSeries.selectedPoint == surfaceSeries.invalidSelectionPosition) selectionText.text = "No selection"; else selectionText.text = surfaceSeries.itemLabel; }
Initialisieren Sie den Cache DataSource
, wenn der Graph vollständig ist, indem Sie eine 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 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); }
Aktivieren des direkten Rendering
Da diese Anwendung potenziell mit vielen sich schnell ändernden Daten arbeitet, verwendet sie aus Leistungsgründen den direkten 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 mitgelieferte Utility-Funktion, um das Oberflächenformat in main.cpp
zu ändern:
#include <QtDataVisualization/qutils.h> ... // Enable antialiasing in direct rendering mode viewer.setFormat(qDefaultSurfaceFormat(true));
Beispiel Inhalt
© 2025 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.