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 "qlogvalue3daxisformatter_p.h" |
31 | #include "qvalue3daxis_p.h" |
32 | #include <QtCore/qmath.h> |
33 | |
34 | QT_BEGIN_NAMESPACE_DATAVISUALIZATION |
35 | |
36 | /*! |
37 | * \class QLogValue3DAxisFormatter |
38 | * \inmodule QtDataVisualization |
39 | * \brief The QLogValue3DAxisFormatter class provides formatting rules for a |
40 | * logarithmic value axis. |
41 | * \since QtDataVisualization 1.1 |
42 | * |
43 | * When a formatter is attached to a value axis, the axis range |
44 | * cannot include negative values or the zero. |
45 | * |
46 | * \sa QValue3DAxisFormatter |
47 | */ |
48 | |
49 | /*! |
50 | * \qmltype LogValueAxis3DFormatter |
51 | * \inqmlmodule QtDataVisualization |
52 | * \since QtDataVisualization 1.1 |
53 | * \ingroup datavisualization_qml |
54 | * \instantiates QLogValue3DAxisFormatter |
55 | * \inherits ValueAxis3DFormatter |
56 | * \brief Provides formatting rules for a logarithmic value axis. |
57 | * |
58 | * When a formatter is attached to a value axis, the axis range |
59 | * cannot include negative values or the zero. |
60 | */ |
61 | |
62 | /*! |
63 | * \qmlproperty real LogValueAxis3DFormatter::base |
64 | * |
65 | * The base of the logarithm used to map axis values. If the base is non-zero, the parent axis |
66 | * segment count will be ignored when the grid line and label positions are calculated. |
67 | * If you want the range to be divided into equal segments like a normal value axis, set this |
68 | * property value to zero. |
69 | * |
70 | * The base has to be zero or a positive value and it cannot be equal to one. |
71 | * Defaults to ten. |
72 | * |
73 | * \sa ValueAxis3D::segmentCount |
74 | */ |
75 | |
76 | /*! |
77 | * \qmlproperty bool LogValueAxis3DFormatter::autoSubGrid |
78 | * |
79 | * Defines whether sub-grid positions are generated automatically. |
80 | * |
81 | * If this property value is set to \c true, the parent axis sub-segment count is ignored |
82 | * when calculating sub-grid line positions. The sub-grid positions are generated automatically |
83 | * according to the \l base property value. |
84 | * The number of sub-grid lines is set to the base value minus one, rounded down. |
85 | * This property is ignored when the base value is zero. |
86 | * Defaults to \c true. |
87 | * |
88 | * \sa base, ValueAxis3D::subSegmentCount |
89 | */ |
90 | |
91 | /*! |
92 | * \qmlproperty bool LogValueAxis3DFormatter::showEdgeLabels |
93 | * |
94 | * Defines whether the first and last label on the axis are visible. |
95 | * |
96 | * When the \l base property value is non-zero, the whole axis range is often |
97 | * not equally divided into |
98 | * segments. The first and last segments are often smaller than the other segments. |
99 | * In extreme cases, this can lead to overlapping labels on the first and last two grid lines. |
100 | * By setting this property to \c false, you can suppress showing the minimum and maximum labels |
101 | * for the axis in cases where the segments do not exactly fit the axis. |
102 | * Defaults to \c true. |
103 | * |
104 | * \sa base, AbstractAxis3D::labels |
105 | */ |
106 | |
107 | /*! |
108 | * \internal |
109 | */ |
110 | QLogValue3DAxisFormatter::QLogValue3DAxisFormatter(QLogValue3DAxisFormatterPrivate *d, |
111 | QObject *parent) : |
112 | QValue3DAxisFormatter(d, parent) |
113 | { |
114 | setAllowNegatives(false); |
115 | setAllowZero(false); |
116 | } |
117 | |
118 | /*! |
119 | * Constructs a new logarithmic value 3D axis formatter with the optional |
120 | * parent \a parent. |
121 | */ |
122 | QLogValue3DAxisFormatter::QLogValue3DAxisFormatter(QObject *parent) : |
123 | QValue3DAxisFormatter(new QLogValue3DAxisFormatterPrivate(this), parent) |
124 | { |
125 | setAllowNegatives(false); |
126 | setAllowZero(false); |
127 | } |
128 | |
129 | /*! |
130 | * Deletes the logarithmic value 3D axis formatter. |
131 | */ |
132 | QLogValue3DAxisFormatter::~QLogValue3DAxisFormatter() |
133 | { |
134 | } |
135 | |
136 | /*! |
137 | * \property QLogValue3DAxisFormatter::base |
138 | * |
139 | * \brief The base of the logarithm used to map axis values. |
140 | * |
141 | * If the base is non-zero, the parent axis |
142 | * segment count will be ignored when the grid line and label positions are calculated. |
143 | * If you want the range to be divided into equal segments like a normal value axis, set this |
144 | * property value to zero. |
145 | * |
146 | * The base has to be zero or a positive value and it cannot be equal to one. |
147 | * Defaults to ten. |
148 | * |
149 | * \sa QValue3DAxis::segmentCount |
150 | */ |
151 | void QLogValue3DAxisFormatter::setBase(qreal base) |
152 | { |
153 | if (base < 0.0f || base == 1.0f) { |
154 | qWarning() << "Warning: The logarithm base must be greater than 0 and not equal to 1," |
155 | << "attempted:" << base; |
156 | return; |
157 | } |
158 | if (dptr()->m_base != base) { |
159 | dptr()->m_base = base; |
160 | markDirty(labelsChange: true); |
161 | emit baseChanged(base); |
162 | } |
163 | } |
164 | |
165 | qreal QLogValue3DAxisFormatter::base() const |
166 | { |
167 | return dptrc()->m_base; |
168 | } |
169 | |
170 | /*! |
171 | * \property QLogValue3DAxisFormatter::autoSubGrid |
172 | * |
173 | * \brief Whether sub-grid positions are generated automatically. |
174 | * |
175 | * If this property value is set to \c true, the parent axis sub-segment count is ignored |
176 | * when calculating sub-grid line positions. The sub-grid positions are generated automatically |
177 | * according to the \l base property value. The number of sub-grid lines is set |
178 | * to the base value minus one, rounded down. This property is ignored when the |
179 | * base value is zero. |
180 | * Defaults to \c true. |
181 | * |
182 | * \sa base, QValue3DAxis::subSegmentCount |
183 | */ |
184 | void QLogValue3DAxisFormatter::setAutoSubGrid(bool enabled) |
185 | { |
186 | if (dptr()->m_autoSubGrid != enabled) { |
187 | dptr()->m_autoSubGrid = enabled; |
188 | markDirty(labelsChange: false); |
189 | emit autoSubGridChanged(enabled); |
190 | } |
191 | } |
192 | |
193 | bool QLogValue3DAxisFormatter::autoSubGrid() const |
194 | { |
195 | return dptrc()->m_autoSubGrid; |
196 | } |
197 | |
198 | /*! |
199 | * \property QLogValue3DAxisFormatter::showEdgeLabels |
200 | * |
201 | * \brief Whether the first and last label on the axis are visible. |
202 | * |
203 | * When the \l base property value is non-zero, the whole axis range is often |
204 | * not equally divided into |
205 | * segments. The first and last segments are often smaller than the other segments. |
206 | * In extreme cases, this can lead to overlapping labels on the first and last two grid lines. |
207 | * By setting this property to \c false, you can suppress showing the minimum and maximum labels |
208 | * for the axis in cases where the segments do not exactly fit the axis. |
209 | * Defaults to \c true. |
210 | * |
211 | * \sa base, QAbstract3DAxis::labels |
212 | */ |
213 | void QLogValue3DAxisFormatter::setShowEdgeLabels(bool enabled) |
214 | { |
215 | if (dptr()->m_showEdgeLabels != enabled) { |
216 | dptr()->m_showEdgeLabels = enabled; |
217 | markDirty(labelsChange: true); |
218 | emit showEdgeLabelsChanged(enabled); |
219 | } |
220 | } |
221 | |
222 | bool QLogValue3DAxisFormatter::showEdgeLabels() const |
223 | { |
224 | return dptrc()->m_showEdgeLabels; |
225 | } |
226 | |
227 | /*! |
228 | * \internal |
229 | */ |
230 | QValue3DAxisFormatter *QLogValue3DAxisFormatter::createNewInstance() const |
231 | { |
232 | return new QLogValue3DAxisFormatter(); |
233 | } |
234 | |
235 | /*! |
236 | * \internal |
237 | */ |
238 | void QLogValue3DAxisFormatter::recalculate() |
239 | { |
240 | dptr()->recalculate(); |
241 | } |
242 | |
243 | /*! |
244 | * \internal |
245 | */ |
246 | float QLogValue3DAxisFormatter::positionAt(float value) const |
247 | { |
248 | return dptrc()->positionAt(value); |
249 | } |
250 | |
251 | /*! |
252 | * \internal |
253 | */ |
254 | float QLogValue3DAxisFormatter::valueAt(float position) const |
255 | { |
256 | return dptrc()->valueAt(position); |
257 | } |
258 | |
259 | /*! |
260 | * \internal |
261 | */ |
262 | void QLogValue3DAxisFormatter::populateCopy(QValue3DAxisFormatter ©) const |
263 | { |
264 | QValue3DAxisFormatter::populateCopy(copy); |
265 | dptrc()->populateCopy(copy); |
266 | } |
267 | |
268 | /*! |
269 | * \internal |
270 | */ |
271 | QLogValue3DAxisFormatterPrivate *QLogValue3DAxisFormatter::dptr() |
272 | { |
273 | return static_cast<QLogValue3DAxisFormatterPrivate *>(d_ptr.data()); |
274 | } |
275 | |
276 | /*! |
277 | * \internal |
278 | */ |
279 | const QLogValue3DAxisFormatterPrivate *QLogValue3DAxisFormatter::dptrc() const |
280 | { |
281 | return static_cast<const QLogValue3DAxisFormatterPrivate *>(d_ptr.data()); |
282 | } |
283 | |
284 | // QLogValue3DAxisFormatterPrivate |
285 | QLogValue3DAxisFormatterPrivate::QLogValue3DAxisFormatterPrivate(QLogValue3DAxisFormatter *q) |
286 | : QValue3DAxisFormatterPrivate(q), |
287 | m_base(10.0), |
288 | m_logMin(0.0), |
289 | m_logMax(0.0), |
290 | m_logRangeNormalizer(0.0), |
291 | m_autoSubGrid(true), |
292 | m_showEdgeLabels(true), |
293 | m_evenMinSegment(true), |
294 | m_evenMaxSegment(true) |
295 | { |
296 | } |
297 | |
298 | QLogValue3DAxisFormatterPrivate::~QLogValue3DAxisFormatterPrivate() |
299 | { |
300 | } |
301 | |
302 | void QLogValue3DAxisFormatterPrivate::recalculate() |
303 | { |
304 | // When doing position/value mappings, base doesn't matter, so just use natural logarithm |
305 | m_logMin = qLn(v: qreal(m_min)); |
306 | m_logMax = qLn(v: qreal(m_max)); |
307 | m_logRangeNormalizer = m_logMax - m_logMin; |
308 | |
309 | int subGridCount = m_axis->subSegmentCount() - 1; |
310 | int segmentCount = m_axis->segmentCount(); |
311 | QString labelFormat = m_axis->labelFormat(); |
312 | qreal segmentStep; |
313 | if (m_base > 0.0) { |
314 | // Update parent axis segment counts |
315 | qreal logMin = qLn(v: qreal(m_min)) / qLn(v: m_base); |
316 | qreal logMax = qLn(v: qreal(m_max)) / qLn(v: m_base); |
317 | qreal logRangeNormalizer = logMax - logMin; |
318 | |
319 | qreal minDiff = qCeil(v: logMin) - logMin; |
320 | qreal maxDiff = logMax - qFloor(v: logMax); |
321 | |
322 | m_evenMinSegment = qFuzzyCompare(p1: 0.0, p2: minDiff); |
323 | m_evenMaxSegment = qFuzzyCompare(p1: 0.0, p2: maxDiff); |
324 | |
325 | segmentCount = qRound(d: logRangeNormalizer - minDiff - maxDiff); |
326 | |
327 | if (!m_evenMinSegment) |
328 | segmentCount++; |
329 | if (!m_evenMaxSegment) |
330 | segmentCount++; |
331 | |
332 | segmentStep = 1.0 / logRangeNormalizer; |
333 | |
334 | if (m_autoSubGrid) { |
335 | subGridCount = qCeil(v: m_base) - 2; // -2 for subgrid because subsegment count is base - 1 |
336 | if (subGridCount < 0) |
337 | subGridCount = 0; |
338 | } |
339 | |
340 | m_gridPositions.resize(asize: segmentCount + 1); |
341 | m_subGridPositions.resize(asize: segmentCount * subGridCount); |
342 | m_labelPositions.resize(asize: segmentCount + 1); |
343 | m_labelStrings.clear(); |
344 | m_labelStrings.reserve(alloc: segmentCount + 1); |
345 | |
346 | // Calculate segment positions |
347 | int index = 0; |
348 | if (!m_evenMinSegment) { |
349 | m_gridPositions[0] = 0.0f; |
350 | m_labelPositions[0] = 0.0f; |
351 | if (m_showEdgeLabels) |
352 | m_labelStrings << qptr()->stringForValue(value: qreal(m_min), format: labelFormat); |
353 | else |
354 | m_labelStrings << QString(); |
355 | index++; |
356 | } |
357 | for (int i = 0; i < segmentCount; i++) { |
358 | float gridValue = float((minDiff + qreal(i)) / qreal(logRangeNormalizer)); |
359 | m_gridPositions[index] = gridValue; |
360 | m_labelPositions[index] = gridValue; |
361 | m_labelStrings << qptr()->stringForValue(value: qPow(x: m_base, y: minDiff + qreal(i) + logMin), |
362 | format: labelFormat); |
363 | index++; |
364 | } |
365 | // Ensure max value doesn't suffer from any rounding errors |
366 | m_gridPositions[segmentCount] = 1.0f; |
367 | m_labelPositions[segmentCount] = 1.0f; |
368 | QString finalLabel; |
369 | if (m_showEdgeLabels || m_evenMaxSegment) |
370 | finalLabel = qptr()->stringForValue(value: qreal(m_max), format: labelFormat); |
371 | |
372 | if (m_labelStrings.size() > segmentCount) |
373 | m_labelStrings.replace(i: segmentCount, t: finalLabel); |
374 | else |
375 | m_labelStrings << finalLabel; |
376 | } else { |
377 | // Grid lines and label positions are the same as the parent class, so call parent impl |
378 | // first to populate those |
379 | QValue3DAxisFormatterPrivate::doRecalculate(); |
380 | |
381 | // Label string list needs to be repopulated |
382 | segmentStep = 1.0 / qreal(segmentCount); |
383 | |
384 | m_labelStrings << qptr()->stringForValue(value: qreal(m_min), format: labelFormat); |
385 | for (int i = 1; i < m_labelPositions.size() - 1; i++) |
386 | m_labelStrings[i] = qptr()->stringForValue(value: qExp(v: segmentStep * qreal(i) |
387 | * m_logRangeNormalizer + m_logMin), |
388 | format: labelFormat); |
389 | m_labelStrings << qptr()->stringForValue(value: qreal(m_max), format: labelFormat); |
390 | |
391 | m_evenMaxSegment = true; |
392 | m_evenMinSegment = true; |
393 | } |
394 | |
395 | // Subgrid line positions are logarithmically spaced |
396 | if (subGridCount > 0) { |
397 | float oneSegmentRange = valueAt(position: float(segmentStep)) - m_min; |
398 | float subSegmentStep = oneSegmentRange / float(subGridCount + 1); |
399 | |
400 | // Since the logarithm has the same curvature across whole axis range, we can just calculate |
401 | // subgrid positions for the first segment and replicate them to other segments. |
402 | QVector<float> actualSubSegmentSteps(subGridCount); |
403 | |
404 | for (int i = 0; i < subGridCount; i++) { |
405 | float currentSubPosition = positionAt(value: m_min + ((i + 1) * subSegmentStep)); |
406 | actualSubSegmentSteps[i] = currentSubPosition; |
407 | } |
408 | |
409 | float firstPartialSegmentAdjustment = float(segmentStep) - m_gridPositions.at(i: 1); |
410 | for (int i = 0; i < segmentCount; i++) { |
411 | for (int j = 0; j < subGridCount; j++) { |
412 | float position = m_gridPositions.at(i) + actualSubSegmentSteps.at(i: j); |
413 | if (!m_evenMinSegment && i == 0) |
414 | position -= firstPartialSegmentAdjustment; |
415 | if (position > 1.0f) |
416 | position = 1.0f; |
417 | if (position < 0.0f) |
418 | position = 0.0f; |
419 | m_subGridPositions[i * subGridCount + j] = position; |
420 | } |
421 | } |
422 | } |
423 | } |
424 | |
425 | void QLogValue3DAxisFormatterPrivate::populateCopy(QValue3DAxisFormatter ©) const |
426 | { |
427 | QLogValue3DAxisFormatter *logFormatter = static_cast<QLogValue3DAxisFormatter *>(©); |
428 | QLogValue3DAxisFormatterPrivate *priv = logFormatter->dptr(); |
429 | |
430 | priv->m_base = m_base; |
431 | priv->m_logMin = m_logMin; |
432 | priv->m_logMax = m_logMax; |
433 | priv->m_logRangeNormalizer = m_logRangeNormalizer; |
434 | } |
435 | |
436 | float QLogValue3DAxisFormatterPrivate::positionAt(float value) const |
437 | { |
438 | qreal logValue = qLn(v: qreal(value)); |
439 | float retval = float((logValue - m_logMin) / m_logRangeNormalizer); |
440 | |
441 | return retval; |
442 | } |
443 | |
444 | float QLogValue3DAxisFormatterPrivate::valueAt(float position) const |
445 | { |
446 | qreal logValue = (qreal(position) * m_logRangeNormalizer) + m_logMin; |
447 | return float(qExp(v: logValue)); |
448 | } |
449 | |
450 | QLogValue3DAxisFormatter *QLogValue3DAxisFormatterPrivate::qptr() |
451 | { |
452 | return static_cast<QLogValue3DAxisFormatter *>(q_ptr); |
453 | } |
454 | |
455 | QT_END_NAMESPACE_DATAVISUALIZATION |
456 | |