// Copyright (C) 2025 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "qquickgraphssurfacenode_p.h" QT_BEGIN_NAMESPACE /*! * \qmltype Surface3DNode * \inherits GraphsNode * \inqmlmodule QtGraphs * \ingroup graphs_qml_3D * \brief Describes the usage of the 3D surface graph node. * * This type enables developers to render a surface graph node in 3D with Qt Quick. * * You will need to import the Qt Graphs module to use this type: * * \snippet doc_src_qmlnodes.qml 0 * * After that you can use Surface3DNode in your qml files: * * \snippet doc_src_qmlnodes.qml 3 * * * \sa Surface3DSeries, ItemModelSurfaceDataProxy, Bars3DNode, Scatter3DNode, * {Qt Graphs C++ Classes for 3D} */ /*! * \qmlproperty Value3DAxis Surface3DNode::axisX * The active x-axis. * * If an axis is not given, a temporary default axis with no labels and an * automatically adjusting range is created. * This temporary axis is destroyed if another axis is explicitly set to the * same orientation. */ /*! * \qmlproperty Value3DAxis Surface3DNode::axisY * The active y-axis. * * If an axis is not given, a temporary default axis with no labels and an * automatically adjusting range is created. * This temporary axis is destroyed if another axis is explicitly set to the * same orientation. */ /*! * \qmlproperty Value3DAxis Surface3DNode::axisZ * The active z-axis. * * If an axis is not given, a temporary default axis with no labels and an * automatically adjusting range is created. * This temporary axis is destroyed if another axis is explicitly set to the * same orientation. */ /*! * \qmlproperty Surface3DSeries Surface3DNode::selectedSeries * \readonly * * The selected series or null. If \l {GraphsNode::selectionMode}{selectionMode} * has the \c MultiSeries flag set, this property holds the series * which owns the selected point. */ /*! * \qmlproperty list Surface3DNode::seriesList * \qmldefault * This property holds the series of the graph. * By default, this property contains an empty list. * To set the series, either use the addSeries() function or define them as * children of the graph. */ /*! * \qmlproperty bool Surface3DNode::flipHorizontalGrid * * In some use cases the horizontal axis grid is mostly covered by the surface, * so it can be more useful to display the horizontal axis grid on top of the * graph rather than on the bottom. A typical use case for this is showing 2D * spectrograms using orthoGraphic projection with a top-down viewpoint. * * If \c{false}, the horizontal axis grid and labels are drawn on the horizontal * background of the graph. * If \c{true}, the horizontal axis grid and labels are drawn on the opposite * side of the graph from the horizontal background. * Defaults to \c{false}. */ /*! * \qmlmethod void Surface3DNode::addSeries(Surface3DSeries series) * Adds the \a series to the graph. * \sa GraphsNode::hasSeries() */ /*! * \qmlmethod void Surface3DNode::removeSeries(Surface3DSeries series) * Removes the \a series from the graph. * \sa GraphsNode::hasSeries() */ /*! * \qmlsignal Surface3DNode::axisXChanged(ValueAxis3D axis) * * This signal is emitted when axisX changes to \a axis. */ /*! * \qmlsignal Surface3DNode::axisYChanged(ValueAxis3D axis) * * This signal is emitted when axisY changes to \a axis. */ /*! * \qmlsignal Surface3DNode::axisZChanged(ValueAxis3D axis) * * This signal is emitted when axisZ changes to \a axis. */ /*! * \qmlsignal Surface3DNode::selectedSeriesChanged(Surface3DSeries series) * * This signal is emitted when selectedSeries changes to \a series. */ /*! * \qmlsignal Surface3DNode::flipHorizontalGridChanged(bool flip) * * This signal is emitted when flipHorizontalGrid changes to \a flip. */ QQuickGraphsSurfaceNode::QQuickGraphsSurfaceNode(QQuick3DNode *parent) : QQuickGraphsNode(parent) , m_flipHorizontalGrid(false) {} QQuickGraphsSurfaceNode::~QQuickGraphsSurfaceNode() {} void QQuickGraphsSurfaceNode::componentComplete() { const QString qmlData = QLatin1StringView(R"QML( import QtQuick; import QtGraphs; Surface3D{} )QML"); QQmlComponent *component = new QQmlComponent(qmlEngine(this), this); component->setData(qmlData.toUtf8(), QUrl()); m_graph.reset(qobject_cast(component->create())); setGraphParent(); QQuickGraphsNode::componentComplete(); //initialize components if (m_axisX) graphSurface()->setAxisX(static_cast(m_axisX)); if (m_axisY) graphSurface()->setAxisY(static_cast(m_axisY)); if (m_axisZ) graphSurface()->setAxisZ(static_cast(m_axisZ)); graphSurface()->setSelectionMode(m_selectionMode); graphSurface()->setFlipHorizontalGrid(m_flipHorizontalGrid); for (auto series : m_seriesList) graphSurface()->addSeries(static_cast(series)); //connect signals connect(graphSurface(), &QQuickGraphsSurface::axisXChanged, this, &QQuickGraphsSurfaceNode::axisXChanged); connect(graphSurface(), &QQuickGraphsSurface::axisYChanged, this, &QQuickGraphsSurfaceNode::axisYChanged); connect(graphSurface(), &QQuickGraphsSurface::axisZChanged, this, &QQuickGraphsSurfaceNode::axisZChanged); connect(graphSurface(), &QQuickGraphsSurface::flipHorizontalGridChanged, this, &QQuickGraphsSurfaceNode::flipHorizontalGridChanged); } QValue3DAxis *QQuickGraphsSurfaceNode::axisX() const { if (graphSurface()) return graphSurface()->axisX(); else return static_cast(m_axisX); } void QQuickGraphsSurfaceNode::setAxisX(QValue3DAxis *axis) { if (m_axisX == axis) return; m_axisX = axis; if (graphSurface()) graphSurface()->setAxisX(axis); } QValue3DAxis *QQuickGraphsSurfaceNode::axisY() const { if (graphSurface()) return graphSurface()->axisY(); else return static_cast(m_axisY); } void QQuickGraphsSurfaceNode::setAxisY(QValue3DAxis *axis) { if (m_axisY == axis) return; m_axisY = axis; if (graphSurface()) graphSurface()->setAxisY(axis); } QValue3DAxis *QQuickGraphsSurfaceNode::axisZ() const { if (graphSurface()) return graphSurface()->axisZ(); else return static_cast(m_axisZ); } void QQuickGraphsSurfaceNode::setAxisZ(QValue3DAxis *axis) { if (m_axisZ == axis) return; m_axisZ = axis; if (graphSurface()) graphSurface()->setAxisZ(axis); } QSurface3DSeries *QQuickGraphsSurfaceNode::selectedSeries() const { if (graphSurface()) return graphSurface()->selectedSeries(); else return nullptr; } void QQuickGraphsSurfaceNode::setSelectionMode(QtGraphs3D::SelectionFlags mode) { if (graphSurface()) graphSurface()->setSelectionMode(mode); else setSelectionMode(mode); } bool QQuickGraphsSurfaceNode::flipHorizontalGrid() const { if (graphSurface()) return graphSurface()->flipHorizontalGrid(); else return m_flipHorizontalGrid; } void QQuickGraphsSurfaceNode::setFlipHorizontalGrid(bool flip) { if (m_flipHorizontalGrid == flip) return; m_flipHorizontalGrid = flip; if (graphSurface()) graphSurface()->setFlipHorizontalGrid(flip); } QList QQuickGraphsSurfaceNode::surfaceSeriesList() { if (graphSurface()) { return graphSurface()->surfaceSeriesList(); } else { QList surfaceSeriesList; for (QAbstract3DSeries *abstractSeries : m_seriesList) { QSurface3DSeries *surfaceSeries = qobject_cast(abstractSeries); if (surfaceSeries) surfaceSeriesList.append(surfaceSeries); } return surfaceSeriesList; } } QQmlListProperty QQuickGraphsSurfaceNode::seriesList() { if (graphSurface()) { return graphSurface()->seriesList(); } else { return QQmlListProperty(this, this, &QQuickGraphsSurfaceNode::appendSeriesFunc, &QQuickGraphsSurfaceNode::countSeriesFunc, &QQuickGraphsSurfaceNode::atSeriesFunc, &QQuickGraphsSurfaceNode::clearSeriesFunc); } } void QQuickGraphsSurfaceNode::appendSeriesFunc(QQmlListProperty *list, QSurface3DSeries *series) { reinterpret_cast(list->data)->addSeries(series); } qsizetype QQuickGraphsSurfaceNode::countSeriesFunc(QQmlListProperty *list) { return reinterpret_cast(list->data)->surfaceSeriesList().size(); } QSurface3DSeries *QQuickGraphsSurfaceNode::atSeriesFunc(QQmlListProperty *list, qsizetype index) { return reinterpret_cast(list->data)->surfaceSeriesList().at(index); } void QQuickGraphsSurfaceNode::clearSeriesFunc(QQmlListProperty *list) { QQuickGraphsSurfaceNode *declSurfaceNode = reinterpret_cast( list->data); QList realList = declSurfaceNode->surfaceSeriesList(); qsizetype count = realList.size(); for (qsizetype i = 0; i < count; i++) declSurfaceNode->removeSeries(realList.at(i)); } void QQuickGraphsSurfaceNode::addSeries(QSurface3DSeries *series) { Q_ASSERT(series && series->type() == QAbstract3DSeries::SeriesType::Surface); if (graphSurface()) graphSurface()->addSeries(series); QQuickGraphsNode::addSeriesInternal(series); } void QQuickGraphsSurfaceNode::removeSeries(QSurface3DSeries *series) { if (graphSurface()) graphSurface()->removeSeries(series); QQuickGraphsNode::removeSeriesInternal(series); } void QQuickGraphsSurfaceNode::clearSelection() { if (graphSurface()) graphSurface()->clearSelection(); } bool QQuickGraphsSurfaceNode::doPicking(QPointF point) { if (graphSurface()) return graphSurface()->doPicking(point); else return false; } bool QQuickGraphsSurfaceNode::doRayPicking(const QVector3D &origin, const QVector3D &direction) { if (graphSurface()) return graphSurface()->doRayPicking(origin, direction); else return false; } /*! * \internal */ QQuickGraphsSurface *QQuickGraphsSurfaceNode::graphSurface() { return static_cast(m_graph.get()); } /*! * \internal */ const QQuickGraphsSurface *QQuickGraphsSurfaceNode::graphSurface() const { return static_cast(m_graph.get()); } QT_END_NAMESPACE