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 | |
9 | QT_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 | */ |
165 | QSurface3DSeries::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 | */ |
177 | QSurface3DSeries::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 | */ |
187 | QSurface3DSeries::QSurface3DSeries(QSurface3DSeriesPrivate *d, QObject *parent) : |
188 | QAbstract3DSeries(d, parent) |
189 | { |
190 | } |
191 | |
192 | /*! |
193 | * Deletes the surface 3D series. |
194 | */ |
195 | QSurface3DSeries::~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 | */ |
208 | void QSurface3DSeries::setDataProxy(QSurfaceDataProxy *proxy) |
209 | { |
210 | Q_D(QSurface3DSeries); |
211 | d->setDataProxy(proxy); |
212 | } |
213 | |
214 | QSurfaceDataProxy *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 | */ |
241 | void 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 | |
251 | QPoint 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 | */ |
263 | QPoint 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 | */ |
282 | void 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 | |
291 | bool 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 | */ |
307 | bool 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 | */ |
323 | void 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 | |
332 | QSurface3DSeries::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 | */ |
345 | void 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 | |
356 | QImage 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 | */ |
369 | void 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 | |
389 | QString 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 | */ |
400 | void 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 | |
409 | QColor QSurface3DSeries::wireframeColor() const |
410 | { |
411 | const Q_D(QSurface3DSeries); |
412 | return d->m_wireframeColor; |
413 | } |
414 | |
415 | // QSurface3DSeriesPrivate |
416 | |
417 | QSurface3DSeriesPrivate::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 | |
428 | QSurface3DSeriesPrivate::~QSurface3DSeriesPrivate() |
429 | { |
430 | } |
431 | |
432 | void 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 | |
442 | void 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 | |
473 | void 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 | |
518 | void 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 | |
528 | void QSurface3DSeriesPrivate::setFlatShadingEnabled(bool enabled) |
529 | { |
530 | m_flatShadingEnabled = enabled; |
531 | if (m_controller) |
532 | m_controller->markSeriesVisualsDirty(); |
533 | } |
534 | |
535 | void 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 | |
547 | void 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 | |
555 | void QSurface3DSeriesPrivate::setWireframeColor(const QColor &color) |
556 | { |
557 | m_wireframeColor = color; |
558 | if (m_controller) |
559 | m_controller->markSeriesVisualsDirty(); |
560 | } |
561 | |
562 | QT_END_NAMESPACE |
563 | |