1 | // Copyright (C) 2023 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include <QtCore/qdebug.h> |
5 | #include "qabstract3daxis_p.h" |
6 | |
7 | QT_BEGIN_NAMESPACE |
8 | |
9 | /*! |
10 | * \class QAbstract3DAxis |
11 | * \inmodule QtGraphs |
12 | * \brief The QAbstract3DAxis class is a base class for the axes of a graph. |
13 | * |
14 | * This class specifies the enumerations, properties, and functions shared by |
15 | * graph axes. It should not be used directly, but one of its subclasses should |
16 | * be used instead. |
17 | * |
18 | * \sa QCategory3DAxis, QValue3DAxis |
19 | */ |
20 | |
21 | /*! |
22 | * \qmltype AbstractAxis3D |
23 | * \inqmlmodule QtGraphs |
24 | * \ingroup graphs_qml |
25 | * \instantiates QAbstract3DAxis |
26 | * \brief A base type for the axes of a graph. |
27 | * |
28 | * This type is uncreatable, but contains properties that are exposed via subtypes. |
29 | * |
30 | * For AbstractAxis3D enums, see \l QAbstract3DAxis::AxisOrientation and |
31 | * \l{QAbstract3DAxis::AxisType}. |
32 | */ |
33 | |
34 | /*! |
35 | * \qmlproperty string AbstractAxis3D::title |
36 | * The title for the axis. |
37 | * |
38 | * \sa titleVisible, titleFixed |
39 | */ |
40 | |
41 | /*! |
42 | * \qmlproperty list AbstractAxis3D::labels |
43 | * The labels for the axis. |
44 | * \note Setting this property for ValueAxis3D does nothing, as it generates labels automatically. |
45 | */ |
46 | |
47 | /*! |
48 | * \qmlproperty AbstractAxis3D.AxisOrientation AbstractAxis3D::orientation |
49 | * The orientation of the axis. |
50 | */ |
51 | |
52 | /*! |
53 | * \qmlproperty AbstractAxis3D.AxisType AbstractAxis3D::type |
54 | * The type of the axis. |
55 | */ |
56 | |
57 | /*! |
58 | * \qmlproperty real AbstractAxis3D::min |
59 | * |
60 | * The minimum value on the axis. |
61 | * When setting this property, the maximum value is adjusted if necessary, to |
62 | * ensure that the range remains valid. |
63 | */ |
64 | |
65 | /*! |
66 | * \qmlproperty real AbstractAxis3D::max |
67 | * |
68 | * The maximum value on the axis. |
69 | * When setting this property, the minimum value is adjusted if necessary, to |
70 | * ensure that the range remains valid. |
71 | */ |
72 | |
73 | /*! |
74 | * \qmlproperty bool AbstractAxis3D::autoAdjustRange |
75 | * |
76 | * Defines whether the axis will automatically adjust the range so that all data fits in it. |
77 | */ |
78 | |
79 | /*! |
80 | * \qmlproperty real AbstractAxis3D::labelAutoRotation |
81 | * |
82 | * The maximum angle the labels can autorotate when the camera angle changes. |
83 | * The angle can be between 0 and 90, inclusive. The default value is 0. |
84 | * If the value is 0, axis labels do not automatically rotate. |
85 | * If the value is greater than zero, labels attempt to orient themselves toward the camera, up to |
86 | * the specified angle. |
87 | */ |
88 | |
89 | /*! |
90 | * \qmlproperty bool AbstractAxis3D::titleVisible |
91 | * |
92 | * Defines whether the axis title is visible in the primary graph view. |
93 | * |
94 | * The default value is \c{false}. |
95 | * |
96 | * \sa title, titleFixed |
97 | */ |
98 | |
99 | /*! |
100 | * \qmlproperty bool AbstractAxis3D::titleFixed |
101 | * |
102 | * The rotation of axis titles. |
103 | * |
104 | * If \c{true}, axis titles in the primary graph view will be rotated towards the camera similarly |
105 | * to the axis labels. |
106 | * If \c{false}, axis titles are only rotated around their axis but are not otherwise oriented |
107 | * towards the camera. |
108 | * This property does not have any effect if the labelAutoRotation property |
109 | * value is zero. |
110 | * Default value is \c{true}. |
111 | * |
112 | * \sa labelAutoRotation, title, titleVisible |
113 | */ |
114 | |
115 | /*! |
116 | * \enum QAbstract3DAxis::AxisOrientation |
117 | * |
118 | * The orientation of the axis object. |
119 | * |
120 | * \value AxisOrientationNone |
121 | * \value AxisOrientationX |
122 | * \value AxisOrientationY |
123 | * \value AxisOrientationZ |
124 | */ |
125 | |
126 | /*! |
127 | * \enum QAbstract3DAxis::AxisType |
128 | * |
129 | * The type of the axis object. |
130 | * |
131 | * \value AxisTypeNone |
132 | * \value AxisTypeCategory |
133 | * \value AxisTypeValue |
134 | */ |
135 | |
136 | /*! |
137 | * \internal |
138 | */ |
139 | QAbstract3DAxis::QAbstract3DAxis(QAbstract3DAxisPrivate *d, QObject *parent) : |
140 | QObject(parent), |
141 | d_ptr(d) |
142 | { |
143 | } |
144 | |
145 | /*! |
146 | * Destroys QAbstract3DAxis. |
147 | */ |
148 | QAbstract3DAxis::~QAbstract3DAxis() |
149 | { |
150 | } |
151 | |
152 | /*! |
153 | * \property QAbstract3DAxis::orientation |
154 | * |
155 | * \brief The orientation of the axis. |
156 | * |
157 | * The value is one of AxisOrientation values. |
158 | */ |
159 | QAbstract3DAxis::AxisOrientation QAbstract3DAxis::orientation() const |
160 | { |
161 | const Q_D(QAbstract3DAxis); |
162 | return d->m_orientation; |
163 | } |
164 | |
165 | /*! |
166 | * \property QAbstract3DAxis::type |
167 | * |
168 | * \brief The type of the axis. |
169 | * |
170 | * The value is one of AxisType values. |
171 | */ |
172 | QAbstract3DAxis::AxisType QAbstract3DAxis::type() const |
173 | { |
174 | const Q_D(QAbstract3DAxis); |
175 | return d->m_type; |
176 | } |
177 | |
178 | /*! |
179 | * \property QAbstract3DAxis::title |
180 | * |
181 | * \brief The title for the axis. |
182 | * |
183 | * \sa titleVisible, titleFixed |
184 | */ |
185 | void QAbstract3DAxis::setTitle(const QString &title) |
186 | { |
187 | Q_D(QAbstract3DAxis); |
188 | if (d->m_title != title) { |
189 | d->m_title = title; |
190 | emit titleChanged(newTitle: title); |
191 | } |
192 | } |
193 | |
194 | QString QAbstract3DAxis::title() const |
195 | { |
196 | const Q_D(QAbstract3DAxis); |
197 | return d->m_title; |
198 | } |
199 | |
200 | /*! |
201 | * \property QAbstract3DAxis::labels |
202 | * |
203 | * \brief The labels for the axis. |
204 | * \note Setting this property for QValue3DAxis does nothing, as it generates labels automatically. |
205 | */ |
206 | void QAbstract3DAxis::setLabels(const QStringList &labels) |
207 | { |
208 | Q_UNUSED(labels); |
209 | } |
210 | |
211 | QStringList QAbstract3DAxis::labels() const |
212 | { |
213 | const Q_D(QAbstract3DAxis); |
214 | const_cast<QAbstract3DAxisPrivate *>(d)->updateLabels(); |
215 | return const_cast<QAbstract3DAxisPrivate *>(d)->m_labels; |
216 | } |
217 | |
218 | /*! |
219 | * Sets the value range of the axis from \a min to \a max. |
220 | * When setting the range, the maximum value is adjusted if necessary, to ensure |
221 | * that the range remains valid. |
222 | * \note For QCategory3DAxis, specifies the index range of rows or columns to |
223 | * show. |
224 | */ |
225 | void QAbstract3DAxis::setRange(float min, float max) |
226 | { |
227 | Q_D(QAbstract3DAxis); |
228 | d->setRange(min, max); |
229 | setAutoAdjustRange(false); |
230 | } |
231 | |
232 | /*! |
233 | * \property QAbstract3DAxis::labelAutoRotation |
234 | * |
235 | * \brief The maximum angle the labels can autorotate when the camera angle changes. |
236 | * |
237 | * The angle can be between 0 and 90, inclusive. The default value is 0. |
238 | * If the value is 0, axis labels do not automatically rotate. |
239 | * If the value is greater than zero, labels attempt to orient themselves toward the camera, up to |
240 | * the specified angle. |
241 | */ |
242 | void QAbstract3DAxis::setLabelAutoRotation(float angle) |
243 | { |
244 | Q_D(QAbstract3DAxis); |
245 | if (angle < 0.0f) |
246 | angle = 0.0f; |
247 | if (angle > 90.0f) |
248 | angle = 90.0f; |
249 | if (d->m_labelAutoRotation != angle) { |
250 | d->m_labelAutoRotation = angle; |
251 | emit labelAutoRotationChanged(angle); |
252 | } |
253 | } |
254 | |
255 | float QAbstract3DAxis::labelAutoRotation() const |
256 | { |
257 | const Q_D(QAbstract3DAxis); |
258 | return d->m_labelAutoRotation; |
259 | } |
260 | |
261 | /*! |
262 | * \property QAbstract3DAxis::titleVisible |
263 | * |
264 | * \brief Whether the axis title is visible in the primary graph view. |
265 | * |
266 | * The default value is \c{false}. |
267 | * |
268 | * \sa title, titleFixed |
269 | */ |
270 | void QAbstract3DAxis::setTitleVisible(bool visible) |
271 | { |
272 | Q_D(QAbstract3DAxis); |
273 | if (d->m_titleVisible != visible) { |
274 | d->m_titleVisible = visible; |
275 | emit titleVisibilityChanged(visible); |
276 | } |
277 | } |
278 | |
279 | bool QAbstract3DAxis::isTitleVisible() const |
280 | { |
281 | const Q_D(QAbstract3DAxis); |
282 | return d->m_titleVisible; |
283 | } |
284 | |
285 | /*! |
286 | * \property QAbstract3DAxis::titleFixed |
287 | * |
288 | * \brief The rotation of the axis titles. |
289 | * |
290 | * If \c{true}, axis titles in the primary graph view will be rotated towards the camera similarly |
291 | * to the axis labels. |
292 | * If \c{false}, axis titles are only rotated around their axis but are not otherwise oriented |
293 | * towards the camera. |
294 | * This property does not have any effect if the labelAutoRotation property |
295 | * value is zero. |
296 | * Default value is \c{true}. |
297 | * |
298 | * \sa labelAutoRotation, title, titleVisible |
299 | */ |
300 | void QAbstract3DAxis::setTitleFixed(bool fixed) |
301 | { |
302 | Q_D(QAbstract3DAxis); |
303 | if (d->m_titleFixed != fixed) { |
304 | d->m_titleFixed = fixed; |
305 | emit titleFixedChanged(fixed); |
306 | } |
307 | } |
308 | |
309 | bool QAbstract3DAxis::isTitleFixed() const |
310 | { |
311 | const Q_D(QAbstract3DAxis); |
312 | return d->m_titleFixed; |
313 | } |
314 | |
315 | /*! |
316 | * \property QAbstract3DAxis::min |
317 | * |
318 | * \brief The minimum value on the axis. |
319 | * |
320 | * When setting this property, the maximum value is adjusted if necessary, to |
321 | * ensure that the range remains valid. |
322 | * \note For QCategory3DAxis, specifies the index of the first row or column to |
323 | * show. |
324 | */ |
325 | void QAbstract3DAxis::setMin(float min) |
326 | { |
327 | Q_D(QAbstract3DAxis); |
328 | d->setMin(min); |
329 | setAutoAdjustRange(false); |
330 | } |
331 | |
332 | /*! |
333 | * \property QAbstract3DAxis::max |
334 | * |
335 | * \brief The maximum value on the axis. |
336 | * |
337 | * When setting this property, the minimum value is adjusted if necessary, to |
338 | * ensure that the range remains valid. |
339 | * \note For QCategory3DAxis, specifies the index of the last row or column to |
340 | * show. |
341 | */ |
342 | void QAbstract3DAxis::setMax(float max) |
343 | { |
344 | Q_D(QAbstract3DAxis); |
345 | d->setMax(max); |
346 | setAutoAdjustRange(false); |
347 | } |
348 | |
349 | float QAbstract3DAxis::min() const |
350 | { |
351 | const Q_D(QAbstract3DAxis); |
352 | return d->m_min; |
353 | } |
354 | |
355 | float QAbstract3DAxis::max() const |
356 | { |
357 | const Q_D(QAbstract3DAxis); |
358 | return d->m_max; |
359 | } |
360 | |
361 | /*! |
362 | * \property QAbstract3DAxis::autoAdjustRange |
363 | * |
364 | * \brief Whether the axis will automatically adjust the range so that all data fits in it. |
365 | * |
366 | * \sa setRange(), setMin(), setMax() |
367 | */ |
368 | void QAbstract3DAxis::setAutoAdjustRange(bool autoAdjust) |
369 | { |
370 | Q_D(QAbstract3DAxis); |
371 | if (d->m_autoAdjust != autoAdjust) { |
372 | d->m_autoAdjust = autoAdjust; |
373 | emit autoAdjustRangeChanged(autoAdjust); |
374 | } |
375 | } |
376 | |
377 | bool QAbstract3DAxis::isAutoAdjustRange() const |
378 | { |
379 | const Q_D(QAbstract3DAxis); |
380 | return d->m_autoAdjust; |
381 | } |
382 | |
383 | /*! |
384 | * \fn QAbstract3DAxis::rangeChanged(float min, float max) |
385 | * |
386 | * Emits the minimum and maximum values of the range, \a min and \a max, when |
387 | * the range changes. |
388 | */ |
389 | |
390 | // QAbstract3DAxisPrivate |
391 | QAbstract3DAxisPrivate::QAbstract3DAxisPrivate(QAbstract3DAxis *q, QAbstract3DAxis::AxisType type) |
392 | : q_ptr(q), |
393 | m_orientation(QAbstract3DAxis::AxisOrientationNone), |
394 | m_type(type), |
395 | m_isDefaultAxis(false), |
396 | m_min(0.0f), |
397 | m_max(10.0f), |
398 | m_autoAdjust(true), |
399 | m_labelAutoRotation(0.0f), |
400 | m_titleVisible(false), |
401 | m_titleFixed(true) |
402 | { |
403 | } |
404 | |
405 | QAbstract3DAxisPrivate::~QAbstract3DAxisPrivate() |
406 | { |
407 | } |
408 | |
409 | void QAbstract3DAxisPrivate::setOrientation(QAbstract3DAxis::AxisOrientation orientation) |
410 | { |
411 | Q_Q(QAbstract3DAxis); |
412 | if (m_orientation == QAbstract3DAxis::AxisOrientationNone) { |
413 | m_orientation = orientation; |
414 | emit q->orientationChanged(orientation); |
415 | } else { |
416 | Q_ASSERT("Attempted to reset axis orientation." ); |
417 | } |
418 | } |
419 | |
420 | void QAbstract3DAxisPrivate::updateLabels() |
421 | { |
422 | // Default implementation does nothing |
423 | } |
424 | |
425 | void QAbstract3DAxisPrivate::setRange(float min, float max, bool suppressWarnings) |
426 | { |
427 | Q_Q(QAbstract3DAxis); |
428 | bool adjusted = false; |
429 | if (!allowNegatives()) { |
430 | if (allowZero()) { |
431 | if (min < 0.0f) { |
432 | min = 0.0f; |
433 | adjusted = true; |
434 | } |
435 | if (max < 0.0f) { |
436 | max = 0.0f; |
437 | adjusted = true; |
438 | } |
439 | } else { |
440 | if (min <= 0.0f) { |
441 | min = 1.0f; |
442 | adjusted = true; |
443 | } |
444 | if (max <= 0.0f) { |
445 | max = 1.0f; |
446 | adjusted = true; |
447 | } |
448 | } |
449 | } |
450 | // If min >= max, we adjust ranges so that |
451 | // m_max becomes (min + 1.0f) |
452 | // as axes need some kind of valid range. |
453 | bool minDirty = false; |
454 | bool maxDirty = false; |
455 | if (m_min != min) { |
456 | m_min = min; |
457 | minDirty = true; |
458 | } |
459 | if (m_max != max || min > max || (!allowMinMaxSame() && min == max)) { |
460 | if (min > max || (!allowMinMaxSame() && min == max)) { |
461 | m_max = min + 1.0f; |
462 | adjusted = true; |
463 | } else { |
464 | m_max = max; |
465 | } |
466 | maxDirty = true; |
467 | } |
468 | |
469 | if (minDirty || maxDirty) { |
470 | if (adjusted && !suppressWarnings) { |
471 | qWarning() << "Warning: Tried to set invalid range for axis." |
472 | " Range automatically adjusted to a valid one:" |
473 | << min << "-" << max << "-->" << m_min << "-" << m_max; |
474 | } |
475 | emit q->rangeChanged(min: m_min, max: m_max); |
476 | } |
477 | |
478 | if (minDirty) |
479 | emit q->minChanged(value: m_min); |
480 | if (maxDirty) |
481 | emit q->maxChanged(value: m_max); |
482 | } |
483 | |
484 | void QAbstract3DAxisPrivate::setMin(float min) |
485 | { |
486 | Q_Q(QAbstract3DAxis); |
487 | if (!allowNegatives()) { |
488 | if (allowZero()) { |
489 | if (min < 0.0f) { |
490 | min = 0.0f; |
491 | qWarning() << "Warning: Tried to set negative minimum for an axis that only" |
492 | "supports positive values and zero:" << min; |
493 | } |
494 | } else { |
495 | if (min <= 0.0f) { |
496 | min = 1.0f; |
497 | qWarning() << "Warning: Tried to set negative or zero minimum for an axis that only" |
498 | "supports positive values:" << min; |
499 | } |
500 | } |
501 | } |
502 | |
503 | if (m_min != min) { |
504 | bool maxChanged = false; |
505 | if (min > m_max || (!allowMinMaxSame() && min == m_max)) { |
506 | float oldMax = m_max; |
507 | m_max = min + 1.0f; |
508 | qWarning() << "Warning: Tried to set minimum to equal or larger than maximum for" |
509 | " value axis. Maximum automatically adjusted to a valid one:" |
510 | << oldMax << "-->" << m_max; |
511 | maxChanged = true; |
512 | } |
513 | m_min = min; |
514 | |
515 | emit q->rangeChanged(min: m_min, max: m_max); |
516 | emit q->minChanged(value: m_min); |
517 | if (maxChanged) |
518 | emit q->maxChanged(value: m_max); |
519 | } |
520 | } |
521 | |
522 | void QAbstract3DAxisPrivate::setMax(float max) |
523 | { |
524 | Q_Q(QAbstract3DAxis); |
525 | if (!allowNegatives()) { |
526 | if (allowZero()) { |
527 | if (max < 0.0f) { |
528 | max = 0.0f; |
529 | qWarning() << "Warning: Tried to set negative maximum for an axis that only" |
530 | "supports positive values and zero:" << max; |
531 | } |
532 | } else { |
533 | if (max <= 0.0f) { |
534 | max = 1.0f; |
535 | qWarning() << "Warning: Tried to set negative or zero maximum for an axis that only" |
536 | "supports positive values:" << max; |
537 | } |
538 | } |
539 | } |
540 | |
541 | if (m_max != max) { |
542 | bool minChanged = false; |
543 | if (m_min > max || (!allowMinMaxSame() && m_min == max)) { |
544 | float oldMin = m_min; |
545 | m_min = max - 1.0f; |
546 | if (!allowNegatives() && m_min < 0.0f) { |
547 | if (allowZero()) |
548 | m_min = 0.0f; |
549 | else |
550 | m_min = max / 2.0f; // Need some positive value smaller than max |
551 | |
552 | if (!allowMinMaxSame() && max == 0.0f) { |
553 | m_min = oldMin; |
554 | qWarning() << "Unable to set maximum value to zero." ; |
555 | return; |
556 | } |
557 | } |
558 | qWarning() << "Warning: Tried to set maximum to equal or smaller than minimum for" |
559 | " value axis. Minimum automatically adjusted to a valid one:" |
560 | << oldMin << "-->" << m_min; |
561 | minChanged = true; |
562 | } |
563 | m_max = max; |
564 | emit q->rangeChanged(min: m_min, max: m_max); |
565 | emit q->maxChanged(value: m_max); |
566 | if (minChanged) |
567 | emit q->minChanged(value: m_min); |
568 | } |
569 | } |
570 | |
571 | QT_END_NAMESPACE |
572 | |