QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskGraduation.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskGraduation.h"
7#include "QskFunctions.h"
8#include "QskIntervalF.h"
9#include "QskTickmarks.h"
10
11#include <QDebug>
12#include <QtMath>
13
14#include <cmath>
15
16namespace Engine
17{
18 // What about using qskFuzzyCompare and friends ???
19
20 inline int fuzzyCompare( double value1, double value2, double intervalSize )
21 {
22 const double eps = std::abs( 1.0e-6 * intervalSize );
23
24 if ( value2 - value1 > eps )
25 return -1;
26
27 if ( value1 - value2 > eps )
28 return 1;
29
30 return 0;
31 }
32
33 inline bool fuzzyContains( const QskIntervalF& interval, double value )
34 {
35 if ( !interval.isValid() )
36 return false;
37
38 if ( fuzzyCompare( value, interval.lowerBound(), interval.length() ) < 0 )
39 return false;
40
41 if ( fuzzyCompare( value, interval.upperBound(), interval.length() ) > 0 )
42 return false;
43
44 return true;
45 }
46
47 double minorStepSize( double intervalSize, int maxSteps )
48 {
49 const double minStep = QskGraduation::stepSize( intervalSize, maxSteps );
50
51 if ( minStep != 0.0 )
52 {
53 // # ticks per interval
54 const int numTicks = qCeil( qAbs( intervalSize / minStep ) ) - 1;
55
56 // Do the minor steps fit into the interval?
57 if ( fuzzyCompare( ( numTicks + 1 ) * qAbs( minStep ),
58 qAbs( intervalSize ), intervalSize ) > 0 )
59 {
60 // The minor steps doesn't fit into the interval
61 return 0.5 * intervalSize;
62 }
63 }
64
65 return minStep;
66 }
67
68 QVector< qreal > strip(
69 const QVector< qreal >& ticks, const QskIntervalF& interval )
70 {
71 if ( !interval.isValid() || ticks.count() == 0 )
72 return QVector< qreal >();
73
74 if ( fuzzyContains( interval, ticks.first() )
75 && fuzzyContains( interval, ticks.last() ) )
76 {
77 return ticks;
78 }
79
80 QVector< qreal > strippedTicks;
81 strippedTicks.reserve( ticks.count() );
82
83 for ( const auto tick : ticks )
84 {
85 if ( fuzzyContains( interval, tick ) )
86 strippedTicks += tick;
87 }
88
89 return strippedTicks;
90 }
91
92 QVector< qreal > buildMajorTicks(
93 const QskIntervalF& interval, qreal stepSize )
94 {
95 int numTicks = qRound( interval.length() / stepSize ) + 1;
96 if ( numTicks > 10000 )
97 numTicks = 10000;
98
99 QVector< qreal > ticks;
100 ticks.reserve( numTicks );
101
102 ticks += interval.lowerBound();
103 for ( int i = 1; i < numTicks - 1; i++ )
104 ticks += interval.lowerBound() + i * stepSize;
105 ticks += interval.upperBound();
106
107 return ticks;
108 }
109
110 void buildMinorTicks(
111 const QVector< qreal >& majorTicks, int maxMinorSteps, qreal stepSize,
112 QVector< qreal >& minorTicks, QVector< qreal >& mediumTicks )
113 {
114 auto minStep = minorStepSize( stepSize, maxMinorSteps );
115 if ( minStep == 0.0 )
116 return;
117
118 // # ticks per interval
119 const int numTicks = qCeil( qAbs( stepSize / minStep ) ) - 1;
120
121 int medIndex = -1;
122 if ( numTicks % 2 )
123 medIndex = numTicks / 2;
124
125 // calculate minor ticks
126
127 for ( int i = 0; i < majorTicks.count(); i++ )
128 {
129 auto val = majorTicks[i];
130 for ( int k = 0; k < numTicks; k++ )
131 {
132 val += minStep;
133
134 double alignedValue = val;
135 if ( fuzzyCompare( val, 0.0, stepSize ) == 0 )
136 alignedValue = 0.0;
137
138 if ( k == medIndex )
139 mediumTicks += alignedValue;
140 else
141 minorTicks += alignedValue;
142 }
143 }
144 }
145
146 QskTickmarks buildTicks(
147 const QskIntervalF& interval, qreal stepSize, int maxMinorSteps )
148 {
149 using T = QskTickmarks;
150
151 const auto boundingInterval = interval.fuzzyAligned( stepSize );
152
153 QVector< qreal > ticks[3];
154 ticks[T::MajorTick] = buildMajorTicks( boundingInterval, stepSize );
155
156 if ( maxMinorSteps > 0 )
157 {
158 buildMinorTicks( ticks[T::MajorTick], maxMinorSteps, stepSize,
159 ticks[T::MinorTick], ticks[T::MediumTick] );
160 }
161
162 for ( auto& t : ticks )
163 {
164 t = strip( t, interval );
165
166 // ticks very close to 0.0 are
167 // explicitely set to 0.0
168
169 for ( int i = 0; i < t.count(); i++ )
170 {
171 if ( fuzzyCompare( t[i], 0.0, stepSize ) == 0 )
172 t[i] = 0.0;
173 }
174 }
175
176 return { ticks[T::MinorTick], ticks[T::MediumTick], ticks[T::MajorTick] };
177 }
178
179}
180
181QskTickmarks QskGraduation::divideInterval(
182 qreal x1, qreal x2, int maxMajorSteps, int maxMinorSteps, qreal stepSize )
183{
184 QskTickmarks tickmarks;
185
186 const auto interval = QskIntervalF::normalized( x1, x2 );
187
188 if ( interval.length() > std::numeric_limits< qreal >::max() )
189 {
190 qWarning() << "QskGraduation::divideInterval: overflow";
191 return tickmarks;
192 }
193
194 if ( interval.length() <= 0.0 || stepSize < 0.0 )
195 return tickmarks;
196
197 if ( stepSize == 0.0 )
198 {
199 if ( maxMajorSteps < 1 )
200 maxMajorSteps = 1;
201
202 stepSize = QskGraduation::stepSize( interval.length(), maxMajorSteps );
203 }
204
205 if ( stepSize != 0.0 )
206 tickmarks = Engine::buildTicks( interval, stepSize, maxMinorSteps );
207
208 return tickmarks;
209}
210
211qreal QskGraduation::stepSize( double length, int numSteps )
212{
213 if ( numSteps <= 0 )
214 return 0.0;
215
216 const auto v = length / numSteps;
217 if ( qFuzzyIsNull( v ) )
218 return 0.0;
219
220 constexpr double base = 10.0;
221
222 // the same as std::log10( std::fabs( v ) );
223 const double lx = std::log( std::fabs( v ) ) / std::log( base );
224 const double p = std::floor( lx );
225
226 const double fraction = std::pow( base, lx - p );
227
228 double stepSize = std::pow( base, p );
229 if ( v < 0 )
230 stepSize = -stepSize;
231
232 for ( const double f : { 2.0, 2.5, 5.0, 10.0 } )
233 {
234 if ( fraction <= f || qFuzzyCompare( fraction, f ) )
235 {
236 stepSize *= f;
237 break;
238 }
239 }
240
241 return stepSize;
242}