目錄html
以前作過一款金融產品,名字叫作財聯社,感興趣的能夠瞅一眼財聯社-產品展現,因爲須要畫複雜的k線圖和一些輔助的圖表,我我的調研了幾個繪圖庫,包括:QWt、QCustomPlot、QtChart和directUI。最後各方考慮,決定使用QCustomPlot來作咱們的基礎繪圖庫,這裏有幾個方面的考慮app
當咱們的繪圖庫選定後,理所固然的就是去研究咱們這個庫了,所以我也花了幾天的時間去研究了咱們這個繪圖庫,並作了一個簡單的demo,感興趣的能夠去看以前寫的文章,demo都在CSDN上放着,若是沒有分須要我發的能夠留言。函數
以前講解的文章我在後邊相關文章小節已經給出,有想法的同窗也能夠直接先去看以前的文章,這樣更容易理解佈局
以下圖所示,是我作的一個測試效果圖,途中包括一個簡單的折線圖和遊標,折線圖的顯示模式有十幾種效果,具體能夠看QCustomplot使用分享(一) 能作什麼事這篇文章裏的截圖,這裏我就不在貼出。測試
這個效果圖只是展現了一部分簡單的功能,我封裝的繪圖控件實際上主要是用於用於加載cvs文件,而後顯示相應的圖表,固然了,若是你想本身獲取數據添加給圖表也是支持的。ui
最後該繪圖控件還提供了不少接口,能夠獲取當前繪圖數據,好比:this
下面的文章中我會分析下主要的接口和核心功能實現spa
圖中的展現效果測試代碼以下,代碼中的關鍵節點就2個設計
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使用分享(一) 能作什麼事文章中觀看code
如圖所示,是工程的頭文件截圖,圖中的文件數量比較多,可是對外咱們使用的可能只是一個ESMPMultiPlot類,這個類中提供了不少接口,足夠咱們使用,固然了若是有特殊需求的話,咱們還能夠進行提供定製
以下是頭文件中的接口,我只是把相關的Public接口列出來了,而這些接口也正好是咱們平時使用比較多的接口,看接口名稱應該都知道接口庫是幹什麼的,所以這裏再也不細說
void SetGraphCount(int); void SetGraphKey(const QVector<double> &); double GetGraphKey(double); void SetGraphValue(int, const QString &, const QString &, const QVector<double> &); void SetGraphScatterStyle(int, int); double GetGraphValue(int, bool);//獲取折線圖所在遊標出y值 參數1:折線下標 參數2:左右遊標標識 double GetGraphValue(int, double);//獲取折線圖所在遊標出y值 參數1:折線下標 參數2:x void SetGraphColor(int, const QColor &); void SetGraphColor(const QString &, const QColor &); void SetGraphUnit(int, const QString &); void SetGraphTitle(int, const QString &); void RefrushGraphID(int, const QString &); int GetGraphIndex(const QString &) const; void SetCursorColor(bool, const QColor &); void ShowCursor(bool visible = true); double GetCursorKey(bool); bool CursorVisible(); double GetCursorValue(bool); void ResizeKeyRange(bool); void SetKeyRange(double, double); void SetVauleRange(double, double); void ConfigureGraph();//設置 std::shared_ptr<AxisRectConfigurations> GetAxisCache();
以下代碼所示,是移動遊標的核心代碼
void ESMPPlot::mouseMoveEvent(QMouseEvent * event) { if (m_bDragCursor && m_pDragCursor) { 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); double lcursor = m_mapLeftCursor.begin().key()->point1->key(); double lcursorx = axisRect()->axis(QCPAxis::atBottom)->coordToPixel(lcursor); double rcursor = m_mapRightCursor.begin().key()->point1->key(); double rcursorx = axisRect()->axis(QCPAxis::atBottom)->coordToPixel(rcursor); if (min > pixelx) { pixelx = min; } else if (max < pixelx) { pixelx = max; } if (m_bLeftCursor) { if (pixelx >= rcursorx - 4 && layer(r_cursorLayer)->visible()) { pixelx = rcursorx - 4; } double key = axisRect()->axis(QCPAxis::atBottom)->pixelToCoord(pixelx); double value1 = m_pDragCursor->point1->value(); double value2 = m_pDragCursor->point2->value(); for each (QCPItemStraightLine * line in m_mapLeftCursor.keys()) { line->point1->setCoords(key, value1); line->point2->setCoords(key, value2); } m_pLeftText->setText(QString::number(GetGraphKey(pixelx))); m_pLeftText->position->setPixelPosition(QPoint(pixelx, axisRect()->rect().bottom() + 25)); } else { if (pixelx <= lcursorx + 4 && layer(l_cursorLayer)->visible()) { pixelx = lcursorx + 4; } double key = axisRect()->axis(QCPAxis::atBottom)->pixelToCoord(pixelx); double value1 = m_pDragCursor->point1->value(); double value2 = m_pDragCursor->point2->value(); for each (QCPItemStraightLine * line in m_mapRightCursor.keys()) { line->point1->setCoords(key, value1); line->point2->setCoords(key, value2); } m_pRightText->setText(QString::number(GetGraphKey(pixelx))); m_pRightText->position->setPixelPosition(QPoint(pixelx, axisRect()->rect().bottom() + 25)); } event->accept(); replot(); emit CursorChanged(m_bLeftCursor); 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獲取原始值,咱們還須要使用一個逆向公式進行轉換回去。
QCP他本身的邏輯是這樣的,每個QCustomPlot類都包括多個座標軸矩形,而一個座標軸矩形裏又能夠包含多個圖表,所以咱們這個控件是這樣的:
當咱們設置的圖表數量大於已有圖表時,須要使用takeAt接口移除多餘的圖表;當咱們設置的圖表數據小於已有圖表時,就須要添加新圖表對象,添加時機是設置圖表數據時
因爲這個函數的代碼量比較大,所以這裏我刪除了一些異常處理代碼和設置屬性代碼
添加圖表數據的流程可能像這面這樣
void ESMPMultiPlot::SetGraphCount(int count) { QCPAxisTickerText * leftTick = new QCPAxisTickerText; axisRect()->axis(QCPAxis::atLeft)->setTicker(QSharedPointer<QCPAxisTickerText>(leftTick)); QCPAxisTickerText * rightTick = new QCPAxisTickerText; axisRect()->axis(QCPAxis::atRight)->setTicker(QSharedPointer<QCPAxisTickerText>(rightTick)); int tickCount = m_iCount * 4;//每一個折線4個大刻度 double tickDistance = (720 + 100)/ tickCount; QMap<double, QString> ticks; for (int i = 0; i <= tickCount; ++i) { ticks[tickDistance * i] = ""; } leftTick->setTicks(ticks); leftTick->setSubTickCount(4);//每一個大刻度包含4個小刻度 double labelDistance = 720 / m_iCount; m_vecVerticalTick.resize(m_iCount); m_vecNames.resize(m_iCount); m_vecUnits.resize(m_iCount); double step = 1.0 / m_iCount; for (int i = 0; i < m_vecVerticalTick.size(); ++i) { m_vecVerticalTick[i] = labelDistance * i + labelDistance / 2; QCPItemText * name = new QCPItemText(this); name->position->setCoords(QPointF(0.01, 1 - (step * i + step / 2))); m_vecNames[m_vecVerticalTick.size() - i - 1] = name; QCPItemText * unit = new QCPItemText(this); unit->position->setCoords(QPointF(0.9, 1 - (step * i + step / 2))); m_vecUnits[m_vecVerticalTick.size() - i - 1] = unit; } RefrushItemPosition(); m_graphConfigure->resize(count); }
毫無疑問,添加圖表數據是咱們這個控件的很是重要的一個藉口
以下代碼所示,看咱們是怎麼添加數據的
void ESMPPlot::SetGraphValue(int index , const QString & xname, const QString & yname, const QVector<double> & values) { if (index >= m_iCount || values.size() == 0) { return; } m_vecIndex[index] = xname; m_vecUnit[index] = yname; m_oldDatas[index] = values; QList<QCPGraph *> graphs = axisRect(index)->graphs(); QCPGraph * graph = nullptr; if (graphs.size() == 0) { graph = addGraph(axisRect(index)->axis(QCPAxis::atBottom) , axisRect(index)->axis(QCPAxis::atLeft)); graph->setLineStyle(QCPGraph::lsLine); graph->setPen(QColor(255, 0, 0, 200)); } else { graph = graphs.at(0); } graph->setData(m_vecKeys, values, true); auto miniter = std::min_element(values.begin(), values.end()); auto maxiter = std::max_element(values.begin(), values.end()); double padding = (*maxiter - *miniter) * 0.2; axisRect(index)->axis(QCPAxis::atLeft)->ticker()->setTickOrigin(*miniter - padding); axisRect(index)->axis(QCPAxis::atLeft)->ticker()->setTickStepStrategy( QCPAxisTicker::tssReadability); axisRect(index)->axis(QCPAxis::atLeft)->ticker()->setTickCount(8); axisRect(index)->axis(QCPAxis::atLeft)->setRange(*miniter - padding, *maxiter + padding); axisRect(index)->axis(QCPAxis::atRight)->ticker()->setTickOrigin(*miniter - padding); axisRect(index)->axis(QCPAxis::atRight)->ticker()->setTickStepStrategy( QCPAxisTicker::tssReadability); axisRect(index)->axis(QCPAxis::atRight)->ticker()->setTickCount(8); axisRect(index)->axis(QCPAxis::atRight)->setRange(*miniter - padding, *maxiter + padding); int leftPadding = QFontMetrics(axisRect(index)->axis(QCPAxis::atLeft)->labelFont()).width(xname); axisRect(index)->axis(QCPAxis::atLeft)->setLabel(xname); int rightPadding = QFontMetrics(axisRect(index)->axis(QCPAxis::atBottom)->labelFont()).width(yname); axisRect(index)->axis(QCPAxis::atBottom)->setLabel(yname); }
QCP自帶的折線圖類型不少,具體咱們能夠參看QCPScatterStyle::ScatterShape這個枚舉類型有多少
void ESMPMultiPlot::SetGraphScatterStyle(int index, int style) { QList<QCPGraph *> graphs = axisRect()->graphs(); if (graphs.size() != 0 && index < graphs.size()) { QCPGraph * graph = graphs.at(0); graph->setScatterStyle(QCPScatterStyle::ScatterShape(style)); } }
還有一些其餘的方法,好比保存圖表、獲取圖表座標、設置圖表顏色等這裏就不細講了,文章篇幅所限,不能一一的都貼出來,有須要的夥伴能夠聯繫我,提供功能定製。
控件咱們將的差很少了,這裏把測試的代碼放出來,你們參考下,首先測試工程截圖以下所示,咱們的測試代碼,大多數都是寫在了main函數中。
這裏簡單說名下,咱們的這個文件用途,第一列Time是表明了x軸的時間,而第二列開始的數據都是咱們的折線圖,一列數據表明一條折線圖,而且列的名稱就是咱們折線圖左側的名稱;列名稱括號裏的單位就是折線圖右側的單位。
限於篇幅,這裏我仍是把無關的代碼刪減了不少,須要完整的源碼的能夠聯繫我。
void ESMultiPlot::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,若是有須要的小夥伴能夠加我諮詢
由於我這裏的程序都是測試程序,所以都是使用的原生效果,若是有須要美化的同窗,或者客戶,我也能夠提供定製美化功能,歡迎諮詢。
很重要--轉載聲明