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

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