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

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