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 | |
11 | QT_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 | */ |
138 | QBar3DSeries::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 | */ |
151 | QBar3DSeries::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 | */ |
162 | QBar3DSeries::~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 | */ |
175 | void QBar3DSeries::setDataProxy(QBarDataProxy *proxy) |
176 | { |
177 | Q_D(QBar3DSeries); |
178 | d->setDataProxy(proxy); |
179 | } |
180 | |
181 | QBarDataProxy *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 | */ |
212 | void 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 | |
222 | QPoint 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 | */ |
234 | QPoint QBar3DSeries::invalidSelectionPosition() |
235 | { |
236 | return Bars3DController::invalidSelectionPosition(); |
237 | } |
238 | |
239 | static 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 | */ |
261 | void QBar3DSeries::setMeshAngle(float angle) |
262 | { |
263 | setMeshRotation(QQuaternion::fromAxisAndAngle(axis: upVector, angle)); |
264 | } |
265 | |
266 | float 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 | */ |
288 | void QBar3DSeries::setRowColors(const QList<QColor> &colors) |
289 | { |
290 | Q_D(QBar3DSeries); |
291 | d->setRowColors(colors); |
292 | } |
293 | QList<QColor> QBar3DSeries::rowColors() const |
294 | { |
295 | const Q_D(QBar3DSeries); |
296 | return d->m_rowColors; |
297 | } |
298 | |
299 | /*! |
300 | * \internal |
301 | */ |
302 | void QBar3DSeries::connectSignals() |
303 | { |
304 | QObject::connect(sender: this, signal: &QAbstract3DSeries::meshRotationChanged, context: this, |
305 | slot: &QBar3DSeries::handleMeshRotationChanged); |
306 | } |
307 | |
308 | /*! |
309 | * \internal |
310 | */ |
311 | void QBar3DSeries::handleMeshRotationChanged(const QQuaternion &rotation) |
312 | { |
313 | emit meshAngleChanged(angle: quaternionAngle(rotation)); |
314 | } |
315 | |
316 | // QBar3DSeriesPrivate |
317 | |
318 | QBar3DSeriesPrivate::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 | |
326 | QBar3DSeriesPrivate::~QBar3DSeriesPrivate() |
327 | { |
328 | } |
329 | |
330 | void 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 | |
340 | void 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 | |
376 | void 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 | |
433 | void 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 | |
443 | void 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 | |
452 | QT_END_NAMESPACE |
453 | |