그래프 갤러리
막대, 산점도 및 표면 그래프 갤러리.
그래프 갤러리에서는 세 가지 그래프 유형과 그 중 일부의 특수 기능을 보여줍니다. 각 그래프에는 애플리케이션 내에 별도의 탭이 마련되어 있습니다.

예제 실행
다음 경로에서 예제를 실행할 수 있습니다:
- Qt Creator
Welcome 모드를 열고 Examples 에서 예제를 선택하십시오. 자세한 내용은 Qt Creator: 튜토리얼: 빌드 및 실행을 참조하십시오.
- Qt Extension for Visual Studio Code
Command Palette 에서 Qt: Open Qt examples 명령을 실행하고 목록에서 예제를 선택합니다. 자세한 내용은 Qt Extension for Visual Studio Code: 튜토리얼: 빌드 및 실행을 참조하십시오.
막대 그래프
Bar Graph 탭에서 Q3DBarWidgetItem을 사용하여 3D 막대 그래프를 만들고, 여러 위젯을 조합하여 다양한 막대 그래프 특성을 조정해 보세요. 이 예제에서는 다음을 수행하는 방법을 보여줍니다:
- Q3DBarWidgetItem과 몇 가지 컨트롤 위젯을 사용하여 애플리케이션 생성하기
- QBar3DSeries 및 QBarDataProxy 을 사용하여 그래프에 데이터를 설정하는 방법
- 위젯 컨트롤을 사용하여 그래프 및 시리즈 속성을 조정하는 방법
- 축 레이블을 클릭하여 행 또는 열 선택하기
- Q3DBarWidgetItem과 함께 사용할 사용자 정의 프록시를 생성하는 방법
그래프와의 상호 작용에 대한 자세한 내용은 이 페이지를 참조하십시오.
애플리케이션 생성
bargraph.cpp에서 QQuickWidget 및 Q3DBarsWidgetItem 를 인스턴스화하고, QQuickWidget 인스턴스를 Q3DBarsWidgetItem 의 위젯으로 설정합니다:m_quickWidget = new QQuickWidget(); m_barGraph = new Q3DBarsWidgetItem(this); m_barGraph->setWidget(m_quickWidget);- 컨테이너 위젯과 가로 및 세로 레이아웃을 생성합니다. 그래프와 세로 레이아웃을 가로 레이아웃에 추가합니다:
m_container = new QWidget(); auto *hLayout = new QHBoxLayout(m_container); QSize screenSize = m_quickWidget->screen()->size(); m_quickWidget->setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 1.75)); m_quickWidget->setMaximumSize(screenSize); m_quickWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_quickWidget->setFocusPolicy(Qt::StrongFocus); hLayout->addWidget(m_quickWidget, 1); auto *vLayout = new QVBoxLayout(); hLayout->addLayout(vLayout); - 데이터 추가 및 그래프와의 기타 상호 작용을 처리할 또 다른 클래스를 생성합니다:
m_modifier = new GraphModifier(m_barGraph, this);
막대 그래프 설정
GraphModifier클래스의 생성자에서 그래프를 설정합니다:GraphModifier::GraphModifier(Q3DBarsWidgetItem *bargraph, QObject *parent) : QObject(parent) , m_graph(bargraph)- 변경할 수 있도록 축과 시계를 멤버 변수로 생성합니다:
, m_temperatureAxis(new QValue3DAxis) , m_yearAxis(new QCategory3DAxis) , m_monthAxis(new QCategory3DAxis) , m_primarySeries(new QBar3DSeries) , m_secondarySeries(new QBar3DSeries) - 그래프의 시각적 속성을 설정합니다:
m_graph->setShadowQuality(QtGraphs3D::ShadowQuality::SoftMedium); m_graph->setMultiSeriesUniform(true); // These are set through the active theme m_graph->activeTheme()->setPlotAreaBackgroundVisible(false); m_graph->activeTheme()->setLabelFont(QFont("Times New Roman", m_fontSize)); m_graph->activeTheme()->setLabelBackgroundVisible(true); - 축을 설정하고 이를 그래프의 활성 축으로 지정합니다:
m_temperatureAxis->setTitle("Average temperature"); m_temperatureAxis->setSegmentCount(m_segments); m_temperatureAxis->setSubSegmentCount(m_subSegments); m_temperatureAxis->setRange(m_minval, m_maxval); m_temperatureAxis->setLabelFormat(u"%.1f "_s + m_celsiusString); m_temperatureAxis->setLabelAutoAngle(30.0f); m_temperatureAxis->setTitleVisible(true); m_yearAxis->setTitle("Year"); m_yearAxis->setLabelAutoAngle(30.0f); m_yearAxis->setTitleVisible(true); m_monthAxis->setTitle("Month"); m_monthAxis->setLabelAutoAngle(30.0f); m_monthAxis->setTitleVisible(true); m_graph->setValueAxis(m_temperatureAxis); m_graph->setRowAxis(m_yearAxis); m_graph->setColumnAxis(m_monthAxis); - 축 레이블에 작은 자동 회전 각도를 적용합니다:
m_yearAxis->setLabelAutoAngle(30.0f);이는 축 레이블이 카메라 방향을 향해 약간 기울어지도록 하여, 극단적인 카메라 각도에서도 축 레이블의 가독성을 높이기 위함입니다.
- 시리즈의 시각적 속성을 초기화합니다. 두 번째 시리즈는 초기에는 표시되지 않는다는 점에 유의하십시오:
m_primarySeries->setItemLabelFormat(u"Oulu - @colLabel @rowLabel: @valueLabel"_s); m_primarySeries->setMesh(QAbstract3DSeries::Mesh::BevelBar); m_primarySeries->setMeshSmooth(false); m_secondarySeries->setItemLabelFormat(u"Helsinki - @colLabel @rowLabel: @valueLabel"_s); m_secondarySeries->setMesh(QAbstract3DSeries::Mesh::BevelBar); m_secondarySeries->setMeshSmooth(false); m_secondarySeries->setVisible(false); - 시리즈를 그래프에 추가합니다:
m_graph->addSeries(m_primarySeries); m_graph->addSeries(m_secondarySeries); - UI의 카메라 각도 변경 버튼이 다양한 카메라 각도를 순환하는 데 사용하는 것과 동일한 메서드를 호출하여 카메라 각도를 설정합니다:
changePresetCamera(); - 새로운 카메라 프리셋이 그래프에 설정됩니다:
static int preset = int(QtGraphs3D::CameraPreset::Front); m_graph->setCameraPreset((QtGraphs3D::CameraPreset) preset); if (++preset > int(QtGraphs3D::CameraPreset::DirectlyBelow)) preset = int(QtGraphs3D::CameraPreset::FrontLow);
그래프에 데이터 추가하기
생성자 끝부분에서 데이터를 설정하는 메서드를 호출합니다:
resetTemperatureData();이 메서드는 두 시계의 프록시를 사용하여 관련 시계에 데이터를 추가합니다:
// Set up data
static const float tempOulu[8][12] = {
{-7.4f, -2.4f, 0.0f, 3.0f, 8.2f, 11.6f, 14.7f, 15.4f, 11.4f, 4.2f, 2.1f, -2.3f}, // 2015
{-13.4f, -3.9f, -1.8f, 3.1f, 10.6f, 13.7f, 17.8f, 13.6f, 10.7f, 3.5f, -3.1f, -4.2f}, // 2016
...
QBarDataArray dataSet;
QBarDataArray dataSet2;
dataSet.reserve(m_years.size());
for (qsizetype year = 0; year < m_years.size(); ++year) {
// Create a data row
QBarDataRow dataRow(m_months.size());
QBarDataRow dataRow2(m_months.size());
for (qsizetype month = 0; month < m_months.size(); ++month) {
// Add data to the row
dataRow[month].setValue(tempOulu[year][month]);
dataRow2[month].setValue(tempHelsinki[year][month]);
}
// Add the row to the set
dataSet.append(dataRow);
dataSet2.append(dataRow2);
}
// Add data to the data proxy (the data proxy assumes ownership of it)
m_primarySeries->dataProxy()->resetArray(dataSet, m_years, m_months);
m_secondarySeries->dataProxy()->resetArray(dataSet2, m_years, m_months);위젯을 사용하여 그래프 제어하기
bargraph.cpp 에 몇 가지 위젯을 추가해 보세요.
- 슬라이더를 추가합니다:
- 마우스나 터치만 사용하는 대신 슬라이더를 사용하여 그래프를 회전시켜 보세요. 이를 수직 레이아웃에 추가하세요:
GraphModifier의 메서드에 연결하세요:GraphModifier에 신호 연결을 위한 슬롯을 생성하세요. 미리 설정된 카메라 각도를 지정하는 대신, 중심점을 중심으로 한 궤도 상의 실제 카메라 위치를 지정하세요:void GraphModifier::rotateX(int angle) { m_xRotation = angle; m_graph->setCameraPosition(m_xRotation, m_yRotation); }
이제 슬라이더를 사용하여 그래프를 회전할 수 있습니다.
제어할 위젯을 수직 레이아웃에 더 추가하세요:
- 그래프 회전
- 라벨 스타일
- 카메라 프리셋
- 배경 표시 여부
- 격자 표시 여부
- 막대 음영의 부드러움
- 두 번째 막대 그래프 계열의 표시 여부
- 값 축 방향
- 축 제목 표시 여부 및 회전
- 표시할 데이터 범위
- 막대 스타일
- 선택 모드
- 테마
- 그림자 품질
- 글꼴
- 글꼴 크기
- 축 레이블 회전
- 데이터 모드
Custom Proxy Data 데이터 모드에서는 일부 위젯 컨트롤이 의도적으로 비활성화됩니다.
축 레이블을 클릭하여 행 또는 열 선택
축 레이블을 통한 선택은 막대 그래프의 기본 기능입니다. 예를 들어, 다음과 같은 방법으로 축 레이블을 클릭하여 행을 선택할 수 있습니다.
- 선택 모드를 다음과 같이 변경합니다.
Row - 연도 레이블을 클릭합니다
- 클릭한 연도가 포함된 행이 선택됩니다
Row 또는 Column 중 하나가 설정되어 있는 한, 이 방법은 ‘ Slice ’ 및 ‘ Item ’ 플래그에서도 동일하게 적용됩니다.
선택 영역으로 확대/축소
카메라 대상을 조정하는 예시로, 버튼을 누르면 선택 영역으로 확대/축소하는 애니메이션을 구현해 봅시다. 애니메이션 초기화는 생성자에서 수행됩니다:
m_defaultAngleX = m_graph->cameraXRotation();
m_defaultAngleY = m_graph->cameraYRotation();
m_defaultZoom = m_graph->cameraZoomLevel();
m_defaultTarget = m_graph->cameraTargetPosition();
m_animationCameraX.setTargetObject(m_graph);
m_animationCameraY.setTargetObject(m_graph);
m_animationCameraZoom.setTargetObject(m_graph);
m_animationCameraTarget.setTargetObject(m_graph);
m_animationCameraX.setPropertyName("cameraXRotation");
m_animationCameraY.setPropertyName("cameraYRotation");
m_animationCameraZoom.setPropertyName("cameraZoomLevel");
m_animationCameraTarget.setPropertyName("cameraTargetPosition");
int duration = 1700;
m_animationCameraX.setDuration(duration);
m_animationCameraY.setDuration(duration);
m_animationCameraZoom.setDuration(duration);
m_animationCameraTarget.setDuration(duration);
// The zoom always first zooms out above the graph and then zooms in
qreal zoomOutFraction = 0.3;
m_animationCameraX.setKeyValueAt(zoomOutFraction, QVariant::fromValue(0.0f));
m_animationCameraY.setKeyValueAt(zoomOutFraction, QVariant::fromValue(90.0f));
m_animationCameraZoom.setKeyValueAt(zoomOutFraction, QVariant::fromValue(50.0f));
m_animationCameraTarget.setKeyValueAt(zoomOutFraction,
QVariant::fromValue(QVector3D(0.0f, 0.0f, 0.0f)));GraphModifier::zoomToSelectedBar() 함수에는 확대/축소 기능이 포함되어 있습니다. QPropertyAnimation 는 m_animationCameraTarget 가 cameraTargetPosition 속성을 대상으로 하며, 이 속성은 (-1, 1) 범위로 정규화된 값을 취합니다.
선택된 막대가 축에 대해 어디에 위치하는지 파악하고, 이를 ` m_animationCameraTarget`의 종료 값으로 사용합니다:
QVector3D endTarget;
float xMin = m_graph->columnAxis()->min();
float xRange = m_graph->columnAxis()->max() - xMin;
float zMin = m_graph->rowAxis()->min();
float zRange = m_graph->rowAxis()->max() - zMin;
endTarget.setX((selectedBar.y() - xMin) / xRange * 2.0f - 1.0f);
endTarget.setZ((selectedBar.x() - zMin) / zRange * 2.0f - 1.0f);
...
m_animationCameraTarget.setEndValue(QVariant::fromValue(endTarget));그런 다음, 애니메이션이 끝날 때 카메라가 항상 그래프의 중심을 대략적으로 향하도록 카메라를 회전시킵니다:
qreal endAngleX = 90.0 - qRadiansToDegrees(qAtan(qreal(endTarget.z() / endTarget.x())));
if (endTarget.x() > 0.0f)
endAngleX -= 180.0f;
float barValue = m_graph->selectedSeries()
->dataProxy()
->itemAt(selectedBar.x(), selectedBar.y())
.value();
float endAngleY = barValue >= 0.0f ? 30.0f : -30.0f;
if (m_graph->valueAxis()->reversed())
endAngleY *= -1.0f;데이터용 모델 프록시
Model Proxy Data 의 데이터 모드를 켜면, 예제의 그래프는 항목 모델 기반 데이터 세트와 해당 프록시를 사용합니다.
RainfallData
RainfallData 클래스는 QItemModelBarDataProxy 를 시리즈 및 축과 함께 설정합니다.
-
std::array<double, 12>를 사용하여 월별로 인덱싱된 연도별 값을 저장합니다. 정적 헬퍼 함수 `readData()`는 데이터를 읽어와 연도별로 인덱싱된 ` QList `를 반환합니다.using YearlyData = std::array<double, 12>; using ModelData = QList<YearlyData>; static ModelData readData(const QString&fileName, int *firstYear) { ModelData result; *firstYear =-1; // 데이터 파일에서 데이터를 읽어와 데이터 항목 목록에 저장합니다 QFile dataFile(fileName); if (!dataFile.open(QIODevice::ReadOnly | QIODevice::Text)) { qWarning() << "Unable to open data file:" << dataFile.fileName() << dataFile.errorString(); result를 반환합니다; } QTextStream stream(&dataFile); int lastYear =-1; while (!stream.atEnd()) { QString line = stream.readLine(); if (line.startsWith(u'#')) // 주석 무시 continue; const auto strList = QStringView{line}.split(',', Qt::SkipEmptyParts); // 각 줄에는 연도, 월, 강수량 값의 세 가지 데이터 항목이 있습니다. if (strList.size() < 3) { qWarning() << "Invalid row read from data:" << line; continue; } // 연도와 월은 int로, 강수량 값은 double로 저장 bool yearOk{}; bool monthOk{}; bool valueOk{}; const int year = strList.at(0).trimmed().toInt(&yearOk); const int month = strList.at(1).trimmed().toInt(&monthOk); const double value = strList.at(2).trimmed().toDouble(&valueOk); if (!yearOk|| !monthOk|| month < 1|| month > 12|| !valueOk) { qWarning() << "Invalid row values:" << line; 계속; } if (year != lastYear) { if (lastYear==-1) { *firstYear = year; } else if (year != lastYear + 1) { qWarning() << "Non-consecutive years" << year << lastYear; return {}; } lastYear = year; result.emplace_back(YearlyData{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); } result.back()[month - 1] = value; } return result; } ... - QList 는 각각 연도와 월을 인덱스로 하는 2차원 배열을 나타냅니다. 이를 바탕으로 QRangeModel 를 채운 다음,
RainfallData클래스의 QItemModelBarDataProxy()에 전달합니다:int firstYear{}; auto data = readData(":/data/raindata.txt"_L1, &firstYear); Q_ASSERT(!data.isEmpty()); updateYearsList(firstYear, firstYear + int(data.size()) - 1); auto *model = new QRangeModel(data, this); m_proxy = new QItemModelBarDataProxy(model); m_proxy->setUseModelCategories(true); m_series = new QBar3DSeries(m_proxy); - 마지막으로, 표시할 생성된 시리즈를 가져오는 함수를 추가합니다:
QBar3DSeries *customSeries() { return m_series; }
산점도
Scatter Graph 탭에서 Q3DScatterWidgetItem 를 사용하여 3D 산점도를 생성합니다. 이 예제는 다음 방법을 보여줍니다:
- Q3DScatterWidgetItem 그래프 설정
- QScatterDataProxy 를 사용하여 그래프에 데이터 설정하기
- 사용자 정의 입력 핸들러 생성 및 추가
기본적인 애플리케이션 생성에 대해서는 막대 그래프를 참조하십시오.
산점도 그래프 설정
ScatterDataModifier의 생성자에서 그래프의 시각적 속성을 다음과 같이 설정합니다:m_graph->setShadowQuality(QtGraphs3D::ShadowQuality::SoftHigh); m_graph->setCameraPreset(QtGraphs3D::CameraPreset::Front); m_graph->setCameraZoomLevel(80.f); // These are set through active theme m_graph->activeTheme()->setTheme(QGraphsTheme::Theme::MixSeries); m_graph->activeTheme()->setColorScheme(QGraphsTheme::ColorScheme::Dark);이 설정들은 모두 필수 사항은 아니지만, 그래프의 기본값을 재정의하는 데 사용됩니다. 사전 설정된 기본값으로 표시되는 모습을 확인하려면 위의 블록을 주석 처리하면 됩니다.
- QScatterDataProxy 와 관련 QScatter3DSeries 를 생성합니다. 시리즈에 대한 사용자 정의 레이블 형식과 메쉬 스무딩을 설정하고 그래프에 추가합니다:
auto *proxy = new QScatterDataProxy; auto *series = new QScatter3DSeries(proxy); series->setItemLabelFormat(u"@xTitle: @xLabel @yTitle: @yLabel @zTitle: @zLabel"_s); series->setMeshSmooth(m_smooth); m_graph->addSeries(series);
산점도 데이터 추가
ScatterDataModifier생성자에서 그래프에 데이터를 추가합니다:addData();- 실제 데이터 추가 작업은 `
addData()` 메서드에서 수행됩니다. 먼저 축을 설정합니다:m_graph->axisX()->setTitle("X"); m_graph->axisY()->setTitle("Y"); m_graph->axisZ()->setTitle("Z");ScatterDataModifier의 생성자에서도 이 작업을 수행할 수 있습니다. 여기서 설정하면 생성자를 더 간단하게 유지할 수 있고, 축 구성을 데이터 근처에 두게 됩니다. - 데이터 배열을 생성하고 데이터를 채웁니다:
QScatterDataArray dataArray; dataArray.reserve(m_itemCount); ... const float limit = qSqrt(m_itemCount) / 2.0f; for (int i = -limit; i < limit; ++i) { for (int j = -limit; j < limit; ++j) { const float x = float(i) + 0.5f; const float y = qCos(qDegreesToRadians(float(i * j) / m_curveDivider)); const float z = float(j) + 0.5f; dataArray.append(QScatterDataItem(x, y, z)); } } - 마지막으로, 프록시에게 우리가 제공한 데이터를 사용하도록 지시합니다:
m_graph->seriesList().at(0)->dataProxy()->resetArray(dataArray);
이제 그래프에 데이터가 반영되어 사용할 준비가 되었습니다. 그래프를 제어하기 위해 위젯을 추가하는 방법에 대한 자세한 내용은 " 위젯을 사용하여 그래프 제어하기" 를 참조하십시오.
기본 입력 처리 방식 변경
기본 입력 처리 메커니즘을 대체하려면, 사용자 정의 동작을 구현하는 ` Q3DScatterWidgetItem`의 새 입력 핸들러를 설정하십시오:
connect(m_graph,
&Q3DGraphsWidgetItem::selectedElementChanged,
this,
&ScatterDataModifier::handleElementSelected);
connect(m_graph, &Q3DGraphsWidgetItem::dragged, this, &ScatterDataModifier::handleAxisDragging);
m_graph->setDragButton(Qt::LeftButton);마우스 이벤트 처리 확장
새로운 ` drag ` 이벤트 핸들러를 구현하십시오. 이 핸들러는 축 드래그 계산을 위한 마우스 이동 거리를 제공합니다(자세한 내용은 ‘축 드래그 구현’ 참조):
connect(m_graph,
&Q3DGraphsWidgetItem::selectedElementChanged,
this,
&ScatterDataModifier::handleElementSelected);
connect(m_graph, &Q3DGraphsWidgetItem::dragged, this, &ScatterDataModifier::handleAxisDragging);
m_graph->setDragButton(Qt::LeftButton);축 드래그 구현
- 그래프에서 선택 신호를 수신하도록 설정하십시오. 생성자에서 이를 수행하고, 해당 신호를 `
handleElementSelected` 메서드에 연결하십시오:connect(m_graph, &Q3DGraphsWidgetItem::selectedElementChanged, this, &ScatterDataModifier::handleElementSelected); connect(m_graph, &Q3DGraphsWidgetItem::dragged, this, &ScatterDataModifier::handleAxisDragging); m_graph->setDragButton(Qt::LeftButton); handleElementSelected에서 선택의 유형을 확인하고, 그에 따라 내부 상태를 설정하세요:switch (type) { case QtGraphs3D::ElementType::AxisXLabel: m_state = StateDraggingX; break; case QtGraphs3D::ElementType::AxisYLabel: m_state = StateDraggingY; break; case QtGraphs3D::ElementType::AxisZLabel: m_state = StateDraggingZ; break; default: m_state = StateNormal; break; }- 실제 드래그 로직은 `
handleAxisDragging` 메서드에서 구현되며, 이 메서드는 `drag` 이벤트에서 호출됩니다:void ScatterDataModifier::handleAxisDragging(QVector2D delta) handleAxisDragging에서 먼저 활성 카메라로부터 씬의 방향을 가져옵니다:// Get scene orientation from active camera float xRotation = m_graph->cameraXRotation(); float yRotation = m_graph->cameraYRotation();- 이 방향 정보를 바탕으로 마우스 이동 방향에 대한 수정 계수를 계산합니다:
// Calculate directional drag multipliers based on rotation float xMulX = qCos(qDegreesToRadians(xRotation)); float xMulY = qSin(qDegreesToRadians(xRotation)); float zMulX = qSin(qDegreesToRadians(xRotation)); float zMulY = qCos(qDegreesToRadians(xRotation)); - 마우스 이동량을 계산하고, 카메라의 y축 회전 각도에 따라 이를 수정합니다:
// Get the drag amount QPoint move = delta.toPoint(); // Flip the effect of y movement if we're viewing from below float yMove = (yRotation < 0) ? -move.y() : move.y(); - 이동 거리를 올바른 축에 적용합니다:
// Adjust axes QValue3DAxis *axis = nullptr; switch (m_state) { case StateDraggingX: axis = m_graph->axisX(); distance = (move.x() * xMulX - yMove * xMulY) / m_dragSpeedModifier; axis->setRange(axis->min() - distance, axis->max() - distance); break; case StateDraggingZ: axis = m_graph->axisZ(); distance = (move.x() * zMulX + yMove * zMulY) / m_dragSpeedModifier; axis->setRange(axis->min() + distance, axis->max() + distance); break; case StateDraggingY: axis = m_graph->axisY(); distance = move.y() / m_dragSpeedModifier; // No need to use adjusted y move here axis->setRange(axis->min() + distance, axis->max() + distance); break; default: break; }
표면 그래프
' Surface Graph ' 탭에서 ' Q3DSurfaceWidgetItem'을 사용하여 3D 표면 그래프를 생성합니다. 이 예제에서는 다음을 수행하는 방법을 보여줍니다:
- QSurfaceDataProxy 기본적인 표면 그래프를 설정하고 데이터를 지정합니다.
- QHeightMapSurfaceDataProxy 를 사용하여 3D 높이 맵을 표시합니다.
- 지형 데이터를 사용하여 3D 높이 맵을 생성하는 방법.
- 그래프를 분석하기 위해 세 가지 선택 모드를 사용하는 방법.
- 축 범위를 사용하여 그래프의 선택된 부분을 표시합니다.
- 사용자 지정 표면 구배를 설정합니다.
- QCustom3DItem 및 QCustom3DLabel 를 사용하여 사용자 지정 항목과 레이블을 추가하십시오.
- 사용자 정의 입력 핸들러를 사용하여 확대/축소 및 이동 기능을 활성화합니다.
- 표면의 특정 영역을 강조 표시하십시오.
기본적인 애플리케이션 제작 방법에 대해서는 막대 그래프(Bar Graph) 를 참조하십시오.
생성된 데이터가 포함된 간단한 서피스
- 먼저, 새로운 ` QSurfaceDataProxy ` 인스턴스를 생성하고 이를 새로운 ` QSurface3DSeries`에 연결합니다:
m_sqrtSinProxy = new QSurfaceDataProxy(); m_sqrtSinSeries = new QSurface3DSeries(m_sqrtSinProxy); - 프록시에 간단한 제곱근 및 사인파 데이터를 채웁니다. `
QSurfaceDataArray` 인스턴스를 생성하고, `QSurfaceDataRow` 요소를 추가합니다. `resetArray()`을 호출하여 생성된 `QSurfaceDataArray`을 ` QSurfaceDataProxy `의 데이터 배열로 설정합니다.QSurfaceDataArray dataArray; dataArray.reserve(sampleCountZ); for (int i = 0; i < sampleCountZ; ++i) { QSurfaceDataRow newRow; newRow.reserve(sampleCountX); // Keep values within range bounds, since just adding step can cause minor // drift due to the rounding errors. float z = qMin(sampleMax, (i * stepZ + sampleMin)); for (int j = 0; j < sampleCountX; ++j) { float x = qMin(sampleMax, (j * stepX + sampleMin)); float R = qSqrt(z * z + x * x) + 0.01f; float y = (qSin(R) / R + 0.24f) * 1.61f; newRow.append(QSurfaceDataItem(x, y, z)); } dataArray.append(newRow); } m_sqrtSinProxy->resetArray(dataArray);
다중 시리즈 높이 맵 데이터
높이 데이터를 포함하는 QImage 를 사용하여 QHeightMapSurfaceDataProxy 를 인스턴스화하여 높이 맵을 생성합니다. QHeightMapSurfaceDataProxy::setValueRanges()를 사용하여 맵의 값 범위를 정의합니다. 이 예제에서 지도는 북위 34.0° ~ 40.0°, 동경 18.0° ~ 24.0°의 가상 위치에 해당합니다. 이 값들은 축상에 지도를 배치하는 데 사용됩니다.
// Create the first surface layer
QImage heightMapImageOne(":/data/layer_1.png");
m_heightMapProxyOne = new QHeightMapSurfaceDataProxy(heightMapImageOne);
m_heightMapSeriesOne = new QSurface3DSeries(m_heightMapProxyOne);
m_heightMapSeriesOne->setItemLabelFormat(u"(@xLabel, @zLabel): @yLabel"_s);
m_heightMapProxyOne->setValueRanges(34.f, 40.f, 18.f, 24.f);높이 지도 이미지를 사용하여 프록시와 시계를 생성하는 방식으로 다른 표면 레이어도 동일한 방법으로 추가합니다. -
지형도 데이터
지형 데이터는 핀란드 국토조사국(National Land Survey of Finland)에서 제공받았습니다. 이 기관은 본 예제에 적합한 ‘ Elevation Model 2 m ’라는 제품을 제공합니다.
지형 데이터는 레비 산(Levi fell)에서 가져온 것입니다. 데이터의 정확도는 필요 수준을 훨씬 상회하므로, 압축하여 PNG 파일로 인코딩했습니다. 원본 ASCII 데이터의 고도 값은 아래 코드 예제에서 보여주는 것처럼 배율을 적용하여 RGB 형식으로 인코딩됩니다. 이 배수는 핀란드에서 가장 높은 지점의 최대 24비트 값을 나누어 계산됩니다.
QHeightMapSurfaceDataProxy 는 1바이트 값만 변환합니다. 핀란드 국토조사국(National Land Survey of Finland)에서 제공한 데이터의 더 높은 정확도를 활용하려면, PNG 파일에서 데이터를 읽어와 QSurface3DSeries 로 디코딩해야 합니다.
- 인코딩 배율을 정의합니다:
// Value used to encode height data as RGB value on PNG file const float packingFactor = 11983.f; - 실제 디코딩을 수행합니다:
QImage heightMapImage(file); uchar *bits = heightMapImage.bits(); int imageHeight = heightMapImage.height(); int imageWidth = heightMapImage.width(); int widthBits = imageWidth * 4; float stepX = width / float(imageWidth); float stepZ = height / float(imageHeight); QSurfaceDataArray dataArray; dataArray.reserve(imageHeight); for (int i = 0; i < imageHeight; ++i) { int p = i * widthBits; float z = height - float(i) * stepZ; QSurfaceDataRow newRow; newRow.reserve(imageWidth); for (int j = 0; j < imageWidth; ++j) { uchar aa = bits[p + 0]; uchar rr = bits[p + 1]; uchar gg = bits[p + 2]; uint color = uint((gg << 16) + (rr << 8) + aa); float y = float(color) / packingFactor; newRow.append(QSurfaceDataItem(float(j) * stepX, y, z)); p += 4; } dataArray.append(newRow); } dataProxy()->resetArray(dataArray);
이제 Surface Graph는 프록시를 통해 데이터를 가져올 수 있습니다.
데이터 세트 선택
다양한 프록시를 시연하기 위해 ‘ Surface Graph ’에는 시리즈 간 전환을 위한 세 개의 라디오 버튼이 있습니다.
Sqrt & Sin 을 사용하면 간단하게 생성된 시계가 활성화됩니다.
- 표면의 격자 표시 활성화, 평면 음영 모드 선택 등 장식적인 기능을 설정합니다.
- 축 레이블 형식과 값 범위를 정의합니다. 낮은 카메라 각도에서도 레이블의 가독성을 높이기 위해 자동 레이블 회전 기능을 설정합니다.
- 그래프에 올바른 시리즈만 추가되고 다른 시리즈는 추가되지 않았는지 확인하십시오.
m_sqrtSinSeries->setDrawMode(QSurface3DSeries::DrawSurfaceAndWireframe);
m_sqrtSinSeries->setShading(QSurface3DSeries::Shading::Flat);
m_graph->axisX()->setLabelFormat("%.2f");
m_graph->axisZ()->setLabelFormat("%.2f");
m_graph->axisX()->setRange(sampleMin, sampleMax);
m_graph->axisY()->setRange(0.f, 2.f);
m_graph->axisZ()->setRange(sampleMin, sampleMax);
m_graph->axisX()->setLabelAutoAngle(30.f);
m_graph->axisY()->setLabelAutoAngle(90.f);
m_graph->axisZ()->setLabelAutoAngle(30.f);
m_graph->removeSeries(m_heightMapSeriesOne);
m_graph->removeSeries(m_heightMapSeriesTwo);
m_graph->removeSeries(m_heightMapSeriesThree);
m_graph->removeSeries(m_topography);
m_graph->removeSeries(m_highlight);
m_graph->addSeries(m_sqrtSinSeries);Multiseries Height Map 를 사용하면 높이 맵 시리즈가 활성화되고 다른 시리즈는 비활성화됩니다. Y축 범위의 자동 조정은 높이 맵 표면에 효과적이므로, 이 기능이 설정되어 있는지 확인하십시오.
m_graph->axisY()->setAutoAdjustRange(true);Textured Topography 를 사용하면 지형 시리즈가 활성화되고 다른 시리즈는 비활성화됩니다. 이 시리즈에서 특정 영역을 강조 표시할 수 있도록 사용자 지정 입력 핸들러를 활성화하십시오.
m_graph->setDragButton(Qt::LeftButton);
QObject::connect(m_graph,
&Q3DGraphsWidgetItem::dragged,
this,
&SurfaceGraphModifier::handleAxisDragging);
QObject::connect(m_graph,
&Q3DGraphsWidgetItem::wheel,
this,
&SurfaceGraphModifier::onWheel);
m_graph->setZoomEnabled(false);이 데이터 세트에 대한 사용자 정의 입력 핸들러 에 대한 자세한 내용은 ‘사용자 정의 입력 핸들러를 사용하여 확대/축소 및 이동 기능 활성화’를 참조하십시오.
선택 모드
Q3DSurfaceWidgetItem 에서 지원하는 세 가지 선택 모드는 라디오 버튼을 통해 사용할 수 있습니다. 선택한 모드를 활성화하거나 해제하려면 다음 인라인 메서드를 추가하십시오:
void toggleModeNone() { m_graph->setSelectionMode(QtGraphs3D::SelectionFlag::None); }
void toggleModeItem() { m_graph->setSelectionMode(QtGraphs3D::SelectionFlag::Item); }
void toggleModeSliceRow()
{
m_graph->setSelectionMode(QtGraphs3D::SelectionFlag::ItemAndRow
| QtGraphs3D::SelectionFlag::Slice
| QtGraphs3D::SelectionFlag::MultiSeries);
}
void toggleModeSliceColumn()
{
m_graph->setSelectionMode(QtGraphs3D::SelectionFlag::ItemAndColumn
| QtGraphs3D::SelectionFlag::Slice
| QtGraphs3D::SelectionFlag::MultiSeries);
}그래프에 표시된 모든 시리즈에 대해 동시에 슬라이스 선택을 지원하려면, 행 및 열 선택 모드에 QtGraphs3D::SelectionFlag::Slice 및 QtGraphs3D::SelectionFlag::MultiSeries 플래그를 추가하십시오.
그래프 분석을 위한 축 범위
이 예제에는 X축과 Z축의 최소값 및 최대값을 조정하기 위한 네 개의 슬라이더 컨트롤이 있습니다. 프록시를 선택하면 이러한 슬라이더는 현재 데이터 세트의 축 범위에 맞춰 조정됩니다:
// Reset range sliders for Sqrt & Sin
m_rangeMinX = sampleMin;
m_rangeMinZ = sampleMin;
m_stepX = (sampleMax - sampleMin) / float(sampleCountX - 1);
m_stepZ = (sampleMax - sampleMin) / float(sampleCountZ - 1);
m_axisMinSliderX->setMinimum(0);
m_axisMinSliderX->setMaximum(sampleCountX - 2);
m_axisMinSliderX->setValue(0);
m_axisMaxSliderX->setMinimum(1);
m_axisMaxSliderX->setMaximum(sampleCountX - 1);
m_axisMaxSliderX->setValue(sampleCountX - 1);
m_axisMinSliderZ->setMinimum(0);
m_axisMinSliderZ->setMaximum(sampleCountZ - 2);
m_axisMinSliderZ->setValue(0);
m_axisMaxSliderZ->setMinimum(1);
m_axisMaxSliderZ->setMaximum(sampleCountZ - 1);
m_axisMaxSliderZ->setValue(sampleCountZ - 1);위젯 컨트롤을 통해 그래프의 X 범위를 설정할 수 있도록 하려면 다음을 추가하십시오:
void SurfaceGraphModifier::setAxisXRange(float min, float max)
{
m_graph->axisX()->setRange(min, max);
}Z 범위에 대한 지원도 동일한 방식으로 추가하십시오.
사용자 정의 표면 그라데이션
Sqrt & Sin 데이터 세트를 사용하면 두 개의 푸시 버튼을 통해 사용자 정의 표면 그라데이션을 적용할 수 있습니다. QLinearGradient 를 사용하여 원하는 색상을 설정하고 그라데이션을 정의하십시오. 또한 그라데이션을 사용하려면 색상 스타일을 Q3DTheme::ColorStyle::RangeGradient로 변경하십시오.
QLinearGradient gr;
gr.setColorAt(0.f, Qt::black);
gr.setColorAt(0.33f, Qt::blue);
gr.setColorAt(0.67f, Qt::red);
gr.setColorAt(1.f, Qt::yellow);
m_sqrtSinSeries->setBaseGradient(gr);
m_sqrtSinSeries->setColorStyle(QGraphsTheme::ColorStyle::RangeGradient);애플리케이션에 사용자 정의 메쉬 추가
애플리케이션에 사용자 정의 메쉬를 추가하려면:
- CMake 빌드의 경우, 메쉬 파일을 `
CMakeLists.txt`에 추가합니다:set(graphgallery_resource_files ... "data/oilrig.mesh" "data/pipe.mesh" "data/refinery.mesh" ... ) qt6_add_resources(widgetgraphgallery "widgetgraphgallery" PREFIX "/" FILES ${graphgallery_resource_files} ) - qmake 빌드의 경우, qrc 리소스 파일에 메쉬 파일을 추가하십시오:
<RCC> <qresource prefix="/"> ... <file>data/refinery.mesh</file> <file>data/oilrig.mesh</file> <file>data/pipe.mesh</file> ... </qresource> </RCC>
그래프에 사용자 정의 항목 추가하기
Multiseries Height Map 데이터 세트를 사용하면 사용자 정의 항목이 그래프에 삽입되며, 체크박스를 통해 표시/숨기기를 전환할 수 있습니다. 또한 다른 체크박스 세트를 통해 상단 두 레이어의 투명도 및 하단 레이어의 강조 표시 등 기타 시각적 변경 사항도 제어할 수 있습니다.
- 작은 QImage 를 생성합니다. 사용자 정의 객체의 색상으로 사용할 단일 색상으로 채웁니다:
- 항목의 위치를 변수에 지정합니다. 이 위치를 사용하여 그래프에서 해당 항목을 제거할 수 있습니다:
QVector3D positionOne = QVector3D(39.f, 77.f, 19.2f); - 모든 매개변수가 포함된 새로운 QCustom3DItem 를 생성합니다:
auto *item = new QCustom3DItem(":/data/oilrig.mesh", positionOne, QVector3D(0.0125f, 0.0125f, 0.0125f), QQuaternion::fromAxisAndAngle(0.f, 1.f, 0.f, 45.f), color); - 그래프에 항목을 추가합니다:
m_graph->addCustomItem(item);
그래프에 사용자 정의 레이블 추가하기
사용자 정의 레이블을 추가하는 방법은 사용자 정의 항목을 추가하는 방법과 매우 유사합니다. 레이블의 경우 사용자 정의 메시가 필요하지 않으며, 단순히 QCustom3DLabel 인스턴스만 있으면 됩니다:
auto *label = new QCustom3DLabel();
label->setText("Oil Rig One");
label->setPosition(positionOneLabel);
label->setScaling(QVector3D(1.f, 1.f, 1.f));
m_graph->addCustomItem(label);그래프에서 사용자 정의 항목 제거하기
그래프에서 특정 항목을 제거하려면, 해당 항목의 위치를 지정하여 ` removeCustomItemAt() `를 호출합니다:
m_graph->removeCustomItemAt(positionOne);참고: 그래프에서 사용자 정의 항목을제거하면 해당 객체도 삭제됩니다. 항목을 보존하려면 대신 ` releaseCustomItem() ` 메서드를 사용하십시오.
표면 시리즈에 텍스처 적용
Textured Topography 데이터 세트를 사용하여 지형 높이 맵과 함께 사용할 맵 텍스처를 생성합니다.
QSurface3DSeries::setTextureFile()를 사용하여 표면의 텍스처로 사용할 이미지를 설정합니다. 텍스처 설정 여부를 제어하는 체크박스와 체크박스 상태에 반응하는 핸들러를 추가합니다:
void SurfaceGraphModifier::toggleSurfaceTexture(bool enable)
{
if (enable)
m_topography->setTextureFile(":/data/maptexture.jpg");
else
m_topography->setTextureFile("");
}이 예제의 이미지는 JPG 파일에서 읽어옵니다. 이 메서드를 사용하여 빈 파일을 설정하면 텍스처가 지워지고, 표면은 테마의 그라데이션이나 색상을 사용합니다.
사용자 정의 입력 핸들러를 사용하여 확대/축소 및 이동 기능 활성화
Textured Topography 데이터 세트를 사용하여 그래프에서 선택 영역을 강조 표시하고 그래프를 패닝할 수 있도록 사용자 정의 입력 핸들러를 만듭니다.
패닝 구현은 ‘축 드래그 구현’에서 보여준 방식과 유사합니다. 차이점은 이 예제에서는 X축과 Z축만 추적하고, 그래프 외부로 표면을 드래그하는 것을 허용하지 않는다는 점입니다. 드래그를 제한하려면 축의 한계를 따르고, 그래프 외부로 나가는 경우 아무 작업도 수행하지 마십시오:
case StateDraggingX:
distance = (move.x() * xMulX - move.y() * xMulY) * m_speedModifier;
m_axisXMinValue -= distance;
m_axisXMaxValue -= distance;
if (m_axisXMinValue < m_areaMinValue) {
float dist = m_axisXMaxValue - m_axisXMinValue;
m_axisXMinValue = m_areaMinValue;
m_axisXMaxValue = m_axisXMinValue + dist;
}
if (m_axisXMaxValue > m_areaMaxValue) {
float dist = m_axisXMaxValue - m_axisXMinValue;
m_axisXMaxValue = m_areaMaxValue;
m_axisXMinValue = m_axisXMaxValue - dist;
}
m_graph->axisX()->setRange(m_axisXMinValue, m_axisXMaxValue);
break;확대/축소(zooming)의 경우, ` wheelEvent ` 이벤트를 처리하고 ` QWheelEvent`의 델타 값에 따라 X 및 Y 축 범위를 조정합니다. Y 축과 XZ 평면 사이의 종횡비가 동일하게 유지되도록 Y 축을 조정합니다. 이렇게 하면 그래프의 높이가 과장되어 표시되는 것을 방지할 수 있습니다:
void SurfaceGraphModifier::onWheel(QWheelEvent *event)
{
float delta = float(event->angleDelta().y());
m_axisXMinValue += delta;
m_axisXMaxValue -= delta;
m_axisZMinValue += delta;
m_axisZMaxValue -= delta;
checkConstraints();
float y = (m_axisXMaxValue - m_axisXMinValue) * m_aspectRatio;
m_graph->axisX()->setRange(m_axisXMinValue, m_axisXMaxValue);
m_graph->axisY()->setRange(100.f, y);
m_graph->axisZ()->setRange(m_axisZMinValue, m_axisZMaxValue);
}다음으로, 표면에 너무 가까이 가거나 너무 멀리 떨어지지 않도록 확대/축소 수준에 제한을 추가합니다. 예를 들어, X축 값이 허용 한계를 밑돌 경우, 즉 확대/축소가 너무 멀리 진행될 경우, 해당 값은 허용되는 최소값으로 설정됩니다. 범위가 최소값 아래로 내려갈 경우, 범위가 한계 내에 머물도록 축의 양쪽 끝을 조정합니다:
if (m_axisXMinValue < m_areaMinValue)
m_axisXMinValue = m_areaMinValue;
if (m_axisXMaxValue > m_areaMaxValue)
m_axisXMaxValue = m_areaMaxValue;
// Don't allow too much zoom in
if ((m_axisXMaxValue - m_axisXMinValue) < m_axisXMinRange) {
float adjust = (m_axisXMinRange - (m_axisXMaxValue - m_axisXMinValue)) / 2.f;
m_axisXMinValue -= adjust;
m_axisXMaxValue += adjust;
}표면의 특정 영역 강조 표시
표면에 하이라이트를 표시하려면, 시리즈의 복사본을 생성하고 y 값에 약간의 오프셋을 추가합니다. 이 예제에서 HighlightSeries 클래스는 handlePositionChange 메서드 내에서 복사본 생성을 구현합니다.
먼저, ` HighlightSeries `에 원본 시계의 포인터를 전달한 다음, ` QSurface3DSeries::selectedPointChanged ` 신호를 수신 대기하도록 설정합니다:
void HighlightSeries::setTopographicSeries(TopographicSeries *series)
{
m_topographicSeries = series;
m_srcWidth = m_topographicSeries->dataArray().at(0).size();
m_srcHeight = m_topographicSeries->dataArray().size();
QObject::connect(m_topographicSeries,
&QSurface3DSeries::selectedPointChanged,
this,
&HighlightSeries::handlePositionChange);
}신호가 트리거되면 위치가 유효한지 확인합니다. 그런 다음 복사된 영역의 범위를 계산하고, 해당 범위가 경계 내에 있는지 확인합니다. 마지막으로, 하이라이트 시퀀스의 데이터 배열을 지형 시퀀스의 데이터 배열에서 가져온 범위로 채웁니다:
void HighlightSeries::handlePositionChange(const QPoint &position)
{
m_position = position;
if (position == invalidSelectionPosition()) {
setVisible(false);
return;
}
int halfWidth = m_width / 2;
int halfHeight = m_height / 2;
int startX = position.x() - halfWidth;
if (startX < 0)
startX = 0;
int endX = position.x() + halfWidth;
if (endX > (m_srcWidth - 1))
endX = m_srcWidth - 1;
int startZ = position.y() - halfHeight;
if (startZ < 0)
startZ = 0;
int endZ = position.y() + halfHeight;
if (endZ > (m_srcHeight - 1))
endZ = m_srcHeight - 1;
const QSurfaceDataArray &srcArray = m_topographicSeries->dataArray();
QSurfaceDataArray dataArray;
dataArray.reserve(endZ - startZ);
for (int i = startZ; i < endZ; ++i) {
QSurfaceDataRow newRow;
newRow.reserve(endX - startX);
QSurfaceDataRow srcRow = srcArray.at(i);
for (int j = startX; j < endX; ++j) {
QVector3D pos = srcRow.at(j).position();
pos.setY(pos.y() + m_heightAdjustment);
newRow.append(QSurfaceDataItem(pos));
}
dataArray.append(newRow);
}
dataProxy()->resetArray(dataArray);
setVisible(true);
}‘하이라이트’ 시리즈를 위한 그라디언트
HighlightSeries 는 QSurface3DSeries 이므로, 시리즈에 적용할 수 있는 모든 장식 메서드를 사용할 수 있습니다. 이 예제에서는 고도를 강조하기 위해 그라데이션을 추가해 보겠습니다. 적합한 그라데이션 스타일은 Y축의 범위에 따라 달라지며, 확대/축소 시 범위가 변경되므로 범위가 변함에 따라 그라데이션 색상 위치도 조정해야 합니다. 이를 위해 그라데이션 색상 위치에 비례 값을 정의합니다:
const float darkRedPos = 1.f;
const float redPos = 0.8f;
const float yellowPos = 0.6f;
const float greenPos = 0.4f;
const float darkGreenPos = 0.2f;그라데이션 수정은 ` handleGradientChange ` 메서드에서 이루어지므로, Y축의 변화에 반응하도록 이 메서드를 연결합니다:
QObject::connect(m_graph->axisY(),
&QValue3DAxis::maxChanged,
m_highlight,
&HighlightSeries::handleGradientChange);Y축의 최대값이 변경되면 새로운 그라데이션 색상 위치를 계산합니다:
void HighlightSeries::handleGradientChange(float value)
{
float ratio = m_minHeight / value;
QLinearGradient gr;
gr.setColorAt(0.f, Qt::black);
gr.setColorAt(darkGreenPos * ratio, Qt::darkGreen);
gr.setColorAt(greenPos * ratio, Qt::green);
gr.setColorAt(yellowPos * ratio, Qt::yellow);
gr.setColorAt(redPos * ratio, Qt::red);
gr.setColorAt(darkRedPos * ratio, Qt::darkRed);
setBaseGradient(gr);
setColorStyle(QGraphsTheme::ColorStyle::RangeGradient);
handleZoomChange(ratio);
}예제 내용
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.