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