1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qsurface3dseries_p.h"
5#include "surface3dcontroller_p.h"
6#include "qvalue3daxis.h"
7#include "qcategory3daxis.h"
8
9QT_BEGIN_NAMESPACE
10
11/*!
12 * \class QSurface3DSeries
13 * \inmodule QtGraphs
14 * \brief The QSurface3DSeries class represents a data series in a 3D surface
15 * graph.
16 *
17 * This class manages the series specific visual elements, as well as the series
18 * data (via a data proxy).
19 *
20 * If no data proxy is set explicitly for the series, the series creates a default
21 * proxy. Setting another proxy will destroy the existing proxy and all data added to it.
22 *
23 * The object mesh set via the QAbstract3DSeries::mesh property defines the selection
24 * pointer shape in a surface series.
25 *
26 * QSurface3DSeries supports the following format tags for QAbstract3DSeries::setItemLabelFormat():
27 * \table
28 * \row
29 * \li @xTitle \li Title from x-axis
30 * \row
31 * \li @yTitle \li Title from y-axis
32 * \row
33 * \li @zTitle \li Title from z-axis
34 * \row
35 * \li @xLabel \li Item value formatted using the format of the x-axis.
36 * For more information, see
37 * \l{QValue3DAxis::setLabelFormat()}.
38 * \row
39 * \li @yLabel \li Item value formatted using the format of the y-axis.
40 * For more information, see
41 * \l{QValue3DAxis::setLabelFormat()}.
42 * \row
43 * \li @zLabel \li Item value formatted using the format of the z-axis.
44 * For more information, see
45 * \l{QValue3DAxis::setLabelFormat()}.
46 * \row
47 * \li @seriesName \li Name of the series
48 * \endtable
49 *
50 * For example:
51 * \snippet doc_src_qtgraphs.cpp 1
52 *
53 * \sa {Qt Graphs Data Handling}
54 */
55
56/*!
57 * \qmltype Surface3DSeries
58 * \inqmlmodule QtGraphs
59 * \ingroup graphs_qml
60 * \instantiates QSurface3DSeries
61 * \inherits Abstract3DSeries
62 * \brief Represents a data series in a 3D surface graph.
63 *
64 * This type manages the series specific visual elements, as well as the series
65 * data (via a data proxy).
66 *
67 * For a more complete description, see QSurface3DSeries.
68 *
69 * \sa {Qt Graphs Data Handling}
70 */
71
72/*!
73 * \qmlproperty SurfaceDataProxy Surface3DSeries::dataProxy
74 *
75 * The active data proxy. The series assumes ownership of any proxy set to
76 * it and deletes any previously set proxy when a new one is added. The proxy cannot be null or
77 * set to another series.
78 */
79
80/*!
81 * \qmlproperty point Surface3DSeries::selectedPoint
82 *
83 * Sets the surface grid point in the position specified by a row and a column
84 * in the data array of the series as selected.
85 * Only one point can be selected at a time.
86 *
87 * To clear selection from this series, invalidSelectionPosition is set as the position.
88 * If this series is added to a graph, the graph can adjust the selection according to user
89 * interaction or if it becomes invalid.
90 *
91 * Removing rows from or inserting rows to the series before the row of the selected point
92 * will adjust the selection so that the same point will stay selected.
93 *
94 * \sa AbstractGraph3D::clearSelection()
95 */
96
97/*!
98 * \qmlproperty point Surface3DSeries::invalidSelectionPosition
99 * A constant property providing an invalid selection position.
100 * This position is set to the selectedPoint property to clear the selection
101 * from this series.
102 *
103 * \sa AbstractGraph3D::clearSelection()
104 */
105
106/*!
107 * \qmlproperty bool Surface3DSeries::flatShadingEnabled
108 *
109 * Sets surface flat shading to enabled. It is preset to \c true by default.
110 * When disabled, the normals on the surface are interpolated making the edges look round.
111 * When enabled, the normals are kept the same on a triangle making the color of the triangle solid.
112 * This makes the data more readable from the model.
113 * \note Flat shaded surfaces require at least GLSL version 1.2 with GL_EXT_gpu_shader4 extension.
114 * The value of the flatShadingSupported property indicates whether flat shading
115 * is supported at runtime.
116 */
117
118/*!
119 * \qmlproperty bool Surface3DSeries::flatShadingSupported
120 *
121 * Indicates whether flat shading for surfaces is supported by the current system.
122 * It requires at least GLSL version 1.2 with GL_EXT_gpu_shader4 extension.
123 *
124 * \note This read-only property is set to its correct value after the first
125 * render pass. Until then it is always \c true.
126 */
127
128/*!
129 * \qmlproperty DrawFlag Surface3DSeries::drawMode
130 *
131 * Sets the drawing mode to one of \l{QSurface3DSeries::DrawFlag}{Surface3DSeries.DrawFlag}.
132 * Clearing all flags is not allowed.
133 */
134
135/*!
136 * \qmlproperty string Surface3DSeries::textureFile
137 *
138 * The texture file name for the surface texture. To clear the texture, an empty
139 * file name is set.
140 */
141
142/*!
143 * \qmlproperty color Surface3DSeries::wireframeColor
144 *
145 * The color used to draw the gridlines of the surface wireframe.
146 */
147
148/*!
149 * \enum QSurface3DSeries::DrawFlag
150 *
151 * The drawing mode of the surface. Values of this enumeration can be combined
152 * with the OR operator.
153 *
154 * \value DrawWireframe
155 * Only the grid is drawn.
156 * \value DrawSurface
157 * Only the surface is drawn.
158 * \value DrawSurfaceAndWireframe
159 * Both the surface and grid are drawn.
160 */
161
162/*!
163 * Constructs a surface 3D series with the parent \a parent.
164 */
165QSurface3DSeries::QSurface3DSeries(QObject *parent) :
166 QAbstract3DSeries(new QSurface3DSeriesPrivate(this), parent)
167{
168 Q_D(QSurface3DSeries);
169 // Default proxy
170 d->setDataProxy(new QSurfaceDataProxy);
171}
172
173/*!
174 * Constructs a surface 3D series with the data proxy \a dataProxy and the
175 * parent \a parent.
176 */
177QSurface3DSeries::QSurface3DSeries(QSurfaceDataProxy *dataProxy, QObject *parent) :
178 QAbstract3DSeries(new QSurface3DSeriesPrivate(this), parent)
179{
180 Q_D(QSurface3DSeries);
181 d->setDataProxy(dataProxy);
182}
183
184/*!
185 * \internal
186 */
187QSurface3DSeries::QSurface3DSeries(QSurface3DSeriesPrivate *d, QObject *parent) :
188 QAbstract3DSeries(d, parent)
189{
190}
191
192/*!
193 * Deletes the surface 3D series.
194 */
195QSurface3DSeries::~QSurface3DSeries()
196{
197}
198
199/*!
200 * \property QSurface3DSeries::dataProxy
201 *
202 * \brief The active data proxy.
203 *
204 * The series assumes ownership of any proxy set to it and deletes any
205 * previously set proxy when a new one is added. The proxy cannot be null or
206 * set to another series.
207 */
208void QSurface3DSeries::setDataProxy(QSurfaceDataProxy *proxy)
209{
210 Q_D(QSurface3DSeries);
211 d->setDataProxy(proxy);
212}
213
214QSurfaceDataProxy *QSurface3DSeries::dataProxy() const
215{
216 const Q_D(QSurface3DSeries);
217 return static_cast<QSurfaceDataProxy *>(d->dataProxy());
218}
219
220/*!
221 * \property QSurface3DSeries::selectedPoint
222 *
223 * \brief The surface grid point that is selected in the series.
224 */
225
226/*!
227 * Selects a surface grid point at the position \a position in the data array of
228 * the series specified by a row and a column.
229 *
230 * Only one point can be selected at a time.
231 *
232 * To clear selection from this series, invalidSelectionPosition() is set as \a position.
233 * If this series is added to a graph, the graph can adjust the selection according to user
234 * interaction or if it becomes invalid.
235 *
236 * Removing rows from or inserting rows to the series before the row of the selected point
237 * will adjust the selection so that the same point will stay selected.
238 *
239 * \sa QAbstract3DGraph::clearSelection()
240 */
241void QSurface3DSeries::setSelectedPoint(const QPoint &position)
242{
243 Q_D(QSurface3DSeries);
244 // Don't do this in private to avoid loops, as that is used for callback from controller.
245 if (d->m_controller)
246 static_cast<Surface3DController *>(d->m_controller)->setSelectedPoint(position, series: this, enterSlice: true);
247 else
248 d->setSelectedPoint(position);
249}
250
251QPoint QSurface3DSeries::selectedPoint() const
252{
253 const Q_D(QSurface3DSeries);
254 return d->m_selectedPoint;
255}
256
257/*!
258 * Returns the QPoint signifying an invalid selection position. This is set to
259 * the selectedPoint property to clear the selection from this series.
260 *
261 * \sa QAbstract3DGraph::clearSelection()
262 */
263QPoint QSurface3DSeries::invalidSelectionPosition()
264{
265 return Surface3DController::invalidSelectionPosition();
266}
267
268/*!
269 * \property QSurface3DSeries::flatShadingEnabled
270 *
271 * \brief Whether surface flat shading is enabled.
272 *
273 * Preset to \c true by default.
274 *
275 * When disabled, the normals on the surface are interpolated making the edges look round.
276 * When enabled, the normals are kept the same on a triangle making the color of the triangle solid.
277 * This makes the data more readable from the model.
278 * \note Flat shaded surfaces require at least GLSL version 1.2 with GL_EXT_gpu_shader4 extension.
279 * The value of the flatShadingSupported property indicates whether flat shading
280 * is supported at runtime.
281 */
282void QSurface3DSeries::setFlatShadingEnabled(bool enabled)
283{
284 Q_D(QSurface3DSeries);
285 if (d->m_flatShadingEnabled != enabled) {
286 d->setFlatShadingEnabled(enabled);
287 emit flatShadingEnabledChanged(enable: enabled);
288 }
289}
290
291bool QSurface3DSeries::isFlatShadingEnabled() const
292{
293 const Q_D(QSurface3DSeries);
294 return d->m_flatShadingEnabled;
295}
296
297/*!
298 * \property QSurface3DSeries::flatShadingSupported
299 *
300 * \brief Whether surface flat shading is supported by the current system.
301 *
302 * Flat shading for surfaces requires at least GLSL version 1.2 with GL_EXT_gpu_shader4 extension.
303 * If \c true, flat shading for surfaces is supported.
304 * \note This read-only property is set to its correct value after the first
305 * render pass. Until then it is always \c true.
306 */
307bool QSurface3DSeries::isFlatShadingSupported() const
308{
309 const Q_D(QSurface3DSeries);
310 if (d->m_controller)
311 return static_cast<Surface3DController *>(d->m_controller)->isFlatShadingSupported();
312 else
313 return true;
314}
315
316/*!
317 * \property QSurface3DSeries::drawMode
318 *
319 * The drawing mode.
320 *
321 * Possible values are the values of DrawFlag. Clearing all flags is not allowed.
322 */
323void QSurface3DSeries::setDrawMode(QSurface3DSeries::DrawFlags mode)
324{
325 Q_D(QSurface3DSeries);
326 if (d->m_drawMode != mode) {
327 d->setDrawMode(mode);
328 emit drawModeChanged(mode);
329 }
330}
331
332QSurface3DSeries::DrawFlags QSurface3DSeries::drawMode() const
333{
334 const Q_D(QSurface3DSeries);
335 return d->m_drawMode;
336}
337
338/*!
339 * \property QSurface3DSeries::texture
340 *
341 * \brief The texture for the surface as a QImage.
342 *
343 * Setting an empty QImage clears the texture.
344 */
345void QSurface3DSeries::setTexture(const QImage &texture)
346{
347 Q_D(QSurface3DSeries);
348 if (d->m_texture != texture) {
349 d->setTexture(texture);
350
351 emit textureChanged(image: texture);
352 d->m_textureFile.clear();
353 }
354}
355
356QImage QSurface3DSeries::texture() const
357{
358 const Q_D(QSurface3DSeries);
359 return d->m_texture;
360}
361
362/*!
363 * \property QSurface3DSeries::textureFile
364 *
365 * \brief The texture for the surface as a file.
366 *
367 * Setting an empty file name clears the texture.
368 */
369void QSurface3DSeries::setTextureFile(const QString &filename)
370{
371 Q_D(QSurface3DSeries);
372 if (d->m_textureFile != filename) {
373 if (filename.isEmpty()) {
374 setTexture(QImage());
375 } else {
376 QImage image(filename);
377 if (image.isNull()) {
378 qWarning() << "Warning: Tried to set invalid image file as surface texture.";
379 return;
380 }
381 setTexture(image);
382 }
383
384 d->m_textureFile = filename;
385 emit textureFileChanged(filename);
386 }
387}
388
389QString QSurface3DSeries::textureFile() const
390{
391 const Q_D(QSurface3DSeries);
392 return d->m_textureFile;
393}
394
395/*!
396 * \property QSurface3DSeries::wireframeColor
397 *
398 * \brief The color for the surface wireframe.
399 */
400void QSurface3DSeries::setWireframeColor(const QColor &color)
401{
402 Q_D(QSurface3DSeries);
403 if (d->m_wireframeColor != color) {
404 d->setWireframeColor(color);
405 emit wireframeColorChanged(color);
406 }
407}
408
409QColor QSurface3DSeries::wireframeColor() const
410{
411 const Q_D(QSurface3DSeries);
412 return d->m_wireframeColor;
413}
414
415// QSurface3DSeriesPrivate
416
417QSurface3DSeriesPrivate::QSurface3DSeriesPrivate(QSurface3DSeries *q)
418 : QAbstract3DSeriesPrivate(q, QAbstract3DSeries::SeriesTypeSurface),
419 m_selectedPoint(Surface3DController::invalidSelectionPosition()),
420 m_flatShadingEnabled(true),
421 m_drawMode(QSurface3DSeries::DrawSurfaceAndWireframe),
422 m_wireframeColor(Qt::black)
423{
424 m_itemLabelFormat = QStringLiteral("@xLabel, @yLabel, @zLabel");
425 m_mesh = QAbstract3DSeries::MeshSphere;
426}
427
428QSurface3DSeriesPrivate::~QSurface3DSeriesPrivate()
429{
430}
431
432void QSurface3DSeriesPrivate::setDataProxy(QAbstractDataProxy *proxy)
433{
434 Q_ASSERT(proxy->type() == QAbstractDataProxy::DataTypeSurface);
435 Q_Q(QSurface3DSeries);
436
437 QAbstract3DSeriesPrivate::setDataProxy(proxy);
438
439 emit q->dataProxyChanged(proxy: static_cast<QSurfaceDataProxy *>(proxy));
440}
441
442void QSurface3DSeriesPrivate::connectControllerAndProxy(Abstract3DController *newController)
443{
444 Q_Q(QSurface3DSeries);
445 QSurfaceDataProxy *surfaceDataProxy = static_cast<QSurfaceDataProxy *>(m_dataProxy);
446
447 if (m_controller && surfaceDataProxy) {
448 //Disconnect old controller/old proxy
449 QObject::disconnect(sender: surfaceDataProxy, signal: 0, receiver: m_controller, member: 0);
450 QObject::disconnect(sender: q_ptr, signal: 0, receiver: m_controller, member: 0);
451 }
452
453 if (newController && surfaceDataProxy) {
454 Surface3DController *controller = static_cast<Surface3DController *>(newController);
455
456 QObject::connect(sender: surfaceDataProxy, signal: &QSurfaceDataProxy::arrayReset, context: controller,
457 slot: &Surface3DController::handleArrayReset);
458 QObject::connect(sender: surfaceDataProxy, signal: &QSurfaceDataProxy::rowsAdded, context: controller,
459 slot: &Surface3DController::handleRowsAdded);
460 QObject::connect(sender: surfaceDataProxy, signal: &QSurfaceDataProxy::rowsChanged, context: controller,
461 slot: &Surface3DController::handleRowsChanged);
462 QObject::connect(sender: surfaceDataProxy, signal: &QSurfaceDataProxy::rowsRemoved, context: controller,
463 slot: &Surface3DController::handleRowsRemoved);
464 QObject::connect(sender: surfaceDataProxy, signal: &QSurfaceDataProxy::rowsInserted, context: controller,
465 slot: &Surface3DController::handleRowsInserted);
466 QObject::connect(sender: surfaceDataProxy, signal: &QSurfaceDataProxy::itemChanged, context: controller,
467 slot: &Surface3DController::handleItemChanged);
468 QObject::connect(sender: q, signal: &QSurface3DSeries::dataProxyChanged, context: controller,
469 slot: &Surface3DController::handleArrayReset);
470 }
471}
472
473void QSurface3DSeriesPrivate::createItemLabel()
474{
475 Q_Q(QSurface3DSeries);
476 static const QString xTitleTag(QStringLiteral("@xTitle"));
477 static const QString yTitleTag(QStringLiteral("@yTitle"));
478 static const QString zTitleTag(QStringLiteral("@zTitle"));
479 static const QString xLabelTag(QStringLiteral("@xLabel"));
480 static const QString yLabelTag(QStringLiteral("@yLabel"));
481 static const QString zLabelTag(QStringLiteral("@zLabel"));
482 static const QString seriesNameTag(QStringLiteral("@seriesName"));
483
484 if (m_selectedPoint == QSurface3DSeries::invalidSelectionPosition()) {
485 m_itemLabel = QString();
486 return;
487 }
488
489 QValue3DAxis *axisX = static_cast<QValue3DAxis *>(m_controller->axisX());
490 QValue3DAxis *axisY = static_cast<QValue3DAxis *>(m_controller->axisY());
491 QValue3DAxis *axisZ = static_cast<QValue3DAxis *>(m_controller->axisZ());
492 QVector3D selectedPosition = q->dataProxy()->itemAt(position: m_selectedPoint)->position();
493
494 m_itemLabel = m_itemLabelFormat;
495
496 m_itemLabel.replace(before: xTitleTag, after: axisX->title());
497 m_itemLabel.replace(before: yTitleTag, after: axisY->title());
498 m_itemLabel.replace(before: zTitleTag, after: axisZ->title());
499
500 if (m_itemLabel.contains(s: xLabelTag)) {
501 QString valueLabelText = axisX->formatter()->stringForValue(
502 value: qreal(selectedPosition.x()), format: axisX->labelFormat());
503 m_itemLabel.replace(before: xLabelTag, after: valueLabelText);
504 }
505 if (m_itemLabel.contains(s: yLabelTag)) {
506 QString valueLabelText = axisY->formatter()->stringForValue(
507 value: qreal(selectedPosition.y()), format: axisY->labelFormat());
508 m_itemLabel.replace(before: yLabelTag, after: valueLabelText);
509 }
510 if (m_itemLabel.contains(s: zLabelTag)) {
511 QString valueLabelText = axisZ->formatter()->stringForValue(
512 value: qreal(selectedPosition.z()), format: axisZ->labelFormat());
513 m_itemLabel.replace(before: zLabelTag, after: valueLabelText);
514 }
515 m_itemLabel.replace(before: seriesNameTag, after: m_name);
516}
517
518void QSurface3DSeriesPrivate::setSelectedPoint(const QPoint &position)
519{
520 Q_Q(QSurface3DSeries);
521 if (position != m_selectedPoint) {
522 markItemLabelDirty();
523 m_selectedPoint = position;
524 emit q->selectedPointChanged(position: m_selectedPoint);
525 }
526}
527
528void QSurface3DSeriesPrivate::setFlatShadingEnabled(bool enabled)
529{
530 m_flatShadingEnabled = enabled;
531 if (m_controller)
532 m_controller->markSeriesVisualsDirty();
533}
534
535void QSurface3DSeriesPrivate::setDrawMode(QSurface3DSeries::DrawFlags mode)
536{
537 if (mode.testFlag(flag: QSurface3DSeries::DrawWireframe)
538 || mode.testFlag(flag: QSurface3DSeries::DrawSurface)) {
539 m_drawMode = mode;
540 if (m_controller)
541 m_controller->markSeriesVisualsDirty();
542 } else {
543 qWarning(msg: "You may not clear all draw flags. Mode not changed.");
544 }
545}
546
547void QSurface3DSeriesPrivate::setTexture(const QImage &texture)
548{
549 Q_Q(QSurface3DSeries);
550 m_texture = texture;
551 if (static_cast<Surface3DController *>(m_controller))
552 static_cast<Surface3DController *>(m_controller)->updateSurfaceTexture(series: q);
553}
554
555void QSurface3DSeriesPrivate::setWireframeColor(const QColor &color)
556{
557 m_wireframeColor = color;
558 if (m_controller)
559 m_controller->markSeriesVisualsDirty();
560}
561
562QT_END_NAMESPACE
563

source code of qtgraphs/src/graphs/data/qsurface3dseries.cpp