このページでは

グラフギャラリー

棒グラフ、散布図、曲面グラフのギャラリー。

「グラフギャラリー」では、これら3種類のグラフすべてと、それぞれの特別な機能の一部を紹介しています。各グラフには、アプリケーション内に専用のタブが用意されています。

棒グラフ、散布図、表面図のタブを備えた、テクスチャ付き地形3D表面グラフ

例の動作

サンプルを実行するには、以下の手順に従ってください:

棒グラフ

「Bar Graph 」タブで、Q3DBars を使用して 3D 棒グラフを作成し、ウィジェットを組み合わせて棒グラフのさまざまな特性を調整します。この例では、以下の手順を説明します:

  • Q3DBarsといくつかのウィジェットを使用したアプリケーションの作成
  • QBar3DSeries と QBarDataProxy を使用してグラフにデータを設定する
  • ウィジェットコントロールを使用して、グラフやシリーズのプロパティを調整する
  • 軸のラベルをクリックして行または列を選択する
  • Q3DBars で使用するカスタムプロキシの作成

グラフとのやり取りに関する詳細については、こちらのページをご覧ください。

アプリケーションの作成

まず、bargraph.cpp 内で、Q3DBarsをインスタンス化します:

m_barsGraph = new Q3DBars();

次に、ウィジェットと、水平および垂直のレイアウトを作成します。

グラフは、QWidget::createWindowContainer() を使用してウィンドウコンテナに埋め込まれます。これは、すべてのデータ可視化グラフクラス(Q3DBars、Q3DScatter、Q3DSurface)がQWindow を継承しているため、必須の処理です。QWindow を継承するクラスをウィジェットとして使用するには、この方法しかありません。

グラフと垂直レイアウトを水平レイアウトに追加します:

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

次に、データの追加やグラフとのその他のやり取りを処理するための別のクラスを作成します:

auto *modifier = new GraphModifier(m_barsGraph, this);
棒グラフの設定

GraphModifier クラスのコンストラクタでグラフを設定します:

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

まず、軸と系列をメンバ変数として定義し、変更を容易に行えるようにします:

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)

次に、グラフの表示設定を行います:

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

軸を設定し、グラフのアクティブな軸として指定します:

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

setLabelAutoRotation() を使用して、軸ラベルにわずかな自動回転角度を設定し、カメラに対してわずかに向きを調整します。これにより、極端なカメラ角度でも軸ラベルの可読性が向上します。

次に、シリーズの視覚的プロパティを初期化します。なお、2番目のシリーズは初期状態では表示されません:

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

シリーズをグラフに追加します:

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

最後に、UI上のカメラ角度変更ボタンがさまざまなカメラ角度を切り替える際に使用しているのと同じメソッドを呼び出して、カメラ角度を設定します:

changePresetCamera();

カメラは、グラフのシーンオブジェクトを介して制御されます:

static int preset = Q3DCamera::CameraPresetFront;

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

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

シーンおよびカメラの使用に関する詳細については、Q3DScene および Q3DCamera を参照してください。

グラフへのデータの追加

コンストラクタの最後に、データを設定するメソッドを呼び出します:

resetTemperatureData();

このメソッドは、2つのシリーズのプロキシにデータを追加します:

// 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);
ウィジェットを使用したグラフの制御

続いて、bargraph.cpp にいくつかのウィジェットを追加します。スライダーを追加します:

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

マウスやタッチ操作だけでなく、スライダーを使ってグラフを回転させましょう。これを垂直レイアウトに追加します:

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

次に、それをGraphModifier 内のメソッドに接続します:

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

GraphModifier に、シグナル接続用のスロットを作成します。カメラはシーンオブジェクトを介して制御されます。今回は、あらかじめ設定されたカメラ角度を指定するのではなく、中心点を中心とする軌道上の実際のカメラ位置を指定します:

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

これで、スライダーを使ってグラフを回転させることができます。

制御対象となるウィジェットを垂直レイアウトにさらに追加します:

  • グラフの回転
  • ラベルのスタイル
  • カメラのプリセット
  • 背景の表示/非表示
  • グリッドの表示/非表示
  • 棒グラフのシェーディングの滑らかさ
  • 2番目の棒グラフシリーズの表示
  • 値軸の方向
  • 軸タイトルの表示と回転
  • 表示するデータ範囲
  • 棒グラフのスタイル
  • 選択モード
  • テーマ
  • 影の質
  • フォント
  • フォントサイズ
  • 軸ラベルの回転
  • データモード

Custom Proxy Data データモードでは、一部のウィジェットコントロールは意図的に無効になっています。

軸ラベルをクリックして行または列を選択する

軸ラベルによる選択は、棒グラフのデフォルトの機能です。例として、次のように軸ラベルをクリックして行を選択できます:

  1. 選択モードをSelectionRow
  2. 年ラベルをクリックします
  3. クリックした年の行が選択されます

この方法は、`SelectionSlice ` および `SelectionItem ` フラグでも同様に機能しますが、その場合は `SelectionRow ` または `SelectionColumn ` のいずれかが設定されている必要があります。

選択範囲へのズーム

カメラのターゲットを調整する例として、ボタンを押すことで選択範囲にズームするアニメーションを実装します。アニメーションの初期化はコンストラクタ内で行われます:

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

関数 `GraphModifier::zoomToSelectedBar() ` にはズーム機能が実装されています。QPropertyAnimation はm_animationCameraTarget がQ3DCamera::target プロパティをターゲットとしており、このプロパティは(-1, 1)の範囲に正規化された値を取ります。

選択されたバーが軸に対してどこにあるかを特定し、それを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));

次に、アニメーションの終了時にカメラが常にグラフの中心付近を向くようにカメラを回転させます:

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;
データ用カスタムプロキシ

Custom Proxy Data のデータモードをオンにすると、カスタムデータセットとそれに対応するプロキシが使用されます。

シンプルで柔軟なデータセット `VariantDataSet` を定義します。ここでは、各データ項目がバリアントリストとなっています。 各項目は複数の値を持つことができ、リスト内のインデックスによって識別されます。この例では、データセットには月ごとの降水量データが格納されており、インデックス 0 の値は年、インデックス 1 の値は月、インデックス 2 の値はその月の降水量となっています。

このカスタムプロキシは、Qt Data Visualization が提供する itemmodel ベースのプロキシと似ており、データを解釈するためにマッピングが必要です。

VariantDataSet

データ項目をQVariantList オブジェクトとして定義します。データセットをクリアする機能と、セットに含まれるデータへの参照を取得する機能を追加します。また、データが追加されたときやセットがクリアされたときに発火するシグナルを追加します:

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

VariantBarDataProxy をQBarDataProxyからサブクラス化し、データセットおよびマッピングに対するゲッターとセッターからなるシンプルなAPIを提供します:

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

このプロキシは、データセットとマッピングの変更を監視し、変更が検出された場合はデータセットを再解決します。変更があるたびにデータセット全体の再解決が行われるため、特に効率的な実装とは言えませんが、この例においては問題にはなりません。

resolveDataSet() メソッド内で、マッピングに基づいてVariantデータの値を行と列にソートします。これは、QItemModelBarDataProxyがマッピングを処理する方法と非常に似ていますが、ここではアイテムモデルのロールではなくリストのインデックスを使用する点が異なります。値のソートが完了したら、それらからQBarDataArray を生成し、親クラスのresetArray() メソッドを呼び出します:

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

VariantDataSet のデータ項目のインデックスと、QBarDataArray の行、列、値との間のマッピング情報をVariantBarDataMapping に格納します。これには、解決されたデータに含める行と列のリストが含まれます:

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

VariantBarDataMapping オブジェクトを使用する主な方法は、コンストラクタでマッピングを指定することですが、remap() メソッドを使用して、後から個別に、またはまとめて設定することも可能です。マッピングが変更された場合はシグナルを発信します。このオブジェクトは、QItemModelBarDataProxy のマッピング機能を簡略化したもので、アイテムモデルではなくバリアントリストで動作するように適応されています。

RainfallData

RainfallData クラス内で、カスタムプロキシを使用した QBar3DSeries のセットアップを処理します:

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

addDataSet() メソッド内でバリアントデータセットにデータを格納します:

voidRainfallData::addDataSet()
{
    // 新しいバリアントデータセットとデータアイテムリストを作成
    m_dataSet=  newVariantDataSet;
    auto *itemList = newVariantDataItemList;

    // データファイルからデータを読み込み、データアイテムリストに格納
    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('#'))// コメントを無視
                continue;
            const autostrList=QStringView{line}.split(',',Qt::SkipEmptyParts);
            // 各行には、年、月、降水量の3つのデータ項目が含まれる
            if(strList.size()< 3) {
                qWarning() << "Invalid row read from data:" << line;
               continue;
            }
            // 年と月を文字列として、降水量をdouble型として
            // VariantDataItemに格納し、そのアイテムをアイテムリストに追加する。
            auto *newItem = newVariantDataItem;
            for(inti= 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();
    }
    ...

データセットをカスタムプロキシに追加し、マッピングを設定します:

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

最後に、表示用に作成された系列を取得する関数を追加します:

QBar3DSeries *customSeries() { return m_series; }

散布図

「Scatter Graph 」タブで、Q3DScatter を使用して 3D 散布図を作成します。この例では、以下の手順を示しています:

  • Q3DScatterグラフの設定
  • QScatterDataProxy を使用してグラフにデータを設定する
  • Q3DInputHandlerを拡張してカスタム入力ハンドラを作成する

基本的なアプリケーションの作成方法については、「棒グラフ」を参照してください。

散布図の設定

まず、ScatterDataModifier のコンストラクタ内で、グラフの視覚的な設定を行います:

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

これらはどれも必須ではありませんが、グラフのデフォルト設定を上書きするために使用されます。上記のブロックをコメントアウトすると、プリセットのデフォルト設定での表示を確認できます。

次に、QScatterDataProxy とそれに関連する QScatter3DSeries を作成します。シリーズに対してカスタムラベル形式とメッシュスムージングを設定し、グラフに追加します:

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);
散布図データの追加

ScatterDataModifier のコンストラクタで行う最後の作業は、グラフにデータを追加することです:

addData();

実際のデータの追加は、addData() メソッド内で行われます。まず、軸を設定します:

m_graph->axisX()->setTitle("X");
m_graph->axisY()->setTitle("Y");
m_graph->axisZ()->setTitle("Z");

これは、ScatterDataModifier のコンストラクタ内でも行うことができます。ここで設定を行うことで、コンストラクタをシンプルに保ち、軸の設定をデータに近い場所に配置することができます。

次に、データ配列を作成し、データを格納します:

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

最後に、プロキシに対して、渡したデータの使用を開始するよう指示します:

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

これで、グラフにデータが反映され、使用可能な状態になりました。グラフを操作するためのウィジェットの追加方法については、「ウィジェットを使用したグラフの操作」を参照してください。

デフォルトの入力処理の置き換え

コンストラクタ内で、m_inputHandler を散布図インスタンスへのポインタで初期化します:

m_inputHandler(new AxesInputHandler(scatter))

Q3DScatterのアクティブな入力ハンドラを、カスタム動作を実装するAxesInputHandler に設定することで、デフォルトの入力処理メカニズムを置き換えます:

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

入力ハンドラはグラフの軸にアクセスする必要があるため、それらを引数として渡します:

// Give our axes to the input handler
m_inputHandler->setAxes(m_graph->axisX(), m_graph->axisZ(), m_graph->axisY());
マウスイベント処理の拡張

まず、デフォルトの入力処理のすべての機能を維持し、その上にカスタム機能を追加するために、カスタム入力ハンドラを QAbstract3DInputHandler ではなく Q3DInputHandler から継承します:

class AxesInputHandler : public Q3DInputHandler

いくつかのマウスイベントを再実装することで、デフォルト機能の拡張を開始します。まず、mousePressEvent を拡張します。これに左マウスボタン用のm_mousePressed フラグを追加し、それ以外のデフォルト機能はそのまま維持します:

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

次に、mouseReleaseEvent を修正して、フラグをクリアし、内部状態をリセットするようにします:

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

次に、mouseMoveEvent を修正します。m_mousePressed フラグがtrue であり、内部状態がStateNormal 以外であるかどうかを確認します。その場合は、マウス移動距離の計算用の入力位置を設定し、軸ドラッグ関数を呼び出します(詳細については「軸ドラッグの実装」を参照してください):

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);
    }
}
軸のドラッグ機能の実装

まず、グラフからの選択シグナルのリスニングを開始します。これをコンストラクタ内で行い、handleElementSelected メソッドに接続します:

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

handleElementSelected 内で、選択の種類を確認し、それに基づいて内部状態を設定します:

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

実際のドラッグ処理は `handleAxisDragging ` メソッドで実装されており、必要な条件が満たされた場合に `mouseMoveEvent` からこのメソッドが呼び出されます:

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

handleAxisDragging では、まずアクティブなカメラからシーンの向きを取得します:

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

次に、その向きに基づいて、マウスの移動方向に対する補正値を計算します:

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

その後、マウスの移動量を計算し、カメラのY軸周りの回転に基づいてそれを補正します:

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

次に、移動距離を正しい軸に適用します:

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

最後に、ドラッグ速度を設定する関数を追加します:

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

これは、マウスの移動距離が画面座標での絶対値であるため、軸の範囲に合わせて調整する必要があるからです。値が大きいほど、ドラッグの速度は遅くなります。 なお、この例では、ドラッグ速度を決定する際にシーンのズームレベルは考慮されていないため、ズームレベルを変更すると範囲の調整に変化が生じることがわかります。

また、軸の範囲やカメラのズームレベルに基づいて、この修正値を自動的に調整することも可能です。

サーフェスグラフ

「Surface Graph 」タブで、Q3DSurface を使用して 3D サーフェスグラフを作成します。この例では、以下の手順を説明します:

  • 基本的な QSurfaceDataProxy を設定し、そのデータを設定する方法。
  • QHeightMapSurfaceDataProxy を使用して 3D 高さマップを表示する方法。
  • 地形データを使用して3D高さマップを作成する。
  • グラフを分析するために、3つの異なる選択モードを使用する。
  • 軸の範囲を使用して、グラフの選択した部分を表示します。
  • カスタムサーフェスのグラデーションを設定します。
  • QCustom3DItem および QCustom3DLabel を使用して、カスタム項目やラベルを追加します。
  • カスタム入力ハンドラを使用して、ズームやパンを有効にします。
  • サーフェスの特定の領域を強調表示します。

基本的なアプリケーションの作成方法については、「棒グラフ」を参照してください。

生成データを使用したシンプルなサーフェス

まず、新しい QSurfaceDataProxy をインスタンス化し、それを新しい QSurface3DSeries に紐付けます:

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

次に、プロキシに単純な平方根と正弦波のデータを格納します。新しいQSurfaceDataArray インスタンスを作成し、そこにQSurfaceDataRow 要素を追加します。resetArray() を呼び出して、作成したQSurfaceDataArray をQSurfaceDataProxyのデータ配列として設定します。

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);
マルチシリーズの高さマップデータ

高さデータを含むQImage を使用して、QHeightMapSurfaceDataProxyをインスタンス化することで、高さマップを作成します。QHeightMapSurfaceDataProxy::setValueRanges() を使用して、マップの値の範囲を定義します。 この例では、マップは北緯34.0°~40.0°、東経18.0°~24.0°という架空の位置を基にしています。これらの値は、マップを座標軸上に配置するために使用されます。

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

他の地表レイヤーも同様の方法で追加します。高度マップ画像を使用して、それらのプロキシと系列を作成します。

地形図データ

地形データはフィンランド国土地測量局から入手しています。同局が提供する「Elevation Model 2 m 」という製品が、この例に適しています。地形データはレヴィ山(Levi fell)のものです。データの精度は必要以上に高いため、圧縮してPNGファイルにエンコードしています。 元のASCIIデータの高さ値は、乗数を使用してRGB形式にエンコードされています。この乗数の計算方法については、後述するコード抜粋でご確認いただけます。乗数は、フィンランドで最も高い地点に対応する最大24ビット値を割り算して算出されます。

QHeightMapSurfaceDataProxyは1バイトの値のみを変換します。フィンランド国土地測量のデータが持つより高い精度を活用するには、PNGファイルからデータを読み込み、QSurface3DSeriesにデコードする必要があります。

まず、エンコード用乗数を定義します:

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

次に、実際のデコードを実行します:

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

これで、プロキシがデータを利用できるようになりました。

データセットの選択

さまざまなプロキシを実演するために、Surface Graph には、系列を切り替えるための3つのラジオボタンが用意されています。

Sqrt & Sin では、自動的に生成されたシンプルなシリーズが有効になっています。まず、サーフェスのグリッド表示を有効にしたり、フラットシェーディングモードを選択したりするなど、装飾的な設定を行います。 次に、軸ラベルの形式と値の範囲を定義します。カメラアングルが低い場合でもラベルの可読性を高めるため、ラベルの自動回転を設定してください。最後に、正しい系列がグラフに追加され、他の系列は追加されていないことを確認してください:

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

「Multiseries Height Map 」では、高さマップのシリーズが有効になり、他のシリーズは無効になります。高さマップのサーフェスにはY軸範囲の自動調整が有効ですので、必ず設定してください。

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

Textured Topography では、地形シリーズが有効になり、他のシリーズは無効になります。このシリーズ上で領域をハイライト表示できるように、カスタム入力ハンドラを有効にしてください:

m_graph->setActiveInputHandler(m_customInputHandler);

このデータセットのカスタム入力ハンドラーに関する詳細については、「カスタム入力ハンドラーを使用してズームおよびパンを有効にする」を参照してください。

選択モード

Q3DSurfaceがサポートする3つの選択モードは、ラジオボタンを使用して設定できます。選択したモードを有効化または無効化するには、以下のインラインメソッドを追加してください:

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

グラフ内のすべての表示されている系列に対して同時にスライス選択を行えるようにするため、行および列の選択モード用に `QAbstract3DGraph::SelectionSlice ` および `QAbstract3DGraph::SelectionMultiSeries ` フラグを追加します。

グラフを分析するための軸範囲

この例には、X軸とZ軸の最小値および最大値を調整するための4つのスライダーコントロールがあります。プロキシを選択すると、これらのスライダーは現在のデータセットの軸範囲に合わせて調整されます:

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

ウィジェットコントロールからグラフへのX範囲の設定機能を追加します:

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

同様の方法で、Z軸の範囲設定機能も追加します。

カスタムサーフェスグラデーション

Sqrt & Sin データセットでは、2つのプッシュボタンを使用してカスタムの表面グラデーションを利用できます。QLinearGradient でグラデーションを定義し、希望の色を設定します。また、グラデーションを使用するには、カラースタイルをQ3DTheme::ColorStyleRangeGradient に変更してください。

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);
アプリケーションへのカスタムメッシュの追加

CMakeビルド用に、メッシュファイルをCMakeLists.txt に追加します:

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

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

また、qmakeで使用するために、qrcリソースファイルにもそれらを追加します:

<RCC>
    <qresource prefix="/">
        ...
        <file>data/refinery.obj</file>
        <file>data/oilrig.obj</file>
        <file>data/pipe.obj</file>
        ...
    </qresource>
</RCC>
グラフへのカスタム項目の追加

Multiseries Height Map データセットを使用すると、カスタムアイテムがグラフに挿入され、チェックボックスを使用して表示/非表示を切り替えることができます。また、別のチェックボックス群を使用して、上層2つのレイヤーの透過性や下層レイヤーのハイライトなど、その他の視覚的特性も制御できます。

まず、小さなQImage を作成します。カスタムオブジェクトの色として使用する単一の色で塗りつぶします:

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

次に、変数にアイテムの位置を指定します。この位置情報を使用して、グラフから正しいアイテムを取り除くことができます:

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

次に、すべてのパラメータを指定して新しい `QCustom3DItem` を作成します:

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

最後に、そのアイテムをグラフに追加します:

m_graph->addCustomItem(item);
グラフへのカスタムラベルの追加

カスタムラベルの追加は、カスタムアイテムの追加と非常によく似ています。ラベルの場合、カスタムメッシュは必要なく、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);
グラフからカスタムアイテムを削除する

グラフから特定のアイテムを削除するには、そのアイテムの位置を指定して `removeCustomItemAt() ` を呼び出します:

m_graph->removeCustomItemAt(positionOne);

注: グラフからカスタムアイテムを削除すると 、オブジェクトも削除されます。アイテムを保持したい場合は、代わりにreleaseCustomItem() メソッドを使用してください。

サーフェス・シリーズへのテクスチャの適用

Textured Topography データセットを使用して、地形高さマップで使用するマップテクスチャを作成します。

QSurface3DSeries::setTextureFile() を使用して、サーフェスにテクスチャとして使用する画像を設定します。テクスチャの設定有無を制御するチェックボックスと、チェックボックスの状態に反応するハンドラを追加します:

void SurfaceGraphModifier::toggleSurfaceTexture(bool enable)
{
    if (enable)
        m_topography->setTextureFile(":/data/maptexture.jpg");
    else
        m_topography->setTextureFile("");
}

この例の画像はJPGファイルから読み込まれます。このメソッドで空のファイルを設定するとテクスチャがクリアされ、サーフェスはテーマのグラデーションまたは色を使用します。

カスタム入力ハンドラを使用してズームとパンを有効にする

Textured Topography データセットを使用して、グラフ上の選択範囲を強調表示し、グラフのパン操作を可能にするカスタム入力ハンドラを作成します。

パン機能の実装は、「軸のドラッグの実装」で示したものと似ています。違いは、この例ではX軸とZ軸のみに追従し、グラフの外側へのサーフェスのドラッグを許可しない点です。ドラッグを制限するには、軸の限界に従い、グラフの外側に出た場合は何も行わないようにします:

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;

ズームについては、wheelEvent をキャッチし、QWheelEvent のデルタ値に応じてX軸とY軸の範囲を調整します。Y軸とXZ平面の縦横比が一定に保たれるようにY軸を調整します。これにより、グラフの高さが誇張されるのを防ぎます:

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

次に、ズームレベルに制限を設けて、表面に近づきすぎたり遠ざかりすぎたりしないようにします。 たとえば、X軸の値が許容限界を下回った場合、つまりズームが遠くなりすぎた場合、その値は許容される最小値に設定されます。範囲が範囲の最小値を下回ろうとする場合は、範囲が限界内に収まるよう、軸の両端が調整されます:

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;
}
曲面の領域を強調表示する

サーフェス上にハイライトを表示するには、シリーズのコピーを作成し、y値にオフセットを加えます。この例では、クラスHighlightSeries がhandlePositionChange メソッド内でコピーの作成を実装しています。

まず、HighlightSeries に元の系列へのポインタを渡してから、QSurface3DSeries::selectedPointChanged シグナルのリスニングを開始します:

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

シグナルがトリガーされたら、位置が有効かどうかを確認します。次に、コピーされる領域の範囲を計算し、それらが境界内に収まっていることを確認します。最後に、ハイライト・シリーズのデータ配列に、トポグラフィ・シリーズのデータ配列からの範囲を格納します:

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);
}
「ハイライト」シリーズへのグラデーション

HighlightSeries はQSurface3DSeriesであるため、シリーズが持つすべての装飾メソッドを利用できます。 この例では、標高を強調するためにグラデーションを追加します。適切なグラデーションのスタイルは Y 軸の範囲によって異なり、ズーム操作によって範囲が変化するため、範囲の変化に合わせてグラデーションの色位置を調整する必要があります。これを行うには、グラデーションの色位置に比例値を設定します:

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;

グラデーションの変更はhandleGradientChange メソッド内で行われるため、Y軸の変化に反応するように接続します:

QObject::connect(m_graph->axisY(), &QValue3DAxis::maxChanged,
                 m_highlight, &HighlightSeries::handleGradientChange);

Y軸の最大値が変更された際には、新しいグラデーションの色位置を計算します:

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

例の内容

サンプルプロジェクト @ 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.