File indexing completed on 2025-01-26 03:34:06

0001 /*
0002     File                 : CartesianCoordinateSystem.cpp
0003     Project              : LabPlot
0004     Description          : Cartesian coordinate system for plots.
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2012-2016 Alexander Semke <alexander.semke@web.de>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h"
0012 #include "backend/lib/macros.h"
0013 #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystemPrivate.h"
0014 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
0015 
0016 #include "backend/nsl/nsl_math.h"
0017 
0018 using Dimension = CartesianCoordinateSystem::Dimension;
0019 
0020 /* ============================================================================ */
0021 /* ========================= coordinate system ================================ */
0022 /* ============================================================================ */
0023 /**
0024  * \class CartesianCoordinateSystem
0025  * \brief Cartesian coordinate system for plots.
0026  */
0027 CartesianCoordinateSystem::CartesianCoordinateSystem(CartesianPlot* plot)
0028     : AbstractCoordinateSystem(plot)
0029     , d(new CartesianCoordinateSystemPrivate(this)) {
0030     d->plot = plot;
0031     // TODO: set some standard scales
0032 }
0033 
0034 CartesianCoordinateSystem::~CartesianCoordinateSystem() {
0035     delete d;
0036 }
0037 
0038 QString CartesianCoordinateSystem::dimensionToString(Dimension dim) {
0039     switch (dim) {
0040     case Dimension::X:
0041         return QLatin1String("x");
0042     case Dimension::Y:
0043         return QLatin1String("y");
0044     }
0045     return {};
0046 }
0047 
0048 QString CartesianCoordinateSystem::info() const {
0049     DEBUG(Q_FUNC_INFO)
0050     if (d->plot)
0051         return QString(QLatin1String("x = ") + d->plot->range(Dimension::X, d->xIndex).toString() + QLatin1String(", y = ")
0052                        + d->plot->range(Dimension::Y, d->yIndex).toString());
0053 
0054     return i18n("no info available");
0055 }
0056 
0057 // ##############################################################################
0058 // ######################### logical to scene mappers ###########################
0059 // ##############################################################################
0060 Points CartesianCoordinateSystem::mapLogicalToScene(const Points& points, MappingFlags flags) const {
0061     // DEBUG(Q_FUNC_INFO << ", (points with flags)")
0062     const QRectF pageRect = d->plot->dataRect();
0063     const bool noPageClipping = pageRect.isNull() || (flags & MappingFlag::SuppressPageClipping);
0064     const bool noPageClippingY = flags & MappingFlag::SuppressPageClippingY;
0065     const bool limit = flags & MappingFlag::Limit;
0066     const double xPage = pageRect.x(), yPage = pageRect.y();
0067     const double w = pageRect.width(), h = pageRect.height();
0068 
0069     // DEBUG(Q_FUNC_INFO << ", xScales/YScales size: " << d->xScales.size() << '/' << d->yScales.size())
0070 
0071     Points result;
0072     result.reserve(points.size());
0073     for (const auto* xScale : d->xScales) {
0074         if (!xScale)
0075             continue;
0076 
0077         for (const auto* yScale : d->yScales) {
0078             if (!yScale)
0079                 continue;
0080 
0081             for (const auto& point : points) {
0082                 double x = point.x(), y = point.y();
0083 
0084                 if (!xScale->contains(x) || !yScale->contains(y))
0085                     continue;
0086                 if (!xScale->map(&x) || !yScale->map(&y))
0087                     continue;
0088 
0089                 if (limit) {
0090                     // set to max/min if passed over
0091                     x = qBound(xPage, x, xPage + w);
0092                     y = qBound(yPage, y, yPage + h);
0093                 }
0094 
0095                 if (noPageClippingY)
0096                     y = yPage + h / 2.;
0097 
0098                 const QPointF mappedPoint(x, y);
0099                 if (noPageClipping || limit || rectContainsPoint(pageRect, mappedPoint))
0100                     result.append(mappedPoint);
0101             }
0102         }
0103     }
0104     result.squeeze();
0105 
0106     return result;
0107 }
0108 
0109 /*!
0110     Maps the points in logical coordinates from @p points and fills the @p visiblePoints with the points in logical coordinates restricted to the current
0111    intervals.
0112     @param logicalPoints List of points in logical coordinates
0113     @param scenePoints List for the points in scene coordinates
0114     @param visiblePoints List for the logical coordinates restricted to the current region of the coordinate system
0115     @param flags
0116  */
0117 void CartesianCoordinateSystem::mapLogicalToScene(const Points& logicalPoints,
0118                                                   Points& scenePoints,
0119                                                   std::vector<bool>& visiblePoints,
0120                                                   MappingFlags flags) const {
0121     // DEBUG(Q_FUNC_INFO << ", (curve with all points)")
0122     const QRectF pageRect = d->plot->dataRect();
0123     const bool noPageClipping = pageRect.isNull() || (flags & MappingFlag::SuppressPageClipping);
0124     const bool noPageClippingY = flags & MappingFlag::SuppressPageClippingY;
0125     const bool limit = flags & MappingFlag::Limit;
0126     const double xPage = pageRect.x(), yPage = pageRect.y();
0127     const double w = pageRect.width(), h = pageRect.height();
0128 
0129     // DEBUG(Q_FUNC_INFO << ", xScales/YScales size: " << d->xScales.size() << '/' << d->yScales.size())
0130 
0131     for (const auto* xScale : d->xScales) {
0132         if (!xScale)
0133             continue;
0134 
0135         for (const auto* yScale : d->yScales) {
0136             if (!yScale)
0137                 continue;
0138 
0139             int i = 0;
0140             for (const auto& point : logicalPoints) {
0141                 double x = point.x(), y = point.y();
0142                 if (!xScale->contains(x) || !yScale->contains(y))
0143                     continue;
0144                 if (!xScale->map(&x) || !yScale->map(&y))
0145                     continue;
0146 
0147                 if (limit) {
0148                     // set to max/min if passed over
0149                     x = qBound(xPage, x, xPage + w);
0150                     y = qBound(yPage, y, yPage + h);
0151                 }
0152 
0153                 if (noPageClippingY)
0154                     y = yPage + h / 2.;
0155 
0156                 const QPointF mappedPoint(x, y);
0157                 if (noPageClipping || limit || rectContainsPoint(pageRect, mappedPoint)) {
0158                     scenePoints.append(mappedPoint);
0159                     visiblePoints[i] = true;
0160                 } else
0161                     visiblePoints[i] = false;
0162 
0163                 i++;
0164             }
0165         }
0166     }
0167 }
0168 
0169 /*!
0170     Maps the points in logical coordinates from \c points and fills the \c visiblePoints with the points in logical coordinates restricted to the current
0171    intervals. If there are points, that lie on another one they will not be added a second time.
0172     @param logicalPoints List of points in logical coordinates
0173     @param scenePoints List for the points in scene coordinates
0174     @param visiblePoints List for the logical coordinates restricted to the current region of the coordinate system
0175  */
0176 void CartesianCoordinateSystem::mapLogicalToScene(int startIndex,
0177                                                   int endIndex,
0178                                                   const Points& logicalPoints,
0179                                                   Points& scenePoints,
0180                                                   std::vector<bool>& visiblePoints,
0181                                                   MappingFlags flags) const {
0182     // DEBUG(Q_FUNC_INFO << ", (curve points)")
0183     const QRectF pageRect = d->plot->dataRect();
0184     const bool noPageClipping = pageRect.isNull() || (flags & MappingFlag::SuppressPageClipping);
0185     const bool noPageClippingY = flags & MappingFlag::SuppressPageClippingY;
0186     const bool limit = flags & MappingFlag::Limit;
0187     const double xPage = pageRect.x(), yPage = pageRect.y();
0188     const double w = pageRect.width(), h = pageRect.height();
0189 
0190     const int numberOfPixelX = std::ceil(pageRect.width());
0191     const int numberOfPixelY = std::ceil(pageRect.height());
0192 
0193     if (numberOfPixelX <= 0 || numberOfPixelY <= 0)
0194         return;
0195 
0196     // eliminate multiple scene points (size (numberOfPixelX + 1) * (numberOfPixelY + 1))
0197     QVector<QVector<bool>> scenePointsUsed(numberOfPixelX + 1);
0198     for (auto& col : scenePointsUsed)
0199         col.resize(numberOfPixelY + 1);
0200 
0201     // DEBUG(Q_FUNC_INFO << ", xScales/YScales size: " << d->xScales.size() << '/' << d->yScales.size())
0202 
0203     for (const auto* xScale : d->xScales) {
0204         if (!xScale)
0205             continue;
0206 
0207         for (const auto* yScale : d->yScales) {
0208             if (!yScale)
0209                 continue;
0210 
0211             for (int i = startIndex; i <= endIndex; i++) {
0212                 const QPointF& point = logicalPoints.at(i);
0213 
0214                 double x = point.x(), y = point.y();
0215                 if (!xScale->contains(x) || !yScale->contains(y))
0216                     continue;
0217                 if (!xScale->map(&x) || !yScale->map(&y))
0218                     continue;
0219 
0220                 if (limit) {
0221                     // set to max/min if passed over
0222                     x = qBound(xPage, x, xPage + w);
0223                     y = qBound(yPage, y, yPage + h);
0224                 }
0225 
0226                 if (noPageClippingY)
0227                     y = yPage + h / 2.;
0228 
0229                 const QPointF mappedPoint(x, y);
0230                 // DEBUG(mappedPoint.x() << ' ' << mappedPoint.y())
0231                 if (noPageClipping || limit || rectContainsPoint(pageRect, mappedPoint)) {
0232                     // TODO: check
0233                     const int indexX = std::round(x - xPage);
0234                     const int indexY = std::round(y - yPage);
0235                     if (scenePointsUsed.at(indexX).at(indexY))
0236                         continue;
0237 
0238                     scenePointsUsed[indexX][indexY] = true;
0239                     scenePoints.append(mappedPoint);
0240                     // DEBUG(mappedPoint.x() << ' ' << mappedPoint.y())
0241                     visiblePoints[i] = true;
0242                 } else
0243                     visiblePoints[i] = false;
0244             }
0245         }
0246     }
0247 }
0248 
0249 /*
0250  * Map a single point
0251  * */
0252 QPointF CartesianCoordinateSystem::mapLogicalToScene(QPointF logicalPoint, bool& visible, MappingFlags flags) const {
0253     // DEBUG(Q_FUNC_INFO << ", (single point)")
0254     const QRectF pageRect = d->plot->dataRect();
0255     const bool noPageClipping = pageRect.isNull() || (flags & MappingFlag::SuppressPageClipping) || (flags & MappingFlag::SuppressPageClippingVisible);
0256     const bool noPageClippingY = flags & MappingFlag::SuppressPageClippingY;
0257     const bool limit = flags & MappingFlag::Limit;
0258     const bool visibleFlag = flags & MappingFlag::SuppressPageClippingVisible;
0259 
0260     double x = logicalPoint.x(), y = logicalPoint.y();
0261     const double xPage = pageRect.x(), yPage = pageRect.y();
0262     const double w = pageRect.width(), h = pageRect.height();
0263 
0264     // DEBUG(Q_FUNC_INFO << ", xScales/YScales size: " << d->xScales.size() << '/' << d->yScales.size())
0265 
0266     for (const auto* xScale : d->xScales) {
0267         if (!xScale)
0268             continue;
0269 
0270         for (const auto* yScale : d->yScales) {
0271             if (!yScale)
0272                 continue;
0273 
0274             if (!xScale->contains(x) || !yScale->contains(y))
0275                 continue;
0276             if (!xScale->map(&x) || !yScale->map(&y))
0277                 continue;
0278 
0279             if (limit) {
0280                 // set to max/min if passed over
0281                 x = qBound(xPage, x, xPage + w);
0282                 y = qBound(yPage, y, yPage + h);
0283             }
0284 
0285             if (noPageClippingY)
0286                 y = pageRect.y() + h / 2.;
0287 
0288             QPointF mappedPoint(x, y);
0289             const bool containsPoint = rectContainsPoint(pageRect, mappedPoint);
0290             if (noPageClipping || limit || containsPoint) {
0291                 if (visibleFlag)
0292                     visible = containsPoint;
0293                 else
0294                     visible = true;
0295                 return mappedPoint;
0296             }
0297         }
0298     }
0299 
0300     visible = false;
0301     return QPointF{};
0302 }
0303 
0304 Lines CartesianCoordinateSystem::mapLogicalToScene(const Lines& lines, MappingFlags flags) const {
0305     QRectF pageRect = d->plot->dataRect();
0306     Lines result;
0307     const bool doPageClipping = !pageRect.isNull() && !(flags & MappingFlag::SuppressPageClipping);
0308 
0309     double xGapBefore;
0310     double xGapAfter = NAN;
0311     double yGapBefore;
0312     double yGapAfter = NAN;
0313 
0314     DEBUG(Q_FUNC_INFO << ", xScales/yScales size: " << d->xScales.size() << '/' << d->yScales.size())
0315 
0316     QVectorIterator<CartesianScale*> xIterator(d->xScales);
0317     while (xIterator.hasNext()) {
0318         const CartesianScale* xScale = xIterator.next();
0319         if (!xScale)
0320             continue;
0321 
0322         xGapBefore = xGapAfter;
0323         if (xIterator.hasNext()) {
0324             const CartesianScale* nextXScale = xIterator.peekNext();
0325             if (!nextXScale)
0326                 continue;
0327             Range<double> nextXRange;
0328             nextXScale->getProperties(&nextXRange);
0329 
0330             double x1 = xScale->end();
0331             double x2 = nextXScale->start();
0332 
0333             bool valid = xScale->map(&x1);
0334             if (valid)
0335                 valid = nextXScale->map(&x2);
0336             if (valid)
0337                 xGapAfter = x2 - x1;
0338             else
0339                 xGapAfter = NAN;
0340         } else
0341             xGapAfter = NAN;
0342 
0343         QVectorIterator<CartesianScale*> yIterator(d->yScales);
0344         while (yIterator.hasNext()) {
0345             const CartesianScale* yScale = yIterator.next();
0346             if (!yScale)
0347                 continue;
0348 
0349             yGapBefore = yGapAfter;
0350             if (yIterator.hasNext()) {
0351                 const CartesianScale* nextYScale = yIterator.peekNext();
0352                 if (!nextYScale)
0353                     continue;
0354 
0355                 double y1 = yScale->end();
0356                 double y2 = nextYScale->start();
0357 
0358                 bool valid = yScale->map(&y1);
0359                 if (valid)
0360                     valid = nextYScale->map(&y2);
0361                 if (valid)
0362                     yGapAfter = y2 - y1;
0363                 else
0364                     yGapAfter = NAN;
0365             } else
0366                 yGapAfter = NAN;
0367 
0368             const QRectF scaleRect = QRectF(xScale->start(), yScale->start(), xScale->end() - xScale->start(), yScale->end() - yScale->start()).normalized();
0369 
0370             for (auto line : lines) {
0371                 // QDEBUG(Q_FUNC_INFO << ", LINE " << line)
0372                 LineClipResult clipResult;
0373                 if (!AbstractCoordinateSystem::clipLineToRect(&line, scaleRect, &clipResult))
0374                     continue;
0375 
0376                 double x1 = line.x1();
0377                 if (!xScale->map(&x1))
0378                     continue;
0379 
0380                 double x2 = line.x2();
0381                 if (!xScale->map(&x2))
0382                     continue;
0383 
0384                 double y1 = line.y1();
0385                 if (!yScale->map(&y1))
0386                     continue;
0387 
0388                 double y2 = line.y2();
0389                 if (!yScale->map(&y2))
0390                     continue;
0391 
0392                 if (flags & MappingFlag::MarkGaps) {
0393                     // mark the end of the gap
0394                     if (!std::isnan(xGapBefore)) {
0395                         if (clipResult.xClippedLeft[0]) {
0396                             QLineF gapMarker(x1 + xGapBefore / 4., y1 - xGapBefore / 2., x1 - xGapBefore / 4., y1 + xGapBefore / 2.);
0397                             //                          if (AbstractCoordinateSystem::clipLineToRect(&gapMarker, pageRect))
0398                             result.append(gapMarker);
0399                         }
0400                         if (clipResult.xClippedLeft[1]) {
0401                             QLineF gapMarker(x2 + xGapBefore / 4., y2 - xGapBefore / 2., x2 - xGapBefore / 4., y2 + xGapBefore / 2.);
0402                             //                          if (AbstractCoordinateSystem::clipLineToRect(&gapMarker, pageRect))
0403                             result.append(gapMarker);
0404                         }
0405                     }
0406 
0407                     // mark the beginning of the gap
0408                     if (!std::isnan(xGapAfter)) {
0409                         if (clipResult.xClippedRight[0]) {
0410                             QLineF gapMarker(x1 + xGapAfter / 4., y1 - xGapAfter / 2., x1 - xGapAfter / 4., y1 + xGapAfter / 2.);
0411                             //                          if (AbstractCoordinateSystem::clipLineToRect(&gapMarker, pageRect))
0412                             result.append(gapMarker);
0413                         }
0414                         if (clipResult.xClippedRight[1]) {
0415                             QLineF gapMarker(x2 + xGapAfter / 4., y2 - xGapAfter / 2., x2 - xGapAfter / 4., y2 + xGapAfter / 2.);
0416                             //                          if (AbstractCoordinateSystem::clipLineToRect(&gapMarker, pageRect))
0417                             result.append(gapMarker);
0418                         }
0419                     }
0420 
0421                     if (!std::isnan(yGapBefore)) {
0422                         if (clipResult.yClippedTop[0]) {
0423                             QLineF gapMarker(x1 + yGapBefore / 2., y1 - yGapBefore / 4., x1 - yGapBefore / 2., y1 + yGapBefore / 4.);
0424                             //                          if (AbstractCoordinateSystem::clipLineToRect(&gapMarker, pageRect))
0425                             result.append(gapMarker);
0426                         }
0427                         if (clipResult.yClippedTop[1]) {
0428                             QLineF gapMarker(x2 + yGapBefore / 2., y2 - yGapBefore / 4., x2 - yGapBefore / 2., y2 + yGapBefore / 4.);
0429                             //                          if (AbstractCoordinateSystem::clipLineToRect(&gapMarker, pageRect))
0430                             result.append(gapMarker);
0431                         }
0432                     }
0433 
0434                     if (!std::isnan(yGapAfter)) {
0435                         if (clipResult.yClippedBottom[0]) {
0436                             QLineF gapMarker(QPointF(x1 + yGapAfter / 2., y1 - yGapAfter / 4.), QPointF(x1 - yGapAfter / 2., y1 + yGapAfter / 4.));
0437                             if (AbstractCoordinateSystem::clipLineToRect(&gapMarker, pageRect))
0438                                 result.append(gapMarker);
0439                         }
0440                         if (clipResult.yClippedBottom[1]) {
0441                             QLineF gapMarker(QPointF(x2 + yGapAfter / 2., y2 - yGapAfter / 4.), QPointF(x2 - yGapAfter / 2., y2 + yGapAfter / 4.));
0442                             if (AbstractCoordinateSystem::clipLineToRect(&gapMarker, pageRect))
0443                                 result.append(gapMarker);
0444                         }
0445                     }
0446                 }
0447 
0448                 QLineF mappedLine(QPointF(x1, y1), QPointF(x2, y2));
0449                 if (doPageClipping) {
0450                     if (!AbstractCoordinateSystem::clipLineToRect(&mappedLine, pageRect)) {
0451                         DEBUG(Q_FUNC_INFO << ", WARNING: OMIT mapped line!")
0452                         continue;
0453                     }
0454                 }
0455 
0456                 //              QDEBUG(Q_FUNC_INFO << ", append line " << mappedLine)
0457                 result.append(mappedLine);
0458             }
0459         }
0460     }
0461 
0462     return result;
0463 }
0464 
0465 // ##############################################################################
0466 // ######################### scene to logical mappers ###########################
0467 // ##############################################################################
0468 Points CartesianCoordinateSystem::mapSceneToLogical(const Points& points, MappingFlags flags) const {
0469     QRectF pageRect = d->plot->dataRect();
0470     Points result;
0471     const bool noPageClipping = pageRect.isNull() || (flags & MappingFlag::SuppressPageClipping);
0472     const bool limit = flags & MappingFlag::Limit;
0473     const bool noPageClippingY = flags & MappingFlag::SuppressPageClippingY;
0474     const double xPage = pageRect.x();
0475     const double yPage = pageRect.y();
0476     const double w = pageRect.width();
0477     const double h = pageRect.height();
0478 
0479     // DEBUG(Q_FUNC_INFO << ", xScales/YScales size: " << d->xScales.size() << '/' << d->yScales.size())
0480 
0481     for (const auto& point : points) {
0482         double x = point.x();
0483         double y = point.y();
0484         if (limit) {
0485             // set to max/min if passed over
0486             x = qBound(xPage, x, xPage + w);
0487             y = qBound(yPage, y, yPage + h);
0488         }
0489 
0490         if (noPageClippingY)
0491             y = yPage + h / 2.;
0492 
0493         if (noPageClipping || limit || pageRect.contains(point)) {
0494             bool found = false;
0495 
0496             for (const auto* xScale : d->xScales) {
0497                 if (found)
0498                     break;
0499                 if (!xScale)
0500                     continue;
0501 
0502                 for (const auto* yScale : d->yScales) {
0503                     if (found)
0504                         break;
0505                     if (!yScale)
0506                         continue;
0507 
0508                     if (!xScale->inverseMap(&x)) {
0509                         x = point.x();
0510                         continue;
0511                     }
0512 
0513                     if (!yScale->inverseMap(&y)) {
0514                         y = point.y();
0515                         continue;
0516                     }
0517 
0518                     if (!xScale->contains(x)) {
0519                         x = point.x();
0520                         continue;
0521                     }
0522 
0523                     if (!yScale->contains(y)) {
0524                         y = point.y();
0525                         continue;
0526                     }
0527 
0528                     result.append(QPointF(x, y));
0529                     found = true;
0530                 }
0531             }
0532         }
0533     }
0534 
0535     return result;
0536 }
0537 
0538 QPointF CartesianCoordinateSystem::mapSceneToLogical(QPointF logicalPoint, MappingFlags flags) const {
0539     QRectF pageRect = d->plot->dataRect();
0540     QPointF result;
0541     bool noPageClipping = pageRect.isNull() || (flags & MappingFlag::SuppressPageClipping);
0542     bool limit = flags & MappingFlag::Limit;
0543     const bool noPageClippingY = flags & MappingFlag::SuppressPageClippingY;
0544 
0545     if (limit) {
0546         // set to max/min if passed over
0547         logicalPoint.setX(qBound(pageRect.x(), logicalPoint.x(), pageRect.x() + pageRect.width()));
0548         logicalPoint.setY(qBound(pageRect.y(), logicalPoint.y(), pageRect.y() + pageRect.height()));
0549     }
0550 
0551     if (noPageClippingY)
0552         logicalPoint.setY(pageRect.y() + pageRect.height() / 2.);
0553 
0554     // DEBUG(Q_FUNC_INFO << ", xScales/YScales size: " << d->xScales.size() << '/' << d->yScales.size())
0555 
0556     if (noPageClipping || limit || pageRect.contains(logicalPoint)) {
0557         double x = logicalPoint.x();
0558         double y = logicalPoint.y();
0559         // DEBUG(Q_FUNC_INFO << ", x/y = " << x << " " << y)
0560 
0561         for (const auto* xScale : d->xScales) {
0562             if (!xScale)
0563                 continue;
0564             for (const auto* yScale : d->yScales) {
0565                 if (!yScale)
0566                     continue;
0567 
0568                 if (!xScale->inverseMap(&x) || !yScale->inverseMap(&y))
0569                     continue;
0570 
0571                 if (!xScale->contains(x) || !yScale->contains(y))
0572                     continue;
0573 
0574                 result.setX(x);
0575                 result.setY(y);
0576                 return result;
0577             }
0578         }
0579     }
0580 
0581     return result;
0582 }
0583 
0584 bool CartesianCoordinateSystem::isValid() const {
0585     if (d->xScales.isEmpty() || d->yScales.isEmpty())
0586         return false;
0587 
0588     for (const auto* scale : d->xScales) {
0589         if (!scale)
0590             return false;
0591     }
0592 
0593     for (const auto* scale : d->yScales) {
0594         if (!scale)
0595             return false;
0596     }
0597     return true;
0598 }
0599 
0600 /**************************************************************************************/
0601 
0602 /**
0603  * \brief Determine the direction relative to the page in different directions
0604  *
0605  * This function is needed for untransformed lengths such as axis tick length.
0606  * \return 1 or -1
0607  */
0608 int CartesianCoordinateSystem::direction(const Dimension dim) const {
0609     switch (dim) {
0610     case Dimension::X: {
0611         if (d->xScales.isEmpty() || !d->xScales.at(0)) {
0612             DEBUG(Q_FUNC_INFO << ", WARNING: no x scale!")
0613             return 1;
0614         }
0615 
0616         return d->xScales.at(0)->direction();
0617     }
0618     case Dimension::Y: {
0619         if (d->yScales.isEmpty() || !d->yScales.at(0)) {
0620             DEBUG(Q_FUNC_INFO << ", WARNING: no y scale!")
0621             return 1;
0622         }
0623 
0624         return d->yScales.at(0)->direction();
0625     }
0626     }
0627     return 1;
0628 }
0629 
0630 // TODO: design elegant, flexible and undo-aware API for changing scales
0631 bool CartesianCoordinateSystem::setScales(const Dimension dim, const QVector<CartesianScale*>& scales) {
0632     DEBUG(Q_FUNC_INFO)
0633     switch (dim) {
0634     case Dimension::X: {
0635         while (!d->xScales.isEmpty())
0636             delete d->xScales.takeFirst();
0637 
0638         d->xScales = scales;
0639         return true; // TODO: check scales validity
0640     }
0641     case Dimension::Y: {
0642         while (!d->yScales.isEmpty())
0643             delete d->yScales.takeFirst();
0644 
0645         d->yScales = scales;
0646         return true; // TODO: check scales validity
0647     }
0648     }
0649     return 1;
0650 }
0651 
0652 QVector<CartesianScale*> CartesianCoordinateSystem::scales(const Dimension dim) const {
0653     DEBUG(Q_FUNC_INFO)
0654     switch (dim) {
0655     case Dimension::X:
0656         return d->xScales; // TODO: should rather return a copy of the scales here
0657     case Dimension::Y:
0658         return d->yScales; // TODO: should rather return a copy of the scales here
0659     }
0660     return QVector<CartesianScale*>();
0661 }
0662 
0663 int CartesianCoordinateSystem::index(const Dimension dim) const {
0664     switch (dim) {
0665     case Dimension::X:
0666         return d->xIndex;
0667     case Dimension::Y:
0668         return d->yIndex;
0669     }
0670     return 0;
0671 }
0672 
0673 void CartesianCoordinateSystem::setIndex(const Dimension dim, const int index) {
0674     switch (dim) {
0675     case Dimension::X:
0676         d->xIndex = index;
0677         d->xScales.clear();
0678         break;
0679     case Dimension::Y:
0680         d->yIndex = index;
0681         d->yScales.clear();
0682         break;
0683     }
0684 }
0685 
0686 /*!
0687  * Adjusted the function QRectF::contains(QPointF) from Qt 4.8.4 to handle the
0688  * comparison of float numbers correctly.
0689  * TODO: check whether the newer versions of Qt do the comparison correctly.
0690  */
0691 bool CartesianCoordinateSystem::rectContainsPoint(const QRectF& rect, QPointF point) const {
0692     qreal l = rect.x();
0693     qreal r = rect.x();
0694     qreal w = rect.width();
0695     qreal h = rect.height();
0696     if (w < 0)
0697         l += w;
0698     else
0699         r += w;
0700     if (nsl_math_essentially_equal(l, r)) // null rect
0701         return false;
0702 
0703     if (nsl_math_definitely_less_than(point.x(), l) || nsl_math_definitely_greater_than(point.x(), r))
0704         return false;
0705 
0706     qreal t = rect.y();
0707     qreal b = rect.y();
0708     if (h < 0)
0709         t += h;
0710     else
0711         b += h;
0712     if (nsl_math_essentially_equal(t, b)) // null rect
0713         return false;
0714 
0715     if (nsl_math_definitely_less_than(point.y(), t) || nsl_math_definitely_greater_than(point.y(), b))
0716         return false;
0717 
0718     return true;
0719 }
0720 
0721 // ##############################################################################
0722 // ######################### Private implementation #############################
0723 // ##############################################################################
0724 CartesianCoordinateSystemPrivate::CartesianCoordinateSystemPrivate(CartesianCoordinateSystem* owner)
0725     : q(owner) {
0726 }
0727 
0728 CartesianCoordinateSystemPrivate::~CartesianCoordinateSystemPrivate() {
0729     while (!xScales.isEmpty())
0730         delete xScales.takeFirst();
0731 
0732     while (!yScales.isEmpty())
0733         delete yScales.takeFirst();
0734 }