Qtライブラリ - Qwt
概要
Qwt(Qt Widget for Technical Application)は、Qtの拡張ライブラリであり、プロット、ダイアル、メータ等のウィジェットを提供する。
ここでは、Qwtのインストールと使用方法を記載する。
Qwtのインストール
Qwtのビルドおよびコンパイルに必要な依存関係のライブラリをインストールする。
これらライブラリは、ビルドおよびコンパイル時のみ必要である。
sudo zypper install Mesa-devel Mesa-KHR-devel
Qwtの公式Webサイトから最新のQwtをダウンロードする。
ダウンロードしたQwtを解凍するため、以下のコマンドを実行する。
tar xf qwt-x.x.x.tar.bz2 cd qwt-x.x.x
または、git clone
を使用して、Qwtのソースコードをダウンロードする。
git clone https://git.code.sf.net/p/qwt/git -b qwt-x.x qwt cd qwt
解凍したQwtディレクトリに移動して、qwtconfig.priファイルを以下のように編集する。
vi qwtconfig.pri
qwtconfig.priファイル # 変更前 unix { QWT_INSTALL_PREFIX = /usr/local/qwt-$$QWT_VERSION # QWT_INSTALL_PREFIX = /usr/local/qwt-$$QWT_VERSION-qt-$$QT_VERSION } win32 { QWT_INSTALL_PREFIX = C:/Qwt-$$QWT_VERSION # QWT_INSTALL_PREFIX = C:/Qwt-$$QWT_VERSION-qt-$$QT_VERSION } # 変更後 unix { QWT_INSTALL_PREFIX = <Qtのインストールディレクトリ>/Qwt-$$QWT_VERSION # QWT_INSTALL_PREFIX = /usr/local/qwt-$$QWT_VERSION-qt-$$QT_VERSION } win32 { QWT_INSTALL_PREFIX = C:/Qwt-$$QWT_VERSION # QWT_INSTALL_PREFIX = C:/Qwt-$$QWT_VERSION-qt-$$QT_VERSION }
ビルドディレクトリを作成する。
mkdir build && cd build
Qwtをビルドおよびインストールする。
qmake ../qwt.pro make -j $(nproc) make install
<Qtのインストールディレクトリ>/Qwt-x.x.x/plugins/designer/libqwt_designer_plugin.soファイルを、以下のディレクトリにコピーする。
- Qt Creatorに統合されているQt Designer
- <Qtのインストールディレクトリ>/Tools/QtCreator/lib/Qt/plugins/designerディレクトリ
- 正常に読み込まれたプラグインおよび読み込みに失敗したプラグインを確認するには、
- [ツール] - [フォームエディター] - [QtDesignerプラグインについて]を選択する。
- スタンドアロンのQt Designer
- <Qtのインストールディレクトリ>/5.15.2/gcc_64/plugins/designerディレクトリ
- 正常に読み込まれたプラグインおよび読み込みに失敗したプラグインを確認するには、[ヘルプ] - [プラグイン]を選択する。
また、Qwtの公式Webサイトには、QwtPolar、QwtPlot3D、QtiPlot等も存在する。
Qtプロジェクトの設定
Qwtを使用するため、Qtプロジェクトファイルに以下の設定を追記する。
# QWT QWT_LOCATION = /<Qtのインストールディレクトリ>/Qwt-x.x.x INCLUDEPATH += $${QWT_LOCATION}/include/ LIBS += -L$${QWT_LOCATION}/lib -lqwt
Qt Designer画面を開いて、Qwtウィジェットが存在するか確認する。
PyQwt
PythonでQwtが使用できるPyQwtライブラリも存在する。
QwtPlot
プロット上に曲線や散布図を表示する場合、まず、Qt Designer画面にてQwtPlotウィジェットを配置する。
次に、QwtPlotItemクラスのインスタンスにデータを渡して、QwtPlotクラスのインスタンスにアタッチする。
QwtPlotの目盛りは自動的に最適に表示される。
一般的には、QwtPlotクラスまたはQwtPlotItemクラスを継承した派生クラスを作成して使用する。
Qwtライブラリのほとんどのサンプルでは、派生クラスを作成している。
プロット画面のコントロールを設定
ズームやパンニングを行うプロット画面の制御は、それぞれQwtPlotZoomerクラス、QwtPlotPannerクラスを使用する。
QwtPlotZoomerクラスの設定は必須であり、設定しない場合は何も表示されない。
QwtPlotZoomerクラスは、ズーム枠を描画するため、QwtPlotPickerクラス(ラバーバンドを扱うクラス)の派生クラスであり、
QwtPlotPannerクラスは、QwtPannerクラスの派生クラスであるため、設定方法は両者で異なる。
以下の例では、QwtPlotクラスの派生クラスを作成している。
// MyPlot.h(QwtPlotクラスの派生クラス)
#pragma once
#include "qwt_plot.h"
class MyPlot : public QwtPlot
{
Q_OBJECT
public:
MyPlot(QWidget *parent = NULL);
~MyPlot();
private:
// メンバ変数宣言
QwtPlotZoomer *m_pZoomer;
QwtPlotPanner *m_pPanner;
};
QwtPlotZoomerクラスにおいて、MouseSelect1はズーム開始、MouseSelect2はリセット、MouseSelect3は1つ前のズームに戻るに対応させる。
ズームレベルはズームスタックに積まれており、通常は、ズームスタックを1つ戻ることでズームアウトする。
// MyPlot.cpp
MyPlot::MyPlot(QWidget *parent) : QwtPlot(parent)
{
// Zoomerの設定
m_pZoomer = new QwtPlotZoomer(canvas());
// ズーム枠の色を設定
m_pZoomer->setRubberBandPen(QColor(Qt::darkBlue));
// マウスの座標値の色を設定
m_pZoomer->setTrackerPen(QColor(Qt::darkBlue));
// マウスボタンの割り当て
m_pZoomer->setMousePattern(QwtEventPattern::MouseSelect1, Qt::LeftButton);
m_pZoomer->setMousePattern(QwtEventPattern::MouseSelect2, Qt::RightButton, Qt::ControlModifier);
m_pZoomer->setMousePattern(QwtEventPattern::MouseSelect3, Qt::RightButton);
// 座標値の表示はズーム枠がアクティブなときのみ
m_pZoomer->setTrackerMode(QwtPicker::ActiveOnly);
// Pannerの設定
m_pPanner = new QwtPlotPanner(canvas());
// パンニングのボタンを中ボタンに設定
m_pPanner->setMouseButton(Qt::MiddleButton);
setAutoReplot(true);
}
プロットの縦横比を固定
プロットウィジェットの目盛りは、初期状態では、プロットウィンドウの縦横比に応じて変化する。
ウィンドウの縦横比に関わらず、縦軸と横軸の目盛り幅を一定にする場合は、QwtPlotRescalerクラスを使用する。
QwtPlotRescalerクラスを使用する場合、QwtPlotクラスの派生クラスを作成することを推奨する。
特に、画像をプロットする場合、表示される画像の縦横比がウィンドウにより変化する。
// MyPlot.h(QwtPlotクラスの派生クラス)
#pragma once
#include "qwt_plot.h"
#include <qwt_plot_rescaler.h>
class MyPlot : public QwtPlot
{
Q_OBJECT
public:
MyPlot(QWidget *parent = NULL);
~MyPlot();
private:
QwtPlotRescaler *m_pRescaler;
};
// MyPlot.cpp
#include "MyPlot.h"
MyPlot::MyPlot(QWidget *parent) : QwtPlot(parent)
{
// QwtPlotRescalerのコンストラクタ、第1引数はQwtPlotCanvasオブジェクトへのポインタ、
// 第2引数は目盛り幅の基準軸、第3引数のように指定することで縦横比を固定
m_pRescaler = new QwtPlotRescaler(canvas(), QwtPlot::xBottom, QwtPlotRescaler::Fixed);
m_pRescaler->setEnabled(true);
// X軸は目盛りが両側に引き伸ばされるように指定
m_pRescaler->setExpandingDirection(QwtPlot::xBottom, QwtPlotRescaler::ExpandBoth);
// Y軸は下方向に引き伸ばされるように指定
m_pRescaler->setExpandingDirection(QwtPlot::yLeft, QwtPlotRescaler::ExpandDown);
// 目盛りの縦横比を指定
m_pRescaler->setAspectRatio(1.0);
// 目盛り幅設定を実行
m_pRescaler->rescale();
setAutoReplot(true);
}
画像のプロット
QwtPlot
クラスは、グラフ等のデータをプロットすることが主目的であるが、画像をプロットすることもできる。
画像をプロットするには、まず、画像用のQwtPlotItem
クラスを継承した派生クラスを作成する。
以下の例では、プロット範囲の矩形と画像データをメンバ変数として定義している。
また、QwtPlotItem
クラスのdraw
メソッドをオーバーライドして、画像を描画している。
※注意
プロットの基準位置は、画像の左下であることに注意すること。
一般的には、コンストラクタの引数で座標を指定することを推奨する。
// ImageItem.h
#pragma once
#include "qwt_plot_item.h"
class ImageItem : public QwtPlotItem
{
public:
ImageItem( QImage *pImg );
~ImageItem(void);
virtual void draw(QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &rc) const;
private:
QImage *m_pimg;
QRectF m_rc;
};
// ImageItem.cpp
#include "ImageItem.h"
#include <qwt_painter.h>
#include <qwt_scale_map.h>
ImageItem::ImageItem(QImage *pImg)
{
// 画像をプロットする座標を指定する
m_pimg = pImg;
m_rc = QRectF(QPoint( 256, 512 ), pImg->size());
}
ImageItem::~ImageItem()
{
}
void ImageItem::draw(QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &rc) const
{
// 描画位置を指定する
// プロット位置とは異なり、ウィンドウ座標である
// 以下のように記述することにより、ビューのパンニングを実行すると画像も動く
QRectF rcImage = QwtScaleMap::transform(xMap, yMap, m_rc);
QwtPainter::drawImage(painter, rcImage, *m_pimg);
}
画像プロットにアタッチするには、他のプロットと同様に記述する。
ImageItem *item = new ImageItem(&m_qimg);
item->attach(m_pPlot);
画像の縮小
以下の例では、ボタンを押下する時、プロットの中心を基準に画像を縮小している。
void MyPlot::ZoomOut()
{
QRectF rcNew;
// 現在のズーム枠を取得
QRectF rc = m_pZoomer->zoomRect();
rc.normalized();
// ズームアウト枠を設定
rcNew.setLeft(rc.left() - dRange * 0.1);
rcNew.setRight(rc.right() + dRange * 0.1);
rcNew.setTop(rc.top() - dRange * 0.1);
rcNew.setBottom(rc.bottom() + dRange * 0.1);
// ズームアウト枠を適用
m_pZoomer->zoom( rcNew );
// ズームスタックをリセット
m_pZoomer->setZoomBase();
canvas()->update();
}
画像の縮小機能を実装する場合、ズームスタックは、都度リセットする方が無難である。
また、パンニングの時もズームスタックを都度リセットして、ズームベースを更新する。
ズームベースとは、ズームの初期化時のズーム範囲のこと。
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
// コンストラクタでパンニング完了時のシグナル・スロットを設定
QObject::connect(m_pPanner, SIGNAL(panned(int, int)), this, SLOT(updateZoom(int, int)));
// ...略
}
// QwtPlotクラスを継承した派生クラスのcppファイル
// パンニング完了時のスロット
void MyPlot::updateZoom(int nX, int nY)
{
QRectF rc = m_pZoomer->zoomRect();
QPointF pnt = rc.topLeft();
// 現在のズーム枠を移動してズームベースを更新
m_pZoomer->setZoomBase(QRectF(QPointF(pnt.x() + nX, pnt.y() + nY), rc.size()));
canvas()->replot();
}
目盛りの設定
標準では、目盛りは実数値が表示されるが、目盛りの値を変更することができる。
以下の例では、度単位の数値を度分秒表記にしている。
まず、QwtScaleDraw
クラスを継承した派生クラスを作成する。
// 縦軸クラス
class DMSScaleDrawLat : public QwtScaleDraw
{
public:
DMSScaleDrawLat()
{
setLabelAlignment( Qt::AlignLeft | Qt::AlignVCenter );
}
// 与えられた数値を度分秒に変換
virtual QwtText label( double dVal ) const
{
int nM;
double dS;
int nD = (int)floor( dVal );
double dS = (dVal - nD)*60;
int nM = (int)floor( dS );
double dS = (dS - nM) * 60;
return QObject::tr( "%1d\n %2' %3\"" ).arg(nD).arg(nM).arg(dS);
}
};
// 横軸クラス
// 縦軸クラスの派生クラスとして、ラベルの向きだけを変更
class DMSScaleDrawLon : public DMSScaleDrawLat
{
public:
DMSScaleDrawLon()
{
setLabelRotation( -90.0 );
}
};
上記で定義した縦軸および横軸クラスを、QwtPlot
クラスを継承した派生クラスの各軸に設定する。
以下の例では、QwtPlot
クラスを継承した派生クラスのコンストラクタで設定している。
MyPlot::MyPlot(QWidget *parent) : QwtPlot(parent)
{
// ...略
// 縦軸および横軸クラスを設定する
setAxisScaleDraw(QwtPlot::xBottom, new DMSScaleDrawLon());
setAxisScaleDraw(QwtPlot::yLeft, new DMSScaleDrawLat());
}
標準の軸ラベルは、与えられた数値に対して、QLocale().toString(value)
で返される文字列を表示する。
(qwt_abstract_scale_draw.hファイルのlabel
メソッド)
この時、大きい数値は指数型の文字列を返す。
指数型の文字列を表示させない場合も、上記のように、QwtPlot
クラスを継承した派生クラスを作成する必要がある。
マーカーを入れる
マーカーは、縦線・横線およびシンボルを描画することができる。
また、マーカーにテキストのラベルを付加することもできる。
例えば、Qwt付属のサンプルでは、ピーク値の表示に使用している。
以下の例では、3種類のマーカーを作成して、全てのマーカーでテキストを右寄せ上寄せに設定している。
マーカーの外観は、他にも様々な設定ができる。
// QwtPlotクラスのポインタ(*m_pPlot)とQwtPlotMarkerクラスのポインタ(*m_pPMarker)をメンバに持つ
// QMainWindowクラスのコンストラクタ
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
// ポイントマーカー
m_pPMarker = std::make_unique<QwtPlotMarker>();
m_pPMarker->setXValue(600.0);
m_pPMarker->setYValue(600.0);
m_pPMarker->setLabelAlignment(Qt::AlignRight | Qt::AlignTop);
QwtText textPMarker("Point marker");
m_pPMarker->setLabel(textPMarker);
// ポイントシンボル
std::unique_ptr<QwtSymbol> pSymbol = std::make_unique<QwtSymbol>(QwtSymbol::Ellipse, QBrush(QColor::fromRgb(0, 0, 0)),
QPen(QColor::fromRgb(0, 0, 0)), QSize(20, 10));
m_pPMarker->setSymbol(pSymbol);
m_pPMarker->attach(m_pPlot);
// 横ラインマーカー
m_pHMarker = std::make_unique<QwtPlotMarker>();
m_pHMarker->setXValue(200.0);
m_pHMarker->setLabelAlignment(Qt::AlignRight | Qt::AlignTop);
m_pHMarker->setLineStyle(QwtPlotMarker::VLine);
m_pHMarker->setLinePen(QPen(Qt::magenta, 0, Qt::DashDotDotLine));
QwtText textHMarker("Virtical line marker");
m_pHMarker->setLabel(textHMarker);
m_pHMarker->attach(m_pPlot);
// 縦ラインマーカー
m_pVMarker = std:make_unique<QwtPlotMarker>();
m_pVMarker->setYValue(100.0);
m_pVMarker->setLabelAlignment(Qt::AlignRight | Qt::AlignTop);
m_pVMarker->setLineStyle(QwtPlotMarker::HLine);
m_pVMarker->setLinePen(QPen(Qt::cyan, 0, Qt::DashDotDotLine));
QwtText textVMarker("Horizontal line marker");
m_pVMarker->setLabel(textVMarker);
m_pVMarker->attach(m_pPlot);
}
ラバーバンドの描画
プロット上に選択枠のような図形を描画する場合、QwtPlotPicker
クラスを使用する。
QwtPlotPicker
クラスは、ズーム枠の親クラスである。
以下の例では、QwtPlot
クラスを継承した派生クラスを作成して、QwtPlotPicker
クラスを使用して設定している。
MyPlot::MyPlot(QWidget *parent) : QwtPlot(parent)
{
// Picker
m_pPicker = std::make_unique<QwtPlotPicker>(canvas());
m_pPicker->setRubberBandPen(QColor(Qt::darkRed));
m_pPicker->setTrackerMode(QwtPicker::ActiveOnly);
}
マウスボタンの設定は、QwtPickerMachine
クラスの派生クラスで管理している。
以下の例では、ズーム枠のような矩形を描画している。
m_pPicker->setRubberBand(QwtPicker::RectRubberBand);
m_pPicker->setStateMachine(new QwtPickerDragRectMachine);
QwtPickerDragRectMachine
クラスは、マウスの左ボタンを押下しながらドラッグすることにより矩形を描画して、
左ボタンを離すと描画を終了する設定である。
以下の例では、楕円を描画している。
m_pPicker->setRubberBand(QwtPicker::EllipseRubberBand);
m_pPicker->setStateMachine(new QwtPickerDragRectMachine);
以下の例では、ポリゴンを描画している。
m_pPicker->setRubberBand(QwtPicker::PolygonRubberBand);
m_pPicker->setStateMachine(new QwtPickerPolygonMachine);
setStateMachine
メソッドは、左ボタンで点を追加、右ボタンで終了する動作である。
しかし、左ボタンで最初の1点、右ボタンで2点目以降を追加、左ボタンで終了となっている。(2012/4 現在)
この動作を、左ボタンで点を追加、右ボタンで終了に変更するため、qwt_picker_machin.cppファイルを変更する。
// qwt_picker_machin.cpp
//! Transition
QList<QwtPickerMachine::Command> QwtPickerPolygonMachine::transition(const QwtEventPattern &eventPattern, const QEvent *event)
{
QList<QwtPickerMachine::Command> cmdList;
switch(event->type())
{
case QEvent::MouseButtonPress:
if(eventPattern.mouseMatch(QwtEventPattern::MouseSelect1, (const QMouseEvent *)event))
{
// if(state() == 0)
// {
cmdList += Begin;
cmdList += Append;
cmdList += Append;
setState( 1 );
// }
// else
// {
// cmdList += End;
// setState(0);
// }
}
if(eventPattern.mouseMatch(QwtEventPattern::MouseSelect2, (const QMouseEvent *)event))
{
if(state() == 1)
{
// cmdList += Append;
cmdList += End;
}
}
break;
}
}
ラバーバンドで描画した図形を、確定図形としてプロット上に描画する場合、
ラバーバンド描画終了時の図形データを、ラバーバンドクラスから取得する必要がある。
ここでは、ラバーバンド描画終了時のシグナルを受信して図形を取り出す手順を使用する。
上記の例では、QwtPlotクラスを継承した派生クラスにラバーバンドクラスがあるため、
プロットアイテムを、プロットの親ウィンドウ側で管理する場合はシグナルをさらに親ウィンドウへ転送する必要がある。
QMainWindowクラスのコンストラクタにおいて、以下のようにシグナル・スロットを記述する。
また、QwtPlotクラスを継承した派生クラスを持つ親ウィンドウにて、転送されるシグナルを受信する。
シグナルの引数は、ラバーバンドの矩形または頂点となっているため、スロットで図形を追加するとよい。
楕円の場合は、矩形選択のスロットが戻るため、選択図形の楕円をスロット側で再作成する必要がある。
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
// ...略
// 矩形、楕円選択の場合
QObject::connect(m_pPicker, SIGNAL(selected(const QRectF&)), this, SIGNAL(selected(const QRectF&)));
// 矩形、楕円選択の場合
QObject::connect(m_pPlot, SIGNAL(selected(const QRectF&)), this, SLOT(appendPoly(const QRectF&)));
// ポリゴン選択の場合
QObject::connect(m_pPicker, SIGNAL(selected(const QVector<QPointF>&)), this, SIGNAL(selected(const QVector<QPointF>&)));
// ポリゴン選択の場合
QObject::connect(m_pPlot, SIGNAL(selected(const QVector<QPointF>&)), this, SLOT(appendPoly(const QVector<QPointF>&)));
// ...略
}
// 矩形の場合のスロット
void MainWindow::appendPoly(const QRectF& rc)
{
QVector<QPointF> tempPoly;
QPointF pnt;
if(/* 楕円選択の場合 */ )
{
double dAlpha = 0.0;
while(dAlpha < 2 * M_PI)
{
pnt.setX(cos(dAlpha) * rc.width() * 0.5 + rc.center().x());
pnt.setY(sin(dAlpha) * rc.height() * 0.5 + rc.center().y());
tempPoly.push_back(pnt);
dAlpha += M_PI / 128;
}
}
else /* 矩形選択の場合 */
{
tempPoly.push_back(rc.topLeft());
tempPoly.push_back(rc.bottomLeft());
tempPoly.push_back(rc.bottomRight());
tempPoly.push_back(rc.topRight());
tempPoly.push_back(rc.topLeft());
}
// QwtPlotCurveクラスとして図形を追加
std::unique_ptr<QwtPlotCurve> pCurve = std::make_unique<QwtPlotCurve>();
pCurve->setSamples(tempPoly);
pCurve->setPen(QPen(QBrush(QColor::fromRgb(255, 0, 0)), 2.0));
pCurve->attach(m_pPlot);
}
// ポリゴンの場合のスロット
void MainWindow::appendPoly(const QVector<QPointF> &poly)
{
// 点列の末尾に始点を追加してポリゴンを閉じる
QVector<QPointF> tempPoly(poly);
tempPoly.push_back(tempPoly.front());
// QwtPlotCurveクラスとして図形を追加
std::unique_ptr<QwtPlotCurve> pCurve = std::make_unique<QwtPlotCurve>();
pCurve->setSamples(tempPoly);
pCurve->setPen(QPen(QBrush(QColor::fromRgb(255, 0, 0)), 2.0));
pCurve->attach(m_pPlot);
}
プロット画面をエクスポートおよび印刷する
QwtPlotRenderer
クラスを使用して、プロット画面をマーカーや軸と共に、エクスポートおよび印刷する。
以下の例では、SVGにエクスポートおよび印刷している。
// 印刷
void ParentWnd::onPrint()
{
// m_printerはQPrinterクラスのオブジェクト
QPrintDialog dlgPrint( &m_printer, this );
if(dlgPrint.exec())
{
QwtPlotRenderer renderer;
// m_pPlotはQwtPlotオブジェクトのポインタ
renderer.renderTo(m_pPlot, m_printer);
}
}
// SVGへエクスポート
void ParentWnd::exportSVG( QString strFName )
{
QwtPlotRenderer renderer;
// 第3引数はフォーマットの指定, 第4引数はドキュメントサイズをミリ単位で指定, 第5引数は解像度(dpi)
renderer.renderDocument(m_pPlot, strFName, tr("svg"), QSizeF(210, 210), 300);
}
ただし、プロットの縦横比を固定しても、印刷時の縦横比は紙サイズに合わせて変化するため、
印刷時において、縦横比を固定する場合は、QwtPlotRenderer
クラスのrender
メソッドを使用して印刷時の範囲を指定する。
(QwtPlotRenderer
クラスのrenderTo
メソッドは使用しない)
QRectF rcPrint;
QPainter printPainter(&m_printer);
rcPrint.setWidth(100);
rcPrint.setHeight(100);
renderer.render(m_pPlot, &printPainter, rcPrint);