1. 程式人生 > >Windows高DPI系列控制元件(二) - 柱狀圖

Windows高DPI系列控制元件(二) - 柱狀圖

[TOC] **原文連結:**[Windows高DPI系列控制元件(二) - 柱狀圖](https://www.cnblogs.com/swarmbees/p/13246989.html) ## 一、QCP QCP全稱QCustomPlot,是一個基於Qt的圖表庫,同時支援Qt4和Qt5,使用起來還是很方便的,不管是編譯成dll還是直接嵌入到我們自己的程式都是極其容易,畢竟只有兩個檔案。之前寫過幾篇簡單的關於QCP的文章,從使用的角度分析了QCP的一些簡單用法,包括:[QCustomplot使用分享(一) 能做什麼事](http://www.cnblogs.com/swarmbees/p/6056225.html)、[QCustomplot使用分享(二) 原始碼解讀](http://www.cnblogs.com/swarmbees/p/6057567.html)、[QCustomplot使用分享(三) 圖](http://www.cnblogs.com/swarmbees/p/6057798.html)、[QCustomplot使用分享(四) QCPAbstractItem](http://www.cnblogs.com/swarmbees/p/6058263.html)、[QCustomplot使用分享(五) 佈局](http://www.cnblogs.com/swarmbees/p/6058942.html)、[QCustomplot使用分享(六) 座標軸和網格線](http://www.cnblogs.com/swarmbees/p/6059812.html)和[QCustomplot使用分享(七) 層(完結)](https://www.cnblogs.com/swarmbees/p/6060473.html),感興趣的同學可以抽時間閱讀一遍。高DPI系列控制元件大多數也都是基於QCP庫進行的定製,首先是對QCP的包裝,其次其次也對QCP原始碼進行了高DPI適配,主要還是針對我自己的DPI框架進行適配。 此高DPI適配框架,在DPI為96整數倍下是非常完美的,無失真情況,其他非整數比縮放情況下會有一些小瑕疵,比如非整數縮放比下圖片進行過拉伸,某些拉伸會導致失真的圖片會發生模糊的情況;其次當縮放比不是0.5的整數倍時,字號放大有可能不是按比例的,會導致介面字型不協調,但是字型不會發虛。**對於大多數使用者來說,目前的高DPI框架都是可以滿足的,比如我個人的顯示就是一個1080P顯示器和一個4K顯示器,兩個顯示器的縮放比分別是100%和200%,沒有任何失真的情況。** 上一篇文章[Windows高DPI系列控制元件(一) - 餅圖](https://www.cnblogs.com/swarmbees/p/13223795.html)中講過怎麼在高DPI顯示器下繪製餅圖,並且坐到沒有任何的失真,畢竟都是自己繪製的。餅圖控制元件是很早以前就完成的功能,這一篇文章主要是對他進行了高DPI的適配,讓餅圖控制元件在我的4K顯示器上也可以很友好的展示,並且可以在兩個顯示器直接無縫切換。 本篇文章是繼[Windows高DPI系列控制元件(一) - 餅圖](https://www.cnblogs.com/swarmbees/p/13223795.html)文章後的第二篇關於高DPI控制元件的例項,包括後續會定製的一些列高DPI控制元件,大多數基於QCP來實現。文章前面也說過,QCP是一個非常強大的繪相簿,比較遺憾的就是沒有餅圖這個控制元件,上一篇[Windows高DPI系列控制元件(一) - 餅圖](https://www.cnblogs.com/swarmbees/p/13223795.html)控制元件是我參考QCP的程式碼自己實現的繪製,效率也是槓槓滴,除此之外QCP也是非常容易擴充套件的,比如可以自己新增新的層,去繪製一些自己需要的東西,多說一句QCP2.0版本區別於1.0版本又一個很大的優勢就是多層繪製的,這樣效率比較高,並且您也可以指定某個層進行單獨重新整理,本篇文章講到的柱狀圖上的tooltips提示實現方式就是新增了一個tooltips繪製層,這樣實現的好處就是跟現有框架的其他層程式碼是解耦的,比如後期您不要這個層了,可以直接刪掉,或者隱藏都是可以的,這樣對整個程式碼執行效率幾乎沒有任何損失。 ## 二、效果展示 如下圖所示,柱狀圖和折線圖適配高DPI後展示效果。 > 左右兩側的顯示物理尺寸一致,也就是視覺上大小一樣大,不同的是左側是1080P顯示器,右側是4K顯示器 **因為是視訊錄製原因,可能會有視覺誤差,實際看的話,左右兩個窗體給人的視覺感受大小是一樣的。**

《來回切換顯示器》


《柱狀圖》

## 三、高DPI適配 ### 1、自定義柱狀圖 QCP庫中的柱狀圖支援多種模式,上一小節中的效果圖是普通的柱狀圖展示效果,除此之外QCP中的柱狀圖可以做到多組同時展示,並且兩組柱狀圖之間可以堆疊,由於適配需要花更多的實際,因此更多複雜的展示效果會在後續的文章中會陸續提供。 如《柱狀圖》展示圖中的效果,我們定製了柱狀圖一個新的柱狀圖類,主要是基於該類我們支援了展示tooltips效果,下面主要介紹下比較重要的幾個實現點 **tips實現** 文章開頭也提到過,QCP2.0是多層繪製的,這樣可以提高繪製效率,並且提升更強的擴充套件能力,如[QCustomplot使用分享(七) 層(完結)](https://www.cnblogs.com/swarmbees/p/6060473.html)這篇文章所分析的那樣。本篇文章中的提示框就是繼承可繪製物件QCPLayerable,介面上幾乎所有的繪製元素包括佈局元素都是繼承自這個類,該類有一個draw函式,實現該介面就可以實現我們自己想要繪製的東西,介面繪製時,繪製區域取決於自己所在的佈局區域。 ``` #ifndef CHARTTIP_H #define CHARTTIP_H #include "qcp/QCustomplot.h" class CBaseToolTip : public QCPLayerable { Q_OBJECT public: CBaseToolTip(QCustomPlot * plot); ~CBaseToolTip(); public: QString LayerName() const; void SetVisible(bool visible);//ÉèÖòãÊÇ·ñ»æÖÆ void SetTipLabel(const QVector & labels); void SetTopLeft(const QPoint & pos); protected: virtual void applyDefaultAntialiasingHint(QCPPainter * painter) const override{}; virtual void draw(QCPPainter * painter) override; virtual void DrawTip(QCPPainter * painter) = 0; protected: QPoint m_AnchorPos; QVector m_Labels; private: }; class CSideToolTip : public CBaseToolTip { public: CSideToolTip(QCustomPlot * plot); ~CSideToolTip(){} protected: virtual void DrawTip(QCPPainter * painter) override; }; class CTopToolTip : public CBaseToolTip { public: CTopToolTip(QCustomPlot * plot); ~CTopToolTip(){} protected: virtual void DrawTip(QCPPainter * painter) override; private: int radius = 3; int height = 46; int width = 125; }; #endif // CHARTTIP_H ``` 上述標頭檔案中,包括三個類,其中CBaseToolTip是tip提示的基類,他負責完成了tip物件的一些公有屬性,比如構造該繪製物件時自動建立tip繪製層,並把層加到QCP視窗物件上;除此之外還可以指定繪製的位置等。 本篇文章中的tip是CSideToolTip類來實現的,下面是該類具體的繪製邏輯,仔細一看下面的diamante,發現這裡又出現了我們熟悉的XXX_SCALE_NUMBER巨集,不錯這個巨集就是用來實現高DPI邏輯的,比如下面繪製矩形框的邏輯,矩形的長和寬都被我們用QCP_PAINTER_SCLAE_NUMBER巨集給包裹起來,這樣繪製時繪製的矩形大小就是我們需要的大小。 ``` #define QCP_PAINTER_SCLAE_NUMBER(a) (a) * painter->dpi_scale ``` 這裡簡單說下這個巨集的作用,QCPPainter原始碼本身是沒有dpi_scale這個變數的,為了更好的適配高DPI顯示器,這個變數是我自己加上的,表示當前繪製painter需要縮放的比例,比如我們繪製tip矩形區域時,就需要對長和寬進行必要的縮放。 > 除過QCPPainter我添加了dpi_scale縮放係數成員變數以外,QCPLayerable基類我也添加了該成員變數,並且在合適的實際都會進行變數更新,後續我會專門寫一篇關於QCP適配高DPI的文章,專門講解針對高DPI適配我都做了哪些工作。 ``` void CSideToolTip::DrawTip(QCPPainter * painter) { //繪製圓圈 painter->setPen(Qt::transparent); // painter->setBrush(QColor(255, 204, 51, 80)); // painter->drawEllipse(m_AnchorPos, 5, 5); painter->setBrush(QColor(255, 181, 26)); painter->drawEllipse(m_AnchorPos, int(QCP_PAINTER_SCLAE_NUMBER(3)), int(QCP_PAINTER_SCLAE_NUMBER(3))); //繪製矩形 135 * 65 QRect rect(0, 0, QCP_PAINTER_SCLAE_NUMBER(100), QCP_PAINTER_SCLAE_NUMBER(30)); rect.moveBottomRight(m_AnchorPos - QPoint(QCP_PAINTER_SCLAE_NUMBER(8), 0)); if (rect.left() < mParentPlot->axisRect()->outerRect().left()) { rect.moveBottomLeft(m_AnchorPos + QPoint(QCP_PAINTER_SCLAE_NUMBER(8), 0)); } painter->setPen(QColor(255, 204, 51)); painter->setBrush(QColor(0, 0, 0, 255 * 0.7)); painter->drawRect(rect); //繪製矩形框文字 QFont font(QStringLiteral("微軟雅黑")); font.setPixelSize(QCP_PAINTER_SCLAE_NUMBER(10)); painter->setFont(font); painter->drawText(QPoint(QCP_PAINTER_SCLAE_NUMBER(8), QCP_PAINTER_SCLAE_NUMBER(13)) + rect.topLeft(), QStringLiteral("兩融餘額:%1").arg(m_Labels[0])); painter->drawText(QPoint(QCP_PAINTER_SCLAE_NUMBER(8), QCP_PAINTER_SCLAE_NUMBER(25)) + rect.topLeft(), QStringLiteral("上證指數:%1").arg(m_Labels[1])); } ``` ### 2、新的柱狀圖 新的柱狀圖被我命名為CTooltipBars,該類沒有什麼比較牛逼的實現,程式碼量也不大,其中有一個比較重要的介面OnCheckHover和一個關鍵訊號HoverIndex; ``` #ifndef TIPBAR_H #define TIPBAR_H #include "../rluilib/dpi_macro.h" #include "qcp/QCustomplot.h" class QMouseEvent; class CTooltipBars : public QCPBars { Q_OBJECT signals : void HoverIndex(double index); public: CTooltipBars(float scale, QCPAxis * keyAxis, QCPAxis * valueAxis); ~CTooltipBars(); public: void SetValueVisible(bool visible); public slots: void OnCheckHover(QMouseEvent * pos); protected: virtual void draw(QCPPainter * painter) override; private: bool m_bValueVisible = false; int m_iLabelHeight = 0; }; #endif // TIPBAR_H ``` **OnCheckHover**:hover行為檢測介面,當我們移動滑鼠時,該介面會重複被觸發去檢測是否hover到了某個柱子上,如果hover成功那麼就會觸發HoverIndex訊號,引數表示hover的柱子序號。 檢測滑鼠hover事件的程式碼如下圖所示,整個程式碼的核心思想就是獲取所有可見的柱子資料,然後迴圈去判斷滑鼠當前的座標是否在哪個柱子的區域內,並觸發HoverIndex訊號,引數為-1時表示沒有hover到任何柱子,那麼此時可能需要隱藏已經展示的tip提示。 ``` void CTooltipBars::OnCheckHover(QMouseEvent * event) { QCPBarsDataContainer::const_iterator visibleBegin, visibleEnd; getVisibleDataBounds(visibleBegin, visibleEnd); QList selectedSegments, unselectedSegments, allSegments; getDataSegments(selectedSegments, unselectedSegments); allSegments << unselectedSegments << selectedSegments; double success = -1; for (int i = 0; i < allSegments.size(); ++i) { bool isSelectedSegment = i >= unselectedSegments.size(); QCPBarsDataContainer::const_iterator begin = visibleBegin; QCPBarsDataContainer::const_iterator end = visibleEnd; mDataContainer->limitIteratorsToDataRange(begin, end, allSegments.at(i)); if (begin == end) { continue; } for (QCPBarsDataContainer::const_iterator it = begin; it != end; ++it) { QRectF rect = getBarRect(it->key, it->value); rect = rect.adjusted(0, -m_iLabelHeight, 0, 0); bool contain = rect.contains(event->pos()); if (contain) { success = it->key; break; } } if (success != -1) { break; } } if (mParentPlot->axisRect()->rect().contains(event->pos()) == false) { success = -1; } emit HoverIndex(success); } ``` **HoverIndex**:柱狀圖hover時觸發訊號,表示滑鼠在引數指定的柱子內 ### 3、測試程式碼 如下程式碼是效果圖中的測試程式碼,我們構造了一個CBarChart物件,然後添加了一些簡單的額測試資料,用起來是不是很爽呢! ``` QWidget * CreateBar(float scale) { CBarChart * barChart = new CBarChart(scale); BarDataList datas; datas.push_back({ 1585816691, 200, }); datas.push_back({ 1588408691, 150, }); datas.push_back({ 1591087091, 220, }); datas.push_back({ 1593679092, 100, }); barChart->SetDatas(datas); barChart->SetYRange(QCPRange(0, 250), 5); return barChart; QStringLiteral("柱狀圖")); } ``` CBarChart類是一個對外的柱狀圖使用類,主要是把支援tip的柱狀圖、tip類和legend類進行了組裝,這裡就不在詳細講解了,該類的成員變數如下程式碼所示,其中還有一些輔助性的成員,比如說折線圖QCPGraph類,QCP唯一視窗類QCustomPlot,該類也是我們繪製圖表時不可或缺的物件,所以的繪製呼叫邏輯都是從該視窗的paintEvent函式開始出發的,並且該類做了各種繪製優化操作,並且支援3種繪製方式,感興趣的同學可以搜尋下QCPAbstractPaintBuffer這個類,這個是繪製buffer基類,其他繪製的實現都是基於該類實現。 ``` struct BarChartPrivate { QVector m_TickKey; QVector m_TickNames; QLabel * m_pBarLabel = nullptr; QLabel * m_pGraphLabel = nullptr; CLegendWidget * m_pLegend = nullptr; CBaseToolTip * m_pToolTip = nullptr; QCPGraph * m_pGraph = nullptr; QList m_pBars; QSharedPointer m_pXAxisTicker; QCustomPlot * m_pWidget = nullptr; QCPMarginGroup * m_pMarginGroup = nullptr; }; ``` ## 四、相關文章 1. [Qt之高DPI顯示器(一) - 解決方案整理](https://www.cnblogs.com/swarmbees/p/12004594.html) 2. [Qt之高DPI顯示器(二) - 自適配解決方案分析](https://www.cnblogs.com/swarmbees/p/12006601.html) 3. [Qt之自繪製餅圖](https://www.cnblogs.com/swarmbees/p/6033217.html) 4. [QCustomPlot之佈局系統](https://blog.csdn.net/yu132563/article/details/53037327) 5. [QCustomplot使用分享(一) 能做什麼事](http://www.cnblogs.com/swarmbees/p/6056225.html) 6. [QCustomplot使用分享(二) 原始碼解讀](http://www.cnblogs.com/swarmbees/p/6057567.html) 7. [QCustomplot使用分享(三) 圖](http://www.cnblogs.com/swarmbees/p/6057798.html) 8. [QCustomplot使用分享(四) QCPAbstractItem](http://www.cnblogs.com/swarmbees/p/6058263.html) 9. [QCustomplot使用分享(五) 佈局](http://www.cnblogs.com/swarmbees/p/6058942.html) 10. [QCustomplot使用分享(六) 座標軸和網格線](http://www.cnblogs.com/swarmbees/p/6059812.html) 11. [QCustomplot使用分享(七) 層(完結)](https://www.cnblogs.com/swarmbees/p/6060473.html) 12. [Windows高DPI系列控制元件(一) - 餅圖](https://www.cnblogs.com/swarmbees/p/13223795.html) *** **值得一看的優秀文章:** 1. **[財聯社-產品展示](https://www.cnblogs.com/swarmbees/p/6707798.html)** 2. **[廣聯達-產品展示](https://www.cnblogs.com/swarmbees/p/10836505.html)** 3. **[Qt定製控制元件列表](https://blog.csdn.net/qq_30392343/article/details/95527107)** 4. **[牛逼哄哄的Qt庫](https://blog.csdn.net/qq_30392343/article/details/95526527)**
如果您覺得文章不錯,不妨給個打賞,寫作不易,感謝各位的支援。您的支援是我最大的動力,謝謝!!!


* * * **很重要--轉載宣告** 1. 本站文章無特別說明,皆為原創,版權所有,轉載時請用連結的方式,給出原文出處。同時寫上原作者:[朝十晚八](https://www.cnblogs.com/swarmbees/) or [Twowords](https://www.jianshu.com/u/7673f8cfb4e6) 2. 如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的