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
8QT_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 */
84QLogValue3DAxisFormatter::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 */
96QLogValue3DAxisFormatter::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 */
106QLogValue3DAxisFormatter::~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 */
125void 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
139qreal 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 */
158void 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
167bool 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 */
187void 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
196bool QLogValue3DAxisFormatter::showEdgeLabels() const
197{
198 return dptrc()->m_showEdgeLabels;
199}
200
201/*!
202 * \internal
203 */
204QValue3DAxisFormatter *QLogValue3DAxisFormatter::createNewInstance() const
205{
206 return new QLogValue3DAxisFormatter();
207}
208
209/*!
210 * \internal
211 */
212void QLogValue3DAxisFormatter::recalculate()
213{
214 dptr()->recalculate();
215}
216
217/*!
218 * \internal
219 */
220float QLogValue3DAxisFormatter::positionAt(float value) const
221{
222 return dptrc()->positionAt(value);
223}
224
225/*!
226 * \internal
227 */
228float QLogValue3DAxisFormatter::valueAt(float position) const
229{
230 return dptrc()->valueAt(position);
231}
232
233/*!
234 * \internal
235 */
236void QLogValue3DAxisFormatter::populateCopy(QValue3DAxisFormatter &copy) const
237{
238 QValue3DAxisFormatter::populateCopy(copy);
239 dptrc()->populateCopy(copy);
240}
241
242/*!
243 * \internal
244 */
245QLogValue3DAxisFormatterPrivate *QLogValue3DAxisFormatter::dptr()
246{
247 return static_cast<QLogValue3DAxisFormatterPrivate *>(d_ptr.data());
248}
249
250/*!
251 * \internal
252 */
253const QLogValue3DAxisFormatterPrivate *QLogValue3DAxisFormatter::dptrc() const
254{
255 return static_cast<const QLogValue3DAxisFormatterPrivate *>(d_ptr.data());
256}
257
258// QLogValue3DAxisFormatterPrivate
259QLogValue3DAxisFormatterPrivate::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
272QLogValue3DAxisFormatterPrivate::~QLogValue3DAxisFormatterPrivate()
273{
274}
275
276void 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
399void QLogValue3DAxisFormatterPrivate::populateCopy(QValue3DAxisFormatter &copy) const
400{
401 QLogValue3DAxisFormatter *logFormatter = static_cast<QLogValue3DAxisFormatter *>(&copy);
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
410float 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
418float QLogValue3DAxisFormatterPrivate::valueAt(float position) const
419{
420 qreal logValue = (qreal(position) * m_logRangeNormalizer) + m_logMin;
421 return float(qExp(v: logValue));
422}
423
424QLogValue3DAxisFormatter *QLogValue3DAxisFormatterPrivate::qptr()
425{
426 return static_cast<QLogValue3DAxisFormatter *>(q_ptr);
427}
428
429QT_END_NAMESPACE
430

source code of qtdatavis3d/src/datavisualization/axis/qlogvalue3daxisformatter.cpp