QMLのコントロール - グラフ

提供: MochiuWiki : SUSE, EC, PCB

概要

QMLを使用したグラフ描画は、Qt Chartsモジュールを使用して実現することができる。
Qt Chartsは、折れ線グラフ、棒グラフ、円グラフ等の多様なグラフタイプをサポートしている。

QMLでのグラフ描画の主な特徴を以下に示す。

  • 宣言的UI
    QMLの宣言的構文でグラフを簡潔に定義可能
  • リアルタイム更新
    データの動的更新に対応
  • インタラクティブ
    ズーム、パン、ツールチップなどのユーザ操作をサポート
  • カスタマイズ可能
    色、フォント、軸、凡例等を細かく設定可能
  • C++連携
    C++側でデータモデルを管理し、QMLで表示可能


Qt Chartsで利用可能な主なグラフタイプを以下に示す。

  • LineSeries
    折れ線グラフ
  • BarSeries
    棒グラフ (垂直)
  • HorizontalBarSeries
    棒グラフ (水平)
  • StackedBarSeries
    積み上げ棒グラフ
  • PieSeries
    円グラフ
  • ScatterSeries
    散布図
  • SplineSeries
    スプライン曲線グラフ
  • AreaSeries
    面グラフ



プロジェクト設定

CMakeLists.txtの設定

Qt6では、CMakeを使用したビルドシステムが推奨される。
Qt Chartsを使用するには、CMakeLists.txtに適切な設定を追加する必要がある。

 cmake_minimum_required(VERSION 3.16)
 
 project(ChartExample VERSION 1.0 LANGUAGES CXX)
 
 set(CMAKE_CXX_STANDARD 17)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
 set(CMAKE_AUTOMOC ON)
 set(CMAKE_AUTORCC ON)
 set(CMAKE_AUTOUIC ON)
 
 # Qt6パッケージの検索
 find_package(Qt6 REQUIRED COMPONENTS Core Gui Quick Charts)
 
 # 実行ファイルの作成
 qt_add_executable(ChartExample
    main.cpp
    chartdatamanager.h
    chartdatamanager.cpp
 )
 
 # QMLモジュールの追加
 qt_add_qml_module(ChartExample
    URI ChartExample
    VERSION 1.0
    QML_FILES
       main.qml
 )
 
 # ライブラリのリンク
 target_link_libraries(ChartExample PRIVATE
    Qt6::Core
    Qt6::Gui
    Qt6::Quick
    Qt6::Charts
 )
 
 # インストール設定
 install(TARGETS ChartExample
    BUNDLE DESTINATION .
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
 )


基本

Qt 6では、QQmlApplicationEngine クラスの使用方法が変更されている。
QML内でQt Chartsを使用するため、main.cppファイルでは以下に示すような初期化を行う。

 #include <QGuiApplication>
 #include <QQmlApplicationEngine>
 #include <QQmlContext>
 #include "chartdatamanager.h"
 
 int main(int argc, char *argv[])
 {
    QGuiApplication app(argc, argv);
 
    // データ管理クラスのインスタンス生成
    ChartDataManager dataManager;
 
    QQmlApplicationEngine engine;
 
    // QMLからC++オブジェクトにアクセスできるように登録
    engine.rootContext()->setContextProperty("dataManager", &dataManager);
 
    // Qt 6では、QUrlの指定方法が推奨される形式に変更
    const QUrl url(u"qrc:/qt/qml/ChartExample/main.qml"_qs);
 
    QObject::connect(
       &engine, &QQmlApplicationEngine::objectCreationFailed,
       &app, []() { QCoreApplication::exit(-1); },
       Qt::QueuedConnection);
 
    engine.load(url);
 
    return app.exec();
 }



折れ線グラフ

基本

Qt6のQMLで折れ線グラフを作成する基本的な方法を以下に示す。

※注意
Qt 6では、importステートメントでバージョン番号を省略できる。

ChartView要素の主要なプロパティを以下に示す。

  • title
    グラフのタイトル
  • legend.visible
    凡例の表示/非表示
  • antialiasing
    アンチエイリアシングの有効化
  • theme
    グラフのテーマ (ChartView.ChartThemeLight、ChartView.ChartThemeDark等)
  • animationOptions
    アニメーション設定 (ChartView.SeriesAnimations等)


ValueAxis要素の主要なプロパティを以下に示す。

  • min
    軸の最小値
  • max
    軸の最大値
  • tickCount
    目盛りの数
  • labelFormat
    ラベルのフォーマット (例: "%.1f")
  • titleText
    軸のタイトル


 import QtQuick
 import QtQuick.Window
 import QtCharts
 
 Window {
    visible: true
    width: 800
    height: 600
    title: "Line Chart Example"
 
    ChartView {
       id: chartView
       anchors.fill: parent
       title: "気温の推移"
       antialiasing: true
       theme: ChartView.ChartThemeLight
 
       // X軸 (時間軸)
       ValueAxis {
          id: axisX
          min: 0
          max: 24
          tickCount: 13
          labelFormat: "%d時"
          titleText: "時刻"
       }
 
       // Y軸 (温度軸)
       ValueAxis {
          id: axisY
          min: 0
          max: 35
          tickCount: 8
          labelFormat: "%.1f°C"
          titleText: "温度"
       }
 
       // 折れ線グラフ
       LineSeries {
          id: lineSeries
          name: "気温"
          axisX: axisX
          axisY: axisY
          color: "#FF5722"
          width: 2
 
          // データポイントの追加
          XYPoint { x: 0; y: 15.2 }
          XYPoint { x: 3; y: 13.8 }
          XYPoint { x: 6; y: 12.5 }
          XYPoint { x: 9; y: 18.3 }
          XYPoint { x: 12; y: 25.6 }
          XYPoint { x: 15; y: 28.9 }
          XYPoint { x: 18; y: 24.3 }
          XYPoint { x: 21; y: 19.7 }
          XYPoint { x: 24; y: 16.4 }
       }
    }
 }


複数の折れ線グラフ

複数のデータ系列を同時に表示することができる。

 import QtQuick
 import QtCharts
 
 ChartView {
    width: 800
    height: 600
    title: "複数データの比較"
    antialiasing: true
    legend.alignment: Qt.AlignBottom
 
    ValueAxis {
       id: axisX
       min: 0
       max: 10
       tickCount: 11
       titleText: "X軸"
    }
 
    ValueAxis {
       id: axisY
       min: 0
       max: 100
       tickCount: 11
       titleText: "Y軸"
    }
 
    // 1つ目の折れ線
    LineSeries {
       name: "データ A"
       axisX: axisX
       axisY: axisY
       color: "#2196F3"
       width: 2
 
       XYPoint { x: 0; y: 10 }
       XYPoint { x: 2; y: 30 }
       XYPoint { x: 4; y: 45 }
       XYPoint { x: 6; y: 65 }
       XYPoint { x: 8; y: 80 }
       XYPoint { x: 10; y: 90 }
    }
 
    // 2つ目の折れ線
    LineSeries {
       name: "データ B"
       axisX: axisX
       axisY: axisY
       color: "#FF5722"
       width: 2
 
       XYPoint { x: 0; y: 20 }
       XYPoint { x: 2; y: 25 }
       XYPoint { x: 4; y: 50 }
       XYPoint { x: 6; y: 60 }
       XYPoint { x: 8; y: 70 }
       XYPoint { x: 10; y: 85 }
    }
 
    // 3つ目の折れ線 (スプライン曲線)
    SplineSeries {
       name: "データ C (スプライン)"
       axisX: axisX
       axisY: axisY
       color: "#4CAF50"
       width: 2
 
       XYPoint { x: 0; y: 5 }
       XYPoint { x: 2; y: 40 }
       XYPoint { x: 4; y: 35 }
       XYPoint { x: 6; y: 55 }
       XYPoint { x: 8; y: 75 }
       XYPoint { x: 10; y: 95 }
    }
 }


C++からデータを動的に追加

C++ (バックエンド) 側でデータを管理して、Qt Chartsに反映することができる。

ChartDataManagerクラスのヘッダファイルを以下に示す。

 // ChartDataManager.hファイル
 
 #ifndef CHARTDATAMANAGER_H
 #define CHARTDATAMANAGER_H
 
 #include <QObject>
 #include <QPointF>
 #include <QVector>
 
 class ChartDataManager : public QObject
 {
    Q_OBJECT
 
 public:
    explicit ChartDataManager(QObject *parent = nullptr);
 
    // QMLから呼び出されるメソッド
    Q_INVOKABLE void addDataPoint(qreal x, qreal y);
    Q_INVOKABLE void clearData();
    Q_INVOKABLE QVector<QPointF> getData() const;
    Q_INVOKABLE void generateRandomData(int count);
 
 signals:
    // データが更新されたことをQMLに通知
    void dataChanged();
 
 private:
    QVector<QPointF> m_data;
 };
 
 #endif // CHARTDATAMANAGER_H


 // ChartDataManager.cppファイル
 
 #include <QRandomGenerator>
 #include "ChartDataManager.h"
 
 ChartDataManager::ChartDataManager(QObject *parent) : QObject(parent)
 {
 }
 
 void ChartDataManager::addDataPoint(qreal x, qreal y)
 {
    m_data.append(QPointF(x, y));
    emit dataChanged();
 }
 
 void ChartDataManager::clearData()
 {
    m_data.clear();
    emit dataChanged();
 }
 
 QVector<QPointF> ChartDataManager::getData() const
 {
    return m_data;
 }
 
 void ChartDataManager::generateRandomData(int count)
 {
    m_data.clear();
 
    for (int i = 0; i < count; ++i) {
       qreal x = i;
       qreal y = QRandomGenerator::global()->bounded(100.0);
       m_data.append(QPointF(x, y));
    }
 
    emit dataChanged();
 }


QMLでC++のデータを使用する例を以下に示す。

 import QtQuick
 import QtQuick.Controls
 import QtCharts
 
 Item {
    width: 800
    height: 600
 
    Column {
       anchors.fill: parent
       spacing: 10
 
       Row {
          spacing: 10
          padding: 10
 
          Button {
             text: "ランダムデータ生成"
             onClicked: {
                dataManager.generateRandomData(20)
                updateChart()
             }
          }
 
          Button {
             text: "クリア"
             onClicked: {
                dataManager.clearData()
                lineSeries.clear()
             }
          }
       }
 
       ChartView {
          id: chartView
          width: parent.width
          height: parent.height - 60
          title: "動的データグラフ"
          antialiasing: true
 
          ValueAxis {
             id: axisX
             min: 0
             max: 20
             tickCount: 11
             titleText: "X"
          }
 
          ValueAxis {
             id: axisY
             min: 0
             max: 100
             tickCount: 11
             titleText: "Y"
          }
 
          LineSeries {
             id: lineSeries
             name: "データ"
             axisX: axisX
             axisY: axisY
             color: "#2196F3"
             width: 2
          }
       }
    }
 
    function updateChart() {
       lineSeries.clear()
       var data = dataManager.getData()
 
       for (var i = 0; i < data.length; i++) {
          lineSeries.append(data[i].x, data[i].y)
       }
    }
 
    Connections {
       target: dataManager
       function onDataChanged() {
          updateChart()
       }
    }
 }



棒グラフ (BarChart)

基本

BarSeries要素の主要なプロパティを以下に示す。

  • labelsVisible
    バーのラベル表示
  • labelsFormat
    ラベルのフォーマット
  • labelsPosition
    ラベルの位置 (AbstractBarSeries.LabelsCenter等)
  • barWidth
    バーの幅 (0.0~1.0)


BarSet要素の主要なプロパティを以下に示す。

  • label
    データセットのラベル
  • color
    バーの色
  • borderColor
    バーの枠線の色
  • borderWidth
    バーの枠線の幅


 import QtQuick
 import QtCharts
 
 ChartView {
    width: 800
    height: 600
    title: "月別売上高"
    antialiasing: true
    legend.alignment: Qt.AlignBottom
 
    BarCategoryAxis {
       id: axisX
       categories: ["1月", "2月", "3月", "4月", "5月", "6月"]
    }
 
    ValueAxis {
       id: axisY
       min: 0
       max: 150
       tickCount: 6
       titleText: "売上 (万円)"
    }
 
    BarSeries {
       id: barSeries
       axisX: axisX
       axisY: axisY
       labelsVisible: true
       labelsFormat: "@value"
       labelsPosition: AbstractBarSeries.LabelsOutsideEnd
 
       BarSet {
          label: "2023年"
          color: "#2196F3"
          values: [85, 92, 78, 105, 118, 95]
       }
 
       BarSet {
          label: "2024年"
          color: "#FF5722"
          values: [95, 88, 102, 115, 128, 110]
       }
    }
 }


積み上げ棒グラフ

複数のデータを積み上げて表示することができる。

 import QtQuick
 import QtCharts
 
 ChartView {
    width: 800
    height: 600
    title: "四半期別売上構成"
    antialiasing: true
    legend.visible: true
 
    BarCategoryAxis {
       id: axisX
       categories: ["Q1", "Q2", "Q3", "Q4"]
    }
 
    ValueAxis {
       id: axisY
       min: 0
       max: 300
       tickCount: 7
       titleText: "売上 (万円)"
    }
 
    StackedBarSeries {
       id: stackedBarSeries
       axisX: axisX
       axisY: axisY
 
       BarSet {
          label: "製品A"
          color: "#2196F3"
          values: [60, 70, 65, 75]
       }
 
       BarSet {
          label: "製品B"
          color: "#FF5722"
          values: [50, 55, 60, 58]
       }
 
       BarSet {
          label: "製品C"
          color: "#4CAF50"
          values: [40, 45, 50, 52]
       }
    }
 }


水平棒グラフ

 import QtQuick
 import QtCharts
 
 ChartView {
    width: 800
    height: 600
    title: "製品別販売数"
    antialiasing: true
 
    ValueAxis {
       id: axisX
       min: 0
       max: 200
       tickCount: 5
       titleText: "販売数"
    }
 
    BarCategoryAxis {
       id: axisY
       categories: ["製品A", "製品B", "製品C", "製品D", "製品E"]
    }
 
    HorizontalBarSeries {
       id: horizontalBarSeries
       axisX: axisX
       axisY: axisY
       labelsVisible: true
 
       BarSet {
          label: "今月"
          color: "#2196F3"
          values: [120, 85, 150, 95, 110]
       }
 
       BarSet {
          label: "先月"
          color: "#9E9E9E"
          values: [110, 90, 140, 100, 105]
       }
    }
 }



その他のグラフタイプ

円グラフ (PieChart)

PieSeries要素の主要なプロパティを以下に示す。

  • holeSize
    中央の穴のサイズ
    0.0~1.0で指定する。
    ドーナツチャートを作成
  • size
    円グラフ全体のサイズ
    0.0~1.0で指定する。
  • startAngle
    開始角度
  • endAngle
    終了角度


PieSlice要素の主要なプロパティを以下に示す。

  • label
    スライスのラベル
  • value
    スライスの値
  • color
    スライスの色
  • exploded
    スライスを分離するかどうか
  • explodeDistanceFactor
    分離距離の倍率
  • labelVisible
    ラベルの表示 / 非表示
  • labelColor
    ラベルの色
  • percentage (読み取り専用)
    スライスの割合


 import QtQuick
 import QtCharts
 
 ChartView {
    width: 600
    height: 600
    title: "市場シェア"
    antialiasing: true
    legend.alignment: Qt.AlignRight
 
    PieSeries {
       id: pieSeries
       size: 0.7
 
       PieSlice {
          label: "製品A"
          value: 35
          color: "#2196F3"
          exploded: true
          explodeDistanceFactor: 0.05
          labelVisible: true
       }
 
       PieSlice {
          label: "製品B"
          value: 28
          color: "#FF5722"
       }
 
       PieSlice {
          label: "製品C"
          value: 20
          color: "#4CAF50"
       }
 
       PieSlice {
          label: "製品D"
          value: 12
          color: "#FFC107"
       }
 
       PieSlice {
          label: "その他"
          value: 5
          color: "#9E9E9E"
       }
    }
 }


ドーナツチャート

ドーナツチャートとは、中央に穴の開いた円グラフのことである。

 import QtQuick
 import QtCharts
 
 ChartView {
    width: 600
    height: 600
    title: "予算配分"
    antialiasing: true
 
    PieSeries {
       id: donutSeries
       size: 0.8
       holeSize: 0.4  // ドーナツの穴のサイズ
 
       PieSlice {
          label: "人件費 40%"
          value: 40
          color: "#2196F3"
          labelVisible: true
       }
 
       PieSlice {
          label: "設備費 25%"
          value: 25
          color: "#FF5722"
          labelVisible: true
       }
 
       PieSlice {
          label: "広告費 20%"
          value: 20
          color: "#4CAF50"
          labelVisible: true
       }
 
       PieSlice {
          label: "その他 15%"
          value: 15
          color: "#FFC107"
          labelVisible: true
       }
    }
 
    // 中央にテキストを表示
    Text {
       anchors.centerIn: parent
       text: "総予算\n1億円"
       font.pixelSize: 24
       font.bold: true
       horizontalAlignment: Text.AlignHCenter
    }
 }


散布図 (ScatterChart)

ScatterSeries要素の主要なプロパティを以下に示す。

  • markerSize
    マーカーのサイズ
  • markerShape
    マーカーの形状
    ScatterSeries.MarkerShapeCircleMarkerShapeRectangle
  • borderColor
    マーカーの枠線の色
  • borderWidth
    マーカーの枠線の幅


 import QtQuick
 import QtCharts
 
 ChartView {
    width: 800
    height: 600
    title: "身長と体重の相関"
    antialiasing: true
    legend.visible: true
 
    ValueAxis {
       id: axisX
       min: 150
       max: 190
       tickCount: 9
       titleText: "身長 (cm)"
    }
 
    ValueAxis {
       id: axisY
       min: 45
       max: 90
       tickCount: 10
       titleText: "体重 (kg)"
    }
 
    ScatterSeries {
       name: "男性"
       axisX: axisX
       axisY: axisY
       markerSize: 10
       color: "#2196F3"
       borderColor: "#1976D2"
       borderWidth: 2
 
       XYPoint { x: 168; y: 65 }
       XYPoint { x: 175; y: 72 }
       XYPoint { x: 172; y: 68 }
       XYPoint { x: 180; y: 78 }
       XYPoint { x: 165; y: 62 }
       XYPoint { x: 178; y: 75 }
       XYPoint { x: 170; y: 67 }
       XYPoint { x: 182; y: 80 }
    }
 
    ScatterSeries {
       name: "女性"
       axisX: axisX
       axisY: axisY
       markerSize: 10
       markerShape: ScatterSeries.MarkerShapeRectangle
       color: "#FF5722"
       borderColor: "#E64A19"
       borderWidth: 2
 
       XYPoint { x: 158; y: 52 }
       XYPoint { x: 162; y: 55 }
       XYPoint { x: 165; y: 58 }
       XYPoint { x: 160; y: 54 }
       XYPoint { x: 168; y: 60 }
       XYPoint { x: 163; y: 56 }
       XYPoint { x: 170; y: 62 }
       XYPoint { x: 155; y: 50 }
    }
 }


面グラフ (AreaChart)

AreaSeries要素の主要なプロパティを以下に示す。

  • upperSeries
    上側の境界線
  • lowerSeries
    下側の境界線
    デフォルトは 0
  • color
    面の色
  • borderColor
    境界線の色
  • borderWidth
    境界線の幅


 import QtQuick
 import QtCharts
 
 ChartView {
    width: 800
    height: 600
    title: "気温の変化範囲"
    antialiasing: true
 
    ValueAxis {
       id: axisX
       min: 0
       max: 12
       tickCount: 13
       labelFormat: "%d月"
       titleText: "月"
    }
 
    ValueAxis {
       id: axisY
       min: -5
       max: 35
       tickCount: 9
       labelFormat: "%.0f°C"
       titleText: "気温"
    }
 
    // 最高気温と最低気温の範囲を面グラフで表示
    AreaSeries {
       name: "気温範囲"
       axisX: axisX
       axisY: axisY
       color: "#80D4E8"
       borderColor: "#2196F3"
       borderWidth: 2
 
       upperSeries: LineSeries {
          // 最高気温
          XYPoint { x: 1; y: 10 }
          XYPoint { x: 2; y: 12 }
          XYPoint { x: 3; y: 16 }
          XYPoint { x: 4; y: 21 }
          XYPoint { x: 5; y: 25 }
          XYPoint { x: 6; y: 28 }
          XYPoint { x: 7; y: 32 }
          XYPoint { x: 8; y: 33 }
          XYPoint { x: 9; y: 28 }
          XYPoint { x: 10; y: 22 }
          XYPoint { x: 11; y: 16 }
          XYPoint { x: 12; y: 11 }
       }
 
       lowerSeries: LineSeries {
          // 最低気温
          XYPoint { x: 1; y: 2 }
          XYPoint { x: 2; y: 3 }
          XYPoint { x: 3; y: 6 }
          XYPoint { x: 4; y: 11 }
          XYPoint { x: 5; y: 16 }
          XYPoint { x: 6; y: 20 }
          XYPoint { x: 7; y: 24 }
          XYPoint { x: 8; y: 25 }
          XYPoint { x: 9; y: 20 }
          XYPoint { x: 10; y: 14 }
          XYPoint { x: 11; y: 8 }
          XYPoint { x: 12; y: 3 }
       }
    }
 }



リアルタイム更新とアニメーション

データのリアルタイム更新

タイマを使用してグラフをリアルタイムに更新することができる。

 import QtQuick
 import QtCharts
 
 Item {
     width: 800
     height: 600
 
     ChartView {
         id: chartView
         anchors.fill: parent
         title: "リアルタイムデータ"
         antialiasing: true
         animationOptions: ChartView.SeriesAnimations
 
         ValueAxis {
             id: axisX
             min: 0
             max: 50
             tickCount: 11
             titleText: "時間"
         }
 
         ValueAxis {
             id: axisY
             min: 0
             max: 100
             tickCount: 11
             titleText: "値"
         }
 
         LineSeries {
             id: lineSeries
             name: "センサー値"
             axisX: axisX
             axisY: axisY
             color: "#2196F3"
             width: 2
         }
     }
 
     Timer {
         id: updateTimer
         interval: 200  // 200ミリ秒ごとに更新
         running: true
         repeat: true
         
         property int counter: 0
         
         onTriggered: {
             // 新しいデータポイントを追加
             var newValue = Math.random() * 80 + 10
             lineSeries.append(counter, newValue)
             
             // データポイントが50を超えたら古いデータを削除
             if (lineSeries.count > 50) {
                 lineSeries.remove(0)
                 
                 // X軸をスクロール
                 axisX.min = counter - 49
                 axisX.max = counter + 1
             }
             
             counter++
         }
     }
 }


アニメーション設定

グラフのアニメーション効果を設定することができる。

ChartViewのanimationOptionsプロパティの値を以下に示す。

  • ChartView.NoAnimation
    アニメーションなし
  • ChartView.GridAxisAnimations
    グリッドと軸のアニメーション
  • ChartView.SeriesAnimations
    データ系列のアニメーション
  • ChartView.AllAnimations
    全てのアニメーション


 import QtQuick
 import QtQuick.Controls
 import QtCharts
 
 Item {
     width: 800
     height: 600
 
     Column {
         anchors.fill: parent
         spacing: 10
 
         Row {
             spacing: 10
             padding: 10
 
             Button {
                 text: "データ更新"
                 onClicked: {
                     updateData()
                 }
             }
 
             Button {
                 text: "アニメーション切替"
                 onClicked: {
                     if (chartView.animationOptions === ChartView.NoAnimation) {
                         chartView.animationOptions = ChartView.AllAnimations
                     } else {
                         chartView.animationOptions = ChartView.NoAnimation
                     }
                 }
             }
         }
 
         ChartView {
             id: chartView
             width: parent.width
             height: parent.height - 60
             title: "アニメーション付きグラフ"
             antialiasing: true
             animationOptions: ChartView.AllAnimations
             animationDuration: 1000  // アニメーション時間 (ミリ秒)
 
             ValueAxis {
                 id: axisX
                 min: 0
                 max: 10
                 tickCount: 11
             }
 
             ValueAxis {
                 id: axisY
                 min: 0
                 max: 100
                 tickCount: 11
             }
 
             LineSeries {
                 id: lineSeries
                 name: "データ"
                 axisX: axisX
                 axisY: axisY
                 color: "#2196F3"
                 width: 2
             }
         }
     }
 
     Component.onCompleted: {
         updateData()
     }
 
     function updateData() {
         lineSeries.clear()
         
         for (var i = 0; i <= 10; i++) {
             var value = Math.random() * 80 + 10
             lineSeries.append(i, value)
         }
     }
 }



インタラクティブ機能

ズームとパン

ChartViewの主要なインタラクティブメソッドを以下に示す。

  • zoomIn()
    ズームイン
  • zoomOut()
    ズームアウト
  • zoomReset()
    ズームをリセット
  • scrollLeft(pixels)
    左にスクロール
  • scrollRight(pixels)
    右にスクロール
  • scrollUp(pixels)
    上にスクロール
  • scrollDown(pixels)
    下にスクロール


 import QtQuick
 import QtQuick.Controls
 import QtCharts
 
 Item {
     width: 800
     height: 600
 
     Column {
         anchors.fill: parent
 
         Row {
             spacing: 10
             padding: 10
 
             Button {
                 text: "ズームイン"
                 onClicked: chartView.zoomIn()
             }
 
             Button {
                 text: "ズームアウト"
                 onClicked: chartView.zoomOut()
             }
 
             Button {
                 text: "リセット"
                 onClicked: chartView.zoomReset()
             }
         }
 
         ChartView {
             id: chartView
             width: parent.width
             height: parent.height - 60
             title: "インタラクティブグラフ"
             antialiasing: true
 
             // マウスホイールでズーム可能
             MouseArea {
                 anchors.fill: parent
                 acceptedButtons: Qt.NoButton
                 
                 onWheel: (wheel) => {
                     if (wheel.angleDelta.y > 0) {
                         chartView.zoomIn()
                     } else {
                         chartView.zoomOut()
                     }
                 }
             }
 
             ValueAxis {
                 id: axisX
                 min: 0
                 max: 100
                 tickCount: 11
             }
 
             ValueAxis {
                 id: axisY
                 min: 0
                 max: 100
                 tickCount: 11
             }
 
             LineSeries {
                 id: lineSeries
                 axisX: axisX
                 axisY: axisY
                 color: "#2196F3"
                 width: 2
             }
         }
     }
 
     Component.onCompleted: {
         // サンプルデータの生成
         for (var i = 0; i <= 100; i++) {
             var y = 50 + 40 * Math.sin(i * 0.1)
             lineSeries.append(i, y)
         }
     }
 }


データポイントのクリック検出

データポイントをクリックした時の処理を記述することができる。

 import QtQuick
 import QtQuick.Controls
 import QtCharts
 
 Item {
     width: 800
     height: 600
 
     ChartView {
         id: chartView
         anchors.fill: parent
         anchors.bottomMargin: 60
         title: "クリッカブルグラフ"
         antialiasing: true
 
         ValueAxis {
             id: axisX
             min: 0
             max: 10
             tickCount: 11
         }
 
         ValueAxis {
             id: axisY
             min: 0
             max: 100
             tickCount: 11
         }
 
         LineSeries {
             id: lineSeries
             axisX: axisX
             axisY: axisY
             color: "#2196F3"
             width: 2
             pointsVisible: true
 
             // ポイントがクリックされたときのシグナル
             onClicked: (point) => {
                 infoText.text = "クリックされたポイント: X=" + 
                                 point.x.toFixed(1) + ", Y=" + 
                                 point.y.toFixed(1)
             }
 
             // ポイントにマウスホバーしたときのシグナル
             onHovered: (point, state) => {
                 if (state) {
                     infoText.text = "ホバー中: X=" + 
                                     point.x.toFixed(1) + ", Y=" + 
                                     point.y.toFixed(1)
                 }
             }
 
             XYPoint { x: 0; y: 20 }
             XYPoint { x: 2; y: 45 }
             XYPoint { x: 4; y: 30 }
             XYPoint { x: 6; y: 75 }
             XYPoint { x: 8; y: 60 }
             XYPoint { x: 10; y: 85 }
         }
     }
 
     Rectangle {
         anchors.bottom: parent.bottom
         width: parent.width
         height: 60
         color: "#F5F5F5"
 
         Text {
             id: infoText
             anchors.centerIn: parent
             text: "グラフのポイントをクリックまたはホバーしてください"
             font.pixelSize: 16
         }
     }
 }



カスタマイズとスタイリング

グラフのテーマ

ChartViewで使用可能なテーマを以下に示す。

  • ChartView.ChartThemeLight
    明るいテーマ (デフォルト)
  • ChartView.ChartThemeBlueCerulean
    青色ベースのテーマ
  • ChartView.ChartThemeDark
    暗いテーマ
  • ChartView.ChartThemeBrownSand
    茶色ベースのテーマ
  • ChartView.ChartThemeBlueNcs
    NCS青色テーマ
  • ChartView.ChartThemeHighContrast
    高コントラストテーマ
  • ChartView.ChartThemeBlueIcy
    氷のような青色テーマ
  • ChartView.ChartThemeQt
    Qtテーマ


 import QtQuick
 import QtQuick.Controls
 import QtCharts
 
 Item {
     width: 800
     height: 600
 
     Column {
         anchors.fill: parent
 
         ComboBox {
             width: 200
             model: ["Light", "Dark", "Blue Cerulean", "Brown Sand", 
                     "High Contrast", "Blue Icy", "Qt"]
             
             onCurrentIndexChanged: {
                 switch(currentIndex) {
                     case 0: chartView.theme = ChartView.ChartThemeLight; break
                     case 1: chartView.theme = ChartView.ChartThemeDark; break
                     case 2: chartView.theme = ChartView.ChartThemeBlueCerulean; break
                     case 3: chartView.theme = ChartView.ChartThemeBrownSand; break
                     case 4: chartView.theme = ChartView.ChartThemeHighContrast; break
                     case 5: chartView.theme = ChartView.ChartThemeBlueIcy; break
                     case 6: chartView.theme = ChartView.ChartThemeQt; break
                 }
             }
         }
 
         ChartView {
             id: chartView
             width: parent.width
             height: parent.height - 50
             title: "テーマ切替"
             antialiasing: true
 
             ValueAxis {
                 id: axisX
                 min: 0
                 max: 10
             }
 
             ValueAxis {
                 id: axisY
                 min: 0
                 max: 100
             }
 
             LineSeries {
                 axisX: axisX
                 axisY: axisY
                 XYPoint { x: 0; y: 20 }
                 XYPoint { x: 2; y: 45 }
                 XYPoint { x: 4; y: 30 }
                 XYPoint { x: 6; y: 75 }
                 XYPoint { x: 8; y: 60 }
                 XYPoint { x: 10; y: 85 }
             }
         }
     }
 }


凡例のカスタマイズ

凡例の位置や外観をカスタマイズすることができる。

ChartView.legend要素の主要なプロパティを以下に示す。

  • visible
    凡例の表示/非表示
  • alignment
    凡例の位置 (Qt.AlignTop、Qt.AlignBottom、Qt.AlignLeft、Qt.AlignRight)
  • font
    フォント設定
  • color
    テキストの色
  • borderColor
    枠線の色
  • markerShape
    マーカーの形状


 import QtQuick
 import QtCharts
 
 ChartView {
     width: 800
     height: 600
     title: "カスタマイズされた凡例"
     antialiasing: true
     
     legend {
         visible: true
         alignment: Qt.AlignRight
         font.family: "Arial"
         font.pixelSize: 14
         font.bold: true
         color: "#333333"
         borderColor: "#2196F3"
         markerShape: Legend.MarkerShapeCircle
     }
 
     ValueAxis {
         id: axisX
         min: 0
         max: 10
     }
 
     ValueAxis {
         id: axisY
         min: 0
         max: 100
     }
 
     LineSeries {
         name: "系列 A"
         axisX: axisX
         axisY: axisY
         color: "#2196F3"
         XYPoint { x: 0; y: 20 }
         XYPoint { x: 5; y: 50 }
         XYPoint { x: 10; y: 80 }
     }
 
     LineSeries {
         name: "系列 B"
         axisX: axisX
         axisY: axisY
         color: "#FF5722"
         XYPoint { x: 0; y: 40 }
         XYPoint { x: 5; y: 60 }
         XYPoint { x: 10; y: 70 }
     }
 }


軸のカスタマイズ

軸の外観を詳細にカスタマイズすることができる。

ValueAxisの詳細プロパティを以下に示す。

  • gridVisible
    グリッド線の表示/非表示
  • gridLineColor
    グリッド線の色
  • minorGridVisible
    補助グリッド線の表示/非表示
  • labelsVisible
    ラベルの表示/非表示
  • labelsFont
    ラベルのフォント
  • labelsColor
    ラベルの色
  • labelsAngle
    ラベルの回転角度
  • lineVisible
    軸線の表示/非表示
  • color
    軸線の色
  • titleFont
    タイトルのフォント
  • titleVisible
    タイトルの表示/非表示


 import QtQuick
 import QtCharts
 
 ChartView {
     width: 800
     height: 600
     title: "カスタマイズされた軸"
     antialiasing: true
     backgroundColor: "#FFFFFF"
     plotAreaColor: "#F9F9F9"
 
     ValueAxis {
         id: axisX
         min: 0
         max: 100
         tickCount: 11
         titleText: "X軸タイトル"
         titleFont.pixelSize: 16
         titleFont.bold: true
         titleVisible: true
         
         labelsFont.pixelSize: 12
         labelsColor: "#333333"
         labelsVisible: true
         
         gridVisible: true
         gridLineColor: "#E0E0E0"
         minorGridVisible: true
         minorGridLineColor: "#F0F0F0"
         
         lineVisible: true
         color: "#2196F3"
     }
 
     ValueAxis {
         id: axisY
         min: 0
         max: 100
         tickCount: 11
         titleText: "Y軸タイトル"
         titleFont.pixelSize: 16
         titleFont.bold: true
         titleVisible: true
         
         labelsFont.pixelSize: 12
         labelsColor: "#333333"
         labelsAngle: -45  // ラベルを45度回転
         
         gridVisible: true
         gridLineColor: "#E0E0E0"
         
         lineVisible: true
         color: "#FF5722"
     }
 
     LineSeries {
         axisX: axisX
         axisY: axisY
         color: "#4CAF50"
         width: 3
         
         XYPoint { x: 0; y: 10 }
         XYPoint { x: 20; y: 35 }
         XYPoint { x: 40; y: 55 }
         XYPoint { x: 60; y: 45 }
         XYPoint { x: 80; y: 70 }
         XYPoint { x: 100; y: 90 }
     }
 }



その他のサンプルコード

リアルタイムセンサダッシュボード

複数のグラフを組み合わせたダッシュボードの例を以下に示す。

このサンプルの特徴を以下に示す。

  • 複数グラフの同時表示
    GridLayoutで複数のグラフを配置
  • リアルタイム更新
    Timerで定期的にデータを更新
  • C++連携
    データ管理をC++側で実装


C++側のデータマネージャ拡張を以下に示す。

 // chartdatamanager.h
 #ifndef CHARTDATAMANAGER_H
 #define CHARTDATAMANAGER_H
 
 #include <QObject>
 #include <QPointF>
 #include <QVector>
 #include <QTimer>
 
 class ChartDataManager : public QObject
 {
     Q_OBJECT
     Q_PROPERTY(double currentTemperature READ currentTemperature NOTIFY temperatureChanged)
     Q_PROPERTY(double currentHumidity READ currentHumidity NOTIFY humidityChanged)
     Q_PROPERTY(double currentPressure READ currentPressure NOTIFY pressureChanged)
 
 public:
     explicit ChartDataManager(QObject *parent = nullptr);
     
     double currentTemperature() const { return m_currentTemperature; }
     double currentHumidity() const { return m_currentHumidity; }
     double currentPressure() const { return m_currentPressure; }
 
     Q_INVOKABLE QVector<QPointF> getTemperatureData() const;
     Q_INVOKABLE QVector<QPointF> getHumidityData() const;
     Q_INVOKABLE QVector<QPointF> getPressureData() const;
     
     Q_INVOKABLE void startSimulation();
     Q_INVOKABLE void stopSimulation();
 
 signals:
     void temperatureChanged();
     void humidityChanged();
     void pressureChanged();
     void dataUpdated();
 
 private slots:
     void updateSensorData();
 
 private:
     QVector<QPointF> m_temperatureData;
     QVector<QPointF> m_humidityData;
     QVector<QPointF> m_pressureData;
     
     double m_currentTemperature;
     double m_currentHumidity;
     double m_currentPressure;
     
     QTimer *m_updateTimer;
     int m_timeCounter;
     
     static constexpr int MAX_DATA_POINTS = 100;
 };
 
 #endif // CHARTDATAMANAGER_H


 // chartdatamanager.cpp
 #include "chartdatamanager.h"
 #include <QRandomGenerator>
 #include <QtMath>
 
 ChartDataManager::ChartDataManager(QObject *parent)
     : QObject(parent)
     , m_currentTemperature(25.0)
     , m_currentHumidity(50.0)
     , m_currentPressure(1013.0)
     , m_timeCounter(0)
 {
     m_updateTimer = new QTimer(this);
     connect(m_updateTimer, &QTimer::timeout, this, &ChartDataManager::updateSensorData);
 }
 
 void ChartDataManager::startSimulation()
 {
     m_updateTimer->start(100);  // 100msごとに更新
 }
 
 void ChartDataManager::stopSimulation()
 {
     m_updateTimer->stop();
 }
 
 void ChartDataManager::updateSensorData()
 {
     // センサーデータをシミュレート
     double time = m_timeCounter * 0.1;
     
     // 温度: 20-30度の範囲で変動
     m_currentTemperature = 25.0 + 5.0 * qSin(time * 0.5) + 
                            (QRandomGenerator::global()->bounded(200) - 100) / 100.0;
     
     // 湿度: 40-60%の範囲で変動
     m_currentHumidity = 50.0 + 10.0 * qCos(time * 0.3) + 
                         (QRandomGenerator::global()->bounded(200) - 100) / 100.0;
     
     // 気圧: 1000-1020hPaの範囲で変動
     m_currentPressure = 1013.0 + 7.0 * qSin(time * 0.2) + 
                         (QRandomGenerator::global()->bounded(200) - 100) / 100.0;
     
     // データポイントを追加
     m_temperatureData.append(QPointF(m_timeCounter, m_currentTemperature));
     m_humidityData.append(QPointF(m_timeCounter, m_currentHumidity));
     m_pressureData.append(QPointF(m_timeCounter, m_currentPressure));
     
     // データポイントが多すぎる場合は古いものを削除
     if (m_temperatureData.size() > MAX_DATA_POINTS) {
         m_temperatureData.removeFirst();
         m_humidityData.removeFirst();
         m_pressureData.removeFirst();
     }
     
     m_timeCounter++;
     
     emit temperatureChanged();
     emit humidityChanged();
     emit pressureChanged();
     emit dataUpdated();
 }
 
 QVector<QPointF> ChartDataManager::getTemperatureData() const
 {
     return m_temperatureData;
 }
 
 QVector<QPointF> ChartDataManager::getHumidityData() const
 {
     return m_humidityData;
 }
 
 QVector<QPointF> ChartDataManager::getPressureData() const
 {
     return m_pressureData;
 }


QML側のダッシュボード実装を以下に示す。

 import QtQuick
 import QtQuick.Controls
 import QtQuick.Layouts
 import QtCharts
 
 ApplicationWindow {
     visible: true
     width: 1200
     height: 800
     title: "センサーダッシュボード"
 
     header: ToolBar {
         RowLayout {
             anchors.fill: parent
             
             Label {
                 text: "リアルタイムセンサーモニタリング"
                 font.pixelSize: 18
                 font.bold: true
                 Layout.leftMargin: 10
             }
             
             Item { Layout.fillWidth: true }
             
             Button {
                 text: simulationRunning ? "停止" : "開始"
                 onClicked: {
                     if (simulationRunning) {
                         dataManager.stopSimulation()
                     } else {
                         dataManager.startSimulation()
                     }
                     simulationRunning = !simulationRunning
                 }
             }
         }
     }
 
     property bool simulationRunning: false
 
     GridLayout {
         anchors.fill: parent
         anchors.margins: 10
         columns: 2
         rows: 2
         rowSpacing: 10
         columnSpacing: 10
 
         // 温度グラフ
         ChartView {
             id: temperatureChart
             Layout.fillWidth: true
             Layout.fillHeight: true
             title: "温度 (°C) - 現在: " + dataManager.currentTemperature.toFixed(1) + "°C"
             antialiasing: true
             legend.visible: false
 
             ValueAxis {
                 id: tempAxisX
                 min: 0
                 max: 100
                 visible: false
             }
 
             ValueAxis {
                 id: tempAxisY
                 min: 15
                 max: 35
                 tickCount: 5
                 labelFormat: "%.1f"
             }
 
             LineSeries {
                 id: tempSeries
                 axisX: tempAxisX
                 axisY: tempAxisY
                 color: "#FF5722"
                 width: 2
             }
         }
 
         // 湿度グラフ
         ChartView {
             id: humidityChart
             Layout.fillWidth: true
             Layout.fillHeight: true
             title: "湿度 (%) - 現在: " + dataManager.currentHumidity.toFixed(1) + "%"
             antialiasing: true
             legend.visible: false
 
             ValueAxis {
                 id: humidAxisX
                 min: 0
                 max: 100
                 visible: false
             }
 
             ValueAxis {
                 id: humidAxisY
                 min: 30
                 max: 70
                 tickCount: 5
                 labelFormat: "%.1f"
             }
 
             LineSeries {
                 id: humidSeries
                 axisX: humidAxisX
                 axisY: humidAxisY
                 color: "#2196F3"
                 width: 2
             }
         }
 
         // 気圧グラフ
         ChartView {
             id: pressureChart
             Layout.fillWidth: true
             Layout.fillHeight: true
             Layout.columnSpan: 2
             title: "気圧 (hPa) - 現在: " + dataManager.currentPressure.toFixed(1) + " hPa"
             antialiasing: true
             legend.visible: false
 
             ValueAxis {
                 id: pressAxisX
                 min: 0
                 max: 100
                 titleText: "時間"
             }
 
             ValueAxis {
                 id: pressAxisY
                 min: 1000
                 max: 1025
                 tickCount: 6
                 labelFormat: "%.1f"
             }
 
             LineSeries {
                 id: pressSeries
                 axisX: pressAxisX
                 axisY: pressAxisY
                 color: "#4CAF50"
                 width: 2
             }
         }
     }
 
     Connections {
         target: dataManager
         
         function onDataUpdated() {
             updateAllCharts()
         }
     }
 
     function updateAllCharts() {
         // 温度データを更新
         tempSeries.clear()
         var tempData = dataManager.getTemperatureData()
         for (var i = 0; i < tempData.length; i++) {
             tempSeries.append(tempData[i].x, tempData[i].y)
         }
         
         // 湿度データを更新
         humidSeries.clear()
         var humidData = dataManager.getHumidityData()
         for (var i = 0; i < humidData.length; i++) {
             humidSeries.append(humidData[i].x, humidData[i].y)
         }
         
         // 気圧データを更新
         pressSeries.clear()
         var pressData = dataManager.getPressureData()
         for (var i = 0; i < pressData.length; i++) {
             pressSeries.append(pressData[i].x, pressData[i].y)
         }
         
         // X軸を更新 (スクロール効果)
         if (tempData.length > 0) {
             var minX = Math.max(0, tempData[tempData.length - 1].x - 100)
             var maxX = Math.max(100, tempData[tempData.length - 1].x)
             
             tempAxisX.min = minX
             tempAxisX.max = maxX
             humidAxisX.min = minX
             humidAxisX.max = maxX
             pressAxisX.min = minX
             pressAxisX.max = maxX
         }
     }
 }



パフォーマンスの最適化

大量データの処理

パフォーマンス最適化のポイントを以下に示す。

  • データの間引き
    表示に必要な解像度に合わせてデータを間引く
  • 非同期更新
    データ更新を非同期的に処理する
  • アニメーションの無効化
    大量データの場合はアニメーションを無効化
  • OpenGLの使用
    ハードウェアアクセラレーションを有効化


 // chartdatamanager.h に追加
 Q_INVOKABLE QVector<QPointF> getDecimatedData(int maxPoints = 1000) const;
 
 // chartdatamanager.cpp に追加
 QVector<QPointF> ChartDataManager::getDecimatedData(int maxPoints) const
 {
     if (m_data.size() <= maxPoints) {
         return m_data;
     }
     
     QVector<QPointF> decimated;
     double step = static_cast<double>(m_data.size()) / maxPoints;
     
     for (int i = 0; i < maxPoints; ++i) {
         int index = static_cast<int>(i * step);
         decimated.append(m_data[index]);
     }
     
     return decimated;
 }


 import QtQuick
 import QtCharts
 
 ChartView {
     id: chartView
     width: 800
     height: 600
     title: "大量データグラフ (最適化済み)"
     antialiasing: true
     animationOptions: ChartView.NoAnimation  // アニメーション無効化
     
     // OpenGLバックエンドの使用
     renderType: ChartView.RenderOpenGL
 
     ValueAxis {
         id: axisX
         min: 0
         max: 10000
     }
 
     ValueAxis {
         id: axisY
         min: 0
         max: 100
     }
 
     LineSeries {
         id: lineSeries
         axisX: axisX
         axisY: axisY
         useOpenGL: true  // OpenGL描画を有効化
     }
 
     function updateChartOptimized() {
         lineSeries.clear()
         
         // データを間引いて取得 (最大1000ポイント)
         var data = dataManager.getDecimatedData(1000)
         
         // バッチ更新
         for (var i = 0; i < data.length; i++) {
             lineSeries.append(data[i].x, data[i].y)
         }
     }
 }


メモリ管理

効率的なメモリ管理の実装例を以下に示す。

 // chartdatamanager.h に追加
 Q_INVOKABLE void setMaxDataPoints(int maxPoints);
 Q_INVOKABLE void clearOldData(int keepLastN);
 
 // chartdatamanager.cpp に追加
 void ChartDataManager::setMaxDataPoints(int maxPoints)
 {
     while (m_data.size() > maxPoints) {
         m_data.removeFirst();
     }
 }
 
 void ChartDataManager::clearOldData(int keepLastN)
 {
     if (m_data.size() > keepLastN) {
         m_data = m_data.mid(m_data.size() - keepLastN);
         emit dataChanged();
     }
 }



トラブルシューティング

グラフが表示されない

CMakeLists.txt または .proファイルにおいて、Qt Chartsが正しくリンクされているかどうかを確認する。

また、QMLファイル内のimportステートメントが正しいかどうかを確認する。(Qt 6では、import QtCharts)

データが更新されない

シグナル / スロット接続が正しいかどうかを確認する。
QMLのConnectionsブロックが正しく設定されているかどうかを確認する。

パフォーマンスが悪い

  • animationOptionsを無効化する。
  • OpenGLレンダリングを有効化する。(useOpenGL: true)
  • データの間引きを行う。


軸のラベルが重なる

  • tickCountプロパティを調整する。
  • labelsAngleプロパティでラベルを回転させる。
  • labelFormatプロパティで表示形式を短縮する。


メモリリーク

  • 不要になったデータポイントを定期的に削除する。
  • clearメソッドを適切に呼び出す。



Qt Chartsのデバッグ

グラフのデバッグに有用な方法を以下に示す。

 import QtQuick
 import QtCharts
 
 ChartView {
    id: chartView
 
    // データの状態を確認
    Component.onCompleted: {
       console.log("Chart created")
       console.log("Series count:", count)
    }
 
    LineSeries {
       id: lineSeries
 
       onPointAdded: (index) => {
          console.log("Point added at index:", index)
          console.log("Total points:", count)
       }
 
       onPointRemoved: (index) => {
          console.log("Point removed at index:", index)
       }
 
       onClicked: (point) => {
          console.log("Point clicked:", point.x, point.y)
       }
    }
 
    // データ更新時のログ
    function updateData() {
       console.log("Updating data...")
       var startTime = Date.now()
 
       // データ更新処理
       var endTime = Date.now()
       console.log("Update took:", endTime - startTime, "ms")
    }
 }