Qwt擴展之-數(shù)據(jù)拾取
在文章之前,首先看看這篇文章要實現(xiàn)的效果:
數(shù)據(jù)拾取就是在鼠標經(jīng)過線條時,會捕獲一些特征數(shù)據(jù),上圖是捕獲離鼠標最接近的點。
Qwt提供了拾取數(shù)據(jù)的現(xiàn)成的類,同時也留有非常好的接口,用戶可以任意擴展,下面就介紹Qwt專門負責拾取數(shù)據(jù)及鼠標跟蹤用的QwtPicker及其子類。并對其擴展,構(gòu)建一個用于顯示鼠標經(jīng)過圖像時捕獲最近點的拾取器。
拾取器
Qwt拾取器QwtPicker,用于顯示鼠標經(jīng)過圖像時的信息,內(nèi)置了一些坐標變換和鼠標位置及動作等函數(shù)
QwtPicker
QwtPicker的繼承關(guān)系如下圖所示
這個類可以捕獲當前鼠標位置及動作,同時使用戶在圖表上顯示一些特殊的信息。
拾取器的”橡皮圈“(Rubber Band)
所謂橡皮圈,就是在圖表上的一些附加顯示 QwtPicker有個QwtPicker::RubberBand的枚舉,此枚舉例舉了默認的橡皮圈:
HLineRubberBand
A horizontal line ( only for QwtPickerMachine::PointSelection )
VLineRubberBand
A vertical line ( only for QwtPickerMachine::PointSelection )
CrossRubberBand
A crosshair ( only for QwtPickerMachine::PointSelection )
RectRubberBand
A rectangle ( only for QwtPickerMachine::RectSelection )
EllipseRubberBand
An ellipse ( only for QwtPickerMachine::RectSelection )
PolygonRubberBand
A polygon ( only for QwtPickerMachine::PolygonSelection )
UserRubberBand
Values >= UserRubberBand can be used to define additional rubber bands.
HLineRubberBand是一個水平線,VLineRubberBand是一個垂直線,CrossRubberBand是十字線,如下圖所示:
坐標變換
在重寫拾取器之前需要先了解qwt的一些函數(shù),其中最重要的就是坐標變換問題
由于qwt是一個繪圖控件,圖形有圖形刻度的坐標,控件有控件的坐標,可能圖形坐標x軸是0到100萬,y軸是0到10萬,這個圖卻在屏幕上只有x方向600像素,y方向400像素,這時,鼠標在圖形屏幕上點(200,200)位置,對應圖形坐標的位置是多少,這需要一個轉(zhuǎn)變,QwtPlotPicker內(nèi)置兩個函數(shù)實現(xiàn)圖形屏幕坐標到圖形數(shù)值坐標的轉(zhuǎn)換以及逆轉(zhuǎn)換:
把圖形數(shù)值坐標轉(zhuǎn)換為屏幕坐標:
QRect???transform?(const?QRectF?&)?const QPoint??transform?(const?QPointF?&)?const
把屏幕坐標轉(zhuǎn)換為圖形數(shù)值坐標:
QRectF?invTransform?(const?QRect?&)?const QPointF?invTransform?(const?QPoint?&)?const
有了這兩個函數(shù),就可以方便的對坐標進行轉(zhuǎn)換了。
自定義拾取器
雖然Qwt內(nèi)置了幾種常用的”橡皮圈“,但是使用者肯定有許多不一樣的需求,例如本文開頭顯示的圖片所示,隨著鼠標的移動,自動捕抓最近的點,并把最近點的信息顯示出來,且文字顏色也有相應的改變,這種特殊要求,就必須自己重寫QwtPicker
重寫QwtPicker主要需要重寫如下虛函數(shù):
//用于控制顯示文字內(nèi)容及區(qū)域的: virtual?QwtText?????trackerText?(const?QPoint?&pos)?const virtual?QRect???trackerRect?(const?QFont?&)?const //用于控制’橡皮筋‘RubberBand的繪制的 virtual?void????drawRubberBand?(QPainter?*)?const //用于控制追蹤鼠標顯示的內(nèi)容(默認是顯示文字及一個矩形背景) virtual?void????drawTracker?(QPainter?*)?const
drawRubberBand
為了實現(xiàn)上面追蹤最近點的拾取器
這里自定義一個拾取器,繼承于QwtPlotPicker
頭文件:
#includeclass?QwtPlotCurve;
class?SAXYDataTracker:?public?QwtPlotPicker
{
public:
????SAXYDataTracker(QWidget?*?canvas);
protected:
????virtual?void?drawRubberBand?(QPainter?*painter)?const;
};實現(xiàn)文件:
SAXYDataTracker::SAXYDataTracker(QWidget*?canvas)?:
????QwtPlotPicker(?canvas?)
{
????setTrackerMode(?QwtPlotPicker::ActiveOnly?);
????setRubberBand(?UserRubberBand??);
????setStateMachine(?new?QwtPickerTrackerMachine()?);
}
void?SAXYDataTracker::drawRubberBand(QPainter*?painter)?const
{
????if?(?!isActive()?||?rubberBand()?==?NoRubberBand?||
????????rubberBandPen().style()?==?Qt::NoPen?)
????{
????????return;
????}
????QPolygon?pickedPoint?=?pickedPoints?();
????if(pickedPoint.count?()?<?1)
????????return;
????QwtPlotPicker::drawRubberBand?(painter);
????//獲取鼠標的客戶坐標位置
????const?QPoint?pos?=?pickedPoint[0];
????QwtPainter::drawLine(?painter,?pos.x(),
????pos.y(),?0,0?);
}這里只做一個簡單的實現(xiàn),就是繪制一條線到(0,0)點
drawRubberBand 是在繪圖畫布上進行重繪,在QwtPickerTrackerMachine的狀態(tài)下,只要鼠標移動就會觸發(fā)
上面只是一個小例子,大家可以忽略,請看下面詳細教程:
最近點捕獲拾取器 求最近點
QwtPlotCurve提供了求最近點的函數(shù)int QwtPlotCurve::closestPoint(const QPoint& _pos_,double * _dist_ = NULL)const
效果是找到鼠標最近的一個點:
不過,注意,此函數(shù)是遍歷整個曲線所有點來求取的,在曲線點數(shù)非常多時,謹慎使用
自定義最近點捕獲Picker
定義求取圖形最近點的函數(shù)
頭文件:
#includeclass?QwtPlotCurve;
class?QwtPlotItem;
class?SAXYDataTracker:?public?QwtPlotPicker
{
public:
????SAXYDataTracker(QWidget?*?canvas);
protected:
????virtual?QwtText?trackerTextF(const?QPointF?&?pos)?const;
????virtual?QRect?trackerRect(const?QFont?&?font)?const;
????virtual?void?drawRubberBand?(QPainter?*painter)?const;
????void?calcClosestPoint(const?QPoint&?pos);
private:
????///
????///?brief?記錄最近點的信息
????///
????class?closePoint
????{
????public:
????????closePoint();
????????QwtPlotCurve?*?curve()?const{return?this->m_curve;}
????????void?setCurve(QwtPlotCurve?*?cur);
????????bool?isValid()?const;
????????QPointF?getClosePoint()?const;
????????int?index()?const{return?this->m_index;}
????????void?setIndex(int?i){this->m_index?=?i;}
????????double?distace()?const{return?this->m_distace;}
????????void?setDistace(double?d){this->m_distace?=?d;}
????????void?setInvalid();
????private:
????????QwtPlotCurve?*m_curve;
????????int?m_index;
????????double?m_distace;
????};
????closePoint?m_closePoint;
private?slots:
????//捕獲鼠標移動的槽
????void?mouseMove(const?QPoint?&pos);
public?slots:
????void?itemAttached(QwtPlotItem*?plotItem,bool?on);
};源文件: (跳過吧?。?/p>
#include "SAXYDataTracker.h"
#include下面等我慢慢介紹上面的代碼:
求全局最近點
函數(shù): void calcClosestPoint(const QPoint& pos);
是用于求取全局最近點的,為了防止頻繁求取,我們把得到的最近點信息保存下來,因此建立了一個內(nèi)部類:
///
///?brief?記錄最近點的信息
///
class?closePoint
{
public:
???closePoint();
???QwtPlotCurve?*?curve()?const{return?this->m_curve;}
???void?setCurve(QwtPlotCurve?*?cur);
???bool?isValid()?const;
???QPointF?getClosePoint()?const;
???int?index()?const{return?this->m_index;}
???void?setIndex(int?i){this->m_index?=?i;}
???double?distace()?const{return?this->m_distace;}
???void?setDistace(double?d){this->m_distace?=?d;}
???void?setInvalid();
private:
???QwtPlotCurve?*m_curve;
???int?m_index;
???double?m_distace;
};此類的作用就是保存最近點的信息,這里并沒有把那個點保存了下來,而是保存了對應的曲線和索引。
通過QPointF getClosePoint() const函數(shù)可以獲取最近點。 void calcClosestPoint(const QPoint& pos);的實現(xiàn)就是遍歷plot里的所有曲線,并求取其最近的那個點,并把信息保存在closePoint里,這里連點距離借用了Qt的QLineF類來求,當然自己寫也是很簡單的事情。
函數(shù)void calcClosestPoint(const QPoint& pos)里,首先把屏幕坐標轉(zhuǎn)換為坐標軸的數(shù)值坐標
//把屏幕坐標轉(zhuǎn)換為圖形的數(shù)值坐標 QPointF?mousePoint?=?invTransform(pos);
并用一個double記錄最短的距離,初始化為double的最大值,用到了stl里的numeric_limits函數(shù)
std::numeric_limits::max?();
最后檢查最大值的曲線與上一次的曲線是否一樣,不一樣就更換RubberBand的顏色,實現(xiàn)RubberBand的顏色跟隨曲線一致
//說明最近點的曲線更換了,標記線的顏色換為當前曲線的顏色
if(m_closePoint.isValid?()?&&?oldCur!=m_closePoint.curve?())
{
?????QPen?p(m_closePoint.curve?()->pen?());
?????p.setWidth?(1);
?????setRubberBandPen?(p);
}繪制鼠標到最近點的連線
最近點求取后,就是繪制鼠標到最近點的連線
本例里重載了三個QwtPicker的虛函數(shù):
????virtual?QwtText?trackerTextF(const?QPointF?&?pos)?const; ????virtual?QRect?trackerRect(const?QFont?&?font)?const; ????virtual?void?drawRubberBand?(QPainter?*painter)?const;
其中virtual void drawRubberBand (QPainter *painter) const;是為了繪制橡皮筋(RubberBand)其實就是圖表的繪制用的。
void?SAXYDataTracker::drawRubberBand(QPainter*?painter)?const
{
????if?(?!isActive()?||?rubberBand()?==?NoRubberBand?||
????????rubberBandPen().style()?==?Qt::NoPen?)
????{
????????return;
????}
????if(!m_closePoint.isValid?())
????????return;
????QPolygon?pickedPoint?=?pickedPoints?();
????if(pickedPoint.count?()?<?1)
????????return;
????//獲取鼠標的客戶坐標位置
????const?QPoint?pos?=?pickedPoint[0];
????const?QPointF?closePoint?=?m_closePoint.getClosePoint?();
????const?QPoint?cvp?=?transform?(closePoint);
????QwtPainter::drawLine?(painter,pos,cvp);
????QRect?r(0,0,10,10);
????r.moveCenter?(cvp);
????QwtPainter::drawRect?(painter,r);
}QPolygon pickedPoint = pickedPoints ();函數(shù)獲取鼠標當前的點,當然,用trackerPosition 一樣能獲取,在這里效果一致:
//獲取鼠標的客戶坐標位置 const?QPoint?pos?=?trackerPosition?(); if(pos.isNull?()) ????return;
m_closePoint.getClosePoint獲取最近的那個點,此點是數(shù)值結(jié)果,需要轉(zhuǎn)變?yōu)槠聊蛔鴺?因此使用了QwtPlotPicker::transform函數(shù),把數(shù)值結(jié)果轉(zhuǎn)換為屏幕坐標
const?QPointF?closePoint?=?m_closePoint.getClosePoint?(); const?QPoint?cvp?=?transform?(closePoint);
之后就是繪制圖形了:
QwtPainter::drawLine?(painter,pos,cvp); QRect?r(0,0,10,10); r.moveCenter?(cvp); QwtPainter::drawRect?(painter,r);
Qwt把繪圖的函數(shù)封裝在QwtPainter的類里,此類全部為靜態(tài)函數(shù),相當于命名空間,負責所有QPainter的操作。這樣可以在const函數(shù)里使用QPainter。
這時就能繪制一個線,從鼠標的位置連接到最近點的位置。
繪制文本
QwtPicker的跟蹤文本繪制有兩個虛函數(shù):
????virtual?QwtText?trackerTextF(const?QPointF?&?pos)?const; ????virtual?QRect?trackerRect(const?QFont?&?font)?const;
virtual QwtText trackerTextF(const QPointF & pos) const;負責顯示的內(nèi)容 virtual QRect trackerRect(const QFont & font) const;負責繪制的區(qū)域
繪制區(qū)域需要根據(jù)內(nèi)容來設(shè)定:
QRect?SAXYDataTracker::trackerRect(const?QFont&?font)?const
{
????QRect?r?=?QwtPlotPicker::trackerRect(?font?);
????r?+=?QMargins(5,5,5,5);
????return?r;
}QwtPlotPicker::trackerRect( font );可獲取默認的繪制區(qū)域,此區(qū)域緊緊包裹著文字,為了好看點,讓區(qū)域外擴5像素r += QMargins(5,5,5,5);
trackerTextF用來控制顯示的信息
QwtText SAXYDataTracker::trackerTextF(const QPointF& pos) const
{
? ?QwtText trackerText;
? ?if(!m_closePoint.isValid ())
? ? ? ?return trackerText;
? ?trackerText.setColor( Qt::black );
? ?QColor lineColor = m_closePoint.curve()->pen ().color ();
? ?QColor bkColor(lineColor);
? ?bkColor.setAlpha (30);
? ?trackerText.setBorderPen( m_closePoint.curve()->pen () );
? ?trackerText.setBackgroundBrush( bkColor );
? ?QPointF point = m_closePoint.getClosePoint ();
? ?QString info = QStringLiteral("y:%2
")
? ? ? ? ? ? ? ? ? .arg(lineColor.name ()).arg(point.y ())
? ? ? ? ? ? ? ? ? +
? ? ? ? ? ? ? ? ? QStringLiteral("x:%2")
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?.arg(lineColor.name ()).arg(point.x ());
? ?trackerText.setText( info );
? ?trackerText.setBorderRadius (5);
? ?return trackerText;
}QwtText 支撐html文本的顯示,同時能比較方便的設(shè)置背景及畫刷,這里使用html把文字用與曲線相同的顏色繪制,背景使用曲線相同顏色的背景,不過透明度更低。
最終效果如下:
這是有5條325000個點的數(shù)據(jù)線的繪制情況:
可以看到C++的響應速度還是很可觀的
要點 Qwt圖形數(shù)值坐標轉(zhuǎn)換為屏幕坐標以及屏幕坐標轉(zhuǎn)換為數(shù)值坐標:
QRect???transform?(const?QRectF?&)?const QPoint??transform?(const?QPointF?&)?const QRectF?invTransform?(const?QRect?&)?const QPointF?invTransform?(const?QPoint?&)?const
QwtPlotCurve求最近點: QwtPlotCurve提供了求最近點的函數(shù)int QwtPlotCurve::closestPoint(const QPoint& _pos_,double * _dist_ = NULL)const
本文源碼都在原文中





