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 #if QT_VERSION >= 0x040700
00442     ticks.reserve( 3600 / secondsMinor);
00443 #endif
00444 
00445     for ( int i = 0; i < 3600; i += secondsMinor )
00446         ticks += dstMin + i * 1000.0;
00447 
00448     return ticks;
00449 }
00450 
00451 static QwtScaleDiv qwtDivideToSeconds( 
00452     const QDateTime &minDate, const QDateTime &maxDate,
00453     double stepSize, int maxMinSteps,
00454     QwtDate::IntervalType intervalType ) 
00455 {
00456     // calculate the min step size
00457     double minStepSize = 0;
00458 
00459     if ( maxMinSteps > 1 ) 
00460     {
00461         minStepSize = qwtDivideMajorStep( stepSize, 
00462             maxMinSteps, intervalType );
00463     }
00464 
00465     bool daylightSaving = false;
00466     if ( minDate.timeSpec() == Qt::LocalTime )
00467     {
00468         daylightSaving = intervalType > QwtDate::Hour;
00469         if ( intervalType == QwtDate::Hour )
00470         {
00471             daylightSaving = stepSize > 1;
00472         }
00473     }
00474 
00475     const double s = qwtMsecsForType( intervalType ) / 1000;
00476     const int secondsMajor = static_cast<int>( stepSize * s );
00477     const double secondsMinor = minStepSize * s;
00478     
00479     // UTC excludes daylight savings. So from the difference
00480     // of a date and its UTC counterpart we can find out
00481     // the daylight saving hours
00482 
00483     const double utcOffset = QwtDate::utcOffset( minDate );
00484     double dstOff = 0;
00485 
00486     QList<double> majorTicks;
00487     QList<double> mediumTicks;
00488     QList<double> minorTicks;
00489 
00490     for ( QDateTime dt = minDate; dt <= maxDate; 
00491         dt = dt.addSecs( secondsMajor ) )
00492     {
00493         if ( !dt.isValid() )
00494             break;
00495 
00496         double majorValue = QwtDate::toDouble( dt );
00497 
00498         if ( daylightSaving )
00499         {
00500             const double offset = utcOffset - QwtDate::utcOffset( dt );
00501             majorValue += offset * 1000.0;
00502 
00503             if ( offset > dstOff )
00504             {
00505                 // we add some minor ticks for the DST hour,
00506                 // otherwise the ticks will be unaligned: 0, 2, 3, 5 ...
00507                 minorTicks += qwtDstTicks( 
00508                     dt, secondsMajor, qRound( secondsMinor ) );
00509             }
00510 
00511             dstOff = offset;
00512         }
00513 
00514         if ( majorTicks.isEmpty() || majorTicks.last() != majorValue )
00515             majorTicks += majorValue;
00516 
00517         if ( secondsMinor > 0.0 )
00518         {
00519             const int numMinorSteps = qFloor( secondsMajor / secondsMinor );
00520 
00521             for ( int i = 1; i < numMinorSteps; i++ )
00522             {
00523                 const QDateTime mt = dt.addMSecs( 
00524                     qRound64( i * secondsMinor * 1000 ) );
00525 
00526                 double minorValue = QwtDate::toDouble( mt );
00527                 if ( daylightSaving )
00528                 {
00529                     const double offset = utcOffset - QwtDate::utcOffset( mt );
00530                     minorValue += offset * 1000.0;
00531                 }
00532 
00533                 if ( minorTicks.isEmpty() || minorTicks.last() != minorValue )
00534                 {
00535                     const bool isMedium = ( numMinorSteps % 2 == 0 ) 
00536                         && ( i != 1 ) && ( i == numMinorSteps / 2 );
00537 
00538                     if ( isMedium )
00539                         mediumTicks += minorValue;
00540                     else
00541                         minorTicks += minorValue;
00542                 }
00543             }
00544         }
00545     }
00546 
00547     QwtScaleDiv scaleDiv;
00548 
00549     scaleDiv.setInterval( QwtDate::toDouble( minDate ),
00550         QwtDate::toDouble( maxDate ) );
00551 
00552     scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks );
00553     scaleDiv.setTicks( QwtScaleDiv::MediumTick, mediumTicks );
00554     scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks );
00555 
00556     return scaleDiv;
00557 }
00558 
00559 static QwtScaleDiv qwtDivideToMonths( 
00560     QDateTime &minDate, const QDateTime &maxDate,
00561     double stepSize, int maxMinSteps ) 
00562 {
00563     // months are intervals with non 
00564     // equidistant ( in ms ) steps: we have to build the 
00565     // scale division manually
00566 
00567     int minStepDays = 0;
00568     int minStepSize = 0.0; 
00569 
00570     if ( maxMinSteps > 1 )
00571     {
00572         if ( stepSize == 1 )
00573         {
00574             if ( maxMinSteps >= 30 )
00575                 minStepDays = 1;
00576             else if ( maxMinSteps >= 6 )
00577                 minStepDays = 5;
00578             else if ( maxMinSteps >= 3 )
00579                 minStepDays = 10;
00580             else
00581                 minStepDays = 15;
00582         }
00583         else
00584         {
00585             minStepSize = qwtDivideMajorStep( 
00586                 stepSize, maxMinSteps, QwtDate::Month );
00587         }
00588     }
00589 
00590     QList<double> majorTicks;
00591     QList<double> mediumTicks;
00592     QList<double> minorTicks;
00593 
00594     for ( QDateTime dt = minDate; 
00595         dt <= maxDate; dt = dt.addMonths( stepSize ) )
00596     {
00597         if ( !dt.isValid() )
00598             break;
00599 
00600         majorTicks += QwtDate::toDouble( dt );
00601 
00602         if ( minStepDays > 0 )
00603         {
00604             for ( int days = minStepDays; 
00605                 days < 30; days += minStepDays )
00606             {
00607                 const double tick = QwtDate::toDouble( dt.addDays( days ) );
00608 
00609                 if ( days == 15 && minStepDays != 15 )
00610                     mediumTicks += tick;
00611                 else
00612                     minorTicks += tick;
00613             }
00614         }
00615         else if ( minStepSize > 0.0 )
00616         {
00617             const int numMinorSteps = qRound( stepSize / (double) minStepSize );
00618 
00619             for ( int i = 1; i < numMinorSteps; i++ )
00620             {
00621                 const double minorValue =
00622                     QwtDate::toDouble( dt.addMonths( i * minStepSize ) );
00623 
00624                 if ( ( numMinorSteps % 2 == 0 ) && ( i == numMinorSteps / 2 ) )
00625                     mediumTicks += minorValue;
00626                 else
00627                     minorTicks += minorValue;
00628             }
00629         }
00630     }
00631 
00632     QwtScaleDiv scaleDiv;
00633     scaleDiv.setInterval( QwtDate::toDouble( minDate ),
00634         QwtDate::toDouble( maxDate ) );
00635 
00636     scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks );
00637     scaleDiv.setTicks( QwtScaleDiv::MediumTick, mediumTicks );
00638     scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks );
00639 
00640     return scaleDiv;
00641 }
00642 
00643 static QwtScaleDiv qwtDivideToYears( 
00644     const QDateTime &minDate, const QDateTime &maxDate,
00645     double stepSize, int maxMinSteps ) 
00646 {
00647     QList<double> majorTicks;
00648     QList<double> mediumTicks;
00649     QList<double> minorTicks;
00650 
00651     double minStepSize = 0.0;
00652 
00653     if ( maxMinSteps > 1 )
00654     {
00655         minStepSize = qwtDivideMajorStep( 
00656             stepSize, maxMinSteps, QwtDate::Year );
00657     }
00658 
00659     int numMinorSteps = 0;
00660     if ( minStepSize > 0.0 )
00661         numMinorSteps = qFloor( stepSize / minStepSize );
00662 
00663     bool dateBC = minDate.date().year() < -1;
00664 
00665     for ( QDateTime dt = minDate; dt <= maxDate;
00666         dt = dt.addYears( stepSize ) )
00667     {
00668         if ( dateBC && dt.date().year() > 1 )
00669         {
00670             // there is no year 0 in the Julian calendar
00671             dt = dt.addYears( -1 );
00672             dateBC = false;
00673         }
00674 
00675         if ( !dt.isValid() )
00676             break;
00677 
00678         majorTicks += QwtDate::toDouble( dt );
00679 
00680         for ( int i = 1; i < numMinorSteps; i++ )
00681         {
00682             QDateTime tickDate;
00683 
00684             const double years = qRound( i * minStepSize );
00685             if ( years >= INT_MAX / 12 )
00686             {
00687                 tickDate = dt.addYears( years );
00688             }
00689             else
00690             {
00691                 tickDate = dt.addMonths( qRound( years * 12 ) );
00692             }
00693 
00694             const bool isMedium = ( numMinorSteps > 2 ) &&
00695                 ( numMinorSteps % 2 == 0 ) && ( i == numMinorSteps / 2 );
00696 
00697             const double minorValue = QwtDate::toDouble( tickDate );
00698             if ( isMedium )
00699                 mediumTicks += minorValue;
00700             else
00701                 minorTicks += minorValue;
00702         }
00703 
00704         if ( QwtDate::maxDate().addYears( -stepSize ) < dt.date() )
00705         {
00706             break;
00707         }
00708     }   
00709 
00710     QwtScaleDiv scaleDiv;
00711     scaleDiv.setInterval( QwtDate::toDouble( minDate ),
00712         QwtDate::toDouble( maxDate ) );
00713 
00714     scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks );
00715     scaleDiv.setTicks( QwtScaleDiv::MediumTick, mediumTicks );
00716     scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks );
00717 
00718     return scaleDiv;
00719 }
00720 
00721 class QwtDateScaleEngine::PrivateData
00722 {
00723 public:
00724     explicit PrivateData( Qt::TimeSpec spec ):
00725         timeSpec( spec ),
00726         utcOffset( 0 ),
00727         week0Type( QwtDate::FirstThursday ),
00728         maxWeeks( 4 )
00729     {
00730     }
00731 
00732     Qt::TimeSpec timeSpec;
00733     int utcOffset;
00734     QwtDate::Week0Type week0Type;
00735     int maxWeeks;
00736 };      
00737 
00738 
00751 QwtDateScaleEngine::QwtDateScaleEngine( Qt::TimeSpec timeSpec ):
00752     QwtLinearScaleEngine( 10 )
00753 {
00754     d_data = new PrivateData( timeSpec );
00755 }
00756 
00758 QwtDateScaleEngine::~QwtDateScaleEngine()
00759 {
00760     delete d_data;
00761 }
00762 
00769 void QwtDateScaleEngine::setTimeSpec( Qt::TimeSpec timeSpec )
00770 {
00771     d_data->timeSpec = timeSpec;
00772 }
00773 
00778 Qt::TimeSpec QwtDateScaleEngine::timeSpec() const
00779 {
00780     return d_data->timeSpec;
00781 }
00782 
00793 void QwtDateScaleEngine::setUtcOffset( int seconds )
00794 {
00795     d_data->utcOffset = seconds;
00796 }
00797 
00805 int QwtDateScaleEngine::utcOffset() const
00806 {
00807     return d_data->utcOffset;
00808 }
00809 
00819 void QwtDateScaleEngine::setWeek0Type( QwtDate::Week0Type week0Type )
00820 {
00821     d_data->week0Type = week0Type;
00822 }
00823 
00828 QwtDate::Week0Type QwtDateScaleEngine::week0Type() const
00829 {
00830     return d_data->week0Type;
00831 }
00832 
00845 void QwtDateScaleEngine::setMaxWeeks( int weeks )
00846 {
00847     d_data->maxWeeks = qMax( weeks, 0 );
00848 }
00849 
00855 int QwtDateScaleEngine::maxWeeks() const
00856 {
00857     return d_data->maxWeeks;
00858 }
00859 
00869 QwtDate::IntervalType QwtDateScaleEngine::intervalType( 
00870     const QDateTime &minDate, const QDateTime &maxDate, 
00871     int maxSteps ) const
00872 {
00873     const double jdMin = minDate.date().toJulianDay();
00874     const double jdMax = maxDate.date().toJulianDay();
00875 
00876     if ( ( jdMax - jdMin ) / 365 > maxSteps )
00877         return QwtDate::Year;
00878 
00879     const int months = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Month );
00880     if ( months > maxSteps * 6 )
00881         return QwtDate::Year;
00882 
00883     const int days = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Day );
00884     const int weeks = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Week );
00885 
00886     if ( weeks > d_data->maxWeeks )
00887     {
00888         if ( days > 4 * maxSteps * 7 )
00889             return QwtDate::Month;
00890     }
00891 
00892     if ( days > maxSteps * 7 )
00893         return QwtDate::Week;
00894 
00895     const int hours = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Hour );
00896     if ( hours > maxSteps * 24 )
00897         return QwtDate::Day;
00898 
00899     const int seconds = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Second );
00900 
00901     if ( seconds >= maxSteps * 3600 )
00902         return QwtDate::Hour;
00903 
00904     if ( seconds >= maxSteps * 60 )
00905         return QwtDate::Minute;
00906 
00907     if ( seconds >= maxSteps )
00908         return QwtDate::Second;
00909 
00910     return QwtDate::Millisecond;
00911 }
00912 
00929 void QwtDateScaleEngine::autoScale( int maxNumSteps,
00930     double &x1, double &x2, double &stepSize ) const
00931 {
00932     stepSize = 0.0;
00933 
00934     QwtInterval interval( x1, x2 );
00935     interval = interval.normalized();
00936 
00937     interval.setMinValue( interval.minValue() - lowerMargin() );
00938     interval.setMaxValue( interval.maxValue() + upperMargin() );
00939 
00940     if ( testAttribute( QwtScaleEngine::Symmetric ) )
00941         interval = interval.symmetrize( reference() );
00942 
00943     if ( testAttribute( QwtScaleEngine::IncludeReference ) )
00944         interval = interval.extend( reference() );
00945 
00946     if ( interval.width() == 0.0 )
00947         interval = buildInterval( interval.minValue() );
00948 
00949     const QDateTime from = toDateTime( interval.minValue() );
00950     const QDateTime to = toDateTime( interval.maxValue() );
00951 
00952     if ( from.isValid() && to.isValid() )
00953     {
00954         if ( maxNumSteps < 1 )
00955             maxNumSteps = 1;
00956 
00957         const QwtDate::IntervalType intvType = 
00958             intervalType( from, to, maxNumSteps );
00959 
00960         const double width = qwtIntervalWidth( from, to, intvType );
00961 
00962         const double stepWidth = qwtDivideScale( width, maxNumSteps, intvType );
00963         if ( stepWidth != 0.0 && !testAttribute( QwtScaleEngine::Floating ) )
00964         {
00965             const QDateTime d1 = alignDate( from, stepWidth, intvType, false );
00966             const QDateTime d2 = alignDate( to, stepWidth, intvType, true );
00967 
00968             interval.setMinValue( QwtDate::toDouble( d1 ) );
00969             interval.setMaxValue( QwtDate::toDouble( d2 ) );
00970         }
00971 
00972         stepSize = stepWidth * qwtMsecsForType( intvType );
00973     }
00974 
00975     x1 = interval.minValue();
00976     x2 = interval.maxValue();
00977 
00978     if ( testAttribute( QwtScaleEngine::Inverted ) )
00979     {
00980         qSwap( x1, x2 );
00981         stepSize = -stepSize;
00982     }
00983 }
00984 
00996 QwtScaleDiv QwtDateScaleEngine::divideScale( double x1, double x2,
00997     int maxMajorSteps, int maxMinorSteps, double stepSize ) const
00998 {
00999     if ( maxMajorSteps < 1 )
01000         maxMajorSteps = 1;
01001 
01002     const double min = qMin( x1, x2 );
01003     const double max = qMax( x1, x2 );
01004 
01005     const QDateTime from = toDateTime( min );
01006     const QDateTime to = toDateTime( max );
01007 
01008     if ( from == to )
01009         return QwtScaleDiv();
01010 
01011     stepSize = qAbs( stepSize );
01012     if ( stepSize > 0.0 )
01013     {
01014         // as interval types above hours are not equidistant
01015         // ( even days might have 23/25 hours because of daylight saving )
01016         // the stepSize is used as a hint only
01017 
01018         maxMajorSteps = qCeil( ( max - min ) / stepSize );
01019     }
01020 
01021     const QwtDate::IntervalType intvType = 
01022         intervalType( from, to, maxMajorSteps );
01023 
01024     QwtScaleDiv scaleDiv;
01025 
01026     if ( intvType == QwtDate::Millisecond )
01027     {
01028         // for milliseconds and below we can use the decimal system
01029         scaleDiv = QwtLinearScaleEngine::divideScale( min, max,
01030             maxMajorSteps, maxMinorSteps, stepSize );
01031     }
01032     else
01033     {
01034         const QDateTime minDate = QwtDate::floor( from, intvType );
01035         const QDateTime maxDate = QwtDate::ceil( to, intvType );
01036 
01037         scaleDiv = buildScaleDiv( minDate, maxDate, 
01038             maxMajorSteps, maxMinorSteps, intvType );
01039 
01040         // scaleDiv has been calculated from an extended interval
01041         // adjusted to the step size. We have to shrink it again.
01042 
01043         scaleDiv = scaleDiv.bounded( min, max );
01044     }
01045 
01046     if ( x1 > x2 )
01047         scaleDiv.invert();
01048 
01049     return scaleDiv;
01050 }
01051 
01052 QwtScaleDiv QwtDateScaleEngine::buildScaleDiv( 
01053     const QDateTime &minDate, const QDateTime &maxDate,
01054     int maxMajorSteps, int maxMinorSteps,
01055     QwtDate::IntervalType intervalType ) const
01056 {
01057     // calculate the step size
01058     const double stepSize = qwtDivideScale( 
01059         qwtIntervalWidth( minDate, maxDate, intervalType ), 
01060         maxMajorSteps, intervalType );
01061 
01062     // align minDate to the step size
01063     QDateTime dt0 = alignDate( minDate, stepSize, intervalType, false );
01064     if ( !dt0.isValid() )
01065     {
01066         // the floored date is out of the range of a 
01067         // QDateTime - we ceil instead.
01068         dt0 = alignDate( minDate, stepSize, intervalType, true );
01069     }
01070 
01071     QwtScaleDiv scaleDiv;
01072 
01073     if ( intervalType <= QwtDate::Week )
01074     {
01075         scaleDiv = qwtDivideToSeconds( dt0, maxDate, 
01076             stepSize, maxMinorSteps, intervalType );
01077     }
01078     else
01079     {
01080         if( intervalType == QwtDate::Month )
01081         {
01082             scaleDiv = qwtDivideToMonths( dt0, maxDate,
01083                 stepSize, maxMinorSteps );
01084         }
01085         else if ( intervalType == QwtDate::Year )
01086         {
01087             scaleDiv = qwtDivideToYears( dt0, maxDate,
01088                 stepSize, maxMinorSteps );
01089         }
01090     }
01091 
01092 
01093     return scaleDiv;
01094 }
01095 
01112 QDateTime QwtDateScaleEngine::alignDate( 
01113     const QDateTime &dateTime, double stepSize, 
01114     QwtDate::IntervalType intervalType, bool up ) const
01115 {
01116     // what about: (year == 1582 && month == 10 && day > 4 && day < 15) ??
01117 
01118     QDateTime dt = dateTime;
01119 
01120     if ( dateTime.timeSpec() == Qt::OffsetFromUTC )
01121     {
01122         dt.setUtcOffset( 0 );
01123     }
01124 
01125     switch( intervalType )
01126     {
01127         case QwtDate::Millisecond:
01128         {
01129             const int ms = qwtAlignValue( 
01130                 dt.time().msec(), stepSize, up ) ;
01131 
01132             dt = QwtDate::floor( dateTime, QwtDate::Second );
01133             dt = dt.addMSecs( ms );
01134 
01135             break;
01136         }
01137         case QwtDate::Second:
01138         {
01139             int second = dt.time().second();
01140             if ( up )
01141             {
01142                 if ( dt.time().msec() > 0 )
01143                     second++;
01144             }
01145 
01146             const int s = qwtAlignValue( second, stepSize, up );
01147 
01148             dt = QwtDate::floor( dt, QwtDate::Minute );
01149             dt = dt.addSecs( s );
01150 
01151             break;
01152         }
01153         case QwtDate::Minute:
01154         {
01155             int minute = dt.time().minute();
01156             if ( up )
01157             {
01158                 if ( dt.time().msec() > 0 || dt.time().second() > 0 )
01159                     minute++;
01160             }
01161 
01162             const int m = qwtAlignValue( minute, stepSize, up );
01163 
01164             dt = QwtDate::floor( dt, QwtDate::Hour );
01165             dt = dt.addSecs( m * 60 );
01166 
01167             break;
01168         }
01169         case QwtDate::Hour:
01170         {
01171             int hour = dt.time().hour();
01172             if ( up )
01173             {
01174                 if ( dt.time().msec() > 0 || dt.time().second() > 0
01175                     || dt.time().minute() > 0 )
01176                 {
01177                     hour++;
01178                 }
01179             }
01180             const int h = qwtAlignValue( hour, stepSize, up );
01181 
01182             dt = QwtDate::floor( dt, QwtDate::Day );
01183             dt = dt.addSecs( h * 3600 );
01184 
01185             break;
01186         }
01187         case QwtDate::Day:
01188         {
01189             // What date do we expect f.e. from an alignment of 5 days ??
01190             // Aligning them to the beginning of the year avoids at least
01191             // jumping major ticks when panning
01192 
01193             int day = dt.date().dayOfYear();
01194             if ( up )
01195             {
01196                 if ( dt.time() > QTime( 0, 0 ) )
01197                     day++;
01198             }
01199 
01200             const int d = qwtAlignValue( day, stepSize, up );
01201 
01202             dt = QwtDate::floor( dt, QwtDate::Year );
01203             dt = dt.addDays( d - 1 );
01204 
01205             break;
01206         }
01207         case QwtDate::Week:
01208         {
01209             const QDate date = QwtDate::dateOfWeek0(
01210                 dt.date().year(), d_data->week0Type );
01211 
01212             int numWeeks = date.daysTo( dt.date() ) / 7;
01213             if ( up )
01214             {
01215                 if ( dt.time() > QTime( 0, 0 ) ||
01216                     date.daysTo( dt.date() ) % 7 )
01217                 {
01218                     numWeeks++;
01219                 }
01220             }
01221 
01222             const int d = qwtAlignValue( numWeeks, stepSize, up ) * 7;
01223 
01224             dt = QwtDate::floor( dt, QwtDate::Day );
01225             dt.setDate( date );
01226             dt = dt.addDays( d );
01227 
01228             break;
01229         }
01230         case QwtDate::Month:
01231         {
01232             int month = dt.date().month();
01233             if ( up )
01234             {
01235                 if ( dt.date().day() > 1 ||
01236                     dt.time() > QTime( 0, 0 ) )
01237                 {
01238                     month++;
01239                 }
01240             }
01241 
01242             const int m = qwtAlignValue( month - 1, stepSize, up );
01243 
01244             dt = QwtDate::floor( dt, QwtDate::Year );
01245             dt = dt.addMonths( m );
01246 
01247             break;
01248         }
01249         case QwtDate::Year:
01250         {
01251             int year = dateTime.date().year();
01252             if ( up )
01253             {
01254                 if ( dateTime.date().dayOfYear() > 1 ||
01255                     dt.time() > QTime( 0, 0 ) )
01256                 {
01257                     year++;
01258                 }
01259             }
01260 
01261             const int y = qwtAlignValue( year, stepSize, up );
01262 
01263             dt = QwtDate::floor( dt, QwtDate::Day );
01264             if ( y == 0 )
01265             {
01266                 // there is no year 0 in the Julian calendar
01267                 dt.setDate( QDate( stepSize, 1, 1 ).addYears( -stepSize ) );
01268             }
01269             else
01270             {
01271                 dt.setDate( QDate( y, 1, 1 ) );
01272             }
01273 
01274             break;
01275         }
01276     }
01277 
01278     if ( dateTime.timeSpec() == Qt::OffsetFromUTC )
01279     {
01280         dt.setUtcOffset( dateTime.utcOffset() );
01281     }
01282 
01283     return dt;
01284 }
01285 
01294 QDateTime QwtDateScaleEngine::toDateTime( double value ) const
01295 {
01296     QDateTime dt = QwtDate::toDateTime( value, d_data->timeSpec );
01297     if ( !dt.isValid() )
01298     {
01299         const QDate date = ( value <= 0.0 ) 
01300             ? QwtDate::minDate() : QwtDate::maxDate();
01301 
01302         dt = QDateTime( date, QTime( 0, 0 ), d_data->timeSpec );
01303     }
01304 
01305     if ( d_data->timeSpec == Qt::OffsetFromUTC )
01306     {
01307         dt = dt.addSecs( d_data->utcOffset );
01308         dt.setUtcOffset( d_data->utcOffset );
01309     }
01310 
01311     return dt;
01312 }
01313 


plotjuggler
Author(s): Davide Faconti
autogenerated on Wed Jul 3 2019 19:28:04