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

source code of qtdatavis3d/src/datavisualization/axis/qvalue3daxisformatter.cpp