1 /**********************************************************************************************
2     Copyright (C) 2014 Oliver Eichler <oliver.eichler@gmx.de>
3 
4     This program is free software: you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation, either version 3 of the License, or
7     (at your option) any later version.
8 
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13 
14     You should have received a copy of the GNU General Public License
15     along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 
17 **********************************************************************************************/
18 
19 #include "plot/CPlotAxis.h"
20 
21 #include <QtWidgets>
22 
qLog10(qreal x)23 inline qreal qLog10(qreal x)
24 {
25     return qLn(x) / qLn(10);
26 }
27 
setLimits(qreal min,qreal max)28 void CPlotAxis::setLimits(qreal min, qreal max)
29 {
30     limitMin = min;
31     limitMax = max;
32 }
33 
34 
setMinMax(qreal givenMin,qreal givenMax)35 void CPlotAxis::setMinMax( qreal givenMin, qreal givenMax )
36 {
37     if(givenMin == givenMax)
38     {
39         if(0.0 != givenMin)
40         {
41             givenMin -= givenMin / 10.0;
42             givenMax += givenMax / 10.0;
43         }
44         else
45         {
46             givenMin -= 0.1;
47             givenMax += 0.1;
48         }
49     }
50 
51     if ( givenMin > givenMax )
52     {
53         qSwap(givenMin, givenMax);
54     }
55 
56     usedMin = givenMin;
57     usedMax = givenMax;
58 
59     calc();
60 
61     initialized = true;
62 }
63 
64 
calc()65 void CPlotAxis::calc()
66 {
67     qreal tmpAbs = qFabs(usedMax - usedMin) * ticScale;
68     qreal tmp = qLog10( tmpAbs / 10.0 );
69 
70     qreal exponent = (int) tmp;
71     qreal residue = tmp - exponent;
72 
73     qreal resSteps[] = {qLog10(0.1), qLog10(0.2), qLog10(0.5), qLog10(1.0), qLog10(2.0), qLog10(5.0), qLog10(10.0)};
74     for(const qreal& step : resSteps)
75     {
76         if(residue <= step)
77         {
78             residue = step;
79             break;
80         }
81     }
82 
83     interval = exponent + residue;
84     interval = qPow( 10, interval ) / ticScale;
85 
86     if ( autoscale )
87     {
88         usedMin = qFloor( usedMin / interval ) * interval;
89         usedMax = qCeil(  usedMax / interval ) * interval;
90     }
91 
92     int t1 = ( int )( usedMin / interval + 0.5);
93     ticStart = interval * t1;
94     if ( ticStart < usedMin )
95     {
96         ticStart += interval;
97     }
98 
99     valid = true;
100 }
101 
102 
fmtsgl(qreal val)103 const QString CPlotAxis::fmtsgl( qreal val )
104 {
105     QString f;
106     int exponent = (0. == val) ? 0 : (int) qLog10( qFabs(val) );
107 
108     //val *= ticScale;
109     if ( abs(exponent) > 5 )
110     {
111         f = "%1.2e";
112     }
113     else if ( exponent >= 0 )
114     {
115         f = "%" + QString( "%1" ).arg(exponent + 1)
116             + ( (0 == exponent) ? ".1f" : ".0f" );
117     }
118     else
119     {
120         f = "%1." + QString( "%1" ).arg(-exponent + 1) + "f";
121     }
122 
123     return f;
124 }
125 
126 
127 /**
128    Generates a sprintf style format string for a given value.
129    <pre>
130    0.001   -> "%1.4f"
131    0.01    -> "%1.3f"
132    0.1     -> "%1.2f"
133    1       -> "%1.1f"
134    10      -> "%2.1f"
135    >10000 scientific notation "%1.3e"
136    </pre>
137 
138    @param val value to calculate the string on
139 
140    @return a zero terminated format string
141 
142  */
fmtdbl(qreal val)143 const QString CPlotAxis::fmtdbl( qreal val )
144 {
145     int exponent = 0;
146 
147     qreal residue = 0;
148     val *= ticScale;
149 
150     if ( val != 0 )
151     {
152         qreal tmp = qLog10( qFabs(val) );
153         exponent = (int) tmp;
154         residue = tmp - exponent;
155     }
156 
157     QString f;
158     if ( abs(exponent) > 5 )
159     {
160         f = "%1.3e";
161     }
162     else if ( exponent >= 0 )
163     {
164         f = "%" + QString( "%1" ).arg(exponent + 1)
165             + ( ((0. == exponent) && (0 > residue)) ? ".2f" : ".1f" );
166     }
167     else
168     {
169         f = "%1." + QString( "%1" ).arg(-exponent + 2) + "f";
170     }
171     return f;
172 }
173 
174 
getScaleWidth(const QFontMetrics & m)175 int CPlotAxis::getScaleWidth( const QFontMetrics& m )
176 {
177     if(!valid)
178     {
179         return 0;
180     }
181 
182     if ( scaleWidth > 0 )
183     {
184         return scaleWidth * m.width( "X" );
185     }
186 
187     int width = 6 * m.width( "X" );
188     QString format_single_prec = ((interval * ticScale) < 1) ? fmtdbl(interval) : fmtsgl(interval);
189 
190     const tic_t* t = ticmark();
191     while (nullptr != t)
192     {
193         int tmp = m.width( QString().sprintf( format_single_prec.toLatin1().data(), t->val * ticScale) );
194         width = qMax(width, tmp);
195 
196         t = ticmark(t);
197     }
198     return width;
199 }
200 
201 
getLimits(qreal & limMin,qreal & limMax,qreal & useMin,qreal & useMax)202 void CPlotAxis::getLimits(qreal& limMin, qreal& limMax, qreal& useMin, qreal& useMax)
203 {
204     limMin = limitMin;
205     limMax = limitMax;
206     useMin = usedMin;
207     useMax = usedMax;
208 }
209 
210 
ticmark(const tic_t * t)211 const CPlotAxis::tic_t* CPlotAxis::ticmark( const tic_t* t )
212 {
213     QString format_single_prec = ((interval * ticScale) < 1) ? fmtdbl(interval) : fmtsgl(interval);
214 
215     switch ( ticType )
216     {
217     case eNoTic:
218         return nullptr;
219         break;
220 
221     case eTicMinMax:
222         if (nullptr == t)
223         {
224             tic.val = usedMin;
225             firstTic = true;
226         }
227         else if (firstTic)
228         {
229             tic.val = usedMax;
230             firstTic = false;
231         }
232         else
233         {
234             return nullptr;
235         }
236         break;
237 
238     case eTicNorm:
239         if (0. == interval)
240         {
241             //qWarning() << "CPlotAxis::ticmark() mode 'norm': interval == 0";
242             return nullptr;
243         }
244         if (nullptr == t)
245         {
246             tic.val = ticStart;
247         }
248         else
249         {
250             tic.val += interval;
251             if(tic.val > usedMax)
252             {
253                 return nullptr;
254             }
255         }
256         break;
257 
258     case eTicFull:
259         if (nullptr == t)
260         {
261             tic.val = usedMin;
262             firstTic = true;
263         }
264         else if (firstTic)
265         {
266             tic.val = ticStart;
267             firstTic = false;
268         }
269         else if (lastTic)
270         {
271             lastTic = false;
272             return nullptr;
273         }
274         else
275         {
276             tic.val += interval;
277             if(tic.val > usedMax)
278             {
279                 tic.val = usedMax;
280                 lastTic = true;
281             }
282         }
283         break;
284     }
285 
286     tic.lbl.sprintf( format_single_prec.toLatin1(), tic.val * ticScale );
287 
288     return &tic;
289 }
290 
291 
setScale(const unsigned int pts)292 void CPlotAxis::setScale( const unsigned int pts )
293 {
294     //if ( !initialized )
295     //qWarning( "you try to set the scale before defining the min & max value. not very sensible." );
296     points = pts;
297     scale = pts / ( usedMax - usedMin );
298 }
299 
300 
resetZoom()301 void CPlotAxis::resetZoom()
302 {
303     setMinMax(limitMin, limitMax);
304 }
305 
306 
zoom(bool in,int point)307 void CPlotAxis::zoom(bool in, int point)
308 {
309     qreal factor = in ? (1 / 1.1) : 1.1;
310 
311     qreal p = pt2val(point);
312     qreal min = (p - usedMin) * (1 - factor) + usedMin;
313     qreal d = min - usedMin * factor;
314     qreal max = usedMax * factor + d;
315 
316     if(qRound(max - min) <= qRound(limitMax - limitMin))
317     {
318         setMinMax(min, max);
319         move(0);
320     }
321 }
322 
323 
move(int delta_pt)324 void CPlotAxis::move(int delta_pt)
325 {
326     qreal delta_val = pt2val(delta_pt) - pt2val(0);
327     bool f = !(usedMax - usedMin < limitMax - limitMin);
328     if (f ^ (usedMin + delta_val < limitMin))
329     {
330         delta_val = (limitMin - usedMin);
331     }
332     if (f ^ (usedMax + delta_val > limitMax))
333     {
334         delta_val = (limitMax - usedMax);
335     }
336     setMinMax(usedMin + delta_val, usedMax + delta_val);
337 }
338