qwt_date_scale_engine.cpp
Go to the documentation of this file.
00001 /* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
00002  * Qwt Widget Library
00003  * Copyright (C) 1997   Josef Wilgen
00004  * Copyright (C) 2002   Uwe Rathmann
00005  *
00006  * This library is free software; you can redistribute it and/or
00007  * modify it under the terms of the Qwt License, Version 1.0
00008  *****************************************************************************/
00009 
00010 #include "qwt_date_scale_engine.h"
00011 #include "qwt_math.h"
00012 #include "qwt_transform.h"
00013 #include <qdatetime.h>
00014 #include <limits.h>
00015 
00016 static inline double qwtMsecsForType( QwtDate::IntervalType type )
00017 {
00018     static const double msecs[] =
00019     {
00020         1.0,
00021         1000.0,
00022         60.0 * 1000.0,
00023         3600.0 * 1000.0,
00024         24.0 * 3600.0 * 1000.0,
00025         7.0 * 24.0 * 3600.0 * 1000.0,
00026         30.0 * 24.0 * 3600.0 * 1000.0,
00027         365.0 * 24.0 * 3600.0 * 1000.0,
00028     };
00029 
00030     if ( type < 0 || type >= static_cast<int>( sizeof( msecs ) / sizeof( msecs[0] ) ) )
00031         return 1.0;
00032 
00033     return msecs[ type ];
00034 }
00035 
00036 static inline int qwtAlignValue(
00037     double value, double stepSize, bool up )
00038 {
00039     double d = value / stepSize;
00040     d = up ? ::ceil( d ) : ::floor( d );
00041 
00042     return static_cast<int>( d * stepSize );
00043 }
00044 
00045 static double qwtIntervalWidth( const QDateTime &minDate,
00046     const QDateTime &maxDate, QwtDate::IntervalType intervalType ) 
00047 {
00048     switch( intervalType )
00049     {
00050         case QwtDate::Millisecond:
00051         {
00052             const double secsTo = minDate.secsTo( maxDate );
00053             const double msecs = maxDate.time().msec() -
00054                 minDate.time().msec();
00055 
00056             return secsTo * 1000 + msecs;
00057         }
00058         case QwtDate::Second:
00059         {
00060             return minDate.secsTo( maxDate );
00061         }
00062         case QwtDate::Minute:
00063         {
00064             const double secsTo = minDate.secsTo( maxDate );
00065             return ::floor( secsTo / 60 );
00066         }
00067         case QwtDate::Hour:
00068         {
00069             const double secsTo = minDate.secsTo( maxDate );
00070             return ::floor( secsTo / 3600 );
00071         }
00072         case QwtDate::Day:
00073         {
00074             return minDate.daysTo( maxDate );
00075         }
00076         case QwtDate::Week:
00077         {
00078             return ::floor( minDate.daysTo( maxDate ) / 7.0 );
00079         }
00080         case QwtDate::Month:
00081         {
00082             const double years = 
00083                 double( maxDate.date().year() ) - minDate.date().year();
00084 
00085             int months = maxDate.date().month() - minDate.date().month();
00086             if ( maxDate.date().day() < minDate.date().day() )
00087                 months--;
00088 
00089             return years * 12 + months;
00090         }
00091         case QwtDate::Year:
00092         {
00093             double years = 
00094                 double( maxDate.date().year() ) - minDate.date().year();
00095 
00096             if ( maxDate.date().month() < minDate.date().month() )
00097                 years -= 1.0;
00098 
00099             return years;
00100         }
00101     }
00102 
00103     return 0.0;
00104 }
00105 
00106 static double qwtRoundedIntervalWidth( 
00107     const QDateTime &minDate, const QDateTime &maxDate, 
00108     QwtDate::IntervalType intervalType ) 
00109 {
00110     const QDateTime minD = QwtDate::floor( minDate, intervalType );
00111     const QDateTime maxD = QwtDate::ceil( maxDate, intervalType );
00112 
00113     return qwtIntervalWidth( minD, maxD, intervalType );
00114 }
00115 
00116 static inline int qwtStepCount( int intervalSize, int maxSteps,
00117     const int limits[], size_t numLimits )
00118 {
00119     for ( uint i = 0; i < numLimits; i++ )
00120     {
00121         const int numSteps = intervalSize / limits[ i ];
00122 
00123         if ( numSteps > 1 && numSteps <= maxSteps &&
00124             numSteps * limits[ i ] == intervalSize )
00125         {
00126             return numSteps;
00127         }
00128     }
00129 
00130     return 0;
00131 }
00132 
00133 static int qwtStepSize( int intervalSize, int maxSteps, uint base ) 
00134 {
00135     if ( maxSteps <= 0 )
00136         return 0;
00137 
00138     if ( maxSteps > 2 )
00139     {
00140         for ( int numSteps = maxSteps; numSteps > 1; numSteps-- )
00141         {
00142             const double stepSize = double( intervalSize ) / numSteps;
00143 
00144             const double p = ::floor( ::log( stepSize ) / ::log( double( base ) ) );
00145             const double fraction = qPow( base, p );
00146 
00147             for ( uint n = base; n >= 1; n /= 2 )
00148             {
00149                 if ( qFuzzyCompare( stepSize, n * fraction ) )
00150                     return qRound( stepSize );
00151 
00152                 if ( n == 3 && ( base % 2 ) == 0 )
00153                 {
00154                     if ( qFuzzyCompare( stepSize, 2 * fraction ) )
00155                         return qRound( stepSize );
00156                 }
00157             }
00158         }
00159     }
00160 
00161     return 0;
00162 }
00163 
00164 static int qwtDivideInterval( double intervalSize, int numSteps, 
00165     const int limits[], size_t numLimits )
00166 {
00167     const int v = qCeil( intervalSize / double( numSteps ) );
00168 
00169     for ( uint i = 0; i < numLimits - 1; i++ )
00170     {
00171         if ( v <= limits[i] )
00172             return limits[i];
00173     }
00174 
00175     return limits[ numLimits - 1 ];
00176 }
00177 
00178 static double qwtDivideScale( double intervalSize, int numSteps,
00179     QwtDate::IntervalType intervalType )
00180 {
00181     if ( intervalType != QwtDate::Day )
00182     {
00183         if ( ( intervalSize > numSteps ) && 
00184             ( intervalSize <= 2 * numSteps ) )
00185         {
00186             return 2.0;
00187         }
00188     }
00189 
00190     double stepSize;
00191 
00192     switch( intervalType )
00193     {
00194         case QwtDate::Second:
00195         case QwtDate::Minute:
00196         {
00197             static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 };
00198     
00199             stepSize = qwtDivideInterval( intervalSize, numSteps,
00200                 limits, sizeof( limits ) / sizeof( int ) );
00201 
00202             break;
00203         }
00204         case QwtDate::Hour:
00205         {
00206             static int limits[] = { 1, 2, 3, 4, 6, 12, 24 };
00207     
00208             stepSize = qwtDivideInterval( intervalSize, numSteps,
00209                 limits, sizeof( limits ) / sizeof( int ) );
00210 
00211             break;
00212         }
00213         case QwtDate::Day:
00214         {
00215             const double v = intervalSize / double( numSteps );
00216             if ( v <= 5.0 )
00217                 stepSize = qCeil( v );
00218             else
00219                 stepSize = qCeil( v / 7 ) * 7;
00220 
00221             break;
00222         }
00223         case QwtDate::Week:
00224         {
00225             static int limits[] = { 1, 2, 4, 8, 12, 26, 52 };
00226 
00227             stepSize = qwtDivideInterval( intervalSize, numSteps,
00228                 limits, sizeof( limits ) / sizeof( int ) );
00229 
00230             break;
00231         }
00232         case QwtDate::Month:
00233         {
00234             static int limits[] = { 1, 2, 3, 4, 6, 12 };
00235 
00236             stepSize = qwtDivideInterval( intervalSize, numSteps,
00237                 limits, sizeof( limits ) / sizeof( int ) );
00238 
00239             break;
00240         }
00241         case QwtDate::Year:
00242         case QwtDate::Millisecond:
00243         default:
00244         {
00245             stepSize = QwtScaleArithmetic::divideInterval(
00246                 intervalSize, numSteps, 10 );
00247         }
00248     }
00249 
00250     return stepSize;
00251 }
00252 
00253 static double qwtDivideMajorStep( double stepSize, int maxMinSteps,
00254     QwtDate::IntervalType intervalType )
00255 {
00256     double minStepSize = 0.0;
00257 
00258     switch( intervalType )
00259     {
00260         case QwtDate::Second:
00261         {
00262             minStepSize = qwtStepSize( stepSize, maxMinSteps, 10 );
00263             if ( minStepSize == 0.0 )
00264                 minStepSize = 0.5 * stepSize;
00265 
00266             break;
00267         }
00268         case QwtDate::Minute:
00269         {
00270             static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 };
00271 
00272             int numSteps;
00273 
00274             if ( stepSize > maxMinSteps )
00275             {
00276                 numSteps = qwtStepCount( stepSize, maxMinSteps, 
00277                     limits, sizeof( limits ) / sizeof( int ) );
00278 
00279             }
00280             else
00281             {
00282                 numSteps = qwtStepCount( stepSize * 60, maxMinSteps, 
00283                     limits, sizeof( limits ) / sizeof( int ) );
00284             }
00285 
00286             if ( numSteps > 0 )
00287                 minStepSize = double( stepSize ) / numSteps;
00288 
00289             break;
00290         }
00291         case QwtDate::Hour:
00292         {
00293             int numSteps = 0;
00294 
00295             if ( stepSize > maxMinSteps )
00296             {
00297                 static int limits[] = { 1, 2, 3, 4, 6, 12, 24, 48, 72 };
00298 
00299                 numSteps = qwtStepCount( stepSize, maxMinSteps,
00300                     limits, sizeof( limits ) / sizeof( int ) );
00301             }
00302             else
00303             {
00304                 static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 };
00305 
00306                 numSteps = qwtStepCount( stepSize * 60, maxMinSteps,
00307                     limits, sizeof( limits ) / sizeof( int ) );
00308             }
00309 
00310             if ( numSteps > 0 )
00311                 minStepSize = double( stepSize ) / numSteps;
00312 
00313             break;
00314         }
00315         case QwtDate::Day:
00316         {
00317             int numSteps = 0;
00318 
00319             if ( stepSize > maxMinSteps )
00320             {
00321                 static int limits[] = { 1, 2, 3, 7, 14, 28 };
00322 
00323                 numSteps = qwtStepCount( stepSize, maxMinSteps,
00324                     limits, sizeof( limits ) / sizeof( int ) );
00325             }
00326             else
00327             {
00328                 static int limits[] = { 1, 2, 3, 4, 6, 12, 24, 48, 72 };
00329 
00330                 numSteps = qwtStepCount( stepSize * 24, maxMinSteps,
00331                     limits, sizeof( limits ) / sizeof( int ) );
00332             }
00333 
00334             if ( numSteps > 0 )
00335                 minStepSize = double( stepSize ) / numSteps;
00336 
00337             break;
00338         }
00339         case QwtDate::Week:
00340         {
00341             const int daysInStep = stepSize * 7;
00342 
00343             if ( maxMinSteps >= daysInStep )
00344             {
00345                 // we want to have one tick per day
00346                 minStepSize = 1.0 / 7.0;
00347             }
00348             else
00349             {
00350                 // when the stepSize is more than a week we want to
00351                 // have a tick for each week
00352 
00353                 const int stepSizeInWeeks = stepSize;
00354 
00355                 if ( stepSizeInWeeks <= maxMinSteps )
00356                 {
00357                     minStepSize = 1;
00358                 }
00359                 else
00360                 {
00361                     minStepSize = QwtScaleArithmetic::divideInterval( 
00362                         stepSizeInWeeks, maxMinSteps, 10 );
00363                 }
00364             }
00365             break;
00366         }
00367         case QwtDate::Month:
00368         {
00369             // fractions of months doesn't make any sense
00370 
00371             if ( stepSize < maxMinSteps )
00372                 maxMinSteps = static_cast<int>( stepSize );
00373 
00374             static int limits[] = { 1, 2, 3, 4, 6, 12 };
00375 
00376             int numSteps = qwtStepCount( stepSize, maxMinSteps,
00377                 limits, sizeof( limits ) / sizeof( int ) );
00378 
00379             if ( numSteps > 0 )
00380                 minStepSize = double( stepSize ) / numSteps;
00381 
00382             break;
00383         }
00384         case QwtDate::Year:
00385         {
00386             if ( stepSize >= maxMinSteps )
00387             {
00388                 minStepSize = QwtScaleArithmetic::divideInterval(
00389                     stepSize, maxMinSteps, 10 );
00390             }
00391             else
00392             {
00393                 // something in months
00394 
00395                 static int limits[] = { 1, 2, 3, 4, 6, 12 };
00396 
00397                 int numSteps = qwtStepCount( 12 * stepSize, maxMinSteps,
00398                     limits, sizeof( limits ) / sizeof( int ) );
00399 
00400                 if ( numSteps > 0 )
00401                     minStepSize = double( stepSize ) / numSteps;
00402             }
00403                 
00404             break;
00405         }
00406         default:
00407             break;
00408     }
00409 
00410     if ( intervalType != QwtDate::Month
00411         && minStepSize == 0.0 )
00412     {
00413         minStepSize = 0.5 * stepSize;
00414     }
00415 
00416     return minStepSize;
00417 }
00418 
00419 static QList<double> qwtDstTicks( const QDateTime &dateTime,
00420     int secondsMajor, int secondsMinor )
00421 {
00422     if ( secondsMinor <= 0 )
00423         QList<double>();
00424 
00425     QDateTime minDate = dateTime.addSecs( -secondsMajor );
00426     minDate = QwtDate::floor( minDate, QwtDate::Hour );
00427 
00428     const double utcOffset = QwtDate::utcOffset( dateTime );
00429 
00430     // find the hours where daylight saving time happens
00431 
00432     double dstMin = QwtDate::toDouble( minDate );
00433     while ( minDate < dateTime &&
00434         QwtDate::utcOffset( minDate ) != utcOffset )
00435     {
00436         minDate = minDate.addSecs( 3600 );
00437         dstMin += 3600 * 1000.0;
00438     }
00439 
00440     QList<double> ticks;
00441     for ( int i = 0; i < 3600; i += secondsMinor )
00442         ticks += dstMin + i * 1000.0;
00443 
00444     return ticks;
00445 }
00446 
00447 static QwtScaleDiv qwtDivideToSeconds( 
00448     const QDateTime &minDate, const QDateTime &maxDate,
00449     double stepSize, int maxMinSteps,
00450     QwtDate::IntervalType intervalType ) 
00451 {
00452     // calculate the min step size
00453     double minStepSize = 0;
00454 
00455     if ( maxMinSteps > 1 ) 
00456     {
00457         minStepSize = qwtDivideMajorStep( stepSize, 
00458             maxMinSteps, intervalType );
00459     }
00460 
00461     bool daylightSaving = false;
00462     if ( minDate.timeSpec() == Qt::LocalTime )
00463     {
00464         daylightSaving = intervalType > QwtDate::Hour;
00465         if ( intervalType == QwtDate::Hour )
00466         {
00467             daylightSaving = stepSize > 1;
00468         }
00469     }
00470 
00471     const double s = qwtMsecsForType( intervalType ) / 1000;
00472     const int secondsMajor = static_cast<int>( stepSize * s );
00473     const double secondsMinor = minStepSize * s;
00474     
00475     // UTC excludes daylight savings. So from the difference
00476     // of a date and its UTC counterpart we can find out
00477     // the daylight saving hours
00478 
00479     const double utcOffset = QwtDate::utcOffset( minDate );
00480     double dstOff = 0;
00481 
00482     QList<double> majorTicks;
00483     QList<double> mediumTicks;
00484     QList<double> minorTicks;
00485 
00486     for ( QDateTime dt = minDate; dt <= maxDate; 
00487         dt = dt.addSecs( secondsMajor ) )
00488     {
00489         if ( !dt.isValid() )
00490             break;
00491 
00492         double majorValue = QwtDate::toDouble( dt );
00493 
00494         if ( daylightSaving )
00495         {
00496             const double offset = utcOffset - QwtDate::utcOffset( dt );
00497             majorValue += offset * 1000.0;
00498 
00499             if ( offset > dstOff )
00500             {
00501                 // we add some minor ticks for the DST hour,
00502                 // otherwise the ticks will be unaligned: 0, 2, 3, 5 ...
00503                 minorTicks += qwtDstTicks( 
00504                     dt, secondsMajor, qRound( secondsMinor ) );
00505             }
00506 
00507             dstOff = offset;
00508         }
00509 
00510         if ( majorTicks.isEmpty() || majorTicks.last() != majorValue )
00511             majorTicks += majorValue;
00512 
00513         if ( secondsMinor > 0.0 )
00514         {
00515             const int numMinorSteps = qFloor( secondsMajor / secondsMinor );
00516 
00517             for ( int i = 1; i < numMinorSteps; i++ )
00518             {
00519                 const QDateTime mt = dt.addMSecs( 
00520                     qRound64( i * secondsMinor * 1000 ) );
00521 
00522                 double minorValue = QwtDate::toDouble( mt );
00523                 if ( daylightSaving )
00524                 {
00525                     const double offset = utcOffset - QwtDate::utcOffset( mt );
00526                     minorValue += offset * 1000.0;
00527                 }
00528 
00529                 if ( minorTicks.isEmpty() || minorTicks.last() != minorValue )
00530                 {
00531                     const bool isMedium = ( numMinorSteps % 2 == 0 ) 
00532                         && ( i != 1 ) && ( i == numMinorSteps / 2 );
00533 
00534                     if ( isMedium )
00535                         mediumTicks += minorValue;
00536                     else
00537                         minorTicks += minorValue;
00538                 }
00539             }
00540         }
00541     }
00542 
00543     QwtScaleDiv scaleDiv;
00544 
00545     scaleDiv.setInterval( QwtDate::toDouble( minDate ),
00546         QwtDate::toDouble( maxDate ) );
00547 
00548     scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks );
00549     scaleDiv.setTicks( QwtScaleDiv::MediumTick, mediumTicks );
00550     scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks );
00551 
00552     return scaleDiv;
00553 }
00554 
00555 static QwtScaleDiv qwtDivideToMonths( 
00556     QDateTime &minDate, const QDateTime &maxDate,
00557     double stepSize, int maxMinSteps ) 
00558 {
00559     // months are intervals with non 
00560     // equidistant ( in ms ) steps: we have to build the 
00561     // scale division manually
00562 
00563     int minStepDays = 0;
00564     int minStepSize = 0.0; 
00565 
00566     if ( maxMinSteps > 1 )
00567     {
00568         if ( stepSize == 1 )
00569         {
00570             if ( maxMinSteps >= 30 )
00571                 minStepDays = 1;
00572             else if ( maxMinSteps >= 6 )
00573                 minStepDays = 5;
00574             else if ( maxMinSteps >= 3 )
00575                 minStepDays = 10;
00576             else
00577                 minStepDays = 15;
00578         }
00579         else
00580         {
00581             minStepSize = qwtDivideMajorStep( 
00582                 stepSize, maxMinSteps, QwtDate::Month );
00583         }
00584     }
00585 
00586     QList<double> majorTicks;
00587     QList<double> mediumTicks;
00588     QList<double> minorTicks;
00589 
00590     for ( QDateTime dt = minDate; 
00591         dt <= maxDate; dt = dt.addMonths( stepSize ) )
00592     {
00593         if ( !dt.isValid() )
00594             break;
00595 
00596         majorTicks += QwtDate::toDouble( dt );
00597 
00598         if ( minStepDays > 0 )
00599         {
00600             for ( int days = minStepDays; 
00601                 days < 30; days += minStepDays )
00602             {
00603                 const double tick = QwtDate::toDouble( dt.addDays( days ) );
00604 
00605                 if ( days == 15 && minStepDays != 15 )
00606                     mediumTicks += tick;
00607                 else
00608                     minorTicks += tick;
00609             }
00610         }
00611         else if ( minStepSize > 0.0 )
00612         {
00613             const int numMinorSteps = qRound( stepSize / (double) minStepSize );
00614 
00615             for ( int i = 1; i < numMinorSteps; i++ )
00616             {
00617                 const double minorValue =
00618                     QwtDate::toDouble( dt.addMonths( i * minStepSize ) );
00619 
00620                 if ( ( numMinorSteps % 2 == 0 ) && ( i == numMinorSteps / 2 ) )
00621                     mediumTicks += minorValue;
00622                 else
00623                     minorTicks += minorValue;
00624             }
00625         }
00626     }
00627 
00628     QwtScaleDiv scaleDiv;
00629     scaleDiv.setInterval( QwtDate::toDouble( minDate ),
00630         QwtDate::toDouble( maxDate ) );
00631 
00632     scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks );
00633     scaleDiv.setTicks( QwtScaleDiv::MediumTick, mediumTicks );
00634     scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks );
00635 
00636     return scaleDiv;
00637 }
00638 
00639 static QwtScaleDiv qwtDivideToYears( 
00640     const QDateTime &minDate, const QDateTime &maxDate,
00641     double stepSize, int maxMinSteps ) 
00642 {
00643     QList<double> majorTicks;
00644     QList<double> mediumTicks;
00645     QList<double> minorTicks;
00646 
00647     double minStepSize = 0.0;
00648 
00649     if ( maxMinSteps > 1 )
00650     {
00651         minStepSize = qwtDivideMajorStep( 
00652             stepSize, maxMinSteps, QwtDate::Year );
00653     }
00654 
00655     int numMinorSteps = 0;
00656     if ( minStepSize > 0.0 )
00657         numMinorSteps = qFloor( stepSize / minStepSize );
00658 
00659     bool dateBC = minDate.date().year() < -1;
00660 
00661     for ( QDateTime dt = minDate; dt <= maxDate;
00662         dt = dt.addYears( stepSize ) )
00663     {
00664         if ( dateBC && dt.date().year() > 1 )
00665         {
00666             // there is no year 0 in the Julian calendar
00667             dt = dt.addYears( -1 );
00668             dateBC = false;
00669         }
00670 
00671         if ( !dt.isValid() )
00672             break;
00673 
00674         majorTicks += QwtDate::toDouble( dt );
00675 
00676         for ( int i = 1; i < numMinorSteps; i++ )
00677         {
00678             QDateTime tickDate;
00679 
00680             const double years = qRound( i * minStepSize );
00681             if ( years >= INT_MAX / 12 )
00682             {
00683                 tickDate = dt.addYears( years );
00684             }
00685             else
00686             {
00687                 tickDate = dt.addMonths( qRound( years * 12 ) );
00688             }
00689 
00690             const bool isMedium = ( numMinorSteps > 2 ) &&
00691                 ( numMinorSteps % 2 == 0 ) && ( i == numMinorSteps / 2 );
00692 
00693             const double minorValue = QwtDate::toDouble( tickDate );
00694             if ( isMedium )
00695                 mediumTicks += minorValue;
00696             else
00697                 minorTicks += minorValue;
00698         }
00699 
00700         if ( QwtDate::maxDate().addYears( -stepSize ) < dt.date() )
00701         {
00702             break;
00703         }
00704     }   
00705 
00706     QwtScaleDiv scaleDiv;
00707     scaleDiv.setInterval( QwtDate::toDouble( minDate ),
00708         QwtDate::toDouble( maxDate ) );
00709 
00710     scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks );
00711     scaleDiv.setTicks( QwtScaleDiv::MediumTick, mediumTicks );
00712     scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks );
00713 
00714     return scaleDiv;
00715 }
00716 
00717 class QwtDateScaleEngine::PrivateData
00718 {
00719 public:
00720     explicit PrivateData( Qt::TimeSpec spec ):
00721         timeSpec( spec ),
00722         utcOffset( 0 ),
00723         week0Type( QwtDate::FirstThursday ),
00724         maxWeeks( 4 )
00725     {
00726     }
00727 
00728     Qt::TimeSpec timeSpec;
00729     int utcOffset;
00730     QwtDate::Week0Type week0Type;
00731     int maxWeeks;
00732 };      
00733 
00734 
00747 QwtDateScaleEngine::QwtDateScaleEngine( Qt::TimeSpec timeSpec ):
00748     QwtLinearScaleEngine( 10 )
00749 {
00750     d_data = new PrivateData( timeSpec );
00751 }
00752 
00754 QwtDateScaleEngine::~QwtDateScaleEngine()
00755 {
00756     delete d_data;
00757 }
00758 
00765 void QwtDateScaleEngine::setTimeSpec( Qt::TimeSpec timeSpec )
00766 {
00767     d_data->timeSpec = timeSpec;
00768 }
00769 
00774 Qt::TimeSpec QwtDateScaleEngine::timeSpec() const
00775 {
00776     return d_data->timeSpec;
00777 }
00778 
00789 void QwtDateScaleEngine::setUtcOffset( int seconds )
00790 {
00791     d_data->utcOffset = seconds;
00792 }
00793 
00801 int QwtDateScaleEngine::utcOffset() const
00802 {
00803     return d_data->utcOffset;
00804 }
00805 
00815 void QwtDateScaleEngine::setWeek0Type( QwtDate::Week0Type week0Type )
00816 {
00817     d_data->week0Type = week0Type;
00818 }
00819 
00824 QwtDate::Week0Type QwtDateScaleEngine::week0Type() const
00825 {
00826     return d_data->week0Type;
00827 }
00828 
00841 void QwtDateScaleEngine::setMaxWeeks( int weeks )
00842 {
00843     d_data->maxWeeks = qMax( weeks, 0 );
00844 }
00845 
00851 int QwtDateScaleEngine::maxWeeks() const
00852 {
00853     return d_data->maxWeeks;
00854 }
00855 
00865 QwtDate::IntervalType QwtDateScaleEngine::intervalType( 
00866     const QDateTime &minDate, const QDateTime &maxDate, 
00867     int maxSteps ) const
00868 {
00869     const double jdMin = minDate.date().toJulianDay();
00870     const double jdMax = maxDate.date().toJulianDay();
00871 
00872     if ( ( jdMax - jdMin ) / 365 > maxSteps )
00873         return QwtDate::Year;
00874 
00875     const int months = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Month );
00876     if ( months > maxSteps * 6 )
00877         return QwtDate::Year;
00878 
00879     const int days = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Day );
00880     const int weeks = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Week );
00881 
00882     if ( weeks > d_data->maxWeeks )
00883     {
00884         if ( days > 4 * maxSteps * 7 )
00885             return QwtDate::Month;
00886     }
00887 
00888     if ( days > maxSteps * 7 )
00889         return QwtDate::Week;
00890 
00891     const int hours = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Hour );
00892     if ( hours > maxSteps * 24 )
00893         return QwtDate::Day;
00894 
00895     const int seconds = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Second );
00896 
00897     if ( seconds >= maxSteps * 3600 )
00898         return QwtDate::Hour;
00899 
00900     if ( seconds >= maxSteps * 60 )
00901         return QwtDate::Minute;
00902 
00903     if ( seconds >= maxSteps )
00904         return QwtDate::Second;
00905 
00906     return QwtDate::Millisecond;
00907 }
00908 
00925 void QwtDateScaleEngine::autoScale( int maxNumSteps,
00926     double &x1, double &x2, double &stepSize ) const
00927 {
00928     stepSize = 0.0;
00929 
00930     QwtInterval interval( x1, x2 );
00931     interval = interval.normalized();
00932 
00933     interval.setMinValue( interval.minValue() - lowerMargin() );
00934     interval.setMaxValue( interval.maxValue() + upperMargin() );
00935 
00936     if ( testAttribute( QwtScaleEngine::Symmetric ) )
00937         interval = interval.symmetrize( reference() );
00938 
00939     if ( testAttribute( QwtScaleEngine::IncludeReference ) )
00940         interval = interval.extend( reference() );
00941 
00942     if ( interval.width() == 0.0 )
00943         interval = buildInterval( interval.minValue() );
00944 
00945     const QDateTime from = toDateTime( interval.minValue() );
00946     const QDateTime to = toDateTime( interval.maxValue() );
00947 
00948     if ( from.isValid() && to.isValid() )
00949     {
00950         if ( maxNumSteps < 1 )
00951             maxNumSteps = 1;
00952 
00953         const QwtDate::IntervalType intvType = 
00954             intervalType( from, to, maxNumSteps );
00955 
00956         const double width = qwtIntervalWidth( from, to, intvType );
00957 
00958         const double stepWidth = qwtDivideScale( width, maxNumSteps, intvType );
00959         if ( stepWidth != 0.0 && !testAttribute( QwtScaleEngine::Floating ) )
00960         {
00961             const QDateTime d1 = alignDate( from, stepWidth, intvType, false );
00962             const QDateTime d2 = alignDate( to, stepWidth, intvType, true );
00963 
00964             interval.setMinValue( QwtDate::toDouble( d1 ) );
00965             interval.setMaxValue( QwtDate::toDouble( d2 ) );
00966         }
00967 
00968         stepSize = stepWidth * qwtMsecsForType( intvType );
00969     }
00970 
00971     x1 = interval.minValue();
00972     x2 = interval.maxValue();
00973 
00974     if ( testAttribute( QwtScaleEngine::Inverted ) )
00975     {
00976         qSwap( x1, x2 );
00977         stepSize = -stepSize;
00978     }
00979 }
00980 
00992 QwtScaleDiv QwtDateScaleEngine::divideScale( double x1, double x2,
00993     int maxMajorSteps, int maxMinorSteps, double stepSize ) const
00994 {
00995     if ( maxMajorSteps < 1 )
00996         maxMajorSteps = 1;
00997 
00998     const double min = qMin( x1, x2 );
00999     const double max = qMax( x1, x2 );
01000 
01001     const QDateTime from = toDateTime( min );
01002     const QDateTime to = toDateTime( max );
01003 
01004     if ( from == to )
01005         return QwtScaleDiv();
01006 
01007     stepSize = qAbs( stepSize );
01008     if ( stepSize > 0.0 )
01009     {
01010         // as interval types above hours are not equidistant
01011         // ( even days might have 23/25 hours because of daylight saving )
01012         // the stepSize is used as a hint only
01013 
01014         maxMajorSteps = qCeil( ( max - min ) / stepSize );
01015     }
01016 
01017     const QwtDate::IntervalType intvType = 
01018         intervalType( from, to, maxMajorSteps );
01019 
01020     QwtScaleDiv scaleDiv;
01021 
01022     if ( intvType == QwtDate::Millisecond )
01023     {
01024         // for milliseconds and below we can use the decimal system
01025         scaleDiv = QwtLinearScaleEngine::divideScale( min, max,
01026             maxMajorSteps, maxMinorSteps, stepSize );
01027     }
01028     else
01029     {
01030         const QDateTime minDate = QwtDate::floor( from, intvType );
01031         const QDateTime maxDate = QwtDate::ceil( to, intvType );
01032 
01033         scaleDiv = buildScaleDiv( minDate, maxDate, 
01034             maxMajorSteps, maxMinorSteps, intvType );
01035 
01036         // scaleDiv has been calculated from an extended interval
01037         // adjusted to the step size. We have to shrink it again.
01038 
01039         scaleDiv = scaleDiv.bounded( min, max );
01040     }
01041 
01042     if ( x1 > x2 )
01043         scaleDiv.invert();
01044 
01045     return scaleDiv;
01046 }
01047 
01048 QwtScaleDiv QwtDateScaleEngine::buildScaleDiv( 
01049     const QDateTime &minDate, const QDateTime &maxDate,
01050     int maxMajorSteps, int maxMinorSteps,
01051     QwtDate::IntervalType intervalType ) const
01052 {
01053     // calculate the step size
01054     const double stepSize = qwtDivideScale( 
01055         qwtIntervalWidth( minDate, maxDate, intervalType ), 
01056         maxMajorSteps, intervalType );
01057 
01058     // align minDate to the step size
01059     QDateTime dt0 = alignDate( minDate, stepSize, intervalType, false );
01060     if ( !dt0.isValid() )
01061     {
01062         // the floored date is out of the range of a 
01063         // QDateTime - we ceil instead.
01064         dt0 = alignDate( minDate, stepSize, intervalType, true );
01065     }
01066 
01067     QwtScaleDiv scaleDiv;
01068 
01069     if ( intervalType <= QwtDate::Week )
01070     {
01071         scaleDiv = qwtDivideToSeconds( dt0, maxDate, 
01072             stepSize, maxMinorSteps, intervalType );
01073     }
01074     else
01075     {
01076         if( intervalType == QwtDate::Month )
01077         {
01078             scaleDiv = qwtDivideToMonths( dt0, maxDate,
01079                 stepSize, maxMinorSteps );
01080         }
01081         else if ( intervalType == QwtDate::Year )
01082         {
01083             scaleDiv = qwtDivideToYears( dt0, maxDate,
01084                 stepSize, maxMinorSteps );
01085         }
01086     }
01087 
01088 
01089     return scaleDiv;
01090 }
01091 
01108 QDateTime QwtDateScaleEngine::alignDate( 
01109     const QDateTime &dateTime, double stepSize, 
01110     QwtDate::IntervalType intervalType, bool up ) const
01111 {
01112     // what about: (year == 1582 && month == 10 && day > 4 && day < 15) ??
01113 
01114     QDateTime dt = dateTime;
01115 
01116     if ( dateTime.timeSpec() == Qt::OffsetFromUTC )
01117     {
01118         dt.setUtcOffset( 0 );
01119     }
01120 
01121     switch( intervalType )
01122     {
01123         case QwtDate::Millisecond:
01124         {
01125             const int ms = qwtAlignValue( 
01126                 dt.time().msec(), stepSize, up ) ;
01127 
01128             dt = QwtDate::floor( dateTime, QwtDate::Second );
01129             dt = dt.addMSecs( ms );
01130 
01131             break;
01132         }
01133         case QwtDate::Second:
01134         {
01135             int second = dt.time().second();
01136             if ( up )
01137             {
01138                 if ( dt.time().msec() > 0 )
01139                     second++;
01140             }
01141 
01142             const int s = qwtAlignValue( second, stepSize, up );
01143 
01144             dt = QwtDate::floor( dt, QwtDate::Minute );
01145             dt = dt.addSecs( s );
01146 
01147             break;
01148         }
01149         case QwtDate::Minute:
01150         {
01151             int minute = dt.time().minute();
01152             if ( up )
01153             {
01154                 if ( dt.time().msec() > 0 || dt.time().second() > 0 )
01155                     minute++;
01156             }
01157 
01158             const int m = qwtAlignValue( minute, stepSize, up );
01159 
01160             dt = QwtDate::floor( dt, QwtDate::Hour );
01161             dt = dt.addSecs( m * 60 );
01162 
01163             break;
01164         }
01165         case QwtDate::Hour:
01166         {
01167             int hour = dt.time().hour();
01168             if ( up )
01169             {
01170                 if ( dt.time().msec() > 0 || dt.time().second() > 0
01171                     || dt.time().minute() > 0 )
01172                 {
01173                     hour++;
01174                 }
01175             }
01176             const int h = qwtAlignValue( hour, stepSize, up );
01177 
01178             dt = QwtDate::floor( dt, QwtDate::Day );
01179             dt = dt.addSecs( h * 3600 );
01180 
01181             break;
01182         }
01183         case QwtDate::Day:
01184         {
01185             // What date do we expect f.e. from an alignment of 5 days ??
01186             // Aligning them to the beginning of the year avoids at least
01187             // jumping major ticks when panning
01188 
01189             int day = dt.date().dayOfYear();
01190             if ( up )
01191             {
01192                 if ( dt.time() > QTime( 0, 0 ) )
01193                     day++;
01194             }
01195 
01196             const int d = qwtAlignValue( day, stepSize, up );
01197 
01198             dt = QwtDate::floor( dt, QwtDate::Year );
01199             dt = dt.addDays( d - 1 );
01200 
01201             break;
01202         }
01203         case QwtDate::Week:
01204         {
01205             const QDate date = QwtDate::dateOfWeek0(
01206                 dt.date().year(), d_data->week0Type );
01207 
01208             int numWeeks = date.daysTo( dt.date() ) / 7;
01209             if ( up )
01210             {
01211                 if ( dt.time() > QTime( 0, 0 ) ||
01212                     date.daysTo( dt.date() ) % 7 )
01213                 {
01214                     numWeeks++;
01215                 }
01216             }
01217 
01218             const int d = qwtAlignValue( numWeeks, stepSize, up ) * 7;
01219 
01220             dt = QwtDate::floor( dt, QwtDate::Day );
01221             dt.setDate( date );
01222             dt = dt.addDays( d );
01223 
01224             break;
01225         }
01226         case QwtDate::Month:
01227         {
01228             int month = dt.date().month();
01229             if ( up )
01230             {
01231                 if ( dt.date().day() > 1 ||
01232                     dt.time() > QTime( 0, 0 ) )
01233                 {
01234                     month++;
01235                 }
01236             }
01237 
01238             const int m = qwtAlignValue( month - 1, stepSize, up );
01239 
01240             dt = QwtDate::floor( dt, QwtDate::Year );
01241             dt = dt.addMonths( m );
01242 
01243             break;
01244         }
01245         case QwtDate::Year:
01246         {
01247             int year = dateTime.date().year();
01248             if ( up )
01249             {
01250                 if ( dateTime.date().dayOfYear() > 1 ||
01251                     dt.time() > QTime( 0, 0 ) )
01252                 {
01253                     year++;
01254                 }
01255             }
01256 
01257             const int y = qwtAlignValue( year, stepSize, up );
01258 
01259             dt = QwtDate::floor( dt, QwtDate::Day );
01260             if ( y == 0 )
01261             {
01262                 // there is no year 0 in the Julian calendar
01263                 dt.setDate( QDate( stepSize, 1, 1 ).addYears( -stepSize ) );
01264             }
01265             else
01266             {
01267                 dt.setDate( QDate( y, 1, 1 ) );
01268             }
01269 
01270             break;
01271         }
01272     }
01273 
01274     if ( dateTime.timeSpec() == Qt::OffsetFromUTC )
01275     {
01276         dt.setUtcOffset( dateTime.utcOffset() );
01277     }
01278 
01279     return dt;
01280 }
01281 
01290 QDateTime QwtDateScaleEngine::toDateTime( double value ) const
01291 {
01292     QDateTime dt = QwtDate::toDateTime( value, d_data->timeSpec );
01293     if ( !dt.isValid() )
01294     {
01295         const QDate date = ( value <= 0.0 ) 
01296             ? QwtDate::minDate() : QwtDate::maxDate();
01297 
01298         dt = QDateTime( date, QTime( 0, 0 ), d_data->timeSpec );
01299     }
01300 
01301     if ( d_data->timeSpec == Qt::OffsetFromUTC )
01302     {
01303         dt = dt.addSecs( d_data->utcOffset );
01304         dt.setUtcOffset( d_data->utcOffset );
01305     }
01306 
01307     return dt;
01308 }
01309 


plotjuggler
Author(s): Davide Faconti
autogenerated on Fri Sep 1 2017 02:41:56