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

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