Source code
Revision control
Copy as Markdown
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qabstract3daxis_p.h"
#include "qsurface3dseries_p.h"
#include "qsurfacedataproxy_p.h"
#include "qgraphs3dlogging_p.h"
QT_BEGIN_NAMESPACE
/*!
* \class QSurfaceDataProxy
* \inmodule QtGraphs
* \ingroup graphs_3D
* \brief The QSurfaceDataProxy class is the data proxy for a 3D surface graph.
*
* A surface data proxy handles surface related data in rows. For this it
* provides two auxiliary type aliases: QtGraphs::QSurfaceDataArray and
* QtGraphs::QSurfaceDataRow. \c QSurfaceDataArray is a QList that
* controls the rows. \c QSurfaceDataRow is a QList that contains
* QSurfaceDataItem objects. For more information about how to feed the data to
* the proxy, see the sample code in the Q3DSurfaceWidgetItem documentation. Since data is
* stored in series, it is necessary to create a series associated with the
* proxy before using these functions for the dataset.
*
* All rows must have the same number of items.
*
* QSurfaceDataProxy takes ownership of all \c QSurfaceDataRow objects passed to
* it, whether directly or in a \c QSurfaceDataArray container.
* To use surface data row pointers to directly modify data after adding the
* array to the proxy, the appropriate signal must be emitted to update the
* graph.
*
* To make a sensible surface, the x-value of each successive item in all rows
* must be either ascending or descending throughout the row. Similarly, the
* z-value of each successive item in all columns must be either ascending or
* descending throughout the column.
*
* \note Currently only surfaces with straight rows and columns are fully
* supported. Any row with items that do not have the exact same z-value or any
* column with items that do not have the exact same x-value may get clipped
* incorrectly if the whole surface does not completely fit within the visible
* x-axis or z-axis ranges.
*
* \note Surfaces with less than two rows or columns are rendered as lines.
*
* \note On some environments, surfaces with a lot of visible vertices may not
* render, because they exceed the per-draw vertex count supported by the
* graphics driver. This is mostly an issue on 32-bit and OpenGL ES2 platforms.
*
* \sa {Qt Graphs Data Handling with 3D}
*/
/*!
* \typealias QSurfaceDataRow
* \relates QSurfaceDataProxy
*
* A list of \l {QSurfaceDataItem} objects.
*/
/*!
* \typealias QSurfaceDataArray
* \relates QSurfaceDataProxy
*
* A list of pointers to \l {QSurfaceDataRow} objects.
*/
/*!
* \qmltype SurfaceDataProxy
* \inqmlmodule QtGraphs
* \ingroup graphs_qml_3D
* \nativetype QSurfaceDataProxy
* \inherits AbstractDataProxy
* \brief The data proxy for a 3D surface graph.
*
* This type handles surface data items. The data is arranged into rows and
* columns, and all rows must have the same number of columns.
*
* This type is uncreatable, but contains properties that are exposed via
* subtypes.
*
* For a more complete description, see QSurfaceDataProxy.
*
* \sa ItemModelSurfaceDataProxy, {Qt Graphs Data Handling with 3D}
*/
/*!
* \qmlproperty int SurfaceDataProxy::rowCount
* The number of rows in the data array.
*/
/*!
* \qmlproperty int SurfaceDataProxy::columnCount
* The number of columns in the data array.
*/
/*!
* \qmlproperty Surface3DSeries SurfaceDataProxy::series
*
* The series this proxy is attached to.
*/
/*!
\qmlsignal SurfaceDataProxy::rowCountChanged(int count)
This signal is emitted when rowCount changes to \a count.
*/
/*!
\qmlsignal SurfaceDataProxy::columnCountChanged(int count)
This signal is emitted when columnCount changes to \a count.
*/
/*!
\qmlsignal SurfaceDataProxy::seriesChanged(Surface3DSeries series)
This signal is emitted when \l series changes to \a series.
*/
/*!
* Constructs QSurfaceDataProxy with the given \a parent.
*/
QSurfaceDataProxy::QSurfaceDataProxy(QObject *parent)
: QAbstractDataProxy(*(new QSurfaceDataProxyPrivate()), parent)
{}
/*!
* \internal
*/
QSurfaceDataProxy::QSurfaceDataProxy(QSurfaceDataProxyPrivate &d, QObject *parent)
: QAbstractDataProxy(d, parent)
{}
/*!
* Deletes the surface data proxy.
*/
QSurfaceDataProxy::~QSurfaceDataProxy() {}
/*!
* \property QSurfaceDataProxy::series
*
* \brief The series this proxy is attached to.
*/
QSurface3DSeries *QSurfaceDataProxy::series() const
{
Q_D(const QSurfaceDataProxy);
if (!d->series())
qCWarning(lcProperties3D, "%s series needs to be created to access data members",
qUtf8Printable(QLatin1String(__FUNCTION__)));
return static_cast<QSurface3DSeries *>(d->series());
}
/*!
* Clears the existing array and triggers the arrayReset() signal.
*/
void QSurfaceDataProxy::resetArray()
{
Q_D(QSurfaceDataProxy);
d->resetArray(QSurfaceDataArray());
emit arrayReset();
emit rowCountChanged(rowCount());
emit columnCountChanged(columnCount());
}
/*!
* Sets the array from \a newArray. If the new array is equal to the
* existing one, this function simply triggers the arrayReset() signal.
*/
void QSurfaceDataProxy::resetArray(QSurfaceDataArray newArray)
{
Q_D(QSurfaceDataProxy);
if (!series())
return;
if (series()->dataArray().data() != newArray.data())
d->resetArray(std::move(newArray));
emit arrayReset();
emit rowCountChanged(rowCount());
emit columnCountChanged(columnCount());
}
/*!
* Changes an existing row by replacing the row at the position \a rowIndex
* with the new row specified by \a row. The new row can be the same as the
* existing row already stored at the \a rowIndex. The new row must have
* the same number of columns as the row it is replacing.
*/
void QSurfaceDataProxy::setRow(qsizetype rowIndex, QSurfaceDataRow row)
{
Q_D(QSurfaceDataProxy);
d->setRow(rowIndex, std::move(row));
emit rowsChanged(rowIndex, 1);
}
/*!
* Changes existing rows by replacing the rows starting at the position
* \a rowIndex with the new rows specifies by \a rows.
* The rows in the \a rows array can be the same as the existing rows already
* stored at the \a rowIndex. The new rows must have the same number of columns
* as the rows they are replacing.
*/
void QSurfaceDataProxy::setRows(qsizetype rowIndex, QSurfaceDataArray rows)
{
Q_D(QSurfaceDataProxy);
d->setRows(rowIndex, std::move(rows));
emit rowsChanged(rowIndex, rows.size());
}
/*!
* Changes a single item at the position specified by \a rowIndex and
* \a columnIndex to the item \a item.
*/
void QSurfaceDataProxy::setItem(qsizetype rowIndex, qsizetype columnIndex, QSurfaceDataItem item)
{
Q_D(QSurfaceDataProxy);
d->setItem(rowIndex, columnIndex, std::move(item));
emit itemChanged(rowIndex, columnIndex);
}
/*!
* Changes a single item at the position \a position to the item \a item.
* The x-value of \a position indicates the row and the y-value indicates the
* column.
*/
void QSurfaceDataProxy::setItem(QPoint position, QSurfaceDataItem item)
{
setItem(position.x(), position.y(), item);
}
/*!
* Adds the new row \a row to the end of an array. The new row must have
* the same number of columns as the rows in the initial array.
*
* Returns the index of the added row.
*/
qsizetype QSurfaceDataProxy::addRow(QSurfaceDataRow row)
{
Q_D(QSurfaceDataProxy);
qsizetype addIndex = d->addRow(std::move(row));
emit rowsAdded(addIndex, 1);
emit rowCountChanged(rowCount());
return addIndex;
}
/*!
* Adds new \a rows to the end of an array. The new rows must have the same
* number of columns as the rows in the initial array.
*
* Returns the index of the first added row.
*/
qsizetype QSurfaceDataProxy::addRows(QSurfaceDataArray rows)
{
Q_D(QSurfaceDataProxy);
qsizetype addIndex = d->addRows(std::move(rows));
emit rowsAdded(addIndex, rows.size());
emit rowCountChanged(rowCount());
return addIndex;
}
/*!
* Inserts the new row \a row into \a rowIndex.
* If \a rowIndex is equal to the array size, the rows are added to the end of
* the array. The new row must have the same number of columns as the rows in
* the initial array.
*/
void QSurfaceDataProxy::insertRow(qsizetype rowIndex, QSurfaceDataRow row)
{
Q_D(QSurfaceDataProxy);
d->insertRow(rowIndex, std::move(row));
emit rowsInserted(rowIndex, 1);
emit rowCountChanged(rowCount());
}
/*!
* Inserts new \a rows into \a rowIndex.
* If \a rowIndex is equal to the array size, the rows are added to the end of
* the array. The new \a rows must have the same number of columns as the rows
* in the initial array.
*/
void QSurfaceDataProxy::insertRows(qsizetype rowIndex, QSurfaceDataArray rows)
{
Q_D(QSurfaceDataProxy);
d->insertRows(rowIndex, std::move(rows));
emit rowsInserted(rowIndex, rows.size());
emit rowCountChanged(rowCount());
}
/*!
* Removes the number of rows specified by \a removeCount starting at the
* position \a rowIndex. Attempting to remove rows past the end of the
* array does nothing.
*/
void QSurfaceDataProxy::removeRows(qsizetype rowIndex, qsizetype removeCount)
{
Q_D(QSurfaceDataProxy);
if (rowIndex < rowCount() && removeCount >= 1) {
d->removeRows(rowIndex, removeCount);
emit rowsRemoved(rowIndex, removeCount);
emit rowCountChanged(rowCount());
}
}
/*!
* Returns the pointer to the item at the position specified by \a rowIndex and
* \a columnIndex. It is guaranteed to be valid only
* until the next call that modifies data.
*/
const QSurfaceDataItem &QSurfaceDataProxy::itemAt(qsizetype rowIndex, qsizetype columnIndex) const
{
const QSurfaceDataArray &dataArray = series()->dataArray();
Q_ASSERT(rowIndex >= 0 && rowIndex < dataArray.size());
const QSurfaceDataRow &dataRow = dataArray[rowIndex];
Q_ASSERT(columnIndex >= 0 && columnIndex < dataRow.size());
return dataRow.at(columnIndex);
}
/*!
* Returns the pointer to the item at the position \a position. The x-value of
* \a position indicates the row and the y-value indicates the column. The item
* is guaranteed to be valid only until the next call that modifies data.
*/
const QSurfaceDataItem &QSurfaceDataProxy::itemAt(QPoint position) const
{
return itemAt(position.x(), position.y());
}
/*!
* \property QSurfaceDataProxy::rowCount
*
* \brief The number of rows in the data array.
*/
qsizetype QSurfaceDataProxy::rowCount() const
{
if (series())
return series()->dataArray().size();
else
return 0;
}
/*!
* \property QSurfaceDataProxy::columnCount
*
* \brief The number of columns in the data array.
*/
qsizetype QSurfaceDataProxy::columnCount() const
{
if (series() && series()->dataArray().size() > 0)
return series()->dataArray().at(0).size();
else
return 0;
}
/*!
* \fn void QSurfaceDataProxy::arrayReset()
*
* This signal is emitted when the data array is reset.
* If the contents of the whole array are changed without calling resetArray(),
* this signal needs to be emitted to update the graph.
*/
/*!
* \fn void QSurfaceDataProxy::rowsAdded(qsizetype startIndex, qsizetype count)
*
* This signal is emitted when the number of rows specified by \a count is
* added, starting at the position \a startIndex.
* If rows are added to the array without calling addRow() or addRows(),
* this signal needs to be emitted to update the graph.
*/
/*!
* \fn void QSurfaceDataProxy::rowsChanged(qsizetype startIndex, qsizetype count)
*
* This signal is emitted when the number of rows specified by \a count is
* changed, starting at the position \a startIndex.
* If rows are changed in the array without calling setRow() or setRows(),
* this signal needs to be emitted to update the graph.
*/
/*!
* \fn void QSurfaceDataProxy::rowsRemoved(qsizetype startIndex, qsizetype count)
*
* This signal is emitted when the number of rows specified by \a count is
* removed, starting at the position \a startIndex.
*
* The index is the current array size if the rows were removed from the end of
* the array. If rows are removed from the array without calling removeRows(),
* this signal needs to be emitted to update the graph.
*/
/*!
* \fn void QSurfaceDataProxy::rowsInserted(qsizetype startIndex, qsizetype count)
*
* This signal is emitted when the number of rows specified by \a count is
* inserted at the position \a startIndex.
*
* If rows are inserted into the array without calling insertRow() or
* insertRows(), this signal needs to be emitted to update the graph.
*/
/*!
* \fn void QSurfaceDataProxy::itemChanged(qsizetype rowIndex, qsizetype columnIndex)
*
* This signal is emitted when the item at the position specified by \a rowIndex
* and \a columnIndex changes.
* If the item is changed in the array without calling setItem(),
* this signal needs to be emitted to update the graph.
*/
// QSurfaceDataProxyPrivate
QSurfaceDataProxyPrivate::QSurfaceDataProxyPrivate()
: QAbstractDataProxyPrivate(QAbstractDataProxy::DataType::Surface)
{}
QSurfaceDataProxyPrivate::~QSurfaceDataProxyPrivate() {}
void QSurfaceDataProxyPrivate::resetArray(QSurfaceDataArray &&newArray)
{
auto *surfaceSeries = static_cast<QSurface3DSeries *>(series());
if (newArray.data() != surfaceSeries->dataArray().data()) {
surfaceSeries->clearArray();
surfaceSeries->setDataArray(newArray);
}
}
void QSurfaceDataProxyPrivate::setRow(qsizetype rowIndex, QSurfaceDataRow &&row)
{
auto *surfaceSeries = static_cast<QSurface3DSeries *>(series());
Q_ASSERT(rowIndex >= 0 && rowIndex < surfaceSeries->dataArray().size());
Q_ASSERT(surfaceSeries->dataArray().at(rowIndex).size() == row.size());
if (row.data() != surfaceSeries->dataArray().at(rowIndex).data()) {
surfaceSeries->clearRow(rowIndex);
QSurfaceDataArray array = surfaceSeries->dataArray();
array[rowIndex] = row;
surfaceSeries->setDataArray(array);
}
}
void QSurfaceDataProxyPrivate::setRows(qsizetype rowIndex, QSurfaceDataArray &&rows)
{
auto *surfaceSeries = static_cast<QSurface3DSeries *>(series());
QSurfaceDataArray array = surfaceSeries->dataArray();
Q_ASSERT(rowIndex >= 0 && (rowIndex + rows.size()) <= array.size());
for (int i = 0; i < rows.size(); i++) {
Q_ASSERT(surfaceSeries->dataArray().at(rowIndex).size() == rows.at(i).size());
if (rows.at(i).data() != array.at(rowIndex).data()) {
surfaceSeries->clearRow(rowIndex);
array[rowIndex] = rows.at(i);
}
rowIndex++;
}
surfaceSeries->setDataArray(array);
}
void QSurfaceDataProxyPrivate::setItem(qsizetype rowIndex, qsizetype columnIndex, QSurfaceDataItem &&item)
{
auto *surfaceSeries = static_cast<QSurface3DSeries *>(series());
Q_ASSERT(rowIndex >= 0 && rowIndex < surfaceSeries->dataArray().size());
QSurfaceDataArray array = surfaceSeries->dataArray();
QSurfaceDataRow &row = array[rowIndex];
Q_ASSERT(columnIndex < row.size());
row[columnIndex] = item;
surfaceSeries->setDataArray(array);
}
qsizetype QSurfaceDataProxyPrivate::addRow(QSurfaceDataRow &&row)
{
auto *surfaceSeries = static_cast<QSurface3DSeries *>(series());
Q_ASSERT(surfaceSeries->dataArray().isEmpty()
|| surfaceSeries->dataArray().at(0).size() == row.size());
qsizetype currentSize = surfaceSeries->dataArray().size();
QSurfaceDataArray array = surfaceSeries->dataArray();
array.append(row);
surfaceSeries->setDataArray(array);
return currentSize;
}
qsizetype QSurfaceDataProxyPrivate::addRows(QSurfaceDataArray &&rows)
{
auto *surfaceSeries = static_cast<QSurface3DSeries *>(series());
qsizetype currentSize = surfaceSeries->dataArray().size();
QSurfaceDataArray array = surfaceSeries->dataArray();
for (int i = 0; i < rows.size(); i++) {
Q_ASSERT(surfaceSeries->dataArray().isEmpty()
|| surfaceSeries->dataArray().at(0).size() == rows.at(i).size());
array.append(rows.at(i));
}
surfaceSeries->setDataArray(array);
return currentSize;
}
void QSurfaceDataProxyPrivate::insertRow(qsizetype rowIndex, QSurfaceDataRow &&row)
{
auto *surfaceSeries = static_cast<QSurface3DSeries *>(series());
Q_ASSERT(rowIndex >= 0 && rowIndex <= surfaceSeries->dataArray().size());
Q_ASSERT(surfaceSeries->dataArray().isEmpty()
|| surfaceSeries->dataArray().at(0).size() == row.size());
QSurfaceDataArray array = surfaceSeries->dataArray();
array.insert(rowIndex, row);
surfaceSeries->setDataArray(array);
}
void QSurfaceDataProxyPrivate::insertRows(qsizetype rowIndex, QSurfaceDataArray &&rows)
{
auto *surfaceSeries = static_cast<QSurface3DSeries *>(series());
Q_ASSERT(rowIndex >= 0 && rowIndex <= surfaceSeries->dataArray().size());
QSurfaceDataArray array = surfaceSeries->dataArray();
for (int i = 0; i < rows.size(); i++) {
Q_ASSERT(surfaceSeries->dataArray().isEmpty()
|| surfaceSeries->dataArray().at(0).size() == rows.at(i).size());
array.insert(rowIndex++, rows.at(i));
}
surfaceSeries->setDataArray(array);
}
void QSurfaceDataProxyPrivate::removeRows(qsizetype rowIndex, qsizetype removeCount)
{
auto *surfaceSeries = static_cast<QSurface3DSeries *>(series());
Q_ASSERT(rowIndex >= 0);
qsizetype maxRemoveCount = surfaceSeries->dataArray().size() - rowIndex;
removeCount = qMin(removeCount, maxRemoveCount);
QSurfaceDataArray array = surfaceSeries->dataArray();
for (int i = 0; i < removeCount; i++) {
surfaceSeries->clearRow(rowIndex);
array.removeAt(rowIndex);
}
surfaceSeries->setDataArray(array);
}
void QSurfaceDataProxyPrivate::limitValues(QVector3D &minValues,
QVector3D &maxValues,
QAbstract3DAxis *axisX,
QAbstract3DAxis *axisY,
QAbstract3DAxis *axisZ) const
{
float min = 0.0f;
float max = 0.0f;
auto *surfaceSeries = static_cast<QSurface3DSeries *>(series());
qsizetype rows = surfaceSeries->dataArray().size();
qsizetype columns = 0;
if (rows)
columns = surfaceSeries->dataArray().at(0).size();
if (rows && columns) {
min = surfaceSeries->dataArray().at(0).at(0).y();
max = surfaceSeries->dataArray().at(0).at(0).y();
}
for (int i = 0; i < rows; i++) {
const QSurfaceDataRow &row = surfaceSeries->dataArray().at(i);
if (!row.isEmpty()) {
for (int j = 0; j < columns; j++) {
float itemValue = surfaceSeries->dataArray().at(i).at(j).y();
if (qIsNaN(itemValue) || qIsInf(itemValue))
continue;
if ((min > itemValue || (qIsNaN(min) || qIsInf(min)))
&& isValidValue(itemValue, axisY)) {
min = itemValue;
}
if (max < itemValue || (qIsNaN(max) || qIsInf(max)))
max = itemValue;
}
}
}
minValues.setY(min);
maxValues.setY(max);
if (columns) {
// Have some defaults
float xLow = surfaceSeries->dataArray().at(0).at(0).x();
float xHigh = surfaceSeries->dataArray().at(0).last().x();
float zLow = surfaceSeries->dataArray().at(0).at(0).z();
float zHigh = surfaceSeries->dataArray().last().at(0).z();
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
float zItemValue = surfaceSeries->dataArray().at(i).at(j).z();
if (qIsNaN(zItemValue) || qIsInf(zItemValue))
continue;
else if (isValidValue(zItemValue, axisZ))
zLow = qMin(zLow, zItemValue);
}
if (!qIsNaN(zLow) && !qIsInf(zLow))
break;
}
for (qsizetype i = rows - 1; i >= 0; i--) {
for (qsizetype j = 0; j < columns; j++) {
float zItemValue = surfaceSeries->dataArray().at(i).at(j).z();
if (qIsNaN(zItemValue) || qIsInf(zItemValue))
continue;
else if (isValidValue(zItemValue, axisZ)) {
if (!qIsNaN(zHigh) && !qIsInf(zHigh))
zHigh = qMax(zHigh, zItemValue);
else
zHigh = zItemValue;
}
}
if (!qIsNaN(zHigh) && !qIsInf(zHigh))
break;
}
for (qsizetype j = 0; j < columns; j++) {
for (qsizetype i = 0; i < rows; i++) {
float xItemValue = surfaceSeries->dataArray().at(i).at(j).x();
if (qIsNaN(xItemValue) || qIsInf(xItemValue))
continue;
else if (isValidValue(xItemValue, axisX))
xLow = qMin(xLow, xItemValue);
}
if (!qIsNaN(xLow) && !qIsInf(xLow))
break;
}
for (qsizetype j = columns - 1; j >= 0; j--) {
for (qsizetype i = 0; i < rows; i++) {
float xItemValue = surfaceSeries->dataArray().at(i).at(j).x();
if (qIsNaN(xItemValue) || qIsInf(xItemValue)) {
continue;
} else if (isValidValue(xItemValue, axisX)) {
if (!qIsNaN(xHigh) && !qIsInf(xHigh))
xHigh = qMax(xHigh, xItemValue);
else
xHigh = xItemValue;
}
}
if (!qIsNaN(xHigh) && !qIsInf(xHigh))
break;
}
minValues.setX(xLow);
minValues.setZ(zLow);
maxValues.setX(xHigh);
maxValues.setZ(zHigh);
} else {
minValues.setX(axisX->d_func()->allowZero() ? 0.0f : 1.0f);
minValues.setZ(axisZ->d_func()->allowZero() ? 0.0f : 1.0f);
maxValues.setX(axisX->d_func()->allowZero() ? 0.0f : 1.0f);
maxValues.setZ(axisZ->d_func()->allowZero() ? 0.0f : 1.0f);
}
}
bool QSurfaceDataProxyPrivate::isValidValue(float value, QAbstract3DAxis *axis) const
{
return (value > 0.0f || (value == 0.0f && axis->d_func()->allowZero())
|| (value < 0.0f && axis->d_func()->allowNegatives()));
}
void QSurfaceDataProxyPrivate::setSeries(QAbstract3DSeries *series)
{
Q_Q(QSurfaceDataProxy);
QAbstractDataProxyPrivate::setSeries(series);
QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(series);
emit q->seriesChanged(surfaceSeries);
}
QT_END_NAMESPACE
#include "moc_qsurfacedataproxy.cpp"