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