/**************************************************************************** ** ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Data Visualization module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 or (at your option) any later version ** approved by the KDE Free Qt Foundation. The licenses are as published by ** the Free Software Foundation and appearing in the file LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qsurfacedataproxy_p.h" #include "qsurface3dseries_p.h" #include "qabstract3daxis_p.h" QT_BEGIN_NAMESPACE_DATAVISUALIZATION /*! * \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 QVector 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 vector 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 * \instantiates 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(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(d_ptr.data()); } /*! * \internal */ const QSurfaceDataProxyPrivate *QSurfaceDataProxy::dptrc() const { return static_cast(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->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->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->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->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(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 && isValidValue(itemValue, axisY)) min = itemValue; if (max < itemValue) 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 < columns; i++) { float zItemValue = m_dataArray->at(0)->at(i).z(); if (qIsNaN(zItemValue) || qIsInf(zItemValue)) continue; else if (isValidValue(zItemValue, axisZ)) zLow = qMin(zLow,zItemValue); } for (int i = 0; i < columns; i++) { float zItemValue = m_dataArray->last()->at(i).z(); if (qIsNaN(zItemValue) || qIsInf(zItemValue)) continue; else if (isValidValue(zItemValue, axisZ)) zHigh = qMax(zHigh, zItemValue); } for (int i = 0; i < rows; i++) { float xItemValue = m_dataArray->at(i)->at(0).x(); if (qIsNaN(xItemValue) || qIsInf(xItemValue)) continue; else if (isValidValue(xItemValue, axisX)) xLow = qMin(xLow, xItemValue); } for (int i = 0; i < rows; i++) { float xItemValue = m_dataArray->at(i)->last().x(); if (qIsNaN(xItemValue) || qIsInf(xItemValue)) continue; else if (isValidValue(xItemValue, axisX)) xHigh = qMax(xHigh, xItemValue); } 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(series); emit qptr()->seriesChanged(surfaceSeries); } QT_END_NAMESPACE_DATAVISUALIZATION