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è)簡(jiǎn)單的實(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)然自己寫也是很簡(jiǎn)單的事情。
函數(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
本文源碼都在原文中