2022-01-14 update log: 코드 중 오타 수정.
이전 포스팅에서 소개했던 그래프를 그려주는 MFC 라이브러리, Chart Control의 기본 사용법입니다. CodeProject에도 기본 사용법이 잘 나와있지만 저같은 초보는 맘먹고 정독해야 겨우 이해할 수 있기 때문에 그 겨우 이해한 내용을 기록으로 남깁니다.
라이브러리 소스는 아래 포스팅 참고하시면 됩니다.
Chart Control 라이브러리를 이용해서 기본 그래프와 캔들차트를 그리는 예제 프로젝트를 만들어 봤고 그 과정으로 Step by step으로 남기겠습니다.
MFC 기본 프로젝트 생성
먼저 Visual Studio 2019를 시작하고 새 프로젝트 만들기를 눌러서 새로운 프로젝트를 시작합니다. 저는 Community 2019버전을 사용했습니다. VS개발환경에 대한 내용은 생략하도록 하겠습니다.
프로젝트 생성마법사에서 "MFC"로 검색을 하고 MFC앱을 선탠하고 다음을 눌러줍니다.
프로젝트 이름과 프로젝트의 위치를 지정해서 한번더 다음을 눌러줍니다.
MFC 앱 설정창에서는 종류를 "대화 상자 기반"으로 바꿔주고 마침을 눌러줍니다. 대화 상자 기반을 이용하는 특별한 이유는 없습니다. 그냥... 전 이것만 사용해봐서 이렇게 해요. ;;
프로젝트가 생성이 되면, F5 한번 눌러서 기본 앱이 잘 실행되는지 확인을 하고 다음으로 넘어가겠습니다.
프로젝트에 라이브러리 추가하기
본문의 앞쪽에서 언급된 Chart Control의 소개 포스팅에서 라이브러리의 소스파일을 다운받고 만들어준 프로젝트 폴더에 저장해 줍니다.
저는 아래 그림처럼 프로젝트 소스코드 폴더에 같이 저장해 주었습니다.
Visual Studio로 돌아가서 솔루션 탐색기에서 다운받아온 라이브러리 소스들을 추가해 줍니다. 저는 편의를 위해서 다음과 같이 "ChartCtrl"이라는 필터를 하나 만들어 주고 그 안에 소스를 추가하였습니다.
추가 하는 방법은 새로 만든 ChartCtrl필터를 우클릭해서 "추가 > 기존 항목" 을 차례대로 선택하고
아까 다운받았던 소스파일들을 몽땅 선택해서 확인을 눌러줍니다.
다이얼로그 구성
리소스 뷰 창에서 메인 다이얼로그를 더블클릭해서 다이얼로그 디자인 페이지를 열어줍니다.
그리고 도구창에서 "Custom Control"을 찾아서 다이얼로그 창으로 끌고와서 적당한 크기로 배치를 해 줍니다.
"Custom Control"의 속성을 다음 그림에 표시된 부분과 같이 수정을 해 줍니다. 마지막 부분의 'Style'은 대화상자의 갱신이 일어날때마다 번쩍거리는 현상을 피하기 위해서 설정해 주는 부분이라고 합니다. 스타일의 상세내용은 MS의 다음 문서의 설명을 참고하면 됩니다.
https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles
이제 마우스 오른쪽 클릭을 해서 '클래스 마법사'를 열어줍니다. 단축키가 'Ctrl + Shift + X'네요. 알아두면 유용합니다.
클래스 마법사 창에서 '멤버 변수'탭으로 이동한 다음 위에서 만들었던 Custom Control에 변수를 등록해 주기 위해서 컨트롤의 아이디를 선택하고 '변수추가' 버튼을 눌러줍니다.
변수 추가창에서 아래와 같이 변수의 이름과 타입을 지정하고 '마침'을 눌러줍니다.
이렇게 하면 메인 다이얼로그 헤더파일인 "MFC_ChartCtrlDlg.h"에 "CChartCtrl" 클래스 선언이 추가됩니다.
그리고 cpp파일에는 자동으로 DDX_Control이 추가됩니다.
그런데 아직 이 프로젝트에 ChartCtrl 클래스 라이브러리를 연결해 주지 않았기 때문에 CChartCtrl이 어떤 형식인지 모르기 때문에 인텔리센스가 빨간줄로 표시하고 있습니다.
라이브러리 추가
준비의 마지막 단계인 ChartCtrl 라이브러리를 연결시켜 줍니다.
다이얼로그 헤더에 다음과 같이 "ChartCtrl.h" 헤더를 include 해주면 되는데 폴더안에 있어서인지 폴더경로까지 넣어주어야 인식을 하네요.
이제 모든 준비가 완료 되었습니다. 여기서 한번더 "F5"키를 눌러서 에러가 없는지 확인해 봅니다. 빠뜨린 내용없이 잘 따라왔다면 문제없이 컴파일이 되고 빈창이 실행됩니다.
이제 여기에 차트를 그려보도록 하겠습니다.
차트 그리기
처음 예제로 f(x, y) 형식의 2차원 데이터를 X-Y평면상에 그려주는 가장 기본적인 그래프와 주식거래 데이터를 표현하는 봉차트를 그려보겠습니다.
차트타입에 맞는 헤더파일 추가
먼저 필요한 헤더파일을 추가해 줍니다. 일반적인 라인을 직교좌표에 그리는 "ChartLineSerie.h"헤더와 봉차트를 그리는 "ChartCandlestickSerie.h"를 추가해 줍니다. 위치는 메인 다이얼로그의 헤더파일입니다.
멤버변수 추가
이제 화면에 그려질 데이터가 저장될 변수를 선언해 줍니다.
'CChartLineSerie'와 'CChartCandlestickSerie'는 각각 라인차트와 봉차트를 그리는데 필요한 데이터가 저장될 구조체입니다.
그리고 아래 그림에 42번 라인에 있는 "ReadData()"함수는 봉차트를 그리기 위해서 필요한 데이터를 외부파일에서 읽어오기 위한 함수입니다. 일단 같이 선언해 줍니다.
봉차트 데이터를 불러올 ReadData()함수 정의
그리고 아래코드는 ReadData() 함수의 정의입니다.
23번째 줄에 보시는 것처럼 저는 일단 봉차트를 그릴 데이터를 수집해서 '날짜, 시가, 종가, 고가, 저가, 거래량' 순으로 쉼표로 구분하여 저장해 두었습니다.
void CMFCChartCtrlDlg::ReadData(SChartCandlestickPoint (&pCandlePoint)[600])
{
UpdateData(TRUE);
//@ 파일에서 데이터 불러오기
// 파일열기
CStringA strCSVfileName = (CStringA)theApp.m_sAppPath + L"\\data\\testdata.csv";
char* BufOfFileName = strCSVfileName.GetBuffer(strCSVfileName.GetLength());
FILE* f = nullptr;
errno_t err;
err = fopen_s(&f, BufOfFileName, "rt"); //읽기모드
if (err || f == NULL)
{
AfxMessageBox(L"파일열기 실패");
return;
}
double temp;
int year, month, day;
for (int i = 0; i < 600; i++)
{
fscanf_s(f, "%4d%2d%2d,%lf,%lf,%lf,%lf,%lf\n", // 날짜, 시가, 종가, 고가, 저가, 거래량
&year, &month, &day,
&pCandlePoint[i].Open,
&pCandlePoint[i].Close,
&pCandlePoint[i].High,
&pCandlePoint[i].Low,
&temp);
COleDateTime date(year, month, day, 0, 0, 0);
pCandlePoint[i].XVal = CChartCtrl::DateToValue(date);
}
fclose(f); //파일 닫기
}
위 코드에서 7번째 줄에 파일을 읽어오기위해서 파일이 저장된 경로를 지정하기 위해서 "m_sAppPath"라는 변수를 사용하였습니다. 이 변수는 지금 만들고 있는 프로그램이 실행이 되면 그 실행 경로를 저장하기 위한 변수입니다. 다음과 같이 'Ctrl + Shift + X'를 눌러서 클래스 마법사를 열어서 변수를 추가해 줍니다.
※확인사항
여기서 한가지 확인할 사항이 있습니다. 변수 m_sAppPath는 Public으로 생성을 하였는데 제컴퓨터의 경우 컴파일을 하면 변수에 접근을 할 수 없다고 에러가 났습니다. 그래서 클래스 마법사에서 생성한 변수가 선언된 코드를 찾아가서 다음과 같이 수정을 해야지만 문제가 해결되었습니다.
그리고 아래 코드를 프로그램 메인 cpp파일의 초기화 루틴에 추가해 줍니다. 현재 프로젝트 기준으로 "MFC_ChartCtrl.cpp"파일에 "BOOL CMFCChartCtrlApp::InitInstance()" 함수입니다. 적당한 위치에 추가를 해 줍니다.
이코드의 역할은 프로그램이 실행되는 경로를 확인하고 그 위치에 "data"라는 폴더의 유무를 확인하고 없으면 만들어줍니다.
//////////////////////////////////////////////////////////////////
/// 실행파일의 경로를 저장
///--------------------------------------------------------------
wchar_t szPath[MAX_PATH];
GetModuleFileName(AfxGetInstanceHandle(), szPath, MAX_PATH);
*wcsrchr(szPath, '\\') = '\0';
m_sAppPath = szPath;
/// data를 저장할 폴더를 만들어주기
///--------------------------------------------------------------
CString strFileName = m_sAppPath + "/data";
if (!::PathIsDirectory(strFileName)) // 폴더가 없다면
{
::CreateDirectory(strFileName, NULL);
}
////////////////////////////////////////////////////////////////////
본격적으로 그래프를 그리기
이제는 정말 모든 준비가 끝났네요. 프로그램에 ChartCtrl 이식도 끝났고 필요한 변수들도 선언이 되었고 필요한 데이터도 준비가 되었습니다. 라인차트를 그릴 데이터는 복잡한 데이터가 필요한게 아니기 때문에 간단하게 사인함수를 이용하도록 하겠습니다.
아래 코드는 프로그램이 시작되면 바로 그래프를 그려주도록 메인 다이얼로그 cpp 파일(MFC_ChartCtrlDlg.cpp)의 "BOOL CMFCChartCtrlDlg::OnInitDialog()" 함수에 "//TODO: 주석"과 "return" 명령어 사이에 추가를 해 줍니다.
// TODO: 여기에 추가 초기화 작업을 추가합니다.
/// 초기화
CChartDateTimeAxis* pBottomAxis = m_ChartCtrl.CreateDateTimeAxis(CChartCtrl::BottomAxis);
CChartStandardAxis* pLeftAxis = m_ChartCtrl.CreateStandardAxis(CChartCtrl::LeftAxis);
pLeftAxis->SetAutomaticMode(CChartAxis::FullAutomatic);
pBottomAxis->SetAutomaticMode(CChartAxis::FullAutomatic);
pBottomAxis->SetDiscrete(false);
m_ChartCtrl.ShowMouseCursor(false);
CChartCrossHairCursor* pCrossHair = m_ChartCtrl.CreateCrossHairCursor();
/// 라인차트 파트
CChartXYSerie* pSeries = nullptr;
pSeries = m_ChartCtrl.CreateLineSerie();
double XVal[50];
double YVal[50];
for (int i = 0; i < 50; i++)
{
COleDateTime date(2017, 6, 1, 0, 0, 0);
XVal[i] = CChartCtrl::DateToValue(date) + i * 16;
YVal[i] = sin(i) * 5000 + 47000;
}
pSeries->SetPoints(XVal, YVal, 50);
pSeries->SetColor(RGB(255, 0, 0));
pSeries->CreateBalloonLabel(5, _T("This is a sin curve"));
/// 봉차트 파트
CChartCandlestickSerie* pCandle = nullptr;
pCandle = m_ChartCtrl.CreateCandlestickSerie();
SChartCandlestickPoint pCandlePoint[600];
ReadData(pCandlePoint);
pCandle->SetPoints(pCandlePoint, 600);
pCandle->SetColor(RGB(0, 255, 0));
pCandle->CreateBalloonLabel(5, _T("This is a candle"));
return TRUE; // 포커스를 컨트롤에 설정하지 않으면 TRUE를 반환합니다.
워낙 잘 만들어진 코드라 어렵고 복잡하진 않지만 ChartCtrl 라이브러리를 사용하는데 가장 중요한 부분이 여기이기 때문에 사용된 기본적인 내용 한줄씩 정리해 보겠습니다.
4~7행: 그래프를 그리기 위해서 먼저 어떤 축을 사용할지 정해야 하는데요. ChartCtrl은 그래프가 그려지는 창 기준으로 상하좌우에 각각 1개씩 총 4개의 축을 설정할 수 있습니다. 여기서는 좌측(LeftAxis)과 하단(BottomAxis) 축을 사용하겠다는 것이다. 좌측은 일반축(StandardAxis) 하단은 날짜와시간축(DateTimeAxis)으로 시정하고 있습니다.
※여기서는 축을 자동(FullAutomatic)으로 설정하였는데 경우에 따라 임의로 지정할 필요도 있을텐데요. 그때는 다음과 같이 축의 최소값, 최대값, 눈금간격, 라벨형식 등을 지정해 줄 수 있습니다.
COleDateTime minValue(2019, 1, 1, 0, 0, 0);
COleDateTime maxValue(2019, 9, 30, 0, 0, 0);
pBottomAxis->SetMinMax(CChartCtrl::DateToValue(minValue), CChartCtrl::DateToValue(maxValue));
pBottomAxis->SetTickIncrement(false, CChartDateTimeAxis::tiMonth, 1);
pBottomAxis->SetTickLabelFormat(false, _T("%b %Y"));
9행: 연속적이지 않은 데이터를 라인으로 연결할때 그 연결선이 각지지 않고 부드러운 곡선으로 이어지도록 하는 옵션입니다. 데이터의 산포정도에 따라 다르겠지만 엑셀에서처럼 그런 부드러운 곡선까지는 안되는것 같습니다.
10행: 그려진 그래프 위로 마우스를 움직일때 마우스 커서를 숨겨주는 옵션입니다.
11행: 10행에서 마우스 커서를 숨기는 대신에 마우스 포인터 위치에서 상하좌우로 그래프 범위 끝까지 연결선을 표시해주는 옵션입니다.
14~15행: 라인차트의 데이터를 저장하기 위한 공간을 설정하는 부분입니다. pSeries 포인터를 저장변수로 설정했습니다.
17~24행: XVal 변수와 YVal변수를 각각 만들어서 각각에 임의의 데이터를 넣어주는 부분입니다. X값은 2017년6월1일 부터의 날짜를 나타내는 더블형 값 넣어주고 Y에도 역시 사인함수를 따라서 증감을 하는 임의의 더블형 값을 넣어서 배열로 만들었습니다. (수식에 사용된 함수는 나중에 그릴 봉차트와 스케일을 맞추기 위해서 임의로 넣은 값입니다.)
25행에 "SetPoints"라는 함수를 사용해서 15행에서 지정한 포인터에 위에서 만든 데이터 값을 넣어줍니다.
26~27행: 그래프의 색상과 표시할 라벨을 설정합니다.
30~32행: 이번에는 봉차트의 데이터를 저장할 변수입니다. pCandlePoint이라는 배열을 저장변수로 설정했습니다.
34행: 앞에서 추가했던 파일에서 데이터를 읽어오는 루틴을 호출합니다. 함수가 실행되고 나면 pCandlePoint에 파일에 있던 데이터가 로딩됩니다.
36행: 파일에서 로딩한 데이터를 그래프를 그릴 구조체에 넣어줍니다. 역시 SetPoints라는 함수를 사용합니다.
37~38행: 그래프 색상과 라벨을 설정합니다.
이제 모든 준비가 끝났습니다. 빌드(F7)를 해서 실행파일이 잘 만들어 지는지 확인합니다.
문제없이 빌드가 완료되었다면 실행파일이 만들어진 위치를 찾아가서 "data"라는 폴더를 만들어주고 다음 파일을 다운받아서 저장합니다.
이 파일은 2017년 4월6일부터 2019년9월20일까지 삼성전자의 일봉데이터입니다.
결과
문제없이 잘 따라왔다면 다음과 같은 그래프를 보실 수 있습니다.
이 그래프는 기본적으로 마우스 좌측 드래그로 그래프 줌이 가능하고 오른쪽 드래그로는 그래프 이동이 가능합니다.
그래프 시인성을 높이기 위해서 몇가지만 더 손을 본다면 정말 유용하게 활용할 수 있는 라이브러리인것 같습니다.
아쉬운건 마우스 휠로 그래프 줌이나 그래프 이동이 된다면 좋을것 같은데 그 기능이 없어서 아쉽네요. 시간이 된다면 추가해 봐야 겠어요. 틀이 탄탄하게 잘 만들어진 라이브러리라서 깊이있는 지식없는 저도 도전해볼만한것 같습니다.
끝!
'Software > C++&MFC' 카테고리의 다른 글
[라이브러리] C++ 라이브러리, Boost 빌드하고 사용하기 (0) | 2019.10.08 |
---|---|
MFC그래프 라이브러리, ChartCtrl 마우스 휠 기능 추가 (0) | 2019.10.07 |
[라이브러리] MFC 그래프 라이브러리 - High-speed Charting Control (ChartCtrl) (0) | 2019.09.20 |
C++ 동적 메모리 (Dynamic Memory) 할당 (2) | 2019.02.28 |
댓글