source: ntrip/trunk/BNC/qwt/qwt_date_scale_engine.cpp@ 8418

Last change on this file since 8418 was 8127, checked in by stoecker, 8 years ago

update qwt and qwtpolar, many QT5 fixes (unfinished)

File size: 35.1 KB
Line 
1/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
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_transform.h"
13#include <qdatetime.h>
14#include <limits.h>
15
16static inline double qwtMsecsForType( QwtDate::IntervalType type )
17{
18 static const double msecs[] =
19 {
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,
28 };
29
30 if ( type < 0 || type >= static_cast<int>( sizeof( msecs ) / sizeof( msecs[0] ) ) )
31 return 1.0;
32
33 return msecs[ type ];
34}
35
36static inline int qwtAlignValue(
37 double value, double stepSize, bool up )
38{
39 double d = value / stepSize;
40 d = up ? ::ceil( d ) : ::floor( d );
41
42 return static_cast<int>( d * stepSize );
43}
44
45static double qwtIntervalWidth( const QDateTime &minDate,
46 const QDateTime &maxDate, QwtDate::IntervalType intervalType )
47{
48 switch( intervalType )
49 {
50 case QwtDate::Millisecond:
51 {
52 const double secsTo = minDate.secsTo( maxDate );
53 const double msecs = maxDate.time().msec() -
54 minDate.time().msec();
55
56 return secsTo * 1000 + msecs;
57 }
58 case QwtDate::Second:
59 {
60 return minDate.secsTo( maxDate );
61 }
62 case QwtDate::Minute:
63 {
64 const double secsTo = minDate.secsTo( maxDate );
65 return ::floor( secsTo / 60 );
66 }
67 case QwtDate::Hour:
68 {
69 const double secsTo = minDate.secsTo( maxDate );
70 return ::floor( secsTo / 3600 );
71 }
72 case QwtDate::Day:
73 {
74 return minDate.daysTo( maxDate );
75 }
76 case QwtDate::Week:
77 {
78 return ::floor( minDate.daysTo( maxDate ) / 7.0 );
79 }
80 case QwtDate::Month:
81 {
82 const double years =
83 double( maxDate.date().year() ) - minDate.date().year();
84
85 int months = maxDate.date().month() - minDate.date().month();
86 if ( maxDate.date().day() < minDate.date().day() )
87 months--;
88
89 return years * 12 + months;
90 }
91 case QwtDate::Year:
92 {
93 double years =
94 double( maxDate.date().year() ) - minDate.date().year();
95
96 if ( maxDate.date().month() < minDate.date().month() )
97 years -= 1.0;
98
99 return years;
100 }
101 }
102
103 return 0.0;
104}
105
106static double qwtRoundedIntervalWidth(
107 const QDateTime &minDate, const QDateTime &maxDate,
108 QwtDate::IntervalType intervalType )
109{
110 const QDateTime minD = QwtDate::floor( minDate, intervalType );
111 const QDateTime maxD = QwtDate::ceil( maxDate, intervalType );
112
113 return qwtIntervalWidth( minD, maxD, intervalType );
114}
115
116static inline int qwtStepCount( int intervalSize, int maxSteps,
117 const int limits[], size_t numLimits )
118{
119 for ( uint i = 0; i < numLimits; i++ )
120 {
121 const int numSteps = intervalSize / limits[ i ];
122
123 if ( numSteps > 1 && numSteps <= maxSteps &&
124 numSteps * limits[ i ] == intervalSize )
125 {
126 return numSteps;
127 }
128 }
129
130 return 0;
131}
132
133static int qwtStepSize( int intervalSize, int maxSteps, uint base )
134{
135 if ( maxSteps <= 0 )
136 return 0;
137
138 if ( maxSteps > 2 )
139 {
140 for ( int numSteps = maxSteps; numSteps > 1; numSteps-- )
141 {
142 const double stepSize = double( intervalSize ) / numSteps;
143
144 const double p = ::floor( ::log( stepSize ) / ::log( double( base ) ) );
145 const double fraction = qPow( base, p );
146
147 for ( uint n = base; n >= 1; n /= 2 )
148 {
149 if ( qFuzzyCompare( stepSize, n * fraction ) )
150 return qRound( stepSize );
151
152 if ( n == 3 && ( base % 2 ) == 0 )
153 {
154 if ( qFuzzyCompare( stepSize, 2 * fraction ) )
155 return qRound( stepSize );
156 }
157 }
158 }
159 }
160
161 return 0;
162}
163
164static int qwtDivideInterval( double intervalSize, int numSteps,
165 const int limits[], size_t numLimits )
166{
167 const int v = qCeil( intervalSize / double( numSteps ) );
168
169 for ( uint i = 0; i < numLimits - 1; i++ )
170 {
171 if ( v <= limits[i] )
172 return limits[i];
173 }
174
175 return limits[ numLimits - 1 ];
176}
177
178static double qwtDivideScale( double intervalSize, int numSteps,
179 QwtDate::IntervalType intervalType )
180{
181 if ( intervalType != QwtDate::Day )
182 {
183 if ( ( intervalSize > numSteps ) &&
184 ( intervalSize <= 2 * numSteps ) )
185 {
186 return 2.0;
187 }
188 }
189
190 double stepSize;
191
192 switch( intervalType )
193 {
194 case QwtDate::Second:
195 case QwtDate::Minute:
196 {
197 static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 };
198
199 stepSize = qwtDivideInterval( intervalSize, numSteps,
200 limits, sizeof( limits ) / sizeof( int ) );
201
202 break;
203 }
204 case QwtDate::Hour:
205 {
206 static int limits[] = { 1, 2, 3, 4, 6, 12, 24 };
207
208 stepSize = qwtDivideInterval( intervalSize, numSteps,
209 limits, sizeof( limits ) / sizeof( int ) );
210
211 break;
212 }
213 case QwtDate::Day:
214 {
215 const double v = intervalSize / double( numSteps );
216 if ( v <= 5.0 )
217 stepSize = qCeil( v );
218 else
219 stepSize = qCeil( v / 7 ) * 7;
220
221 break;
222 }
223 case QwtDate::Week:
224 {
225 static int limits[] = { 1, 2, 4, 8, 12, 26, 52 };
226
227 stepSize = qwtDivideInterval( intervalSize, numSteps,
228 limits, sizeof( limits ) / sizeof( int ) );
229
230 break;
231 }
232 case QwtDate::Month:
233 {
234 static int limits[] = { 1, 2, 3, 4, 6, 12 };
235
236 stepSize = qwtDivideInterval( intervalSize, numSteps,
237 limits, sizeof( limits ) / sizeof( int ) );
238
239 break;
240 }
241 case QwtDate::Year:
242 case QwtDate::Millisecond:
243 default:
244 {
245 stepSize = QwtScaleArithmetic::divideInterval(
246 intervalSize, numSteps, 10 );
247 }
248 }
249
250 return stepSize;
251}
252
253static double qwtDivideMajorStep( double stepSize, int maxMinSteps,
254 QwtDate::IntervalType intervalType )
255{
256 double minStepSize = 0.0;
257
258 switch( intervalType )
259 {
260 case QwtDate::Second:
261 {
262 minStepSize = qwtStepSize( stepSize, maxMinSteps, 10 );
263 if ( minStepSize == 0.0 )
264 minStepSize = 0.5 * stepSize;
265
266 break;
267 }
268 case QwtDate::Minute:
269 {
270 static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 };
271
272 int numSteps;
273
274 if ( stepSize > maxMinSteps )
275 {
276 numSteps = qwtStepCount( stepSize, maxMinSteps,
277 limits, sizeof( limits ) / sizeof( int ) );
278
279 }
280 else
281 {
282 numSteps = qwtStepCount( stepSize * 60, maxMinSteps,
283 limits, sizeof( limits ) / sizeof( int ) );
284 }
285
286 if ( numSteps > 0 )
287 minStepSize = double( stepSize ) / numSteps;
288
289 break;
290 }
291 case QwtDate::Hour:
292 {
293 int numSteps = 0;
294
295 if ( stepSize > maxMinSteps )
296 {
297 static int limits[] = { 1, 2, 3, 4, 6, 12, 24, 48, 72 };
298
299 numSteps = qwtStepCount( stepSize, maxMinSteps,
300 limits, sizeof( limits ) / sizeof( int ) );
301 }
302 else
303 {
304 static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 };
305
306 numSteps = qwtStepCount( stepSize * 60, maxMinSteps,
307 limits, sizeof( limits ) / sizeof( int ) );
308 }
309
310 if ( numSteps > 0 )
311 minStepSize = double( stepSize ) / numSteps;
312
313 break;
314 }
315 case QwtDate::Day:
316 {
317 int numSteps = 0;
318
319 if ( stepSize > maxMinSteps )
320 {
321 static int limits[] = { 1, 2, 3, 7, 14, 28 };
322
323 numSteps = qwtStepCount( stepSize, maxMinSteps,
324 limits, sizeof( limits ) / sizeof( int ) );
325 }
326 else
327 {
328 static int limits[] = { 1, 2, 3, 4, 6, 12, 24, 48, 72 };
329
330 numSteps = qwtStepCount( stepSize * 24, maxMinSteps,
331 limits, sizeof( limits ) / sizeof( int ) );
332 }
333
334 if ( numSteps > 0 )
335 minStepSize = double( stepSize ) / numSteps;
336
337 break;
338 }
339 case QwtDate::Week:
340 {
341 const int daysInStep = stepSize * 7;
342
343 if ( maxMinSteps >= daysInStep )
344 {
345 // we want to have one tick per day
346 minStepSize = 1.0 / 7.0;
347 }
348 else
349 {
350 // when the stepSize is more than a week we want to
351 // have a tick for each week
352
353 const int stepSizeInWeeks = stepSize;
354
355 if ( stepSizeInWeeks <= maxMinSteps )
356 {
357 minStepSize = 1;
358 }
359 else
360 {
361 minStepSize = QwtScaleArithmetic::divideInterval(
362 stepSizeInWeeks, maxMinSteps, 10 );
363 }
364 }
365 break;
366 }
367 case QwtDate::Month:
368 {
369 // fractions of months doesn't make any sense
370
371 if ( stepSize < maxMinSteps )
372 maxMinSteps = static_cast<int>( stepSize );
373
374 static int limits[] = { 1, 2, 3, 4, 6, 12 };
375
376 int numSteps = qwtStepCount( stepSize, maxMinSteps,
377 limits, sizeof( limits ) / sizeof( int ) );
378
379 if ( numSteps > 0 )
380 minStepSize = double( stepSize ) / numSteps;
381
382 break;
383 }
384 case QwtDate::Year:
385 {
386 if ( stepSize >= maxMinSteps )
387 {
388 minStepSize = QwtScaleArithmetic::divideInterval(
389 stepSize, maxMinSteps, 10 );
390 }
391 else
392 {
393 // something in months
394
395 static int limits[] = { 1, 2, 3, 4, 6, 12 };
396
397 int numSteps = qwtStepCount( 12 * stepSize, maxMinSteps,
398 limits, sizeof( limits ) / sizeof( int ) );
399
400 if ( numSteps > 0 )
401 minStepSize = double( stepSize ) / numSteps;
402 }
403
404 break;
405 }
406 default:
407 break;
408 }
409
410 if ( intervalType != QwtDate::Month
411 && minStepSize == 0.0 )
412 {
413 minStepSize = 0.5 * stepSize;
414 }
415
416 return minStepSize;
417}
418
419static QList<double> qwtDstTicks( const QDateTime &dateTime,
420 int secondsMajor, int secondsMinor )
421{
422 if ( secondsMinor <= 0 )
423 QList<double>();
424
425 QDateTime minDate = dateTime.addSecs( -secondsMajor );
426 minDate = QwtDate::floor( minDate, QwtDate::Hour );
427
428 const double utcOffset = QwtDate::utcOffset( dateTime );
429
430 // find the hours where daylight saving time happens
431
432 double dstMin = QwtDate::toDouble( minDate );
433 while ( minDate < dateTime &&
434 QwtDate::utcOffset( minDate ) != utcOffset )
435 {
436 minDate = minDate.addSecs( 3600 );
437 dstMin += 3600 * 1000.0;
438 }
439
440 QList<double> ticks;
441 for ( int i = 0; i < 3600; i += secondsMinor )
442 ticks += dstMin + i * 1000.0;
443
444 return ticks;
445}
446
447static 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 = qFloor( 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
555static 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
639static 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 = qFloor( 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 >= 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
717class QwtDateScaleEngine::PrivateData
718{
719public:
720 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;
730 QwtDate::Week0Type week0Type;
731 int maxWeeks;
732};
733
734
735/*!
736 \brief Constructor
737
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.
742
743 \param timeSpec Time specification
744
745 \sa setTimeSpec(), setMaxWeeks(), setWeek0Type()
746 */
747QwtDateScaleEngine::QwtDateScaleEngine( Qt::TimeSpec timeSpec ):
748 QwtLinearScaleEngine( 10 )
749{
750 d_data = new PrivateData( timeSpec );
751}
752
753//! Destructor
754QwtDateScaleEngine::~QwtDateScaleEngine()
755{
756 delete d_data;
757}
758
759/*!
760 Set the time specification used by the engine
761
762 \param timeSpec Time specification
763 \sa timeSpec(), setUtcOffset(), toDateTime()
764 */
765void QwtDateScaleEngine::setTimeSpec( Qt::TimeSpec timeSpec )
766{
767 d_data->timeSpec = timeSpec;
768}
769
770/*!
771 \return Time specification used by the engine
772 \sa setTimeSpec(), utcOffset(), toDateTime()
773 */
774Qt::TimeSpec QwtDateScaleEngine::timeSpec() const
775{
776 return d_data->timeSpec;
777}
778
779/*!
780 Set the offset in seconds from Coordinated Universal Time
781
782 \param seconds Offset in seconds
783
784 \note The offset has no effect beside for the time specification
785 Qt::OffsetFromUTC.
786
787 \sa QDate::utcOffset(), setTimeSpec(), toDateTime()
788 */
789void QwtDateScaleEngine::setUtcOffset( int seconds )
790{
791 d_data->utcOffset = seconds;
792}
793
794/*!
795 \return Offset in seconds from Coordinated Universal Time
796 \note The offset has no effect beside for the time specification
797 Qt::OffsetFromUTC.
798
799 \sa QDate::setUtcOffset(), setTimeSpec(), toDateTime()
800 */
801int QwtDateScaleEngine::utcOffset() const
802{
803 return d_data->utcOffset;
804}
805
806/*!
807 Sets how to identify the first week of a year.
808
809 \param week0Type Mode how to identify the first week of a year
810
811 \sa week0Type(), setMaxWeeks()
812 \note week0Type has no effect beside for intervals classified as
813 QwtDate::Week.
814 */
815void QwtDateScaleEngine::setWeek0Type( QwtDate::Week0Type week0Type )
816{
817 d_data->week0Type = week0Type;
818}
819
820/*!
821 \return Setting how to identify the first week of a year.
822 \sa setWeek0Type(), maxWeeks()
823 */
824QwtDate::Week0Type QwtDateScaleEngine::week0Type() const
825{
826 return d_data->week0Type;
827}
828
829/*!
830 Set a upper limit for the number of weeks, when an interval
831 can be classified as Qt::Week.
832
833 The default setting is 4 weeks.
834
835 \param weeks Upper limit for the number of weeks
836
837 \note In business charts a year is often devided
838 into weeks [1-52]
839 \sa maxWeeks(), setWeek0Type()
840 */
841void QwtDateScaleEngine::setMaxWeeks( int weeks )
842{
843 d_data->maxWeeks = qMax( weeks, 0 );
844}
845
846/*!
847 \return Upper limit for the number of weeks, when an interval
848 can be classified as Qt::Week.
849 \sa setMaxWeeks(), week0Type()
850 */
851int QwtDateScaleEngine::maxWeeks() const
852{
853 return d_data->maxWeeks;
854}
855
856/*!
857 Classification of a date/time interval division
858
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
862
863 \return Interval classification
864 */
865QwtDate::IntervalType QwtDateScaleEngine::intervalType(
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 > d_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
909/*!
910 Align and divide an interval
911
912 The algorithm aligns and divides the interval into steps.
913
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().
917
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)
922
923 \sa QwtScaleEngine::setAttribute()
924*/
925void 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
936 if ( testAttribute( QwtScaleEngine::Symmetric ) )
937 interval = interval.symmetrize( reference() );
938
939 if ( testAttribute( QwtScaleEngine::IncludeReference ) )
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
974 if ( testAttribute( QwtScaleEngine::Inverted ) )
975 {
976 qSwap( x1, x2 );
977 stepSize = -stepSize;
978 }
979}
980
981/*!
982 \brief Calculate a scale division for a date/time interval
983
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
991*/
992QwtScaleDiv QwtDateScaleEngine::divideScale( double x1, double x2,
993 int maxMajorSteps, int maxMinorSteps, double stepSize ) const
994{
995 if ( maxMajorSteps < 1 )
996 maxMajorSteps = 1;
997
998 const double min = qMin( x1, x2 );
999 const double max = qMax( 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 = qCeil( ( 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
1048QwtScaleDiv 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
1092/*!
1093 Align a date/time value for a step size
1094
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.
1100
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
1105
1106 \return Aligned date/time value
1107 */
1108QDateTime QwtDateScaleEngine::alignDate(
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 dt.setUtcOffset( 0 );
1119 }
1120
1121 switch( intervalType )
1122 {
1123 case QwtDate::Millisecond:
1124 {
1125 const int ms = qwtAlignValue(
1126 dt.time().msec(), stepSize, up ) ;
1127
1128 dt = QwtDate::floor( dateTime, QwtDate::Second );
1129 dt = dt.addMSecs( ms );
1130
1131 break;
1132 }
1133 case QwtDate::Second:
1134 {
1135 int second = dt.time().second();
1136 if ( up )
1137 {
1138 if ( dt.time().msec() > 0 )
1139 second++;
1140 }
1141
1142 const int s = qwtAlignValue( second, stepSize, up );
1143
1144 dt = QwtDate::floor( dt, QwtDate::Minute );
1145 dt = dt.addSecs( s );
1146
1147 break;
1148 }
1149 case QwtDate::Minute:
1150 {
1151 int minute = dt.time().minute();
1152 if ( up )
1153 {
1154 if ( dt.time().msec() > 0 || dt.time().second() > 0 )
1155 minute++;
1156 }
1157
1158 const int m = qwtAlignValue( minute, stepSize, up );
1159
1160 dt = QwtDate::floor( dt, QwtDate::Hour );
1161 dt = dt.addSecs( m * 60 );
1162
1163 break;
1164 }
1165 case QwtDate::Hour:
1166 {
1167 int hour = dt.time().hour();
1168 if ( up )
1169 {
1170 if ( dt.time().msec() > 0 || dt.time().second() > 0
1171 || dt.time().minute() > 0 )
1172 {
1173 hour++;
1174 }
1175 }
1176 const int h = qwtAlignValue( hour, stepSize, up );
1177
1178 dt = QwtDate::floor( dt, QwtDate::Day );
1179 dt = dt.addSecs( h * 3600 );
1180
1181 break;
1182 }
1183 case QwtDate::Day:
1184 {
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
1188
1189 int day = dt.date().dayOfYear();
1190 if ( up )
1191 {
1192 if ( dt.time() > QTime( 0, 0 ) )
1193 day++;
1194 }
1195
1196 const int d = qwtAlignValue( day, stepSize, up );
1197
1198 dt = QwtDate::floor( dt, QwtDate::Year );
1199 dt = dt.addDays( d - 1 );
1200
1201 break;
1202 }
1203 case QwtDate::Week:
1204 {
1205 const QDate date = QwtDate::dateOfWeek0(
1206 dt.date().year(), d_data->week0Type );
1207
1208 int numWeeks = date.daysTo( dt.date() ) / 7;
1209 if ( up )
1210 {
1211 if ( dt.time() > QTime( 0, 0 ) ||
1212 date.daysTo( dt.date() ) % 7 )
1213 {
1214 numWeeks++;
1215 }
1216 }
1217
1218 const int d = qwtAlignValue( numWeeks, stepSize, up ) * 7;
1219
1220 dt = QwtDate::floor( dt, QwtDate::Day );
1221 dt.setDate( date );
1222 dt = dt.addDays( d );
1223
1224 break;
1225 }
1226 case QwtDate::Month:
1227 {
1228 int month = dt.date().month();
1229 if ( up )
1230 {
1231 if ( dt.date().day() > 1 ||
1232 dt.time() > QTime( 0, 0 ) )
1233 {
1234 month++;
1235 }
1236 }
1237
1238 const int m = qwtAlignValue( month - 1, stepSize, up );
1239
1240 dt = QwtDate::floor( dt, QwtDate::Year );
1241 dt = dt.addMonths( m );
1242
1243 break;
1244 }
1245 case QwtDate::Year:
1246 {
1247 int year = dateTime.date().year();
1248 if ( up )
1249 {
1250 if ( dateTime.date().dayOfYear() > 1 ||
1251 dt.time() > QTime( 0, 0 ) )
1252 {
1253 year++;
1254 }
1255 }
1256
1257 const int y = qwtAlignValue( year, stepSize, up );
1258
1259 dt = QwtDate::floor( dt, QwtDate::Day );
1260 if ( y == 0 )
1261 {
1262 // there is no year 0 in the Julian calendar
1263 dt.setDate( QDate( stepSize, 1, 1 ).addYears( -stepSize ) );
1264 }
1265 else
1266 {
1267 dt.setDate( QDate( y, 1, 1 ) );
1268 }
1269
1270 break;
1271 }
1272 }
1273
1274 if ( dateTime.timeSpec() == Qt::OffsetFromUTC )
1275 {
1276 dt.setUtcOffset( dateTime.utcOffset() );
1277 }
1278
1279 return dt;
1280}
1281
1282/*!
1283 Translate a double value into a QDateTime object.
1284
1285 For QDateTime result is bounded by QwtDate::minDate() and QwtDate::maxDate()
1286
1287 \return QDateTime object initialized with timeSpec() and utcOffset().
1288 \sa timeSpec(), utcOffset(), QwtDate::toDateTime()
1289 */
1290QDateTime QwtDateScaleEngine::toDateTime( double value ) const
1291{
1292 QDateTime dt = QwtDate::toDateTime( value, d_data->timeSpec );
1293 if ( !dt.isValid() )
1294 {
1295 const QDate date = ( value <= 0.0 )
1296 ? QwtDate::minDate() : QwtDate::maxDate();
1297
1298 dt = QDateTime( date, QTime( 0, 0 ), d_data->timeSpec );
1299 }
1300
1301 if ( d_data->timeSpec == Qt::OffsetFromUTC )
1302 {
1303 dt = dt.addSecs( d_data->utcOffset );
1304 dt.setUtcOffset( d_data->utcOffset );
1305 }
1306
1307 return dt;
1308}
1309
Note: See TracBrowser for help on using the repository browser.