blob: 1c61084cf1c1fa8ad83fc7696e3af082368cde73 [file] [log] [blame]
/****************************************************************************
**
** 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<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->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<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 && 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<QSurface3DSeries *>(series);
emit qptr()->seriesChanged(surfaceSeries);
}
QT_END_NAMESPACE_DATAVISUALIZATION