반응형

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;
}

 

결과는 위 코드와 동일하다.

 

※ 참고

Documnetation

 

반응형
Posted by J-sean
:
반응형

wxChartDir은 ChartDir(ChartDirector)의 wxWidgets 프레임워크 래퍼(wrapper)다.

실제 차트를 그리고(렌더링) 데이터를 처리하는 핵심 엔진은 ChartDir이기 때문에, wxChartDir을 사용하려면 ChartDir이 필요하다.

wxChartDir로 wxWidgets 애플리케이션에 차트를 그려보자.

 

우선 wxChartDir을 빌드하자.

vcpkg install wxchartdir

 

ChartDirector에서 ChartDirector for C++을 다운로드받자.

 

다운로드받은 ChartDirector for C++을 확인해 보면 위와 같은 구조로 되어 있는데, 개발에 필요한 부분은 include와 lib64(또는 lib32) 디렉토리의 파일들이다.

위에서 vcpkg로 빌드한 wxChartDir의 폴더에 각각의 파일을 복사해 넣자. (chartdirXX.dll 파일도 lib64 디렉토리에 있다)

 

Include, Library 폴더 등을 적당히 세팅하고 아래 코드를 입력한다.

#include <wx/wx.h>
#include <wxchartdir/chartdir.h>
#include <wxchartdir/wxchartviewer.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 OnHello(wxCommandEvent& event);
	void OnExit(wxCommandEvent& event);
	void OnAbout(wxCommandEvent& event);

	wxPanel* panel;
};

enum
{
	ID_Hello = 1
};

bool MyApp::OnInit()
{
	MyFrame* frame = new MyFrame();
	frame->Show(true);
	return true;
}

// 메인 프레임의 위치(default position)와 크기(550x400)를 설정한다.
MyFrame::MyFrame() : wxFrame(nullptr, wxID_ANY, "Hello World", wxDefaultPosition, wxSize(550, 400))
{
	wxMenu* menuFile = new wxMenu;
	menuFile->Append(ID_Hello, "&Hello...\tCtrl-H", "Help string shown in status bar for this menu item");
	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 wxWidgets!");

	panel = new wxPanel(this, wxID_ANY);

	wxButton* myButton = new wxButton(panel, wxID_ANY, "Press Me", wxPoint(420, 30), wxSize(100, 30));
	myButton->Bind(wxEVT_BUTTON, &MyFrame::OnHello, this);

	Bind(wxEVT_MENU, &MyFrame::OnHello, this, ID_Hello);
	Bind(wxEVT_MENU, &MyFrame::OnAbout, this, wxID_ABOUT);
	Bind(wxEVT_MENU, &MyFrame::OnExit, this, wxID_EXIT);
}

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::OnHello(wxCommandEvent& event)
{
	// 데이터와 라벨 준비
	double data[] = { 85, 156, 179.5, 211, 123 };
	const char* labels[] = { "Mon", "Tue", "Wed", "Thu", "Fri" };

	// 400x300 크기의 XYChart 객체 생성
	XYChart* c = new XYChart(400, 300);

	// 플롯 영역 설정 (여백 조정)
	c->setPlotArea(40, 20, 300, 250);

	// 바 차트 레이어 추가
	c->addBarLayer(DoubleArray(data, (int)(sizeof(data) / sizeof(data[0]))));

	// X축에 라벨 설정
	c->xAxis()->setLabels(StringArray(labels, (int)(sizeof(labels) / sizeof(labels[0]))));

	// 차트 뷰어 생성 (wxPanel 위에 위치)
	wxChartViewer* chartViewer = new wxChartViewer(panel, wxID_ANY, wxPoint(10, 10), wxSize(400, 300));

	// 생성한 차트를 뷰어에 연결
	// wxChartViewer는 setChart에서 내부적으로 상태를 업데이트하거나 사용할 수 있지만, 
	// 라이브러리의 방식에 따라 BaseChart(XYChart)의 생명주기를 어떻게 관리해야 할지 주의해야 한다.
	chartViewer->setChart(c);

	// 문서에 따르면 setChart를 호출한 후 곧바로 BaseChart를 삭제하거나 로컬 스택에 할당하는 것도 완벽하게 안전하다.
	// "So it is perfectly safe to immediately delete the BaseChart after calling wxChartViewer::setChart"
	delete c;
}

 

버튼을 클릭하면 차트가 표시된다.

 

ChartDirector는 상용 라이브러리이기 때문에 라이선스를 구입하지 않으면 아래에 노란 광고가 표시된다.

 

 

더보기

ChartViewer의 크기를 XYChart 객체보다 10픽셀 정도(정확히는 9픽셀) 작게 하면 노란 광고를 보이지 않게 할 수 있다.  

(이 예제는 필요 없지만, X축 레이블이 광고와 겹친다면 setPlotArea()에서 플롯 영역의 높이도 조정해야 한다, 아래 히스토그램 예제 참고)

.
.
.
// 400x300 크기의 XYChart 객체 생성
XYChart* c = new XYChart(400, 300);
.
.
.
// 차트 뷰어 생성 (wxPanel 위에 위치)
wxChartViewer* chartViewer = new wxChartViewer(panel, wxID_ANY, wxPoint(10, 10), wxSize(400, 300-10));
// 차트 뷰어의 크기를 XYChart 객체보다 10픽셀 정도 작게 설정하면 노란 광고 배너가 보이지 않게 된다.
.
.
.

  

  

 

Line Chart를 만들어 보자.

#include <wx/wx.h>
#include <wxchartdir/chartdir.h>
#include <wxchartdir/wxchartviewer.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 OnHello(wxCommandEvent& event);
	void OnExit(wxCommandEvent& event);
	void OnAbout(wxCommandEvent& event);

	wxPanel* panel;
};

enum
{
	ID_Hello = 1
};

bool MyApp::OnInit()
{
	MyFrame* frame = new MyFrame();
	frame->Show(true);
	return true;
}

// 메인 프레임의 위치(default position)와 크기(550x400)를 설정한다.
MyFrame::MyFrame() : wxFrame(nullptr, wxID_ANY, "Hello World", wxDefaultPosition, wxSize(550, 400))
{
	wxMenu* menuFile = new wxMenu;
	menuFile->Append(ID_Hello, "&Hello...\tCtrl-H", "Help string shown in status bar for this menu item");
	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 wxWidgets!");

	panel = new wxPanel(this, wxID_ANY);

	wxButton* myButton = new wxButton(panel, wxID_ANY, "Press Me", wxPoint(420, 30), wxSize(100, 30));
	myButton->Bind(wxEVT_BUTTON, &MyFrame::OnHello, this);

	Bind(wxEVT_MENU, &MyFrame::OnHello, this, ID_Hello);
	Bind(wxEVT_MENU, &MyFrame::OnAbout, this, wxID_ABOUT);
	Bind(wxEVT_MENU, &MyFrame::OnExit, this, wxID_EXIT);
}

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::OnHello(wxCommandEvent& event)
{
	// The data for the line chart
	double data[] = { 30, 28, 40, 55, 75, 68, 54, 60, 50, 62, 75, 65, 75, 91, 60, 55, 53, 35, 50, 66,
		56, 48, 52, 65, 62 };
	const int data_size = (int)(sizeof(data) / sizeof(*data));

	// The labels for the line chart
	const char* labels[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12",
		"13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24" };
	const int labels_size = (int)(sizeof(labels) / sizeof(*labels));

	// Create a XYChart object of size 250 x 250 pixels
	XYChart* c = new XYChart(250, 250);

	// Set the plotarea at (30, 20) and of size 200 x 200 pixels
	c->setPlotArea(30, 20, 200, 200);

	// Add a line chart layer using the given data
	c->addLineLayer(DoubleArray(data, data_size));

	// Set the labels on the x axis.
	c->xAxis()->setLabels(StringArray(labels, labels_size));

	// Display 1 out of 3 labels on the x-axis.
	c->xAxis()->setLabelStep(3);

	// Create a ChartViewer to display the chart 
	wxChartViewer* chartViewer = new wxChartViewer(panel, wxID_ANY, wxPoint(10, 10), wxSize(250, 250));
	chartViewer->setChart(c);

	//free up resources
	delete c;
}

 

 

간단한 리얼타임 차트 만들기

2026.05.10 - [C, C++] - [wxWidgets] wxChartDir Simple Real-Time Chart 간단한 리얼타임 차트

 

 

히스토그램과 벨 커브를 그려보자.

#include <wx/wx.h>
#include <wxchartdir/chartdir.h>
#include <wxchartdir/wxchartviewer.h>

#include <math.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 OnHello(wxCommandEvent& event);
	void OnExit(wxCommandEvent& event);
	void OnAbout(wxCommandEvent& event);

	wxPanel* panel;
};

enum
{
	ID_Hello = 1
};

bool MyApp::OnInit()
{
	MyFrame* frame = new MyFrame();
	frame->Show(true);
	return true;
}

// 메인 프레임의 위치(default position)와 크기(750x500)를 설정한다.
MyFrame::MyFrame() : wxFrame(nullptr, wxID_ANY, "Hello World", wxDefaultPosition, wxSize(750, 500))
{
	wxMenu* menuFile = new wxMenu;
	menuFile->Append(ID_Hello, "&Hello...\tCtrl-H", "Help string shown in status bar for this menu item");
	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 wxWidgets!");

	panel = new wxPanel(this, wxID_ANY);

	wxButton* myButton = new wxButton(panel, wxID_ANY, "Press Me", wxPoint(620, 30), wxSize(100, 30));
	myButton->Bind(wxEVT_BUTTON, &MyFrame::OnHello, this);

	Bind(wxEVT_MENU, &MyFrame::OnHello, this, ID_Hello);
	Bind(wxEVT_MENU, &MyFrame::OnAbout, this, wxID_ABOUT);
	Bind(wxEVT_MENU, &MyFrame::OnExit, this, wxID_EXIT);
}

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::OnHello(wxCommandEvent& event)
{
	char buffer[1024];

	//
	// This example demonstrates creating a histogram with a bell curve from raw data. About half of
	// the code is to sort the raw data into slots and to generate the points on the bell curve. The
	// remaining half of the code is the actual charting code.
	//

	// 히스토그램 및 벨 커브 데이터 생성

	// Generate a random guassian distributed data series as the input data for this example.
	RanSeries* r = new RanSeries(66);
	DoubleArray samples = r->getGaussianSeries(200, 100, 10);

	//
	// Classify the numbers into slots. In this example, the slot width is 5 units.
	//
	double slotSize = 5;

	// Compute the min and max values, and extend them to the slot boundary.
	ArrayMath m = ArrayMath(samples);
	double minX = (int)(m.minValue() / slotSize) * slotSize;
	double maxX = (int)(m.maxValue() / slotSize) * slotSize + slotSize;

	// We can now determine the number of slots
	// slot의 개수를 계산한다. slotCount는 (maxX - minX) / slotSize로 계산되며, 소수점 첫째 자리에서 반올림하여 정수로 변환된다.
	int slotCount = (int)((maxX - minX) / slotSize + 0.5);
	double* frequency = new double[slotCount];
	memset(frequency, 0, sizeof(*frequency) * slotCount);

	// Count the data points contained in each slot
	// frequency에 각 slot에 해당하는 데이터 포인트의 개수를 저장한다. samples 배열의 각 요소에 대해, 해당 요소가 속하는 slot의
	// 인덱스를 계산하여 frequency 배열에서 해당 slot의 개수를 증가시킨다. slotIndex는 (samples[i] - minX) / slotSize로 계산되며
	// 이는 samples[i]가 minX에서 시작하여 slotSize 간격으로 나누어진 slot 중 어디에 위치하는지를 나타낸다.
	for (int i = 0; i < samples.len; ++i) {
		int slotIndex = (int)((samples[i] - minX) / slotSize);
		frequency[slotIndex] = frequency[slotIndex] + 1;
	}

	//
	// Compute Normal Distribution Curve
	//

	// The mean and standard deviation of the data
	double mean = m.avg();
	double stdDev = m.stdDev();

	// The normal distribution curve (bell curve) is a standard statistics curve. We need to
	// vertically scale it to make it proportion to the frequency count.
	double scaleFactor = slotSize * samples.len / stdDev / sqrt(2 * 3.1416);

	// In this example, we plot the bell curve up to 3 standard deviations.
	double stdDevWidth = 3.0;

	// We generate 4 points per standard deviation to be joined with a spline curve.
	int bellCurveResolution = (int)(stdDevWidth * 4 + 1);
	double* bellCurve = new double[bellCurveResolution];
	for (int i = 0; i < bellCurveResolution; ++i) {
		double z = 2 * i * stdDevWidth / (bellCurveResolution - 1) - stdDevWidth;
		bellCurve[i] = exp(-z * z / 2) * scaleFactor;
	}

	//
	// At this stage, we have obtained all data and can plot the chart.
	//

	// 히스토그램과 벨 커브 차트 생성

	// Create a XYChart object of size 600 x 360 pixels
	XYChart* c = new XYChart(600, 360);

	// Set the plotarea at (50, 30) and of size 500 x 300 pixels, with transparent background and
	// border and light grey (0xcccccc) horizontal grid lines
	c->setPlotArea(50, 30, 500, 300, Chart::Transparent, -1, Chart::Transparent, 0xcccccc);
	// 이 예제대로 플롯 영역의 높이를 300으로 설정하면 노란 광고 배너가 플롯 영역의 x축 레이블과 겹쳐서 보이게 된다.
	// 플롯 영역의 높이를 20정도 줄이면(300-20) 광고 배너와 겹치지 않게 된다. 그리고 아래 chartViewer의 크기도 20정도
	// 줄이면(360-20) 광고 배너도 보이지않게 할 수 있다.

	// Display the mean and standard deviation on the chart
	sprintf(buffer, "Mean = %.1f, Standard Deviation = %.1f", mean, stdDev);
	c->addTitle(buffer, "Arial");

	// Set the x and y axis label font to 12pt Arial
	c->xAxis()->setLabelStyle("Arial", 12);
	c->yAxis()->setLabelStyle("Arial", 12);

	// Set the x and y axis stems to transparent, and the x-axis tick color to grey (0x888888)
	c->xAxis()->setColors(Chart::Transparent, Chart::TextColor, Chart::TextColor, 0x888888);
	c->yAxis()->setColors(Chart::Transparent);

	// 벨 커브 그리기, 벨 커브가 필요 없다면 이 부분을 제거하면 된다.
	// Draw the bell curve as a spline layer in red (0xdd0000) with 2-pixel line width
	SplineLayer* bellLayer = c->addSplineLayer(DoubleArray(bellCurve, bellCurveResolution), 0xdd0000
	);
	bellLayer->setXData(mean - stdDevWidth * stdDev, mean + stdDevWidth * stdDev);
	bellLayer->setLineWidth(2);

	// 히스토그램 그리기
	// Draw the histogram as bars in blue (0x6699bb) with dark blue (0x336688) border
	BarLayer* histogramLayer = c->addBarLayer(DoubleArray(frequency, slotCount), 0x6699bb);
	histogramLayer->setBorderColor(0x336688);
	// The center of the bars span from minX + half_bar_width to maxX - half_bar_width
	histogramLayer->setXData(minX + slotSize / 2.0, maxX - slotSize / 2.0);
	// Configure the bars to touch each other with no gap in between
	histogramLayer->setBarGap(Chart::TouchBar);
	// Use rounded corners for decoration
	histogramLayer->setRoundedCorners();

	// ChartDirector by default will extend the x-axis scale by 0.5 unit to cater for the bar width.
	// It is because a bar plotted at x actually occupies (x +/- half_bar_width), and the bar width
	// is normally 1 for label based x-axis. However, this chart is using a linear x-axis instead of
	// label based. So we disable the automatic extension and add a dummy layer to extend the x-axis
	// scale to cover minX to maxX.
	c->xAxis()->setIndent(false);
	c->addLineLayer()->setXData(minX, maxX);

	// For the automatic y-axis labels, set the minimum spacing to 40 pixels.
	c->yAxis()->setTickDensity(40);

	// 차트 뷰어 생성 (wxPanel 위에 위치)
	wxChartViewer* chartViewer = new wxChartViewer(panel, wxID_ANY, wxPoint(10, 10), wxSize(600, 360));

	// 생성한 차트를 뷰어에 연결
	chartViewer->setChart(c);

	// 문서에 따르면 setChart를 호출한 후 곧바로 BaseChart를 삭제하거나 로컬 스택에 할당하는 것도 완벽하게 안전하다.
	// "So it is perfectly safe to immediately delete the BaseChart after calling wxChartViewer::setChart"
	delete c;
}

 

 

 

Angular Meter를 만들어 보자.

#include <wx/wx.h>
#include <wxchartdir/chartdir.h>
#include <wxchartdir/wxchartviewer.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 OnHello(wxCommandEvent& event);
	void OnExit(wxCommandEvent& event);
	void OnAbout(wxCommandEvent& event);

	wxPanel* panel;
};

enum
{
	ID_Hello = 1
};

bool MyApp::OnInit()
{
	MyFrame* frame = new MyFrame();
	frame->Show(true);
	return true;
}

// 메인 프레임의 위치(default position)와 크기(550x400)를 설정한다.
MyFrame::MyFrame() : wxFrame(nullptr, wxID_ANY, "Hello World", wxDefaultPosition, wxSize(550, 400))
{
	wxMenu* menuFile = new wxMenu;
	menuFile->Append(ID_Hello, "&Hello...\tCtrl-H", "Help string shown in status bar for this menu item");
	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 wxWidgets!");

	panel = new wxPanel(this, wxID_ANY);

	wxButton* myButton = new wxButton(panel, wxID_ANY, "Press Me", wxPoint(420, 30), wxSize(100, 30));
	myButton->Bind(wxEVT_BUTTON, &MyFrame::OnHello, this);

	Bind(wxEVT_MENU, &MyFrame::OnHello, this, ID_Hello);
	Bind(wxEVT_MENU, &MyFrame::OnAbout, this, wxID_ABOUT);
	Bind(wxEVT_MENU, &MyFrame::OnExit, this, wxID_EXIT);
}

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::OnHello(wxCommandEvent& event)
{
	// The value to display on the meter
	double value = 72.55;

	// Create an AngularMeter object of size 300 x 180 pixels with transparent background
	//AngularMeter* m = new AngularMeter(300, 180, Chart::Transparent);
	// 
	// Chart::Transparent는 wxChartViewer에서 검정색으로 렌더링될 수 있으므로, 패널의 배경색을 설정한다.
	wxColour bgColour = panel->GetBackgroundColour();
	int chartBgColor = (bgColour.Red() << 16) | (bgColour.Green() << 8) | bgColour.Blue();
	AngularMeter* m = new AngularMeter(300, 180, chartBgColor);

	// Center at (150, 150), scale radius = 128 pixels, scale angle -90 to +90 degrees
	m->setMeter(150, 150, 128, -90, 90);

	// Add a pale grey (0xeeeeee) scale background of 148 pixels radius, with a 10 pixel thick light
	// grey (0xcccccc) border
	m->addScaleBackground(148, 0xeeeeee, 10, 0xcccccc);

	// Meter scale is 0 - 100, with major tick every 20 units, minor tick every 10 units, and micro
	// tick every 5 units
	m->setScale(0, 100, 20, 10, 5);

	// Set the scale label style to 15pt Arial Italic. Set the major/minor/micro tick lengths to
	// 16/16/10 pixels pointing inwards, and their widths to 2/1/1 pixels.
	m->setLabelStyle("Arial Italic", 16);
	m->setTickLength(-16, -16, -10);
	m->setLineWidth(0, 2, 1, 1);

	// Add a smooth color scale to the meter
	double smoothColorScale[] = { 0, 0x3333ff, 25, 0x0088ff, 50, 0x00ff00, 75, 0xdddd00, 100, 0xff0000 };
	const int smoothColorScale_size = (int)(sizeof(smoothColorScale) / sizeof(*smoothColorScale));
	m->addColorScale(DoubleArray(smoothColorScale, smoothColorScale_size));

	// Add a text label centered at (150, 125) with 15pt Arial Italic font
	m->addText(150, 125, "CPU", "Arial Italic", 15, Chart::TextColor, Chart::BottomCenter);

	// Add a red (0xff0000) pointer at the specified value
	// addPointer() 메서드와 addPointer2() 메서드는 포인터의 모양이 약간 다르다.
	m->addPointer2(value, 0xff0000);

	// Create a wxChartViewer to display the meter, with a size of 300 x 180 pixels
	wxChartViewer* meterViewer = new wxChartViewer(panel, wxID_ANY, wxPoint(10, 10), wxSize(300, 180));
	meterViewer->setChart(m);

	//free up resources
	delete m;
}

 

 

※ 참고

ChartDirector Documentation

wxChartDir

wxChartDir Documentation

 

반응형
Posted by J-sean
: