Auf dieser Seite

Diagrammgalerie

Galerie mit Balken-, Streu- und Oberflächen-Diagrammen.

Die Diagrammgalerie veranschaulicht alle drei Diagrammtypen und einige ihrer besonderen Merkmale. Die Diagramme verfügen in der Anwendung über eigene Registerkarten.

Texturierte topografische 3D-Oberflächengrafik mit den Registerkarten „Balken“, „Streudiagramm“ und „Oberfläche“

Ausführen des Beispiels

Sie können das Beispiel wie folgt ausführen:

Balkendiagramm

Erstellen Sie auf der Registerkarte „ Bar Graph “ mithilfe von Q3DBars ein 3D-Balkendiagramm und kombinieren Sie verschiedene Widgets, um verschiedene Eigenschaften des Balkendiagramms anzupassen. Das Beispiel zeigt, wie Sie:

  • Erstellen einer Anwendung mit Q3DBars und einigen Widgets
  • Verwenden von „QBar3DSeries“ und „QBarDataProxy“, um Daten für das Diagramm festzulegen
  • einige Diagramm- und Serieneigenschaften mithilfe von Widget-Steuerelementen anzupassen
  • eine Zeile oder Spalte durch Klicken auf eine Achsenbeschriftung auswählen
  • Erstellen eines benutzerdefinierten Proxys zur Verwendung mit Q3DBars

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

Erstellen der Anwendung

Erstellen Sie zunächst in ` bargraph.cpp` eine Instanz von `Q3DBars`:

m_barsGraph = new Q3DBars();

Erstellen Sie anschließend das Widget sowie das horizontale und vertikale Layout.

Das Diagramm wird mithilfe von ` QWidget::createWindowContainer()` in einen Fenstercontainer eingebettet. Dies ist erforderlich, da alle Klassen für Datenvisualisierungsdiagramme (Q3DBars, Q3DScatter, Q3DSurface) von ` QWindow` erben. Nur so kann eine Klasse, die von ` QWindow ` erbt, als Widget verwendet werden.

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, um das Hinzufügen von Daten und andere Interaktionen mit dem Diagramm zu verwalten:

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),

Erstellen Sie zunächst die Achsen und die Reihen als Membervariablen, um deren einfache Änderung zu ermöglichen:

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 anschließend einige visuelle Eigenschaften 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 legen Sie sie als aktive Achsen des Diagramms fest:

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

Weisen Sie den Achsenbeschriftungen mit `setLabelAutoRotation()` einen kleinen Autodrehwinkel zu, damit sie leicht zur Kamera hin ausgerichtet sind. Dies verbessert die Lesbarkeit der Achsenbeschriftungen bei extremen Kamerawinkeln.

Als Nächstes initialisieren Sie die visuellen Eigenschaften der Zeitreihen. Beachten Sie, dass die zweite Zeitreihe 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 Reihen dem Diagramm hinzu:

m_graph->addSeries(m_primarySeries);
m_graph->addSeries(m_secondarySeries);

Legen Sie abschließend den Kamerawinkel fest, indem Sie dieselbe Methode aufrufen, die auch die Schaltfläche zur Änderung des Kamerawinkels in der Benutzeroberfläche verwendet, um zwischen 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 Diagramm

Rufen Sie am Ende des Konstruktors eine Methode auf, die die Daten einrichtet:

resetTemperatureData();

Diese Methode fügt Daten zu den Proxies der beiden Reihen 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);
Steuerung des Diagramms mithilfe von Widgets

Fügen Sie anschließend einige Widgets in „ bargraph.cpp “ hinzu. 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 den Touchscreen zu nutzen. 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 anschließend mit einer Methode in „ GraphModifier “:

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

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

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 die Grafik zu drehen.

Fügen Sie dem vertikalen Layout weitere Widgets hinzu, um Folgendes zu steuern:

  • Grafikdrehung
  • Beschriftungsstil
  • Kamera-Voreinstellung
  • Sichtbarkeit des Hintergrunds
  • Sichtbarkeit des Rasters
  • Glättung der Balkenschattierung
  • Sichtbarkeit der zweiten Balkenreihe
  • Richtung der Werteachse
  • Sichtbarkeit und Drehung der Achsenbeschriftung
  • Anzuzeigender Datenbereich
  • Balkenstil
  • Auswahlmodus
  • Design
  • Schattenqualität
  • Schriftart
  • Schriftgröße
  • Drehung der Achsenbeschriftung
  • Datenmodus

Einige Widget-Steuerelemente sind im Datenmodus „ Custom Proxy Data “ absichtlich deaktiviert.

Auswahl einer Zeile oder Spalte durch Klicken auf eine Achsenbeschriftung

Die Auswahl über Achsenbeschriftungen ist die Standardfunktion für Balkendiagramme. So können Sie beispielsweise Zeilen auswählen, indem Sie wie folgt auf eine Achsenbeschriftung klicken:

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

Die gleiche Methode funktioniert auch mit den Flags „ SelectionSlice “ und „ SelectionItem “, sofern zusätzlich entweder „ SelectionRow “ oder „ SelectionColumn “ gesetzt ist.

Zoomen auf die Auswahl

Als Beispiel für die Anpassung des Kameraziels implementieren Sie eine Animation, bei der durch einen Tastendruck auf die Auswahl gezoomt wird. Die Initialisierung der Animation erfolgt im Konstruktor:

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 Zoom-Funktionalität. „ QPropertyAnimation “ und „ m_animationCameraTarget “ beziehen sich auf die Eigenschaft „ Q3DCamera::target “, die einen auf den Bereich (-1, 1) normierten Wert annimmt.

Ermitteln Sie, wo sich der ausgewählte Balken relativ zu den Achsen befindet, und verwenden Sie dies 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 anschließend die Kamera so, dass sie am Ende der Animation stets ungefähr auf die Mitte des Diagramms ausgerichtet ist:

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

Durch Aktivieren des Datenmodus „ Custom Proxy Data “ 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 am Index Null das Jahr, der Wert am Index Eins den Monat und der Wert am Index Zwei die Niederschlagsmenge in diesem Monat angibt.

Der benutzerdefinierte Proxy ähnelt den von Qt Data Visualization bereitgestellten, auf ItemModels basierenden Proxys und erfordert eine Zuordnung, um die Daten zu interpretieren.

VariantDataSet

Definieren Sie die Datenelemente als QVariantList -Objekte. Fügen Sie Funktionen zum Leeren des Datensatzes und zum Abfragen einer Referenz auf die im Datensatz enthaltenen Daten hinzu. Fügen Sie außerdem Signale hinzu, die ausgelöst 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

Erstellen Sie eine Unterklasse von ` VariantBarDataProxy ` aus `QBarDataProxy` und stellen Sie eine einfache API mit Getter- und Setter-Methoden für den Datensatz und die Zuordnung bereit:

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 überwacht Änderungen am Datensatz und am Mapping und löst den Datensatz neu auf, sobald Änderungen festgestellt werden. Dies ist keine besonders effiziente Implementierung, da jede Änderung eine Neuauflösung des gesamten Datensatzes zur Folge hat, was für dieses Beispiel jedoch kein Problem darstellt.

Sortieren Sie in der Methode „ resolveDataSet() “ die Variantendatenwerte basierend auf dem Mapping in Zeilen und Spalten. Dies ähnelt stark der Art und Weise, wie QItemModelBarDataProxy das Mapping handhabt, außer dass Sie hier Listenindizes anstelle von Item-Model-Rollen verwenden. Sobald die Werte sortiert sind, generieren Sie daraus „ QBarDataArray “ und rufen Sie 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 den Indizes der Datenelemente von „ VariantDataSet “ und den Zeilen, Spalten und Werten von „ QBarDataArray “ in „ VariantBarDataMapping “. Diese 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, ein ` VariantBarDataMapping `-Objekt zu verwenden, besteht darin, die Zuordnungen im Konstruktor anzugeben. Sie können sie jedoch auch später mit der Methode ` remap() ` festlegen, entweder einzeln oder alle zusammen. Bei Änderungen an der Zuordnung wird ein Signal ausgelöst. Das Ergebnis ist eine vereinfachte Version der Zuordnungsfunktionalität von `QItemModelBarDataProxy`, die für die Arbeit mit Variant-Listen anstelle von Elementmodellen angepasst wurde.

RainfallData

Übernehmen Sie die Einrichtung von `QBar3DSeries` mit dem benutzerdefinierten Proxy in der Klasse ` RainfallData `:

m_proxy = new VariantBarDataProxy;
m_series = new QBar3DSeries(m_proxy);

Füllen Sie den Variant-Datensatz in der Methode „ addDataSet() “ mit Daten:

void RainfallData::addDataSet()
{
    // Erstellen Sie einen neuen Variantendatensatz und eine Datenelementliste
    m_dataSet = new VariantDataSet;
    auto *itemList = new VariantDataItemList;

    // Lesen Sie Daten aus einer Datendatei in die Datenelementliste ein
    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;
            }
            // Jahr und Monat als Zeichenketten sowie den Niederschlagswert als Double
            // in einem Variant-Datenelement  speichern  und das Element zur Elementliste hinzufügen. 
            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 die Zuordnung 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, um die erstellte Reihe zur Anzeige abzurufen:

QBar3DSeries *customSeries() { return m_series; }

Streudiagramm

Erstellen Sie auf der Registerkarte „ Scatter Graph “ mithilfe von Q3DScatter ein 3D-Punktdiagramm. Das Beispiel zeigt, wie Sie:

  • Einrichten des Q3DScatter-Diagramms
  • Verwenden von `QScatterDataProxy`, um Daten für das Diagramm festzulegen
  • Erstellen eines benutzerdefinierten Eingabe-Handlers durch Erweiterung von Q3DInputHandler

Informationen zur Erstellung einfacher Anwendungen finden Sie unter „Balkendiagramm“.

Einrichten des Streudiagramms

Legen Sie zunächst im Konstruktor von ` ScatterDataModifier` einige visuelle Eigenschaften für das Diagramm fest:

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

Keine dieser Einstellungen ist zwingend erforderlich, sie dienen jedoch dazu, die Standardwerte des Diagramms zu überschreiben. Sie können ausprobieren, wie es mit den voreingestellten Standardwerten aussieht, indem Sie den obigen Block auskommentieren.

Erstellen Sie als Nächstes einen `QScatterDataProxy` und die zugehörige `QScatter3DSeries`. Legen Sie ein benutzerdefiniertes Beschriftungsformat und eine Gitterglättung für die Reihe 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 Streudiagrammdaten

Als letzten Schritt im Konstruktor von ` ScatterDataModifier ` müssen Sie dem Diagramm Daten hinzufügen:

addData();

Das eigentliche Hinzufügen der 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");

Dies könnte man auch im Konstruktor von ` ScatterDataModifier` tun. Wenn man es hier tut, bleibt der Konstruktor einfacher und die Konfiguration der Achsen befindet sich in der Nähe der Daten.

Als Nächstes erstellen Sie ein Daten-Array und füllen es:

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

Weisen Sie den Proxy schließlich an, die von uns bereitgestellten Daten zu verwenden:

m_graph->seriesList().at(0)->dataProxy()->resetArray(dataArray);

Nun enthält das Diagramm die Daten und ist einsatzbereit. Informationen zum Hinzufügen von Widgets zur Steuerung des Diagramms finden Sie unter „Verwenden von Widgets zur Steuerung des Diagramms“.

Ersetzen der Standard-Eingabeverarbeitung

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

m_inputHandler(new AxesInputHandler(scatter))

Ersetzen Sie den Standardmechanismus zur Eingabeverarbeitung, indem Sie den aktiven Eingabehandler 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 Eingabehandler benötigt Zugriff auf die Achsen des Diagramms, übergebe sie daher an ihn:

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

Erben Sie zunächst den benutzerdefinierten Eingabe-Handler von `Q3DInputHandler` anstelle von `QAbstract3DInputHandler`, um die gesamte Funktionalität der Standard-Eingabeverarbeitung beizubehalten und darauf aufbauend die benutzerdefinierten Funktionen 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 dafür 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 ` so, dass das Flag gelöscht und der interne Zustand zurückgesetzt wird:

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

Ändern Sie anschließend die Datei „ mouseMoveEvent “. Prüfen Sie, ob das Flag „ m_mousePressed “ auf „ true “ gesetzt ist und der interne Zustand nicht „ StateNormal “ ist. Ist dies der Fall, legen Sie die Eingabepositionen für die Berechnung der Mausbewegungsdistanz fest und rufen Sie die Funktion zum Ziehen der Achsen auf (Einzelheiten finden Sie unter „Implementierung des Ziehens von Achsen“ ):

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);
    }
}
Implementierung des Achsen-Draggings

Beginnen Sie zunächst damit, das Auswahlsignal des Diagramms abzuhören. Führen Sie dies im Konstruktor durch 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 legen Sie den internen Status entsprechend fest:

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 Drag-and-Drop-Logik ist in der Methode ` handleAxisDragging ` implementiert, die von ` mouseMoveEvent` aufgerufen wird, sofern 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 Szenenausrichtung von der aktiven Kamera abgerufen:

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

Berechnen Sie anschließend anhand dieser Orientierung die Modifikatoren für die Mausbewegungsrichtung:

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

Berechne anschließend die Mausbewegung und passe sie entsprechend der y-Drehung der Kamera an:

// 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();

Wende anschließend die Verschiebung 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 abschließend eine Funktion zum Einstellen der Ziehgeschwindigkeit hinzu:

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

Dies ist erforderlich, da die Mausbewegungsdistanz in absoluten Bildschirmkoordinaten angegeben wird und Sie diese an den Achsenbereich anpassen müssen. Je größer der Wert, desto langsamer erfolgt das Ziehen. Beachten Sie, dass in diesem Beispiel der Zoomfaktor der Szene bei der Bestimmung der Ziehgeschwindigkeit nicht berücksichtigt wird, sodass Sie Änderungen bei der Bereichsanpassung bemerken werden, wenn Sie den Zoomfaktor ändern.

Sie könnten den Modifikator auch automatisch basierend auf dem Achsenbereich und der Kamera-Zoomstufe anpassen.

Oberflächendiagramm

Erstellen Sie auf der Registerkarte „ Surface Graph “ mithilfe von Q3DSurface ein 3D-Oberflächendiagramm. Das Beispiel zeigt, wie Sie:

  • einen einfachen `QSurfaceDataProxy` einrichtet und Daten dafür festlegt.
  • Verwenden von `QHeightMapSurfaceDataProxy` zur Darstellung von 3D-Höhenkarten.
  • topografische Daten zur Erstellung von 3D-Höhenkarten verwenden.
  • Drei verschiedene Auswahlmodi zur Untersuchung des Diagramms nutzen.
  • Verwenden Sie Achsenbereiche, um ausgewählte Teile des Diagramms anzuzeigen.
  • Legen Sie einen benutzerdefinierten Oberflächenverlauf fest.
  • Fügen Sie mit QCustom3DItem und QCustom3DLabel benutzerdefinierte Elemente und Beschriftungen hinzu.
  • Verwenden Sie einen benutzerdefinierten Eingabe-Handler, um Zoomen und Verschieben zu ermöglichen.
  • Heben Sie einen Bereich der Oberfläche hervor.

Informationen zur Erstellung einfacher Anwendungen finden Sie unter „Balkendiagramm“.

Einfache Oberfläche mit generierten Daten

Erstellen Sie zunächst eine neue Instanz von `QSurfaceDataProxy` und verknüpfen Sie diese mit einer neuen `QSurface3DSeries`:

m_sqrtSinProxy = new QSurfaceDataProxy();
m_sqrtSinSeries = new QSurface3DSeries(m_sqrtSinProxy);

Füllen Sie anschließend den Proxy mit einfachen Daten für Quadratwurzeln und Sinuswellen. Erstellen Sie eine neue Instanz von ` QSurfaceDataArray ` und fügen Sie ihr ` QSurfaceDataRow `-Elemente hinzu. Legen Sie das erstellte ` QSurfaceDataArray ` als Datenarray für den `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);
Höhenkartendaten mit mehreren Reihen

Erstellen Sie die Höhenkarte, indem Sie einen `QHeightMapSurfaceDataProxy` mit einem ` QImage ` instanziieren, das die Höhendaten enthält. Verwenden Sie ` QHeightMapSurfaceDataProxy::setValueRanges()`, um den Wertebereich der Karte zu definieren. Im Beispiel stammt die Karte aus einem fiktiven Gebiet zwischen 34,0° N und 40,0° N sowie 18,0° O und 24,0° O. Diese Werte werden verwendet, um die Karte auf den Achsen zu positionieren.

// 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 für diese mithilfe von Höhenkartenbildern ein Proxy und eine Reihe erstellen.

Topografische Kartendaten

Die topografischen Daten stammen vom finnischen Landesvermessungsamt. Dieses stellt ein Produkt namens „ Elevation Model 2 m “ zur Verfügung, das für dieses Beispiel geeignet ist. Die topografischen Daten stammen aus dem Levi-Gebirge. Die Genauigkeit der Daten übertrifft die Anforderungen bei weitem, weshalb sie komprimiert und in eine PNG-Datei kodiert wurden. Der Höhenwert der ursprünglichen ASCII-Daten wird mithilfe eines Multiplikators in das RGB-Format kodiert, wie Sie später in einem Codeausschnitt sehen werden. Der Multiplikator wird berechnet, indem der größte 24-Bit-Wert durch die Höhe des höchsten Punktes in Finnland geteilt wird.

QHeightMapSurfaceDataProxy konvertiert nur Ein-Byte-Werte. Um die höhere Genauigkeit der Daten des finnischen Landesvermessungsamtes zu nutzen, lesen Sie die Daten aus der PNG-Datei ein und dekodieren Sie 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;

Führen Sie anschließend 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);

Nun kann der Proxy die Daten nutzen.

Auswahl des Datensatzes

Um verschiedene Proxys zu demonstrieren, verfügt „ Surface Graph “ über drei Optionsfelder, mit denen zwischen den Reihen gewechselt werden kann.

Bei „ Sqrt & Sin “ ist die einfach generierte Reihe aktiviert. Legen Sie zunächst die Darstellungsoptionen fest, z. B. das Aktivieren des Rasters für die Oberfläche und die Auswahl des Flachschattierungsmodus. Definieren Sie als Nächstes das Format der Achsenbeschriftungen und die Wertebereiche. Aktivieren Sie die automatische Drehung der Beschriftungen, um deren Lesbarkeit bei flachen Kamerawinkeln zu verbessern. Stellen Sie abschließend sicher, dass die richtige Reihe zum Diagramm hinzugefügt wurde 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 “ sind die Höhenkartenreihen aktiviert und die anderen deaktiviert. Die automatische Anpassung des Y-Achsen-Bereichs eignet sich gut für die Höhenkartensoberfläche; stellen Sie daher sicher, dass diese Option aktiviert ist.

m_graph->axisY()->setAutoAdjustRange(true);

Bei „ Textured Topography “ ist die topografische Reihe aktiviert und die anderen deaktiviert. Aktivieren Sie für diese Reihe einen benutzerdefinierten Eingabe-Handler, um Bereiche darin hervorheben zu können:

m_graph->setActiveInputHandler(m_customInputHandler);

Informationen zum benutzerdefinierten Eingabe-Handler für diesen Datensatz finden Sie unter „Benutzerdefinierten Eingabe-Handler verwenden, um Zoomen und Schwenken zu aktivieren “.

Auswahlmodi

Die drei von Q3DSurface unterstützten Auswahlmodi können über Optionsfelder 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 die gleichzeitige Auswahl von Schnitten für alle sichtbaren Reihen im Diagramm zu unterstützen.

Achsenbereiche zur Untersuchung des Diagramms

Das Beispiel verfügt über vier Schieberegler zum Anpassen der Minimal- und Maximalwerte für die X- und Z-Achsen. Bei der Auswahl des Proxys werden diese Schieberegler so angepasst, dass sie den Achsenbereichen des aktuellen Datensatzes entsprechen:

// 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 Unterstützung für die Einstellung des X-Bereichs über die Widget-Steuerelemente im Diagramm hinzu:

void SurfaceGraphModifier::setAxisXRange(float min, float max)
{
    m_graph->axisX()->setRange(min, max);
}

Fügen Sie auf die gleiche Weise die Unterstützung für den Z-Bereich hinzu.

Benutzerdefinierte Oberflächenverläufe

Mit dem Datensatz „ Sqrt & Sin “ können benutzerdefinierte Oberflächenverläufe über zwei Schaltflächen verwendet werden. Definieren Sie den Verlauf unter „ QLinearGradient “, wo die gewünschten Farben festgelegt werden. Ändern Sie außerdem den Farbstil auf „ Q3DTheme::ColorStyleRangeGradient “, um den Verlauf 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 benutzerdefinierter Netze zur Anwendung

Fügen Sie die Mesh-Dateien für den CMake-Build zu „ CMakeLists.txt “ 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 außerdem in die QRC-Ressourcendatei ein, um sie mit Qmake zu verwenden:

<RCC>
    <qresource prefix="/">
        ...
        <file>data/refinery.obj</file>
        <file>data/oilrig.obj</file>
        <file>data/pipe.obj</file>
        ...
    </qresource>
</RCC>
Hinzufügen eines benutzerdefinierten Elements 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. Weitere visuelle Eigenschaften lassen sich ebenfalls über eine weitere Reihe von Kontrollkästchen steuern, darunter die Transparenz der beiden obersten Ebenen und eine Hervorhebung der untersten Ebene.

Erstellen Sie zunächst eine kleine „ QImage “. Füllen Sie diese mit einer einzigen Farbe, die als Farbe für das benutzerdefinierte Objekt dienen soll:

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

Legen Sie anschließend die Position des Elements in einer Variablen fest. 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 anschließend 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 das Element schließlich dem Diagramm hinzu:

m_graph->addCustomItem(item);
Hinzufügen eines benutzerdefinierten Labels zu einem Diagramm

Das Hinzufügen einer benutzerdefinierten Beschriftung erfolgt sehr ähnlich wie das Hinzufügen eines benutzerdefinierten Elements. Für die Beschriftung ist kein benutzerdefiniertes Mesh erforderlich, sondern lediglich 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: Durch das Entfernen eines benutzerdefinierten Elements aus dem Diagramm wird auch das Objekt gelöscht. Wenn Sie das Element beibehalten möchten, verwenden Sie stattdessen die Methode ` releaseCustomItem() `.

Textur für eine 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 Oberfläche verwendet werden soll. Fügen Sie ein Kontrollkästchen hinzu, um zu steuern, ob die Textur festgelegt ist oder nicht, sowie einen Handler, der auf den Status 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 mit dieser Methode eine leere Datei festlegen, wird die Textur gelöscht, und die Oberfläche verwendet die Farbverläufe oder Farben des Themas.

Verwenden Sie einen benutzerdefinierten Eingabe-Handler, um Zoomen und Verschieben zu ermöglichen

Erstellen Sie mit dem Datensatz „ Textured Topography “ einen benutzerdefinierten Eingabe-Handler, um die Auswahl im Diagramm hervorzuheben und das Verschieben des Diagramms zu ermöglichen.

Die Implementierung des Verschiebens ähnelt der in „Implementierung des Achsenziehens“ gezeigten. Der Unterschied besteht darin, dass Sie in diesem Beispiel nur die X- und Z-Achsen verfolgen und das Ziehen der Oberfläche außerhalb des Diagramms nicht zulassen. Um das Ziehen zu begrenzen, halten Sie sich an die Grenzen der Achsen und führen Sie keine Aktion aus, wenn der Bereich des Diagramms verlassen wird:

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 “ ab und passen Sie die Bereiche der X- und Y-Achse entsprechend dem Delta-Wert unter „ QWheelEvent “ an. Passen Sie die Y-Achse so an, dass das Seitenverhältnis zwischen der Y-Achse und der XZ-Ebene gleich bleibt. Dies verhindert, dass ein Diagramm entsteht, in dem die Höhe übertrieben dargestellt wird:

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 Begrenzungen für den Zoomfaktor hinzu, damit dieser nicht zu nah an die Oberfläche heranrückt oder sich zu weit davon entfernt. Wenn beispielsweise der Wert für die X-Achse unter die zulässige Grenze fällt, d. h. der Zoom zu weit entfernt ist, wird der Wert auf den minimal zulässigen Wert 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 auf der Oberfläche anzuzeigen, erstellen Sie eine Kopie der Reihe und fügen Sie dem y-Wert einen Versatz hinzu. In diesem Beispiel implementiert die Klasse „ HighlightSeries “ die Erstellung der Kopie in ihrer Methode „ handlePositionChange “.

Übergeben Sie zunächst den Zeiger auf die ursprüngliche Reihe an ` HighlightSeries ` und beginnen Sie dann, auf das Signal ` QSurface3DSeries::selectedPointChanged ` zu warten:

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 anschließend die Bereiche für den kopierten Bereich und stellen Sie sicher, dass diese innerhalb der Grenzen liegen. Füllen Sie schließlich das Datenarray der „highlight“-Reihe mit dem Bereich aus dem Datenarray der „topography“-Reihe:

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 zur „Highlight“-Serie

Da die „ HighlightSeries “ vom Typ „QSurface3DSeries“ ist, stehen alle Dekorationsmethoden zur Verfügung, über die eine Serie verfügen kann. Fügen Sie in diesem Beispiel einen Farbverlauf hinzu, um die Höhenlage hervorzuheben. Da der geeignete Farbverlaufsstil vom Bereich der Y-Achse abhängt und wir diesen Bereich beim Zoomen ändern, müssen die Farbpositionen des Farbverlaufs an die Änderungen des Bereichs angepasst werden. Definieren Sie dazu 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 Anpassung des Farbverlaufs erfolgt in der Methode „ handleGradientChange “, verbinden Sie diese daher so, dass sie auf Änderungen an 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 Farbpositionen des Farbverlaufs:

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

Beispielinhalt

Beispielprojekt @ code.qt.io

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.