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





