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 }