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