1 /* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
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"
12 #include "qwt_transform.h"
13 #include <qdatetime.h>
16 static inline double qwtMsecsForType( QwtDate::IntervalType type
)
18 static const double msecs
[] =
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] ) ) )
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
;
60 return minDate
.secsTo( maxDate
);
64 const double secsTo
= minDate
.secsTo( maxDate
);
65 return ::floor( secsTo
/ 60 );
69 const double secsTo
= minDate
.secsTo( maxDate
);
70 return ::floor( secsTo
/ 3600 );
74 return minDate
.daysTo( maxDate
);
78 return ::floor( minDate
.daysTo( maxDate
) / 7.0 );
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() )
89 return years
* 12 + months
;
94 double( maxDate
.date().year() ) - minDate
.date().year();
96 if ( maxDate
.date().month() < minDate
.date().month() )
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
)
133 static int qwtStepSize( int intervalSize
, int maxSteps
, uint base
)
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
);
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
] )
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
) )
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 ) );
206 static int limits
[] = { 1, 2, 3, 4, 6, 12, 24 };
208 stepSize
= qwtDivideInterval( intervalSize
, numSteps
,
209 limits
, sizeof( limits
) / sizeof( int ) );
215 const double v
= intervalSize
/ double( numSteps
);
217 stepSize
= qCeil( v
);
219 stepSize
= qCeil( v
/ 7 ) * 7;
225 static int limits
[] = { 1, 2, 4, 8, 12, 26, 52 };
227 stepSize
= qwtDivideInterval( intervalSize
, numSteps
,
228 limits
, sizeof( limits
) / sizeof( int ) );
234 static int limits
[] = { 1, 2, 3, 4, 6, 12 };
236 stepSize
= qwtDivideInterval( intervalSize
, numSteps
,
237 limits
, sizeof( limits
) / sizeof( int ) );
242 case QwtDate::Millisecond
:
245 stepSize
= QwtScaleArithmetic::divideInterval(
246 intervalSize
, numSteps
, 10 );
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
;
268 case QwtDate::Minute
:
270 static int limits
[] = { 1, 2, 5, 10, 15, 20, 30, 60 };
274 if ( stepSize
> maxMinSteps
)
276 numSteps
= qwtStepCount( stepSize
, maxMinSteps
,
277 limits
, sizeof( limits
) / sizeof( int ) );
282 numSteps
= qwtStepCount( stepSize
* 60, maxMinSteps
,
283 limits
, sizeof( limits
) / sizeof( int ) );
287 minStepSize
= double( stepSize
) / numSteps
;
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 ) );
304 static int limits
[] = { 1, 2, 5, 10, 15, 20, 30, 60 };
306 numSteps
= qwtStepCount( stepSize
* 60, maxMinSteps
,
307 limits
, sizeof( limits
) / sizeof( int ) );
311 minStepSize
= double( stepSize
) / numSteps
;
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 ) );
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 ) );
335 minStepSize
= double( stepSize
) / numSteps
;
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;
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
)
361 minStepSize
= QwtScaleArithmetic::divideInterval(
362 stepSizeInWeeks
, maxMinSteps
, 10 );
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 ) );
380 minStepSize
= double( stepSize
) / numSteps
;
386 if ( stepSize
>= maxMinSteps
)
388 minStepSize
= QwtScaleArithmetic::divideInterval(
389 stepSize
, maxMinSteps
, 10 );
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 ) );
401 minStepSize
= double( stepSize
) / numSteps
;
410 if ( intervalType
!= QwtDate::Month
411 && minStepSize
== 0.0 )
413 minStepSize
= 0.5 * stepSize
;
419 static QList
<double> qwtDstTicks( const QDateTime
&dateTime
,
420 int secondsMajor
, int secondsMinor
)
422 if ( secondsMinor
<= 0 )
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;
441 for ( int i
= 0; i
< 3600; i
+= secondsMinor
)
442 ticks
+= dstMin
+ i
* 1000.0;
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
);
482 QList
<double> majorTicks
;
483 QList
<double> mediumTicks
;
484 QList
<double> minorTicks
;
486 for ( QDateTime dt
= minDate
; dt
<= maxDate
;
487 dt
= dt
.addSecs( secondsMajor
) )
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
) );
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 );
535 mediumTicks
+= minorValue
;
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
);
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
564 int minStepSize
= 0.0;
566 if ( maxMinSteps
> 1 )
570 if ( maxMinSteps
>= 30 )
572 else if ( maxMinSteps
>= 6 )
574 else if ( maxMinSteps
>= 3 )
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
) )
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 )
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
;
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
);
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 );
674 majorTicks
+= QwtDate::toDouble( dt
);
676 for ( int i
= 1; i
< numMinorSteps
; i
++ )
680 const double years
= qRound( i
* minStepSize
);
681 if ( years
>= INT_MAX
/ 12 )
683 tickDate
= dt
.addYears( years
);
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
);
695 mediumTicks
+= minorValue
;
697 minorTicks
+= minorValue
;
700 if ( QwtDate::maxDate().addYears( -stepSize
) < dt
.date() )
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
);
717 class QwtDateScaleEngine::PrivateData
720 PrivateData( Qt::TimeSpec spec
):
723 week0Type( QwtDate::FirstThursday
),
728 Qt::TimeSpec timeSpec
;
730 QwtDate::Week0Type week0Type
;
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
);
754 QwtDateScaleEngine::~QwtDateScaleEngine()
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
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
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
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
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
,
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 )
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
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 )
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
) )
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
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 )
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
);
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
);
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
);
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
);
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
);
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
);
1133 case QwtDate::Second
:
1135 int second
= dt
.time().second();
1138 if ( dt
.time().msec() > 0 )
1142 const int s
= qwtAlignValue( second
, stepSize
, up
);
1144 dt
= QwtDate::floor( dt
, QwtDate::Minute
);
1145 dt
= dt
.addSecs( s
);
1149 case QwtDate::Minute
:
1151 int minute
= dt
.time().minute();
1154 if ( dt
.time().msec() > 0 || dt
.time().second() > 0 )
1158 const int m
= qwtAlignValue( minute
, stepSize
, up
);
1160 dt
= QwtDate::floor( dt
, QwtDate::Hour
);
1161 dt
= dt
.addSecs( m
* 60 );
1167 int hour
= dt
.time().hour();
1170 if ( dt
.time().msec() > 0 || dt
.time().second() > 0
1171 || dt
.time().minute() > 0 )
1176 const int h
= qwtAlignValue( hour
, stepSize
, up
);
1178 dt
= QwtDate::floor( dt
, QwtDate::Day
);
1179 dt
= dt
.addSecs( h
* 3600 );
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();
1192 if ( dt
.time() > QTime( 0, 0 ) )
1196 const int d
= qwtAlignValue( day
, stepSize
, up
);
1198 dt
= QwtDate::floor( dt
, QwtDate::Year
);
1199 dt
= dt
.addDays( d
- 1 );
1205 const QDate date
= QwtDate::dateOfWeek0(
1206 dt
.date().year(), d_data
->week0Type
);
1208 int numWeeks
= date
.daysTo( dt
.date() ) / 7;
1211 if ( dt
.time() > QTime( 0, 0 ) ||
1212 date
.daysTo( dt
.date() ) % 7 )
1218 const int d
= qwtAlignValue( numWeeks
, stepSize
, up
) * 7;
1220 dt
= QwtDate::floor( dt
, QwtDate::Day
);
1222 dt
= dt
.addDays( d
);
1226 case QwtDate::Month
:
1228 int month
= dt
.date().month();
1231 if ( dt
.date().day() > 1 ||
1232 dt
.time() > QTime( 0, 0 ) )
1238 const int m
= qwtAlignValue( month
- 1, stepSize
, up
);
1240 dt
= QwtDate::floor( dt
, QwtDate::Year
);
1241 dt
= dt
.addMonths( m
);
1247 int year
= dateTime
.date().year();
1250 if ( dateTime
.date().dayOfYear() > 1 ||
1251 dt
.time() > QTime( 0, 0 ) )
1257 const int y
= qwtAlignValue( year
, stepSize
, up
);
1259 dt
= QwtDate::floor( dt
, QwtDate::Day
);
1262 // there is no year 0 in the Julian calendar
1263 dt
.setDate( QDate( stepSize
, 1, 1 ).addYears( -stepSize
) );
1267 dt
.setDate( QDate( y
, 1, 1 ) );
1274 if ( dateTime
.timeSpec() == Qt::OffsetFromUTC
)
1276 dt
.setUtcOffset( dateTime
.utcOffset() );
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
);