饼图基本完成 (附源码) 欢迎大家提建议
饼图做得差不多了,现在只能做到这个效果了。第一次用GDI+,进度有点慢。 立体饼图是从最下一层往上绘,有多高就绘多少次,效率确实不高。 还有其它工作,以后再改。
现在有个问题:像下边这种情况,不知道标注指示框该怎么处理?
下面是源码:
// MyPie.h : header file /////////////////////////////////////////////////////////////////////// //饼图数据结构定义 typedef struct _PIEITEM{ CString m_strItemName; //名称 int m_nNum; //数量 float m_percent; //百分比(>=0 && <=1) COLORREF m_penColor; //边框颜色 COLORREF m_brushColor; //填充颜色 }PIEITEM; //饼图项数据结构定义 typedef struct _PIEINFO{ PIEITEM *m_pPieItem; //饼图数据项数组 int m_nCount; //数据项个数 int m_nPieHeight; //饼图高度(当m_nPieHeight==1时就为平面饼图,m_nPieHeight>1就为立体饼图) int m_nOffset; //扇形的偏移距离(当m_nOffset==0时,每块扇形是合拢的,m_nOffset>0时,每块扇形就会分开) }PIEINFO; //饼图数据结构定义 //一个自封装的类,用于实现饼图绘制 class CMyPie : public CWnd { protected: //Data member PIEINFO m_pieInfo; //保存饼图信息 protected: //Method void DrawPie(CDC *pDC,CRect rc,PIEINFO pieInfo); //绘制饼图 // Construction public: CMyPie(); // Attributes public: // Operations public: void SetPieInfo(PIEINFO pieInfo); //设置饼图信息 void ReleasePieInfo(void); //取消PieInfo的设置(与SetPieInfo成对使用) // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CMyPie) //}}AFX_VIRTUAL public: virtual ~CMyPie(); // Generated message map functions protected: //{{AFX_MSG(CMyPie) afx_msg void OnPaint(); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; ////////////////////////////////////////////////////////////////////////////////////////////////////////////// //MyPie.cpp #define PI 3.1415926535898 // CMyPie CMyPie::CMyPie() { m_pieInfo.m_nCount=0; m_pieInfo.m_nOffset=0; m_pieInfo.m_nPieHeight=0; m_pieInfo.m_pPieItem=NULL; } CMyPie::~CMyPie() { } void CMyPie::SetPieInfo(PIEINFO pieInfo) { //设置饼图信息 m_pieInfo.m_nCount=pieInfo.m_nCount; m_pieInfo.m_nOffset=pieInfo.m_nOffset; m_pieInfo.m_nPieHeight=pieInfo.m_nPieHeight; m_pieInfo.m_pPieItem=new PIEITEM[pieInfo.m_nCount]; if(m_pieInfo.m_pPieItem!=NULL) { for(int i=0;i<pieInfo.m_nCount;i++) { m_pieInfo.m_pPieItem[i].m_brushColor=pieInfo.m_pPieItem[i].m_brushColor; m_pieInfo.m_pPieItem[i].m_penColor=pieInfo.m_pPieItem[i].m_penColor; m_pieInfo.m_pPieItem[i].m_strItemName=pieInfo.m_pPieItem[i].m_strItemName; m_pieInfo.m_pPieItem[i].m_percent=pieInfo.m_pPieItem[i].m_percent; m_pieInfo.m_pPieItem[i].m_nNum=pieInfo.m_pPieItem[i].m_nNum; } } } void CMyPie::ReleasePieInfo() { //取消PieInfo的设置(与SetPieInfo成对使用) m_pieInfo.m_nCount=0; m_pieInfo.m_nOffset=0; m_pieInfo.m_nPieHeight=0; if(m_pieInfo.m_pPieItem!=NULL) { delete [] m_pieInfo.m_pPieItem; m_pieInfo.m_pPieItem=NULL; } } BEGIN_MESSAGE_MAP(CMyPie, CWnd)//CGDIOperation) //{{AFX_MSG_MAP(CMyPie) ON_WM_PAINT() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CMyPie message handlers void CMyPie::OnPaint() { CPaintDC dc(this); // device context for painting // TODO: Add your message handler code here if(m_pieInfo.m_pPieItem!=NULL&&m_pieInfo.m_nCount>0) { CRect rc,rcDraw; this->GetClientRect(&rc); CDC dcMemory; dcMemory.CreateCompatibleDC(&dc); //创建内存设备描述符表 CBitmap bitmap,*pOldBitmap; bitmap.CreateCompatibleBitmap(&dc,rc.Width(),rc.Height()); pOldBitmap=dcMemory.SelectObject(&bitmap); dcMemory.BitBlt(0,0,rc.Width(),rc.Height(),&dc,0,0,SRCCOPY); rcDraw=rc; //把饼图区域压缩成200*90 int a = (rc.Width()-200) / 2; int b = (rc.Height()-90) / 2; rcDraw.DeflateRect(a,b,a,b); DrawPie(&dcMemory,rcDraw,m_pieInfo); //在内存中绘制饼图 dc.BitBlt(rc.left,rc.top,rc.Width(),rc.Height(),&dcMemory,0,0,SRCCOPY); //将饼图从内存设备描述符表绘制到显示设备描述符表 dcMemory.SelectObject(pOldBitmap); bitmap.DeleteObject(); dcMemory.DeleteDC(); } // Do not call CWnd::OnPaint() for painting messages } //画饼 主体函数 void CMyPie::DrawPie(CDC *pDC,CRect rc,PIEINFO pieInfo) { //此时传进来的rc为圆的外接矩形 int nX = rc.left;//左上角点 int nY = rc.top; int nWidth = rc.right - rc.left;//宽、高 int nHeight = rc.bottom - rc.top; float zdX = nX + nWidth/2.0;//饼中心点坐标 float zdY = nY + nHeight/2.0; float jd = 0.0;//已走过扇形度数 Color brushColor; Graphics graphics(pDC->m_hDC); graphics.SetSmoothingMode(SmoothingModeAntiAlias); SolidBrush theSolidBrush(Color(0,0,0)); Color penColor; Pen thePen(Color(0,0,0),1); float half,sx,sy,XsectorEnd,YsectorEnd,XsectorStart,YsectorStart; nY += pieInfo.m_nPieHeight + 1; nHeight -= pieInfo.m_nPieHeight; for (int j=0;j<pieInfo.m_nPieHeight;j++) //高度为nPieHeight,从底往上画nPieHeight次 { nY--; zdY = nY + nHeight/2.0; jd = 0.0; //已走过扇形度数 for(int i=0; i<pieInfo.m_nCount; jd+=pieInfo.m_pPieItem[i].m_percent * 360, i++)//逐个画扇形 { float a = nWidth/2.0; float b = nHeight/2.0; float c = tan((jd+pieInfo.m_pPieItem[i].m_percent * 360)*PI/180); float d = tan(jd*PI/180); //扇形终点 if ((jd+pieInfo.m_pPieItem[i].m_percent * 360) > 90 && (jd+pieInfo.m_pPieItem[i].m_percent * 360) < 270 ) XsectorEnd = -sqrt( a*a * b*b / ( a*a * c*c + b*b ) ); else XsectorEnd = sqrt( a*a * b*b / ( a*a * c*c + b*b ) ); YsectorEnd = zdY + c * XsectorEnd; XsectorEnd += zdX ; //扇形起点 if (jd > 90 && jd < 270) XsectorStart = -sqrt( a*a * b*b / ( a*a * d*d + b*b ) ); else XsectorStart = sqrt( a*a * b*b / ( a*a * d*d + b*b ) ); YsectorStart =zdY + d * XsectorStart; XsectorStart += zdX; //指示线终点 half=pieInfo.m_pPieItem[i].m_percent * 360 / 2; //指示线终点位于原长短轴加40的椭圆上 a += 40; b += 40; c = tan((jd+half)*PI/180); if ((jd+half) > 90 && (jd+half) < 270 ) sx = -sqrt( a*a * b*b / ( a*a * c*c + b*b ) ); else sx = sqrt( a*a * b*b / ( a*a * c*c + b*b ) ); sy = zdY + c * sx; sx += zdX ; brushColor.SetFromCOLORREF(pieInfo.m_pPieItem[i].m_brushColor); // theGDIColor = theGDIPlusColor.ToCOLORREF(); theSolidBrush.SetColor(brushColor); if (pieInfo.m_nPieHeight-1 == j) penColor.SetFromCOLORREF(RGB(0xf3,0xf3,0xf3)); //最顶层扇形边缘画灰白色 else penColor.SetFromCOLORREF(pieInfo.m_pPieItem[i].m_penColor); thePen.SetColor(penColor); //扇形向指示线方向偏移 m_nOffset ,得到X、Y轴的偏移量 float Xoffset = pieInfo.m_nOffset * cos((jd+half)*PI/180); float Yoffset = pieInfo.m_nOffset * sin((jd+half)*PI/180); graphics.TranslateTransform(Xoffset,Yoffset); //画扇形 if (pieInfo.m_pPieItem[i].m_percent > 0)//大于0.0%时才画扇形 { graphics.FillPie(&theSolidBrush, RectF(nX,nY,nWidth,nHeight), jd, pieInfo.m_pPieItem[i].m_percent * 360); graphics.DrawPie(&thePen, RectF(nX,nY,nWidth,nHeight), jd, pieInfo.m_pPieItem[i].m_percent * 360); if (j == pieInfo.m_nOffset/2 -1) //在中间层时绘制指示标注 { graphics.DrawLine(&thePen,PointF(zdX,zdY),PointF(sx,sy));//指示线 //输出文字 CString strItemName = pieInfo.m_pPieItem[i].m_strItemName; CString strPercent,strNum; float percent = pieInfo.m_pPieItem[i].m_percent * 100; strPercent.Format("%5.2f",percent); strNum.Format("%d台",pieInfo.m_pPieItem[i].m_nNum); strItemName = strItemName + "\n" + strPercent + "% " + strNum; WCHAR *wchar = strItemName.AllocSysString(); SolidBrush RectangleBrush(Color(150,251,254,156)); StringFormat stringFormat;//格式化文本输出格式 stringFormat.SetAlignment(StringAlignmentCenter);//水平居中 stringFormat.SetLineAlignment(StringAlignmentCenter);//垂直居中 FontFamily fontFamily(L"Arial"); Font theFont(&fontFamily,9,FontStyleRegular,UnitPoint); if (sx <= zdX) //终点在饼中心左边,指示框向左偏 { graphics.DrawLine(&thePen,PointF(sx,sy),PointF(sx-5,sy)); graphics.FillRectangle(&RectangleBrush, RectF(sx-95,sy-25,90,50)); graphics.DrawRectangle(&thePen,RectF(sx-95,sy-25,90,50)); graphics.DrawString(wchar,-1,&theFont, RectF(sx-95,sy-25,90,50), &stringFormat,&theSolidBrush); } else//向右偏 { graphics.DrawLine(&thePen,PointF(sx,sy),PointF(sx+5,sy)); graphics.FillRectangle(&RectangleBrush,RectF(sx+5,sy-25,90,50)); graphics.DrawRectangle(&thePen,RectF(sx+5,sy-25,90,50)); graphics.DrawString(wchar,-1,&theFont,RectF(sx+5,sy-25,90,50), &stringFormat,&theSolidBrush); } }//end 在中间层时绘制指示标注 } //end 大于0时画 //坐标移回去 graphics.TranslateTransform(-Xoffset,-Yoffset); }//end of i }//end of j }