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