軸の処理
QMLでカスタム入力ハンドラを使用して軸のドラッグを実装し、カスタム軸フォーマッタを作成する方法。
「軸の処理」では、軸に関する 2 つの異なるカスタム機能について解説します。これらの機能には、アプリケーション内にそれぞれ専用のタブが用意されています。
以下のセクションでは、これらの機能にのみ焦点を当て、基本的な機能の説明は省略します。QMLのサンプルに関するより詳細なドキュメントについては、「Simple Scatter Graph」を参照してください。

例の動作確認
以下の手順でサンプルを実行できます:
- Qt Creator
Welcome モードを開き、Examples からこの例を選択します。詳細については、Qt Creator の「チュートリアル:ビルドと実行」を参照してください。
- Qt Extension for Visual Studio Code
Command Palette から「Qt: Open Qt examples 」コマンドを実行し、リストからこの例を選択します。詳細については、Qt Extension for Visual Studio Code: Tutorial: Build and run を参照してください。
軸のドラッグ
「Axis Dragging 」タブで、軸ラベルをドラッグして軸の範囲を変更できるようにするカスタム入力ハンドラを QML で実装します。さらに、正射影を使用し、カスタムアイテムのプロパティを動的に更新します。
デフォルトの入力処理のオーバーライド
デフォルトの入力処理メカニズムを無効にするには、Scatter3Dグラフのアクティブな入力ハンドラをnull に設定します:
Scatter3D {
id: scatterGraph
inputHandler: null
...次に、MouseArea を追加し、親要素(scatterGraph が含まれているItem と同じ)を埋めるように設定します。また、この例では他のボタンは必要ないため、マウスの左ボタン押下のみを受け付けるように設定します:
MouseArea {
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton
...次に、マウスクリックを監視し、イベントがキャッチされたら、グラフに選択クエリを送信します:
onPressed: (mouse)=> {
scatterGraph.scene.selectionQueryPosition = 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 >= AbstractGraph3D.ElementAxisXLabel
&& selectedElement <= AbstractGraph3D.ElementAxisZLabel) {
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 AbstractGraph3D.ElementAxisXLabel:
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 AbstractGraph3D.ElementAxisYLabel:
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 AbstractGraph3D.ElementAxisZLabel:
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 = AbstractGraph3D.ShadowQualityMedium
} else {
text = "Display Perspective";
scatterGraph.orthoProjection = true;
}
}
}カスタムアイテムの場合は、scatterGraph のcustomItemList に1を追加します:
customItemList: [
Custom3DItem {
id: qtCube
meshFile: ":/qml/qmlaxishandling/cube.obj"
textureFile: ":/qml/qmlaxishandling/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 はいくつかの仮想メソッドを再実装する必要があります:
virtual QValue3DAxisFormatter *createNewInstance() const;
virtual void populateCopy(QValue3DAxisFormatter ©) const;
virtual void recalculate();
virtual QString stringForValue(qreal value, const QString &format) const;最初の2つは単純で、CustomFormatter の新しいインスタンスを作成し、必要なデータをそこにコピーするだけです。レンダリング用にフォーマッタのキャッシュを作成および更新するには、これら2つのメソッドを使用します。populateCopy() のスーパークラスの実装を呼び出すことを忘れないでください:
QValue3DAxisFormatter *CustomFormatter::createNewInstance() const
{
return new CustomFormatter();
}
void CustomFormatter::populateCopy(QValue3DAxisFormatter ©) const
{
QValue3DAxisFormatter::populateCopy(copy);
CustomFormatter *customFormatter = static_cast<CustomFormatter *>(©);
customFormatter->m_originDate = m_originDate;
customFormatter->m_selectionFormat = m_selectionFormat;
}CustomFormatter 処理の大部分は `recalculate() ` メソッドで行われます。このメソッド内で、フォーマッタはグリッド、サブグリッド、ラベルの位置を計算し、ラベル文字列の書式設定も行います。カスタムフォーマッタでは、軸のセグメント数を無視し、グリッド線を常に真夜中の位置に描画してください。サブセグメント数とラベルの位置決めは通常通り処理されます:
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;
// 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;
}
}
}
}軸ラベルは、日付のみが表示されるようにフォーマットされます。ただし、選択ラベルのタイムスタンプの解像度を高めるには、ユーザーがカスタマイズできるように、カスタムフォーマッタに別のプロパティを指定します:
Q_PROPERTY(QString selectionFormat READ selectionFormat WRITE setSelectionFormat NOTIFY selectionFormatChanged)この選択フォーマットプロパティは、再実装されたstringToValue メソッドで使用されます。このメソッドでは、送信されたフォーマットは無視され、代わりにカスタム選択フォーマットが適用されます:
QString CustomFormatter::stringForValue(qreal value, const QString &format) const
{
Q_UNUSED(format);
return valueToDateTime(value).toString(m_selectionFormat);
}新しいカスタムフォーマッタをQMLで利用できるようにするには、それを宣言してQMLモジュールとして定義します。その方法については、「Surface Graph Gallery」を参照してください。
QML
QML コード内で、各次元ごとに異なる軸を定義します。
axisZ: valueAxis
axisY: logAxis
axisX: dateAxisZ軸は通常のValueAxis3Dです:
ValueAxis3D {
id: valueAxis
segmentCount: 5
subSegmentCount: 2
labelFormat: "%.2f"
min: 0
max: 10
}Y軸については、対数軸を定義します。ValueAxis3Dに対数スケールを表示させるには、軸のformatter プロパティにLogValueAxis3DFormatterを指定します:
ValueAxis3D {
id: logAxis
formatter: LogValueAxis3DFormatter {
id: logAxisFormatter
base: 10
autoSubGrid: true
showEdgeLabels: true
}
labelFormat: "%.2f"
}そして最後に、X軸には新しいCustomFormatter を使用します:
ValueAxis3D {
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
}アプリケーションの残りの部分は、軸の変更やグラフの表示を行う、比較的直感的なロジックで構成されています。
例の概要
© 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.