blob: 4bc3e7e393a1a1fcf737b43c2a77eb46d1d02f0c [file] [log] [blame]
/****************************************************************************
**
** 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 "qvalue3daxisformatter_p.h"
#include "qvalue3daxis_p.h"
QT_BEGIN_NAMESPACE_DATAVISUALIZATION
/*!
* \class QValue3DAxisFormatter
* \inmodule QtDataVisualization
* \brief The QValue3DAxisFormatter class is a base class for value axis
* formatters.
* \since QtDataVisualization 1.1
*
* This class provides formatting rules for a linear value 3D axis. Subclass it if you
* want to implement custom value axes.
*
* The base class has no public API beyond constructors and destructors. It is meant to be only
* used internally. However, subclasses may implement public properties as needed.
*
* \sa QValue3DAxis, QLogValue3DAxisFormatter
*/
/*!
* \qmltype ValueAxis3DFormatter
* \inqmlmodule QtDataVisualization
* \since QtDataVisualization 1.1
* \ingroup datavisualization_qml
* \instantiates QValue3DAxisFormatter
* \brief A base type for value axis formatters.
*
* This type provides formatting rules for a linear value 3D axis.
* This type is the default type for ValueAxis3D and thus never needs to be explicitly created.
* This type has no public functionality.
*
* \sa ValueAxis3D
*/
/*!
* \internal
*/
QValue3DAxisFormatter::QValue3DAxisFormatter(QValue3DAxisFormatterPrivate *d, QObject *parent) :
QObject(parent),
d_ptr(d)
{
}
/*!
* Constructs a new value 3D axis formatter with the optional parent \a parent.
*/
QValue3DAxisFormatter::QValue3DAxisFormatter(QObject *parent) :
QObject(parent),
d_ptr(new QValue3DAxisFormatterPrivate(this))
{
}
/*!
* Deletes the value 3D axis formatter.
*/
QValue3DAxisFormatter::~QValue3DAxisFormatter()
{
}
/*!
* Allows the parent axis to have negative values if \a allow is \c true.
*/
void QValue3DAxisFormatter::setAllowNegatives(bool allow)
{
d_ptr->m_allowNegatives = allow;
}
/*!
* Returns \c true if negative values are valid values for the parent axis.
* The default implementation always returns \c true.
*/
bool QValue3DAxisFormatter::allowNegatives() const
{
return d_ptr->m_allowNegatives;
}
/*!
* Allows the parent axis to have a zero value if \a allow is \c true.
*/
void QValue3DAxisFormatter::setAllowZero(bool allow)
{
d_ptr->m_allowZero = allow;
}
/*!
* Returns \c true if zero is a valid value for the parent axis.
* The default implementation always returns \c true.
*/
bool QValue3DAxisFormatter::allowZero() const
{
return d_ptr->m_allowZero;
}
/*!
* Creates a new empty value 3D axis formatter. Must be reimplemented in a
* subclass.
*
* Returns the new formatter. The renderer uses this method to cache a copy of
* the formatter. The ownership of the new copy is transferred to the caller.
*/
QValue3DAxisFormatter *QValue3DAxisFormatter::createNewInstance() const
{
return new QValue3DAxisFormatter();
}
/*!
* Resizes and populates the label and grid line position arrays and the label
* strings array, as well as calculates any values needed to map a value to its
* position. The parent axis can be accessed from inside this function.
*
* This method must be reimplemented in a subclass if the default array contents are not suitable.
*
* See gridPositions(), subGridPositions(), labelPositions(), and labelStrings() methods for
* documentation about the arrays that need to be resized and populated.
*
* \sa gridPositions(), subGridPositions(), labelPositions(), labelStrings(), axis()
*/
void QValue3DAxisFormatter::recalculate()
{
d_ptr->doRecalculate();
}
/*!
* Returns the formatted label string using the specified \a value and
* \a format.
*
* Reimplement this method in a subclass to resolve the formatted string for a given \a value
* if the default formatting rules specified for QValue3DAxis::labelFormat property are not
* sufficient.
*
* \sa recalculate(), labelStrings(), QValue3DAxis::labelFormat
*/
QString QValue3DAxisFormatter::stringForValue(qreal value, const QString &format) const
{
return d_ptr->stringForValue(value, format);
}
/*!
* Returns the normalized position along the axis for the given \a value.
* The returned value should be between \c 0.0 (the minimum value) and
* \c 1.0 (the maximum value), inclusive, if the value is within the parent
* axis range.
*
* Reimplement this method if the position cannot be resolved by linear
* interpolation between the parent axis minimum and maximum values.
*
* \sa recalculate(), valueAt()
*/
float QValue3DAxisFormatter::positionAt(float value) const
{
return d_ptr->positionAt(value);
}
/*!
* Returns the value at the normalized \a position along the axis.
* The \a position value should be between \c 0.0 (the minimum value) and
* \c 1.0 (the maximum value), inclusive, to obtain values within the parent
* axis range.
*
* Reimplement this method if the value cannot be resolved by linear
* interpolation between the parent axis minimum and maximum values.
*
* \sa recalculate(), positionAt()
*/
float QValue3DAxisFormatter::valueAt(float position) const
{
return d_ptr->valueAt(position);
}
/*!
* Copies all the values necessary for resolving positions, values, and strings
* with this formatter to the \a copy of the formatter. When reimplementing
* this method in a subclass, call the superclass version at some point.
* The renderer uses this method to cache a copy of the formatter.
*
* Returns the new copy. The ownership of the new copy transfers to the caller.
*/
void QValue3DAxisFormatter::populateCopy(QValue3DAxisFormatter &copy) const
{
d_ptr->doPopulateCopy(*(copy.d_ptr.data()));
}
/*!
* Marks this formatter dirty, prompting the renderer to make a new copy of its cache on the next
* renderer synchronization. This method should be called by a subclass whenever the formatter
* is changed in a way that affects the resolved values. Set \a labelsChange to
* \c true if the change requires regenerating the parent axis label strings.
*/
void QValue3DAxisFormatter::markDirty(bool labelsChange)
{
d_ptr->markDirty(labelsChange);
}
/*!
* Returns the parent axis. The parent axis must only be accessed in the recalculate()
* method to maintain thread safety in environments using a threaded renderer.
*
* \sa recalculate()
*/
QValue3DAxis *QValue3DAxisFormatter::axis() const
{
return d_ptr->m_axis;
}
/*!
* Returns a reference to the array of normalized grid line positions.
* The default array size is equal to the segment count of the parent axis plus one, but
* a subclassed implementation of the recalculate() method may resize the array differently.
* The values should be between \c 0.0 (the minimum value) and \c 1.0 (the
* maximum value), inclusive.
*
* \sa QValue3DAxis::segmentCount, recalculate()
*/
QVector<float> &QValue3DAxisFormatter::gridPositions() const
{
return d_ptr->m_gridPositions;
}
/*!
* Returns a reference to the array of normalized subgrid line positions.
* The default array size is equal to the segment count of the parent axis times
* the sub-segment count of the parent axis minus one, but a subclassed
* implementation of the recalculate() method may resize the array differently.
* The values should be between \c 0.0 (the minimum value) and \c 1.0 (the
* maximum value), inclusive.
*
* \sa QValue3DAxis::segmentCount, QValue3DAxis::subSegmentCount, recalculate()
*/
QVector<float> &QValue3DAxisFormatter::subGridPositions() const
{
return d_ptr->m_subGridPositions;
}
/*!
* Returns a reference to the array of normalized label positions.
* The default array size is equal to the segment count of the parent axis plus one, but
* a subclassed implementation of the recalculate() method may resize the array
* differently. The values should be between \c 0.0 (the minimum value) and
* \c 1.0 (the maximum value), inclusive.
* By default, the label at the index zero corresponds to the minimum value
* of the axis.
*
* \sa QValue3DAxis::segmentCount, QAbstract3DAxis::labels, recalculate()
*/
QVector<float> &QValue3DAxisFormatter::labelPositions() const
{
return d_ptr->m_labelPositions;
}
/*!
* Returns a reference to the string list containing formatter label strings.
* The array size must be equal to the size of the label positions array, which
* the indexes also correspond to.
*
* \sa labelPositions()
*/
QStringList &QValue3DAxisFormatter::labelStrings() const
{
return d_ptr->m_labelStrings;
}
/*!
* Sets the \a locale that this formatter uses.
* The graph automatically sets the formatter's locale to a graph's locale whenever the parent axis
* is set as an active axis of the graph, the axis formatter is set to an axis attached to
* the graph, or the graph's locale changes.
*
* \sa locale(), QAbstract3DGraph::locale
*/
void QValue3DAxisFormatter::setLocale(const QLocale &locale)
{
d_ptr->m_cLocaleInUse = (locale == QLocale::c());
d_ptr->m_locale = locale;
markDirty(true);
}
/*!
* Returns the current locale this formatter is using.
*/
QLocale QValue3DAxisFormatter::locale() const
{
return d_ptr->m_locale;
}
// QValue3DAxisFormatterPrivate
QValue3DAxisFormatterPrivate::QValue3DAxisFormatterPrivate(QValue3DAxisFormatter *q)
: QObject(0),
q_ptr(q),
m_needsRecalculate(true),
m_min(0.0f),
m_max(0.0f),
m_rangeNormalizer(0.0f),
m_axis(0),
m_preparsedParamType(Utils::ParamTypeUnknown),
m_allowNegatives(true),
m_allowZero(true),
m_formatPrecision(6), // 6 and 'g' are defaults in Qt API for format precision and spec
m_formatSpec('g'),
m_cLocaleInUse(true)
{
}
QValue3DAxisFormatterPrivate::~QValue3DAxisFormatterPrivate()
{
}
void QValue3DAxisFormatterPrivate::recalculate()
{
// Only recalculate if we need to and have m_axis pointer. If we do not have
// m_axis, either we are not attached to an axis or this is a renderer cache.
if (m_axis && m_needsRecalculate) {
m_min = m_axis->min();
m_max = m_axis->max();
m_rangeNormalizer = (m_max - m_min);
q_ptr->recalculate();
m_needsRecalculate = false;
}
}
void QValue3DAxisFormatterPrivate::doRecalculate()
{
int segmentCount = m_axis->segmentCount();
int subGridCount = m_axis->subSegmentCount() - 1;
QString labelFormat = m_axis->labelFormat();
m_gridPositions.resize(segmentCount + 1);
m_subGridPositions.resize(segmentCount * subGridCount);
m_labelPositions.resize(segmentCount + 1);
m_labelStrings.clear();
m_labelStrings.reserve(segmentCount + 1);
// Use qreals for intermediate calculations for better accuracy on label values
qreal segmentStep = 1.0 / qreal(segmentCount);
qreal subSegmentStep = 0;
if (subGridCount > 0)
subSegmentStep = segmentStep / qreal(subGridCount + 1);
// Calculate positions
qreal rangeNormalizer = qreal(m_max - m_min);
for (int i = 0; i < segmentCount; i++) {
qreal gridValue = segmentStep * qreal(i);
m_gridPositions[i] = float(gridValue);
m_labelPositions[i] = float(gridValue);
m_labelStrings << q_ptr->stringForValue(gridValue * rangeNormalizer + qreal(m_min),
labelFormat);
if (m_subGridPositions.size()) {
for (int j = 0; j < subGridCount; j++)
m_subGridPositions[i * subGridCount + j] = gridValue + subSegmentStep * (j + 1);
}
}
// Ensure max value doesn't suffer from any rounding errors
m_gridPositions[segmentCount] = 1.0f;
m_labelPositions[segmentCount] = 1.0f;
m_labelStrings << q_ptr->stringForValue(qreal(m_max), labelFormat);
}
void QValue3DAxisFormatterPrivate::populateCopy(QValue3DAxisFormatter &copy)
{
recalculate();
q_ptr->populateCopy(copy);
}
void QValue3DAxisFormatterPrivate::doPopulateCopy(QValue3DAxisFormatterPrivate &copy)
{
copy.m_min = m_min;
copy.m_max = m_max;
copy.m_rangeNormalizer = m_rangeNormalizer;
copy.m_gridPositions = m_gridPositions;
copy.m_labelPositions = m_labelPositions;
copy.m_subGridPositions = m_subGridPositions;
}
QString QValue3DAxisFormatterPrivate::stringForValue(qreal value, const QString &format)
{
if (m_previousLabelFormat.compare(format)) {
// Format string different than the previous one used, reparse it
m_labelFormatArray = format.toUtf8();
m_previousLabelFormat = format;
m_preparsedParamType = Utils::preParseFormat(format, m_formatPreStr, m_formatPostStr,
m_formatPrecision, m_formatSpec);
}
if (m_cLocaleInUse) {
return Utils::formatLabelSprintf(m_labelFormatArray, m_preparsedParamType, value);
} else {
return Utils::formatLabelLocalized(m_preparsedParamType, value, m_locale, m_formatPreStr,
m_formatPostStr, m_formatPrecision, m_formatSpec,
m_labelFormatArray);
}
}
float QValue3DAxisFormatterPrivate::positionAt(float value) const
{
return ((value - m_min) / m_rangeNormalizer);
}
float QValue3DAxisFormatterPrivate::valueAt(float position) const
{
return ((position * m_rangeNormalizer) + m_min);
}
void QValue3DAxisFormatterPrivate::setAxis(QValue3DAxis *axis)
{
Q_ASSERT(axis);
// These signals are all connected to markDirtyNoLabelChange slot, even though most of them
// do require labels to be regenerated. This is because the label regeneration is triggered
// elsewhere in these cases.
connect(axis, &QValue3DAxis::segmentCountChanged,
this, &QValue3DAxisFormatterPrivate::markDirtyNoLabelChange);
connect(axis, &QValue3DAxis::subSegmentCountChanged,
this, &QValue3DAxisFormatterPrivate::markDirtyNoLabelChange);
connect(axis, &QValue3DAxis::labelFormatChanged,
this, &QValue3DAxisFormatterPrivate::markDirtyNoLabelChange);
connect(axis, &QAbstract3DAxis::rangeChanged,
this, &QValue3DAxisFormatterPrivate::markDirtyNoLabelChange);
m_axis = axis;
}
void QValue3DAxisFormatterPrivate::markDirty(bool labelsChange)
{
m_needsRecalculate = true;
if (m_axis) {
if (labelsChange)
m_axis->dptr()->emitLabelsChanged();
if (m_axis && m_axis->orientation() != QAbstract3DAxis::AxisOrientationNone)
emit m_axis->dptr()->formatterDirty();
}
}
void QValue3DAxisFormatterPrivate::markDirtyNoLabelChange()
{
markDirty(false);
}
QT_END_NAMESPACE_DATAVISUALIZATION