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 mit Q3DBars ein 3D-Balkendiagramm und kombinieren Sie die Verwendung von Widgets, um verschiedene Eigenschaften des Balkendiagramms anzupassen. Das Beispiel zeigt, wie das geht:

  • Erstellen einer Anwendung mit Q3DBars und einigen Widgets
  • QBar3DSeries und QBarDataProxy verwenden, um Daten in das Diagramm einzugeben
  • Anpassen einiger Diagramm- und Reiheneigenschaften mithilfe von Widget-Steuerelementen
  • Auswahl einer Zeile oder Spalte durch Anklicken einer Achsenbeschriftung
  • Erstellen eines benutzerdefinierten Proxys zur Verwendung mit Q3DBars

Informationen zur Interaktion mit dem Diagramm finden Sie auf dieser Seite.

Erstellen der Anwendung

Zuerst instanziieren Sie in bargraph.cpp Q3DBars :

m_barsGraph = new Q3DBars();

Erstellen Sie dann das Widget sowie die horizontalen und vertikalen Layouts.

Das Diagramm wird mit QWidget::createWindowContainer() in einen Fenster-Container eingebettet. Dies ist erforderlich, da alle Diagrammklassen zur Datenvisualisierung (Q3DBars, Q3DScatter, Q3DSurface) von QWindow erben. Dies ist die einzige Möglichkeit, eine Klasse, die QWindow erbt, als Widget zu verwenden.

Fügen Sie das Diagramm und das vertikale Layout zum horizontalen Layout hinzu:

m_barsWidget = new QWidget;
auto *hLayout = new QHBoxLayout(m_barsWidget);
m_container = QWidget::createWindowContainer(m_barsGraph, m_barsWidget);
m_barsGraph->resize(minimumGraphSize);
m_container->setMinimumSize(minimumGraphSize);
m_container->setMaximumSize(maximumGraphSize);
m_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_container->setFocusPolicy(Qt::StrongFocus);
hLayout->addWidget(m_container, 1);

auto *vLayout = new QVBoxLayout();
hLayout->addLayout(vLayout);

Erstellen Sie als nächstes eine weitere Klasse, die das Hinzufügen von Daten und andere Interaktionen mit dem Diagramm übernimmt:

auto *modifier = new GraphModifier(m_barsGraph, this);
Einrichten des Balkendiagramms

Richten Sie das Diagramm im Konstruktor der Klasse GraphModifier ein:

GraphModifier::GraphModifier(Q3DBars *bargraph, QObject *parent) :
      QObject(parent),
      m_graph(bargraph),

Legen Sie zunächst die Achsen und die Reihen in Mitgliedsvariablen an, um sie leicht ändern zu können:

m_temperatureAxis(new QValue3DAxis),
m_yearAxis(new QCategory3DAxis),
m_monthAxis(new QCategory3DAxis),
m_primarySeries(new QBar3DSeries),
m_secondarySeries(new QBar3DSeries),
m_celsiusString(u"°C"_s)

Legen Sie dann einige visuelle Qualitäten für das Diagramm fest:

m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftMedium);
m_graph->activeTheme()->setBackgroundEnabled(false);
m_graph->activeTheme()->setFont(QFont("Times New Roman", m_fontSize));
m_graph->activeTheme()->setLabelBackgroundEnabled(true);
m_graph->setMultiSeriesUniform(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->setLabelAutoRotation(30.0f);
m_temperatureAxis->setTitleVisible(true);

m_yearAxis->setTitle("Year");
m_yearAxis->setLabelAutoRotation(30.0f);
m_yearAxis->setTitleVisible(true);
m_monthAxis->setTitle("Month");
m_monthAxis->setLabelAutoRotation(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 mit setLabelAutoRotation() einen kleinen Autorotationswinkel, damit sie sich leicht in Richtung der Kamera orientieren. Dies verbessert die Lesbarkeit der Achsenbeschriftungen bei extremen Kamerawinkeln.

Als nächstes initialisieren Sie die visuellen Eigenschaften der Serie. Beachten Sie, dass die zweite Reihe zunächst nicht sichtbar ist:

m_primarySeries->setItemLabelFormat(u"Oulu - @colLabel @rowLabel: @valueLabel"_s);
m_primarySeries->setMesh(QAbstract3DSeries::MeshBevelBar);
m_primarySeries->setMeshSmooth(false);

m_secondarySeries->setItemLabelFormat(u"Helsinki - @colLabel @rowLabel: @valueLabel"_s);
m_secondarySeries->setMesh(QAbstract3DSeries::MeshBevelBar);
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 schließlich den Kamerawinkel fest, indem Sie dieselbe Methode aufrufen, die auch die Schaltfläche zum Ändern des Kamerawinkels in der Benutzeroberfläche verwendet, um zwischen den verschiedenen Kamerawinkeln zu wechseln:

changePresetCamera();

Die Kamera wird über das Szenenobjekt des Diagramms gesteuert:

static int preset = Q3DCamera::CameraPresetFront;

m_graph->scene()->activeCamera()->setCameraPreset((Q3DCamera::CameraPreset)preset);

if (++preset > Q3DCamera::CameraPresetDirectlyBelow)
    preset = Q3DCamera::CameraPresetFrontLow;

Weitere Informationen zur Verwendung von Szenen und Kameras finden Sie unter Q3DScene und Q3DCamera.

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 Proxies der beiden Serien hinzu:

// 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
...
auto *dataSet = new QBarDataArray;
auto *dataSet2 = new QBarDataArray;

dataSet->reserve(m_years.size());
for (qsizetype year = 0; year < m_years.size(); ++year) {
    // Create a data row
    auto *dataRow = new QBarDataRow(m_months.size());
    auto *dataRow2 = new QBarDataRow(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:

auto *rotationSliderX = new QSlider(Qt::Horizontal, m_barsWidget);
rotationSliderX->setTickInterval(30);
rotationSliderX->setTickPosition(QSlider::TicksBelow);
rotationSliderX->setMinimum(-180);
rotationSliderX->setValue(0);
rotationSliderX->setMaximum(180);

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:

vLayout->addWidget(new QLabel(u"Rotate horizontally"_s));
vLayout->addWidget(rotationSliderX, 0, Qt::AlignTop);

Verbinden Sie ihn dann mit einer Methode in GraphModifier:

QObject::connect(rotationSliderX, &QSlider::valueChanged, modifier, &GraphModifier::rotateX);

Erstellen Sie einen Slot in GraphModifier für die Signalverbindung. Die Kamera wird über das Szenenobjekt gesteuert. Diesmal geben Sie die tatsächliche Kameraposition entlang der Umlaufbahn um den Mittelpunkt an, anstatt einen voreingestellten Kamerawinkel anzugeben:

void GraphModifier::rotateX(int rotation)
{
    m_xRotation = rotation;
    m_graph->scene()->activeCamera()->setCameraPosition(m_xRotation, m_yRotation);
}

Sie können nun den Schieberegler verwenden, um das Diagramm zu drehen.

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 beispielsweise Zeilen durch Anklicken einer Achsenbeschriftung auf folgende Weise auswählen:

  1. Ändern Sie den Auswahlmodus auf SelectionRow
  2. Klicken Sie auf eine Jahresbeschriftung
  3. Die Zeile mit dem angeklickten Jahr wird ausgewählt.

Die gleiche Methode funktioniert mit den Flags SelectionSlice und SelectionItem, sofern entweder SelectionRow oder SelectionColumn 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:

Q3DCamera *camera = m_graph->scene()->activeCamera();
m_defaultAngleX = camera->xRotation();
m_defaultAngleY = camera->yRotation();
m_defaultZoom = camera->zoomLevel();
m_defaultTarget = camera->target();

m_animationCameraX.setTargetObject(camera);
m_animationCameraY.setTargetObject(camera);
m_animationCameraZoom.setTargetObject(camera);
m_animationCameraTarget.setTargetObject(camera);

m_animationCameraX.setPropertyName("xRotation");
m_animationCameraY.setPropertyName("yRotation");
m_animationCameraZoom.setPropertyName("zoomLevel");
m_animationCameraTarget.setPropertyName("target");

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 Q3DCamera::target, 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 Datenmodus Custom Proxy Data einschalten, werden ein benutzerdefinierter Datensatz und der entsprechende Proxy verwendet.

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 speichert der Datensatz monatliche Niederschlagsdaten, wobei der Wert in Index Null das Jahr, der Wert in Index Eins den Monat und der Wert in Index Zwei die Niederschlagsmenge in diesem Monat angibt.

Der benutzerdefinierte Proxy ähnelt den itemmodel-basierten Proxies, die von Qt Data Visualization zur Verfügung gestellt werden, und er erfordert ein Mapping zur Interpretation der Daten.

VariantDataSet

Definieren Sie die Datenelemente als QVariantList Objekte. Fügen Sie Funktionen zum Löschen des Datensatzes und zur Abfrage einer Referenz auf die im Satz enthaltenen Daten hinzu. Fügen Sie außerdem Signale hinzu, die ausgegeben werden, wenn Daten hinzugefügt oder der Datensatz geleert 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();
VariantBarDataProxy

Unterklasse VariantBarDataProxy von QBarDataProxy und Bereitstellung einer einfachen 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. Dies ist keine besonders effiziente Implementierung, da jede Änderung eine Neuauflösung des gesamten Datensatzes zur Folge hat, aber das ist für dieses Beispiel kein Problem.

In der Methode resolveDataSet() werden die Werte der Variantendaten auf der Grundlage der Zuordnung in Zeilen und Spalten sortiert. Dies ist sehr ähnlich, wie QItemModelBarDataProxy das Mapping handhabt, mit dem Unterschied, dass Sie hier Listenindizes anstelle von Elementmodellrollen verwenden. Sobald die Werte sortiert sind, generieren Sie aus ihnen QBarDataArray 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(nullptr);
        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
    auto *newProxyArray = new QBarDataArray;
    for (const QString &rowKey : rowList) {
        auto *newProxyRow = new QBarDataRow(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);
}
VariantBarDataMapping

Speichern Sie die Zuordnungsinformationen zwischen VariantDataSet Datenelementindizes und 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();

Der primäre Weg, ein VariantBarDataMapping Objekt zu verwenden, besteht darin, die Zuordnungen im Konstruktor zu übergeben, obwohl Sie die remap() Methode verwenden können, um sie später entweder einzeln oder alle zusammen zu setzen. Ein Signal ausgeben, wenn sich das Mapping ändert. Das Ergebnis ist eine vereinfachte Version der Mapping-Funktionalität von QItemModelBarDataProxy, die an die Arbeit mit Variantenlisten anstelle von Item-Modellen angepasst wurde.

RegenfallDaten

Behandelt 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();
    } ...

Fügen Sie den Datensatz zum benutzerdefinierten Proxy hinzu und legen Sie das Mapping fest:

// 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 abschließend eine Funktion hinzu, mit der Sie die erstellten Serien zur Anzeige abrufen können:

QBar3DSeries *customSeries() { return m_series; }

Punktediagramm

Erstellen Sie auf der Registerkarte Scatter Graph mit Q3DScatter ein 3D-Punktdiagramm. Das Beispiel zeigt, wie das geht:

Grundlegende Informationen zur Erstellung einer Anwendung finden Sie unter Balkendiagramm.

Einrichten des Punktediagramms

Stellen Sie zunächst einige visuelle Qualitäten für das Diagramm im Konstruktor von ScatterDataModifier ein:

m_graph->activeTheme()->setType(Q3DTheme::ThemeStoneMoss);
m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftHigh);
m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFront);
m_graph->scene()->activeCamera()->setZoomLevel(80.f);

Keines dieser Elemente ist obligatorisch, sondern dient dazu, die Standardeinstellungen des Diagramms außer Kraft zu setzen. Sie können ausprobieren, wie es mit den voreingestellten Standardwerten aussieht, indem Sie den obigen Block auskommentieren.

Als Nächstes erstellen Sie eine QScatterDataProxy und die zugehörige QScatter3DSeries. Legen Sie ein benutzerdefiniertes Beschriftungsformat und eine Netzglä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

Der letzte Schritt im ScatterDataModifier Konstruktor ist das Hinzufügen von Daten zum Diagramm:

addData();

Das eigentliche Hinzufügen von Daten erfolgt in der Methode addData(). Zunächst müssen die Achsen konfiguriert werden:

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 es hier tun, bleibt der Konstruktor einfacher und die Konfiguration der Achsen in der Nähe der Daten.

Als nächstes erstellen Sie ein Datenarray und füllen es auf:

auto *dataArray = new QScatterDataArray;
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);

Das Diagramm verfügt nun über 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

Initialisieren Sie m_inputHandler im Konstruktor mit einem Zeiger auf die Instanz des Streudiagramms:

m_inputHandler(new AxesInputHandler(scatter))

Ersetzen Sie den Standardmechanismus für die Eingabeverarbeitung, indem Sie den aktiven Input-Handler von Q3DScatter auf AxesInputHandler setzen, der das benutzerdefinierte Verhalten implementiert:

// Give ownership of the handler to the graph and make it the active handler
m_graph->setActiveInputHandler(m_inputHandler);

Der Input-Handler benötigt Zugriff auf die Achsen des Graphen, also übergeben Sie sie ihm:

// Give our axes to the input handler
m_inputHandler->setAxes(m_graph->axisX(), m_graph->axisZ(), m_graph->axisY());
Erweiterung der Maus-Ereignisbehandlung

Erben Sie zunächst die benutzerdefinierte Eingabehandlung von Q3DInputHandler anstelle von QAbstract3DInputHandler, um die gesamte Funktionalität der Standard-Eingabehandlung beizubehalten und die benutzerdefinierte Funktionalität darüber hinaus hinzuzufügen:

class AxesInputHandler : public Q3DInputHandler

Beginnen Sie mit der Erweiterung der Standardfunktionalität, indem Sie einige der Mausereignisse neu implementieren. Erweitern Sie zunächst mousePressEvent. Fügen Sie ein m_mousePressed Flag für die linke Maustaste hinzu, und behalten Sie den Rest der Standardfunktionalität bei:

void AxesInputHandler::mousePressEvent(QMouseEvent *event, const QPoint &mousePos)
{
    Q3DInputHandler::mousePressEvent(event, mousePos);
    if (Qt::LeftButton == event->button())
        m_mousePressed = true;
}

Als nächstes ändern Sie mouseReleaseEvent, um das Flag zu löschen und den internen Zustand zurückzusetzen:

void AxesInputHandler::mouseReleaseEvent(QMouseEvent *event, const QPoint &mousePos)
{
    Q3DInputHandler::mouseReleaseEvent(event, mousePos);
    m_mousePressed = false;
    m_state = StateNormal;
}

Ändern Sie dann mouseMoveEvent. Prüfen Sie, ob das Flag m_mousePressed true lautet und der interne Zustand ein anderer als StateNormal ist. Wenn dies der Fall ist, setzen Sie die Eingabepositionen für die Berechnung der Mausbewegungsdistanz und rufen Sie die Achsenziehfunktion auf (siehe Implementieren von Achsenziehen für Details):

void AxesInputHandler::mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos)
{
    // Check if we're trying to drag axis label
    if (m_mousePressed && m_state != StateNormal) {
        setPreviousInputPos(inputPosition());
        setInputPosition(mousePos);
        handleAxisDragging();
    } else {
        Q3DInputHandler::mouseMoveEvent(event, mousePos);
    }
}
Implementieren von Axis Dragging

Hören Sie zunächst auf das Auswahlsignal aus dem Diagramm. Tun Sie dies im Konstruktor, und verbinden Sie es mit der Methode handleElementSelected:

// Connect to the item selection signal from graph
connect(graph, &QAbstract3DGraph::selectedElementChanged, this,
        &AxesInputHandler::handleElementSelected);

Überprüfen Sie in handleElementSelected den Typ der Auswahl und setzen Sie den internen Status auf der Grundlage dieser Auswahl:

switch (type) {
case QAbstract3DGraph::ElementAxisXLabel:
    m_state = StateDraggingX;
    break;
case QAbstract3DGraph::ElementAxisYLabel:
    m_state = StateDraggingY;
    break;
case QAbstract3DGraph::ElementAxisZLabel:
    m_state = StateDraggingZ;
    break;
default:
    m_state = StateNormal;
    break;
}

Die eigentliche Ziehlogik ist in der Methode handleAxisDragging implementiert, die von mouseMoveEvent aufgerufen wird, wenn die erforderlichen Bedingungen erfüllt sind:

// Check if we're trying to drag axis label
if (m_mousePressed && m_state != StateNormal) {

In handleAxisDragging wird zunächst die Ausrichtung der Szene von der aktiven Kamera ermittelt:

// Get scene orientation from active camera
float xRotation = scene()->activeCamera()->xRotation();
float yRotation = scene()->activeCamera()->yRotation();

Berechnen Sie dann die Modifikatoren für die Bewegungsrichtung der Maus 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 dann die Mausbewegung und ändern Sie sie auf der Grundlage der y-Drehung der Kamera:

// Get the drag amount
QPoint move = inputPosition() - previousInputPos();

// Flip the effect of y movement if we're viewing from below
float yMove = (yRotation < 0) ? -move.y() : move.y();

Wenden Sie dann den verschobenen Abstand auf die richtige Achse an:

// Adjust axes
switch (m_state) {
case StateDraggingX:
    distance = (move.x() * xMulX - yMove * xMulY) / m_speedModifier;
    m_axisX->setRange(m_axisX->min() - distance, m_axisX->max() - distance);
    break;
case StateDraggingZ:
    distance = (move.x() * zMulX + yMove * zMulY) / m_speedModifier;
    m_axisZ->setRange(m_axisZ->min() + distance, m_axisZ->max() + distance);
    break;
case StateDraggingY:
    distance = move.y() / m_speedModifier; // No need to use adjusted y move here
    m_axisY->setRange(m_axisY->min() + distance, m_axisY->max() + distance);
    break;
default:
    break;
}

Fügen Sie schließlich eine Funktion zur Einstellung der Ziehgeschwindigkeit hinzu:

inline void setDragSpeedModifier(float modifier) { m_speedModifier = modifier; }

Diese wird benötigt, da der Mausbewegungsabstand in Bildschirmkoordinaten absolut ist, und Sie ihn an den Achsenbereich anpassen müssen. Je größer der Wert ist, desto langsamer wird das Ziehen. Beachten Sie, dass in diesem Beispiel die Zoomstufe der Szene bei der Bestimmung der Ziehgeschwindigkeit nicht berücksichtigt wird, so dass Sie Änderungen in der Bereichsanpassung feststellen werden, wenn Sie die Zoomstufe ändern.

Sie könnten den Modifikator auch automatisch auf der Grundlage des Achsenbereichs und der Zoomstufe der Kamera anpassen.

Oberflächendiagramm

Auf der Registerkarte Surface Graph können Sie mit Q3DSurface ein 3D-Oberflächendiagramm erstellen. 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 dann den Proxy mit einfachen Quadratwurzel- und Sinuswellen-Daten. Erstellen Sie eine neue QSurfaceDataArray Instanz und fügen Sie QSurfaceDataRow Elemente hinzu. Legen Sie das erstellte QSurfaceDataArray als Datenfeld für QSurfaceDataProxy fest, indem Sie resetArray() aufrufen.

auto *dataArray = new QSurfaceDataArray;
dataArray->reserve(sampleCountZ);
for (int i = 0 ; i < sampleCountZ ; ++i) {
    auto *newRow = new QSurfaceDataRow;
    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 mithilfe von Höhenkartenbildern erstellen.

Topografische Kartendaten

Die topografischen Daten stammen von der Nationalen Landvermessung Finnlands. Sie 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, den Sie später in einem Codeauszug sehen werden, in das RGB-Format kodiert. Der Multiplikator wird berechnet, indem der größte 24-Bit-Wert durch den höchsten Punkt in Finnland dividiert 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 in QSurface3DSeries.

Definieren Sie zunächst den Kodierungsmultiplikator:

// Value used to encode height data as RGB value on PNG file
const float packingFactor = 11983.f;

Dann 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);

auto *dataArray = new QSurfaceDataArray;
dataArray->reserve(imageHeight);
for (int i = 0; i < imageHeight; ++i) {
    int p = i * widthBits;
    float z = height - float(i) * stepZ;
    auto *newRow = new QSurfaceDataRow;
    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);

Jetzt sind die Daten für den Proxy nutzbar.

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 zunächst die dekorativen Merkmale fest, wie z. B. die Aktivierung des Rasters für die Oberfläche und die Auswahl des flachen Schattierungsmodus. Legen Sie dann das Achsenetikettenformat und die Wertebereiche fest. Stellen Sie die automatische Etikettendrehung ein, um die Lesbarkeit der Beschriftung bei niedrigen Kamerawinkeln zu verbessern. Stellen Sie abschließend sicher, dass die richtige Serie zum Diagramm hinzugefügt wird und die anderen nicht:

m_sqrtSinSeries->setDrawMode(QSurface3DSeries::DrawSurfaceAndWireframe);
m_sqrtSinSeries->setFlatShadingEnabled(true);

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()->setLabelAutoRotation(30.f);
m_graph->axisY()->setLabelAutoRotation(90.f);
m_graph->axisZ()->setLabelAutoRotation(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 topographische Serie aktiviert und andere deaktiviert. Aktivieren Sie eine benutzerdefinierte Eingabehandlung für diese Serie, um Bereiche auf ihr hervorheben zu können:

m_graph->setActiveInputHandler(m_customInputHandler);

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 Q3DSurface 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(QAbstract3DGraph::SelectionNone); }
void toggleModeItem() { m_graph->setSelectionMode(QAbstract3DGraph::SelectionItem); }
void toggleModeSliceRow() { m_graph->setSelectionMode(QAbstract3DGraph::SelectionItemAndRow
                                                      | QAbstract3DGraph::SelectionSlice
                                                      | QAbstract3DGraph::SelectionMultiSeries); }
void toggleModeSliceColumn() { m_graph->setSelectionMode(QAbstract3DGraph::SelectionItemAndColumn
                                                         | QAbstract3DGraph::SelectionSlice
                                                         | QAbstract3DGraph::SelectionMultiSeries); }

Fügen Sie die Flags QAbstract3DGraph::SelectionSlice und QAbstract3DGraph::SelectionMultiSeries für die Zeilen- und Spaltenauswahlmodi hinzu, um eine Slice-Auswahl für alle sichtbaren Reihen im Diagramm gleichzeitig zu ermöglichen.

Achsenbereiche zum Betrachten des Diagramms

Das Beispiel verfügt über vier Schieberegler zur Einstellung der Minimal- und Maximalwerte für die X- und Z-Achsen. 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 dem Diagramm Unterstützung für die Einstellung des X-Bereichs über die Widget-Steuerungen 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 Sqrt & Sin Datensatz 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 auf Q3DTheme::ColorStyleRangeGradient, 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(Q3DTheme::ColorStyleRangeGradient);
Hinzufügen von benutzerdefinierten Meshes zur Anwendung

Fügen Sie die Mesh-Dateien zu CMakeLists.txt für cmake build hinzu:

set(graphgallery_resource_files
    ...
    "data/oilrig.obj"
    "data/pipe.obj"
    "data/refinery.obj"
    ...
)

qt6_add_resources(graphgallery "graphgallery"
    PREFIX
        "/"
    FILES
        ${graphgallery_resource_files}
)

Fügen Sie sie auch in die qrc-Ressourcendatei für die Verwendung mit qmake ein:

<RCC>
    <qresource prefix="/">
        ...
        <file>data/refinery.obj</file>
        <file>data/oilrig.obj</file>
        <file>data/pipe.obj</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 über Kontrollkästchen ein- oder ausgeschaltet werden. Auch andere visuelle Qualitäten können mit einem weiteren Satz von Kontrollkästchen gesteuert werden, einschließlich Durchsichtigkeit für die beiden oberen Ebenen und Hervorhebung für die untere Ebene.

Beginnen Sie mit der Erstellung einer kleinen QImage. Füllen Sie es mit einer einzigen Farbe, die als Farbe für das benutzerdefinierte Objekt verwendet werden soll:

QImage color = QImage(2, 2, QImage::Format_RGB32);
color.fill(Qt::red);

Geben Sie dann die Position des Objekts in einer Variablen an. Die Position kann dann verwendet werden, um das richtige Objekt aus dem Diagramm zu entfernen:

QVector3D positionOne = QVector3D(39.f, 77.f, 19.2f);

Erstellen Sie dann ein neues QCustom3DItem mit allen Parametern:

auto *item = new QCustom3DItem(":/data/oilrig.obj", positionOne,
                               QVector3D(0.025f, 0.025f, 0.025f),
                               QQuaternion::fromAxisAndAngle(0.f, 1.f, 0.f, 45.f),
                               color);

Fügen Sie schließlich das Element zum Diagramm hinzu:

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 Instanz von QCustom3DLabel:

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. Wenn Sie das Element beibehalten möchten, 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_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 CustomInputHandler::wheelEvent(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_axisX->setRange(m_axisXMinValue, m_axisXMaxValue);
    m_axisY->setRange(100.f, y);
    m_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. das Zoomen zu weit geht, wird der Wert auf den zulässigen Mindestwert gesetzt. Wenn der Bereich unter das Bereichsminimum fällt, 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->dataProxy()->array()->at(0)->size();
    m_srcHeight = m_topographicSeries->dataProxy()->array()->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. Füllen Sie schließlich 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.y() - halfWidth;
    if (startX < 0 )
        startX = 0;
    int endX = position.y() + halfWidth;
    if (endX > (m_srcWidth - 1))
        endX = m_srcWidth - 1;
    int startZ = position.x() - halfHeight;
    if (startZ < 0 )
        startZ = 0;
    int endZ = position.x() + halfHeight;
    if (endZ > (m_srcHeight - 1))
        endZ = m_srcHeight - 1;

    QSurfaceDataProxy *srcProxy = m_topographicSeries->dataProxy();
    const QSurfaceDataArray &srcArray = *srcProxy->array();

    auto *dataArray = new QSurfaceDataArray;
    dataArray->reserve(endZ - startZ);
    for (int i = startZ; i < endZ; ++i) {
        auto *newRow = new QSurfaceDataRow;
        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() + 0.1f);
            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 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(Q3DTheme::ColorStyleRangeGradient);
}

Beispiel Inhalt

Beispielprojekt @ code.qt.io

© 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.