// Copyright (C) 2025 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "qcustom3ditem.h" #include "qquickgraphsnode_p.h" #include QT_BEGIN_NAMESPACE /*! * \qmltype GraphsNode * \inherits Node * \qmlabstract * \inqmlmodule QtGraphs * \ingroup graphs_qml_3D * \brief Base type for 3D graphs. * * The uncreatable base type for all 3D graph nodes in Qt Graphs. * * \sa BarsNode, ScatterNode, SurfaceNode, {Qt Graphs C++ Classes for 3D} */ /*! * \qmlproperty Graphs3D.SelectionMode GraphsNode::selectionMode * The active selection mode in the graph. * One of the \l graphs3d.selectionflag enum values. */ /*! * \qmlproperty GraphsTheme GraphsNode::theme * The active theme of the graph. * * \sa GraphsTheme */ /*! * \qmlproperty list GraphsNode::customItemList * * The list of \l{Custom3DItem} items added to the graph. The graph takes * ownership of the added items. */ /*! * \qmlproperty bool GraphsNode::polar * * If \c {true}, the horizontal axes are changed into polar axes. The x-axis * becomes the angular axis and the z-axis becomes the radial axis. * Polar mode is not available for bar graphs. * * Defaults to \c{false}. * * \sa radialLabelOffset */ /*! * \qmlproperty real GraphsNode::labelMargin * * This property specifies the margin for the placement of the axis labels. * * Negative values place the labels inside the plot-area while positive values * place them outside the plot-area. Label automatic rotation is disabled when * the value is negative. Defaults to \c 0.1 * * \sa QAbstract3DAxis::labelAutoAngle * */ /*! * \qmlproperty real GraphsNode::radialLabelOffset * * This property specifies the normalized horizontal offset for the axis labels * of the radial polar axis. The value \c 0.0 indicates that the labels should * be drawn next to the 0-angle angular axis grid line. The value \c 1.0 * indicates that the labels are drawn in their usual place at the edge of the * graph background. This property is ignored if the polar property value is * \c{false}. Defaults to \c 1.0. * * \sa polar */ /*! * \qmlmethod void GraphsNode::clearSelection() * Clears selection from all attached series. */ /*! * \qmlmethod bool GraphsNode::hasSeries(Abstract3DSeries series) * Returns whether the \a series has already been added to the graph. */ /*! * \qmlmethod qsizetype GraphsNode::addCustomItem(Custom3DItem item) * * Adds a Custom3DItem \a item to the graph. Graph takes ownership of the added * item. * * \return index to the added item if add was successful, -1 if trying to add a * null item, and index of the item if trying to add an already added item. * * \sa removeCustomItems(), removeCustomItem(), removeCustomItemAt() */ /*! * \qmlmethod void GraphsNode::removeCustomItems() * * Removes all custom items. Deletes the resources allocated to them. */ /*! * \qmlmethod void GraphsNode::removeCustomItem(Custom3DItem item) * * Removes the custom \a {item}. Deletes the resources allocated to it. */ /*! * \qmlmethod void GraphsNode::removeCustomItemAt(vector3d position) * * Removes all custom items at \a {position}. Deletes the resources allocated to them. */ /*! * \qmlmethod void GraphsNode::releaseCustomItem(Custom3DItem item) * * Gets ownership of \a item back and removes the \a item from the graph. * * \note If the same item is added back to the graph, the texture file needs to * be re-set. * * \sa Custom3DItem::textureFile */ /*! * \qmlmethod void GraphsNode::doPicking(QPoint point) * * Performs picking using view coordinates from \a point * on the elements of the graph, selecting the first item hit. * Default input handling performs this upon receiving the onTapped event. * * \sa selectedElement */ /*! * \qmlmethod void GraphsNode::doRayPicking(QVector3D origin, QVector3D direction) * * Performs picking starting from \a origin and in \a direction * on the elements of the graph, selecting the first item hit. * * \sa selectedElement */ /*! * \qmlmethod int GraphsNode::selectedLabelIndex() * * Can be used to query the index of the selected label after receiving * \c selectedElementChanged signal with any label type. Selection is valid * until the next \c selectedElementChanged signal. * * \return index of the selected label, or -1. * * \sa selectedElement */ /*! * \qmlmethod Abstract3DAxis GraphsNode::selectedAxis() * * Can be used to get the selected axis after receiving \c selectedElementChanged * signal with any label type. Selection is valid until the next * \c selectedElementChanged signal. * * \return the selected axis, or null. * * \sa selectedElement */ /*! * \qmlmethod qsizetype GraphsNode::selectedCustomItemIndex() * * Can be used to query the index of the selected custom item after receiving * \c selectedElementChanged signal with * \l{QtGraphs3D::ElementType::CustomItem}{ElementType.CustomItem} type. * Selection is valid until the next \c selectedElementChanged signal. * * \return index of the selected custom item, or -1. * * \sa selectedElement */ /*! * \qmlmethod Custom3DItem GraphsNode::selectedCustomItem() * * Can be used to get the selected custom item after receiving * \c selectedElementChanged signal with * \l{QtGraphs3D::ElementType::CustomItem}{ElementType.CustomItem} type. * Ownership of the item remains with the graph. * Selection is valid until the next \c selectedElementChanged signal. * * \return the selected custom item, or null. * * \sa selectedElement */ /*! * \qmlproperty Graphs3D.ElementType GraphsNode::selectedElement * \readonly * * The element selected in the graph. * * This property can be used to query the selected element type. * The type is valid until a new selection is made in the graph and the * \c selectedElementChanged signal is emitted. * * The signal can be used for example for implementing customized input * handling, as demonstrated by the \l {Axis Handling} example. * * \sa selectedLabelIndex(), selectedAxis(), selectedCustomItemIndex(), * selectedCustomItem(), BarsNode::selectedSeries, ScatterNode::selectedSeries, * Scene3D::selectionQueryPosition, Graphs3D.ElementType */ /*! * \qmlproperty real GraphsNode::aspectRatio * * The ratio of the graph scaling between the longest axis on the horizontal * plane and the y-axis. Defaults to \c{2.0}. * * \note Has no effect on Bars3D. * * \sa horizontalAspectRatio */ /*! * \qmlproperty real GraphsNode::horizontalAspectRatio * * The ratio of the graph scaling between the x-axis and z-axis. * The value of \c 0.0 indicates automatic scaling according to axis ranges. * Defaults to \c{0.0}. * * \note Has no effect on BarsNode, which handles scaling on the horizontal plane * via the \l{BarsNode::barThickness}{barThickness} and * \l{BarsNode::barSpacing}{barSpacing} properties. Polar graphs also ignore this * property. * * \sa aspectRatio, polar, BarsNode::barThickness, BarsNode::barSpacing */ /*! * \qmlproperty Graphs3D.OptimizationHint GraphsNode::optimizationHint * * \brief Specifies whether the default or legacy mode is used for rendering optimization. * * The default mode uses instanced rendering, and provides the full feature set * at the best level of performance on most systems. The static mode optimizes * graph rendering and is ideal for large non-changing data sets. It is slower * with dynamic data changes and item rotations. Selection is not optimized, so * using the static mode with massive data sets is not advisable. Legacy mode * renders all items in th graph individually, without instancing. It should be * used only if default mode does not work, that is the same as if the target * system does not support instancing. Defaults to * \l{QtGraphs3D::OptimizationHint::Default}{Default}. * * \note On some environments, large graphs using static optimization may not * render, because all of the items are rendered using a single draw call, and * different graphics drivers support different maximum vertice counts per call. * This is mostly an issue on 32bit and OpenGL ES2 platforms. To work around * this issue, choose an item mesh with a low vertex count or use the point mesh. * * \sa Abstract3DSeries::mesh, Graphs3D.OptimizationHint */ /*! * \qmlproperty locale GraphsNode::locale * * Sets the locale used for formatting various numeric labels. * Defaults to the \c{"C"} locale. * * \sa Value3DAxis::labelFormat */ /*! * \qmlproperty vector3d GraphsNode::queriedGraphPosition * \readonly * * This read-only property contains the latest graph position values along each * axis queried using Scene3D::graphPositionQuery. The values are normalized to * range \c{[-1, 1]}. If the queried position was outside the graph bounds, the * values will not reflect the real position, but will instead be some undefined * position outside the range \c{[-1, 1]}. The value will be undefined until a * query is made. * * There is no single correct 3D coordinate to match a particular screen * position, so to be consistent, the queries are always done against the inner * sides of an invisible box surrounding the graph. * * \note Bar graphs only allow querying graph position at the graph floor level, * so the y-value is always zero for bar graphs and valid queries can be only * made at screen positions that contain the floor of the graph. * * \sa Scene3D::graphPositionQuery */ /*! * \qmlproperty real GraphsNode::margin * * The absolute value used for the space left between the edge of the * plottable graph area and the edge of the graph background. * * If the margin value is negative, the margins are determined automatically and * can vary according to the size of the items in the series and the type of the * graph. The value is interpreted as a fraction of the y-axis range if the * graph aspect ratios have not been changed from the default values. * Defaults to \c{-1.0}. * * \note Setting a smaller margin for a scatter graph than the automatically * determined margin can cause the scatter items at the edges of the graph to * overlap with the graph background. * * \note On scatter and surface graphs, if the margin is small in comparison to * the axis label size, the positions of the edge labels of the axes are * adjusted to avoid overlap with the edge labels of the neighboring axes. */ /*! * \qmlproperty Graphs3D.GridLineType GraphsNode::gridLineType * * Defines whether the grid lines type is \c Graphs3D.GridLineType.Shader or * \c Graphs3D.GridLineType.Geometry. * * This value affects all grid lines. * * \sa Graphs3D.GridLineType */ QQuickGraphsNode::QQuickGraphsNode(QQuick3DNode *parent) : QQuick3DNode(parent) , m_selectionMode(QtGraphs3D::SelectionFlag::Item) , m_aspectRatio(2.0) , m_optimizationHint(QtGraphs3D::OptimizationHint::Default) , m_polar(false) , m_labelMargin(.1f) , m_radialLabelOffset(1.0f) , m_horizontalAspectRatio(0.0) , m_locale(QLocale::c()) , m_margin(-1.0) , m_gridLineType(QtGraphs3D::GridLineType::Geometry) { QGraphsTheme *theme = new QGraphsTheme(this); setTheme(theme); } QQuickGraphsNode::~QQuickGraphsNode() {} void QQuickGraphsNode::componentComplete() { QQuick3DNode::componentComplete(); //initialize properties m_graph->setTheme(m_theme); m_graph->setAspectRatio(m_aspectRatio); m_graph->setOptimizationHint(m_optimizationHint); m_graph->setPolar(m_polar); m_graph->setLabelMargin(m_labelMargin); m_graph->setRadialLabelOffset(m_radialLabelOffset); m_graph->setHorizontalAspectRatio(m_horizontalAspectRatio); m_graph->setLocale(m_locale); m_graph->setMargin(m_margin); m_graph->setGridLineType(m_gridLineType); for (auto item : m_customItemList) m_graph->addCustomItem(item); //connect signals connect(m_graph.get(), &QQuickGraphsItem::selectionModeChanged, this, &QQuickGraphsNode::selectionModeChanged); connect(m_graph.get(), &QQuickGraphsItem::themeChanged, this, &QQuickGraphsNode::themeChanged); connect(m_graph.get(), &QQuickGraphsItem::aspectRatioChanged, this, &QQuickGraphsNode::aspectRatioChanged); connect(m_graph.get(), &QQuickGraphsItem::optimizationHintChanged, this, &QQuickGraphsNode::optimizationHintChanged); connect(m_graph.get(), &QQuickGraphsItem::polarChanged, this, &QQuickGraphsNode::polarChanged); connect(m_graph.get(), &QQuickGraphsItem::labelMarginChanged, this, &QQuickGraphsNode::labelMarginChanged); connect(m_graph.get(), &QQuickGraphsItem::radialLabelOffsetChanged, this, &QQuickGraphsNode::radialLabelOffsetChanged); connect(m_graph.get(), &QQuickGraphsItem::horizontalAspectRatioChanged, this, &QQuickGraphsNode::horizontalAspectRatioChanged); connect(m_graph.get(), &QQuickGraphsItem::localeChanged, this, &QQuickGraphsNode::localeChanged); connect(m_graph.get(), &QQuickGraphsItem::queriedGraphPositionChanged, this, &QQuickGraphsNode::queriedGraphPositionChanged); connect(m_graph.get(), &QQuickGraphsItem::marginChanged, this, &QQuickGraphsNode::marginChanged); connect(m_graph.get(), &QQuickGraphsItem::gridLineTypeChanged, this, &QQuickGraphsNode::gridLineTypeChanged); } void QQuickGraphsNode::setGraphParent() { if (m_graph) m_graph->setParentNode(this); } void QQuickGraphsNode::setSelectionMode(QtGraphs3D::SelectionFlags selectionMode) { if (m_selectionMode == selectionMode) return; m_selectionMode = selectionMode; if (m_graph) m_graph->setSelectionMode(m_selectionMode); } QtGraphs3D::SelectionFlags QQuickGraphsNode::selectionMode() const { if (m_graph) return m_graph->selectionMode(); else return m_selectionMode; } void QQuickGraphsNode::setTheme(QGraphsTheme *theme) { if (m_theme == theme) return; m_theme = theme; if (m_graph) m_graph->setTheme(m_theme); } QGraphsTheme *QQuickGraphsNode::theme() const { if (m_graph) return m_graph->theme(); else return m_theme; } QQmlListProperty QQuickGraphsNode::customItemList() const { if (m_graph) return m_graph->customItemList(); else return QQmlListProperty(); } QtGraphs3D::ElementType QQuickGraphsNode::selectedElement() const { if (m_graph) return m_graph->selectedElement(); else return QtGraphs3D::ElementType::None; } void QQuickGraphsNode::setAspectRatio(qreal aspectRatio) { if (m_aspectRatio == aspectRatio) return; m_aspectRatio = aspectRatio; if (m_graph) m_graph->setAspectRatio(aspectRatio); } qreal QQuickGraphsNode::aspectRatio() const { if (m_graph) return m_graph->aspectRatio(); else return m_aspectRatio; } void QQuickGraphsNode::setOptimizationHint(QtGraphs3D::OptimizationHint optimizationHint) { if (m_optimizationHint == optimizationHint) return; m_optimizationHint = optimizationHint; if (m_graph) m_graph->setOptimizationHint(optimizationHint); } QtGraphs3D::OptimizationHint QQuickGraphsNode::optimizationHint() const { if (m_graph) return m_graph->optimizationHint(); else return m_optimizationHint; } void QQuickGraphsNode::setPolar(bool enabled) { if (m_polar == enabled) return; m_polar = enabled; if (m_graph) m_graph->setPolar(enabled); } bool QQuickGraphsNode::isPolar() const { if (m_graph) return m_graph->isPolar(); else return m_polar; } void QQuickGraphsNode::setLabelMargin(float labelMargin) { if (m_labelMargin == labelMargin) return; m_labelMargin = labelMargin; if (m_graph) m_graph->setLabelMargin(labelMargin); } float QQuickGraphsNode::labelMargin() const { if (m_graph) return m_graph->labelMargin(); else return m_labelMargin; } void QQuickGraphsNode::setRadialLabelOffset(float radialLabelOffset) { if (m_radialLabelOffset == radialLabelOffset) return; m_radialLabelOffset = radialLabelOffset; if (m_graph) m_graph->setRadialLabelOffset(radialLabelOffset); } float QQuickGraphsNode::radialLabelOffset() const { if (m_graph) return m_graph->radialLabelOffset(); else return m_radialLabelOffset; } void QQuickGraphsNode::setHorizontalAspectRatio(qreal horizontalAspectRatio) { if (m_horizontalAspectRatio == horizontalAspectRatio) return; m_horizontalAspectRatio = horizontalAspectRatio; if (m_graph) m_graph->setHorizontalAspectRatio(horizontalAspectRatio); } qreal QQuickGraphsNode::horizontalAspectRatio() const { if (m_graph) return m_graph->horizontalAspectRatio(); else return m_horizontalAspectRatio; } void QQuickGraphsNode::setLocale(QLocale locale) { if (m_locale == locale) return; m_locale = locale; if (m_graph) m_graph->setLocale(locale); } QLocale QQuickGraphsNode::locale() const { if (m_graph) return m_graph->locale(); else return m_locale; } QVector3D QQuickGraphsNode::queriedGraphPosition() const { if (m_graph) return m_graph->queriedGraphPosition(); else return QVector3D(); } qreal QQuickGraphsNode::margin() const { if (m_graph) return m_graph->margin(); else return m_margin; } void QQuickGraphsNode::setMargin(qreal margin) { if (m_margin == margin) return; m_margin = margin; if (m_graph) m_graph->setMargin(margin); } QtGraphs3D::GridLineType QQuickGraphsNode::gridLineType() const { if (m_graph) return m_graph->gridLineType(); else return m_gridLineType; } void QQuickGraphsNode::setGridLineType(const QtGraphs3D::GridLineType &gridLineType) { if (m_gridLineType == gridLineType) return; m_gridLineType = gridLineType; if (m_graph) m_graph->setGridLineType(gridLineType); } bool QQuickGraphsNode::hasSeries(QAbstract3DSeries *series) { if (m_graph) return m_graph->hasSeries(series); else return m_seriesList.contains(series); } void QQuickGraphsNode::addSeriesInternal(QAbstract3DSeries *series) { insertSeries(m_seriesList.size(), series); } void QQuickGraphsNode::insertSeries(qsizetype index, QAbstract3DSeries *series) { if (series) { if (m_seriesList.contains(series)) { qsizetype oldIndex = m_seriesList.indexOf(series); if (index != oldIndex) { m_seriesList.removeOne(series); if (oldIndex < index) index--; m_seriesList.insert(index, series); } } else { m_seriesList.insert(index, series); } } } void QQuickGraphsNode::removeSeriesInternal(QAbstract3DSeries *series) { if (series) m_seriesList.removeAll(series); } QList QQuickGraphsNode::seriesList() { return m_seriesList; } qsizetype QQuickGraphsNode::addCustomItem(QCustom3DItem *item) { if (m_graph) m_graph->addCustomItem(item); m_customItemList.append(item); return m_customItemList.size() - 1; } void QQuickGraphsNode::removeCustomItems() { if (m_graph) m_graph->removeCustomItems(); m_customItemList.clear(); } void QQuickGraphsNode::removeCustomItem(QCustom3DItem *item) { if (m_graph) m_graph->deleteCustomItem(item); m_customItemList.removeOne(item); } void QQuickGraphsNode::removeCustomItemAt(QVector3D position) { if (m_graph) m_graph->removeCustomItemAt(position); for (QCustom3DItem *item : m_customItemList) { if (item->position() == position) m_customItemList.removeOne(item); } } void QQuickGraphsNode::releaseCustomItem(QCustom3DItem *item) { if (m_graph) m_graph->releaseCustomItem(item); m_customItemList.removeOne(item); } int QQuickGraphsNode::selectedLabelIndex() const { if (m_graph) return selectedLabelIndex(); else return -1; } QAbstract3DAxis *QQuickGraphsNode::selectedAxis() const { if (m_graph) return m_graph->selectedAxis(); return nullptr; } qsizetype QQuickGraphsNode::selectedCustomItemIndex() const { if (m_graph) return m_graph->selectedCustomItemIndex(); else return -1; } QCustom3DItem *QQuickGraphsNode::selectedCustomItem() const { if (m_graph) return m_graph->selectedCustomItem(); else return nullptr; } QT_END_NAMESPACE