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
34QT_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 */
110QLogValue3DAxisFormatter::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 */
122QLogValue3DAxisFormatter::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 */
132QLogValue3DAxisFormatter::~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 */
151void 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
165qreal 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 */
184void 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
193bool 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 */
213void 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
222bool QLogValue3DAxisFormatter::showEdgeLabels() const
223{
224 return dptrc()->m_showEdgeLabels;
225}
226
227/*!
228 * \internal
229 */
230QValue3DAxisFormatter *QLogValue3DAxisFormatter::createNewInstance() const
231{
232 return new QLogValue3DAxisFormatter();
233}
234
235/*!
236 * \internal
237 */
238void QLogValue3DAxisFormatter::recalculate()
239{
240 dptr()->recalculate();
241}
242
243/*!
244 * \internal
245 */
246float QLogValue3DAxisFormatter::positionAt(float value) const
247{
248 return dptrc()->positionAt(value);
249}
250
251/*!
252 * \internal
253 */
254float QLogValue3DAxisFormatter::valueAt(float position) const
255{
256 return dptrc()->valueAt(position);
257}
258
259/*!
260 * \internal
261 */
262void QLogValue3DAxisFormatter::populateCopy(QValue3DAxisFormatter &copy) const
263{
264 QValue3DAxisFormatter::populateCopy(copy);
265 dptrc()->populateCopy(copy);
266}
267
268/*!
269 * \internal
270 */
271QLogValue3DAxisFormatterPrivate *QLogValue3DAxisFormatter::dptr()
272{
273 return static_cast<QLogValue3DAxisFormatterPrivate *>(d_ptr.data());
274}
275
276/*!
277 * \internal
278 */
279const QLogValue3DAxisFormatterPrivate *QLogValue3DAxisFormatter::dptrc() const
280{
281 return static_cast<const QLogValue3DAxisFormatterPrivate *>(d_ptr.data());
282}
283
284// QLogValue3DAxisFormatterPrivate
285QLogValue3DAxisFormatterPrivate::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
298QLogValue3DAxisFormatterPrivate::~QLogValue3DAxisFormatterPrivate()
299{
300}
301
302void 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
425void QLogValue3DAxisFormatterPrivate::populateCopy(QValue3DAxisFormatter &copy) const
426{
427 QLogValue3DAxisFormatter *logFormatter = static_cast<QLogValue3DAxisFormatter *>(&copy);
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
436float 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
444float QLogValue3DAxisFormatterPrivate::valueAt(float position) const
445{
446 qreal logValue = (qreal(position) * m_logRangeNormalizer) + m_logMin;
447 return float(qExp(v: logValue));
448}
449
450QLogValue3DAxisFormatter *QLogValue3DAxisFormatterPrivate::qptr()
451{
452 return static_cast<QLogValue3DAxisFormatter *>(q_ptr);
453}
454
455QT_END_NAMESPACE_DATAVISUALIZATION
456

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