Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #include "qabstract3dgraph.h"
++ #include "qabstract3dgraph_p.h"
++ #include "abstract3dcontroller_p.h"
++ #include "qabstract3dinputhandler_p.h"
++ #include "q3dscene_p.h"
++ #include "qutils.h"
++ #include "utils_p.h"
++
++ #include <QtGui/QGuiApplication>
++ #include <QtGui/QOpenGLContext>
++ #include <QtOpenGL/QOpenGLPaintDevice>
++ #include <QtGui/QPainter>
++ #include <QtOpenGL/QOpenGLFramebufferObject>
++ #include <QtGui/QOffscreenSurface>
++ #if defined(Q_OS_MACOS)
++ #include <qpa/qplatformwindow_p.h>
++ #endif
++
++ QT_BEGIN_NAMESPACE
++
++ /*!
++ * \class QAbstract3DGraph
++ * \inmodule QtDataVisualization
++ * \brief The QAbstract3DGraph class provides a window and render loop for graphs.
++ * \since QtDataVisualization 1.0
++ *
++ * This class subclasses a QWindow and provides render loop for graphs inheriting it.
++ *
++ * You should not need to use this class directly, but one of its subclasses instead.
++ *
++ * Anti-aliasing is turned on by default on C++, except in OpenGL ES2
++ * environments, where anti-aliasing is not supported by Qt Data Visualization.
++ * To specify non-default anti-aliasing for a graph, give a custom surface format as
++ * a constructor parameter. You can use the convenience function \c qDefaultSurfaceFormat()
++ * to create the surface format object.
++ *
++ * \note QAbstract3DGraph sets window flag \c Qt::FramelessWindowHint on by default. If you want to display
++ * graph windows as standalone windows with regular window frame, clear this flag after constructing
++ * the graph. For example:
++ *
++ * \code
++ * Q3DBars *graphWindow = new Q3DBars;
++ * graphWindow->setFlags(graphWindow->flags() ^ Qt::FramelessWindowHint);
++ * \endcode
++ *
++ * \sa Q3DBars, Q3DScatter, Q3DSurface, {Qt Data Visualization C++ Classes}
++ */
++
++ /*!
++ \enum QAbstract3DGraph::SelectionFlag
++
++ Item selection modes. Values of this enumeration can be combined with OR operator.
++
++ \value SelectionNone
++ Selection mode disabled.
++ \value SelectionItem
++ Selection highlights a single item.
++ \value SelectionRow
++ Selection highlights a single row.
++ \value SelectionItemAndRow
++ Combination flag for highlighting both item and row with different colors.
++ \value SelectionColumn
++ Selection highlights a single column.
++ \value SelectionItemAndColumn
++ Combination flag for highlighting both item and column with different colors.
++ \value SelectionRowAndColumn
++ Combination flag for highlighting both row and column.
++ \value SelectionItemRowAndColumn
++ Combination flag for highlighting item, row, and column.
++ \value SelectionSlice
++ Setting this mode flag indicates that the graph should take care of the slice view handling
++ automatically. If you wish to control the slice view yourself via Q3DScene, do not set this
++ flag. When setting this mode flag, either \c SelectionRow or \c SelectionColumn must also
++ be set, but not both. Slicing is supported by Q3DBars and Q3DSurface only.
++ When this flag is set, slice mode is entered in the following situations:
++ \list
++ \li When selection is changed explicitly via series API to a visible item
++ \li When selection is changed by clicking on the graph
++ \li When the selection mode changes and the selected item is visible
++ \endlist
++ \value SelectionMultiSeries
++ Setting this mode means that items for all series at same position are highlighted, instead
++ of just the selected item. The actual selection in the other series doesn't change.
++ When setting this mode flag, one or more of the basic selection flags (\c {SelectionItem},
++ \c {SelectionRow}, or \c SelectionColumn) must also be set.
++ Multi-series selection is not supported for Q3DScatter.
++ */
++
++ /*!
++ \enum QAbstract3DGraph::ShadowQuality
++
++ Quality of shadows.
++
++ \value ShadowQualityNone
++ Shadows are disabled.
++ \value ShadowQualityLow
++ Shadows are rendered in low quality.
++ \value ShadowQualityMedium
++ Shadows are rendered in medium quality.
++ \value ShadowQualityHigh
++ Shadows are rendered in high quality.
++ \value ShadowQualitySoftLow
++ Shadows are rendered in low quality with softened edges.
++ \value ShadowQualitySoftMedium
++ Shadows are rendered in medium quality with softened edges.
++ \value ShadowQualitySoftHigh
++ Shadows are rendered in high quality with softened edges.
++ */
++
++ /*!
++ \enum QAbstract3DGraph::ElementType
++ \since QtDataVisualization 1.1
++
++ Type of an element in the graph.
++
++ \value ElementNone
++ No defined element.
++ \value ElementSeries
++ A series (that is, an item in a series).
++ \value ElementAxisXLabel
++ The x-axis label.
++ \value ElementAxisYLabel
++ The y-axis label.
++ \value ElementAxisZLabel
++ The z-axis label.
++ \value ElementCustomItem
++ A custom item.
++ */
++
++ /*!
++ \enum QAbstract3DGraph::OptimizationHint
++ \since Qt Data Visualization 1.1
++
++ The optimization hint for rendering.
++
++ \value OptimizationDefault
++ Provides the full feature set at a reasonable performance.
++ \value OptimizationStatic
++ Optimizes the rendering of static data sets at the expense of some features.
++ */
++
++ /*!
++ * \internal
++ */
++ QAbstract3DGraph::QAbstract3DGraph(QAbstract3DGraphPrivate *d, const QSurfaceFormat *format,
++ QWindow *parent)
++ : QWindow(parent),
++ d_ptr(d)
++ {
++ qRegisterMetaType<QAbstract3DGraph::ShadowQuality>("QAbstract3DGraph::ShadowQuality");
++ qRegisterMetaType<QAbstract3DGraph::ElementType>("QAbstract3DGraph::ElementType");
++
++ // Default to frameless window, as typically graphs are not toplevel
++ setFlags(flags() | Qt::FramelessWindowHint);
++
++ QSurfaceFormat surfaceFormat;
++ if (format) {
++ surfaceFormat = *format;
++ // Make sure renderable type is correct
++ surfaceFormat.setRenderableType(QSurfaceFormat::DefaultRenderableType);
++ } else {
++ surfaceFormat = qDefaultSurfaceFormat(true);
++ }
++
++ d_ptr->m_context = new QOpenGLContext(this);
++ setSurfaceType(QWindow::OpenGLSurface);
++ setFormat(surfaceFormat);
++
++ create();
++
++ d_ptr->m_context->setFormat(requestedFormat());
++ d_ptr->m_context->create();
++
++ QOffscreenSurface tempSurface;
++ tempSurface.setFormat(d_ptr->m_context->format());
++ tempSurface.create();
++
++ bool makeSuccess = d_ptr->m_context->makeCurrent(&tempSurface);
++
++ // If we fail to get context, just abort
++ if (!makeSuccess || !QOpenGLContext::currentContext()) {
++ tempSurface.destroy();
++ return;
++ }
++
++ initializeOpenGLFunctions();
++
++ const GLubyte *shaderVersion = glGetString(GL_SHADING_LANGUAGE_VERSION);
++ #ifndef QT_NO_DEBUG
++ const GLubyte *openGLVersion = glGetString(GL_VERSION);
++ qDebug() << "OpenGL version:" << (const char *)openGLVersion;
++ qDebug() << "GLSL version:" << (const char *)shaderVersion;
++ #endif
++
++ if (!Utils::isOpenGLES()) {
++ // If we have real OpenGL, GLSL version must be 1.2 or over. Quit if not.
++ QStringList splitversionstr =
++ QString::fromLatin1((const char *)shaderVersion).split(QChar::fromLatin1(' '));
++ if (splitversionstr[0].toFloat() < 1.2)
++ qFatal("GLSL version must be 1.20 or higher. Try installing latest display drivers.");
++ }
++
++ d_ptr->m_initialized = true;
++ d_ptr->m_context->makeCurrent(this);
++
++ d_ptr->renderLater();
++
++ #if defined(Q_OS_MACOS)
++ // Enable touch events for Mac touchpads
++ if (auto *cocoaWindow = nativeInterface<QNativeInterface::Private::QCocoaWindow>())
++ cocoaWindow->enableTrackpadTouchDelivery(true);
++ #endif
++ }
++
++ /*!
++ * Destroys QAbstract3DGraph.
++ */
++ QAbstract3DGraph::~QAbstract3DGraph()
++ {
++ }
++
++ /*!
++ * Adds the given \a inputHandler to the graph. The input handlers added via addInputHandler
++ * are not taken in to use directly. Only the ownership of the \a inputHandler is given to the graph.
++ * The \a inputHandler must not be null or already added to another graph.
++ *
++ * \sa releaseInputHandler(), setActiveInputHandler()
++ */
++ void QAbstract3DGraph::addInputHandler(QAbstract3DInputHandler *inputHandler)
++ {
++ d_ptr->m_visualController->addInputHandler(inputHandler);
++ }
++
++ /*!
++ * Releases the ownership of the \a inputHandler back to the caller, if it was added to this graph.
++ * If the released \a inputHandler is in use there will be no input handler active after this call.
++ *
++ * If the default input handler is released and added back later, it behaves as any other input handler would.
++ *
++ * \sa addInputHandler(), setActiveInputHandler()
++ */
++ void QAbstract3DGraph::releaseInputHandler(QAbstract3DInputHandler *inputHandler)
++ {
++ d_ptr->m_visualController->releaseInputHandler(inputHandler);
++ }
++
++ /*!
++ * \property QAbstract3DGraph::activeInputHandler
++ *
++ * \brief The active input handler used in the graph.
++ */
++
++ /*!
++ * Sets \a inputHandler as the active input handler used in the graph.
++ * Implicitly calls addInputHandler() to transfer ownership of \a inputHandler
++ * to this graph.
++ *
++ * If \a inputHandler is null, no input handler will be active after this call.
++ *
++ * \sa addInputHandler(), releaseInputHandler()
++ */
++ void QAbstract3DGraph::setActiveInputHandler(QAbstract3DInputHandler *inputHandler)
++ {
++ d_ptr->m_visualController->setActiveInputHandler(inputHandler);
++ }
++
++ QAbstract3DInputHandler *QAbstract3DGraph::activeInputHandler() const
++ {
++ return d_ptr->m_visualController->activeInputHandler();
++ }
++
++ /*!
++ * Returns the list of all added input handlers.
++ *
++ * \sa addInputHandler()
++ */
++ QList<QAbstract3DInputHandler *> QAbstract3DGraph::inputHandlers() const
++ {
++ return d_ptr->m_visualController->inputHandlers();
++ }
++
++ /*!
++ * Adds the given \a theme to the graph. The themes added via addTheme are not taken in to use
++ * directly. Only the ownership of the theme is given to the graph.
++ * The \a theme must not be null or already added to another graph.
++ *
++ * \sa releaseTheme(), setActiveTheme()
++ */
++ void QAbstract3DGraph::addTheme(Q3DTheme *theme)
++ {
++ d_ptr->m_visualController->addTheme(theme);
++ }
++
++ /*!
++ * Releases the ownership of the \a theme back to the caller, if it was added to this graph.
++ * If the released \a theme is in use, a new default theme will be created and set active.
++ *
++ * If the default theme is released and added back later, it behaves as any other theme would.
++ *
++ * \sa addTheme(), setActiveTheme()
++ */
++ void QAbstract3DGraph::releaseTheme(Q3DTheme *theme)
++ {
++ d_ptr->m_visualController->releaseTheme(theme);
++ }
++
++ /*!
++ * \property QAbstract3DGraph::activeTheme
++ *
++ * \brief The active theme of the graph.
++ */
++
++ /*!
++ * Sets \a theme as the active theme to be used for the graph. Implicitly calls
++ * addTheme() to transfer the ownership of the theme to this graph.
++ *
++ * If \a theme is null, a temporary default theme is created. This temporary theme is destroyed
++ * if any theme is explicitly set later.
++ * Properties of the theme can be modified even after setting it, and the modifications take
++ * effect immediately.
++ */
++ void QAbstract3DGraph::setActiveTheme(Q3DTheme *theme)
++ {
++ d_ptr->m_visualController->setActiveTheme(theme);
++ }
++
++
++ Q3DTheme *QAbstract3DGraph::activeTheme() const
++ {
++ return d_ptr->m_visualController->activeTheme();
++ }
++
++ /*!
++ * Returns the list of all added themes.
++ *
++ * \sa addTheme()
++ */
++ QList<Q3DTheme *> QAbstract3DGraph::themes() const
++ {
++ return d_ptr->m_visualController->themes();
++ }
++
++ /*!
++ * \property QAbstract3DGraph::selectionMode
++ *
++ * \brief Item selection mode.
++ *
++ * A combination of SelectionFlags. By default, \c SelectionItem.
++ * Different graph types support different selection modes.
++ *
++ * \sa SelectionFlags
++ */
++ void QAbstract3DGraph::setSelectionMode(SelectionFlags mode)
++ {
++ d_ptr->m_visualController->setSelectionMode(mode);
++ }
++
++ QAbstract3DGraph::SelectionFlags QAbstract3DGraph::selectionMode() const
++ {
++ return d_ptr->m_visualController->selectionMode();
++ }
++
++ /*!
++ * \property QAbstract3DGraph::shadowQuality
++ *
++ * \brief The quality of the shadow.
++ *
++ * One of the ShadowQuality enum values. By default, \c ShadowQualityMedium.
++ *
++ * \note If setting the shadow quality to a certain level fails, the level is lowered
++ * until it is successfully set. The \c shadowQualityChanged signal is emitted each time
++ * a change is made.
++ *
++ * \sa ShadowQuality
++ */
++ void QAbstract3DGraph::setShadowQuality(ShadowQuality quality)
++ {
++ d_ptr->m_visualController->setShadowQuality(quality);
++ }
++
++ QAbstract3DGraph::ShadowQuality QAbstract3DGraph::shadowQuality() const
++ {
++ return d_ptr->m_visualController->shadowQuality();
++ }
++
++ /*!
++ * Returns \c true if shadows are supported with the current configuration.
++ * OpenGL ES2 configurations do not support shadows.
++ */
++ bool QAbstract3DGraph::shadowsSupported() const
++ {
++ return d_ptr->m_visualController->shadowsSupported();
++ }
++
++ /*!
++ * \property QAbstract3DGraph::scene
++ *
++ * \brief The Q3DScene pointer that can be used to manipulate the scene and
++ * access the scene elements, such as the active camera.
++ *
++ * This property is read-only.
++ */
++ Q3DScene *QAbstract3DGraph::scene() const
++ {
++ return d_ptr->m_visualController->scene();
++ }
++
++ /*!
++ * Clears selection from all attached series.
++ */
++ void QAbstract3DGraph::clearSelection()
++ {
++ d_ptr->m_visualController->clearSelection();
++ }
++
++ /*!
++ * Returns whether the \a series has already been added to the graph.
++ *
++ * \since 6.3
++ */
++ bool QAbstract3DGraph::hasSeries(QAbstract3DSeries *series) const
++ {
++ return d_ptr->m_visualController->hasSeries(series);
++ }
++
++ /*!
++ * Adds a QCustom3DItem \a item to the graph. Graph takes ownership of the added item.
++ *
++ * Returns the index to the added item if the add operation was successful, -1
++ * if trying to add a null item, and the index of the item if trying to add an
++ * already added item.
++ *
++ * Items are rendered in the order they have been inserted. The rendering order needs to
++ * be taken into account when having solid and transparent items.
++ *
++ * \sa removeCustomItems(), removeCustomItem(), removeCustomItemAt(), customItems()
++ *
++ * \since QtDataVisualization 1.1
++ */
++ int QAbstract3DGraph::addCustomItem(QCustom3DItem *item)
++ {
++ return d_ptr->m_visualController->addCustomItem(item);
++ }
++
++ /*!
++ * Removes all custom items. Deletes the resources allocated to them.
++ *
++ * \since QtDataVisualization 1.1
++ */
++ void QAbstract3DGraph::removeCustomItems()
++ {
++ d_ptr->m_visualController->deleteCustomItems();
++ }
++
++ /*!
++ * Removes the custom \a {item}. Deletes the resources allocated to it.
++ *
++ * \since QtDataVisualization 1.1
++ */
++ void QAbstract3DGraph::removeCustomItem(QCustom3DItem *item)
++ {
++ d_ptr->m_visualController->deleteCustomItem(item);
++ }
++
++ /*!
++ * Removes all custom items at \a {position}. Deletes the resources allocated to them.
++ *
++ * \since QtDataVisualization 1.1
++ */
++ void QAbstract3DGraph::removeCustomItemAt(const QVector3D &position)
++ {
++ d_ptr->m_visualController->deleteCustomItem(position);
++ }
++
++ /*!
++ * Gets ownership of given \a item back and removes the \a item from the graph.
++ *
++ * \since QtDataVisualization 1.1
++ *
++ * \note If the same item is added back to the graph, the texture or the texture file needs to be
++ * re-set.
++ *
++ * \sa QCustom3DItem::setTextureImage(), QCustom3DItem::setTextureFile()
++ */
++ void QAbstract3DGraph::releaseCustomItem(QCustom3DItem *item)
++ {
++ return d_ptr->m_visualController->releaseCustomItem(item);
++ }
++
++ /*!
++ * Returns the list of all added custom items.
++ * \since QtDataVisualization 1.2
++ * \sa addCustomItem()
++ */
++ QList<QCustom3DItem *> QAbstract3DGraph::customItems() const
++ {
++ return d_ptr->m_visualController->customItems();
++ }
++
++ /*!
++ * 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.
++ *
++ * Returns the index of the selected label, or -1.
++ *
++ * \since QtDataVisualization 1.1
++ *
++ * \sa selectedElement
++ */
++ int QAbstract3DGraph::selectedLabelIndex() const
++ {
++ return d_ptr->m_visualController->selectedLabelIndex();
++ }
++
++ /*!
++ * 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.
++ *
++ * Returns the pointer to the selected axis, or null.
++ *
++ * \since QtDataVisualization 1.1
++ *
++ * \sa selectedElement
++ */
++ QAbstract3DAxis *QAbstract3DGraph::selectedAxis() const
++ {
++ return d_ptr->m_visualController->selectedAxis();
++ }
++
++ /*!
++ * Can be used to query the index of the selected custom item after receiving \c selectedElementChanged
++ * signal with QAbstract3DGraph::ElementCustomItem type. Selection is valid until the next
++ * \c selectedElementChanged signal.
++ *
++ * Returns the index of the selected custom item, or -1.
++ *
++ * \since QtDataVisualization 1.1
++ *
++ * \sa selectedElement
++ */
++ int QAbstract3DGraph::selectedCustomItemIndex() const
++ {
++ return d_ptr->m_visualController->selectedCustomItemIndex();
++ }
++
++ /*!
++ * Can be used to get the selected custom item after receiving \c selectedElementChanged signal with
++ * QAbstract3DGraph::ElementCustomItem type. Ownership of the item remains with the graph.
++ * Selection is valid until the next \c selectedElementChanged signal.
++ *
++ * Returns the pointer to the selected custom item, or null.
++ *
++ * \since QtDataVisualization 1.1
++ *
++ * \sa selectedElement
++ */
++ QCustom3DItem *QAbstract3DGraph::selectedCustomItem() const
++ {
++ return d_ptr->m_visualController->selectedCustomItem();
++ }
++
++ /*!
++ * \property QAbstract3DGraph::selectedElement
++ *
++ * \brief 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 custom input handlers, as
++ * demonstrated in the \l {Graph Gallery} example under \uicontrol {Scatter Graph} tab.
++ *
++ * \sa selectedLabelIndex(), selectedAxis(), selectedCustomItemIndex(), selectedCustomItem(),
++ * Q3DBars::selectedSeries(), Q3DScatter::selectedSeries(), Q3DSurface::selectedSeries(),
++ * Q3DScene::setSelectionQueryPosition()
++ *
++ * \since QtDataVisualization 1.1
++ */
++ QAbstract3DGraph::ElementType QAbstract3DGraph::selectedElement() const
++ {
++ return d_ptr->m_visualController->selectedElement();
++ }
++
++ /*!
++ * Renders current frame to an image of \a imageSize. Default size is the window size. Image is
++ * rendered with antialiasing level given in \a msaaSamples. Default level is \c{0}.
++ *
++ * \since QtDataVisualization 1.1
++ *
++ * Returns the rendered image.
++ *
++ * \note OpenGL ES2 does not support anitialiasing, so \a msaaSamples is always forced to \c{0}.
++ */
++ QImage QAbstract3DGraph::renderToImage(int msaaSamples, const QSize &imageSize)
++ {
++ QSize renderSize = imageSize;
++ if (renderSize.isEmpty())
++ renderSize = size();
++ return d_ptr->renderToImage(msaaSamples, renderSize);
++ }
++
++ /*!
++ * \property QAbstract3DGraph::measureFps
++ * \since QtDataVisualization 1.1
++ *
++ * \brief Whether rendering is done continuously instead of on demand.
++ *
++ * If \c {true}, rendering is continuous and the value of the currentFps
++ * property is updated. Defaults to \c{false}.
++ *
++ * \sa currentFps
++ */
++ void QAbstract3DGraph::setMeasureFps(bool enable)
++ {
++ d_ptr->m_visualController->setMeasureFps(enable);
++ }
++
++ bool QAbstract3DGraph::measureFps() const
++ {
++ return d_ptr->m_visualController->measureFps();
++ }
++
++ /*!
++ * \property QAbstract3DGraph::currentFps
++ * \since QtDataVisualization 1.1
++ *
++ * \brief The rendering results for the last second.
++ *
++ * The results are stored in this read-only property when FPS measuring is
++ * enabled. It takes at least a second before this value is updated after
++ * measuring is activated.
++ *
++ * \sa measureFps
++ */
++ qreal QAbstract3DGraph::currentFps() const
++ {
++ return d_ptr->m_visualController->currentFps();
++ }
++
++ /*!
++ * \property QAbstract3DGraph::orthoProjection
++ * \since QtDataVisualization 1.1
++ *
++ * \brief Whether orthographic projection is used for displaying the graph.
++ *
++ * Defaults to \c{false}.
++ * \note Shadows will be disabled when set to \c{true}.
++ *
++ * \sa QAbstract3DAxis::labelAutoRotation, Q3DCamera::cameraPreset
++ */
++ void QAbstract3DGraph::setOrthoProjection(bool enable)
++ {
++ d_ptr->m_visualController->setOrthoProjection(enable);
++ }
++
++ bool QAbstract3DGraph::isOrthoProjection() const
++ {
++ return d_ptr->m_visualController->isOrthoProjection();
++ }
++
++ /*!
++ * \property QAbstract3DGraph::aspectRatio
++ * \since QtDataVisualization 1.1
++ *
++ * \brief 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 Q3DBars.
++ *
++ * \sa horizontalAspectRatio
++ */
++ void QAbstract3DGraph::setAspectRatio(qreal ratio)
++ {
++ d_ptr->m_visualController->setAspectRatio(ratio);
++ }
++
++ qreal QAbstract3DGraph::aspectRatio() const
++ {
++ return d_ptr->m_visualController->aspectRatio();
++ }
++
++ /*!
++ * \property QAbstract3DGraph::optimizationHints
++ *
++ * \brief Whether the default or static mode is used for rendering optimization.
++ *
++ * The default mode provides the full feature set at a reasonable level of
++ * performance. 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.
++ * Static optimization works only on scatter graphs.
++ * Defaults to \l{OptimizationDefault}.
++ *
++ * \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 QAbstract3DSeries::mesh
++ */
++ void QAbstract3DGraph::setOptimizationHints(OptimizationHints hints)
++ {
++ d_ptr->m_visualController->setOptimizationHints(hints);
++ }
++
++ QAbstract3DGraph::OptimizationHints QAbstract3DGraph::optimizationHints() const
++ {
++ return d_ptr->m_visualController->optimizationHints();
++ }
++
++ /*!
++ * \property QAbstract3DGraph::polar
++ * \since QtDataVisualization 1.2
++ *
++ * \brief Whether horizontal axes are changed into polar axes.
++ *
++ * If \c {true}, 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 orthoProjection, radialLabelOffset
++ */
++ void QAbstract3DGraph::setPolar(bool enable)
++ {
++ d_ptr->m_visualController->setPolar(enable);
++ }
++
++ bool QAbstract3DGraph::isPolar() const
++ {
++ return d_ptr->m_visualController->isPolar();
++ }
++
++ /*!
++ * \property QAbstract3DGraph::radialLabelOffset
++ * \since QtDataVisualization 1.2
++ *
++ * \brief 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. Defaults to \c 1.0.
++ *
++ * This property is ignored if the \l polar property value is \c{false}.
++ *
++ * \sa polar
++ */
++ void QAbstract3DGraph::setRadialLabelOffset(float offset)
++ {
++ d_ptr->m_visualController->setRadialLabelOffset(offset);
++ }
++
++ float QAbstract3DGraph::radialLabelOffset() const
++ {
++ return d_ptr->m_visualController->radialLabelOffset();
++ }
++
++ /*!
++ * \property QAbstract3DGraph::horizontalAspectRatio
++ * \since QtDataVisualization 1.2
++ *
++ * \brief 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}.
++ *
++ * Has no effect on Q3DBars, which handles scaling on the horizontal plane via
++ * the \l{Q3DBars::barThickness}{barThickness} and \l{Q3DBars::barSpacing}{barSpacing} properties.
++ * Polar graphs also ignore this property.
++ *
++ * \sa aspectRatio, polar, Q3DBars::barThickness, Q3DBars::barSpacing
++ */
++ void QAbstract3DGraph::setHorizontalAspectRatio(qreal ratio)
++ {
++ d_ptr->m_visualController->setHorizontalAspectRatio(ratio);
++ }
++
++ qreal QAbstract3DGraph::horizontalAspectRatio() const
++ {
++ return d_ptr->m_visualController->horizontalAspectRatio();
++ }
++
++ /*!
++ * \property QAbstract3DGraph::reflection
++ * \since QtDataVisualization 1.2
++ *
++ * \brief Whether floor reflections are on or off.
++ *
++ * Defaults to \c{false}.
++ *
++ * Affects only Q3DBars. However, in Q3DBars graphs holding both positive and
++ * negative values, reflections are not supported for custom items that
++ * intersect the floor plane. In that case, reflections should be turned off
++ * to avoid incorrect rendering.
++ *
++ * If using a custom surface format, the stencil buffer needs to be defined
++ * (QSurfaceFormat::setStencilBufferSize()) for reflections to work.
++ *
++ * \sa reflectivity
++ */
++ void QAbstract3DGraph::setReflection(bool enable)
++ {
++ d_ptr->m_visualController->setReflection(enable);
++ }
++
++ bool QAbstract3DGraph::isReflection() const
++ {
++ return d_ptr->m_visualController->reflection();
++ }
++
++ /*!
++ * \property QAbstract3DGraph::reflectivity
++ * \since QtDataVisualization 1.2
++ *
++ * \brief Floor reflectivity.
++ *
++ * Larger numbers make the floor more reflective. The valid range is \c{[0...1]}.
++ * Defaults to \c{0.5}.
++ *
++ * \note Affects only Q3DBars.
++ *
++ * \sa reflection
++ */
++ void QAbstract3DGraph::setReflectivity(qreal reflectivity)
++ {
++ d_ptr->m_visualController->setReflectivity(reflectivity);
++ }
++
++ qreal QAbstract3DGraph::reflectivity() const
++ {
++ return d_ptr->m_visualController->reflectivity();
++ }
++
++ /*!
++ * \property QAbstract3DGraph::locale
++ * \since QtDataVisualization 1.2
++ *
++ * \brief The locale used for formatting various numeric labels.
++ *
++ * Defaults to the \c{"C"} locale.
++ *
++ * \sa QValue3DAxis::labelFormat
++ */
++ void QAbstract3DGraph::setLocale(const QLocale &locale)
++ {
++ d_ptr->m_visualController->setLocale(locale);
++ }
++
++ QLocale QAbstract3DGraph::locale() const
++ {
++ return d_ptr->m_visualController->locale();
++ }
++
++ /*!
++ * \property QAbstract3DGraph::queriedGraphPosition
++ * \since QtDataVisualization 1.2
++ *
++ * \brief The latest queried graph position values along each axis.
++ *
++ * This read-only property contains the results from
++ * Q3DScene::graphPositionQuery. The values are normalized to the range \c{[-1, 1]}.
++ * If the queried position was outside the graph bounds, the values
++ * will not reflect the real position, but will instead indicate an 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 the valid queries can be only made at
++ * screen positions that contain the floor of the graph.
++ *
++ * \sa Q3DScene::graphPositionQuery
++ */
++ QVector3D QAbstract3DGraph::queriedGraphPosition() const
++ {
++ return d_ptr->m_visualController->queriedGraphPosition();
++ }
++
++ /*!
++ * \property QAbstract3DGraph::margin
++ * \since QtDataVisualization 1.2
++ *
++ * \brief 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 beed 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.
++ */
++ void QAbstract3DGraph::setMargin(qreal margin)
++ {
++ d_ptr->m_visualController->setMargin(margin);
++ }
++
++ qreal QAbstract3DGraph::margin() const
++ {
++ return d_ptr->m_visualController->margin();
++ }
++
++ /*!
++ * Returns \c{true} if the OpenGL context of the graph has been successfully initialized.
++ * Trying to use a graph when the context initialization has failed typically results in a crash.
++ * A common reason for a context initialization failure is lack of sufficient platform support
++ * for OpenGL.
++ */
++ bool QAbstract3DGraph::hasContext() const
++ {
++ if (d_ptr->m_initialized)
++ return true;
++ else
++ return false;
++ }
++
++ /*!
++ * \internal
++ */
++ bool QAbstract3DGraph::event(QEvent *event)
++ {
++ switch (event->type()) {
++ case QEvent::UpdateRequest:
++ d_ptr->renderNow();
++ return true;
++ case QEvent::TouchBegin:
++ case QEvent::TouchCancel:
++ case QEvent::TouchUpdate:
++ case QEvent::TouchEnd:
++ d_ptr->m_visualController->touchEvent(static_cast<QTouchEvent *>(event));
++ return true;
++ default:
++ return QWindow::event(event);
++ }
++ }
++
++ /*!
++ * \internal
++ */
++ void QAbstract3DGraph::resizeEvent(QResizeEvent *event)
++ {
++ Q_UNUSED(event);
++
++ if (d_ptr->m_visualController) {
++ Q3DScene *scene = d_ptr->m_visualController->scene();
++ scene->d_ptr->setWindowSize(QSize(width(), height()));
++ scene->d_ptr->setViewport(QRect(0, 0, width(), height()));
++ }
++ }
++
++ /*!
++ * \internal
++ */
++ void QAbstract3DGraph::exposeEvent(QExposeEvent *event)
++ {
++ Q_UNUSED(event);
++
++ if (isExposed())
++ d_ptr->renderNow();
++ }
++
++ /*!
++ * \internal
++ */
++ void QAbstract3DGraph::mouseDoubleClickEvent(QMouseEvent *event)
++ {
++ d_ptr->m_visualController->mouseDoubleClickEvent(event);
++ }
++
++ /*!
++ * \internal
++ */
++ void QAbstract3DGraph::touchEvent(QTouchEvent *event)
++ {
++ d_ptr->m_visualController->touchEvent(event);
++ }
++
++ /*!
++ * \internal
++ */
++ void QAbstract3DGraph::mousePressEvent(QMouseEvent *event)
++ {
++ d_ptr->m_visualController->mousePressEvent(event, event->pos());
++ }
++
++ /*!
++ * \internal
++ */
++ void QAbstract3DGraph::mouseReleaseEvent(QMouseEvent *event)
++ {
++ d_ptr->m_visualController->mouseReleaseEvent(event, event->pos());
++ }
++
++ /*!
++ * \internal
++ */
++ void QAbstract3DGraph::mouseMoveEvent(QMouseEvent *event)
++ {
++ d_ptr->m_visualController->mouseMoveEvent(event, event->pos());
++ }
++
++ #if QT_CONFIG(wheelevent)
++ /*!
++ * \internal
++ */
++ void QAbstract3DGraph::wheelEvent(QWheelEvent *event)
++ {
++ d_ptr->m_visualController->wheelEvent(event);
++ }
++ #endif
++
++ QAbstract3DGraphPrivate::QAbstract3DGraphPrivate(QAbstract3DGraph *q)
++ : QObject(0),
++ q_ptr(q),
++ m_updatePending(false),
++ m_visualController(0),
++ m_devicePixelRatio(1.f),
++ m_offscreenSurface(0),
++ m_initialized(false)
++ {
++ }
++
++ QAbstract3DGraphPrivate::~QAbstract3DGraphPrivate()
++ {
++ if (m_offscreenSurface) {
++ m_offscreenSurface->destroy();
++ delete m_offscreenSurface;
++ }
++ if (m_context) {
++ if (!m_context->makeCurrent(q_ptr))
++ m_context->doneCurrent();
++ }
++
++ delete m_visualController;
++ }
++
++ void QAbstract3DGraphPrivate::setVisualController(Abstract3DController *controller)
++ {
++ m_visualController = controller;
++
++ q_ptr->setLocale(QLocale());
++
++ QObject::connect(m_visualController, &Abstract3DController::activeInputHandlerChanged, q_ptr,
++ &QAbstract3DGraph::activeInputHandlerChanged);
++ QObject::connect(m_visualController, &Abstract3DController::activeThemeChanged, q_ptr,
++ &QAbstract3DGraph::activeThemeChanged);
++ QObject::connect(m_visualController, &Abstract3DController::selectionModeChanged, q_ptr,
++ &QAbstract3DGraph::selectionModeChanged);
++ QObject::connect(m_visualController, &Abstract3DController::shadowQualityChanged, q_ptr,
++ &QAbstract3DGraph::shadowQualityChanged);
++ QObject::connect(m_visualController, &Abstract3DController::optimizationHintsChanged, q_ptr,
++ &QAbstract3DGraph::optimizationHintsChanged);
++ QObject::connect(m_visualController, &Abstract3DController::elementSelected, q_ptr,
++ &QAbstract3DGraph::selectedElementChanged);
++
++ QObject::connect(m_visualController, &Abstract3DController::needRender, this,
++ &QAbstract3DGraphPrivate::renderLater);
++
++ QObject::connect(m_visualController, &Abstract3DController::axisXChanged, this,
++ &QAbstract3DGraphPrivate::handleAxisXChanged);
++ QObject::connect(m_visualController, &Abstract3DController::axisYChanged, this,
++ &QAbstract3DGraphPrivate::handleAxisYChanged);
++ QObject::connect(m_visualController, &Abstract3DController::axisZChanged, this,
++ &QAbstract3DGraphPrivate::handleAxisZChanged);
++
++ QObject::connect(m_visualController, &Abstract3DController::measureFpsChanged, q_ptr,
++ &QAbstract3DGraph::measureFpsChanged);
++ QObject::connect(m_visualController, &Abstract3DController::currentFpsChanged, q_ptr,
++ &QAbstract3DGraph::currentFpsChanged);
++
++ QObject::connect(m_visualController, &Abstract3DController::orthoProjectionChanged, q_ptr,
++ &QAbstract3DGraph::orthoProjectionChanged);
++
++ QObject::connect(m_visualController, &Abstract3DController::aspectRatioChanged, q_ptr,
++ &QAbstract3DGraph::aspectRatioChanged);
++ QObject::connect(m_visualController, &Abstract3DController::polarChanged, q_ptr,
++ &QAbstract3DGraph::polarChanged);
++ QObject::connect(m_visualController, &Abstract3DController::radialLabelOffsetChanged, q_ptr,
++ &QAbstract3DGraph::radialLabelOffsetChanged);
++ QObject::connect(m_visualController, &Abstract3DController::horizontalAspectRatioChanged, q_ptr,
++ &QAbstract3DGraph::horizontalAspectRatioChanged);
++
++ QObject::connect(m_visualController, &Abstract3DController::reflectionChanged, q_ptr,
++ &QAbstract3DGraph::reflectionChanged);
++ QObject::connect(m_visualController, &Abstract3DController::reflectivityChanged, q_ptr,
++ &QAbstract3DGraph::reflectivityChanged);
++ QObject::connect(m_visualController, &Abstract3DController::localeChanged, q_ptr,
++ &QAbstract3DGraph::localeChanged);
++ QObject::connect(m_visualController, &Abstract3DController::queriedGraphPositionChanged, q_ptr,
++ &QAbstract3DGraph::queriedGraphPositionChanged);
++ QObject::connect(m_visualController, &Abstract3DController::marginChanged, q_ptr,
++ &QAbstract3DGraph::marginChanged);
++ }
++
++ void QAbstract3DGraphPrivate::handleDevicePixelRatioChange()
++ {
++ if (q_ptr->devicePixelRatio() == m_devicePixelRatio || !m_visualController)
++ return;
++
++ m_devicePixelRatio = q_ptr->devicePixelRatio();
++ m_visualController->scene()->setDevicePixelRatio(m_devicePixelRatio);
++ }
++
++ void QAbstract3DGraphPrivate::render()
++ {
++ handleDevicePixelRatioChange();
++ m_visualController->synchDataToRenderer();
++ m_visualController->render();
++ }
++
++ void QAbstract3DGraphPrivate::renderLater()
++ {
++ if (!m_updatePending) {
++ m_updatePending = true;
++ QCoreApplication::postEvent(q_ptr, new QEvent(QEvent::UpdateRequest));
++ }
++ }
++
++ void QAbstract3DGraphPrivate::renderNow()
++ {
++ if (!q_ptr->isExposed())
++ return;
++
++ m_updatePending = false;
++
++ m_context->makeCurrent(q_ptr);
++
++ render();
++
++ m_context->swapBuffers(q_ptr);
++ }
++
++ QImage QAbstract3DGraphPrivate::renderToImage(int msaaSamples, const QSize &imageSize)
++ {
++ QImage image;
++ QOpenGLFramebufferObject *fbo;
++ QOpenGLFramebufferObjectFormat fboFormat;
++ if (!m_offscreenSurface) {
++ // Create an offscreen surface for rendering to images without rendering on screen
++ m_offscreenSurface = new QOffscreenSurface(q_ptr->screen());
++ m_offscreenSurface->setFormat(q_ptr->requestedFormat());
++ m_offscreenSurface->create();
++ }
++ // Render the wanted frame offscreen
++ m_context->makeCurrent(m_offscreenSurface);
++ fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
++ if (!Utils::isOpenGLES()) {
++ fboFormat.setInternalTextureFormat(GL_RGB);
++ fboFormat.setSamples(msaaSamples);
++ }
++ fbo = new QOpenGLFramebufferObject(imageSize, fboFormat);
++ if (fbo->isValid()) {
++ QRect originalViewport = m_visualController->m_scene->viewport();
++ m_visualController->m_scene->d_ptr->setWindowSize(imageSize);
++ m_visualController->m_scene->d_ptr->setViewport(QRect(0, 0,
++ imageSize.width(),
++ imageSize.height()));
++ m_visualController->synchDataToRenderer();
++ fbo->bind();
++ m_visualController->requestRender(fbo);
++ image = fbo->toImage();
++ fbo->release();
++ m_visualController->m_scene->d_ptr->setWindowSize(originalViewport.size());
++ m_visualController->m_scene->d_ptr->setViewport(originalViewport);
++ }
++ delete fbo;
++ m_context->makeCurrent(q_ptr);
++
++ return image;
++ }
++
++ QT_END_NAMESPACE