Merged in f5soh/librepilot/update_credits (pull request #529)
[librepilot.git] / ground / gcs / src / libs / qwt / src / qwt_date_scale_engine.cpp
blob874a827a59aa46680aa23da8766be619d0021aa9
1 /* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
2 * Qwt Widget Library
3 * Copyright (C) 1997 Josef Wilgen
4 * Copyright (C) 2002 Uwe Rathmann
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 *****************************************************************************/
10 #include "qwt_date_scale_engine.h"
11 #include "qwt_math.h"
12 #include "qwt_transform.h"
13 #include <qdatetime.h>
14 #include <limits.h>
16 static inline double qwtMsecsForType( QwtDate::IntervalType type )
18 static const double msecs[] =
20 1.0,
21 1000.0,
22 60.0 * 1000.0,
23 3600.0 * 1000.0,
24 24.0 * 3600.0 * 1000.0,
25 7.0 * 24.0 * 3600.0 * 1000.0,
26 30.0 * 24.0 * 3600.0 * 1000.0,
27 365.0 * 24.0 * 3600.0 * 1000.0,
30 if ( type < 0 || type >= static_cast<int>( sizeof( msecs ) / sizeof( msecs[0] ) ) )
31 return 1.0;
33 return msecs[ type ];
36 static inline int qwtAlignValue(
37 double value, double stepSize, bool up )
39 double d = value / stepSize;
40 d = up ? ::ceil( d ) : ::floor( d );
42 return static_cast<int>( d * stepSize );
45 static double qwtIntervalWidth( const QDateTime &minDate,
46 const QDateTime &maxDate, QwtDate::IntervalType intervalType )
48 switch( intervalType )
50 case QwtDate::Millisecond:
52 const double secsTo = minDate.secsTo( maxDate );
53 const double msecs = maxDate.time().msec() -
54 minDate.time().msec();
56 return secsTo * 1000 + msecs;
58 case QwtDate::Second:
60 return minDate.secsTo( maxDate );
62 case QwtDate::Minute:
64 const double secsTo = minDate.secsTo( maxDate );
65 return ::floor( secsTo / 60 );
67 case QwtDate::Hour:
69 const double secsTo = minDate.secsTo( maxDate );
70 return ::floor( secsTo / 3600 );
72 case QwtDate::Day:
74 return minDate.daysTo( maxDate );
76 case QwtDate::Week:
78 return ::floor( minDate.daysTo( maxDate ) / 7.0 );
80 case QwtDate::Month:
82 const double years =
83 double( maxDate.date().year() ) - minDate.date().year();
85 int months = maxDate.date().month() - minDate.date().month();
86 if ( maxDate.date().day() < minDate.date().day() )
87 months--;
89 return years * 12 + months;
91 case QwtDate::Year:
93 double years =
94 double( maxDate.date().year() ) - minDate.date().year();
96 if ( maxDate.date().month() < minDate.date().month() )
97 years -= 1.0;
99 return years;
103 return 0.0;
106 static double qwtRoundedIntervalWidth(
107 const QDateTime &minDate, const QDateTime &maxDate,
108 QwtDate::IntervalType intervalType )
110 const QDateTime minD = QwtDate::floor( minDate, intervalType );
111 const QDateTime maxD = QwtDate::ceil( maxDate, intervalType );
113 return qwtIntervalWidth( minD, maxD, intervalType );
116 static inline int qwtStepCount( int intervalSize, int maxSteps,
117 const int limits[], size_t numLimits )
119 for ( uint i = 0; i < numLimits; i++ )
121 const int numSteps = intervalSize / limits[ i ];
123 if ( numSteps > 1 && numSteps <= maxSteps &&
124 numSteps * limits[ i ] == intervalSize )
126 return numSteps;
130 return 0;
133 static int qwtStepSize( int intervalSize, int maxSteps, uint base )
135 if ( maxSteps <= 0 )
136 return 0;
138 if ( maxSteps > 2 )
140 for ( int numSteps = maxSteps; numSteps > 1; numSteps-- )
142 const double stepSize = double( intervalSize ) / numSteps;
144 const double p = ::floor( ::log( stepSize ) / ::log( double( base ) ) );
145 const double fraction = qPow( base, p );
147 for ( uint n = base; n >= 1; n /= 2 )
149 if ( qFuzzyCompare( stepSize, n * fraction ) )
150 return qRound( stepSize );
152 if ( n == 3 && ( base % 2 ) == 0 )
154 if ( qFuzzyCompare( stepSize, 2 * fraction ) )
155 return qRound( stepSize );
161 return 0;
164 static int qwtDivideInterval( double intervalSize, int numSteps,
165 const int limits[], size_t numLimits )
167 const int v = qCeil( intervalSize / double( numSteps ) );
169 for ( uint i = 0; i < numLimits - 1; i++ )
171 if ( v <= limits[i] )
172 return limits[i];
175 return limits[ numLimits - 1 ];
178 static double qwtDivideScale( double intervalSize, int numSteps,
179 QwtDate::IntervalType intervalType )
181 if ( intervalType != QwtDate::Day )
183 if ( ( intervalSize > numSteps ) &&
184 ( intervalSize <= 2 * numSteps ) )
186 return 2.0;
190 double stepSize;
192 switch( intervalType )
194 case QwtDate::Second:
195 case QwtDate::Minute:
197 static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 };
199 stepSize = qwtDivideInterval( intervalSize, numSteps,
200 limits, sizeof( limits ) / sizeof( int ) );
202 break;
204 case QwtDate::Hour:
206 static int limits[] = { 1, 2, 3, 4, 6, 12, 24 };
208 stepSize = qwtDivideInterval( intervalSize, numSteps,
209 limits, sizeof( limits ) / sizeof( int ) );
211 break;
213 case QwtDate::Day:
215 const double v = intervalSize / double( numSteps );
216 if ( v <= 5.0 )
217 stepSize = qCeil( v );
218 else
219 stepSize = qCeil( v / 7 ) * 7;
221 break;
223 case QwtDate::Week:
225 static int limits[] = { 1, 2, 4, 8, 12, 26, 52 };
227 stepSize = qwtDivideInterval( intervalSize, numSteps,
228 limits, sizeof( limits ) / sizeof( int ) );
230 break;
232 case QwtDate::Month:
234 static int limits[] = { 1, 2, 3, 4, 6, 12 };
236 stepSize = qwtDivideInterval( intervalSize, numSteps,
237 limits, sizeof( limits ) / sizeof( int ) );
239 break;
241 case QwtDate::Year:
242 case QwtDate::Millisecond:
243 default:
245 stepSize = QwtScaleArithmetic::divideInterval(
246 intervalSize, numSteps, 10 );
250 return stepSize;
253 static double qwtDivideMajorStep( double stepSize, int maxMinSteps,
254 QwtDate::IntervalType intervalType )
256 double minStepSize = 0.0;
258 switch( intervalType )
260 case QwtDate::Second:
262 minStepSize = qwtStepSize( stepSize, maxMinSteps, 10 );
263 if ( minStepSize == 0.0 )
264 minStepSize = 0.5 * stepSize;
266 break;
268 case QwtDate::Minute:
270 static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 };
272 int numSteps;
274 if ( stepSize > maxMinSteps )
276 numSteps = qwtStepCount( stepSize, maxMinSteps,
277 limits, sizeof( limits ) / sizeof( int ) );
280 else
282 numSteps = qwtStepCount( stepSize * 60, maxMinSteps,
283 limits, sizeof( limits ) / sizeof( int ) );
286 if ( numSteps > 0 )
287 minStepSize = double( stepSize ) / numSteps;
289 break;
291 case QwtDate::Hour:
293 int numSteps = 0;
295 if ( stepSize > maxMinSteps )
297 static int limits[] = { 1, 2, 3, 4, 6, 12, 24, 48, 72 };
299 numSteps = qwtStepCount( stepSize, maxMinSteps,
300 limits, sizeof( limits ) / sizeof( int ) );
302 else
304 static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 };
306 numSteps = qwtStepCount( stepSize * 60, maxMinSteps,
307 limits, sizeof( limits ) / sizeof( int ) );
310 if ( numSteps > 0 )
311 minStepSize = double( stepSize ) / numSteps;
313 break;
315 case QwtDate::Day:
317 int numSteps = 0;
319 if ( stepSize > maxMinSteps )
321 static int limits[] = { 1, 2, 3, 7, 14, 28 };
323 numSteps = qwtStepCount( stepSize, maxMinSteps,
324 limits, sizeof( limits ) / sizeof( int ) );
326 else
328 static int limits[] = { 1, 2, 3, 4, 6, 12, 24, 48, 72 };
330 numSteps = qwtStepCount( stepSize * 24, maxMinSteps,
331 limits, sizeof( limits ) / sizeof( int ) );
334 if ( numSteps > 0 )
335 minStepSize = double( stepSize ) / numSteps;
337 break;
339 case QwtDate::Week:
341 const int daysInStep = stepSize * 7;
343 if ( maxMinSteps >= daysInStep )
345 // we want to have one tick per day
346 minStepSize = 1.0 / 7.0;
348 else
350 // when the stepSize is more than a week we want to
351 // have a tick for each week
353 const int stepSizeInWeeks = stepSize;
355 if ( stepSizeInWeeks <= maxMinSteps )
357 minStepSize = 1;
359 else
361 minStepSize = QwtScaleArithmetic::divideInterval(
362 stepSizeInWeeks, maxMinSteps, 10 );
365 break;
367 case QwtDate::Month:
369 // fractions of months doesn't make any sense
371 if ( stepSize < maxMinSteps )
372 maxMinSteps = static_cast<int>( stepSize );
374 static int limits[] = { 1, 2, 3, 4, 6, 12 };
376 int numSteps = qwtStepCount( stepSize, maxMinSteps,
377 limits, sizeof( limits ) / sizeof( int ) );
379 if ( numSteps > 0 )
380 minStepSize = double( stepSize ) / numSteps;
382 break;
384 case QwtDate::Year:
386 if ( stepSize >= maxMinSteps )
388 minStepSize = QwtScaleArithmetic::divideInterval(
389 stepSize, maxMinSteps, 10 );
391 else
393 // something in months
395 static int limits[] = { 1, 2, 3, 4, 6, 12 };
397 int numSteps = qwtStepCount( 12 * stepSize, maxMinSteps,
398 limits, sizeof( limits ) / sizeof( int ) );
400 if ( numSteps > 0 )
401 minStepSize = double( stepSize ) / numSteps;
404 break;
406 default:
407 break;
410 if ( intervalType != QwtDate::Month
411 && minStepSize == 0.0 )
413 minStepSize = 0.5 * stepSize;
416 return minStepSize;
419 static QList<double> qwtDstTicks( const QDateTime &dateTime,
420 int secondsMajor, int secondsMinor )
422 if ( secondsMinor <= 0 )
423 QList<double>();
425 QDateTime minDate = dateTime.addSecs( -secondsMajor );
426 minDate = QwtDate::floor( minDate, QwtDate::Hour );
428 const double utcOffset = QwtDate::utcOffset( dateTime );
430 // find the hours where daylight saving time happens
432 double dstMin = QwtDate::toDouble( minDate );
433 while ( minDate < dateTime &&
434 QwtDate::utcOffset( minDate ) != utcOffset )
436 minDate = minDate.addSecs( 3600 );
437 dstMin += 3600 * 1000.0;
440 QList<double> ticks;
441 for ( int i = 0; i < 3600; i += secondsMinor )
442 ticks += dstMin + i * 1000.0;
444 return ticks;
447 static QwtScaleDiv qwtDivideToSeconds(
448 const QDateTime &minDate, const QDateTime &maxDate,
449 double stepSize, int maxMinSteps,
450 QwtDate::IntervalType intervalType )
452 // calculate the min step size
453 double minStepSize = 0;
455 if ( maxMinSteps > 1 )
457 minStepSize = qwtDivideMajorStep( stepSize,
458 maxMinSteps, intervalType );
461 bool daylightSaving = false;
462 if ( minDate.timeSpec() == Qt::LocalTime )
464 daylightSaving = intervalType > QwtDate::Hour;
465 if ( intervalType == QwtDate::Hour )
467 daylightSaving = stepSize > 1;
471 const double s = qwtMsecsForType( intervalType ) / 1000;
472 const int secondsMajor = static_cast<int>( stepSize * s );
473 const double secondsMinor = minStepSize * s;
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
479 const double utcOffset = QwtDate::utcOffset( minDate );
480 double dstOff = 0;
482 QList<double> majorTicks;
483 QList<double> mediumTicks;
484 QList<double> minorTicks;
486 for ( QDateTime dt = minDate; dt <= maxDate;
487 dt = dt.addSecs( secondsMajor ) )
489 if ( !dt.isValid() )
490 break;
492 double majorValue = QwtDate::toDouble( dt );
494 if ( daylightSaving )
496 const double offset = utcOffset - QwtDate::utcOffset( dt );
497 majorValue += offset * 1000.0;
499 if ( offset > dstOff )
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 ) );
507 dstOff = offset;
510 if ( majorTicks.isEmpty() || majorTicks.last() != majorValue )
511 majorTicks += majorValue;
513 if ( secondsMinor > 0.0 )
515 const int numMinorSteps = qFloor( secondsMajor / secondsMinor );
517 for ( int i = 1; i < numMinorSteps; i++ )
519 const QDateTime mt = dt.addMSecs(
520 qRound64( i * secondsMinor * 1000 ) );
522 double minorValue = QwtDate::toDouble( mt );
523 if ( daylightSaving )
525 const double offset = utcOffset - QwtDate::utcOffset( mt );
526 minorValue += offset * 1000.0;
529 if ( minorTicks.isEmpty() || minorTicks.last() != minorValue )
531 const bool isMedium = ( numMinorSteps % 2 == 0 )
532 && ( i != 1 ) && ( i == numMinorSteps / 2 );
534 if ( isMedium )
535 mediumTicks += minorValue;
536 else
537 minorTicks += minorValue;
543 QwtScaleDiv scaleDiv;
545 scaleDiv.setInterval( QwtDate::toDouble( minDate ),
546 QwtDate::toDouble( maxDate ) );
548 scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks );
549 scaleDiv.setTicks( QwtScaleDiv::MediumTick, mediumTicks );
550 scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks );
552 return scaleDiv;
555 static QwtScaleDiv qwtDivideToMonths(
556 QDateTime &minDate, const QDateTime &maxDate,
557 double stepSize, int maxMinSteps )
559 // months are intervals with non
560 // equidistant ( in ms ) steps: we have to build the
561 // scale division manually
563 int minStepDays = 0;
564 int minStepSize = 0.0;
566 if ( maxMinSteps > 1 )
568 if ( stepSize == 1 )
570 if ( maxMinSteps >= 30 )
571 minStepDays = 1;
572 else if ( maxMinSteps >= 6 )
573 minStepDays = 5;
574 else if ( maxMinSteps >= 3 )
575 minStepDays = 10;
577 minStepDays = 15;
579 else
581 minStepSize = qwtDivideMajorStep(
582 stepSize, maxMinSteps, QwtDate::Month );
586 QList<double> majorTicks;
587 QList<double> mediumTicks;
588 QList<double> minorTicks;
590 for ( QDateTime dt = minDate;
591 dt <= maxDate; dt = dt.addMonths( stepSize ) )
593 if ( !dt.isValid() )
594 break;
596 majorTicks += QwtDate::toDouble( dt );
598 if ( minStepDays > 0 )
600 for ( int days = minStepDays;
601 days < 30; days += minStepDays )
603 const double tick = QwtDate::toDouble( dt.addDays( days ) );
605 if ( days == 15 && minStepDays != 15 )
606 mediumTicks += tick;
607 else
608 minorTicks += tick;
611 else if ( minStepSize > 0.0 )
613 const int numMinorSteps = qRound( stepSize / (double) minStepSize );
615 for ( int i = 1; i < numMinorSteps; i++ )
617 const double minorValue =
618 QwtDate::toDouble( dt.addMonths( i * minStepSize ) );
620 if ( ( numMinorSteps % 2 == 0 ) && ( i == numMinorSteps / 2 ) )
621 mediumTicks += minorValue;
622 else
623 minorTicks += minorValue;
628 QwtScaleDiv scaleDiv;
629 scaleDiv.setInterval( QwtDate::toDouble( minDate ),
630 QwtDate::toDouble( maxDate ) );
632 scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks );
633 scaleDiv.setTicks( QwtScaleDiv::MediumTick, mediumTicks );
634 scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks );
636 return scaleDiv;
639 static QwtScaleDiv qwtDivideToYears(
640 const QDateTime &minDate, const QDateTime &maxDate,
641 double stepSize, int maxMinSteps )
643 QList<double> majorTicks;
644 QList<double> mediumTicks;
645 QList<double> minorTicks;
647 double minStepSize = 0.0;
649 if ( maxMinSteps > 1 )
651 minStepSize = qwtDivideMajorStep(
652 stepSize, maxMinSteps, QwtDate::Year );
655 int numMinorSteps = 0;
656 if ( minStepSize > 0.0 )
657 numMinorSteps = qFloor( stepSize / minStepSize );
659 bool dateBC = minDate.date().year() < -1;
661 for ( QDateTime dt = minDate; dt <= maxDate;
662 dt = dt.addYears( stepSize ) )
664 if ( dateBC && dt.date().year() > 1 )
666 // there is no year 0 in the Julian calendar
667 dt = dt.addYears( -1 );
668 dateBC = false;
671 if ( !dt.isValid() )
672 break;
674 majorTicks += QwtDate::toDouble( dt );
676 for ( int i = 1; i < numMinorSteps; i++ )
678 QDateTime tickDate;
680 const double years = qRound( i * minStepSize );
681 if ( years >= INT_MAX / 12 )
683 tickDate = dt.addYears( years );
685 else
687 tickDate = dt.addMonths( qRound( years * 12 ) );
690 const bool isMedium = ( numMinorSteps > 2 ) &&
691 ( numMinorSteps % 2 == 0 ) && ( i == numMinorSteps / 2 );
693 const double minorValue = QwtDate::toDouble( tickDate );
694 if ( isMedium )
695 mediumTicks += minorValue;
696 else
697 minorTicks += minorValue;
700 if ( QwtDate::maxDate().addYears( -stepSize ) < dt.date() )
702 break;
706 QwtScaleDiv scaleDiv;
707 scaleDiv.setInterval( QwtDate::toDouble( minDate ),
708 QwtDate::toDouble( maxDate ) );
710 scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks );
711 scaleDiv.setTicks( QwtScaleDiv::MediumTick, mediumTicks );
712 scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks );
714 return scaleDiv;
717 class QwtDateScaleEngine::PrivateData
719 public:
720 PrivateData( Qt::TimeSpec spec ):
721 timeSpec( spec ),
722 utcOffset( 0 ),
723 week0Type( QwtDate::FirstThursday ),
724 maxWeeks( 4 )
728 Qt::TimeSpec timeSpec;
729 int utcOffset;
730 QwtDate::Week0Type week0Type;
731 int maxWeeks;
736 \brief Constructor
738 The engine is initialized to build scales for the
739 given time specification. It classifies intervals > 4 weeks
740 as >= Qt::Month. The first week of a year is defined like
741 for QwtDate::FirstThursday.
743 \param timeSpec Time specification
745 \sa setTimeSpec(), setMaxWeeks(), setWeek0Type()
747 QwtDateScaleEngine::QwtDateScaleEngine( Qt::TimeSpec timeSpec ):
748 QwtLinearScaleEngine( 10 )
750 d_data = new PrivateData( timeSpec );
753 //! Destructor
754 QwtDateScaleEngine::~QwtDateScaleEngine()
756 delete d_data;
760 Set the time specification used by the engine
762 \param timeSpec Time specification
763 \sa timeSpec(), setUtcOffset(), toDateTime()
765 void QwtDateScaleEngine::setTimeSpec( Qt::TimeSpec timeSpec )
767 d_data->timeSpec = timeSpec;
771 \return Time specification used by the engine
772 \sa setTimeSpec(), utcOffset(), toDateTime()
774 Qt::TimeSpec QwtDateScaleEngine::timeSpec() const
776 return d_data->timeSpec;
780 Set the offset in seconds from Coordinated Universal Time
782 \param seconds Offset in seconds
784 \note The offset has no effect beside for the time specification
785 Qt::OffsetFromUTC.
787 \sa QDate::utcOffset(), setTimeSpec(), toDateTime()
789 void QwtDateScaleEngine::setUtcOffset( int seconds )
791 d_data->utcOffset = seconds;
795 \return Offset in seconds from Coordinated Universal Time
796 \note The offset has no effect beside for the time specification
797 Qt::OffsetFromUTC.
799 \sa QDate::setUtcOffset(), setTimeSpec(), toDateTime()
801 int QwtDateScaleEngine::utcOffset() const
803 return d_data->utcOffset;
807 Sets how to identify the first week of a year.
809 \param week0Type Mode how to identify the first week of a year
811 \sa week0Type(), setMaxWeeks()
812 \note week0Type has no effect beside for intervals classified as
813 QwtDate::Week.
815 void QwtDateScaleEngine::setWeek0Type( QwtDate::Week0Type week0Type )
817 d_data->week0Type = week0Type;
821 \return Setting how to identify the first week of a year.
822 \sa setWeek0Type(), maxWeeks()
824 QwtDate::Week0Type QwtDateScaleEngine::week0Type() const
826 return d_data->week0Type;
830 Set a upper limit for the number of weeks, when an interval
831 can be classified as Qt::Week.
833 The default setting is 4 weeks.
835 \param weeks Upper limit for the number of weeks
837 \note In business charts a year is often devided
838 into weeks [1-52]
839 \sa maxWeeks(), setWeek0Type()
841 void QwtDateScaleEngine::setMaxWeeks( int weeks )
843 d_data->maxWeeks = qMax( weeks, 0 );
847 \return Upper limit for the number of weeks, when an interval
848 can be classified as Qt::Week.
849 \sa setMaxWeeks(), week0Type()
851 int QwtDateScaleEngine::maxWeeks() const
853 return d_data->maxWeeks;
857 Classification of a date/time interval division
859 \param minDate Minimum ( = earlier ) of the interval
860 \param maxDate Maximum ( = later ) of the interval
861 \param maxSteps Maximum for the number of steps
863 \return Interval classification
865 QwtDate::IntervalType QwtDateScaleEngine::intervalType(
866 const QDateTime &minDate, const QDateTime &maxDate,
867 int maxSteps ) const
869 const double jdMin = minDate.date().toJulianDay();
870 const double jdMax = maxDate.date().toJulianDay();
872 if ( ( jdMax - jdMin ) / 365 > maxSteps )
873 return QwtDate::Year;
875 const int months = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Month );
876 if ( months > maxSteps * 6 )
877 return QwtDate::Year;
879 const int days = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Day );
880 const int weeks = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Week );
882 if ( weeks > d_data->maxWeeks )
884 if ( days > 4 * maxSteps * 7 )
885 return QwtDate::Month;
888 if ( days > maxSteps * 7 )
889 return QwtDate::Week;
891 const int hours = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Hour );
892 if ( hours > maxSteps * 24 )
893 return QwtDate::Day;
895 const int seconds = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Second );
897 if ( seconds >= maxSteps * 3600 )
898 return QwtDate::Hour;
900 if ( seconds >= maxSteps * 60 )
901 return QwtDate::Minute;
903 if ( seconds >= maxSteps )
904 return QwtDate::Second;
906 return QwtDate::Millisecond;
910 Align and divide an interval
912 The algorithm aligns and divides the interval into steps.
914 Datetime interval divisions are usually not equidistant and the
915 calculated stepSize can only be used as an approximation
916 for the steps calculated by divideScale().
918 \param maxNumSteps Max. number of steps
919 \param x1 First limit of the interval (In/Out)
920 \param x2 Second limit of the interval (In/Out)
921 \param stepSize Step size (Out)
923 \sa QwtScaleEngine::setAttribute()
925 void QwtDateScaleEngine::autoScale( int maxNumSteps,
926 double &x1, double &x2, double &stepSize ) const
928 stepSize = 0.0;
930 QwtInterval interval( x1, x2 );
931 interval = interval.normalized();
933 interval.setMinValue( interval.minValue() - lowerMargin() );
934 interval.setMaxValue( interval.maxValue() + upperMargin() );
936 if ( testAttribute( QwtScaleEngine::Symmetric ) )
937 interval = interval.symmetrize( reference() );
939 if ( testAttribute( QwtScaleEngine::IncludeReference ) )
940 interval = interval.extend( reference() );
942 if ( interval.width() == 0.0 )
943 interval = buildInterval( interval.minValue() );
945 const QDateTime from = toDateTime( interval.minValue() );
946 const QDateTime to = toDateTime( interval.maxValue() );
948 if ( from.isValid() && to.isValid() )
950 if ( maxNumSteps < 1 )
951 maxNumSteps = 1;
953 const QwtDate::IntervalType intvType =
954 intervalType( from, to, maxNumSteps );
956 const double width = qwtIntervalWidth( from, to, intvType );
958 const double stepWidth = qwtDivideScale( width, maxNumSteps, intvType );
959 if ( stepWidth != 0.0 && !testAttribute( QwtScaleEngine::Floating ) )
961 const QDateTime d1 = alignDate( from, stepWidth, intvType, false );
962 const QDateTime d2 = alignDate( to, stepWidth, intvType, true );
964 interval.setMinValue( QwtDate::toDouble( d1 ) );
965 interval.setMaxValue( QwtDate::toDouble( d2 ) );
968 stepSize = stepWidth * qwtMsecsForType( intvType );
971 x1 = interval.minValue();
972 x2 = interval.maxValue();
974 if ( testAttribute( QwtScaleEngine::Inverted ) )
976 qSwap( x1, x2 );
977 stepSize = -stepSize;
982 \brief Calculate a scale division for a date/time interval
984 \param x1 First interval limit
985 \param x2 Second interval limit
986 \param maxMajorSteps Maximum for the number of major steps
987 \param maxMinorSteps Maximum number of minor steps
988 \param stepSize Step size. If stepSize == 0, the scaleEngine
989 calculates one.
990 \return Calculated scale division
992 QwtScaleDiv QwtDateScaleEngine::divideScale( double x1, double x2,
993 int maxMajorSteps, int maxMinorSteps, double stepSize ) const
995 if ( maxMajorSteps < 1 )
996 maxMajorSteps = 1;
998 const double min = qMin( x1, x2 );
999 const double max = qMax( x1, x2 );
1001 const QDateTime from = toDateTime( min );
1002 const QDateTime to = toDateTime( max );
1004 if ( from == to )
1005 return QwtScaleDiv();
1007 stepSize = qAbs( stepSize );
1008 if ( stepSize > 0.0 )
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
1014 maxMajorSteps = qCeil( ( max - min ) / stepSize );
1017 const QwtDate::IntervalType intvType =
1018 intervalType( from, to, maxMajorSteps );
1020 QwtScaleDiv scaleDiv;
1022 if ( intvType == QwtDate::Millisecond )
1024 // for milliseconds and below we can use the decimal system
1025 scaleDiv = QwtLinearScaleEngine::divideScale( min, max,
1026 maxMajorSteps, maxMinorSteps, stepSize );
1028 else
1030 const QDateTime minDate = QwtDate::floor( from, intvType );
1031 const QDateTime maxDate = QwtDate::ceil( to, intvType );
1033 scaleDiv = buildScaleDiv( minDate, maxDate,
1034 maxMajorSteps, maxMinorSteps, intvType );
1036 // scaleDiv has been calculated from an extended interval
1037 // adjusted to the step size. We have to shrink it again.
1039 scaleDiv = scaleDiv.bounded( min, max );
1042 if ( x1 > x2 )
1043 scaleDiv.invert();
1045 return scaleDiv;
1048 QwtScaleDiv QwtDateScaleEngine::buildScaleDiv(
1049 const QDateTime &minDate, const QDateTime &maxDate,
1050 int maxMajorSteps, int maxMinorSteps,
1051 QwtDate::IntervalType intervalType ) const
1053 // calculate the step size
1054 const double stepSize = qwtDivideScale(
1055 qwtIntervalWidth( minDate, maxDate, intervalType ),
1056 maxMajorSteps, intervalType );
1058 // align minDate to the step size
1059 QDateTime dt0 = alignDate( minDate, stepSize, intervalType, false );
1060 if ( !dt0.isValid() )
1062 // the floored date is out of the range of a
1063 // QDateTime - we ceil instead.
1064 dt0 = alignDate( minDate, stepSize, intervalType, true );
1067 QwtScaleDiv scaleDiv;
1069 if ( intervalType <= QwtDate::Week )
1071 scaleDiv = qwtDivideToSeconds( dt0, maxDate,
1072 stepSize, maxMinorSteps, intervalType );
1074 else
1076 if( intervalType == QwtDate::Month )
1078 scaleDiv = qwtDivideToMonths( dt0, maxDate,
1079 stepSize, maxMinorSteps );
1081 else if ( intervalType == QwtDate::Year )
1083 scaleDiv = qwtDivideToYears( dt0, maxDate,
1084 stepSize, maxMinorSteps );
1089 return scaleDiv;
1093 Align a date/time value for a step size
1095 For Qt::Day alignments there is no "natural day 0" -
1096 instead the first day of the year is used to avoid jumping
1097 major ticks positions when panning a scale. For other alignments
1098 ( f.e according to the first day of the month ) alignDate()
1099 has to be overloaded.
1101 \param dateTime Date/time value
1102 \param stepSize Step size
1103 \param intervalType Interval type
1104 \param up When true dateTime is ceiled - otherwise it is floored
1106 \return Aligned date/time value
1108 QDateTime QwtDateScaleEngine::alignDate(
1109 const QDateTime &dateTime, double stepSize,
1110 QwtDate::IntervalType intervalType, bool up ) const
1112 // what about: (year == 1582 && month == 10 && day > 4 && day < 15) ??
1114 QDateTime dt = dateTime;
1116 if ( dateTime.timeSpec() == Qt::OffsetFromUTC )
1118 dt.setUtcOffset( 0 );
1121 switch( intervalType )
1123 case QwtDate::Millisecond:
1125 const int ms = qwtAlignValue(
1126 dt.time().msec(), stepSize, up ) ;
1128 dt = QwtDate::floor( dateTime, QwtDate::Second );
1129 dt = dt.addMSecs( ms );
1131 break;
1133 case QwtDate::Second:
1135 int second = dt.time().second();
1136 if ( up )
1138 if ( dt.time().msec() > 0 )
1139 second++;
1142 const int s = qwtAlignValue( second, stepSize, up );
1144 dt = QwtDate::floor( dt, QwtDate::Minute );
1145 dt = dt.addSecs( s );
1147 break;
1149 case QwtDate::Minute:
1151 int minute = dt.time().minute();
1152 if ( up )
1154 if ( dt.time().msec() > 0 || dt.time().second() > 0 )
1155 minute++;
1158 const int m = qwtAlignValue( minute, stepSize, up );
1160 dt = QwtDate::floor( dt, QwtDate::Hour );
1161 dt = dt.addSecs( m * 60 );
1163 break;
1165 case QwtDate::Hour:
1167 int hour = dt.time().hour();
1168 if ( up )
1170 if ( dt.time().msec() > 0 || dt.time().second() > 0
1171 || dt.time().minute() > 0 )
1173 hour++;
1176 const int h = qwtAlignValue( hour, stepSize, up );
1178 dt = QwtDate::floor( dt, QwtDate::Day );
1179 dt = dt.addSecs( h * 3600 );
1181 break;
1183 case QwtDate::Day:
1185 // What date do we expect f.e. from an alignment of 5 days ??
1186 // Aligning them to the beginning of the year avoids at least
1187 // jumping major ticks when panning
1189 int day = dt.date().dayOfYear();
1190 if ( up )
1192 if ( dt.time() > QTime( 0, 0 ) )
1193 day++;
1196 const int d = qwtAlignValue( day, stepSize, up );
1198 dt = QwtDate::floor( dt, QwtDate::Year );
1199 dt = dt.addDays( d - 1 );
1201 break;
1203 case QwtDate::Week:
1205 const QDate date = QwtDate::dateOfWeek0(
1206 dt.date().year(), d_data->week0Type );
1208 int numWeeks = date.daysTo( dt.date() ) / 7;
1209 if ( up )
1211 if ( dt.time() > QTime( 0, 0 ) ||
1212 date.daysTo( dt.date() ) % 7 )
1214 numWeeks++;
1218 const int d = qwtAlignValue( numWeeks, stepSize, up ) * 7;
1220 dt = QwtDate::floor( dt, QwtDate::Day );
1221 dt.setDate( date );
1222 dt = dt.addDays( d );
1224 break;
1226 case QwtDate::Month:
1228 int month = dt.date().month();
1229 if ( up )
1231 if ( dt.date().day() > 1 ||
1232 dt.time() > QTime( 0, 0 ) )
1234 month++;
1238 const int m = qwtAlignValue( month - 1, stepSize, up );
1240 dt = QwtDate::floor( dt, QwtDate::Year );
1241 dt = dt.addMonths( m );
1243 break;
1245 case QwtDate::Year:
1247 int year = dateTime.date().year();
1248 if ( up )
1250 if ( dateTime.date().dayOfYear() > 1 ||
1251 dt.time() > QTime( 0, 0 ) )
1253 year++;
1257 const int y = qwtAlignValue( year, stepSize, up );
1259 dt = QwtDate::floor( dt, QwtDate::Day );
1260 if ( y == 0 )
1262 // there is no year 0 in the Julian calendar
1263 dt.setDate( QDate( stepSize, 1, 1 ).addYears( -stepSize ) );
1265 else
1267 dt.setDate( QDate( y, 1, 1 ) );
1270 break;
1274 if ( dateTime.timeSpec() == Qt::OffsetFromUTC )
1276 dt.setUtcOffset( dateTime.utcOffset() );
1279 return dt;
1283 Translate a double value into a QDateTime object.
1285 For QDateTime result is bounded by QwtDate::minDate() and QwtDate::maxDate()
1287 \return QDateTime object initialized with timeSpec() and utcOffset().
1288 \sa timeSpec(), utcOffset(), QwtDate::toDateTime()
1290 QDateTime QwtDateScaleEngine::toDateTime( double value ) const
1292 QDateTime dt = QwtDate::toDateTime( value, d_data->timeSpec );
1293 if ( !dt.isValid() )
1295 const QDate date = ( value <= 0.0 )
1296 ? QwtDate::minDate() : QwtDate::maxDate();
1298 dt = QDateTime( date, QTime( 0, 0 ), d_data->timeSpec );
1301 if ( d_data->timeSpec == Qt::OffsetFromUTC )
1303 dt = dt.addSecs( d_data->utcOffset );
1304 dt.setUtcOffset( d_data->utcOffset );
1307 return dt;