Qwt User's Guide: qwt_date_scale_engine.cpp Source File

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 {
52 case QwtDate::Millisecond:
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:
240 case QwtDate::Millisecond:
241 default:
242 {
243 stepSize = QwtScaleArithmetic::divideInterval(
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 {
359 minStepSize = QwtScaleArithmetic::divideInterval(
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 {
386 minStepSize = QwtScaleArithmetic::divideInterval(
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;
730 QwtDate::Week0Type week0Type;
731 int maxWeeks;
732 };
733
734
747 QwtDateScaleEngine::QwtDateScaleEngine( Qt::TimeSpec timeSpec )
748 : QwtLinearScaleEngine( 10 )
749 {
750 m_data = new PrivateData( timeSpec );
751 }
752
754 QwtDateScaleEngine::~QwtDateScaleEngine()
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
789 void QwtDateScaleEngine::setUtcOffset( int seconds )
790 {
791 m_data->utcOffset = seconds;
792 }
793
801 int QwtDateScaleEngine::utcOffset() const
802 {
803 return m_data->utcOffset;
804 }
805
815 void QwtDateScaleEngine::setWeek0Type( QwtDate::Week0Type week0Type )
816 {
817 m_data->week0Type = week0Type;
818 }
819
824 QwtDate::Week0Type QwtDateScaleEngine::week0Type() const
825 {
826 return m_data->week0Type;
827 }
828
841 void QwtDateScaleEngine::setMaxWeeks( int weeks )
842 {
843 m_data->maxWeeks = qMax( weeks, 0 );
844 }
845
851 int QwtDateScaleEngine::maxWeeks() const
852 {
853 return m_data->maxWeeks;
854 }
855
865 QwtDate::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 > 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
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
992 QwtScaleDiv 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 = 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
1108 QDateTime 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 #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 )
1304 ? QwtDate::minDate() : QwtDate::maxDate();
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
QList< double >
QwtDate
A collection of methods around date/time values.
Definition: qwt_date.h:43
QwtDate::floor
static QDateTime floor(const QDateTime &, IntervalType)
Definition: qwt_date.cpp:425
QwtDate::toDateTime
static QDateTime toDateTime(double value, Qt::TimeSpec=Qt::UTC)
Definition: qwt_date.cpp:261
QwtDate::minDate
static QDate minDate()
Definition: qwt_date.cpp:499
QwtDate::ceil
static QDateTime ceil(const QDateTime &, IntervalType)
Definition: qwt_date.cpp:323
QwtDate::Week0Type
Week0Type
Definition: qwt_date.h:50
QwtDate::dateOfWeek0
static QDate dateOfWeek0(int year, Week0Type)
Date of the first day of the first week for a year.
Definition: qwt_date.cpp:542
QwtDate::IntervalType
IntervalType
Definition: qwt_date.h:76
QwtDate::Month
@ Month
The interval is related to months.
Definition: qwt_date.h:96
QwtDate::Day
@ Day
The interval is related to days.
Definition: qwt_date.h:90
QwtDate::Millisecond
@ Millisecond
The interval is related to milliseconds.
Definition: qwt_date.h:78
QwtDate::Minute
@ Minute
The interval is related to minutes.
Definition: qwt_date.h:84
QwtDate::Hour
@ Hour
The interval is related to hours.
Definition: qwt_date.h:87
QwtDate::Second
@ Second
The interval is related to seconds.
Definition: qwt_date.h:81
QwtDate::Week
@ Week
The interval is related to weeks.
Definition: qwt_date.h:93
QwtDate::Year
@ Year
The interval is related to years.
Definition: qwt_date.h:99
QwtDate::utcOffset
static int utcOffset(const QDateTime &)
Definition: qwt_date.cpp:635
QwtDate::maxDate
static QDate maxDate()
Definition: qwt_date.cpp:521
QwtDate::toDouble
static double toDouble(const QDateTime &)
Definition: qwt_date.cpp:298
QwtDateScaleEngine::setMaxWeeks
void setMaxWeeks(int)
Definition: qwt_date_scale_engine.cpp:841
QwtDateScaleEngine::week0Type
QwtDate::Week0Type week0Type() const
Definition: qwt_date_scale_engine.cpp:824
QwtDateScaleEngine::~QwtDateScaleEngine
virtual ~QwtDateScaleEngine()
Destructor.
Definition: qwt_date_scale_engine.cpp:754
QwtDateScaleEngine::alignDate
virtual QDateTime alignDate(const QDateTime &, double stepSize, QwtDate::IntervalType, bool up) const
Definition: qwt_date_scale_engine.cpp:1108
QwtDateScaleEngine::toDateTime
QDateTime toDateTime(double) const
Definition: qwt_date_scale_engine.cpp:1298
QwtDateScaleEngine::divideScale
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.
Definition: qwt_date_scale_engine.cpp:992
QwtDateScaleEngine::setUtcOffset
void setUtcOffset(int seconds)
Definition: qwt_date_scale_engine.cpp:789
QwtDateScaleEngine::setWeek0Type
void setWeek0Type(QwtDate::Week0Type)
Definition: qwt_date_scale_engine.cpp:815
QwtDateScaleEngine::intervalType
virtual QwtDate::IntervalType intervalType(const QDateTime &, const QDateTime &, int maxSteps) const
Definition: qwt_date_scale_engine.cpp:865
QwtDateScaleEngine::timeSpec
Qt::TimeSpec timeSpec() const
Definition: qwt_date_scale_engine.cpp:774
QwtDateScaleEngine::QwtDateScaleEngine
QwtDateScaleEngine(Qt::TimeSpec=Qt::LocalTime)
Constructor.
Definition: qwt_date_scale_engine.cpp:747
QwtDateScaleEngine::autoScale
virtual void autoScale(int maxNumSteps, double &x1, double &x2, double &stepSize) const override
Definition: qwt_date_scale_engine.cpp:925
QwtDateScaleEngine::utcOffset
int utcOffset() const
Definition: qwt_date_scale_engine.cpp:801
QwtDateScaleEngine::setTimeSpec
void setTimeSpec(Qt::TimeSpec)
Definition: qwt_date_scale_engine.cpp:765
QwtDateScaleEngine::maxWeeks
int maxWeeks() const
Definition: qwt_date_scale_engine.cpp:851
QwtInterval
A class representing an interval.
Definition: qwt_interval.h:23
QwtInterval::minValue
double minValue() const
Definition: qwt_interval.h:192
QwtInterval::normalized
QwtInterval normalized() const
Normalize the limits of the interval.
Definition: qwt_interval.cpp:29
QwtInterval::width
double width() const
Return the width of an interval.
Definition: qwt_interval.h:227
QwtInterval::setMaxValue
void setMaxValue(double)
Definition: qwt_interval.h:186
QwtInterval::maxValue
double maxValue() const
Definition: qwt_interval.h:198
QwtInterval::extend
QwtInterval extend(double value) const
Extend the interval.
Definition: qwt_interval.cpp:363
QwtInterval::setMinValue
void setMinValue(double)
Definition: qwt_interval.h:176
QwtInterval::symmetrize
QwtInterval symmetrize(double value) const
Definition: qwt_interval.cpp:317
QwtLinearScaleEngine
A scale engine for linear scales.
Definition: qwt_scale_engine.h:152
QwtLinearScaleEngine::divideScale
virtual QwtScaleDiv divideScale(double x1, double x2, int maxMajorSteps, int maxMinorSteps, double stepSize=0.0) const override
Calculate a scale division for an interval.
Definition: qwt_scale_engine.cpp:577
QwtScaleArithmetic::divideInterval
static double divideInterval(double intervalSize, int numSteps, uint base)
Definition: qwt_scale_engine.cpp:160
QwtScaleDiv
A class representing a scale division.
Definition: qwt_scale_div.h:34
QwtScaleDiv::bounded
QwtScaleDiv bounded(double lowerBound, double upperBound) const
Definition: qwt_scale_div.cpp:263
QwtScaleDiv::invert
void invert()
Definition: qwt_scale_div.cpp:224
QwtScaleDiv::setInterval
void setInterval(double lowerBound, double upperBound)
Definition: qwt_scale_div.cpp:91
QwtScaleDiv::MediumTick
@ MediumTick
Medium ticks.
Definition: qwt_scale_div.h:46
QwtScaleDiv::MinorTick
@ MinorTick
Minor ticks.
Definition: qwt_scale_div.h:43
QwtScaleDiv::MajorTick
@ MajorTick
Major ticks.
Definition: qwt_scale_div.h:49
QwtScaleDiv::setTicks
void setTicks(int tickType, const QList< double > &)
Definition: qwt_scale_div.cpp:297
QwtScaleEngine::reference
double reference() const
Definition: qwt_scale_engine.cpp:474
QwtScaleEngine::Floating
@ Floating
Definition: qwt_scale_engine.h:72
QwtScaleEngine::Inverted
@ Inverted
Turn the scale upside down.
Definition: qwt_scale_engine.h:75
QwtScaleEngine::Symmetric
@ Symmetric
Build a scale which is symmetric to the reference() value.
Definition: qwt_scale_engine.h:63
QwtScaleEngine::IncludeReference
@ IncludeReference
Build a scale which includes the reference() value.
Definition: qwt_scale_engine.h:60
QwtScaleEngine::upperMargin
double upperMargin() const
Definition: qwt_scale_engine.cpp:291
QwtScaleEngine::testAttribute
bool testAttribute(Attribute) const
Definition: qwt_scale_engine.cpp:431
QwtScaleEngine::buildInterval
QwtInterval buildInterval(double value) const
Build an interval around a value.
Definition: qwt_scale_engine.cpp:395
QwtScaleEngine::lowerMargin
double lowerMargin() const
Definition: qwt_scale_engine.cpp:280