summaryrefslogtreecommitdiffstats
path: root/src/datavisualization/axis/qlogvalue3daxisformatter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/datavisualization/axis/qlogvalue3daxisformatter.cpp')
-rw-r--r--src/datavisualization/axis/qlogvalue3daxisformatter.cpp432
1 files changed, 432 insertions, 0 deletions
diff --git a/src/datavisualization/axis/qlogvalue3daxisformatter.cpp b/src/datavisualization/axis/qlogvalue3daxisformatter.cpp
new file mode 100644
index 00000000..7367e7c5
--- /dev/null
+++ b/src/datavisualization/axis/qlogvalue3daxisformatter.cpp
@@ -0,0 +1,432 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the QtDataVisualization module.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+**
+****************************************************************************/
+
+#include "qlogvalue3daxisformatter_p.h"
+#include "qvalue3daxis_p.h"
+#include <QtCore/qmath.h>
+
+QT_BEGIN_NAMESPACE_DATAVISUALIZATION
+
+/*!
+ * \class QLogValue3DAxisFormatter
+ * \inmodule QtDataVisualization
+ * \brief QLogValue3DAxisFormatter implements logarithmic value axis formatter.
+ * \since QtDataVisualization 1.1
+ *
+ * This class provides formatting rules for a logarithmic QValue3DAxis.
+ *
+ * When a QLogValue3DAxisFormatter is attached to a QValue3DAxis, 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
+ * \brief LogValueAxis3DFormatter implements logarithmic value axis formatter.
+ *
+ * This type provides formatting rules for a logarithmic ValueAxis3D.
+ * When a LogValueAxis3DFormatter is attached to a ValueAxis3D, the axis range
+ * cannot include negative values or the zero.
+ */
+
+/*!
+ * \qmlproperty real LogValueAxis3DFormatter::base
+ *
+ * The \a 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 normal value axis, set this
+ * property value to zero.
+ *
+ * The \a base has to be zero or positive value and not equal to one.
+ * Defaults to ten.
+ *
+ * \sa ValueAxis3D::segmentCount
+ */
+
+/*!
+ * \qmlproperty bool LogValueAxis3DFormatter::autoSubGrid
+ *
+ * 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 base property value.
+ * The number of sub-grid lines is set to base value minus one, rounded down.
+ * This property is ignored when base property value is zero.
+ * Defaults to \c true.
+ *
+ * \sa base, ValueAxis3D::subSegmentCount
+ */
+
+/*!
+ * \qmlproperty bool LogValueAxis3DFormatter::showEdgeLabels
+ *
+ * When the 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 QLogValue3DAxisFormatter instance with optional \a parent.
+ */
+QLogValue3DAxisFormatter::QLogValue3DAxisFormatter(QObject *parent) :
+ QValue3DAxisFormatter(new QLogValue3DAxisFormatterPrivate(this), parent)
+{
+ setAllowNegatives(false);
+ setAllowZero(false);
+}
+
+/*!
+ * Destroys QLogValue3DAxisFormatter.
+ */
+QLogValue3DAxisFormatter::~QLogValue3DAxisFormatter()
+{
+}
+
+/*!
+ * \property QLogValue3DAxisFormatter::base
+ *
+ * The \a 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 normal value axis, set this
+ * property value to zero.
+ *
+ * The \a base has to be zero or positive value and not 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
+ *
+ * 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 base property value.
+ * The number of sub-grid lines is set to base value minus one, rounded down.
+ * This property is ignored when base property 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
+ *
+ * When the 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 &copy) 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 &copy) const
+{
+ QLogValue3DAxisFormatter *logFormatter = static_cast<QLogValue3DAxisFormatter *>(&copy);
+ 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