Revision control
++ // 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 "qscatter3dseries_p.h"
++ #include "qscatterdataproxy_p.h"
++ #include "qgraphs3dlogging_p.h"
++
++ QT_BEGIN_NAMESPACE
++
++ /*!
++ * \class QScatterDataProxy
++ * \inmodule QtGraphs
++ * \ingroup graphs_3D
++ * \brief The QScatterDataProxy class is the data proxy for 3D scatter graphs.
++ *
++ * A scatter data proxy handles adding, inserting, changing, and removing data
++ * items. Since data is stored in series, it is necessary
++ * to create a series associated with the proxy before using these functions for
++ * the dataset.
++ *
++ * QScatterDataProxy takes ownership of all
++ * QtGraphs::QScatterDataArray and QScatterDataItem objects passed to
++ * it.
++ *
++ * \sa {Qt Graphs Data Handling with 3D}
++ */
++
++ /*!
++ * \typealias QScatterDataArray
++ * \relates QScatterDataProxy
++ *
++ * A list of \l {QScatterDataItem} objects.
++ */
++
++ /*!
++ * \qmltype ScatterDataProxy
++ * \inqmlmodule QtGraphs
++ * \ingroup graphs_qml_3D
++ * \nativetype QScatterDataProxy
++ * \inherits AbstractDataProxy
++ * \brief The data proxy for 3D scatter graphs.
++ *
++ * This type handles adding, inserting, changing, and removing data items.
++ *
++ * This type is uncreatable, but contains properties that are exposed via
++ * subtypes.
++ *
++ * \sa ItemModelScatterDataProxy, {Qt Graphs Data Handling with 3D}
++ */
++
++ /*!
++ * \qmlproperty int ScatterDataProxy::itemCount
++ * The number of items in the array.
++ */
++
++ /*!
++ * \qmlproperty Scatter3DSeries ScatterDataProxy::series
++ *
++ * The series this proxy is attached to.
++ */
++
++ /*!
++ \qmlsignal ScatterDataProxy::itemCountChanged(int count)
++
++ This signal is emitted when itemCount changes to \a count.
++ */
++
++ /*!
++ \qmlsignal ScatterDataProxy::seriesChanged(Scatter3DSeries series)
++
++ This signal is emitted when \l series changes to \a series.
++ */
++
++ /*!
++ * Constructs QScatterDataProxy with the given \a parent.
++ */
++ QScatterDataProxy::QScatterDataProxy(QObject *parent)
++ : QAbstractDataProxy(*(new QScatterDataProxyPrivate()), parent)
++ {}
++
++ /*!
++ * \internal
++ */
++ QScatterDataProxy::QScatterDataProxy(QScatterDataProxyPrivate &d, QObject *parent)
++ : QAbstractDataProxy(d, parent)
++ {}
++
++ /*!
++ * Deletes the scatter data proxy.
++ */
++ QScatterDataProxy::~QScatterDataProxy() {}
++
++ /*!
++ * \property QScatterDataProxy::series
++ *
++ * \brief The series this proxy is attached to.
++ */
++ QScatter3DSeries *QScatterDataProxy::series() const
++ {
++ Q_D(const QScatterDataProxy);
++ if (!d->series())
++ qCWarning(lcGraphs3D, "%s series needs to be created to access data members",
++ qUtf8Printable(QLatin1String(__FUNCTION__)));
++ return static_cast<QScatter3DSeries *>(d->series());
++ }
++
++ /*!
++ * Clears the existing array and triggers the arrayReset() signal.
++ */
++ void QScatterDataProxy::resetArray()
++ {
++ series()->clearArray();
++
++ emit arrayReset();
++ emit itemCountChanged(itemCount());
++ }
++
++ /*!
++ * Sets the array from \a newArray. If the new array is equal to the
++ * existing one, this function simply triggers the arrayReset() signal.
++ */
++ void QScatterDataProxy::resetArray(QScatterDataArray newArray)
++ {
++ Q_D(QScatterDataProxy);
++ if (!series())
++ return;
++
++ if (!series()->dataArray().isSharedWith(newArray))
++ d->resetArray(std::move(newArray));
++
++ emit arrayReset();
++ emit itemCountChanged(itemCount());
++ }
++
++ /*!
++ * Sets the scale array from \a newArray. If the new array is equal to the
++ * existing one, this function simply triggers the scaleArrayReset() signal.
++ */
++ void QScatterDataProxy::resetScaleArray(QList<QVector3D> newArray)
++ {
++ Q_D(QScatterDataProxy);
++ if (!series())
++ return;
++
++ if (!series()->scaleArray().isSharedWith(newArray))
++ d->resetScaleArray(std::move(newArray));
++
++ emit scaleArrayReset();
++ }
++
++ /*!
++ * Replaces the item at the position \a index with the item \a item.
++ */
++ void QScatterDataProxy::setItem(qsizetype index, QScatterDataItem item)
++ {
++ Q_D(QScatterDataProxy);
++ d->setItem(index, std::move(item));
++ emit itemsChanged(index, 1);
++ }
++
++ /*!
++ * Replaces the items starting from the position \a index with the items
++ * specified by \a items.
++ */
++ void QScatterDataProxy::setItems(qsizetype index, QScatterDataArray items)
++ {
++ Q_D(QScatterDataProxy);
++ d->setItems(index, std::move(items));
++ emit itemsChanged(index, items.size());
++ }
++
++ /*!
++ * Adds the item \a item to the end of the array.
++ *
++ * Returns the index of the added item.
++ */
++ qsizetype QScatterDataProxy::addItem(QScatterDataItem item)
++ {
++ Q_D(QScatterDataProxy);
++ qsizetype addIndex = d->addItem(std::move(item));
++ emit itemsAdded(addIndex, 1);
++ emit itemCountChanged(itemCount());
++ return addIndex;
++ }
++
++ /*!
++ * Adds the items specified by \a items to the end of the array.
++ *
++ * Returns the index of the first added item.
++ */
++ qsizetype QScatterDataProxy::addItems(QScatterDataArray items)
++ {
++ Q_D(QScatterDataProxy);
++ qsizetype addIndex = d->addItems(std::move(items));
++ emit itemsAdded(addIndex, items.size());
++ emit itemCountChanged(itemCount());
++ return addIndex;
++ }
++
++ /*!
++ * Inserts the item \a item to the position \a index. If the index is equal to
++ * the data array size, the item is added to the array.
++ */
++ void QScatterDataProxy::insertItem(qsizetype index, QScatterDataItem item)
++ {
++ Q_D(QScatterDataProxy);
++ d->insertItem(index, std::move(item));
++ emit itemsInserted(index, 1);
++ emit itemCountChanged(itemCount());
++ }
++
++ /*!
++ * Inserts the items specified by \a items to the position \a index. If the
++ * index is equal to data array size, the items are added to the array.
++ */
++ void QScatterDataProxy::insertItems(qsizetype index, QScatterDataArray items)
++ {
++ Q_D(QScatterDataProxy);
++ d->insertItems(index, std::move(items));
++ emit itemsInserted(index, items.size());
++ emit itemCountChanged(itemCount());
++ }
++
++ /*!
++ * Removes the number of items specified by \a removeCount starting at the
++ * position \a index. Attempting to remove items past the end of
++ * the array does nothing.
++ */
++ void QScatterDataProxy::removeItems(qsizetype index, qsizetype removeCount)
++ {
++ if (index >= series()->dataArray().size())
++ return;
++
++ Q_D(QScatterDataProxy);
++ d->removeItems(index, removeCount);
++ emit itemsRemoved(index, removeCount);
++ emit itemCountChanged(itemCount());
++ }
++
++ /*!
++ * \property QScatterDataProxy::itemCount
++ *
++ * \brief The number of items in the array.
++ */
++ qsizetype QScatterDataProxy::itemCount() const
++ {
++ if (series())
++ return series()->dataArray().size();
++ else
++ return 0;
++ }
++
++ /*!
++ * Returns the pointer to the item at the index \a index. It is guaranteed to be
++ * valid only until the next call that modifies data.
++ */
++ const QScatterDataItem &QScatterDataProxy::itemAt(qsizetype index) const
++ {
++ return series()->dataArray().at(index);
++ }
++
++ /*!
++ * Returns the scale data at the index \a index. It is guaranteed to be
++ * valid only until the next call that modifies data.
++ */
++ QVector3D QScatterDataProxy::scaleAt(qsizetype index) const
++ {
++ QVector3D ret(1.0f, 1.0f, 1.0f);
++ if (series()->scaleArray().isEmpty())
++ return ret;
++ if (series()->scaleArray().size() <= index) {
++ qCWarning(lcGraphs3D, "Scale data size %" PRIdQSIZETYPE " does not match the access index %" PRIdQSIZETYPE
++ ". The default scale value will be returned.", series()->scaleArray().size(), index);
++ return ret;
++ }
++ return series()->scaleArray().at(index);
++ }
++
++ /*!
++ * \fn void QScatterDataProxy::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 QScatterDataProxy::itemsAdded(qsizetype startIndex, qsizetype count)
++ *
++ * This signal is emitted when the number of items specified by \a count are
++ * added, starting at the position \a startIndex.
++ * If items are added to the array without calling addItem() or addItems(),
++ * this signal needs to be emitted to update the graph.
++ */
++
++ /*!
++ * \fn void QScatterDataProxy::itemsChanged(qsizetype startIndex, qsizetype count)
++ *
++ * This signal is emitted when the number of items specified by \a count are
++ * changed, starting at the position \a startIndex.
++ * If items are changed in the array without calling setItem() or setItems(),
++ * this signal needs to be emitted to update the graph.
++ */
++
++ /*!
++ * \fn void QScatterDataProxy::itemsRemoved(qsizetype startIndex, qsizetype count)
++ *
++ * This signal is emitted when the number of rows specified by \a count are
++ * removed, starting at the position \a startIndex.
++ * The index may be larger than the current array size if items are removed from
++ * the end. If items are removed from the array without calling removeItems(),
++ * this signal needs to be emitted to update the graph.
++ */
++
++ /*!
++ * \fn void QScatterDataProxy::itemsInserted(qsizetype startIndex, qsizetype count)
++ *
++ * This signal is emitted when the number of items specified by \a count are
++ * inserted, starting at the position \a startIndex.
++ * If items are inserted into the array without calling insertItem() or
++ * insertItems(), this signal needs to be emitted to update the graph.
++ */
++
++ // QScatterDataProxyPrivate
++
++ QScatterDataProxyPrivate::QScatterDataProxyPrivate()
++ : QAbstractDataProxyPrivate(QAbstractDataProxy::DataType::Scatter)
++ {}
++
++ QScatterDataProxyPrivate::~QScatterDataProxyPrivate() {}
++
++ void QScatterDataProxyPrivate::resetArray(QScatterDataArray &&newArray)
++ {
++ auto *scatterSeries = static_cast<QScatter3DSeries *>(series());
++ if (!newArray.isSharedWith(scatterSeries->dataArray()))
++ scatterSeries->setDataArray(newArray);
++ }
++
++ void QScatterDataProxyPrivate::resetScaleArray(QList<QVector3D> &&newArray)
++ {
++ auto *scatterSeries = static_cast<QScatter3DSeries *>(series());
++ if (!newArray.isSharedWith(scatterSeries->scaleArray()))
++ scatterSeries->setScaleArray(newArray);
++ }
++
++ void QScatterDataProxyPrivate::setItem(qsizetype index, QScatterDataItem &&item)
++ {
++ auto *scatterSeries = static_cast<QScatter3DSeries *>(series());
++ Q_ASSERT(index >= 0 && index < scatterSeries->dataArray().size());
++ QScatterDataArray array = scatterSeries->dataArray();
++ array[index] = item;
++ scatterSeries->setDataArray(array);
++ }
++
++ void QScatterDataProxyPrivate::setItems(qsizetype index, QScatterDataArray &&items)
++ {
++ auto *scatterSeries = static_cast<QScatter3DSeries *>(series());
++ Q_ASSERT(index >= 0 && (index + items.size()) <= scatterSeries->dataArray().size());
++ QScatterDataArray array = scatterSeries->dataArray();
++ for (int i = 0; i < items.size(); i++)
++ array[index++] = items[i];
++ scatterSeries->setDataArray(array);
++ }
++
++ qsizetype QScatterDataProxyPrivate::addItem(QScatterDataItem &&item)
++ {
++ auto *scatterSeries = static_cast<QScatter3DSeries *>(series());
++ qsizetype currentSize = scatterSeries->dataArray().size();
++ QScatterDataArray array = scatterSeries->dataArray();
++ array.append(item);
++ scatterSeries->setDataArray(array);
++ return currentSize;
++ }
++
++ qsizetype QScatterDataProxyPrivate::addItems(QScatterDataArray &&items)
++ {
++ auto *scatterSeries = static_cast<QScatter3DSeries *>(series());
++ qsizetype currentSize = 0;
++ if (scatterSeries) {
++ currentSize = scatterSeries->dataArray().size();
++ QScatterDataArray array = scatterSeries->dataArray();
++ array += items;
++ scatterSeries->setDataArray(array);
++ }
++ return currentSize;
++ }
++
++ void QScatterDataProxyPrivate::insertItem(qsizetype index, QScatterDataItem &&item)
++ {
++ auto *scatterSeries = static_cast<QScatter3DSeries *>(series());
++ Q_ASSERT(index >= 0 && index <= scatterSeries->dataArray().size());
++ QScatterDataArray array = scatterSeries->dataArray();
++ array.insert(index, item);
++ scatterSeries->setDataArray(array);
++ }
++
++ void QScatterDataProxyPrivate::insertItems(qsizetype index, QScatterDataArray &&items)
++ {
++ auto *scatterSeries = static_cast<QScatter3DSeries *>(series());
++ Q_ASSERT(index >= 0 && index <= scatterSeries->dataArray().size());
++ QScatterDataArray array = scatterSeries->dataArray();
++ for (int i = 0; i < items.size(); i++)
++ array.insert(index++, items.at(i));
++ scatterSeries->setDataArray(array);
++ }
++
++ void QScatterDataProxyPrivate::removeItems(qsizetype index, qsizetype removeCount)
++ {
++ auto *scatterSeries = static_cast<QScatter3DSeries *>(series());
++ Q_ASSERT(index >= 0);
++ qsizetype maxRemoveCount = scatterSeries->dataArray().size() - index;
++ removeCount = qMin(removeCount, maxRemoveCount);
++ QScatterDataArray array = scatterSeries->dataArray();
++ array.remove(index, removeCount);
++ scatterSeries->setDataArray(array);
++ }
++
++ void QScatterDataProxyPrivate::limitValues(QVector3D &minValues,
++ QVector3D &maxValues,
++ QAbstract3DAxis *axisX,
++ QAbstract3DAxis *axisY,
++ QAbstract3DAxis *axisZ) const
++ {
++ auto *scatterSeries = static_cast<QScatter3DSeries *>(series());
++ if (scatterSeries->dataArray().isEmpty())
++ return;
++
++ QVector3D firstPos = scatterSeries->dataArray().at(0).position();
++
++ float minX = firstPos.x();
++ float maxX = minX;
++ float minY = firstPos.y();
++ float maxY = minY;
++ float minZ = firstPos.z();
++ float maxZ = minZ;
++
++ if (scatterSeries->dataArray().size() > 1) {
++ for (int i = 1; i < scatterSeries->dataArray().size(); i++) {
++ QVector3D pos = scatterSeries->dataArray().at(i).position();
++
++ float value = pos.x();
++ if (qIsNaN(value) || qIsInf(value))
++ continue;
++ if (isValidValue(minX, value, axisX))
++ minX = value;
++ if (maxX < value)
++ maxX = value;
++
++ value = pos.y();
++ if (qIsNaN(value) || qIsInf(value))
++ continue;
++ if (isValidValue(minY, value, axisY))
++ minY = value;
++ if (maxY < value)
++ maxY = value;
++
++ value = pos.z();
++ if (qIsNaN(value) || qIsInf(value))
++ continue;
++ if (isValidValue(minZ, value, axisZ))
++ minZ = value;
++ if (maxZ < value)
++ maxZ = value;
++ }
++ }
++
++ minValues.setX(minX);
++ minValues.setY(minY);
++ minValues.setZ(minZ);
++
++ maxValues.setX(maxX);
++ maxValues.setY(maxY);
++ maxValues.setZ(maxZ);
++ }
++
++ bool QScatterDataProxyPrivate::isValidValue(float axisValue,
++ float value,
++ QAbstract3DAxis *axis) const
++ {
++ return (axisValue > value
++ && (value > 0.0f || (value == 0.0f && axis->d_func()->allowZero())
++ || (value < 0.0f && axis->d_func()->allowNegatives())));
++ }
++
++ void QScatterDataProxyPrivate::setSeries(QAbstract3DSeries *series)
++ {
++ Q_Q(QScatterDataProxy);
++ QAbstractDataProxyPrivate::setSeries(series);
++ QScatter3DSeries *scatterSeries = static_cast<QScatter3DSeries *>(series);
++ emit q->seriesChanged(scatterSeries);
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qscatterdataproxy.cpp"