このページでは

軸の処理

QMLでカスタム入力ハンドラを使用して軸のドラッグを実装し、カスタム軸フォーマッタを作成します。

「軸の処理」では、軸に関する2つの異なるカスタム機能について解説します。これらの機能には、アプリケーション内にそれぞれ専用のタブが用意されています。

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

球体と日付ベースの軸を用いた3D散布図

例の動作確認

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

軸のドラッグ

「Axis Dragging 」タブで、軸ラベルをドラッグして軸の範囲を変更できるようにするカスタム入力ハンドラをQMLで実装します。さらに、正射影を使用し、カスタムアイテムのプロパティを動的に更新します。

デフォルトの入力処理のオーバーライド

デフォルトの入力処理メカニズムを無効にするには、Scatter3D のデフォルトの入力ハンドラを解除します:

unsetDefaultInputHandler();
    ...

次に、`MouseArea ` を追加し、親要素(scatterGraph が含まれているのと同じ `Item `)を埋めるように設定します。また、この例では他のボタンは必要ないため、マウスの左ボタン押下のみを受け付けるように設定します:

MouseArea {
    anchors.fill: parent
    hoverEnabled: true
    acceptedButtons: Qt.LeftButton
    ...

次に、マウスクリックを監視し、イベントが捕捉されたら、グラフに選択クエリを送信します:

onPressed: (mouse)=> {
               scatterGraph.doPicking(Qt.point(mouse.x, mouse.y));
           }

onPositionChanged シグナルハンドラは、移動距離の計算に必要な現在のマウス位置を捕捉します:

onPositionChanged: (mouse)=> {
                       currentMouseX = mouse.x;
                       currentMouseY = mouse.y;
    ...

onPositionChanged の最後に、後で説明する移動距離の計算に備えて、直前のマウス位置を保存します:

...
    previousMouseX = currentMouseX;
    previousMouseY = currentMouseY;
}
マウス移動を軸範囲の変化に変換する

scatterGraph 内で、onSelectedElementChanged を監視します。このシグナルは、inputArea のonPressed で選択クエリが行われた後に発火します。要素の型は、関心のある型であるため、メインコンポーネントで定義したプロパティ(property int selectedAxisLabel: -1 )に設定します:

onSelectedElementChanged: {
    if (selectedElement >= Graphs3D.ElementType.AxisXLabel
            && selectedElement <= Graphs3D.ElementType.AxisZLabel) {
        selectedAxisLabel = selectedElement;
    } else {
        selectedAxisLabel = -1;
    }
}

次に、inputArea のonPositionChanged に戻り、マウスボタンが押されているか、および現在選択されている軸ラベルがあるかを確認します。条件が満たされている場合、マウスの動きから軸範囲の更新への変換を行う関数を呼び出します:

...
if (pressed && selectedAxisLabel != -1)
    axisDragView.dragAxis();
...

このケースでは、カメラの回転が固定されているため、変換は簡単です。あらかじめ計算された値を使用し、マウスの移動距離を算出し、その値を選択された軸の範囲に適用することができます:

function dragAxis() {
    // Do nothing if previous mouse position is uninitialized
    if (previousMouseX === -1)
        return;

    // Directional drag multipliers based on rotation. Camera is locked to 45 degrees, so we
    // can use one precalculated value instead of calculating xx, xy, zx and zy individually
    var cameraMultiplier = 0.70710678;

    // Calculate the mouse move amount
    var moveX = currentMouseX - previousMouseX;
    var moveY = currentMouseY - previousMouseY;

    // Adjust axes
    switch (selectedAxisLabel) {
    case Graphs3D.ElementType.AxisXLabel:
        var distance = ((moveX - moveY) * cameraMultiplier) / dragSpeedModifier;
        // Check if we need to change min or max first to avoid invalid ranges
        if (distance > 0) {
            scatterGraph.axisX.min -= distance;
            scatterGraph.axisX.max -= distance;
        } else {
            scatterGraph.axisX.max -= distance;
            scatterGraph.axisX.min -= distance;
        }
        break;
    case Graphs3D.ElementType.AxisYLabel:
        distance = moveY / dragSpeedModifier;
        // Check if we need to change min or max first to avoid invalid ranges
        if (distance > 0) {
            scatterGraph.axisY.max += distance;
            scatterGraph.axisY.min += distance;
        } else {
            scatterGraph.axisY.min += distance;
            scatterGraph.axisY.max += distance;
        }
        break;
    case Graphs3D.ElementType.AxisZLabel:
        distance = ((moveX + moveY) * cameraMultiplier) / dragSpeedModifier;
        // Check if we need to change min or max first to avoid invalid ranges
        if (distance > 0) {
            scatterGraph.axisZ.max += distance;
            scatterGraph.axisZ.min += distance;
        } else {
            scatterGraph.axisZ.min += distance;
            scatterGraph.axisZ.max += distance;
        }
        break;
    }
}

マウス移動から軸範囲の更新へのより高度な変換については、「Graph Gallery」を参照してください。

その他の機能

このサンプルでは、正投影の使用方法や、カスタム項目のプロパティをリアルタイムで更新する方法についても説明しています。

正投影は非常に簡単です。scatterGraph のorthoProjection プロパティを変更するだけで済みます。このサンプルには、これをオン/オフに切り替えるためのボタンが用意されています:

Button {
    id: orthoToggle
    width: axisDragView.portraitMode ? parent.width : parent.width / 3
    text: "Display Orthographic"
    anchors.left: axisDragView.portraitMode ? parent.left : rangeToggle.right
    anchors.top: axisDragView.portraitMode ? rangeToggle.bottom : parent.top
    onClicked: {
        if (scatterGraph.orthoProjection) {
            text = "Display Orthographic";
            scatterGraph.orthoProjection = false;
            // Orthographic projection disables shadows, so we need to switch them back on
            scatterGraph.shadowQuality = Graphs3D.ShadowQuality.Medium
        } else {
            text = "Display Perspective";
            scatterGraph.orthoProjection = true;
        }
    }
}

カスタムアイテムの場合は、scatterGraph のcustomItemList に1を追加します:

customItemList: [
    Custom3DItem {
        id: qtCube
        meshFile: ":/qml/axishandling/cube.mesh"
        textureFile: ":/qml/axishandling/cubetexture.png"
        position: Qt.vector3d(0.65, 0.35, 0.65)
        scaling: Qt.vector3d(0.3, 0.3, 0.3)
    }
]

グラフ内のすべてのアイテムを追加、削除、およびローテーションするためのタイマーを実装し、その同じタイマーを使用してカスタムアイテムのローテーションも行います:

onTriggered: {
    rotationAngle = rotationAngle + 1;
    qtCube.setRotationAxisAndAngle(Qt.vector3d(1, 0, 1), rotationAngle);
    ...

軸フォーマッタ

「Axis Formatter 」タブで、カスタム軸フォーマッタを作成します。また、定義済みの軸フォーマッタの使用方法も示されています。

カスタム軸フォーマッタ

軸フォーマッタをカスタマイズするには、`QValue3DAxisFormatter` をサブクラス化する必要がありますが、これは QML コードだけでは実現できません。この例では、軸が float 値をタイムスタンプとして解釈し、軸ラベルに日付を表示します。これを実現するために、`QValue3DAxisFormatter` をサブクラス化する `CustomFormatter` という新しいクラスを導入します:

class CustomFormatter : public QValue3DAxisFormatter
{
...

QScatter3DSeries のfloat値は、データ幅の違いによりQDateTime の値に直接キャストできないため、両者の間にある種のマッピングが必要です。このマッピングを行うには、フォーマッタに基準日を指定し、QScatter3DSeries からのfloat値をその基準値からの日付オフセットとして解釈します。基準日はプロパティとして指定されます:

Q_PROPERTY(QDate originDate READ originDate WRITE setOriginDate NOTIFY originDateChanged)

値からQDateTime へのマッピングには、valueToDateTime() メソッドを使用します:

QDateTime CustomFormatter::valueToDateTime(qreal value) const
{
    return m_originDate.startOfDay().addMSecs(qint64(oneDayMs * value));
}

軸フォーマッタとして機能させるには、CustomFormatter はいくつかの仮想メソッドを再実装する必要があります:

QValue3DAxisFormatter *createNewInstance() const override;
void populateCopy(QValue3DAxisFormatter &copy) override;
void recalculate() override;
QString stringForValue(qreal value, const QString &format) override;

最初の2つのメソッドは単純で、CustomFormatter の新しいインスタンスを作成し、必要なデータをそこにコピーするだけです。これら2つのメソッドを使用して、レンダリング用のフォーマッタのキャッシュを作成および更新します。populateCopy() のスーパークラスの実装を呼び出すことを忘れないでください:

QValue3DAxisFormatter *CustomFormatter::createNewInstance() const
{
    return new CustomFormatter();
}

void CustomFormatter::populateCopy(QValue3DAxisFormatter &copy)
{
    QValue3DAxisFormatter::populateCopy(copy);

    CustomFormatter *customFormatter = static_cast<CustomFormatter *>(&copy);
    customFormatter->m_originDate = m_originDate;
    customFormatter->m_selectionFormat = m_selectionFormat;
}

CustomFormatter その処理の大部分は `recalculate() ` メソッドで行われます。このメソッドでは、フォーマッタがグリッド、サブグリッド、ラベルの位置を計算し、ラベル文字列の書式設定も行います。カスタムフォーマッタでは、軸のセグメント数を無視し、グリッド線を常に午前0時に描画するようにします。サブセグメント数とラベルの位置決めは通常通り処理されます:

void CustomFormatter::recalculate()
{
    // We want our axis to always have gridlines at date breaks

    // Convert range into QDateTimes
    QDateTime minTime = valueToDateTime(qreal(axis()->min()));
    QDateTime maxTime = valueToDateTime(qreal(axis()->max()));

    // Find out the grid counts
    QTime midnight(0, 0);
    QDateTime minFullDate(minTime.date(), midnight);
    int gridCount = 0;
    if (minFullDate != minTime)
        minFullDate = minFullDate.addDays(1);
    QDateTime maxFullDate(maxTime.date(), midnight);

    gridCount += minFullDate.daysTo(maxFullDate) + 1;
    int subGridCount = axis()->subSegmentCount() - 1;

    QList<float> gridPositions;
    QList<float> subGridPositions;
    QList<float> labelPositions;
    QStringList labelStrings;

    // Reserve space for position arrays and label strings
    gridPositions.resize(gridCount);
    subGridPositions.resize((gridCount + 1) * subGridCount);
    labelPositions.resize(gridCount);
    labelStrings.reserve(gridCount);

    // Calculate positions and format labels
    qint64 startMs = minTime.toMSecsSinceEpoch();
    qint64 endMs = maxTime.toMSecsSinceEpoch();
    qreal dateNormalizer = endMs - startMs;
    qreal firstLineOffset = (minFullDate.toMSecsSinceEpoch() - startMs) / dateNormalizer;
    qreal segmentStep = oneDayMs / dateNormalizer;
    qreal subSegmentStep = 0;
    if (subGridCount > 0)
        subSegmentStep = segmentStep / qreal(subGridCount + 1);

    for (int i = 0; i < gridCount; i++) {
        qreal gridValue = firstLineOffset + (segmentStep * qreal(i));
        gridPositions[i] = float(gridValue);
        labelPositions[i] = float(gridValue);
        labelStrings << minFullDate.addDays(i).toString(axis()->labelFormat());
    }

    for (int i = 0; i <= gridCount; i++) {
        if (subGridPositions.size()) {
            for (int j = 0; j < subGridCount; j++) {
                float position;
                if (i)
                    position = gridPositions.at(i - 1) + subSegmentStep * (j + 1);
                else
                    position = gridPositions.at(0) - segmentStep + subSegmentStep * (j + 1);
                if (position > 1.0f || position < 0.0f)
                    position = gridPositions.at(0);
                subGridPositions[i * subGridCount + j] = position;
            }
        }
    }
    setGridPoitions(gridPositions);
    setSubGridPositions(subGridPositions);
    setlabelPositions(labelPositions);
    setLabelStrings(labelStrings);
}

軸ラベルは、日付のみが表示されるようにフォーマットされます。ただし、選択ラベルのタイムスタンプの解像度を高めるには、ユーザーがカスタマイズできるように、カスタムフォーマッタに別のプロパティを指定します:

Q_PROPERTY(QString selectionFormat READ selectionFormat WRITE setSelectionFormat NOTIFY
               selectionFormatChanged)

この選択形式のプロパティは、再実装されたstringToValue メソッドで使用されます。このメソッドでは、送信された形式は無視され、代わりにカスタム選択形式が適用されます。

QString CustomFormatter::stringForValue(qreal value, const QString &format)
{
    Q_UNUSED(format);

    return valueToDateTime(value).toString(m_selectionFormat);
}

新しいカスタムフォーマッタを QML から利用可能にするには、それを宣言し、QML モジュールとして設定します。その方法については、「Surface Graph Gallery」を参照してください。

QML

QMLコードでは、各次元ごとに異なる軸を定義します:

axisZ: valueAxis
axisY: logAxis
axisX: dateAxis

Z軸は、通常のValue3DAxis です:

Value3DAxis {
    id: valueAxis
    segmentCount: 5
    subSegmentCount: 2
    labelFormat: "%.2f"
    min: 0
    max: 10
}

Y軸については、対数軸を定義します。Value3DAxis に対数スケールを表示させるには、軸のformatter プロパティにLogValue3DAxisFormatter を指定します:

Value3DAxis {
    id: logAxis
    formatter: LogValue3DAxisFormatter {
        id: logAxisFormatter
        base: 10
        autoSubGrid: true
        edgeLabelsVisible: true
    }
    labelFormat: "%.2f"
}

そして最後に、X軸には新しいCustomFormatter を使用します:

Value3DAxis {
    id: dateAxis
    formatter: CustomFormatter {
        originDate: "2023-01-01"
        selectionFormat: "yyyy-MM-dd HH:mm:ss"
    }
    subSegmentCount: 2
    labelFormat: "yyyy-MM-dd"
    min: 0
    max: 14
}

アプリケーションの残りの部分は、軸の変更やグラフの表示を行う、比較的直感的なロジックで構成されています。

サンプル内容

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