File indexing completed on 2025-01-26 03:34:09
0001 /* 0002 File : CartesianPlot.cpp 0003 Project : LabPlot 0004 Description : Cartesian plot 0005 -------------------------------------------------------------------- 0006 SPDX-FileCopyrightText: 2011-2023 Alexander Semke <alexander.semke@web.de> 0007 SPDX-FileCopyrightText: 2016-2021 Stefan Gerlach <stefan.gerlach@uni.kn> 0008 SPDX-FileCopyrightText: 2017-2018 Garvit Khatri <garvitdelhi@gmail.com> 0009 SPDX-License-Identifier: GPL-2.0-or-later 0010 */ 0011 0012 #include "CartesianPlot.h" 0013 #include "CartesianPlotPrivate.h" 0014 #include "Histogram.h" 0015 #include "XYConvolutionCurve.h" 0016 #include "XYCorrelationCurve.h" 0017 #include "XYCurve.h" 0018 #include "XYDataReductionCurve.h" 0019 #include "XYDifferentiationCurve.h" 0020 #include "XYEquationCurve.h" 0021 #include "XYFitCurve.h" 0022 #include "XYFourierFilterCurve.h" 0023 #include "XYFourierTransformCurve.h" 0024 #include "XYHilbertTransformCurve.h" 0025 #include "XYIntegrationCurve.h" 0026 #include "XYInterpolationCurve.h" 0027 #include "XYSmoothCurve.h" 0028 #include "backend/core/Project.h" 0029 #include "backend/core/column/Column.h" 0030 #include "backend/core/datatypes/DateTime2StringFilter.h" 0031 #include "backend/lib/XmlStreamReader.h" 0032 #include "backend/lib/commandtemplates.h" 0033 #include "backend/lib/macros.h" 0034 #include "backend/lib/trace.h" 0035 #include "backend/worksheet/Image.h" 0036 #include "backend/worksheet/InfoElement.h" 0037 #include "backend/worksheet/Line.h" 0038 #include "backend/worksheet/TextLabel.h" 0039 #include "backend/worksheet/Worksheet.h" 0040 #include "backend/worksheet/plots/PlotArea.h" 0041 #include "backend/worksheet/plots/cartesian/Axis.h" 0042 #include "backend/worksheet/plots/cartesian/BarPlot.h" 0043 #include "backend/worksheet/plots/cartesian/BoxPlot.h" 0044 #include "backend/worksheet/plots/cartesian/CartesianPlotLegend.h" 0045 #include "backend/worksheet/plots/cartesian/CustomPoint.h" 0046 #include "backend/worksheet/plots/cartesian/ErrorBar.h" 0047 #include "backend/worksheet/plots/cartesian/KDEPlot.h" 0048 #include "backend/worksheet/plots/cartesian/LollipopPlot.h" 0049 #include "backend/worksheet/plots/cartesian/QQPlot.h" 0050 #include "backend/worksheet/plots/cartesian/ReferenceLine.h" 0051 #include "backend/worksheet/plots/cartesian/ReferenceRange.h" 0052 #include "backend/worksheet/plots/cartesian/Symbol.h" 0053 #include "kdefrontend/ThemeHandler.h" 0054 #include "kdefrontend/widgets/ThemesWidget.h" 0055 0056 #include <KConfig> 0057 #include <KConfigGroup> 0058 #include <KLocalizedString> 0059 0060 #include <QDir> 0061 #include <QIcon> 0062 #include <QKeyEvent> 0063 #include <QMenu> 0064 #include <QPainter> 0065 #include <QWidgetAction> 0066 0067 using Dimension = CartesianCoordinateSystem::Dimension; 0068 0069 namespace { 0070 enum Action { 0071 0072 New = 0x1000, 0073 NewTextLabel = 0x1001, 0074 NewCustomPoint = 0x1002, 0075 NewReferenceRange = 0x1003, 0076 NewReferenceLine = 0x1004, 0077 NewImage = 0x1005, 0078 0079 NavigateNextCurve = 0x2001, 0080 NavigatePrevCurve = 0x2002, 0081 0082 Move = 0x4000, 0083 MoveLeft = 0x4001, 0084 MoveRight = 0x4002, 0085 MoveUp = 0x4003, 0086 MoveDown = 0x4004, 0087 0088 Abort = 0x8000, 0089 0090 }; 0091 0092 Action evaluateKeys(int key, Qt::KeyboardModifiers) { 0093 if (key == Qt::Key_N) 0094 return Action::NavigateNextCurve; 0095 else if (key == Qt::Key_P) 0096 return Action::NavigatePrevCurve; 0097 else if (key == Qt::Key_T) 0098 return Action::NewTextLabel; 0099 else if (key == Qt::Key_R) 0100 return Action::NewReferenceRange; 0101 else if (key == Qt::Key_L) 0102 return Action::NewReferenceLine; 0103 else if (key == Qt::Key_I) 0104 return Action::NewImage; 0105 else if (key == Qt::Key_N) 0106 return Action::NavigateNextCurve; 0107 else if (key == Qt::Key_P) 0108 return Action::NavigatePrevCurve; 0109 else if (key == Qt::Key_M) 0110 return Action::NewCustomPoint; 0111 else if (key == Qt::Key_Escape) 0112 return Action::Abort; 0113 else if (key == Qt::Key_Left) 0114 return Action::MoveLeft; 0115 else if (key == Qt::Key_Right) 0116 return Action::MoveRight; 0117 else if (key == Qt::Key_Up) 0118 return Action::MoveUp; 0119 else if (key == Qt::Key_Down) 0120 return Action::MoveDown; 0121 return Action::Abort; 0122 } 0123 } 0124 0125 /** 0126 * \class CartesianPlot 0127 * \brief A xy-plot. 0128 */ 0129 CartesianPlot::CartesianPlot(const QString& name) 0130 : AbstractPlot(name, new CartesianPlotPrivate(this), AspectType::CartesianPlot) { 0131 init(); 0132 } 0133 0134 CartesianPlot::CartesianPlot(const QString& name, CartesianPlotPrivate* dd) 0135 : AbstractPlot(name, dd, AspectType::CartesianPlot) { 0136 init(); 0137 } 0138 0139 CartesianPlot::~CartesianPlot() { 0140 if (m_menusInitialized) { 0141 delete m_addNewMenu; 0142 delete dataAnalysisMenu; 0143 delete themeMenu; 0144 } 0145 0146 while (!m_coordinateSystems.isEmpty()) 0147 delete m_coordinateSystems.takeFirst(); 0148 0149 // no need to delete objects added with addChild() 0150 0151 // no need to delete the d-pointer here - it inherits from QGraphicsItem 0152 // and is deleted during the cleanup in QGraphicsScene 0153 } 0154 0155 /*! 0156 initializes all member variables of \c CartesianPlot 0157 */ 0158 void CartesianPlot::init() { 0159 m_coordinateSystems.append(new CartesianCoordinateSystem(this)); 0160 m_plotArea = new PlotArea(name() + QStringLiteral(" plot area"), this); 0161 connect(m_plotArea, &WorksheetElement::changed, this, &WorksheetElement::changed); 0162 addChildFast(m_plotArea); 0163 0164 // title 0165 m_title = new TextLabel(this->name() + QLatin1String(" - ") + i18n("Title"), TextLabel::Type::PlotTitle); 0166 addChild(m_title); 0167 m_title->setHidden(true); 0168 m_title->setParentGraphicsItem(m_plotArea->graphicsItem()); 0169 0170 // offset between the plot area and the area defining the coordinate system, in scene units. 0171 Q_D(CartesianPlot); 0172 d->horizontalPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Unit::Centimeter); 0173 d->verticalPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Unit::Centimeter); 0174 d->rightPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Unit::Centimeter); 0175 d->bottomPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Unit::Centimeter); 0176 d->symmetricPadding = true; 0177 0178 // cursor line 0179 d->cursorLine = new Line(QString()); 0180 d->cursorLine->setPrefix(QLatin1String("Cursor")); 0181 d->cursorLine->setHidden(true); 0182 addChild(d->cursorLine); 0183 d->cursorLine->setStyle(Qt::SolidLine); 0184 d->cursorLine->setColor(Qt::red); // TODO: use theme specific initial settings 0185 d->cursorLine->setWidth(Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point)); 0186 connect(d->cursorLine, &Line::updatePixmapRequested, [=] { 0187 d->update(); 0188 }); 0189 connect(d->cursorLine, &Line::updateRequested, [=] { 0190 d->update(); 0191 }); 0192 0193 connect(this, &AbstractAspect::childAspectAdded, this, &CartesianPlot::childAdded); 0194 connect(this, &AbstractAspect::childAspectRemoved, this, &CartesianPlot::childRemoved); 0195 0196 graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true); 0197 graphicsItem()->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true); 0198 graphicsItem()->setFlag(QGraphicsItem::ItemIsSelectable, true); 0199 graphicsItem()->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); 0200 graphicsItem()->setFlag(QGraphicsItem::ItemIsFocusable, true); 0201 0202 // theme is not set at this point, initialize the color palette with default colors 0203 this->setColorPalette(KConfig()); 0204 } 0205 0206 /*! 0207 initializes all children of \c CartesianPlot and 0208 setups a default plot of type \c type with a plot title. 0209 */ 0210 void CartesianPlot::setType(Type type) { 0211 Q_D(CartesianPlot); 0212 0213 d->type = type; 0214 0215 switch (type) { 0216 case Type::FourAxes: { 0217 // Axes 0218 Axis* axis = new Axis(QLatin1String("x"), Axis::Orientation::Horizontal); 0219 axis->setDefault(true); 0220 axis->setSuppressRetransform(true); 0221 addChild(axis); 0222 axis->setPosition(Axis::Position::Bottom); 0223 axis->setRange(0., 1.); 0224 axis->setMajorTicksDirection(Axis::ticksIn); 0225 axis->setMinorTicksDirection(Axis::ticksIn); 0226 axis->setMinorTicksNumber(1); 0227 axis->setSuppressRetransform(false); 0228 0229 axis = new Axis(QLatin1String("x2"), Axis::Orientation::Horizontal); 0230 axis->title()->setText(QString()); 0231 axis->setDefault(true); 0232 axis->setSuppressRetransform(true); 0233 0234 addChild(axis); 0235 axis->setPosition(Axis::Position::Top); 0236 axis->setRange(0., 1.); 0237 axis->setMajorTicksDirection(Axis::ticksIn); 0238 axis->setMinorTicksDirection(Axis::ticksIn); 0239 axis->setMinorTicksNumber(1); 0240 axis->majorGridLine()->setStyle(Qt::NoPen); 0241 axis->minorGridLine()->setStyle(Qt::NoPen); 0242 axis->setLabelsPosition(Axis::LabelsPosition::NoLabels); 0243 axis->setSuppressRetransform(false); 0244 0245 axis = new Axis(QLatin1String("y"), Axis::Orientation::Vertical); 0246 axis->setDefault(true); 0247 axis->setSuppressRetransform(true); 0248 addChild(axis); 0249 axis->setPosition(Axis::Position::Left); 0250 axis->setRange(0., 1.); 0251 axis->setMajorTicksDirection(Axis::ticksIn); 0252 axis->setMinorTicksDirection(Axis::ticksIn); 0253 axis->setMinorTicksNumber(1); 0254 axis->setSuppressRetransform(false); 0255 0256 axis = new Axis(QLatin1String("y2"), Axis::Orientation::Vertical); 0257 axis->title()->setText(QString()); 0258 axis->setDefault(true); 0259 axis->setSuppressRetransform(true); 0260 addChild(axis); 0261 axis->setPosition(Axis::Position::Right); 0262 axis->setRange(0., 1.); 0263 axis->setMajorTicksDirection(Axis::ticksIn); 0264 axis->setMinorTicksDirection(Axis::ticksIn); 0265 axis->setMinorTicksNumber(1); 0266 axis->majorGridLine()->setStyle(Qt::NoPen); 0267 axis->minorGridLine()->setStyle(Qt::NoPen); 0268 axis->setLabelsPosition(Axis::LabelsPosition::NoLabels); 0269 axis->setSuppressRetransform(false); 0270 0271 break; 0272 } 0273 case Type::TwoAxes: { 0274 Axis* axis = new Axis(QLatin1String("x"), Axis::Orientation::Horizontal); 0275 axis->setDefault(true); 0276 axis->setSuppressRetransform(true); 0277 addChild(axis); 0278 axis->setPosition(Axis::Position::Bottom); 0279 axis->setRange(0., 1.); 0280 axis->setMajorTicksDirection(Axis::ticksBoth); 0281 axis->setMinorTicksDirection(Axis::ticksBoth); 0282 axis->setMinorTicksNumber(1); 0283 axis->setArrowType(Axis::ArrowType::FilledSmall); 0284 axis->setSuppressRetransform(false); 0285 0286 axis = new Axis(QLatin1String("y"), Axis::Orientation::Vertical); 0287 axis->setDefault(true); 0288 axis->setSuppressRetransform(true); 0289 addChild(axis); 0290 axis->setPosition(Axis::Position::Left); 0291 axis->setRange(0., 1.); 0292 axis->setMajorTicksDirection(Axis::ticksBoth); 0293 axis->setMinorTicksDirection(Axis::ticksBoth); 0294 axis->setMinorTicksNumber(1); 0295 axis->setArrowType(Axis::ArrowType::FilledSmall); 0296 axis->setSuppressRetransform(false); 0297 0298 break; 0299 } 0300 case Type::TwoAxesCentered: { 0301 d->xRanges[0].range.setRange(-0.5, 0.5); 0302 d->yRanges[0].range.setRange(-0.5, 0.5); 0303 0304 d->horizontalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Centimeter); 0305 d->verticalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Centimeter); 0306 0307 m_plotArea->borderLine()->setStyle(Qt::NoPen); 0308 0309 Axis* axis = new Axis(QLatin1String("x"), Axis::Orientation::Horizontal); 0310 axis->title()->setText(QString()); 0311 axis->setDefault(true); 0312 axis->setSuppressRetransform(true); 0313 addChild(axis); 0314 axis->setPosition(Axis::Position::Centered); 0315 axis->setRange(-0.5, 0.5); 0316 axis->setMajorTicksDirection(Axis::ticksBoth); 0317 axis->setMinorTicksDirection(Axis::ticksBoth); 0318 axis->setMinorTicksNumber(1); 0319 axis->setArrowType(Axis::ArrowType::FilledSmall); 0320 axis->setSuppressRetransform(false); 0321 0322 axis = new Axis(QLatin1String("y"), Axis::Orientation::Vertical); 0323 axis->title()->setText(QString()); 0324 axis->setDefault(true); 0325 axis->setSuppressRetransform(true); 0326 addChild(axis); 0327 axis->setPosition(Axis::Position::Centered); 0328 axis->setRange(-0.5, 0.5); 0329 axis->setMajorTicksDirection(Axis::ticksBoth); 0330 axis->setMinorTicksDirection(Axis::ticksBoth); 0331 axis->setMinorTicksNumber(1); 0332 axis->setArrowType(Axis::ArrowType::FilledSmall); 0333 axis->setSuppressRetransform(false); 0334 0335 break; 0336 } 0337 case Type::TwoAxesCenteredZero: { 0338 d->xRanges[0].range.setRange(-0.5, 0.5); 0339 d->yRanges[0].range.setRange(-0.5, 0.5); 0340 0341 d->horizontalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Centimeter); 0342 d->verticalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Centimeter); 0343 0344 m_plotArea->borderLine()->setStyle(Qt::NoPen); 0345 0346 Axis* axis = new Axis(QLatin1String("x"), Axis::Orientation::Horizontal); 0347 axis->title()->setText(QString()); 0348 axis->setDefault(true); 0349 axis->setSuppressRetransform(true); 0350 addChild(axis); 0351 axis->setPosition(Axis::Position::Logical); 0352 axis->setOffset(0); 0353 axis->setLogicalPosition(0); 0354 axis->setRange(-0.5, 0.5); 0355 axis->setMajorTicksDirection(Axis::ticksBoth); 0356 axis->setMinorTicksDirection(Axis::ticksBoth); 0357 axis->setMinorTicksNumber(1); 0358 axis->setArrowType(Axis::ArrowType::FilledSmall); 0359 axis->setSuppressRetransform(false); 0360 0361 axis = new Axis(QLatin1String("y"), Axis::Orientation::Vertical); 0362 axis->title()->setText(QString()); 0363 axis->setDefault(true); 0364 axis->setSuppressRetransform(true); 0365 addChild(axis); 0366 axis->setPosition(Axis::Position::Logical); 0367 axis->setOffset(0); 0368 axis->setLogicalPosition(0); 0369 axis->setRange(-0.5, 0.5); 0370 axis->setMajorTicksDirection(Axis::ticksBoth); 0371 axis->setMinorTicksDirection(Axis::ticksBoth); 0372 axis->setMinorTicksNumber(1); 0373 axis->setArrowType(Axis::ArrowType::FilledSmall); 0374 axis->setSuppressRetransform(false); 0375 0376 break; 0377 } 0378 } 0379 0380 d->xRanges[0].prev = range(Dimension::X); 0381 d->yRanges[0].prev = range(Dimension::Y); 0382 0383 // Geometry, specify the plot rect in scene coordinates. 0384 // TODO: Use default settings for left, top, width, height and for min/max for the coordinate system 0385 double x = Worksheet::convertToSceneUnits(2, Worksheet::Unit::Centimeter); 0386 double y = Worksheet::convertToSceneUnits(2, Worksheet::Unit::Centimeter); 0387 double w = Worksheet::convertToSceneUnits(10, Worksheet::Unit::Centimeter); 0388 double h = Worksheet::convertToSceneUnits(10, Worksheet::Unit::Centimeter); 0389 0390 // all plot children are initialized -> set the geometry of the plot in scene coordinates. 0391 d->rect = QRectF(x, y, w, h); 0392 0393 const auto* worksheet = static_cast<const Worksheet*>(parentAspect()); 0394 if (worksheet && worksheet->layout() != Worksheet::Layout::NoLayout) 0395 retransform(); 0396 } 0397 0398 CartesianPlot::Type CartesianPlot::type() const { 0399 Q_D(const CartesianPlot); 0400 return d->type; 0401 } 0402 0403 void CartesianPlot::initActions() { 0404 //"add new" actions 0405 addCurveAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve")), i18n("xy-curve"), this); 0406 addEquationCurveAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-equation-curve")), i18n("xy-curve from a Formula"), this); 0407 0408 // statistical plots 0409 addHistogramAction = new QAction(QIcon::fromTheme(QStringLiteral("view-object-histogram-linear")), i18n("Histogram"), this); 0410 addBoxPlotAction = new QAction(BoxPlot::staticIcon(), i18n("Box Plot"), this); 0411 addKDEPlotAction = new QAction(i18n("KDE Plot"), this); 0412 addQQPlotAction = new QAction(i18n("Q-Q Plot"), this); 0413 0414 // bar plots 0415 addBarPlotAction = new QAction(QIcon::fromTheme(QStringLiteral("office-chart-bar")), i18n("Bar Plot"), this); 0416 addLollipopPlotAction = new QAction(LollipopPlot::staticIcon(), i18n("Lollipop Plot"), this); 0417 0418 // analysis curves, no icons yet 0419 addDataReductionCurveAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve")), i18n("Data Reduction"), this); 0420 addDifferentiationCurveAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve")), i18n("Differentiation"), this); 0421 addIntegrationCurveAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve")), i18n("Integration"), this); 0422 addInterpolationCurveAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-interpolation-curve")), i18n("Interpolation"), this); 0423 addSmoothCurveAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-smoothing-curve")), i18n("Smooth"), this); 0424 addFitCurveAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Fit"), this); 0425 addFourierFilterCurveAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fourier-filter-curve")), i18n("Fourier Filter"), this); 0426 addFourierTransformCurveAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fourier-transform-curve")), i18n("Fourier Transform"), this); 0427 addHilbertTransformCurveAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve")), i18n("Hilbert Transform"), this); 0428 addConvolutionCurveAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve")), i18n("(De-)Convolution"), this); 0429 addCorrelationCurveAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve")), i18n("Auto-/Cross-Correlation"), this); 0430 0431 addLegendAction = new QAction(QIcon::fromTheme(QStringLiteral("text-field")), i18n("Legend"), this); 0432 if (children<CartesianPlotLegend>().size() > 0) 0433 addLegendAction->setEnabled(false); // only one legend is allowed -> disable the action 0434 0435 addHorizontalAxisAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-axis-horizontal")), i18n("Horizontal Axis"), this); 0436 addVerticalAxisAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-axis-vertical")), i18n("Vertical Axis"), this); 0437 addTextLabelAction = new QAction(QIcon::fromTheme(QStringLiteral("draw-text")), i18n("Text"), this); 0438 addImageAction = new QAction(QIcon::fromTheme(QStringLiteral("viewimage")), i18n("Image"), this); 0439 addInfoElementAction = new QAction(QIcon::fromTheme(QStringLiteral("draw-text")), i18n("Info Element"), this); 0440 addCustomPointAction = new QAction(QIcon::fromTheme(QStringLiteral("draw-cross")), i18n("Custom Point"), this); 0441 addReferenceLineAction = new QAction(QIcon::fromTheme(QStringLiteral("draw-line")), i18n("Reference Line"), this); 0442 addReferenceRangeAction = new QAction(QIcon::fromTheme(QStringLiteral("draw-rectangle")), i18n("Reference Range"), this); 0443 0444 connect(addCurveAction, &QAction::triggered, this, [=]() { 0445 addChild(new XYCurve(QStringLiteral("xy-curve"))); 0446 }); 0447 connect(addEquationCurveAction, &QAction::triggered, this, [=]() { 0448 addChild(new XYEquationCurve(QStringLiteral("f(x)"))); 0449 }); 0450 0451 // bar plots 0452 connect(addBarPlotAction, &QAction::triggered, this, [=]() { 0453 addChild(new BarPlot(i18n("Bar Plot"))); 0454 }); 0455 connect(addLollipopPlotAction, &QAction::triggered, this, [=]() { 0456 addChild(new LollipopPlot(i18n("Lollipop Plot"))); 0457 }); 0458 0459 // statistical plots 0460 connect(addBoxPlotAction, &QAction::triggered, this, [=]() { 0461 addChild(new BoxPlot(i18n("Box Plot"))); 0462 }); 0463 connect(addHistogramAction, &QAction::triggered, this, [=]() { 0464 addChild(new Histogram(i18n("Histogram"))); 0465 }); 0466 connect(addQQPlotAction, &QAction::triggered, this, [=]() { 0467 addChild(new QQPlot(i18n("Q-Q Plot"))); 0468 }); 0469 connect(addKDEPlotAction, &QAction::triggered, this, [=]() { 0470 addChild(new KDEPlot(i18n("KDE Plot"))); 0471 }); 0472 0473 // analysis curves 0474 connect(addDataReductionCurveAction, &QAction::triggered, this, &CartesianPlot::addDataReductionCurve); 0475 connect(addDifferentiationCurveAction, &QAction::triggered, this, &CartesianPlot::addDifferentiationCurve); 0476 connect(addIntegrationCurveAction, &QAction::triggered, this, &CartesianPlot::addIntegrationCurve); 0477 connect(addInterpolationCurveAction, &QAction::triggered, this, &CartesianPlot::addInterpolationCurve); 0478 connect(addSmoothCurveAction, &QAction::triggered, this, &CartesianPlot::addSmoothCurve); 0479 connect(addFitCurveAction, &QAction::triggered, this, &CartesianPlot::addFitCurve); 0480 connect(addFourierFilterCurveAction, &QAction::triggered, this, &CartesianPlot::addFourierFilterCurve); 0481 connect(addFourierTransformCurveAction, &QAction::triggered, this, [=]() { 0482 addChild(new XYFourierTransformCurve(i18n("Fourier Transform"))); 0483 }); 0484 connect(addHilbertTransformCurveAction, &QAction::triggered, this, [=]() { 0485 addChild(new XYHilbertTransformCurve(i18n("Hilbert Transform"))); 0486 }); 0487 connect(addConvolutionCurveAction, &QAction::triggered, this, [=]() { 0488 addChild(new XYConvolutionCurve(i18n("Convolution"))); 0489 }); 0490 connect(addCorrelationCurveAction, &QAction::triggered, this, [=]() { 0491 addChild(new XYCorrelationCurve(i18n("Auto-/Cross-Correlation"))); 0492 }); 0493 0494 connect(addLegendAction, &QAction::triggered, this, static_cast<void (CartesianPlot::*)()>(&CartesianPlot::addLegend)); 0495 connect(addHorizontalAxisAction, &QAction::triggered, this, &CartesianPlot::addHorizontalAxis); 0496 connect(addVerticalAxisAction, &QAction::triggered, this, &CartesianPlot::addVerticalAxis); 0497 connect(addTextLabelAction, &QAction::triggered, this, &CartesianPlot::addTextLabel); 0498 connect(addImageAction, &QAction::triggered, this, &CartesianPlot::addImage); 0499 connect(addInfoElementAction, &QAction::triggered, this, &CartesianPlot::addInfoElement); 0500 connect(addCustomPointAction, &QAction::triggered, this, &CartesianPlot::addCustomPoint); 0501 connect(addReferenceLineAction, &QAction::triggered, this, &CartesianPlot::addReferenceLine); 0502 connect(addReferenceRangeAction, &QAction::triggered, this, &CartesianPlot::addReferenceRange); 0503 0504 // Analysis menu actions 0505 // addDataOperationAction = new QAction(i18n("Data Operation"), this); 0506 addDataReductionAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve")), i18n("Data Reduction"), this); 0507 addDifferentiationAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve")), i18n("Differentiate"), this); 0508 addIntegrationAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve")), i18n("Integrate"), this); 0509 addInterpolationAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-interpolation-curve")), i18n("Interpolate"), this); 0510 addSmoothAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-smoothing-curve")), i18n("Smooth"), this); 0511 addConvolutionAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve")), i18n("Convolute/Deconvolute"), this); 0512 addCorrelationAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve")), i18n("Auto-/Cross-Correlation"), this); 0513 0514 QAction* fitAction = new QAction(i18n("Linear"), this); 0515 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitLinear)); 0516 addFitActions.append(fitAction); 0517 0518 fitAction = new QAction(i18n("Power"), this); 0519 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitPower)); 0520 addFitActions.append(fitAction); 0521 0522 fitAction = new QAction(i18n("Exponential (degree 1)"), this); 0523 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitExp1)); 0524 addFitActions.append(fitAction); 0525 0526 fitAction = new QAction(i18n("Exponential (degree 2)"), this); 0527 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitExp2)); 0528 addFitActions.append(fitAction); 0529 0530 fitAction = new QAction(i18n("Inverse exponential"), this); 0531 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitInvExp)); 0532 addFitActions.append(fitAction); 0533 0534 fitAction = new QAction(i18n("Gauss"), this); 0535 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitGauss)); 0536 addFitActions.append(fitAction); 0537 0538 fitAction = new QAction(i18n("Cauchy-Lorentz"), this); 0539 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitCauchyLorentz)); 0540 addFitActions.append(fitAction); 0541 0542 fitAction = new QAction(i18n("Arc Tangent"), this); 0543 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitTan)); 0544 addFitActions.append(fitAction); 0545 0546 fitAction = new QAction(i18n("Hyperbolic Tangent"), this); 0547 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitTanh)); 0548 addFitActions.append(fitAction); 0549 0550 fitAction = new QAction(i18n("Error Function"), this); 0551 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitErrFunc)); 0552 addFitActions.append(fitAction); 0553 0554 fitAction = new QAction(i18n("Custom"), this); 0555 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitCustom)); 0556 addFitActions.append(fitAction); 0557 0558 addFourierFilterAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fourier-filter-curve")), i18n("Fourier Filter"), this); 0559 addFourierTransformAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fourier-transform-curve")), i18n("Fourier Transform"), this); 0560 addHilbertTransformAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve")), i18n("Hilbert Transform"), this); 0561 0562 connect(addDataReductionAction, &QAction::triggered, this, &CartesianPlot::addDataReductionCurve); 0563 connect(addDifferentiationAction, &QAction::triggered, this, &CartesianPlot::addDifferentiationCurve); 0564 connect(addIntegrationAction, &QAction::triggered, this, &CartesianPlot::addIntegrationCurve); 0565 connect(addInterpolationAction, &QAction::triggered, this, &CartesianPlot::addInterpolationCurve); 0566 connect(addSmoothAction, &QAction::triggered, this, &CartesianPlot::addSmoothCurve); 0567 connect(addConvolutionAction, &QAction::triggered, this, [=]() { 0568 addChild(new XYConvolutionCurve(i18n("Convolution"))); 0569 }); 0570 connect(addCorrelationAction, &QAction::triggered, this, [=]() { 0571 addChild(new XYCorrelationCurve(i18n("Auto-/Cross-Correlation"))); 0572 }); 0573 for (const auto& action : addFitActions) 0574 connect(action, &QAction::triggered, this, &CartesianPlot::addFitCurve); 0575 connect(addFourierFilterAction, &QAction::triggered, this, &CartesianPlot::addFourierFilterCurve); 0576 connect(addFourierTransformAction, &QAction::triggered, this, [=]() { 0577 addChild(new XYFourierTransformCurve(i18n("Fourier Transform"))); 0578 }); 0579 connect(addHilbertTransformAction, &QAction::triggered, this, [=]() { 0580 addChild(new XYHilbertTransformCurve(i18n("Hilbert Transform"))); 0581 }); 0582 } 0583 0584 void CartesianPlot::initMenus() { 0585 initActions(); 0586 0587 m_addNewMenu = new QMenu(i18n("Add New")); 0588 m_addNewMenu->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); 0589 m_addNewMenu->addAction(addCurveAction); 0590 m_addNewMenu->addAction(addEquationCurveAction); 0591 0592 auto* addNewStatisticalPlotsMenu = new QMenu(i18n("Statistical Plots"), m_addNewMenu); 0593 addNewStatisticalPlotsMenu->addAction(addHistogramAction); 0594 addNewStatisticalPlotsMenu->addAction(addBoxPlotAction); 0595 addNewStatisticalPlotsMenu->addAction(addKDEPlotAction); 0596 addNewStatisticalPlotsMenu->addAction(addQQPlotAction); 0597 m_addNewMenu->addMenu(addNewStatisticalPlotsMenu); 0598 0599 auto* addNewBarPlotsMenu = new QMenu(i18n("Bar Plots"), m_addNewMenu); 0600 addNewBarPlotsMenu->addAction(addBarPlotAction); 0601 addNewBarPlotsMenu->addAction(addLollipopPlotAction); 0602 m_addNewMenu->addMenu(addNewBarPlotsMenu); 0603 0604 m_addNewMenu->addSeparator(); 0605 0606 addNewAnalysisMenu = new QMenu(i18n("Analysis Curve"), m_addNewMenu); 0607 addNewAnalysisMenu->addAction(addFitCurveAction); 0608 addNewAnalysisMenu->addSeparator(); 0609 addNewAnalysisMenu->addAction(addDifferentiationCurveAction); 0610 addNewAnalysisMenu->addAction(addIntegrationCurveAction); 0611 addNewAnalysisMenu->addSeparator(); 0612 addNewAnalysisMenu->addAction(addInterpolationCurveAction); 0613 addNewAnalysisMenu->addAction(addSmoothCurveAction); 0614 addNewAnalysisMenu->addSeparator(); 0615 addNewAnalysisMenu->addAction(addFourierFilterCurveAction); 0616 addNewAnalysisMenu->addAction(addFourierTransformCurveAction); 0617 addNewAnalysisMenu->addAction(addHilbertTransformCurveAction); 0618 addNewAnalysisMenu->addSeparator(); 0619 addNewAnalysisMenu->addAction(addConvolutionCurveAction); 0620 addNewAnalysisMenu->addAction(addCorrelationCurveAction); 0621 addNewAnalysisMenu->addSeparator(); 0622 addNewAnalysisMenu->addAction(addDataReductionCurveAction); 0623 m_addNewMenu->addMenu(addNewAnalysisMenu); 0624 0625 m_addNewMenu->addSeparator(); 0626 m_addNewMenu->addAction(addLegendAction); 0627 m_addNewMenu->addSeparator(); 0628 m_addNewMenu->addAction(addHorizontalAxisAction); 0629 m_addNewMenu->addAction(addVerticalAxisAction); 0630 m_addNewMenu->addSeparator(); 0631 m_addNewMenu->addAction(addTextLabelAction); 0632 m_addNewMenu->addAction(addImageAction); 0633 m_addNewMenu->addAction(addInfoElementAction); 0634 m_addNewMenu->addSeparator(); 0635 m_addNewMenu->addAction(addCustomPointAction); 0636 m_addNewMenu->addAction(addReferenceLineAction); 0637 m_addNewMenu->addAction(addReferenceRangeAction); 0638 0639 // Data manipulation menu 0640 // QMenu* dataManipulationMenu = new QMenu(i18n("Data Manipulation")); 0641 // dataManipulationMenu->setIcon(QIcon::fromTheme(QStringLiteral("zoom-draw"))); 0642 // dataManipulationMenu->addAction(addDataOperationAction); 0643 // dataManipulationMenu->addAction(addDataReductionAction); 0644 0645 // analysis menu 0646 dataAnalysisMenu = new QMenu(i18n("Analysis")); 0647 0648 QMenu* dataFitMenu = new QMenu(i18n("Fit"), dataAnalysisMenu); 0649 dataFitMenu->setIcon(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve"))); 0650 dataFitMenu->addAction(addFitActions.at(0)); 0651 dataFitMenu->addAction(addFitActions.at(1)); 0652 dataFitMenu->addAction(addFitActions.at(2)); 0653 dataFitMenu->addAction(addFitActions.at(3)); 0654 dataFitMenu->addAction(addFitActions.at(4)); 0655 dataFitMenu->addSeparator(); 0656 dataFitMenu->addAction(addFitActions.at(5)); 0657 dataFitMenu->addAction(addFitActions.at(6)); 0658 dataFitMenu->addSeparator(); 0659 dataFitMenu->addAction(addFitActions.at(7)); 0660 dataFitMenu->addAction(addFitActions.at(8)); 0661 dataFitMenu->addAction(addFitActions.at(9)); 0662 dataFitMenu->addSeparator(); 0663 dataFitMenu->addAction(addFitActions.at(10)); 0664 dataAnalysisMenu->addMenu(dataFitMenu); 0665 0666 dataAnalysisMenu->addSeparator(); 0667 dataAnalysisMenu->addAction(addDifferentiationAction); 0668 dataAnalysisMenu->addAction(addIntegrationAction); 0669 dataAnalysisMenu->addSeparator(); 0670 dataAnalysisMenu->addAction(addInterpolationAction); 0671 dataAnalysisMenu->addAction(addSmoothAction); 0672 dataAnalysisMenu->addSeparator(); 0673 dataAnalysisMenu->addAction(addFourierFilterAction); 0674 dataAnalysisMenu->addAction(addFourierTransformAction); 0675 dataAnalysisMenu->addAction(addHilbertTransformAction); 0676 dataAnalysisMenu->addSeparator(); 0677 dataAnalysisMenu->addAction(addConvolutionAction); 0678 dataAnalysisMenu->addAction(addCorrelationAction); 0679 dataAnalysisMenu->addSeparator(); 0680 // dataAnalysisMenu->insertMenu(nullptr, dataManipulationMenu); 0681 dataAnalysisMenu->addAction(addDataReductionAction); 0682 0683 // theme menu 0684 themeMenu = new QMenu(i18n("Theme")); 0685 themeMenu->setIcon(QIcon::fromTheme(QStringLiteral("color-management"))); 0686 #ifndef SDK 0687 connect(themeMenu, &QMenu::aboutToShow, this, [=]() { 0688 if (!themeMenu->isEmpty()) 0689 return; 0690 auto* themeWidget = new ThemesWidget(nullptr); 0691 themeWidget->setFixedMode(); 0692 connect(themeWidget, &ThemesWidget::themeSelected, this, &CartesianPlot::loadTheme); 0693 connect(themeWidget, &ThemesWidget::themeSelected, themeMenu, &QMenu::close); 0694 0695 auto* widgetAction = new QWidgetAction(this); 0696 widgetAction->setDefaultWidget(themeWidget); 0697 themeMenu->addAction(widgetAction); 0698 }); 0699 #endif 0700 0701 m_menusInitialized = true; 0702 } 0703 0704 QMenu* CartesianPlot::createContextMenu() { 0705 if (!m_menusInitialized) 0706 initMenus(); 0707 0708 QMenu* menu = WorksheetElement::createContextMenu(); 0709 // seems to be a bug, because the tooltips are not shown 0710 menu->setToolTipsVisible(true); 0711 QAction* visibilityAction = this->visibilityAction(); 0712 0713 menu->insertMenu(visibilityAction, m_addNewMenu); 0714 menu->insertSeparator(visibilityAction); 0715 menu->insertMenu(visibilityAction, themeMenu); 0716 menu->insertSeparator(visibilityAction); 0717 0718 if (children<XYCurve>().isEmpty()) { 0719 addInfoElementAction->setEnabled(false); 0720 addInfoElementAction->setToolTip(QStringLiteral("No curve inside plot.")); 0721 } else { 0722 addInfoElementAction->setEnabled(true); 0723 addInfoElementAction->setToolTip(QString()); 0724 } 0725 0726 return menu; 0727 } 0728 0729 QMenu* CartesianPlot::addNewMenu() { 0730 if (!m_menusInitialized) 0731 initMenus(); 0732 0733 return m_addNewMenu; 0734 } 0735 0736 QMenu* CartesianPlot::analysisMenu() { 0737 if (!m_menusInitialized) 0738 initMenus(); 0739 0740 return dataAnalysisMenu; 0741 } 0742 0743 int CartesianPlot::cSystemIndex(WorksheetElement* e) { 0744 if (!e) 0745 return -1; 0746 0747 auto type = e->type(); 0748 if (type == AspectType::CartesianPlot) 0749 return -1; 0750 else if (dynamic_cast<Plot*>(e) || e->coordinateBindingEnabled() || type == AspectType::Axis) 0751 return e->coordinateSystemIndex(); 0752 return -1; 0753 } 0754 0755 /*! 0756 Returns an icon to be used in the project explorer. 0757 */ 0758 QIcon CartesianPlot::icon() const { 0759 return QIcon::fromTheme(QStringLiteral("office-chart-line")); 0760 } 0761 0762 QVector<AbstractAspect*> CartesianPlot::dependsOn() const { 0763 // aspects which the plotted data in the worksheet depends on (spreadsheets and later matrices) 0764 QVector<AbstractAspect*> aspects; 0765 0766 for (const auto* curve : children<XYCurve>()) { 0767 if (curve->xColumn() && curve->xColumn()->parentAspect()->type() == AspectType::Spreadsheet) 0768 aspects << curve->xColumn()->parentAspect(); 0769 0770 if (curve->yColumn() && curve->yColumn()->parentAspect()->type() == AspectType::Spreadsheet) 0771 aspects << curve->yColumn()->parentAspect(); 0772 } 0773 0774 return aspects; 0775 } 0776 0777 QVector<AspectType> CartesianPlot::pasteTypes() const { 0778 QVector<AspectType> types{AspectType::XYCurve, 0779 AspectType::Histogram, 0780 AspectType::BarPlot, 0781 AspectType::LollipopPlot, 0782 AspectType::BoxPlot, 0783 AspectType::KDEPlot, 0784 AspectType::QQPlot, 0785 AspectType::Axis, 0786 AspectType::XYEquationCurve, 0787 AspectType::XYConvolutionCurve, 0788 AspectType::XYCorrelationCurve, 0789 AspectType::XYDataReductionCurve, 0790 AspectType::XYDifferentiationCurve, 0791 AspectType::XYFitCurve, 0792 AspectType::XYFourierFilterCurve, 0793 AspectType::XYFourierTransformCurve, 0794 AspectType::XYIntegrationCurve, 0795 AspectType::XYInterpolationCurve, 0796 AspectType::XYSmoothCurve, 0797 AspectType::TextLabel, 0798 AspectType::Image, 0799 AspectType::InfoElement, 0800 AspectType::CustomPoint, 0801 AspectType::ReferenceLine}; 0802 0803 // only allow to paste a legend if there is no legend available yet in the plot 0804 if (!m_legend) 0805 types << AspectType::CartesianPlotLegend; 0806 0807 return types; 0808 } 0809 0810 void CartesianPlot::navigate(int cSystemIndex, NavigationOperation op) { 0811 PERFTRACE(QLatin1String(Q_FUNC_INFO)); 0812 const auto* cSystem = coordinateSystem(cSystemIndex); 0813 int xIndex = -1, yIndex = -1; 0814 if (cSystem) { 0815 xIndex = cSystem->index(Dimension::X); 0816 yIndex = cSystem->index(Dimension::Y); 0817 } 0818 0819 if (op == NavigationOperation::ScaleAuto) { 0820 if (!cSystem) { // all csystems 0821 for (int i = 0; i < coordinateSystemCount(); i++) { 0822 auto* cSystem = coordinateSystem(i); 0823 auto xDirty = rangeDirty(Dimension::X, cSystem->index(Dimension::X)); 0824 auto yDirty = rangeDirty(Dimension::Y, cSystem->index(Dimension::Y)); 0825 0826 if (xDirty || yDirty || !autoScale(Dimension::X, cSystem->index(Dimension::X)) || !autoScale(Dimension::Y, cSystem->index(Dimension::Y))) { 0827 setRangeDirty(Dimension::X, cSystem->index(Dimension::X), true); 0828 setRangeDirty(Dimension::Y, cSystem->index(Dimension::Y), true); 0829 } 0830 if (!autoScale(Dimension::X, cSystem->index(Dimension::X))) 0831 enableAutoScale(Dimension::X, cSystem->index(Dimension::X), true, true); 0832 else // if already autoscale set, scaleAutoX will not be called anymore, so force it to do 0833 scaleAuto(Dimension::X, cSystem->index(Dimension::X)); 0834 0835 if (!autoScale(Dimension::Y, cSystem->index(Dimension::Y))) 0836 enableAutoScale(Dimension::Y, cSystem->index(Dimension::Y), true, true); 0837 else 0838 scaleAuto(Dimension::Y, cSystem->index(Dimension::Y)); 0839 } 0840 WorksheetElementContainer::retransform(); 0841 } else { 0842 auto xDirty = rangeDirty(Dimension::X, xIndex); 0843 auto yDirty = rangeDirty(Dimension::Y, yIndex); 0844 0845 if (xDirty || yDirty || !autoScale(Dimension::X, xIndex) || !autoScale(Dimension::Y, yIndex)) { 0846 setRangeDirty(Dimension::X, xIndex, true); 0847 setRangeDirty(Dimension::Y, yIndex, true); 0848 } 0849 if (!autoScale(Dimension::X, cSystem->index(Dimension::X))) 0850 enableAutoScale(Dimension::X, cSystem->index(Dimension::X), true, true); 0851 else 0852 scaleAuto(Dimension::X, cSystem->index(Dimension::X), true); 0853 0854 if (!autoScale(Dimension::Y, cSystem->index(Dimension::Y))) 0855 enableAutoScale(Dimension::Y, cSystem->index(Dimension::Y), true, true); 0856 else 0857 scaleAuto(Dimension::Y, cSystem->index(Dimension::Y), true); 0858 WorksheetElementContainer::retransform(); 0859 } 0860 } else if (op == NavigationOperation::ScaleAutoX) { 0861 bool update = rangeDirty(Dimension::X, xIndex); 0862 if (!autoScale(Dimension::X, xIndex)) { 0863 enableAutoScale(Dimension::X, xIndex, true, true); 0864 update = true; 0865 } else 0866 update |= scaleAuto(Dimension::X, xIndex); 0867 if (update) { 0868 for (int i = 0; i < m_coordinateSystems.count(); i++) { 0869 auto cs = coordinateSystem(i); 0870 if ((cSystemIndex == -1 || xIndex == cs->index(Dimension::X)) && autoScale(Dimension::Y, cs->index(Dimension::Y))) 0871 scaleAuto(Dimension::Y, cs->index(Dimension::Y), false); 0872 } 0873 WorksheetElementContainer::retransform(); 0874 } 0875 } else if (op == NavigationOperation::ScaleAutoY) { 0876 bool update = rangeDirty(Dimension::Y, yIndex); 0877 if (!autoScale(Dimension::Y, yIndex)) { 0878 enableAutoScale(Dimension::Y, yIndex, true, true); 0879 update = true; 0880 } else 0881 update |= scaleAuto(Dimension::Y, yIndex); 0882 if (update) { 0883 for (int i = 0; i < m_coordinateSystems.count(); i++) { 0884 auto cs = coordinateSystem(i); 0885 if ((cSystemIndex == -1 || yIndex == cs->index(Dimension::Y)) && autoScale(Dimension::X, cs->index(Dimension::X))) 0886 scaleAuto(Dimension::X, cs->index(Dimension::X), false); 0887 } 0888 WorksheetElementContainer::retransform(); 0889 } 0890 } else if (op == NavigationOperation::ZoomIn) 0891 zoomIn(xIndex, yIndex); 0892 else if (op == NavigationOperation::ZoomOut) 0893 zoomOut(xIndex, yIndex); 0894 else if (op == NavigationOperation::ZoomInX) 0895 zoomInX(xIndex); 0896 else if (op == NavigationOperation::ZoomOutX) 0897 zoomOutX(xIndex); 0898 else if (op == NavigationOperation::ZoomInY) 0899 zoomInY(yIndex); 0900 else if (op == NavigationOperation::ZoomOutY) 0901 zoomOutY(yIndex); 0902 else if (op == NavigationOperation::ShiftLeftX) 0903 shiftLeftX(xIndex); 0904 else if (op == NavigationOperation::ShiftRightX) 0905 shiftRightX(xIndex); 0906 else if (op == NavigationOperation::ShiftUpY) 0907 shiftUpY(yIndex); 0908 else if (op == NavigationOperation::ShiftDownY) 0909 shiftDownY(yIndex); 0910 } 0911 0912 void CartesianPlot::processDropEvent(const QVector<quintptr>& vec) { 0913 PERFTRACE(QLatin1String(Q_FUNC_INFO)); 0914 0915 QVector<AbstractColumn*> columns; 0916 for (auto a : vec) { 0917 auto* aspect = (AbstractAspect*)a; 0918 auto* column = qobject_cast<AbstractColumn*>(aspect); 0919 if (column) 0920 columns << column; 0921 } 0922 0923 // return if there are no columns being dropped. 0924 // TODO: extend this later when we allow to drag&drop plots, etc. 0925 if (columns.isEmpty()) 0926 return; 0927 0928 // determine the first column with "x plot designation" as the x-data column for all curves to be created 0929 const AbstractColumn* xColumn = nullptr; 0930 for (const auto* column : qAsConst(columns)) { 0931 if (column->plotDesignation() == AbstractColumn::PlotDesignation::X) { 0932 xColumn = column; 0933 break; 0934 } 0935 } 0936 0937 // if no column with "x plot designation" is available, use the x-data column of the first curve in the plot, 0938 if (xColumn == nullptr) { 0939 QVector<XYCurve*> curves = children<XYCurve>(); 0940 if (!curves.isEmpty()) 0941 xColumn = curves.at(0)->xColumn(); 0942 } 0943 0944 // use the first dropped column if no column with "x plot designation" nor curves are available 0945 if (xColumn == nullptr) 0946 xColumn = columns.at(0); 0947 0948 // create curves 0949 bool curvesAdded = false; 0950 for (const auto* column : qAsConst(columns)) { 0951 if (column == xColumn) 0952 continue; 0953 0954 XYCurve* curve = new XYCurve(column->name()); 0955 curve->setSuppressRetransform(true); // suppress retransform, all curved will be recalculated at the end 0956 curve->setXColumn(xColumn); 0957 curve->setYColumn(column); 0958 addChild(curve); 0959 curve->setSuppressRetransform(false); 0960 curvesAdded = true; 0961 } 0962 0963 if (curvesAdded) { 0964 // In addChild() the curve gets the coordinatesystem which is the default coordinate system 0965 dataChanged(defaultCoordinateSystem()->index(Dimension::X), defaultCoordinateSystem()->index(Dimension::Y)); 0966 } 0967 } 0968 0969 bool CartesianPlot::isPanningActive() const { 0970 Q_D(const CartesianPlot); 0971 return d->panningStarted; 0972 } 0973 0974 bool CartesianPlot::isPrinted() const { 0975 Q_D(const CartesianPlot); 0976 return d->m_printing; 0977 } 0978 0979 bool CartesianPlot::isSelected() const { 0980 Q_D(const CartesianPlot); 0981 return d->isSelected(); 0982 } 0983 0984 // ############################################################################## 0985 // ################################ getter methods ############################ 0986 // ############################################################################## 0987 BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeType, rangeType, rangeType) 0988 BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, niceExtend, niceExtend) 0989 BASIC_SHARED_D_READER_IMPL(CartesianPlot, int, rangeLastValues, rangeLastValues) 0990 BASIC_SHARED_D_READER_IMPL(CartesianPlot, int, rangeFirstValues, rangeFirstValues) 0991 0992 BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, xRangeBreakingEnabled, xRangeBreakingEnabled) 0993 BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeBreaks, xRangeBreaks, xRangeBreaks) 0994 BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, yRangeBreakingEnabled, yRangeBreakingEnabled) 0995 BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeBreaks, yRangeBreaks, yRangeBreaks) 0996 0997 BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, cursor0Enable, cursor0Enable) 0998 BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, cursor1Enable, cursor1Enable) 0999 BASIC_SHARED_D_READER_IMPL(CartesianPlot, QString, theme, theme) 1000 1001 Line* CartesianPlot::cursorLine() const { 1002 Q_D(const CartesianPlot); 1003 return d->cursorLine; 1004 } 1005 1006 /*! 1007 returns the actual bounding rectangular of the plot area showing data (plot's rectangular minus padding) 1008 in plot's coordinates 1009 */ 1010 QRectF CartesianPlot::dataRect() const { 1011 Q_D(const CartesianPlot); 1012 return d->dataRect; 1013 } 1014 1015 CartesianPlot::MouseMode CartesianPlot::mouseMode() const { 1016 Q_D(const CartesianPlot); 1017 return d->mouseMode; 1018 } 1019 1020 const QString CartesianPlot::rangeDateTimeFormat(const Dimension dim) const { 1021 const int index{defaultCoordinateSystem()->index(dim)}; 1022 return rangeDateTimeFormat(dim, index); 1023 } 1024 1025 const QString CartesianPlot::rangeDateTimeFormat(const Dimension dim, const int index) const { 1026 Q_D(const CartesianPlot); 1027 return d->rangeConst(dim, index).dateTimeFormat(); 1028 } 1029 1030 // ############################################################################## 1031 // ###################### setter methods and undo commands #################### 1032 // ############################################################################## 1033 /*! 1034 set the rectangular, defined in scene coordinates 1035 */ 1036 class CartesianPlotSetRectCmd : public QUndoCommand { 1037 public: 1038 CartesianPlotSetRectCmd(CartesianPlotPrivate* private_obj, const QRectF& rect) 1039 : m_private(private_obj) 1040 , m_rect(rect) { 1041 setText(i18n("%1: change geometry rect", m_private->name())); 1042 } 1043 1044 void redo() override { 1045 // const double horizontalRatio = m_rect.width() / m_private->rect.width(); 1046 // const double verticalRatio = m_rect.height() / m_private->rect.height(); 1047 1048 qSwap(m_private->rect, m_rect); 1049 1050 // m_private->q->handleResize(horizontalRatio, verticalRatio, false); 1051 m_private->retransform(); 1052 Q_EMIT m_private->q->rectChanged(m_private->rect); 1053 } 1054 1055 void undo() override { 1056 redo(); 1057 } 1058 1059 private: 1060 CartesianPlotPrivate* m_private; 1061 QRectF m_rect; 1062 }; 1063 1064 void CartesianPlot::setRect(const QRectF& rect) { 1065 Q_D(CartesianPlot); 1066 if (rect != d->rect) 1067 exec(new CartesianPlotSetRectCmd(d, rect)); 1068 } 1069 1070 class CartesianPlotSetPrevRectCmd : public QUndoCommand { 1071 public: 1072 CartesianPlotSetPrevRectCmd(CartesianPlotPrivate* private_obj, const QRectF& rect) 1073 : m_private(private_obj) 1074 , m_rect(rect) { 1075 setText(i18n("%1: change geometry rect", m_private->name())); 1076 } 1077 1078 void redo() override { 1079 if (m_initilized) { 1080 qSwap(m_private->rect, m_rect); 1081 m_private->retransform(); 1082 Q_EMIT m_private->q->rectChanged(m_private->rect); 1083 } else { 1084 // this function is called for the first time, 1085 // nothing to do, we just need to remember what the previous rect was 1086 // which has happened already in the constructor. 1087 m_initilized = true; 1088 } 1089 } 1090 1091 void undo() override { 1092 redo(); 1093 } 1094 1095 private: 1096 CartesianPlotPrivate* m_private; 1097 QRectF m_rect; 1098 bool m_initilized{false}; 1099 }; 1100 1101 void CartesianPlot::setPrevRect(const QRectF& prevRect) { 1102 Q_D(CartesianPlot); 1103 exec(new CartesianPlotSetPrevRectCmd(d, prevRect)); 1104 } 1105 1106 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeType, CartesianPlot::RangeType, rangeType, rangeChanged) 1107 void CartesianPlot::setRangeType(RangeType type) { 1108 Q_D(CartesianPlot); 1109 if (type != d->rangeType) 1110 exec(new CartesianPlotSetRangeTypeCmd(d, type, ki18n("%1: set range type"))); 1111 } 1112 1113 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetNiceExtend, bool, niceExtend, niceExtendChanged) 1114 void CartesianPlot::setNiceExtend(const bool value) { 1115 Q_D(CartesianPlot); 1116 if (value != d->niceExtend) 1117 exec(new CartesianPlotSetNiceExtendCmd(d, value, ki18n("%1: set nice extend"))); 1118 } 1119 1120 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeLastValues, int, rangeLastValues, rangeChanged) 1121 void CartesianPlot::setRangeLastValues(int values) { 1122 Q_D(CartesianPlot); 1123 if (values != d->rangeLastValues) 1124 exec(new CartesianPlotSetRangeLastValuesCmd(d, values, ki18n("%1: set range"))); 1125 } 1126 1127 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeFirstValues, int, rangeFirstValues, rangeChanged) 1128 void CartesianPlot::setRangeFirstValues(int values) { 1129 Q_D(CartesianPlot); 1130 if (values != d->rangeFirstValues) 1131 exec(new CartesianPlotSetRangeFirstValuesCmd(d, values, ki18n("%1: set range"))); 1132 } 1133 1134 // x/y ranges 1135 class CartesianPlotSetRangeFormatIndexCmd : public QUndoCommand { 1136 public: 1137 CartesianPlotSetRangeFormatIndexCmd(CartesianPlotPrivate* private_obj, const Dimension dim, RangeT::Format format, int index) 1138 : m_private(private_obj) 1139 , m_dimension(dim) 1140 , m_format(format) 1141 , m_index(index) { 1142 setText(i18n("%1: change %2-range %3 format", m_private->name(), CartesianCoordinateSystem::dimensionToString(dim), index + 1)); 1143 } 1144 1145 void redo() override { 1146 m_formatOld = m_private->rangeConst(m_dimension, m_index).format(); 1147 m_private->setFormat(m_dimension, m_index, m_format); 1148 Q_EMIT m_private->q->rangeFormatChanged(m_dimension, m_index, m_format); 1149 m_private->rangeFormatChanged(m_dimension); 1150 } 1151 1152 void undo() override { 1153 m_private->setFormat(m_dimension, m_index, m_formatOld); 1154 Q_EMIT m_private->q->rangeFormatChanged(m_dimension, m_index, m_formatOld); 1155 m_private->rangeFormatChanged(m_dimension); 1156 } 1157 1158 private: 1159 CartesianPlotPrivate* m_private; 1160 Dimension m_dimension; 1161 RangeT::Format m_format; 1162 int m_index; 1163 RangeT::Format m_formatOld{RangeT::Format::Numeric}; 1164 }; 1165 1166 RangeT::Format CartesianPlot::xRangeFormatDefault() const { 1167 return rangeFormat(Dimension::X, defaultCoordinateSystem()->index(Dimension::X)); 1168 } 1169 RangeT::Format CartesianPlot::yRangeFormatDefault() const { 1170 return rangeFormat(Dimension::Y, defaultCoordinateSystem()->index(Dimension::Y)); 1171 } 1172 RangeT::Format CartesianPlot::rangeFormat(const Dimension dim, const int index) const { 1173 Q_D(const CartesianPlot); 1174 if (index < 0 || index > rangeCount(dim)) { 1175 DEBUG(Q_FUNC_INFO << ", index " << index << " out of range") 1176 return RangeT::Format::Numeric; 1177 } 1178 return d->rangeConst(dim, index).format(); 1179 } 1180 RangeT::Format CartesianPlot::xRangeFormat(const int index) const { 1181 return rangeFormat(Dimension::X, index); 1182 } 1183 RangeT::Format CartesianPlot::yRangeFormat(const int index) const { 1184 return rangeFormat(Dimension::Y, index); 1185 } 1186 1187 void CartesianPlot::setRangeFormat(const Dimension dim, const RangeT::Format format) { 1188 setRangeFormat(dim, defaultCoordinateSystem()->index(dim), format); 1189 } 1190 1191 void CartesianPlot::setRangeFormat(const Dimension dim, const int index, const RangeT::Format format) { 1192 Q_D(CartesianPlot); 1193 if (index < 0 || index > rangeCount(dim)) { 1194 DEBUG(Q_FUNC_INFO << ", index " << index << " out of range") 1195 return; 1196 } 1197 if (format != rangeFormat(dim, index)) { 1198 exec(new CartesianPlotSetRangeFormatIndexCmd(d, dim, format, index)); 1199 if (project()) 1200 project()->setChanged(true); 1201 } 1202 } 1203 1204 void CartesianPlot::setXRangeFormat(const int index, const RangeT::Format format) { 1205 setRangeFormat(Dimension::X, index, format); 1206 } 1207 void CartesianPlot::setYRangeFormat(const int index, const RangeT::Format format) { 1208 setRangeFormat(Dimension::Y, index, format); 1209 } 1210 1211 // auto scale 1212 1213 // is auto scale enabled for x axis index (index == -1: all axes) 1214 bool CartesianPlot::autoScale(const Dimension dim, int index) const { 1215 if (index == -1) { 1216 for (int i = 0; i < rangeCount(dim); i++) { 1217 if (!range(dim, i).autoScale()) 1218 return false; 1219 } 1220 return true; 1221 } 1222 return range(dim, index).autoScale(); 1223 } 1224 1225 class CartesianPlotEnableAutoScaleIndexCmd : public QUndoCommand { 1226 public: 1227 CartesianPlotEnableAutoScaleIndexCmd(CartesianPlotPrivate* private_obj, const Dimension dim, bool autoScale, int index, bool fullRange) 1228 : m_private(private_obj) 1229 , m_dimension(dim) 1230 , m_autoScale(autoScale) 1231 , m_index(index) 1232 , m_fullRange(fullRange) { 1233 setText(i18n("%1: change %2-range %3 auto scaling", m_private->name(), CartesianCoordinateSystem::dimensionToString(dim), m_index + 1)); 1234 } 1235 1236 void redo() override { 1237 m_autoScaleOld = m_private->autoScale(m_dimension, m_index); 1238 m_private->enableAutoScale(m_dimension, m_index, m_autoScale); 1239 if (m_autoScale) { 1240 m_oldRange = m_private->range(m_dimension, m_index); 1241 m_private->q->scaleAuto(m_dimension, m_index, m_fullRange); 1242 } 1243 Q_EMIT m_private->q->autoScaleChanged(m_dimension, m_index, m_autoScale); 1244 } 1245 1246 void undo() override { 1247 if (!m_autoScaleOld) { 1248 m_private->range(m_dimension, m_index) = m_oldRange; 1249 m_private->retransformScale(m_dimension, m_index); 1250 } 1251 m_private->enableAutoScale(m_dimension, m_index, m_autoScaleOld); 1252 Q_EMIT m_private->q->autoScaleChanged(m_dimension, m_index, m_autoScaleOld); 1253 } 1254 1255 private: 1256 CartesianPlotPrivate* m_private; 1257 Dimension m_dimension; 1258 bool m_autoScale; 1259 bool m_autoScaleOld{false}; 1260 int m_index; 1261 Range<double> m_oldRange = Range<double>(0.0, 0.0); 1262 bool m_fullRange; 1263 }; 1264 1265 // set auto scale for x/y range index (index == -1: all ranges) 1266 void CartesianPlot::enableAutoScale(const Dimension dim, int index, const bool enable, bool fullRange) { 1267 PERFTRACE(QLatin1String(Q_FUNC_INFO)); 1268 Q_D(CartesianPlot); 1269 if (index < -1 || index >= rangeCount(dim)) { 1270 DEBUG(Q_FUNC_INFO << QStringLiteral("Warning: Invalid index: ").arg(index).toStdString()); 1271 return; 1272 } 1273 1274 if (index == -1) { // all x ranges 1275 for (int i = 0; i < rangeCount(dim); i++) 1276 enableAutoScale(dim, i, enable, fullRange); 1277 return; 1278 } 1279 1280 if (enable != range(dim, index).autoScale()) { 1281 DEBUG(Q_FUNC_INFO << ", x range " << index << " enable auto scale: " << enable) 1282 // TODO: maybe using the first and then adding the first one as parent to the next undo command 1283 exec(new CartesianPlotEnableAutoScaleIndexCmd(d, dim, enable, index, fullRange)); 1284 if (project()) 1285 project()->setChanged(true); 1286 } 1287 } 1288 1289 int CartesianPlot::rangeCount(const Dimension dim) const { 1290 Q_D(const CartesianPlot); 1291 return d ? d->rangeCount(dim) : 0; 1292 } 1293 1294 const Range<double>& CartesianPlot::range(const Dimension dim, int index) const { 1295 if (index == -1) 1296 index = defaultCoordinateSystem()->index(dim); 1297 Q_D(const CartesianPlot); 1298 return d->rangeConst(dim, index); 1299 } 1300 1301 void CartesianPlot::setRangeDefault(const Dimension dim, const Range<double> range) { 1302 const int index{defaultCoordinateSystem()->index(dim)}; 1303 setRange(dim, index, range); 1304 } 1305 1306 class CartesianPlotSetRangeIndexCmd : public QUndoCommand { 1307 public: 1308 CartesianPlotSetRangeIndexCmd(CartesianPlot::Private* target, const Dimension dim, Range<double> newValue, int index) 1309 : QUndoCommand() 1310 , m_target(target) 1311 , m_index(index) 1312 , m_dimension(dim) 1313 , m_otherValue(newValue) { 1314 } 1315 void redo() override { 1316 m_target->setRangeDirty(m_dimension, m_index, true); 1317 auto tmp = m_target->rangeConst(m_dimension, m_index); 1318 m_target->setRange(m_dimension, m_index, m_otherValue); 1319 m_otherValue = tmp; 1320 finalize(); 1321 } 1322 void undo() override { 1323 redo(); 1324 } 1325 virtual void finalize() { 1326 m_target->retransformScale(m_dimension, m_index, true); 1327 Dimension dim_other = Dimension::Y; 1328 if (m_dimension == Dimension::Y) 1329 dim_other = Dimension::X; 1330 1331 QVector<int> scaledIndices; 1332 for (int i = 0; i < m_target->q->coordinateSystemCount(); i++) { 1333 auto cs = m_target->q->coordinateSystem(i); 1334 auto index_other = cs->index(dim_other); 1335 if (cs->index(m_dimension) == m_index && scaledIndices.indexOf(index_other) == -1) { 1336 scaledIndices << index_other; 1337 if (m_target->q->autoScale(dim_other, index_other) && m_target->q->scaleAuto(dim_other, index_other, false)) 1338 m_target->retransformScale(dim_other, index_other); 1339 } 1340 } 1341 m_target->q->WorksheetElementContainer::retransform(); 1342 Q_EMIT m_target->q->rangeChanged(m_dimension, m_index, m_target->rangeConst(m_dimension, m_index)); 1343 } 1344 1345 private: 1346 CartesianPlot::Private* m_target; 1347 int m_index; 1348 Dimension m_dimension; 1349 Range<double> m_otherValue; // old value in redo, new value in undo 1350 }; 1351 1352 void CartesianPlot::setRange(const Dimension dim, const int index, const Range<double>& range) { 1353 Q_D(CartesianPlot); 1354 DEBUG(Q_FUNC_INFO << ", range = " << range.toStdString() << ", auto scale = " << range.autoScale()) 1355 1356 if (range.start() == range.end()) { 1357 // User entered invalid range 1358 Q_EMIT rangeChanged(dim, index, this->range(dim, index)); // Feedback 1359 return; 1360 } 1361 1362 auto r = d->checkRange(range); 1363 if (index >= 0 && index < rangeCount(dim) && r.finite() && r != d->rangeConst(dim, index)) { 1364 exec(new CartesianPlotSetRangeIndexCmd(d, dim, r, index)); 1365 } else if (index < 0 || index >= rangeCount(dim)) 1366 DEBUG(Q_FUNC_INFO << QStringLiteral("Warning: wrong index: %1").arg(index).toStdString()); 1367 1368 DEBUG(Q_FUNC_INFO << ", DONE. range = " << range.toStdString() << ", auto scale = " << range.autoScale()) 1369 } 1370 1371 const Range<double>& CartesianPlot::dataRange(const Dimension dim, int index) { 1372 if (index == -1) 1373 index = defaultCoordinateSystem()->index(dim); 1374 1375 if (rangeDirty(dim, index)) 1376 calculateDataRange(dim, index, true); 1377 1378 Q_D(CartesianPlot); 1379 return d->dataRange(dim, index); 1380 } 1381 1382 bool CartesianPlot::rangeDirty(const Dimension dim, int index) const { 1383 Q_D(const CartesianPlot); 1384 if (index >= 0) 1385 return d->rangeDirty(dim, index); 1386 else { 1387 bool dirty = false; 1388 for (int i = 0; i < rangeCount(dim); i++) 1389 dirty |= d->rangeDirty(dim, i); 1390 return dirty; 1391 } 1392 } 1393 1394 void CartesianPlot::setRangeDirty(const Dimension dim, int index, bool dirty) { 1395 Q_D(CartesianPlot); 1396 if (index >= rangeCount(dim)) 1397 return; 1398 if (index >= 0) 1399 d->setRangeDirty(dim, index, dirty); 1400 else { 1401 for (int i = 0; i < rangeCount(dim); i++) 1402 d->setRangeDirty(dim, i, dirty); 1403 } 1404 } 1405 1406 void CartesianPlot::addXRange() { 1407 Q_D(CartesianPlot); 1408 d->xRanges.append(CartesianPlot::Private::RichRange()); 1409 if (project()) 1410 project()->setChanged(true); 1411 } 1412 void CartesianPlot::addYRange() { 1413 Q_D(CartesianPlot); 1414 d->yRanges.append(CartesianPlot::Private::RichRange()); 1415 if (project()) 1416 project()->setChanged(true); 1417 } 1418 void CartesianPlot::addXRange(const Range<double>& range) { 1419 Q_D(CartesianPlot); 1420 d->xRanges.append(CartesianPlot::Private::RichRange(range)); 1421 if (project()) 1422 project()->setChanged(true); 1423 } 1424 void CartesianPlot::addYRange(const Range<double>& range) { 1425 Q_D(CartesianPlot); 1426 d->yRanges.append(CartesianPlot::Private::RichRange(range)); 1427 if (project()) 1428 project()->setChanged(true); 1429 } 1430 1431 void CartesianPlot::removeRange(const Dimension dim, int index) { 1432 Q_D(CartesianPlot); 1433 if (index < 0 || index > rangeCount(dim)) { 1434 DEBUG(Q_FUNC_INFO << ", index " << index << " out of range") 1435 return; 1436 } 1437 1438 switch (dim) { 1439 case Dimension::X: 1440 d->xRanges.remove(index); 1441 break; 1442 case Dimension::Y: 1443 d->yRanges.remove(index); 1444 break; 1445 } 1446 1447 if (project()) 1448 project()->setChanged(true); 1449 } 1450 1451 void CartesianPlot::setMin(const Dimension dim, int index, double value) { 1452 DEBUG(Q_FUNC_INFO << ", direction: " << CartesianCoordinateSystem::dimensionToString(dim).toStdString() << "value = " << value) 1453 if (index >= rangeCount(dim)) 1454 return; 1455 Range<double> r{range(dim, index)}; 1456 r.setStart(value); 1457 DEBUG(Q_FUNC_INFO << ", new range = " << r.toStdString()) 1458 setRange(dim, index, r); 1459 } 1460 1461 void CartesianPlot::setMax(const Dimension dim, int index, double value) { 1462 DEBUG(Q_FUNC_INFO << ", direction: " << CartesianCoordinateSystem::dimensionToString(dim).toStdString() << "value = " << value) 1463 if (index >= rangeCount(dim)) 1464 return; 1465 Range<double> r{range(dim, index)}; 1466 r.setEnd(value); 1467 1468 setRange(dim, index, r); 1469 } 1470 1471 // x/y scale 1472 1473 class CartesianPlotSetScaleIndexCmd : public QUndoCommand { 1474 public: 1475 CartesianPlotSetScaleIndexCmd(CartesianPlotPrivate* private_obj, const Dimension dim, RangeT::Scale scale, int index) 1476 : m_private(private_obj) 1477 , m_dimension(dim) 1478 , m_scale(scale) 1479 , m_index(index) { 1480 setText(i18n("%1: change x-range %2 scale", m_private->name(), index + 1)); 1481 } 1482 1483 void redo() override { 1484 m_scaleOld = m_private->rangeConst(m_dimension, m_index).scale(); 1485 m_private->setScale(m_dimension, m_index, m_scale); 1486 m_private->retransformScale(m_dimension, m_index); 1487 m_private->q->WorksheetElementContainer::retransform(); 1488 Q_EMIT m_private->q->scaleChanged(m_dimension, m_index, m_scale); 1489 } 1490 1491 void undo() override { 1492 m_private->setScale(m_dimension, m_index, m_scaleOld); 1493 m_private->retransformScale(m_dimension, m_index); 1494 m_private->q->WorksheetElementContainer::retransform(); 1495 Q_EMIT m_private->q->scaleChanged(m_dimension, m_index, m_scaleOld); 1496 } 1497 1498 private: 1499 CartesianPlotPrivate* m_private; 1500 Dimension m_dimension; 1501 RangeT::Scale m_scale; 1502 int m_index; 1503 RangeT::Scale m_scaleOld{RangeT::Scale::Linear}; 1504 }; 1505 1506 RangeT::Scale CartesianPlot::rangeScale(const Dimension dim, const int index) const { 1507 if (index < 0 || index > rangeCount(dim)) { 1508 DEBUG(Q_FUNC_INFO << ", index " << index << " out of range") 1509 return RangeT::Scale::Linear; 1510 } 1511 return range(dim, index).scale(); 1512 } 1513 1514 RangeT::Scale CartesianPlot::xRangeScale() const { 1515 return xRangeScale(defaultCoordinateSystem()->index(Dimension::X)); 1516 } 1517 RangeT::Scale CartesianPlot::yRangeScale() const { 1518 return yRangeScale(defaultCoordinateSystem()->index(Dimension::Y)); 1519 } 1520 RangeT::Scale CartesianPlot::xRangeScale(const int index) const { 1521 return rangeScale(Dimension::X, index); 1522 } 1523 RangeT::Scale CartesianPlot::yRangeScale(const int index) const { 1524 return rangeScale(Dimension::Y, index); 1525 } 1526 void CartesianPlot::setXRangeScale(const RangeT::Scale scale) { 1527 setXRangeScale(defaultCoordinateSystem()->index(Dimension::X), scale); 1528 } 1529 1530 void CartesianPlot::setYRangeScale(const RangeT::Scale scale) { 1531 setYRangeScale(defaultCoordinateSystem()->index(Dimension::Y), scale); 1532 } 1533 1534 void CartesianPlot::setRangeScale(const Dimension dim, const int index, const RangeT::Scale scale) { 1535 Q_D(CartesianPlot); 1536 if (index < 0 || index > rangeCount(dim)) { 1537 DEBUG(Q_FUNC_INFO << ", index " << index << " out of range") 1538 return; 1539 } 1540 1541 auto newRange = range(Dimension::X, index); 1542 newRange.setScale(scale); 1543 auto r = d->checkRange(newRange); 1544 if (index >= 0 && index < rangeCount(dim) && r.finite() && r != d->rangeConst(dim, index)) { 1545 if (r == newRange) { 1546 exec(new CartesianPlotSetScaleIndexCmd(d, dim, scale, index)); 1547 if (project()) 1548 project()->setChanged(true); 1549 } else 1550 setRange(dim, index, r); 1551 } 1552 } 1553 1554 void CartesianPlot::setXRangeScale(const int index, const RangeT::Scale scale) { 1555 setRangeScale(Dimension::X, index, scale); 1556 } 1557 void CartesianPlot::setYRangeScale(const int index, const RangeT::Scale scale) { 1558 setRangeScale(Dimension::Y, index, scale); 1559 } 1560 1561 // coordinate systems 1562 1563 int CartesianPlot::coordinateSystemCount() const { 1564 return m_coordinateSystems.size(); 1565 } 1566 1567 CartesianCoordinateSystem* CartesianPlot::coordinateSystem(int index) const { 1568 // DEBUG(Q_FUNC_INFO << ", nr of cSystems = " << coordinateSystemCount() << ", index = " << index) 1569 if (index >= coordinateSystemCount() || index < 0) 1570 return nullptr; 1571 1572 return dynamic_cast<CartesianCoordinateSystem*>(m_coordinateSystems.at(index)); 1573 } 1574 1575 void CartesianPlot::addCoordinateSystem() { 1576 auto cSystem = new CartesianCoordinateSystem(this); 1577 addCoordinateSystem(cSystem); 1578 // retransform scales, because otherwise the CartesianCoordinateSystem 1579 // does not have any scales 1580 retransformScale(Dimension::X, cSystem->index(Dimension::X)); 1581 retransformScale(Dimension::Y, cSystem->index(Dimension::Y)); 1582 } 1583 void CartesianPlot::addCoordinateSystem(CartesianCoordinateSystem* s) { 1584 m_coordinateSystems.append(s); 1585 if (project()) 1586 project()->setChanged(true); 1587 } 1588 void CartesianPlot::removeCoordinateSystem(int index) { 1589 if (index < 0 || index > coordinateSystemCount()) { 1590 DEBUG(Q_FUNC_INFO << ", index " << index << " out of range") 1591 return; 1592 } 1593 1594 // TODO: deleting cSystem? 1595 m_coordinateSystems.remove(index); 1596 if (project()) 1597 project()->setChanged(true); 1598 } 1599 1600 STD_SETTER_CMD_IMPL_S(CartesianPlot, SetDefaultCoordinateSystemIndex, int, defaultCoordinateSystemIndex) 1601 int CartesianPlot::defaultCoordinateSystemIndex() const { 1602 Q_D(const CartesianPlot); 1603 return d->defaultCoordinateSystemIndex; 1604 } 1605 void CartesianPlot::setDefaultCoordinateSystemIndex(int index) { 1606 Q_D(CartesianPlot); 1607 if (index != d->defaultCoordinateSystemIndex) 1608 exec(new CartesianPlotSetDefaultCoordinateSystemIndexCmd(d, index, ki18n("%1: set default plot range"))); 1609 } 1610 CartesianCoordinateSystem* CartesianPlot::defaultCoordinateSystem() const { 1611 Q_D(const CartesianPlot); 1612 return d->defaultCoordinateSystem(); 1613 } 1614 1615 void CartesianPlot::setCoordinateSystemRangeIndex(int cSystemIndex, Dimension dim, int rangeIndex) { 1616 coordinateSystem(cSystemIndex)->setIndex(dim, rangeIndex); 1617 retransformScale(dim, rangeIndex); 1618 } 1619 1620 // range breaks 1621 1622 STD_SETTER_CMD_IMPL_S(CartesianPlot, SetXRangeBreakingEnabled, bool, xRangeBreakingEnabled) 1623 void CartesianPlot::setXRangeBreakingEnabled(bool enabled) { 1624 Q_D(CartesianPlot); 1625 if (enabled != d->xRangeBreakingEnabled) { 1626 exec(new CartesianPlotSetXRangeBreakingEnabledCmd(d, enabled, ki18n("%1: x-range breaking enabled"))); 1627 retransformScales(); // TODO: replace by retransformScale(Dimension::X, ) with the corresponding index! 1628 WorksheetElementContainer::retransform(); // retransformScales does not contain any retransfrom() anymore 1629 } 1630 } 1631 1632 STD_SETTER_CMD_IMPL_S(CartesianPlot, SetXRangeBreaks, CartesianPlot::RangeBreaks, xRangeBreaks) 1633 void CartesianPlot::setXRangeBreaks(const RangeBreaks& breakings) { 1634 Q_D(CartesianPlot); 1635 exec(new CartesianPlotSetXRangeBreaksCmd(d, breakings, ki18n("%1: x-range breaks changed"))); 1636 retransformScales(); // TODO: replace by retransformScale(Dimension::X, ) with the corresponding index! 1637 WorksheetElementContainer::retransform(); // retransformScales does not contain any retransfrom() anymore 1638 } 1639 1640 STD_SETTER_CMD_IMPL_S(CartesianPlot, SetYRangeBreakingEnabled, bool, yRangeBreakingEnabled) 1641 void CartesianPlot::setYRangeBreakingEnabled(bool enabled) { 1642 Q_D(CartesianPlot); 1643 if (enabled != d->yRangeBreakingEnabled) { 1644 exec(new CartesianPlotSetYRangeBreakingEnabledCmd(d, enabled, ki18n("%1: y-range breaking enabled"))); 1645 retransformScales(); // TODO: replace by retransformScale(Dimension::Y, ) with the corresponding index! 1646 WorksheetElementContainer::retransform(); // retransformScales does not contain any retransfrom() anymore 1647 } 1648 } 1649 1650 STD_SETTER_CMD_IMPL_S(CartesianPlot, SetYRangeBreaks, CartesianPlot::RangeBreaks, yRangeBreaks) 1651 void CartesianPlot::setYRangeBreaks(const RangeBreaks& breaks) { 1652 Q_D(CartesianPlot); 1653 exec(new CartesianPlotSetYRangeBreaksCmd(d, breaks, ki18n("%1: y-range breaks changed"))); 1654 retransformScales(); // TODO: replace by retransformScale(Dimension::Y, ) with the corresponding index! 1655 WorksheetElementContainer::retransform(); // retransformScales does not contain any retransfrom() anymore 1656 } 1657 1658 // cursor 1659 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetCursor0Enable, bool, cursor0Enable, updateCursor) 1660 void CartesianPlot::setCursor0Enable(const bool& enable) { 1661 Q_D(CartesianPlot); 1662 if (enable != d->cursor0Enable && defaultCoordinateSystem()->isValid()) { 1663 if (std::isnan(d->cursor0Pos.x())) { // if never set, set initial position 1664 d->cursor0Pos.setX(defaultCoordinateSystem()->mapSceneToLogical(QPointF(0, 0)).x()); 1665 mousePressCursorModeSignal(0, d->cursor0Pos); // simulate mousePress to update values in the cursor dock 1666 } 1667 exec(new CartesianPlotSetCursor0EnableCmd(d, enable, ki18n("%1: Cursor0 enable"))); 1668 } 1669 } 1670 1671 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetCursor1Enable, bool, cursor1Enable, updateCursor) 1672 void CartesianPlot::setCursor1Enable(const bool& enable) { 1673 Q_D(CartesianPlot); 1674 if (enable != d->cursor1Enable && defaultCoordinateSystem()->isValid()) { 1675 if (std::isnan(d->cursor1Pos.x())) { // if never set, set initial position 1676 d->cursor1Pos.setX(defaultCoordinateSystem()->mapSceneToLogical(QPointF(0, 0)).x()); 1677 mousePressCursorModeSignal(1, d->cursor1Pos); // simulate mousePress to update values in the cursor dock 1678 } 1679 exec(new CartesianPlotSetCursor1EnableCmd(d, enable, ki18n("%1: Cursor1 enable"))); 1680 } 1681 } 1682 1683 // theme 1684 1685 STD_SETTER_CMD_IMPL_S(CartesianPlot, SetTheme, QString, theme) 1686 void CartesianPlot::setTheme(const QString& theme) { 1687 Q_D(CartesianPlot); 1688 QString info; 1689 if (!theme.isEmpty()) 1690 info = i18n("%1: load theme %2", name(), theme); 1691 else 1692 info = i18n("%1: load default theme", name()); 1693 beginMacro(info); 1694 exec(new CartesianPlotSetThemeCmd(d, theme, ki18n("%1: set theme"))); 1695 loadTheme(theme); 1696 endMacro(); 1697 } 1698 1699 void CartesianPlot::retransform() { 1700 Q_D(CartesianPlot); 1701 d->retransform(); 1702 } 1703 1704 // ################################################################ 1705 // ########################## Slots ############################### 1706 // ################################################################ 1707 void CartesianPlot::addHorizontalAxis() { 1708 DEBUG(Q_FUNC_INFO) 1709 Axis* axis = new Axis(QStringLiteral("x-axis"), Axis::Orientation::Horizontal); 1710 addChild(axis); 1711 axis->setSuppressRetransform(true); // retransformTicks() needs plot 1712 axis->setCoordinateSystemIndex(defaultCoordinateSystemIndex()); 1713 if (axis->rangeType() == Axis::RangeType::Auto) { 1714 axis->setUndoAware(false); 1715 // use x range of default plot range 1716 axis->setRange(range(Dimension::X)); 1717 axis->setMajorTicksNumber(range(Dimension::X).autoTickCount()); 1718 axis->setUndoAware(true); 1719 } 1720 axis->setSuppressRetransform(false); 1721 axis->retransform(); 1722 } 1723 1724 void CartesianPlot::addVerticalAxis() { 1725 Axis* axis = new Axis(QStringLiteral("y-axis"), Axis::Orientation::Vertical); 1726 axis->setSuppressRetransform(true); // retransformTicks() needs plot 1727 addChild(axis); 1728 axis->setCoordinateSystemIndex(defaultCoordinateSystemIndex()); 1729 if (axis->rangeType() == Axis::RangeType::Auto) { 1730 axis->setUndoAware(false); 1731 // use y range of default plot range 1732 axis->setRange(range(Dimension::Y)); 1733 axis->setMajorTicksNumber(range(Dimension::Y).autoTickCount()); 1734 axis->setUndoAware(true); 1735 } 1736 axis->setSuppressRetransform(false); 1737 axis->retransform(); 1738 } 1739 1740 void CartesianPlot::addHistogramFit(Histogram* hist, nsl_sf_stats_distribution type) { 1741 if (!hist) 1742 return; 1743 1744 beginMacro(i18n("%1: distribution fit to '%2'", name(), hist->name())); 1745 auto* curve = new XYFitCurve(i18n("Distribution Fit to '%1'", hist->name())); 1746 // curve->setCoordinateSystemIndex(defaultCoordinateSystemIndex()); 1747 curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Histogram); 1748 curve->setDataSourceHistogram(hist); 1749 1750 // set fit model category and type and initialize fit 1751 XYFitCurve::FitData fitData = curve->fitData(); 1752 fitData.modelCategory = nsl_fit_model_distribution; 1753 fitData.modelType = (int)type; 1754 DEBUG("TYPE = " << type) 1755 fitData.algorithm = nsl_fit_algorithm_ml; // ML distribution fit 1756 DEBUG("INITFITDATA:") 1757 XYFitCurve::initFitData(fitData); 1758 DEBUG("SETFITDATA:") 1759 curve->setFitData(fitData); 1760 1761 DEBUG("RECALCULATE:") 1762 curve->recalculate(); 1763 1764 // add the child after the fit was calculated so the dock widgets gets the fit results 1765 // and call retransform() after this to calculate and to paint the data points of the fit-curve 1766 DEBUG("ADDCHILD:") 1767 this->addChild(curve); 1768 DEBUG("RETRANSFORM:") 1769 curve->retransform(); 1770 DEBUG("DONE:") 1771 1772 endMacro(); 1773 } 1774 1775 /*! 1776 * returns the first selected XYCurve in the plot 1777 */ 1778 const XYCurve* CartesianPlot::currentCurve() const { 1779 for (const auto* curve : children<const XYCurve>()) { 1780 if (curve->graphicsItem()->isSelected()) 1781 return curve; 1782 } 1783 1784 return nullptr; 1785 } 1786 1787 void CartesianPlot::addDataReductionCurve() { 1788 auto* curve = new XYDataReductionCurve(i18n("Data Reduction")); 1789 const XYCurve* curCurve = currentCurve(); 1790 if (curCurve) { 1791 beginMacro(i18n("%1: reduce '%2'", name(), curCurve->name())); 1792 curve->setName(i18n("Reduction of '%1'", curCurve->name())); 1793 curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Curve); 1794 curve->setDataSourceCurve(curCurve); 1795 this->addChild(curve); 1796 curve->recalculate(); 1797 Q_EMIT curve->dataReductionDataChanged(curve->dataReductionData()); 1798 } else { 1799 beginMacro(i18n("%1: add data reduction curve", name())); 1800 this->addChild(curve); 1801 } 1802 1803 endMacro(); 1804 } 1805 1806 void CartesianPlot::addDifferentiationCurve() { 1807 auto* curve = new XYDifferentiationCurve(i18n("Differentiation")); 1808 const XYCurve* curCurve = currentCurve(); 1809 if (curCurve) { 1810 beginMacro(i18n("%1: differentiate '%2'", name(), curCurve->name())); 1811 curve->setName(i18n("Derivative of '%1'", curCurve->name())); 1812 curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Curve); 1813 curve->setDataSourceCurve(curCurve); 1814 this->addChild(curve); 1815 curve->recalculate(); 1816 Q_EMIT curve->differentiationDataChanged(curve->differentiationData()); 1817 } else { 1818 beginMacro(i18n("%1: add differentiation curve", name())); 1819 this->addChild(curve); 1820 } 1821 1822 endMacro(); 1823 } 1824 1825 void CartesianPlot::addIntegrationCurve() { 1826 auto* curve = new XYIntegrationCurve(i18n("Integration")); 1827 const XYCurve* curCurve = currentCurve(); 1828 if (curCurve) { 1829 beginMacro(i18n("%1: integrate '%2'", name(), curCurve->name())); 1830 curve->setName(i18n("Integral of '%1'", curCurve->name())); 1831 curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Curve); 1832 curve->setDataSourceCurve(curCurve); 1833 this->addChild(curve); 1834 curve->recalculate(); 1835 Q_EMIT curve->integrationDataChanged(curve->integrationData()); 1836 } else { 1837 beginMacro(i18n("%1: add integration curve", name())); 1838 this->addChild(curve); 1839 } 1840 1841 endMacro(); 1842 } 1843 1844 void CartesianPlot::addInterpolationCurve() { 1845 auto* curve = new XYInterpolationCurve(i18n("Interpolation")); 1846 const XYCurve* curCurve = currentCurve(); 1847 if (curCurve) { 1848 beginMacro(i18n("%1: interpolate '%2'", name(), curCurve->name())); 1849 curve->setName(i18n("Interpolation of '%1'", curCurve->name())); 1850 curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Curve); 1851 curve->setDataSourceCurve(curCurve); 1852 curve->recalculate(); 1853 this->addChild(curve); 1854 Q_EMIT curve->interpolationDataChanged(curve->interpolationData()); 1855 } else { 1856 beginMacro(i18n("%1: add interpolation curve", name())); 1857 this->addChild(curve); 1858 } 1859 1860 endMacro(); 1861 } 1862 1863 void CartesianPlot::addSmoothCurve() { 1864 auto* curve = new XYSmoothCurve(i18n("Smooth")); 1865 const XYCurve* curCurve = currentCurve(); 1866 if (curCurve) { 1867 beginMacro(i18n("%1: smooth '%2'", name(), curCurve->name())); 1868 curve->setName(i18n("Smoothing of '%1'", curCurve->name())); 1869 curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Curve); 1870 curve->setDataSourceCurve(curCurve); 1871 this->addChild(curve); 1872 curve->recalculate(); 1873 Q_EMIT curve->smoothDataChanged(curve->smoothData()); 1874 } else { 1875 beginMacro(i18n("%1: add smoothing curve", name())); 1876 this->addChild(curve); 1877 } 1878 1879 endMacro(); 1880 } 1881 1882 void CartesianPlot::addFitCurve() { 1883 auto* curve = new XYFitCurve(i18n("Fit")); 1884 const auto* curCurve = currentCurve(); 1885 if (curCurve) { 1886 beginMacro(i18n("%1: fit to '%2'", name(), curCurve->name())); 1887 curve->setName(i18n("Fit to '%1'", curCurve->name())); 1888 curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Curve); 1889 curve->setDataSourceCurve(curCurve); 1890 1891 // set the fit model category and type 1892 const auto* action = qobject_cast<const QAction*>(QObject::sender()); 1893 if (action) { 1894 auto type = static_cast<XYAnalysisCurve::AnalysisAction>(action->data().toInt()); 1895 curve->initFitData(type); 1896 } else { 1897 DEBUG(Q_FUNC_INFO << "WARNING: no action found!") 1898 } 1899 curve->initStartValues(curCurve); 1900 1901 // fit with weights for y if the curve has error bars for y 1902 if (curCurve->yErrorBar()->type() == ErrorBar::Type::Symmetric && curCurve->yErrorBar()->plusColumn()) { 1903 auto fitData = curve->fitData(); 1904 fitData.yWeightsType = nsl_fit_weight_instrumental; 1905 curve->setFitData(fitData); 1906 curve->setYErrorColumn(curCurve->yErrorBar()->plusColumn()); 1907 } 1908 1909 curve->recalculate(); 1910 1911 // add the child after the fit was calculated so the dock widgets gets the fit results 1912 // and call retransform() after this to calculate and to paint the data points of the fit-curve 1913 this->addChild(curve); 1914 curve->retransform(); 1915 } else { 1916 beginMacro(i18n("%1: add fit curve", name())); 1917 this->addChild(curve); 1918 } 1919 1920 endMacro(); 1921 } 1922 1923 void CartesianPlot::addFourierFilterCurve() { 1924 auto* curve = new XYFourierFilterCurve(i18n("Fourier Filter")); 1925 const XYCurve* curCurve = currentCurve(); 1926 if (curCurve) { 1927 beginMacro(i18n("%1: Fourier filtering of '%2'", name(), curCurve->name())); 1928 curve->setName(i18n("Fourier filtering of '%1'", curCurve->name())); 1929 curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Curve); 1930 curve->setDataSourceCurve(curCurve); 1931 } else { 1932 beginMacro(i18n("%1: add Fourier filter curve", name())); 1933 } 1934 this->addChild(curve); 1935 1936 endMacro(); 1937 } 1938 1939 /*! 1940 * public helper function to set a legend object created outside of CartesianPlot, e.g. in \c OriginProjectParser. 1941 */ 1942 void CartesianPlot::addLegend(CartesianPlotLegend* legend) { 1943 m_legend = legend; 1944 this->addChild(legend); 1945 } 1946 1947 void CartesianPlot::addLegend() { 1948 // don't do anything if there's already a legend 1949 if (m_legend) 1950 return; 1951 1952 m_legend = new CartesianPlotLegend(i18n("Legend")); 1953 this->addChild(m_legend); 1954 m_legend->retransform(); 1955 1956 // only one legend is allowed -> disable the action 1957 if (m_menusInitialized) 1958 addLegendAction->setEnabled(false); 1959 } 1960 1961 void CartesianPlot::addInfoElement() { 1962 XYCurve* curve = nullptr; 1963 auto curves = children<XYCurve>(); 1964 if (curves.count()) 1965 curve = curves.first(); 1966 1967 double pos; 1968 Q_D(CartesianPlot); 1969 if (d->calledFromContextMenu) { 1970 pos = d->logicalPos.x(); 1971 d->calledFromContextMenu = false; 1972 } else 1973 pos = range(Dimension::X).center(); 1974 1975 auto* element = new InfoElement(i18n("Info Element"), this, curve, pos); 1976 this->addChild(element); 1977 element->setParentGraphicsItem(graphicsItem()); 1978 element->retransform(); // must be done, because the element must be retransformed (see https://invent.kde.org/marmsoler/labplot/issues/9) 1979 } 1980 1981 void CartesianPlot::addTextLabel() { 1982 auto* label = new TextLabel(i18n("Text Label"), this); 1983 1984 Q_D(CartesianPlot); 1985 if (d->calledFromContextMenu) { 1986 auto position = label->position(); 1987 position.point = label->parentPosToRelativePos(d->scenePos, position); 1988 position.point = label->align(position.point, label->graphicsItem()->boundingRect(), label->horizontalAlignment(), label->verticalAlignment(), false); 1989 label->setPosition(position); 1990 d->calledFromContextMenu = false; 1991 } 1992 1993 this->addChild(label); 1994 label->setParentGraphicsItem(graphicsItem()); 1995 label->retransform(); 1996 } 1997 1998 void CartesianPlot::addImage() { 1999 auto* image = new Image(i18n("Image")); 2000 2001 Q_D(CartesianPlot); 2002 if (d->calledFromContextMenu) { 2003 auto position = image->position(); 2004 position.point = image->parentPosToRelativePos(d->scenePos, position); 2005 position.point = image->align(position.point, image->graphicsItem()->boundingRect(), image->horizontalAlignment(), image->verticalAlignment(), false); 2006 image->setPosition(position); 2007 d->calledFromContextMenu = false; 2008 } 2009 2010 // make the new image somewhat smaller so it's completely visible also on smaller plots 2011 image->setWidth((int)Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Centimeter)); 2012 2013 this->addChild(image); 2014 image->retransform(); 2015 } 2016 2017 void CartesianPlot::addCustomPoint() { 2018 Q_D(CartesianPlot); 2019 auto* point = new CustomPoint(this, i18n("Custom Point")); 2020 point->setCoordinateSystemIndex(defaultCoordinateSystemIndex()); 2021 2022 beginMacro(i18n("%1: add %2", name(), point->name())); 2023 2024 // must be before setting the position 2025 this->addChild(point); 2026 2027 if (d->calledFromContextMenu) { 2028 point->setCoordinateBindingEnabled(true); 2029 point->setPositionLogical(d->logicalPos); 2030 d->calledFromContextMenu = false; 2031 } else { 2032 auto p = point->position(); 2033 p.point = QPointF(0, 0); // Exactly in the middle of the plot in scene coordinates 2034 point->setPosition(p); 2035 point->setCoordinateBindingEnabled(true); 2036 } 2037 2038 endMacro(); 2039 point->retransform(); 2040 } 2041 2042 void CartesianPlot::addReferenceLine() { 2043 Q_D(CartesianPlot); 2044 auto* line = new ReferenceLine(this, i18n("Reference Line")); 2045 line->setCoordinateSystemIndex(defaultCoordinateSystemIndex()); 2046 2047 if (d->calledFromContextMenu) { 2048 line->setPositionLogical(d->logicalPos); 2049 d->calledFromContextMenu = false; 2050 } 2051 2052 this->addChild(line); 2053 line->retransform(); 2054 } 2055 2056 void CartesianPlot::addReferenceRange() { 2057 auto* range = new ReferenceRange(this, i18n("Reference Range")); 2058 range->setCoordinateSystemIndex(defaultCoordinateSystemIndex()); 2059 2060 // Q_D(CartesianPlot); 2061 // if (d->calledFromContextMenu) { 2062 // range->setPositionLogical(d->logicalPos); 2063 // d->calledFromContextMenu = false; 2064 // } 2065 2066 this->addChild(range); 2067 range->retransform(); 2068 } 2069 2070 int CartesianPlot::curveCount() { 2071 return children<XYCurve>().size(); 2072 } 2073 2074 int CartesianPlot::curveTotalCount() const { 2075 return children<Plot>().size(); 2076 } 2077 2078 const XYCurve* CartesianPlot::getCurve(int index) { 2079 return children<XYCurve>().at(index); 2080 } 2081 2082 double CartesianPlot::cursorPos(int cursorNumber) { 2083 Q_D(const CartesianPlot); 2084 return (cursorNumber == 0 ? d->cursor0Pos.x() : d->cursor1Pos.x()); 2085 } 2086 2087 /*! 2088 * returns the index of the child \c curve in the list of all "curve-like" 2089 * children (xy-curve, histogram, boxplot, etc.). 2090 * This function is used when applying the theme color to the newly added "curve".: 2091 */ 2092 int CartesianPlot::curveChildIndex(const WorksheetElement* curve) const { 2093 int index = 0; 2094 const auto& children = this->children<WorksheetElement>(); 2095 for (auto* child : children) { 2096 if (child == curve) 2097 break; 2098 2099 if (dynamic_cast<const Plot*>(child)) 2100 ++index; 2101 } 2102 2103 return index; 2104 } 2105 2106 void CartesianPlot::childAdded(const AbstractAspect* child) { 2107 auto* elem = dynamic_cast<const WorksheetElement*>(child); 2108 if (!elem) 2109 return; 2110 2111 Q_D(CartesianPlot); 2112 int cSystemIndex = defaultCoordinateSystemIndex(); 2113 // check/change ranges when adding new children like curves for example. 2114 // The ranges must not be checked if just an element like a TextLabel, Custompoint, ... was added 2115 bool checkRanges = false; 2116 const auto* plot = dynamic_cast<const Plot*>(child); 2117 2118 if (plot) { 2119 connect(plot, &WorksheetElement::visibleChanged, this, &CartesianPlot::curveVisibilityChanged); 2120 connect(plot, &WorksheetElement::aspectDescriptionChanged, this, &CartesianPlot::updateLegend); 2121 connect(plot, &Plot::legendVisibleChanged, this, &CartesianPlot::updateLegend); 2122 connect(plot, &Plot::appearanceChanged, this, &CartesianPlot::updateLegend); 2123 connect(plot, &Plot::appearanceChanged, this, QOverload<>::of(&CartesianPlot::plotColorChanged)); // forward to Worksheet to update CursorDock 2124 2125 connect(plot, &Plot::dataChanged, [this, elem] { 2126 this->dataChanged(const_cast<WorksheetElement*>(elem)); 2127 }); 2128 2129 // don't set the default coordinate system index during the project load, 2130 // the index is read in child's load(). 2131 // same for range and legend updates - settings are read in load(). 2132 if (!isLoading()) { 2133 const_cast<Plot*>(plot)->setCoordinateSystemIndex(cSystemIndex); 2134 checkRanges = true; 2135 updateLegend(); 2136 } 2137 } 2138 2139 const auto* curve = dynamic_cast<const XYCurve*>(child); 2140 const auto* hist = dynamic_cast<const Histogram*>(child); 2141 const auto* boxPlot = dynamic_cast<const BoxPlot*>(child); 2142 const auto* barPlot = dynamic_cast<const BarPlot*>(child); 2143 const auto* lollipopPlot = dynamic_cast<const LollipopPlot*>(child); 2144 2145 const auto* axis = dynamic_cast<const Axis*>(child); 2146 2147 if (curve) { 2148 DEBUG(Q_FUNC_INFO << ", CURVE") 2149 // x data 2150 connect(curve, &XYCurve::xColumnChanged, this, [this, curve](const AbstractColumn* column) { 2151 if (curveTotalCount() == 1) // first curve addded 2152 checkAxisFormat(curve->coordinateSystemIndex(), column, Axis::Orientation::Horizontal); 2153 }); 2154 connect(curve, &XYCurve::xDataChanged, [this, curve]() { 2155 this->dataChanged(const_cast<XYCurve*>(curve), Dimension::X); 2156 }); 2157 connect(curve->xErrorBar(), &ErrorBar::typeChanged, [this, curve]() { 2158 this->dataChanged(const_cast<XYCurve*>(curve), Dimension::X); 2159 }); 2160 connect(curve->xErrorBar(), &ErrorBar::plusColumnChanged, [this, curve]() { 2161 this->dataChanged(const_cast<XYCurve*>(curve), Dimension::X); 2162 }); 2163 connect(curve->xErrorBar(), &ErrorBar::minusColumnChanged, [this, curve]() { 2164 this->dataChanged(const_cast<XYCurve*>(curve), Dimension::X); 2165 }); 2166 2167 // y data 2168 connect(curve, &XYCurve::yColumnChanged, this, [this, curve](const AbstractColumn* column) { 2169 if (curveTotalCount() == 1) 2170 checkAxisFormat(curve->coordinateSystemIndex(), column, Axis::Orientation::Vertical); 2171 }); 2172 connect(curve, &XYCurve::yDataChanged, [this, curve]() { 2173 this->dataChanged(const_cast<XYCurve*>(curve), Dimension::Y); 2174 }); 2175 connect(curve->yErrorBar(), &ErrorBar::typeChanged, [this, curve]() { 2176 this->dataChanged(const_cast<XYCurve*>(curve), Dimension::Y); 2177 }); 2178 connect(curve->yErrorBar(), &ErrorBar::plusColumnChanged, [this, curve]() { 2179 this->dataChanged(const_cast<XYCurve*>(curve), Dimension::Y); 2180 }); 2181 connect(curve->yErrorBar(), &ErrorBar::minusColumnChanged, [this, curve]() { 2182 this->dataChanged(const_cast<XYCurve*>(curve), Dimension::Y); 2183 }); 2184 2185 // update the legend on line and symbol properties changes 2186 connect(curve, &XYCurve::aspectDescriptionChanged, this, &CartesianPlot::curveNameChanged); 2187 connect(curve, &XYCurve::lineTypeChanged, this, &CartesianPlot::updateLegend); 2188 2189 // in case the first curve is added, check whether we start plotting datetime data 2190 if (!isLoading() && curveTotalCount() == 1) { 2191 checkAxisFormat(curve->coordinateSystemIndex(), curve->xColumn(), Axis::Orientation::Horizontal); 2192 checkAxisFormat(curve->coordinateSystemIndex(), curve->yColumn(), Axis::Orientation::Vertical); 2193 } 2194 2195 Q_EMIT curveAdded(curve); 2196 } else if (hist) { 2197 DEBUG(Q_FUNC_INFO << ", HISTOGRAM") 2198 if (!isLoading() && curveTotalCount() == 1) 2199 checkAxisFormat(hist->coordinateSystemIndex(), hist->dataColumn(), Axis::Orientation::Horizontal); 2200 } else if (boxPlot) { 2201 DEBUG(Q_FUNC_INFO << ", BOX PLOT") 2202 if (curveTotalCount() == 1) { 2203 connect(boxPlot, &BoxPlot::orientationChanged, this, &CartesianPlot::boxPlotOrientationChanged); 2204 if (!isLoading()) { 2205 boxPlotOrientationChanged(boxPlot->orientation()); 2206 if (!boxPlot->dataColumns().isEmpty()) 2207 checkAxisFormat(boxPlot->coordinateSystemIndex(), boxPlot->dataColumns().constFirst(), Axis::Orientation::Vertical); 2208 } 2209 } 2210 } else if (barPlot) { 2211 DEBUG(Q_FUNC_INFO << ", BAR PLOT") 2212 connect(barPlot, &BarPlot::dataColumnsChanged, this, &CartesianPlot::updateLegend); 2213 } else if (lollipopPlot) { 2214 DEBUG(Q_FUNC_INFO << ", LOLLIPOP PLOT") 2215 connect(lollipopPlot, &LollipopPlot::dataColumnsChanged, this, &CartesianPlot::updateLegend); 2216 } else if (axis) { 2217 connect(axis, &Axis::shiftSignal, this, &CartesianPlot::axisShiftSignal); 2218 } else { 2219 const auto* infoElement = dynamic_cast<const InfoElement*>(child); 2220 if (infoElement) 2221 connect(this, &CartesianPlot::curveRemoved, infoElement, &InfoElement::removeCurve); 2222 2223 // if an element is hovered, the curves which are handled manually in this class 2224 // must be unhovered 2225 if (elem) 2226 connect(elem, &WorksheetElement::hovered, this, &CartesianPlot::childHovered); 2227 } 2228 2229 if (isLoading()) 2230 return; 2231 2232 auto rangeChanged = false; 2233 if (checkRanges && INRANGE(cSystemIndex, 0, m_coordinateSystems.count())) { 2234 auto xIndex = coordinateSystem(cSystemIndex)->index(Dimension::X); 2235 auto yIndex = coordinateSystem(cSystemIndex)->index(Dimension::Y); 2236 setRangeDirty(Dimension::X, xIndex, true); 2237 setRangeDirty(Dimension::Y, yIndex, true); 2238 2239 if (autoScale(Dimension::X, xIndex) && autoScale(Dimension::Y, yIndex)) 2240 rangeChanged = scaleAuto(xIndex, yIndex); 2241 else if (autoScale(Dimension::X, xIndex)) 2242 rangeChanged = scaleAuto(Dimension::X, xIndex); 2243 else if (autoScale(Dimension::Y, yIndex)) 2244 rangeChanged = scaleAuto(Dimension::Y, yIndex); 2245 2246 if (rangeChanged) 2247 WorksheetElementContainer::retransform(); 2248 } 2249 2250 if (!this->pasted() && !child->pasted() && !child->isMoved()) { 2251 // new child was added which might change the ranges and the axis tick labels. 2252 // adjust the plot area padding if the axis label is outside of the plot area 2253 if (rangeChanged) { 2254 const auto& axes = children<Axis>(); 2255 for (auto* axis : axes) { 2256 if (axis->orientation() == WorksheetElement::Orientation::Vertical) { 2257 double delta = plotArea()->graphicsItem()->boundingRect().x() - axis->graphicsItem()->boundingRect().x(); 2258 if (delta > 0) { 2259 setUndoAware(false); 2260 // setSuppressRetransform(true); 2261 setSymmetricPadding(false); 2262 setHorizontalPadding(horizontalPadding() + delta); 2263 // setSuppressRetransform(false); 2264 setUndoAware(true); 2265 } 2266 break; 2267 } 2268 } 2269 } 2270 2271 // if a theme was selected, apply the theme settings for newly added children, 2272 // load default theme settings otherwise. 2273 // TODO const_cast<WorksheetElement*>(elem)->setCoordinateSystemIndex(defaultCoordinateSystemIndex()); 2274 if (!d->theme.isEmpty()) { 2275 KConfig config(ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig); 2276 const_cast<WorksheetElement*>(elem)->loadThemeConfig(config); 2277 } else { 2278 KConfig config; 2279 const_cast<WorksheetElement*>(elem)->loadThemeConfig(config); 2280 } 2281 } 2282 } 2283 2284 // set format of axis from data column 2285 void CartesianPlot::checkAxisFormat(const int cSystemIndex, const AbstractColumn* column, Axis::Orientation orientation) { 2286 if (isLoading()) 2287 return; 2288 2289 const auto* col = qobject_cast<const Column*>(column); 2290 if (!col) 2291 return; 2292 2293 const int xIndex = coordinateSystem(cSystemIndex)->index(Dimension::X); 2294 const int yIndex = coordinateSystem(cSystemIndex)->index(Dimension::Y); 2295 2296 Q_D(CartesianPlot); 2297 if (col->columnMode() == AbstractColumn::ColumnMode::DateTime) { 2298 setUndoAware(false); 2299 if (orientation == Axis::Orientation::Horizontal) 2300 setXRangeFormat(xIndex, RangeT::Format::DateTime); 2301 else 2302 setYRangeFormat(yIndex, RangeT::Format::DateTime); 2303 setUndoAware(true); 2304 2305 // set column's datetime format for all horizontal axis 2306 for (auto* axis : children<Axis>()) { 2307 if (axis->orientation() == orientation) { 2308 const auto* cSystem{coordinateSystem(axis->coordinateSystemIndex())}; 2309 const auto* filter = static_cast<DateTime2StringFilter*>(col->outputFilter()); 2310 d->xRanges[cSystem ? cSystem->index(Dimension::X) : 0].range.setDateTimeFormat(filter->format()); 2311 axis->setUndoAware(false); 2312 axis->setLabelsDateTimeFormat(rangeDateTimeFormat(Dimension::X, xIndex)); 2313 axis->setUndoAware(true); 2314 } 2315 } 2316 } else { 2317 setUndoAware(false); 2318 if (orientation == Axis::Orientation::Horizontal) 2319 setXRangeFormat(xIndex, RangeT::Format::Numeric); 2320 else 2321 setYRangeFormat(yIndex, RangeT::Format::Numeric); 2322 2323 setUndoAware(true); 2324 } 2325 } 2326 2327 void CartesianPlot::boxPlotOrientationChanged(BoxPlot::Orientation orientation) { 2328 const auto& axes = children<Axis>(); 2329 2330 // don't show any labels for the first axis orthogonal to the orientation of the boxplot 2331 for (auto* axis : axes) { 2332 if (axis->orientation() != orientation) { 2333 if (axis->labelsTextType() != Axis::LabelsTextType::CustomValues) { 2334 axis->setUndoAware(false); 2335 axis->setLabelsPosition(Axis::LabelsPosition::NoLabels); 2336 axis->setUndoAware(true); 2337 } 2338 break; 2339 } 2340 } 2341 2342 // don't show any labels for the first axis parallel to the orientation of the boxplot 2343 for (auto* axis : axes) { 2344 if (axis->orientation() == orientation) { 2345 if (axis->labelsTextType() != Axis::LabelsTextType::CustomValues) { 2346 axis->setUndoAware(false); 2347 axis->setLabelsPosition(Axis::LabelsPosition::Out); 2348 axis->setUndoAware(true); 2349 } 2350 break; 2351 } 2352 } 2353 } 2354 2355 void CartesianPlot::childRemoved(const AbstractAspect* /*parent*/, const AbstractAspect* /*before*/, const AbstractAspect* child) { 2356 QDEBUG(Q_FUNC_INFO << ", CHILD = " << child) 2357 if (m_legend == child) { 2358 DEBUG(Q_FUNC_INFO << ", a legend") 2359 if (m_menusInitialized) 2360 addLegendAction->setEnabled(true); 2361 m_legend = nullptr; 2362 } else { 2363 const auto* curve = qobject_cast<const XYCurve*>(child); 2364 Q_D(CartesianPlot); 2365 if (curve) { 2366 DEBUG(Q_FUNC_INFO << ", a curve") 2367 updateLegend(); 2368 Q_EMIT curveRemoved(curve); 2369 const auto cs = coordinateSystem(curve->coordinateSystemIndex()); 2370 const auto xIndex = cs->index(Dimension::X); 2371 const auto yIndex = cs->index(Dimension::Y); 2372 d->xRanges[xIndex].dirty = true; 2373 d->yRanges[yIndex].dirty = true; 2374 2375 bool rangeChanged = false; 2376 if (autoScale(Dimension::X, xIndex) && autoScale(Dimension::Y, yIndex)) 2377 rangeChanged = scaleAuto(xIndex, yIndex); 2378 else if (autoScale(Dimension::X, xIndex)) 2379 rangeChanged = scaleAuto(Dimension::X, xIndex); 2380 else if (autoScale(Dimension::Y, yIndex)) 2381 rangeChanged = scaleAuto(Dimension::Y, yIndex); 2382 2383 if (rangeChanged) 2384 WorksheetElementContainer::retransform(); 2385 } 2386 } 2387 } 2388 2389 /*! 2390 * \brief CartesianPlot::childHovered 2391 * Unhover all curves, when another child is hovered. The hover handling for the curves is done in their parent (CartesianPlot), 2392 * because the hover should set when the curve is hovered and not just the bounding rect (for more see hoverMoveEvent) 2393 */ 2394 void CartesianPlot::childHovered() { 2395 Q_D(CartesianPlot); 2396 bool curveSender = qobject_cast<XYCurve*>(QObject::sender()) != nullptr; 2397 if (!d->isSelected()) { 2398 if (isHovered()) 2399 setHover(false); 2400 else 2401 d->update(); // realy needed? 2402 } 2403 if (!curveSender) { 2404 for (auto curve : children<XYCurve>()) 2405 curve->setHover(false); 2406 } 2407 } 2408 2409 void CartesianPlot::updateLegend() { 2410 if (m_legend) 2411 m_legend->retransform(); 2412 } 2413 2414 /*! 2415 called when in one of the curves the data was changed. 2416 Autoscales the coordinate system and the x-axes, when "auto-scale" is active. 2417 */ 2418 void CartesianPlot::dataChanged(int xIndex, int yIndex, WorksheetElement* sender) { 2419 DEBUG(Q_FUNC_INFO << ", x/y index = " << xIndex << "/" << yIndex) 2420 if (isLoading()) 2421 return; 2422 2423 Q_D(CartesianPlot); 2424 if (d->suppressRetransform) 2425 return; 2426 2427 if (xIndex == -1) { 2428 for (int i = 0; i < rangeCount(Dimension::X); i++) 2429 d->setRangeDirty(Dimension::X, i, true); 2430 } else 2431 d->setRangeDirty(Dimension::X, xIndex, true); 2432 2433 if (yIndex == -1) { 2434 for (int i = 0; i < rangeCount(Dimension::Y); i++) 2435 d->setRangeDirty(Dimension::Y, i, true); 2436 } else 2437 d->setRangeDirty(Dimension::Y, yIndex, true); 2438 2439 bool updated = false; 2440 if (autoScale(Dimension::X, xIndex) && autoScale(Dimension::Y, yIndex)) 2441 updated = scaleAuto(xIndex, yIndex); 2442 else if (autoScale(Dimension::X, xIndex)) 2443 updated = scaleAuto(Dimension::X, xIndex); 2444 else if (autoScale(Dimension::Y, yIndex)) 2445 updated = scaleAuto(Dimension::Y, yIndex); 2446 2447 if (updated) 2448 WorksheetElementContainer::retransform(); 2449 else { 2450 // even if the plot ranges were not changed, either no auto scale active or the new data 2451 // is within the current ranges and no change of the ranges is required, 2452 // retransform the curve in order to show the changes 2453 if (sender) 2454 sender->retransform(); 2455 else { 2456 // no sender available, the function was called directly in the file filter (live data source got new data) 2457 // or in Project::load() -> retransform all available curves since we don't know which curves are affected. 2458 // TODO: this logic can be very expensive 2459 for (auto* child : children<XYCurve>()) { 2460 child->recalcLogicalPoints(); 2461 child->retransform(); 2462 } 2463 } 2464 } 2465 } 2466 2467 void CartesianPlot::dataChanged(WorksheetElement* element) { 2468 DEBUG(Q_FUNC_INFO) 2469 if (project() && project()->isLoading()) 2470 return; 2471 2472 Q_D(CartesianPlot); 2473 if (d->suppressRetransform) 2474 return; 2475 2476 if (!element) 2477 return; 2478 2479 int cSystemIndex = element->coordinateSystemIndex(); 2480 if (cSystemIndex == -1) 2481 return; 2482 2483 const auto xIndex = coordinateSystem(cSystemIndex)->index(Dimension::X); 2484 const auto yIndex = coordinateSystem(cSystemIndex)->index(Dimension::Y); 2485 2486 dataChanged(xIndex, yIndex, element); 2487 } 2488 2489 /*! 2490 called when in one of the curves the data in one direction was changed. 2491 Autoscales the coordinate system and the x/y-axes, when "auto-scale" is active. 2492 */ 2493 void CartesianPlot::dataChanged(XYCurve* curve, const Dimension dim) { 2494 DEBUG(Q_FUNC_INFO) 2495 if (project() && project()->isLoading()) 2496 return; 2497 2498 Q_D(CartesianPlot); 2499 if (d->suppressRetransform) 2500 return; 2501 2502 if (!curve) 2503 return; 2504 2505 int cSystemIndex = curve->coordinateSystemIndex(); 2506 if (cSystemIndex == -1) 2507 return; 2508 auto index = coordinateSystem(cSystemIndex)->index(dim); 2509 Dimension dim_other = Dimension::Y; 2510 switch (dim) { 2511 case Dimension::X: 2512 d->xRanges[index].dirty = true; 2513 break; 2514 case Dimension::Y: 2515 dim_other = Dimension::X; 2516 d->yRanges[index].dirty = true; 2517 break; 2518 } 2519 2520 bool updated = false; 2521 if (autoScale(dim, index)) 2522 updated = this->scaleAuto(dim, index); 2523 2524 QVector<int> scaled; 2525 for (auto* acSystem : m_coordinateSystems) { 2526 auto* cSystem = static_cast<CartesianCoordinateSystem*>(acSystem); 2527 if (cSystem->index(dim) == index && scaled.indexOf(cSystem->index(dim_other)) == -1 && // do not scale again 2528 autoScale(dim_other, cSystem->index(dim_other))) { 2529 scaled << cSystem->index(dim_other); 2530 updated |= scaleAuto(dim_other, cSystem->index(dim_other), false); 2531 } 2532 } 2533 DEBUG(Q_FUNC_INFO << ", updated = " << updated) 2534 2535 if (updated) 2536 WorksheetElementContainer::retransform(); 2537 else { 2538 // even if the plot ranges were not changed, either no auto scale active or the new data 2539 // is within the current ranges and no change of the ranges is required, 2540 // retransform the curve in order to show the changes 2541 curve->retransform(); 2542 } 2543 2544 // in case there is only one curve and its column mode was changed, check whether we start plotting datetime data 2545 if (children<XYCurve>().size() == 1) { 2546 const auto* col = curve->column(dim); 2547 const auto rangeFormat{range(dim, index).format()}; 2548 if (col && col->columnMode() == AbstractColumn::ColumnMode::DateTime && rangeFormat != RangeT::Format::DateTime) { 2549 setUndoAware(false); 2550 setRangeFormat(dim, index, RangeT::Format::DateTime); 2551 setUndoAware(true); 2552 } 2553 } 2554 Q_EMIT curveDataChanged(curve); 2555 } 2556 2557 void CartesianPlot::curveVisibilityChanged() { 2558 const int index = static_cast<WorksheetElement*>(QObject::sender())->coordinateSystemIndex(); 2559 const int xIndex = coordinateSystem(index)->index(Dimension::X); 2560 const int yIndex = coordinateSystem(index)->index(Dimension::Y); 2561 setRangeDirty(Dimension::X, xIndex, true); 2562 setRangeDirty(Dimension::Y, yIndex, true); 2563 updateLegend(); 2564 if (autoScale(Dimension::X, xIndex) && autoScale(Dimension::Y, yIndex)) 2565 this->scaleAuto(xIndex, yIndex); 2566 else if (autoScale(Dimension::X, xIndex)) 2567 this->scaleAuto(Dimension::X, xIndex, false); 2568 else if (autoScale(Dimension::Y, yIndex)) 2569 this->scaleAuto(Dimension::Y, yIndex, false); 2570 2571 WorksheetElementContainer::retransform(); 2572 2573 Q_EMIT curveVisibilityChangedSignal(); 2574 } 2575 2576 void CartesianPlot::plotColorChanged() { 2577 const auto* curve = qobject_cast<const Plot*>(QObject::sender()); 2578 Q_EMIT plotColorChanged(curve->color(), curve->name()); 2579 } 2580 2581 void CartesianPlot::setMouseMode(MouseMode mouseMode) { 2582 Q_D(CartesianPlot); 2583 2584 d->mouseMode = mouseMode; 2585 d->setHandlesChildEvents(mouseMode != MouseMode::Selection); 2586 2587 QList<QGraphicsItem*> items = d->childItems(); 2588 if (mouseMode == MouseMode::Selection) { 2589 d->setZoomSelectionBandShow(false); 2590 d->setCursor(Qt::ArrowCursor); 2591 for (auto* item : items) 2592 item->setFlag(QGraphicsItem::ItemStacksBehindParent, false); 2593 } else { 2594 if (mouseMode == MouseMode::ZoomSelection || mouseMode == MouseMode::Crosshair) 2595 d->setCursor(Qt::CrossCursor); 2596 else if (mouseMode == MouseMode::ZoomXSelection) 2597 d->setCursor(Qt::SizeHorCursor); 2598 else if (mouseMode == MouseMode::ZoomYSelection) 2599 d->setCursor(Qt::SizeVerCursor); 2600 2601 for (auto* item : items) 2602 item->setFlag(QGraphicsItem::ItemStacksBehindParent, true); 2603 } 2604 2605 // when doing zoom selection, prevent the graphics item from being movable 2606 // if it's currently movable (no worksheet layout available) 2607 const auto* worksheet = qobject_cast<const Worksheet*>(parentAspect()); 2608 if (worksheet) { 2609 if (mouseMode == MouseMode::Selection) { 2610 if (worksheet->layout() != Worksheet::Layout::NoLayout) 2611 graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); 2612 else 2613 graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true); 2614 } else // zoom m_selection 2615 graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); 2616 } 2617 2618 Q_EMIT mouseModeChanged(mouseMode); 2619 } 2620 2621 BASIC_SHARED_D_ACCESSOR_IMPL(CartesianPlot, bool, isInteractive, Interactive, interactive) 2622 2623 // auto scale x axis 'index' when auto scale is enabled (index == -1: all x axes) 2624 bool CartesianPlot::scaleAuto(const Dimension dim, int index, bool fullRange, bool suppressRetransformScale) { 2625 DEBUG(Q_FUNC_INFO << ", dim = " << int(dim) << ", index = " << index << ", full range = " << fullRange) 2626 PERFTRACE(QLatin1String(Q_FUNC_INFO)); 2627 Q_D(CartesianPlot); 2628 if (index == -1) { // all ranges 2629 bool updated = false; 2630 for (int i = 0; i < rangeCount(dim); i++) { 2631 if (autoScale(dim, i) && scaleAuto(dim, i, fullRange, true)) { 2632 if (!suppressRetransformScale) 2633 d->retransformScale(dim, i); 2634 updated = true; // at least one was updated 2635 } 2636 } 2637 return updated; 2638 } 2639 2640 auto& r{d->range(dim, index)}; 2641 DEBUG(Q_FUNC_INFO << ", " << CartesianCoordinateSystem::dimensionToString(dim).toStdString() << " range dirty = " << rangeDirty(dim, index)) 2642 if (rangeDirty(dim, index)) { 2643 calculateDataRange(dim, index, fullRange); 2644 setRangeDirty(dim, index, false); 2645 2646 if (fullRange) { 2647 // If not fullrange the y range will be used. So that means 2648 // the yrange would not change and therefore it must not be dirty 2649 for (const auto* c : m_coordinateSystems) { 2650 // All x ranges with this xIndex must be dirty 2651 const auto* cs = dynamic_cast<const CartesianCoordinateSystem*>(c); 2652 if (cs && cs->index(dim) == index) { 2653 switch (dim) { 2654 case Dimension::X: 2655 setRangeDirty(Dimension::Y, cs->index(Dimension::Y), true); 2656 break; 2657 case Dimension::Y: 2658 setRangeDirty(Dimension::X, cs->index(Dimension::X), true); 2659 break; 2660 } 2661 } 2662 } 2663 } 2664 } 2665 auto dataRange = d->dataRange(dim, index); // dataRange used for nice extend 2666 if (dataRange.finite() && d->niceExtend) 2667 dataRange.niceExtend(); // auto scale to nice range 2668 2669 // if no curve: do not reset to [0, 1] 2670 2671 DEBUG(Q_FUNC_INFO << ", range " << index << " = " << r.toStdString() << "., data range = " << d->dataRange(dim, index).toStdString()) 2672 bool update = false; 2673 if (!qFuzzyCompare(dataRange.start(), r.start()) && std::isfinite(dataRange.start())) { 2674 r.start() = dataRange.start(); 2675 update = true; 2676 } 2677 2678 if (!qFuzzyCompare(dataRange.end(), r.end()) && std::isfinite(dataRange.end())) { 2679 r.end() = dataRange.end(); 2680 update = true; 2681 } 2682 2683 if (update) { 2684 switch (dim) { 2685 case Dimension::X: 2686 DEBUG(Q_FUNC_INFO << ", set new x range = " << r.toStdString()) 2687 break; 2688 case Dimension::Y: 2689 DEBUG(Q_FUNC_INFO << ", set new y range = " << r.toStdString()) 2690 break; 2691 } 2692 // in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value 2693 if (r.isZero()) { 2694 const double value = r.start(); 2695 if (!qFuzzyIsNull(value)) 2696 r.setRange(value * 0.9, value * 1.1); 2697 else 2698 r.setRange(-0.1, 0.1); 2699 } else 2700 r.extend(r.size() * d->autoScaleOffsetFactor); 2701 2702 Q_EMIT rangeChanged(dim, index, r); 2703 2704 if (!suppressRetransformScale) 2705 d->retransformScale(dim, index); 2706 } 2707 2708 return update; 2709 } 2710 2711 // auto scale all x axis xIndex and y axis yIndex when auto scale is enabled (index == -1: all x/y axes) 2712 bool CartesianPlot::scaleAuto(int xIndex, int yIndex, bool fullRange, bool suppressRetransformScale) { 2713 DEBUG(Q_FUNC_INFO << " x/y index = " << xIndex << " / " << yIndex) 2714 PERFTRACE(QLatin1String(Q_FUNC_INFO)); 2715 bool updateX = scaleAuto(Dimension::X, xIndex, fullRange, suppressRetransformScale); 2716 bool updateY = scaleAuto(Dimension::Y, yIndex, fullRange, suppressRetransformScale); 2717 DEBUG(Q_FUNC_INFO << ", update X/Y = " << updateX << "/" << updateY) 2718 2719 // x range is dirty, because scaleAutoY sets it to dirty. 2720 // TODO: check if it can be removed 2721 if (xIndex < 0) { 2722 for (int i = 0; i < m_coordinateSystems.count(); i++) { 2723 setRangeDirty(Dimension::X, coordinateSystem(i)->index(Dimension::X), false); 2724 // setRangeDirty(Dimension::Y, coordinateSystem(i)->index(Dimension::Y), false); 2725 } 2726 } else { 2727 setRangeDirty(Dimension::X, xIndex, false); 2728 // setRangeDirty(Dimension::Y, coordinateSystem(cSystemIndex)->index(Dimension::Y), false); 2729 } 2730 2731 return (updateX || updateY); 2732 } 2733 2734 /*! 2735 * Calculates and saves data range. 2736 */ 2737 void CartesianPlot::calculateDataRange(const Dimension dim, const int index, bool completeRange) { 2738 DEBUG(Q_FUNC_INFO << ", direction = " << CartesianCoordinateSystem::dimensionToString(dim).toStdString() << ", index = " << index 2739 << ", complete range = " << completeRange) 2740 Q_D(CartesianPlot); 2741 2742 d->dataRange(dim, index).setRange(INFINITY, -INFINITY); 2743 auto range{d->range(dim, index)}; // get reference to range from private 2744 2745 // loop over all curves/plots and determine the maximum and minimum values 2746 for (const auto* plot : this->children<const Plot>()) { 2747 if (!plot->isVisible() || !plot->hasData()) 2748 continue; 2749 2750 if (coordinateSystem(plot->coordinateSystemIndex())->index(dim) != index) 2751 continue; 2752 2753 if (dynamic_cast<const XYCurve*>(plot)) { 2754 auto* curve = static_cast<const XYCurve*>(plot); 2755 DEBUG("CURVE \"" << STDSTRING(curve->name()) << "\"") 2756 auto* column = curve->column(dim); 2757 if (!column) { 2758 DEBUG(" NO column!") 2759 continue; 2760 } 2761 2762 // range of indices 2763 Range<int> indexRange{0, 0}; 2764 if (!completeRange && d->rangeType == RangeType::Free) { 2765 Dimension dim_other = Dimension::Y; 2766 switch (dim) { 2767 case Dimension::X: 2768 break; 2769 case Dimension::Y: 2770 dim_other = Dimension::X; 2771 break; 2772 } 2773 2774 if (curve->column(dim_other)) { // only data within y range 2775 const int index = coordinateSystem(curve->coordinateSystemIndex())->index(dim_other); 2776 DEBUG(Q_FUNC_INFO << ", free incomplete range with y column. y range = " << d->range(dim_other, index).toStdString()) 2777 curve->column(dim_other)->indicesMinMax(d->range(dim_other, index).start(), 2778 d->range(dim_other, index).end(), 2779 indexRange.start(), 2780 indexRange.end()); 2781 } 2782 } else { // all data 2783 DEBUG(Q_FUNC_INFO << ", else. range type = " << (int)d->rangeType) 2784 switch (d->rangeType) { 2785 case RangeType::Free: 2786 indexRange.setRange(0, column->rowCount() - 1); 2787 break; 2788 case RangeType::Last: 2789 indexRange.setRange(column->rowCount() - d->rangeLastValues, column->rowCount() - 1); 2790 break; 2791 case RangeType::First: 2792 indexRange.setRange(0, d->rangeFirstValues - 1); 2793 break; 2794 } 2795 } 2796 DEBUG(Q_FUNC_INFO << ", index range = " << indexRange.toStdString()) 2797 2798 curve->minMax(dim, indexRange, range, true); 2799 } else if (plot->type() == AspectType::KDEPlot) { 2800 const int minIndex = 0; 2801 const int maxIndex = static_cast<const KDEPlot*>(plot)->gridPointsCount() - 1; 2802 Range<int> indexRange{minIndex, maxIndex}; 2803 plot->minMax(dim, indexRange, range, true); 2804 } else if (plot->type() == AspectType::QQPlot) { 2805 Range<int> indexRange{0, 99}; // 100 percentile values are calculated, max index is 99 2806 plot->minMax(dim, indexRange, range, true); 2807 } else { 2808 range.setStart(plot->minimum(dim)); 2809 range.setEnd(plot->maximum(dim)); 2810 } 2811 2812 if (range.start() < d->dataRange(dim, index).start()) 2813 d->dataRange(dim, index).start() = range.start(); 2814 2815 if (range.end() > d->dataRange(dim, index).end()) 2816 d->dataRange(dim, index).end() = range.end(); 2817 2818 DEBUG(Q_FUNC_INFO << ", plot's range i = " << d->dataRange(dim, index).toStdString(false)) 2819 } 2820 2821 // data range is used to nice extend, so set correct scale 2822 d->dataRange(dim, index).setScale(range.scale()); 2823 2824 // check ranges for nonlinear scales 2825 if (d->dataRange(dim, index).scale() != RangeT::Scale::Linear) 2826 d->dataRange(dim, index) = d->checkRange(d->dataRange(dim, index)); 2827 2828 DEBUG(Q_FUNC_INFO << ", data " << CartesianCoordinateSystem::dimensionToString(dim).toStdString() 2829 << " range = " << d->dataRange(dim, index).toStdString(false)) 2830 } 2831 2832 void CartesianPlot::retransformScales() { 2833 Q_D(CartesianPlot); 2834 d->retransformScales(-1, -1); 2835 } 2836 void CartesianPlot::retransformScale(Dimension dim, int index) { 2837 Q_D(CartesianPlot); 2838 d->retransformScale(dim, index); 2839 } 2840 2841 // zoom 2842 2843 void CartesianPlot::zoomIn(int xIndex, int yIndex, const QPointF& sceneRelPos) { 2844 setUndoAware(false); 2845 enableAutoScale(Dimension::X, xIndex, false); 2846 enableAutoScale(Dimension::Y, yIndex, false); 2847 setUndoAware(true); 2848 setRangeDirty(Dimension::X, xIndex, true); 2849 setRangeDirty(Dimension::Y, yIndex, true); 2850 zoom(xIndex, Dimension::X, true, sceneRelPos.x()); // zoom in x 2851 zoom(yIndex, Dimension::Y, true, sceneRelPos.y()); // zoom in y 2852 2853 Q_D(CartesianPlot); 2854 d->retransformScales(xIndex, yIndex); 2855 WorksheetElementContainer::retransform(); 2856 } 2857 2858 void CartesianPlot::zoomOut(int xIndex, int yIndex, const QPointF& sceneRelPos) { 2859 setUndoAware(false); 2860 enableAutoScale(Dimension::X, xIndex, false); 2861 enableAutoScale(Dimension::Y, yIndex, false); 2862 setUndoAware(true); 2863 setRangeDirty(Dimension::X, xIndex, true); 2864 setRangeDirty(Dimension::Y, yIndex, true); 2865 zoom(xIndex, Dimension::X, false, sceneRelPos.x()); // zoom out x 2866 zoom(yIndex, Dimension::Y, false, sceneRelPos.y()); // zoom out y 2867 2868 Q_D(CartesianPlot); 2869 d->retransformScales(xIndex, yIndex); 2870 WorksheetElementContainer::retransform(); 2871 } 2872 2873 void CartesianPlot::zoomInX(int index) { 2874 zoomInOut(index, Dimension::X, true); 2875 } 2876 2877 void CartesianPlot::zoomOutX(int index) { 2878 zoomInOut(index, Dimension::X, false); 2879 } 2880 2881 void CartesianPlot::zoomInY(int index) { 2882 zoomInOut(index, Dimension::Y, true); 2883 } 2884 2885 void CartesianPlot::zoomOutY(int index) { 2886 zoomInOut(index, Dimension::Y, false); 2887 } 2888 2889 void CartesianPlot::zoomInOut(const int index, const Dimension dim, const bool zoomIn, const double relScenePosRange) { 2890 Dimension dim_other = Dimension::Y; 2891 if (dim == Dimension::Y) 2892 dim_other = Dimension::X; 2893 2894 setUndoAware(false); 2895 enableAutoScale(dim, index, false); 2896 setUndoAware(true); 2897 setRangeDirty(dim_other, index, true); 2898 zoom(index, dim, zoomIn, relScenePosRange); 2899 2900 bool retrans = false; 2901 for (int i = 0; i < m_coordinateSystems.count(); i++) { 2902 const auto cs = coordinateSystem(i); 2903 if (index == -1 || index == cs->index(dim)) { 2904 if (autoScale(dim_other, cs->index(dim_other))) 2905 scaleAuto(dim_other, cs->index(dim_other), false); 2906 retrans = true; 2907 } 2908 } 2909 2910 Q_D(CartesianPlot); 2911 if (retrans) { 2912 // If the other dimension is autoScale it will be scaled and then 2913 // retransformScale() will be called. So here we just have to do 2914 // it for the nontransformed scale because in zoom it will not be done 2915 if (index == -1) { 2916 for (int i = 0; i < rangeCount(dim); i++) 2917 d->retransformScale(dim, i); 2918 } else 2919 d->retransformScale(dim, index); 2920 WorksheetElementContainer::retransform(); 2921 } 2922 } 2923 2924 /*! 2925 * helper function called in other zoom*() functions 2926 * and doing the actual change of the data ranges. 2927 * @param x if set to \true the x-range is modified, the y-range for \c false 2928 * @param in the "zoom in" is performed if set to \c \true, "zoom out" for \c false 2929 */ 2930 void CartesianPlot::zoom(int index, const Dimension dim, bool zoom_in, const double relPosSceneRange) { 2931 Q_D(CartesianPlot); 2932 2933 Range<double> range; 2934 if (index == -1) { 2935 QVector<int> zoomedIndices; 2936 for (int i = 0; i < m_coordinateSystems.count(); i++) { 2937 int idx = coordinateSystem(i)->index(dim); 2938 if (zoomedIndices.contains(idx)) 2939 continue; 2940 zoom(idx, dim, zoom_in, relPosSceneRange); 2941 zoomedIndices.append(idx); 2942 } 2943 return; 2944 } 2945 range = d->range(dim, index); 2946 2947 double factor = m_zoomFactor; 2948 if (zoom_in) 2949 factor = 1. / factor; 2950 range.zoom(factor, d->niceExtend, relPosSceneRange); 2951 2952 if (range.finite()) 2953 d->setRange(dim, index, range); 2954 } 2955 2956 /*! 2957 * helper function called in other shift*() functions 2958 * and doing the actual change of the data ranges. 2959 * @param x if set to \true the x-range is modified, the y-range for \c false 2960 * @param leftOrDown the "shift left" for x or "shift dows" for y is performed if set to \c \true, 2961 * "shift right" or "shift up" for \c false 2962 */ 2963 void CartesianPlot::shift(int index, const Dimension dim, bool leftOrDown) { 2964 setUndoAware(false); 2965 enableAutoScale(dim, index, false); 2966 setUndoAware(true); 2967 Q_D(CartesianPlot); 2968 2969 Range<double> range; 2970 if (index == -1) { 2971 QVector<int> shiftedIndices; 2972 for (int i = 0; i < m_coordinateSystems.count(); i++) { 2973 int idx = coordinateSystem(i)->index(dim); 2974 if (shiftedIndices.contains(idx)) 2975 continue; 2976 shift(idx, dim, leftOrDown); 2977 shiftedIndices.append(idx); 2978 } 2979 return; 2980 } 2981 range = d->range(dim, index); 2982 2983 double offset = 0.0, factor = 0.1; 2984 2985 if (!leftOrDown) 2986 factor *= -1.; 2987 2988 const double start{range.start()}, end{range.end()}; 2989 switch (range.scale()) { 2990 case RangeT::Scale::Linear: { 2991 offset = range.size() * factor; 2992 range += offset; 2993 break; 2994 } 2995 case RangeT::Scale::Log10: { 2996 if (start == 0 || end / start <= 0) 2997 break; 2998 offset = log10(end / start) * factor; 2999 range *= pow(10, offset); 3000 break; 3001 } 3002 case RangeT::Scale::Log2: { 3003 if (start == 0 || end / start <= 0) 3004 break; 3005 offset = log2(end / start) * factor; 3006 range *= exp2(offset); 3007 break; 3008 } 3009 case RangeT::Scale::Ln: { 3010 if (start == 0 || end / start <= 0) 3011 break; 3012 offset = log(end / start) * factor; 3013 range *= exp(offset); 3014 break; 3015 } 3016 case RangeT::Scale::Sqrt: 3017 if (start < 0 || end < 0) 3018 break; 3019 offset = (sqrt(end) - sqrt(start)) * factor; 3020 range += offset * offset; 3021 break; 3022 case RangeT::Scale::Square: 3023 offset = (end * end - start * start) * factor; 3024 range += sqrt(std::abs(offset)); 3025 break; 3026 case RangeT::Scale::Inverse: 3027 offset = (1. / start - 1. / end) * factor; 3028 range += 1. / std::abs(offset); 3029 break; 3030 } 3031 3032 if (range.finite()) 3033 d->setRange(dim, index, range); 3034 3035 d->retransformScale(dim, index); 3036 3037 auto dim_other = Dimension::X; 3038 switch (dim) { 3039 case Dimension::X: 3040 dim_other = Dimension::Y; 3041 break; 3042 case Dimension::Y: 3043 dim_other = Dimension::X; 3044 break; 3045 } 3046 3047 bool retrans = false; 3048 for (const auto cSystem : m_coordinateSystems) { 3049 const auto cs = static_cast<CartesianCoordinateSystem*>(cSystem); 3050 if ((index == -1 || index == cs->index(dim))) { 3051 if (autoScale(dim_other, cs->index(dim_other))) { 3052 setRangeDirty(dim_other, cs->index(dim_other), true); 3053 scaleAuto(dim_other, cs->index(dim_other), false); 3054 } 3055 retrans = true; 3056 } 3057 } 3058 3059 if (retrans) 3060 WorksheetElementContainer::retransform(); 3061 } 3062 3063 void CartesianPlot::shiftLeftX(int index) { 3064 shift(index, Dimension::X, true); 3065 } 3066 3067 void CartesianPlot::shiftRightX(int index) { 3068 shift(index, Dimension::X, false); 3069 } 3070 3071 void CartesianPlot::shiftUpY(int index) { 3072 shift(index, Dimension::Y, false); 3073 } 3074 3075 void CartesianPlot::shiftDownY(int index) { 3076 shift(index, Dimension::Y, true); 3077 } 3078 3079 void CartesianPlot::cursor() { 3080 Q_D(CartesianPlot); 3081 d->retransformScales(-1, -1); // TODO: needed to retransform all scales? 3082 } 3083 3084 void CartesianPlot::wheelEvent(const QPointF& sceneRelPos, int delta, int xIndex, int yIndex, bool considerDimension, Dimension dim) { 3085 Q_D(CartesianPlot); 3086 d->wheelEvent(sceneRelPos, delta, xIndex, yIndex, considerDimension, dim); 3087 } 3088 3089 void CartesianPlot::mousePressZoomSelectionMode(QPointF logicPos, int cSystemIndex) { 3090 Q_D(CartesianPlot); 3091 d->mousePressZoomSelectionMode(logicPos, cSystemIndex); 3092 } 3093 void CartesianPlot::mousePressCursorMode(int cursorNumber, QPointF logicPos) { 3094 Q_D(CartesianPlot); 3095 d->mousePressCursorMode(cursorNumber, logicPos); 3096 } 3097 void CartesianPlot::mouseMoveZoomSelectionMode(QPointF logicPos, int cSystemIndex) { 3098 Q_D(CartesianPlot); 3099 d->mouseMoveZoomSelectionMode(logicPos, cSystemIndex); 3100 } 3101 3102 void CartesianPlot::mouseMoveSelectionMode(QPointF logicStart, QPointF logicEnd) { 3103 Q_D(CartesianPlot); 3104 d->mouseMoveSelectionMode(logicStart, logicEnd); 3105 } 3106 3107 void CartesianPlot::mouseMoveCursorMode(int cursorNumber, QPointF logicPos) { 3108 Q_D(CartesianPlot); 3109 d->mouseMoveCursorMode(cursorNumber, logicPos); 3110 } 3111 3112 void CartesianPlot::mouseReleaseZoomSelectionMode(int cSystemIndex) { 3113 Q_D(CartesianPlot); 3114 d->mouseReleaseZoomSelectionMode(cSystemIndex); 3115 } 3116 3117 void CartesianPlot::mouseHoverZoomSelectionMode(QPointF logicPos, int cSystemIndex) { 3118 Q_D(CartesianPlot); 3119 d->mouseHoverZoomSelectionMode(logicPos, cSystemIndex); 3120 } 3121 3122 void CartesianPlot::mouseHoverOutsideDataRect() { 3123 Q_D(CartesianPlot); 3124 d->mouseHoverOutsideDataRect(); 3125 } 3126 3127 // ##################################################################### 3128 // ################### Private implementation ########################## 3129 // ##################################################################### 3130 CartesianPlotPrivate::CartesianPlotPrivate(CartesianPlot* plot) 3131 : AbstractPlotPrivate(plot) 3132 , q(plot) { 3133 m_cursor0Text.prepare(); 3134 m_cursor1Text.prepare(); 3135 } 3136 3137 CartesianPlotPrivate::~CartesianPlotPrivate() = default; 3138 3139 /*! 3140 updates the position of plot rectangular in scene coordinates to \c r and recalculates the scales. 3141 The size of the plot corresponds to the size of the plot area, the area which is filled with the background color etc. 3142 and which can pose the parent item for several sub-items (like TextLabel). 3143 Note, the size of the area used to define the coordinate system doesn't need to be equal to this plot area. 3144 Also, the size (=bounding box) of CartesianPlot can be greater than the size of the plot area. 3145 */ 3146 void CartesianPlotPrivate::retransform() { 3147 const bool suppress = suppressRetransform || q->isLoading(); 3148 trackRetransformCalled(suppress); 3149 for (int i = 0; i < xRanges.count(); i++) 3150 DEBUG(Q_FUNC_INFO << ", x range " << i + 1 << " : " << xRanges.at(i).range.toStdString() 3151 << ", scale = " << ENUM_TO_STRING(RangeT, Scale, xRanges.at(i).range.scale())); 3152 if (suppress) 3153 return; 3154 3155 PERFTRACE(QLatin1String(Q_FUNC_INFO)); 3156 prepareGeometryChange(); 3157 setPos(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2); 3158 3159 updateDataRect(); 3160 3161 // plotArea position is always (0, 0) in parent's coordinates, don't need to update here 3162 q->plotArea()->setRect(rect); 3163 3164 WorksheetElementContainerPrivate::recalcShapeAndBoundingRect(); 3165 3166 retransformScales(-1, -1); 3167 3168 q->WorksheetElementContainer::retransform(); 3169 } 3170 3171 /*! 3172 * \brief CartesianPlotPrivate::retransformScale 3173 * Sets new Scales to coordinate systems and updates the ranges of the axis 3174 * Needed when the range (xRange/yRange) changed 3175 * \param index 3176 */ 3177 void CartesianPlotPrivate::retransformScale(const Dimension dim, int index, bool suppressSignals) { 3178 if (index == -1) { 3179 for (int i = 0; i < rangeCount(dim); i++) 3180 retransformScale(dim, i); 3181 return; 3182 } 3183 3184 // we have to recalculate the data range and auto scale in case of scale changes 3185 if (range(dim, index).scale() != dataRange(dim, index).scale() && autoScale(dim, index)) { 3186 q->calculateDataRange(dim, index); 3187 q->scaleAuto(dim, index, true, true); 3188 } 3189 auto r = range(dim, index); 3190 QDEBUG(Q_FUNC_INFO << CartesianCoordinateSystem::dimensionToString(dim) << "range =" << r.toString() << ", scale =" << r.scale() 3191 << ", auto scale = " << r.autoScale()) 3192 3193 static const int breakGap = 20; 3194 Range<double> plotSceneRange; 3195 switch (dim) { 3196 case Dimension::X: 3197 plotSceneRange = {dataRect.x(), dataRect.x() + dataRect.width()}; 3198 break; 3199 case Dimension::Y: 3200 plotSceneRange = {dataRect.y() + dataRect.height(), dataRect.y()}; 3201 break; 3202 }; 3203 Range<double> sceneRange, logicalRange; 3204 bool scaleChanged = false; 3205 for (const auto cSystem : coordinateSystems()) { 3206 const auto cs = static_cast<CartesianCoordinateSystem*>(cSystem); 3207 if (cs->index(dim) != index) 3208 continue; 3209 3210 QVector<CartesianScale*> scales; 3211 3212 // check whether we have x/y-range breaks - the first break, if available, should be valid 3213 bool hasValidBreak = (rangeBreakingEnabled(dim) && !rangeBreaks(dim).list.isEmpty() && rangeBreaks(dim).list.first().isValid()); 3214 if (!hasValidBreak) { // no breaks available -> range goes from the start to the end of the plot 3215 sceneRange = plotSceneRange; 3216 logicalRange = r; 3217 3218 if (sceneRange.length() > 0) 3219 scales << this->createScale(logicalRange.scale(), sceneRange, logicalRange); 3220 } else { 3221 double sceneEndLast = plotSceneRange.start(); 3222 double logicalEndLast = r.start(); 3223 auto rbs = rangeBreaks(dim); 3224 for (const auto& rb : qAsConst(rbs.list)) { 3225 if (!rb.isValid()) 3226 break; 3227 3228 // current range goes from the end of the previous one (or from the plot beginning) to curBreak.start 3229 sceneRange.start() = sceneEndLast; 3230 if (&rb == &rangeBreaks(dim).list.first()) 3231 sceneRange.start() -= breakGap; 3232 sceneRange.end() = plotSceneRange.start() + plotSceneRange.size() * rb.position; 3233 logicalRange = Range<double>(logicalEndLast, rb.range.start()); 3234 3235 if (sceneRange.length() > 0) 3236 scales << this->createScale(r.scale(), sceneRange, logicalRange); 3237 3238 sceneEndLast = sceneRange.end(); 3239 logicalEndLast = rb.range.end(); 3240 } 3241 3242 // add the remaining range going from the last available range break to the end of the plot (=end of the y-data range) 3243 sceneRange.setRange(sceneEndLast - breakGap, plotSceneRange.end()); 3244 logicalRange.setRange(logicalEndLast, r.end()); 3245 3246 if (sceneRange.length() > 0) 3247 scales << this->createScale(r.scale(), sceneRange, logicalRange); 3248 } 3249 cs->setScales(dim, scales); 3250 scaleChanged = true; 3251 } 3252 3253 if (scaleChanged) 3254 Q_EMIT q->scaleRetransformed(q, dim, index); 3255 3256 // Set ranges in the axis 3257 for (int i = 0; i < q->rangeCount(dim); i++) { 3258 auto& rangep = ranges(dim)[i]; 3259 // Fix range when values not valid 3260 rangep.range.fixLimits(); 3261 3262 QDEBUG(Q_FUNC_INFO << ", range" << i << ":" << rangep.range.toString()) 3263 const double deltaMin = rangep.range.start() - rangep.prev.start(); 3264 const double deltaMax = rangep.range.end() - rangep.prev.end(); 3265 3266 if (!qFuzzyIsNull(deltaMin) && !suppressSignals) 3267 Q_EMIT q->minChanged(dim, i, rangep.range.start()); 3268 if (!qFuzzyIsNull(deltaMax) && !suppressSignals) 3269 Q_EMIT q->maxChanged(dim, i, rangep.range.end()); 3270 3271 rangep.prev = rangep.range; 3272 3273 for (auto* axis : q->children<Axis>()) { 3274 QDEBUG(Q_FUNC_INFO << ", auto-scale axis" << axis->name() << "of scale" << axis->scale()) 3275 // use ranges of axis 3276 auto r = axis->range(); 3277 r.setScale(rangep.range.scale()); 3278 3279 int axisIndex = q->coordinateSystem(axis->coordinateSystemIndex())->index(dim); 3280 if (axis->rangeType() != Axis::RangeType::Auto || axisIndex != i) 3281 continue; 3282 if ((dim == Dimension::Y && axis->orientation() != Axis::Orientation::Vertical) 3283 || (dim == Dimension::X && axis->orientation() != Axis::Orientation::Horizontal)) 3284 continue; 3285 3286 if (!qFuzzyIsNull(deltaMax)) 3287 r.setEnd(rangep.range.end()); 3288 if (!qFuzzyIsNull(deltaMin)) 3289 r.setStart(rangep.range.start()); 3290 3291 axis->setUndoAware(false); 3292 axis->setSuppressRetransform(true); 3293 axis->setRange(r); 3294 axis->setUndoAware(true); 3295 axis->setSuppressRetransform(false); 3296 // TODO; 3297 // if (axis->position() == Axis::Position::Centered && deltaYMin != 0) { 3298 // axis->setOffset(axis->offset() + deltaYMin, false); 3299 // } 3300 } 3301 } 3302 } 3303 3304 /* 3305 * calculate x and y scales from scence range and logical range (x/y range) for all coordinate systems 3306 */ 3307 void CartesianPlotPrivate::retransformScales(int xIndex, int yIndex) { 3308 DEBUG(Q_FUNC_INFO << ", SCALES x/y index = " << xIndex << "/" << yIndex) 3309 for (int i = 0; i < xRanges.count(); i++) 3310 DEBUG(Q_FUNC_INFO << ", x range " << i + 1 << " : " << xRanges.at(i).range.toStdString() << ", scale = " 3311 << ENUM_TO_STRING(RangeT, Scale, xRanges.at(i).range.scale()) << ", auto scale = " << xRanges.at(i).range.autoScale()); 3312 for (int i = 0; i < yRanges.count(); i++) 3313 DEBUG(Q_FUNC_INFO << ", y range " << i + 1 << " : " << yRanges.at(i).range.toStdString() << ", scale = " 3314 << ENUM_TO_STRING(RangeT, Scale, yRanges.at(i).range.scale()) << ", auto scale = " << yRanges.at(i).range.autoScale()); 3315 3316 PERFTRACE(QLatin1String(Q_FUNC_INFO)); 3317 3318 retransformScale(Dimension::X, xIndex); 3319 retransformScale(Dimension::Y, yIndex); 3320 } 3321 3322 /* 3323 * calculates the rectangular of the are showing the actual data (plot's rect minus padding), 3324 * in plot's coordinates. 3325 */ 3326 void CartesianPlotPrivate::updateDataRect() { 3327 dataRect = mapRectFromScene(rect); 3328 3329 double paddingLeft = horizontalPadding; 3330 double paddingRight = rightPadding; 3331 double paddingTop = verticalPadding; 3332 double paddingBottom = bottomPadding; 3333 if (symmetricPadding) { 3334 paddingRight = horizontalPadding; 3335 paddingBottom = verticalPadding; 3336 } 3337 3338 dataRect.setX(dataRect.x() + paddingLeft); 3339 dataRect.setY(dataRect.y() + paddingTop); 3340 3341 double newHeight = dataRect.height() - paddingBottom; 3342 if (newHeight < 0) 3343 newHeight = 0; 3344 dataRect.setHeight(newHeight); 3345 3346 double newWidth = dataRect.width() - paddingRight; 3347 if (newWidth < 0) 3348 newWidth = 0; 3349 dataRect.setWidth(newWidth); 3350 } 3351 3352 CartesianCoordinateSystem* CartesianPlotPrivate::coordinateSystem(const int index) const { 3353 if (index < 0) 3354 return defaultCoordinateSystem(); 3355 3356 return static_cast<CartesianCoordinateSystem*>(q->m_coordinateSystems.at(index)); 3357 } 3358 3359 QVector<AbstractCoordinateSystem*> CartesianPlotPrivate::coordinateSystems() const { 3360 return q->m_coordinateSystems; 3361 } 3362 3363 /*! 3364 * \brief CartesianPlotPrivate::rangeChanged 3365 * This function will be called if the range shall be updated, because some other parameters (like the datarange type, datarange points) 3366 * changed. In this case the ranges must be updated if autoscale is turned on 3367 * At the end signals for all ranges are send out that they changed. 3368 */ 3369 void CartesianPlotPrivate::rangeChanged() { 3370 DEBUG(Q_FUNC_INFO) 3371 for (const auto* cSystem : q->m_coordinateSystems) { 3372 const auto cs = static_cast<const CartesianCoordinateSystem*>(cSystem); 3373 int xIndex = cs->index(Dimension::X); 3374 int yIndex = cs->index(Dimension::Y); 3375 xRanges[xIndex].dirty = true; 3376 yRanges[yIndex].dirty = true; 3377 if (autoScale(Dimension::X, xIndex) && autoScale(Dimension::Y, yIndex)) 3378 q->scaleAuto(xIndex, yIndex); 3379 else if (autoScale(Dimension::X, xIndex)) 3380 q->scaleAuto(Dimension::X, xIndex, false); 3381 else if (autoScale(Dimension::Y, yIndex)) 3382 q->scaleAuto(Dimension::Y, yIndex, false); 3383 } 3384 q->WorksheetElementContainer::retransform(); 3385 } 3386 3387 void CartesianPlotPrivate::niceExtendChanged() { 3388 DEBUG(Q_FUNC_INFO) 3389 for (const auto* cSystem : q->m_coordinateSystems) { 3390 const auto cs = static_cast<const CartesianCoordinateSystem*>(cSystem); 3391 int xIndex = cs->index(Dimension::X); 3392 int yIndex = cs->index(Dimension::Y); 3393 xRanges[xIndex].dirty = true; 3394 yRanges[yIndex].dirty = true; 3395 if (autoScale(Dimension::X, xIndex) && autoScale(Dimension::Y, yIndex)) 3396 q->scaleAuto(xIndex, yIndex); 3397 else if (autoScale(Dimension::X, xIndex)) 3398 q->scaleAuto(Dimension::X, xIndex, false); 3399 else if (autoScale(Dimension::Y, yIndex)) 3400 q->scaleAuto(Dimension::Y, yIndex, false); 3401 } 3402 q->WorksheetElementContainer::retransform(); 3403 } 3404 3405 void CartesianPlotPrivate::rangeFormatChanged(const Dimension dim) { 3406 DEBUG(Q_FUNC_INFO) 3407 switch (dim) { 3408 case Dimension::X: { 3409 for (auto* axis : q->children<Axis>()) { 3410 // TODO: only if x range of axis's plot range is changed 3411 if (axis->orientation() == Axis::Orientation::Horizontal) 3412 axis->retransformTickLabelStrings(); 3413 } 3414 break; 3415 } 3416 case Dimension::Y: { 3417 for (auto* axis : q->children<Axis>()) { 3418 // TODO: only if x range of axis's plot range is changed 3419 if (axis->orientation() == Axis::Orientation::Horizontal) 3420 axis->retransformTickLabelStrings(); 3421 } 3422 break; 3423 } 3424 } 3425 } 3426 3427 CartesianPlot::RangeBreaks CartesianPlotPrivate::rangeBreaks(const Dimension dim) { 3428 switch (dim) { 3429 case Dimension::X: 3430 return xRangeBreaks; 3431 case Dimension::Y: 3432 return yRangeBreaks; 3433 } 3434 return CartesianPlot::RangeBreaks(); 3435 } 3436 3437 bool CartesianPlotPrivate::rangeBreakingEnabled(const Dimension dim) { 3438 switch (dim) { 3439 case Dimension::X: 3440 return xRangeBreakingEnabled; 3441 case Dimension::Y: 3442 return yRangeBreakingEnabled; 3443 } 3444 return false; 3445 } 3446 3447 /*! 3448 * helper function for checkXRange() and checkYRange() 3449 */ 3450 Range<double> CartesianPlotPrivate::checkRange(const Range<double>& range) { 3451 double start = range.start(), end = range.end(); 3452 const auto scale = range.scale(); 3453 if (scale == RangeT::Scale::Linear || (start > 0 && end > 0)) // nothing to do 3454 return range; 3455 if (start >= 0 && end >= 0 && scale == RangeT::Scale::Sqrt) // nothing to do 3456 return range; 3457 // TODO: check if start == end? 3458 3459 double min = 0.01, max = 1.; 3460 3461 if (scale == RangeT::Scale::Sqrt) { 3462 if (start < 0) 3463 start = 0.; 3464 } else if (start <= 0) 3465 start = min; 3466 if (scale == RangeT::Scale::Sqrt) { 3467 if (end < 0) 3468 end = max; 3469 } else if (end <= 0) 3470 end = max; 3471 3472 auto newRange = range; 3473 newRange.setStart(start); 3474 newRange.setEnd(end); 3475 return newRange; 3476 } 3477 3478 /*! 3479 * check for negative values in the range when non-linear scalings are used 3480 */ 3481 void CartesianPlotPrivate::checkRange(Dimension dim, int index) { 3482 const auto range = ranges(dim).at(index).range; 3483 DEBUG(Q_FUNC_INFO << ", " << CartesianCoordinateSystem::dimensionToString(dim).toStdString() << " range " << index + 1 << " : " << range.toStdString() 3484 << ", scale = " << ENUM_TO_STRING(RangeT, Scale, range.scale())) 3485 3486 const auto newRange = checkRange(range); 3487 3488 const double start = newRange.start(), end = newRange.end(); 3489 if (start != range.start()) { 3490 DEBUG(Q_FUNC_INFO << ", old/new start = " << range.start() << "/" << start) 3491 q->setMin(dim, index, start); 3492 } 3493 if (end != range.end()) { 3494 DEBUG(Q_FUNC_INFO << ", old/new end = " << range.end() << "/" << end) 3495 q->setMax(dim, index, end); 3496 } 3497 } 3498 3499 CartesianScale* CartesianPlotPrivate::createScale(RangeT::Scale scale, const Range<double>& sceneRange, const Range<double>& logicalRange) { 3500 QDEBUG(Q_FUNC_INFO << ", scale =" << scale << ", scene range : " << sceneRange.toString() << ", logical range : " << logicalRange.toString()); 3501 3502 Range<double> range(std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max()); 3503 3504 switch (scale) { 3505 case RangeT::Scale::Linear: 3506 return CartesianScale::createLinearScale(range, sceneRange, logicalRange); 3507 case RangeT::Scale::Log10: 3508 case RangeT::Scale::Log2: 3509 case RangeT::Scale::Ln: 3510 return CartesianScale::createLogScale(range, sceneRange, logicalRange, scale); 3511 case RangeT::Scale::Sqrt: 3512 return CartesianScale::createSqrtScale(range, sceneRange, logicalRange); 3513 case RangeT::Scale::Square: 3514 return CartesianScale::createSquareScale(range, sceneRange, logicalRange); 3515 case RangeT::Scale::Inverse: 3516 return CartesianScale::createInverseScale(range, sceneRange, logicalRange); 3517 } 3518 3519 return nullptr; 3520 } 3521 3522 /*! 3523 * Reimplemented from QGraphicsItem. 3524 */ 3525 QVariant CartesianPlotPrivate::itemChange(GraphicsItemChange change, const QVariant& value) { 3526 if (change == QGraphicsItem::ItemPositionChange) { 3527 const QPointF& itemPos = value.toPointF(); // item's center point in parent's coordinates; 3528 const qreal x = itemPos.x(); 3529 const qreal y = itemPos.y(); 3530 3531 // calculate the new rect and forward the changes to the frontend 3532 QRectF newRect; 3533 const qreal w = rect.width(); 3534 const qreal h = rect.height(); 3535 newRect.setX(x - w / 2); 3536 newRect.setY(y - h / 2); 3537 newRect.setWidth(w); 3538 newRect.setHeight(h); 3539 Q_EMIT q->rectChanged(newRect); 3540 } 3541 return QGraphicsItem::itemChange(change, value); 3542 } 3543 3544 // ############################################################################## 3545 // ################################## Events ################################## 3546 // ############################################################################## 3547 3548 void CartesianPlotPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { 3549 const auto* cSystem{defaultCoordinateSystem()}; 3550 scenePos = event->pos(); 3551 if (!cSystem->isValid()) 3552 return; 3553 logicalPos = cSystem->mapSceneToLogical(scenePos, AbstractCoordinateSystem::MappingFlag::Limit); 3554 calledFromContextMenu = true; 3555 auto* menu = q->createContextMenu(); 3556 Q_EMIT q->contextMenuRequested(q->AbstractAspect::type(), menu); 3557 } 3558 3559 /*! 3560 * \brief CartesianPlotPrivate::mousePressEvent 3561 * In this function only basic stuff is done. The mousePressEvent is forwarded to the Worksheet, which 3562 * has access to all cartesian plots and can apply the changes to all plots if the option "applyToAll" 3563 * is set. The worksheet calls then the corresponding mousepressZoomMode/CursorMode function in this class 3564 * This is done for mousePress, mouseMove and mouseRelease event 3565 * This function sends a signal with the logical position, because this is the only value which is the same 3566 * in all plots. Using the scene coordinates is not possible 3567 * \param event 3568 */ 3569 void CartesianPlotPrivate::mousePressEvent(QGraphicsSceneMouseEvent* event) { 3570 const auto* cSystem{defaultCoordinateSystem()}; 3571 auto* w = static_cast<Worksheet*>(q->parent(AspectType::Worksheet))->currentSelection(); 3572 int index = CartesianPlot::cSystemIndex(w); 3573 if (index >= 0) 3574 cSystem = static_cast<CartesianCoordinateSystem*>(q->m_coordinateSystems.at(index)); 3575 if (mouseMode == CartesianPlot::MouseMode::Selection) { 3576 if (interactive && dataRect.contains(event->pos())) { 3577 panningStarted = true; 3578 m_panningStart = event->pos(); 3579 setCursor(Qt::ClosedHandCursor); 3580 } 3581 } else if (mouseMode == CartesianPlot::MouseMode::ZoomSelection || mouseMode == CartesianPlot::MouseMode::ZoomXSelection 3582 || mouseMode == CartesianPlot::MouseMode::ZoomYSelection) { 3583 if (!cSystem->isValid()) 3584 return; 3585 const QPointF logicalPos = cSystem->mapSceneToLogical(event->pos(), AbstractCoordinateSystem::MappingFlag::Limit); 3586 Q_EMIT q->mousePressZoomSelectionModeSignal(logicalPos); 3587 return; 3588 } else if (mouseMode == CartesianPlot::MouseMode::Cursor) { 3589 if (!cSystem->isValid()) 3590 return; 3591 const QPointF logicalPos = cSystem->mapSceneToLogical(event->pos(), AbstractCoordinateSystem::MappingFlag::Limit); 3592 setCursor(Qt::SizeHorCursor); 3593 double cursorPenWidth2 = cursorLine->pen().width() / 2.; 3594 if (cursorPenWidth2 < 10.) 3595 cursorPenWidth2 = 10.; 3596 3597 bool visible; 3598 if (cursor0Enable 3599 && std::abs(event->pos().x() - cSystem->mapLogicalToScene(QPointF(cursor0Pos.x(), range(Dimension::Y).start()), visible).x()) < cursorPenWidth2) { 3600 selectedCursor = 0; 3601 } else if (cursor1Enable 3602 && std::abs(event->pos().x() - cSystem->mapLogicalToScene(QPointF(cursor1Pos.x(), range(Dimension::Y).start()), visible).x()) 3603 < cursorPenWidth2) { 3604 selectedCursor = 1; 3605 } else if (QApplication::keyboardModifiers() & Qt::ControlModifier) { 3606 cursor1Enable = true; 3607 selectedCursor = 1; 3608 Q_EMIT q->cursor1EnableChanged(cursor1Enable); 3609 } else { 3610 cursor0Enable = true; 3611 selectedCursor = 0; 3612 Q_EMIT q->cursor0EnableChanged(cursor0Enable); 3613 } 3614 Q_EMIT q->mousePressCursorModeSignal(selectedCursor, logicalPos); 3615 } 3616 3617 QGraphicsItem::mousePressEvent(event); 3618 } 3619 3620 void CartesianPlotPrivate::mousePressZoomSelectionMode(QPointF logicalPos, int cSystemIndex) { 3621 // DEBUG(Q_FUNC_INFO << ", csystem index = " << cSystemIndex) 3622 const CartesianCoordinateSystem* cSystem; 3623 if (cSystemIndex == -1 || cSystemIndex >= q->m_coordinateSystems.count()) 3624 cSystem = defaultCoordinateSystem(); 3625 else 3626 cSystem = static_cast<CartesianCoordinateSystem*>(q->m_coordinateSystems.at(cSystemIndex)); 3627 3628 int xIndex = cSystem->index(Dimension::X); 3629 int yIndex = cSystem->index(Dimension::Y); 3630 3631 bool visible; 3632 const QPointF scenePos = cSystem->mapLogicalToScene(logicalPos, visible, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping); 3633 if (mouseMode == CartesianPlot::MouseMode::ZoomSelection) { 3634 if (logicalPos.x() < range(Dimension::X, xIndex).start()) 3635 logicalPos.setX(range(Dimension::X, xIndex).start()); 3636 3637 if (logicalPos.x() > range(Dimension::X, xIndex).end()) 3638 logicalPos.setX(range(Dimension::X, xIndex).end()); 3639 3640 if (logicalPos.y() < range(Dimension::Y, yIndex).start()) 3641 logicalPos.setY(range(Dimension::Y, yIndex).start()); 3642 3643 if (logicalPos.y() > range(Dimension::Y, yIndex).end()) 3644 logicalPos.setY(range(Dimension::Y, yIndex).end()); 3645 3646 m_selectionStart = scenePos; 3647 } else if (mouseMode == CartesianPlot::MouseMode::ZoomXSelection) { 3648 logicalPos.setY(range(Dimension::Y, yIndex).start()); // must be done, because the other plots can have other ranges, value must be in the scenes 3649 m_selectionStart.setX(scenePos.x()); 3650 m_selectionStart.setY(dataRect.y()); 3651 } else if (mouseMode == CartesianPlot::MouseMode::ZoomYSelection) { 3652 logicalPos.setX(range(Dimension::X, xIndex).start()); // must be done, because the other plots can have other ranges, value must be in the scenes 3653 m_selectionStart.setX(dataRect.x()); 3654 m_selectionStart.setY(scenePos.y()); 3655 } 3656 m_selectionEnd = m_selectionStart; 3657 m_selectionBandIsShown = true; 3658 } 3659 3660 void CartesianPlotPrivate::mousePressCursorMode(int cursorNumber, QPointF logicalPos) { 3661 cursorNumber == 0 ? cursor0Enable = true : cursor1Enable = true; 3662 3663 QPointF p1(logicalPos.x(), range(Dimension::Y).start()); 3664 QPointF p2(logicalPos.x(), range(Dimension::Y).end()); 3665 3666 if (cursorNumber == 0) 3667 cursor0Pos = QPointF(logicalPos.x(), 0); 3668 else 3669 cursor1Pos = QPointF(logicalPos.x(), 0); 3670 3671 update(); 3672 } 3673 3674 void CartesianPlotPrivate::updateCursor() { 3675 update(); 3676 } 3677 3678 void CartesianPlotPrivate::setZoomSelectionBandShow(bool show) { 3679 m_selectionBandIsShown = show; 3680 } 3681 3682 void CartesianPlotPrivate::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { 3683 const auto* cSystem{defaultCoordinateSystem()}; 3684 auto* w = static_cast<Worksheet*>(q->parent(AspectType::Worksheet))->currentSelection(); 3685 int index = CartesianPlot::cSystemIndex(w); 3686 if (index >= 0) 3687 cSystem = static_cast<CartesianCoordinateSystem*>(q->m_coordinateSystems.at(index)); 3688 3689 if (mouseMode == CartesianPlot::MouseMode::Selection) { 3690 if (panningStarted && dataRect.contains(event->pos())) { 3691 // don't retransform on small mouse movement deltas 3692 const int deltaXScene = (m_panningStart.x() - event->pos().x()); 3693 const int deltaYScene = (m_panningStart.y() - event->pos().y()); 3694 if (std::abs(deltaXScene) < 5 && std::abs(deltaYScene) < 5) 3695 return; 3696 3697 if (!cSystem->isValid()) 3698 return; 3699 const QPointF logicalEnd = cSystem->mapSceneToLogical(event->pos()); 3700 const QPointF logicalStart = cSystem->mapSceneToLogical(m_panningStart); 3701 m_panningStart = event->pos(); 3702 Q_EMIT q->mouseMoveSelectionModeSignal(logicalStart, logicalEnd); 3703 } else 3704 QGraphicsItem::mouseMoveEvent(event); 3705 } else if (mouseMode == CartesianPlot::MouseMode::ZoomSelection || mouseMode == CartesianPlot::MouseMode::ZoomXSelection 3706 || mouseMode == CartesianPlot::MouseMode::ZoomYSelection) { 3707 QGraphicsItem::mouseMoveEvent(event); 3708 if (!boundingRect().contains(event->pos())) { 3709 q->info(QString()); 3710 return; 3711 } 3712 if (!cSystem->isValid()) 3713 return; 3714 const QPointF logicalPos = cSystem->mapSceneToLogical(event->pos(), AbstractCoordinateSystem::MappingFlag::Limit); 3715 Q_EMIT q->mouseMoveZoomSelectionModeSignal(logicalPos); 3716 3717 } else if (mouseMode == CartesianPlot::MouseMode::Cursor) { 3718 QGraphicsItem::mouseMoveEvent(event); 3719 if (!boundingRect().contains(event->pos())) { 3720 q->info(i18n("Not inside of the bounding rect")); 3721 return; 3722 } 3723 3724 // updating treeview data and cursor position 3725 // updating cursor position is done in Worksheet, because 3726 // multiple plots must be updated 3727 if (!cSystem->isValid()) 3728 return; 3729 const QPointF logicalPos = cSystem->mapSceneToLogical(event->pos(), AbstractCoordinateSystem::MappingFlag::Limit); 3730 Q_EMIT q->mouseMoveCursorModeSignal(selectedCursor, logicalPos); 3731 } 3732 } 3733 3734 bool CartesianPlotPrivate::translateRange(int xIndex, int yIndex, const QPointF& logicalStart, const QPointF& logicalEnd, bool translateX, bool translateY) { 3735 // handle the change in x 3736 bool translationX = false, translationY = false; 3737 if (translateX && logicalStart.x() - logicalEnd.x() != 0) { // TODO: find better method 3738 translationX = true; 3739 double start{logicalStart.x()}, end{logicalEnd.x()}; 3740 switch (range(Dimension::X, xIndex).scale()) { 3741 case RangeT::Scale::Linear: { 3742 const double delta = (start - end); 3743 range(Dimension::X, xIndex).translate(delta); 3744 break; 3745 } 3746 case RangeT::Scale::Log10: { 3747 if (end == 0 || start / end <= 0) 3748 break; 3749 const double delta = log10(start / end); 3750 range(Dimension::X, xIndex) *= pow(10, delta); 3751 break; 3752 } 3753 case RangeT::Scale::Log2: { 3754 if (end == 0 || start / end <= 0) 3755 break; 3756 const double delta = log2(start / end); 3757 range(Dimension::X, xIndex) *= exp2(delta); 3758 break; 3759 } 3760 case RangeT::Scale::Ln: { 3761 if (end == 0 || start / end <= 0) 3762 break; 3763 const double delta = log(start / end); 3764 range(Dimension::X, xIndex) *= exp(delta); 3765 break; 3766 } 3767 case RangeT::Scale::Sqrt: { 3768 if (start < 0 || end < 0) 3769 break; 3770 const double delta = sqrt(start) - sqrt(end); 3771 range(Dimension::X, xIndex).translate(delta * delta); 3772 break; 3773 } 3774 case RangeT::Scale::Square: { 3775 if (end <= start) 3776 break; 3777 const double delta = end * end - start * start; 3778 range(Dimension::X, xIndex).translate(sqrt(delta)); 3779 break; 3780 } 3781 case RangeT::Scale::Inverse: { 3782 if (start == 0. || end == 0. || end <= start) 3783 break; 3784 const double delta = 1. / start - 1. / end; 3785 range(Dimension::X, xIndex).translate(1. / delta); 3786 break; 3787 } 3788 } 3789 } 3790 3791 if (translateY && logicalStart.y() - logicalEnd.y() != 0) { 3792 translationY = true; 3793 // handle the change in y 3794 double start = logicalStart.y(); 3795 double end = logicalEnd.y(); 3796 switch (range(Dimension::Y, yIndex).scale()) { 3797 case RangeT::Scale::Linear: { 3798 const double deltaY = (start - end); 3799 range(Dimension::Y, yIndex).translate(deltaY); 3800 break; 3801 } 3802 case RangeT::Scale::Log10: { 3803 if (end == 0 || start / end <= 0) 3804 break; 3805 const double deltaY = log10(start / end); 3806 range(Dimension::Y, yIndex) *= pow(10, deltaY); 3807 break; 3808 } 3809 case RangeT::Scale::Log2: { 3810 if (end == 0 || start / end <= 0) 3811 break; 3812 const double deltaY = log2(start / end); 3813 range(Dimension::Y, yIndex) *= exp2(deltaY); 3814 break; 3815 } 3816 case RangeT::Scale::Ln: { 3817 if (end == 0 || start / end <= 0) 3818 break; 3819 const double deltaY = log(start / end); 3820 range(Dimension::Y, yIndex) *= exp(deltaY); 3821 break; 3822 } 3823 case RangeT::Scale::Sqrt: { 3824 if (start < 0 || end < 0) 3825 break; 3826 const double delta = sqrt(start) - sqrt(end); 3827 range(Dimension::Y, yIndex).translate(delta * delta); 3828 break; 3829 } 3830 case RangeT::Scale::Square: { 3831 if (end <= start) 3832 break; 3833 const double delta = end * end - start * start; 3834 range(Dimension::Y, yIndex).translate(sqrt(delta)); 3835 break; 3836 } 3837 case RangeT::Scale::Inverse: { 3838 if (start == 0. || end == 0. || end <= start) 3839 break; 3840 const double delta = 1. / start - 1. / end; 3841 range(Dimension::Y, yIndex).translate(1. / delta); 3842 break; 3843 } 3844 } 3845 } 3846 3847 q->setUndoAware(false); 3848 if (translationX) { 3849 q->enableAutoScale(Dimension::X, xIndex, false); 3850 retransformScale(Dimension::X, xIndex); 3851 } 3852 if (translationY) { 3853 q->enableAutoScale(Dimension::Y, yIndex, false); 3854 retransformScale(Dimension::Y, yIndex); 3855 } 3856 q->setUndoAware(true); 3857 3858 // If x or y should not be translated, means, that it was done before 3859 // so the ranges must get dirty. 3860 if (translationX || translationY || !translateX || !translateY) { 3861 q->setRangeDirty(Dimension::X, xIndex, true); 3862 q->setRangeDirty(Dimension::Y, yIndex, true); 3863 } 3864 3865 return translationX || translationY || !translateX || !translateY; 3866 } 3867 3868 void CartesianPlotPrivate::mouseMoveSelectionMode(QPointF logicalStart, QPointF logicalEnd) { 3869 const bool autoscaleRanges = true; // consumes a lot of power, maybe making an option to turn off/on! 3870 auto* w = static_cast<Worksheet*>(q->parent(AspectType::Worksheet))->currentSelection(); 3871 int index = CartesianPlot::cSystemIndex(w); 3872 if (!w || w->parent(AspectType::CartesianPlot) != q) 3873 index = -1; 3874 3875 bool translated = false; 3876 if (index < 0) { 3877 QVector<int> translatedIndicesX, translatedIndicesY; 3878 for (int i = 0; i < q->m_coordinateSystems.count(); i++) { 3879 auto cs = coordinateSystem(i); 3880 int xIndex = cs->index(Dimension::X); 3881 int yIndex = cs->index(Dimension::Y); 3882 bool translateX = !translatedIndicesX.contains(xIndex); 3883 bool translateY = !translatedIndicesY.contains(yIndex); 3884 if (translateRange(xIndex, yIndex, logicalStart, logicalEnd, translateX, translateY)) { 3885 translated = true; 3886 if (autoscaleRanges && logicalStart.y() == logicalEnd.y() && autoScale(Dimension::Y, cs->index(Dimension::Y))) { 3887 // only x was changed, so autoscale y 3888 q->scaleAuto(Dimension::Y, cs->index(Dimension::Y), false); 3889 } 3890 if (autoscaleRanges && logicalStart.x() == logicalEnd.x() && autoScale(Dimension::X, cs->index(Dimension::X))) { 3891 // only y was changed, so autoscale x 3892 q->scaleAuto(Dimension::X, cs->index(Dimension::X), false); 3893 } 3894 } 3895 if (translateX) 3896 translatedIndicesX.append(static_cast<CartesianCoordinateSystem*>(q->m_coordinateSystems[i])->index(Dimension::X)); 3897 if (translateY) 3898 translatedIndicesY.append(static_cast<CartesianCoordinateSystem*>(q->m_coordinateSystems[i])->index(Dimension::Y)); 3899 } 3900 } else { 3901 auto cs = coordinateSystem(index); 3902 int xIndex = cs->index(Dimension::X); 3903 int yIndex = cs->index(Dimension::Y); 3904 translated = translateRange(xIndex, yIndex, logicalStart, logicalEnd, true, true); 3905 if (autoscaleRanges && logicalStart.y() == logicalEnd.y() && autoScale(Dimension::Y, yIndex)) { 3906 // only x was changed, so autoscale y 3907 q->scaleAuto(Dimension::Y, yIndex, false); 3908 } 3909 if (autoscaleRanges && logicalStart.x() == logicalEnd.x() && autoScale(Dimension::X, xIndex)) { 3910 // only y was changed, so autoscale x 3911 q->scaleAuto(Dimension::X, xIndex, false); 3912 } 3913 } 3914 3915 if (translated) 3916 q->WorksheetElementContainer::retransform(); 3917 } 3918 3919 void CartesianPlotPrivate::mouseMoveZoomSelectionMode(QPointF logicalPos, int cSystemIndex) { 3920 QString info; 3921 const CartesianCoordinateSystem* cSystem; 3922 if (cSystemIndex == -1 || cSystemIndex >= q->m_coordinateSystems.count()) 3923 cSystem = defaultCoordinateSystem(); 3924 else 3925 cSystem = q->coordinateSystem(cSystemIndex); 3926 3927 int xIndex = cSystem->index(Dimension::X); 3928 int yIndex = cSystem->index(Dimension::Y); 3929 3930 const auto xRangeFormat{range(Dimension::X, xIndex).format()}; 3931 const auto yRangeFormat{range(Dimension::Y, yIndex).format()}; 3932 const auto xRangeDateTimeFormat{range(Dimension::X, xIndex).dateTimeFormat()}; 3933 if (!cSystem->isValid()) 3934 return; 3935 const QPointF logicalStart = cSystem->mapSceneToLogical(m_selectionStart, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping); 3936 3937 if (mouseMode == CartesianPlot::MouseMode::ZoomSelection) { 3938 bool visible; 3939 m_selectionEnd = cSystem->mapLogicalToScene(logicalPos, visible, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping); 3940 QPointF logicalEnd = logicalPos; 3941 if (xRangeFormat == RangeT::Format::Numeric) 3942 info = QString::fromUtf8("Δx=") + QString::number(logicalEnd.x() - logicalStart.x()); 3943 else 3944 info = i18n("from x=%1 to x=%2", 3945 QDateTime::fromMSecsSinceEpoch(logicalStart.x(), Qt::UTC).toString(xRangeDateTimeFormat), 3946 QDateTime::fromMSecsSinceEpoch(logicalEnd.x(), Qt::UTC).toString(xRangeDateTimeFormat)); 3947 3948 info += QLatin1String(", "); 3949 if (yRangeFormat == RangeT::Format::Numeric) 3950 info += QString::fromUtf8("Δy=") + QString::number(logicalEnd.y() - logicalStart.y()); 3951 else 3952 info += i18n("from y=%1 to y=%2", 3953 QDateTime::fromMSecsSinceEpoch(logicalStart.y(), Qt::UTC).toString(xRangeDateTimeFormat), 3954 QDateTime::fromMSecsSinceEpoch(logicalEnd.y(), Qt::UTC).toString(xRangeDateTimeFormat)); 3955 } else if (mouseMode == CartesianPlot::MouseMode::ZoomXSelection) { 3956 logicalPos.setY(range(Dimension::Y, yIndex).start()); // must be done, because the other plots can have other ranges, value must be in the scenes 3957 bool visible; 3958 m_selectionEnd.setX( 3959 cSystem->mapLogicalToScene(logicalPos, visible, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping).x()); // event->pos().x()); 3960 m_selectionEnd.setY(dataRect.bottom()); 3961 QPointF logicalEnd = logicalPos; 3962 if (xRangeFormat == RangeT::Format::Numeric) 3963 info = QString::fromUtf8("Δx=") + QString::number(logicalEnd.x() - logicalStart.x()); 3964 else 3965 info = i18n("from x=%1 to x=%2", 3966 QDateTime::fromMSecsSinceEpoch(logicalStart.x(), Qt::UTC).toString(xRangeDateTimeFormat), 3967 QDateTime::fromMSecsSinceEpoch(logicalEnd.x(), Qt::UTC).toString(xRangeDateTimeFormat)); 3968 } else if (mouseMode == CartesianPlot::MouseMode::ZoomYSelection) { 3969 m_selectionEnd.setX(dataRect.right()); 3970 logicalPos.setX(range(Dimension::X, xIndex).start()); // must be done, because the other plots can have other ranges, value must be in the scenes 3971 bool visible; 3972 m_selectionEnd.setY( 3973 cSystem->mapLogicalToScene(logicalPos, visible, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping).y()); // event->pos().y()); 3974 QPointF logicalEnd = logicalPos; 3975 if (yRangeFormat == RangeT::Format::Numeric) 3976 info = QString::fromUtf8("Δy=") + QString::number(logicalEnd.y() - logicalStart.y()); 3977 else 3978 info = i18n("from y=%1 to y=%2", 3979 QDateTime::fromMSecsSinceEpoch(logicalStart.y(), Qt::UTC).toString(xRangeDateTimeFormat), 3980 QDateTime::fromMSecsSinceEpoch(logicalEnd.y(), Qt::UTC).toString(xRangeDateTimeFormat)); 3981 } 3982 q->info(info); 3983 update(); 3984 } 3985 3986 void CartesianPlotPrivate::mouseMoveCursorMode(int cursorNumber, QPointF logicalPos) { 3987 const auto xRangeFormat{range(Dimension::X).format()}; 3988 const auto xRangeDateTimeFormat{range(Dimension::X).dateTimeFormat()}; 3989 3990 QPointF p1(logicalPos.x(), 0); 3991 cursorNumber == 0 ? cursor0Pos = p1 : cursor1Pos = p1; 3992 3993 QString info; 3994 if (xRangeFormat == RangeT::Format::Numeric) 3995 info = QString::fromUtf8("x=") + QString::number(logicalPos.x()); 3996 else 3997 info = i18n("x=%1", QDateTime::fromMSecsSinceEpoch(logicalPos.x(), Qt::UTC).toString(xRangeDateTimeFormat)); 3998 q->info(info); 3999 update(); 4000 } 4001 4002 void CartesianPlotPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { 4003 if (mouseMode == CartesianPlot::MouseMode::Selection) { 4004 setCursor(Qt::ArrowCursor); 4005 panningStarted = false; 4006 4007 // TODO: why do we do this all the time?!?! 4008 const QPointF& itemPos = pos(); // item's center point in parent's coordinates; 4009 const qreal x = itemPos.x(); 4010 const qreal y = itemPos.y(); 4011 4012 // calculate the new rect and set it 4013 QRectF newRect; 4014 const qreal w = rect.width(); 4015 const qreal h = rect.height(); 4016 newRect.setX(x - w / 2); 4017 newRect.setY(y - h / 2); 4018 newRect.setWidth(w); 4019 newRect.setHeight(h); 4020 4021 // TODO: autoscale 4022 4023 suppressRetransform = true; 4024 q->setRect(newRect); 4025 suppressRetransform = false; 4026 4027 QGraphicsItem::mouseReleaseEvent(event); 4028 } else if (mouseMode == CartesianPlot::MouseMode::ZoomSelection || mouseMode == CartesianPlot::MouseMode::ZoomXSelection 4029 || mouseMode == CartesianPlot::MouseMode::ZoomYSelection) { 4030 Q_EMIT q->mouseReleaseZoomSelectionModeSignal(); 4031 } 4032 } 4033 4034 void CartesianPlotPrivate::mouseReleaseZoomSelectionMode(int cSystemIndex, bool suppressRetransform) { 4035 m_selectionBandIsShown = false; 4036 // don't zoom if very small region was selected, avoid occasional/unwanted zooming 4037 if (std::abs(m_selectionEnd.x() - m_selectionStart.x()) < 20 && std::abs(m_selectionEnd.y() - m_selectionStart.y()) < 20) 4038 return; 4039 4040 int xIndex = -1, yIndex = -1; 4041 if (cSystemIndex == -1 || cSystemIndex >= q->m_coordinateSystems.count()) { 4042 for (int i = 0; i < q->m_coordinateSystems.count(); i++) 4043 mouseReleaseZoomSelectionMode(i, true); 4044 } else { 4045 auto cSystem = coordinateSystem(cSystemIndex); 4046 xIndex = cSystem->index(Dimension::X); 4047 yIndex = cSystem->index(Dimension::Y); 4048 4049 // determine the new plot ranges 4050 if (!cSystem->isValid()) 4051 return; 4052 QPointF logicalZoomStart = cSystem->mapSceneToLogical(m_selectionStart, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping); 4053 QPointF logicalZoomEnd = cSystem->mapSceneToLogical(m_selectionEnd, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping); 4054 4055 if (mouseMode == CartesianPlot::MouseMode::ZoomSelection || mouseMode == CartesianPlot::MouseMode::ZoomXSelection) { 4056 if (m_selectionEnd.x() > m_selectionStart.x()) 4057 range(Dimension::X, xIndex).setRange(logicalZoomStart.x(), logicalZoomEnd.x()); 4058 else 4059 range(Dimension::X, xIndex).setRange(logicalZoomEnd.x(), logicalZoomStart.x()); 4060 4061 if (niceExtend) 4062 range(Dimension::X, xIndex).niceExtend(); 4063 } 4064 4065 if (mouseMode == CartesianPlot::MouseMode::ZoomSelection || mouseMode == CartesianPlot::MouseMode::ZoomYSelection) { 4066 if (m_selectionEnd.y() > m_selectionStart.y()) 4067 range(Dimension::Y, yIndex).setRange(logicalZoomEnd.y(), logicalZoomStart.y()); 4068 else 4069 range(Dimension::Y, yIndex).setRange(logicalZoomStart.y(), logicalZoomEnd.y()); 4070 4071 if (niceExtend) 4072 range(Dimension::Y, yIndex).niceExtend(); 4073 } 4074 4075 if (mouseMode == CartesianPlot::MouseMode::ZoomSelection) { 4076 q->setRangeDirty(Dimension::X, xIndex, true); 4077 q->setRangeDirty(Dimension::Y, yIndex, true); 4078 q->enableAutoScale(Dimension::X, xIndex, false); 4079 q->enableAutoScale(Dimension::Y, yIndex, false); 4080 } else if (mouseMode == CartesianPlot::MouseMode::ZoomXSelection) { 4081 q->setRangeDirty(Dimension::X, xIndex, true); 4082 q->setRangeDirty(Dimension::Y, yIndex, true); 4083 q->enableAutoScale(Dimension::X, xIndex, false); 4084 if (q->autoScale(Dimension::Y, yIndex)) 4085 q->scaleAuto(Dimension::Y, yIndex, false, true); 4086 } else if (mouseMode == CartesianPlot::MouseMode::ZoomYSelection) { 4087 q->setRangeDirty(Dimension::X, xIndex, true); 4088 q->setRangeDirty(Dimension::Y, yIndex, true); 4089 q->enableAutoScale(Dimension::Y, yIndex, false); 4090 if (q->autoScale(Dimension::X, xIndex)) 4091 q->scaleAuto(Dimension::X, xIndex, false, true); 4092 } 4093 } 4094 4095 if (!suppressRetransform) { 4096 retransformScales(xIndex, yIndex); 4097 q->WorksheetElementContainer::retransform(); 4098 } 4099 } 4100 4101 void CartesianPlotPrivate::wheelEvent(QGraphicsSceneWheelEvent* event) { 4102 if (!interactive) 4103 return; 4104 4105 auto* w = static_cast<Worksheet*>(q->parent(AspectType::Worksheet))->currentSelection(); 4106 int cSystemIndex = CartesianPlot::cSystemIndex(w); 4107 int xIndex = -1, yIndex = -1; 4108 if (w && w->parent(AspectType::CartesianPlot) == q) { 4109 xIndex = coordinateSystem(cSystemIndex)->index(Dimension::X); 4110 yIndex = coordinateSystem(cSystemIndex)->index(Dimension::Y); 4111 } 4112 4113 const auto pos = event->pos(); 4114 const auto posLogical = coordinateSystem(0)->mapLogicalToScene({pos}); 4115 const double xSceneRelPos = (pos.x() - dataRect.left()) / dataRect.width(); 4116 // if (xSceneRelPos < 0) 4117 // xSceneRelPos = 0; 4118 // if (xSceneRelPos > 1) 4119 // xSceneRelPos = 1; 4120 const double ySceneRelPos = (dataRect.bottom() - pos.y()) / dataRect.height(); 4121 // if (ySceneRelPos < 0) 4122 // ySceneRelPos = 0; 4123 // if (ySceneRelPos > 1) 4124 // ySceneRelPos = 1; 4125 const QPointF sceneRelPos(xSceneRelPos, ySceneRelPos); 4126 4127 bool considerDimension = false; 4128 Dimension dim = Dimension::X; 4129 if (w && w->type() == AspectType::Axis) { 4130 const auto* axis = static_cast<Axis*>(w); 4131 considerDimension = true; 4132 if (axis->orientation() == Axis::Orientation::Vertical) 4133 dim = Dimension::Y; 4134 } 4135 4136 Q_EMIT q->wheelEventSignal(sceneRelPos, event->delta(), xIndex, yIndex, considerDimension, dim); 4137 } 4138 4139 void CartesianPlotPrivate::wheelEvent(const QPointF& sceneRelPos, int delta, int xIndex, int yIndex, bool considerDimension, Dimension dim) { 4140 if (considerDimension) { 4141 // Only one dimension 4142 switch (dim) { 4143 case Dimension::X: 4144 q->zoomInOut(xIndex, dim, delta > 0, sceneRelPos.x()); 4145 break; 4146 case Dimension::Y: 4147 q->zoomInOut(yIndex, dim, delta > 0, sceneRelPos.y()); 4148 break; 4149 } 4150 return; 4151 } 4152 4153 if (delta > 0) 4154 q->zoomIn(xIndex, yIndex, sceneRelPos); 4155 else 4156 q->zoomOut(xIndex, yIndex, sceneRelPos); 4157 } 4158 4159 void CartesianPlotPrivate::keyPressEvent(QKeyEvent* event) { 4160 const auto key = event->key(); 4161 // const bool ctrl = event->modifiers() & Qt::KeyboardModifier::ControlModifier; 4162 // const bool shift = event->modifiers() & Qt::KeyboardModifier::ShiftModifier; 4163 // const bool alt = event->modifiers() & Qt::KeyboardModifier::AltModifier; 4164 Action a = evaluateKeys(key, event->modifiers()); 4165 4166 if (a == Action::Abort) { 4167 m_selectionBandIsShown = false; 4168 } else if (a & Action::Move) { 4169 const auto* worksheet = static_cast<const Worksheet*>(q->parentAspect()); 4170 if (worksheet->layout() == Worksheet::Layout::NoLayout) { 4171 // no layout is active -> use arrow keys to move the plot on the worksheet 4172 const int delta = 5; 4173 QRectF rect = q->rect(); 4174 4175 if (a == Action::MoveLeft) { 4176 rect.setX(rect.x() - delta); 4177 rect.setWidth(rect.width() - delta); 4178 } else if (a == Action::MoveRight) { 4179 rect.setX(rect.x() + delta); 4180 rect.setWidth(rect.width() + delta); 4181 } else if (a == Action::MoveUp) { 4182 rect.setY(rect.y() - delta); 4183 rect.setHeight(rect.height() - delta); 4184 } else if (a == Action::MoveDown) { 4185 rect.setY(rect.y() + delta); 4186 rect.setHeight(rect.height() + delta); 4187 } 4188 4189 q->setRect(rect); 4190 } 4191 } else if (a == Action::NavigateNextCurve) // (key == Qt::Key_Tab) 4192 navigateNextPrevCurve(); 4193 else if (a == Action::NavigatePrevCurve) // (key == Qt::SHIFT + Qt::Key_Tab) 4194 navigateNextPrevCurve(false /*next*/); 4195 else if (a & Action::New) { 4196 const auto* cSystem{defaultCoordinateSystem()}; 4197 if (cSystem->isValid()) { 4198 logicalPos = cSystem->mapSceneToLogical(scenePos, AbstractCoordinateSystem::MappingFlag::Limit); 4199 calledFromContextMenu = true; 4200 } 4201 if (a == Action::NewTextLabel) 4202 q->addTextLabel(); 4203 else if (a == Action::NewReferenceLine) 4204 q->addReferenceLine(); 4205 else if (a == Action::NewReferenceRange) 4206 q->addReferenceRange(); 4207 else if (a == Action::NewCustomPoint) 4208 q->addCustomPoint(); 4209 else if (a == Action::NewImage) 4210 q->addImage(); 4211 } 4212 QGraphicsItem::keyPressEvent(event); 4213 } 4214 4215 void CartesianPlotPrivate::navigateNextPrevCurve(bool next) const { 4216 const auto& curves = q->children<XYCurve>(); 4217 if (curves.isEmpty()) 4218 return; 4219 4220 // determine the current selected curve 4221 const XYCurve* selectedCurve = nullptr; 4222 int index = 0; 4223 for (const auto* curve : curves) { 4224 if (curve->graphicsItem()->isSelected()) { 4225 selectedCurve = curve; 4226 break; 4227 } 4228 ++index; 4229 } 4230 4231 int newIndex = 0; 4232 if (selectedCurve) { 4233 if (next) { // havigate to the next curve 4234 if (index < curves.size() - 1) 4235 newIndex = index + 1; 4236 } else { // navigate to the previous curve 4237 if (index > 0) 4238 newIndex = index - 1; 4239 else 4240 newIndex = curves.size() - 1; 4241 } 4242 } 4243 4244 auto* w = static_cast<Worksheet*>(q->parent(AspectType::Worksheet)); 4245 4246 // deselect the current curve 4247 if (selectedCurve) 4248 w->setItemSelectedInView(selectedCurve->graphicsItem(), false); 4249 else { 4250 // no curve is selected, either the plot itself or some 4251 // other children like axis, etc. are selected. 4252 // deselect all of them 4253 w->setItemSelectedInView(this, false); // deselect the plot 4254 4255 // deselect children 4256 const auto& elements = q->children<WorksheetElement>(AbstractAspect::ChildIndexFlag::IncludeHidden); 4257 for (auto* element : elements) 4258 w->setItemSelectedInView(element->graphicsItem(), false); 4259 } 4260 4261 // select the new curve 4262 w->setItemSelectedInView(curves.at(newIndex)->graphicsItem(), true); 4263 } 4264 4265 void CartesianPlotPrivate::hoverMoveEvent(QGraphicsSceneHoverEvent* event) { 4266 QPointF point = event->pos(); 4267 scenePos = point; 4268 QString info; 4269 const auto* cSystem{defaultCoordinateSystem()}; 4270 auto* w = static_cast<Worksheet*>(q->parent(AspectType::Worksheet))->currentSelection(); 4271 int index = CartesianPlot::cSystemIndex(w); 4272 int xIndex = cSystem->index(Dimension::X), yIndex = cSystem->index(Dimension::Y); 4273 if (!w || w->parent(AspectType::CartesianPlot) != q) { 4274 xIndex = -1; 4275 yIndex = -1; 4276 } else if (index >= 0) { 4277 cSystem = static_cast<CartesianCoordinateSystem*>(q->m_coordinateSystems.at(index)); 4278 xIndex = cSystem->index(Dimension::X); 4279 yIndex = cSystem->index(Dimension::Y); 4280 } 4281 4282 const auto xRangeFormat{range(Dimension::X, xIndex).format()}; 4283 const auto yRangeFormat{range(Dimension::Y, yIndex).format()}; 4284 const auto xRangeDateTimeFormat{range(Dimension::X, xIndex).dateTimeFormat()}; 4285 const auto yRangeDateTimeFormat{range(Dimension::Y, yIndex).dateTimeFormat()}; 4286 if (dataRect.contains(point)) { 4287 if (!cSystem->isValid()) 4288 return; 4289 QPointF logicalPoint = cSystem->mapSceneToLogical(point); 4290 4291 if ((mouseMode == CartesianPlot::MouseMode::ZoomSelection) || mouseMode == CartesianPlot::MouseMode::Selection 4292 || mouseMode == CartesianPlot::MouseMode::Crosshair) { 4293 info = QStringLiteral("x="); 4294 if (xRangeFormat == RangeT::Format::Numeric) 4295 info += QString::number(logicalPoint.x()); 4296 else 4297 info += QDateTime::fromMSecsSinceEpoch(logicalPoint.x(), Qt::UTC).toString(xRangeDateTimeFormat); 4298 4299 info += QStringLiteral(", y="); 4300 if (yRangeFormat == RangeT::Format::Numeric) 4301 info += QString::number(logicalPoint.y()); 4302 else 4303 info += QDateTime::fromMSecsSinceEpoch(logicalPoint.y(), Qt::UTC).toString(yRangeDateTimeFormat); 4304 } 4305 4306 if (mouseMode == CartesianPlot::MouseMode::ZoomSelection && !m_selectionBandIsShown) { 4307 Q_EMIT q->mouseHoverZoomSelectionModeSignal(logicalPoint); 4308 } else if (mouseMode == CartesianPlot::MouseMode::ZoomXSelection && !m_selectionBandIsShown) { 4309 info = QStringLiteral("x="); 4310 if (xRangeFormat == RangeT::Format::Numeric) 4311 info += QString::number(logicalPoint.x()); 4312 else 4313 info += QDateTime::fromMSecsSinceEpoch(logicalPoint.x(), Qt::UTC).toString(xRangeDateTimeFormat); 4314 Q_EMIT q->mouseHoverZoomSelectionModeSignal(logicalPoint); 4315 } else if (mouseMode == CartesianPlot::MouseMode::ZoomYSelection && !m_selectionBandIsShown) { 4316 info = QStringLiteral("y="); 4317 if (yRangeFormat == RangeT::Format::Numeric) 4318 info += QString::number(logicalPoint.y()); 4319 else 4320 info += QDateTime::fromMSecsSinceEpoch(logicalPoint.y(), Qt::UTC).toString(yRangeDateTimeFormat); 4321 Q_EMIT q->mouseHoverZoomSelectionModeSignal(logicalPoint); 4322 } else if (mouseMode == CartesianPlot::MouseMode::Selection) { 4323 // hover the nearest curve to the mousepointer 4324 // hovering curves is implemented in the parent, because no ignoreEvent() exists 4325 // for it. Checking all curves and hover the first 4326 bool hovered = false; 4327 const auto& curves = q->children<Plot>(); 4328 for (int i = curves.count() - 1; i >= 0; i--) { // because the last curve is above the other curves 4329 auto* curve = curves[i]; 4330 if (hovered) { // if a curve is already hovered, disable hover for the rest 4331 curve->setHover(false); 4332 continue; 4333 } 4334 if (curve->activatePlot(event->pos()) && !curve->isLocked()) { 4335 curve->setHover(true); 4336 hovered = true; 4337 continue; 4338 } 4339 curve->setHover(false); 4340 } 4341 } else if (mouseMode == CartesianPlot::MouseMode::Crosshair) { 4342 m_crosshairPos = event->pos(); 4343 update(); 4344 } else if (mouseMode == CartesianPlot::MouseMode::Cursor) { 4345 info = QStringLiteral("x="); 4346 if (yRangeFormat == RangeT::Format::Numeric) 4347 info += QString::number(logicalPoint.x()); 4348 else 4349 info += QDateTime::fromMSecsSinceEpoch(logicalPoint.x(), Qt::UTC).toString(xRangeDateTimeFormat); 4350 4351 double cursorPenWidth2 = cursorLine->pen().width() / 2.; 4352 if (cursorPenWidth2 < 10.) 4353 cursorPenWidth2 = 10.; 4354 4355 bool visible; 4356 if ((cursor0Enable 4357 && std::abs(point.x() - defaultCoordinateSystem()->mapLogicalToScene(QPointF(cursor0Pos.x(), range(Dimension::Y).start()), visible).x()) 4358 < cursorPenWidth2) 4359 || (cursor1Enable 4360 && std::abs(point.x() - defaultCoordinateSystem()->mapLogicalToScene(QPointF(cursor1Pos.x(), range(Dimension::Y).start()), visible).x()) 4361 < cursorPenWidth2)) 4362 setCursor(Qt::SizeHorCursor); 4363 else 4364 setCursor(Qt::ArrowCursor); 4365 4366 update(); 4367 } 4368 } else 4369 Q_EMIT q->mouseHoverOutsideDataRectSignal(); 4370 4371 q->info(info); 4372 4373 QGraphicsItem::hoverMoveEvent(event); 4374 } 4375 4376 void CartesianPlotPrivate::mouseHoverOutsideDataRect() { 4377 m_insideDataRect = false; 4378 update(); 4379 } 4380 4381 void CartesianPlotPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) { 4382 for (auto* curve : q->children<XYCurve>()) 4383 curve->setHover(false); 4384 4385 m_hovered = false; 4386 QGraphicsItem::hoverLeaveEvent(event); 4387 } 4388 4389 void CartesianPlotPrivate::mouseHoverZoomSelectionMode(QPointF logicPos, int cSystemIndex) { 4390 m_insideDataRect = true; 4391 4392 const CartesianCoordinateSystem* cSystem; 4393 auto* w = static_cast<Worksheet*>(q->parent(AspectType::Worksheet))->currentSelection(); 4394 int index = CartesianPlot::cSystemIndex(w); 4395 if (w && w->parent(AspectType::CartesianPlot) == q && index != -1) 4396 cSystem = coordinateSystem(index); 4397 else if (cSystemIndex == -1 || cSystemIndex >= q->m_coordinateSystems.count()) 4398 cSystem = defaultCoordinateSystem(); 4399 else 4400 cSystem = coordinateSystem(cSystemIndex); 4401 4402 bool visible; 4403 if (mouseMode == CartesianPlot::MouseMode::ZoomSelection && !m_selectionBandIsShown) { 4404 } else if (mouseMode == CartesianPlot::MouseMode::ZoomXSelection && !m_selectionBandIsShown) { 4405 QPointF p1(logicPos.x(), range(Dimension::Y, cSystem->index(Dimension::Y)).start()); 4406 QPointF p2(logicPos.x(), range(Dimension::Y, cSystem->index(Dimension::Y)).end()); 4407 m_selectionStartLine.setP1(cSystem->mapLogicalToScene(p1, visible, CartesianCoordinateSystem::MappingFlag::Limit)); 4408 m_selectionStartLine.setP2(cSystem->mapLogicalToScene(p2, visible, CartesianCoordinateSystem::MappingFlag::Limit)); 4409 } else if (mouseMode == CartesianPlot::MouseMode::ZoomYSelection && !m_selectionBandIsShown) { 4410 QPointF p1(range(Dimension::X, cSystem->index(Dimension::X)).start(), logicPos.y()); 4411 QPointF p2(range(Dimension::X, cSystem->index(Dimension::X)).end(), logicPos.y()); 4412 m_selectionStartLine.setP1(cSystem->mapLogicalToScene(p1, visible, CartesianCoordinateSystem::MappingFlag::Limit)); 4413 m_selectionStartLine.setP2(cSystem->mapLogicalToScene(p2, visible, CartesianCoordinateSystem::MappingFlag::Limit)); 4414 } 4415 4416 update(); // because if previous another selection mode was selected, the lines must be deleted 4417 } 4418 4419 void CartesianPlotPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget*) { 4420 if (!isVisible() || m_printing) 4421 return; 4422 4423 if ((mouseMode == CartesianPlot::MouseMode::ZoomXSelection || mouseMode == CartesianPlot::MouseMode::ZoomYSelection) && (!m_selectionBandIsShown) 4424 && m_insideDataRect) { 4425 painter->setPen(zoomSelectPen); 4426 painter->drawLine(m_selectionStartLine); 4427 } else if (m_selectionBandIsShown) { 4428 QPointF selectionStart = m_selectionStart; 4429 if (m_selectionStart.x() > dataRect.right()) 4430 selectionStart.setX(dataRect.right()); 4431 if (m_selectionStart.x() < dataRect.left()) 4432 selectionStart.setX(dataRect.left()); 4433 if (m_selectionStart.y() > dataRect.bottom()) 4434 selectionStart.setY(dataRect.bottom()); 4435 if (m_selectionStart.y() < dataRect.top()) 4436 selectionStart.setY(dataRect.top()); 4437 4438 QPointF selectionEnd = m_selectionEnd; 4439 if (m_selectionEnd.x() > dataRect.right()) 4440 selectionEnd.setX(dataRect.right()); 4441 if (m_selectionEnd.x() < dataRect.left()) 4442 selectionEnd.setX(dataRect.left()); 4443 if (m_selectionEnd.y() > dataRect.bottom()) 4444 selectionEnd.setY(dataRect.bottom()); 4445 if (m_selectionEnd.y() < dataRect.top()) 4446 selectionEnd.setY(dataRect.top()); 4447 painter->save(); 4448 painter->setPen(zoomSelectPen); 4449 painter->drawRect(QRectF(selectionStart, selectionEnd)); 4450 painter->setBrush(Qt::blue); 4451 painter->setOpacity(0.2); 4452 painter->drawRect(QRectF(selectionStart, selectionEnd)); 4453 painter->restore(); 4454 } else if (mouseMode == CartesianPlot::MouseMode::Crosshair) { 4455 painter->setPen(crossHairPen); 4456 4457 // horizontal line 4458 double x1 = dataRect.left(); 4459 double y1 = m_crosshairPos.y(); 4460 double x2 = dataRect.right(); 4461 double y2 = y1; 4462 painter->drawLine(x1, y1, x2, y2); 4463 4464 // vertical line 4465 x1 = m_crosshairPos.x(); 4466 y1 = dataRect.bottom(); 4467 x2 = x1; 4468 y2 = dataRect.top(); 4469 painter->drawLine(x1, y1, x2, y2); 4470 } 4471 4472 // draw cursor lines if available 4473 if (cursor0Enable || cursor1Enable) { 4474 painter->save(); 4475 painter->setPen(cursorLine->pen()); 4476 painter->setOpacity(cursorLine->opacity()); 4477 QFont font = painter->font(); 4478 font.setPointSize(font.pointSize() * 4); 4479 painter->setFont(font); 4480 4481 bool visible; 4482 QPointF p1 = defaultCoordinateSystem()->mapLogicalToScene(QPointF(cursor0Pos.x(), range(Dimension::Y).start()), visible); 4483 if (cursor0Enable && visible) { 4484 QPointF p2 = defaultCoordinateSystem()->mapLogicalToScene(QPointF(cursor0Pos.x(), range(Dimension::Y).end()), visible); 4485 painter->drawLine(p1, p2); 4486 QPointF textPos = p2; 4487 textPos.setX(p2.x() - m_cursor0Text.size().width() / 2); 4488 textPos.setY(p2.y() - m_cursor0Text.size().height()); 4489 if (textPos.y() < boundingRect().y()) 4490 textPos.setY(boundingRect().y()); 4491 painter->drawStaticText(textPos, m_cursor0Text); 4492 } 4493 4494 p1 = defaultCoordinateSystem()->mapLogicalToScene(QPointF(cursor1Pos.x(), range(Dimension::Y).start()), visible); 4495 if (cursor1Enable && visible) { 4496 QPointF p2 = defaultCoordinateSystem()->mapLogicalToScene(QPointF(cursor1Pos.x(), range(Dimension::Y).end()), visible); 4497 painter->drawLine(p1, p2); 4498 QPointF textPos = p2; 4499 // TODO: Moving this stuff into other function to not calculate it every time 4500 textPos.setX(p2.x() - m_cursor1Text.size().width() / 2); 4501 textPos.setY(p2.y() - m_cursor1Text.size().height()); 4502 if (textPos.y() < boundingRect().y()) 4503 textPos.setY(boundingRect().y()); 4504 painter->drawStaticText(textPos, m_cursor1Text); 4505 } 4506 4507 painter->restore(); 4508 } 4509 4510 const bool selected = isSelected(); 4511 const bool hovered = (m_hovered && !selected); 4512 if ((hovered || selected) && !m_printing) { 4513 static double penWidth = 2.; // why static? 4514 const QRectF& br = q->m_plotArea->graphicsItem()->boundingRect(); 4515 const qreal width = br.width(); 4516 const qreal height = br.height(); 4517 const QRectF rect = QRectF(-width / 2 + penWidth / 2, -height / 2 + penWidth / 2, width - penWidth, height - penWidth); 4518 4519 if (hovered) 4520 painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), penWidth)); 4521 else 4522 painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), penWidth)); 4523 4524 painter->drawRect(rect); 4525 } 4526 } 4527 4528 // ############################################################################## 4529 // ################## Serialization/Deserialization ########################### 4530 // ############################################################################## 4531 4532 //! Save as XML 4533 void CartesianPlot::save(QXmlStreamWriter* writer) const { 4534 Q_D(const CartesianPlot); 4535 4536 writer->writeStartElement(QStringLiteral("cartesianPlot")); 4537 writeBasicAttributes(writer); 4538 writeCommentElement(writer); 4539 4540 // applied theme 4541 if (!d->theme.isEmpty()) { 4542 writer->writeStartElement(QStringLiteral("theme")); 4543 writer->writeAttribute(QStringLiteral("name"), d->theme); 4544 writer->writeEndElement(); 4545 } 4546 4547 // cursor 4548 d->cursorLine->save(writer); 4549 4550 // geometry 4551 writer->writeStartElement(QStringLiteral("geometry")); 4552 writer->writeAttribute(QStringLiteral("x"), QString::number(d->rect.x())); 4553 writer->writeAttribute(QStringLiteral("y"), QString::number(d->rect.y())); 4554 writer->writeAttribute(QStringLiteral("width"), QString::number(d->rect.width())); 4555 writer->writeAttribute(QStringLiteral("height"), QString::number(d->rect.height())); 4556 writer->writeAttribute(QStringLiteral("visible"), QString::number(d->isVisible())); 4557 writer->writeEndElement(); 4558 4559 // coordinate system and padding 4560 // new style 4561 writer->writeStartElement(QStringLiteral("xRanges")); 4562 for (const auto& range : d->xRanges) { 4563 writer->writeStartElement(QStringLiteral("xRange")); 4564 writer->writeAttribute(QStringLiteral("autoScale"), QString::number(range.range.autoScale())); 4565 writer->writeAttribute(QStringLiteral("start"), QString::number(range.range.start(), 'g', 16)); 4566 writer->writeAttribute(QStringLiteral("end"), QString::number(range.range.end(), 'g', 16)); 4567 writer->writeAttribute(QStringLiteral("scale"), QString::number(static_cast<int>(range.range.scale()))); 4568 writer->writeAttribute(QStringLiteral("format"), QString::number(static_cast<int>(range.range.format()))); 4569 writer->writeAttribute(QStringLiteral("dateTimeFormat"), range.range.dateTimeFormat()); 4570 writer->writeEndElement(); 4571 } 4572 writer->writeEndElement(); 4573 writer->writeStartElement(QStringLiteral("yRanges")); 4574 for (const auto& range : d->yRanges) { 4575 writer->writeStartElement(QStringLiteral("yRange")); 4576 writer->writeAttribute(QStringLiteral("autoScale"), QString::number(range.range.autoScale())); 4577 writer->writeAttribute(QStringLiteral("start"), QString::number(range.range.start(), 'g', 16)); 4578 writer->writeAttribute(QStringLiteral("end"), QString::number(range.range.end(), 'g', 16)); 4579 writer->writeAttribute(QStringLiteral("scale"), QString::number(static_cast<int>(range.range.scale()))); 4580 writer->writeAttribute(QStringLiteral("format"), QString::number(static_cast<int>(range.range.format()))); 4581 writer->writeAttribute(QStringLiteral("dateTimeFormat"), range.range.dateTimeFormat()); 4582 writer->writeEndElement(); 4583 } 4584 writer->writeEndElement(); 4585 writer->writeStartElement(QStringLiteral("coordinateSystems")); 4586 writer->writeAttribute(QStringLiteral("defaultCoordinateSystem"), QString::number(defaultCoordinateSystemIndex())); 4587 // padding 4588 writer->writeAttribute(QStringLiteral("horizontalPadding"), QString::number(d->horizontalPadding)); 4589 writer->writeAttribute(QStringLiteral("verticalPadding"), QString::number(d->verticalPadding)); 4590 writer->writeAttribute(QStringLiteral("rightPadding"), QString::number(d->rightPadding)); 4591 writer->writeAttribute(QStringLiteral("bottomPadding"), QString::number(d->bottomPadding)); 4592 writer->writeAttribute(QStringLiteral("symmetricPadding"), QString::number(d->symmetricPadding)); 4593 writer->writeAttribute(QStringLiteral("niceExtend"), QString::number(d->niceExtend)); 4594 for (const auto& cSystem : m_coordinateSystems) { 4595 writer->writeStartElement(QStringLiteral("coordinateSystem")); 4596 writer->writeAttribute(QStringLiteral("xIndex"), QString::number(static_cast<CartesianCoordinateSystem*>(cSystem)->index(Dimension::X))); 4597 writer->writeAttribute(QStringLiteral("yIndex"), QString::number(static_cast<CartesianCoordinateSystem*>(cSystem)->index(Dimension::Y))); 4598 writer->writeEndElement(); 4599 } 4600 writer->writeEndElement(); 4601 // OLD style (pre 2.9.0) 4602 // writer->writeStartElement( QStringLiteral("coordinateSystem") ); 4603 // writer->writeAttribute( QStringLiteral("autoScaleX"), QString::number(d->autoScaleX) ); 4604 // writer->writeAttribute( QStringLiteral("autoScaleY"), QString::number(d->autoScaleY) ); 4605 // writer->writeAttribute( QStringLiteral("xMin"), QString::number(d->range(Dimension::X, 0).start(), 'g', 16)); 4606 // writer->writeAttribute( QStringLiteral("xMax"), QString::number(d->range(Dimension::X, 0).end(), 'g', 16) ); 4607 // writer->writeAttribute( QStringLiteral("yMin"), QString::number(d->yRange.range.start(), 'g', 16) ); 4608 // writer->writeAttribute( QStringLiteral("yMax"), QString::number(d->yRange.range.end(), 'g', 16) ); 4609 // writer->writeAttribute( QStringLiteral("xScale"), QString::number(static_cast<int>(d->range(Dimension::X, 0).scale())) ); 4610 // writer->writeAttribute( QStringLiteral("yScale"), QString::number(static_cast<int>(d->yScale)) ); 4611 // writer->writeAttribute( QStringLiteral("xRangeFormat"), QString::number(static_cast<int>(xRangeFormat(0))) ); 4612 // writer->writeAttribute( QStringLiteral("yRangeFormat"), QString::number(static_cast<int>(d->yRangeFormat)) ); 4613 // writer->writeAttribute( QStringLiteral("horizontalPadding"), QString::number(d->horizontalPadding) ); 4614 // writer->writeAttribute( QStringLiteral("verticalPadding"), QString::number(d->verticalPadding) ); 4615 // writer->writeAttribute( QStringLiteral("rightPadding"), QString::number(d->rightPadding) ); 4616 // writer->writeAttribute( QStringLiteral("bottomPadding"), QString::number(d->bottomPadding) ); 4617 // writer->writeAttribute( QStringLiteral("symmetricPadding"), QString::number(d->symmetricPadding)); 4618 4619 // x-scale breaks 4620 if (d->xRangeBreakingEnabled || !d->xRangeBreaks.list.isEmpty()) { 4621 writer->writeStartElement(QStringLiteral("xRangeBreaks")); 4622 writer->writeAttribute(QStringLiteral("enabled"), QString::number(d->xRangeBreakingEnabled)); 4623 for (const auto& rb : d->xRangeBreaks.list) { 4624 writer->writeStartElement(QStringLiteral("xRangeBreak")); 4625 writer->writeAttribute(QStringLiteral("start"), QString::number(rb.range.start())); 4626 writer->writeAttribute(QStringLiteral("end"), QString::number(rb.range.end())); 4627 writer->writeAttribute(QStringLiteral("position"), QString::number(rb.position)); 4628 writer->writeAttribute(QStringLiteral("style"), QString::number(static_cast<int>(rb.style))); 4629 writer->writeEndElement(); 4630 } 4631 writer->writeEndElement(); 4632 } 4633 4634 // y-scale breaks 4635 if (d->yRangeBreakingEnabled || !d->yRangeBreaks.list.isEmpty()) { 4636 writer->writeStartElement(QStringLiteral("yRangeBreaks")); 4637 writer->writeAttribute(QStringLiteral("enabled"), QString::number(d->yRangeBreakingEnabled)); 4638 for (const auto& rb : d->yRangeBreaks.list) { 4639 writer->writeStartElement(QStringLiteral("yRangeBreak")); 4640 writer->writeAttribute(QStringLiteral("start"), QString::number(rb.range.start())); 4641 writer->writeAttribute(QStringLiteral("end"), QString::number(rb.range.end())); 4642 writer->writeAttribute(QStringLiteral("position"), QString::number(rb.position)); 4643 writer->writeAttribute(QStringLiteral("style"), QString::number(static_cast<int>(rb.style))); 4644 writer->writeEndElement(); 4645 } 4646 writer->writeEndElement(); 4647 } 4648 4649 // serialize all children (plot area, title text label, axes and curves) 4650 const auto& elements = children<WorksheetElement>(ChildIndexFlag::IncludeHidden); 4651 for (auto* elem : elements) 4652 elem->save(writer); 4653 4654 writer->writeEndElement(); // cartesianPlot 4655 } 4656 4657 //! Load from XML 4658 bool CartesianPlot::load(XmlStreamReader* reader, bool preview) { 4659 Q_D(CartesianPlot); 4660 4661 if (!readBasicAttributes(reader)) 4662 return false; 4663 4664 QXmlStreamAttributes attribs; 4665 QString str; 4666 bool titleLabelRead = false; 4667 bool hasCoordinateSystems = false; // new since 2.9.0 4668 4669 while (!reader->atEnd()) { 4670 reader->readNext(); 4671 if (reader->isEndElement() && reader->name() == QLatin1String("cartesianPlot")) 4672 break; 4673 4674 if (!reader->isStartElement()) 4675 continue; 4676 4677 if (reader->name() == QLatin1String("comment")) { 4678 if (!readCommentElement(reader)) 4679 return false; 4680 } else if (!preview && reader->name() == QLatin1String("theme")) { 4681 attribs = reader->attributes(); 4682 d->theme = attribs.value(QStringLiteral("name")).toString(); 4683 } else if (!preview && reader->name() == QLatin1String("cursor")) { 4684 d->cursorLine->load(reader, preview); 4685 } else if (!preview && reader->name() == QLatin1String("geometry")) { 4686 attribs = reader->attributes(); 4687 4688 str = attribs.value(QStringLiteral("x")).toString(); 4689 if (str.isEmpty()) 4690 reader->raiseMissingAttributeWarning(QStringLiteral("x")); 4691 else 4692 d->rect.setX(str.toDouble()); 4693 4694 str = attribs.value(QStringLiteral("y")).toString(); 4695 if (str.isEmpty()) 4696 reader->raiseMissingAttributeWarning(QStringLiteral("y")); 4697 else 4698 d->rect.setY(str.toDouble()); 4699 4700 str = attribs.value(QStringLiteral("width")).toString(); 4701 if (str.isEmpty()) 4702 reader->raiseMissingAttributeWarning(QStringLiteral("width")); 4703 else 4704 d->rect.setWidth(str.toDouble()); 4705 4706 str = attribs.value(QStringLiteral("height")).toString(); 4707 if (str.isEmpty()) 4708 reader->raiseMissingAttributeWarning(QStringLiteral("height")); 4709 else 4710 d->rect.setHeight(str.toDouble()); 4711 4712 str = attribs.value(QStringLiteral("visible")).toString(); 4713 if (str.isEmpty()) 4714 reader->raiseMissingAttributeWarning(QStringLiteral("visible")); 4715 else 4716 d->setVisible(str.toInt()); 4717 } else if (!preview && reader->name() == QLatin1String("xRanges")) { 4718 d->xRanges.clear(); 4719 } else if (!preview && reader->name() == QLatin1String("xRange")) { 4720 attribs = reader->attributes(); 4721 4722 // TODO: Range<double> range = Range::load(reader) 4723 Range<double> range; 4724 str = attribs.value(QStringLiteral("autoScale")).toString(); 4725 QDEBUG(Q_FUNC_INFO << ", str =" << str << ", value = " << str.toInt()) 4726 if (str.isEmpty()) 4727 reader->raiseMissingAttributeWarning(QStringLiteral("autoScale")); 4728 else 4729 range.setAutoScale(str.toInt()); 4730 str = attribs.value(QStringLiteral("start")).toString(); 4731 if (str.isEmpty()) 4732 reader->raiseMissingAttributeWarning(QStringLiteral("start")); 4733 else 4734 range.setStart(str.toDouble()); 4735 str = attribs.value(QStringLiteral("end")).toString(); 4736 if (str.isEmpty()) 4737 reader->raiseMissingAttributeWarning(QStringLiteral("end")); 4738 else 4739 range.setEnd(str.toDouble()); 4740 str = attribs.value(QStringLiteral("scale")).toString(); 4741 if (str.isEmpty()) 4742 reader->raiseMissingAttributeWarning(QStringLiteral("scale")); 4743 else 4744 range.setScale(static_cast<RangeT::Scale>(str.toInt())); 4745 str = attribs.value(QStringLiteral("format")).toString(); 4746 if (str.isEmpty()) 4747 reader->raiseMissingAttributeWarning(QStringLiteral("format")); 4748 else 4749 range.setFormat(static_cast<RangeT::Format>(str.toInt())); 4750 str = attribs.value(QStringLiteral("dateTimeFormat")).toString(); 4751 if (str.isEmpty()) 4752 reader->raiseMissingAttributeWarning(QStringLiteral("dateTimeFormat")); 4753 else 4754 range.setDateTimeFormat(str); 4755 4756 DEBUG(Q_FUNC_INFO << ", auto scale =" << range.autoScale()) 4757 addXRange(range); 4758 } else if (!preview && reader->name() == QLatin1String("yRanges")) { 4759 d->yRanges.clear(); 4760 } else if (!preview && reader->name() == QLatin1String("yRange")) { 4761 attribs = reader->attributes(); 4762 4763 // TODO: Range<double> range = Range::load(reader) 4764 Range<double> range; 4765 str = attribs.value(QStringLiteral("autoScale")).toString(); 4766 if (str.isEmpty()) 4767 reader->raiseMissingAttributeWarning(QStringLiteral("autoScale")); 4768 else 4769 range.setAutoScale(str.toInt()); 4770 str = attribs.value(QStringLiteral("start")).toString(); 4771 if (str.isEmpty()) 4772 reader->raiseMissingAttributeWarning(QStringLiteral("start")); 4773 else 4774 range.setStart(str.toDouble()); 4775 str = attribs.value(QStringLiteral("end")).toString(); 4776 if (str.isEmpty()) 4777 reader->raiseMissingAttributeWarning(QStringLiteral("end")); 4778 else 4779 range.setEnd(str.toDouble()); 4780 str = attribs.value(QStringLiteral("scale")).toString(); 4781 if (str.isEmpty()) 4782 reader->raiseMissingAttributeWarning(QStringLiteral("scale")); 4783 else 4784 range.setScale(static_cast<RangeT::Scale>(str.toInt())); 4785 str = attribs.value(QStringLiteral("format")).toString(); 4786 if (str.isEmpty()) 4787 reader->raiseMissingAttributeWarning(QStringLiteral("format")); 4788 else 4789 range.setFormat(static_cast<RangeT::Format>(str.toInt())); 4790 str = attribs.value(QStringLiteral("dateTimeFormat")).toString(); 4791 if (str.isEmpty()) 4792 reader->raiseMissingAttributeWarning(QStringLiteral("dateTimeFormat")); 4793 else 4794 range.setDateTimeFormat(str); 4795 4796 addYRange(range); 4797 } else if (!preview && reader->name() == QLatin1String("coordinateSystems")) { 4798 attribs = reader->attributes(); 4799 READ_INT_VALUE("defaultCoordinateSystem", defaultCoordinateSystemIndex, int); 4800 DEBUG(Q_FUNC_INFO << ", got default cSystem index = " << d->defaultCoordinateSystemIndex) 4801 4802 READ_DOUBLE_VALUE("horizontalPadding", horizontalPadding); 4803 READ_DOUBLE_VALUE("verticalPadding", verticalPadding); 4804 READ_DOUBLE_VALUE("rightPadding", rightPadding); 4805 READ_DOUBLE_VALUE("bottomPadding", bottomPadding); 4806 READ_INT_VALUE("symmetricPadding", symmetricPadding, bool); 4807 hasCoordinateSystems = true; 4808 4809 m_coordinateSystems.clear(); 4810 4811 if (Project::xmlVersion() < 7) { 4812 d->niceExtend = true; 4813 } else { 4814 str = attribs.value(QStringLiteral("niceExtend")).toString(); 4815 if (str.isEmpty()) 4816 reader->raiseMissingAttributeWarning(QStringLiteral("niceExtend")); 4817 else 4818 d->niceExtend = str.toInt(); 4819 } 4820 4821 } else if (!preview && reader->name() == QLatin1String("coordinateSystem")) { 4822 attribs = reader->attributes(); 4823 // new style 4824 str = attribs.value(QStringLiteral("xIndex")).toString(); 4825 if (str.isEmpty()) 4826 reader->raiseMissingAttributeWarning(QStringLiteral("xIndex")); 4827 else { 4828 CartesianCoordinateSystem* cSystem{new CartesianCoordinateSystem(this)}; 4829 cSystem->setIndex(Dimension::X, str.toInt()); 4830 4831 str = attribs.value(QStringLiteral("yIndex")).toString(); 4832 if (str.isEmpty()) 4833 reader->raiseMissingAttributeWarning(QStringLiteral("yIndex")); 4834 else 4835 cSystem->setIndex(Dimension::Y, str.toInt()); 4836 4837 addCoordinateSystem(cSystem); 4838 } 4839 4840 // old style (pre 2.9.0, to read old projects) 4841 if (!hasCoordinateSystems) { 4842 str = attribs.value(QStringLiteral("autoScaleX")).toString(); 4843 if (str.isEmpty()) 4844 reader->raiseMissingAttributeWarning(QStringLiteral("autoScaleX")); 4845 else 4846 d->xRanges[0].range.setAutoScale(str.toInt()); 4847 str = attribs.value(QStringLiteral("autoScaleY")).toString(); 4848 if (str.isEmpty()) 4849 reader->raiseMissingAttributeWarning(QStringLiteral("autoScaleY")); 4850 else 4851 d->yRanges[0].range.setAutoScale(str.toInt()); 4852 4853 str = attribs.value(QStringLiteral("xMin")).toString(); 4854 if (str.isEmpty()) 4855 reader->raiseMissingAttributeWarning(QStringLiteral("xMin")); 4856 else { 4857 d->xRanges[0].range.start() = str.toDouble(); 4858 d->xRanges[0].prev.start() = d->range(Dimension::X, 0).start(); 4859 } 4860 4861 str = attribs.value(QStringLiteral("xMax")).toString(); 4862 if (str.isEmpty()) 4863 reader->raiseMissingAttributeWarning(QStringLiteral("xMax")); 4864 else { 4865 d->xRanges[0].range.end() = str.toDouble(); 4866 d->xRanges[0].prev.end() = d->range(Dimension::X, 0).end(); 4867 } 4868 4869 str = attribs.value(QStringLiteral("yMin")).toString(); 4870 if (str.isEmpty()) 4871 reader->raiseMissingAttributeWarning(QStringLiteral("yMin")); 4872 else { 4873 d->yRanges[0].range.start() = str.toDouble(); 4874 d->yRanges[0].prev.start() = range(Dimension::Y, 0).start(); 4875 } 4876 4877 str = attribs.value(QStringLiteral("yMax")).toString(); 4878 if (str.isEmpty()) 4879 reader->raiseMissingAttributeWarning(QStringLiteral("yMax")); 4880 else { 4881 d->yRanges[0].range.end() = str.toDouble(); 4882 d->yRanges[0].prev.end() = range(Dimension::Y, 0).end(); 4883 } 4884 4885 str = attribs.value(QStringLiteral("xScale")).toString(); 4886 if (str.isEmpty()) 4887 reader->raiseMissingAttributeWarning(QStringLiteral("xScale")); 4888 else { 4889 int scale{str.toInt()}; 4890 // convert old scale 4891 if (scale > (int)RangeT::Scale::Ln) 4892 scale -= 3; 4893 d->xRanges[0].range.scale() = static_cast<RangeT::Scale>(scale); 4894 } 4895 str = attribs.value(QStringLiteral("yScale")).toString(); 4896 if (str.isEmpty()) 4897 reader->raiseMissingAttributeWarning(QStringLiteral("yScale")); 4898 else { 4899 int scale{str.toInt()}; 4900 // convert old scale 4901 if (scale > (int)RangeT::Scale::Ln) 4902 scale -= 3; 4903 d->yRanges[0].range.scale() = static_cast<RangeT::Scale>(scale); 4904 } 4905 4906 str = attribs.value(QStringLiteral("xRangeFormat")).toString(); 4907 if (str.isEmpty()) 4908 reader->raiseMissingAttributeWarning(QStringLiteral("xRangeFormat")); 4909 else 4910 d->xRanges[0].range.format() = static_cast<RangeT::Format>(str.toInt()); 4911 str = attribs.value(QStringLiteral("yRangeFormat")).toString(); 4912 if (str.isEmpty()) 4913 reader->raiseMissingAttributeWarning(QStringLiteral("yRangeFormat")); 4914 else 4915 d->yRanges[0].range.format() = static_cast<RangeT::Format>(str.toInt()); 4916 4917 str = attribs.value(QStringLiteral("xRangeDateTimeFormat")).toString(); 4918 if (!str.isEmpty()) 4919 d->xRanges[0].range.setDateTimeFormat(str); 4920 4921 str = attribs.value(QStringLiteral("yRangeDateTimeFormat")).toString(); 4922 if (!str.isEmpty()) 4923 d->yRanges[0].range.setDateTimeFormat(str); 4924 4925 READ_DOUBLE_VALUE("horizontalPadding", horizontalPadding); 4926 READ_DOUBLE_VALUE("verticalPadding", verticalPadding); 4927 READ_DOUBLE_VALUE("rightPadding", rightPadding); 4928 READ_DOUBLE_VALUE("bottomPadding", bottomPadding); 4929 READ_INT_VALUE("symmetricPadding", symmetricPadding, bool); 4930 } 4931 } else if (!preview && reader->name() == QLatin1String("xRangeBreaks")) { 4932 // delete default range break 4933 d->xRangeBreaks.list.clear(); 4934 4935 attribs = reader->attributes(); 4936 READ_INT_VALUE("enabled", xRangeBreakingEnabled, bool); 4937 } else if (!preview && reader->name() == QLatin1String("xRangeBreak")) { 4938 attribs = reader->attributes(); 4939 4940 RangeBreak b; 4941 str = attribs.value(QStringLiteral("start")).toString(); 4942 if (str.isEmpty()) 4943 reader->raiseMissingAttributeWarning(QStringLiteral("start")); 4944 else 4945 b.range.start() = str.toDouble(); 4946 4947 str = attribs.value(QStringLiteral("end")).toString(); 4948 if (str.isEmpty()) 4949 reader->raiseMissingAttributeWarning(QStringLiteral("end")); 4950 else 4951 b.range.end() = str.toDouble(); 4952 4953 str = attribs.value(QStringLiteral("position")).toString(); 4954 if (str.isEmpty()) 4955 reader->raiseMissingAttributeWarning(QStringLiteral("position")); 4956 else 4957 b.position = str.toDouble(); 4958 4959 str = attribs.value(QStringLiteral("style")).toString(); 4960 if (str.isEmpty()) 4961 reader->raiseMissingAttributeWarning(QStringLiteral("style")); 4962 else 4963 b.style = CartesianPlot::RangeBreakStyle(str.toInt()); 4964 4965 d->xRangeBreaks.list << b; 4966 } else if (!preview && reader->name() == QLatin1String("yRangeBreaks")) { 4967 // delete default range break 4968 d->yRangeBreaks.list.clear(); 4969 4970 attribs = reader->attributes(); 4971 READ_INT_VALUE("enabled", yRangeBreakingEnabled, bool); 4972 } else if (!preview && reader->name() == QLatin1String("yRangeBreak")) { 4973 attribs = reader->attributes(); 4974 4975 RangeBreak b; 4976 str = attribs.value(QStringLiteral("start")).toString(); 4977 if (str.isEmpty()) 4978 reader->raiseMissingAttributeWarning(QStringLiteral("start")); 4979 else 4980 b.range.start() = str.toDouble(); 4981 4982 str = attribs.value(QStringLiteral("end")).toString(); 4983 if (str.isEmpty()) 4984 reader->raiseMissingAttributeWarning(QStringLiteral("end")); 4985 else 4986 b.range.end() = str.toDouble(); 4987 4988 str = attribs.value(QStringLiteral("position")).toString(); 4989 if (str.isEmpty()) 4990 reader->raiseMissingAttributeWarning(QStringLiteral("position")); 4991 else 4992 b.position = str.toDouble(); 4993 4994 str = attribs.value(QStringLiteral("style")).toString(); 4995 if (str.isEmpty()) 4996 reader->raiseMissingAttributeWarning(QStringLiteral("style")); 4997 else 4998 b.style = CartesianPlot::RangeBreakStyle(str.toInt()); 4999 5000 d->yRangeBreaks.list << b; 5001 } else if (!preview && reader->name() == QLatin1String("textLabel")) { 5002 if (!titleLabelRead) { 5003 m_title->setIsLoading(true); 5004 // the first text label is always the title label 5005 m_title->load(reader, preview); 5006 titleLabelRead = true; 5007 5008 // TODO: the name is read in m_title->load() but we overwrite it here 5009 // since the old projects don't have this " - Title" appendix yet that we add in init(). 5010 // can be removed in couple of releases 5011 m_title->setName(name() + QLatin1String(" - ") + i18n("Title")); 5012 } else { 5013 auto* label = new TextLabel(QStringLiteral("text label"), this); 5014 label->setIsLoading(true); 5015 if (label->load(reader, preview)) { 5016 addChildFast(label); 5017 label->setParentGraphicsItem(graphicsItem()); 5018 } else { 5019 delete label; 5020 return false; 5021 } 5022 } 5023 } else if (!preview && reader->name() == QLatin1String("image")) { 5024 auto* image = new Image(QString()); 5025 image->setIsLoading(true); 5026 if (image->load(reader, preview)) 5027 addChildFast(image); 5028 else { 5029 delete image; 5030 return false; 5031 } 5032 } else if (!preview && reader->name() == QLatin1String("infoElement")) { 5033 auto* marker = new InfoElement(QStringLiteral("Marker"), this); 5034 marker->setIsLoading(true); 5035 if (marker->load(reader, preview)) { 5036 addChildFast(marker); 5037 marker->setParentGraphicsItem(graphicsItem()); 5038 } else { 5039 delete marker; 5040 return false; 5041 } 5042 } else if (!preview && reader->name() == QLatin1String("plotArea")) { 5043 m_plotArea->setIsLoading(true); 5044 m_plotArea->load(reader, preview); 5045 } else if (!preview && reader->name() == QLatin1String("axis")) { 5046 auto* axis = new Axis(QString()); 5047 axis->setIsLoading(true); 5048 if (axis->load(reader, preview)) 5049 addChildFast(axis); 5050 else { 5051 delete axis; 5052 return false; 5053 } 5054 } else if (reader->name() == QLatin1String("xyCurve")) { 5055 auto* curve = new XYCurve(QString()); 5056 curve->setIsLoading(true); 5057 if (curve->load(reader, preview)) 5058 addChildFast(curve); 5059 else { 5060 delete curve; 5061 return false; 5062 } 5063 } else if (reader->name() == QLatin1String("xyEquationCurve")) { 5064 auto* curve = new XYEquationCurve(QString()); 5065 curve->setIsLoading(true); 5066 if (curve->load(reader, preview)) 5067 addChildFast(curve); 5068 else { 5069 delete curve; 5070 return false; 5071 } 5072 } else if (reader->name() == QLatin1String("xyDataReductionCurve")) { 5073 auto* curve = new XYDataReductionCurve(QString()); 5074 curve->setIsLoading(true); 5075 if (curve->load(reader, preview)) 5076 addChildFast(curve); 5077 else { 5078 delete curve; 5079 return false; 5080 } 5081 } else if (reader->name() == QLatin1String("xyDifferentiationCurve")) { 5082 auto* curve = new XYDifferentiationCurve(QString()); 5083 curve->setIsLoading(true); 5084 if (curve->load(reader, preview)) 5085 addChildFast(curve); 5086 else { 5087 delete curve; 5088 return false; 5089 } 5090 } else if (reader->name() == QLatin1String("xyIntegrationCurve")) { 5091 auto* curve = new XYIntegrationCurve(QString()); 5092 curve->setIsLoading(true); 5093 if (curve->load(reader, preview)) 5094 addChildFast(curve); 5095 else { 5096 delete curve; 5097 return false; 5098 } 5099 } else if (reader->name() == QLatin1String("xyInterpolationCurve")) { 5100 auto* curve = new XYInterpolationCurve(QString()); 5101 curve->setIsLoading(true); 5102 if (curve->load(reader, preview)) 5103 addChildFast(curve); 5104 else { 5105 delete curve; 5106 return false; 5107 } 5108 } else if (reader->name() == QLatin1String("xySmoothCurve")) { 5109 auto* curve = new XYSmoothCurve(QString()); 5110 curve->setIsLoading(true); 5111 if (curve->load(reader, preview)) 5112 addChildFast(curve); 5113 else { 5114 delete curve; 5115 return false; 5116 } 5117 } else if (reader->name() == QLatin1String("xyFitCurve")) { 5118 auto* curve = new XYFitCurve(QString()); 5119 curve->setIsLoading(true); 5120 if (curve->load(reader, preview)) 5121 addChildFast(curve); 5122 else { 5123 delete curve; 5124 return false; 5125 } 5126 } else if (reader->name() == QLatin1String("xyFourierFilterCurve")) { 5127 auto* curve = new XYFourierFilterCurve(QString()); 5128 curve->setIsLoading(true); 5129 if (curve->load(reader, preview)) 5130 addChildFast(curve); 5131 else { 5132 delete curve; 5133 return false; 5134 } 5135 } else if (reader->name() == QLatin1String("xyFourierTransformCurve")) { 5136 auto* curve = new XYFourierTransformCurve(QString()); 5137 curve->setIsLoading(true); 5138 if (curve->load(reader, preview)) 5139 addChildFast(curve); 5140 else { 5141 delete curve; 5142 return false; 5143 } 5144 } else if (reader->name() == QLatin1String("xyHilbertTransformCurve")) { 5145 auto* curve = new XYHilbertTransformCurve(QString()); 5146 curve->setIsLoading(true); 5147 if (curve->load(reader, preview)) 5148 addChildFast(curve); 5149 else { 5150 delete curve; 5151 return false; 5152 } 5153 } else if (reader->name() == QLatin1String("xyConvolutionCurve")) { 5154 auto* curve = new XYConvolutionCurve(QString()); 5155 curve->setIsLoading(true); 5156 if (curve->load(reader, preview)) 5157 addChildFast(curve); 5158 else { 5159 delete curve; 5160 return false; 5161 } 5162 } else if (reader->name() == QLatin1String("xyCorrelationCurve")) { 5163 auto* curve = new XYCorrelationCurve(QString()); 5164 curve->setIsLoading(true); 5165 if (curve->load(reader, preview)) 5166 addChildFast(curve); 5167 else { 5168 delete curve; 5169 return false; 5170 } 5171 } else if (!preview && reader->name() == QLatin1String("cartesianPlotLegend")) { 5172 m_legend = new CartesianPlotLegend(QString()); 5173 m_legend->setIsLoading(true); 5174 if (m_legend->load(reader, preview)) 5175 addChildFast(m_legend); 5176 else { 5177 delete m_legend; 5178 return false; 5179 } 5180 } else if (!preview && reader->name() == QLatin1String("customPoint")) { 5181 auto* point = new CustomPoint(this, QString()); 5182 point->setIsLoading(true); 5183 if (point->load(reader, preview)) 5184 addChildFast(point); 5185 else { 5186 delete point; 5187 return false; 5188 } 5189 } else if (!preview && reader->name() == QLatin1String("referenceLine")) { 5190 auto* line = new ReferenceLine(this, QString()); 5191 line->setIsLoading(true); 5192 if (line->load(reader, preview)) 5193 addChildFast(line); 5194 else { 5195 delete line; 5196 return false; 5197 } 5198 } else if (!preview && reader->name() == QLatin1String("referenceRange")) { 5199 auto* range = new ReferenceRange(this, QString()); 5200 range->setIsLoading(true); 5201 if (range->load(reader, preview)) 5202 addChildFast(range); 5203 else { 5204 delete range; 5205 return false; 5206 } 5207 } else if (reader->name() == QLatin1String("boxPlot")) { 5208 auto* boxPlot = new BoxPlot(QStringLiteral("BoxPlot")); 5209 boxPlot->setIsLoading(true); 5210 if (boxPlot->load(reader, preview)) 5211 addChildFast(boxPlot); 5212 else { 5213 delete boxPlot; 5214 return false; 5215 } 5216 } else if (reader->name() == QLatin1String("barPlot")) { 5217 auto* barPlot = new BarPlot(QStringLiteral("BarPlot")); 5218 barPlot->setIsLoading(true); 5219 if (barPlot->load(reader, preview)) 5220 addChildFast(barPlot); 5221 else { 5222 delete barPlot; 5223 return false; 5224 } 5225 } else if (reader->name() == QLatin1String("lollipopPlot")) { 5226 auto* plot = new LollipopPlot(QStringLiteral("LollipopPlot")); 5227 plot->setIsLoading(true); 5228 if (plot->load(reader, preview)) 5229 addChildFast(plot); 5230 else { 5231 delete plot; 5232 return false; 5233 } 5234 } else if (reader->name() == QLatin1String("Histogram")) { 5235 auto* hist = new Histogram(QStringLiteral("Histogram")); 5236 hist->setIsLoading(true); 5237 if (hist->load(reader, preview)) 5238 addChildFast(hist); 5239 else { 5240 delete hist; 5241 return false; 5242 } 5243 } else if (reader->name() == QLatin1String("QQPlot")) { 5244 auto* plot = new QQPlot(QStringLiteral("Q-Q Plot")); 5245 plot->setIsLoading(true); 5246 if (plot->load(reader, preview)) 5247 addChildFast(plot); 5248 else { 5249 delete plot; 5250 return false; 5251 } 5252 } else if (reader->name() == QLatin1String("KDEPlot")) { 5253 auto* plot = new KDEPlot(QStringLiteral("KDE Plot")); 5254 plot->setIsLoading(true); 5255 if (plot->load(reader, preview)) 5256 addChildFast(plot); 5257 else 5258 return false; 5259 } else { // unknown element 5260 if (!preview) 5261 reader->raiseUnknownElementWarning(); 5262 if (!reader->skipToEndElement()) 5263 return false; 5264 } 5265 } 5266 5267 // the file can be corrupted, either because of bugs like in 5268 // https://invent.kde.org/education/labplot/-/issues/598 or because it was manually compromized. 5269 // In order not to crash because of the wrong indices, add a safety check here. 5270 // TODO: check the ranges and the coordinate system to make sure they're available. 5271 if (d->defaultCoordinateSystemIndex > m_coordinateSystems.size() - 1) 5272 d->defaultCoordinateSystemIndex = 0; 5273 5274 if (preview) 5275 return true; 5276 5277 // if a theme was used, initialize the color palette 5278 if (!d->theme.isEmpty()) { 5279 // TODO: check whether the theme config really exists 5280 KConfig config(ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig); 5281 this->setColorPalette(config); 5282 } else { 5283 // initialize the color palette with default colors 5284 this->setColorPalette(KConfig()); 5285 } 5286 5287 return true; 5288 } 5289 5290 // ############################################################################## 5291 // ######################### Theme management ################################## 5292 // ############################################################################## 5293 void CartesianPlot::loadTheme(const QString& theme) { 5294 if (!theme.isEmpty()) { 5295 KConfig config(ThemeHandler::themeFilePath(theme), KConfig::SimpleConfig); 5296 loadThemeConfig(config); 5297 } else { 5298 KConfig config; 5299 loadThemeConfig(config); 5300 } 5301 } 5302 5303 void CartesianPlot::loadThemeConfig(const KConfig& config) { 5304 Q_D(CartesianPlot); 5305 5306 QString theme = QString(); 5307 if (config.hasGroup(QStringLiteral("Theme"))) { 5308 theme = config.name(); 5309 5310 // theme path is saved with UNIX dir separator 5311 theme = theme.right(theme.length() - theme.lastIndexOf(QLatin1Char('/')) - 1); 5312 DEBUG(Q_FUNC_INFO << ", set theme to " << STDSTRING(theme)); 5313 } 5314 5315 // loadThemeConfig() can be called from 5316 // 1. CartesianPlot::setTheme() when the user changes the theme for the plot 5317 // 2. Worksheet::setTheme() -> Worksheet::loadTheme() when the user changes the theme for the worksheet 5318 // In the second case (i.e. when d->theme is not equal to theme yet), 5319 /// we need to put the new theme name on the undo-stack. 5320 if (theme != d->theme) 5321 exec(new CartesianPlotSetThemeCmd(d, theme, ki18n("%1: set theme"))); 5322 5323 // load the color palettes for the curves 5324 this->setColorPalette(config); 5325 5326 // load the theme for all the children 5327 const auto& elements = children<WorksheetElement>(ChildIndexFlag::IncludeHidden); 5328 for (auto* child : elements) 5329 child->loadThemeConfig(config); 5330 5331 d->update(this->rect()); 5332 5333 Q_EMIT changed(); 5334 } 5335 5336 void CartesianPlot::saveTheme(KConfig& config) { 5337 const QVector<Axis*>& axisElements = children<Axis>(ChildIndexFlag::IncludeHidden); 5338 const QVector<PlotArea*>& plotAreaElements = children<PlotArea>(ChildIndexFlag::IncludeHidden); 5339 const QVector<TextLabel*>& textLabelElements = children<TextLabel>(ChildIndexFlag::IncludeHidden); 5340 5341 axisElements.at(0)->saveThemeConfig(config); 5342 plotAreaElements.at(0)->saveThemeConfig(config); 5343 textLabelElements.at(0)->saveThemeConfig(config); 5344 5345 const auto& children = this->children<XYCurve>(ChildIndexFlag::IncludeHidden); 5346 for (auto* child : children) 5347 child->saveThemeConfig(config); 5348 } 5349 5350 // Generating colors from 5-color theme palette 5351 void CartesianPlot::setColorPalette(const KConfig& config) { 5352 if (config.hasGroup(QStringLiteral("Theme"))) { 5353 KConfigGroup group = config.group(QStringLiteral("Theme")); 5354 5355 // read the five colors defining the palette 5356 m_themeColorPalette.clear(); 5357 m_themeColorPalette.append(group.readEntry(QStringLiteral("ThemePaletteColor1"), QColor())); 5358 m_themeColorPalette.append(group.readEntry(QStringLiteral("ThemePaletteColor2"), QColor())); 5359 m_themeColorPalette.append(group.readEntry(QStringLiteral("ThemePaletteColor3"), QColor())); 5360 m_themeColorPalette.append(group.readEntry(QStringLiteral("ThemePaletteColor4"), QColor())); 5361 m_themeColorPalette.append(group.readEntry(QStringLiteral("ThemePaletteColor5"), QColor())); 5362 } else { 5363 // no theme is available, provide "default colors" 5364 m_themeColorPalette.clear(); 5365 5366 m_themeColorPalette.append(QColor(28, 113, 216)); 5367 m_themeColorPalette.append(QColor(255, 120, 0)); 5368 m_themeColorPalette.append(QColor(224, 27, 36)); 5369 m_themeColorPalette.append(QColor(46, 194, 126)); 5370 m_themeColorPalette.append(QColor(246, 211, 45)); 5371 m_themeColorPalette.append(QColor(143, 19, 178)); 5372 m_themeColorPalette.append(QColor(0, 255, 255)); 5373 m_themeColorPalette.append(QColor(235, 26, 209)); 5374 m_themeColorPalette.append(QColor(41, 221, 37)); 5375 m_themeColorPalette.append(QColor(33, 6, 227)); 5376 m_themeColorPalette.append(QColor(14, 136, 22)); 5377 m_themeColorPalette.append(QColor(147, 97, 22)); 5378 m_themeColorPalette.append(QColor(85, 85, 91)); 5379 m_themeColorPalette.append(QColor(156, 4, 4)); 5380 // TODO: maybe removing black? 5381 m_themeColorPalette.append(QColor(0, 0, 0)); 5382 } 5383 5384 // use the color of the axis lines as the color for the different mouse cursor lines 5385 Q_D(CartesianPlot); 5386 const KConfigGroup& group = config.group(QStringLiteral("Axis")); 5387 const QColor& color = group.readEntry(QStringLiteral("LineColor"), QColor(Qt::black)); 5388 d->zoomSelectPen.setColor(color); 5389 d->crossHairPen.setColor(color); 5390 } 5391 5392 const QList<QColor>& CartesianPlot::themeColorPalette() const { 5393 return m_themeColorPalette; 5394 } 5395 5396 const QColor CartesianPlot::themeColorPalette(int index) const { 5397 const int i = index % m_themeColorPalette.count(); 5398 return m_themeColorPalette.at(i); 5399 }