📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)

 
(同じ利用者による、間の10版が非表示)
13行目: 13行目:
ダウンロードしたQwtを解凍するため、以下のコマンドを実行する。<br>
ダウンロードしたQwtを解凍するため、以下のコマンドを実行する。<br>
  tar xf qwt-x.x.x.tar.bz2
  tar xf qwt-x.x.x.tar.bz2
cd qwt-x.x.x
<br>
または、<code>git clone</code>を使用して、Qwtのソースコードをダウンロードする。<br>
git clone https://git.code.sf.net/p/qwt/git -b qwt-x.x qwt
cd qwt
<br>
<br>
解凍したQwtディレクトリに移動して、qwtconfig.priファイルを以下のように編集する。<br>
解凍したQwtディレクトリに移動して、qwtconfig.priファイルを以下のように編集する。<br>
41行目: 46行目:
  }
  }
<br>
<br>
Qwtプロジェクトをビルドおよびコンパイルして、インストールする。<br>
ビルドディレクトリを作成する。<br>
  qmake qwt.pro
mkdir build && cd build
  make -j 8
<br>
Qwtをビルドおよびインストールする。<br>
  qmake ../qwt.pro
  make -j $(nproc)
  make install
  make install
<br>
<br>
62行目: 70行目:
Qwtを使用するため、Qtプロジェクトファイルに以下の設定を追記する。<br>
Qwtを使用するため、Qtプロジェクトファイルに以下の設定を追記する。<br>
  # QWT
  # QWT
  QWT_LOCATION = /home/suse/InstallSoftware/Qt/Qwt-6.1.6
  QWT_LOCATION = /<Qtのインストールディレクトリ>/Qwt-x.x.x
  INCLUDEPATH += $${QWT_LOCATION}/include/
  INCLUDEPATH += $${QWT_LOCATION}/include/
  LIBS += -L$${QWT_LOCATION}/lib -lqwt
  LIBS += -L$${QWT_LOCATION}/lib -lqwt
264行目: 272行目:
  ImageItem *item = new ImageItem(&m_qimg);
  ImageItem *item = new ImageItem(&m_qimg);
  item->attach(m_pPlot);
  item->attach(m_pPlot);
</syntaxhighlight>
<br>
[[ファイル:Qt Qwt Plot 1.jpg|フレームなし|中央]]
<br><br>
== 画像の縮小 ==
以下の例では、ボタンを押下する時、プロットの中心を基準に画像を縮小している。<br>
<syntaxhighlight lang="c++">
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();
}
</syntaxhighlight>
<br>
画像の縮小機能を実装する場合、ズームスタックは、都度リセットする方が無難である。<br>
また、パンニングの時もズームスタックを都度リセットして、ズームベースを更新する。<br>
<u>ズームベースとは、ズームの初期化時のズーム範囲のこと。</u><br>
<syntaxhighlight lang="c++">
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
  // コンストラクタでパンニング完了時のシグナル・スロットを設定
  QObject::connect(m_pPanner, SIGNAL(panned(int, int)), this, SLOT(updateZoom(int, int)));
  // ...略
}
</syntaxhighlight>
<br>
<syntaxhighlight lang="c++">
// 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();
}
</syntaxhighlight>
<br><br>
== 目盛りの設定 ==
標準では、目盛りは実数値が表示されるが、目盛りの値を変更することができる。<br>
<br>
以下の例では、度単位の数値を度分秒表記にしている。<br>
<br>
まず、<code>QwtScaleDraw</code>クラスを継承した派生クラスを作成する。<br>
<syntaxhighlight lang="c++">
// 縦軸クラス
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 );
    }
};
</syntaxhighlight>
<br>
上記で定義した縦軸および横軸クラスを、<code>QwtPlot</code>クラスを継承した派生クラスの各軸に設定する。<br>
以下の例では、<code>QwtPlot</code>クラスを継承した派生クラスのコンストラクタで設定している。<br>
<syntaxhighlight lang="c++">
MyPlot::MyPlot(QWidget *parent) : QwtPlot(parent)
{
    // ...略
    // 縦軸および横軸クラスを設定する
    setAxisScaleDraw(QwtPlot::xBottom, new DMSScaleDrawLon());
    setAxisScaleDraw(QwtPlot::yLeft, new DMSScaleDrawLat());
}
</syntaxhighlight>
<br>
標準の軸ラベルは、与えられた数値に対して、<code>QLocale().toString(value)</code>で返される文字列を表示する。<br>
(qwt_abstract_scale_draw.hファイルの<code>label</code>メソッド)<br>
<br>
この時、大きい数値は指数型の文字列を返す。<br>
指数型の文字列を表示させない場合も、上記のように、<code>QwtPlot</code>クラスを継承した派生クラスを作成する必要がある。<br>
<br>
[[ファイル:Qt Qwt Plot 2.jpg|フレームなし|中央]]
<br><br>
== マーカーを入れる ==
マーカーは、縦線・横線およびシンボルを描画することができる。<br>
また、マーカーにテキストのラベルを付加することもできる。<br>
<br>
例えば、Qwt付属のサンプルでは、ピーク値の表示に使用している。<br>
<br>
以下の例では、3種類のマーカーを作成して、全てのマーカーでテキストを右寄せ上寄せに設定している。<br>
マーカーの外観は、他にも様々な設定ができる。<br>
<syntaxhighlight lang="c++">
// 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);
}
</syntaxhighlight>
<br>
[[ファイル:Qt Qwt Plot 3.jpg|フレームなし|中央]]
<br><br>
== ラバーバンドの描画 ==
プロット上に選択枠のような図形を描画する場合、<code>QwtPlotPicker</code>クラスを使用する。<br>
<code>QwtPlotPicker</code>クラスは、ズーム枠の親クラスである。<br>
<br>
以下の例では、<code>QwtPlot</code>クラスを継承した派生クラスを作成して、<code>QwtPlotPicker</code>クラスを使用して設定している。<br>
<syntaxhighlight lang="c++">
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);
}
</syntaxhighlight>
<br>
マウスボタンの設定は、<code>QwtPickerMachine</code>クラスの派生クラスで管理している。<br>
以下の例では、ズーム枠のような矩形を描画している。<br>
<syntaxhighlight lang="c++">
m_pPicker->setRubberBand(QwtPicker::RectRubberBand);
m_pPicker->setStateMachine(new QwtPickerDragRectMachine);
</syntaxhighlight>
<br>
<code>QwtPickerDragRectMachine</code>クラスは、マウスの左ボタンを押下しながらドラッグすることにより矩形を描画して、<br>
左ボタンを離すと描画を終了する設定である。<br>
<br>
以下の例では、楕円を描画している。<br>
<syntaxhighlight lang="c++">
m_pPicker->setRubberBand(QwtPicker::EllipseRubberBand);
m_pPicker->setStateMachine(new QwtPickerDragRectMachine);
</syntaxhighlight>
<br>
以下の例では、ポリゴンを描画している。<br>
<syntaxhighlight lang="c++">
m_pPicker->setRubberBand(QwtPicker::PolygonRubberBand);
m_pPicker->setStateMachine(new QwtPickerPolygonMachine);
</syntaxhighlight>
<br>
<code>setStateMachine</code>メソッドは、左ボタンで点を追加、右ボタンで終了する動作である。<br>
しかし、左ボタンで最初の1点、右ボタンで2点目以降を追加、左ボタンで終了となっている。(2012/4 現在)<br>
<br>
この動作を、左ボタンで点を追加、右ボタンで終了に変更するため、qwt_picker_machin.cppファイルを変更する。<br>
<syntaxhighlight lang="c++">
// 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;
    }
}
</syntaxhighlight>
<br>
ラバーバンドで描画した図形を、確定図形としてプロット上に描画する場合、<br>
ラバーバンド描画終了時の図形データを、ラバーバンドクラスから取得する必要がある。<br>
<br>
ここでは、ラバーバンド描画終了時のシグナルを受信して図形を取り出す手順を使用する。<br>
<br>
上記の例では、QwtPlotクラスを継承した派生クラスにラバーバンドクラスがあるため、<br>
プロットアイテムを、プロットの親ウィンドウ側で管理する場合はシグナルをさらに親ウィンドウへ転送する必要がある。<br>
<br>
QMainWindowクラスのコンストラクタにおいて、以下のようにシグナル・スロットを記述する。<br>
また、QwtPlotクラスを継承した派生クラスを持つ親ウィンドウにて、転送されるシグナルを受信する。<br>
<br>
シグナルの引数は、ラバーバンドの矩形または頂点となっているため、スロットで図形を追加するとよい。<br>
楕円の場合は、矩形選択のスロットが戻るため、選択図形の楕円をスロット側で再作成する必要がある。<br>
<syntaxhighlight lang="c++">
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);
}
</syntaxhighlight>
<br><br>
== プロット画面をエクスポートおよび印刷する ==
<code>QwtPlotRenderer</code>クラスを使用して、プロット画面をマーカーや軸と共に、エクスポートおよび印刷する。<br>
<br>
以下の例では、SVGにエクスポートおよび印刷している。<br>
<syntaxhighlight lang="c++">
// 印刷
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);
}
</syntaxhighlight>
<br>
ただし、プロットの縦横比を固定しても、印刷時の縦横比は紙サイズに合わせて変化するため、<br>
印刷時において、縦横比を固定する場合は、<code>QwtPlotRenderer</code>クラスの<code>render</code>メソッドを使用して印刷時の範囲を指定する。<br>
(<code>QwtPlotRenderer</code>クラスの<code>renderTo</code>メソッドは使用しない)<br>
<syntaxhighlight lang="c++">
QRectF rcPrint;
QPainter printPainter(&m_printer);
rcPrint.setWidth(100);
rcPrint.setHeight(100);
renderer.render(m_pPlot, &printPainter, rcPrint);
  </syntaxhighlight>
  </syntaxhighlight>
<br><br>
<br><br>