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
34QT_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 */
152QBar3DSeries::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 */
164QBar3DSeries::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 */
174QBar3DSeries::~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 */
187void QBar3DSeries::setDataProxy(QBarDataProxy *proxy)
188{
189 d_ptr->setDataProxy(proxy);
190}
191
192QBarDataProxy *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 */
222void 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
231QPoint 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 */
242QPoint QBar3DSeries::invalidSelectionPosition()
243{
244 return Bars3DController::invalidSelectionPosition();
245}
246
247static 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 */
269void QBar3DSeries::setMeshAngle(float angle)
270{
271 setMeshRotation(QQuaternion::fromAxisAndAngle(axis: upVector, angle));
272}
273
274float 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 */
287QBar3DSeriesPrivate *QBar3DSeries::dptr()
288{
289 return static_cast<QBar3DSeriesPrivate *>(d_ptr.data());
290}
291
292/*!
293 * \internal
294 */
295const QBar3DSeriesPrivate *QBar3DSeries::dptrc() const
296{
297 return static_cast<const QBar3DSeriesPrivate *>(d_ptr.data());
298}
299
300// QBar3DSeriesPrivate
301
302QBar3DSeriesPrivate::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
310QBar3DSeriesPrivate::~QBar3DSeriesPrivate()
311{
312}
313
314QBar3DSeries *QBar3DSeriesPrivate::qptr()
315{
316 return static_cast<QBar3DSeries *>(q_ptr);
317}
318
319void 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
328void 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
361void 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
415void QBar3DSeriesPrivate::handleMeshRotationChanged(const QQuaternion &rotation)
416{
417 emit qptr()->meshAngleChanged(angle: quaternionAngle(rotation));
418}
419
420void 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
429void QBar3DSeriesPrivate::connectSignals()
430{
431 QObject::connect(sender: q_ptr, signal: &QAbstract3DSeries::meshRotationChanged, receiver: this,
432 slot: &QBar3DSeriesPrivate::handleMeshRotationChanged);
433}
434
435QT_END_NAMESPACE_DATAVISUALIZATION
436

source code of qtdatavis3d/src/datavisualization/data/qbar3dseries.cpp