目錄html
原文連接:QCustomplot使用分享(九) 繪製圖表-多功能遊標app
上一篇文章QCustomplot使用分享(八) 層(完結)講述了第一篇QCustomPlot控件的使用,主要是展現了多維度折線圖,而且有一個簡單的遊標展現效果。本篇文章是在上一篇文章的基礎上進行的功能增強,主要是針對遊標進行優化,提供更加豐富的遊標功能。函數
以下圖所示,是我作的一個測試效果圖,途中包括一個簡單的折線圖和一系列遊標,折線圖的顯示模式有十幾種效果,具體能夠看QCustomplot使用分享(一) 能作什麼事這篇文章裏的截圖,這裏我就不在貼出。佈局
這個效果圖主要展現了遊標的使用,其餘相關功能能夠參考以前寫的文章,本篇文章最後也會經過相關文章小節提供,感興趣的同窗能夠去文末查找。測試
演示demo中的數據是讀取於cvs文件,若是你們本身想從其餘渠道獲取數據也能夠,這個繪圖控件已經添加了足夠的接口可供調用。優化
繪圖控件提供的遊標功能以下,好比:ui
下面的文章中我會分析下主要的接口和核心功能實現this
圖中的展現效果測試代碼以下,代碼中的關鍵節點就2個spa
ESCsvDBOperater * csvDBOperater = new ESCsvDBOperater(nullptr); csvDBOperater->loadCSVFile(qApp->applicationDirPath() + "\\temp\\test31.csv"); QStringList names = csvDBOperater->getCSVNames(); auto callback = [this, names](const QString & name, const QVector<double> & data){ int index = names.indexOf(name); if (index != -1) { if (index == 0) { ui->widget->SetGraphKey(data); } else { int l = name.indexOf("("); int r = name.indexOf(")"); if (l != -1 && r != -1) { ui->widget->SetGraphValue(index - 1, name.left(l), /*name.mid(l + 1, r - l - 1)*/"", data); ui->widget->SetGraphScatterStyle(index - 1, 4); } else { ui->widget->SetGraphValue(index - 1, name, "", data); } } }
固然QCP不只僅能顯示折線圖,他還能夠顯示各類各樣的效果圖,感興趣的到QCustomplot使用分享(一) 能作什麼事文章中觀看設計
如圖所示,是工程的頭文件截圖,圖中的文件數量比較多,可是對外咱們使用的可能只是一個ESMPMultiPlot類,這個類中提供了不少接口,足夠咱們使用,固然了若是有特殊需求的話,能夠進行提供定製
以下是頭文件中的接口,我只是把相關的Public接口列出來了,而這些接口也正好是咱們平時使用比較多的接口,看接口名稱應該都知道接口是幹什麼的,所以再也不細說
void ClearCache();//清空上一個csv繪圖數據 void SetGraphCount(int);//設置折線圖個數 void SetGraphKey(const QVector<double> &);//設置x軸數據 void SetGraphKeyRange(double, double);//設置x軸範圍,即時間範圍 void SetGraphScatterStyle(int, int);//設置折線圖樣式 void SetGraphValue(int, const QString &, const QString & , const QVector<double> &);//設置折線圖數據 void AppendGraphValue(int, double, double);//追加折線圖數據 void AppendGraphValue(int, const QVector<double> &, const QVector<double> &);//追加折線圖數據 QVector<double> GetGraphValues(int, int);//獲取折線圖 遊標區間值 參數1:折線下標 參數2:遊標order QString GetGraphName(int) const; void SetGraphColor(int, const QColor &);//設置折線圖顏色 QColor GetGraphColor(int);//獲取折線圖顏色 void SetSingleCursor(bool single);//啓動單遊標 bool IsSingleCursor(int index) const;//測試遊標是不是單遊標 void ShowCursor(bool visible = true);//設置遊標是否顯示 void AppendCursor(const QColor & color);//新增遊標 void LockedCursor(int, bool);//鎖定指定遊標 參數2表示是否鎖定 int CursorCount() const; bool CursorVisible() const;//遊標是否顯示 void SetCursorColor(int index, const QColor &);//設置遊標顏色 第二個參數指示哪一個遊標 double GetCursorKey(bool);//獲取遊標對象x軸值 true表示左遊標 false表示右遊標 double GetCursorKey(int index, bool);//獲取遊標對象x軸值 true表示左遊標 false表示右遊標 void ResizeKeyRange(bool, int index = 0);//設置x軸縮放 true時按遊標縮放 false時恢復默認狀態 void ResizeValueRange();//y軸自適應 void ConfigureGraph();//設置 void ConfigureGraphAmplitude(int);//雙擊右側單位時觸發 void SavePng(const QString & = "");//保存圖片 一、分析時 自動執行並傳入路徑 二、點擊保存圖形按鈕時 傳空路徑
以下是模擬添加遊標的代碼,經過一個變量i來模擬不一樣狀況,添加不一樣類型的遊標,當前支持添加可移動單遊標、可移動雙遊標、可鎖定拖動雙遊標
void ESMultiPlot::on_pushButton_add_cursor_clicked() { graphColor.append(Qt::red); graphColor.append(Qt::green); graphColor.append(Qt::blue); graphColor.append(Qt::gray); graphColor.append(Qt::cyan); graphColor.append(Qt::yellow); graphColor.append(Qt::magenta); static int i = 1; if (i % 3 == 0) { ui->widget->SetSingleCursor(true); ui->widget->AppendCursor(graphColor[rand() % 6 + 1]); } else if (i % 3 == 1) { ui->widget->SetSingleCursor(false); ui->widget->AppendCursor(graphColor[rand() % 6 + 1]); ui->widget->LockedCursor(i, false); } else { ui->widget->SetSingleCursor(false); ui->widget->AppendCursor(graphColor[rand() % 6 + 1]); ui->widget->LockedCursor(i, true); } ++i; }
如上代碼所示,SetSingleCursor設置爲true時,表示接下來要添加的遊標是單遊標;LockedCursor能夠鎖定指定雙遊標,對單遊標不生效。
多遊標模式下移動遊標比一組遊標複雜一些,咱們須要循環監測全部的遊標,並獲取一個可移動遊標。
這裏獲取移動遊標的邏輯爲距離鼠標按下的位置在5個像素之內的遊標,而且優先響應先構造的遊標,若是左右遊標同時知足的話優先響應右遊標
void ESMPMultiPlot::mousePressEvent(QMouseEvent * event) { if (m_bCursor) { m_bDrag = true; for (int i = 0; i < m_pCursors.size(); ++i) { QCPItemStraightLine * leftCursor = m_pCursors.at(i).leftCursor; bool ispressed = false; double distance = leftCursor->selectTest(event->pos(), false); if (distance <= 5 && axisRect()->rect().contains(event->pos())) { m_bDragType = 1; m_bLeftCursor = true; ispressed = true; m_bLock = m_pCursors.at(i).lock; m_bSingleCursor = m_pCursors.at(i).single; m_bOrder = i; } QCPItemStraightLine * rightCursor = m_pCursors.at(i).rightCursor; distance = rightCursor->selectTest(event->pos(), false); if (distance <= 5 && axisRect()->rect().contains(event->pos())) { m_bDragType = 1; m_bLeftCursor = false; ispressed = true; m_bLock = m_pCursors.at(i).lock; m_bSingleCursor = m_pCursors.at(i).single; m_bOrder = i; } if (ispressed) { break; } } } for (int i = 0; i < m_vecNames.size(); ++i) { double distance = m_vecNames[i]->selectTest(event->pos(), false); //QPointF posF = m_vecNames[i]->position->pixelPosition; if (distance <= 13 && m_vecNames[i]->visible()) { m_bDragType = 2; m_iDragIndex = i; break; } } __super::mousePressEvent(event); }
QCustomplot使用分享(八) 層(完結)文章講述的是一組遊標移動,移動遊標時須要考慮的點比較少,分別是:
本篇文章的多組遊標移動相對來講考慮的點就須要更多一些,分別是:
遊標默認值遊標組(一個遊標、或者兩個遊標);左右遊標是針對兩個遊標而言
基礎規則
單遊標
雙遊標非鎖定-移動左側遊標
雙遊標非鎖定-移動右側遊標
雙遊標鎖定
以下代碼所示,是移動遊標的核心代碼,主要的移動狀況,上邊已經說了,下邊移動邏輯就不在細說,感興趣的同窗能夠本身查看,須要提供定製的能夠加我QQ。
void ESMPMultiPlot::mouseMoveEvent(QMouseEvent * event) { if (m_bDragType == 1 && m_bDrag) { double pixelx = event->pos().x(); QCPRange keyRange = axisRect()->axis(QCPAxis::atBottom)->range(); double min = axisRect()->axis(QCPAxis::atBottom)->coordToPixel(keyRange.lower); double max = axisRect()->axis(QCPAxis::atBottom)->coordToPixel(keyRange.upper); if (min + 1 > pixelx) { pixelx = min + 1; } else if (max - 1 < pixelx) { pixelx = max - 1; } //按住左遊標移動 double move_distance = 0; double rcursor = m_pCursors[m_bOrder].rightCursor->point1->key(); double rcursorx = axisRect()->axis(QCPAxis::atBottom)->coordToPixel(rcursor); double lcursor = m_pCursors[m_bOrder].leftCursor->point1->key(); double lcursorx = axisRect()->axis(QCPAxis::atBottom)->coordToPixel(lcursor); if (m_bLeftCursor) { //修正左邊 if (m_bOrder != 0) { double rcursor; if (m_pCursors[m_bOrder - 1].rightCursor->visible()) { rcursor = m_pCursors[m_bOrder - 1].rightCursor->point1->key(); } else//左側是單遊標 { rcursor = m_pCursors[m_bOrder - 1].leftCursor->point1->key(); } double rcursorx = axisRect()->axis(QCPAxis::atBottom)->coordToPixel(rcursor); if (pixelx <= rcursorx + 4) { pixelx = rcursorx + 4; } move_distance = rcursorx - pixelx;//可向左移動距離(向左爲負) } else { if (pixelx <= min + 2) { pixelx = min + 2; } move_distance = min - pixelx;//可向左移動距離(向左爲負) } //修正右邊 if (m_bLock)//鎖定移動 { move_distance = pixelx - lcursorx;//往右準備移動的距離 if (m_bOrder == m_pCursors.size() - 1) { if (rcursorx + move_distance > max - 2) { move_distance = max - 2 - rcursorx;//往右真正可移動距離 } } else { double nlcursor = m_pCursors[m_bOrder + 1].leftCursor->point1->key(); double nlcursorx = axisRect()->axis(QCPAxis::atBottom)->coordToPixel(nlcursor); if (rcursorx + move_distance > nlcursorx - 4) { move_distance = nlcursorx - 4 - rcursorx;//往右真正可移動距離 } } } else { if (m_bSingleCursor) { move_distance = pixelx - lcursorx;//往右準備移動的距離 if (m_bOrder == m_pCursors.size() - 1) { if (lcursorx + move_distance > max - 2) { move_distance = max - 2 - lcursorx;//往右真正可移動距離 } } else { double nlcursor = m_pCursors[m_bOrder + 1].leftCursor->point1->key(); double nlcursorx = axisRect()->axis(QCPAxis::atBottom)->coordToPixel(nlcursor); if (lcursorx + move_distance > nlcursorx - 4) { move_distance = nlcursorx - 4 - lcursorx;//往右真正可移動距離 } } } else { if (pixelx >= rcursorx - 4) { pixelx = rcursorx - 4; } move_distance = pixelx - lcursorx;//可向右移動距離(向右爲正) } } } else//按住右遊標移動 { //修正右邊 if (m_bOrder != m_pCursors.size() - 1) { double lcursor = m_pCursors[m_bOrder + 1].leftCursor->point1->key(); double lcursorx = axisRect()->axis(QCPAxis::atBottom)->coordToPixel(lcursor); if (pixelx >= lcursorx - 4) { pixelx = lcursorx - 4; } move_distance = pixelx - lcursorx;//可向右移動距離 } else { if (pixelx >= max - 2) { pixelx = max - 2; } move_distance = pixelx - lcursorx;//可向右移動距離 } //修正左邊 if (m_bLock)//鎖定移動 { move_distance = pixelx - rcursorx;//往左準備移動的距離 if (m_bOrder == 0) { if (lcursorx + move_distance <= min + 2) { move_distance = min + 2 - lcursorx;//往左真正可移動距離 } } else { double nlcursor = m_pCursors[m_bOrder - 1].rightCursor->point1->key(); double nlcursorx = axisRect()->axis(QCPAxis::atBottom)->coordToPixel(nlcursor); if (lcursorx + move_distance <= nlcursorx + 4) { move_distance = nlcursorx + 4 - lcursorx;//往右真正可移動距離 } } } else { if (pixelx <= lcursorx + 4) { pixelx = lcursorx + 4; } move_distance = pixelx - rcursorx;//可向左移動距離(向左爲負) } } double key; if (m_bLeftCursor) { key = axisRect()->axis(QCPAxis::atBottom)->pixelToCoord(lcursorx + move_distance); m_pCursors[m_bOrder].leftCursor->point1->setCoords(key, m_pCursors[m_bOrder].leftCursor->point1->value()); m_pCursors[m_bOrder].leftCursor->point2->setCoords(key, m_pCursors[m_bOrder].leftCursor->point2->value()); } else { key = axisRect()->axis(QCPAxis::atBottom)->pixelToCoord(rcursorx + move_distance); m_pCursors[m_bOrder].rightCursor->point1->setCoords(key, m_pCursors[m_bOrder].rightCursor->point1->value()); m_pCursors[m_bOrder].rightCursor->point2->setCoords(key, m_pCursors[m_bOrder].rightCursor->point2->value()); } if (m_bLock) { if (m_bLeftCursor) { key = axisRect()->axis(QCPAxis::atBottom)->pixelToCoord(rcursorx + move_distance); m_pCursors[m_bOrder].rightCursor->point1->setCoords(key, m_pCursors[m_bOrder].rightCursor->point1->value()); m_pCursors[m_bOrder].rightCursor->point2->setCoords(key, m_pCursors[m_bOrder].rightCursor->point2->value()); } else { key = axisRect()->axis(QCPAxis::atBottom)->pixelToCoord(lcursorx + move_distance); m_pCursors[m_bOrder].leftCursor->point1->setCoords(key, m_pCursors[m_bOrder].leftCursor->point1->value()); m_pCursors[m_bOrder].leftCursor->point2->setCoords(key, m_pCursors[m_bOrder].leftCursor->point2->value()); } } event->accept(); replot(); emit CursorChanged(m_bLeftCursor); return; } else if (m_bDragType == 2) { double pixely = event->pos().y(); QCPRange keyRange = axisRect()->axis(QCPAxis::atLeft)->range(); double max = axisRect()->axis(QCPAxis::atLeft)->coordToPixel(keyRange.lower); double min = axisRect()->axis(QCPAxis::atLeft)->coordToPixel(keyRange.upper); if (min > pixely) { pixely = min; } else if (max < pixely) { pixely = max; } m_vecNames[m_iDragIndex]->position->setType(QCPItemPosition::ptPlotCoords); double coordy1 = axisRect()->axis(QCPAxis::atLeft)->pixelToCoord(pixely); double coordx = m_vecNames[m_iDragIndex]->position->coords().rx(); double coordy = m_vecNames[m_iDragIndex]->position->coords().ry(); m_vecNames[m_iDragIndex]->position->setCoords(coordx, coordy1); m_vecUnits[m_iDragIndex]->position->setType(QCPItemPosition::ptPlotCoords); m_vecUnits[m_iDragIndex]->position->setCoords(m_vecUnits[m_iDragIndex]->position->coords().rx(), coordy1); (*m_graphConfigure)[m_iDragIndex].position += (coordy1 - coordy); RefrushGraph(m_iDragIndex); event->accept(); replot(); return; } __super::mouseMoveEvent(event); }
在ESMPPlot類中,m_mapLeftCursor和m_mapRightCursor分別是左右遊標,爲何這裏取了一個map呢?答案是:當時設計的時候是支持多個垂直襬放的遊標能夠進行遊標同步,若是炒股的同窗可能就會知道,k線和指標之間可能會有一個數值方便的線,無論在哪一個繪圖區進行移動,另外一個圖表裏的線也會跟着移動
不瞭解這個的同窗也沒關係,咱們這個控件默認的就是一個表,所以這個map裏也就只存了一個指,所以能夠不關心這個問題
在ESMPMultiPlot類中,咱們模擬了ESMPPlot的功能,這個時候呢?咱們的座標軸矩形只有一個了,x軸都是同樣的,表示時間,對於不一樣曲線的y軸咱們進行了平移,以達到不一樣的顯示位置
這裏邊有一個很重的技巧,那就是咱們對y軸數據進行了一次單位換算,讓他顯示的時候能夠更好顯示在咱們制定的區域內,可能像下面這樣
/* y1p=(y1-Yzero1)/Ygrid1+Xaxis1;%核心轉換公式,將原始座標值y1轉換爲新座標值y1p y1;%原始數值 Yzero1;%零點幅值,決定曲線1零點位置的變量 Ygrid1;%單格幅值,決定曲線1每一個單元格大小的量 Xaxis1;%顯示位置,決定曲線1在畫圖板中顯示位置的變量 */
固然了,咱們轉換後的座標只是爲了顯示方便而已,若是咱們根據UI獲取原始值,咱們還須要使用一個逆向公式進行轉換回去。
還有一些其餘的方法,好比保存圖表、獲取圖表座標、設置圖表顏色等這裏就不細講了,文章篇幅所限,不能一一的都貼出來,有須要的夥伴能夠聯繫我,提供功能定製。
控件咱們將的差很少了,這裏把測試的代碼放出來,你們參考下,首先測試工程截圖以下所示,咱們的測試代碼,大多數都是寫在了main函數中。
這裏簡單說名下,咱們的這個文件用途,第一列Time是表明了x軸的時間,而第二列開始的數據都是咱們的折線圖,一列數據表明一條折線圖,而且列的名稱就是咱們折線圖左側的名稱;列名稱括號裏的單位就是折線圖右側的單位。
限於篇幅,這裏我仍是把無關的代碼刪減了不少,須要完整的源碼的能夠聯繫我。
void ESMPMultiPlot::LoadData() { ESCsvDBOperater * csvDBOperater = new ESCsvDBOperater(nullptr); csvDBOperater->loadCSVFile(qApp->applicationDirPath() + "\\temp\\test31.csv"); QStringList names = csvDBOperater->getCSVNames(); auto callback = [this, names](const QString & name, const QVector<double> & data){ 添加圖表數據 }; ui->widget->SetGraphCount(names.size() - 1); for (int i = 0; i < names.size(); ++i) { csvDBOperater->receiveData(names[i], callback); } double start = csvDBOperater->getStartTime(); double end = csvDBOperater->getEndTime(); csvDBOperater->receiveData(names[2], 10.201, 10.412, callback); QVector<double> tiems = csvDBOperater->getRangeTimeDatas(10.201, 10.412); ui->widget->SetGraphKeyRange(start, end); }
QCustomPlot是一個很是強大的繪圖類,而且效率很高,對效率要求較高的程序均可以使用。
本篇文章是繼前7篇講解QCP後的第二篇使用案例,後續還會陸續提供更多複雜的功能。
這個控件已經被我封裝成一個dll,若是有須要的小夥伴能夠加我諮詢
由於我這裏的程序都是測試程序,所以都是使用的原生效果,若是有須要美化的同窗,或者客戶,我也能夠提供定製美化功能,歡迎諮詢。
![]() |
![]() |
很重要--轉載聲明