Qwt User's Guide 6.3.0
Loading...
Searching...
No Matches
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
18static 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
38static 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
47static double qwtIntervalWidth( const QDateTime& minDate,
48 const QDateTime& maxDate, QwtDate::IntervalType intervalType )
49{
50 switch( intervalType )
51 {
53 {
54 return minDate.msecsTo( maxDate );
55 }
56 case QwtDate::Second:
57 {
58 return minDate.secsTo( maxDate );
59 }
60 case QwtDate::Minute:
61 {
62 const double secsTo = minDate.secsTo( maxDate );
63 return std::floor( secsTo / 60 );
64 }
65 case QwtDate::Hour:
66 {
67 const double secsTo = minDate.secsTo( maxDate );
68 return std::floor( secsTo / 3600 );
69 }
70 case QwtDate::Day:
71 {
72 return minDate.daysTo( maxDate );
73 }
74 case QwtDate::Week:
75 {
76 return std::floor( minDate.daysTo( maxDate ) / 7.0 );
77 }
78 case QwtDate::Month:
79 {
80 const double years =
81 double( maxDate.date().year() ) - minDate.date().year();
82
83 int months = maxDate.date().month() - minDate.date().month();
84 if ( maxDate.date().day() < minDate.date().day() )
85 months--;
86
87 return years * 12 + months;
88 }
89 case QwtDate::Year:
90 {
91 double years =
92 double( maxDate.date().year() ) - minDate.date().year();
93
94 if ( maxDate.date().month() < minDate.date().month() )
95 years -= 1.0;
96
97 return years;
98 }
99 }
100
101 return 0.0;
102}
103
104static 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
114static 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
131static 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
162static 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
176static double qwtDivideScale( double intervalSize, int numSteps,
177 QwtDate::IntervalType intervalType )
178{
179 if ( intervalType != QwtDate::Day )
180 {
181 if ( ( intervalSize > numSteps ) &&
182 ( intervalSize <= 2 * numSteps ) )
183 {
184 return 2.0;
185 }
186 }
187
188 double stepSize;
189
190 switch( intervalType )
191 {
192 case QwtDate::Second:
193 case QwtDate::Minute:
194 {
195 static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 };
196
197 stepSize = qwtDivideInterval( intervalSize, numSteps,
198 limits, sizeof( limits ) / sizeof( int ) );
199
200 break;
201 }
202 case QwtDate::Hour:
203 {
204 static int limits[] = { 1, 2, 3, 4, 6, 12, 24 };
205
206 stepSize = qwtDivideInterval( intervalSize, numSteps,
207 limits, sizeof( limits ) / sizeof( int ) );
208
209 break;
210 }
211 case QwtDate::Day:
212 {
213 const double v = intervalSize / double( numSteps );
214 if ( v <= 5.0 )
215 stepSize = std::ceil( v );
216 else
217 stepSize = std::ceil( v / 7 ) * 7;
218
219 break;
220 }
221 case QwtDate::Week:
222 {
223 static int limits[] = { 1, 2, 4, 8, 12, 26, 52 };
224
225 stepSize = qwtDivideInterval( intervalSize, numSteps,
226 limits, sizeof( limits ) / sizeof( int ) );
227
228 break;
229 }
230 case QwtDate::Month:
231 {
232 static int limits[] = { 1, 2, 3, 4, 6, 12 };
233
234 stepSize = qwtDivideInterval( intervalSize, numSteps,
235 limits, sizeof( limits ) / sizeof( int ) );
236
237 break;
238 }
239 case QwtDate::Year:
241 default:
242 {
244 intervalSize, numSteps, 10 );
245 }
246 }
247
248 return stepSize;
249}
250
251static double qwtDivideMajorStep( double stepSize, int maxMinSteps,
252 QwtDate::IntervalType intervalType )
253{
254 double minStepSize = 0.0;
255
256 switch( intervalType )
257 {
258 case QwtDate::Second:
259 {
260 minStepSize = qwtStepSize( stepSize, maxMinSteps, 10 );
261 if ( minStepSize == 0.0 )
262 minStepSize = 0.5 * stepSize;
263
264 break;
265 }
266 case QwtDate::Minute:
267 {
268 static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 };
269
270 int numSteps;
271
272 if ( stepSize > maxMinSteps )
273 {
274 numSteps = qwtStepCount( stepSize, maxMinSteps,
275 limits, sizeof( limits ) / sizeof( int ) );
276
277 }
278 else
279 {
280 numSteps = qwtStepCount( stepSize * 60, maxMinSteps,
281 limits, sizeof( limits ) / sizeof( int ) );
282 }
283
284 if ( numSteps > 0 )
285 minStepSize = stepSize / numSteps;
286
287 break;
288 }
289 case QwtDate::Hour:
290 {
291 int numSteps = 0;
292
293 if ( stepSize > maxMinSteps )
294 {
295 static int limits[] = { 1, 2, 3, 4, 6, 12, 24, 48, 72 };
296
297 numSteps = qwtStepCount( stepSize, maxMinSteps,
298 limits, sizeof( limits ) / sizeof( int ) );
299 }
300 else
301 {
302 static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 };
303
304 numSteps = qwtStepCount( stepSize * 60, maxMinSteps,
305 limits, sizeof( limits ) / sizeof( int ) );
306 }
307
308 if ( numSteps > 0 )
309 minStepSize = stepSize / numSteps;
310
311 break;
312 }
313 case QwtDate::Day:
314 {
315 int numSteps = 0;
316
317 if ( stepSize > maxMinSteps )
318 {
319 static int limits[] = { 1, 2, 3, 7, 14, 28 };
320
321 numSteps = qwtStepCount( stepSize, maxMinSteps,
322 limits, sizeof( limits ) / sizeof( int ) );
323 }
324 else
325 {
326 static int limits[] = { 1, 2, 3, 4, 6, 12, 24, 48, 72 };
327
328 numSteps = qwtStepCount( stepSize * 24, maxMinSteps,
329 limits, sizeof( limits ) / sizeof( int ) );
330 }
331
332 if ( numSteps > 0 )
333 minStepSize = stepSize / numSteps;
334
335 break;
336 }
337 case QwtDate::Week:
338 {
339 const int daysInStep = stepSize * 7;
340
341 if ( maxMinSteps >= daysInStep )
342 {
343 // we want to have one tick per day
344 minStepSize = 1.0 / 7.0;
345 }
346 else
347 {
348 // when the stepSize is more than a week we want to
349 // have a tick for each week
350
351 const int stepSizeInWeeks = stepSize;
352
353 if ( stepSizeInWeeks <= maxMinSteps )
354 {
355 minStepSize = 1;
356 }
357 else
358 {
360 stepSizeInWeeks, maxMinSteps, 10 );
361 }
362 }
363 break;
364 }
365 case QwtDate::Month:
366 {
367 // fractions of months doesn't make any sense
368
369 if ( stepSize < maxMinSteps )
370 maxMinSteps = static_cast< int >( stepSize );
371
372 static int limits[] = { 1, 2, 3, 4, 6, 12 };
373
374 int numSteps = qwtStepCount( stepSize, maxMinSteps,
375 limits, sizeof( limits ) / sizeof( int ) );
376
377 if ( numSteps > 0 )
378 minStepSize = stepSize / numSteps;
379
380 break;
381 }
382 case QwtDate::Year:
383 {
384 if ( stepSize >= maxMinSteps )
385 {
387 stepSize, maxMinSteps, 10 );
388 }
389 else
390 {
391 // something in months
392
393 static int limits[] = { 1, 2, 3, 4, 6, 12 };
394
395 int numSteps = qwtStepCount( 12 * stepSize, maxMinSteps,
396 limits, sizeof( limits ) / sizeof( int ) );
397
398 if ( numSteps > 0 )
399 minStepSize = stepSize / numSteps;
400 }
401
402 break;
403 }
404 default:
405 break;
406 }
407
408 if ( intervalType != QwtDate::Month
409 && minStepSize == 0.0 )
410 {
411 minStepSize = 0.5 * stepSize;
412 }
413
414 return minStepSize;
415}
416
417static QList< double > qwtDstTicks( const QDateTime& dateTime,
418 int secondsMajor, int secondsMinor )
419{
420 if ( secondsMinor <= 0 )
421 return 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
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 = 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
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 = 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
717class 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
749{
750 m_data = new PrivateData( timeSpec );
751}
752
755{
756 delete m_data;
757}
758
765void QwtDateScaleEngine::setTimeSpec( Qt::TimeSpec timeSpec )
766{
767 m_data->timeSpec = timeSpec;
768}
769
775{
776 return m_data->timeSpec;
777}
778
790{
791 m_data->utcOffset = seconds;
792}
793
802{
803 return m_data->utcOffset;
804}
805
816{
817 m_data->week0Type = week0Type;
818}
819
825{
826 return m_data->week0Type;
827}
828
842{
843 m_data->maxWeeks = qMax( weeks, 0 );
844}
845
852{
853 return m_data->maxWeeks;
854}
855
866 const QDateTime& minDate, const QDateTime& maxDate,
867 int maxSteps ) const
868{
869 const double jdMin = minDate.date().toJulianDay();
870 const double jdMax = maxDate.date().toJulianDay();
871
872 if ( ( jdMax - jdMin ) / 365 > maxSteps )
873 return QwtDate::Year;
874
875 const int months = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Month );
876 if ( months > maxSteps * 6 )
877 return QwtDate::Year;
878
879 const int days = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Day );
880 const int weeks = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Week );
881
882 if ( weeks > m_data->maxWeeks )
883 {
884 if ( days > 4 * maxSteps * 7 )
885 return QwtDate::Month;
886 }
887
888 if ( days > maxSteps * 7 )
889 return QwtDate::Week;
890
891 const int hours = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Hour );
892 if ( hours > maxSteps * 24 )
893 return QwtDate::Day;
894
895 const int seconds = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Second );
896
897 if ( seconds >= maxSteps * 3600 )
898 return QwtDate::Hour;
899
900 if ( seconds >= maxSteps * 60 )
901 return QwtDate::Minute;
902
903 if ( seconds >= maxSteps )
904 return QwtDate::Second;
905
907}
908
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
937 interval = interval.symmetrize( reference() );
938
940 interval = interval.extend( reference() );
941
942 if ( interval.width() == 0.0 )
943 interval = buildInterval( interval.minValue() );
944
945 const QDateTime from = toDateTime( interval.minValue() );
946 const QDateTime to = toDateTime( interval.maxValue() );
947
948 if ( from.isValid() && to.isValid() )
949 {
950 if ( maxNumSteps < 1 )
951 maxNumSteps = 1;
952
953 const QwtDate::IntervalType intvType =
954 intervalType( from, to, maxNumSteps );
955
956 const double width = qwtIntervalWidth( from, to, intvType );
957
958 const double stepWidth = qwtDivideScale( width, maxNumSteps, intvType );
959 if ( stepWidth != 0.0 && !testAttribute( QwtScaleEngine::Floating ) )
960 {
961 const QDateTime d1 = alignDate( from, stepWidth, intvType, false );
962 const QDateTime d2 = alignDate( to, stepWidth, intvType, true );
963
964 interval.setMinValue( QwtDate::toDouble( d1 ) );
965 interval.setMaxValue( QwtDate::toDouble( d2 ) );
966 }
967
968 stepSize = stepWidth * qwtMsecsForType( intvType );
969 }
970
971 x1 = interval.minValue();
972 x2 = interval.maxValue();
973
975 {
976 qSwap( x1, x2 );
977 stepSize = -stepSize;
978 }
979}
980
993 int maxMajorSteps, int maxMinorSteps, double stepSize ) const
994{
995 if ( maxMajorSteps < 1 )
996 maxMajorSteps = 1;
997
998 const double min = qwtMinF( x1, x2 );
999 const double max = qwtMaxF( x1, x2 );
1000
1001 const QDateTime from = toDateTime( min );
1002 const QDateTime to = toDateTime( max );
1003
1004 if ( from == to )
1005 return QwtScaleDiv();
1006
1007 stepSize = qAbs( stepSize );
1008 if ( stepSize > 0.0 )
1009 {
1010 // as interval types above hours are not equidistant
1011 // ( even days might have 23/25 hours because of daylight saving )
1012 // the stepSize is used as a hint only
1013
1014 maxMajorSteps = qwtCeil( ( max - min ) / stepSize );
1015 }
1016
1017 const QwtDate::IntervalType intvType =
1018 intervalType( from, to, maxMajorSteps );
1019
1020 QwtScaleDiv scaleDiv;
1021
1022 if ( intvType == QwtDate::Millisecond )
1023 {
1024 // for milliseconds and below we can use the decimal system
1025 scaleDiv = QwtLinearScaleEngine::divideScale( min, max,
1026 maxMajorSteps, maxMinorSteps, stepSize );
1027 }
1028 else
1029 {
1030 const QDateTime minDate = QwtDate::floor( from, intvType );
1031 const QDateTime maxDate = QwtDate::ceil( to, intvType );
1032
1033 scaleDiv = buildScaleDiv( minDate, maxDate,
1034 maxMajorSteps, maxMinorSteps, intvType );
1035
1036 // scaleDiv has been calculated from an extended interval
1037 // adjusted to the step size. We have to shrink it again.
1038
1039 scaleDiv = scaleDiv.bounded( min, max );
1040 }
1041
1042 if ( x1 > x2 )
1043 scaleDiv.invert();
1044
1045 return scaleDiv;
1046}
1047
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 {
1077 {
1078 scaleDiv = qwtDivideToMonths( dt0, maxDate,
1079 stepSize, maxMinorSteps );
1080 }
1081 else if ( intervalType == QwtDate::Year )
1082 {
1083 scaleDiv = qwtDivideToYears( dt0, maxDate,
1084 stepSize, maxMinorSteps );
1085 }
1086 }
1087
1088
1089 return scaleDiv;
1090}
1091
1109 const QDateTime& dateTime, double stepSize,
1110 QwtDate::IntervalType intervalType, bool up ) const
1111{
1112 // what about: (year == 1582 && month == 10 && day > 4 && day < 15) ??
1113
1114 QDateTime dt = dateTime;
1115
1116 if ( dateTime.timeSpec() == Qt::OffsetFromUTC )
1117 {
1118#if QT_VERSION >= 0x050200
1119 dt.setOffsetFromUtc( 0 );
1120#else
1121 dt.setUtcOffset( 0 );
1122#endif
1123 }
1124
1125 switch( intervalType )
1126 {
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
1298QDateTime QwtDateScaleEngine::toDateTime( double value ) const
1299{
1300 QDateTime dt = QwtDate::toDateTime( value, m_data->timeSpec );
1301 if ( !dt.isValid() )
1302 {
1303 const QDate date = ( value <= 0.0 )
1305
1306 dt = QDateTime( date, QTime( 0, 0 ), m_data->timeSpec );
1307 }
1308
1309 if ( m_data->timeSpec == Qt::OffsetFromUTC )
1310 {
1311 dt = dt.addSecs( m_data->utcOffset );
1312#if QT_VERSION >= 0x050200
1313 dt.setOffsetFromUtc( m_data->utcOffset );
1314#else
1315 dt.setUtcOffset( m_data->utcOffset );
1316#endif
1317 }
1318
1319 return dt;
1320}
1321
A collection of methods around date/time values.
Definition qwt_date.h:43
static QDateTime floor(const QDateTime &, IntervalType)
Definition qwt_date.cpp:425
static QDateTime toDateTime(double value, Qt::TimeSpec=Qt::UTC)
Definition qwt_date.cpp:261
static QDate minDate()
Definition qwt_date.cpp:499
static QDateTime ceil(const QDateTime &, IntervalType)
Definition qwt_date.cpp:323
static QDate dateOfWeek0(int year, Week0Type)
Date of the first day of the first week for a year.
Definition qwt_date.cpp:542
IntervalType
Definition qwt_date.h:76
@ Month
The interval is related to months.
Definition qwt_date.h:96
@ Day
The interval is related to days.
Definition qwt_date.h:90
@ Millisecond
The interval is related to milliseconds.
Definition qwt_date.h:78
@ Minute
The interval is related to minutes.
Definition qwt_date.h:84
@ Hour
The interval is related to hours.
Definition qwt_date.h:87
@ Second
The interval is related to seconds.
Definition qwt_date.h:81
@ Week
The interval is related to weeks.
Definition qwt_date.h:93
@ Year
The interval is related to years.
Definition qwt_date.h:99
static int utcOffset(const QDateTime &)
Definition qwt_date.cpp:635
static QDate maxDate()
Definition qwt_date.cpp:521
static double toDouble(const QDateTime &)
Definition qwt_date.cpp:298
QwtDate::Week0Type week0Type() const
virtual ~QwtDateScaleEngine()
Destructor.
virtual QDateTime alignDate(const QDateTime &, double stepSize, QwtDate::IntervalType, bool up) const
QDateTime toDateTime(double) const
virtual QwtScaleDiv divideScale(double x1, double x2, int maxMajorSteps, int maxMinorSteps, double stepSize=0.0) const override
Calculate a scale division for a date/time interval.
void setUtcOffset(int seconds)
void setWeek0Type(QwtDate::Week0Type)
virtual QwtDate::IntervalType intervalType(const QDateTime &, const QDateTime &, int maxSteps) const
Qt::TimeSpec timeSpec() const
QwtDateScaleEngine(Qt::TimeSpec=Qt::LocalTime)
Constructor.
virtual void autoScale(int maxNumSteps, double &x1, double &x2, double &stepSize) const override
void setTimeSpec(Qt::TimeSpec)
A class representing an interval.
double minValue() const
QwtInterval normalized() const
Normalize the limits of the interval.
double width() const
Return the width of an interval.
void setMaxValue(double)
double maxValue() const
QwtInterval extend(double value) const
Extend the interval.
void setMinValue(double)
QwtInterval symmetrize(double value) const
A scale engine for linear scales.
virtual QwtScaleDiv divideScale(double x1, double x2, int maxMajorSteps, int maxMinorSteps, double stepSize=0.0) const override
Calculate a scale division for an interval.
static double divideInterval(double intervalSize, int numSteps, uint base)
A class representing a scale division.
QwtScaleDiv bounded(double lowerBound, double upperBound) const
void setInterval(double lowerBound, double upperBound)
@ MediumTick
Medium ticks.
@ MinorTick
Minor ticks.
@ MajorTick
Major ticks.
void setTicks(int tickType, const QList< double > &)
double reference() const
@ Inverted
Turn the scale upside down.
@ Symmetric
Build a scale which is symmetric to the reference() value.
@ IncludeReference
Build a scale which includes the reference() value.
double upperMargin() const
bool testAttribute(Attribute) const
QwtInterval buildInterval(double value) const
Build an interval around a value.
double lowerMargin() const