このページでは

サーフェスグラフギャラリー

Surface3Dグラフの3つの活用方法を紹介するギャラリーです。

「サーフェスグラフギャラリー」では、Surface3Dグラフを用いた3つの異なるカスタム機能を紹介しています。これらの機能は、アプリケーション内にそれぞれ専用のタブが用意されています。

以下のセクションでは、これらの機能にのみ焦点を当て、基本的な機能の説明は省略しています。より詳細なQMLのサンプルドキュメントについては、「Simple Scatter Graph」を参照してください。

スペクトログラムを虹色の山状の波紋として表現した3D表面グラフ

例の動作

以下の手順でサンプルを実行できます:

標高マップ

「Height Map 」タブで、標高データからサーフェスグラフを生成します。使用するデータは、ニュージーランドのルアペフ山およびナウルホエ山の標高マップです。

グラフへのデータの追加

データは、ハイトマップ画像から高さ情報を読み取る `HeightMapSurfaceDataProxy` を使用して設定されます。このプロキシ自体は `Surface3DSeries` に含まれています。 HeightMapSurfaceDataProxy内部では、heightMapFile プロパティが標高データを含む画像ファイルを指定します。プロキシ内のvalueプロパティは、表面領域の幅、奥行き、および高さの最小値と最大値を定義します。z およびx の値は、実世界の位置に近い緯度と経度で指定され、y はメートル単位で指定されます。

注: グラフのアスペクト比は 実世界の縮尺に合わせておらず、代わりに高さが誇張されています。

Surface3DSeries {
    id: heightSeries
    flatShadingEnabled: false
    drawMode: Surface3DSeries.DrawSurface

    HeightMapSurfaceDataProxy {
        heightMapFile: "://qml/qmlsurfacegallery/heightmap.png"
        // We don't want the default data values set by heightmap proxy, but use
        // actual coordinate and height values instead
        autoScaleY: true
        minYValue: 740
        maxYValue: 2787
        minZValue: -374 // ~ -39.374411"N
        maxZValue: -116 // ~ -39.115971"N
        minXValue: 472  // ~ 175.471767"E
        maxXValue: 781  // ~ 175.780758"E
    }

    onDrawModeChanged: heightMapView.checkState()
}
データの表示

main.qml で、データを表示するためのSurface3D要素を設定します。

まず、サーフェスに使用するカスタムグラデーションを定義します。ColorGradient を使用して、位置 0.0 から 1.0 までの色を指定し、グラフをより鮮やかに見せるために 2 つのストップを追加します。

ColorGradient {
    id: surfaceGradient
    ColorGradientStop { position: 0.0; color: "darkgreen"}
    ColorGradientStop { position: 0.15; color: "darkslategray" }
    ColorGradientStop { position: 0.7; color: "peru" }
    ColorGradientStop { position: 1.0; color: "white" }
}

この要素を、Surface3Dで使用されるtheme のbaseGradients プロパティに設定します:

theme: Theme3D {
    type: Theme3D.ThemeStoneMoss
    font.family: "STCaiyun"
    font.pointSize: 35
    colorStyle: Theme3D.ColorStyleRangeGradient
    baseGradients: [surfaceGradient] // Use the custom gradient
}

ボタンを使用して、Surface3Dのその他の機能を制御します。

最初のボタンは、サーフェスのグリッドの表示/非表示を切り替えます。描画モードを完全にクリアすることはできないため、サーフェス自体が可視状態でない限り、サーフェスのグリッドを非表示にすることはできません:

onClicked: {
    if (heightSeries.drawMode & Surface3DSeries.DrawWireframe)
        heightSeries.drawMode &= ~Surface3DSeries.DrawWireframe;
    else
        heightSeries.drawMode |= Surface3DSeries.DrawWireframe;
}

2番目のボタンは、サーフェスグリッドの色を設定します:

onClicked: {
    if (Qt.colorEqual(heightSeries.wireframeColor, "#000000")) {
        heightSeries.wireframeColor = "red";
        text = "Black surface\ngrid color";
    } else {
        heightSeries.wireframeColor = "black";
        text = "Red surface\ngrid color";
    }
}

3番目のボタンは、サーフェス描画モードにおいてサーフェスの表示/非表示を切り替えます。描画モードを完全にクリアすることはできないため、サーフェスグリッドが表示されていない限り、サーフェス自体を非表示にすることはできません:

onClicked: {
    if (heightSeries.drawMode & Surface3DSeries.DrawSurface)
        heightSeries.drawMode &= ~Surface3DSeries.DrawSurface;
    else
        heightSeries.drawMode |= Surface3DSeries.DrawSurface;
}

4番目のボタンはシェーディングモードを設定します。OpenGL ES システムでこのサンプルを実行している場合、フラットシェーディングは利用できません:

onClicked: {
    if (heightSeries.flatShadingEnabled) {
        heightSeries.flatShadingEnabled = false;
        text = "Show\nFlat"
    } else {
        heightSeries.flatShadingEnabled = true;
        text = "Show\nSmooth"
    }
}

残りのボタンは、グラフの背景機能を制御します。

スペクトログラム

「Spectrogram 」タブでは、極座標および直交座標のスペクトログラムを表示し、正投影を用いて2Dで表示します。

スペクトログラムとは、値の違いを強調するために範囲のグラデーションが適用されたサーフェスグラフのことです。通常、スペクトログラムは2次元サーフェスとして表示され、これはグラフのトップダウン正投影ビューによってシミュレートされます。2D効果を確実に得るには、正投影モードの際には、マウスやタッチによるグラフの回転を無効にしてください。

スペクトログラムの作成

2Dスペクトログラムを作成するには、ItemModelSurfaceDataProxyを持つSurface3DSeriesに指定されたデータを使用して、Surface3Dアイテムを定義します:

Surface3D {
    id: surfaceGraph
    anchors.fill: parent

    Surface3DSeries {
        id: surfaceSeries
        flatShadingEnabled: false
        drawMode: Surface3DSeries.DrawSurface
        baseGradient: surfaceGradient
        colorStyle: Theme3D.ColorStyleRangeGradient
        itemLabelFormat: "(@xLabel, @zLabel): @yLabel"

        ItemModelSurfaceDataProxy {
            itemModel: surfaceData.model
            rowRole: "radius"
            columnRole: "angle"
            yPosRole: "value"
        }
    }

2D効果を有効にするための重要なプロパティは、orthoProjection およびscene.activeCamera.cameraPreset です。グラフの正投影を有効にして遠近感を排除し、グラフを真上から直接見ることでY軸の視覚的な影響をなくします:

// Remove the perspective and view the graph from top down to achieve 2D effect
orthoProjection: true
scene.activeCamera.cameraPreset: Camera3D.CameraPresetDirectlyAbove

この視点では、水平軸のグリッドの大部分がサーフェスによって隠れてしまうため、水平グリッドを反転させてグラフの上に表示するようにします:

flipHorizontalGrid: true
極座標スペクトログラム

データによっては、直交座標グラフの代わりに極座標グラフを使用したほうが自然な場合があります。これは、polar プロパティによってサポートされています。

極座標モードと直交座標モードを切り替えるボタンを追加します:

Button {
    id: polarToggle
    anchors.margins: 5
    anchors.left: parent.left
    anchors.top: parent.top
    width: spectrogramView.buttonWidth // Calculated elsewhere based on screen orientation
    text: "Switch to\n" + (surfaceGraph.polar ? "cartesian" : "polar")
    onClicked: surfaceGraph.polar = !surfaceGraph.polar;
}

極座標モードでは、X軸は角度の極軸に変換され、Z軸は半径の極軸に変換されます。曲面の点は、新しい軸に基づいて再計算されます。

デフォルトでは、半径軸のラベルはグラフの外側に描画されます。グラフ内の0度の角度軸のすぐ隣にラベルを描画するには、ラベルのオフセットをわずかに設定します:

radialLabelOffset: 0.01

2D効果を確実に適用するには、デフォルトの入力ハンドラをカスタムハンドラで上書きして、正投影モードでのグラフの回転を無効にします。このカスタムハンドラは、投影モードに基づいてrotationEnabled プロパティを自動的に切り替えます:

inputHandler: TouchInputHandler3D {
    rotationEnabled: !surfaceGraph.orthoProjection
}

オシロスコープ

「Oscilloscope 」タブでは、アプリケーション内でC++とQMLを組み合わせて、動的に変化するデータを表示します。

C++ でのデータソース

アイテムモデルに基づくプロキシは、単純なグラフや静的なグラフには適していますが、リアルタイムで変化するデータを表示する際は、最高のパフォーマンスを得るために基本プロキシを使用してください。これらはQMLではサポートされていません。これは、それらに格納されるデータアイテムがQObject を継承しておらず、したがってQMLコードから直接操作できないためです。 この制限を克服するには、C++で単純なDataSource クラスを実装し、シリーズのデータプロキシにデータを入力します。

QMLから呼び出せる2つのメソッドを提供するDataSource クラスを作成します。

class DataSource : public QObject
{
    Q_OBJECT
    ...
Q_INVOKABLE void generateData(int cacheCount, int rowCount, int columnCount,
                              float xMin, float xMax,
                              float yMin, float yMax,
                              float zMin, float zMax);

Q_INVOKABLE void update(QSurface3DSeries *series);

1つ目のメソッド `generateData()` は、表示するためのシミュレートされたオシロスコープデータのキャッシュを作成します。データは、`QSurfaceDataProxy` が受け入れられる形式でキャッシュされます:

// Populate caches
auto *generator = QRandomGenerator::global();
for (int i = 0; i < cacheCount; ++i) {
    QSurfaceDataArray &cache = m_data[i];
    float cacheXAdjustment = cacheStep * i;
    float cacheIndexAdjustment = cacheIndexStep * i;
    for (int j = 0; j < rowCount; ++j) {
        QSurfaceDataRow &row = *(cache[j]);
        float rowMod = (float(j)) / float(rowCount);
        float yRangeMod = yRange * rowMod;
        float zRangeMod = zRange * rowMod;
        float z = zRangeMod + zMin;
        qreal rowColWaveAngleMul = M_PI * M_PI * rowMod;
        float rowColWaveMul = yRangeMod * 0.2f;
        for (int k = 0; k < columnCount; k++) {
            float colMod = (float(k)) / float(columnCount);
            float xRangeMod = xRange * colMod;
            float x = xRangeMod + xMin + cacheXAdjustment;
            float colWave = float(qSin((2.0 * M_PI * colMod) - (1.0 / 2.0 * M_PI)) + 1.0);
            float y = (colWave * ((float(qSin(rowColWaveAngleMul * colMod) + 1.0))))
                    * rowColWaveMul
                    + generator->bounded(0.15f) * yRangeMod;

            int index = k + cacheIndexAdjustment;
            if (index >= columnCount) {
                // Wrap over
                index -= columnCount;
                x -= xRange;
            }
            row[index] = QVector3D(x, y, z);
        }
    }
}

2つ目のメソッド `update()` は、キャッシュされたデータの一セットを別の配列にコピーし、`QSurfaceDataProxy::resetArray()` を呼び出すことで、その配列をシリーズのデータプロキシに設定します。オーバーヘッドを最小限に抑えるため、配列の次元が変更されていない場合は、同じ配列を再利用します:

// Each iteration uses data from a different cached array
if (++m_index >= m_data.size())
    m_index = 0;

const QSurfaceDataArray &array = m_data.at(m_index);
int newRowCount = array.size();
int newColumnCount = array.at(0)->size();

// If the first time or the dimensions of the cache array have changed,
// reconstruct the reset array
if (!m_resetArray || series->dataProxy()->rowCount() != newRowCount
        || series->dataProxy()->columnCount() != newColumnCount) {
    m_resetArray = new QSurfaceDataArray();
    m_resetArray->reserve(newRowCount);
    for (int i = 0; i < newRowCount; ++i)
        m_resetArray->append(new QSurfaceDataRow(newColumnCount));
}

// Copy items from our cache to the reset array
for (int i = 0; i < newRowCount; ++i) {
    const QSurfaceDataRow &sourceRow = *(array.at(i));
    QSurfaceDataRow &row = *(*m_resetArray)[i];
    std::copy(sourceRow.cbegin(), sourceRow.cend(), row.begin());
}

// Notify the proxy that data has changed
series->dataProxy()->resetArray(m_resetArray);

以前にプロキシに設定された配列ポインタに対して操作を行っている場合でも、配列内のデータを変更した後は、グラフにデータの描画を促すために、QSurfaceDataProxy::resetArray() を呼び出す必要があります。

QML から `DataSource ` メソッドにアクセスできるようにするには、`DataSource` を `QML_ELEMENT` として公開します:

class DataSource : public QObject
{
    Q_OBJECT
    QML_ELEMENT

さらに、CMakeLists.txt内でそれをQMLモジュールとして宣言します:

qt6_add_qml_module(qmlsurfacegallery
    URI SurfaceGallery
    VERSION 1.0
    NO_RESOURCE_TARGET_PATH
    SOURCES
        datasource.cpp datasource.h
    ...
)

すべての環境およびビルドにおいて、DataSource クラスのメソッドのパラメータとしてQSurface3DSeriesポインタを使用するには、メタタイプが登録されていることを確認してください:

qRegisterMetaType<QSurface3DSeries *>();
QMLアプリケーション

DataSource を使用するには、QML モジュールをインポートし、使用するDataSource のインスタンスを作成します:

import SurfaceGallery
...
DataSource {
    id: dataSource
}

Surface3Dグラフを定義し、それにSurface3DSeriesを割り当てます:

Surface3D {
    id: surfaceGraph
    anchors.fill: parent

    Surface3DSeries {
        id: surfaceSeries
        drawMode: Surface3DSeries.DrawSurfaceAndWireframe
        itemLabelFormat: "@xLabel, @zLabel: @yLabel"

グラフに追加する Surface3DSeries に対してプロキシを指定しないでください。これにより、そのシリーズはデフォルトの QSurfaceDataProxy を利用することになります。

itemLabelVisible を使用して、項目のラベルを非表示にしてください。動的で変化の激しいデータの場合、浮動式の選択ラベルは注意をそらし、読みづらくなる可能性があります。

itemLabelVisible: false

選択ポインタの上部に表示されるデフォルトの浮動ラベルの代わりに、Text 要素に選択項目の情報を表示することができます:

onItemLabelChanged: {
    if (surfaceSeries.selectedPoint == surfaceSeries.invalidSelectionPosition)
        selectionText.text = "No selection";
    else
        selectionText.text = surfaceSeries.itemLabel;
}

グラフの生成が完了したら、ヘルパー関数 `generateData()` を呼び出して `DataSource ` のキャッシュを初期化します。この関数は、DataSource 内の同名のメソッドを呼び出します:

Component.onCompleted: oscilloscopeView.generateData();
...
function generateData() {
    dataSource.generateData(oscilloscopeView.sampleCache, oscilloscopeView.sampleRows,
                            oscilloscopeView.sampleColumns,
                            surfaceGraph.axisX.min, surfaceGraph.axisX.max,
                            surfaceGraph.axisY.min, surfaceGraph.axisY.max,
                            surfaceGraph.axisZ.min, surfaceGraph.axisZ.max);
}

データの更新をトリガーするには、Timer を定義し、指定された間隔でDataSource のupdate() メソッドを呼び出します:

Timer {
    id: refreshTimer
    interval: 1000 / frequencySlider.value
    running: true
    repeat: true
    onTriggered: dataSource.update(surfaceSeries);
}
ダイレクトレンダリングの有効化

このアプリケーションは、急速に変化する大量のデータを扱う可能性があるため、パフォーマンス向上のためにダイレクトレンダリングモードを使用します。このモードでアンチエイリアシングを有効にするには、アプリケーションウィンドウのサーフェスフォーマットを変更してください。QQuickView で使用されるデフォルトのフォーマットは、アンチエイリアシングをサポートしていません。main.cpp 内で提供されているユーティリティ関数を使用して、サーフェスフォーマットを変更してください:

#include <QtDataVisualization/qutils.h>
...
// Enable antialiasing in direct rendering mode
viewer.setFormat(qDefaultSurfaceFormat(true));

サンプル内容

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