Qwt User's Guide  6.2.0
qwt_date_scale_engine.cpp
1 /******************************************************************************
2  * Qwt Widget Library
3  * Copyright (C) 1997 Josef Wilgen
4  * Copyright (C) 2002 Uwe Rathmann
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the Qwt License, Version 1.0
8  *****************************************************************************/
9 
10 #include "qwt_date_scale_engine.h"
11 #include "qwt_math.h"
12 #include "qwt_interval.h"
13 
14 #include <qdatetime.h>
15 
16 #include <limits>
17 
18 static inline double qwtMsecsForType( int type )
19 {
20  static const double msecs[] =
21  {
22  1.0,
23  1000.0,
24  60.0 * 1000.0,
25  3600.0 * 1000.0,
26  24.0 * 3600.0 * 1000.0,
27  7.0 * 24.0 * 3600.0 * 1000.0,
28  30.0 * 24.0 * 3600.0 * 1000.0,
29  365.0 * 24.0 * 3600.0 * 1000.0,
30  };
31 
32  if ( type < 0 || type >= static_cast< int >( sizeof( msecs ) / sizeof( msecs[0] ) ) )
33  return 1.0;
34 
35  return msecs[ type ];
36 }
37 
38 static inline int qwtAlignValue(
39  double value, double stepSize, bool up )
40 {
41  double d = value / stepSize;
42  d = up ? std::ceil( d ) : std::floor( d );
43 
44  return static_cast< int >( d * stepSize );
45 }
46 
47 static double qwtIntervalWidth( const QDateTime& minDate,
48  const QDateTime& maxDate, QwtDate::IntervalType intervalType )
49 {
50  switch( intervalType )
51  {
53  {
54  return minDate.msecsTo( maxDate );
55  }
56  case QwtDate::Second:
57  {
58  return minDate.secsTo( maxDate );
59  }
60  case QwtDate::Minute:
61  {
62  const double secsTo = minDate.secsTo( maxDate );
63  return std::floor( secsTo / 60 );
64  }
65  case QwtDate::Hour:
66  {
67  const double secsTo = minDate.secsTo( maxDate );
68  return std::floor( secsTo / 3600 );
69  }
70  case QwtDate::Day:
71  {
72  return minDate.daysTo( maxDate );
73  }
74  case QwtDate::Week:
75  {
76  return std::floor( minDate.daysTo( maxDate ) / 7.0 );
77  }
78  case QwtDate::Month:
79  {
80  const double years =
81  double( maxDate.date().year() ) - minDate.date().year();
82 
83  int months = maxDate.date().month() - minDate.date().month();
84  if ( maxDate.date().day() < minDate.date().day() )
85  months--;
86 
87  return years * 12 + months;
88  }
89  case QwtDate::Year:
90  {
91  double years =
92  double( maxDate.date().year() ) - minDate.date().year();
93 
94  if ( maxDate.date().month() < minDate.date().month() )
95  years -= 1.0;
96 
97  return years;
98  }
99  }
100 
101  return 0.0;
102 }
103 
104 static double qwtRoundedIntervalWidth(
105  const QDateTime& minDate, const QDateTime& maxDate,
106  QwtDate::IntervalType intervalType )
107 {
108  const QDateTime minD = QwtDate::floor( minDate, intervalType );
109  const QDateTime maxD = QwtDate::ceil( maxDate, intervalType );
110 
111  return qwtIntervalWidth( minD, maxD, intervalType );
112 }
113 
114 static inline int qwtStepCount( int intervalSize, int maxSteps,
115  const int limits[], size_t numLimits )
116 {
117  for ( uint i = 0; i < numLimits; i++ )
118  {
119  const int numSteps = intervalSize / limits[ i ];
120 
121  if ( numSteps > 1 && numSteps <= maxSteps &&
122  numSteps * limits[ i ] == intervalSize )
123  {
124  return numSteps;
125  }
126  }
127 
128  return 0;
129 }
130 
131 static int qwtStepSize( int intervalSize, int maxSteps, uint base )
132 {
133  if ( maxSteps <= 0 )
134  return 0;
135 
136  if ( maxSteps > 2 )
137  {
138  for ( int numSteps = maxSteps; numSteps > 1; numSteps-- )
139  {
140  const double stepSize = double( intervalSize ) / numSteps;
141 
142  const double p = std::floor( std::log( stepSize ) / std::log( double( base ) ) );
143  const double fraction = std::pow( base, p );
144 
145  for ( uint n = base; n >= 1; n /= 2 )
146  {
147  if ( qFuzzyCompare( stepSize, n * fraction ) )
148  return qRound( stepSize );
149 
150  if ( n == 3 && ( base % 2 ) == 0 )
151  {
152  if ( qFuzzyCompare( stepSize, 2 * fraction ) )
153  return qRound( stepSize );
154  }
155  }
156  }
157  }
158 
159  return 0;
160 }
161 
162 static int qwtDivideInterval( double intervalSize, int numSteps,
163  const int limits[], size_t numLimits )
164 {
165  const int v = qwtCeil( intervalSize / double( numSteps ) );
166 
167  for ( uint i = 0; i < numLimits - 1; i++ )
168  {
169  if ( v <= limits[i] )
170  return limits[i];
171  }
172 
173  return limits[ numLimits - 1 ];
174 }
175 
176 static double qwtDivideScale( double intervalSize, int numSteps,
177  QwtDate::IntervalType intervalType )
178 {
179  if ( intervalType != QwtDate::Day )
180  {
181  if ( ( intervalSize > numSteps ) &&
182  ( intervalSize <= 2 * numSteps ) )
183  {
184  return 2.0;
185  }
186  }
187 
188  double stepSize;
189 
190  switch( intervalType )
191  {
192  case QwtDate::Second:
193  case QwtDate::Minute:
194  {
195  static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 };
196 
197  stepSize = qwtDivideInterval( intervalSize, numSteps,
198  limits, sizeof( limits ) / sizeof( int ) );
199 
200  break;
201  }
202  case QwtDate::Hour:
203  {
204  static int limits[] = { 1, 2, 3, 4, 6, 12, 24 };
205 
206  stepSize = qwtDivideInterval( intervalSize, numSteps,
207  limits, sizeof( limits ) / sizeof( int ) );
208 
209  break;
210  }
211  case QwtDate::Day:
212  {
213  const double v = intervalSize / double( numSteps );
214  if ( v <= 5.0 )
215  stepSize = std::ceil( v );
216  else
217  stepSize = std::ceil( v / 7 ) * 7;
218 
219  break;
220  }
221  case QwtDate::Week:
222  {
223  static int limits[] = { 1, 2, 4, 8, 12, 26, 52 };
224 
225  stepSize = qwtDivideInterval( intervalSize, numSteps,
226  limits, sizeof( limits ) / sizeof( int ) );
227 
228  break;
229  }
230  case QwtDate::Month:
231  {
232  static int limits[] = { 1, 2, 3, 4, 6, 12 };
233 
234  stepSize = qwtDivideInterval( intervalSize, numSteps,
235  limits, sizeof( limits ) / sizeof( int ) );
236 
237  break;
238  }
239  case QwtDate::Year:
241  default:
242  {
244  intervalSize, numSteps, 10 );
245  }
246  }
247 
248  return stepSize;
249 }
250 
251 static double qwtDivideMajorStep( double stepSize, int maxMinSteps,
252  QwtDate::IntervalType intervalType )
253 {
254  double minStepSize = 0.0;
255 
256  switch( intervalType )
257  {
258  case QwtDate::Second:
259  {
260  minStepSize = qwtStepSize( stepSize, maxMinSteps, 10 );
261  if ( minStepSize == 0.0 )
262  minStepSize = 0.5 * stepSize;
263 
264  break;
265  }
266  case QwtDate::Minute:
267  {
268  static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 };
269 
270  int numSteps;
271 
272  if ( stepSize > maxMinSteps )
273  {
274  numSteps = qwtStepCount( stepSize, maxMinSteps,
275  limits, sizeof( limits ) / sizeof( int ) );
276 
277  }
278  else
279  {
280  numSteps = qwtStepCount( stepSize * 60, maxMinSteps,
281  limits, sizeof( limits ) / sizeof( int ) );
282  }
283 
284  if ( numSteps > 0 )
285  minStepSize = stepSize / numSteps;
286 
287  break;
288  }
289  case QwtDate::Hour:
290  {
291  int numSteps = 0;
292 
293  if ( stepSize > maxMinSteps )
294  {
295  static int limits[] = { 1, 2, 3, 4, 6, 12, 24, 48, 72 };
296 
297  numSteps = qwtStepCount( stepSize, maxMinSteps,
298  limits, sizeof( limits ) / sizeof( int ) );
299  }
300  else
301  {
302  static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 };
303 
304  numSteps = qwtStepCount( stepSize * 60, maxMinSteps,
305  limits, sizeof( limits ) / sizeof( int ) );
306  }
307 
308  if ( numSteps > 0 )
309  minStepSize = stepSize / numSteps;
310 
311  break;
312  }
313  case QwtDate::Day:
314  {
315  int numSteps = 0;
316 
317  if ( stepSize > maxMinSteps )
318  {
319  static int limits[] = { 1, 2, 3, 7, 14, 28 };
320 
321  numSteps = qwtStepCount( stepSize, maxMinSteps,
322  limits, sizeof( limits ) / sizeof( int ) );
323  }
324  else
325  {
326  static int limits[] = { 1, 2, 3, 4, 6, 12, 24, 48, 72 };
327 
328  numSteps = qwtStepCount( stepSize * 24, maxMinSteps,
329  limits, sizeof( limits ) / sizeof( int ) );
330  }
331 
332  if ( numSteps > 0 )
333  minStepSize = stepSize / numSteps;
334 
335  break;
336  }
337  case QwtDate::Week:
338  {
339  const int daysInStep = stepSize * 7;
340 
341  if ( maxMinSteps >= daysInStep )
342  {
343  // we want to have one tick per day
344  minStepSize = 1.0 / 7.0;
345  }
346  else
347  {
348  // when the stepSize is more than a week we want to
349  // have a tick for each week
350 
351  const int stepSizeInWeeks = stepSize;
352 
353  if ( stepSizeInWeeks <= maxMinSteps )
354  {
355  minStepSize = 1;
356  }
357  else
358  {
360  stepSizeInWeeks, maxMinSteps, 10 );
361  }
362  }
363  break;
364  }
365  case QwtDate::Month:
366  {
367  // fractions of months doesn't make any sense
368 
369  if ( stepSize < maxMinSteps )
370  maxMinSteps = static_cast< int >( stepSize );
371 
372  static int limits[] = { 1, 2, 3, 4, 6, 12 };
373 
374  int numSteps = qwtStepCount( stepSize, maxMinSteps,
375  limits, sizeof( limits ) / sizeof( int ) );
376 
377  if ( numSteps > 0 )
378  minStepSize = stepSize / numSteps;
379 
380  break;
381  }
382  case QwtDate::Year:
383  {
384  if ( stepSize >= maxMinSteps )
385  {
387  stepSize, maxMinSteps, 10 );
388  }
389  else
390  {
391  // something in months
392 
393  static int limits[] = { 1, 2, 3, 4, 6, 12 };
394 
395  int numSteps = qwtStepCount( 12 * stepSize, maxMinSteps,
396  limits, sizeof( limits ) / sizeof( int ) );
397 
398  if ( numSteps > 0 )
399  minStepSize = stepSize / numSteps;
400  }
401 
402  break;
403  }
404  default:
405  break;
406  }
407 
408  if ( intervalType != QwtDate::Month
409  && minStepSize == 0.0 )
410  {
411  minStepSize = 0.5 * stepSize;
412  }
413 
414  return minStepSize;
415 }
416 
417 static QList< double > qwtDstTicks( const QDateTime& dateTime,
418  int secondsMajor, int secondsMinor )
419 {
420  if ( secondsMinor <= 0 )
421  QList< double >();
422 
423  QDateTime minDate = dateTime.addSecs( -secondsMajor );
424  minDate = QwtDate::floor( minDate, QwtDate::Hour );
425 
426  const double utcOffset = QwtDate::utcOffset( dateTime );
427 
428  // find the hours where daylight saving time happens
429 
430  double dstMin = QwtDate::toDouble( minDate );
431  while ( minDate < dateTime &&
432  QwtDate::utcOffset( minDate ) != utcOffset )
433  {
434  minDate = minDate.addSecs( 3600 );
435  dstMin += 3600 * 1000.0;
436  }
437 
438  QList< double > ticks;
439  ticks.reserve( 3600 / secondsMinor);
440 
441  for ( int i = 0; i < 3600; i += secondsMinor )
442  ticks += dstMin + i * 1000.0;
443 
444  return ticks;
445 }
446 
447 static QwtScaleDiv qwtDivideToSeconds(
448  const QDateTime& minDate, const QDateTime& maxDate,
449  double stepSize, int maxMinSteps,
450  QwtDate::IntervalType intervalType )
451 {
452  // calculate the min step size
453  double minStepSize = 0;
454 
455  if ( maxMinSteps > 1 )
456  {
457  minStepSize = qwtDivideMajorStep( stepSize,
458  maxMinSteps, intervalType );
459  }
460 
461  bool daylightSaving = false;
462  if ( minDate.timeSpec() == Qt::LocalTime )
463  {
464  daylightSaving = intervalType > QwtDate::Hour;
465  if ( intervalType == QwtDate::Hour )
466  {
467  daylightSaving = stepSize > 1;
468  }
469  }
470 
471  const double s = qwtMsecsForType( intervalType ) / 1000;
472  const int secondsMajor = static_cast< int >( stepSize * s );
473  const double secondsMinor = minStepSize * s;
474 
475  // UTC excludes daylight savings. So from the difference
476  // of a date and its UTC counterpart we can find out
477  // the daylight saving hours
478 
479  const double utcOffset = QwtDate::utcOffset( minDate );
480  double dstOff = 0;
481 
482  QList< double > majorTicks;
483  QList< double > mediumTicks;
484  QList< double > minorTicks;
485 
486  for ( QDateTime dt = minDate; dt <= maxDate;
487  dt = dt.addSecs( secondsMajor ) )
488  {
489  if ( !dt.isValid() )
490  break;
491 
492  double majorValue = QwtDate::toDouble( dt );
493 
494  if ( daylightSaving )
495  {
496  const double offset = utcOffset - QwtDate::utcOffset( dt );
497  majorValue += offset * 1000.0;
498 
499  if ( offset > dstOff )
500  {
501  // we add some minor ticks for the DST hour,
502  // otherwise the ticks will be unaligned: 0, 2, 3, 5 ...
503  minorTicks += qwtDstTicks(
504  dt, secondsMajor, qRound( secondsMinor ) );
505  }
506 
507  dstOff = offset;
508  }
509 
510  if ( majorTicks.isEmpty() || majorTicks.last() != majorValue )
511  majorTicks += majorValue;
512 
513  if ( secondsMinor > 0.0 )
514  {
515  const int numMinorSteps = qwtFloor( secondsMajor / secondsMinor );
516 
517  for ( int i = 1; i < numMinorSteps; i++ )
518  {
519  const QDateTime mt = dt.addMSecs(
520  qRound64( i * secondsMinor * 1000 ) );
521 
522  double minorValue = QwtDate::toDouble( mt );
523  if ( daylightSaving )
524  {
525  const double offset = utcOffset - QwtDate::utcOffset( mt );
526  minorValue += offset * 1000.0;
527  }
528 
529  if ( minorTicks.isEmpty() || minorTicks.last() != minorValue )
530  {
531  const bool isMedium = ( numMinorSteps % 2 == 0 )
532  && ( i != 1 ) && ( i == numMinorSteps / 2 );
533 
534  if ( isMedium )
535  mediumTicks += minorValue;
536  else
537  minorTicks += minorValue;
538  }
539  }
540  }
541  }
542 
543  QwtScaleDiv scaleDiv;
544 
545  scaleDiv.setInterval( QwtDate::toDouble( minDate ),
546  QwtDate::toDouble( maxDate ) );
547 
548  scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks );
549  scaleDiv.setTicks( QwtScaleDiv::MediumTick, mediumTicks );
550  scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks );
551 
552  return scaleDiv;
553 }
554 
555 static QwtScaleDiv qwtDivideToMonths(
556  QDateTime& minDate, const QDateTime& maxDate,
557  double stepSize, int maxMinSteps )
558 {
559  // months are intervals with non
560  // equidistant ( in ms ) steps: we have to build the
561  // scale division manually
562 
563  int minStepDays = 0;
564  int minStepSize = 0.0;
565 
566  if ( maxMinSteps > 1 )
567  {
568  if ( stepSize == 1 )
569  {
570  if ( maxMinSteps >= 30 )
571  minStepDays = 1;
572  else if ( maxMinSteps >= 6 )
573  minStepDays = 5;
574  else if ( maxMinSteps >= 3 )
575  minStepDays = 10;
576  else
577  minStepDays = 15;
578  }
579  else
580  {
581  minStepSize = qwtDivideMajorStep(
582  stepSize, maxMinSteps, QwtDate::Month );
583  }
584  }
585 
586  QList< double > majorTicks;
587  QList< double > mediumTicks;
588  QList< double > minorTicks;
589 
590  for ( QDateTime dt = minDate;
591  dt <= maxDate; dt = dt.addMonths( stepSize ) )
592  {
593  if ( !dt.isValid() )
594  break;
595 
596  majorTicks += QwtDate::toDouble( dt );
597 
598  if ( minStepDays > 0 )
599  {
600  for ( int days = minStepDays;
601  days < 30; days += minStepDays )
602  {
603  const double tick = QwtDate::toDouble( dt.addDays( days ) );
604 
605  if ( days == 15 && minStepDays != 15 )
606  mediumTicks += tick;
607  else
608  minorTicks += tick;
609  }
610  }
611  else if ( minStepSize > 0.0 )
612  {
613  const int numMinorSteps = qRound( stepSize / (double) minStepSize );
614 
615  for ( int i = 1; i < numMinorSteps; i++ )
616  {
617  const double minorValue =
618  QwtDate::toDouble( dt.addMonths( i * minStepSize ) );
619 
620  if ( ( numMinorSteps % 2 == 0 ) && ( i == numMinorSteps / 2 ) )
621  mediumTicks += minorValue;
622  else
623  minorTicks += minorValue;
624  }
625  }
626  }
627 
628  QwtScaleDiv scaleDiv;
629  scaleDiv.setInterval( QwtDate::toDouble( minDate ),
630  QwtDate::toDouble( maxDate ) );
631 
632  scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks );
633  scaleDiv.setTicks( QwtScaleDiv::MediumTick, mediumTicks );
634  scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks );
635 
636  return scaleDiv;
637 }
638 
639 static QwtScaleDiv qwtDivideToYears(
640  const QDateTime& minDate, const QDateTime& maxDate,
641  double stepSize, int maxMinSteps )
642 {
643  QList< double > majorTicks;
644  QList< double > mediumTicks;
645  QList< double > minorTicks;
646 
647  double minStepSize = 0.0;
648 
649  if ( maxMinSteps > 1 )
650  {
651  minStepSize = qwtDivideMajorStep(
652  stepSize, maxMinSteps, QwtDate::Year );
653  }
654 
655  int numMinorSteps = 0;
656  if ( minStepSize > 0.0 )
657  numMinorSteps = qwtFloor( stepSize / minStepSize );
658 
659  bool dateBC = minDate.date().year() < -1;
660 
661  for ( QDateTime dt = minDate; dt <= maxDate;
662  dt = dt.addYears( stepSize ) )
663  {
664  if ( dateBC && dt.date().year() > 1 )
665  {
666  // there is no year 0 in the Julian calendar
667  dt = dt.addYears( -1 );
668  dateBC = false;
669  }
670 
671  if ( !dt.isValid() )
672  break;
673 
674  majorTicks += QwtDate::toDouble( dt );
675 
676  for ( int i = 1; i < numMinorSteps; i++ )
677  {
678  QDateTime tickDate;
679 
680  const double years = qRound( i * minStepSize );
681  if ( years >= std::numeric_limits< int >::max() / 12 )
682  {
683  tickDate = dt.addYears( years );
684  }
685  else
686  {
687  tickDate = dt.addMonths( qRound( years * 12 ) );
688  }
689 
690  const bool isMedium = ( numMinorSteps > 2 ) &&
691  ( numMinorSteps % 2 == 0 ) && ( i == numMinorSteps / 2 );
692 
693  const double minorValue = QwtDate::toDouble( tickDate );
694  if ( isMedium )
695  mediumTicks += minorValue;
696  else
697  minorTicks += minorValue;
698  }
699 
700  if ( QwtDate::maxDate().addYears( -stepSize ) < dt.date() )
701  {
702  break;
703  }
704  }
705 
706  QwtScaleDiv scaleDiv;
707  scaleDiv.setInterval( QwtDate::toDouble( minDate ),
708  QwtDate::toDouble( maxDate ) );
709 
710  scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks );
711  scaleDiv.setTicks( QwtScaleDiv::MediumTick, mediumTicks );
712  scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks );
713 
714  return scaleDiv;
715 }
716 
717 class QwtDateScaleEngine::PrivateData
718 {
719  public:
720  explicit PrivateData( Qt::TimeSpec spec )
721  : timeSpec( spec )
722  , utcOffset( 0 )
723  , week0Type( QwtDate::FirstThursday )
724  , maxWeeks( 4 )
725  {
726  }
727 
728  Qt::TimeSpec timeSpec;
729  int utcOffset;
731  int maxWeeks;
732 };
733 
734 
748  : QwtLinearScaleEngine( 10 )
749 {
750  m_data = new PrivateData( timeSpec );
751 }
752 
755 {
756  delete m_data;
757 }
758 
765 void QwtDateScaleEngine::setTimeSpec( Qt::TimeSpec timeSpec )
766 {
767  m_data->timeSpec = timeSpec;
768 }
769 
774 Qt::TimeSpec QwtDateScaleEngine::timeSpec() const
775 {
776  return m_data->timeSpec;
777 }
778 
790 {
791  m_data->utcOffset = seconds;
792 }
793 
802 {
803  return m_data->utcOffset;
804 }
805 
816 {
817  m_data->week0Type = week0Type;
818 }
819 
825 {
826  return m_data->week0Type;
827 }
828 
842 {
843  m_data->maxWeeks = qMax( weeks, 0 );
844 }
845 
852 {
853  return m_data->maxWeeks;
854 }
855 
866  const QDateTime& minDate, const QDateTime& maxDate,
867  int maxSteps ) const
868 {
869  const double jdMin = minDate.date().toJulianDay();
870  const double jdMax = maxDate.date().toJulianDay();
871 
872  if ( ( jdMax - jdMin ) / 365 > maxSteps )
873  return QwtDate::Year;
874 
875  const int months = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Month );
876  if ( months > maxSteps * 6 )
877  return QwtDate::Year;
878 
879  const int days = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Day );
880  const int weeks = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Week );
881 
882  if ( weeks > m_data->maxWeeks )
883  {
884  if ( days > 4 * maxSteps * 7 )
885  return QwtDate::Month;
886  }
887 
888  if ( days > maxSteps * 7 )
889  return QwtDate::Week;
890 
891  const int hours = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Hour );
892  if ( hours > maxSteps * 24 )
893  return QwtDate::Day;
894 
895  const int seconds = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Second );
896 
897  if ( seconds >= maxSteps * 3600 )
898  return QwtDate::Hour;
899 
900  if ( seconds >= maxSteps * 60 )
901  return QwtDate::Minute;
902 
903  if ( seconds >= maxSteps )
904  return QwtDate::Second;
905 
906  return QwtDate::Millisecond;
907 }
908 
925 void QwtDateScaleEngine::autoScale( int maxNumSteps,
926  double& x1, double& x2, double& stepSize ) const
927 {
928  stepSize = 0.0;
929 
930  QwtInterval interval( x1, x2 );
931  interval = interval.normalized();
932 
933  interval.setMinValue( interval.minValue() - lowerMargin() );
934  interval.setMaxValue( interval.maxValue() + upperMargin() );
935 
937  interval = interval.symmetrize( reference() );
938 
940  interval = interval.extend( reference() );
941 
942  if ( interval.width() == 0.0 )
943  interval = buildInterval( interval.minValue() );
944 
945  const QDateTime from = toDateTime( interval.minValue() );
946  const QDateTime to = toDateTime( interval.maxValue() );
947 
948  if ( from.isValid() && to.isValid() )
949  {
950  if ( maxNumSteps < 1 )
951  maxNumSteps = 1;
952 
953  const QwtDate::IntervalType intvType =
954  intervalType( from, to, maxNumSteps );
955 
956  const double width = qwtIntervalWidth( from, to, intvType );
957 
958  const double stepWidth = qwtDivideScale( width, maxNumSteps, intvType );
959  if ( stepWidth != 0.0 && !testAttribute( QwtScaleEngine::Floating ) )
960  {
961  const QDateTime d1 = alignDate( from, stepWidth, intvType, false );
962  const QDateTime d2 = alignDate( to, stepWidth, intvType, true );
963 
964  interval.setMinValue( QwtDate::toDouble( d1 ) );
965  interval.setMaxValue( QwtDate::toDouble( d2 ) );
966  }
967 
968  stepSize = stepWidth * qwtMsecsForType( intvType );
969  }
970 
971  x1 = interval.minValue();
972  x2 = interval.maxValue();
973 
975  {
976  qSwap( x1, x2 );
977  stepSize = -stepSize;
978  }
979 }
980 
993  int maxMajorSteps, int maxMinorSteps, double stepSize ) const
994 {
995  if ( maxMajorSteps < 1 )
996  maxMajorSteps = 1;
997 
998  const double min = qwtMinF( x1, x2 );
999  const double max = qwtMaxF( x1, x2 );
1000 
1001  const QDateTime from = toDateTime( min );
1002  const QDateTime to = toDateTime( max );
1003 
1004  if ( from == to )
1005  return QwtScaleDiv();
1006 
1007  stepSize = qAbs( stepSize );
1008  if ( stepSize > 0.0 )
1009  {
1010  // as interval types above hours are not equidistant
1011  // ( even days might have 23/25 hours because of daylight saving )
1012  // the stepSize is used as a hint only
1013 
1014  maxMajorSteps = qwtCeil( ( max - min ) / stepSize );
1015  }
1016 
1017  const QwtDate::IntervalType intvType =
1018  intervalType( from, to, maxMajorSteps );
1019 
1020  QwtScaleDiv scaleDiv;
1021 
1022  if ( intvType == QwtDate::Millisecond )
1023  {
1024  // for milliseconds and below we can use the decimal system
1025  scaleDiv = QwtLinearScaleEngine::divideScale( min, max,
1026  maxMajorSteps, maxMinorSteps, stepSize );
1027  }
1028  else
1029  {
1030  const QDateTime minDate = QwtDate::floor( from, intvType );
1031  const QDateTime maxDate = QwtDate::ceil( to, intvType );
1032 
1033  scaleDiv = buildScaleDiv( minDate, maxDate,
1034  maxMajorSteps, maxMinorSteps, intvType );
1035 
1036  // scaleDiv has been calculated from an extended interval
1037  // adjusted to the step size. We have to shrink it again.
1038 
1039  scaleDiv = scaleDiv.bounded( min, max );
1040  }
1041 
1042  if ( x1 > x2 )
1043  scaleDiv.invert();
1044 
1045  return scaleDiv;
1046 }
1047 
1048 QwtScaleDiv QwtDateScaleEngine::buildScaleDiv(
1049  const QDateTime& minDate, const QDateTime& maxDate,
1050  int maxMajorSteps, int maxMinorSteps,
1051  QwtDate::IntervalType intervalType ) const
1052 {
1053  // calculate the step size
1054  const double stepSize = qwtDivideScale(
1055  qwtIntervalWidth( minDate, maxDate, intervalType ),
1056  maxMajorSteps, intervalType );
1057 
1058  // align minDate to the step size
1059  QDateTime dt0 = alignDate( minDate, stepSize, intervalType, false );
1060  if ( !dt0.isValid() )
1061  {
1062  // the floored date is out of the range of a
1063  // QDateTime - we ceil instead.
1064  dt0 = alignDate( minDate, stepSize, intervalType, true );
1065  }
1066 
1067  QwtScaleDiv scaleDiv;
1068 
1069  if ( intervalType <= QwtDate::Week )
1070  {
1071  scaleDiv = qwtDivideToSeconds( dt0, maxDate,
1072  stepSize, maxMinorSteps, intervalType );
1073  }
1074  else
1075  {
1076  if( intervalType == QwtDate::Month )
1077  {
1078  scaleDiv = qwtDivideToMonths( dt0, maxDate,
1079  stepSize, maxMinorSteps );
1080  }
1081  else if ( intervalType == QwtDate::Year )
1082  {
1083  scaleDiv = qwtDivideToYears( dt0, maxDate,
1084  stepSize, maxMinorSteps );
1085  }
1086  }
1087 
1088 
1089  return scaleDiv;
1090 }
1091 
1109  const QDateTime& dateTime, double stepSize,
1110  QwtDate::IntervalType intervalType, bool up ) const
1111 {
1112  // what about: (year == 1582 && month == 10 && day > 4 && day < 15) ??
1113 
1114  QDateTime dt = dateTime;
1115 
1116  if ( dateTime.timeSpec() == Qt::OffsetFromUTC )
1117  {
1118 #if QT_VERSION >= 0x050200
1119  dt.setOffsetFromUtc( 0 );
1120 #else
1121  dt.setUtcOffset( 0 );
1122 #endif
1123  }
1124 
1125  switch( intervalType )
1126  {
1127  case QwtDate::Millisecond:
1128  {
1129  const int ms = qwtAlignValue(
1130  dt.time().msec(), stepSize, up );
1131 
1132  dt = QwtDate::floor( dateTime, QwtDate::Second );
1133  dt = dt.addMSecs( ms );
1134 
1135  break;
1136  }
1137  case QwtDate::Second:
1138  {
1139  int second = dt.time().second();
1140  if ( up )
1141  {
1142  if ( dt.time().msec() > 0 )
1143  second++;
1144  }
1145 
1146  const int s = qwtAlignValue( second, stepSize, up );
1147 
1148  dt = QwtDate::floor( dt, QwtDate::Minute );
1149  dt = dt.addSecs( s );
1150 
1151  break;
1152  }
1153  case QwtDate::Minute:
1154  {
1155  int minute = dt.time().minute();
1156  if ( up )
1157  {
1158  if ( dt.time().msec() > 0 || dt.time().second() > 0 )
1159  minute++;
1160  }
1161 
1162  const int m = qwtAlignValue( minute, stepSize, up );
1163 
1164  dt = QwtDate::floor( dt, QwtDate::Hour );
1165  dt = dt.addSecs( m * 60 );
1166 
1167  break;
1168  }
1169  case QwtDate::Hour:
1170  {
1171  int hour = dt.time().hour();
1172  if ( up )
1173  {
1174  if ( dt.time().msec() > 0 || dt.time().second() > 0
1175  || dt.time().minute() > 0 )
1176  {
1177  hour++;
1178  }
1179  }
1180  const int h = qwtAlignValue( hour, stepSize, up );
1181 
1182  dt = QwtDate::floor( dt, QwtDate::Day );
1183  dt = dt.addSecs( h * 3600 );
1184 
1185  break;
1186  }
1187  case QwtDate::Day:
1188  {
1189  // What date do we expect f.e. from an alignment of 5 days ??
1190  // Aligning them to the beginning of the year avoids at least
1191  // jumping major ticks when panning
1192 
1193  int day = dt.date().dayOfYear();
1194  if ( up )
1195  {
1196  if ( dt.time() > QTime( 0, 0 ) )
1197  day++;
1198  }
1199 
1200  const int d = qwtAlignValue( day, stepSize, up );
1201 
1202  dt = QwtDate::floor( dt, QwtDate::Year );
1203  dt = dt.addDays( d - 1 );
1204 
1205  break;
1206  }
1207  case QwtDate::Week:
1208  {
1209  const QDate date = QwtDate::dateOfWeek0(
1210  dt.date().year(), m_data->week0Type );
1211 
1212  int numWeeks = date.daysTo( dt.date() ) / 7;
1213  if ( up )
1214  {
1215  if ( dt.time() > QTime( 0, 0 ) ||
1216  date.daysTo( dt.date() ) % 7 )
1217  {
1218  numWeeks++;
1219  }
1220  }
1221 
1222  const int d = qwtAlignValue( numWeeks, stepSize, up ) * 7;
1223 
1224  dt = QwtDate::floor( dt, QwtDate::Day );
1225  dt.setDate( date );
1226  dt = dt.addDays( d );
1227 
1228  break;
1229  }
1230  case QwtDate::Month:
1231  {
1232  int month = dt.date().month();
1233  if ( up )
1234  {
1235  if ( dt.date().day() > 1 ||
1236  dt.time() > QTime( 0, 0 ) )
1237  {
1238  month++;
1239  }
1240  }
1241 
1242  const int m = qwtAlignValue( month - 1, stepSize, up );
1243 
1244  dt = QwtDate::floor( dt, QwtDate::Year );
1245  dt = dt.addMonths( m );
1246 
1247  break;
1248  }
1249  case QwtDate::Year:
1250  {
1251  int year = dateTime.date().year();
1252  if ( up )
1253  {
1254  if ( dateTime.date().dayOfYear() > 1 ||
1255  dt.time() > QTime( 0, 0 ) )
1256  {
1257  year++;
1258  }
1259  }
1260 
1261  const int y = qwtAlignValue( year, stepSize, up );
1262 
1263  dt = QwtDate::floor( dt, QwtDate::Day );
1264  if ( y == 0 )
1265  {
1266  // there is no year 0 in the Julian calendar
1267  dt.setDate( QDate( stepSize, 1, 1 ).addYears( -stepSize ) );
1268  }
1269  else
1270  {
1271  dt.setDate( QDate( y, 1, 1 ) );
1272  }
1273 
1274  break;
1275  }
1276  }
1277 
1278  if ( dateTime.timeSpec() == Qt::OffsetFromUTC )
1279  {
1280 #if QT_VERSION >= 0x050200
1281  dt.setOffsetFromUtc( dateTime.offsetFromUtc() );
1282 #else
1283  dt.setUtcOffset( dateTime.utcOffset() );
1284 #endif
1285  }
1286 
1287  return dt;
1288 }
1289 
1298 QDateTime QwtDateScaleEngine::toDateTime( double value ) const
1299 {
1300  QDateTime dt = QwtDate::toDateTime( value, m_data->timeSpec );
1301  if ( !dt.isValid() )
1302  {
1303  const QDate date = ( value <= 0.0 )
1305 
1306  dt = QDateTime( date, QTime( 0, 0 ), m_data->timeSpec );
1307  }
1308 
1309  if ( m_data->timeSpec == Qt::OffsetFromUTC )
1310  {
1311  dt = dt.addSecs( m_data->utcOffset );
1312 #if QT_VERSION >= 0x050200
1313  dt.setOffsetFromUtc( m_data->utcOffset );
1314 #else
1315  dt.setUtcOffset( m_data->utcOffset );
1316 #endif
1317  }
1318 
1319  return dt;
1320 }
1321 
A collection of methods around date/time values.
Definition: qwt_date.h:43
static QDateTime floor(const QDateTime &, IntervalType)
Definition: qwt_date.cpp:425
static QDateTime toDateTime(double value, Qt::TimeSpec=Qt::UTC)
Definition: qwt_date.cpp:261
static QDate minDate()
Definition: qwt_date.cpp:499
static QDateTime ceil(const QDateTime &, IntervalType)
Definition: qwt_date.cpp:323
Week0Type
Definition: qwt_date.h:50
static QDate dateOfWeek0(int year, Week0Type)
Date of the first day of the first week for a year.
Definition: qwt_date.cpp:542
IntervalType
Definition: qwt_date.h:76
@ Month
The interval is related to months.
Definition: qwt_date.h:96
@ Day
The interval is related to days.
Definition: qwt_date.h:90
@ Millisecond
The interval is related to milliseconds.
Definition: qwt_date.h:78
@ Minute
The interval is related to minutes.
Definition: qwt_date.h:84
@ Hour
The interval is related to hours.
Definition: qwt_date.h:87
@ Second
The interval is related to seconds.
Definition: qwt_date.h:81
@ Week
The interval is related to weeks.
Definition: qwt_date.h:93
@ Year
The interval is related to years.
Definition: qwt_date.h:99
static int utcOffset(const QDateTime &)
Definition: qwt_date.cpp:635
static QDate maxDate()
Definition: qwt_date.cpp:521
static double toDouble(const QDateTime &)
Definition: qwt_date.cpp:298
QwtDate::Week0Type week0Type() const
virtual ~QwtDateScaleEngine()
Destructor.
virtual QDateTime alignDate(const QDateTime &, double stepSize, QwtDate::IntervalType, bool up) const
QDateTime toDateTime(double) const
virtual QwtScaleDiv divideScale(double x1, double x2, int maxMajorSteps, int maxMinorSteps, double stepSize=0.0) const override
Calculate a scale division for a date/time interval.
void setUtcOffset(int seconds)
void setWeek0Type(QwtDate::Week0Type)
virtual QwtDate::IntervalType intervalType(const QDateTime &, const QDateTime &, int maxSteps) const
Qt::TimeSpec timeSpec() const
QwtDateScaleEngine(Qt::TimeSpec=Qt::LocalTime)
Constructor.
virtual void autoScale(int maxNumSteps, double &x1, double &x2, double &stepSize) const override
void setTimeSpec(Qt::TimeSpec)
A class representing an interval.
Definition: qwt_interval.h:23
double minValue() const
Definition: qwt_interval.h:192
QwtInterval normalized() const
Normalize the limits of the interval.
double width() const
Return the width of an interval.
Definition: qwt_interval.h:227
void setMaxValue(double)
Definition: qwt_interval.h:186
double maxValue() const
Definition: qwt_interval.h:198
QwtInterval extend(double value) const
Extend the interval.
void setMinValue(double)
Definition: qwt_interval.h:176
QwtInterval symmetrize(double value) const
A scale engine for linear scales.
virtual QwtScaleDiv divideScale(double x1, double x2, int maxMajorSteps, int maxMinorSteps, double stepSize=0.0) const override
Calculate a scale division for an interval.
static double divideInterval(double intervalSize, int numSteps, uint base)
A class representing a scale division.
Definition: qwt_scale_div.h:34
QwtScaleDiv bounded(double lowerBound, double upperBound) const
void setInterval(double lowerBound, double upperBound)
@ MediumTick
Medium ticks.
Definition: qwt_scale_div.h:46
@ MinorTick
Minor ticks.
Definition: qwt_scale_div.h:43
@ MajorTick
Major ticks.
Definition: qwt_scale_div.h:49
void setTicks(int tickType, const QList< double > &)
double reference() const
@ Inverted
Turn the scale upside down.
@ Symmetric
Build a scale which is symmetric to the reference() value.
@ IncludeReference
Build a scale which includes the reference() value.
double upperMargin() const
bool testAttribute(Attribute) const
QwtInterval buildInterval(double value) const
Build an interval around a value.
double lowerMargin() const