SDRAngel  4.11.5
Developer docs for <a href="https://github.com/f4exb/sdrangel">SDRangel<\a>, an Open Source Qt5 / OpenGL 3.0+ SDR and signal analyzer frontend to various hardware.
scaleengine.cpp
Go to the documentation of this file.
1 // Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
3 // written by Christian Daniel //
4 // //
5 // This program is free software; you can redistribute it and/or modify //
6 // it under the terms of the GNU General Public License as published by //
7 // the Free Software Foundation as version 3 of the License, or //
8 // (at your option) any later version. //
9 // //
10 // This program is distributed in the hope that it will be useful, //
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 // GNU General Public License V3 for more details. //
14 // //
15 // You should have received a copy of the GNU General Public License //
16 // along with this program. If not, see <http://www.gnu.org/licenses/>. //
18 
19 #include <math.h>
20 #include <QFontMetrics>
21 #include <QDataStream>
22 #include "gui/scaleengine.h"
23 
24 /*
25 static double trunc(double d)
26 {
27  return (d > 0) ? floor(d) : ceil(d);
28 }
29 */
30 
31 QString ScaleEngine::formatTick(double value, int decimalPlaces)
32 {
34  {
36  return QString("%1").arg(m_makeOpposite ? -value : value, 0, 'e', m_fixedDecimalPlaces);
37  } else {
38  return QString("%1").arg(m_makeOpposite ? -value : value, 0, 'f', decimalPlaces);
39  }
40  }
41  else
42  {
43  if (m_scale < 1.0f) { // sub second prints just as is
44  return QString("%1").arg(m_makeOpposite ? -value : value, 0, 'f', decimalPlaces);
45  }
46 
47  QString str;
48  double actual = value * m_scale; // this is the actual value in seconds
49  double orig = fabs(actual);
50  double tmp;
51 
52  if(orig >= 86400.0) {
53  tmp = floor(actual / 86400.0);
54  str = QString("%1.").arg(tmp, 0, 'f', 0);
55  actual -= tmp * 86400.0;
56  if(actual < 0.0)
57  actual *= -1.0;
58  }
59 
60  if(orig >= 3600.0) {
61  tmp = floor(actual / 3600.0);
62  str += QString("%1:").arg(tmp, 2, 'f', 0, QChar('0'));
63  actual -= tmp * 3600.0;
64  if(actual < 0.0)
65  actual *= -1.0;
66  }
67 
68  if(orig >= 60.0) {
69  tmp = floor(actual / 60.0);
70  str += QString("%1:").arg(tmp, 2, 'f', 0, QChar('0'));
71  actual -= tmp * 60.0;
72  if(actual < 0.0)
73  actual *= -1.0;
74  }
75 
76  tmp = m_makeOpposite ? -actual : actual;
77  str += QString("%1").arg(tmp, 2, 'f', decimalPlaces, QChar('0'));
78 
79  return str;
80  }
81 }
82 
84 {
85  QFontMetricsF fontMetrics(m_font);
86 
87  if(m_orientation == Qt::Vertical) {
88  m_charSize = fontMetrics.height();
89  } else {
90  QString str("012345679.,-");
91  int i;
92  float size;
93  float max = 0.0f;
94  for(i = 0; i < str.length(); i++) {
95  size = fontMetrics.width(QString(str[i]));
96  if(size > max)
97  max = size;
98  }
99  m_charSize = max;
100  }
101 }
102 
104 {
105  double median, range, freqBase;
106 
107  median = ((m_rangeMax - m_rangeMin) / 2.0) + m_rangeMin;
108  range = (m_rangeMax - m_rangeMin);
109  freqBase = (median == 0 ? range : median);
110  m_scale = 1.0;
111 
112  switch(m_physicalUnit) {
113  case Unit::None:
114  case Unit::Scientific:
115  m_unitStr.clear();
116  break;
117 
118  case Unit::Frequency:
119  if(freqBase < 1000.0) {
120  m_unitStr = QObject::tr("Hz");
121  } else if(freqBase < 1000000.0) {
122  m_unitStr = QObject::tr("kHz");
123  m_scale = 1000.0;
124  } else if(freqBase < 1000000000.0) {
125  m_unitStr = QObject::tr("MHz");
126  m_scale = 1000000.0;
127  } else if(freqBase < 1000000000000.0){
128  m_unitStr = QObject::tr("GHz");
129  m_scale = 1000000000.0;
130  } else {
131  m_unitStr = QObject::tr("THz");
132  m_scale = 1000000000000.0;
133  }
134  break;
135 
136  case Unit::Information:
137  if(median < 1024.0) {
138  m_unitStr = QObject::tr("Bytes");
139  } else if(median < 1048576.0) {
140  m_unitStr = QObject::tr("KiBytes");
141  m_scale = 1024.0;
142  } else if(median < 1073741824.0) {
143  m_unitStr = QObject::tr("MiBytes");
144  m_scale = 1048576.0;
145  } else if(median < 1099511627776.0) {
146  m_unitStr = QObject::tr("GiBytes");
147  m_scale = 1073741824.0;
148  } else if(median < 1125899906842624.0) {
149  m_unitStr = QObject::tr("TiBytes");
150  m_scale = 1099511627776.0;
151  } else {
152  m_unitStr = QObject::tr("PiBytes");
153  m_scale = 1125899906842624.0;
154  }
155  break;
156 
157  case Unit::Percent:
158  m_unitStr = QString("%");
159  break;
160 
161  case Unit::Decibel:
162  m_unitStr = QString("dB");
163  break;
164 
166  m_unitStr = QString("dBm");
167  break;
168 
170  m_unitStr = QString("dBµV");
171  break;
172 
173  case Unit::AngleDegrees:
174  m_unitStr = QString("°");
175  break;
176 
177  case Unit::Time:
178  case Unit::TimeHMS:
179  if (median < 0.001) {
180  m_unitStr = QString("µs");
181  m_scale = 0.000001;
182  } else if (median < 1.0) {
183  m_unitStr = QString("ms");
184  m_scale = 0.001;
185  } else if (median < 1000.0) {
186  m_unitStr = QString("s");
187  } else {
188  m_unitStr = QString("ks");
189  m_scale = 1000.0;
190  }
191  break;
192 
193  case Unit::Volt:
194  if (median < 1e-9) {
195  m_unitStr = QString("pV");
196  m_scale = 1e-12;
197  } else if (median < 1e-6) {
198  m_unitStr = QString("nV");
199  m_scale = 1e-9;
200  } else if (median < 1e-3) {
201  m_unitStr = QString("µV");
202  m_scale = 1e-6;
203  } else if (median < 1.0) {
204  m_unitStr = QString("mV");
205  m_scale = 1e-3;
206  } else {
207  m_unitStr = QString("V");
208  }
209  break;
210  }
211 }
212 
213 double ScaleEngine::calcMajorTickUnits(double distance, int* retDecimalPlaces)
214 {
215  double sign;
216  double log10x;
217  double exponent;
218  double base;
219  int decimalPlaces;
220 
221  if(distance == 0.0)
222  return 0.0;
223 
224  sign = (distance > 0.0) ? 1.0 : -1.0;
225  log10x = log10(fabs(distance));
226  exponent = floor(log10x);
227  base = pow(10.0, log10x - exponent);
228  decimalPlaces = (int)(-exponent);
229 /*
230  if((m_physicalUnit == Unit::Time) && (distance >= 1.0)) {
231  if(retDecimalPlaces != NULL)
232  *retDecimalPlaces = 0;
233  if(distance < 1.0)
234  return 1.0;
235  else if(distance < 5.0)
236  return 5.0;
237  else if(distance < 10.0)
238  return 10.0;
239  else if(distance < 15.0)
240  return 15.0;
241  else if(distance < 30.0)
242  return 30.0;
243  else if(distance < 60.0)
244  return 60.0;
245  else if(distance < 5.0 * 60.0)
246  return 5.0 * 60.0;
247  else if(distance < 10.0 * 60.0)
248  return 10.0 * 60.0;
249  else if(distance < 15.0 * 60.0)
250  return 15.0 * 60.0;
251  else if(distance < 30.0 * 60.0)
252  return 30.0 * 60.0;
253  else if(distance < 3600.0)
254  return 3600.0;
255  else if(distance < 2.0 * 3600.0)
256  return 2.0 * 3600.0;
257  else if(distance < 3.0 * 3600.0)
258  return 3.0 * 3600.0;
259  else if(distance < 6.0 * 3600.0)
260  return 6.0 * 3600.0;
261  else if(distance < 12.0 * 3600.0)
262  return 12.0 * 3600.0;
263  else if(distance < 86000.0)
264  return 86000.0;
265  else if(distance < 2.0 * 86000.0)
266  return 2.0 * 86000.0;
267  else if(distance < 7.0 * 86000.0)
268  return 7.0 * 86000.0;
269  else if(distance < 10.0 * 86000.0)
270  return 10.0 * 86000.0;
271  else if(distance < 30.0 * 86000.0)
272  return 30.0 * 86000.0;
273  else return 90.0 * 86000.0;
274  } */
275 
276  if(base <= 1.0) {
277  base = 1.0;
278  } else if(base <= 2.0) {
279  base = 2.0;
280  } else if(base <= 2.5) {
281  base = 2.5;
282  if(decimalPlaces >= 0)
283  decimalPlaces++;
284  } else if(base <= 5.0) {
285  base = 5.0;
286  } else {
287  base = 10.0;
288  }
289 
290  if(retDecimalPlaces != 0) {
291  if(decimalPlaces < 0)
292  decimalPlaces = 0;
293  *retDecimalPlaces = decimalPlaces;
294  }
295 
296  return sign * base * pow(10.0, exponent);
297 }
298 
299 int ScaleEngine::calcTickTextSize(double distance)
300 {
301  int tmp;
302  int tickLen;
303  int decimalPlaces;
304 
305  tickLen = 1;
306  tmp = formatTick(m_rangeMin / m_scale, 0).length();
307  if(tmp > tickLen)
308  tickLen = tmp;
309  tmp = formatTick(m_rangeMax / m_scale, 0).length();
310  if(tmp > tickLen)
311  tickLen = tmp;
312 
313  calcMajorTickUnits(distance, &decimalPlaces);
314 
315  return tickLen + decimalPlaces + 1;
316 }
317 
319 {
320  Tick tick;
321  QFontMetricsF fontMetrics(m_font);
322 
323  m_tickList.clear();
324  tick.major = true;
325 
328  tick.textSize = fontMetrics.boundingRect(tick.text).width();
329  if(m_orientation == Qt::Vertical)
330  tick.textPos = tick.pos - fontMetrics.ascent() / 2;
331  else tick.textPos = tick.pos - fontMetrics.boundingRect(tick.text).width() / 2;
332  m_tickList.append(tick);
333 
336  tick.textSize = fontMetrics.boundingRect(tick.text).width();
337  if(m_orientation == Qt::Vertical)
338  tick.textPos = tick.pos - fontMetrics.ascent() / 2;
339  else tick.textPos = tick.pos - fontMetrics.boundingRect(tick.text).width() / 2;
340  m_tickList.append(tick);
341 }
342 
344 {
345  float majorTickSize;
346  double rangeMinScaled;
347  double rangeMaxScaled;
348  int maxNumMajorTicks;
349  int numMajorTicks;
350  int step;
351  int skip;
352  double value;
353  double value2;
354  int i;
355  int j;
356  Tick tick;
357  float pos;
358  QString str;
359  QFontMetricsF fontMetrics(m_font);
360  float endPos;
361  float lastEndPos;
362  bool done;
363 
364  if(!m_recalc)
365  return;
366  m_recalc = false;
367 
368  m_tickList.clear();
369 
370  calcScaleFactor();
371  rangeMinScaled = m_rangeMin / m_scale;
372  rangeMaxScaled = m_rangeMax / m_scale;
373 
374  if(m_orientation == Qt::Vertical)
375  {
376  maxNumMajorTicks = (int)(m_size / (fontMetrics.lineSpacing() * 1.3f));
377  m_majorTickValueDistance = calcMajorTickUnits((rangeMaxScaled - rangeMinScaled) / maxNumMajorTicks, &m_decimalPlaces);
378  }
379  else
380  {
381  majorTickSize = (calcTickTextSize((rangeMaxScaled - rangeMinScaled) / 20) + 2) * m_charSize;
382 
383  if(majorTickSize != 0.0) {
384  maxNumMajorTicks = (int)(m_size / majorTickSize);
385  } else {
386  maxNumMajorTicks = 20;
387  }
388 
389  m_majorTickValueDistance = calcMajorTickUnits((rangeMaxScaled - rangeMinScaled) / maxNumMajorTicks, &m_decimalPlaces);
390  }
391 
392  numMajorTicks = (int)((rangeMaxScaled - rangeMinScaled) / m_majorTickValueDistance);
393 
394  if(numMajorTicks == 0) {
395  forceTwoTicks();
396  return;
397  }
398 
399  if(maxNumMajorTicks > 0)
400  m_numMinorTicks = (int)(m_size / (maxNumMajorTicks * fontMetrics.height()));
401  else m_numMinorTicks = 0;
402  if(m_numMinorTicks < 1)
403  m_numMinorTicks = 0;
404  else if(m_numMinorTicks < 2)
405  m_numMinorTicks = 1;
406  else if(m_numMinorTicks < 5)
407  m_numMinorTicks = 2;
408  else if(m_numMinorTicks < 10)
409  m_numMinorTicks = 5;
410  else m_numMinorTicks = 10;
411 
413 
414  skip = 0;
415 
416  if(rangeMinScaled == rangeMaxScaled)
417  return;
418 
419  while(true) {
420  m_tickList.clear();
421 
422  step = 0;
423  lastEndPos = -100000000;
424  done = true;
425 
426  for(i = 0; true; i++) {
427  value = majorTickValue(i);
428 
429  for(j = 1; j < m_numMinorTicks; j++) {
430  value2 = value + minorTickValue(j);
431  if(value2 < rangeMinScaled)
432  continue;
433  if(value2 > rangeMaxScaled)
434  break;
435  pos = getPosFromValue((value + minorTickValue(j)) * m_scale);
436  if((pos >= 0) && (pos < m_size)) {
437  tick.pos = pos;
438  tick.major = false;
439  tick.textPos = -1;
440  tick.textSize = -1;
441  tick.text.clear();
442  }
443  m_tickList.append(tick);
444  }
445 
446  pos = getPosFromValue(value * m_scale);
447  if(pos < 0.0)
448  continue;
449  if(pos >= m_size)
450  break;
451 
452  tick.pos = pos;
453  tick.major = true;
454  tick.textPos = -1;
455  tick.textSize = -1;
456  tick.text.clear();
457 
458  if(step % (skip + 1) != 0) {
459  m_tickList.append(tick);
460  step++;
461  continue;
462  }
463  step++;
464 
465  str = formatTick(value, m_decimalPlaces);
466  tick.text = str;
467  tick.textSize = fontMetrics.boundingRect(tick.text).width();
468  if(m_orientation == Qt::Vertical) {
469  tick.textPos = pos - fontMetrics.ascent() / 2;
470  endPos = tick.textPos + fontMetrics.ascent();
471  } else {
472  tick.textPos = pos - fontMetrics.boundingRect(tick.text).width() / 2;
473  endPos = tick.textPos + tick.textSize;
474  }
475 
476  if(lastEndPos >= tick.textPos) {
477  done = false;
478  break;
479  } else {
480  lastEndPos = endPos;
481  }
482 
483  m_tickList.append(tick);
484  }
485  if(done)
486  break;
487  skip++;
488  }
489 
490  // make sure we have at least two major ticks with numbers
491  numMajorTicks = 0;
492  for(i = 0; i < m_tickList.count(); i++) {
493  tick = m_tickList.at(i);
494  if(tick.major)
495  numMajorTicks++;
496  }
497  if(numMajorTicks < 2)
498  forceTwoTicks();
499 }
500 
502 {
504 }
505 
507 {
508  if(m_numMinorTicks < 1)
509  return 0.0;
510  return (m_majorTickValueDistance * tick) / m_numMinorTicks;
511 }
512 
514  m_orientation(Qt::Horizontal),
515  m_charSize(8),
516  m_size(1.0f),
518  m_rangeMin(-1.0),
519  m_rangeMax(1.0),
520  m_recalc(true),
521  m_scale(1.0f),
524  m_numMinorTicks(1),
525  m_decimalPlaces(1),
527  m_makeOpposite(false)
528 {
529 }
530 
531 void ScaleEngine::setOrientation(Qt::Orientation orientation)
532 {
533  m_orientation = orientation;
534  m_recalc = true;
535 }
536 
537 void ScaleEngine::setFont(const QFont& font)
538 {
539  m_font = font;
540  m_recalc = true;
541  calcCharSize();
542 }
543 
544 void ScaleEngine::setSize(float size)
545 {
546  if(size > 0.0f) {
547  m_size = size;
548  } else {
549  m_size = 1.0f;
550  }
551  m_recalc = true;
552 }
553 
554 void ScaleEngine::setRange(Unit::Physical physicalUnit, float rangeMin, float rangeMax)
555 {
556  double tmpRangeMin;
557  double tmpRangeMax;
558 /*
559  if(rangeMin < rangeMax) {
560  tmpRangeMin = rangeMin;
561  tmpRangeMax = rangeMax;
562  } else if(rangeMin > rangeMax) {
563  tmpRangeMin = rangeMax;
564  tmpRangeMax = rangeMin;
565  } else {
566  tmpRangeMin = rangeMin * 0.99;
567  tmpRangeMax = rangeMin * 1.01 + 0.01;
568  }
569 */
570  tmpRangeMin = rangeMin;
571  tmpRangeMax = rangeMax;
572 
573  if((tmpRangeMin != m_rangeMin) || (tmpRangeMax != m_rangeMax) || (m_physicalUnit != physicalUnit)) {
574  m_physicalUnit = physicalUnit;
575  m_rangeMin = tmpRangeMin;
576  m_rangeMax = tmpRangeMax;
577  m_recalc = true;
578  }
579 }
580 
581 float ScaleEngine::getPosFromValue(double value)
582 {
583  return ((value - m_rangeMin) / (m_rangeMax - m_rangeMin)) * (m_size - 1.0);
584 }
585 
587 {
588  return ((pos * (m_rangeMax - m_rangeMin)) / (m_size - 1.0)) + m_rangeMin;
589 }
590 
592 {
593  reCalc();
594  return m_tickList;
595 }
596 
598 {
599  if(m_unitStr.length() > 0)
600  return QString("%1 %2").arg(formatTick(m_rangeMin / m_scale, m_decimalPlaces)).arg(m_unitStr);
601  else return QString("%1").arg(formatTick(m_rangeMin / m_scale, m_decimalPlaces));
602 }
603 
605 {
606  if(m_unitStr.length() > 0)
607  return QString("%1 %2").arg(formatTick(m_rangeMax / m_scale, m_decimalPlaces)).arg(m_unitStr);
608  else return QString("%1").arg(formatTick(m_rangeMax / m_scale, m_decimalPlaces));
609 }
610 
612 {
613  float max;
614  float len;
615  int i;
616 
617  reCalc();
618  max = 0.0f;
619  for(i = 0; i < m_tickList.count(); i++) {
620  len = m_tickList[i].textSize;
621  if(len > max)
622  max = len;
623  }
624  return max;
625 }
float m_size
Definition: scaleengine.h:46
Qt::Orientation m_orientation
Definition: scaleengine.h:41
void setSize(float size)
double minorTickValue(int tick)
void forceTwoTicks()
float getPosFromValue(double value)
QString formatTick(double value, int decimalPlaces)
Definition: scaleengine.cpp:31
int m_fixedDecimalPlaces
Definition: scaleengine.h:60
QString getRangeMinStr()
QString m_unitStr
Definition: scaleengine.h:54
double m_scale
Definition: scaleengine.h:53
float m_rangeMax
Definition: scaleengine.h:49
bool m_makeOpposite
Definition: scaleengine.h:61
Fixed< IntType, IntBits > floor(Fixed< IntType, IntBits > const &x)
Definition: fixed.h:2301
double majorTickValue(int tick)
float getValueFromPos(double pos)
double m_majorTickValueDistance
Definition: scaleengine.h:56
float m_charSize
Definition: scaleengine.h:43
void calcScaleFactor()
int m_decimalPlaces
Definition: scaleengine.h:59
int m_numMinorTicks
Definition: scaleengine.h:58
int32_t i
Definition: decimators.h:244
void setFont(const QFont &font)
QString getRangeMaxStr()
double m_firstMajorTickValue
Definition: scaleengine.h:57
float m_rangeMin
Definition: scaleengine.h:48
QFont m_font
Definition: scaleengine.h:42
const TickList & getTickList()
QList< Tick > TickList
Definition: scaleengine.h:37
void calcCharSize()
Definition: scaleengine.cpp:83
void setRange(Unit::Physical physicalUnit, float rangeMin, float rangeMax)
Unit::Physical m_physicalUnit
Definition: scaleengine.h:47
double calcMajorTickUnits(double distance, int *retDecimalPlaces)
float getScaleWidth()
int calcTickTextSize(double distance)
void setOrientation(Qt::Orientation orientation)
TickList m_tickList
Definition: scaleengine.h:55
T max(const T &x, const T &y)
Definition: framework.h:446
bool m_recalc
Definition: scaleengine.h:52