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 
33 QT_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  */
QValue3DAxisFormatter(QValue3DAxisFormatterPrivate * d,QObject * parent)69 QValue3DAxisFormatter::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  */
QValue3DAxisFormatter(QObject * parent)78 QValue3DAxisFormatter::QValue3DAxisFormatter(QObject *parent) :
79     QObject(parent),
80     d_ptr(new QValue3DAxisFormatterPrivate(this))
81 {
82 }
83 
84 /*!
85  * Deletes the value 3D axis formatter.
86  */
~QValue3DAxisFormatter()87 QValue3DAxisFormatter::~QValue3DAxisFormatter()
88 {
89 }
90 
91 /*!
92  * Allows the parent axis to have negative values if \a allow is \c true.
93  */
setAllowNegatives(bool allow)94 void 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  */
allowNegatives() const103 bool 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  */
setAllowZero(bool allow)111 void 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  */
allowZero() const120 bool 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  */
createNewInstance() const132 QValue3DAxisFormatter *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  */
recalculate()149 void 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  */
stringForValue(qreal value,const QString & format) const164 QString 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  */
positionAt(float value) const180 float 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  */
valueAt(float position) const196 float 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  */
populateCopy(QValue3DAxisFormatter & copy) const209 void QValue3DAxisFormatter::populateCopy(QValue3DAxisFormatter &copy) const
210 {
211     d_ptr->doPopulateCopy(*(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  */
markDirty(bool labelsChange)220 void 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  */
axis() const231 QValue3DAxis *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  */
gridPositions() const245 QVector<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  */
subGridPositions() const260 QVector<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  */
labelPositions() const276 QVector<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  */
labelStrings() const288 QStringList &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  */
setLocale(const QLocale & locale)301 void QValue3DAxisFormatter::setLocale(const QLocale &locale)
302 {
303     d_ptr->m_cLocaleInUse = (locale == QLocale::c());
304     d_ptr->m_locale = locale;
305     markDirty(true);
306 }
307 /*!
308  * Returns the current locale this formatter is using.
309  */
locale() const310 QLocale QValue3DAxisFormatter::locale() const
311 {
312     return d_ptr->m_locale;
313 }
314 
315 // QValue3DAxisFormatterPrivate
QValue3DAxisFormatterPrivate(QValue3DAxisFormatter * q)316 QValue3DAxisFormatterPrivate::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 
~QValue3DAxisFormatterPrivate()333 QValue3DAxisFormatterPrivate::~QValue3DAxisFormatterPrivate()
334 {
335 }
336 
recalculate()337 void 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 
doRecalculate()351 void 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(segmentCount + 1);
358     m_subGridPositions.resize(segmentCount * subGridCount);
359 
360     m_labelPositions.resize(segmentCount + 1);
361     m_labelStrings.clear();
362     m_labelStrings.reserve(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(gridValue * rangeNormalizer + qreal(m_min),
377                                                 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(qreal(m_max), labelFormat);
388 }
389 
populateCopy(QValue3DAxisFormatter & copy)390 void QValue3DAxisFormatterPrivate::populateCopy(QValue3DAxisFormatter &copy)
391 {
392     recalculate();
393     q_ptr->populateCopy(copy);
394 }
395 
doPopulateCopy(QValue3DAxisFormatterPrivate & copy)396 void 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 
stringForValue(qreal value,const QString & format)407 QString QValue3DAxisFormatterPrivate::stringForValue(qreal value, const QString &format)
408 {
409     if (m_previousLabelFormat.compare(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, m_formatPreStr, m_formatPostStr,
414                                                      m_formatPrecision, m_formatSpec);
415     }
416 
417     if (m_cLocaleInUse) {
418         return Utils::formatLabelSprintf(m_labelFormatArray, m_preparsedParamType, value);
419     } else {
420         return Utils::formatLabelLocalized(m_preparsedParamType, value, m_locale, m_formatPreStr,
421                                            m_formatPostStr, m_formatPrecision, m_formatSpec,
422                                            m_labelFormatArray);
423     }
424 }
425 
positionAt(float value) const426 float QValue3DAxisFormatterPrivate::positionAt(float value) const
427 {
428     return ((value - m_min) / m_rangeNormalizer);
429 }
430 
valueAt(float position) const431 float QValue3DAxisFormatterPrivate::valueAt(float position) const
432 {
433     return ((position * m_rangeNormalizer) + m_min);
434 }
435 
setAxis(QValue3DAxis * axis)436 void 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(axis, &QValue3DAxis::segmentCountChanged,
444             this, &QValue3DAxisFormatterPrivate::markDirtyNoLabelChange);
445     connect(axis, &QValue3DAxis::subSegmentCountChanged,
446             this, &QValue3DAxisFormatterPrivate::markDirtyNoLabelChange);
447     connect(axis, &QValue3DAxis::labelFormatChanged,
448             this, &QValue3DAxisFormatterPrivate::markDirtyNoLabelChange);
449     connect(axis, &QAbstract3DAxis::rangeChanged,
450             this, &QValue3DAxisFormatterPrivate::markDirtyNoLabelChange);
451 
452     m_axis = axis;
453 }
454 
markDirty(bool labelsChange)455 void 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 
markDirtyNoLabelChange()466 void QValue3DAxisFormatterPrivate::markDirtyNoLabelChange()
467 {
468     markDirty(false);
469 }
470 
471 QT_END_NAMESPACE_DATAVISUALIZATION
472