qwt_scale_engine.cpp

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 <cfloat>
00011 #include "qwt_math.h"
00012 #include "qwt_scale_map.h"
00013 #include "qwt_scale_engine.h"
00014 
00015 static const double _eps = DBL_EPSILON;
00016 
00029 int QwtScaleArithmetic::compareEps(double value1, double value2, 
00030     double intervalSize) 
00031 {
00032     const double eps = qwtAbs(_eps * intervalSize);
00033 
00034     if ( value2 - value1 > eps )
00035         return -1;
00036 
00037     if ( value1 - value2 > eps )
00038         return 1;
00039 
00040     return 0;
00041 }
00042 
00051 double QwtScaleArithmetic::ceilEps(double value, 
00052     double intervalSize) 
00053 {
00054     const double eps = _eps * intervalSize;
00055 
00056     value = (value - eps) / intervalSize;
00057     return ceil(value) * intervalSize;
00058 }
00059 
00068 double QwtScaleArithmetic::floorEps(double value, double intervalSize) 
00069 {
00070     const double eps = _eps * intervalSize;
00071 
00072     value = (value + eps) / intervalSize;
00073     return floor(value) * intervalSize;
00074 }
00075 
00076 /*
00077   \brief Divide an interval into steps
00078 
00079   \f$stepSize = (intervalSize - intervalSize * 10e^{-6}) / numSteps\f$
00080 
00081   \param intervalSize Interval size
00082   \param numSteps Number of steps
00083   \return Step size
00084 */
00085 double QwtScaleArithmetic::divideEps(double intervalSize, double numSteps) 
00086 {
00087     if ( numSteps == 0.0 || intervalSize == 0.0 )
00088         return 0.0;
00089 
00090     return (intervalSize - (_eps * intervalSize)) / numSteps;
00091 } 
00092 
00099 double QwtScaleArithmetic::ceil125(double x) 
00100 {
00101     if (x == 0.0) 
00102         return 0.0;
00103 
00104     const double sign = (x > 0) ? 1.0 : -1.0;
00105     const double lx = log10(fabs(x));
00106     const double p10 = floor(lx);
00107     
00108     double fr = pow(10.0, lx - p10);
00109     if (fr <=1.0)
00110        fr = 1.0; 
00111     else if (fr <= 2.0)
00112        fr = 2.0;
00113     else if (fr <= 5.0) 
00114        fr = 5.0;
00115     else
00116        fr = 10.0;
00117 
00118     return sign * fr * pow(10.0, p10);
00119 }
00120 
00127 double QwtScaleArithmetic::floor125(double x) 
00128 {
00129     if (x == 0.0)
00130         return 0.0;
00131 
00132     double sign = (x > 0) ? 1.0 : -1.0;
00133     const double lx = log10(fabs(x));
00134     const double p10 = floor(lx);
00135 
00136     double fr = pow(10.0, lx - p10);
00137     if (fr >= 10.0)
00138        fr = 10.0;
00139     else if (fr >= 5.0)
00140        fr = 5.0;
00141     else if (fr >= 2.0)
00142        fr = 2.0;
00143     else
00144        fr = 1.0;
00145 
00146     return sign * fr * pow(10.0, p10);
00147 }
00148 
00149 class QwtScaleEngine::PrivateData
00150 {
00151 public:
00152     PrivateData():
00153         attributes(QwtScaleEngine::NoAttribute),
00154         loMargin(0.0),
00155         hiMargin(0.0),
00156         referenceValue(0.0)
00157     {
00158     }
00159 
00160     int attributes;       // scale attributes
00161 
00162     double loMargin;      // margins
00163     double hiMargin;
00164 
00165     double referenceValue; // reference value
00166 
00167 };
00168 
00170 QwtScaleEngine::QwtScaleEngine()
00171 {
00172     d_data = new PrivateData;
00173 }
00174 
00175 
00177 QwtScaleEngine::~QwtScaleEngine ()
00178 {
00179     delete d_data;
00180 }
00181 
00188 double QwtScaleEngine::loMargin() const 
00189 { 
00190     return d_data->loMargin; 
00191 }
00192 
00199 double QwtScaleEngine::hiMargin() const 
00200 { 
00201     return d_data->hiMargin; 
00202 }
00203 
00220 void QwtScaleEngine::setMargins(double mlo, double mhi)
00221 {
00222     d_data->loMargin = qwtMax(mlo,0.0);
00223     d_data->hiMargin = qwtMax(mhi,0.0);
00224 }
00225 
00234 double QwtScaleEngine::divideInterval(
00235     double intervalSize, int numSteps) const
00236 {
00237     if ( numSteps <= 0 )
00238         return 0.0;
00239 
00240     double v = QwtScaleArithmetic::divideEps(intervalSize, numSteps);
00241     return QwtScaleArithmetic::ceil125(v);
00242 }
00243 
00252 bool QwtScaleEngine::contains(
00253     const QwtDoubleInterval &interval, double value) const
00254 {
00255     if (!interval.isValid() )
00256         return false;
00257     
00258     if ( QwtScaleArithmetic::compareEps(value, 
00259         interval.minValue(), interval.width()) < 0 )
00260     {
00261         return false;
00262     }
00263 
00264     if ( QwtScaleArithmetic::compareEps(value, 
00265         interval.maxValue(), interval.width()) > 0 )
00266     {
00267         return false;
00268     }
00269 
00270     return true;
00271 }
00272 
00281 QwtValueList QwtScaleEngine::strip( 
00282     const QwtValueList& ticks, 
00283     const QwtDoubleInterval &interval) const
00284 {
00285     if ( !interval.isValid() || ticks.count() == 0 )
00286         return QwtValueList();
00287 
00288     if ( contains(interval, ticks.first())
00289         && contains(interval, ticks.last()) )
00290     {
00291         return ticks;
00292     }
00293 
00294     QwtValueList strippedTicks;
00295     for ( int i = 0; i < (int)ticks.count(); i++ )
00296     {
00297         if ( contains(interval, ticks[i]) )
00298             strippedTicks += ticks[i];
00299     }
00300     return strippedTicks;
00301 }
00302 
00310 QwtDoubleInterval QwtScaleEngine::buildInterval(double v) const
00311 {
00312     const double delta = (v == 0.0) ? 0.5 : qwtAbs(0.5 * v);
00313     return QwtDoubleInterval(v - delta, v + delta);
00314 }
00315 
00340 void QwtScaleEngine::setAttribute(Attribute attribute, bool on)
00341 {
00342     if (on)
00343        d_data->attributes |= attribute;
00344     else
00345        d_data->attributes &= (~attribute);
00346 }
00347 
00354 bool QwtScaleEngine::testAttribute(Attribute attribute) const
00355 {
00356     return bool(d_data->attributes & attribute);
00357 }
00358 
00365 void QwtScaleEngine::setAttributes(int attributes)
00366 {
00367     d_data->attributes = attributes;
00368 }
00369 
00373 int QwtScaleEngine::attributes() const
00374 {
00375     return d_data->attributes;
00376 }
00377 
00385 void QwtScaleEngine::setReference(double r)
00386 {
00387     d_data->referenceValue = r;
00388 }
00389 
00394 double QwtScaleEngine::reference() const 
00395 { 
00396     return d_data->referenceValue; 
00397 }
00398 
00402 QwtScaleTransformation *QwtLinearScaleEngine::transformation() const
00403 {
00404     return new QwtScaleTransformation(QwtScaleTransformation::Linear);
00405 }
00406 
00417 void QwtLinearScaleEngine::autoScale(int maxNumSteps, 
00418     double &x1, double &x2, double &stepSize) const
00419 {
00420     QwtDoubleInterval interval(x1, x2);
00421     interval = interval.normalized();
00422 
00423     interval.setMinValue(interval.minValue() - loMargin());
00424     interval.setMaxValue(interval.maxValue() + hiMargin());
00425 
00426     if (testAttribute(QwtScaleEngine::Symmetric))
00427         interval = interval.symmetrize(reference());
00428  
00429     if (testAttribute(QwtScaleEngine::IncludeReference))
00430         interval = interval.extend(reference());
00431 
00432     if (interval.width() == 0.0)
00433         interval = buildInterval(interval.minValue());
00434 
00435     stepSize = divideInterval(interval.width(), qwtMax(maxNumSteps, 1));
00436 
00437     if ( !testAttribute(QwtScaleEngine::Floating) )
00438         interval = align(interval, stepSize);
00439 
00440     x1 = interval.minValue();
00441     x2 = interval.maxValue();
00442 
00443     if (testAttribute(QwtScaleEngine::Inverted))
00444     {
00445         qSwap(x1, x2);
00446         stepSize = -stepSize;
00447     }
00448 }
00449 
00462 QwtScaleDiv QwtLinearScaleEngine::divideScale(double x1, double x2,
00463     int maxMajSteps, int maxMinSteps, double stepSize) const
00464 {
00465     QwtDoubleInterval interval = QwtDoubleInterval(x1, x2).normalized();
00466     if (interval.width() <= 0 )
00467         return QwtScaleDiv();
00468 
00469     stepSize = qwtAbs(stepSize);
00470     if ( stepSize == 0.0 )
00471     {
00472         if ( maxMajSteps < 1 )
00473             maxMajSteps = 1;
00474 
00475         stepSize = divideInterval(interval.width(), maxMajSteps);
00476     }
00477 
00478     QwtScaleDiv scaleDiv;
00479 
00480     if ( stepSize != 0.0 )
00481     {
00482         QwtValueList ticks[QwtScaleDiv::NTickTypes];
00483         buildTicks(interval, stepSize, maxMinSteps, ticks);
00484 
00485         scaleDiv = QwtScaleDiv(interval, ticks);
00486     }
00487 
00488     if ( x1 > x2 )
00489         scaleDiv.invert();
00490 
00491     return scaleDiv;
00492 }
00493 
00494 void QwtLinearScaleEngine::buildTicks(
00495     const QwtDoubleInterval& interval, double stepSize, int maxMinSteps,
00496     QwtValueList ticks[QwtScaleDiv::NTickTypes]) const
00497 {
00498     const QwtDoubleInterval boundingInterval =
00499         align(interval, stepSize);
00500     
00501     ticks[QwtScaleDiv::MajorTick] = 
00502         buildMajorTicks(boundingInterval, stepSize);
00503 
00504     if ( maxMinSteps > 0 )
00505     {
00506         buildMinorTicks(ticks[QwtScaleDiv::MajorTick], maxMinSteps, stepSize,
00507             ticks[QwtScaleDiv::MinorTick], ticks[QwtScaleDiv::MediumTick]);
00508     }
00509     
00510     for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ )
00511     {
00512         ticks[i] = strip(ticks[i], interval);
00513 
00514         // ticks very close to 0.0 are 
00515         // explicitely set to 0.0
00516 
00517         for ( int j = 0; j < (int)ticks[i].count(); j++ )
00518         {
00519             if ( QwtScaleArithmetic::compareEps(ticks[i][j], 0.0, stepSize) == 0 )
00520                 ticks[i][j] = 0.0;
00521         }
00522     }
00523 }
00524 
00525 QwtValueList QwtLinearScaleEngine::buildMajorTicks(
00526     const QwtDoubleInterval &interval, double stepSize) const
00527 {
00528     int numTicks = qRound(interval.width() / stepSize) + 1;
00529 #if 1
00530     if ( numTicks > 10000 )
00531         numTicks = 10000;
00532 #endif
00533 
00534     QwtValueList ticks;
00535 
00536     ticks += interval.minValue();
00537     for (int i = 1; i < numTicks - 1; i++)
00538         ticks += interval.minValue() + i * stepSize;
00539     ticks += interval.maxValue();
00540 
00541     return ticks;
00542 }
00543 
00544 void QwtLinearScaleEngine::buildMinorTicks(
00545     const QwtValueList& majorTicks,
00546     int maxMinSteps, double stepSize,
00547     QwtValueList &minorTicks, 
00548     QwtValueList &mediumTicks) const
00549 {   
00550     double minStep = divideInterval(stepSize, maxMinSteps);
00551     if (minStep == 0.0)  
00552         return; 
00553         
00554     // # minor steps per interval
00555     int nMin = qwtAbs(qRound(stepSize / minStep)) - 1;
00556     
00557     // Do the minor steps fit into the interval?
00558     if ( QwtScaleArithmetic::compareEps((nMin +  1) * qwtAbs(minStep), 
00559         qwtAbs(stepSize), stepSize) > 0)
00560     {   
00561         nMin = 1;
00562         minStep = stepSize * 0.5;
00563     }
00564 
00565     int medIndex = -1;
00566     if ( nMin % 2 )
00567         medIndex = nMin / 2;
00568 
00569     // calculate minor ticks
00570 
00571     for (int i = 0; i < (int)majorTicks.count(); i++)
00572     {
00573         double val = majorTicks[i];
00574         for (int k=0; k< nMin; k++)
00575         {
00576             val += minStep;
00577 
00578             double alignedValue = val;
00579             if (QwtScaleArithmetic::compareEps(val, 0.0, stepSize) == 0) 
00580                 alignedValue = 0.0;
00581 
00582             if ( k == medIndex )
00583                 mediumTicks += alignedValue;
00584             else
00585                 minorTicks += alignedValue;
00586         }
00587     }
00588 }
00589 
00601 QwtDoubleInterval QwtLinearScaleEngine::align(
00602     const QwtDoubleInterval &interval, double stepSize) const
00603 {
00604     const double x1 = 
00605         QwtScaleArithmetic::floorEps(interval.minValue(), stepSize);
00606     const double x2 = 
00607         QwtScaleArithmetic::ceilEps(interval.maxValue(), stepSize);
00608 
00609     return QwtDoubleInterval(x1, x2);
00610 }
00611 
00615 QwtScaleTransformation *QwtLog10ScaleEngine::transformation() const
00616 {
00617     return new QwtScaleTransformation(QwtScaleTransformation::Log10);
00618 }
00619 
00630 void QwtLog10ScaleEngine::autoScale(int maxNumSteps, 
00631     double &x1, double &x2, double &stepSize) const
00632 {
00633     if ( x1 > x2 )
00634         qSwap(x1, x2);
00635 
00636     QwtDoubleInterval interval(x1 / pow(10.0, loMargin()), 
00637         x2 * pow(10.0, hiMargin()) );
00638 
00639     double logRef = 1.0;
00640     if (reference() > LOG_MIN / 2)
00641         logRef = qwtMin(reference(), LOG_MAX / 2);
00642 
00643     if (testAttribute(QwtScaleEngine::Symmetric))
00644     {
00645         const double delta = qwtMax(interval.maxValue() / logRef,  
00646             logRef / interval.minValue());
00647         interval.setInterval(logRef / delta, logRef * delta);
00648     }
00649 
00650     if (testAttribute(QwtScaleEngine::IncludeReference))
00651         interval = interval.extend(logRef);
00652 
00653     interval = interval.limited(LOG_MIN, LOG_MAX);
00654 
00655     if (interval.width() == 0.0)
00656         interval = buildInterval(interval.minValue());
00657 
00658     stepSize = divideInterval(log10(interval).width(), qwtMax(maxNumSteps, 1));
00659     if ( stepSize < 1.0 )
00660         stepSize = 1.0;
00661 
00662     if (!testAttribute(QwtScaleEngine::Floating))
00663         interval = align(interval, stepSize);
00664 
00665     x1 = interval.minValue();
00666     x2 = interval.maxValue();
00667 
00668     if (testAttribute(QwtScaleEngine::Inverted))
00669     {
00670         qSwap(x1, x2);
00671         stepSize = -stepSize;
00672     }
00673 }
00674 
00687 QwtScaleDiv QwtLog10ScaleEngine::divideScale(double x1, double x2,
00688     int maxMajSteps, int maxMinSteps, double stepSize) const
00689 {
00690     QwtDoubleInterval interval = QwtDoubleInterval(x1, x2).normalized();
00691     interval = interval.limited(LOG_MIN, LOG_MAX);
00692 
00693     if (interval.width() <= 0 )
00694         return QwtScaleDiv();
00695 
00696     if (interval.maxValue() / interval.minValue() < 10.0)
00697     {
00698         // scale width is less than one decade -> build linear scale
00699     
00700         QwtLinearScaleEngine linearScaler;
00701         linearScaler.setAttributes(attributes());
00702         linearScaler.setReference(reference());
00703         linearScaler.setMargins(loMargin(), hiMargin());
00704 
00705         return linearScaler.divideScale(x1, x2, 
00706             maxMajSteps, maxMinSteps, stepSize);
00707     }
00708 
00709     stepSize = qwtAbs(stepSize);
00710     if ( stepSize == 0.0 )
00711     {
00712         if ( maxMajSteps < 1 )
00713             maxMajSteps = 1;
00714 
00715         stepSize = divideInterval(log10(interval).width(), maxMajSteps);
00716         if ( stepSize < 1.0 )
00717             stepSize = 1.0; // major step must be >= 1 decade
00718     }
00719 
00720     QwtScaleDiv scaleDiv;
00721     if ( stepSize != 0.0 )
00722     {
00723         QwtValueList ticks[QwtScaleDiv::NTickTypes];
00724         buildTicks(interval, stepSize, maxMinSteps, ticks);
00725 
00726         scaleDiv = QwtScaleDiv(interval, ticks);
00727     }
00728 
00729     if ( x1 > x2 )
00730         scaleDiv.invert();
00731 
00732     return scaleDiv;
00733 }
00734 
00735 void QwtLog10ScaleEngine::buildTicks(
00736     const QwtDoubleInterval& interval, double stepSize, int maxMinSteps,
00737     QwtValueList ticks[QwtScaleDiv::NTickTypes]) const
00738 {
00739     const QwtDoubleInterval boundingInterval =
00740         align(interval, stepSize);
00741     
00742     ticks[QwtScaleDiv::MajorTick] = 
00743         buildMajorTicks(boundingInterval, stepSize);
00744 
00745     if ( maxMinSteps > 0 )
00746     {
00747         ticks[QwtScaleDiv::MinorTick] = buildMinorTicks(
00748             ticks[QwtScaleDiv::MajorTick], maxMinSteps, stepSize);
00749     }
00750     
00751     for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ )
00752         ticks[i] = strip(ticks[i], interval);
00753 }
00754 
00755 QwtValueList QwtLog10ScaleEngine::buildMajorTicks(
00756     const QwtDoubleInterval &interval, double stepSize) const
00757 {
00758     double width = log10(interval).width();
00759 
00760     int numTicks = qRound(width / stepSize) + 1;
00761     if ( numTicks > 10000 )
00762         numTicks = 10000;
00763 
00764     const double lxmin = log(interval.minValue());
00765     const double lxmax = log(interval.maxValue());
00766     const double lstep = (lxmax - lxmin) / double(numTicks - 1);
00767 
00768     QwtValueList ticks;
00769 
00770     ticks += interval.minValue();
00771 
00772     for (int i = 1; i < numTicks; i++)
00773        ticks += exp(lxmin + double(i) * lstep);
00774 
00775     ticks += interval.maxValue();
00776 
00777     return ticks;
00778 }
00779 
00780 QwtValueList QwtLog10ScaleEngine::buildMinorTicks(
00781     const QwtValueList &majorTicks, 
00782     int maxMinSteps, double stepSize) const
00783 {   
00784     if (stepSize < 1.1)            // major step width is one decade
00785     {
00786         if ( maxMinSteps < 1 )
00787             return QwtValueList();
00788             
00789         int k0, kstep, kmax;
00790         
00791         if (maxMinSteps >= 8)
00792         {
00793             k0 = 2;
00794             kmax = 9;
00795             kstep = 1;
00796         }   
00797         else if (maxMinSteps >= 4)
00798         {
00799             k0 = 2;
00800             kmax = 8;
00801             kstep = 2;
00802         }   
00803         else if (maxMinSteps >= 2)
00804         {
00805             k0 = 2;
00806             kmax = 5;
00807             kstep = 3;
00808         }
00809         else
00810         {
00811             k0 = 5;
00812             kmax = 5;
00813             kstep = 1;
00814         }
00815 
00816         QwtValueList minorTicks;
00817 
00818         for (int i = 0; i < (int)majorTicks.count(); i++)
00819         {
00820             const double v = majorTicks[i];
00821             for (int k = k0; k<= kmax; k+=kstep)
00822                 minorTicks += v * double(k);
00823         }
00824 
00825         return minorTicks;
00826     }
00827     else  // major step > one decade
00828     {
00829         double minStep = divideInterval(stepSize, maxMinSteps);
00830         if ( minStep == 0.0 )
00831             return QwtValueList();
00832 
00833         if ( minStep < 1.0 )
00834             minStep = 1.0;
00835 
00836         // # subticks per interval
00837         int nMin = qRound(stepSize / minStep) - 1;
00838 
00839         // Do the minor steps fit into the interval?
00840 
00841         if ( QwtScaleArithmetic::compareEps((nMin +  1) * minStep, 
00842             qwtAbs(stepSize), stepSize) > 0)
00843         {
00844             nMin = 0;
00845         }
00846 
00847         if (nMin < 1)
00848             return QwtValueList();      // no subticks
00849 
00850         // substep factor = 10^substeps
00851         const double minFactor = qwtMax(pow(10.0, minStep), 10.0);
00852 
00853         QwtValueList minorTicks;
00854         for (int i = 0; i < (int)majorTicks.count(); i++)
00855         {
00856             double val = majorTicks[i];
00857             for (int k=0; k< nMin; k++)
00858             {
00859                 val *= minFactor;
00860                 minorTicks += val;
00861             }
00862         }
00863         return minorTicks;
00864     }
00865 }
00866 
00878 QwtDoubleInterval QwtLog10ScaleEngine::align(
00879     const QwtDoubleInterval &interval, double stepSize) const
00880 {
00881     const QwtDoubleInterval intv = log10(interval);
00882 
00883     const double x1 = QwtScaleArithmetic::floorEps(intv.minValue(), stepSize);
00884     const double x2 = QwtScaleArithmetic::ceilEps(intv.maxValue(), stepSize);
00885 
00886     return pow10(QwtDoubleInterval(x1, x2));
00887 }
00888 
00893 QwtDoubleInterval QwtLog10ScaleEngine::log10(
00894     const QwtDoubleInterval &interval) const
00895 {
00896     return QwtDoubleInterval(::log10(interval.minValue()),
00897             ::log10(interval.maxValue()));
00898 }
00899 
00903 QwtDoubleInterval QwtLog10ScaleEngine::pow10(
00904     const QwtDoubleInterval &interval) const
00905 {
00906     return QwtDoubleInterval(pow(10.0, interval.minValue()),
00907             pow(10.0, interval.maxValue()));
00908 }

Generated on Thu May 1 15:44:09 2008 for Qwt User's Guide by  doxygen 1.5.0