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

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