[wxWidgets] wxChartDir Simple Real-Time Chart 간단한 리얼타임 차트
C, C++ 2026. 5. 10. 22:40 |wxChartDir을 이용해 간단한 실시간 차트를 그려보자.
2026.05.05 - [C, C++] - [wxWidgets] wxChartDir 차트 그리기
#include <wx/wx.h>
#include <wxchartdir/chartdir.h>
#include <wxchartdir/wxchartviewer.h>
#include <time.h>
#include <stdlib.h>
#pragma comment(lib, "chartdir70.lib")
#pragma comment(lib, "wxchartdir.lib")
class MyApp : public wxApp
{
public:
bool OnInit() override;
};
wxIMPLEMENT_APP(MyApp);
class MyFrame : public wxFrame
{
public:
MyFrame();
private:
void OnToggle(wxCommandEvent& event);
void OnExit(wxCommandEvent& event);
void OnAbout(wxCommandEvent& event);
void OnTimer(wxTimerEvent& event); // 실시간 업데이트를 위한 타이머 이벤트
void drawChart(wxChartViewer* viewer); // 차트를 갱신하는 함수
wxPanel* m_panel; // 차트를 담을 패널
wxChartViewer* m_chartViewer; // 차트를 담을 뷰어
wxTimer m_timer; // 주기적으로 차트를 갱신할 타이머
static const int m_sampleSize = 100; // 실시간으로 표시될 데이터 개수
double m_timeStamps[m_sampleSize]; // X축 시간 데이터
double m_dataSeries[m_sampleSize]; // Y축 값 데이터
};
enum
{
ID_ToggleTimer = 1
};
bool MyApp::OnInit()
{
MyFrame* frame = new MyFrame();
frame->Show(true);
return true;
}
// 메인 프레임의 위치(default position)와 크기(600x450)를 설정한다.
MyFrame::MyFrame() : wxFrame(nullptr, wxID_ANY, "Real-Time Chart Example", wxDefaultPosition, wxSize(600, 450))
{
wxMenu* menuFile = new wxMenu;
menuFile->Append(ID_ToggleTimer, "&Toggle Timer...\tCtrl-H", "Start/Stop the real-time update");
menuFile->AppendSeparator();
menuFile->Append(wxID_EXIT);
wxMenu* menuHelp = new wxMenu;
menuHelp->Append(wxID_ABOUT);
wxMenuBar* menuBar = new wxMenuBar;
menuBar->Append(menuFile, "&File");
menuBar->Append(menuHelp, "&Help");
SetMenuBar(menuBar);
CreateStatusBar();
SetStatusText("Welcome to Real-Time Chart!");
m_panel = new wxPanel(this, wxID_ANY);
wxButton* myButton = new wxButton(m_panel, wxID_ANY, "Toggle Timer", wxPoint(450, 10), wxSize(100, 30));
myButton->Bind(wxEVT_BUTTON, &MyFrame::OnToggle, this); // 토글 버튼 이벤트와 타이머 토글 함수 연결
Bind(wxEVT_MENU, &MyFrame::OnToggle, this, ID_ToggleTimer); // 토글 메뉴 아이템과 타이머 토글 함수 연결
Bind(wxEVT_MENU, &MyFrame::OnAbout, this, wxID_ABOUT);
Bind(wxEVT_MENU, &MyFrame::OnExit, this, wxID_EXIT);
// 차트 뷰어 설정 및 위치/크기 고정
m_chartViewer = new wxChartViewer(m_panel, wxID_ANY, wxPoint(10, 30), wxSize(550, 330));
// 시간 배열과 데이터 배열 초기화
// 현재 시간부터 과거로 100개의 타임스탬프를 생성 (250ms 간격), 데이터는 아직 없으므로 NoValue로 초기화
double currentTime = Chart::chartTime2((int)time(0));
// chartTime2는 int값을 차트에서 사용하는 시간 형식으로 변환하는 함수
// time(0)은 현재 시간을 time_t 형식으로 반환하는 함수이며, 이를 int로 캐스팅하여 chartTime2에 전달한다.
for (int i = 0; i < m_sampleSize; ++i) {
m_timeStamps[i] = currentTime + (i - m_sampleSize) * 0.25; // 과거로 100개의 타임스탬프 생성 (250ms 간격)
m_dataSeries[i] = Chart::NoValue; // 아직 데이터가 없으므로 NoValue 부여
}
// 타이머 시작 (250ms 마다 OnTimer 실행)
m_timer.Bind(wxEVT_TIMER, &MyFrame::OnTimer, this, m_timer.GetId());
m_timer.Start(250);
// 초기 차트 그리기
drawChart(m_chartViewer);
}
void MyFrame::OnExit(wxCommandEvent& event)
{
Close(true);
}
void MyFrame::OnAbout(wxCommandEvent& event)
{
wxMessageBox("This is a wxWidgets Hello World example", "About Hello World", wxOK | wxICON_INFORMATION);
}
void MyFrame::OnToggle(wxCommandEvent& event)
{
// 타이머 일시정지/재시작 토글 처리
if (m_timer.IsRunning()) {
m_timer.Stop();
SetStatusText("Timer Stopped.");
}
else {
m_timer.Start(250);
SetStatusText("Timer Started.");
}
}
void MyFrame::OnTimer(wxTimerEvent& event)
{
// 데이터를 하나씩 앞으로 이동 (Shift)
for (int i = 1; i < m_sampleSize; ++i) {
m_timeStamps[i - 1] = m_timeStamps[i];
m_dataSeries[i - 1] = m_dataSeries[i];
}
// 가장 최신(마지막) 인덱스에 새 타임스탬프 입력
m_timeStamps[m_sampleSize - 1] = m_timeStamps[m_sampleSize - 2] + 0.25; // 250ms 증가
// 바로 직전 데이터에 랜덤 변화량을 주어 값을 생성 (Random Walk)
double currentVal = (m_dataSeries[m_sampleSize - 2] == Chart::NoValue) ? 50 : m_dataSeries[m_sampleSize - 2];
currentVal += (rand() % 11) - 5;
// 값이 범위(10~90)를 크게 벗어나지 않게 보정
if (currentVal < 10)
currentVal = 10;
if (currentVal > 90)
currentVal = 90;
m_dataSeries[m_sampleSize - 1] = currentVal;
// 차트 갱신
drawChart(m_chartViewer);
}
void MyFrame::drawChart(wxChartViewer* viewer)
{
// 550x330 크기의 XYChart 객체 생성
XYChart* c = new XYChart(550, 330);
// 백그라운드 색상 및 플롯 영역, 그리드 크기 설정
c->setBackground(0xeeeeee);
// 플롯 영역을 설정 (x, y, width, height) 및 배경색, 대체색(-1), 엣지색(-1), 가로 그리드색, 세로 그리드색
c->setPlotArea(40, 20, 480, 260, 0xffffff, -1, -1, 0xcccccc, 0xcccccccc);
// X축을 시간 포맷(hh:nn:ss)으로 설정
c->xAxis()->setLabelFormat("{value|hh:nn:ss}"); // value: 차트가 표시하는 값, hh:nn:ss: 시간 포맷
c->xAxis()->setTickDensity(75); // X축 눈금 간격을 조절하여 시간 간격이 일정하게 보이도록 설정 (75 픽셀마다 눈금 표시)
c->xAxis()->setTitle("Time");
// Y축은 실시간 데이터 변동이 보이게 크기를 고정 (0~100)
c->yAxis()->setLinearScale(0, 100, 20); // 0부터 100까지 20 간격으로 눈금 표시
c->yAxis()->setTitle("Value");
// 라인 레이어를 생성하고 선 색상을 지정, "Live Data"는 범례(Legend)에 표시될 이름
LineLayer* layer = c->addLineLayer(DoubleArray(m_dataSeries, m_sampleSize), 0xff0000, "Live Data");
layer->setXData(DoubleArray(m_timeStamps, m_sampleSize));
layer->setLineWidth(2); // 선의 두께를 2로 설정하여 실시간 데이터의 변동이 더 잘 보이도록 함
// 범례(Lengend) 박스 설정 (x, y, vertical layout, font, font size) 및 배경색과 테두리 색상
c->addLegend(430, 30, false, "Arial Bold", 9)->setBackground(0xeeeeee, 0xcccccc);
// 생성한 차트를 뷰어에 연결하면 내부적으로 화면이 갱신됨
viewer->setChart(c);
//viewer->updateViewPort(true, true); // 뷰포트 업데이트 (차트가 변경되었음을 알림)
// setchart()는 새로운 차트를 뷰어에 설정하는 함수이고, updateViewPort()는 뷰포트가 변경되었음을 알리는 함수이다.
// setchart()를 호출하면 내부적으로 차트가 변경되었다고 인식하지만, updateViewPort()를 명시적으로 호출하여 뷰포트
// 업데이트를 트리거하는 것이 좋다. 이렇게 하면 차트가 변경된 후에 뷰포트가 올바르게 갱신되어 화면에 최신 차트가 표시된다.
// 지금은 마우스 휠로 확대/축소하는 기능은 없지만 그런 기능이 추가되면 차트가 변경될 때마다 명시적으로 updateViewPort()를
// 호출하여 뷰포트를 갱신하는 것이 좋다.
// ChartViewer에 넣은 뒤 완벽하게 안전하게 지울 수 있음
delete c;
}

단순한 배열이 아닌 deque 자료구조를 사용해 보자.
#include <wx/wx.h>
#include <wxchartdir/chartdir.h>
#include <wxchartdir/wxchartviewer.h>
#include <time.h>
#include <stdlib.h>
#include <deque>
#pragma comment(lib, "chartdir70.lib")
#pragma comment(lib, "wxchartdir.lib")
class MyApp : public wxApp
{
public:
bool OnInit() override;
};
wxIMPLEMENT_APP(MyApp);
class MyFrame : public wxFrame
{
public:
MyFrame();
private:
void OnToggle(wxCommandEvent& event);
void OnExit(wxCommandEvent& event);
void OnAbout(wxCommandEvent& event);
void OnTimer(wxTimerEvent& event); // 실시간 업데이트를 위한 타이머 이벤트
void drawChart(wxChartViewer* viewer); // 차트를 갱신하는 함수
wxPanel* m_panel; // 차트를 담을 패널
wxChartViewer* m_chartViewer; // 차트를 담을 뷰어
wxTimer m_timer; // 주기적으로 차트를 갱신할 타이머
static const int m_sampleSize = 100; // 실시간으로 표시될 데이터 개수
std::deque<double> m_timeStamps; // X축 시간 데이터
std::deque<double> m_dataSeries; // Y축 값 데이터
};
enum
{
ID_ToggleTimer = 1
};
bool MyApp::OnInit()
{
MyFrame* frame = new MyFrame();
frame->Show(true);
return true;
}
// 메인 프레임의 위치(default position)와 크기(600x450)를 설정한다.
MyFrame::MyFrame() : wxFrame(nullptr, wxID_ANY, "Real-Time Chart Example", wxDefaultPosition, wxSize(600, 450))
{
wxMenu* menuFile = new wxMenu;
menuFile->Append(ID_ToggleTimer, "&Toggle Timer...\tCtrl-H", "Start/Stop the real-time update");
menuFile->AppendSeparator();
menuFile->Append(wxID_EXIT);
wxMenu* menuHelp = new wxMenu;
menuHelp->Append(wxID_ABOUT);
wxMenuBar* menuBar = new wxMenuBar;
menuBar->Append(menuFile, "&File");
menuBar->Append(menuHelp, "&Help");
SetMenuBar(menuBar);
CreateStatusBar();
SetStatusText("Welcome to Real-Time Chart!");
m_panel = new wxPanel(this, wxID_ANY);
wxButton* myButton = new wxButton(m_panel, wxID_ANY, "Toggle Timer", wxPoint(450, 10), wxSize(100, 30));
myButton->Bind(wxEVT_BUTTON, &MyFrame::OnToggle, this); // 토글 버튼 이벤트와 타이머 토글 함수 연결
Bind(wxEVT_MENU, &MyFrame::OnToggle, this, ID_ToggleTimer); // 토글 메뉴 아이템과 타이머 토글 함수 연결
Bind(wxEVT_MENU, &MyFrame::OnAbout, this, wxID_ABOUT);
Bind(wxEVT_MENU, &MyFrame::OnExit, this, wxID_EXIT);
// 차트 뷰어 설정 및 위치/크기 고정
m_chartViewer = new wxChartViewer(m_panel, wxID_ANY, wxPoint(10, 30), wxSize(550, 330));
// 시간 배열과 데이터 배열 초기화
// 현재 시간부터 과거로 100개의 타임스탬프를 생성 (250ms 간격), 데이터는 아직 없으므로 NoValue로 초기화
double currentTime = Chart::chartTime2((int)time(0));
// chartTime2는 int값을 차트에서 사용하는 시간 형식으로 변환하는 함수
// time(0)은 현재 시간을 time_t 형식으로 반환하는 함수이며, 이를 int로 캐스팅하여 chartTime2에 전달한다.
for (int i = 0; i < m_sampleSize; ++i) {
m_timeStamps.push_back(currentTime + (i - m_sampleSize) * 0.25); // 과거로 100개의 타임스탬프 생성 (250ms 간격)
m_dataSeries.push_back(Chart::NoValue); // 아직 데이터가 없으므로 NoValue 부여
}
// 타이머 시작 (250ms 마다 OnTimer 실행)
m_timer.Bind(wxEVT_TIMER, &MyFrame::OnTimer, this, m_timer.GetId());
m_timer.Start(250);
// 초기 차트 그리기
drawChart(m_chartViewer);
}
void MyFrame::OnExit(wxCommandEvent& event)
{
Close(true);
}
void MyFrame::OnAbout(wxCommandEvent& event)
{
wxMessageBox("This is a wxWidgets Hello World example", "About Hello World", wxOK | wxICON_INFORMATION);
}
void MyFrame::OnToggle(wxCommandEvent& event)
{
// 타이머 일시정지/재시작 토글 처리
if (m_timer.IsRunning()) {
m_timer.Stop();
SetStatusText("Timer Stopped.");
}
else {
m_timer.Start(250);
SetStatusText("Timer Started.");
}
}
void MyFrame::OnTimer(wxTimerEvent& event)
{
// 데이터 업데이트: 가장 오래된 타임스탬프와 데이터 제거, 새로운 타임스탬프와 데이터 추가
m_timeStamps.pop_front(); // 가장 오래된 타임스탬프 제거
m_dataSeries.pop_front(); // 가장 오래된 데이터 제거
// 새로운 타임스탬프 추가: 가장 최신 타임스탬프에 250ms를 더하여 새로운 타임스탬프 생성
//m_timeStamps[m_sampleSize - 1] = m_timeStamps[m_sampleSize - 2] + 0.25; // 250ms 증가
m_timeStamps.push_back(m_timeStamps.back() + 0.25); // 가장 최신 타임스탬프에 250ms 증가하여 추가
// 바로 직전 데이터에 랜덤 변화량을 주어 값을 생성 (Random Walk)
double currentVal = (m_dataSeries.empty() || m_dataSeries.back() == Chart::NoValue) ? 50 : m_dataSeries.back();
currentVal += (rand() % 11) - 5;
// 값이 범위(10~90)를 크게 벗어나지 않게 보정
if (currentVal < 10)
currentVal = 10;
if (currentVal > 90)
currentVal = 90;
m_dataSeries.push_back(currentVal); // 가장 최신 데이터 추가
// 차트 갱신
drawChart(m_chartViewer);
}
void MyFrame::drawChart(wxChartViewer* viewer)
{
// 550x330 크기의 XYChart 객체 생성
XYChart* c = new XYChart(550, 330);
// 백그라운드 색상 및 플롯 영역, 그리드 크기 설정
c->setBackground(0xeeeeee);
// 플롯 영역을 설정 (x, y, width, height) 및 배경색, 대체색(-1), 엣지색(-1), 가로 그리드색, 세로 그리드색
c->setPlotArea(40, 20, 480, 260, 0xffffff, -1, -1, 0xcccccc, 0xcccccccc);
// X축을 시간 포맷(hh:nn:ss)으로 설정
c->xAxis()->setLabelFormat("{value|hh:nn:ss}"); // value: 차트가 표시하는 값, hh:nn:ss: 시간 포맷
c->xAxis()->setTickDensity(75); // X축 눈금 간격을 조절하여 시간 간격이 일정하게 보이도록 설정 (75 픽셀마다 눈금 표시)
c->xAxis()->setTitle("Time");
// Y축은 실시간 데이터 변동이 보이게 크기를 고정 (0~100)
c->yAxis()->setLinearScale(0, 100, 20); // 0부터 100까지 20 간격으로 눈금 표시
c->yAxis()->setTitle("Value");
// 라인 레이어를 생성하고 선 색상을 지정, "Live Data"는 범례(Legend)에 표시될 이름
// m_dataSeries와 m_timeStamps는 deque이므로, 이를 벡터로 변환하고 데이터만 DoubleArray로 전달하여 차트에 데이터를 설정한다.
// 이렇게 변환해야 하기때문에 애초에 벡터로 선언하는게 나았을 수도 있지만 deque로 선언한 이유는 실시간 데이터 업데이트에서
// 앞의 데이터를 제거하고 뒤에 새로운 데이터를 추가만하는 작업이 더 효율적이기 때문이다. 벡터는 앞의 데이터를 제거할 때 모든
// 요소를 이동해야 하지만, deque는 양쪽에서 삽입과 삭제가 가능하므로 작업이 더 효율적이다. 그냥 내 추측이다.
LineLayer* layer = c->addLineLayer(DoubleArray(std::vector<double>(m_dataSeries.begin(), m_dataSeries.end()).data(), m_dataSeries.size()), 0xff0000, "Live Data");
layer->setXData(DoubleArray(std::vector<double>(m_timeStamps.begin(), m_timeStamps.end()).data(), m_timeStamps.size()));
layer->setLineWidth(2); // 선의 두께를 2로 설정하여 실시간 데이터의 변동이 더 잘 보이도록 함
// 범례(Lengend) 박스 설정 (x, y, vertical layout, font, font size) 및 배경색과 테두리 색상
c->addLegend(430, 30, false, "Arial Bold", 9)->setBackground(0xeeeeee, 0xcccccc);
// 생성한 차트를 뷰어에 연결하면 내부적으로 화면이 갱신됨
viewer->setChart(c);
//viewer->updateViewPort(true, true); // 뷰포트 업데이트 (차트가 변경되었음을 알림)
// setchart()는 새로운 차트를 뷰어에 설정하는 함수이고, updateViewPort()는 뷰포트가 변경되었음을 알리는 함수이다.
// setchart()를 호출하면 내부적으로 차트가 변경되었다고 인식하지만, updateViewPort()를 명시적으로 호출하여 뷰포트
// 업데이트를 트리거하는 것이 좋다. 이렇게 하면 차트가 변경된 후에 뷰포트가 올바르게 갱신되어 화면에 최신 차트가 표시된다.
// 지금은 마우스 휠로 확대/축소하는 기능은 없지만 그런 기능이 추가되면 차트가 변경될 때마다 명시적으로 updateViewPort()를
// 호출하여 뷰포트를 갱신하는 것이 좋다.
// ChartViewer에 넣은 뒤 완벽하게 안전하게 지울 수 있음
delete c;
}
결과는 위 코드와 동일하다.
※ 참고
'C, C++' 카테고리의 다른 글
| [wxWidgets] Timer 타이머 (0) | 2026.05.10 |
|---|---|
| [wxWidgets] wxChartDir 차트 그리기 (0) | 2026.05.05 |
| [wxWidgets] wxCharts 차트 그리기 (0) | 2026.05.04 |
| [wxWidgets] Window Handle & Titlebar Icon 윈도우 핸들 구하기 및 타이틀바 아이콘 바꾸기 (0) | 2026.05.04 |
| [wxWidgets] wxFormBuiler XRC 파일 사용하기 (0) | 2026.05.03 |







