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、示例下載