MochiuWiki : SUSE, EC, PCB
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
QMLのコントロール - グラフのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
QMLのコントロール - グラフ
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == QMLを使用したグラフ描画は、Qt Chartsモジュールを使用して実現することができる。<br> Qt Chartsは、折れ線グラフ、棒グラフ、円グラフ等の多様なグラフタイプをサポートしている。<br> <br> QMLでのグラフ描画の主な特徴を以下に示す。<br> * 宣言的UI *: QMLの宣言的構文でグラフを簡潔に定義可能 * リアルタイム更新 *: データの動的更新に対応 * インタラクティブ *: ズーム、パン、ツールチップなどのユーザ操作をサポート * カスタマイズ可能 *: 色、フォント、軸、凡例等を細かく設定可能 * C++連携 *: C++側でデータモデルを管理し、QMLで表示可能 <br> Qt Chartsで利用可能な主なグラフタイプを以下に示す。<br> * LineSeries *: 折れ線グラフ * BarSeries *: 棒グラフ (垂直) * HorizontalBarSeries *: 棒グラフ (水平) * StackedBarSeries *: 積み上げ棒グラフ * PieSeries *: 円グラフ * ScatterSeries *: 散布図 * SplineSeries *: スプライン曲線グラフ * AreaSeries *: 面グラフ <br><br> == プロジェクト設定 == ==== CMakeLists.txtの設定 ==== Qt6では、CMakeを使用したビルドシステムが推奨される。<br> Qt Chartsを使用するには、CMakeLists.txtに適切な設定を追加する必要がある。<br> <br> <syntaxhighlight lang="cmake"> 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} ) </syntaxhighlight> <br> ==== 基本 ==== Qt 6では、<code>QQmlApplicationEngine</code> クラスの使用方法が変更されている。<br> QML内でQt Chartsを使用するため、main.cppファイルでは以下に示すような初期化を行う。<br> <br> <syntaxhighlight lang="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(); } </syntaxhighlight> <br><br> == 折れ線グラフ == ==== 基本 ==== Qt6のQMLで折れ線グラフを作成する基本的な方法を以下に示す。<br> <br> <u>※注意</u><br> <u>Qt 6では、importステートメントでバージョン番号を省略できる。</u><br> <br> ChartView要素の主要なプロパティを以下に示す。<br> * title *: グラフのタイトル * legend.visible *: 凡例の表示/非表示 * antialiasing *: アンチエイリアシングの有効化 * theme *: グラフのテーマ (ChartView.ChartThemeLight、ChartView.ChartThemeDark等) * animationOptions *: アニメーション設定 (ChartView.SeriesAnimations等) <br> ValueAxis要素の主要なプロパティを以下に示す。<br> * min *: 軸の最小値 * max *: 軸の最大値 * tickCount *: 目盛りの数 * labelFormat *: ラベルのフォーマット (例: "%.1f") * titleText *: 軸のタイトル <br> <syntaxhighlight lang="qml"> 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 } } } } </syntaxhighlight> <br> ==== 複数の折れ線グラフ ==== 複数のデータ系列を同時に表示することができる。<br> <br> <syntaxhighlight lang="qml"> 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 } } } </syntaxhighlight> <br> ==== C++からデータを動的に追加 ==== C++ (バックエンド) 側でデータを管理して、Qt Chartsに反映することができる。<br> <br> ChartDataManagerクラスのヘッダファイルを以下に示す。<br> <br> <syntaxhighlight lang="cpp"> // 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 </syntaxhighlight> <br> <syntaxhighlight lang="cpp"> // 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(); } </syntaxhighlight> <br> QMLでC++のデータを使用する例を以下に示す。<br> <br> <syntaxhighlight lang="qml"> 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() } } } </syntaxhighlight> <br><br> == 棒グラフ (BarChart) == ==== 基本 ==== BarSeries要素の主要なプロパティを以下に示す。<br> * labelsVisible *: バーのラベル表示 * labelsFormat *: ラベルのフォーマット * labelsPosition *: ラベルの位置 (AbstractBarSeries.LabelsCenter等) * barWidth *: バーの幅 (0.0~1.0) <br> BarSet要素の主要なプロパティを以下に示す。<br> * label *: データセットのラベル * color *: バーの色 * borderColor *: バーの枠線の色 * borderWidth *: バーの枠線の幅 <br> <syntaxhighlight lang="qml"> 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] } } } </syntaxhighlight> <br> ==== 積み上げ棒グラフ ==== 複数のデータを積み上げて表示することができる。<br> <br> <syntaxhighlight lang="qml"> 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] } } } </syntaxhighlight> <br> ==== 水平棒グラフ ==== <syntaxhighlight lang="qml"> 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] } } } </syntaxhighlight> <br><br> == その他のグラフタイプ == ==== 円グラフ (PieChart) ==== PieSeries要素の主要なプロパティを以下に示す。<br> * holeSize *: 中央の穴のサイズ (0.0~1.0、ドーナツチャートを作成) * size *: 円グラフ全体のサイズ (0.0~1.0) * startAngle *: 開始角度 * endAngle *: 終了角度 <br> PieSlice要素の主要なプロパティを以下に示す。<br> * label *: スライスのラベル * value *: スライスの値 * color *: スライスの色 * exploded *: スライスを分離するかどうか * explodeDistanceFactor *: 分離距離の倍率 * labelVisible *: ラベルの表示/非表示 * labelColor *: ラベルの色 * percentage *: スライスの割合 (読み取り専用) <br> <syntaxhighlight lang="qml"> 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" } } } </syntaxhighlight> <br> ==== ドーナツチャート ==== ドーナツチャートとは、中央に穴の開いた円グラフのことである。<br> <br> <syntaxhighlight lang="qml"> 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 } } </syntaxhighlight> <br> ==== 散布図 (ScatterChart) ==== ScatterSeries要素の主要なプロパティを以下に示す。<br> * markerSize *: マーカーのサイズ * markerShape *: マーカーの形状 (ScatterSeries.MarkerShapeCircle、MarkerShapeRectangle等) * borderColor *: マーカーの枠線の色 * borderWidth *: マーカーの枠線の幅 <br> <syntaxhighlight lang="qml"> 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 } } } </syntaxhighlight> <br> ==== 面グラフ (AreaChart) ==== AreaSeries要素の主要なプロパティを以下に示す。<br> * upperSeries *: 上側の境界線 * lowerSeries *: 下側の境界線 (省略時は0) * color *: 面の色 * borderColor *: 境界線の色 * borderWidth *: 境界線の幅 <br> <syntaxhighlight lang="qml"> 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 } } } } </syntaxhighlight> <br><br> == リアルタイム更新とアニメーション == ==== データのリアルタイム更新 ==== タイマを使用してグラフをリアルタイムに更新することができる。<br> <br> <syntaxhighlight lang="qml"> 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++ } } } </syntaxhighlight> <br> ==== アニメーション設定 ==== グラフのアニメーション効果を設定することができる。<br> <br> ChartViewのanimationOptionsプロパティの値を以下に示す。<br> * ChartView.NoAnimation *: アニメーションなし * ChartView.GridAxisAnimations *: グリッドと軸のアニメーション * ChartView.SeriesAnimations *: データ系列のアニメーション * ChartView.AllAnimations *: 全てのアニメーション <br> <syntaxhighlight lang="qml"> 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) } } } </syntaxhighlight> <br><br> == インタラクティブ機能 == ==== ズームとパン ==== ChartViewの主要なインタラクティブメソッドを以下に示す。<br> * zoomIn() *: ズームイン * zoomOut() *: ズームアウト * zoomReset() *: ズームをリセット * scrollLeft(pixels) *: 左にスクロール * scrollRight(pixels) *: 右にスクロール * scrollUp(pixels) *: 上にスクロール * scrollDown(pixels) *: 下にスクロール <br> <syntaxhighlight lang="qml"> 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) } } } </syntaxhighlight> <br> ==== データポイントのクリック検出 ==== データポイントをクリックした時の処理を記述することができる。<br> <br> <syntaxhighlight lang="qml"> 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 } } } </syntaxhighlight> <br><br> == カスタマイズとスタイリング == ==== グラフのテーマ ==== ChartViewで使用可能なテーマを以下に示す。<br> <br> * ChartView.ChartThemeLight *: 明るいテーマ (デフォルト) * ChartView.ChartThemeBlueCerulean *: 青色ベースのテーマ * ChartView.ChartThemeDark *: 暗いテーマ * ChartView.ChartThemeBrownSand *: 茶色ベースのテーマ * ChartView.ChartThemeBlueNcs *: NCS青色テーマ * ChartView.ChartThemeHighContrast *: 高コントラストテーマ * ChartView.ChartThemeBlueIcy *: 氷のような青色テーマ * ChartView.ChartThemeQt *: Qtテーマ <br> <syntaxhighlight lang="qml"> 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 } } } } } </syntaxhighlight> <br> ==== 凡例のカスタマイズ ==== 凡例の位置や外観をカスタマイズすることができる。<br> <br> ChartView.legend要素の主要なプロパティを以下に示す。<br> * visible *: 凡例の表示/非表示 * alignment *: 凡例の位置 (Qt.AlignTop、Qt.AlignBottom、Qt.AlignLeft、Qt.AlignRight) * font *: フォント設定 * color *: テキストの色 * borderColor *: 枠線の色 * markerShape *: マーカーの形状 <br> <syntaxhighlight lang="qml"> 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 } } } </syntaxhighlight> <br> ==== 軸のカスタマイズ ==== 軸の外観を詳細にカスタマイズすることができる。<br> <br> ValueAxisの詳細プロパティを以下に示す。<br> * gridVisible *: グリッド線の表示/非表示 * gridLineColor *: グリッド線の色 * minorGridVisible *: 補助グリッド線の表示/非表示 * labelsVisible *: ラベルの表示/非表示 * labelsFont *: ラベルのフォント * labelsColor *: ラベルの色 * labelsAngle *: ラベルの回転角度 * lineVisible *: 軸線の表示/非表示 * color *: 軸線の色 * titleFont *: タイトルのフォント * titleVisible *: タイトルの表示/非表示 <br> <syntaxhighlight lang="qml"> 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 } } } </syntaxhighlight> <br><br> == その他のサンプルコード == ==== リアルタイムセンサダッシュボード ==== 複数のグラフを組み合わせたダッシュボードの例を以下に示す。<br> <br> このサンプルの特徴を以下に示す。<br> * 複数グラフの同時表示 *: GridLayoutで複数のグラフを配置 * リアルタイム更新 *: Timerで定期的にデータを更新 * C++連携 *: データ管理をC++側で実装 <br> C++側のデータマネージャ拡張を以下に示す。<br> <br> <syntaxhighlight lang="cpp"> // 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 </syntaxhighlight> <br> <syntaxhighlight lang="cpp"> // 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; } </syntaxhighlight> <br> QML側のダッシュボード実装を以下に示す。<br> <br> <syntaxhighlight lang="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 } } } </syntaxhighlight> <br><br> == パフォーマンスの最適化 == ==== 大量データの処理 ==== パフォーマンス最適化のポイントを以下に示す。<br> * データの間引き *: 表示に必要な解像度に合わせてデータを間引く * 非同期更新 *: データ更新を非同期的に処理する * アニメーションの無効化 *: 大量データの場合はアニメーションを無効化 * OpenGLの使用 *: ハードウェアアクセラレーションを有効化 <br> <syntaxhighlight lang="cpp"> // 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; } </syntaxhighlight> <br> <syntaxhighlight lang="qml"> 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) } } } </syntaxhighlight> <br> ==== メモリ管理 ==== 効率的なメモリ管理の実装例を以下に示す。<br> <br> <syntaxhighlight lang="cpp"> // 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(); } } </syntaxhighlight> <br><br> == トラブルシューティング == ==== グラフが表示されない ==== CMakeLists.txt または .proファイルにおいて、Qt Chartsが正しくリンクされているかどうかを確認する。<br> <br> また、QMLファイル内のimportステートメントが正しいかどうかを確認する。(Qt 6では、<code>import QtCharts</code>)<br> <br> ==== データが更新されない ==== シグナル / スロット接続が正しいかどうかを確認する。<br> QMLのConnectionsブロックが正しく設定されているかどうかを確認する。<br> <br> ==== パフォーマンスが悪い ==== * animationOptionsを無効化する。 * OpenGLレンダリングを有効化する。(useOpenGL: true) * データの間引きを行う。 <br> ==== 軸のラベルが重なる ==== * tickCountプロパティを調整する。 * labelsAngleプロパティでラベルを回転させる。 * labelFormatプロパティで表示形式を短縮する。 <br> ==== メモリリーク ==== * 不要になったデータポイントを定期的に削除する。 * clearメソッドを適切に呼び出す。 <br><br> == Qt Chartsのデバッグ == グラフのデバッグに有用な方法を以下に示す。<br> <br> <syntaxhighlight lang="qml"> 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") } } </syntaxhighlight> <br><br> __FORCETOC__ [[カテゴリ:Qt]]
QMLのコントロール - グラフ
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse