Revision control

++ // Copyright (C) 2017 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #include "qsurfacedataproxy_p.h"
++ #include "qsurface3dseries_p.h"
++ #include "qabstract3daxis_p.h"
++
++ QT_BEGIN_NAMESPACE
++
++ /*!
++ * \class QSurfaceDataProxy
++ * \inmodule QtDataVisualization
++ * \brief The QSurfaceDataProxy class is the data proxy for a 3D surface graph.
++ * \since QtDataVisualization 1.0
++ *
++ * A surface data proxy handles surface related data in rows. For this it
++ * provides two auxiliary typedefs: QtDataVisualization::QSurfaceDataArray and
++ * QtDataVisualization::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 Q3DSurface documentation.
++ *
++ * 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 not considered valid surfaces and will
++ * not be rendered.
++ *
++ * \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 Data Visualization Data Handling}
++ */
++
++ /*!
++ * \typedef QSurfaceDataRow
++ * \relates QSurfaceDataProxy
++ *
++ * A list of \l {QSurfaceDataItem} objects.
++ */
++
++ /*!
++ * \typedef QSurfaceDataArray
++ * \relates QSurfaceDataProxy
++ *
++ * A list of pointers to \l {QSurfaceDataRow} objects.
++ */
++
++ /*!
++ * \qmltype SurfaceDataProxy
++ * \inqmlmodule QtDataVisualization
++ * \since QtDataVisualization 1.0
++ * \ingroup datavisualization_qml
++ * \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 Data Visualization Data Handling}
++ */
++
++ /*!
++ * \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.
++ */
++
++ /*!
++ * Constructs QSurfaceDataProxy with the given \a parent.
++ */
++ QSurfaceDataProxy::QSurfaceDataProxy(QObject *parent) :
++ QAbstractDataProxy(new QSurfaceDataProxyPrivate(this), 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
++ {
++ return static_cast<QSurface3DSeries *>(d_ptr->series());
++ }
++
++ /*!
++ * Takes ownership of the array \a newArray. Clears the existing array if the
++ * new array differs from it. If the arrays are the same, this function
++ * just triggers the arrayReset() signal.
++ *
++ * Passing a null array deletes the old array and creates a new empty array.
++ * All rows in \a newArray must be of same length.
++ */
++ void QSurfaceDataProxy::resetArray(QSurfaceDataArray *newArray)
++ {
++ if (dptr()->m_dataArray != newArray) {
++ dptr()->resetArray(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(int rowIndex, QSurfaceDataRow *row)
++ {
++ dptr()->setRow(rowIndex, 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(int rowIndex, const QSurfaceDataArray &rows)
++ {
++ dptr()->setRows(rowIndex, 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(int rowIndex, int columnIndex, const QSurfaceDataItem &item)
++ {
++ dptr()->setItem(rowIndex, columnIndex, 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(const QPoint &position, const 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.
++ */
++ int QSurfaceDataProxy::addRow(QSurfaceDataRow *row)
++ {
++ int addIndex = dptr()->addRow(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.
++ */
++ int QSurfaceDataProxy::addRows(const QSurfaceDataArray &rows)
++ {
++ int addIndex = dptr()->addRows(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(int rowIndex, QSurfaceDataRow *row)
++ {
++ dptr()->insertRow(rowIndex, 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(int rowIndex, const QSurfaceDataArray &rows)
++ {
++ dptr()->insertRows(rowIndex, 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(int rowIndex, int removeCount)
++ {
++ if (rowIndex < rowCount() && removeCount >= 1) {
++ dptr()->removeRows(rowIndex, removeCount);
++ emit rowsRemoved(rowIndex, removeCount);
++ emit rowCountChanged(rowCount());
++ }
++ }
++
++ /*!
++ * Returns the pointer to the data array.
++ */
++ const QSurfaceDataArray *QSurfaceDataProxy::array() const
++ {
++ return dptrc()->m_dataArray;
++ }
++
++ /*!
++ * 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(int rowIndex, int columnIndex) const
++ {
++ const QSurfaceDataArray &dataArray = *dptrc()->m_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(const QPoint &position) const
++ {
++ return itemAt(position.x(), position.y());
++ }
++
++ /*!
++ * \property QSurfaceDataProxy::rowCount
++ *
++ * \brief The number of rows in the data array.
++ */
++ int QSurfaceDataProxy::rowCount() const
++ {
++ return dptrc()->m_dataArray->size();
++ }
++
++ /*!
++ * \property QSurfaceDataProxy::columnCount
++ *
++ * \brief The number of columns in the data array.
++ */
++ int QSurfaceDataProxy::columnCount() const
++ {
++ if (dptrc()->m_dataArray->size() > 0)
++ return dptrc()->m_dataArray->at(0)->size();
++ else
++ return 0;
++ }
++
++ /*!
++ * \internal
++ */
++ QSurfaceDataProxyPrivate *QSurfaceDataProxy::dptr()
++ {
++ return static_cast<QSurfaceDataProxyPrivate *>(d_ptr.data());
++ }
++
++ /*!
++ * \internal
++ */
++ const QSurfaceDataProxyPrivate *QSurfaceDataProxy::dptrc() const
++ {
++ return static_cast<const QSurfaceDataProxyPrivate *>(d_ptr.data());
++ }
++
++ /*!
++ * \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(int startIndex, int 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(int startIndex, int 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(int startIndex, int 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(int startIndex, int 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(int rowIndex, int 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(QSurfaceDataProxy *q)
++ : QAbstractDataProxyPrivate(q, QAbstractDataProxy::DataTypeSurface),
++ m_dataArray(new QSurfaceDataArray)
++ {
++ }
++
++ QSurfaceDataProxyPrivate::~QSurfaceDataProxyPrivate()
++ {
++ clearArray();
++ }
++
++ void QSurfaceDataProxyPrivate::resetArray(QSurfaceDataArray *newArray)
++ {
++ if (!newArray)
++ newArray = new QSurfaceDataArray;
++
++ if (newArray != m_dataArray) {
++ clearArray();
++ m_dataArray = newArray;
++ }
++ }
++
++ void QSurfaceDataProxyPrivate::setRow(int rowIndex, QSurfaceDataRow *row)
++ {
++ Q_ASSERT(rowIndex >= 0 && rowIndex < m_dataArray->size());
++ Q_ASSERT(m_dataArray->at(rowIndex)->size() == row->size());
++
++ if (row != m_dataArray->at(rowIndex)) {
++ clearRow(rowIndex);
++ (*m_dataArray)[rowIndex] = row;
++ }
++ }
++
++ void QSurfaceDataProxyPrivate::setRows(int rowIndex, const QSurfaceDataArray &rows)
++ {
++ QSurfaceDataArray &dataArray = *m_dataArray;
++ Q_ASSERT(rowIndex >= 0 && (rowIndex + rows.size()) <= dataArray.size());
++
++ for (int i = 0; i < rows.size(); i++) {
++ Q_ASSERT(m_dataArray->at(rowIndex)->size() == rows.at(i)->size());
++ if (rows.at(i) != dataArray.at(rowIndex)) {
++ clearRow(rowIndex);
++ dataArray[rowIndex] = rows.at(i);
++ }
++ rowIndex++;
++ }
++ }
++
++ void QSurfaceDataProxyPrivate::setItem(int rowIndex, int columnIndex, const QSurfaceDataItem &item)
++ {
++ Q_ASSERT(rowIndex >= 0 && rowIndex < m_dataArray->size());
++ QSurfaceDataRow &row = *(*m_dataArray)[rowIndex];
++ Q_ASSERT(columnIndex < row.size());
++ row[columnIndex] = item;
++ }
++
++ int QSurfaceDataProxyPrivate::addRow(QSurfaceDataRow *row)
++ {
++ Q_ASSERT(m_dataArray->isEmpty()
++ || m_dataArray->at(0)->size() == row->size());
++ int currentSize = m_dataArray->size();
++ m_dataArray->append(row);
++ return currentSize;
++ }
++
++ int QSurfaceDataProxyPrivate::addRows(const QSurfaceDataArray &rows)
++ {
++ int currentSize = m_dataArray->size();
++ for (int i = 0; i < rows.size(); i++) {
++ Q_ASSERT(m_dataArray->isEmpty()
++ || m_dataArray->at(0)->size() == rows.at(i)->size());
++ m_dataArray->append(rows.at(i));
++ }
++ return currentSize;
++ }
++
++ void QSurfaceDataProxyPrivate::insertRow(int rowIndex, QSurfaceDataRow *row)
++ {
++ Q_ASSERT(rowIndex >= 0 && rowIndex <= m_dataArray->size());
++ Q_ASSERT(m_dataArray->isEmpty()
++ || m_dataArray->at(0)->size() == row->size());
++ m_dataArray->insert(rowIndex, row);
++ }
++
++ void QSurfaceDataProxyPrivate::insertRows(int rowIndex, const QSurfaceDataArray &rows)
++ {
++ Q_ASSERT(rowIndex >= 0 && rowIndex <= m_dataArray->size());
++
++ for (int i = 0; i < rows.size(); i++) {
++ Q_ASSERT(m_dataArray->isEmpty()
++ || m_dataArray->at(0)->size() == rows.at(i)->size());
++ m_dataArray->insert(rowIndex++, rows.at(i));
++ }
++ }
++
++ void QSurfaceDataProxyPrivate::removeRows(int rowIndex, int removeCount)
++ {
++ Q_ASSERT(rowIndex >= 0);
++ int maxRemoveCount = m_dataArray->size() - rowIndex;
++ removeCount = qMin(removeCount, maxRemoveCount);
++ for (int i = 0; i < removeCount; i++) {
++ clearRow(rowIndex);
++ m_dataArray->removeAt(rowIndex);
++ }
++ }
++
++ QSurfaceDataProxy *QSurfaceDataProxyPrivate::qptr()
++ {
++ return static_cast<QSurfaceDataProxy *>(q_ptr);
++ }
++
++ void QSurfaceDataProxyPrivate::limitValues(QVector3D &minValues, QVector3D &maxValues,
++ QAbstract3DAxis *axisX, QAbstract3DAxis *axisY,
++ QAbstract3DAxis *axisZ) const
++ {
++ float min = 0.0f;
++ float max = 0.0f;
++
++ int rows = m_dataArray->size();
++ int columns = 0;
++ if (rows)
++ columns = m_dataArray->at(0)->size();
++
++ if (rows && columns) {
++ min = m_dataArray->at(0)->at(0).y();
++ max = m_dataArray->at(0)->at(0).y();
++ }
++
++ for (int i = 0; i < rows; i++) {
++ QSurfaceDataRow *row = m_dataArray->at(i);
++ if (row) {
++ for (int j = 0; j < columns; j++) {
++ float itemValue = m_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 = m_dataArray->at(0)->at(0).x();
++ float xHigh = m_dataArray->at(0)->last().x();
++ float zLow = m_dataArray->at(0)->at(0).z();
++ float zHigh = m_dataArray->last()->at(0).z();
++ for (int i = 0; i < rows; i++) {
++ for (int j = 0; j < columns; j++) {
++ float zItemValue = m_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 (int i = rows - 1; i >= 0; i--) {
++ for (int j = 0; j < columns; j++) {
++ float zItemValue = m_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 (int j = 0; j<columns; j++){
++ for (int i = 0; i < rows; i++) {
++ float xItemValue = m_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 (int j = columns-1; j >= 0; j--){
++ for (int i = 0; i < rows; i++) {
++ float xItemValue = m_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_ptr->allowZero() ? 0.0f : 1.0f);
++ minValues.setZ(axisZ->d_ptr->allowZero() ? 0.0f : 1.0f);
++ maxValues.setX(axisX->d_ptr->allowZero() ? 0.0f : 1.0f);
++ maxValues.setZ(axisZ->d_ptr->allowZero() ? 0.0f : 1.0f);
++ }
++ }
++
++ bool QSurfaceDataProxyPrivate::isValidValue(float value, QAbstract3DAxis *axis) const
++ {
++ return (value > 0.0f || (value == 0.0f && axis->d_ptr->allowZero())
++ || (value < 0.0f && axis->d_ptr->allowNegatives()));
++ }
++
++ void QSurfaceDataProxyPrivate::clearRow(int rowIndex)
++ {
++ if (m_dataArray->at(rowIndex)) {
++ delete m_dataArray->at(rowIndex);
++ (*m_dataArray)[rowIndex] = 0;
++ }
++ }
++
++ void QSurfaceDataProxyPrivate::clearArray()
++ {
++ for (int i = 0; i < m_dataArray->size(); i++)
++ clearRow(i);
++ m_dataArray->clear();
++ delete m_dataArray;
++ }
++
++ void QSurfaceDataProxyPrivate::setSeries(QAbstract3DSeries *series)
++ {
++ QAbstractDataProxyPrivate::setSeries(series);
++ QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(series);
++ emit qptr()->seriesChanged(surfaceSeries);
++ }
++
++ QT_END_NAMESPACE