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