Qt之自繪制餅圖


1、說明

    最近在搞繪圖方面的工作,說實話C++的第三方繪圖庫並不算多,總之我了解的有:qtcharts、ChartDirector、qwt、kdchart和QCustomPlot。這幾個庫各有利弊。

  • qtcharts:qt5.7之后才開源的模塊,支持繪制各種圖標,並且功能相當豐富,但是可擴展性差,如果自己想高度定制,比較困難,主要是和qt的源碼風格有決定性的關系。
  • ChartDirector:開源的第三方繪圖庫,使用方便,推薦使用
  • qwt:主要繪制儀表盤類似的東西(這個庫可以編譯后加入qt幫助文檔)
  • kdchart:不僅可以繪制圖表,而且可以繪制甘特圖,功能也都挺好使,我個人之前在qt4.7的時候使用過
  • QCustomPlot:簡答的繪圖庫,因為只有兩個文件,如果想高度定制我個人推薦這個靠譜,畢竟理解起來容易些

2、效果展示

    下邊是繪制的餅圖展示效果,當然了不能滿足大多數人的需要,我主要是在這里提供一種思路,如果需要在繪制上有所調整的小伙伴可以下載demo自行修改。

圖1 展示圖1

圖2 展示2

圖3 展示圖3

3、思路分析

    上邊三張展示圖,如果要說從理解難以成都來說,展示圖3是比較容易理解。下邊我就幾個需要注意的細節描述下:

  • 圖表矩形距離邊框距離,影響圖表繪制矩形的因素
  • 圖表繪制方向,默認是逆時針
  • 圖表文本描述位置
  • legend描述位置,demo中已經提供了接口,可以支持不同legend的展現形式
  • 箭頭長短
  • 空心餅圖(圓環圖)

    餅圖繪制關鍵步驟:

  • 添加數據項->構造數據緩存->繪制圖表
  • 窗口大小變化->構造數據項矩形->構造數據緩存->繪制圖表

4、源碼解說

    首先來看兩個結構體,主要是用來緩存數據,PieItemPrivate存儲的是每一個item項的內容,包括item的legend,文本、顏色、值和一些輔助的結構體;PieChartPrivate結構是餅圖類的私有數據存儲結構,具體含義看注釋

 1 struct PieItemPrivate
 2 {
 3     PieItem item;//用戶插入數據時的結構,包括注釋、值和顏色
 4     QPainterPath path;//項繪制時區域
 5     QPoint labelPos;//文本位置
 6     QRect m_LegendRect;//legend的矩形
 7 };
 8 
 9 struct PieChartPrivate
10 {
11     bool m_bLegendVisible = false;//是否顯示圖例
12     int m_Minx = 25;//左右最小邊距
13     int m_Miny = 25;//上下最小邊距
14     int m_MinDiameter = 130;//餅圖最小直徑
15     int m_RingWidth = 0;//如果是環,環的寬度
16     int m_StartRotationAngle = 0;//繪制item項的時候,其實角度
17     int m_LegendWidth = 100;//圖表寬度  可以在插入新數據項的時候更新,計算展示legend所需要的最小尺寸
18     int m_LegendHeight = 30;//圖例高度  可以在插入新數據項的時候更新,計算展示legend所需要的最小尺寸
19     double m_SumValue = 0;//所有item的value和
20     QRect m_PieRect;//餅圖繪制矩形
21     QColor m_LabelColor = QColor(0, 0, 0);//百分比文字顏色
22     QString m_RingLabel = QStringLiteral("餅圖");//圖表中心文字描述
23     QVector<PieItemPrivate> m_Items;//圖表項
24 };

1、當有新數據或者窗口大小發生變化時,計算數據緩存

 1 void PieChart::ConstructData()
 2 {
 3     int pos = d_ptr->m_StartRotationAngle;
 4     int angle;
 5     QPainterPath subPath;
 6     subPath.addEllipse(d_ptr->m_PieRect.adjusted(d_ptr->m_RingWidth, d_ptr->m_RingWidth, -d_ptr->m_RingWidth, -d_ptr->m_RingWidth));
 7     
 8     for (auto iter = d_ptr->m_Items.begin(); iter != d_ptr->m_Items.end(); ++iter)
 9     {
10         angle = 16 * iter->item.value / d_ptr->m_SumValue * 360;
11     
12         QPainterPath path;
13         path.moveTo(d_ptr->m_PieRect.center());
14         path.arcTo(d_ptr->m_PieRect.x(), d_ptr->m_PieRect.y(), d_ptr->m_PieRect.width(), d_ptr->m_PieRect.height(), pos / 16.0, angle / 16.0);
15         path.closeSubpath();
16         
17         if (d_ptr->m_RingWidth > 0 && d_ptr->m_RingWidth <= d_ptr->m_PieRect.width() / 2)
18         {
19             path -= subPath;
20         }
21         
22         iter->path = path;
23 
24         double labelAngle = (pos + angle / 2) / 16;
25         double tx = (d_ptr->m_PieRect.width() - d_ptr->m_RingWidth) / 2 * qCos(labelAngle / 360 * 2 * 3.1415926);
26         double ty = -(d_ptr->m_PieRect.width() - d_ptr->m_RingWidth) / 2 * qSin(labelAngle / 360 * 2 * 3.1415926);
27 
28         iter->labelPos = QPoint(tx, ty) + d_ptr->m_PieRect.center();
29 
30         pos += angle;
31     }
32 }

2、當窗口大小發生變化時,重新計算各項所在矩形,ConstructRect方式是用來計算各子項矩形區域的,內部調用ConstructCornerLayout方法是生產制定的布局,有興趣的小伙伴可以寫自己的矩形區域計算方式,開達到不同的繪制效果。

 1 void PieChart::ConstructRect(const QSize & size)
 2 {
 3     switch (d_ptr->m_Items.size())
 4     {
 5     case 4:
 6         ConstructCornerLayout(size);
 7     default:
 8         break;
 9     }
10 }
11 //該方法是針對4個legend,並且在四角的位置所計算的布局方式,小伙伴也可以實現自己的布局計算,然后在ConstructRect接口中調用
12 void PieChart::ConstructCornerLayout(const QSize & size)
13 {
14     int currentR = d_ptr->m_MinDiameter;
15     int diameter;
16     int horiWidth = size.width();
17     if (d_ptr->m_bLegendVisible)
18     {
19         horiWidth -= d_ptr->m_LegendWidth * 2;
20     }
21 
22     if (horiWidth > size.height())
23     {
24         diameter = size.height();
25     }
26     else
27     {
28         diameter = horiWidth;
29     }
30 
31     int x, y;
32     int r = diameter - d_ptr->m_Minx * 2;
33     currentR = r > currentR ? r : currentR;
34     if (d_ptr->m_bLegendVisible)
35     {
36         x = d_ptr->m_Minx + d_ptr->m_LegendWidth;
37         y = (size.height() - currentR) / 2;
38       //計算4個legend位置
39         d_ptr->m_Items[1].m_LegendRect = QRect(d_ptr->m_Minx, d_ptr->m_Miny, d_ptr->m_LegendWidth, d_ptr->m_LegendHeight);
40         d_ptr->m_Items[0].m_LegendRect = QRect(x + r, d_ptr->m_Miny, d_ptr->m_LegendWidth, d_ptr->m_LegendHeight);
41         d_ptr->m_Items[3].m_LegendRect = QRect(x + r, size.height() - d_ptr->m_Miny - 30, d_ptr->m_LegendWidth, d_ptr->m_LegendHeight);
42         d_ptr->m_Items[2].m_LegendRect = QRect(d_ptr->m_Minx, size.height() - d_ptr->m_Miny - 30, d_ptr->m_LegendWidth, d_ptr->m_LegendHeight);
43     }
44     else
45     {
46         x = d_ptr->m_Minx;
47         y = d_ptr->m_Miny;
48     }
49 
50     d_ptr->m_PieRect = QRect(x, y, currentR, currentR);//計算餅圖位置
51 }

5、測試代碼

 1 int main(int argc, char *argv[])
 2 {
 3     QApplication a(argc, argv);
 4 
 5     PieChart w;
 6     w.AddData(100, Qt::red, "red");
 7     w.AddData(100, Qt::green, "green");
 8     w.AddData(100, Qt::blue, "blue");
 9     w.AddData(100, Qt::gray, "gray");
10     w.show();
11 
12     return a.exec();
13 }

6、示例下載

    Qt之自繪制餅圖

 

如果您覺得文章不錯,不妨給個 打賞,寫作不易,感謝各位的支持。您的支持是我最大的動力,謝謝!!! 

 

  


很重要--轉載聲明

  1. 本站文章無特別說明,皆為原創,版權所有,轉載時請用鏈接的方式,給出原文出處。同時寫上原作者:朝十晚八 or Twowords
  2. 如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM