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

Surface3D グラフの3つの異なる使い方を紹介するギャラリー。

Surface Graph GalleryではSurface3D グラフを使った3つの異なるカスタム機能をデモンストレーションしています。これらの機能は、アプリケーション内にそれぞれのタブを持っています。

以下の節では、これらの機能のみに焦点を当て、基本的な機能の説明は省略します - より詳細な QML サンプルドキュメントは、単純散布図を参照してください。

サンプルを実行する

Qt Creator からサンプルを実行するには、Welcome モードを開き、Examples からサンプルを選択します。詳細については、Building and Running an Example を参照してください。

高さマップ

Height Map タブで、高さデータからサーフェスグラフを生成します。使用するデータは、ニュージーランドのルアペフ山とンガウルホー山の高さマップです。

グラフへのデータの追加

高さマップ画像から高さ情報を読み取るHeightMapSurfaceDataProxy を使ってデータを設定します。プロキシ自体はSurface3DSeries に含まれています。HeightMapSurfaceDataProxy 内のheightMapFile プロパティは、高さデータを含む画像ファイルを指定します。プロキシ内の値プロパティは、表面積の幅、奥行き、高さの最小値と最大値を定義します。zx の値は緯度と経度で、ほぼ実世界の位置にあり、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 で使用するthemebaseGradients プロパティに設定します:

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 スペクトログラムを作成するには、Surface3DSeries で指定されたデータでSurface3D アイテムをItemModelSurfaceDataProxy で定義します:

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 効果を有効にするための主なプロパティはorthoProjectionscene.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のコードから直接操作することができないからです。この制限を克服するために、単純なDataSource クラスを C++ で実装し、 シリーズのデータプロキシにデータを入力します。

DataSource クラスを作成し、QML から呼び出せるメソッドを二つ用意します:

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

最初のメソッド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() は、キャッシュされたデータの 1 セットを別の配列にコピーし、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);
}

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

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

©2024 The Qt Company Ltd. 本書に含まれるドキュメントの著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。