QMLのコントロール - グラフ
概要
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.MarkerShapeCircle、MarkerShapeRectangle等
- 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")
}
}