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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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