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 "qcategory3daxis.h"
++ #include "qquickgraphsscatter_p.h"
++ #include "qscatter3dseries_p.h"
++ #include "qvalue3daxis.h"
++ #include "qgraphs3dlogging_p.h"
++
++ QT_BEGIN_NAMESPACE
++
++ /*!
++ * \class QScatter3DSeries
++ * \inmodule QtGraphs
++ * \ingroup graphs_3D
++ * \brief The QScatter3DSeries class represents a data series in a 3D scatter
++ * graph.
++ *
++ * This class manages the series-specific visual elements, as well as the series
++ * data (via a data proxy).
++ *
++ * Regarding the proxy-series relationship, it is crucial to highlight
++ * a couple of key points. In this context, data is stored in series and
++ * users can access the dataset through the series. This series is controlled
++ * or represented by a proxy object. Thus, the proxy can be used to manage various
++ * operations on the data and update the actual dataset. However, it is necessary
++ * to create a series associated with this proxy to edit the dataset.
++ *
++ * If no data proxy is set explicitly for the series, the series creates a
++ * default proxy. Setting another proxy will destroy the existing proxy and all
++ * data added to the series.
++ *
++ * QScatter3DSeries supports the following format tags for QAbstract3DSeries::setItemLabelFormat():
++ * \table
++ * \row
++ * \li @xTitle \li Title from x-axis
++ * \row
++ * \li @yTitle \li Title from y-axis
++ * \row
++ * \li @zTitle \li Title from z-axis
++ * \row
++ * \li @xLabel \li Item value formatted using the format of the x-axis.
++ * For more information, see
++ * \l{QValue3DAxis::labelFormat}.
++ * \row
++ * \li @yLabel \li Item value formatted using the format of the y-axis.
++ * For more information, see
++ * \l{QValue3DAxis::labelFormat}.
++ * \row
++ * \li @zLabel \li Item value formatted using the format of the z-axis.
++ * For more information, see
++ * \l{QValue3DAxis::labelFormat}.
++ * \row
++ * \li @seriesName \li Name of the series
++ * \endtable
++ *
++ * For example:
++ * \snippet doc_src_qtgraphs.cpp labelformat-scatter
++ *
++ * \sa {Qt Graphs Data Handling with 3D}
++ */
++
++ /*!
++ * \qmltype Scatter3DSeries
++ * \inqmlmodule QtGraphs
++ * \ingroup graphs_qml_3D
++ * \nativetype QScatter3DSeries
++ * \inherits Abstract3DSeries
++ * \brief Represents a data series in a 3D scatter graph.
++ *
++ * This type manages the series specific visual elements, as well as the series
++ * data (via a data proxy).
++ *
++ * Scatter3DSeries supports the following format tags for itemLabelFormat:
++ * \table
++ * \row
++ * \li @xTitle \li Title from x-axis
++ * \row
++ * \li @yTitle \li Title from y-axis
++ * \row
++ * \li @zTitle \li Title from z-axis
++ * \row
++ * \li @xLabel \li Item value formatted using the format of the x-axis.
++ * For more information, see
++ * \l{QValue3DAxis::labelFormat}{labelFormat}.
++ * \row
++ * \li @yLabel \li Item value formatted using the format of the y-axis.
++ * For more information, see
++ * \l{QValue3DAxis::labelFormat}{labelFormat}.
++ * \row
++ * \li @zLabel \li Item value formatted using the format of the z-axis.
++ * For more information, see
++ * \l{QValue3DAxis::labelFormat}{labelFormat}.
++ * \row
++ * \li @seriesName \li Name of the series
++ * \endtable
++ *
++ * For a more complete description, see QScatter3DSeries.
++ *
++ * \sa {Qt Graphs Data Handling with 3D}
++ */
++
++ /*!
++ * \qmlproperty ScatterDataProxy Scatter3DSeries::dataProxy
++ *
++ * Sets the active data proxy. The series assumes ownership of any proxy set to
++ * it and deletes any previously set proxy when a new one is added. The proxy
++ * cannot be null or set to another series.
++ */
++
++ /*!
++ * \qmlproperty qsizetype Scatter3DSeries::selectedItem
++ *
++ * The item that is selected at the index in the data array of the series.
++ * Only one item can be selected at a time.
++ * To clear the selection from this series, the invalidSelectionIndex is set as the
++ * index. If this series is added to a graph, the graph can adjust the selection
++ * according to user interaction or if it becomes invalid. Selecting an item on
++ * another added series will also clear the selection. Removing items from or
++ * inserting items into the series before the selected item will adjust the
++ * selection so that the same item will stay selected.
++ *
++ * \sa GraphsItem3D::clearSelection()
++ */
++
++ /*!
++ * \qmlproperty real Scatter3DSeries::itemSize
++ *
++ * Sets the item size for the series. The size must be between \c 0.0 and
++ * \c 1.0. Setting the size to \c 0.0 causes the item size to be automatically
++ * scaled based on the total number of items in all the series for the graph.
++ * The preset default is \c 0.0.
++ */
++
++ /*!
++ * \qmlproperty qsizetype Scatter3DSeries::invalidSelectionIndex
++ * A constant property providing an invalid index for selection. This index is
++ * set to the selectedItem property to clear the selection from this series.
++ *
++ * \sa GraphsItem3D::clearSelection()
++ */
++
++ /*!
++ * \qmlproperty ScatterDataArray Scatter3DSeries::dataArray
++ *
++ * Holds the reference to the data array.
++ *
++ * dataArrayChanged signal is emitted when data array is set, unless \a newDataArray
++ * is identical to the previous one.
++ *
++ * \note Before doing anything regarding the data array, a series must be created for
++ * the relevant proxy.
++ */
++
++ /*!
++ \qmlsignal Scatter3DSeries::dataProxyChanged(ScatterDataProxy proxy)
++
++ This signal is emitted when dataProxy changes to \a proxy.
++ */
++
++ /*!
++ \qmlsignal Scatter3DSeries::selectedItemChanged(qsizetype index)
++
++ This signal is emitted when selectedItem changes to \a index.
++ */
++
++ /*!
++ \qmlsignal Scatter3DSeries::itemSizeChanged(float size)
++
++ This signal is emitted when itemSize changes to \a size.
++ */
++
++ /*!
++ \qmlsignal Scatter3DSeries::dataArrayChanged(ScatterDataArray array)
++
++ This signal is emitted when dataArray changes to \a array.
++ */
++
++ /*!
++ * Constructs a scatter 3D series with the parent \a parent.
++ */
++ QScatter3DSeries::QScatter3DSeries(QObject *parent)
++ : QAbstract3DSeries(*(new QScatter3DSeriesPrivate()), parent)
++ {
++ Q_D(QScatter3DSeries);
++ // Default proxy
++ d->setDataProxy(new QScatterDataProxy);
++ }
++
++ /*!
++ * Constructs a scatter 3D series with the data proxy \a dataProxy and the
++ * parent \a parent.
++ */
++ QScatter3DSeries::QScatter3DSeries(QScatterDataProxy *dataProxy, QObject *parent)
++ : QAbstract3DSeries(*(new QScatter3DSeriesPrivate()), parent)
++ {
++ Q_D(QScatter3DSeries);
++ d->setDataProxy(dataProxy);
++ }
++
++ /*!
++ * \property QScatter3DSeries::axisX
++ * \since 6.11
++ *
++ * \brief Holds an additional X-axis for the series
++ * If an axis is given, the series will be fitted
++ * to the minimum and maximum values of the axis.
++ */
++ void QScatter3DSeries::setAxisX(QValue3DAxis *axis) {
++ Q_D(QScatter3DSeries);
++ if (d->m_axisX == axis) {
++ qCDebug(lcProperties3D) << __FUNCTION__
++ << "value is already set to:" << axis;
++ return;
++ }
++ d->setAxisX(axis);
++ emit axisXChanged(axis);
++ }
++
++ QValue3DAxis *QScatter3DSeries::axisX() const {
++ Q_D(const QScatter3DSeries);
++ return d->m_axisX;
++ }
++
++ void QScatter3DSeries::resetAxisX()
++ {
++ Q_D(QScatter3DSeries);
++ if (!d->m_axisX) {
++ qCDebug(lcProperties3D) << __FUNCTION__
++ << "axis X is not set";
++ return;
++ }
++ d->resetAxisX();
++ emit axisXChanged(nullptr);
++ }
++
++ /*!
++ * \property QScatter3DSeries::axisY
++ * \since 6.11
++ *
++ * \brief Holds an additional Y-axis for the series
++ * If an axis is given, the series will be fitted
++ * to the minimum and maximum values of the axis.
++ */
++ void QScatter3DSeries::setAxisY(QValue3DAxis *axis) {
++ Q_D(QScatter3DSeries);
++ if (d->m_axisY == axis) {
++ qCDebug(lcProperties3D) << __FUNCTION__
++ << "value is already set to:" << axis;
++ return;
++ }
++ d->setAxisY(axis);
++ emit axisYChanged(axis);
++ }
++
++ QValue3DAxis *QScatter3DSeries::axisY() const {
++ Q_D(const QScatter3DSeries);
++ return d->m_axisY;
++ }
++
++ void QScatter3DSeries::resetAxisY()
++ {
++ Q_D(QScatter3DSeries);
++ if (!d->m_axisY) {
++ qCDebug(lcProperties3D) << __FUNCTION__
++ << "axis Y is not set";
++ return;
++ }
++ d->resetAxisY();
++ emit axisYChanged(nullptr);
++ }
++
++ /*!
++ * \property QScatter3DSeries::axisZ
++ * \since 6.11
++ *
++ * \brief Holds an additional Z-axis for the series
++ * If an axis is given, the series will be fitted
++ * to the minimum and maximum values of the axis.
++ */
++ void QScatter3DSeries::setAxisZ(QValue3DAxis *axis) {
++ Q_D(QScatter3DSeries);
++ if (d->m_axisZ == axis) {
++ qCDebug(lcProperties3D) << __FUNCTION__
++ << "value is already set to:" << axis;
++ return;
++ }
++ d->setAxisZ(axis);
++ emit axisZChanged(axis);
++ }
++
++ QValue3DAxis *QScatter3DSeries::axisZ() const {
++ Q_D(const QScatter3DSeries);
++ return d->m_axisZ;
++ }
++
++ void QScatter3DSeries::resetAxisZ()
++ {
++ Q_D(QScatter3DSeries);
++ if (!d->m_axisZ) {
++ qCDebug(lcProperties3D) << __FUNCTION__
++ << "axis Z is not set";
++ return;
++ }
++ d->resetAxisZ();
++ emit axisZChanged(nullptr);
++ }
++
++ /*!
++ * \internal
++ */
++ QScatter3DSeries::QScatter3DSeries(QScatter3DSeriesPrivate &d, QObject *parent)
++ : QAbstract3DSeries(d, parent)
++ {}
++
++ /*!
++ * Deletes the scatter 3D series.
++ */
++ QScatter3DSeries::~QScatter3DSeries() {}
++
++ /*!
++ * \property QScatter3DSeries::dataProxy
++ *
++ * \brief The active data proxy.
++ *
++ * Sets the active data proxy for the series to \a proxy. The series assumes
++ * ownership of any proxy set to it and deletes any previously set proxy when
++ * a new one is added. The \a proxy argument cannot be null or set to another
++ * series.
++ */
++ void QScatter3DSeries::setDataProxy(QScatterDataProxy *proxy)
++ {
++ Q_D(QScatter3DSeries);
++ d->setDataProxy(proxy);
++ }
++
++ QScatterDataProxy *QScatter3DSeries::dataProxy() const
++ {
++ Q_D(const QScatter3DSeries);
++ return static_cast<QScatterDataProxy *>(d->dataProxy());
++ }
++
++ /*!
++ * \property QScatter3DSeries::selectedItem
++ *
++ * \brief The item that is selected in the series.
++ *
++ * Selects the item at the index \a index in the data array of the series.
++ * Only one item can be selected at a time.
++ *
++ * To clear the selection from this series, invalidSelectionIndex() is set as \a
++ * index. If this series is added to a graph, the graph can adjust the selection
++ * according to user interaction or if it becomes invalid. Selecting an item on
++ * another added series will also clear the selection.
++ *
++ * Removing items from or inserting items into the series before the selected item
++ * will adjust the selection so that the same item will stay selected.
++ *
++ * \sa Q3DGraphsWidgetItem::clearSelection()
++ */
++ void QScatter3DSeries::setSelectedItem(qsizetype index)
++ {
++ Q_D(QScatter3DSeries);
++ // Don't do this in private to avoid loops, as that is used for callback from
++ // graph.
++ if (d->m_graph)
++ static_cast<QQuickGraphsScatter *>(d->m_graph)->setSelectedItem(index, this);
++ else
++ d->setSelectedItem(index);
++ }
++
++ qsizetype QScatter3DSeries::selectedItem() const
++ {
++ Q_D(const QScatter3DSeries);
++ return d->m_selectedItem;
++ }
++
++ /*!
++ * \property QScatter3DSeries::itemSize
++ *
++ * \brief Item size for the series.
++ *
++ * The size must be between \c 0.0f and \c 1.0f. Setting the size to \c 0.0f
++ * causes the item size to be automatically scaled based on the total number of
++ * items in all the series for the graph.
++ *
++ * The preset default is \c 0.0f.
++ */
++ void QScatter3DSeries::setItemSize(float size)
++ {
++ Q_D(QScatter3DSeries);
++ if (size < 0.0f || size > 1.0f) {
++ qCWarning(lcProperties3D, "%s invalid size. Valid range for itemSize is 0.0f...1.0f",
++ qUtf8Printable(QLatin1String(__FUNCTION__)));
++ return;
++ } else if (size == d->m_itemSize) {
++ qCDebug(lcProperties3D, "%s value is already set to: %.1f",
++ qUtf8Printable(QLatin1String(__FUNCTION__)), size);
++ return;
++ }
++ d->setItemSize(size);
++ emit itemSizeChanged(size);
++ }
++
++ float QScatter3DSeries::itemSize() const
++ {
++ Q_D(const QScatter3DSeries);
++ return d->m_itemSize;
++ }
++
++ /*!
++ * \property QScatter3DSeries::dataArray
++ *
++ * \brief Data array for the series.
++ *
++ * Holds the reference to the data array.
++ *
++ * dataArrayChanged signal is emitted when data array is set, unless \a newDataArray
++ * is identical to the previous one.
++ *
++ * \note Before doing anything regarding the data array, a series must be created for
++ * the relevant proxy.
++ *
++ * \sa clearArray()
++ */
++ void QScatter3DSeries::setDataArray(const QScatterDataArray &newDataArray)
++ {
++ Q_D(QScatter3DSeries);
++ if (d->m_dataArray.isSharedWith(newDataArray)) {
++ qCDebug(lcProperties3D) << __FUNCTION__
++ << "newDataArray is the same than the old one";
++ return;
++ }
++ d->setDataArray(newDataArray);
++ emit dataArrayChanged(newDataArray);
++ }
++
++ /*!
++ * Clears the data array.
++ */
++ void QScatter3DSeries::clearArray()
++ {
++ Q_D(QScatter3DSeries);
++ d->clearArray();
++ }
++
++ const QScatterDataArray &QScatter3DSeries::dataArray() const &
++ {
++ Q_D(const QScatter3DSeries);
++ return d->m_dataArray;
++ }
++
++ QScatterDataArray QScatter3DSeries::dataArray() &&
++ {
++ Q_D(QScatter3DSeries);
++ return std::move(d->m_dataArray);
++ }
++
++ /*!
++ * \property QScatter3DSeries::scaleArray
++ *
++ * \brief Scale array for the series.
++ *
++ * Holds the reference to the scale array.
++ *
++ * scaleArrayChanged signal is emitted when scale array is set, unless \a newScaleArray
++ * is identical to the previous one.
++ *
++ * \note Before doing anything regarding the scale array, a series must be created for
++ * the relevant proxy.
++ *
++ * \note If a scale is not specified for the data, a default value of (1.0,1.0,1.0)
++ * will be used.
++ *
++ * \sa clearScaleArray()
++ */
++ void QScatter3DSeries::setScaleArray(const QList<QVector3D> &newScaleArray)
++ {
++ Q_D(QScatter3DSeries);
++ if (d->m_scaleArray.isSharedWith(newScaleArray)) {
++ qCDebug(lcProperties3D) << __FUNCTION__
++ << "newScaleArray is the same than the old one";
++ return;
++ }
++ d->setScaleArray(newScaleArray);
++ emit scaleArrayChanged(newScaleArray);
++ }
++
++ /*!
++ * Clears the scale array.
++ */
++ void QScatter3DSeries::clearScaleArray()
++ {
++ Q_D(QScatter3DSeries);
++ d->clearScaleArray();
++ }
++
++ const QList<QVector3D> &QScatter3DSeries::scaleArray() const &
++ {
++ Q_D(const QScatter3DSeries);
++ return d->m_scaleArray;
++ }
++
++ QList<QVector3D> QScatter3DSeries::scaleArray() &&
++ {
++ Q_D(QScatter3DSeries);
++ return std::move(d->m_scaleArray);
++ }
++
++ /*!
++ * Returns an invalid index for selection. This index is set to the selectedItem
++ * property to clear the selection from this series.
++ *
++ * \sa Q3DGraphsWidgetItem::clearSelection()
++ */
++ qsizetype QScatter3DSeries::invalidSelectionIndex()
++ {
++ return QQuickGraphsScatter::invalidSelectionIndex();
++ }
++
++ // QScatter3DSeriesPrivate
++
++ QScatter3DSeriesPrivate::QScatter3DSeriesPrivate()
++ : QAbstract3DSeriesPrivate(QAbstract3DSeries::SeriesType::Scatter)
++ , m_selectedItem(QQuickGraphsScatter::invalidSelectionIndex())
++ , m_itemSize(0.0f)
++ {
++ m_itemLabelFormat = QStringLiteral("@xLabel, @yLabel, @zLabel");
++ m_mesh = QAbstract3DSeries::Mesh::Sphere;
++ }
++
++ QScatter3DSeriesPrivate::~QScatter3DSeriesPrivate()
++ {
++ clearArray();
++ }
++
++ void QScatter3DSeriesPrivate::setDataProxy(QAbstractDataProxy *proxy)
++ {
++ Q_Q(QScatter3DSeries);
++ Q_ASSERT(proxy->type() == QAbstractDataProxy::DataType::Scatter);
++
++ QAbstract3DSeriesPrivate::setDataProxy(proxy);
++
++ emit q->dataProxyChanged(static_cast<QScatterDataProxy *>(proxy));
++ }
++
++ void QScatter3DSeriesPrivate::connectGraphAndProxy(QQuickGraphsItem *newGraph)
++ {
++ Q_Q(QScatter3DSeries);
++ QScatterDataProxy *scatterDataProxy = static_cast<QScatterDataProxy *>(m_dataProxy);
++
++ if (m_graph && scatterDataProxy) {
++ // Disconnect old graph/old proxy
++ QObject::disconnect(scatterDataProxy, 0, m_graph, 0);
++ QObject::disconnect(q, 0, m_graph, 0);
++ }
++
++ if (newGraph && scatterDataProxy) {
++ QQuickGraphsScatter *graph = static_cast<QQuickGraphsScatter *>(newGraph);
++ QObject::connect(scatterDataProxy,
++ &QScatterDataProxy::arrayReset,
++ graph,
++ &QQuickGraphsScatter::handleArrayReset);
++ QObject::connect(scatterDataProxy,
++ &QScatterDataProxy::itemsAdded,
++ graph,
++ &QQuickGraphsScatter::handleItemsAdded);
++ QObject::connect(scatterDataProxy,
++ &QScatterDataProxy::itemsChanged,
++ graph,
++ &QQuickGraphsScatter::handleItemsChanged);
++ QObject::connect(scatterDataProxy,
++ &QScatterDataProxy::itemsRemoved,
++ graph,
++ &QQuickGraphsScatter::handleItemsRemoved);
++ QObject::connect(scatterDataProxy,
++ &QScatterDataProxy::itemsInserted,
++ graph,
++ &QQuickGraphsScatter::handleItemsInserted);
++ QObject::connect(q,
++ &QScatter3DSeries::dataProxyChanged,
++ graph,
++ &QQuickGraphsScatter::handleArrayReset);
++ }
++ }
++
++ void QScatter3DSeriesPrivate::createItemLabel()
++ {
++ Q_Q(QScatter3DSeries);
++ static const QString xTitleTag(QStringLiteral("@xTitle"));
++ static const QString yTitleTag(QStringLiteral("@yTitle"));
++ static const QString zTitleTag(QStringLiteral("@zTitle"));
++ static const QString xLabelTag(QStringLiteral("@xLabel"));
++ static const QString yLabelTag(QStringLiteral("@yLabel"));
++ static const QString zLabelTag(QStringLiteral("@zLabel"));
++ static const QString seriesNameTag(QStringLiteral("@seriesName"));
++
++ if (m_selectedItem == QScatter3DSeries::invalidSelectionIndex()) {
++ m_itemLabel = QString(hiddenLabelTag);
++ return;
++ }
++
++ QValue3DAxis *axisX = static_cast<QValue3DAxis *>(m_graph->axisX());
++ QValue3DAxis *axisY = static_cast<QValue3DAxis *>(m_graph->axisY());
++ QValue3DAxis *axisZ = static_cast<QValue3DAxis *>(m_graph->axisZ());
++ QVector3D selectedPosition = q->dataProxy()->itemAt(m_selectedItem).position();
++
++ m_itemLabel = m_itemLabelFormat;
++
++ m_itemLabel.replace(xTitleTag, axisX->title());
++ m_itemLabel.replace(yTitleTag, axisY->title());
++ m_itemLabel.replace(zTitleTag, axisZ->title());
++
++ if (m_itemLabel.contains(xLabelTag)) {
++ QString valueLabelText = axisX->formatter()->stringForValue(qreal(selectedPosition.x()),
++ axisX->labelFormat());
++ m_itemLabel.replace(xLabelTag, valueLabelText);
++ }
++ if (m_itemLabel.contains(yLabelTag)) {
++ QString valueLabelText = axisY->formatter()->stringForValue(qreal(selectedPosition.y()),
++ axisY->labelFormat());
++ m_itemLabel.replace(yLabelTag, valueLabelText);
++ }
++ if (m_itemLabel.contains(zLabelTag)) {
++ QString valueLabelText = axisZ->formatter()->stringForValue(qreal(selectedPosition.z()),
++ axisZ->labelFormat());
++ m_itemLabel.replace(zLabelTag, valueLabelText);
++ }
++ m_itemLabel.replace(seriesNameTag, m_name);
++ }
++
++ void QScatter3DSeriesPrivate::setSelectedItem(qsizetype index)
++ {
++ Q_Q(QScatter3DSeries);
++ if (index == m_selectedItem) {
++ qCDebug(lcProperties3D, "%s Value is already set to: %" PRIdQSIZETYPE,
++ qUtf8Printable(QLatin1String(__FUNCTION__)), index);
++ return;
++ }
++ markItemLabelDirty();
++ m_selectedItem = index;
++ emit q->selectedItemChanged(m_selectedItem);
++ }
++
++ void QScatter3DSeriesPrivate::setItemSize(float size)
++ {
++ m_itemSize = size;
++ if (m_graph)
++ m_graph->markSeriesVisualsDirty();
++ }
++
++ void QScatter3DSeriesPrivate::setDataArray(const QScatterDataArray &newDataArray)
++ {
++ m_dataArray = newDataArray;
++ }
++
++ void QScatter3DSeriesPrivate::clearArray()
++ {
++ m_dataArray.clear();
++ }
++
++ void QScatter3DSeriesPrivate::setScaleArray(const QList<QVector3D> &newScaleArray)
++ {
++ m_scaleArray = newScaleArray;
++ }
++
++ void QScatter3DSeriesPrivate::clearScaleArray()
++ {
++ m_scaleArray.clear();
++ }
++
++ void QScatter3DSeriesPrivate::setAxisX(QValue3DAxis *axis)
++ {
++ m_axisX = axis;
++ }
++
++ void QScatter3DSeriesPrivate::resetAxisX()
++ {
++ m_axisX = nullptr;
++ }
++
++ void QScatter3DSeriesPrivate::setAxisY(QValue3DAxis *axis)
++ {
++ m_axisY = axis;
++ }
++
++ void QScatter3DSeriesPrivate::resetAxisY()
++ {
++ m_axisY = nullptr;
++ }
++
++ void QScatter3DSeriesPrivate::setAxisZ(QValue3DAxis *axis)
++ {
++ m_axisZ = axis;
++ }
++
++ void QScatter3DSeriesPrivate::resetAxisZ()
++ {
++ m_axisZ = nullptr;
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qscatter3dseries.cpp"