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

source code of qtgraphs/src/graphs3d/axis/qlogvalue3daxisformatter.cpp