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 // When doing position/value mappings, base doesn't matter, so just use
288 // natural logarithm
289 m_logMin = qLn(v: qreal(m_min));
290 m_logMax = qLn(v: qreal(m_max));
291 m_logRangeNormalizer = m_logMax - m_logMin;
292
293 int subGridCount = m_axis->subSegmentCount() - 1;
294 int segmentCount = m_axis->segmentCount();
295 QString labelFormat = m_axis->labelFormat();
296 qreal segmentStep;
297 if (m_base > 0.0) {
298 // Update parent axis segment counts
299 qreal logMin = qLn(v: qreal(m_min)) / qLn(v: m_base);
300 qreal logMax = qLn(v: qreal(m_max)) / qLn(v: m_base);
301 qreal logRangeNormalizer = logMax - logMin;
302
303 qreal minDiff = qCeil(v: logMin) - logMin;
304 qreal maxDiff = logMax - qFloor(v: logMax);
305
306 m_evenMinSegment = qFuzzyCompare(p1: qreal(0.0), p2: minDiff);
307 m_evenMaxSegment = qFuzzyCompare(p1: qreal(0.0), p2: maxDiff);
308
309 segmentCount = qRound(d: logRangeNormalizer - minDiff - maxDiff);
310
311 if (!m_evenMinSegment)
312 segmentCount++;
313 if (!m_evenMaxSegment)
314 segmentCount++;
315
316 segmentStep = 1.0 / logRangeNormalizer;
317
318 if (m_autoSubGrid) {
319 subGridCount = qCeil(v: m_base) - 2; // -2 for subgrid because subsegment count is base - 1
320 if (subGridCount < 0)
321 subGridCount = 0;
322 }
323
324 m_gridPositions.resize(size: segmentCount + 1);
325 m_subGridPositions.resize(size: segmentCount * subGridCount);
326 m_labelPositions.resize(size: segmentCount + 1);
327 m_labelStrings.clear();
328 m_labelStrings.reserve(asize: segmentCount + 1);
329
330 // Calculate segment positions
331 int index = 0;
332 if (!m_evenMinSegment) {
333 m_gridPositions[0] = 0.0f;
334 m_labelPositions[0] = 0.0f;
335 if (m_edgeLabelsVisible)
336 m_labelStrings << q->stringForValue(value: qreal(m_min), format: labelFormat);
337 else
338 m_labelStrings << QString();
339 index++;
340 }
341 for (int i = 0; i < segmentCount; i++) {
342 float gridValue = float((minDiff + qreal(i)) / qreal(logRangeNormalizer));
343 m_gridPositions[index] = gridValue;
344 m_labelPositions[index] = gridValue;
345 m_labelStrings << q->stringForValue(value: qPow(x: m_base, y: minDiff + qreal(i) + logMin),
346 format: labelFormat);
347 index++;
348 }
349 // Ensure max value doesn't suffer from any rounding errors
350 m_gridPositions[segmentCount] = 1.0f;
351 m_labelPositions[segmentCount] = 1.0f;
352 QString finalLabel;
353 if (m_edgeLabelsVisible || m_evenMaxSegment)
354 finalLabel = q->stringForValue(value: qreal(m_max), format: labelFormat);
355
356 if (m_labelStrings.size() > segmentCount)
357 m_labelStrings.replace(i: segmentCount, t: finalLabel);
358 else
359 m_labelStrings << finalLabel;
360 } else {
361 // Grid lines and label positions are the same as the parent class, so call
362 // parent impl first to populate those
363 QValue3DAxisFormatterPrivate::doRecalculate();
364
365 // Label string list needs to be repopulated
366 segmentStep = 1.0 / qreal(segmentCount);
367
368 m_labelStrings << q->stringForValue(value: qreal(m_min), format: labelFormat);
369 for (int i = 1; i < m_labelPositions.size() - 1; i++)
370 m_labelStrings[i] = q->stringForValue(value: qExp(v: segmentStep * qreal(i) * m_logRangeNormalizer
371 + m_logMin),
372 format: labelFormat);
373 m_labelStrings << q->stringForValue(value: qreal(m_max), format: labelFormat);
374
375 m_evenMaxSegment = true;
376 m_evenMinSegment = true;
377 }
378
379 // Subgrid line positions are logarithmically spaced
380 if (subGridCount > 0) {
381 float oneSegmentRange = valueAt(position: float(segmentStep)) - m_min;
382 float subSegmentStep = oneSegmentRange / float(subGridCount + 1);
383
384 // Since the logarithm has the same curvature across whole axis range, we
385 // can just calculate subgrid positions for the first segment and replicate
386 // them to other segments.
387 QList<float> actualSubSegmentSteps(subGridCount);
388
389 for (int i = 0; i < subGridCount; i++) {
390 float currentSubPosition = positionAt(value: m_min + ((i + 1) * subSegmentStep));
391 actualSubSegmentSteps[i] = currentSubPosition;
392 }
393
394 float firstPartialSegmentAdjustment = float(segmentStep) - m_gridPositions.at(i: 1);
395 for (int i = 0; i < segmentCount; i++) {
396 for (int j = 0; j < subGridCount; j++) {
397 float position = m_gridPositions.at(i) + actualSubSegmentSteps.at(i: j);
398 if (!m_evenMinSegment && i == 0)
399 position -= firstPartialSegmentAdjustment;
400 if (position > 1.0f)
401 position = 1.0f;
402 if (position < 0.0f)
403 position = 0.0f;
404 m_subGridPositions[i * subGridCount + j] = position;
405 }
406 }
407 }
408}
409
410void QLogValue3DAxisFormatterPrivate::populateCopy(QValue3DAxisFormatter &copy) const
411{
412 QLogValue3DAxisFormatter *logFormatter = static_cast<QLogValue3DAxisFormatter *>(&copy);
413 QLogValue3DAxisFormatterPrivate *priv = logFormatter->d_func();
414
415 priv->m_base = m_base;
416 priv->m_logMin = m_logMin;
417 priv->m_logMax = m_logMax;
418 priv->m_logRangeNormalizer = m_logRangeNormalizer;
419}
420
421float QLogValue3DAxisFormatterPrivate::positionAt(float value) const
422{
423 qreal logValue = qLn(v: qreal(value));
424 float retval = float((logValue - m_logMin) / m_logRangeNormalizer);
425
426 return retval;
427}
428
429float QLogValue3DAxisFormatterPrivate::valueAt(float position) const
430{
431 qreal logValue = (qreal(position) * m_logRangeNormalizer) + m_logMin;
432 return float(qExp(v: logValue));
433}
434
435QT_END_NAMESPACE
436

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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