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 | */ |
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 | */ |
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 | */ |
87 | QValue3DAxisFormatter::~QValue3DAxisFormatter() |
88 | { |
89 | } |
90 | |
91 | /*! |
92 | * Allows the parent axis to have negative values if \a allow is \c true. |
93 | */ |
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 | */ |
103 | 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 | */ |
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 | */ |
120 | 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 | */ |
132 | 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 | */ |
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 | */ |
164 | 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 | */ |
180 | 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 | */ |
196 | 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 | */ |
209 | void QValue3DAxisFormatter::populateCopy(QValue3DAxisFormatter ©) 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 | */ |
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 | */ |
231 | 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 | */ |
245 | 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 | */ |
260 | 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 | */ |
276 | 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 | */ |
288 | 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 | */ |
301 | void 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 | */ |
310 | QLocale QValue3DAxisFormatter::locale() const |
311 | { |
312 | return d_ptr->m_locale; |
313 | } |
314 | |
315 | // QValue3DAxisFormatterPrivate |
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 | |
333 | QValue3DAxisFormatterPrivate::~QValue3DAxisFormatterPrivate() |
334 | { |
335 | } |
336 | |
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 | |
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(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 | |
390 | void QValue3DAxisFormatterPrivate::populateCopy(QValue3DAxisFormatter ©) |
391 | { |
392 | recalculate(); |
393 | q_ptr->populateCopy(copy); |
394 | } |
395 | |
396 | void QValue3DAxisFormatterPrivate::doPopulateCopy(QValue3DAxisFormatterPrivate ©) |
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 | |
407 | QString 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 | |
426 | float QValue3DAxisFormatterPrivate::positionAt(float value) const |
427 | { |
428 | return ((value - m_min) / m_rangeNormalizer); |
429 | } |
430 | |
431 | float QValue3DAxisFormatterPrivate::valueAt(float position) const |
432 | { |
433 | return ((position * m_rangeNormalizer) + m_min); |
434 | } |
435 | |
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(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 | |
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 | |
466 | void QValue3DAxisFormatterPrivate::markDirtyNoLabelChange() |
467 | { |
468 | markDirty(labelsChange: false); |
469 | } |
470 | |
471 | QT_END_NAMESPACE_DATAVISUALIZATION |
472 | |