このページでは

グラフギャラリー

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

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

グラデーション着色とカスタマイズ機能を備えた3D表面プロット

例の動作

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

棒グラフ

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

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

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

アプリケーションの作成
  1. bargraph.cpp 内で、QQuickWidget およびQ3DBarsWidgetItem をインスタンス化し、QQuickWidget インスタンスをQ3DBarsWidgetItem のウィジェットとして設定します:
    m_quickWidget = new QQuickWidget();
    m_barGraph = new Q3DBarsWidgetItem(this);
    m_barGraph->setWidget(m_quickWidget);
  2. コンテナウィジェット、および水平レイアウトと垂直レイアウトを作成します。グラフと垂直レイアウトを水平レイアウトに追加します:
    m_container = new QWidget();
    auto *hLayout = new QHBoxLayout(m_container);
    QSize screenSize = m_quickWidget->screen()->size();
    m_quickWidget->setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 1.75));
    m_quickWidget->setMaximumSize(screenSize);
    m_quickWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    m_quickWidget->setFocusPolicy(Qt::StrongFocus);
    hLayout->addWidget(m_quickWidget, 1);
    
    auto *vLayout = new QVBoxLayout();
    hLayout->addLayout(vLayout);
  3. データの追加やグラフとのその他のやり取りを処理するための別のクラスを作成します:
    m_modifier = new GraphModifier(m_barGraph, this);
棒グラフの設定
  1. GraphModifier クラスのコンストラクタ内でグラフを設定します:
    GraphModifier::GraphModifier(Q3DBarsWidgetItem *bargraph, QObject *parent)
        : QObject(parent)
        , m_graph(bargraph)
  2. 変更に対応できるよう、軸と系列をメンバ変数として作成します:
    , m_temperatureAxis(new QValue3DAxis)
    , m_yearAxis(new QCategory3DAxis)
    , m_monthAxis(new QCategory3DAxis)
    , m_primarySeries(new QBar3DSeries)
    , m_secondarySeries(new QBar3DSeries)
  3. グラフの表示設定を行います:
    m_graph->setShadowQuality(QtGraphs3D::ShadowQuality::SoftMedium);
    m_graph->setMultiSeriesUniform(true);
    // These are set through the active theme
    m_graph->activeTheme()->setPlotAreaBackgroundVisible(false);
    m_graph->activeTheme()->setLabelFont(QFont("Times New Roman", m_fontSize));
    m_graph->activeTheme()->setLabelBackgroundVisible(true);
  4. 軸を設定し、グラフのアクティブな軸として設定します:
    m_temperatureAxis->setTitle("Average temperature");
    m_temperatureAxis->setSegmentCount(m_segments);
    m_temperatureAxis->setSubSegmentCount(m_subSegments);
    m_temperatureAxis->setRange(m_minval, m_maxval);
    m_temperatureAxis->setLabelFormat(u"%.1f "_s + m_celsiusString);
    m_temperatureAxis->setLabelAutoAngle(30.0f);
    m_temperatureAxis->setTitleVisible(true);
    
    m_yearAxis->setTitle("Year");
    m_yearAxis->setLabelAutoAngle(30.0f);
    m_yearAxis->setTitleVisible(true);
    
    m_monthAxis->setTitle("Month");
    m_monthAxis->setLabelAutoAngle(30.0f);
    m_monthAxis->setTitleVisible(true);
    
    m_graph->setValueAxis(m_temperatureAxis);
    m_graph->setRowAxis(m_yearAxis);
    m_graph->setColumnAxis(m_monthAxis);
  5. 軸ラベルにわずかな自動回転角度を設定します:
    m_yearAxis->setLabelAutoAngle(30.0f);

    これは、軸ラベルをカメラ方向にわずかに向けるためであり、これにより極端なカメラアングルでも軸ラベルの視認性が向上します。

  6. シリーズの視覚的プロパティを初期化します。なお、2番目のシリーズは初期状態では表示されませんのでご注意ください:
    m_primarySeries->setItemLabelFormat(u"Oulu - @colLabel @rowLabel: @valueLabel"_s);
    m_primarySeries->setMesh(QAbstract3DSeries::Mesh::BevelBar);
    m_primarySeries->setMeshSmooth(false);
    
    m_secondarySeries->setItemLabelFormat(u"Helsinki - @colLabel @rowLabel: @valueLabel"_s);
    m_secondarySeries->setMesh(QAbstract3DSeries::Mesh::BevelBar);
    m_secondarySeries->setMeshSmooth(false);
    m_secondarySeries->setVisible(false);
  7. グラフに系列を追加します:
    m_graph->addSeries(m_primarySeries);
    m_graph->addSeries(m_secondarySeries);
  8. UI上の「カメラアングル変更」ボタンが、さまざまなカメラアングルを切り替える際に使用しているのと同じメソッドを呼び出して、カメラアングルを設定します:
    changePresetCamera();
  9. 新しいカメラプリセットがグラフに設定されます:
    static int preset = int(QtGraphs3D::CameraPreset::Front);
    
    m_graph->setCameraPreset((QtGraphs3D::CameraPreset) preset);
    
    if (++preset > int(QtGraphs3D::CameraPreset::DirectlyBelow))
        preset = int(QtGraphs3D::CameraPreset::FrontLow);
グラフへのデータの追加

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

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
...
QBarDataArray dataSet;
QBarDataArray dataSet2;

dataSet.reserve(m_years.size());
for (qsizetype year = 0; year < m_years.size(); ++year) {
    // Create a data row
    QBarDataRow dataRow(m_months.size());
    QBarDataRow dataRow2(m_months.size());
    for (qsizetype month = 0; month < m_months.size(); ++month) {
        // Add data to the row
        dataRow[month].setValue(tempOulu[year][month]);
        dataRow2[month].setValue(tempHelsinki[year][month]);
    }
    // Add the row to the set
    dataSet.append(dataRow);
    dataSet2.append(dataRow2);
}

// Add data to the data proxy (the data proxy assumes ownership of it)
m_primarySeries->dataProxy()->resetArray(dataSet, m_years, m_months);
m_secondarySeries->dataProxy()->resetArray(dataSet2, m_years, m_months);
ウィジェットを使用したグラフの制御

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

  1. スライダーを追加します:
    auto *rotationSliderX = new QSlider(Qt::Horizontal, m_container);
    rotationSliderX->setTickInterval(30);
    rotationSliderX->setTickPosition(QSlider::TicksBelow);
    rotationSliderX->setMinimum(-180);
    rotationSliderX->setValue(0);
    rotationSliderX->setMaximum(180);
  2. マウスやタッチ操作だけでなく、スライダーを使ってグラフを回転させましょう。これを垂直レイアウトに追加します:
    vLayout->addWidget(new QLabel(u"Rotate horizontally"_s));
    vLayout->addWidget(rotationSliderX, 0, Qt::AlignTop);
  3. GraphModifier 内のメソッドに接続します:
    QObject::connect(rotationSliderX, &QSlider::valueChanged, m_modifier, &GraphModifier::rotateX);
  4. GraphModifier 内に、シグナル接続用のスロットを作成します。あらかじめ設定されたカメラ角度を指定するのではなく、中心点を中心とする軌道上の実際のカメラ位置を指定します:
    void GraphModifier::rotateX(int angle)
    {
        m_xRotation = angle;
        m_graph->setCameraPosition(m_xRotation, m_yRotation);
    }

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

制御するために、垂直レイアウトにウィジェットを追加します:

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

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

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

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

  1. 選択モードを次のように変更しますRow
  2. 年ラベルをクリックする
  3. クリックした年の行が選択されます

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

選択箇所へのズーム

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

m_defaultAngleX = m_graph->cameraXRotation();
m_defaultAngleY = m_graph->cameraYRotation();
m_defaultZoom = m_graph->cameraZoomLevel();
m_defaultTarget = m_graph->cameraTargetPosition();

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

m_animationCameraX.setPropertyName("cameraXRotation");
m_animationCameraY.setPropertyName("cameraYRotation");
m_animationCameraZoom.setPropertyName("cameraZoomLevel");
m_animationCameraTarget.setPropertyName("cameraTargetPosition");

int duration = 1700;
m_animationCameraX.setDuration(duration);
m_animationCameraY.setDuration(duration);
m_animationCameraZoom.setDuration(duration);
m_animationCameraTarget.setDuration(duration);

// The zoom always first zooms out above the graph and then zooms in
qreal zoomOutFraction = 0.3;
m_animationCameraX.setKeyValueAt(zoomOutFraction, QVariant::fromValue(0.0f));
m_animationCameraY.setKeyValueAt(zoomOutFraction, QVariant::fromValue(90.0f));
m_animationCameraZoom.setKeyValueAt(zoomOutFraction, QVariant::fromValue(50.0f));
m_animationCameraTarget.setKeyValueAt(zoomOutFraction,
                                      QVariant::fromValue(QVector3D(0.0f, 0.0f, 0.0f)));

関数 `GraphModifier::zoomToSelectedBar() ` にはズーム機能が実装されています。QPropertyAnimation `m_animationCameraTarget ` は `cameraTargetPosition ` プロパティをターゲットとしており、このプロパティは (-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;
データ用モデルプロキシ

Model Proxy Data のデータモードをオンにすると、この例のグラフでは、アイテムモデルに基づくデータセットと、それに対応するプロキシが使用されます。

RainfallData

RainfallData クラスは、QItemModelBarDataProxy を、系列や軸とともに設定します。

  1. std::array<double, 12> を使用して、月をインデックスとする 1 年間の値を格納します。静的ヘルパー関数 `readData() ` はデータを読み取り、年をインデックスとする `QList ` を返します。
    usingYearlyData=std::array<double, 12>;
    usingModelData=QList<YearlyData>;
    
    staticModelData readData(constQString&fileName, int *firstYear)
    {
        ModelData result;
    
        *firstYear =-1;
    
        // データファイルからデータを読み込み、データ項目リストに格納する
        QFile dataFile(fileName);
        if(!dataFile.open(QIODevice::ReadOnly|QIODevice::Text)) {
            qWarning() << "Unable to open data file:" << dataFile.fileName() << dataFile.errorString();
            resultを返す;
        }
    
        QTextStream stream(&dataFile);
        intlastYear=-1;
        while(!stream.atEnd()) {
            QString line=stream.readLine();
            if(line.startsWith(u'#'))// コメントを無視
                continue;
            const autostrList=QStringView{line}.split(',',Qt::SkipEmptyParts);
            // 各行には、年、月、降水量という3つのデータ項目が含まれる
            if(strList.size()< 3) {
                qWarning() << "Invalid row read from data:" << line;
               continue;
            }
            // 年と月をint型で、降水量をdouble型で格納
            boolyearOk{};
           boolmonthOk{};
            boolvalueOk{};
            const intyear=strList.at(0).trimmed().toInt(&yearOk);
            const intmonth=strList.at(1).trimmed().toInt(&monthOk);
           const doublevalue=strList.at(2).trimmed().toDouble(&valueOk);
            if(!yearOk|| !monthOk||month< 1||month> 12|| !valueOk) {
                qWarning() << "Invalid row values:" << line;
               continue;
            }
            if(year!=lastYear) {
                if(lastYear==-1) {
                    *firstYear =year;
                }else if(year!=lastYear+ 1) {
                    qWarning() << "Non-consecutive years" << year << lastYear;
                   return{};
                }
                lastYear=year;
                result.emplace_back(YearlyData{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
            }
            result.back()[month- 1] =value;
        }
        returnresult;
    }
        ...
  2. QList は、それぞれ年と月をインデックスとする2次元配列を表します。これをもとに、QRangeModel にデータを格納し、RainfallData クラス内のQItemModelBarDataProxy()に渡します:
    int firstYear{};
    auto data = readData(":/data/raindata.txt"_L1, &firstYear);
    Q_ASSERT(!data.isEmpty());
    updateYearsList(firstYear, firstYear + int(data.size()) - 1);
    auto *model = new QRangeModel(data, this);
    
    m_proxy = new QItemModelBarDataProxy(model);
    m_proxy->setUseModelCategories(true);
    m_series = new QBar3DSeries(m_proxy);
  3. 最後に、表示用に作成されたシリーズを取得する関数を追加します:
    QBar3DSeries *customSeries() { return m_series; }
  • 散布図

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

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

    散布図の設定
    1. ScatterDataModifier のコンストラクタ内で、グラフの視覚的な設定を行います:
      m_graph->setShadowQuality(QtGraphs3D::ShadowQuality::SoftHigh);
      m_graph->setCameraPreset(QtGraphs3D::CameraPreset::Front);
      m_graph->setCameraZoomLevel(80.f);
      // These are set through active theme
      m_graph->activeTheme()->setTheme(QGraphsTheme::Theme::MixSeries);
      m_graph->activeTheme()->setColorScheme(QGraphsTheme::ColorScheme::Dark);

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

    2. 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);
    散布図データの追加
    1. ScatterDataModifier のコンストラクタ内で、グラフにデータを追加します:
      addData();
    2. 実際のデータの追加は、addData() メソッド内で行われます。まず、軸を設定します:
      m_graph->axisX()->setTitle("X");
      m_graph->axisY()->setTitle("Y");
      m_graph->axisZ()->setTitle("Z");

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

    3. データ配列を作成し、データを格納します:
      QScatterDataArray dataArray;
      dataArray.reserve(m_itemCount);
          ...
      const float limit = qSqrt(m_itemCount) / 2.0f;
      for (int i = -limit; i < limit; ++i) {
          for (int j = -limit; j < limit; ++j) {
              const float x = float(i) + 0.5f;
              const float y = qCos(qDegreesToRadians(float(i * j) / m_curveDivider));
              const float z = float(j) + 0.5f;
              dataArray.append(QScatterDataItem(x, y, z));
          }
      }
    4. 最後に、プロキシに対して、渡したデータの使用を開始するよう指示します:
      m_graph->seriesList().at(0)->dataProxy()->resetArray(dataArray);

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

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

    デフォルトの入力処理メカニズムを置き換えるには、カスタム動作を実装するQ3DScatterWidgetItem の新しい入力ハンドラを設定します。

    connect(m_graph,
            &Q3DGraphsWidgetItem::selectedElementChanged,
            this,
            &ScatterDataModifier::handleElementSelected);
    connect(m_graph, &Q3DGraphsWidgetItem::dragged, this, &ScatterDataModifier::handleAxisDragging);
    m_graph->setDragButton(Qt::LeftButton);
    マウスイベント処理の拡張

    新しいdrag イベントハンドラを実装します。これは、軸のドラッグ計算に必要なマウスの移動距離を提供します(詳細については、「軸のドラッグの実装」を参照してください):

    connect(m_graph,
            &Q3DGraphsWidgetItem::selectedElementChanged,
            this,
            &ScatterDataModifier::handleElementSelected);
    connect(m_graph, &Q3DGraphsWidgetItem::dragged, this, &ScatterDataModifier::handleAxisDragging);
    m_graph->setDragButton(Qt::LeftButton);
    軸のドラッグの実装
    1. グラフからの選択シグナルのリスニングを開始します。これはコンストラクタ内で行い、handleElementSelected メソッドに接続してください:
      connect(m_graph,
              &Q3DGraphsWidgetItem::selectedElementChanged,
              this,
              &ScatterDataModifier::handleElementSelected);
      connect(m_graph, &Q3DGraphsWidgetItem::dragged, this, &ScatterDataModifier::handleAxisDragging);
      m_graph->setDragButton(Qt::LeftButton);
    2. handleElementSelected 内で、選択の種類を確認し、それに基づいて内部状態を設定します:
      switch (type) {
      case QtGraphs3D::ElementType::AxisXLabel:
          m_state = StateDraggingX;
          break;
      case QtGraphs3D::ElementType::AxisYLabel:
          m_state = StateDraggingY;
          break;
      case QtGraphs3D::ElementType::AxisZLabel:
          m_state = StateDraggingZ;
          break;
      default:
          m_state = StateNormal;
          break;
      }
    3. 実際のドラッグ処理は、`drag `イベントから呼び出される`handleAxisDragging `メソッド内で実装されています:
      void ScatterDataModifier::handleAxisDragging(QVector2D delta)
    4. handleAxisDragging では、まずアクティブなカメラからシーンの向きを取得します:
      // Get scene orientation from active camera
      float xRotation = m_graph->cameraXRotation();
      float yRotation = m_graph->cameraYRotation();
    5. その向きに基づいて、マウスの移動方向に対する補正値を計算します:
      // 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));
    6. マウスの移動量を計算し、カメラのY軸回転に基づいてそれを修正します:
      // Get the drag amount
      QPoint move = delta.toPoint();
      
      // Flip the effect of y movement if we're viewing from below
      float yMove = (yRotation < 0) ? -move.y() : move.y();
    7. 移動距離を正しい軸に適用する:
      // Adjust axes
      QValue3DAxis *axis = nullptr;
      switch (m_state) {
      case StateDraggingX:
          axis = m_graph->axisX();
          distance = (move.x() * xMulX - yMove * xMulY) / m_dragSpeedModifier;
          axis->setRange(axis->min() - distance, axis->max() - distance);
          break;
      case StateDraggingZ:
          axis = m_graph->axisZ();
          distance = (move.x() * zMulX + yMove * zMulY) / m_dragSpeedModifier;
          axis->setRange(axis->min() + distance, axis->max() + distance);
          break;
      case StateDraggingY:
          axis = m_graph->axisY();
          distance = move.y() / m_dragSpeedModifier; // No need to use adjusted y move here
          axis->setRange(axis->min() + distance, axis->max() + distance);
          break;
      default:
          break;
      }

    サーフェスグラフ

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

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

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

    生成データを使用したシンプルなサーフェス
    1. まず、新しいQSurfaceDataProxy をインスタンス化し、新しいQSurface3DSeries に接続します:
      m_sqrtSinProxy = new QSurfaceDataProxy();
      m_sqrtSinSeries = new QSurface3DSeries(m_sqrtSinProxy);
    2. プロキシに単純な平方根と正弦波のデータを入力します。QSurfaceDataArray インスタンスを作成し、そこにQSurfaceDataRow 要素を追加します。resetArray() を呼び出すことで、作成されたQSurfaceDataArray をQSurfaceDataProxy のデータ配列として設定します。
      QSurfaceDataArray dataArray;
      dataArray.reserve(sampleCountZ);
      for (int i = 0; i < sampleCountZ; ++i) {
          QSurfaceDataRow newRow;
          newRow.reserve(sampleCountX);
          // Keep values within range bounds, since just adding step can cause minor
          // drift due to the rounding errors.
          float z = qMin(sampleMax, (i * stepZ + sampleMin));
          for (int j = 0; j < sampleCountX; ++j) {
              float x = qMin(sampleMax, (j * stepX + sampleMin));
              float R = qSqrt(z * z + x * x) + 0.01f;
              float y = (qSin(R) / R + 0.24f) * 1.61f;
              newRow.append(QSurfaceDataItem(x, y, z));
          }
          dataArray.append(newRow);
      }
      
      m_sqrtSinProxy->resetArray(dataArray);
    マルチシリーズの高さマップデータ

    高さデータを含む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 にデコードしてください。

    1. エンコード乗数を定義します:
      // Value used to encode height data as RGB value on PNG file
      const float packingFactor = 11983.f;
    2. 実際のデコードを実行します:
      QImage heightMapImage(file);
      uchar *bits = heightMapImage.bits();
      int imageHeight = heightMapImage.height();
      int imageWidth = heightMapImage.width();
      int widthBits = imageWidth * 4;
      float stepX = width / float(imageWidth);
      float stepZ = height / float(imageHeight);
      
      QSurfaceDataArray dataArray;
      dataArray.reserve(imageHeight);
      for (int i = 0; i < imageHeight; ++i) {
          int p = i * widthBits;
          float z = height - float(i) * stepZ;
          QSurfaceDataRow newRow;
          newRow.reserve(imageWidth);
          for (int j = 0; j < imageWidth; ++j) {
              uchar aa = bits[p + 0];
              uchar rr = bits[p + 1];
              uchar gg = bits[p + 2];
              uint color = uint((gg << 16) + (rr << 8) + aa);
              float y = float(color) / packingFactor;
              newRow.append(QSurfaceDataItem(float(j) * stepX, y, z));
              p += 4;
          }
          dataArray.append(newRow);
      }
      
      dataProxy()->resetArray(dataArray);

    これで、Surface Graphはプロキシを介してデータを取得できるようになります。

    データセットの選択

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

    Sqrt & Sin では、生成された単純な系列が有効になっています。

    1. サーフェスのグリッドを有効にしたり、フラットシェーディングモードを選択したりするなど、装飾的な機能を設定します。
    2. 軸ラベルの形式と値の範囲を定義します。カメラアングルが低い場合でもラベルが読みやすいように、ラベルの自動回転を設定します。
    3. 正しいシリーズがグラフに追加され、他のシリーズは追加されていないことを確認してください。
    m_sqrtSinSeries->setDrawMode(QSurface3DSeries::DrawSurfaceAndWireframe);
    m_sqrtSinSeries->setShading(QSurface3DSeries::Shading::Flat);
    
    m_graph->axisX()->setLabelFormat("%.2f");
    m_graph->axisZ()->setLabelFormat("%.2f");
    m_graph->axisX()->setRange(sampleMin, sampleMax);
    m_graph->axisY()->setRange(0.f, 2.f);
    m_graph->axisZ()->setRange(sampleMin, sampleMax);
    m_graph->axisX()->setLabelAutoAngle(30.f);
    m_graph->axisY()->setLabelAutoAngle(90.f);
    m_graph->axisZ()->setLabelAutoAngle(30.f);
    
    m_graph->removeSeries(m_heightMapSeriesOne);
    m_graph->removeSeries(m_heightMapSeriesTwo);
    m_graph->removeSeries(m_heightMapSeriesThree);
    m_graph->removeSeries(m_topography);
    m_graph->removeSeries(m_highlight);
    
    m_graph->addSeries(m_sqrtSinSeries);

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

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

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

    m_graph->setDragButton(Qt::LeftButton);
    QObject::connect(m_graph,
                     &Q3DGraphsWidgetItem::dragged,
                     this,
                     &SurfaceGraphModifier::handleAxisDragging);
    
    QObject::connect(m_graph,
                     &Q3DGraphsWidgetItem::wheel,
                     this,
                     &SurfaceGraphModifier::onWheel);
    m_graph->setZoomEnabled(false);

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

    選択モード

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

    void toggleModeNone() { m_graph->setSelectionMode(QtGraphs3D::SelectionFlag::None); }
    void toggleModeItem() { m_graph->setSelectionMode(QtGraphs3D::SelectionFlag::Item); }
    void toggleModeSliceRow()
    {
        m_graph->setSelectionMode(QtGraphs3D::SelectionFlag::ItemAndRow
                                  | QtGraphs3D::SelectionFlag::Slice
                                  | QtGraphs3D::SelectionFlag::MultiSeries);
    }
    void toggleModeSliceColumn()
    {
        m_graph->setSelectionMode(QtGraphs3D::SelectionFlag::ItemAndColumn
                                  | QtGraphs3D::SelectionFlag::Slice
                                  | QtGraphs3D::SelectionFlag::MultiSeries);
    }

    グラフ内のすべての表示されている系列に対して同時にスライス選択を行えるようにするには、行および列の選択モードに対して、QtGraphs3D::SelectionFlag::Slice およびQtGraphs3D::SelectionFlag::MultiSeries フラグを追加します。

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

    この例には、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::ColorStyle::RangeGradientに変更してください。

    QLinearGradient gr;
    gr.setColorAt(0.f, Qt::black);
    gr.setColorAt(0.33f, Qt::blue);
    gr.setColorAt(0.67f, Qt::red);
    gr.setColorAt(1.f, Qt::yellow);
    
    m_sqrtSinSeries->setBaseGradient(gr);
    m_sqrtSinSeries->setColorStyle(QGraphsTheme::ColorStyle::RangeGradient);
    アプリケーションへのカスタムメッシュの追加

    アプリケーションにカスタムメッシュを追加するには:

    • CMakeビルドの場合、メッシュファイルをCMakeLists.txt に追加します:
      set(graphgallery_resource_files
          ...
          "data/oilrig.mesh"
          "data/pipe.mesh"
          "data/refinery.mesh"
          ...
      )
      
      qt6_add_resources(widgetgraphgallery "widgetgraphgallery"
          PREFIX
              "/"
          FILES
              ${graphgallery_resource_files}
      )
    • qmake ビルドの場合。qrc リソースファイルにメッシュファイルを追加します:
      <RCC>
          <qresource prefix="/">
              ...
              <file>data/refinery.mesh</file>
              <file>data/oilrig.mesh</file>
              <file>data/pipe.mesh</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.mesh",
                                     positionOne,
                                     QVector3D(0.0125f, 0.0125f, 0.0125f),
                                     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_graph->axisX()->setRange(m_axisXMinValue, m_axisXMaxValue);
        break;

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

    void SurfaceGraphModifier::onWheel(QWheelEvent *event)
    {
        float delta = float(event->angleDelta().y());
    
        m_axisXMinValue += delta;
        m_axisXMaxValue -= delta;
        m_axisZMinValue += delta;
        m_axisZMaxValue -= delta;
        checkConstraints();
    
        float y = (m_axisXMaxValue - m_axisXMinValue) * m_aspectRatio;
    
        m_graph->axisX()->setRange(m_axisXMinValue, m_axisXMaxValue);
        m_graph->axisY()->setRange(100.f, y);
        m_graph->axisZ()->setRange(m_axisZMinValue, m_axisZMaxValue);
    }

    次に、ズームレベルに制限を設け、表面に近づきすぎたり遠すぎたりしないようにします。 たとえば、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->dataArray().at(0).size();
        m_srcHeight = m_topographicSeries->dataArray().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.x() - halfWidth;
        if (startX < 0)
            startX = 0;
        int endX = position.x() + halfWidth;
        if (endX > (m_srcWidth - 1))
            endX = m_srcWidth - 1;
        int startZ = position.y() - halfHeight;
        if (startZ < 0)
            startZ = 0;
        int endZ = position.y() + halfHeight;
        if (endZ > (m_srcHeight - 1))
            endZ = m_srcHeight - 1;
    
        const QSurfaceDataArray &srcArray = m_topographicSeries->dataArray();
    
        QSurfaceDataArray dataArray;
        dataArray.reserve(endZ - startZ);
        for (int i = startZ; i < endZ; ++i) {
            QSurfaceDataRow newRow;
            newRow.reserve(endX - startX);
            QSurfaceDataRow srcRow = srcArray.at(i);
            for (int j = startX; j < endX; ++j) {
                QVector3D pos = srcRow.at(j).position();
                pos.setY(pos.y() + m_heightAdjustment);
                newRow.append(QSurfaceDataItem(pos));
            }
            dataArray.append(newRow);
        }
    
        dataProxy()->resetArray(dataArray);
        setVisible(true);
    }
    「ハイライト」シリーズへのグラデーション

    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(QGraphsTheme::ColorStyle::RangeGradient);
    
        handleZoomChange(ratio);
    }

    例の内容

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