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

source code of qtgraphs/src/graphs/data/qbar3dseries.cpp