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 }