본문 바로가기
컴퓨터/MFC_API

Pie (파이) 그래프를 그려 보자

by adnoctum 2013. 1. 5.




   pie 그래프를 그려 보자. CDC::AngleArc 를 사용하면 아주 간편하게 그릴 수 있다. 


pie 그래프를 그리기 위해서는 우선 부채꼴을 그려야 할 것이다. 부채꼴을 그리기 위해서는 CDC::AngleArc 함수를 사용하면 되는데, 우선 프로그램 상에서의 좌표계를 살펴 보자. 





모니터에서 0 도는 위와 같이 3시 방향이다. + 각도는 위처럼 반시계 방향으로 돌아가는 각도이다. 보통 sin 이나 cos 같은 수학함수의 파라미터는 단위가 radian 인데 AngleArc 는 (희안하게도 불편하게) degree 이다. 이 점을 주의하자. 어쨌든 위와 같다는 것을 기억하고, AngleArc 를 보면 다음과 같다. 





AngleArc 에서는 우선 모니터 좌표계로 중심점 (x,y) (위에서 푸른 점) 을 설정 후, 반지름을 pixel 단위로 넘겨 주고, fStartAngle 을 설정해 준다. 이 때 fStartAngle 은 위처럼 x 축을 0 도로 했을 때의 각도이다. fSweepAngle 은 start angle 에서 반시계 방향으로 돌릴 각도이다. 상당히 쉽다. 위처럼 할 때 주의해야 할 점 하나는 CDC::AngleArc 는 현재의 pen 의 위치에서 부채꼴의 시작 위치로 선을 그은 후 부채꼴을 그린다는 점이다. 따라서 완전한 부채꼴 하나만을 그리기 위해선 우선 (x,y) 로 CDC::MoveTo 로 pen 을 옮긴 후 그려야 한다는 것이다. 내가 만든 루틴의 결과는 다음과 같다. 







위를 위한 source code 는 다음과 같다. 자세한 내용은 본문으로 쓰지 않고 코드 주석으로 남겨 놓는다. 


환경 : Visual Studio 2010 on Windows 7  Professional SP1, English, 64-bit. 


bool CStimuliWnd::save_data_as_pie_graph( const std::wstring& out_name, // 저장할 그림 파일 이름.  std::vector<double>& value, // 이 값들을 색으로 바꿔서 pie graph 를 그릴 것이다.  double min_value, // 값이 가질 수 있는 이론적인 최소값 double max_value // 값이 가질 수 있는 이론적인 최대값 ) { const double radian = 180.0/3.1415926535;  int width = 500; // 결과 그림 파일의 size.  int height = 500;  int n = (int)(value.size()); // 그려야 할 부채꼴의 개수. CImage image;  image.Create(width, height, 24);  CDC *pDC = CDC::FromHandle(image.GetDC());  CBrush brush(RGB(255,255,255)); CPen grayPen(PS_SOLID, 1, RGB(128,128,128)); // 부채꼴 사이에 그릴 선의 색.  CPen *pOldPen = pDC->SelectObject(&grayPen);  CBrush *pOldBrush = pDC->SelectObject(&brush);  pDC->FillSolidRect(0,0,width,height,0xffffff); // pie 그래프 밖의 색은 흰 색으로.  pDC->SelectObject(pOldBrush);  const double rad2deg = 180.0/3.1415926535; // radian -> degree 로 바꿀 때.  CPoint image_center = get_center_pos(0, 0, width, height);  // 0 rad. 는 현재 3시 방향. 첫 번째 segment 를 12시 방향으로 바꾸되,  // 한 segment 의 절반만큼 다시 반시계 방향으로 돌려서 맞춘다. double theta_step = 2*3.1415926535/n; // radian.  // π/2 rad. 만큼 반시계 방향으로 돌린 후 다시 Θ/2 만큼 더 돌린다.  double theta = 0.5*theta_step + 0.5*3.1415926535; // radian.  double r = __min(width, height)*0.5 - 2; // 사각형의 그림 파일에 꽉 차면 보기 싫다. double f = 1.0/(max_value - min_value); // 값을 color code 로 바꿀 때 필요한 상수. std::vector<double>::const_iterator pos; for(pos = value.begin(); pos != value.end(); pos++, theta -= theta_step){ pDC->MoveTo(image_center);  pDC->BeginPath(); // path 를 열어서, 부채꼴을 path 에 집어 넣자.  pDC->AngleArc(image_center.x, image_center.y, r, theta*rad2deg, -theta_step*rad2deg);  pDC->EndPath();  CRgn arc;  arc.CreateFromPath(pDC); // 위에서 만든 path로부터 region 을 만들자.  // [value]의 각 원소의 값에 따라 녹색의 밝기를 조절하자.  // [min_value]는 검은색, [max_value]는 완전한 녹색으로.  int code = (int)(0.5 + (*pos - min_value)*f*255);  if(code > 255) code = 255;  COLORREF cr = RGB(0,code, 0);  // [min_value] 보다 작으면 잘못된 값, 또는 없는 값.  // 이럴 땐 회색으로 색칠하자.  if(*pos < min_value) cr = 0x404040;  CBrush brush(cr);  pDC->FillRgn(&arc, &brush); // 부채꼴 모양으로 만들어진 region 을 칠하자.  } // 부채꼴 사이사이에 회색 선을 다시 그려 주자.  theta = 0.5*theta_step + 0.5*3.1415926535; for(pos = value.begin(); pos != value.end(); pos++, theta -= theta_step){ pDC->MoveTo(image_center);  pDC->AngleArc(image_center.x, image_center.y, r, theta*rad2deg,  -theta_step*rad2deg);  } pDC->SelectObject(pOldPen);  image.ReleaseDC();  image.Save(out_name.c_str());  return true; }


현재 나는 작업의 특성 상 가장 첫 segment (부채꼴)이 12시 방향에서 시작하지 않고 있으니 참고 한다. 또한, 위 코드는 부채꼴의 각도가 매우 작은 경우를 고려하지 않고 있으니 이 부분도 주의 한다. 나의 경우 8개의 부채꼴의 합으로 원을 표현하는 것은 변하지 않는 것이라[각주:1] 위처럼 하여도 별 문제가 없었다.  


우선 AngleArc 로 부채꼴을 그릴 것인데, 그렇게 DC 에 의해 그려지는 경로를 Path 로 집어 넣은 후, 그것으로부터 CRgn 객체의 영역을 만든다. 그 후 그 영역 내부를 FillRgn 함수를 이용하여 칠하자. AngleArc 는 current pen 으로 부채꼴을 그리는데 부채꼴의 안은 색을 칠하지 않는다. 따라서 위처럼 해주면 된다. 


위의 경우 CImage 를 이용해서 쉽게 그림 파일로 저장했는데, CDC 만 이용해도 위 작업은 대부분 그대로 진행할 수 있고, 저장 역시 CDC 를 쉽게 bmp 로 저장할 수 있으니 위 코드는 Visual C++ 6.0 에서도 쉽게 구현할 수 있을 것이다. 






좀 informal 하게, ㅋㅋ. 사실 위 작업을 하기 전까지는 부채꼴까지 일일이 그렸었는데, 이렇게저렇게 살펴 보다 보니 위처럼 각도로 쉽게 부채꼴을 그리는 API 가 있었다. 위의 작업을 하기 위해 대략 3번의 변화가 있었다. 가장 처음은 부채꼴을 직접 그린 후, 이미 그려진 다른 그림 위에 pie 그래프를 올려 놓아야 했기에 BitBlt 의 raster operation 을 적당히 이용해서 작업을 했었다(다른 프로그램에서). 그 후, 위 프로그램에서 하려다 보니, 단순히 pie 그래프만 그리면 되었기 때문에 BitBlt 를 여러 번 이용할 일은 없어졌다. 그래서 부채꼴만 다시 그렸는데, 문제는 BitBlt 를 쓰지 않고 직접 그린 부채꼴 안쪽을 내가 원하는 색으로 칠하자니 조금 번거로워 졌다. FloodFill 을 사용했는데, 색을 칠하는 시작 위치는 간단하게 sweep angle 의 절반, r 을 절반으로 잡아서 부채꼴의 가운데로 지정해서 FloodFill 을 시켰다. 그런데 이렇게 하다 보면 부채꼴 가장자리가 몇 픽셀씩 없어지곤 했다. 그 부분이 계속 눈에 거슬렸었지. 그래서 계속 이렇게 저렇게 찾아 보다가, AngleArc 를 알게 되었다. 그러자..., ㅋㅋ, path --> region --> fill region 이 단박에 떠 올랐고, 그래서 위처럼 간단하게 변경하였다. 아놔, 이렇게 간단하게 할 수 있는 일을. 가끔씩 너무 기본적인 것까지 일일이 하려 한다니까, ㅋ. 




  1. 정립된 심리학 검사라 거의 변하지 않는다고 가정했다. [본문으로]