From bd1f7f2d11f19e0a85600b880e2b9d6bf3e8e7e4 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Tue, 28 Nov 2023 09:55:17 +0100 Subject: [PATCH] QskScaleEngine -> QskGraduation --- src/CMakeLists.txt | 4 +- src/common/QskGraduation.cpp | 332 +++++++++++++++++++++++++++++++ src/common/QskGraduation.h | 38 ++++ src/common/QskScaleEngine.cpp | 363 ---------------------------------- src/common/QskScaleEngine.h | 64 ------ 5 files changed, 372 insertions(+), 429 deletions(-) create mode 100644 src/common/QskGraduation.cpp create mode 100644 src/common/QskGraduation.h delete mode 100644 src/common/QskScaleEngine.cpp delete mode 100644 src/common/QskScaleEngine.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 556a859d..f7a5c3c3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,6 +15,7 @@ list(APPEND HEADERS common/QskGradient.h common/QskGradientDirection.h common/QskGradientStop.h + common/QskGraduation.h common/QskGraduationMetrics.h common/QskHctColor.h common/QskIntervalF.h @@ -28,7 +29,6 @@ list(APPEND HEADERS common/QskPlacementPolicy.h common/QskPlatform.h common/QskRgbValue.h - common/QskScaleEngine.h common/QskScaleTickmarks.h common/QskShadowMetrics.h common/QskSizePolicy.h @@ -49,6 +49,7 @@ list(APPEND SOURCES common/QskGradient.cpp common/QskGradientDirection.cpp common/QskGradientStop.cpp + common/QskGraduation.cpp common/QskGraduationMetrics.cpp common/QskHctColor.cpp common/QskIntervalF.cpp @@ -60,7 +61,6 @@ list(APPEND SOURCES common/QskPlatform.cpp common/QskPlacementPolicy.cpp common/QskRgbValue.cpp - common/QskScaleEngine.cpp common/QskScaleTickmarks.cpp common/QskShadowMetrics.cpp common/QskSizePolicy.cpp diff --git a/src/common/QskGraduation.cpp b/src/common/QskGraduation.cpp new file mode 100644 index 00000000..fe28347c --- /dev/null +++ b/src/common/QskGraduation.cpp @@ -0,0 +1,332 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskGraduation.h" +#include "QskFunctions.h" +#include "QskIntervalF.h" +#include "QskScaleTickmarks.h" + +#include +#include + +#include + +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 = std::abs( 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 suggestedStepSize( double intervalSize, int numSteps ) + { + if ( numSteps <= 0 ) + return 0.0; + + const auto v = intervalSize / numSteps; + if ( qFuzzyIsNull( v ) ) + 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 ); + + double stepSize = std::pow( base, p ); + if ( v < 0 ) + stepSize = -stepSize; + + for ( const double f : { 2.0, 2.5, 5.0, 10.0 } ) + { + if ( fraction <= f || qFuzzyCompare( fraction, f ) ) + { + stepSize *= f; + break; + } + } + + return stepSize; + } +} + +namespace Engine +{ + double minorStepSize( double intervalSize, int maxSteps ) + { + const double minStep = suggestedStepSize( 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; + } + + QVector< qreal > strip( + const QVector< qreal >& ticks, const QskIntervalF& interval ) + { + 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; + } + + QskIntervalF align( const QskIntervalF& interval, qreal stepSize ) + { + 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 > buildMajorTicks( + const QskIntervalF& interval, qreal stepSize ) + { + 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 buildMinorTicks( + const QVector< qreal >& majorTicks, int maxMinorSteps, qreal stepSize, + QVector< qreal >& minorTicks, QVector< qreal >& mediumTicks ) + { + 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; + } + } + } + + QskScaleTickmarks buildTicks( + const QskIntervalF& interval, qreal stepSize, int maxMinorSteps ) + { + 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; + } + +} + +QskScaleTickmarks QskGraduation::divideInterval( + qreal x1, qreal x2, int maxMajorSteps, int maxMinorSteps, qreal stepSize) +{ + QskScaleTickmarks tickmarks; + + const auto interval = QskIntervalF::normalized( x1, x2 ); + + if ( interval.width() > std::numeric_limits< qreal >::max() ) + { + qWarning() << "QskGraduation::divideInterval: overflow"; + return tickmarks; + } + + if ( interval.width() <= 0 ) + return tickmarks; + + stepSize = qAbs( stepSize ); + if ( stepSize == 0.0 ) + { + if ( maxMajorSteps < 1 ) + maxMajorSteps = 1; + + stepSize = suggestedStepSize( interval.width(), maxMajorSteps ); + } + + if ( stepSize != 0.0 ) + { + tickmarks = Engine::buildTicks( interval, stepSize, maxMinorSteps ); + } + + if ( x1 > x2 ) + tickmarks.invert(); + + return tickmarks; +} + +void QskGraduation::calculate( Attributes attributes, int maxNumSteps, + qreal& x1, qreal& x2, qreal& stepSize) +{ + auto interval = QskIntervalF::normalized( x1, x2 ); + + interval.setLowerBound( interval.lowerBound() ); + interval.setUpperBound( interval.upperBound() ); + + stepSize = suggestedStepSize( interval.width(), qMax( maxNumSteps, 1 ) ); + + if ( !( attributes & Floating ) ) + interval = Engine::align( interval, stepSize ); + + x1 = interval.lowerBound(); + x2 = interval.upperBound(); + + if ( attributes & Inverted ) + { + qSwap( x1, x2 ); + stepSize = -stepSize; + } +} + +qreal QskGraduation::alignedStepSize( double intervalSize, int numSteps ) +{ + if ( intervalSize <= 0.0 ) + return 0.0; + + return suggestedStepSize( intervalSize, numSteps ); +} + +#include "moc_QskGraduation.cpp" diff --git a/src/common/QskGraduation.h b/src/common/QskGraduation.h new file mode 100644 index 00000000..c499dfa3 --- /dev/null +++ b/src/common/QskGraduation.h @@ -0,0 +1,38 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_GRADUATION_H +#define QSK_GRADUATION_H + +#include +#include + +class QskScaleTickmarks; + +namespace QskGraduation +{ + Q_NAMESPACE_EXPORT( QSK_EXPORT ) + + enum Attribute + { + Inverted = 1 << 0, + Floating = 1 << 1 + }; + + Q_ENUM_NS( Attribute ) + + Q_DECLARE_FLAGS( Attributes, Attribute ) + Q_DECLARE_OPERATORS_FOR_FLAGS( Attributes ) + + QskScaleTickmarks divideInterval( qreal x1, qreal x2, + int maxMajorSteps, int maxMinorSteps, qreal stepSize = 0.0 ); + + void calculate( Attributes, int maxNumSteps, + qreal& x1, qreal& x2, qreal& stepSize ); + + qreal alignedStepSize( double intervalSize, int numSteps ); +} + +#endif diff --git a/src/common/QskScaleEngine.cpp b/src/common/QskScaleEngine.cpp deleted file mode 100644 index 57742c79..00000000 --- a/src/common/QskScaleEngine.cpp +++ /dev/null @@ -1,363 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) 2016 Uwe Rathmann - * SPDX-License-Identifier: BSD-3-Clause - *****************************************************************************/ - -// code copied from Qwt - with permission from the author ( = myself ) - -#include "QskScaleEngine.h" -#include "QskFunctions.h" -#include "QskIntervalF.h" -#include "QskScaleTickmarks.h" - -#include -#include - -#include - -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 = std::abs( 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 divideInterval( double intervalSize, int numSteps ) - { - if ( numSteps <= 0 ) - return 0.0; - - const auto v = intervalSize / numSteps; - if ( qFuzzyIsNull( v ) ) - 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 ); - - double stepSize = std::pow( base, p ); - if ( v < 0 ) - stepSize = -stepSize; - - for ( const double f : { 2.0, 2.5, 5.0, 10.0 } ) - { - if ( fraction <= f || qFuzzyCompare( fraction, f ) ) - { - stepSize *= f; - break; - } - } - - 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; - } - } -} - -qreal QskScaleEngine::alignedStepSize( double intervalSize, int numSteps ) const -{ - if ( intervalSize <= 0.0 ) - return 0.0; - - return divideInterval( intervalSize, numSteps ); -} - -#include "moc_QskScaleEngine.cpp" diff --git a/src/common/QskScaleEngine.h b/src/common/QskScaleEngine.h deleted file mode 100644 index 334ba809..00000000 --- a/src/common/QskScaleEngine.h +++ /dev/null @@ -1,64 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) 2016 Uwe Rathmann - * SPDX-License-Identifier: BSD-3-Clause - *****************************************************************************/ - -#ifndef QSK_SCALE_ENGINE_H -#define QSK_SCALE_ENGINE_H - -#include -#include - -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; - - qreal alignedStepSize( double intervalSize, int numSteps ) 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; -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS( QskScaleEngine::Attributes ) - -#endif