mirror of https://github.com/qt/qtgraphs.git
428 lines
13 KiB
C++
428 lines
13 KiB
C++
|
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||
|
|
|
||
|
|
#include "qlogvalue3daxisformatter_p.h"
|
||
|
|
#include "qvalue3daxis_p.h"
|
||
|
|
#include <QtCore/qmath.h>
|
||
|
|
|
||
|
|
QT_BEGIN_NAMESPACE
|
||
|
|
|
||
|
|
/*!
|
||
|
|
* \class QLogValue3DAxisFormatter
|
||
|
|
* \inmodule QtGraphs
|
||
|
|
* \brief The QLogValue3DAxisFormatter class 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.
|
||
|
|
*
|
||
|
|
* \sa QValue3DAxisFormatter
|
||
|
|
*/
|
||
|
|
|
||
|
|
/*!
|
||
|
|
* \qmltype LogValueAxis3DFormatter
|
||
|
|
* \inqmlmodule QtGraphs
|
||
|
|
* \ingroup graphs_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.
|
||
|
|
QList<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
|