File indexing completed on 2024-05-19 03:48:55

0001 /*
0002     File                 : Range.h
0003     Project              : LabPlot
0004     Description          : basic data range class
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2020-2022 Stefan Gerlach <stefan.gerlach@uni.kn>
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #ifndef RANGE_H
0011 #define RANGE_H
0012 
0013 #include "backend/gsl/parser.h"
0014 #include "backend/nsl/nsl_math.h"
0015 #include "macros.h" //const auto numberLocale = QLocale();
0016 
0017 #include <KLocalizedString>
0018 
0019 #include <QDateTime>
0020 #include <QStringList>
0021 
0022 #include <cmath>
0023 
0024 class QString;
0025 
0026 //! Auxiliary class for a data range
0027 /**
0028  *  This class represents a data range [start, end] where start can be > end.
0029  *  It replaces the Interval class and has following attributes:
0030  *  * start
0031  *  * end
0032  *  * format (Numeric or Datetime)
0033  *  * datetime format ("YY-MM-DD ...")
0034  *  * scale (Linear, Log, ...)
0035  *
0036  *  Only types supporting comparison are supported
0037  */
0038 class RangeT { // access enum without template
0039     Q_GADGET
0040 public:
0041     enum class Format { Numeric, DateTime };
0042     Q_ENUM(Format)
0043     // see https://www.originlab.com/doc/Origin-Help/AxesRef-Scale#Type
0044     // TODO: InverseOffset, Prob, Probit, Logit, Weibull
0045     enum class Scale { Linear, Log10, Log2, Ln, Sqrt, Square, Inverse };
0046     Q_ENUM(Scale)
0047     // static const QStringList& scaleNames(); // see Range.cpp
0048     //  TODO: when we have C++17: use inline initialization
0049     static inline QStringList scaleNames{i18n("Linear"), i18n("Log10"), i18n("Log2"), i18n("Ln"), i18n("Sqrt"), i18n("Square")};
0050     static bool isLogScale(Scale scale) {
0051         if (scale == Scale::Log10 || scale == Scale::Log2 || scale == Scale::Ln)
0052             return true;
0053         return false;
0054     }
0055 };
0056 
0057 template<class T>
0058 class Range : RangeT {
0059 public:
0060     Range() {
0061     }
0062     Range(T start, T end, Format format = Format::Numeric, Scale scale = Scale::Linear) {
0063         this->setRange(start, end, format, scale);
0064     }
0065     // start and end as localized strings like "pi + 1.5" (will be parsed)
0066     Range(const QString& start, const QString& end, const Format format = Format::Numeric, const Scale scale = Scale::Linear) {
0067         const auto numberLocale = QLocale();
0068         // min
0069         double min = parse(qPrintable(start.simplified()), qPrintable(numberLocale.name()));
0070         if (parse_errors() > 0) // if parsing fails, try default locale
0071             min = parse(qPrintable(start.simplified()), "en_US");
0072         if (parse_errors() > 0)
0073             min = 0;
0074 
0075         // max
0076         double max = parse(qPrintable(end.simplified()), qPrintable(numberLocale.name()));
0077         if (parse_errors() > 0) // if parsing fails, try default locale
0078             max = parse(qPrintable(end.simplified()), "en_US");
0079         if (parse_errors() > 0)
0080             max = 1.;
0081 
0082         // TODO: check for NAN, INF?
0083         this->setRange(min, max, format, scale);
0084     }
0085 
0086     T start() const {
0087         return m_start;
0088     }
0089     T end() const {
0090         return m_end;
0091     }
0092     T& start() {
0093         return m_start;
0094     }
0095     T& end() {
0096         return m_end;
0097     }
0098     Format format() const {
0099         return m_format;
0100     }
0101     Format& format() {
0102         return m_format;
0103     }
0104     Scale scale() const {
0105         return m_scale;
0106     }
0107     Scale& scale() {
0108         return m_scale;
0109     }
0110     QString dateTimeFormat() const {
0111         return m_dateTimeFormat;
0112     }
0113     const QString& dateTimeFormat() {
0114         return m_dateTimeFormat;
0115     }
0116     bool autoScale() const {
0117         return m_autoScale;
0118     }
0119 
0120     void setStart(T start) {
0121         m_start = start;
0122     }
0123     void setEnd(T end) {
0124         m_end = end;
0125     }
0126     void setRange(T start, T end, Format format, Scale scale) {
0127         m_start = start;
0128         m_end = end;
0129         m_format = format;
0130         m_scale = scale;
0131     }
0132     void setRange(T start, T end) { // set range keeping format and scale
0133         m_start = start;
0134         m_end = end;
0135     }
0136     void setFormat(Format format) {
0137         m_format = format;
0138     }
0139     void setScale(Scale scale) {
0140         m_scale = scale;
0141     }
0142     void setDateTimeFormat(const QString& format) {
0143         m_dateTimeFormat = format;
0144     }
0145     void setAutoScale(bool b) {
0146         m_autoScale = b;
0147     }
0148 
0149     T size() const {
0150         return m_end - m_start;
0151     }
0152     T length() const {
0153         return qAbs(m_end - m_start);
0154     }
0155     T center() const {
0156         switch (m_scale) {
0157         case Scale::Linear:
0158             return (m_start + m_end) / 2.;
0159         case Scale::Log10:
0160             return std::pow(10., log10(m_end * m_start) / 2.);
0161         case Scale::Log2:
0162             return std::pow(2., log2(m_end * m_start) / 2.);
0163         case Scale::Ln:
0164             return std::exp(log(m_end * m_start) / 2.);
0165         case Scale::Sqrt:
0166             return std::pow((std::sqrt(m_end) + std::sqrt(m_start)) / 2., 2.);
0167         case Scale::Square:
0168             return std::sqrt((std::pow(m_end, 2.) + std::pow(m_start, 2.)) / 2.);
0169         case Scale::Inverse:
0170             return 1. / ((1. / m_end + 1. / m_start) / 2.);
0171         }
0172         return T();
0173     }
0174     // calculate step size from number of steps
0175     T stepSize(const int steps) const {
0176         return (steps > 1) ? size() / static_cast<T>(steps - 1) : 0;
0177     }
0178     bool isZero() const {
0179         return (m_end == m_start);
0180     }
0181     bool valid() const {
0182         return (!qIsNaN(m_start) && !qIsNaN(m_end));
0183     }
0184     bool finite() const {
0185         return (qIsFinite(m_start) && qIsFinite(m_end));
0186     }
0187     bool inverse() const {
0188         return (m_start > m_end);
0189     }
0190     int direction() const {
0191         return m_start <= m_end ? 1 : -1;
0192     }
0193     // relative precision (high when interval is small compared to range). At least 4 digits
0194     int relativePrecision() const {
0195         return qMax(4, -nsl_math_rounded_decimals(std::abs(m_start) / length()) + 1);
0196     }
0197     bool contains(const Range<T>& other) const {
0198         return (qMin(m_start, m_end) <= qMin(other.start(), other.end()) && qMax(m_start, m_end) >= qMax(other.start(), other.end()));
0199     }
0200     bool contains(T value) const {
0201         return (qMin(m_start, m_end) <= value && qMax(m_start, m_end) >= value);
0202     }
0203     void translate(T offset) {
0204         m_start += offset;
0205         m_end += offset;
0206     }
0207     void extend(T value) {
0208         m_start -= value;
0209         m_end += value;
0210     }
0211     bool operator==(const Range<T>& other) const {
0212         return (m_start == other.start() && m_end == other.end() && m_format == other.format() && m_scale == other.scale());
0213     }
0214     bool operator!=(const Range<T>& other) const {
0215         return (m_start != other.start() || m_end != other.end() || m_format != other.format() || m_scale != other.scale());
0216     }
0217     Range<T>& operator+=(const T value) {
0218         m_start += value;
0219         m_end += value;
0220         return *this;
0221     }
0222     Range<T>& operator*=(const T value) {
0223         m_start *= value;
0224         m_end *= value;
0225         return *this;
0226     }
0227 
0228     //! Return a string in the format 'start .. end' and uses system locale (specialization see below)
0229     // round == true rounds the double values
0230     QString toString(bool round = true, QLocale locale = QLocale()) const {
0231         Q_UNUSED(round)
0232         if (m_format == Format::Numeric)
0233             return locale.toString(m_start) + QStringLiteral(" .. ") + locale.toString(m_end);
0234         else
0235             return QDateTime::fromMSecsSinceEpoch(m_start, Qt::UTC).toString(m_dateTimeFormat) + QStringLiteral(" .. ")
0236                 + QDateTime::fromMSecsSinceEpoch(m_end, Qt::UTC).toString(m_dateTimeFormat);
0237     }
0238     std::string toStdString(bool round = true) const {
0239         return STDSTRING(toString(round));
0240     }
0241     //! Return a string in the format 'start .. end' and uses number locale
0242     QString toLocaleString(bool round = true) const {
0243         return this->toString(round, QLocale());
0244     }
0245 
0246     // fix start and end when not supported by scale
0247     void fixLimits() {
0248         switch (m_scale) {
0249         case Scale::Linear: // all values are allowed (catch inf, nan?)
0250             break;
0251         case Scale::Log10:
0252             if (m_end <= 0)
0253                 m_end = 10.;
0254             if (m_start <= 0)
0255                 m_start = m_end / 10.;
0256             break;
0257         case Scale::Log2:
0258             if (m_end <= 0)
0259                 m_end = 2.;
0260             if (m_start <= 0)
0261                 m_start = m_end / 2.;
0262             break;
0263         case Scale::Ln:
0264             if (m_end <= 0)
0265                 m_end = M_E;
0266             if (m_start <= 0)
0267                 m_start = m_end / M_E;
0268             break;
0269         case Scale::Sqrt:
0270         case Scale::Square:
0271         case Scale::Inverse:
0272             if (m_end <= 0) // end must be > 0 for start to be < end
0273                 m_end = 1.;
0274             if (m_start < 0)
0275                 m_start = 0.;
0276             break;
0277         }
0278     }
0279 
0280     // extend/shrink range to nice numbers (used in auto scaling)
0281     //  get nice size to extend to (see Glassner: Graphic Gems)
0282     double niceSize(double size, bool round) {
0283         const double exponent = std::floor(log10(size));
0284         const double fraction = size / std::pow(10., exponent);
0285 
0286         double niceFraction; // nice (rounded) fraction
0287         if (round) {
0288             if (fraction < 1.5)
0289                 niceFraction = 1;
0290             else if (fraction <= 2.5)
0291                 niceFraction = 2;
0292             else if (fraction < 7)
0293                 niceFraction = 5;
0294             else
0295                 niceFraction = 10;
0296         } else {
0297             if (fraction <= 1)
0298                 niceFraction = 1;
0299             else if (fraction <= 2)
0300                 niceFraction = 2;
0301             else if (fraction <= 5)
0302                 niceFraction = 5;
0303             else
0304                 niceFraction = 10;
0305         }
0306         DEBUG(Q_FUNC_INFO << ", round = " << round << ", fraction = " << fraction);
0307         DEBUG(Q_FUNC_INFO << ", nice fraction = " << niceFraction);
0308 
0309         return niceFraction * std::pow(10., exponent);
0310     }
0311     void niceShrink() {
0312         niceExtend(false);
0313     }
0314     void niceExtend(bool extend = true) { // extend == false means shrink
0315         if (length() == 0)
0316             return;
0317 
0318         if (isLogScale(scale()) && (m_start <= 0 || m_end <= 0))
0319             return;
0320 
0321         double base = 10.;
0322         double oldSize = size();
0323         switch (scale()) {
0324         case Scale::Linear:
0325         case Scale::Log10:
0326             break; // default base
0327         case Scale::Log2:
0328             base = 2.;
0329             break;
0330         case Scale::Ln:
0331             base = M_E;
0332             break;
0333         case Scale::Sqrt:
0334             oldSize = sqrt(oldSize);
0335             break;
0336         case Scale::Square:
0337             oldSize *= oldSize;
0338             break;
0339         case Scale::Inverse:
0340             oldSize = 1. / oldSize;
0341         }
0342 
0343         if (isLogScale(scale())) {
0344             if ((extend && m_start < m_end) || (!extend && m_start > m_end)) {
0345                 m_start = nsl_math_round_basex(m_start, -1, base);
0346                 m_end = nsl_math_round_basex(m_end, -1, base) * base;
0347             } else {
0348                 m_start = nsl_math_round_basex(m_start, -1, base) * base;
0349                 m_end = nsl_math_round_basex(m_end, -1, base);
0350             }
0351             return; // log scales end here
0352         }
0353 
0354         DEBUG(Q_FUNC_INFO << ", scale = " << (int)scale() << ", old size = " << oldSize)
0355 
0356         const double newSize = niceSize(oldSize, false);
0357         DEBUG(Q_FUNC_INFO << ", new size = " << newSize);
0358         const double maxTicks = 10; // TODO: parameter?
0359         const double spacing = niceSize(newSize / (maxTicks - 1), true);
0360         DEBUG("spacing = " << spacing)
0361 
0362         // extend/shrink range
0363         double new_start = m_start, new_end = m_end;
0364         switch (scale()) {
0365         case Scale::Linear:
0366         case Scale::Log10: // log-scales already done
0367         case Scale::Log2:
0368         case Scale::Ln:
0369             break;
0370         case Scale::Sqrt:
0371             if (m_start < 0 || m_end < 0)
0372                 return;
0373             new_start = sqrt(m_start);
0374             new_end = sqrt(m_end);
0375             break;
0376         case Scale::Square:
0377             new_start = m_start * m_start;
0378             new_end = m_end * m_end;
0379             break;
0380         case Scale::Inverse:
0381             if (m_start == 0 || m_end == 0)
0382                 return;
0383             new_start = 1. / m_start;
0384             new_end = 1. / m_end;
0385         }
0386 
0387         if ((extend && m_start < m_end) || (!extend && m_start > m_end)) {
0388             new_start = std::floor(new_start / spacing) * spacing;
0389             new_end = std::ceil(new_end / spacing) * spacing;
0390         } else {
0391             new_start = std::ceil(new_start / spacing) * spacing;
0392             new_end = std::floor(new_end / spacing) * spacing;
0393         }
0394         DEBUG(" tmp new range: " << new_start << " .. " << new_end)
0395 
0396         switch (scale()) {
0397         case Scale::Linear:
0398         case Scale::Log10: // log-scales already done
0399         case Scale::Log2:
0400         case Scale::Ln:
0401             break;
0402         case Scale::Sqrt:
0403             new_start *= new_start;
0404             new_end *= new_end;
0405             break;
0406         case Scale::Square:
0407             if (new_start < 0 || new_end < 0)
0408                 return;
0409             new_start = sqrt(new_start);
0410             new_end = sqrt(new_end);
0411             break;
0412         case Scale::Inverse:
0413             if (new_start == 0 || new_end == 0)
0414                 return;
0415             new_start = 1. / new_start;
0416             new_end = 1. / new_end;
0417         }
0418 
0419         if (std::abs(new_end - new_start) == 0) // avoid empty range
0420             return;
0421 
0422         m_start = new_start;
0423         m_end = new_end;
0424         DEBUG(Q_FUNC_INFO << ", new range: " << toStdString())
0425     }
0426     int autoTickCount() const {
0427         QDEBUG(Q_FUNC_INFO << ", scale = " << scale())
0428 
0429         if (length() == 0)
0430             return 0;
0431 
0432         if (isLogScale(scale()) && (m_start <= 0 || m_end <= 0))
0433             return 1; // datetime test expects value > 0
0434 
0435         double order = 0.;
0436         switch (scale()) {
0437         case Scale::Linear:
0438         case Scale::Sqrt:
0439         case Scale::Square:
0440         case Scale::Inverse:
0441             break;
0442         case Scale::Log10:
0443             order = log10(m_end) - log10(m_start);
0444             break;
0445         case Scale::Log2:
0446             order = log2(m_end) - log2(m_start);
0447             break;
0448         case Scale::Ln:
0449             order = log(m_end) - log(m_start);
0450             break;
0451         }
0452 
0453         if (isLogScale(scale())) {
0454             DEBUG(Q_FUNC_INFO << ", order = " << std::round(order))
0455             if (order >= 0)
0456                 return std::round(order) + 1;
0457             else // reverse range
0458                 return -std::round(order) + 1;
0459         } // log scales end here
0460 
0461         DEBUG(Q_FUNC_INFO << ", range = " << toStdString() << ", length() = " << length())
0462         order = pow(10.0, std::floor(log10(length())));
0463 
0464         DEBUG(Q_FUNC_INFO << ", order of magnitude = " << order)
0465         const int factor = qRound(100 * length() / order);
0466         DEBUG(Q_FUNC_INFO << ", factor = " << factor)
0467 
0468         // set number of ticks for certain multiple of small numbers
0469         if (factor % 30 == 0)
0470             return 3 + 1;
0471         if (factor % 40 == 0)
0472             return 4 + 1;
0473         if (factor % 70 == 0)
0474             return 7 + 1;
0475         if (factor % 50 == 0)
0476             return 5 + 1;
0477         if (factor % 90 == 0)
0478             return 9 + 1;
0479         if (factor % 175 == 0)
0480             return 7 + 1;
0481         if (factor % 25 == 0)
0482             return 5 + 1;
0483         if (factor % 105 == 0)
0484             return 7 + 1;
0485         if (factor % 115 == 0)
0486             return 5 + 1;
0487 
0488         return 11 + 1;
0489     }
0490     // TODO: touches(), merge(), subtract(), split(), etc. (see Interval)
0491 
0492     /*!
0493      * TODO: implement zooming depending on the relZoomPosScene also for non linear scales!
0494      */
0495     void zoom(const double factor, const bool nice, const double relZoomPosScene = 0.5) {
0496         const double start{this->start()}, end{this->end()};
0497         switch (scale()) {
0498         case RangeT::Scale::Linear: {
0499             if (relZoomPosScene == 0.5 || nice)
0500                 extend(size() * (factor - 1.) / 2.);
0501             else {
0502                 // zoom and shift in one step
0503                 //                                                              Example:                Example 1      Example 2        Example 3
0504                 //                                                              Start                   0               0             4.5
0505                 //                                                              End                     10              10            9.5
0506                 //                                                              factor:                 0.5             0.5           2
0507                 //                                                              relZoomPosScene:        0.9             0.5           0.9
0508                 //                                                              Expected new start:     2.5             0
0509                 //                                                              Expected new end:       7.5             10
0510                 const double pos = start + (end - start) * relZoomPosScene; // Number at pos scene     9               5             9
0511                 this->start() += (pos - start) * (1 - factor); // 4.5             2.5           0
0512                 this->end() -= (end - pos) * (1 - factor); // 9.5             7.5          relZoomPos at number    0.9 (fine)      0.5 (fine)           (Number
0513                                                            // at pos scene - start) / (end - start)
0514             }
0515             break;
0516         }
0517         case RangeT::Scale::Log10: {
0518             if (start == 0 || end / start <= 0)
0519                 break;
0520             const double diff = log10(end / start) * (factor - 1.);
0521             const double extend = pow(10, diff / 2.);
0522             this->end() *= extend;
0523             this->start() /= extend;
0524             break;
0525         }
0526         case RangeT::Scale::Log2: {
0527             if (start == 0 || end / start <= 0)
0528                 break;
0529             const double diff = log2(end / start) * (factor - 1.);
0530             const double extend = exp2(diff / 2.);
0531             this->end() *= extend;
0532             this->start() /= extend;
0533             break;
0534         }
0535         case RangeT::Scale::Ln: {
0536             if (start == 0 || end / start <= 0)
0537                 break;
0538             const double diff = log(end / start) * (factor - 1.);
0539             const double extend = exp(diff / 2.);
0540             this->end() *= extend;
0541             this->start() /= extend;
0542             break;
0543         }
0544         case RangeT::Scale::Sqrt: {
0545             if (start < 0 || end < 0)
0546                 break;
0547             const double diff = (sqrt(end) - sqrt(start)) * (factor - 1.);
0548             extend(diff * diff / 4.);
0549             break;
0550         }
0551         case RangeT::Scale::Square: {
0552             const double diff = (end * end - start * start) * (factor - 1.);
0553             extend(sqrt(std::abs(diff / 2.)));
0554             break;
0555         }
0556         case RangeT::Scale::Inverse: {
0557             const double diff = (1. / start - 1. / end) * (factor - 1.);
0558             extend(1. / std::abs(diff / 2.));
0559             break;
0560         }
0561         }
0562 
0563         // make nice again
0564         if (nice) {
0565             if (factor < 1) // zoomIn
0566                 niceShrink();
0567             else
0568                 niceExtend();
0569         }
0570     }
0571 
0572 private:
0573     T m_start{0}; // start value
0574     T m_end{1}; // end value
0575     Format m_format{Format::Numeric}; // format (Numeric or DateTime)
0576     QString m_dateTimeFormat{QLatin1String("yyyy-MM-dd hh:mm:ss")}; // only used for DateTime
0577     Scale m_scale{Scale::Linear}; // scale (Linear, Log , ...)
0578     bool m_autoScale{true}; // auto adapt start and end to all curves using this range (in plot)
0579 };
0580 
0581 // specialization
0582 template<>
0583 inline QString Range<double>::toString(bool round, QLocale locale) const {
0584     if (m_format == Format::Numeric) {
0585         if (round) {
0586             const int relPrec = relativePrecision();
0587             // DEBUG(Q_FUNC_INFO << ", rel prec = " << relPrec)
0588             return locale.toString(nsl_math_round_precision(m_start, relPrec), 'g', relPrec) + QLatin1String(" .. ")
0589                 + locale.toString(nsl_math_round_precision(m_end, relPrec), 'g', relPrec);
0590         } else
0591             return locale.toString(m_start, 'g', 12) + QLatin1String(" .. ") + locale.toString(m_end, 'g', 12);
0592     } else
0593         return QDateTime::fromMSecsSinceEpoch(m_start, Qt::UTC).toString(m_dateTimeFormat) + QLatin1String(" .. ")
0594             + QDateTime::fromMSecsSinceEpoch(m_end, Qt::UTC).toString(m_dateTimeFormat);
0595 }
0596 
0597 #endif