1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qvalue3daxis_p.h"
5#include "qvalue3daxisformatter_p.h"
6#include "qgraphs3dlogging_p.h"
7
8QT_BEGIN_NAMESPACE
9
10/*!
11 * \class QValue3DAxisFormatter
12 * \inmodule QtGraphs
13 * \ingroup graphs_3D
14 * \brief The QValue3DAxisFormatter class is a base class for 3D value axis
15 * formatters.
16 *
17 * This class provides formatting rules for a linear value 3D axis. Subclass it
18 * if you want to implement custom value axes.
19 *
20 * The base class has no public API beyond constructors and destructors. It is
21 * meant to be only used internally. However, subclasses may implement public
22 * properties as needed.
23 *
24 * \sa QValue3DAxis, QLogValue3DAxisFormatter
25 */
26
27/*!
28 * \qmltype Value3DAxisFormatter
29 * \inqmlmodule QtGraphs
30 * \ingroup graphs_qml_3D
31 * \nativetype QValue3DAxisFormatter
32 * \brief A base type for 3D value axis formatters.
33 *
34 * This type provides formatting rules for a linear value 3D axis.
35 * This type is the default type for Value3DAxis and thus never needs to be
36 * explicitly created. This type has no public functionality.
37 *
38 * \sa Value3DAxis
39 */
40
41/*!
42 * \internal
43 */
44QValue3DAxisFormatter::QValue3DAxisFormatter(QValue3DAxisFormatterPrivate &d, QObject *parent)
45 : QObject(d, parent)
46{}
47
48/*!
49 * Constructs a new value 3D axis formatter with the optional parent \a parent.
50 */
51QValue3DAxisFormatter::QValue3DAxisFormatter(QObject *parent)
52 : QObject(*(new QValue3DAxisFormatterPrivate()), parent)
53{}
54
55/*!
56 * Deletes the value 3D axis formatter.
57 */
58QValue3DAxisFormatter::~QValue3DAxisFormatter() {}
59
60/*!
61 * Allows the parent axis to have negative values if \a allow is \c true.
62 */
63void QValue3DAxisFormatter::setAllowNegatives(bool allow)
64{
65 Q_D(QValue3DAxisFormatter);
66 if (d->m_allowNegatives == allow) {
67 qCDebug(lcAProperties3D) << __FUNCTION__
68 << "value is already set to: " << allow;
69 return;
70 }
71
72 d->m_allowNegatives = allow;
73}
74
75/*!
76 * Returns \c true if negative values are valid values for the parent axis.
77 * The default implementation always returns \c true.
78 */
79bool QValue3DAxisFormatter::allowNegatives() const
80{
81 Q_D(const QValue3DAxisFormatter);
82 return d->m_allowNegatives;
83}
84
85/*!
86 * Allows the parent axis to have a zero value if \a allow is \c true.
87 */
88void QValue3DAxisFormatter::setAllowZero(bool allow)
89{
90 Q_D(QValue3DAxisFormatter);
91 if (d->m_allowZero == allow) {
92 qCDebug(lcAProperties3D) << __FUNCTION__
93 << "value is already set to: " << allow;
94 return;
95 }
96
97 d->m_allowZero = allow;
98}
99
100/*!
101 * Returns \c true if zero is a valid value for the parent axis.
102 * The default implementation always returns \c true.
103 */
104bool QValue3DAxisFormatter::allowZero() const
105{
106 Q_D(const QValue3DAxisFormatter);
107 return d->m_allowZero;
108}
109
110/*!
111 * \internal
112 */
113void QValue3DAxisFormatter::setAxis(QValue3DAxis *axis)
114{
115 Q_ASSERT(axis);
116 Q_D(QValue3DAxisFormatter);
117
118 // These signals are all connected to markDirtyNoLabelChange slot, even though
119 // most of them do require labels to be regenerated. This is because the label
120 // regeneration is triggered elsewhere in these cases.
121 connect(sender: axis,
122 signal: &QValue3DAxis::segmentCountChanged,
123 context: this,
124 slot: &QValue3DAxisFormatter::markDirtyNoLabelChange);
125 connect(sender: axis,
126 signal: &QValue3DAxis::subSegmentCountChanged,
127 context: this,
128 slot: &QValue3DAxisFormatter::markDirtyNoLabelChange);
129 connect(sender: axis,
130 signal: &QValue3DAxis::labelFormatChanged,
131 context: this,
132 slot: &QValue3DAxisFormatter::markDirtyNoLabelChange);
133 connect(sender: axis,
134 signal: &QAbstract3DAxis::rangeChanged,
135 context: this,
136 slot: &QValue3DAxisFormatter::markDirtyNoLabelChange);
137
138 d->setAxis(axis);
139}
140
141/*!
142 * \internal
143 */
144void QValue3DAxisFormatter::markDirtyNoLabelChange()
145{
146 Q_D(QValue3DAxisFormatter);
147 d->markDirty(labelsChange: false);
148}
149
150/*!
151 * Creates a new empty value 3D axis formatter. Must be reimplemented in a
152 * subclass.
153 *
154 * Returns the new formatter. The renderer uses this method to cache a copy of
155 * the formatter. The ownership of the new copy is transferred to the caller.
156 */
157QValue3DAxisFormatter *QValue3DAxisFormatter::createNewInstance() const
158{
159 return new QValue3DAxisFormatter();
160}
161
162/*!
163 * Resizes and populates the label and grid line position arrays and the label
164 * strings array, as well as calculates any values needed to map a value to its
165 * position. The parent axis can be accessed from inside this function.
166 *
167 * This method must be reimplemented in a subclass if the default array contents
168 * are not suitable.
169 *
170 * See gridPositions(), subGridPositions(), labelPositions(), and labelStrings()
171 * methods for documentation about the arrays that need to be resized and
172 * populated.
173 *
174 * \sa gridPositions(), subGridPositions(), labelPositions(), labelStrings(),
175 * axis()
176 */
177void QValue3DAxisFormatter::recalculate()
178{
179 Q_D(QValue3DAxisFormatter);
180 d->doRecalculate();
181}
182
183/*!
184 * Returns the formatted label string using the specified \a value and
185 * \a format.
186 *
187 * Reimplement this method in a subclass to resolve the formatted string for a
188 * given \a value if the default formatting rules specified for
189 * QValue3DAxis::labelFormat property are not sufficient.
190 *
191 * \sa recalculate(), labelStrings(), QValue3DAxis::labelFormat
192 */
193QString QValue3DAxisFormatter::stringForValue(qreal value, const QString &format)
194{
195 Q_D(QValue3DAxisFormatter);
196 return d->stringForValue(value, format);
197}
198
199/*!
200 * Returns the normalized position along the axis for the given \a value.
201 * The returned value should be between \c 0.0 (the minimum value) and
202 * \c 1.0 (the maximum value), inclusive, if the value is within the parent
203 * axis range.
204 *
205 * Reimplement this method if the position cannot be resolved by linear
206 * interpolation between the parent axis minimum and maximum values.
207 *
208 * \sa recalculate(), valueAt()
209 */
210float QValue3DAxisFormatter::positionAt(float value) const
211{
212 Q_D(const QValue3DAxisFormatter);
213 return d->positionAt(value);
214}
215
216/*!
217 * Returns the value at the normalized \a position along the axis.
218 * The \a position value should be between \c 0.0 (the minimum value) and
219 * \c 1.0 (the maximum value), inclusive, to obtain values within the parent
220 * axis range.
221 *
222 * Reimplement this method if the value cannot be resolved by linear
223 * interpolation between the parent axis minimum and maximum values.
224 *
225 * \sa recalculate(), positionAt()
226 */
227float QValue3DAxisFormatter::valueAt(float position) const
228{
229 Q_D(const QValue3DAxisFormatter);
230 return d->valueAt(position);
231}
232
233/*!
234 * Copies all the values necessary for resolving positions, values, and strings
235 * with this formatter to the \a copy of the formatter. When reimplementing
236 * this method in a subclass, call the superclass version at some point.
237 * The renderer uses this method to cache a copy of the formatter.
238 *
239 * Returns the new copy. The ownership of the new copy transfers to the caller.
240 */
241void QValue3DAxisFormatter::populateCopy(QValue3DAxisFormatter &copy)
242{
243 Q_D(QValue3DAxisFormatter);
244 d->doPopulateCopy(copy&: *(copy.d_func()));
245}
246
247/*!
248 * Marks this formatter as dirty, prompting the renderer to make a new copy of its
249 * cache on the next renderer synchronization. This method should be called by a
250 * subclass whenever the formatter is changed in a way that affects the resolved
251 * values. Set \a labelsChange to \c true if the change requires regenerating
252 * the parent axis label strings.
253 */
254void QValue3DAxisFormatter::markDirty(bool labelsChange)
255{
256 Q_D(QValue3DAxisFormatter);
257 d->markDirty(labelsChange);
258}
259
260/*!
261 * Returns the parent axis. The parent axis must only be accessed in the
262 * recalculate() method to maintain thread safety in environments using a
263 * threaded renderer.
264 *
265 * \sa recalculate()
266 */
267QValue3DAxis *QValue3DAxisFormatter::axis() const
268{
269 Q_D(const QValue3DAxisFormatter);
270 return d->m_axis;
271}
272
273/*!
274 * Returns a reference to the array of normalized grid line positions.
275 * The default array size is equal to the segment count of the parent axis plus
276 * one, but a subclassed implementation of the recalculate() method may resize
277 * the array differently.
278 *
279 * \sa QValue3DAxis::segmentCount, recalculate()
280 */
281const QList<float> &QValue3DAxisFormatter::gridPositions() const &
282{
283 Q_D(const QValue3DAxisFormatter);
284 return d->m_gridPositions;
285}
286
287/*!
288 * Returns an array of normalized grid line positions by value.
289 * The default array size is equal to the segment count of the parent axis plus
290 * one, but a subclassed implementation of the recalculate() method may resize
291 * the array differently.
292 *
293 * \sa QValue3DAxis::segmentCount, recalculate()
294 */
295QList<float> QValue3DAxisFormatter::gridPositions() &&
296{
297 Q_D(QValue3DAxisFormatter);
298 return std::move(d->m_gridPositions);
299}
300
301/*!
302 * Sets a list of new grid positions from \a gridPositions.
303 * The values should be between \c 0.0 (the minimum value) and \c 1.0 (the
304 * maximum value), inclusive.
305 *
306 * \sa QValue3DAxis::segmentCount, recalculate(), gridPositions
307 */
308void QValue3DAxisFormatter::setGridPoitions(QList<float> gridPositions)
309{
310 Q_D(QValue3DAxisFormatter);
311 d->m_gridPositions = gridPositions;
312}
313
314/*!
315 * Returns a reference to the array of normalized sub-grid line positions.
316 * The default array size is equal to the segment count of the parent axis times
317 * the sub-segment count of the parent axis minus one, but a subclassed
318 * implementation of the recalculate() method may resize the array differently.
319 *
320 * \sa QValue3DAxis::segmentCount, QValue3DAxis::subSegmentCount, recalculate()
321 */
322const QList<float> &QValue3DAxisFormatter::subGridPositions() const &
323{
324 Q_D(const QValue3DAxisFormatter);
325 return d->m_subGridPositions;
326}
327
328/*!
329 * Returns an array of normalized sub-grid line positions by value.
330 * The default array size is equal to the segment count of the parent axis times
331 * the sub-segment count of the parent axis minus one, but a subclassed
332 * implementation of the recalculate() method may resize the array differently.
333 *
334 * \sa QValue3DAxis::segmentCount, QValue3DAxis::subSegmentCount, recalculate()
335 */
336QList<float> QValue3DAxisFormatter::subGridPositions() &&
337{
338 Q_D(QValue3DAxisFormatter);
339 return std::move(d->m_subGridPositions);
340}
341
342/*!
343 * Sets a list of new sub-grid positions from \a subGridPositions.
344 * The values should be between \c 0.0 (the minimum value) and \c 1.0 (the
345 * maximum value), inclusive.
346 *
347 * \sa QValue3DAxis::segmentCount, QValue3DAxis::subSegmentCount, recalculate(),
348 * subGridPositions
349 */
350void QValue3DAxisFormatter::setSubGridPositions(QList<float> subGridPositions)
351{
352 Q_D(QValue3DAxisFormatter);
353 d->m_subGridPositions = subGridPositions;
354}
355
356/*!
357 * Returns a reference to the array of normalized label positions.
358 * The default array size is equal to the segment count of the parent axis plus
359 * one, but a subclassed implementation of the recalculate() method may resize
360 * the array differently. By default, the label at the index zero corresponds to
361 * the minimum value of the axis.
362 *
363 * \sa QValue3DAxis::segmentCount, QAbstract3DAxis::labels, recalculate()
364 */
365const QList<float> &QValue3DAxisFormatter::labelPositions() const &
366{
367 Q_D(const QValue3DAxisFormatter);
368 return d->m_labelPositions;
369}
370
371/*!
372 * Returns an array of normalized label positions by value.
373 * The default array size is equal to the segment count of the parent axis plus
374 * one, but a subclassed implementation of the recalculate() method may resize
375 * the array differently. By default, the label at the index zero corresponds to
376 * the minimum value of the axis.
377 *
378 * \sa QValue3DAxis::segmentCount, QAbstract3DAxis::labels, recalculate()
379 */
380QList<float> QValue3DAxisFormatter::labelPositions() &&
381{
382 Q_D(QValue3DAxisFormatter);
383 return std::move(d->m_labelPositions);
384}
385
386/*!
387 * Sets a list of new label positions from \a labelPositions.
388 * The values should be between \c 0.0 (the minimum value) and
389 * \c 1.0 (the maximum value), inclusive.
390 *
391 * \sa QValue3DAxis::segmentCount, QAbstract3DAxis::labels, recalculate(),
392 * labelPositions()
393 */
394void QValue3DAxisFormatter::setlabelPositions(QList<float> labelPositions)
395{
396 Q_D(QValue3DAxisFormatter);
397 d->m_labelPositions = labelPositions;
398}
399
400/*!
401 * Returns a reference to the string list containing formatter label strings.
402 *
403 * \sa labelPositions()
404 */
405const QStringList &QValue3DAxisFormatter::labelStrings() const &
406{
407 Q_D(const QValue3DAxisFormatter);
408 return d->m_labelStrings;
409}
410
411/*!
412 * Returns a string list by value containing formatter label strings.
413 *
414 * \sa labelPositions()
415 */
416QStringList QValue3DAxisFormatter::labelStrings() &&
417{
418 Q_D(QValue3DAxisFormatter);
419 return std::move(d->m_labelStrings);
420}
421
422/*!
423 * Sets a list of new label strings from \a labelStrings.
424 * The array size must be equal to the size of the label positions array, which
425 * the indexes also correspond to.
426 * \sa labelPositions(), labelStrings()
427 */
428void QValue3DAxisFormatter::setLabelStrings(QStringList labelStrings)
429{
430 Q_D(QValue3DAxisFormatter);
431 d->m_labelStrings = labelStrings;
432}
433
434/*!
435 * Sets the \a locale that this formatter uses.
436 * The graph automatically sets the formatter's locale to a graph's locale
437 * whenever the parent axis is set as an active axis of the graph, the axis
438 * formatter is set to an axis attached to the graph, or the graph's locale
439 * changes.
440 *
441 * \sa locale(), Q3DGraphsWidgetItem::locale
442 */
443void QValue3DAxisFormatter::setLocale(const QLocale &locale)
444{
445 Q_D(QValue3DAxisFormatter);
446 d->m_cLocaleInUse = (locale == QLocale::c());
447 d->m_locale = locale;
448 markDirty(labelsChange: true);
449}
450/*!
451 * Returns the current locale this formatter is using.
452 */
453QLocale QValue3DAxisFormatter::locale() const
454{
455 Q_D(const QValue3DAxisFormatter);
456 return d->m_locale;
457}
458
459// QValue3DAxisFormatterPrivate
460QValue3DAxisFormatterPrivate::QValue3DAxisFormatterPrivate()
461 : m_needsRecalculate(true)
462 , m_min(0.0f)
463 , m_max(0.0f)
464 , m_rangeNormalizer(0.0f)
465 , m_axis(0)
466 , m_preparsedParamType(Utils::ParamType::Unknown)
467 , m_allowNegatives(true)
468 , m_allowZero(true)
469 , m_formatPrecision(6)
470 , // 6 and 'g' are defaults in Qt API for format precision and spec
471 m_formatSpec('g')
472 , m_cLocaleInUse(true)
473{}
474
475QValue3DAxisFormatterPrivate::~QValue3DAxisFormatterPrivate() {}
476
477void QValue3DAxisFormatterPrivate::recalculate()
478{
479 Q_Q(QValue3DAxisFormatter);
480 // Only recalculate if we need to and have m_axis pointer. If we do not have
481 // m_axis, either we are not attached to an axis or this is a renderer cache.
482 if (m_axis && m_needsRecalculate) {
483 m_min = m_axis->min();
484 m_max = m_axis->max();
485 m_rangeNormalizer = (m_max - m_min);
486
487 q->recalculate();
488 m_needsRecalculate = false;
489 }
490}
491
492void QValue3DAxisFormatterPrivate::doRecalculate()
493{
494 Q_Q(QValue3DAxisFormatter);
495 int segmentCount = m_axis->segmentCount();
496 int subGridCount = m_axis->subSegmentCount() - 1;
497 QString labelFormat = m_axis->labelFormat();
498
499 m_gridPositions.resize(size: segmentCount + 1);
500 m_subGridPositions.resize(size: segmentCount * subGridCount);
501
502 m_labelPositions.resize(size: segmentCount + 1);
503 m_labelStrings.clear();
504 m_labelStrings.reserve(asize: segmentCount + 1);
505
506 // Use qreals for intermediate calculations for better accuracy on label
507 // values
508 qreal segmentStep = 1.0 / qreal(segmentCount);
509 qreal subSegmentStep = 0;
510 if (subGridCount > 0)
511 subSegmentStep = segmentStep / qreal(subGridCount + 1);
512
513 // Calculate positions
514 qreal rangeNormalizer = qreal(m_max - m_min);
515 for (int i = 0; i < segmentCount; i++) {
516 qreal gridValue = segmentStep * qreal(i);
517 m_gridPositions[i] = float(gridValue);
518 m_labelPositions[i] = float(gridValue);
519 m_labelStrings << q->stringForValue(value: gridValue * rangeNormalizer + qreal(m_min), format: labelFormat);
520 if (m_subGridPositions.size()) {
521 for (int j = 0; j < subGridCount; j++)
522 m_subGridPositions[i * subGridCount + j] = gridValue + subSegmentStep * (j + 1);
523 }
524 }
525
526 // Ensure max value doesn't suffer from any rounding errors
527 m_gridPositions[segmentCount] = 1.0f;
528 m_labelPositions[segmentCount] = 1.0f;
529 m_labelStrings << q->stringForValue(value: qreal(m_max), format: labelFormat);
530}
531
532void QValue3DAxisFormatterPrivate::populateCopy(QValue3DAxisFormatter &copy)
533{
534 Q_Q(QValue3DAxisFormatter);
535 recalculate();
536 q->populateCopy(copy);
537}
538
539void QValue3DAxisFormatterPrivate::doPopulateCopy(QValue3DAxisFormatterPrivate &copy)
540{
541 copy.m_min = m_min;
542 copy.m_max = m_max;
543 copy.m_rangeNormalizer = m_rangeNormalizer;
544
545 copy.m_gridPositions = m_gridPositions;
546 copy.m_labelPositions = m_labelPositions;
547 copy.m_subGridPositions = m_subGridPositions;
548}
549
550QString QValue3DAxisFormatterPrivate::stringForValue(qreal value, const QString &format)
551{
552 if (m_previousLabelFormat.compare(s: format)) {
553 // Format string different than the previous one used, reparse it
554 m_labelFormatArray = format.toUtf8();
555 m_previousLabelFormat = format;
556 m_preparsedParamType = Utils::preParseFormat(format,
557 preStr&: m_formatPreStr,
558 postStr&: m_formatPostStr,
559 precision&: m_formatPrecision,
560 formatSpec&: m_formatSpec);
561 }
562
563 if (m_cLocaleInUse) {
564 return Utils::formatLabelSprintf(format: m_labelFormatArray, paramType: m_preparsedParamType, value);
565 } else {
566 return Utils::formatLabelLocalized(paramType: m_preparsedParamType,
567 value,
568 locale: m_locale,
569 preStr: m_formatPreStr,
570 postStr: m_formatPostStr,
571 precision: m_formatPrecision,
572 formatSpec: m_formatSpec,
573 format: m_labelFormatArray);
574 }
575}
576
577float QValue3DAxisFormatterPrivate::positionAt(float value) const
578{
579 return ((value - m_min) / m_rangeNormalizer);
580}
581
582float QValue3DAxisFormatterPrivate::valueAt(float position) const
583{
584 return ((position * m_rangeNormalizer) + m_min);
585}
586
587void QValue3DAxisFormatterPrivate::setAxis(QValue3DAxis *axis)
588{
589 Q_ASSERT(axis);
590 m_axis = axis;
591}
592
593void QValue3DAxisFormatterPrivate::markDirty(bool labelsChange)
594{
595 m_needsRecalculate = true;
596 if (m_axis) {
597 if (labelsChange)
598 m_axis->d_func()->emitLabelsChanged();
599 if (m_axis && m_axis->orientation() != QAbstract3DAxis::AxisOrientation::None)
600 m_axis->d_func()->emitFormatterDirty();
601 }
602}
603
604QT_END_NAMESPACE
605

source code of qtgraphs/src/graphs3d/axis/qvalue3daxisformatter.cpp