some classes for scales/axes

This commit is contained in:
Uwe Rathmann 2020-11-13 15:34:02 +01:00
parent 80611901ac
commit 4fd41e7be2
7 changed files with 751 additions and 0 deletions

View File

@ -0,0 +1,359 @@
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/
// code cpoied from Qwt - with permission from the author ( = myself )
#include "QskScaleEngine.h"
#include "QskFunctions.h"
#include "QskIntervalF.h"
#include "QskScaleTickmarks.h"
#include <QDebug>
#include <QtMath>
#include <cmath>
namespace
{
// What about using qskFuzzyCompare and friends ???
const double _eps = 1.0e-6;
inline int fuzzyCompare( double value1, double value2, double intervalSize )
{
const double eps = qAbs( 1.0e-6 * intervalSize );
if ( value2 - value1 > eps )
return -1;
if ( value1 - value2 > eps )
return 1;
return 0;
}
inline bool fuzzyContains( const QskIntervalF& interval, double value )
{
if ( !interval.isValid() )
return false;
if ( fuzzyCompare( value, interval.lowerBound(), interval.width() ) < 0 )
return false;
if ( fuzzyCompare( value, interval.upperBound(), interval.width() ) > 0 )
return false;
return true;
}
double ceilEps( double value, double intervalSize )
{
const double eps = _eps * intervalSize;
value = ( value - eps ) / intervalSize;
return std::ceil( value ) * intervalSize;
}
double floorEps( double value, double intervalSize )
{
const double eps = _eps * intervalSize;
value = ( value + eps ) / intervalSize;
return std::floor( value ) * intervalSize;
}
double divideEps( double intervalSize, double numSteps )
{
if ( numSteps == 0.0 || intervalSize == 0.0 )
return 0.0;
return ( intervalSize - ( _eps * intervalSize ) ) / numSteps;
}
double divideInterval( double intervalSize, int numSteps )
{
if ( numSteps <= 0 )
return 0.0;
const auto v = divideEps( intervalSize, numSteps );
if ( v == 0.0 )
return 0.0;
constexpr double base = 10.0;
// the same as std::log10( std::fabs( v ) );
const double lx = std::log( std::fabs( v ) ) / std::log( base );
const double p = std::floor( lx );
const double fraction = std::pow( base, lx - p );
uint n = base;
while ( ( n > 1 ) && ( fraction <= n / 2 ) )
n /= 2;
double stepSize = n * std::pow( base, p );
if ( v < 0 )
stepSize = -stepSize;
return stepSize;
}
}
namespace
{
double minorStepSize( double intervalSize, int maxSteps )
{
const double minStep = divideInterval( intervalSize, maxSteps );
if ( minStep != 0.0 )
{
// # ticks per interval
const int numTicks = qCeil( qAbs( intervalSize / minStep ) ) - 1;
// Do the minor steps fit into the interval?
if ( fuzzyCompare( ( numTicks + 1 ) * qAbs( minStep ),
qAbs( intervalSize ), intervalSize ) > 0 )
{
// The minor steps doesn't fit into the interval
return 0.5 * intervalSize;
}
}
return minStep;
}
}
QskScaleEngine::QskScaleEngine()
{
}
QskScaleEngine::~QskScaleEngine()
{
}
void QskScaleEngine::setAttribute( Attribute attribute, bool on )
{
if ( on )
m_attributes |= attribute;
else
m_attributes &= ~attribute;
}
bool QskScaleEngine::testAttribute( Attribute attribute ) const
{
return m_attributes & attribute;
}
void QskScaleEngine::setAttributes( Attributes attributes )
{
m_attributes = attributes;
}
QskScaleEngine::Attributes QskScaleEngine::attributes() const
{
return m_attributes;
}
QskScaleTickmarks QskScaleEngine::divideScale(
qreal x1, qreal x2, int maxMajorSteps, int maxMinorSteps, qreal stepSize) const
{
QskScaleTickmarks tickmarks;
const auto interval = QskIntervalF::normalized( x1, x2 );
if ( interval.width() > std::numeric_limits<qreal>::max() )
{
qWarning() << "QskScaleEngine::divideScale: overflow";
return tickmarks;
}
if ( interval.width() <= 0 )
return tickmarks;
stepSize = qAbs( stepSize );
if ( stepSize == 0.0 )
{
if ( maxMajorSteps < 1 )
maxMajorSteps = 1;
stepSize = divideInterval( interval.width(), maxMajorSteps );
}
if ( stepSize != 0.0 )
{
tickmarks = buildTicks( interval, stepSize, maxMinorSteps );
}
if ( x1 > x2 )
tickmarks.invert();
return tickmarks;
}
void QskScaleEngine::autoScale(int maxNumSteps, qreal& x1, qreal& x2, qreal& stepSize) const
{
auto interval = QskIntervalF::normalized( x1, x2 );
interval.setLowerBound( interval.lowerBound() );
interval.setUpperBound( interval.upperBound() );
stepSize = divideInterval( interval.width(), qMax( maxNumSteps, 1 ) );
if ( !testAttribute( QskScaleEngine::Floating ) )
interval = align( interval, stepSize );
x1 = interval.lowerBound();
x2 = interval.upperBound();
if ( testAttribute( QskScaleEngine::Inverted ) )
{
qSwap( x1, x2 );
stepSize = -stepSize;
}
}
QskIntervalF QskScaleEngine::align( const QskIntervalF& interval, qreal stepSize ) const
{
auto x1 = interval.lowerBound();
auto x2 = interval.upperBound();
// when there is no rounding beside some effect, when
// calculating with doubles, we keep the original value
const auto max = std::numeric_limits<qreal>::max();
if ( -max + stepSize <= x1 )
{
const auto x = floorEps( x1, stepSize );
if ( qFuzzyIsNull( x ) || !qFuzzyCompare( x1, x ) )
x1 = x;
}
if ( max - stepSize >= x2 )
{
const auto x = ceilEps( x2, stepSize );
if ( qFuzzyIsNull( x ) || !qFuzzyCompare( x2, x ) )
x2 = x;
}
return QskIntervalF( x1, x2 );
}
QVector<qreal> QskScaleEngine::strip(
const QVector<qreal>& ticks, const QskIntervalF& interval ) const
{
if ( !interval.isValid() || ticks.count() == 0 )
return QVector<qreal>();
if ( fuzzyContains( interval, ticks.first() )
&& fuzzyContains( interval, ticks.last() ) )
{
return ticks;
}
QVector<qreal> strippedTicks;
for ( int i = 0; i < ticks.count(); i++ )
{
if ( fuzzyContains( interval, ticks[i] ) )
strippedTicks += ticks[i];
}
return strippedTicks;
}
QskScaleTickmarks QskScaleEngine::buildTicks(
const QskIntervalF &interval, qreal stepSize, int maxMinorSteps ) const
{
using T = QskScaleTickmarks;
const auto boundingInterval = align( interval, stepSize );
QVector<qreal> ticks[3];
ticks[T::MajorTick] = buildMajorTicks( boundingInterval, stepSize );
if ( maxMinorSteps > 0 )
{
buildMinorTicks( ticks[T::MajorTick], maxMinorSteps, stepSize,
ticks[T::MinorTick], ticks[T::MediumTick] );
}
for ( auto& t : ticks )
{
t = strip( t, interval );
// ticks very close to 0.0 are
// explicitely set to 0.0
for ( int i = 0; i < t.count(); i++ )
{
if ( fuzzyCompare( t[i], 0.0, stepSize ) == 0 )
t[i] = 0.0;
}
}
QskScaleTickmarks tickmarks;
tickmarks.setMinorTicks( ticks[T::MinorTick] );
tickmarks.setMediumTicks( ticks[T::MediumTick] );
tickmarks.setMajorTicks( ticks[T::MajorTick] );
return tickmarks;
}
QVector<qreal> QskScaleEngine::buildMajorTicks(
const QskIntervalF& interval, qreal stepSize ) const
{
int numTicks = qRound( interval.width() / stepSize ) + 1;
if ( numTicks > 10000 )
numTicks = 10000;
QVector<qreal> ticks;
ticks.reserve( numTicks );
ticks += interval.lowerBound();
for ( int i = 1; i < numTicks - 1; i++ )
ticks += interval.lowerBound() + i * stepSize;
ticks += interval.upperBound();
return ticks;
}
void QskScaleEngine::buildMinorTicks(
const QVector<qreal>& majorTicks, int maxMinorSteps, qreal stepSize,
QVector<qreal> &minorTicks, QVector<qreal> &mediumTicks ) const
{
auto minStep = minorStepSize( stepSize, maxMinorSteps );
if ( minStep == 0.0 )
return;
// # ticks per interval
const int numTicks = qCeil( qAbs( stepSize / minStep ) ) - 1;
int medIndex = -1;
if ( numTicks % 2 )
medIndex = numTicks / 2;
// calculate minor ticks
for ( int i = 0; i < majorTicks.count(); i++ )
{
auto val = majorTicks[i];
for ( int k = 0; k < numTicks; k++ )
{
val += minStep;
double alignedValue = val;
if ( fuzzyCompare( val, 0.0, stepSize ) == 0 )
alignedValue = 0.0;
if ( k == medIndex )
mediumTicks += alignedValue;
else
minorTicks += alignedValue;
}
}
}
#include "moc_QskScaleEngine.cpp"

View File

@ -0,0 +1,60 @@
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/
#ifndef QSK_SCALE_ENGINE_H
#define QSK_SCALE_ENGINE_H
#include <QskGlobal.h>
#include <qmetatype.h>
class QskScaleTickmarks;
class QskIntervalF;
class QSK_EXPORT QskScaleEngine
{
Q_GADGET
public:
enum Attribute
{
Inverted = 1 << 0,
Floating = 1 << 1
};
Q_ENUM( Attribute )
Q_DECLARE_FLAGS( Attributes, Attribute )
QskScaleEngine();
~QskScaleEngine();
void setAttribute( Attribute, bool on = true );
bool testAttribute( Attribute ) const;
void setAttributes( Attributes );
Attributes attributes() const;
QskScaleTickmarks divideScale(qreal x1, qreal x2,
int maxMajorSteps, int maxMinorSteps, qreal stepSize = 0.0) const;
void autoScale( int maxNumSteps, qreal &x1, qreal &x2, qreal &stepSize ) const;
private:
QskIntervalF align( const QskIntervalF&, qreal stepSize ) const;
QVector<qreal> strip( const QVector<qreal>&, const QskIntervalF& ) const;
QskScaleTickmarks buildTicks(
const QskIntervalF&, qreal stepSize, int maxMinorSteps ) const;
QVector<qreal> buildMajorTicks( const QskIntervalF&, qreal stepSize ) const;
void buildMinorTicks( const QVector<qreal>& majorTicks,
int maxMinorSteps, qreal stepSize, QVector<qreal>& minorTicks,
QVector<qreal>& mediumTicks ) const;
Attributes m_attributes;
};
#endif

View File

@ -0,0 +1,69 @@
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/
#include "QskScaleTickmarks.h"
#include <algorithm>
int QskScaleTickmarks::tickCount() const noexcept
{
return m_ticks[ MajorTick ].count()
+ m_ticks[ MediumTick ].count()
+ m_ticks[ MinorTick ].count();
}
QskScaleTickmarks::QskScaleTickmarks()
{
}
QskScaleTickmarks::~QskScaleTickmarks()
{
}
int QskScaleTickmarks::tickCount( TickType type ) const noexcept
{
return m_ticks[ type ].count();
}
QVector<qreal> QskScaleTickmarks::ticks( TickType type ) const noexcept
{
return m_ticks[ type ];
}
void QskScaleTickmarks::setTicks(TickType type, const QVector<qreal>& ticks )
{
m_ticks[ type ] = ticks;
}
void QskScaleTickmarks::reset()
{
m_ticks[ 0 ].clear();
m_ticks[ 1 ].clear();
m_ticks[ 2 ].clear();
}
void QskScaleTickmarks::invert()
{
std::reverse( m_ticks[ 0 ].begin(), m_ticks[ 0 ].end() );
std::reverse( m_ticks[ 1 ].begin(), m_ticks[ 1 ].end() );
std::reverse( m_ticks[ 2 ].begin(), m_ticks[ 2 ].end() );
}
uint QskScaleTickmarks::hash( uint seed ) const
{
seed = qHash( m_ticks[0], seed );
seed = qHash( m_ticks[1], seed );
seed = qHash( m_ticks[2], seed );
return seed;
}
bool QskScaleTickmarks::operator==( const QskScaleTickmarks &other ) const noexcept
{
return ( m_ticks[ 0 ] == other.m_ticks[ 0 ] )
&& ( m_ticks[ 1 ] == other.m_ticks[ 1 ] )
&& ( m_ticks[ 2 ] == other.m_ticks[ 2 ] );
}
#include "moc_QskScaleTickmarks.cpp"

View File

@ -0,0 +1,96 @@
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/
#ifndef QSK_SCALE_TICKMARKS_H
#define QSK_SCALE_TICKMARKS_H
#include <QskIntervalF.h>
#include <QVector>
class QSK_EXPORT QskScaleTickmarks
{
Q_GADGET
Q_PROPERTY( QVector<qreal> majorTicks READ majorTicks WRITE setMajorTicks )
Q_PROPERTY( QVector<qreal> mediumTicks READ mediumTicks WRITE setMediumTicks )
Q_PROPERTY( QVector<qreal> minorTicks READ minorTicks WRITE setMinorTicks )
public:
enum TickType
{
MinorTick,
MediumTick,
MajorTick,
};
Q_ENUM( TickType )
QskScaleTickmarks();
~QskScaleTickmarks();
bool operator==( const QskScaleTickmarks& ) const noexcept;
bool operator!=( const QskScaleTickmarks& ) const noexcept;
int tickCount() const noexcept;
int tickCount( TickType ) const noexcept;
QVector<qreal> ticks( TickType ) const noexcept;
void setTicks( TickType, const QVector<qreal> & );
void setMinorTicks( const QVector<qreal>& );
QVector<qreal> minorTicks() const noexcept;
void setMediumTicks( const QVector<qreal>& );
QVector<qreal> mediumTicks() const noexcept;
void setMajorTicks( const QVector<qreal>& );
QVector<qreal> majorTicks() const noexcept;
void invert();
void reset();
uint hash( uint seed = 0 ) const;
private:
QVector< qreal > m_ticks[ 3 ];
};
inline void QskScaleTickmarks::setMinorTicks( const QVector<qreal>& ticks )
{
setTicks( MinorTick, ticks );
}
inline QVector<qreal> QskScaleTickmarks::minorTicks() const noexcept
{
return ticks( MinorTick );
}
inline void QskScaleTickmarks::setMediumTicks( const QVector<qreal>& ticks )
{
setTicks( MediumTick, ticks );
}
inline QVector<qreal> QskScaleTickmarks::mediumTicks() const noexcept
{
return ticks( MediumTick );
}
inline void QskScaleTickmarks::setMajorTicks( const QVector<qreal>& ticks )
{
setTicks( MajorTick, ticks );
}
inline QVector<qreal> QskScaleTickmarks::majorTicks() const noexcept
{
return ticks( MajorTick );
}
inline bool QskScaleTickmarks::operator!=(
const QskScaleTickmarks& other ) const noexcept
{
return !( *this == other );
}
#endif

View File

@ -0,0 +1,127 @@
#include "QskTickmarksNode.h"
#include "QskScaleTickmarks.h"
#include <QSGFlatColorMaterial>
#include <QSGGeometryNode>
#include <QRectF>
QSK_QT_PRIVATE_BEGIN
#include <private/qsgnode_p.h>
QSK_QT_PRIVATE_END
static constexpr inline qreal qskTickFactor( QskScaleTickmarks::TickType type )
{
using TM = QskScaleTickmarks;
return type == TM::MinorTick ? 0.7 : ( type == TM::MinorTick ? 0.85 : 1.0 );
}
class QskTickmarksNodePrivate final : public QSGGeometryNodePrivate
{
public:
QskTickmarksNodePrivate()
: geometry( QSGGeometry::defaultAttributes_Point2D(), 0 )
{
geometry.setDrawingMode( GL_LINES );
geometry.setVertexDataPattern( QSGGeometry::StaticPattern );
}
QSGGeometry geometry;
QSGFlatColorMaterial material;
QskIntervalF boundaries;
QskScaleTickmarks tickmarks;
QRectF rect;
int lineWidth = 0;
uint hash = 0;
};
QskTickmarksNode::QskTickmarksNode()
: QSGGeometryNode( *new QskTickmarksNodePrivate )
{
Q_D( QskTickmarksNode );
setGeometry( &d->geometry );
setMaterial( &d->material );
}
QskTickmarksNode::~QskTickmarksNode()
{
}
void QskTickmarksNode::update(
const QColor& color, const QRectF& rect,
const QskIntervalF& boundaries, const QskScaleTickmarks& tickmarks,
int lineWidth, Qt::Orientation orientation )
{
Q_D( QskTickmarksNode );
if( lineWidth != d->lineWidth )
{
d->lineWidth = lineWidth;
d->geometry.setLineWidth( lineWidth );
markDirty( QSGNode::DirtyGeometry );
}
const uint hash = tickmarks.hash( 17435 );
if( ( hash != d->hash ) || ( rect != d->rect ) )
{
d->hash = hash;
d->rect = rect;
d->geometry.allocate( tickmarks.tickCount() * 2 );
auto vertexData = d->geometry.vertexDataAsPoint2D();
const qreal min = boundaries.lowerBound();
const qreal range = boundaries.width();
using TM = QskScaleTickmarks;
for( int i = TM::MinorTick; i <= TM::MajorTick; i++ )
{
const auto tickType = static_cast< TM::TickType >(i);
const auto ticks = tickmarks.ticks( tickType );
if ( orientation == Qt::Horizontal )
{
const qreal ratio = rect.width() / range;
const float len = rect.height() * qskTickFactor( tickType );
for( const auto tick : ticks )
{
const auto x = rect.x() + ( tick - min ) * ratio;
vertexData[ 0 ].set( x, rect.bottom() );
vertexData[ 1 ].set( x, rect.bottom() - len );
vertexData += 2;
}
}
else
{
const qreal ratio = rect.height() / range;
const float len = rect.width() * qskTickFactor( tickType );
for( const auto tick : ticks )
{
const auto y = rect.bottom() - ( tick - min ) * ratio;
vertexData[ 0 ].set( rect.right(), y );
vertexData[ 1 ].set( rect.right() - len, y );
vertexData += 2;
}
}
}
d->geometry.markVertexDataDirty();
markDirty( QSGNode::DirtyGeometry );
}
if ( color != d->material.color() )
{
d->material.setColor( color );
markDirty( QSGNode::DirtyMaterial );
}
}

View File

@ -0,0 +1,34 @@
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/
#ifndef QSK_TICKMARKS_NODE_H
#define QSK_TICKMARKS_NODE_H
#include "QskGlobal.h"
#include <qsgnode.h>
#include <qnamespace.h>
class QColor;
class QRectF;
class QskIntervalF;
class QskScaleTickmarks;
class QskTickmarksNodePrivate;
class QSK_EXPORT QskTickmarksNode : public QSGGeometryNode
{
public:
QskTickmarksNode();
~QskTickmarksNode() override;
void update( const QColor&, const QRectF&, const QskIntervalF&,
const QskScaleTickmarks&, int tickLineWidth, Qt::Orientation );
private:
Q_DECLARE_PRIVATE( QskTickmarksNode )
};
#endif

View File

@ -30,6 +30,8 @@ HEADERS += \
common/QskObjectCounter.h \
common/QskRgbValue.h \
common/QskRgbPalette.h \
common/QskScaleEngine.h \
common/QskScaleTickmarks.h \
common/QskShadowMetrics.h \
common/QskSizePolicy.h \
common/QskTextColors.h \
@ -50,6 +52,8 @@ SOURCES += \
common/QskObjectCounter.cpp \
common/QskRgbValue.cpp \
common/QskRgbPalette.cpp \
common/QskScaleEngine.cpp \
common/QskScaleTickmarks.cpp \
common/QskShadowMetrics.cpp \
common/QskSizePolicy.cpp \
common/QskTextColors.cpp \
@ -92,6 +96,7 @@ HEADERS += \
nodes/QskTextRenderer.h \
nodes/QskTextureNode.h \
nodes/QskTextureRenderer.h \
nodes/QskTickmarksNode.h \
nodes/QskVertex.h
SOURCES += \
@ -108,6 +113,7 @@ SOURCES += \
nodes/QskTextRenderer.cpp \
nodes/QskTextureNode.cpp \
nodes/QskTextureRenderer.cpp \
nodes/QskTickmarksNode.cpp \
nodes/QskVertex.cpp
HEADERS += \