Diagramm-Galerie
Galerie der Balken-, Streu- und Oberflächendiagramme.
Die Diagrammgalerie zeigt alle drei Diagrammtypen und einige ihrer besonderen Eigenschaften. Die Diagramme haben ihre eigenen Registerkarten in der Anwendung.
Ausführen des Beispiels
Zum Ausführen des Beispiels von 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.
Balkendiagramm
Erstellen Sie auf der Registerkarte Bar Graph ein 3D-Balkendiagramm mit Q3DBarWidgetItem und kombinieren Sie die Verwendung von Widgets, um verschiedene Balkendiagrammqualitäten anzupassen. Das Beispiel zeigt, wie das geht:
- Erstellen einer Anwendung mit Q3DBarWidgetItem und einigen Steuerwidgets
- QBar3DSeries und QBarDataProxy verwenden, um Daten in das Diagramm einzugeben
- Anpassen einiger Eigenschaften des Diagramms und der Reihen mithilfe von Widget-Steuerelementen
- Auswählen einer Zeile oder Spalte durch Anklicken einer Achsenbeschriftung
- Erstellen eines benutzerdefinierten Proxys zur Verwendung mit Q3DBarWidgetItem
Informationen zur Interaktion mit dem Diagramm finden Sie auf dieser Seite.
Erstellen der Anwendung
- In
bargraph.cpp
instanziieren Sie QQuickWidget und Q3DBarsWidgetItem, und legen Sie die Instanz QQuickWidget als Widget für Q3DBarsWidgetItem fest:m_quickWidget = new QQuickWidget(); m_barGraph = new Q3DBarsWidgetItem(this); m_barGraph->setWidget(m_quickWidget);
- Erstellen Sie ein Container-Widget sowie horizontale und vertikale Layouts. Fügen Sie den Graphen und das vertikale Layout zum horizontalen Layout hinzu:
m_container = new QWidget(); auto *hLayout = new QHBoxLayout(m_container); QSize screenSize = m_quickWidget->screen()->size(); m_quickWidget->setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 1.75)); m_quickWidget->setMaximumSize(screenSize); m_quickWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_quickWidget->setFocusPolicy(Qt::StrongFocus); hLayout->addWidget(m_quickWidget, 1); auto *vLayout = new QVBoxLayout(); hLayout->addLayout(vLayout);
- Erstellen Sie eine weitere Klasse, um das Hinzufügen von Daten und andere Interaktionen mit dem Diagramm zu behandeln:
m_modifier = new GraphModifier(m_barGraph, this);
Einrichten des Balkendiagramms
- Richten Sie das Diagramm im Konstruktor der Klasse
GraphModifier
ein:GraphModifier::GraphModifier(Q3DBarsWidgetItem *bargraph, QObject *parent) : QObject(parent) , m_graph(bargraph)
- Legen Sie die Achsen und die Reihen in Mitgliedsvariablen an, um deren Änderung zu unterstützen:
, m_temperatureAxis(new QValue3DAxis) , m_yearAxis(new QCategory3DAxis) , m_monthAxis(new QCategory3DAxis) , m_primarySeries(new QBar3DSeries) , m_secondarySeries(new QBar3DSeries)
- Legen Sie einige visuelle Qualitäten für das Diagramm fest:
m_graph->setShadowQuality(QtGraphs3D::ShadowQuality::SoftMedium); m_graph->setMultiSeriesUniform(true); // These are set through the active theme m_graph->activeTheme()->setPlotAreaBackgroundVisible(false); m_graph->activeTheme()->setLabelFont(QFont("Times New Roman", m_fontSize)); m_graph->activeTheme()->setLabelBackgroundVisible(true);
- Richten Sie die Achsen ein und machen Sie sie zu den aktiven Achsen des Diagramms:
m_temperatureAxis->setTitle("Average temperature"); m_temperatureAxis->setSegmentCount(m_segments); m_temperatureAxis->setSubSegmentCount(m_subSegments); m_temperatureAxis->setRange(m_minval, m_maxval); m_temperatureAxis->setLabelFormat(u"%.1f "_s + m_celsiusString); m_temperatureAxis->setLabelAutoAngle(30.0f); m_temperatureAxis->setTitleVisible(true); m_yearAxis->setTitle("Year"); m_yearAxis->setLabelAutoAngle(30.0f); m_yearAxis->setTitleVisible(true); m_monthAxis->setTitle("Month"); m_monthAxis->setLabelAutoAngle(30.0f); m_monthAxis->setTitleVisible(true); m_graph->setValueAxis(m_temperatureAxis); m_graph->setRowAxis(m_yearAxis); m_graph->setColumnAxis(m_monthAxis);
- Geben Sie den Achsenbeschriftungen einen kleinen Autorotationswinkel:
m_yearAxis->setLabelAutoAngle(30.0f);
Dadurch werden sie leicht in Richtung der Kamera ausgerichtet, was die Lesbarkeit der Achsenbeschriftungen bei extremen Kamerawinkeln verbessert.
- Initialisieren Sie die visuellen Eigenschaften der Serie. Beachten Sie, dass die zweite Serie zunächst nicht sichtbar ist:
m_primarySeries->setItemLabelFormat(u"Oulu - @colLabel @rowLabel: @valueLabel"_s); m_primarySeries->setMesh(QAbstract3DSeries::Mesh::BevelBar); m_primarySeries->setMeshSmooth(false); m_secondarySeries->setItemLabelFormat(u"Helsinki - @colLabel @rowLabel: @valueLabel"_s); m_secondarySeries->setMesh(QAbstract3DSeries::Mesh::BevelBar); m_secondarySeries->setMeshSmooth(false); m_secondarySeries->setVisible(false);
- Fügen Sie die Reihe zum Diagramm hinzu:
m_graph->addSeries(m_primarySeries); m_graph->addSeries(m_secondarySeries);
- Legen Sie den Kamerawinkel fest, indem Sie dieselbe Methode aufrufen, die auch die Schaltfläche zum Ändern des Kamerawinkels in der Benutzeroberfläche verwendet, um zwischen verschiedenen Kamerawinkeln zu wechseln:
changePresetCamera();
- Die neue Kameravoreinstellung wird in das Diagramm eingefügt:
static int preset = int(QtGraphs3D::CameraPreset::Front); m_graph->setCameraPreset((QtGraphs3D::CameraPreset) preset); if (++preset > int(QtGraphs3D::CameraPreset::DirectlyBelow)) preset = int(QtGraphs3D::CameraPreset::FrontLow);
Hinzufügen von Daten zum Graphen
Rufen Sie am Ende des Konstruktors eine Methode auf, die die Daten einrichtet:
resetTemperatureData();
Diese Methode fügt Daten zu den entsprechenden Reihen hinzu, indem sie Proxys der beiden Reihen verwendet:
// Set up data static const float tempOulu[8][12] = { {-7.4f, -2.4f, 0.0f, 3.0f, 8.2f, 11.6f, 14.7f, 15.4f, 11.4f, 4.2f, 2.1f, -2.3f}, // 2015 {-13.4f, -3.9f, -1.8f, 3.1f, 10.6f, 13.7f, 17.8f, 13.6f, 10.7f, 3.5f, -3.1f, -4.2f}, // 2016 ... QBarDataArray dataSet; QBarDataArray dataSet2; dataSet.reserve(m_years.size()); for (qsizetype year = 0; year < m_years.size(); ++year) { // Create a data row QBarDataRow dataRow(m_months.size()); QBarDataRow dataRow2(m_months.size()); for (qsizetype month = 0; month < m_months.size(); ++month) { // Add data to the row dataRow[month].setValue(tempOulu[year][month]); dataRow2[month].setValue(tempHelsinki[year][month]); } // Add the row to the set dataSet.append(dataRow); dataSet2.append(dataRow2); } // Add data to the data proxy (the data proxy assumes ownership of it) m_primarySeries->dataProxy()->resetArray(dataSet, m_years, m_months); m_secondarySeries->dataProxy()->resetArray(dataSet2, m_years, m_months);
Verwendung von Widgets zur Steuerung des Diagramms
Fahren Sie mit dem Hinzufügen einiger Widgets in bargraph.cpp
fort.
- Fügen Sie einen Schieberegler hinzu:
- Verwenden Sie den Schieberegler, um das Diagramm zu drehen, anstatt nur die Maus oder die Berührung zu verwenden. Fügen Sie ihn dem vertikalen Layout hinzu:
- Verbinden Sie ihn mit einer Methode in
GraphModifier
: - Erstellen Sie einen Slot in
GraphModifier
für die Signalverbindung. Geben Sie die tatsächliche Kameraposition entlang des Orbits um den Mittelpunkt an, anstatt einen voreingestellten Kamerawinkel anzugeben:void GraphModifier::rotateX(int angle) { m_xRotation = angle; m_graph->setCameraPosition(m_xRotation, m_yRotation); }
Sie können nun den Schieberegler zum Drehen des Diagramms verwenden.
Fügen Sie dem vertikalen Layout weitere Widgets zur Steuerung hinzu:
- Drehung des Diagramms
- Beschriftungsstil
- Kamera-Voreinstellung
- Sichtbarkeit des Hintergrunds
- Sichtbarkeit des Gitters
- Glattheit der Balkenschattierung
- Sichtbarkeit der zweiten Balkenreihe
- Richtung der Wertachse
- Sichtbarkeit und Drehung des Achsentitels
- Anzuzeigender Datenbereich
- Stil des Balkens
- Modus der Auswahl
- Thema
- Qualität des Schattens
- Schriftart
- Schriftgröße
- Achsenbeschriftung drehen
- Datenmodus
Einige Widget-Steuerelemente sind absichtlich deaktiviert, wenn sie sich im Datenmodus Custom Proxy Data befinden.
Auswählen einer Zeile oder Spalte durch Anklicken einer Achsenbeschriftung
Die Auswahl nach Achsenetikett ist eine Standardfunktion für Balkendiagramme. Sie können zum Beispiel Zeilen durch Anklicken einer Achsenbeschriftung wie folgt auswählen:
- Ändern Sie den Auswahlmodus auf
Row
- Klicken Sie auf eine Jahresbeschriftung
- Die Zeile mit dem angeklickten Jahr wird ausgewählt.
Die gleiche Methode funktioniert mit den Flags Slice
und Item
, sofern entweder Row
oder Column
ebenfalls gesetzt ist.
Zoomen auf die Auswahl
Als Beispiel für die Anpassung des Kameraziels implementieren Sie eine Animation zum Zoomen auf die Auswahl durch einen Tastendruck. Die Initialisierung der Animation wird im Konstruktor durchgeführt:
m_defaultAngleX = m_graph->cameraXRotation(); m_defaultAngleY = m_graph->cameraYRotation(); m_defaultZoom = m_graph->cameraZoomLevel(); m_defaultTarget = m_graph->cameraTargetPosition(); m_animationCameraX.setTargetObject(m_graph); m_animationCameraY.setTargetObject(m_graph); m_animationCameraZoom.setTargetObject(m_graph); m_animationCameraTarget.setTargetObject(m_graph); m_animationCameraX.setPropertyName("cameraXRotation"); m_animationCameraY.setPropertyName("cameraYRotation"); m_animationCameraZoom.setPropertyName("cameraZoomLevel"); m_animationCameraTarget.setPropertyName("cameraTargetPosition"); int duration = 1700; m_animationCameraX.setDuration(duration); m_animationCameraY.setDuration(duration); m_animationCameraZoom.setDuration(duration); m_animationCameraTarget.setDuration(duration); // The zoom always first zooms out above the graph and then zooms in qreal zoomOutFraction = 0.3; m_animationCameraX.setKeyValueAt(zoomOutFraction, QVariant::fromValue(0.0f)); m_animationCameraY.setKeyValueAt(zoomOutFraction, QVariant::fromValue(90.0f)); m_animationCameraZoom.setKeyValueAt(zoomOutFraction, QVariant::fromValue(50.0f)); m_animationCameraTarget.setKeyValueAt(zoomOutFraction, QVariant::fromValue(QVector3D(0.0f, 0.0f, 0.0f)));
Die Funktion GraphModifier::zoomToSelectedBar()
enthält die Zooming-Funktionalität. QPropertyAnimation m_animationCameraTarget
zielt auf die Eigenschaft cameraTargetPosition, die einen auf den Bereich (-1, 1) normierten Wert annimmt.
Finden Sie heraus, wo sich der ausgewählte Balken relativ zu den Achsen befindet, und verwenden Sie diesen Wert als Endwert für m_animationCameraTarget
:
QVector3D endTarget; float xMin = m_graph->columnAxis()->min(); float xRange = m_graph->columnAxis()->max() - xMin; float zMin = m_graph->rowAxis()->min(); float zRange = m_graph->rowAxis()->max() - zMin; endTarget.setX((selectedBar.y() - xMin) / xRange * 2.0f - 1.0f); endTarget.setZ((selectedBar.x() - zMin) / zRange * 2.0f - 1.0f); ... m_animationCameraTarget.setEndValue(QVariant::fromValue(endTarget));
Drehen Sie dann die Kamera so, dass sie am Ende der Animation immer ungefähr auf die Mitte des Diagramms zeigt:
qreal endAngleX = 90.0 - qRadiansToDegrees(qAtan(qreal(endTarget.z() / endTarget.x()))); if (endTarget.x() > 0.0f) endAngleX -= 180.0f; float barValue = m_graph->selectedSeries() ->dataProxy() ->itemAt(selectedBar.x(), selectedBar.y()) .value(); float endAngleY = barValue >= 0.0f ? 30.0f : -30.0f; if (m_graph->valueAxis()->reversed()) endAngleY *= -1.0f;
Benutzerdefinierter Proxy für Daten
Wenn Sie den Custom Proxy Data Datenmodus einschalten, verwendet das Diagramm im Beispiel einen benutzerdefinierten Datensatz und den entsprechenden Proxy.
Definieren Sie einen einfachen flexiblen Datensatz, VariantDataSet
, bei dem jedes Datenelement eine Variantenliste ist. Jedes Element kann mehrere Werte haben, die durch ihren Index in der Liste identifiziert werden. In diesem Fall ist der Datensatz für die Speicherung monatlicher Niederschlagsdaten vorgesehen. Der Wert in Index Null steht für das Jahr, der Wert in Index Eins für den Monat und der Wert in Index Zwei für die Niederschlagsmenge in diesem Monat.
Der benutzerdefinierte Proxy ähnelt den itemmodel-basierten Proxies, QItemModelBarDataProxy, die von QtGraphs bereitgestellt werden, und erfordert ein Mapping zur Interpretation der Daten.
Implementierung eines Datensatzes
Definieren Sie die Datenelemente als QVariantList Objekte. Fügen Sie Funktionen zum Löschen des Datensatzes und zur Abfrage eines Verweises auf die im Satz enthaltenen Daten hinzu. Fügen Sie außerdem Signale hinzu, die ausgegeben werden, wenn Daten hinzugefügt oder die Menge gelöscht wird:
using VariantDataItem = QVariantList; using VariantDataItemList = QList<VariantDataItem *>; ... void clear(); int addItem(VariantDataItem *item); int addItems(VariantDataItemList *itemList); const VariantDataItemList &itemList() const; Q_SIGNALS: void itemsAdded(int index, int count); void dataCleared();
Implementieren Sie einen Daten-Proxy
Leiten Sie die Klasse VariantBarDataProxy
von QBarDataProxy ab und implementieren Sie eine einfache API mit Gettern und Settern für den Datensatz und das Mapping:
class VariantBarDataProxy : public QBarDataProxy ... // Doesn't gain ownership of the dataset, but does connect to it to listen for // data changes. void setDataSet(VariantDataSet *newSet); VariantDataSet *dataSet(); // Map key (row, column, value) to value index in data item (VariantItem). // Doesn't gain ownership of mapping, but does connect to it to listen for // mapping changes. Modifying mapping that is set to proxy will trigger // dataset re-resolving. void setMapping(VariantBarDataMapping *mapping); VariantBarDataMapping *mapping();
Der Proxy lauscht auf Änderungen im Datensatz und in der Zuordnung und löst den Datensatz auf, wenn Änderungen festgestellt werden. Diese Implementierung ist zwar nicht besonders effizient, da jede Änderung die Neuauflösung des gesamten Datensatzes auslöst, aber das ist für dieses Beispiel nicht von Belang.
In der Methode resolveDataSet()
sortieren Sie die Werte der Variantendaten auf der Grundlage der Zuordnung in Zeilen und Spalten. Dies ist sehr ähnlich, wie QItemModelBarDataProxy das Mapping handhabt, außer dass Sie hier Listenindizes anstelle von Elementmodellrollen verwenden. Sobald die Werte sortiert sind, generieren Sie eine QBarDataArray
aus ihnen und rufen die Methode resetArray()
in der übergeordneten Klasse auf:
void VariantBarDataProxy::resolveDataSet() { // If we have no data or mapping, or the categories are not defined, simply // clear the array if (m_dataSet.isNull() || m_mapping.isNull() || !m_mapping->rowCategories().size() || !m_mapping->columnCategories().size()) { resetArray(); return; } const VariantDataItemList &itemList = m_dataSet->itemList(); int rowIndex = m_mapping->rowIndex(); int columnIndex = m_mapping->columnIndex(); int valueIndex = m_mapping->valueIndex(); const QStringList &rowList = m_mapping->rowCategories(); const QStringList &columnList = m_mapping->columnCategories(); // Sort values into rows and columns using ColumnValueMap = QHash<QString, float>; QHash<QString, ColumnValueMap> itemValueMap; for (const VariantDataItem *item : itemList) { itemValueMap[item->at(rowIndex).toString()][item->at(columnIndex).toString()] = item->at(valueIndex).toReal(); } // Create a new data array in format the parent class understands QBarDataArray newProxyArray; for (const QString &rowKey : rowList) { QBarDataRow newProxyRow(columnList.size()); for (qsizetype i = 0; i < columnList.size(); ++i) newProxyRow[i].setValue(itemValueMap[rowKey][columnList.at(i)]); newProxyArray.append(newProxyRow); } // Finally, reset the data array in the parent class resetArray(newProxyArray); }
Implementieren eines Datenmappers
Speichern Sie die Zuordnungsinformationen zwischen den Indizes der Datenelemente von VariantDataSet
und den Zeilen, Spalten und Werten von QBarDataArray
in VariantBarDataMapping
. Sie enthält die Listen der Zeilen und Spalten, die in die aufgelösten Daten aufgenommen werden sollen:
Q_PROPERTY(int rowIndex READ rowIndex WRITE setRowIndex NOTIFY rowIndexChanged) Q_PROPERTY(int columnIndex READ columnIndex WRITE setColumnIndex NOTIFY columnIndexChanged) Q_PROPERTY(int valueIndex READ valueIndex WRITE setValueIndex NOTIFY valueIndexChanged) Q_PROPERTY(QStringList rowCategories READ rowCategories WRITE setRowCategories NOTIFY rowCategoriesChanged) Q_PROPERTY(QStringList columnCategories READ columnCategories WRITE setColumnCategories NOTIFY columnCategoriesChanged) ... explicit VariantBarDataMapping(int rowIndex, int columnIndex, int valueIndex, const QStringList &rowCategories, const QStringList &columnCategories); ... void remap(int rowIndex, int columnIndex, int valueIndex, const QStringList &rowCategories, const QStringList &columnCategories); ... void mappingChanged();
Die primäre Art und Weise, ein VariantBarDataMapping
-Objekt zu verwenden, besteht darin, die Zuordnungen im Konstruktor anzugeben, obwohl Sie auch die remap()
-Methode verwenden können, um sie später zu setzen, entweder einzeln oder alle zusammen. Ein Signal ausgeben, wenn sich das Mapping ändert. Das Ergebnis ist eine vereinfachte Version der Mapping-Funktionalität von QItemModelBarDataProxy, angepasst an die Arbeit mit Variantenlisten anstelle von Item-Modellen.
RegenfallDaten
- Behandeln Sie die Einrichtung von QBar3DSeries mit dem benutzerdefinierten Proxy in der Klasse
RainfallData
:m_proxy = new VariantBarDataProxy; m_series = new QBar3DSeries(m_proxy);
- Befüllen Sie den Variantendatensatz in der Methode
addDataSet()
:void RainfallData::addDataSet() { // Erstellen eines neuen Variantendatensatzes und einer Datenelementlistem_dataSet = new VariantDataSet; auto *itemList = new VariantDataItemList; // Einlesen von Daten aus einer Datendatei in die Datenelementliste QFile dataFile(":/data/raindata.txt"); if (dataFile.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream stream(&dataFile); while (!stream.atEnd()) { QString line = stream.readLine(); if (line.startsWith('#')) // Kommentare ignorieren continue; const auto strList = QStringView{line}.split(',', Qt::SkipEmptyParts); // Jede Zeile enthält drei Datenelemente: Jahr, Monat und Niederschlagswert if (strList.size() < 3) { qWarning() << "Invalid row read from data:" << line; continue; } // Speichern Sie Jahr und Monat als Strings und den Niederschlagswert als Double // in einem VariantDataItem und fügen Sie das Element zur Elementliste hinzu. auto *newItem = new VariantDataItem; for(int i = 0; i < 2;++i) newItem->append(strList.at(i).trimmed().toString()); newItem->append(strList.at(2).trimmed().toDouble()); itemList->append(newItem); } else { qWarning() << "Unable to open data file:" << dataFile.fileName(); } ...
- Verwenden Sie die benutzerdefinierten Proxy-Funktionen, um den Datensatz zur Serie hinzuzufügen und die Zuordnung festzulegen:
// Add items to the data set and set it to the proxy m_dataSet->addItems(itemList); m_proxy->setDataSet(m_dataSet); // Create new mapping for the data and set it to the proxy m_mapping = new VariantBarDataMapping(0, 1, 2, m_years, m_numericMonths); m_proxy->setMapping(m_mapping);
- Fügen Sie schließlich eine Funktion hinzu, um die erstellte Serie zur Anzeige zu erhalten:
QBar3DSeries *customSeries() { return m_series; }
Streuungsdiagramm
Erstellen Sie auf der Registerkarte Scatter Graph mit Q3DScatterWidgetItem ein 3D-Punktdiagramm. Das Beispiel zeigt, wie das geht:
- Einrichten des Q3DScatterWidgetItem Diagramms
- QScatterDataProxy verwenden, um Daten in das Diagramm einzugeben
- Erstellen und Hinzufügen eines benutzerdefinierten Eingabe-Handlers
Grundlegende Informationen zur Erstellung einer Anwendung finden Sie unter Balkendiagramm.
Einrichten des Punktediagramms
- Richten Sie einige visuelle Qualitäten für das Diagramm im Konstruktor von
ScatterDataModifier
ein:m_graph->setShadowQuality(QtGraphs3D::ShadowQuality::SoftHigh); m_graph->setCameraPreset(QtGraphs3D::CameraPreset::Front); m_graph->setCameraZoomLevel(80.f); // These are set through active theme m_graph->activeTheme()->setTheme(QGraphsTheme::Theme::MixSeries); m_graph->activeTheme()->setColorScheme(QGraphsTheme::ColorScheme::Dark);
Keine dieser Einstellungen ist obligatorisch, aber sie dienen dazu, die Standardeinstellungen des Diagramms außer Kraft zu setzen. Um das Aussehen mit den voreingestellten Standardwerten zu beobachten, kann der obige Block auskommentiert werden.
- Erstellen Sie eine QScatterDataProxy und die zugehörige QScatter3DSeries. Legen Sie ein benutzerdefiniertes Beschriftungsformat und eine Maschenglättung für die Serie fest und fügen Sie sie dem Diagramm hinzu:
auto *proxy = new QScatterDataProxy; auto *series = new QScatter3DSeries(proxy); series->setItemLabelFormat(u"@xTitle: @xLabel @yTitle: @yLabel @zTitle: @zLabel"_s); series->setMeshSmooth(m_smooth); m_graph->addSeries(series);
Hinzufügen von Streudaten
- Fügen Sie im
ScatterDataModifier
Konstruktor Daten zum Diagramm hinzu:addData();
- Die eigentliche Hinzufügung von Daten erfolgt in der Methode
addData()
. Konfigurieren Sie zunächst die Achsen:m_graph->axisX()->setTitle("X"); m_graph->axisY()->setTitle("Y"); m_graph->axisZ()->setTitle("Z");
Sie könnten dies auch im Konstruktor von
ScatterDataModifier
tun. Wenn Sie dies hier tun, bleibt der Konstruktor einfacher und die Achsenkonfiguration in der Nähe der Daten. - Erstellen Sie ein Datenarray und füllen Sie es auf:
QScatterDataArray dataArray; dataArray.reserve(m_itemCount); ... const float limit = qSqrt(m_itemCount) / 2.0f; for (int i = -limit; i < limit; ++i) { for (int j = -limit; j < limit; ++j) { const float x = float(i) + 0.5f; const float y = qCos(qDegreesToRadians(float(i * j) / m_curveDivider)); const float z = float(j) + 0.5f; dataArray.append(QScatterDataItem(x, y, z)); } }
- Schließlich weisen Sie den Proxy an, die Daten zu verwenden, die wir ihm gegeben haben:
m_graph->seriesList().at(0)->dataProxy()->resetArray(dataArray);
Jetzt hat das Diagramm die Daten und ist einsatzbereit. Informationen zum Hinzufügen von Widgets zur Steuerung des Diagramms finden Sie unter Verwendung von Widgets zur Steuerung des Diagramms.
Ersetzen der Standard-Eingabebehandlung
Um den Standardmechanismus für die Eingabeverarbeitung zu ersetzen, setzen Sie die neuen Eingabe-Handler von Q3DScatterWidgetItem, die das benutzerdefinierte Verhalten implementieren:
connect(m_graph, &Q3DGraphsWidgetItem::selectedElementChanged, this, &ScatterDataModifier::handleElementSelected); connect(m_graph, &Q3DGraphsWidgetItem::dragged, this, &ScatterDataModifier::handleAxisDragging); m_graph->setDragButton(Qt::LeftButton);
Erweitern der Maus-Ereignisbehandlung
Implementieren Sie einen neuen drag
Event-Handler. Er liefert eine Mausbewegungsdistanz für die Berechnung der Achsenverschiebung (siehe Implementieren der Achsenverschiebung für Details):
connect(m_graph, &Q3DGraphsWidgetItem::selectedElementChanged, this, &ScatterDataModifier::handleElementSelected); connect(m_graph, &Q3DGraphsWidgetItem::dragged, this, &ScatterDataModifier::handleAxisDragging); m_graph->setDragButton(Qt::LeftButton);
Implementieren der Achsenverschiebung
- Beginnen Sie, auf das Selektionssignal aus dem Diagramm zu hören. Tun Sie das im Konstruktor und verbinden Sie es mit der Methode
handleElementSelected
:connect(m_graph, &Q3DGraphsWidgetItem::selectedElementChanged, this, &ScatterDataModifier::handleElementSelected); connect(m_graph, &Q3DGraphsWidgetItem::dragged, this, &ScatterDataModifier::handleAxisDragging); m_graph->setDragButton(Qt::LeftButton);
- Überprüfen Sie in
handleElementSelected
den Typ der Auswahl und setzen Sie den internen Status auf der Grundlage dieser Auswahl:switch (type) { case QtGraphs3D::ElementType::AxisXLabel: m_state = StateDraggingX; break; case QtGraphs3D::ElementType::AxisYLabel: m_state = StateDraggingY; break; case QtGraphs3D::ElementType::AxisZLabel: m_state = StateDraggingZ; break; default: m_state = StateNormal; break; }
- Die eigentliche Logik des Ziehens wird in der Methode
handleAxisDragging
implementiert, die vom Ereignisdrag
aufgerufen wird:void ScatterDataModifier::handleAxisDragging(QVector2D delta)
- In
handleAxisDragging
wird zunächst die Ausrichtung der Szene von der aktiven Kamera ermittelt:// Get scene orientation from active camera float xRotation = m_graph->cameraXRotation(); float yRotation = m_graph->cameraYRotation();
- Berechnen Sie die Modifikatoren für die Mausbewegungsrichtung auf der Grundlage der Ausrichtung:
// Calculate directional drag multipliers based on rotation float xMulX = qCos(qDegreesToRadians(xRotation)); float xMulY = qSin(qDegreesToRadians(xRotation)); float zMulX = qSin(qDegreesToRadians(xRotation)); float zMulY = qCos(qDegreesToRadians(xRotation));
- Berechnen Sie die Mausbewegung und modifizieren Sie sie auf der Grundlage der y-Drehung der Kamera:
// Get the drag amount QPoint move = delta.toPoint(); // Flip the effect of y movement if we're viewing from below float yMove = (yRotation < 0) ? -move.y() : move.y();
- Wenden Sie den verschobenen Abstand auf die richtige Achse an:
// Adjust axes QValue3DAxis *axis = nullptr; switch (m_state) { case StateDraggingX: axis = m_graph->axisX(); distance = (move.x() * xMulX - yMove * xMulY) / m_dragSpeedModifier; axis->setRange(axis->min() - distance, axis->max() - distance); break; case StateDraggingZ: axis = m_graph->axisZ(); distance = (move.x() * zMulX + yMove * zMulY) / m_dragSpeedModifier; axis->setRange(axis->min() + distance, axis->max() + distance); break; case StateDraggingY: axis = m_graph->axisY(); distance = move.y() / m_dragSpeedModifier; // No need to use adjusted y move here axis->setRange(axis->min() + distance, axis->max() + distance); break; default: break; }
Oberflächendiagramm
Erstellen Sie auf der Registerkarte Surface Graph mit Q3DSurfaceWidgetItem ein 3D-Oberflächendiagramm. Das Beispiel zeigt, wie das geht:
- Ein grundlegendes QSurfaceDataProxy einrichten und Daten dafür festlegen.
- QHeightMapSurfaceDataProxy für die Darstellung von 3D-Höhenkarten verwenden.
- Verwenden Sie topografische Daten, um 3D-Höhenkarten zu erstellen.
- Drei verschiedene Auswahlmodi für die Untersuchung des Diagramms zu verwenden.
- Verwenden Sie Achsenbereiche, um ausgewählte Teile des Diagramms anzuzeigen.
- Legen Sie einen benutzerdefinierten Oberflächengradienten fest.
- Hinzufügen von benutzerdefinierten Elementen und Beschriftungen mit QCustom3DItem und QCustom3DLabel.
- Verwenden Sie einen benutzerdefinierten Input-Handler, um das Zoomen und Schwenken zu aktivieren.
- Markieren Sie einen Bereich der Oberfläche.
Grundlegende Informationen zur Anwendungserstellung finden Sie unter Balkendiagramm.
Einfache Oberfläche mit generierten Daten
- Erstellen Sie zunächst ein neues QSurfaceDataProxy und verbinden Sie es mit einem neuen QSurface3DSeries:
m_sqrtSinProxy = new QSurfaceDataProxy(); m_sqrtSinSeries = new QSurface3DSeries(m_sqrtSinProxy);
- Füllen Sie den Proxy mit einfachen Quadratwurzel- und Sinuswellen-Daten. Erstellen Sie eine
QSurfaceDataArray
Instanz und fügen SieQSurfaceDataRow
Elemente hinzu. Legen Sie das erstellteQSurfaceDataArray
als Datenarray für das QSurfaceDataProxy fest, indem SieresetArray()
aufrufen.QSurfaceDataArray dataArray; dataArray.reserve(sampleCountZ); for (int i = 0; i < sampleCountZ; ++i) { QSurfaceDataRow newRow; newRow.reserve(sampleCountX); // Keep values within range bounds, since just adding step can cause minor // drift due to the rounding errors. float z = qMin(sampleMax, (i * stepZ + sampleMin)); for (int j = 0; j < sampleCountX; ++j) { float x = qMin(sampleMax, (j * stepX + sampleMin)); float R = qSqrt(z * z + x * x) + 0.01f; float y = (qSin(R) / R + 0.24f) * 1.61f; newRow.append(QSurfaceDataItem(x, y, z)); } dataArray.append(newRow); } m_sqrtSinProxy->resetArray(dataArray);
Multiseries-Höhenkartendaten
Erstellen Sie die Höhenkarte, indem Sie eine QHeightMapSurfaceDataProxy mit einer QImage, die die Höhendaten enthält, instanziieren. Verwenden Sie QHeightMapSurfaceDataProxy::setValueRanges(), um den Wertebereich der Karte zu definieren. Im Beispiel ist die Karte von einer imaginären Position von 34,0° N - 40,0° N und 18,0° E - 24,0° E. Diese Werte werden zur Positionierung der Karte auf den Achsen verwendet.
// Create the first surface layer QImage heightMapImageOne(":/data/layer_1.png"); m_heightMapProxyOne = new QHeightMapSurfaceDataProxy(heightMapImageOne); m_heightMapSeriesOne = new QSurface3DSeries(m_heightMapProxyOne); m_heightMapSeriesOne->setItemLabelFormat(u"(@xLabel, @zLabel): @yLabel"_s); m_heightMapProxyOne->setValueRanges(34.f, 40.f, 18.f, 24.f);
Fügen Sie die anderen Oberflächenebenen auf die gleiche Weise hinzu, indem Sie einen Proxy und eine Serie für sie mit Hilfe von Höhenkartenbildern erstellen. -
Topographische Kartendaten
Die topografischen Daten stammen von der Nationalen Landvermessung Finnlands. Diese stellt ein Produkt namens Elevation Model 2 m
zur Verfügung, das für dieses Beispiel geeignet ist.
Die topografischen Daten stammen von Levi fell. Die Genauigkeit der Daten geht weit über den Bedarf hinaus, weshalb sie komprimiert und in eine PNG-Datei kodiert wurden. Der Höhenwert der ursprünglichen ASCII-Daten wird mit Hilfe eines Multiplikators in das RGB-Format umgewandelt, wie im folgenden Codebeispiel gezeigt wird. Der Multiplikator wird berechnet, indem der größte 24-Bit-Wert durch den höchsten Punkt in Finnland geteilt wird.
QHeightMapSurfaceDataProxy konvertiert nur Ein-Byte-Werte. Um die höhere Genauigkeit der Daten der Nationalen Landvermessung von Finnland zu nutzen, lesen Sie die Daten aus der PNG-Datei und dekodieren Sie sie in eine QSurface3DSeries.
- Definieren Sie den Kodierungsmultiplikator:
// Value used to encode height data as RGB value on PNG file const float packingFactor = 11983.f;
- Führen Sie die eigentliche Dekodierung durch:
QImage heightMapImage(file); uchar *bits = heightMapImage.bits(); int imageHeight = heightMapImage.height(); int imageWidth = heightMapImage.width(); int widthBits = imageWidth * 4; float stepX = width / float(imageWidth); float stepZ = height / float(imageHeight); QSurfaceDataArray dataArray; dataArray.reserve(imageHeight); for (int i = 0; i < imageHeight; ++i) { int p = i * widthBits; float z = height - float(i) * stepZ; QSurfaceDataRow newRow; newRow.reserve(imageWidth); for (int j = 0; j < imageWidth; ++j) { uchar aa = bits[p + 0]; uchar rr = bits[p + 1]; uchar gg = bits[p + 2]; uint color = uint((gg << 16) + (rr << 8) + aa); float y = float(color) / packingFactor; newRow.append(QSurfaceDataItem(float(j) * stepX, y, z)); p += 4; } dataArray.append(newRow); } dataProxy()->resetArray(dataArray);
Nun kann ein Surface Graph die Daten über den Proxy konsumieren.
Auswählen des Datensatzes
Um verschiedene Proxies zu demonstrieren, verfügt Surface Graph über drei Optionsfelder, mit denen Sie zwischen den Reihen wechseln können.
Mit Sqrt & Sin wird die einfach generierte Serie aktiviert.
- Legen Sie die dekorativen Merkmale fest, wie z. B. die Aktivierung des Rasters für die Oberfläche und die Auswahl des flachen Schattierungsmodus.
- Definieren Sie das Achsenetikettenformat und die Wertebereiche. Stellen Sie die automatische Etikettendrehung ein, um die Lesbarkeit der Beschriftung bei niedrigen Kamerawinkeln zu verbessern.
- Stellen Sie sicher, dass die richtige Serie zum Diagramm hinzugefügt wird und die anderen nicht.
m_sqrtSinSeries->setDrawMode(QSurface3DSeries::DrawSurfaceAndWireframe); m_sqrtSinSeries->setShading(QSurface3DSeries::Shading::Flat); m_graph->axisX()->setLabelFormat("%.2f"); m_graph->axisZ()->setLabelFormat("%.2f"); m_graph->axisX()->setRange(sampleMin, sampleMax); m_graph->axisY()->setRange(0.f, 2.f); m_graph->axisZ()->setRange(sampleMin, sampleMax); m_graph->axisX()->setLabelAutoAngle(30.f); m_graph->axisY()->setLabelAutoAngle(90.f); m_graph->axisZ()->setLabelAutoAngle(30.f); m_graph->removeSeries(m_heightMapSeriesOne); m_graph->removeSeries(m_heightMapSeriesTwo); m_graph->removeSeries(m_heightMapSeriesThree); m_graph->removeSeries(m_topography); m_graph->removeSeries(m_highlight); m_graph->addSeries(m_sqrtSinSeries);
Mit Multiseries Height Map werden die Höhenkartenreihen aktiviert und die anderen deaktiviert. Die automatische Anpassung des Y-Achsenbereichs funktioniert gut für die Höhenkartenoberfläche, stellen Sie also sicher, dass sie eingestellt ist.
m_graph->axisY()->setAutoAdjustRange(true);
Mit Textured Topography wird die topografische Serie aktiviert und andere werden deaktiviert. Aktivieren Sie eine benutzerdefinierte Eingabehandlung für diese Serie, um Bereiche auf ihr hervorheben zu können:
m_graph->setDragButton(Qt::LeftButton); QObject::connect(m_graph, &Q3DGraphsWidgetItem::dragged, this, &SurfaceGraphModifier::handleAxisDragging); QObject::connect(m_graph, &Q3DGraphsWidgetItem::wheel, this, &SurfaceGraphModifier::onWheel); m_graph->setZoomEnabled(false);
Informationen zum benutzerdefinierten Input-Handler für diesen Datensatz finden Sie unter Benutzerdefinierten Input-Handler verwenden, um Zoomen und Schwenken zu aktivieren.
Auswahlmodi
Die drei von Q3DSurfaceWidgetItem unterstützten Auswahlmodi können mit Optionsfeldern verwendet werden. Um den ausgewählten Modus zu aktivieren oder zu deaktivieren, fügen Sie die folgenden Inline-Methoden hinzu:
void toggleModeNone() { m_graph->setSelectionMode(QtGraphs3D::SelectionFlag::None); } void toggleModeItem() { m_graph->setSelectionMode(QtGraphs3D::SelectionFlag::Item); } void toggleModeSliceRow() { m_graph->setSelectionMode(QtGraphs3D::SelectionFlag::ItemAndRow | QtGraphs3D::SelectionFlag::Slice | QtGraphs3D::SelectionFlag::MultiSeries); } void toggleModeSliceColumn() { m_graph->setSelectionMode(QtGraphs3D::SelectionFlag::ItemAndColumn | QtGraphs3D::SelectionFlag::Slice | QtGraphs3D::SelectionFlag::MultiSeries); }
Um die gleichzeitige Auswahl aller sichtbaren Reihen im Diagramm zu unterstützen, fügen Sie die Flags QtGraphs3D::SelectionFlag::Slice
und QtGraphs3D::SelectionFlag::MultiSeries
für die Zeilen- und Spaltenauswahlmodi hinzu.
Achsenbereiche zum Betrachten des Diagramms
Das Beispiel verfügt über vier Schieberegler zur Einstellung der Minimal- und Maximalwerte für die X- und Z-Achse. Bei der Auswahl des Proxys werden diese Schieberegler so eingestellt, dass sie mit den Achsenbereichen des aktuellen Datensatzes übereinstimmen:
// Reset range sliders for Sqrt & Sin m_rangeMinX = sampleMin; m_rangeMinZ = sampleMin; m_stepX = (sampleMax - sampleMin) / float(sampleCountX - 1); m_stepZ = (sampleMax - sampleMin) / float(sampleCountZ - 1); m_axisMinSliderX->setMinimum(0); m_axisMinSliderX->setMaximum(sampleCountX - 2); m_axisMinSliderX->setValue(0); m_axisMaxSliderX->setMinimum(1); m_axisMaxSliderX->setMaximum(sampleCountX - 1); m_axisMaxSliderX->setValue(sampleCountX - 1); m_axisMinSliderZ->setMinimum(0); m_axisMinSliderZ->setMaximum(sampleCountZ - 2); m_axisMinSliderZ->setValue(0); m_axisMaxSliderZ->setMinimum(1); m_axisMaxSliderZ->setMaximum(sampleCountZ - 1); m_axisMaxSliderZ->setValue(sampleCountZ - 1);
Fügen Sie die Unterstützung für die Einstellung des X-Bereichs über die Widget-Steuerungen zum Diagramm hinzu:
void SurfaceGraphModifier::setAxisXRange(float min, float max) { m_graph->axisX()->setRange(min, max); }
Fügen Sie die Unterstützung für den Z-Bereich auf die gleiche Weise hinzu.
Benutzerdefinierte Oberflächenverläufe
Mit dem Datensatz Sqrt & Sin können benutzerdefinierte Oberflächengradienten mit zwei Drucktasten verwendet werden. Definieren Sie den Farbverlauf mit QLinearGradient, wo die gewünschten Farben eingestellt werden. Ändern Sie außerdem den Farbstil in Q3DTheme::ColorStyle::RangeGradient, um den Farbverlauf zu verwenden.
QLinearGradient gr; gr.setColorAt(0.f, Qt::black); gr.setColorAt(0.33f, Qt::blue); gr.setColorAt(0.67f, Qt::red); gr.setColorAt(1.f, Qt::yellow); m_sqrtSinSeries->setBaseGradient(gr); m_sqrtSinSeries->setColorStyle(QGraphsTheme::ColorStyle::RangeGradient);
Hinzufügen von benutzerdefinierten Meshes zur Anwendung
So fügen Sie der Anwendung benutzerdefinierte Meshes hinzu:
- Für ein cmake-Build. Fügen Sie die Mesh-Dateien zu
CMakeLists.txt
hinzu:set(graphgallery_resource_files ... "data/oilrig.mesh" "data/pipe.mesh" "data/refinery.mesh" ... ) qt6_add_resources(widgetgraphgallery "widgetgraphgallery" PREFIX "/" FILES ${graphgallery_resource_files} )
- Für ein qmake-Build. Fügen Sie die Mesh-Dateien in die qrc-Ressourcendatei ein:
<RCC> <qresource prefix="/"> ... <file>data/refinery.mesh</file> <file>data/oilrig.mesh</file> <file>data/pipe.mesh</file> ... </qresource> </RCC>
Hinzufügen von benutzerdefinierten Elementen zu einem Diagramm
Mit dem Datensatz Multiseries Height Map werden benutzerdefinierte Elemente in das Diagramm eingefügt und können mit Kontrollkästchen ein- oder ausgeschaltet werden. Andere visuelle Änderungen können ebenfalls mit einem anderen Satz von Kontrollkästchen gesteuert werden, einschließlich Durchsichtigkeit für die beiden oberen Ebenen und Hervorhebung für die untere Ebene.
- Erstellen einer kleinen QImage. Füllen Sie es mit einer einzigen Farbe, die als Farbe für das benutzerdefinierte Objekt verwendet werden soll:
- Geben Sie die Position des Objekts in einer Variablen an. Die Position kann dann verwendet werden, um das richtige Element aus dem Diagramm zu entfernen:
QVector3D positionOne = QVector3D(39.f, 77.f, 19.2f);
- Erstellen Sie eine neue QCustom3DItem mit allen Parametern:
auto *item = new QCustom3DItem(":/data/oilrig.mesh", positionOne, QVector3D(0.025f, 0.025f, 0.025f), QQuaternion::fromAxisAndAngle(0.f, 1.f, 0.f, 45.f), color);
- Fügen Sie das Element in das Diagramm ein:
m_graph->addCustomItem(item);
Hinzufügen einer benutzerdefinierten Beschriftung zu einem Diagramm
Das Hinzufügen eines benutzerdefinierten Etiketts ist dem Hinzufügen eines benutzerdefinierten Elements sehr ähnlich. Für die Beschriftung wird kein benutzerdefiniertes Netz benötigt, sondern nur eine QCustom3DLabel Instanz:
auto *label = new QCustom3DLabel(); label->setText("Oil Rig One"); label->setPosition(positionOneLabel); label->setScaling(QVector3D(1.f, 1.f, 1.f)); m_graph->addCustomItem(label);
Entfernen eines benutzerdefinierten Elements aus einem Diagramm
Um ein bestimmtes Element aus dem Diagramm zu entfernen, rufen Sie removeCustomItemAt()
mit der Position des Elements auf:
m_graph->removeCustomItemAt(positionOne);
Hinweis: Wenn Sie ein benutzerdefiniertes Element aus dem Diagramm entfernen, wird auch das Objekt gelöscht. Um das Element zu erhalten, verwenden Sie stattdessen die Methode releaseCustomItem()
.
Texturieren einer Oberflächenreihe
Erstellen Sie mit dem Datensatz Textured Topography eine Kartentextur, die mit der topografischen Höhenkarte verwendet werden soll.
Legen Sie mit QSurface3DSeries::setTextureFile() ein Bild fest, das als Textur auf einer Fläche verwendet werden soll. Fügen Sie ein Kontrollkästchen hinzu, um zu steuern, ob die Textur gesetzt ist oder nicht, und einen Handler, der auf den Zustand des Kontrollkästchens reagiert:
void SurfaceGraphModifier::toggleSurfaceTexture(bool enable) { if (enable) m_topography->setTextureFile(":/data/maptexture.jpg"); else m_topography->setTextureFile(""); }
Das Bild in diesem Beispiel wird aus einer JPG-Datei gelesen. Wenn Sie eine leere Datei mit der Methode einstellen, wird die Textur gelöscht, und die Oberfläche verwendet die Farbverläufe oder Farben des Themas.
Verwenden Sie einen benutzerdefinierten Input-Handler, um das Zoomen und Schwenken zu aktivieren
Erstellen Sie mit dem Datensatz Textured Topography einen benutzerdefinierten Input-Handler, um die Auswahl im Diagramm zu markieren und das Schwenken des Diagramms zu ermöglichen.
Die Implementierung des Schwenks ähnelt derjenigen, die in Implementing Axis Dragging gezeigt wird. Der Unterschied besteht darin, dass Sie in diesem Beispiel nur die X- und Z-Achse verfolgen und das Ziehen der Oberfläche außerhalb des Diagramms nicht zulassen. Um das Ziehen zu begrenzen, folgen Sie den Grenzen der Achsen und tun nichts, wenn Sie außerhalb des Diagramms gehen:
case StateDraggingX: distance = (move.x() * xMulX - move.y() * xMulY) * m_speedModifier; m_axisXMinValue -= distance; m_axisXMaxValue -= distance; if (m_axisXMinValue < m_areaMinValue) { float dist = m_axisXMaxValue - m_axisXMinValue; m_axisXMinValue = m_areaMinValue; m_axisXMaxValue = m_axisXMinValue + dist; } if (m_axisXMaxValue > m_areaMaxValue) { float dist = m_axisXMaxValue - m_axisXMinValue; m_axisXMaxValue = m_areaMaxValue; m_axisXMinValue = m_axisXMaxValue - dist; } m_graph->axisX()->setRange(m_axisXMinValue, m_axisXMaxValue); break;
Zum Zoomen fangen Sie die wheelEvent
ein und passen die Bereiche der X- und Y-Achse entsprechend dem Delta-Wert auf QWheelEvent an. Passen Sie die Y-Achse so an, dass das Seitenverhältnis zwischen der Y-Achse und der XZ-Ebene gleich bleibt. Dadurch wird verhindert, dass das Diagramm in der Höhe übertrieben ist:
void SurfaceGraphModifier::onWheel(QWheelEvent *event) { float delta = float(event->angleDelta().y()); m_axisXMinValue += delta; m_axisXMaxValue -= delta; m_axisZMinValue += delta; m_axisZMaxValue -= delta; checkConstraints(); float y = (m_axisXMaxValue - m_axisXMinValue) * m_aspectRatio; m_graph->axisX()->setRange(m_axisXMinValue, m_axisXMaxValue); m_graph->axisY()->setRange(100.f, y); m_graph->axisZ()->setRange(m_axisZMinValue, m_axisZMaxValue); }
Fügen Sie als Nächstes einige Grenzen für die Zoomstufe hinzu, damit sie nicht zu nahe an die Oberfläche herankommt oder zu weit von ihr entfernt ist. Wenn z. B. der Wert für die X-Achse unter den zulässigen Grenzwert fällt, d. h. wenn der Zoom zu weit geht, wird der Wert auf den zulässigen Mindestwert gesetzt. Wenn der Bereich unter das Bereichsminimum sinkt, werden beide Enden der Achse so angepasst, dass der Bereich an der Grenze bleibt:
if (m_axisXMinValue < m_areaMinValue) m_axisXMinValue = m_areaMinValue; if (m_axisXMaxValue > m_areaMaxValue) m_axisXMaxValue = m_areaMaxValue; // Don't allow too much zoom in if ((m_axisXMaxValue - m_axisXMinValue) < m_axisXMinRange) { float adjust = (m_axisXMinRange - (m_axisXMaxValue - m_axisXMinValue)) / 2.f; m_axisXMinValue -= adjust; m_axisXMaxValue += adjust; }
Einen Bereich der Oberfläche hervorheben
Um eine Hervorhebung zu implementieren, die auf der Oberfläche angezeigt werden soll, erstellen Sie eine Kopie der Reihe und fügen Sie einen Versatz zum y-Wert hinzu. In diesem Beispiel implementiert die Klasse HighlightSeries
die Erstellung der Kopie in ihrer Methode handlePositionChange
.
Übergeben Sie zunächst HighlightSeries
den Zeiger auf die ursprüngliche Reihe, und beginnen Sie dann, auf das Signal QSurface3DSeries::selectedPointChanged zu hören:
void HighlightSeries::setTopographicSeries(TopographicSeries *series) { m_topographicSeries = series; m_srcWidth = m_topographicSeries->dataArray().at(0).size(); m_srcHeight = m_topographicSeries->dataArray().size(); QObject::connect(m_topographicSeries, &QSurface3DSeries::selectedPointChanged, this, &HighlightSeries::handlePositionChange); }
Wenn das Signal ausgelöst wird, prüfen Sie, ob die Position gültig ist. Berechnen Sie dann die Bereiche für den kopierten Bereich und prüfen Sie, ob sie innerhalb der Grenzen bleiben. Schließlich füllen Sie das Datenfeld der Highlight-Serie mit dem Bereich aus dem Datenfeld der Topografie-Serie:
void HighlightSeries::handlePositionChange(const QPoint &position) { m_position = position; if (position == invalidSelectionPosition()) { setVisible(false); return; } int halfWidth = m_width / 2; int halfHeight = m_height / 2; int startX = position.x() - halfWidth; if (startX < 0) startX = 0; int endX = position.x() + halfWidth; if (endX > (m_srcWidth - 1)) endX = m_srcWidth - 1; int startZ = position.y() - halfHeight; if (startZ < 0) startZ = 0; int endZ = position.y() + halfHeight; if (endZ > (m_srcHeight - 1)) endZ = m_srcHeight - 1; const QSurfaceDataArray &srcArray = m_topographicSeries->dataArray(); QSurfaceDataArray dataArray; dataArray.reserve(endZ - startZ); for (int i = startZ; i < endZ; ++i) { QSurfaceDataRow newRow; newRow.reserve(endX - startX); QSurfaceDataRow srcRow = srcArray.at(i); for (int j = startX; j < endX; ++j) { QVector3D pos = srcRow.at(j).position(); pos.setY(pos.y() + m_heightAdjustment); newRow.append(QSurfaceDataItem(pos)); } dataArray.append(newRow); } dataProxy()->resetArray(dataArray); setVisible(true); }
Ein Farbverlauf für die Highlight-Serie
Da es sich bei HighlightSeries
um eine QSurface3DSeries handelt, stehen alle Dekorationsmethoden zur Verfügung, die eine Serie haben kann. In diesem Beispiel fügen Sie einen Farbverlauf hinzu, um die Höhe zu betonen. Da der geeignete Verlaufsstil vom Bereich der Y-Achse abhängt und wir den Bereich beim Zoomen ändern, müssen die Farbpositionen des Verlaufs angepasst werden, wenn sich der Bereich ändert. Zu diesem Zweck definieren Sie proportionale Werte für die Farbpositionen des Farbverlaufs:
const float darkRedPos = 1.f; const float redPos = 0.8f; const float yellowPos = 0.6f; const float greenPos = 0.4f; const float darkGreenPos = 0.2f;
Die Änderung des Farbverlaufs erfolgt in der Methode handleGradientChange
. Verbinden Sie sie also so, dass sie auf Änderungen auf der Y-Achse reagiert:
QObject::connect(m_graph->axisY(), &QValue3DAxis::maxChanged, m_highlight, &HighlightSeries::handleGradientChange);
Wenn sich der Maximalwert der Y-Achse ändert, berechnen Sie die neuen Farbverlaufspositionen:
void HighlightSeries::handleGradientChange(float value) { float ratio = m_minHeight / value; QLinearGradient gr; gr.setColorAt(0.f, Qt::black); gr.setColorAt(darkGreenPos * ratio, Qt::darkGreen); gr.setColorAt(greenPos * ratio, Qt::green); gr.setColorAt(yellowPos * ratio, Qt::yellow); gr.setColorAt(redPos * ratio, Qt::red); gr.setColorAt(darkRedPos * ratio, Qt::darkRed); setBaseGradient(gr); setColorStyle(QGraphsTheme::ColorStyle::RangeGradient); handleZoomChange(ratio); }
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.