QCustomPlot 개요
QCustomPlot은 Qt C++ 환경에서 2D 그래프를 그리기 위한 경량 라이브러리이다. 실시간 데이터 시각화, 트렌드 차트, 산점도, 막대그래프 등 다양한 플롯을 지원하며 별도 의존 라이브러리 없이 qcustomplot.h와 qcustomplot.cpp 두 파일만 추가하면 바로 사용할 수 있다. 출력 기능을 위해 printsupport 모듈이 필요하다.
프로젝트 구성
프로젝트에 소스 파일을 포함하려면 먼저 .pro 파일에 다음과 같이 추가한다.
QT += printsupport다음으로 qcustomplot.h와 qcustomplot.cpp를 프로젝트 디렉터리에 사하고 기존 파일로 추가한다. 사용처에서는 아래 더를 포함한다.
#include "qcustomplot.h"위젯 배치 후 코드에서 인스턴스를 생성하면 된다.
QCustomPlot *plotView = new QCustomPlot(this);심 클래스와 레이어
QCustomPlot은 여러 레이어로 화면을 구성한다. 대표적인 클래스는 다음과 같다.
- QCustomPlot: 전체 차트 위젯을 관리하는 최상위 클래스
- QCPLayer: 화면에 그려지는 모든 요소의 레이어
- QCPAxisRect: 축 영역. 기본적으로 상하좌우 개의 축을 포함
- QCPAbstractPlottable:
QCPGraph,QCPCurve,QCPBars,QCPColorMap등의 베이스 클래스
기본적으로 background, grid, main, axis, legend, overlay 레이어가 있으며, 일반적으로 그래프는 main 레이어에 그려진다.
기본 곡선 플롯
아래 예제는 단일 곡선을 생성하고 데이터를 연결하는 방법을 보여준다.
QVector<double> keys(101), values(101);
for (int i = 0; i < 101; ++i) {
keys[i] = i / 5.0 - 10.0;
values[i] = qPow(keys[i], 3);
}
QCPGraph *curve = plotView->addGraph();
curve->setData(keys, values);
curve->setName("y = x³");
curve->setPen(QPen(QColor(32, 178, 170), 2));
plotView->xAxis->setLabel("x");
plotView->yAxis->setLabel("y");
plotView->xAxis->setRange(-11, 11);
plotView->yAxis->setRange(-1100, 1100);
plotView->replot();스타일 커스터마이징
배경 그라데이션
QLinearGradient bgGrad;
bgGrad.setStart(0, 0);
bgGrad.setFinalStop(0, 360);
bgGrad.setColorAt(0, QColor(90, 90, 90));
bgGrad.setColorAt(1, QColor(40, 40, 40));
plotView->setBackground(bgGrad);
QLinearGradient rectGrad;
rectGrad.setStart(0, 0);
rectGrad.setFinalStop(0, 360);
rectGrad.setColorAt(0, QColor(90, 90, 90));
rectGrad.setColorAt(1, QColor(35, 35, 35));
plotView->axisRect()->setBackground(rectGrad);그리드 스타일
plotView->xAxis->grid()->setPen(QPen(QColor(150, 150, 150), 1, Qt::DotLine));
plotView->yAxis->grid()->setPen(QPen(QColor(150, 150, 150), 1, Qt::DotLine));
plotView->xAxis->grid()->setSubGridPen(QPen(QColor(70, 70, 70), 1, Qt::DotLine));
plotView->yAxis->grid()->setSubGridPen(QPen(QColor(70, 70, 70), 1, Qt::DotLine));
plotView->xAxis->grid()->setSubGridVisible(true);
plotView->yAxis->grid()->setSubGridVisible(true);
plotView->xAxis->grid()->setZeroLinePen(QPen(Qt::red));
plotView->yAxis->grid()->setZeroLinePen(QPen(Qt::red));선 스타일 및 채우기
QPen linePen;
QStringList styleNames;
styleNames << "None" << "Line" << "StepLeft" << "StepRight" << "StepCenter" << "Impulse";
for (int s = QCPGraph::lsNone; s <= QCPGraph::lsImpulse; ++s) {
plotView->addGraph();
linePen.setColor(QColor(qSin(s * 1.1 + 1.0) * 70 + 90,
qSin(s * 0.4 + 0.5) * 70 + 90,
qSin(s * 0.2 + 2.0) * 70 + 90));
plotView->graph()->setPen(linePen);
plotView->graph()->setName(styleNames.at(s - QCPGraph::lsNone));
plotView->graph()->setLineStyle(static_cast<QCPGraph::LineStyle>(s));
plotView->graph()->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, 6));
QVector<double> keys(20), values(20);
for (int k = 0; k < 20; ++k) {
keys[k] = k / 20.0 * 6 * M_PI;
values[k] = 5 * qCos(keys[k]) / (keys[k] + 1) + (s - QCPGraph::lsNone) * 3;
}
plotView->graph()->setData(keys, values);
plotView->graph()->rescaleAxes(true);
}두 곡선 사이 영역을 채우려면 setChannelFillGraph를 사용한다.
QCPGraph *upper = plotView->addGraph();
QCPGraph *lower = plotView->addGraph();
upper->setBrush(QBrush(QColor(0, 0, 255, 30)));
upper->setChannelFillGraph(lower);축 제어
축 가시성
plotView->yAxis2->setVisible(true);
plotView->xAxis2->setVisible(true);
plotView->axisRect()->setupFullAxesBox();범위 및 레이블
plotView->xAxis->setRange(-1, 1);
plotView->yAxis->setRange(0, 1);
plotView->xAxis->setLabel("시간");
plotView->yAxis->setLabel("값");
plotView->xAxis->setTickLabelRotation(30);축 금 설정
plotView->xAxis->ticker()->setTickCount(11);
plotView->xAxis->ticker()->setTickStepStrategy(QCPAxisTicker::tssReadability);
plotView->xAxis->setTickLengthIn(3);
plotView->xAxis->setTickLengthOut(5);
plotView->xAxis->setSubTickLengthIn(15);
plotView->xAxis->setSubTickLengthOut(5);
plotView->xAxis->ticker()->setTickOrigin(1);축 펜 및 색상
plotView->xAxis->setBasePen(QPen(Qt::white, 1));
plotView->xAxis->setTickPen(QPen(Qt::white, 1));
plotView->xAxis->setSubTickPen(QPen(Qt::white, 1));
plotView->xAxis->setTickLabelColor(Qt::white);
plotView->xAxis->setLabelColor(Qt::white);
plotView->xAxis->setUpperEnding(QCPLineEnding::esSpikeArrow);
plotView->xAxis->setLowerEnding(QCPLineEnding::esDisc);
plotView->yAxis-&setUpperEnding(QCPLineEnding::esSquare);축 동기화
connect(plotView->xAxis, SIGNAL(rangeChanged(QCPRange)),
plotView->xAxis2, SLOT(setRange(QCPRange)));
connect(plotView->yAxis, SIGNAL(rangeChanged(QCPRange)),
plotView->yAxis2, SLOT(setRange(QCPRange)));시간 형식 축
X을 날짜/시간으로 표시하려면 QCPAxisTickerDateTime를 사용한다. 내부적으로는 epoch 기준 초 단위 double 값으로 변환해야 한다.
QSharedPointer<QCPAxisTickerDateTime> dateTicker(new QCPAxisTickerDateTime);
dateTicker->setDateTimeFormat("yyyy-MM-dd hh:mm:ss");
plotView->xAxis->setTicker(dateTicker);
QDateTime dt = QDateTime::currentDateTime();
double key = dt.toMSecsSinceEpoch() / 1000.0;범 및 상호작용
범례를 활성화하려면 legend의 setVisible을 호출한다.
plotView->legend->setVisible(true);사용자 상호작용은 setInteractions로 한 번에 설정할 수 있다.
plotView->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom |
QCP::iSelectAxes | QCP::iSelectLegend |
QCP::iSelectPlottables);자동 범위 및 축소/확대
이터에 맞게 축 범위를 재조정하려면 rescaleAxes를 사용한다. 여러 곡선이 있을 때는 첫 번째는 제한 없이, 이후 곡선은 onlyEnlarge를 true로 설정해 기존 범위를 유지한다.
plotView->graph(0)->rescaleAxes();
plotView->graph(1)->rescaleAxes(true);
plotView->graph(2)->rescaleAxes(true);확대/축소 비율은 setRangeZoomFactor로 조정한다.
plotView->axisRect()->setRangeZoomFactor(1.2, 1.0);마우스 휠로 특정 축만 확대/축소하려면 wheelEvent를 재정의한다.
void MainWindow::wheelEvent(QWheelEvent *event)
{
if (plotView->xAxis->selectedParts().testFlag(QCPAxis::spAxis)) {
plotView->axisRect()->setRangeZoom(plotView->xAxis->orientation());
} else if (plotView->yAxis->selectedParts().testFlag(QCPAxis::spAxis)) {
plotView->axisRect()->setRangeZoom(plotView->yAxis->orientation());
} else {
plotView->axisRect()->setRangeZoom(Qt::Horizontal | Qt::Vertical);
}
QMainWindow::wheelEvent(event);
}이미지 내보내기
그려진 차트를 PNG, PDF 등으로 저장할 수 있다.
plotView->savePng("chart.png", 400, 300);
plotView->savePdf("chart.pdf");완성 예제
#include "qcustomplot.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
QCustomPlot *plotView = new QCustomPlot(this);
plotView->resize(400, 400);
QVector<double> x(101), y(101);
for (int i = 0; i < 101; ++i) {
x[i] = i / 5.0 - 10.0;
y[i] = qPow(x[i], 3);
}
QCPGraph *curve = plotView->addGraph();
curve->setData(x, y);
curve->setPen(QPen(QColor(32, 178, 170), 2));
plotView->xAxis->setLabel("x");
plotView->yAxis->setLabel("y");
plotView->setBackground(QColor(50, 50, 50));
plotView->xAxis->setTickLabelColor(Qt::white);
plotView->xAxis->setLabelColor(QColor(0, 160, 230));
plotView->xAxis->setBasePen(QPen(QColor(32, 178, 170)));
plotView->xAxis->setTickPen(QPen(QColor(128, 0, 255)));
plotView->xAxis->setSubTickPen(QColor(255, 165, 0));
QFont font = plotView->xAxis->labelFont();
font.setPixelSize(20);
plotView->xAxis->setLabelFont(font);
plotView->yAxis->setTickLabelColor(Qt::white);
plotView->yAxis->setLabelColor(QColor(0, 160, 230));
plotView->yAxis->setBasePen(QPen(QColor(32, 178, 170)));
plotView->yAxis->setTickPen(QPen(QColor(128, 0, 255)));
plotView->yAxis->setSubTickPen(QColor(255, 165, 0));
QFont yFont = plotView->yAxis->labelFont();
yFont.setPixelSize(20);
plotView->yAxis->setLabelFont(yFont);
plotView->xAxis->setRange(-11, 11);
plotView->yAxis->setRange(-1100, 1100);
plotView->replot();
}