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

0001 /*
0002     File                 : CartesianPlotPrivate.h
0003     Project              : LabPlot
0004     Description          : Private members of CartesianPlot.
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2014-2017 Alexander Semke <alexander.semke@web.de>
0007     SPDX-FileCopyrightText: 2020 Stefan Gerlach <stefan.gerlach@uni.kn>
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #ifndef CARTESIANPLOTPRIVATE_H
0012 #define CARTESIANPLOTPRIVATE_H
0013 
0014 #include "../AbstractPlotPrivate.h"
0015 #include "CartesianCoordinateSystem.h"
0016 #include "CartesianPlot.h"
0017 #include "backend/worksheet/Worksheet.h"
0018 
0019 #include <QGraphicsSceneMouseEvent>
0020 #include <QPen>
0021 #include <QStaticText>
0022 
0023 using Dimension = CartesianCoordinateSystem::Dimension;
0024 
0025 class CartesianPlotPrivate : public AbstractPlotPrivate {
0026 public:
0027     explicit CartesianPlotPrivate(CartesianPlot*);
0028     ~CartesianPlotPrivate();
0029 
0030     void retransform() override;
0031     void retransformScale(const Dimension, int index, bool suppressSignals = false);
0032     void retransformScales(int xIndex, int yIndex);
0033     void rangeChanged();
0034     void niceExtendChanged();
0035     void rangeFormatChanged(const Dimension dim);
0036     void wheelEvent(const QPointF& sceneRelPos, int delta, int xIndex, int yIndex, bool considerDimension, Dimension dim);
0037     void mouseMoveZoomSelectionMode(QPointF logicalPos, int cSystemIndex);
0038     void mouseMoveSelectionMode(QPointF logicalStart, QPointF logicalEnd);
0039     void mouseMoveCursorMode(int cursorNumber, QPointF logicalPos);
0040     void mouseReleaseZoomSelectionMode(int cSystemIndex, bool suppressRetransform = false);
0041     void mouseHoverZoomSelectionMode(QPointF logicPos, int cSystemIndex);
0042     void mouseHoverOutsideDataRect();
0043     void mousePressZoomSelectionMode(QPointF logicalPos, int cSystemIndex);
0044     void mousePressCursorMode(int cursorNumber, QPointF logicalPos);
0045     void updateCursor();
0046     void setZoomSelectionBandShow(bool show);
0047     bool translateRange(int xIndex, int yIndex, const QPointF& logicalStart, const QPointF& logicalEnd, bool translateX, bool translateY);
0048 
0049     CartesianPlot::Type type{CartesianPlot::Type::FourAxes};
0050     QString theme;
0051     QRectF dataRect;
0052     CartesianPlot::RangeType rangeType{CartesianPlot::RangeType::Free};
0053     int rangeFirstValues{1000}, rangeLastValues{1000};
0054 
0055     struct RichRange {
0056         RichRange(const Range<double>& r = Range<double>())
0057             : range(r) {
0058             if (!range.autoScale())
0059                 dirty = true;
0060             else
0061                 dataRange = range;
0062         }
0063         Range<double> range; // current range
0064         Range<double> prev{Range<double>(NAN, NAN)};
0065         Range<double> dataRange; // range of data in plot. Cached to be faster in autoscaling/rescaling
0066         bool dirty{false}; // recalculate the range before displaying, because data range or display range changed
0067     };
0068 
0069     QVector<RichRange>& ranges(const Dimension dim) {
0070         switch (dim) {
0071         case Dimension::X:
0072             return xRanges;
0073         case Dimension::Y:
0074             return yRanges;
0075         }
0076         return yRanges;
0077     }
0078 
0079     bool rangeDirty(const Dimension dim, int index) const {
0080         if (index < -1 || index >= rangeCount(dim)) {
0081             DEBUG(Q_FUNC_INFO << QStringLiteral("WARNING: wrong index: %1").arg(index).toStdString());
0082             return false;
0083         } else if (index == -1)
0084             index = defaultCoordinateSystem()->index(dim);
0085         switch (dim) {
0086         case Dimension::X:
0087             return xRanges.at(index).dirty;
0088         case Dimension::Y:
0089             return yRanges.at(index).dirty;
0090         }
0091         return false;
0092     }
0093 
0094     void setRangeDirty(const Dimension dim, int index, const bool dirty) {
0095         if (index < -1 || index >= rangeCount(dim)) {
0096             DEBUG(Q_FUNC_INFO << QStringLiteral("WARNING: wrong index: %1").arg(index).toStdString());
0097             return;
0098         } else if (index == -1)
0099             index = defaultCoordinateSystem()->index(dim);
0100         switch (dim) {
0101         case Dimension::X:
0102             xRanges[index].dirty = dirty;
0103             break;
0104         case Dimension::Y:
0105             yRanges[index].dirty = dirty;
0106             break;
0107         }
0108     }
0109 
0110     void setRange(const Dimension dim, int index, const Range<double>& range) {
0111         if (index < -1 || index >= rangeCount(dim)) {
0112             DEBUG(Q_FUNC_INFO << QStringLiteral("WARNING: wrong index: %1").arg(index).toStdString());
0113             return;
0114         } else if (index == -1)
0115             index = defaultCoordinateSystem()->index(dim);
0116         switch (dim) {
0117         case Dimension::X:
0118             xRanges[index].range = range;
0119             break;
0120         case Dimension::Y:
0121             yRanges[index].range = range;
0122             break;
0123         }
0124     }
0125 
0126     void setFormat(const Dimension dim, int index, RangeT::Format format) {
0127         if (index < -1 || index >= rangeCount(dim)) {
0128             DEBUG(Q_FUNC_INFO << QStringLiteral("WARNING: wrong index: %1").arg(index).toStdString());
0129             return;
0130         } else if (index == -1)
0131             index = defaultCoordinateSystem()->index(dim);
0132         switch (dim) {
0133         case Dimension::X:
0134             xRanges[index].range.setFormat(format);
0135             break;
0136         case Dimension::Y:
0137             yRanges[index].range.setFormat(format);
0138             break;
0139         }
0140     }
0141 
0142     void setScale(const Dimension dim, int index, RangeT::Scale scale) {
0143         if (index < -1 || index >= rangeCount(dim)) {
0144             DEBUG(Q_FUNC_INFO << QStringLiteral("WARNING: wrong index: %1").arg(index).toStdString());
0145             index = defaultCoordinateSystem()->index(dim);
0146         } else if (index == -1)
0147             index = defaultCoordinateSystem()->index(dim);
0148         switch (dim) {
0149         case Dimension::X:
0150             xRanges[index].range.setScale(scale);
0151             break;
0152         case Dimension::Y:
0153             yRanges[index].range.setScale(scale);
0154             break;
0155         }
0156     }
0157 
0158     Range<double>& range(const Dimension dim, int index = -1) {
0159         if (index < -1 || index >= rangeCount(dim)) {
0160             DEBUG(Q_FUNC_INFO << QStringLiteral("WARNING: wrong index: %1").arg(index).toStdString());
0161             index = defaultCoordinateSystem()->index(dim);
0162         } else if (index == -1)
0163             index = defaultCoordinateSystem()->index(dim);
0164         switch (dim) {
0165         case Dimension::X:
0166             return xRanges[index].range;
0167         case Dimension::Y:
0168             return yRanges[index].range;
0169         }
0170         return yRanges[index].range;
0171     }
0172 
0173     const Range<double>& rangeConst(const Dimension dim, int index = -1) const {
0174         if (index < -1 || index >= rangeCount(dim)) {
0175             DEBUG(Q_FUNC_INFO << QStringLiteral("WARNING: wrong index: %1").arg(index).toStdString());
0176             index = defaultCoordinateSystem()->index(dim);
0177         } else if (index == -1)
0178             index = defaultCoordinateSystem()->index(dim);
0179         switch (dim) {
0180         case Dimension::X:
0181             return xRanges[index].range;
0182         case Dimension::Y:
0183             return yRanges[index].range;
0184         }
0185         return yRanges[index].range;
0186     }
0187 
0188     Range<double>& dataRange(const Dimension dim, int index = -1) {
0189         if (index < -1 || index >= rangeCount(dim)) {
0190             DEBUG(Q_FUNC_INFO << QStringLiteral("WARNING: wrong index: %1").arg(index).toStdString());
0191             index = defaultCoordinateSystem()->index(dim);
0192         } else if (index == -1)
0193             index = defaultCoordinateSystem()->index(dim);
0194 
0195         switch (dim) {
0196         case Dimension::X:
0197             return xRanges[index].dataRange;
0198         case Dimension::Y:
0199             break;
0200         }
0201         return yRanges[index].dataRange;
0202     }
0203 
0204     bool autoScale(const Dimension dim, int index = -1) const {
0205         if (index < -1 || index >= rangeCount(dim)) {
0206             DEBUG(Q_FUNC_INFO << QStringLiteral("WARNING: wrong index: %1").arg(index).toStdString());
0207             return false;
0208         }
0209         if (index == -1) {
0210             for (int i = 0; i < rangeCount(dim); i++)
0211                 if (!autoScale(dim, i))
0212                     return false;
0213             return true;
0214         }
0215 
0216         switch (dim) {
0217         case Dimension::X:
0218             return xRanges[index].range.autoScale();
0219         case Dimension::Y:
0220             return yRanges[index].range.autoScale();
0221         }
0222         return yRanges[index].range.autoScale();
0223     }
0224 
0225     int rangeCount(const Dimension dim) const {
0226         switch (dim) {
0227         case Dimension::X:
0228             return xRanges.size();
0229         case Dimension::Y:
0230             return yRanges.size();
0231         }
0232         return 0;
0233     }
0234 
0235     void enableAutoScale(const Dimension dim, int index = -1, bool b = true) {
0236         if (index < -1 || index >= rangeCount(dim)) {
0237             DEBUG(Q_FUNC_INFO << QStringLiteral("WARNING: wrong index: %1").arg(index).toStdString());
0238             return;
0239         }
0240         if (index == -1) {
0241             for (int i = 0; i < rangeCount(dim); i++)
0242                 enableAutoScale(dim, i, b);
0243             return;
0244         }
0245 
0246         switch (dim) {
0247         case Dimension::X:
0248             xRanges[index].range.setAutoScale(b);
0249             break;
0250         case Dimension::Y:
0251             yRanges[index].range.setAutoScale(b);
0252             break;
0253         }
0254     }
0255 
0256     void checkRange(Dimension, int index);
0257     Range<double> checkRange(const Range<double>&);
0258     CartesianPlot::RangeBreaks rangeBreaks(Dimension);
0259     bool rangeBreakingEnabled(Dimension);
0260 
0261     // the following factor determines the size of the offset between the min/max points of the curves
0262     // and the coordinate system ranges, when doing auto scaling
0263     // Factor 0 corresponds to the exact match - min/max values of the curves correspond to the start/end values of the ranges.
0264     // TODO: make this factor optional.
0265     // Provide in the UI the possibility to choose between "exact" or 0% offset, 2%, 5% and 10% for the auto fit option
0266     double autoScaleOffsetFactor{0.0};
0267     // TODO: move to Range?
0268     bool xRangeBreakingEnabled{false}, yRangeBreakingEnabled{false};
0269     CartesianPlot::RangeBreaks xRangeBreaks, yRangeBreaks;
0270 
0271     // cached values of minimum and maximum for all visible curves
0272     // Range<double> curvesXRange{qInf(), -qInf()}, curvesYRange{qInf(), -qInf()};
0273 
0274     CartesianPlot* const q;
0275     int defaultCoordinateSystemIndex{0};
0276 
0277     QVector<RichRange> xRanges{{}}, yRanges{{}}; // at least one range must exist.
0278     bool niceExtend{true};
0279     CartesianCoordinateSystem* coordinateSystem(int index) const;
0280     QVector<AbstractCoordinateSystem*> coordinateSystems() const;
0281     CartesianCoordinateSystem* defaultCoordinateSystem() const {
0282         return static_cast<CartesianCoordinateSystem*>(q->m_coordinateSystems.at(defaultCoordinateSystemIndex));
0283     }
0284 
0285     CartesianPlot::MouseMode mouseMode{CartesianPlot::MouseMode::Selection};
0286     bool panningStarted{false};
0287     bool interactive{true};
0288     QPointF scenePos; // current position under the mouse cursor in scene coordinates
0289     QPointF logicalPos; // current position under the mouse cursor in plot coordinates
0290     bool calledFromContextMenu{false}; // we set the current position under the cursor when "add new" is called via the context menu
0291 
0292     // Cursor
0293     bool cursor0Enable{false};
0294     int selectedCursor{0};
0295     QPointF cursor0Pos{QPointF(qQNaN(), qQNaN())};
0296     bool cursor1Enable{false};
0297     QPointF cursor1Pos{QPointF(qQNaN(), qQNaN())};
0298     Line* cursorLine{nullptr};
0299 
0300     // other mouse cursor modes
0301     QPen zoomSelectPen{Qt::black, 3, Qt::SolidLine};
0302     QPen crossHairPen{Qt::black, 2, Qt::DotLine};
0303 
0304 Q_SIGNALS:
0305     void mousePressZoomSelectionModeSignal(QPointF logicalPos);
0306     void mousePressCursorModeSignal(QPointF logicalPos);
0307 
0308 private:
0309     QVariant itemChange(GraphicsItemChange change, const QVariant& value) override;
0310     virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent*) override;
0311     virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override;
0312     virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override;
0313     virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*) override;
0314     virtual void wheelEvent(QGraphicsSceneWheelEvent*) override;
0315     virtual void keyPressEvent(QKeyEvent*) override;
0316     virtual void hoverMoveEvent(QGraphicsSceneHoverEvent*) override;
0317     virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override;
0318     virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget* widget = nullptr) override;
0319 
0320     void updateDataRect();
0321     CartesianScale* createScale(RangeT::Scale, const Range<double>& sceneRange, const Range<double>& logicalRange);
0322 
0323     void navigateNextPrevCurve(bool next = true) const;
0324 
0325     bool m_insideDataRect{false};
0326     bool m_selectionBandIsShown{false};
0327     QPointF m_selectionStart;
0328     QPointF m_selectionEnd;
0329     QLineF m_selectionStartLine;
0330     QPointF m_panningStart;
0331     QPointF m_crosshairPos; // current position of the mouse cursor in scene coordinates
0332 
0333     QStaticText m_cursor0Text{QStringLiteral("1")};
0334     QStaticText m_cursor1Text{QStringLiteral("2")};
0335 
0336     friend class MultiRangeTest2;
0337     friend class CartesianPlotTest;
0338 };
0339 
0340 #endif