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 Q3DBars 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 Q3DBars y algunos widgets.
- Utilizar QBar3DSeries y QBarDataProxy para introducir datos en el gráfico
- Ajustar algunas propiedades del gráfico y de las series utilizando controles de widgets
- Seleccionar una fila o una columna haciendo clic en una etiqueta de eje
- Crear un proxy personalizado para utilizarlo con Q3DBars
Para obtener información sobre cómo interactuar con el gráfico, consulte esta página.
Creación de la aplicación
Primero, en bargraph.cpp, instancie Q3DBars:
m_barsGraph = new Q3DBars();
A continuación, cree el widget y los diseños horizontal y vertical.
El gráfico se incrusta en una ventana contenedora utilizando QWidget::createWindowContainer(). Esto es necesario porque todas las clases de gráficos de visualización de datos (Q3DBars, Q3DScatter, Q3DSurface) heredan de QWindow. Esta es la única manera de utilizar una clase que hereda QWindow como widget.
Añade el gráfico y el diseño vertical al horizontal:
m_barsWidget = new QWidget; auto *hLayout = new QHBoxLayout(m_barsWidget); m_container = QWidget::createWindowContainer(m_barsGraph, m_barsWidget); m_barsGraph->resize(minimumGraphSize); m_container->setMinimumSize(minimumGraphSize); m_container->setMaximumSize(maximumGraphSize); m_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_container->setFocusPolicy(Qt::StrongFocus); hLayout->addWidget(m_container, 1); auto *vLayout = new QVBoxLayout(); hLayout->addLayout(vLayout);
A continuación, crea otra clase para manejar la adición de datos y otras interacciones con el gráfico:
auto *modifier = new GraphModifier(m_barsGraph, this);
Configurar el gráfico de barras
Configura la gráfica en el constructor de la clase GraphModifier:
GraphModifier::GraphModifier(Q3DBars *bargraph, QObject *parent) : QObject(parent), m_graph(bargraph),
Primero, crea los ejes y las series en variables miembro para poder cambiarlas fácilmente:
m_temperatureAxis(new QValue3DAxis), m_yearAxis(new QCategory3DAxis), m_monthAxis(new QCategory3DAxis), m_primarySeries(new QBar3DSeries), m_secondarySeries(new QBar3DSeries), m_celsiusString(u"°C"_s)
Luego, establece algunas cualidades visuales para el gráfico:
m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftMedium); m_graph->activeTheme()->setBackgroundEnabled(false); m_graph->activeTheme()->setFont(QFont("Times New Roman", m_fontSize)); m_graph->activeTheme()->setLabelBackgroundEnabled(true); m_graph->setMultiSeriesUniform(true);
Configura los ejes y conviértelos en los ejes activos de la gráfica:
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->setLabelAutoRotation(30.0f); m_temperatureAxis->setTitleVisible(true); m_yearAxis->setTitle("Year"); m_yearAxis->setLabelAutoRotation(30.0f); m_yearAxis->setTitleVisible(true); m_monthAxis->setTitle("Month"); m_monthAxis->setLabelAutoRotation(30.0f); m_monthAxis->setTitleVisible(true); m_graph->setValueAxis(m_temperatureAxis); m_graph->setRowAxis(m_yearAxis); m_graph->setColumnAxis(m_monthAxis);
Dé a las etiquetas de los ejes un pequeño ángulo de autorotación con setLabelAutoRotation() para que se orienten ligeramente hacia la cámara. Esto mejora la legibilidad de las etiquetas de los ejes en ángulos de cámara extremos.
A continuación, 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::MeshBevelBar); m_primarySeries->setMeshSmooth(false); m_secondarySeries->setItemLabelFormat(u"Helsinki - @colLabel @rowLabel: @valueLabel"_s); m_secondarySeries->setMesh(QAbstract3DSeries::MeshBevelBar); 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);
Por último, 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 pasar por varios ángulos de cámara:
changePresetCamera();
La cámara se controla a través del objeto escena del gráfico:
static int preset = Q3DCamera::CameraPresetFront; m_graph->scene()->activeCamera()->setCameraPreset((Q3DCamera::CameraPreset)preset); if (++preset > Q3DCamera::CameraPresetDirectlyBelow) preset = Q3DCamera::CameraPresetFrontLow;
Para más información sobre el uso de escenas y cámaras, ver Q3DScene y Q3DCamera.
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 los 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 ... auto *dataSet = new QBarDataArray; auto *dataSet2 = new QBarDataArray; dataSet->reserve(m_years.size()); for (qsizetype year = 0; year < m_years.size(); ++year) { // Create a data row auto *dataRow = new QBarDataRow(m_months.size()); auto *dataRow2 = new QBarDataRow(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);
Usando Widgets para Controlar la Gráfica
Continúa añadiendo algunos widgets en bargraph.cpp. Añade un deslizador:
auto *rotationSliderX = new QSlider(Qt::Horizontal, m_barsWidget); rotationSliderX->setTickInterval(30); rotationSliderX->setTickPosition(QSlider::TicksBelow); rotationSliderX->setMinimum(-180); rotationSliderX->setValue(0); rotationSliderX->setMaximum(180);
Usa el deslizador para rotar la gráfica en lugar de usar el ratón o el touch. Añádelo al diseño vertical:
vLayout->addWidget(new QLabel(u"Rotate horizontally"_s)); vLayout->addWidget(rotationSliderX, 0, Qt::AlignTop);
Luego, conéctalo a un método en GraphModifier:
Crea una ranura en GraphModifier para la conexión de la señal. La cámara se controla a través del objeto de escena. Esta vez, 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 rotation) { m_xRotation = rotation; m_graph->scene()->activeCamera()->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
SelectionRow - 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 SelectionSlice y SelectionItem, siempre que SelectionRow o SelectionColumn 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:
Q3DCamera *camera = m_graph->scene()->activeCamera(); m_defaultAngleX = camera->xRotation(); m_defaultAngleY = camera->yRotation(); m_defaultZoom = camera->zoomLevel(); m_defaultTarget = camera->target(); m_animationCameraX.setTargetObject(camera); m_animationCameraY.setTargetObject(camera); m_animationCameraZoom.setTargetObject(camera); m_animationCameraTarget.setTargetObject(camera); m_animationCameraX.setPropertyName("xRotation"); m_animationCameraY.setPropertyName("yRotation"); m_animationCameraZoom.setPropertyName("zoomLevel"); m_animationCameraTarget.setPropertyName("target"); 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 Q3DCamera::target, 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;
Proxy personalizado para datos
Al activar el modo de datos Custom Proxy Data, se utiliza un conjunto de datos personalizado y el proxy correspondiente.
Defina un conjunto de datos flexible simple, VariantDataSet, donde cada elemento de datos es una lista de variantes. Cada elemento puede tener varios valores, identificados por su índice en la lista. En este caso, el conjunto de datos almacena datos de precipitaciones mensuales, donde el valor en el índice cero es el año, el valor en el índice uno es el mes y el valor en el índice dos es la cantidad de precipitaciones en ese mes.
El proxy personalizado es similar a los proxies basados en modelos de elementos proporcionados por Qt Data Visualization, y requiere un mapeo para interpretar los datos.
VariantDataSet
Define los elementos de datos como objetos QVariantList. Añade funcionalidad para borrar el conjunto de datos y consultar una referencia a los datos contenidos en el conjunto. Además, añada señales que se emitan cuando se añadan datos o se borre el conjunto:
using VariantDataItem = QVariantList; using VariantDataItemList = QList<VariantDataItem *>; ... void clear(); int addItem(VariantDataItem *item); int addItems(VariantDataItemList *itemList); const VariantDataItemList &itemList() const; Q_SIGNALS: void itemsAdded(int index, int count); void dataCleared();
VariantBarDataProxy
Subclase VariantBarDataProxy de QBarDataProxy y proporcionar una API simple de getters y setters para el conjunto de datos y la asignación:
class VariantBarDataProxy : public QBarDataProxy ... // Doesn't gain ownership of the dataset, but does connect to it to listen for data changes. void setDataSet(VariantDataSet *newSet); VariantDataSet *dataSet(); // Map key (row, column, value) to value index in data item (VariantItem). // Doesn't gain ownership of mapping, but does connect to it to listen for mapping changes. // Modifying mapping that is set to proxy will trigger dataset re-resolving. void setMapping(VariantBarDataMapping *mapping); VariantBarDataMapping *mapping();
El proxy escucha los cambios en el conjunto de datos y la asignación, y resuelve el conjunto de datos si se detecta algún cambio. No se trata de una implementación especialmente eficiente, ya que cualquier cambio provocará una nueva resolución de todo el conjunto de datos, pero eso no es un problema para este ejemplo.
En el método resolveDataSet(), ordena los valores de los datos variantes en filas y columnas basándose en el mapeo. Esto es muy similar a cómo QItemModelBarDataProxy maneja el mapeo, excepto que aquí se utilizan índices de lista en lugar de roles de modelos de elementos. Una vez ordenados los valores, genera QBarDataArray a partir de ellos, y llama al método resetArray() en la clase padre:
void VariantBarDataProxy::resolveDataSet() { // If we have no data or mapping, or the categories are not defined, simply clear the array if (m_dataSet.isNull() || m_mapping.isNull() || !m_mapping->rowCategories().size() || !m_mapping->columnCategories().size()) { resetArray(nullptr); return; } const VariantDataItemList &itemList = m_dataSet->itemList(); int rowIndex = m_mapping->rowIndex(); int columnIndex = m_mapping->columnIndex(); int valueIndex = m_mapping->valueIndex(); const QStringList &rowList = m_mapping->rowCategories(); const QStringList &columnList = m_mapping->columnCategories(); // Sort values into rows and columns using ColumnValueMap = QHash<QString, float>; QHash <QString, ColumnValueMap> itemValueMap; for (const VariantDataItem *item : itemList) { itemValueMap[item->at(rowIndex).toString()][item->at(columnIndex).toString()] = item->at(valueIndex).toReal(); } // Create a new data array in format the parent class understands auto *newProxyArray = new QBarDataArray; for (const QString &rowKey : rowList) { auto *newProxyRow = new QBarDataRow(columnList.size()); for (qsizetype i = 0; i < columnList.size(); ++i) (*newProxyRow)[i].setValue(itemValueMap[rowKey][columnList.at(i)]); newProxyArray->append(newProxyRow); } // Finally, reset the data array in the parent class resetArray(newProxyArray); }
VariantBarDataMapping
Almacena la información de mapeo entre los índices de elementos de datos de VariantDataSet y las filas, columnas y valores de QBarDataArray en VariantBarDataMapping. Contiene las listas de filas y columnas que se incluirán en los datos resueltos:
Q_PROPERTY(int rowIndex READ rowIndex WRITE setRowIndex NOTIFY rowIndexChanged) Q_PROPERTY(int columnIndex READ columnIndex WRITE setColumnIndex NOTIFY columnIndexChanged) Q_PROPERTY(int valueIndex READ valueIndex WRITE setValueIndex NOTIFY valueIndexChanged) Q_PROPERTY(QStringList rowCategories READ rowCategories WRITE setRowCategories NOTIFY rowCategoriesChanged) Q_PROPERTY(QStringList columnCategories READ columnCategories WRITE setColumnCategories NOTIFY columnCategoriesChanged) ... explicit VariantBarDataMapping(int rowIndex, int columnIndex, int valueIndex, const QStringList &rowCategories, const QStringList &columnCategories); ... void remap(int rowIndex, int columnIndex, int valueIndex, const QStringList &rowCategories, const QStringList &columnCategories); ... void mappingChanged();
La forma principal de utilizar un objeto VariantBarDataMapping es dar los mapeos en el constructor, aunque se puede utilizar el método remap() para establecerlos posteriormente, ya sea individualmente o todos juntos. Emite una señal si cambia el mapeado. El resultado es una versión simplificada de la funcionalidad de mapeo de QItemModelBarDataProxy, adaptada para trabajar con listas de variantes en lugar de modelos de elementos.
RainfallData
Maneja la configuración de QBar3DSeries con el proxy personalizado en la clase RainfallData:
m_proxy = new VariantBarDataProxy; m_series = new QBar3DSeries(m_proxy);
Rellenar el conjunto de datos de la variante en el método addDataSet():
void RainfallData::addDataSet() { // Crear un nuevo conjunto de datos de variantes y una nueva lista de elementos de datosm_dataSet = new VariantDataSet; auto *itemList = new VariantDataItemList; // Leer datos de un archivo de datos en la lista de elementos de datos QFile dataFile(":/data/raindata.txt"); if (dataFile.open(QIODevice::Sólo lectura | QIODevice::Texto)) { QTextStream stream(&dataFile); while (!stream.atEnd()) { QString line = stream.readLine(); if (line.startsWith('#')) // 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 cadenas, y el valor de la lluvia como doble // en un elemento de datos variantes y añade el elemento a la lista de elementos. auto *newItem = new VariantDataItem; for(int i = 0; i < 2; ++i) newItem->append(strList.at(i).trimmed().toString()); newItem->append(strList.at(2).trimmed().toDouble()); itemList->append(newItem); } } else { qWarning() << "Unable to open data file:" << dataFile.fileName(); } ...
Añade el conjunto de datos al proxy personalizado y establece la asignación:
// Add items to the data set and set it to the proxy m_dataSet->addItems(itemList); m_proxy->setDataSet(m_dataSet); // Create new mapping for the data and set it to the proxy m_mapping = new VariantBarDataMapping(0, 1, 2, m_years, m_numericMonths); m_proxy->setMapping(m_mapping);
Por último, añade una función para obtener las series creadas para su visualización:
QBar3DSeries *customSeries() { return m_series; }
Gráfico de dispersión
En la pestaña Scatter Graph, cree un gráfico de dispersión 3D utilizando Q3DScatter. El ejemplo muestra cómo hacerlo:
- Configurar el gráfico Q3DScatter
- Utilizar QScatterDataProxy para introducir datos en el gráfico
- Crear un manejador de entrada personalizado extendiendo Q3DInputHandler
Para la creación de aplicaciones básicas, véase Gráfico de barras.
Configuración del gráfico de dispersión
En primer lugar, configure algunas cualidades visuales para el gráfico en el constructor de ScatterDataModifier:
m_graph->activeTheme()->setType(Q3DTheme::ThemeStoneMoss); m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftHigh); m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFront); m_graph->scene()->activeCamera()->setZoomLevel(80.f);
Ninguna de ellas es obligatoria, pero se utilizan para anular los valores predeterminados del gráfico. Puedes probar cómo se ve con los valores predeterminados comentando el bloque anterior.
A continuación, 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
Lo último que hay que hacer en el constructor ScatterDataModifier es añadir datos al gráfico:
addData();
La adición real de datos se realiza en el método addData(). En primer lugar, configurar 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.
A continuación, crea un array de datos y rellénalo:
auto *dataArray = new QScatterDataArray; 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 comience 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
Inicialice m_inputHandler en el constructor con un puntero a la instancia del gráfico de dispersión:
m_inputHandler(new AxesInputHandler(scatter))Reemplazar el mecanismo de manejo de entrada por defecto estableciendo el manejador de entrada activo de Q3DScatter a AxesInputHandler, que implementa el comportamiento personalizado:
// Give ownership of the handler to the graph and make it the active handler m_graph->setActiveInputHandler(m_inputHandler);
El manejador de entrada necesita acceso a los ejes del gráfico, así que pásaselos:
// Give our axes to the input handler m_inputHandler->setAxes(m_graph->axisX(), m_graph->axisZ(), m_graph->axisY());
Extendiendo el Manejo de Eventos del Ratón
Primero, hereda el manejador de entrada personalizado de Q3DInputHandler en lugar de QAbstract3DInputHandler para mantener toda la funcionalidad del manejador de entrada por defecto, y para añadir la funcionalidad personalizada sobre él:
class AxesInputHandler : public Q3DInputHandler
Comienza a extender la funcionalidad por defecto re-implementando algunos de los eventos del ratón. Primero, extiende mousePressEvent. Añádele una bandera m_mousePressed para el botón izquierdo del ratón, y mantén el resto de la funcionalidad por defecto:
void AxesInputHandler::mousePressEvent(QMouseEvent *event, const QPoint &mousePos) { Q3DInputHandler::mousePressEvent(event, mousePos); if (Qt::LeftButton == event->button()) m_mousePressed = true; }
A continuación, modifica mouseReleaseEvent para borrar la bandera, y restablecer el estado interno:
void AxesInputHandler::mouseReleaseEvent(QMouseEvent *event, const QPoint &mousePos) { Q3DInputHandler::mouseReleaseEvent(event, mousePos); m_mousePressed = false; m_state = StateNormal; }
A continuación, modifica mouseMoveEvent. Comprueba si la bandera m_mousePressed es true y el estado interno es algo distinto de StateNormal. Si es así, establece las posiciones de entrada para los cálculos de distancia de movimiento del ratón, y llama a la función de arrastre de ejes (ver Implementación del arrastre de ejes para más detalles):
void AxesInputHandler::mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos) { // Check if we're trying to drag axis label if (m_mousePressed && m_state != StateNormal) { setPreviousInputPos(inputPosition()); setInputPosition(mousePos); handleAxisDragging(); } else { Q3DInputHandler::mouseMoveEvent(event, mousePos); } }
Implementación del arrastre de ejes
Primero, empieza a escuchar la señal de selección del gráfico. Hazlo en el constructor, y conéctalo al método handleElementSelected:
// Connect to the item selection signal from graph connect(graph, &QAbstract3DGraph::selectedElementChanged, this, &AxesInputHandler::handleElementSelected);
En handleElementSelected, comprueba el tipo de selección, y establece el estado interno basado en él:
switch (type) { case QAbstract3DGraph::ElementAxisXLabel: m_state = StateDraggingX; break; case QAbstract3DGraph::ElementAxisYLabel: m_state = StateDraggingY; break; case QAbstract3DGraph::ElementAxisZLabel: m_state = StateDraggingZ; break; default: m_state = StateNormal; break; }
La lógica de arrastre real se implementa en el método handleAxisDragging, que se llama desde mouseMoveEvent, si se cumplen las condiciones requeridas:
// Check if we're trying to drag axis label if (m_mousePressed && m_state != StateNormal) {
En handleAxisDragging, primero se obtiene la orientación de la escena de la cámara activa:
// Get scene orientation from active camera float xRotation = scene()->activeCamera()->xRotation(); float yRotation = scene()->activeCamera()->yRotation();
Luego, calcula los modificadores para la dirección de movimiento del ratón en base a 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));
Después, 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 = inputPosition() - previousInputPos(); // Flip the effect of y movement if we're viewing from below float yMove = (yRotation < 0) ? -move.y() : move.y();
Luego, aplica la distancia movida al eje correcto:
// Adjust axes switch (m_state) { case StateDraggingX: distance = (move.x() * xMulX - yMove * xMulY) / m_speedModifier; m_axisX->setRange(m_axisX->min() - distance, m_axisX->max() - distance); break; case StateDraggingZ: distance = (move.x() * zMulX + yMove * zMulY) / m_speedModifier; m_axisZ->setRange(m_axisZ->min() + distance, m_axisZ->max() + distance); break; case StateDraggingY: distance = move.y() / m_speedModifier; // No need to use adjusted y move here m_axisY->setRange(m_axisY->min() + distance, m_axisY->max() + distance); break; default: break; }
Por último, añade una función para ajustar la velocidad de arrastre:
inline void setDragSpeedModifier(float modifier) { m_speedModifier = modifier; }
Esto es necesario, ya que la distancia de movimiento del ratón es absoluta en coordenadas de pantalla, y necesitas ajustarla al rango del eje. Cuanto mayor sea el valor, más lento será el arrastre. Ten en cuenta que en este ejemplo, el nivel de zoom de la escena no se tiene en cuenta a la hora de determinar la velocidad de arrastre, por lo que notarás cambios en el ajuste del rango a medida que cambies el nivel de zoom.
También podrías ajustar el modificador automáticamente basándote en el rango del eje y el nivel de zoom de la cámara.
Gráfico de superficie
En la pestaña Surface Graph, cree un gráfico de superficie 3D utilizando Q3DSurface. 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);
A continuación, rellene el proxy con una raíz cuadrada simple y datos de onda sinusoidal. Crea una nueva instancia QSurfaceDataArray, y añádele elementos QSurfaceDataRow. Establezca el QSurfaceDataArray creado como la matriz de datos para el QSurfaceDataProxy llamando a resetArray().
auto *dataArray = new QSurfaceDataArray; dataArray->reserve(sampleCountZ); for (int i = 0 ; i < sampleCountZ ; ++i) { auto *newRow = new QSurfaceDataRow; 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 llamado Elevation Model 2 m, que es 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 altura de los datos ASCII originales se codifica en formato RGB utilizando un multiplicador, que verá más adelante en un extracto de código. 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 QSurface3DSeries.
En primer lugar, defina el multiplicador de codificación:
// Value used to encode height data as RGB value on PNG file const float packingFactor = 11983.f;
A continuación, 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); auto *dataArray = new QSurfaceDataArray; dataArray->reserve(imageHeight); for (int i = 0; i < imageHeight; ++i) { int p = i * widthBits; float z = height - float(i) * stepZ; auto *newRow = new QSurfaceDataRow; 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, los datos son utilizables por el proxy.
Selección del conjunto de datos
Para demostrar diferentes proxies, Surface Graph tiene tres botones de radio para cambiar entre las series.
Con Sqrt & Sin, se activa la serie generada simple. En primer lugar, configure las características decorativas, como activar la cuadrícula para la superficie y seleccionar el modo de sombreado plano. A continuación, 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. Por último, asegúrese de que la serie correcta se añade al gráfico y las demás no:
m_sqrtSinSeries->setDrawMode(QSurface3DSeries::DrawSurfaceAndWireframe); m_sqrtSinSeries->setFlatShadingEnabled(true); 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()->setLabelAutoRotation(30.f); m_graph->axisY()->setLabelAutoRotation(90.f); m_graph->axisZ()->setLabelAutoRotation(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->setActiveInputHandler(m_customInputHandler);
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 Q3DSurface 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(QAbstract3DGraph::SelectionNone); } void toggleModeItem() { m_graph->setSelectionMode(QAbstract3DGraph::SelectionItem); } void toggleModeSliceRow() { m_graph->setSelectionMode(QAbstract3DGraph::SelectionItemAndRow | QAbstract3DGraph::SelectionSlice | QAbstract3DGraph::SelectionMultiSeries); } void toggleModeSliceColumn() { m_graph->setSelectionMode(QAbstract3DGraph::SelectionItemAndColumn | QAbstract3DGraph::SelectionSlice | QAbstract3DGraph::SelectionMultiSeries); }
Añada las banderas QAbstract3DGraph::SelectionSlice y QAbstract3DGraph::SelectionMultiSeries para los modos de selección de fila y columna para soportar hacer una selección de corte a todas las series visibles en el gráfico simultáneamente.
Rangos de ejes para estudiar el gráfico
El ejemplo tiene cuatro controles deslizantes para ajustar los valores mínimo y máximo de 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);
Añade soporte para ajustar el rango X desde los controles del widget al gráfico:
void SurfaceGraphModifier::setAxisXRange(float min, float max) { m_graph->axisX()->setRange(min, max); }
Añadir 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::ColorStyleRangeGradient 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(Q3DTheme::ColorStyleRangeGradient);
Añadir mallas personalizadas a la aplicación
Agregue los archivos de malla a CMakeLists.txt para cmake build:
set(graphgallery_resource_files
...
"data/oilrig.obj"
"data/pipe.obj"
"data/refinery.obj"
...
)
qt6_add_resources(graphgallery "graphgallery"
PREFIX
"/"
FILES
${graphgallery_resource_files}
)También, agréguelos en el archivo de recursos qrc para usarlos con qmake:
<RCC>
<qresource prefix="/">
...
<file>data/refinery.obj</file>
<file>data/oilrig.obj</file>
<file>data/pipe.obj</file>
...
</qresource>
</RCC>Adición de 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. Otras cualidades visuales también se pueden controlar con otro conjunto de casillas de verificación, incluyendo ver a través de las dos capas superiores, y un punto culminante para la capa inferior.
Comience por crear un pequeño QImage. Rellénelo con un único color para utilizarlo como color del objeto personalizado:
A continuación, 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);
A continuación, cree un nuevo QCustom3DItem con todos los parámetros:
auto *item = new QCustom3DItem(":/data/oilrig.obj", positionOne, QVector3D(0.025f, 0.025f, 0.025f), QQuaternion::fromAxisAndAngle(0.f, 1.f, 0.f, 45.f), color);
Por último, añada 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. Si desea 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_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 CustomInputHandler::wheelEvent(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_axisX->setRange(m_axisXMinValue, m_axisXMaxValue); m_axisY->setRange(100.f, y); m_axisZ->setRange(m_axisZMinValue, m_axisZMaxValue); }
A continuación, añada 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->dataProxy()->array()->at(0)->size(); m_srcHeight = m_topographicSeries->dataProxy()->array()->size(); QObject::connect(m_topographicSeries, &QSurface3DSeries::selectedPointChanged, this, &HighlightSeries::handlePositionChange); }
Cuando la señal se dispara, comprueba que la posición es válida. A continuación, 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.y() - halfWidth; if (startX < 0 ) startX = 0; int endX = position.y() + halfWidth; if (endX > (m_srcWidth - 1)) endX = m_srcWidth - 1; int startZ = position.x() - halfHeight; if (startZ < 0 ) startZ = 0; int endZ = position.x() + halfHeight; if (endZ > (m_srcHeight - 1)) endZ = m_srcHeight - 1; QSurfaceDataProxy *srcProxy = m_topographicSeries->dataProxy(); const QSurfaceDataArray &srcArray = *srcProxy->array(); auto *dataArray = new QSurfaceDataArray; dataArray->reserve(endZ - startZ); for (int i = startZ; i < endZ; ++i) { auto *newRow = new QSurfaceDataRow; 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() + 0.1f); newRow->append(QSurfaceDataItem(pos)); } dataArray->append(newRow); } dataProxy()->resetArray(dataArray); setVisible(true); }
Un degradado para la serie Highlight
Como HighlightSeries es 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(Q3DTheme::ColorStyleRangeGradient); }
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.