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 | |
7 | QT_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 | */ |
43 | QValue3DAxisFormatter::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 | */ |
52 | QValue3DAxisFormatter::QValue3DAxisFormatter(QObject *parent) : |
53 | QObject(parent), |
54 | d_ptr(new QValue3DAxisFormatterPrivate(this)) |
55 | { |
56 | } |
57 | |
58 | /*! |
59 | * Deletes the value 3D axis formatter. |
60 | */ |
61 | QValue3DAxisFormatter::~QValue3DAxisFormatter() |
62 | { |
63 | } |
64 | |
65 | /*! |
66 | * Allows the parent axis to have negative values if \a allow is \c true. |
67 | */ |
68 | void 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 | */ |
77 | bool 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 | */ |
85 | void 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 | */ |
94 | bool 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 | */ |
106 | QValue3DAxisFormatter *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 | */ |
123 | void 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 | */ |
138 | QString 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 | */ |
154 | float 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 | */ |
170 | float 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 | */ |
183 | void QValue3DAxisFormatter::populateCopy(QValue3DAxisFormatter ©) 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 | */ |
194 | void 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 | */ |
205 | QValue3DAxis *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 | */ |
219 | QList<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 | */ |
234 | QList<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 | */ |
250 | QList<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 | */ |
262 | QStringList &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 | */ |
275 | void 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 | */ |
284 | QLocale QValue3DAxisFormatter::locale() const |
285 | { |
286 | return d_ptr->m_locale; |
287 | } |
288 | |
289 | // QValue3DAxisFormatterPrivate |
290 | QValue3DAxisFormatterPrivate::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 | |
307 | QValue3DAxisFormatterPrivate::~QValue3DAxisFormatterPrivate() |
308 | { |
309 | } |
310 | |
311 | void 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 | |
325 | void 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 | |
364 | void QValue3DAxisFormatterPrivate::populateCopy(QValue3DAxisFormatter ©) |
365 | { |
366 | recalculate(); |
367 | q_ptr->populateCopy(copy); |
368 | } |
369 | |
370 | void QValue3DAxisFormatterPrivate::doPopulateCopy(QValue3DAxisFormatterPrivate ©) |
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 | |
381 | QString 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 | |
400 | float QValue3DAxisFormatterPrivate::positionAt(float value) const |
401 | { |
402 | return ((value - m_min) / m_rangeNormalizer); |
403 | } |
404 | |
405 | float QValue3DAxisFormatterPrivate::valueAt(float position) const |
406 | { |
407 | return ((position * m_rangeNormalizer) + m_min); |
408 | } |
409 | |
410 | void 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 | |
429 | void 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 | |
440 | void QValue3DAxisFormatterPrivate::markDirtyNoLabelChange() |
441 | { |
442 | markDirty(labelsChange: false); |
443 | } |
444 | |
445 | QT_END_NAMESPACE |
446 | |