Qt之自繪製餅圖

朝十晚八發表於2016-11-05

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之自繪製餅圖

 

相關文章