Galería de gráficos
Galería de gráficos de barras, de dispersión y de superficie.
LaGalería de Grá ficos muestra los tres tipos de gráficos y algunas de sus características especiales. Los gráficos tienen sus propias pestañas en la aplicación.

Ejecutar el ejemplo
Para ejecutar el ejemplo desde Qt Creatorabra el modo Welcome y seleccione el ejemplo de Examples. Para más información, consulte Qt Creator: Tutorial: Construir y ejecutar.
Gráfico de barras
En la pestaña Bar Graph, cree un gráfico de barras 3D utilizando Q3DBarWidgetItem y combine el uso de widgets para ajustar varias cualidades del gráfico de barras. El ejemplo muestra cómo:
- Crear una aplicación con Q3DBarWidgetItem y algunos widgets de control.
- Utilizar QBar3DSeries y QBarDataProxy para establecer datos en el gráfico
- Ajustar algunas propiedades del gráfico y de las series utilizando controles widget
- Seleccionar una fila o una columna haciendo clic en una etiqueta de eje
- Crear un proxy personalizado para utilizarlo con Q3DBarWidgetItem
Para obtener información sobre cómo interactuar con el gráfico, consulte esta página.
Creación de la aplicación
- En
bargraph.cpp, instancie QQuickWidget y Q3DBarsWidgetItem, y establezca la instancia QQuickWidget como el widget para Q3DBarsWidgetItem:m_quickWidget = new QQuickWidget(); m_barGraph = new Q3DBarsWidgetItem(this); m_barGraph->setWidget(m_quickWidget);
- Crea un widget contenedor y diseños horizontal y vertical. Añade el gráfico y el diseño vertical al horizontal:
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);
- Crea otra clase para manejar la adición de datos y otras interacciones con el gráfico:
m_modifier = new GraphModifier(m_barGraph, this);
Configurar el gráfico de barras
- Configure el gráfico en el constructor de la clase
GraphModifier:GraphModifier::GraphModifier(Q3DBarsWidgetItem *bargraph, QObject *parent) : QObject(parent) , m_graph(bargraph)
- Crea los ejes y las series en variables miembro para poder cambiarlas:
, m_temperatureAxis(new QValue3DAxis) , m_yearAxis(new QCategory3DAxis) , m_monthAxis(new QCategory3DAxis) , m_primarySeries(new QBar3DSeries) , m_secondarySeries(new QBar3DSeries)
- Establecer algunas cualidades visuales para el gráfico:
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);
- Configurar los ejes y hacerlos los ejes activos del gráfico:
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);
- Dar a las etiquetas de los ejes un pequeño ángulo de autorrotación:
m_yearAxis->setLabelAutoAngle(30.0f);
Esto se hace para que se orienten ligeramente hacia la cámara, lo que mejora la legibilidad de las etiquetas de los ejes en ángulos de cámara extremos.
- Inicialice las propiedades visuales de las series. Tenga en cuenta que la segunda serie no es visible inicialmente:
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);
- Añada la serie al gráfico:
m_graph->addSeries(m_primarySeries); m_graph->addSeries(m_secondarySeries);
- Establezca el ángulo de la cámara llamando al mismo método que utiliza el botón de cambio de ángulo de la cámara en la interfaz de usuario para recorrer varios ángulos de cámara:
changePresetCamera();
- El nuevo preajuste de cámara se establece en el gráfico:
static int preset = int(QtGraphs3D::CameraPreset::Front); m_graph->setCameraPreset((QtGraphs3D::CameraPreset) preset); if (++preset > int(QtGraphs3D::CameraPreset::DirectlyBelow)) preset = int(QtGraphs3D::CameraPreset::FrontLow);
Añadir datos al gráfico
Al final del constructor, llama a un método que configura los datos:
resetTemperatureData();
Este método añade datos a las series relevantes usando proxies de las dos series:
// 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);
Uso de widgets para controlar el gráfico
Continúa añadiendo algunos widgets en bargraph.cpp.
- Añade un deslizador:
- Usa el deslizador para rotar la gráfica en lugar de usar el ratón o el toque. Añádelo al diseño vertical:
- Conéctelo a un método en
GraphModifier: - Cree una ranura en
GraphModifierpara la conexión de la señal. Especifica la posición real de la cámara a lo largo de la órbita alrededor del punto central, en lugar de especificar un ángulo de cámara preestablecido:void GraphModifier::rotateX(int angle) { m_xRotation = angle; m_graph->setCameraPosition(m_xRotation, m_yRotation); }
Ahora puede utilizar el control deslizante para girar el gráfico.
Añade más widgets al diseño vertical para controlarlo:
- Rotación del gráfico
- Estilo de etiqueta
- Preajuste de cámara
- Visibilidad del fondo
- Visibilidad de la cuadrícula
- Suavidad del sombreado de la barra
- Visibilidad de la segunda serie de barras
- Dirección del eje de valores
- Visibilidad y rotación del título del eje
- Rango de datos a mostrar
- Estilo de la barra
- Modo de selección
- Tema
- Calidad de la sombra
- Fuente
- Tamaño de fuente
- Rotación de la etiqueta del eje
- Modo de datos
Algunos controles de widget se desactivan intencionadamente cuando están en el modo de datos Custom Proxy Data.
Seleccionar una fila o columna haciendo clic en una etiqueta de eje
La selección por etiqueta de eje es una funcionalidad por defecto para los gráficos de barras. Por ejemplo, puede seleccionar filas haciendo clic en una etiqueta de eje de la siguiente manera:
- Cambie el modo de selección a
Row - Haga clic en una etiqueta de año
- Se selecciona la fila con el año seleccionado
El mismo método funciona con las banderas Slice y Item, siempre que Row o Column también estén activadas.
Zoom a la selección
Como ejemplo de ajuste del objetivo de la cámara, implemente una animación de zoom a la selección mediante la pulsación de un botón. La inicialización de la animación se realiza en el constructor:
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)));
La función GraphModifier::zoomToSelectedBar() contiene la funcionalidad de zoom. QPropertyAnimation m_animationCameraTarget tiene como objetivo la propiedad cameraTargetPosition, que toma un valor normalizado al rango (-1, 1).
Averigüe dónde se encuentra la barra seleccionada en relación a los ejes, y utilícela como valor final para 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));
A continuación, gira la cámara para que siempre apunte aproximadamente al centro del gráfico al final de la animación:
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;
Modelo Proxy para Datos
Al activar el modo de datos Model Proxy Data, el gráfico del ejemplo utiliza un conjunto de datos basado en un modelo de elementos y el proxy correspondiente.
RainfallData
La clase RainfallData configura un QItemModelBarDataProxy junto con las series y los ejes.
- Utilizamos un
std::array<double, 12>para almacenar los valores de un año indexados por mes. La función estáticareadData()helper lee los datos, devolviendo un QList de ellos indexados por año:using DatosAño = std::array<double, 12>; using DatosModelo = QList<YearlyData>;static ModelData readData(const QString &fileName, int *firstYear) { ModelData result; *firstYear =-1; // Leer datos de un archivo de datos en la lista de elementos de datos QFile dataFile(fileName); if (!dataFile.open(QIODevice::SóloLectura | QIODevice::Texto)) { qWarning() << "Unable to open data file:" << dataFile.fileName() << dataFile.errorString(); return result; } QTextStream stream(&dataFile); int ultimoAño =-1; while (!stream.atEnd()) { QString line = stream.readLine(); if (line.startsWith(u'#')) // Ignorar comentarios continue; const auto strList = QStringView{línea}.split(',', Qt::SkipEmptyParts); // Cada línea tiene tres datos: Año, mes y valor de la precipitación if (strList.size() < 3) { qWarning() << "Invalid row read from data:" << line; continue; } // Almacena el año y el mes como int, y el valor de la lluvia como 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; continue; } 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, 0}); } result.back()[month - 1] = value; } return result; } ...
- El QList representa un array bidimensional indexado por año y mes, respectivamente. A partir de ella, rellenamos un QRangeModel y lo pasamos a un QItemModelBarDataProxy() en la clase
RainfallData: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);
- Por último, añadimos una función para obtener la serie creada para su visualización:
QBar3DSeries *customSeries() { return m_series; }
Gráfico de dispersión
En la pestaña Scatter Graph, crea un gráfico de dispersión 3D utilizando Q3DScatterWidgetItem. El ejemplo muestra cómo hacerlo:
- Configurar el gráfico Q3DScatterWidgetItem
- Utilizar QScatterDataProxy para introducir datos en el gráfico
- Crear y añadir un manejador de entrada personalizado
Para la creación de aplicaciones básicas, véase Gráfico de barras.
Configuración del gráfico de dispersión
- Configure algunas cualidades visuales para el gráfico en el constructor de la página
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);
Ninguno de estos ajustes es obligatorio, pero sirven para anular los valores predeterminados del gráfico. Para observar la apariencia con los valores predeterminados, se puede comentar el bloque anterior.
- Cree un QScatterDataProxy y el QScatter3DSeries asociado. Establezca un formato de etiqueta personalizado y un suavizado de malla para la serie y añádalo al gráfico:
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);
Añadir datos de dispersión
- En el constructor
ScatterDataModifier, añada datos al gráfico:addData();
- La adición real de datos se realiza en el método
addData(). Primero, configura los ejes:m_graph->axisX()->setTitle("X"); m_graph->axisY()->setTitle("Y"); m_graph->axisZ()->setTitle("Z");
Podrías hacerlo también en el constructor de
ScatterDataModifier. Hacerlo aquí mantiene el constructor más simple y la configuración de los ejes cerca de los datos. - Crea un array de datos y rellénalo:
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)); } }
- Finalmente, dile al proxy que empiece a usar los datos que le dimos:
m_graph->seriesList().at(0)->dataProxy()->resetArray(dataArray);
Ahora, el gráfico tiene los datos y está listo para su uso. Para obtener información sobre la adición de widgets para controlar el gráfico, consulte Uso de widgets para controlar el gráfico.
Sustitución de la gestión de entradas por defecto
Para reemplazar el mecanismo de manejo de entrada por defecto, establezca los nuevos manejadores de entrada de Q3DScatterWidgetItem, que implementa el comportamiento personalizado:
connect(m_graph, &Q3DGraphsWidgetItem::selectedElementChanged, this, &ScatterDataModifier::handleElementSelected); connect(m_graph, &Q3DGraphsWidgetItem::dragged, this, &ScatterDataModifier::handleAxisDragging); m_graph->setDragButton(Qt::LeftButton);
Extendiendo el Manejo de Eventos del Ratón
Implemente un nuevo manejador de eventos drag. Proporciona una distancia de movimiento del ratón para el cálculo del arrastre del eje (ver Implementación del Arrastre del Eje para más detalles):
connect(m_graph, &Q3DGraphsWidgetItem::selectedElementChanged, this, &ScatterDataModifier::handleElementSelected); connect(m_graph, &Q3DGraphsWidgetItem::dragged, this, &ScatterDataModifier::handleAxisDragging); m_graph->setDragButton(Qt::LeftButton);
Implementación del arrastre de ejes
- Empieza a escuchar la señal de selección del gráfico. Hazlo en el constructor, y conéctalo al método
handleElementSelected:connect(m_graph, &Q3DGraphsWidgetItem::selectedElementChanged, this, &ScatterDataModifier::handleElementSelected); connect(m_graph, &Q3DGraphsWidgetItem::dragged, this, &ScatterDataModifier::handleAxisDragging); m_graph->setDragButton(Qt::LeftButton);
- En
handleElementSelected, comprueba el tipo de selección, y establece el estado interno basado en él: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; }
- La lógica real de arrastre se implementa en el método
handleAxisDragging, que es llamado desde el eventodrag:void ScatterDataModifier::handleAxisDragging(QVector2D delta)
- En
handleAxisDragging, primero se obtiene la orientación de la escena de la cámara activa:// Get scene orientation from active camera float xRotation = m_graph->cameraXRotation(); float yRotation = m_graph->cameraYRotation();
- Calcula los modificadores para la dirección de movimiento del ratón basándose en la orientación:
// 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));
- Calcula el movimiento del ratón, y modifícalo basándote en la rotación y de la cámara:
// 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();
- Aplica la distancia movida al eje correcto:
// 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; }
Gráfico de superficie
En la pestaña Surface Graph, cree un gráfico de superficie 3D utilizando Q3DSurfaceWidgetItem. El ejemplo muestra cómo hacerlo:
- Configurar un QSurfaceDataProxy básico y establecer datos para él.
- Utilizar QHeightMapSurfaceDataProxy para mostrar mapas de altura en 3D.
- Utilizar datos topográficos para crear mapas de altura en 3D.
- Utilizar tres modos de selección diferentes para estudiar el gráfico.
- Utilizar rangos de ejes para mostrar partes seleccionadas del gráfico.
- Establezca un gradiente de superficie personalizado.
- Añadir elementos personalizados y etiquetas con QCustom3DItem y QCustom3DLabel.
- Utilice un controlador de entrada personalizado para activar el zoom y la panorámica.
- Resaltar un área de la superficie.
Para la creación de aplicaciones básicas, consulte Gráfico de barras.
Superficie simple con datos generados
- Primero, instancie un nuevo QSurfaceDataProxy y adjúntelo a un nuevo QSurface3DSeries:
m_sqrtSinProxy = new QSurfaceDataProxy(); m_sqrtSinSeries = new QSurface3DSeries(m_sqrtSinProxy);
- Rellene el proxy con una raíz cuadrada simple y datos de onda sinusoidal. Cree una instancia de
QSurfaceDataArrayy añádale elementos deQSurfaceDataRow. Establezca elQSurfaceDataArraycreado como la matriz de datos para el QSurfaceDataProxy llamando aresetArray().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);
Datos del mapa de alturas multiserie
Cree el mapa de alturas instanciando un QHeightMapSurfaceDataProxy con un QImage que contenga los datos de altura. Utilice QHeightMapSurfaceDataProxy::setValueRanges() para definir el rango de valores del mapa. En el ejemplo, el mapa es de una posición imaginaria de 34,0° N - 40,0° N y 18,0° E - 24,0° E. Estos valores se utilizan para posicionar el mapa en los ejes.
// 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);
Añada las otras capas de superficie de la misma manera, creando un proxy y una serie para ellas utilizando imágenes de mapas de altura. -
Datos del mapa topográfico
Los datos topográficos se obtienen del National Land Survey de Finlandia. Proporciona un producto denominado Elevation Model 2 m, adecuado para este ejemplo.
Los datos topográficos proceden de Levi fell. La precisión de los datos supera con creces las necesidades, por lo que se comprimen y codifican en un archivo PNG. El valor de la altura de los datos ASCII originales se codifica en formato RGB utilizando un multiplicador, como se muestra en el ejemplo de código siguiente. El multiplicador se calcula dividiendo el mayor valor de 24 bits por el punto más alto de Finlandia.
QHeightMapSurfaceDataProxy convierte sólo valores de un byte. Para utilizar la mayor precisión de los datos del National Land Survey of Finland, lea los datos del archivo PNG y descodifíquelos en un QSurface3DSeries.
- Defina el multiplicador de codificación:
// Value used to encode height data as RGB value on PNG file const float packingFactor = 11983.f;
- Realice la descodificación propiamente dicha:
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);
Ahora, un Gráfico de Superficie puede consumir los datos a través del proxy.
Seleccionando el Conjunto de Datos
Para mostrar diferentes proxies, la página Surface Graph tiene tres botones de radio para cambiar entre las series.
Con Sqrt & Sin, se activa la serie generada simple.
- Defina las características decorativas, como activar la cuadrícula para la superficie y seleccionar el modo de sombreado plano.
- Defina el formato de la etiqueta del eje y los rangos de valores. Establezca la rotación automática de la etiqueta para mejorar su legibilidad en ángulos de cámara bajos.
- Asegúrese de que se añade al gráfico la serie correcta y las demás no.
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);
Con Multiseries Height Map, se activan las series del mapa de alturas y se desactivan las demás. El ajuste automático del rango del eje Y funciona bien para la superficie del mapa de alturas, así que asegúrese de que está activado.
m_graph->axisY()->setAutoAdjustRange(true);
Con Textured Topography, se activan las series topográficas y se desactivan las demás. Active un manejador de entrada personalizado para esta serie, para poder resaltar áreas en ella:
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);
Consulte Usar manejador de entrada personalizado para activar el zoom y la panorámica para obtener información sobre el manejador de entrada personalizado para esta serie de datos.
Modos de selección
Los tres modos de selección soportados por Q3DSurfaceWidgetItem pueden utilizarse con botones de radio. Para activar el modo seleccionado o borrarlo, añada los siguientes métodos en línea:
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); }
Para soportar hacer una selección de cortes a todas las series visibles en el gráfico simultáneamente, añada las banderas QtGraphs3D::SelectionFlag::Slice y QtGraphs3D::SelectionFlag::MultiSeries para los modos de selección de filas y columnas.
Rangos de ejes para estudiar el gráfico
El ejemplo tiene cuatro controles deslizantes para ajustar los valores mínimo y máximo para los ejes X y Z. Al seleccionar el proxy, estos controles deslizantes se ajustan para que coincidan con los rangos de los ejes del conjunto de datos actual:
// 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);
Para añadir soporte para ajustar el rango X desde los controles del widget al gráfico, añada:
void SurfaceGraphModifier::setAxisXRange(float min, float max) { m_graph->axisX()->setRange(min, max); }
Añada el soporte para el rango Z de la misma manera.
Gradientes de superficie personalizados
Con el conjunto de datos Sqrt & Sin, se pueden utilizar gradientes de superficie personalizados con dos botones. Defina el gradiente con QLinearGradient, donde se establecen los colores deseados. Además, cambie el estilo de color a Q3DTheme::ColorStyle::RangeGradient para utilizar el gradiente.
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);
Añadir mallas personalizadas a la aplicación
Para añadir mallas personalizadas a la aplicación:
- Para una construcción cmake. Agregue los archivos de malla a
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} ) - Para una compilación qmake. Añade los archivos de malla en el archivo de recursos qrc:
<RCC> <qresource prefix="/"> ... <file>data/refinery.mesh</file> <file>data/oilrig.mesh</file> <file>data/pipe.mesh</file> ... </qresource> </RCC>
Añadir elementos personalizados a un gráfico
Con el conjunto de datos Multiseries Height Map, los elementos personalizados se insertan en el gráfico y pueden activarse o desactivarse mediante casillas de verificación. También pueden controlarse otros cambios visuales con otro conjunto de casillas de verificación, como la transparencia de las dos capas superiores y el resaltado de la capa inferior.
- Creación de un pequeño QImage. Rellénelo con un único color para utilizarlo como color del objeto personalizado:
- Especifique la posición del objeto en una variable. La posición se puede utilizar para eliminar el elemento correcto del gráfico:
QVector3D positionOne = QVector3D(39.f, 77.f, 19.2f);
- Crear un nuevo QCustom3DItem con todos los parámetros:
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);
- Añadir el elemento al gráfico:
m_graph->addCustomItem(item);
Añadir una etiqueta personalizada a un gráfico
Añadir una etiqueta personalizada es muy similar a añadir un elemento personalizado. Para la etiqueta, no se necesita una malla personalizada, sino sólo una instancia de 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);
Eliminar un elemento personalizado de un gráfico
Para eliminar un elemento específico del gráfico, llame a removeCustomItemAt() con la posición del elemento:
m_graph->removeCustomItemAt(positionOne);
Nota: Al eliminar un elemento personalizado del gráfico también se elimina el objeto. Para conservar el elemento, utilice el método releaseCustomItem().
Texturizar una serie de superficie
Con el conjunto de datos Textured Topography, cree una textura de mapa para utilizarla con el mapa de altura topográfica.
Establezca una imagen para ser utilizada como textura en una superficie con QSurface3DSeries::setTextureFile(). Añade una casilla de verificación para controlar si la textura se establece o no, y un controlador para reaccionar al estado de la casilla de verificación:
void SurfaceGraphModifier::toggleSurfaceTexture(bool enable) { if (enable) m_topography->setTextureFile(":/data/maptexture.jpg"); else m_topography->setTextureFile(""); }
La imagen en este ejemplo se lee de un archivo JPG. Configurar un archivo vacío con el método borra la textura, y la superficie utiliza los gradientes o colores del tema.
Utilizar un manejador de entrada personalizado para activar el zoom y la panorámica
Con el conjunto de datos Textured Topography, cree un manejador de entrada personalizado para resaltar la selección en el gráfico y permitir el paneo del gráfico.
La implementación del paneo es similar a la mostrada en Implementación del Arrastre de Ejes. La diferencia es que, en este ejemplo, sólo se siguen los ejes X y Z y no se permite arrastrar la superficie fuera del gráfico. Para limitar el arrastre, siga los límites de los ejes y no haga nada si se sale del gráfico:
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;
Para hacer zoom, coja el wheelEvent y ajuste los rangos de los ejes X e Y de acuerdo con el valor delta en QWheelEvent. Ajusta el eje Y de forma que la relación de aspecto entre el eje Y y el plano XZ permanezca igual. Esto evita obtener un gráfico en el que la altura es exagerada:
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); }
A continuación, añade algunos límites al nivel de zoom, para que no se acerque ni se aleje demasiado de la superficie. Por ejemplo, si el valor del eje X se sitúa por debajo del límite permitido, es decir, el zoom se aleja demasiado, el valor se establece en el valor mínimo permitido. Si el rango se va por debajo del mínimo del rango, ambos extremos del eje se ajustan para que el rango se mantenga en el límite:
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; }
Resaltar un área de la superficie
Para implementar un resalte que se muestre en la superficie, cree una copia de la serie y añada algún desplazamiento al valor y. En este ejemplo, la clase HighlightSeries implementa la creación de la copia en su método handlePositionChange.
Primero, dale a HighlightSeries el puntero a la serie original, y luego empieza a escuchar la señal 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); }
Cuando la señal se dispara, comprueba que la posición es válida. Después, calcula los rangos para el área copiada, y comprueba que se mantienen dentro de los límites. Por último, rellene la matriz de datos de la serie de relieve con el rango de la matriz de datos de la serie de topografía:
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); }
Un degradado para la serie Highlight
Como HighlightSeries es un QSurface3DSeries, todos los métodos de decoración que una serie puede tener están disponibles. En este ejemplo, añade un degradado para resaltar la elevación. Debido a que el estilo de gradiente adecuado depende del rango del eje Y y cambiamos el rango al hacer zoom, las posiciones de color del gradiente deben ajustarse a medida que cambia el rango. Para ello, defina valores proporcionales para las posiciones del color del degradado:
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;
La modificación del gradiente se hace en el método handleGradientChange, así que conéctelo para que reaccione a los cambios en el eje Y:
QObject::connect(m_graph->axisY(), &QValue3DAxis::maxChanged, m_highlight, &HighlightSeries::handleGradientChange);
Cuando ocurra un cambio en el valor máximo del eje Y, calcule las nuevas posiciones de color del gradiente:
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); }
Contenido del ejemplo
© 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.