| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt Data Visualization module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 or (at your option) any later version |
| ** approved by the KDE Free Qt Foundation. The licenses are as published by |
| ** the Free Software Foundation and appearing in the file LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qlogvalue3daxisformatter_p.h" |
| #include "qvalue3daxis_p.h" |
| #include <QtCore/qmath.h> |
| |
| QT_BEGIN_NAMESPACE_DATAVISUALIZATION |
| |
| /*! |
| * \class QLogValue3DAxisFormatter |
| * \inmodule QtDataVisualization |
| * \brief The QLogValue3DAxisFormatter class provides formatting rules for a |
| * logarithmic value axis. |
| * \since QtDataVisualization 1.1 |
| * |
| * When a formatter is attached to a value axis, the axis range |
| * cannot include negative values or the zero. |
| * |
| * \sa QValue3DAxisFormatter |
| */ |
| |
| /*! |
| * \qmltype LogValueAxis3DFormatter |
| * \inqmlmodule QtDataVisualization |
| * \since QtDataVisualization 1.1 |
| * \ingroup datavisualization_qml |
| * \instantiates QLogValue3DAxisFormatter |
| * \inherits ValueAxis3DFormatter |
| * \brief Provides formatting rules for a logarithmic value axis. |
| * |
| * When a formatter is attached to a value axis, the axis range |
| * cannot include negative values or the zero. |
| */ |
| |
| /*! |
| * \qmlproperty real LogValueAxis3DFormatter::base |
| * |
| * The base of the logarithm used to map axis values. If the base is non-zero, the parent axis |
| * segment count will be ignored when the grid line and label positions are calculated. |
| * If you want the range to be divided into equal segments like a normal value axis, set this |
| * property value to zero. |
| * |
| * The base has to be zero or a positive value and it cannot be equal to one. |
| * Defaults to ten. |
| * |
| * \sa ValueAxis3D::segmentCount |
| */ |
| |
| /*! |
| * \qmlproperty bool LogValueAxis3DFormatter::autoSubGrid |
| * |
| * Defines whether sub-grid positions are generated automatically. |
| * |
| * If this property value is set to \c true, the parent axis sub-segment count is ignored |
| * when calculating sub-grid line positions. The sub-grid positions are generated automatically |
| * according to the \l base property value. |
| * The number of sub-grid lines is set to the base value minus one, rounded down. |
| * This property is ignored when the base value is zero. |
| * Defaults to \c true. |
| * |
| * \sa base, ValueAxis3D::subSegmentCount |
| */ |
| |
| /*! |
| * \qmlproperty bool LogValueAxis3DFormatter::showEdgeLabels |
| * |
| * Defines whether the first and last label on the axis are visible. |
| * |
| * When the \l base property value is non-zero, the whole axis range is often |
| * not equally divided into |
| * segments. The first and last segments are often smaller than the other segments. |
| * In extreme cases, this can lead to overlapping labels on the first and last two grid lines. |
| * By setting this property to \c false, you can suppress showing the minimum and maximum labels |
| * for the axis in cases where the segments do not exactly fit the axis. |
| * Defaults to \c true. |
| * |
| * \sa base, AbstractAxis3D::labels |
| */ |
| |
| /*! |
| * \internal |
| */ |
| QLogValue3DAxisFormatter::QLogValue3DAxisFormatter(QLogValue3DAxisFormatterPrivate *d, |
| QObject *parent) : |
| QValue3DAxisFormatter(d, parent) |
| { |
| setAllowNegatives(false); |
| setAllowZero(false); |
| } |
| |
| /*! |
| * Constructs a new logarithmic value 3D axis formatter with the optional |
| * parent \a parent. |
| */ |
| QLogValue3DAxisFormatter::QLogValue3DAxisFormatter(QObject *parent) : |
| QValue3DAxisFormatter(new QLogValue3DAxisFormatterPrivate(this), parent) |
| { |
| setAllowNegatives(false); |
| setAllowZero(false); |
| } |
| |
| /*! |
| * Deletes the logarithmic value 3D axis formatter. |
| */ |
| QLogValue3DAxisFormatter::~QLogValue3DAxisFormatter() |
| { |
| } |
| |
| /*! |
| * \property QLogValue3DAxisFormatter::base |
| * |
| * \brief The base of the logarithm used to map axis values. |
| * |
| * If the base is non-zero, the parent axis |
| * segment count will be ignored when the grid line and label positions are calculated. |
| * If you want the range to be divided into equal segments like a normal value axis, set this |
| * property value to zero. |
| * |
| * The base has to be zero or a positive value and it cannot be equal to one. |
| * Defaults to ten. |
| * |
| * \sa QValue3DAxis::segmentCount |
| */ |
| void QLogValue3DAxisFormatter::setBase(qreal base) |
| { |
| if (base < 0.0f || base == 1.0f) { |
| qWarning() << "Warning: The logarithm base must be greater than 0 and not equal to 1," |
| << "attempted:" << base; |
| return; |
| } |
| if (dptr()->m_base != base) { |
| dptr()->m_base = base; |
| markDirty(true); |
| emit baseChanged(base); |
| } |
| } |
| |
| qreal QLogValue3DAxisFormatter::base() const |
| { |
| return dptrc()->m_base; |
| } |
| |
| /*! |
| * \property QLogValue3DAxisFormatter::autoSubGrid |
| * |
| * \brief Whether sub-grid positions are generated automatically. |
| * |
| * If this property value is set to \c true, the parent axis sub-segment count is ignored |
| * when calculating sub-grid line positions. The sub-grid positions are generated automatically |
| * according to the \l base property value. The number of sub-grid lines is set |
| * to the base value minus one, rounded down. This property is ignored when the |
| * base value is zero. |
| * Defaults to \c true. |
| * |
| * \sa base, QValue3DAxis::subSegmentCount |
| */ |
| void QLogValue3DAxisFormatter::setAutoSubGrid(bool enabled) |
| { |
| if (dptr()->m_autoSubGrid != enabled) { |
| dptr()->m_autoSubGrid = enabled; |
| markDirty(false); |
| emit autoSubGridChanged(enabled); |
| } |
| } |
| |
| bool QLogValue3DAxisFormatter::autoSubGrid() const |
| { |
| return dptrc()->m_autoSubGrid; |
| } |
| |
| /*! |
| * \property QLogValue3DAxisFormatter::showEdgeLabels |
| * |
| * \brief Whether the first and last label on the axis are visible. |
| * |
| * When the \l base property value is non-zero, the whole axis range is often |
| * not equally divided into |
| * segments. The first and last segments are often smaller than the other segments. |
| * In extreme cases, this can lead to overlapping labels on the first and last two grid lines. |
| * By setting this property to \c false, you can suppress showing the minimum and maximum labels |
| * for the axis in cases where the segments do not exactly fit the axis. |
| * Defaults to \c true. |
| * |
| * \sa base, QAbstract3DAxis::labels |
| */ |
| void QLogValue3DAxisFormatter::setShowEdgeLabels(bool enabled) |
| { |
| if (dptr()->m_showEdgeLabels != enabled) { |
| dptr()->m_showEdgeLabels = enabled; |
| markDirty(true); |
| emit showEdgeLabelsChanged(enabled); |
| } |
| } |
| |
| bool QLogValue3DAxisFormatter::showEdgeLabels() const |
| { |
| return dptrc()->m_showEdgeLabels; |
| } |
| |
| /*! |
| * \internal |
| */ |
| QValue3DAxisFormatter *QLogValue3DAxisFormatter::createNewInstance() const |
| { |
| return new QLogValue3DAxisFormatter(); |
| } |
| |
| /*! |
| * \internal |
| */ |
| void QLogValue3DAxisFormatter::recalculate() |
| { |
| dptr()->recalculate(); |
| } |
| |
| /*! |
| * \internal |
| */ |
| float QLogValue3DAxisFormatter::positionAt(float value) const |
| { |
| return dptrc()->positionAt(value); |
| } |
| |
| /*! |
| * \internal |
| */ |
| float QLogValue3DAxisFormatter::valueAt(float position) const |
| { |
| return dptrc()->valueAt(position); |
| } |
| |
| /*! |
| * \internal |
| */ |
| void QLogValue3DAxisFormatter::populateCopy(QValue3DAxisFormatter ©) const |
| { |
| QValue3DAxisFormatter::populateCopy(copy); |
| dptrc()->populateCopy(copy); |
| } |
| |
| /*! |
| * \internal |
| */ |
| QLogValue3DAxisFormatterPrivate *QLogValue3DAxisFormatter::dptr() |
| { |
| return static_cast<QLogValue3DAxisFormatterPrivate *>(d_ptr.data()); |
| } |
| |
| /*! |
| * \internal |
| */ |
| const QLogValue3DAxisFormatterPrivate *QLogValue3DAxisFormatter::dptrc() const |
| { |
| return static_cast<const QLogValue3DAxisFormatterPrivate *>(d_ptr.data()); |
| } |
| |
| // QLogValue3DAxisFormatterPrivate |
| QLogValue3DAxisFormatterPrivate::QLogValue3DAxisFormatterPrivate(QLogValue3DAxisFormatter *q) |
| : QValue3DAxisFormatterPrivate(q), |
| m_base(10.0), |
| m_logMin(0.0), |
| m_logMax(0.0), |
| m_logRangeNormalizer(0.0), |
| m_autoSubGrid(true), |
| m_showEdgeLabels(true), |
| m_evenMinSegment(true), |
| m_evenMaxSegment(true) |
| { |
| } |
| |
| QLogValue3DAxisFormatterPrivate::~QLogValue3DAxisFormatterPrivate() |
| { |
| } |
| |
| void QLogValue3DAxisFormatterPrivate::recalculate() |
| { |
| // When doing position/value mappings, base doesn't matter, so just use natural logarithm |
| m_logMin = qLn(qreal(m_min)); |
| m_logMax = qLn(qreal(m_max)); |
| m_logRangeNormalizer = m_logMax - m_logMin; |
| |
| int subGridCount = m_axis->subSegmentCount() - 1; |
| int segmentCount = m_axis->segmentCount(); |
| QString labelFormat = m_axis->labelFormat(); |
| qreal segmentStep; |
| if (m_base > 0.0) { |
| // Update parent axis segment counts |
| qreal logMin = qLn(qreal(m_min)) / qLn(m_base); |
| qreal logMax = qLn(qreal(m_max)) / qLn(m_base); |
| qreal logRangeNormalizer = logMax - logMin; |
| |
| qreal minDiff = qCeil(logMin) - logMin; |
| qreal maxDiff = logMax - qFloor(logMax); |
| |
| m_evenMinSegment = qFuzzyCompare(0.0, minDiff); |
| m_evenMaxSegment = qFuzzyCompare(0.0, maxDiff); |
| |
| segmentCount = qRound(logRangeNormalizer - minDiff - maxDiff); |
| |
| if (!m_evenMinSegment) |
| segmentCount++; |
| if (!m_evenMaxSegment) |
| segmentCount++; |
| |
| segmentStep = 1.0 / logRangeNormalizer; |
| |
| if (m_autoSubGrid) { |
| subGridCount = qCeil(m_base) - 2; // -2 for subgrid because subsegment count is base - 1 |
| if (subGridCount < 0) |
| subGridCount = 0; |
| } |
| |
| m_gridPositions.resize(segmentCount + 1); |
| m_subGridPositions.resize(segmentCount * subGridCount); |
| m_labelPositions.resize(segmentCount + 1); |
| m_labelStrings.clear(); |
| m_labelStrings.reserve(segmentCount + 1); |
| |
| // Calculate segment positions |
| int index = 0; |
| if (!m_evenMinSegment) { |
| m_gridPositions[0] = 0.0f; |
| m_labelPositions[0] = 0.0f; |
| if (m_showEdgeLabels) |
| m_labelStrings << qptr()->stringForValue(qreal(m_min), labelFormat); |
| else |
| m_labelStrings << QString(); |
| index++; |
| } |
| for (int i = 0; i < segmentCount; i++) { |
| float gridValue = float((minDiff + qreal(i)) / qreal(logRangeNormalizer)); |
| m_gridPositions[index] = gridValue; |
| m_labelPositions[index] = gridValue; |
| m_labelStrings << qptr()->stringForValue(qPow(m_base, minDiff + qreal(i) + logMin), |
| labelFormat); |
| index++; |
| } |
| // Ensure max value doesn't suffer from any rounding errors |
| m_gridPositions[segmentCount] = 1.0f; |
| m_labelPositions[segmentCount] = 1.0f; |
| QString finalLabel; |
| if (m_showEdgeLabels || m_evenMaxSegment) |
| finalLabel = qptr()->stringForValue(qreal(m_max), labelFormat); |
| |
| if (m_labelStrings.size() > segmentCount) |
| m_labelStrings.replace(segmentCount, finalLabel); |
| else |
| m_labelStrings << finalLabel; |
| } else { |
| // Grid lines and label positions are the same as the parent class, so call parent impl |
| // first to populate those |
| QValue3DAxisFormatterPrivate::doRecalculate(); |
| |
| // Label string list needs to be repopulated |
| segmentStep = 1.0 / qreal(segmentCount); |
| |
| m_labelStrings << qptr()->stringForValue(qreal(m_min), labelFormat); |
| for (int i = 1; i < m_labelPositions.size() - 1; i++) |
| m_labelStrings[i] = qptr()->stringForValue(qExp(segmentStep * qreal(i) |
| * m_logRangeNormalizer + m_logMin), |
| labelFormat); |
| m_labelStrings << qptr()->stringForValue(qreal(m_max), labelFormat); |
| |
| m_evenMaxSegment = true; |
| m_evenMinSegment = true; |
| } |
| |
| // Subgrid line positions are logarithmically spaced |
| if (subGridCount > 0) { |
| float oneSegmentRange = valueAt(float(segmentStep)) - m_min; |
| float subSegmentStep = oneSegmentRange / float(subGridCount + 1); |
| |
| // Since the logarithm has the same curvature across whole axis range, we can just calculate |
| // subgrid positions for the first segment and replicate them to other segments. |
| QVector<float> actualSubSegmentSteps(subGridCount); |
| |
| for (int i = 0; i < subGridCount; i++) { |
| float currentSubPosition = positionAt(m_min + ((i + 1) * subSegmentStep)); |
| actualSubSegmentSteps[i] = currentSubPosition; |
| } |
| |
| float firstPartialSegmentAdjustment = float(segmentStep) - m_gridPositions.at(1); |
| for (int i = 0; i < segmentCount; i++) { |
| for (int j = 0; j < subGridCount; j++) { |
| float position = m_gridPositions.at(i) + actualSubSegmentSteps.at(j); |
| if (!m_evenMinSegment && i == 0) |
| position -= firstPartialSegmentAdjustment; |
| if (position > 1.0f) |
| position = 1.0f; |
| if (position < 0.0f) |
| position = 0.0f; |
| m_subGridPositions[i * subGridCount + j] = position; |
| } |
| } |
| } |
| } |
| |
| void QLogValue3DAxisFormatterPrivate::populateCopy(QValue3DAxisFormatter ©) const |
| { |
| QLogValue3DAxisFormatter *logFormatter = static_cast<QLogValue3DAxisFormatter *>(©); |
| QLogValue3DAxisFormatterPrivate *priv = logFormatter->dptr(); |
| |
| priv->m_base = m_base; |
| priv->m_logMin = m_logMin; |
| priv->m_logMax = m_logMax; |
| priv->m_logRangeNormalizer = m_logRangeNormalizer; |
| } |
| |
| float QLogValue3DAxisFormatterPrivate::positionAt(float value) const |
| { |
| qreal logValue = qLn(qreal(value)); |
| float retval = float((logValue - m_logMin) / m_logRangeNormalizer); |
| |
| return retval; |
| } |
| |
| float QLogValue3DAxisFormatterPrivate::valueAt(float position) const |
| { |
| qreal logValue = (qreal(position) * m_logRangeNormalizer) + m_logMin; |
| return float(qExp(logValue)); |
| } |
| |
| QLogValue3DAxisFormatter *QLogValue3DAxisFormatterPrivate::qptr() |
| { |
| return static_cast<QLogValue3DAxisFormatter *>(q_ptr); |
| } |
| |
| QT_END_NAMESPACE_DATAVISUALIZATION |