1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <QtGraphsWidgets/q3dsurfacewidgetitem.h>
5#include <private/q3dsurfacewidgetitem_p.h>
6#include <private/qquickgraphssurface_p.h>
7#include "q3dsurfacewidgetitem.h"
8
9QT_BEGIN_NAMESPACE
10
11/*!
12 * \class Q3DSurfaceWidgetItem
13 * \inmodule QtGraphsWidgets
14 * \ingroup graphs_3D_widgets
15 * \brief The Q3DSurfaceWidgetItem class provides methods for rendering 3D surface plots.
16 *
17 * This class enables developers to render 3D surface plots and to view them by
18 * rotating the scene freely. The visual properties of the surface such as draw
19 * mode and shading can be controlled via QSurface3DSeries.
20 *
21 * The Q3DSurfaceWidgetItem supports selection by showing a highlighted ball on the data
22 * point where the user has clicked with the left mouse button (when the default
23 * input handler is in use) or selected via QSurface3DSeries. The selection pointer
24 * is accompanied by a label, which in the default case shows the value of the data
25 * point and the coordinates of the point.
26 *
27 * The value range and the label format shown on the axis can be controlled
28 * through QValue3DAxis.
29 *
30 * To rotate the graph, hold down the right mouse button and move the mouse.
31 * Zooming is done using the mouse wheel. Both actions assume the default input
32 * handler is in use.
33 *
34 * If no axes are set explicitly for Q3DSurfaceWidgetItem, temporary default axes with no
35 * labels are created. These default axes can be modified via axis accessors,
36 * but as soon as any axis is set explicitly for the orientation, the default
37 * axis for that orientation is destroyed.
38 *
39 * \section1 How to construct a minimal Q3DSurfaceWidgetItem graph
40 *
41 * First, construct Q3DSurfaceWidgetItem. Since we are running the graph as a top-level
42 * window in this example, we need to clear the \c Qt::FramelessWindowHint flag,
43 * which is set by default:
44 *
45 * \snippet doc_src_q3dsurface_construction.cpp 0
46 *
47 * Now Q3DSurfaceWidgetItem is ready to receive data to be rendered. Create data elements
48 * to receive values:
49 *
50 * \note In the new proxy-series relationship, data is held in series.
51 * Therefore, for the proxy to be able to add, delete, or edit the data, it is
52 * a prerequisite to create a series first.
53 *
54 * \snippet doc_src_q3dsurface_construction.cpp 1
55 *
56 * First feed the data to the row elements and then add their pointers to the
57 * data element:
58 *
59 * \snippet doc_src_q3dsurface_construction.cpp 2
60 *
61 * Create a new series and set data to it:
62 *
63 * \snippet doc_src_q3dsurface_construction.cpp 3
64 *
65 * Finally you will need to set it visible:
66 *
67 * \snippet doc_src_q3dsurface_construction.cpp 4
68 *
69 * The complete code needed to create and display this graph is:
70 *
71 * \snippet doc_src_q3dsurface_construction.cpp 5
72 *
73 * And this is what those few lines of code produce:
74 *
75 * \image q3dsurface-minimal.png
76 *
77 * The scene can be rotated, zoomed into, and a surface point can be selected to
78 * view its position, but no other interactions are included in this minimal code
79 * example. You can learn more by familiarizing yourself with the examples
80 * provided, like the \l{Surface Graph Gallery}.
81 *
82 *
83 * \sa Q3DBarsWidgetItem, Q3DScatterWidgetItem, {Qt Graphs C++ Classes for 3D}
84 */
85
86/*!
87 * Constructs a new 3D surface graph with the optional \a parent.
88 */
89Q3DSurfaceWidgetItem::Q3DSurfaceWidgetItem(QObject *parent)
90 : Q3DGraphsWidgetItem(*(new Q3DSurfaceWidgetItemPrivate()), parent, QStringLiteral("Surface3D"))
91{}
92
93/*!
94 * Destroys the 3D surface graph.
95 */
96Q3DSurfaceWidgetItem::~Q3DSurfaceWidgetItem() {}
97
98/*!
99 * Adds the \a series to the graph. A graph can contain multiple series, but
100 * has only one set of axes. If the newly added series has specified a selected
101 * item, it will be highlighted and any existing selection will be cleared. Only
102 * one added series can have an active selection.
103 *
104 * \sa Q3DGraphsWidgetItem::hasSeries()
105 */
106void Q3DSurfaceWidgetItem::addSeries(QSurface3DSeries *series)
107{
108 graphSurface()->addSeries(series);
109}
110
111/*!
112 * Removes the \a series from the graph.
113 *
114 * \sa Q3DGraphsWidgetItem::hasSeries()
115 */
116void Q3DSurfaceWidgetItem::removeSeries(QSurface3DSeries *series)
117{
118 graphSurface()->removeSeries(series);
119}
120
121/*!
122 * Returns the list of series added to this graph.
123 *
124 * \sa Q3DGraphsWidgetItem::hasSeries()
125 */
126QList<QSurface3DSeries *> Q3DSurfaceWidgetItem::seriesList() const
127{
128 QList<QSurface3DSeries *> surfaceSeriesList;
129 for (QAbstract3DSeries *abstractSeries : graphSurface()->m_seriesList) {
130 QSurface3DSeries *surfaceSeries = qobject_cast<QSurface3DSeries *>(object: abstractSeries);
131 if (surfaceSeries)
132 surfaceSeriesList.append(t: surfaceSeries);
133 }
134
135 return surfaceSeriesList;
136}
137
138/*!
139 * \property Q3DSurfaceWidgetItem::axisX
140 *
141 * \brief The active x-axis.
142 *
143 * Sets \a axis as the active x-axis. Implicitly calls addAxis() to transfer the
144 * ownership of the axis to this graph.
145 *
146 * If \a axis is null, a temporary default axis with no labels and an
147 * automatically adjusting range is created.
148 *
149 * This temporary axis is destroyed if another axis is set explicitly to the
150 * same orientation.
151 *
152 * \sa addAxis(), releaseAxis()
153 */
154void Q3DSurfaceWidgetItem::setAxisX(QValue3DAxis *axis)
155{
156 graphSurface()->setAxisX(axis);
157}
158
159QValue3DAxis *Q3DSurfaceWidgetItem::axisX() const
160{
161 return static_cast<QValue3DAxis *>(graphSurface()->axisX());
162}
163
164/*!
165 * \property Q3DSurfaceWidgetItem::axisY
166 *
167 * \brief The active y-axis.
168 *
169 * Sets \a axis as the active y-axis. Implicitly calls addAxis() to transfer the
170 * ownership of the axis to this graph.
171 *
172 * If \a axis is null, a temporary default axis with no labels and an
173 * automatically adjusting range is created.
174 *
175 * This temporary axis is destroyed if another axis is set explicitly to the
176 * same orientation.
177 *
178 * \sa addAxis(), releaseAxis()
179 */
180void Q3DSurfaceWidgetItem::setAxisY(QValue3DAxis *axis)
181{
182 graphSurface()->setAxisY(axis);
183}
184
185QValue3DAxis *Q3DSurfaceWidgetItem::axisY() const
186{
187 return static_cast<QValue3DAxis *>(graphSurface()->axisY());
188}
189
190/*!
191 * \property Q3DSurfaceWidgetItem::axisZ
192 *
193 * \brief The active z-axis.
194 *
195 * Sets \a axis as the active z-axis. Implicitly calls addAxis() to transfer the
196 * ownership of the axis to this graph.
197 *
198 * If \a axis is null, a temporary default axis with no labels and an
199 * automatically adjusting range is created.
200 *
201 * This temporary axis is destroyed if another axis is set explicitly to the
202 * same orientation.
203 *
204 * \sa addAxis(), releaseAxis()
205 */
206void Q3DSurfaceWidgetItem::setAxisZ(QValue3DAxis *axis)
207{
208 graphSurface()->setAxisZ(axis);
209}
210
211QValue3DAxis *Q3DSurfaceWidgetItem::axisZ() const
212{
213 return static_cast<QValue3DAxis *>(graphSurface()->axisZ());
214}
215
216/*!
217 * \property Q3DSurfaceWidgetItem::selectedSeries
218 * \readonly
219 *
220 * \brief The selected series or null.
221 *
222 * If selectionMode has \c MultiSeries set, this
223 * property holds the series which owns the selected point.
224 */
225QSurface3DSeries *Q3DSurfaceWidgetItem::selectedSeries() const
226{
227 return graphSurface()->selectedSeries();
228}
229
230/*!
231 * \property Q3DSurfaceWidgetItem::flipHorizontalGrid
232 *
233 * \brief Whether the horizontal axis grid is displayed on top of the graph
234 * rather than on the bottom.
235 *
236 * In some use cases, the horizontal axis grid is mostly covered by the surface,
237 * so it can be more useful to display the horizontal axis grid on top of the
238 * graph rather than on the bottom. A typical use case for this is showing 2D
239 * spectrograms using orthographic projection with a top-down viewpoint.
240 *
241 * If \c{false}, the horizontal axis grid and labels are drawn on the horizontal
242 * background of the graph. If \c{true}, the horizontal axis grid and labels are
243 * drawn on the opposite side of the graph from the horizontal background.
244 * Defaults to \c{false}.
245 */
246void Q3DSurfaceWidgetItem::setFlipHorizontalGrid(bool flip)
247{
248 graphSurface()->setFlipHorizontalGrid(flip);
249}
250
251bool Q3DSurfaceWidgetItem::flipHorizontalGrid() const
252{
253 return graphSurface()->flipHorizontalGrid();
254}
255
256bool Q3DSurfaceWidgetItem::event(QEvent *event)
257{
258 return Q3DGraphsWidgetItem::event(event);
259}
260
261/*!
262 * Adds \a axis to the graph. The axes added via addAxis are not yet taken to
263 * use, addAxis is simply used to give the ownership of the \a axis to the
264 * graph. The \a axis must not be null or added to another graph.
265 *
266 * \sa releaseAxis(), setAxisX(), setAxisY(), setAxisZ()
267 */
268void Q3DSurfaceWidgetItem::addAxis(QValue3DAxis *axis)
269{
270 return graphSurface()->addAxis(axis);
271}
272
273/*!
274 * Releases the ownership of the \a axis back to the caller, if it is added to
275 * this graph. If the released \a axis is in use, a new default axis will be
276 * created and set active.
277 *
278 * If the default axis is released and added back later, it behaves as any other
279 * axis would.
280 *
281 * \sa addAxis(), setAxisX(), setAxisY(), setAxisZ()
282 */
283void Q3DSurfaceWidgetItem::releaseAxis(QValue3DAxis *axis)
284{
285 return graphSurface()->releaseAxis(axis);
286}
287
288/*!
289 * Exports the requested slice view to an image.
290 * The sliced result is the series of \a index. To export all series, set
291 * \a index to -1.
292 * The exported slice is a line of row or column, which is defined by \a sliceType,
293 * at a given \a requestedIndex.
294 *
295 * The \l sliceImageChanged signal is emitted when the image is ready, and can
296 * be captured as follows:
297 *
298 * \code
299 * connect(item, &Q3DSurfaceWidgetItem::sliceImageChanged, this, [](const QImage &image) {
300 * // ~~~
301 * });
302 *
303 * item->renderSliceToImage(sliceType, index);
304 * \endcode
305 *
306 * Image is rendered with the current antialiasing settings.
307 *
308 * \sa QQuickItem::grabToImage(), sliceImageChanged()
309 *
310 * \since 6.10
311 */
312void Q3DSurfaceWidgetItem::renderSliceToImage(int index,
313 int requestedIndex,
314 QtGraphs3D::SliceCaptureType sliceType)
315{
316 auto graph = graphSurface();
317 disconnect(sender: graph,
318 signal: &QQuickGraphsSurface::sliceImageChanged,
319 receiver: this,
320 slot: &Q3DSurfaceWidgetItem::sliceImageChanged);
321 connect(sender: graph,
322 signal: &QQuickGraphsSurface::sliceImageChanged,
323 context: this,
324 slot: &Q3DSurfaceWidgetItem::sliceImageChanged);
325 graphSurface()->renderSliceToImage(index, requestedIndex, sliceType);
326}
327
328/*!
329 * Returns the list of all added axes.
330 *
331 * \sa addAxis()
332 */
333QList<QValue3DAxis *> Q3DSurfaceWidgetItem::axes() const
334{
335 QList<QAbstract3DAxis *> abstractAxes = graphSurface()->axes();
336 QList<QValue3DAxis *> retList;
337 for (QAbstract3DAxis *axis : std::as_const(t&: abstractAxes))
338 retList.append(t: static_cast<QValue3DAxis *>(axis));
339
340 return retList;
341}
342
343/*!
344 * \fn Q3DSurfaceWidgetItem::sliceImageChanged(const QImage &image)
345 * \since 6.10
346 * Emitted when \l renderSliceToImage has prepared the \a{image}.
347 */
348
349/*!
350 * \internal
351 */
352QQuickGraphsSurface *Q3DSurfaceWidgetItem::graphSurface()
353{
354 Q_D(Q3DSurfaceWidgetItem);
355 return static_cast<QQuickGraphsSurface *>(d->m_graphsItem.get());
356}
357
358/*!
359 * \internal
360 */
361const QQuickGraphsSurface *Q3DSurfaceWidgetItem::graphSurface() const
362{
363 Q_D(const Q3DSurfaceWidgetItem);
364 return static_cast<const QQuickGraphsSurface *>(d->m_graphsItem.get());
365}
366
367QT_END_NAMESPACE
368

source code of qtgraphs/src/graphs3d/widget/q3dsurfacewidgetitem.cpp