1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2017 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 "qbar3dseries_p.h" |
31 | #include "bars3dcontroller_p.h" |
32 | #include <QtCore/qmath.h> |
33 | |
34 | QT_BEGIN_NAMESPACE_DATAVISUALIZATION |
35 | |
36 | /*! |
37 | * \class QBar3DSeries |
38 | * \inmodule QtDataVisualization |
39 | * \brief The QBar3DSeries class represents a data series in a 3D bar 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 | * QBar3DSeries supports the following format tags for QAbstract3DSeries::setItemLabelFormat(): |
49 | * \table |
50 | * \row |
51 | * \li @rowTitle \li Title from row axis |
52 | * \row |
53 | * \li @colTitle \li Title from column axis |
54 | * \row |
55 | * \li @valueTitle \li Title from value axis |
56 | * \row |
57 | * \li @rowIdx \li Visible row index. Localized using the graph locale. |
58 | * \row |
59 | * \li @colIdx \li Visible column index. Localized using the graph locale. |
60 | * \row |
61 | * \li @rowLabel \li Label from row axis |
62 | * \row |
63 | * \li @colLabel \li Label from column axis |
64 | * \row |
65 | * \li @valueLabel \li Item value formatted using the format of the value |
66 | * axis attached to the graph. For more information, |
67 | * see \l{QValue3DAxis::labelFormat}. |
68 | * \row |
69 | * \li @seriesName \li Name of the series |
70 | * \row |
71 | * \li %<format spec> \li Item value in the specified format. Formatted |
72 | * using the same rules as \l{QValue3DAxis::labelFormat}. |
73 | * \endtable |
74 | * |
75 | * For example: |
76 | * \snippet doc_src_qtdatavisualization.cpp 1 |
77 | * |
78 | * \sa {Qt Data Visualization Data Handling}, QAbstract3DGraph::locale |
79 | */ |
80 | |
81 | /*! |
82 | * \qmltype Bar3DSeries |
83 | * \inqmlmodule QtDataVisualization |
84 | * \since QtDataVisualization 1.0 |
85 | * \ingroup datavisualization_qml |
86 | * \instantiates QBar3DSeries |
87 | * \inherits Abstract3DSeries |
88 | * \brief Represents a data series in a 3D bar 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 QBar3DSeries. |
94 | * |
95 | * \sa {Qt Data Visualization Data Handling} |
96 | */ |
97 | |
98 | /*! |
99 | * \qmlproperty BarDataProxy Bar3DSeries::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 Bar3DSeries::selectedBar |
108 | * |
109 | * The bar in the series that is selected. |
110 | * |
111 | * The position of the selected bar is specified as a row and column in the |
112 | * data array of the series. |
113 | * |
114 | * Only one bar can be selected at a time. |
115 | * |
116 | * To clear selection from this series, set invalidSelectionPosition as the position. |
117 | * |
118 | * If this series is added to a graph, the graph can adjust the selection according to user |
119 | * interaction or if it becomes invalid. Selecting a bar on another added series will also |
120 | * clear the selection. |
121 | * |
122 | * Removing rows from or inserting rows to the series before the row of the selected bar |
123 | * will adjust the selection so that the same bar will stay selected. |
124 | * |
125 | * \sa {AbstractGraph3D::clearSelection()}{AbstractGraph3D.clearSelection()} |
126 | */ |
127 | |
128 | /*! |
129 | * \qmlproperty point Bar3DSeries::invalidSelectionPosition |
130 | * A constant property providing an invalid position for selection. This |
131 | * position is set to the selectedBar property to clear the selection from this |
132 | * series. |
133 | * |
134 | * \sa {AbstractGraph3D::clearSelection()}{AbstractGraph3D.clearSelection()} |
135 | */ |
136 | |
137 | /*! |
138 | * \qmlproperty real Bar3DSeries::meshAngle |
139 | * |
140 | * A convenience property for defining the series rotation angle in degrees. |
141 | * |
142 | * \note When reading this property, it is calculated from the |
143 | * \l{Abstract3DSeries::meshRotation}{Abstract3DSeries.meshRotation} value |
144 | * using floating point precision and always returns a value from zero to 360 degrees. |
145 | * |
146 | * \sa {Abstract3DSeries::meshRotation}{Abstract3DSeries.meshRotation} |
147 | */ |
148 | |
149 | /*! |
150 | * Constructsa bar 3D series with the parent \a parent. |
151 | */ |
152 | QBar3DSeries::QBar3DSeries(QObject *parent) : |
153 | QAbstract3DSeries(new QBar3DSeriesPrivate(this), parent) |
154 | { |
155 | // Default proxy |
156 | dptr()->setDataProxy(new QBarDataProxy); |
157 | dptr()->connectSignals(); |
158 | } |
159 | |
160 | /*! |
161 | * Constructs a bar 3D series with the data proxy \a dataProxy and the parent |
162 | * \a parent. |
163 | */ |
164 | QBar3DSeries::QBar3DSeries(QBarDataProxy *dataProxy, QObject *parent) : |
165 | QAbstract3DSeries(new QBar3DSeriesPrivate(this), parent) |
166 | { |
167 | dptr()->setDataProxy(dataProxy); |
168 | dptr()->connectSignals(); |
169 | } |
170 | |
171 | /*! |
172 | * Deletes a bar 3D series. |
173 | */ |
174 | QBar3DSeries::~QBar3DSeries() |
175 | { |
176 | } |
177 | |
178 | /*! |
179 | * \property QBar3DSeries::dataProxy |
180 | * |
181 | * \brief The active data proxy. |
182 | * |
183 | * The series assumes ownership of any proxy set to it and deletes any |
184 | * previously set proxy when a new one is added. The proxy cannot be null or |
185 | * set to another series. |
186 | */ |
187 | void QBar3DSeries::setDataProxy(QBarDataProxy *proxy) |
188 | { |
189 | d_ptr->setDataProxy(proxy); |
190 | } |
191 | |
192 | QBarDataProxy *QBar3DSeries::dataProxy() const |
193 | { |
194 | return static_cast<QBarDataProxy *>(d_ptr->dataProxy()); |
195 | } |
196 | |
197 | /*! |
198 | * \property QBar3DSeries::selectedBar |
199 | * |
200 | * \brief The bar in the series that is selected. |
201 | * |
202 | */ |
203 | |
204 | /*! |
205 | * Selects the bar at the \a position position, specified as a row and column in |
206 | * the data array of the series. |
207 | * |
208 | * Only one bar can be selected at a time. |
209 | * |
210 | * To clear selection from this series, invalidSelectionPosition() is set as |
211 | * \a position. |
212 | * |
213 | * If this series is added to a graph, the graph can adjust the selection according to user |
214 | * interaction or if it becomes invalid. Selecting a bar on another added series will also |
215 | * clear the selection. |
216 | * |
217 | * Removing rows from or inserting rows to the series before the row of the selected bar |
218 | * will adjust the selection so that the same bar will stay selected. |
219 | * |
220 | * \sa QAbstract3DGraph::clearSelection() |
221 | */ |
222 | void QBar3DSeries::setSelectedBar(const QPoint &position) |
223 | { |
224 | // Don't do this in private to avoid loops, as that is used for callback from controller. |
225 | if (d_ptr->m_controller) |
226 | static_cast<Bars3DController *>(d_ptr->m_controller)->setSelectedBar(position, series: this, enterSlice: true); |
227 | else |
228 | dptr()->setSelectedBar(position); |
229 | } |
230 | |
231 | QPoint QBar3DSeries::selectedBar() const |
232 | { |
233 | return dptrc()->m_selectedBar; |
234 | } |
235 | |
236 | /*! |
237 | * Returns an invalid position for selection. This position is set to the |
238 | * selectedBar property to clear the selection from this series. |
239 | * |
240 | * \sa QAbstract3DGraph::clearSelection() |
241 | */ |
242 | QPoint QBar3DSeries::invalidSelectionPosition() |
243 | { |
244 | return Bars3DController::invalidSelectionPosition(); |
245 | } |
246 | |
247 | static inline float quaternionAngle(const QQuaternion &rotation) |
248 | { |
249 | return qRadiansToDegrees(radians: qAcos(v: rotation.scalar())) * 2.f; |
250 | } |
251 | |
252 | /*! |
253 | \property QBar3DSeries::meshAngle |
254 | |
255 | \brief The series rotation angle in degrees. |
256 | |
257 | Setting this property is equivalent to the following call: |
258 | |
259 | \code |
260 | setMeshRotation(QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, angle)) |
261 | \endcode |
262 | |
263 | \note When reading this property, it is calculated from the |
264 | QAbstract3DSeries::meshRotation value using floating point precision |
265 | and always returns a value from zero to 360 degrees. |
266 | |
267 | \sa QAbstract3DSeries::meshRotation |
268 | */ |
269 | void QBar3DSeries::setMeshAngle(float angle) |
270 | { |
271 | setMeshRotation(QQuaternion::fromAxisAndAngle(axis: upVector, angle)); |
272 | } |
273 | |
274 | float QBar3DSeries::meshAngle() const |
275 | { |
276 | QQuaternion rotation = meshRotation(); |
277 | |
278 | if (rotation.isIdentity() || rotation.x() != 0.0f || rotation.z() != 0.0f) |
279 | return 0.0f; |
280 | else |
281 | return quaternionAngle(rotation); |
282 | } |
283 | |
284 | /*! |
285 | * \internal |
286 | */ |
287 | QBar3DSeriesPrivate *QBar3DSeries::dptr() |
288 | { |
289 | return static_cast<QBar3DSeriesPrivate *>(d_ptr.data()); |
290 | } |
291 | |
292 | /*! |
293 | * \internal |
294 | */ |
295 | const QBar3DSeriesPrivate *QBar3DSeries::dptrc() const |
296 | { |
297 | return static_cast<const QBar3DSeriesPrivate *>(d_ptr.data()); |
298 | } |
299 | |
300 | // QBar3DSeriesPrivate |
301 | |
302 | QBar3DSeriesPrivate::QBar3DSeriesPrivate(QBar3DSeries *q) |
303 | : QAbstract3DSeriesPrivate(q, QAbstract3DSeries::SeriesTypeBar), |
304 | m_selectedBar(Bars3DController::invalidSelectionPosition()) |
305 | { |
306 | m_itemLabelFormat = QStringLiteral("@valueLabel" ); |
307 | m_mesh = QAbstract3DSeries::MeshBevelBar; |
308 | } |
309 | |
310 | QBar3DSeriesPrivate::~QBar3DSeriesPrivate() |
311 | { |
312 | } |
313 | |
314 | QBar3DSeries *QBar3DSeriesPrivate::qptr() |
315 | { |
316 | return static_cast<QBar3DSeries *>(q_ptr); |
317 | } |
318 | |
319 | void QBar3DSeriesPrivate::setDataProxy(QAbstractDataProxy *proxy) |
320 | { |
321 | Q_ASSERT(proxy->type() == QAbstractDataProxy::DataTypeBar); |
322 | |
323 | QAbstract3DSeriesPrivate::setDataProxy(proxy); |
324 | |
325 | emit qptr()->dataProxyChanged(proxy: static_cast<QBarDataProxy *>(proxy)); |
326 | } |
327 | |
328 | void QBar3DSeriesPrivate::connectControllerAndProxy(Abstract3DController *newController) |
329 | { |
330 | QBarDataProxy *barDataProxy = static_cast<QBarDataProxy *>(m_dataProxy); |
331 | |
332 | if (m_controller && barDataProxy) { |
333 | // Disconnect old controller/old proxy |
334 | QObject::disconnect(sender: barDataProxy, signal: 0, receiver: m_controller, member: 0); |
335 | QObject::disconnect(sender: q_ptr, signal: 0, receiver: m_controller, member: 0); |
336 | } |
337 | |
338 | if (newController && barDataProxy) { |
339 | Bars3DController *controller = static_cast<Bars3DController *>(newController); |
340 | QObject::connect(sender: barDataProxy, signal: &QBarDataProxy::arrayReset, receiver: controller, |
341 | slot: &Bars3DController::handleArrayReset); |
342 | QObject::connect(sender: barDataProxy, signal: &QBarDataProxy::rowsAdded, receiver: controller, |
343 | slot: &Bars3DController::handleRowsAdded); |
344 | QObject::connect(sender: barDataProxy, signal: &QBarDataProxy::rowsChanged, receiver: controller, |
345 | slot: &Bars3DController::handleRowsChanged); |
346 | QObject::connect(sender: barDataProxy, signal: &QBarDataProxy::rowsRemoved, receiver: controller, |
347 | slot: &Bars3DController::handleRowsRemoved); |
348 | QObject::connect(sender: barDataProxy, signal: &QBarDataProxy::rowsInserted, receiver: controller, |
349 | slot: &Bars3DController::handleRowsInserted); |
350 | QObject::connect(sender: barDataProxy, signal: &QBarDataProxy::itemChanged, receiver: controller, |
351 | slot: &Bars3DController::handleItemChanged); |
352 | QObject::connect(sender: barDataProxy, signal: &QBarDataProxy::rowLabelsChanged, receiver: controller, |
353 | slot: &Bars3DController::handleDataRowLabelsChanged); |
354 | QObject::connect(sender: barDataProxy, signal: &QBarDataProxy::columnLabelsChanged, receiver: controller, |
355 | slot: &Bars3DController::handleDataColumnLabelsChanged); |
356 | QObject::connect(sender: qptr(), signal: &QBar3DSeries::dataProxyChanged, receiver: controller, |
357 | slot: &Bars3DController::handleArrayReset); |
358 | } |
359 | } |
360 | |
361 | void QBar3DSeriesPrivate::createItemLabel() |
362 | { |
363 | static const QString rowIndexTag(QStringLiteral("@rowIdx" )); |
364 | static const QString rowLabelTag(QStringLiteral("@rowLabel" )); |
365 | static const QString rowTitleTag(QStringLiteral("@rowTitle" )); |
366 | static const QString colIndexTag(QStringLiteral("@colIdx" )); |
367 | static const QString colLabelTag(QStringLiteral("@colLabel" )); |
368 | static const QString colTitleTag(QStringLiteral("@colTitle" )); |
369 | static const QString valueTitleTag(QStringLiteral("@valueTitle" )); |
370 | static const QString valueLabelTag(QStringLiteral("@valueLabel" )); |
371 | static const QString seriesNameTag(QStringLiteral("@seriesName" )); |
372 | |
373 | if (m_selectedBar == QBar3DSeries::invalidSelectionPosition()) { |
374 | m_itemLabel = QString(); |
375 | return; |
376 | } |
377 | |
378 | QLocale locale(QLocale::c()); |
379 | if (m_controller) |
380 | locale = m_controller->locale(); |
381 | |
382 | QCategory3DAxis *categoryAxisZ = static_cast<QCategory3DAxis *>(m_controller->axisZ()); |
383 | QCategory3DAxis *categoryAxisX = static_cast<QCategory3DAxis *>(m_controller->axisX()); |
384 | QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(m_controller->axisY()); |
385 | qreal selectedBarValue = qreal(qptr()->dataProxy()->itemAt(position: m_selectedBar)->value()); |
386 | |
387 | // Custom format expects printf format specifier. There is no tag for it. |
388 | m_itemLabel = valueAxis->formatter()->stringForValue(value: selectedBarValue, format: m_itemLabelFormat); |
389 | |
390 | int selBarPosRow = m_selectedBar.x(); |
391 | int selBarPosCol = m_selectedBar.y(); |
392 | m_itemLabel.replace(before: rowIndexTag, after: locale.toString(i: selBarPosRow)); |
393 | if (categoryAxisZ->labels().size() > selBarPosRow) |
394 | m_itemLabel.replace(before: rowLabelTag, after: categoryAxisZ->labels().at(i: selBarPosRow)); |
395 | else |
396 | m_itemLabel.replace(before: rowLabelTag, after: QString()); |
397 | m_itemLabel.replace(before: rowTitleTag, after: categoryAxisZ->title()); |
398 | m_itemLabel.replace(before: colIndexTag, after: locale.toString(i: selBarPosCol)); |
399 | if (categoryAxisX->labels().size() > selBarPosCol) |
400 | m_itemLabel.replace(before: colLabelTag, after: categoryAxisX->labels().at(i: selBarPosCol)); |
401 | else |
402 | m_itemLabel.replace(before: colLabelTag, after: QString()); |
403 | m_itemLabel.replace(before: colTitleTag, after: categoryAxisX->title()); |
404 | m_itemLabel.replace(before: valueTitleTag, after: valueAxis->title()); |
405 | |
406 | if (m_itemLabel.contains(s: valueLabelTag)) { |
407 | QString valueLabelText = valueAxis->formatter()->stringForValue(value: selectedBarValue, |
408 | format: valueAxis->labelFormat()); |
409 | m_itemLabel.replace(before: valueLabelTag, after: valueLabelText); |
410 | } |
411 | |
412 | m_itemLabel.replace(before: seriesNameTag, after: m_name); |
413 | } |
414 | |
415 | void QBar3DSeriesPrivate::handleMeshRotationChanged(const QQuaternion &rotation) |
416 | { |
417 | emit qptr()->meshAngleChanged(angle: quaternionAngle(rotation)); |
418 | } |
419 | |
420 | void QBar3DSeriesPrivate::setSelectedBar(const QPoint &position) |
421 | { |
422 | if (position != m_selectedBar) { |
423 | markItemLabelDirty(); |
424 | m_selectedBar = position; |
425 | emit qptr()->selectedBarChanged(position: m_selectedBar); |
426 | } |
427 | } |
428 | |
429 | void QBar3DSeriesPrivate::connectSignals() |
430 | { |
431 | QObject::connect(sender: q_ptr, signal: &QAbstract3DSeries::meshRotationChanged, receiver: this, |
432 | slot: &QBar3DSeriesPrivate::handleMeshRotationChanged); |
433 | } |
434 | |
435 | QT_END_NAMESPACE_DATAVISUALIZATION |
436 | |