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