"Fossies" - the Fresh Open Source Software Archive

Member "labplot-2.8.2/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp" (24 Feb 2021, 144988 Bytes) of package /linux/privat/labplot-2.8.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "CartesianPlot.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.8.1_vs_2.8.2.

    1 /***************************************************************************
    2     File                 : CartesianPlot.cpp
    3     Project              : LabPlot
    4     Description          : Cartesian plot
    5     --------------------------------------------------------------------
    6     Copyright            : (C) 2011-2020 by Alexander Semke (alexander.semke@web.de)
    7     Copyright            : (C) 2016-2018 by Stefan Gerlach (stefan.gerlach@uni.kn)
    8     Copyright            : (C) 2017-2018 by Garvit Khatri (garvitdelhi@gmail.com)
    9 
   10  ***************************************************************************/
   11 
   12 /***************************************************************************
   13  *                                                                         *
   14  *  This program is free software; you can redistribute it and/or modify   *
   15  *  it under the terms of the GNU General Public License as published by   *
   16  *  the Free Software Foundation; either version 2 of the License, or      *
   17  *  (at your option) any later version.                                    *
   18  *                                                                         *
   19  *  This program is distributed in the hope that it will be useful,        *
   20  *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
   21  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
   22  *  GNU General Public License for more details.                           *
   23  *                                                                         *
   24  *   You should have received a copy of the GNU General Public License     *
   25  *   along with this program; if not, write to the Free Software           *
   26  *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
   27  *   Boston, MA  02110-1301  USA                                           *
   28  *                                                                         *
   29  ***************************************************************************/
   30 
   31 #include "CartesianPlot.h"
   32 #include "CartesianPlotPrivate.h"
   33 #include "XYCurve.h"
   34 #include "Histogram.h"
   35 #include "XYEquationCurve.h"
   36 #include "XYDataReductionCurve.h"
   37 #include "XYDifferentiationCurve.h"
   38 #include "XYIntegrationCurve.h"
   39 #include "XYInterpolationCurve.h"
   40 #include "XYSmoothCurve.h"
   41 #include "XYFitCurve.h"
   42 #include "XYFourierFilterCurve.h"
   43 #include "XYFourierTransformCurve.h"
   44 #include "XYConvolutionCurve.h"
   45 #include "XYCorrelationCurve.h"
   46 #include "backend/core/Project.h"
   47 #include "backend/core/datatypes/DateTime2StringFilter.h"
   48 #include "backend/spreadsheet/Spreadsheet.h"
   49 #include "backend/worksheet/plots/cartesian/CartesianPlotLegend.h"
   50 #include "backend/worksheet/plots/cartesian/CustomPoint.h"
   51 #include "backend/worksheet/plots/cartesian/ReferenceLine.h"
   52 #include "backend/worksheet/plots/PlotArea.h"
   53 #include "backend/worksheet/plots/AbstractPlotPrivate.h"
   54 #include "backend/worksheet/Worksheet.h"
   55 #include "backend/worksheet/plots/cartesian/Axis.h"
   56 #include "backend/worksheet/Image.h"
   57 #include "backend/worksheet/TextLabel.h"
   58 #include "backend/lib/XmlStreamReader.h"
   59 #include "backend/lib/commandtemplates.h"
   60 #include "backend/lib/macros.h"
   61 #include "backend/lib/trace.h"
   62 #include "kdefrontend/spreadsheet/PlotDataDialog.h" //for PlotDataDialog::AnalysisAction. TODO: find a better place for this enum.
   63 #include "kdefrontend/ThemeHandler.h"
   64 #include "kdefrontend/widgets/ThemesWidget.h"
   65 
   66 #include <QDir>
   67 #include <QDropEvent>
   68 #include <QIcon>
   69 #include <QMenu>
   70 #include <QMimeData>
   71 #include <QPainter>
   72 #include <QWidgetAction>
   73 
   74 #include <array>
   75 #include <cmath>
   76 
   77 #include <KConfig>
   78 #include <KConfigGroup>
   79 #include <KLocalizedString>
   80 
   81 /**
   82  * \class CartesianPlot
   83  * \brief A xy-plot.
   84  *
   85  *
   86  */
   87 CartesianPlot::CartesianPlot(const QString &name)
   88     : AbstractPlot(name, new CartesianPlotPrivate(this), AspectType::CartesianPlot) {
   89 
   90     init();
   91 }
   92 
   93 CartesianPlot::CartesianPlot(const QString &name, CartesianPlotPrivate *dd)
   94     : AbstractPlot(name, dd, AspectType::CartesianPlot) {
   95 
   96     init();
   97 }
   98 
   99 CartesianPlot::~CartesianPlot() {
  100     if (m_menusInitialized) {
  101         delete addNewMenu;
  102         delete zoomMenu;
  103         delete themeMenu;
  104     }
  105 
  106     delete m_coordinateSystem;
  107 
  108     //don't need to delete objects added with addChild()
  109 
  110     //no need to delete the d-pointer here - it inherits from QGraphicsItem
  111     //and is deleted during the cleanup in QGraphicsScene
  112 }
  113 
  114 /*!
  115     initializes all member variables of \c CartesianPlot
  116 */
  117 void CartesianPlot::init() {
  118     Q_D(CartesianPlot);
  119 
  120     d->cSystem = new CartesianCoordinateSystem(this);
  121     m_coordinateSystem = d->cSystem;
  122 
  123     d->rangeType = RangeType::Free;
  124     d->xRangeFormat = RangeFormat::Numeric;
  125     d->yRangeFormat = RangeFormat::Numeric;
  126     d->xRangeDateTimeFormat = "yyyy-MM-dd hh:mm:ss";
  127     d->yRangeDateTimeFormat = "yyyy-MM-dd hh:mm:ss";
  128     d->rangeFirstValues = 1000;
  129     d->rangeLastValues = 1000;
  130     d->autoScaleX = true;
  131     d->autoScaleY = true;
  132     d->xScale = Scale::Linear;
  133     d->yScale = Scale::Linear;
  134     d->xRangeBreakingEnabled = false;
  135     d->yRangeBreakingEnabled = false;
  136 
  137     //the following factor determines the size of the offset between the min/max points of the curves
  138     //and the coordinate system ranges, when doing auto scaling
  139     //Factor 0 corresponds to the exact match - min/max values of the curves correspond to the start/end values of the ranges.
  140     //TODO: make this factor optional.
  141     //Provide in the UI the possibility to choose between "exact" or 0% offset, 2%, 5% and 10% for the auto fit option
  142     d->autoScaleOffsetFactor = 0.0f;
  143 
  144     m_plotArea = new PlotArea(name() + " plot area", this);
  145     addChildFast(m_plotArea);
  146 
  147     //Plot title
  148     m_title = new TextLabel(this->name() + QLatin1String("- ") + i18n("Title"), TextLabel::Type::PlotTitle);
  149     addChild(m_title);
  150     m_title->setHidden(true);
  151     m_title->setParentGraphicsItem(m_plotArea->graphicsItem());
  152 
  153     //offset between the plot area and the area defining the coordinate system, in scene units.
  154     d->horizontalPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Unit::Centimeter);
  155     d->verticalPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Unit::Centimeter);
  156     d->rightPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Unit::Centimeter);
  157     d->bottomPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Unit::Centimeter);
  158     d->symmetricPadding = true;
  159 
  160     connect(this, &AbstractAspect::aspectAdded, this, &CartesianPlot::childAdded);
  161     connect(this, &AbstractAspect::aspectRemoved, this, &CartesianPlot::childRemoved);
  162 
  163     graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true);
  164     graphicsItem()->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
  165     graphicsItem()->setFlag(QGraphicsItem::ItemIsSelectable, true);
  166     graphicsItem()->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
  167     graphicsItem()->setFlag(QGraphicsItem::ItemIsFocusable, true);
  168 
  169     //theme is not set at this point, initialize the color palette with default colors
  170     this->setColorPalette(KConfig());
  171 }
  172 
  173 /*!
  174     initializes all children of \c CartesianPlot and
  175     setups a default plot of type \c type with a plot title.
  176 */
  177 void CartesianPlot::setType(Type type) {
  178     Q_D(CartesianPlot);
  179 
  180     d->type = type;
  181 
  182     switch (type) {
  183     case Type::FourAxes: {
  184             d->xMin = 0.0;
  185             d->xMax = 1.0;
  186             d->yMin = 0.0;
  187             d->yMax = 1.0;
  188 
  189             //Axes
  190             Axis* axis = new Axis(QLatin1String("x"), Axis::Orientation::Horizontal);
  191             axis->setDefault(true);
  192             axis->setSuppressRetransform(true);
  193             addChild(axis);
  194             axis->setPosition(Axis::Position::Bottom);
  195             axis->setStart(0);
  196             axis->setEnd(1);
  197             axis->setMajorTicksDirection(Axis::ticksIn);
  198             axis->setMajorTicksNumber(6);
  199             axis->setMinorTicksDirection(Axis::ticksIn);
  200             axis->setMinorTicksNumber(1);
  201             axis->setSuppressRetransform(false);
  202 
  203             axis = new Axis(QLatin1String("x2"), Axis::Orientation::Horizontal);
  204             axis->setDefault(true);
  205             axis->setSuppressRetransform(true);
  206             addChild(axis);
  207             axis->setPosition(Axis::Position::Top);
  208             axis->setStart(0);
  209             axis->setEnd(1);
  210             axis->setMajorTicksDirection(Axis::ticksIn);
  211             axis->setMajorTicksNumber(6);
  212             axis->setMinorTicksDirection(Axis::ticksIn);
  213             axis->setMinorTicksNumber(1);
  214             QPen pen = axis->minorGridPen();
  215             pen.setStyle(Qt::NoPen);
  216             axis->setMajorGridPen(pen);
  217             pen = axis->minorGridPen();
  218             pen.setStyle(Qt::NoPen);
  219             axis->setMinorGridPen(pen);
  220             axis->setLabelsPosition(Axis::LabelsPosition::NoLabels);
  221             axis->title()->setText(QString());
  222             axis->setSuppressRetransform(false);
  223 
  224             axis = new Axis(QLatin1String("y"), Axis::Orientation::Vertical);
  225             axis->setDefault(true);
  226             axis->setSuppressRetransform(true);
  227             addChild(axis);
  228             axis->setPosition(Axis::Position::Left);
  229             axis->setStart(0);
  230             axis->setEnd(1);
  231             axis->setMajorTicksDirection(Axis::ticksIn);
  232             axis->setMajorTicksNumber(6);
  233             axis->setMinorTicksDirection(Axis::ticksIn);
  234             axis->setMinorTicksNumber(1);
  235             axis->setSuppressRetransform(false);
  236 
  237             axis = new Axis(QLatin1String("y2"), Axis::Orientation::Vertical);
  238             axis->setDefault(true);
  239             axis->setSuppressRetransform(true);
  240             addChild(axis);
  241             axis->setPosition(Axis::Position::Right);
  242             axis->setStart(0);
  243             axis->setEnd(1);
  244             axis->setOffset(1);
  245             axis->setMajorTicksDirection(Axis::ticksIn);
  246             axis->setMajorTicksNumber(6);
  247             axis->setMinorTicksDirection(Axis::ticksIn);
  248             axis->setMinorTicksNumber(1);
  249             pen = axis->minorGridPen();
  250             pen.setStyle(Qt::NoPen);
  251             axis->setMajorGridPen(pen);
  252             pen = axis->minorGridPen();
  253             pen.setStyle(Qt::NoPen);
  254             axis->setMinorGridPen(pen);
  255             axis->setLabelsPosition(Axis::LabelsPosition::NoLabels);
  256             axis->title()->setText(QString());
  257             axis->setSuppressRetransform(false);
  258 
  259             break;
  260         }
  261     case Type::TwoAxes: {
  262             d->xMin = 0.0;
  263             d->xMax = 1.0;
  264             d->yMin = 0.0;
  265             d->yMax = 1.0;
  266 
  267             Axis* axis = new Axis(QLatin1String("x"), Axis::Orientation::Horizontal);
  268             axis->setDefault(true);
  269             axis->setSuppressRetransform(true);
  270             addChild(axis);
  271             axis->setPosition(Axis::Position::Bottom);
  272             axis->setStart(0);
  273             axis->setEnd(1);
  274             axis->setMajorTicksDirection(Axis::ticksBoth);
  275             axis->setMajorTicksNumber(6);
  276             axis->setMinorTicksDirection(Axis::ticksBoth);
  277             axis->setMinorTicksNumber(1);
  278             axis->setArrowType(Axis::ArrowType::FilledSmall);
  279             axis->setSuppressRetransform(false);
  280 
  281             axis = new Axis(QLatin1String("y"), Axis::Orientation::Vertical);
  282             axis->setDefault(true);
  283             axis->setSuppressRetransform(true);
  284             addChild(axis);
  285             axis->setPosition(Axis::Position::Left);
  286             axis->setStart(0);
  287             axis->setEnd(1);
  288             axis->setMajorTicksDirection(Axis::ticksBoth);
  289             axis->setMajorTicksNumber(6);
  290             axis->setMinorTicksDirection(Axis::ticksBoth);
  291             axis->setMinorTicksNumber(1);
  292             axis->setArrowType(Axis::ArrowType::FilledSmall);
  293             axis->setSuppressRetransform(false);
  294 
  295             break;
  296         }
  297     case Type::TwoAxesCentered: {
  298             d->xMin = -0.5;
  299             d->xMax = 0.5;
  300             d->yMin = -0.5;
  301             d->yMax = 0.5;
  302 
  303             d->horizontalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Centimeter);
  304             d->verticalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Centimeter);
  305 
  306             QPen pen = m_plotArea->borderPen();
  307             pen.setStyle(Qt::NoPen);
  308             m_plotArea->setBorderPen(pen);
  309 
  310             Axis* axis = new Axis(QLatin1String("x"), Axis::Orientation::Horizontal);
  311             axis->setDefault(true);
  312             axis->setSuppressRetransform(true);
  313             addChild(axis);
  314             axis->setPosition(Axis::Position::Centered);
  315             axis->setStart(-0.5);
  316             axis->setEnd(0.5);
  317             axis->setMajorTicksDirection(Axis::ticksBoth);
  318             axis->setMajorTicksNumber(6);
  319             axis->setMinorTicksDirection(Axis::ticksBoth);
  320             axis->setMinorTicksNumber(1);
  321             axis->setArrowType(Axis::ArrowType::FilledSmall);
  322             axis->title()->setText(QString());
  323             axis->setSuppressRetransform(false);
  324 
  325             axis = new Axis(QLatin1String("y"), Axis::Orientation::Vertical);
  326             axis->setDefault(true);
  327             axis->setSuppressRetransform(true);
  328             addChild(axis);
  329             axis->setPosition(Axis::Position::Centered);
  330             axis->setStart(-0.5);
  331             axis->setEnd(0.5);
  332             axis->setMajorTicksDirection(Axis::ticksBoth);
  333             axis->setMajorTicksNumber(6);
  334             axis->setMinorTicksDirection(Axis::ticksBoth);
  335             axis->setMinorTicksNumber(1);
  336             axis->setArrowType(Axis::ArrowType::FilledSmall);
  337             axis->title()->setText(QString());
  338             axis->setSuppressRetransform(false);
  339 
  340             break;
  341         }
  342     case Type::TwoAxesCenteredZero: {
  343             d->xMin = -0.5;
  344             d->xMax = 0.5;
  345             d->yMin = -0.5;
  346             d->yMax = 0.5;
  347 
  348             d->horizontalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Centimeter);
  349             d->verticalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Centimeter);
  350 
  351             QPen pen = m_plotArea->borderPen();
  352             pen.setStyle(Qt::NoPen);
  353             m_plotArea->setBorderPen(pen);
  354 
  355             Axis* axis = new Axis(QLatin1String("x"), Axis::Orientation::Horizontal);
  356             axis->setDefault(true);
  357             axis->setSuppressRetransform(true);
  358             addChild(axis);
  359             axis->setPosition(Axis::Position::Custom);
  360             axis->setOffset(0);
  361             axis->setStart(-0.5);
  362             axis->setEnd(0.5);
  363             axis->setMajorTicksDirection(Axis::ticksBoth);
  364             axis->setMajorTicksNumber(6);
  365             axis->setMinorTicksDirection(Axis::ticksBoth);
  366             axis->setMinorTicksNumber(1);
  367             axis->setArrowType(Axis::ArrowType::FilledSmall);
  368             axis->title()->setText(QString());
  369             axis->setSuppressRetransform(false);
  370 
  371             axis = new Axis(QLatin1String("y"), Axis::Orientation::Vertical);
  372             axis->setDefault(true);
  373             axis->setSuppressRetransform(true);
  374             addChild(axis);
  375             axis->setPosition(Axis::Position::Custom);
  376             axis->setOffset(0);
  377             axis->setStart(-0.5);
  378             axis->setEnd(0.5);
  379             axis->setMajorTicksDirection(Axis::ticksBoth);
  380             axis->setMajorTicksNumber(6);
  381             axis->setMinorTicksDirection(Axis::ticksBoth);
  382             axis->setMinorTicksNumber(1);
  383             axis->setArrowType(Axis::ArrowType::FilledSmall);
  384             axis->title()->setText(QString());
  385             axis->setSuppressRetransform(false);
  386 
  387             break;
  388         }
  389     }
  390 
  391     d->xMinPrev = d->xMin;
  392     d->xMaxPrev = d->xMax;
  393     d->yMinPrev = d->yMin;
  394     d->yMaxPrev = d->yMax;
  395 
  396     //Geometry, specify the plot rect in scene coordinates.
  397     //TODO: Use default settings for left, top, width, height and for min/max for the coordinate system
  398     float x = Worksheet::convertToSceneUnits(2, Worksheet::Unit::Centimeter);
  399     float y = Worksheet::convertToSceneUnits(2, Worksheet::Unit::Centimeter);
  400     float w = Worksheet::convertToSceneUnits(10, Worksheet::Unit::Centimeter);
  401     float h = Worksheet::convertToSceneUnits(10, Worksheet::Unit::Centimeter);
  402 
  403     //all plot children are initialized -> set the geometry of the plot in scene coordinates.
  404     d->rect = QRectF(x,y,w,h);
  405     d->retransform();
  406 }
  407 
  408 CartesianPlot::Type CartesianPlot::type() const {
  409     Q_D(const CartesianPlot);
  410     return d->type;
  411 }
  412 
  413 void CartesianPlot::initActions() {
  414     //"add new" actions
  415     addCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve"), this);
  416     addHistogramAction = new QAction(QIcon::fromTheme("view-object-histogram-linear"), i18n("Histogram"), this);
  417     addEquationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-equation-curve"), i18n("xy-curve from a mathematical Equation"), this);
  418 // no icons yet
  419     addDataReductionCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Data Reduction"), this);
  420     addDifferentiationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Differentiation"), this);
  421     addIntegrationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Integration"), this);
  422     addInterpolationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-interpolation-curve"), i18n("Interpolation"), this);
  423     addSmoothCurveAction = new QAction(QIcon::fromTheme("labplot-xy-smoothing-curve"), i18n("Smooth"), this);
  424     addFitCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Fit"), this);
  425     addFourierFilterCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-filter-curve"), i18n("Fourier Filter"), this);
  426     addFourierTransformCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-transform-curve"), i18n("Fourier Transform"), this);
  427     addConvolutionCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"),i18n("(De-)Convolution"), this);
  428     addCorrelationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"),i18n("Auto-/Cross-Correlation"), this);
  429 
  430     addLegendAction = new QAction(QIcon::fromTheme("text-field"), i18n("Legend"), this);
  431     if (children<CartesianPlotLegend>().size()>0)
  432         addLegendAction->setEnabled(false); //only one legend is allowed -> disable the action
  433 
  434     addHorizontalAxisAction = new QAction(QIcon::fromTheme("labplot-axis-horizontal"), i18n("Horizontal Axis"), this);
  435     addVerticalAxisAction = new QAction(QIcon::fromTheme("labplot-axis-vertical"), i18n("Vertical Axis"), this);
  436     addTextLabelAction = new QAction(QIcon::fromTheme("draw-text"), i18n("Text Label"), this);
  437     addImageAction = new QAction(QIcon::fromTheme("viewimage"), i18n("Image"), this);
  438     addCustomPointAction = new QAction(QIcon::fromTheme("draw-cross"), i18n("Custom Point"), this);
  439     addReferenceLineAction = new QAction(QIcon::fromTheme("draw-line"), i18n("Reference Line"), this);
  440 
  441     connect(addCurveAction, &QAction::triggered, this, &CartesianPlot::addCurve);
  442     connect(addHistogramAction,&QAction::triggered, this, &CartesianPlot::addHistogram);
  443     connect(addEquationCurveAction, &QAction::triggered, this, &CartesianPlot::addEquationCurve);
  444     connect(addDataReductionCurveAction, &QAction::triggered, this, &CartesianPlot::addDataReductionCurve);
  445     connect(addDifferentiationCurveAction, &QAction::triggered, this, &CartesianPlot::addDifferentiationCurve);
  446     connect(addIntegrationCurveAction, &QAction::triggered, this, &CartesianPlot::addIntegrationCurve);
  447     connect(addInterpolationCurveAction, &QAction::triggered, this, &CartesianPlot::addInterpolationCurve);
  448     connect(addSmoothCurveAction, &QAction::triggered, this, &CartesianPlot::addSmoothCurve);
  449     connect(addFitCurveAction, &QAction::triggered, this, &CartesianPlot::addFitCurve);
  450     connect(addFourierFilterCurveAction, &QAction::triggered, this, &CartesianPlot::addFourierFilterCurve);
  451     connect(addFourierTransformCurveAction, &QAction::triggered, this, &CartesianPlot::addFourierTransformCurve);
  452     connect(addConvolutionCurveAction, &QAction::triggered, this, &CartesianPlot::addConvolutionCurve);
  453     connect(addCorrelationCurveAction, &QAction::triggered, this, &CartesianPlot::addCorrelationCurve);
  454 
  455     connect(addLegendAction, &QAction::triggered, this, static_cast<void (CartesianPlot::*)()>(&CartesianPlot::addLegend));
  456     connect(addHorizontalAxisAction, &QAction::triggered, this, &CartesianPlot::addHorizontalAxis);
  457     connect(addVerticalAxisAction, &QAction::triggered, this, &CartesianPlot::addVerticalAxis);
  458     connect(addTextLabelAction, &QAction::triggered, this, &CartesianPlot::addTextLabel);
  459     connect(addImageAction, &QAction::triggered, this, &CartesianPlot::addImage);
  460     connect(addCustomPointAction, &QAction::triggered, this, &CartesianPlot::addCustomPoint);
  461     connect(addReferenceLineAction, &QAction::triggered, this, &CartesianPlot::addReferenceLine);
  462 
  463     //Analysis menu actions
  464 //  addDataOperationAction = new QAction(i18n("Data Operation"), this);
  465     addDataReductionAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Data Reduction"), this);
  466     addDifferentiationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Differentiate"), this);
  467     addIntegrationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Integrate"), this);
  468     addInterpolationAction = new QAction(QIcon::fromTheme("labplot-xy-interpolation-curve"), i18n("Interpolate"), this);
  469     addSmoothAction = new QAction(QIcon::fromTheme("labplot-xy-smoothing-curve"), i18n("Smooth"), this);
  470     addConvolutionAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Convolute/Deconvolute"), this);
  471     addCorrelationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Auto-/Cross-Correlation"), this);
  472 
  473     QAction* fitAction = new QAction(i18n("Linear"), this);
  474     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitLinear));
  475     addFitAction.append(fitAction);
  476 
  477     fitAction = new QAction(i18n("Power"), this);
  478     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitPower));
  479     addFitAction.append(fitAction);
  480 
  481     fitAction = new QAction(i18n("Exponential (degree 1)"), this);
  482     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitExp1));
  483     addFitAction.append(fitAction);
  484 
  485     fitAction = new QAction(i18n("Exponential (degree 2)"), this);
  486     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitExp2));
  487     addFitAction.append(fitAction);
  488 
  489     fitAction = new QAction(i18n("Inverse exponential"), this);
  490     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitInvExp));
  491     addFitAction.append(fitAction);
  492 
  493     fitAction = new QAction(i18n("Gauss"), this);
  494     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitGauss));
  495     addFitAction.append(fitAction);
  496 
  497     fitAction = new QAction(i18n("Cauchy-Lorentz"), this);
  498     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitCauchyLorentz));
  499     addFitAction.append(fitAction);
  500 
  501     fitAction = new QAction(i18n("Arc Tangent"), this);
  502     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitTan));
  503     addFitAction.append(fitAction);
  504 
  505     fitAction = new QAction(i18n("Hyperbolic Tangent"), this);
  506     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitTanh));
  507     addFitAction.append(fitAction);
  508 
  509     fitAction = new QAction(i18n("Error Function"), this);
  510     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitErrFunc));
  511     addFitAction.append(fitAction);
  512 
  513     fitAction = new QAction(i18n("Custom"), this);
  514     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitCustom));
  515     addFitAction.append(fitAction);
  516 
  517     addFourierFilterAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-filter-curve"), i18n("Fourier Filter"), this);
  518     addFourierTransformAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-transform-curve"), i18n("Fourier Transform"), this);
  519 
  520     connect(addDataReductionAction, &QAction::triggered, this, &CartesianPlot::addDataReductionCurve);
  521     connect(addDifferentiationAction, &QAction::triggered, this, &CartesianPlot::addDifferentiationCurve);
  522     connect(addIntegrationAction, &QAction::triggered, this, &CartesianPlot::addIntegrationCurve);
  523     connect(addInterpolationAction, &QAction::triggered, this, &CartesianPlot::addInterpolationCurve);
  524     connect(addSmoothAction, &QAction::triggered, this, &CartesianPlot::addSmoothCurve);
  525     connect(addConvolutionAction, &QAction::triggered, this, &CartesianPlot::addConvolutionCurve);
  526     connect(addCorrelationAction, &QAction::triggered, this, &CartesianPlot::addCorrelationCurve);
  527     for (const auto& action : addFitAction)
  528         connect(action, &QAction::triggered, this, &CartesianPlot::addFitCurve);
  529     connect(addFourierFilterAction, &QAction::triggered, this, &CartesianPlot::addFourierFilterCurve);
  530     connect(addFourierTransformAction, &QAction::triggered, this, &CartesianPlot::addFourierTransformCurve);
  531 
  532     //zoom/navigate actions
  533     scaleAutoAction = new QAction(QIcon::fromTheme("labplot-auto-scale-all"), i18n("Auto Scale"), this);
  534     scaleAutoXAction = new QAction(QIcon::fromTheme("labplot-auto-scale-x"), i18n("Auto Scale X"), this);
  535     scaleAutoYAction = new QAction(QIcon::fromTheme("labplot-auto-scale-y"), i18n("Auto Scale Y"), this);
  536     zoomInAction = new QAction(QIcon::fromTheme("zoom-in"), i18n("Zoom In"), this);
  537     zoomOutAction = new QAction(QIcon::fromTheme("zoom-out"), i18n("Zoom Out"), this);
  538     zoomInXAction = new QAction(QIcon::fromTheme("labplot-zoom-in-x"), i18n("Zoom In X"), this);
  539     zoomOutXAction = new QAction(QIcon::fromTheme("labplot-zoom-out-x"), i18n("Zoom Out X"), this);
  540     zoomInYAction = new QAction(QIcon::fromTheme("labplot-zoom-in-y"), i18n("Zoom In Y"), this);
  541     zoomOutYAction = new QAction(QIcon::fromTheme("labplot-zoom-out-y"), i18n("Zoom Out Y"), this);
  542     shiftLeftXAction = new QAction(QIcon::fromTheme("labplot-shift-left-x"), i18n("Shift Left X"), this);
  543     shiftRightXAction = new QAction(QIcon::fromTheme("labplot-shift-right-x"), i18n("Shift Right X"), this);
  544     shiftUpYAction = new QAction(QIcon::fromTheme("labplot-shift-up-y"), i18n("Shift Up Y"), this);
  545     shiftDownYAction = new QAction(QIcon::fromTheme("labplot-shift-down-y"), i18n("Shift Down Y"), this);
  546 
  547     connect(scaleAutoAction, &QAction::triggered, this, &CartesianPlot::scaleAutoTriggered);
  548     connect(scaleAutoXAction, &QAction::triggered, this, &CartesianPlot::scaleAutoTriggered);
  549     connect(scaleAutoYAction, &QAction::triggered, this, &CartesianPlot::scaleAutoTriggered);
  550     connect(zoomInAction, &QAction::triggered, this, &CartesianPlot::zoomIn);
  551     connect(zoomOutAction, &QAction::triggered, this, &CartesianPlot::zoomOut);
  552     connect(zoomInXAction, &QAction::triggered, this, &CartesianPlot::zoomInX);
  553     connect(zoomOutXAction, &QAction::triggered, this, &CartesianPlot::zoomOutX);
  554     connect(zoomInYAction, &QAction::triggered, this, &CartesianPlot::zoomInY);
  555     connect(zoomOutYAction, &QAction::triggered, this, &CartesianPlot::zoomOutY);
  556     connect(shiftLeftXAction, &QAction::triggered, this, &CartesianPlot::shiftLeftX);
  557     connect(shiftRightXAction, &QAction::triggered, this, &CartesianPlot::shiftRightX);
  558     connect(shiftUpYAction, &QAction::triggered, this, &CartesianPlot::shiftUpY);
  559     connect(shiftDownYAction, &QAction::triggered, this, &CartesianPlot::shiftDownY);
  560 
  561     //visibility action
  562     visibilityAction = new QAction(QIcon::fromTheme("view-visible"), i18n("Visible"), this);
  563     visibilityAction->setCheckable(true);
  564     connect(visibilityAction, &QAction::triggered, this, &CartesianPlot::visibilityChanged);
  565 }
  566 
  567 void CartesianPlot::initMenus() {
  568     initActions();
  569 
  570     addNewMenu = new QMenu(i18n("Add New"));
  571     addNewMenu->setIcon(QIcon::fromTheme("list-add"));
  572     addNewMenu->addAction(addCurveAction);
  573     addNewMenu->addAction(addHistogramAction);
  574     addNewMenu->addAction(addEquationCurveAction);
  575     addNewMenu->addSeparator();
  576 
  577     addNewAnalysisMenu = new QMenu(i18n("Analysis Curve"));
  578     addNewAnalysisMenu->addAction(addFitCurveAction);
  579     addNewAnalysisMenu->addSeparator();
  580     addNewAnalysisMenu->addAction(addDifferentiationCurveAction);
  581     addNewAnalysisMenu->addAction(addIntegrationCurveAction);
  582     addNewAnalysisMenu->addSeparator();
  583     addNewAnalysisMenu->addAction(addInterpolationCurveAction);
  584     addNewAnalysisMenu->addAction(addSmoothCurveAction);
  585     addNewAnalysisMenu->addSeparator();
  586     addNewAnalysisMenu->addAction(addFourierFilterCurveAction);
  587     addNewAnalysisMenu->addAction(addFourierTransformCurveAction);
  588     addNewAnalysisMenu->addSeparator();
  589     addNewAnalysisMenu->addAction(addConvolutionCurveAction);
  590     addNewAnalysisMenu->addAction(addCorrelationCurveAction);
  591     addNewAnalysisMenu->addSeparator();
  592     addNewAnalysisMenu->addAction(addDataReductionCurveAction);
  593     addNewMenu->addMenu(addNewAnalysisMenu);
  594 
  595     addNewMenu->addSeparator();
  596     addNewMenu->addAction(addLegendAction);
  597     addNewMenu->addSeparator();
  598     addNewMenu->addAction(addHorizontalAxisAction);
  599     addNewMenu->addAction(addVerticalAxisAction);
  600     addNewMenu->addSeparator();
  601     addNewMenu->addAction(addTextLabelAction);
  602     addNewMenu->addAction(addImageAction);
  603     addNewMenu->addSeparator();
  604     addNewMenu->addAction(addCustomPointAction);
  605     addNewMenu->addAction(addReferenceLineAction);
  606 
  607     zoomMenu = new QMenu(i18n("Zoom/Navigate"));
  608     zoomMenu->setIcon(QIcon::fromTheme("zoom-draw"));
  609     zoomMenu->addAction(scaleAutoAction);
  610     zoomMenu->addAction(scaleAutoXAction);
  611     zoomMenu->addAction(scaleAutoYAction);
  612     zoomMenu->addSeparator();
  613     zoomMenu->addAction(zoomInAction);
  614     zoomMenu->addAction(zoomOutAction);
  615     zoomMenu->addSeparator();
  616     zoomMenu->addAction(zoomInXAction);
  617     zoomMenu->addAction(zoomOutXAction);
  618     zoomMenu->addSeparator();
  619     zoomMenu->addAction(zoomInYAction);
  620     zoomMenu->addAction(zoomOutYAction);
  621     zoomMenu->addSeparator();
  622     zoomMenu->addAction(shiftLeftXAction);
  623     zoomMenu->addAction(shiftRightXAction);
  624     zoomMenu->addSeparator();
  625     zoomMenu->addAction(shiftUpYAction);
  626     zoomMenu->addAction(shiftDownYAction);
  627 
  628     // Data manipulation menu
  629 //  QMenu* dataManipulationMenu = new QMenu(i18n("Data Manipulation"));
  630 //  dataManipulationMenu->setIcon(QIcon::fromTheme("zoom-draw"));
  631 //  dataManipulationMenu->addAction(addDataOperationAction);
  632 //  dataManipulationMenu->addAction(addDataReductionAction);
  633 
  634     // Data fit menu
  635     QMenu* dataFitMenu = new QMenu(i18n("Fit"));
  636     dataFitMenu->setIcon(QIcon::fromTheme("labplot-xy-fit-curve"));
  637     dataFitMenu->addAction(addFitAction.at(0));
  638     dataFitMenu->addAction(addFitAction.at(1));
  639     dataFitMenu->addAction(addFitAction.at(2));
  640     dataFitMenu->addAction(addFitAction.at(3));
  641     dataFitMenu->addAction(addFitAction.at(4));
  642     dataFitMenu->addSeparator();
  643     dataFitMenu->addAction(addFitAction.at(5));
  644     dataFitMenu->addAction(addFitAction.at(6));
  645     dataFitMenu->addSeparator();
  646     dataFitMenu->addAction(addFitAction.at(7));
  647     dataFitMenu->addAction(addFitAction.at(8));
  648     dataFitMenu->addAction(addFitAction.at(9));
  649     dataFitMenu->addSeparator();
  650     dataFitMenu->addAction(addFitAction.at(10));
  651 
  652     //analysis menu
  653     dataAnalysisMenu = new QMenu(i18n("Analysis"));
  654     dataAnalysisMenu->addMenu(dataFitMenu);
  655     dataAnalysisMenu->addSeparator();
  656     dataAnalysisMenu->addAction(addDifferentiationAction);
  657     dataAnalysisMenu->addAction(addIntegrationAction);
  658     dataAnalysisMenu->addSeparator();
  659     dataAnalysisMenu->addAction(addInterpolationAction);
  660     dataAnalysisMenu->addAction(addSmoothAction);
  661     dataAnalysisMenu->addSeparator();
  662     dataAnalysisMenu->addAction(addFourierFilterAction);
  663     dataAnalysisMenu->addAction(addFourierTransformAction);
  664     dataAnalysisMenu->addSeparator();
  665     dataAnalysisMenu->addAction(addConvolutionAction);
  666     dataAnalysisMenu->addAction(addCorrelationAction);
  667     dataAnalysisMenu->addSeparator();
  668 //  dataAnalysisMenu->insertMenu(nullptr, dataManipulationMenu);
  669     dataAnalysisMenu->addAction(addDataReductionAction);
  670 
  671     //themes menu
  672     themeMenu = new QMenu(i18n("Apply Theme"));
  673     themeMenu->setIcon(QIcon::fromTheme("color-management"));
  674     auto* themeWidget = new ThemesWidget(nullptr);
  675     themeWidget->setFixedMode();
  676     connect(themeWidget, &ThemesWidget::themeSelected, this, &CartesianPlot::loadTheme);
  677     connect(themeWidget, &ThemesWidget::themeSelected, themeMenu, &QMenu::close);
  678 
  679     auto* widgetAction = new QWidgetAction(this);
  680     widgetAction->setDefaultWidget(themeWidget);
  681     themeMenu->addAction(widgetAction);
  682 
  683     m_menusInitialized = true;
  684 }
  685 
  686 QMenu* CartesianPlot::createContextMenu() {
  687     if (!m_menusInitialized)
  688         initMenus();
  689 
  690     QMenu* menu = WorksheetElement::createContextMenu();
  691     QAction* firstAction = menu->actions().at(1);
  692 
  693 
  694     menu->insertMenu(firstAction, addNewMenu);
  695     menu->insertSeparator(firstAction);
  696     menu->insertMenu(firstAction, zoomMenu);
  697     menu->insertSeparator(firstAction);
  698     menu->insertMenu(firstAction, themeMenu);
  699     menu->insertSeparator(firstAction);
  700 
  701     visibilityAction->setChecked(isVisible());
  702     menu->insertAction(firstAction, visibilityAction);
  703 
  704     return menu;
  705 }
  706 
  707 QMenu* CartesianPlot::analysisMenu() {
  708     if (!m_menusInitialized)
  709         initMenus();
  710 
  711     return dataAnalysisMenu;
  712 }
  713 
  714 /*!
  715     Returns an icon to be used in the project explorer.
  716 */
  717 QIcon CartesianPlot::icon() const {
  718     return QIcon::fromTheme("office-chart-line");
  719 }
  720 
  721 QVector<AbstractAspect*> CartesianPlot::dependsOn() const {
  722     //aspects which the plotted data in the worksheet depends on (spreadsheets and later matrices)
  723     QVector<AbstractAspect*> aspects;
  724 
  725     for (const auto* curve : children<XYCurve>()) {
  726         if (curve->xColumn() && dynamic_cast<Spreadsheet*>(curve->xColumn()->parentAspect()) )
  727             aspects << curve->xColumn()->parentAspect();
  728 
  729         if (curve->yColumn() && dynamic_cast<Spreadsheet*>(curve->yColumn()->parentAspect()) )
  730             aspects << curve->yColumn()->parentAspect();
  731     }
  732 
  733     return aspects;
  734 }
  735 
  736 void CartesianPlot::navigate(NavigationOperation op) {
  737     Q_D(CartesianPlot);
  738     if (op == NavigationOperation::ScaleAuto) {
  739         if (d->curvesXMinMaxIsDirty || d->curvesYMinMaxIsDirty || !autoScaleX() || !autoScaleY()) {
  740             d->curvesXMinMaxIsDirty = true;
  741             d->curvesYMinMaxIsDirty = true;
  742         }
  743         scaleAuto();
  744     } else if (op == NavigationOperation::ScaleAutoX) setAutoScaleX(true);
  745     else if (op == NavigationOperation::ScaleAutoY) setAutoScaleY(true);
  746     else if (op == NavigationOperation::ZoomIn) zoomIn();
  747     else if (op == NavigationOperation::ZoomOut) zoomOut();
  748     else if (op == NavigationOperation::ZoomInX) zoomInX();
  749     else if (op == NavigationOperation::ZoomOutX) zoomOutX();
  750     else if (op == NavigationOperation::ZoomInY) zoomInY();
  751     else if (op == NavigationOperation::ZoomOutY) zoomOutY();
  752     else if (op == NavigationOperation::ShiftLeftX) shiftLeftX();
  753     else if (op == NavigationOperation::ShiftRightX) shiftRightX();
  754     else if (op == NavigationOperation::ShiftUpY) shiftUpY();
  755     else if (op == NavigationOperation::ShiftDownY) shiftDownY();
  756 }
  757 
  758 void CartesianPlot::setSuppressDataChangedSignal(bool value) {
  759     Q_D(CartesianPlot);
  760     d->suppressRetransform = value;
  761 }
  762 
  763 void CartesianPlot::processDropEvent(const QVector<quintptr>& vec) {
  764     PERFTRACE("CartesianPlot::processDropEvent");
  765 
  766     QVector<AbstractColumn*> columns;
  767     for (auto a : vec) {
  768         auto* aspect = (AbstractAspect*)a;
  769         auto* column = dynamic_cast<AbstractColumn*>(aspect);
  770         if (column)
  771             columns << column;
  772     }
  773 
  774     //return if there are no columns being dropped.
  775     //TODO: extend this later when we allow to drag&drop plots, etc.
  776     if (columns.isEmpty())
  777         return;
  778 
  779     //determine the first column with "x plot designation" as the x-data column for all curves to be created
  780     const AbstractColumn* xColumn = nullptr;
  781     for (const auto* column : columns) {
  782         if (column->plotDesignation() == AbstractColumn::PlotDesignation::X) {
  783             xColumn = column;
  784             break;
  785         }
  786     }
  787 
  788     //if no column with "x plot designation" is available, use the x-data column of the first curve in the plot,
  789     if (xColumn == nullptr) {
  790         QVector<XYCurve*> curves = children<XYCurve>();
  791         if (!curves.isEmpty())
  792             xColumn = curves.at(0)->xColumn();
  793     }
  794 
  795     //use the first dropped column if no column with "x plot designation" nor curves are available
  796     if (xColumn == nullptr)
  797         xColumn = columns.at(0);
  798 
  799     //create curves
  800     bool curvesAdded = false;
  801     for (const auto* column : columns) {
  802         if (column == xColumn)
  803             continue;
  804 
  805         XYCurve* curve = new XYCurve(column->name());
  806         curve->suppressRetransform(true); //suppress retransform, all curved will be recalculated at the end
  807         curve->setXColumn(xColumn);
  808         curve->setYColumn(column);
  809         addChild(curve);
  810         curve->suppressRetransform(false);
  811         curvesAdded = true;
  812     }
  813 
  814     if (curvesAdded)
  815         dataChanged();
  816 }
  817 
  818 bool CartesianPlot::isPanningActive() const {
  819     Q_D(const CartesianPlot);
  820     return d->panningStarted;
  821 }
  822 
  823 bool CartesianPlot::isHovered() const {
  824     Q_D(const CartesianPlot);
  825     return d->m_hovered;
  826 }
  827 bool CartesianPlot::isPrinted() const {
  828     Q_D(const CartesianPlot);
  829     return d->m_printing;
  830 }
  831 
  832 bool CartesianPlot::isSelected() const {
  833     Q_D(const CartesianPlot);
  834     return d->isSelected();
  835 }
  836 
  837 //##############################################################################
  838 //################################  getter methods  ############################
  839 //##############################################################################
  840 BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeType, rangeType, rangeType)
  841 BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeFormat, xRangeFormat, xRangeFormat)
  842 BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeFormat, yRangeFormat, yRangeFormat)
  843 BASIC_SHARED_D_READER_IMPL(CartesianPlot, int, rangeLastValues, rangeLastValues)
  844 BASIC_SHARED_D_READER_IMPL(CartesianPlot, int, rangeFirstValues, rangeFirstValues)
  845 BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, autoScaleX, autoScaleX)
  846 BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, xMin, xMin)
  847 BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, xMax, xMax)
  848 BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::Scale, xScale, xScale)
  849 BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, xRangeBreakingEnabled, xRangeBreakingEnabled)
  850 CLASS_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeBreaks, xRangeBreaks, xRangeBreaks)
  851 
  852 BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, autoScaleY, autoScaleY)
  853 BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, yMin, yMin)
  854 BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, yMax, yMax)
  855 BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::Scale, yScale, yScale)
  856 BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, yRangeBreakingEnabled, yRangeBreakingEnabled)
  857 CLASS_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeBreaks, yRangeBreaks, yRangeBreaks)
  858 
  859 CLASS_SHARED_D_READER_IMPL(CartesianPlot, QPen, cursorPen, cursorPen);
  860 CLASS_SHARED_D_READER_IMPL(CartesianPlot, bool, cursor0Enable, cursor0Enable);
  861 CLASS_SHARED_D_READER_IMPL(CartesianPlot, bool, cursor1Enable, cursor1Enable);
  862 CLASS_SHARED_D_READER_IMPL(CartesianPlot, QString, theme, theme)
  863 
  864 /*!
  865     returns the actual bounding rectangular of the plot area showing data (plot's rectangular minus padding)
  866     in plot's coordinates
  867  */
  868 QRectF CartesianPlot::dataRect() const {
  869     Q_D(const CartesianPlot);
  870     return d->dataRect;
  871 }
  872 
  873 CartesianPlot::MouseMode CartesianPlot::mouseMode() const {
  874     Q_D(const CartesianPlot);
  875     return d->mouseMode;
  876 }
  877 
  878 const QString& CartesianPlot::xRangeDateTimeFormat() const {
  879     Q_D(const CartesianPlot);
  880     return d->xRangeDateTimeFormat;
  881 }
  882 
  883 const QString& CartesianPlot::yRangeDateTimeFormat() const {
  884     Q_D(const CartesianPlot);
  885     return d->yRangeDateTimeFormat;
  886 }
  887 
  888 //##############################################################################
  889 //######################  setter methods and undo commands  ####################
  890 //##############################################################################
  891 /*!
  892     set the rectangular, defined in scene coordinates
  893  */
  894 class CartesianPlotSetRectCmd : public QUndoCommand {
  895 public:
  896     CartesianPlotSetRectCmd(CartesianPlotPrivate* private_obj, QRectF rect) : m_private(private_obj), m_rect(rect) {
  897         setText(i18n("%1: change geometry rect", m_private->name()));
  898     };
  899 
  900     void redo() override {
  901 //      const double horizontalRatio = m_rect.width() / m_private->rect.width();
  902 //      const double verticalRatio = m_rect.height() / m_private->rect.height();
  903 
  904         qSwap(m_private->rect, m_rect);
  905 
  906 //      m_private->q->handleResize(horizontalRatio, verticalRatio, false);
  907         m_private->retransform();
  908         emit m_private->q->rectChanged(m_private->rect);
  909     };
  910 
  911     void undo() override {
  912         redo();
  913     }
  914 
  915 private:
  916     CartesianPlotPrivate* m_private;
  917     QRectF m_rect;
  918 };
  919 
  920 void CartesianPlot::setRect(const QRectF& rect) {
  921     Q_D(CartesianPlot);
  922     if (rect != d->rect)
  923         exec(new CartesianPlotSetRectCmd(d, rect));
  924 }
  925 
  926 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeType, CartesianPlot::RangeType, rangeType, rangeChanged);
  927 void CartesianPlot::setRangeType(RangeType type) {
  928     Q_D(CartesianPlot);
  929     if (type != d->rangeType)
  930         exec(new CartesianPlotSetRangeTypeCmd(d, type, ki18n("%1: set range type")));
  931 }
  932 
  933 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeFormat, CartesianPlot::RangeFormat, xRangeFormat, xRangeFormatChanged);
  934 void CartesianPlot::setXRangeFormat(RangeFormat format) {
  935     Q_D(CartesianPlot);
  936     if (format != d->xRangeFormat)
  937         exec(new CartesianPlotSetXRangeFormatCmd(d, format, ki18n("%1: set x-range format")));
  938 }
  939 
  940 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeFormat, CartesianPlot::RangeFormat, yRangeFormat, yRangeFormatChanged);
  941 void CartesianPlot::setYRangeFormat(RangeFormat format) {
  942     Q_D(CartesianPlot);
  943     if (format != d->yRangeFormat)
  944         exec(new CartesianPlotSetYRangeFormatCmd(d, format, ki18n("%1: set y-range format")));
  945 }
  946 
  947 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeLastValues, int, rangeLastValues, rangeChanged);
  948 void CartesianPlot::setRangeLastValues(int values) {
  949     Q_D(CartesianPlot);
  950     if (values != d->rangeLastValues)
  951         exec(new CartesianPlotSetRangeLastValuesCmd(d, values, ki18n("%1: set range")));
  952 }
  953 
  954 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeFirstValues, int, rangeFirstValues, rangeChanged);
  955 void CartesianPlot::setRangeFirstValues(int values) {
  956     Q_D(CartesianPlot);
  957     if (values != d->rangeFirstValues)
  958         exec(new CartesianPlotSetRangeFirstValuesCmd(d, values, ki18n("%1: set range")));
  959 }
  960 
  961 
  962 class CartesianPlotSetAutoScaleXCmd : public QUndoCommand {
  963 public:
  964     CartesianPlotSetAutoScaleXCmd(CartesianPlotPrivate* private_obj, bool autoScale) :
  965         m_private(private_obj), m_autoScale(autoScale), m_autoScaleOld(false), m_minOld(0.0), m_maxOld(0.0) {
  966         setText(i18n("%1: change x-range auto scaling", m_private->name()));
  967     };
  968 
  969     void redo() override {
  970         m_autoScaleOld = m_private->autoScaleX;
  971         if (m_autoScale) {
  972             m_minOld = m_private->xMin;
  973             m_maxOld = m_private->xMax;
  974             m_private->q->scaleAutoX();
  975         }
  976         m_private->autoScaleX = m_autoScale;
  977         emit m_private->q->xAutoScaleChanged(m_autoScale);
  978     };
  979 
  980     void undo() override {
  981         if (!m_autoScaleOld) {
  982             m_private->xMin = m_minOld;
  983             m_private->xMax = m_maxOld;
  984             m_private->retransformScales();
  985         }
  986         m_private->autoScaleX = m_autoScaleOld;
  987         emit m_private->q->xAutoScaleChanged(m_autoScaleOld);
  988     }
  989 
  990 private:
  991     CartesianPlotPrivate* m_private;
  992     bool m_autoScale;
  993     bool m_autoScaleOld;
  994     double m_minOld;
  995     double m_maxOld;
  996 };
  997 
  998 void CartesianPlot::setAutoScaleX(bool autoScaleX) {
  999     Q_D(CartesianPlot);
 1000     if (autoScaleX != d->autoScaleX)
 1001         exec(new CartesianPlotSetAutoScaleXCmd(d, autoScaleX));
 1002 }
 1003 
 1004 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMin, double, xMin, retransformScales)
 1005 void CartesianPlot::setXMin(double xMin) {
 1006     Q_D(CartesianPlot);
 1007     if (xMin != d->xMin && xMin != -INFINITY && xMin != INFINITY) {
 1008         d->curvesYMinMaxIsDirty = true;
 1009         exec(new CartesianPlotSetXMinCmd(d, xMin, ki18n("%1: set min x")));
 1010         if (d->autoScaleY)
 1011             scaleAutoY();
 1012     }
 1013 }
 1014 
 1015 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMax, double, xMax, retransformScales)
 1016 void CartesianPlot::setXMax(double xMax) {
 1017     Q_D(CartesianPlot);
 1018     if (xMax != d->xMax && xMax != -INFINITY && xMax != INFINITY) {
 1019         d->curvesYMinMaxIsDirty = true;
 1020         exec(new CartesianPlotSetXMaxCmd(d, xMax, ki18n("%1: set max x")));
 1021         if (d->autoScaleY)
 1022             scaleAutoY();
 1023     }
 1024 }
 1025 
 1026 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXScale, CartesianPlot::Scale, xScale, retransformScales)
 1027 void CartesianPlot::setXScale(Scale scale) {
 1028     Q_D(CartesianPlot);
 1029     if (scale != d->xScale)
 1030         exec(new CartesianPlotSetXScaleCmd(d, scale, ki18n("%1: set x scale")));
 1031 }
 1032 
 1033 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeBreakingEnabled, bool, xRangeBreakingEnabled, retransformScales)
 1034 void CartesianPlot::setXRangeBreakingEnabled(bool enabled) {
 1035     Q_D(CartesianPlot);
 1036     if (enabled != d->xRangeBreakingEnabled)
 1037         exec(new CartesianPlotSetXRangeBreakingEnabledCmd(d, enabled, ki18n("%1: x-range breaking enabled")));
 1038 }
 1039 
 1040 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeBreaks, CartesianPlot::RangeBreaks, xRangeBreaks, retransformScales)
 1041 void CartesianPlot::setXRangeBreaks(const RangeBreaks& breakings) {
 1042     Q_D(CartesianPlot);
 1043     exec(new CartesianPlotSetXRangeBreaksCmd(d, breakings, ki18n("%1: x-range breaks changed")));
 1044 }
 1045 
 1046 class CartesianPlotSetAutoScaleYCmd : public QUndoCommand {
 1047 public:
 1048     CartesianPlotSetAutoScaleYCmd(CartesianPlotPrivate* private_obj, bool autoScale) :
 1049         m_private(private_obj), m_autoScale(autoScale), m_autoScaleOld(false), m_minOld(0.0), m_maxOld(0.0) {
 1050         setText(i18n("%1: change y-range auto scaling", m_private->name()));
 1051     };
 1052 
 1053     void redo() override {
 1054         m_autoScaleOld = m_private->autoScaleY;
 1055         if (m_autoScale) {
 1056             m_minOld = m_private->yMin;
 1057             m_maxOld = m_private->yMax;
 1058             m_private->q->scaleAutoY();
 1059         }
 1060         m_private->autoScaleY = m_autoScale;
 1061         emit m_private->q->yAutoScaleChanged(m_autoScale);
 1062     };
 1063 
 1064     void undo() override {
 1065         if (!m_autoScaleOld) {
 1066             m_private->yMin = m_minOld;
 1067             m_private->yMax = m_maxOld;
 1068             m_private->retransformScales();
 1069         }
 1070         m_private->autoScaleY = m_autoScaleOld;
 1071         emit m_private->q->yAutoScaleChanged(m_autoScaleOld);
 1072     }
 1073 
 1074 private:
 1075     CartesianPlotPrivate* m_private;
 1076     bool m_autoScale;
 1077     bool m_autoScaleOld;
 1078     double m_minOld;
 1079     double m_maxOld;
 1080 };
 1081 
 1082 void CartesianPlot::setAutoScaleY(bool autoScaleY) {
 1083     Q_D(CartesianPlot);
 1084     if (autoScaleY != d->autoScaleY)
 1085         exec(new CartesianPlotSetAutoScaleYCmd(d, autoScaleY));
 1086 }
 1087 
 1088 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYMin, double, yMin, retransformScales)
 1089 void CartesianPlot::setYMin(double yMin) {
 1090     Q_D(CartesianPlot);
 1091     if (yMin != d->yMin) {
 1092         d->curvesXMinMaxIsDirty = true;
 1093         exec(new CartesianPlotSetYMinCmd(d, yMin, ki18n("%1: set min y")));
 1094         if (d->autoScaleX)
 1095             scaleAutoX();
 1096     }
 1097 }
 1098 
 1099 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYMax, double, yMax, retransformScales)
 1100 void CartesianPlot::setYMax(double yMax) {
 1101     Q_D(CartesianPlot);
 1102     if (yMax != d->yMax) {
 1103         d->curvesXMinMaxIsDirty = true;
 1104         exec(new CartesianPlotSetYMaxCmd(d, yMax, ki18n("%1: set max y")));
 1105         if (d->autoScaleX)
 1106             scaleAutoX();
 1107     }
 1108 }
 1109 
 1110 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYScale, CartesianPlot::Scale, yScale, retransformScales)
 1111 void CartesianPlot::setYScale(Scale scale) {
 1112     Q_D(CartesianPlot);
 1113     if (scale != d->yScale)
 1114         exec(new CartesianPlotSetYScaleCmd(d, scale, ki18n("%1: set y scale")));
 1115 }
 1116 
 1117 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeBreakingEnabled, bool, yRangeBreakingEnabled, retransformScales)
 1118 void CartesianPlot::setYRangeBreakingEnabled(bool enabled) {
 1119     Q_D(CartesianPlot);
 1120     if (enabled != d->yRangeBreakingEnabled)
 1121         exec(new CartesianPlotSetYRangeBreakingEnabledCmd(d, enabled, ki18n("%1: y-range breaking enabled")));
 1122 }
 1123 
 1124 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeBreaks, CartesianPlot::RangeBreaks, yRangeBreaks, retransformScales)
 1125 void CartesianPlot::setYRangeBreaks(const RangeBreaks& breaks) {
 1126     Q_D(CartesianPlot);
 1127     exec(new CartesianPlotSetYRangeBreaksCmd(d, breaks, ki18n("%1: y-range breaks changed")));
 1128 }
 1129 
 1130 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetCursorPen, QPen, cursorPen, update)
 1131 void CartesianPlot::setCursorPen(const QPen &pen) {
 1132     Q_D(CartesianPlot);
 1133     if (pen != d->cursorPen)
 1134         exec(new CartesianPlotSetCursorPenCmd(d, pen, ki18n("%1: y-range breaks changed")));
 1135 }
 1136 
 1137 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetCursor0Enable, bool, cursor0Enable, updateCursor)
 1138 void CartesianPlot::setCursor0Enable(const bool &enable) {
 1139     Q_D(CartesianPlot);
 1140     if (enable != d->cursor0Enable) {
 1141         if (std::isnan(d->cursor0Pos.x())) { // if never set, set initial position
 1142             d->cursor0Pos.setX(d->cSystem->mapSceneToLogical(QPointF(0,0)).x());
 1143             mousePressCursorModeSignal(0, d->cursor0Pos); // simulate mousePress to update values in the cursor dock
 1144         }
 1145         exec(new CartesianPlotSetCursor0EnableCmd(d, enable, ki18n("%1: Cursor0 enable")));
 1146     }
 1147 }
 1148 
 1149 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetCursor1Enable, bool, cursor1Enable, updateCursor)
 1150 void CartesianPlot::setCursor1Enable(const bool &enable) {
 1151     Q_D(CartesianPlot);
 1152     if (enable != d->cursor1Enable) {
 1153         if (std::isnan(d->cursor1Pos.x())) { // if never set, set initial position
 1154             d->cursor1Pos.setX(d->cSystem->mapSceneToLogical(QPointF(0,0)).x());
 1155             mousePressCursorModeSignal(1, d->cursor1Pos); // simulate mousePress to update values in the cursor dock
 1156         }
 1157         exec(new CartesianPlotSetCursor1EnableCmd(d, enable, ki18n("%1: Cursor1 enable")));
 1158     }
 1159 }
 1160 
 1161 STD_SETTER_CMD_IMPL_S(CartesianPlot, SetTheme, QString, theme)
 1162 void CartesianPlot::setTheme(const QString& theme) {
 1163     Q_D(CartesianPlot);
 1164     if (theme != d->theme) {
 1165         QString info;
 1166         if (!theme.isEmpty())
 1167             info = i18n("%1: load theme %2", name(), theme);
 1168         else
 1169             info = i18n("%1: load default theme", name());
 1170         beginMacro(info);
 1171         exec(new CartesianPlotSetThemeCmd(d, theme, ki18n("%1: set theme")));
 1172         loadTheme(theme);
 1173         endMacro();
 1174     }
 1175 }
 1176 
 1177 //################################################################
 1178 //########################## Slots ###############################
 1179 //################################################################
 1180 void CartesianPlot::addHorizontalAxis() {
 1181     Axis* axis = new Axis("x-axis", Axis::Orientation::Horizontal);
 1182     if (axis->autoScale()) {
 1183         axis->setUndoAware(false);
 1184         axis->setStart(xMin());
 1185         axis->setEnd(xMax());
 1186         axis->setUndoAware(true);
 1187     }
 1188     addChild(axis);
 1189 }
 1190 
 1191 void CartesianPlot::addVerticalAxis() {
 1192     Axis* axis = new Axis("y-axis", Axis::Orientation::Vertical);
 1193     if (axis->autoScale()) {
 1194         axis->setUndoAware(false);
 1195         axis->setStart(yMin());
 1196         axis->setEnd(yMax());
 1197         axis->setUndoAware(true);
 1198     }
 1199     addChild(axis);
 1200 }
 1201 
 1202 void CartesianPlot::addCurve() {
 1203     addChild(new XYCurve("xy-curve"));
 1204 }
 1205 
 1206 void CartesianPlot::addEquationCurve() {
 1207     addChild(new XYEquationCurve("f(x)"));
 1208 }
 1209 
 1210 void CartesianPlot::addHistogram() {
 1211     addChild(new Histogram("Histogram"));
 1212 }
 1213 
 1214 /*!
 1215  * returns the first selected XYCurve in the plot
 1216  */
 1217 const XYCurve* CartesianPlot::currentCurve() const {
 1218     for (const auto* curve : this->children<const XYCurve>()) {
 1219         if (curve->graphicsItem()->isSelected())
 1220             return curve;
 1221     }
 1222 
 1223     return nullptr;
 1224 }
 1225 
 1226 void CartesianPlot::addDataReductionCurve() {
 1227     auto* curve = new XYDataReductionCurve("Data reduction");
 1228     const XYCurve* curCurve = currentCurve();
 1229     if (curCurve) {
 1230         beginMacro( i18n("%1: reduce '%2'", name(), curCurve->name()) );
 1231         curve->setName( i18n("Reduction of '%1'", curCurve->name()) );
 1232         curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Curve);
 1233         curve->setDataSourceCurve(curCurve);
 1234         this->addChild(curve);
 1235         curve->recalculate();
 1236         emit curve->dataReductionDataChanged(curve->dataReductionData());
 1237     } else {
 1238         beginMacro(i18n("%1: add data reduction curve", name()));
 1239         this->addChild(curve);
 1240     }
 1241 
 1242     endMacro();
 1243 }
 1244 
 1245 void CartesianPlot::addDifferentiationCurve() {
 1246     auto* curve = new XYDifferentiationCurve("Differentiation");
 1247     const XYCurve* curCurve = currentCurve();
 1248     if (curCurve) {
 1249         beginMacro( i18n("%1: differentiate '%2'", name(), curCurve->name()) );
 1250         curve->setName( i18n("Derivative of '%1'", curCurve->name()) );
 1251         curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Curve);
 1252         curve->setDataSourceCurve(curCurve);
 1253         this->addChild(curve);
 1254         curve->recalculate();
 1255         emit curve->differentiationDataChanged(curve->differentiationData());
 1256     } else {
 1257         beginMacro(i18n("%1: add differentiation curve", name()));
 1258         this->addChild(curve);
 1259     }
 1260 
 1261     endMacro();
 1262 }
 1263 
 1264 void CartesianPlot::addIntegrationCurve() {
 1265     auto* curve = new XYIntegrationCurve("Integration");
 1266     const XYCurve* curCurve = currentCurve();
 1267     if (curCurve) {
 1268         beginMacro( i18n("%1: integrate '%2'", name(), curCurve->name()) );
 1269         curve->setName( i18n("Integral of '%1'", curCurve->name()) );
 1270         curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Curve);
 1271         curve->setDataSourceCurve(curCurve);
 1272         this->addChild(curve);
 1273         curve->recalculate();
 1274         emit curve->integrationDataChanged(curve->integrationData());
 1275     } else {
 1276         beginMacro(i18n("%1: add integration curve", name()));
 1277         this->addChild(curve);
 1278     }
 1279 
 1280     endMacro();
 1281 }
 1282 
 1283 void CartesianPlot::addInterpolationCurve() {
 1284     auto* curve = new XYInterpolationCurve("Interpolation");
 1285     const XYCurve* curCurve = currentCurve();
 1286     if (curCurve) {
 1287         beginMacro( i18n("%1: interpolate '%2'", name(), curCurve->name()) );
 1288         curve->setName( i18n("Interpolation of '%1'", curCurve->name()) );
 1289         curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Curve);
 1290         curve->setDataSourceCurve(curCurve);
 1291         curve->recalculate();
 1292         this->addChild(curve);
 1293         emit curve->interpolationDataChanged(curve->interpolationData());
 1294     } else {
 1295         beginMacro(i18n("%1: add interpolation curve", name()));
 1296         this->addChild(curve);
 1297     }
 1298 
 1299     endMacro();
 1300 }
 1301 
 1302 void CartesianPlot::addSmoothCurve() {
 1303     auto* curve = new XYSmoothCurve("Smooth");
 1304     const XYCurve* curCurve = currentCurve();
 1305     if (curCurve) {
 1306         beginMacro( i18n("%1: smooth '%2'", name(), curCurve->name()) );
 1307         curve->setName( i18n("Smoothing of '%1'", curCurve->name()) );
 1308         curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Curve);
 1309         curve->setDataSourceCurve(curCurve);
 1310         this->addChild(curve);
 1311         curve->recalculate();
 1312         emit curve->smoothDataChanged(curve->smoothData());
 1313     } else {
 1314         beginMacro(i18n("%1: add smoothing curve", name()));
 1315         this->addChild(curve);
 1316     }
 1317 
 1318     endMacro();
 1319 }
 1320 
 1321 void CartesianPlot::addFitCurve() {
 1322     auto* curve = new XYFitCurve("fit");
 1323     const XYCurve* curCurve = currentCurve();
 1324     if (curCurve) {
 1325         beginMacro( i18n("%1: fit to '%2'", name(), curCurve->name()) );
 1326         curve->setName( i18n("Fit to '%1'", curCurve->name()) );
 1327         curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Curve);
 1328         curve->setDataSourceCurve(curCurve);
 1329 
 1330 
 1331         //set the fit model category and type
 1332         const auto* action = qobject_cast<const QAction*>(QObject::sender());
 1333         PlotDataDialog::AnalysisAction type = (PlotDataDialog::AnalysisAction)action->data().toInt();
 1334         curve->initFitData(type);
 1335         curve->initStartValues(curCurve);
 1336 
 1337         //fit with weights for y if the curve has error bars for y
 1338         if (curCurve->yErrorType() == XYCurve::ErrorType::Symmetric && curCurve->yErrorPlusColumn()) {
 1339             XYFitCurve::FitData fitData = curve->fitData();
 1340             fitData.yWeightsType = nsl_fit_weight_instrumental;
 1341             curve->setFitData(fitData);
 1342             curve->setYErrorColumn(curCurve->yErrorPlusColumn());
 1343         }
 1344 
 1345         curve->recalculate();
 1346 
 1347         //add the child after the fit was calculated so the dock widgets gets the fit results
 1348         //and call retransform() after this to calculate and to paint the data points of the fit-curve
 1349         this->addChild(curve);
 1350         curve->retransform();
 1351     } else {
 1352         beginMacro(i18n("%1: add fit curve", name()));
 1353         this->addChild(curve);
 1354     }
 1355 
 1356     endMacro();
 1357 }
 1358 
 1359 void CartesianPlot::addFourierFilterCurve() {
 1360     auto* curve = new XYFourierFilterCurve("Fourier filter");
 1361     const XYCurve* curCurve = currentCurve();
 1362     if (curCurve) {
 1363         beginMacro( i18n("%1: Fourier filtering of '%2'", name(), curCurve->name()) );
 1364         curve->setName( i18n("Fourier filtering of '%1'", curCurve->name()) );
 1365         curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Curve);
 1366         curve->setDataSourceCurve(curCurve);
 1367         this->addChild(curve);
 1368     } else {
 1369         beginMacro(i18n("%1: add Fourier filter curve", name()));
 1370         this->addChild(curve);
 1371     }
 1372 
 1373     endMacro();
 1374 }
 1375 
 1376 void CartesianPlot::addFourierTransformCurve() {
 1377     auto* curve = new XYFourierTransformCurve("Fourier transform");
 1378     this->addChild(curve);
 1379 }
 1380 
 1381 void CartesianPlot::addConvolutionCurve() {
 1382     auto* curve = new XYConvolutionCurve("Convolution");
 1383     this->addChild(curve);
 1384 }
 1385 
 1386 void CartesianPlot::addCorrelationCurve() {
 1387     auto* curve = new XYCorrelationCurve("Auto-/Cross-Correlation");
 1388     this->addChild(curve);
 1389 }
 1390 
 1391 /*!
 1392  * public helper function to set a legend object created outside of CartesianPlot, e.g. in \c OriginProjectParser.
 1393  */
 1394 void CartesianPlot::addLegend(CartesianPlotLegend* legend) {
 1395     m_legend = legend;
 1396     this->addChild(legend);
 1397 }
 1398 
 1399 void CartesianPlot::addLegend() {
 1400     //don't do anything if there's already a legend
 1401     if (m_legend)
 1402         return;
 1403 
 1404     m_legend = new CartesianPlotLegend(this, "legend");
 1405     this->addChild(m_legend);
 1406     m_legend->retransform();
 1407 
 1408     //only one legend is allowed -> disable the action
 1409     if (m_menusInitialized)
 1410         addLegendAction->setEnabled(false);
 1411 }
 1412 
 1413 void CartesianPlot::addTextLabel() {
 1414     auto* label = new TextLabel("text label");
 1415     this->addChild(label);
 1416     label->setParentGraphicsItem(graphicsItem());
 1417 }
 1418 
 1419 void CartesianPlot::addImage() {
 1420     auto* image = new Image("image");
 1421     this->addChild(image);
 1422 }
 1423 
 1424 void CartesianPlot::addCustomPoint() {
 1425     auto* point = new CustomPoint(this, "custom point");
 1426     this->addChild(point);
 1427     point->retransform();
 1428 }
 1429 
 1430 void CartesianPlot::addReferenceLine() {
 1431     auto* line = new ReferenceLine(this, "reference line");
 1432     this->addChild(line);
 1433     line->retransform();
 1434 }
 1435 
 1436 int CartesianPlot::curveCount(){
 1437     return children<XYCurve>().length();
 1438 }
 1439 
 1440 const XYCurve* CartesianPlot::getCurve(int index){
 1441     return children<XYCurve>().at(index);
 1442 }
 1443 
 1444 double CartesianPlot::cursorPos(int cursorNumber) {
 1445     Q_D(CartesianPlot);
 1446     if (cursorNumber == 0)
 1447         return d->cursor0Pos.x();
 1448     else
 1449         return d->cursor1Pos.x();
 1450 }
 1451 
 1452 void CartesianPlot::childAdded(const AbstractAspect* child) {
 1453     Q_D(CartesianPlot);
 1454 
 1455     const bool firstCurve = (children<XYCurve>().size() + children<Histogram>().size() == 1);
 1456     const auto* curve = qobject_cast<const XYCurve*>(child);
 1457     if (curve) {
 1458         connect(curve, &XYCurve::dataChanged, this, &CartesianPlot::dataChanged);
 1459         connect(curve, &XYCurve::xColumnChanged, this, [this](const AbstractColumn* column) {
 1460             const bool firstCurve = (children<XYCurve>().size() + children<Histogram>().size() == 1);
 1461             if (firstCurve)
 1462                 checkAxisFormat(column, Axis::Orientation::Horizontal);
 1463         });
 1464         connect(curve, &XYCurve::xDataChanged, this, &CartesianPlot::xDataChanged);
 1465         connect(curve, &XYCurve::xErrorTypeChanged, this, &CartesianPlot::dataChanged);
 1466         connect(curve, &XYCurve::xErrorPlusColumnChanged, this, &CartesianPlot::dataChanged);
 1467         connect(curve, &XYCurve::xErrorMinusColumnChanged, this, &CartesianPlot::dataChanged);
 1468         connect(curve, &XYCurve::yDataChanged, this, &CartesianPlot::yDataChanged);
 1469         connect(curve, &XYCurve::yColumnChanged, this, [this](const AbstractColumn* column) {
 1470             const bool firstCurve = (children<XYCurve>().size() + children<Histogram>().size() == 1);
 1471             if (firstCurve)
 1472                 checkAxisFormat(column, Axis::Orientation::Vertical);
 1473         });
 1474         connect(curve, &XYCurve::yErrorTypeChanged, this, &CartesianPlot::dataChanged);
 1475         connect(curve, &XYCurve::yErrorPlusColumnChanged, this, &CartesianPlot::dataChanged);
 1476         connect(curve, &XYCurve::yErrorMinusColumnChanged, this, &CartesianPlot::dataChanged);
 1477         connect(curve, QOverload<bool>::of(&XYCurve::visibilityChanged),
 1478                 this, &CartesianPlot::curveVisibilityChanged);
 1479 
 1480         //update the legend on changes of the name, line and symbol styles
 1481         connect(curve, &XYCurve::aspectDescriptionChanged, this, &CartesianPlot::updateLegend);
 1482         connect(curve, &XYCurve::aspectDescriptionChanged, this, &CartesianPlot::curveNameChanged);
 1483         connect(curve, &XYCurve::lineTypeChanged, this, &CartesianPlot::updateLegend);
 1484         connect(curve, &XYCurve::linePenChanged, this, &CartesianPlot::updateLegend);
 1485         connect(curve, &XYCurve::linePenChanged, this, static_cast<void (CartesianPlot::*)(QPen)>(&CartesianPlot::curveLinePenChanged));
 1486         connect(curve, &XYCurve::lineOpacityChanged, this, &CartesianPlot::updateLegend);
 1487         connect(curve, &XYCurve::symbolsStyleChanged, this, &CartesianPlot::updateLegend);
 1488         connect(curve, &XYCurve::symbolsSizeChanged, this, &CartesianPlot::updateLegend);
 1489         connect(curve, &XYCurve::symbolsRotationAngleChanged, this, &CartesianPlot::updateLegend);
 1490         connect(curve, &XYCurve::symbolsOpacityChanged, this, &CartesianPlot::updateLegend);
 1491         connect(curve, &XYCurve::symbolsBrushChanged, this, &CartesianPlot::updateLegend);
 1492         connect(curve, &XYCurve::symbolsPenChanged, this, &CartesianPlot::updateLegend);
 1493         connect(curve, &XYCurve::linePenChanged, this, QOverload<QPen>::of(&CartesianPlot::curveLinePenChanged)); // forward to Worksheet to update CursorDock
 1494 
 1495         updateLegend();
 1496         d->curvesXMinMaxIsDirty = true;
 1497         d->curvesYMinMaxIsDirty = true;
 1498 
 1499         //in case the first curve is added, check whether we start plotting datetime data
 1500         if (firstCurve) {
 1501             checkAxisFormat(curve->xColumn(), Axis::Orientation::Horizontal);
 1502             checkAxisFormat(curve->yColumn(), Axis::Orientation::Vertical);
 1503         }
 1504 
 1505         emit curveAdded(curve);
 1506 
 1507     } else {
 1508         const auto* hist = qobject_cast<const Histogram*>(child);
 1509         if (hist) {
 1510             connect(hist, &Histogram::dataChanged, this, &CartesianPlot::dataChanged);
 1511             connect(hist, &Histogram::visibilityChanged, this, &CartesianPlot::curveVisibilityChanged);
 1512 
 1513             updateLegend();
 1514             d->curvesXMinMaxIsDirty = true;
 1515             d->curvesYMinMaxIsDirty = true;
 1516 
 1517             if (firstCurve)
 1518                 checkAxisFormat(hist->dataColumn(), Axis::Orientation::Horizontal);
 1519         }
 1520         // if an element is hovered, the curves which are handled manually in this class
 1521         // must be unhovered
 1522         const auto* element = static_cast<const WorksheetElement*>(child);
 1523         connect(element, &WorksheetElement::hovered, this, &CartesianPlot::childHovered);
 1524     }
 1525 
 1526     if (!isLoading()) {
 1527         //if a theme was selected, apply the theme settings for newly added children,
 1528         //load default theme settings otherwise.
 1529         const auto* elem = dynamic_cast<const WorksheetElement*>(child);
 1530         if (elem) {
 1531             if (!d->theme.isEmpty()) {
 1532                 KConfig config(ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig);
 1533                 const_cast<WorksheetElement*>(elem)->loadThemeConfig(config);
 1534             } else {
 1535                 KConfig config;
 1536                 const_cast<WorksheetElement*>(elem)->loadThemeConfig(config);
 1537             }
 1538         }
 1539     }
 1540 }
 1541 
 1542 void CartesianPlot::checkAxisFormat(const AbstractColumn* column, Axis::Orientation orientation) {
 1543     const auto* col = dynamic_cast<const Column*>(column);
 1544     if (!col)
 1545         return;
 1546 
 1547     if (col->columnMode() == AbstractColumn::ColumnMode::DateTime) {
 1548         setUndoAware(false);
 1549         if (orientation == Axis::Orientation::Horizontal)
 1550             setXRangeFormat(RangeFormat::DateTime);
 1551         else
 1552             setYRangeFormat(RangeFormat::DateTime);
 1553         setUndoAware(true);
 1554 
 1555         //set column's datetime format for all horizontal axis
 1556         Q_D(CartesianPlot);
 1557         for (auto* axis : children<Axis>()) {
 1558             if (axis->orientation() == orientation) {
 1559                 auto* filter = static_cast<DateTime2StringFilter*>(col->outputFilter());
 1560                 d->xRangeDateTimeFormat = filter->format();
 1561                 axis->setUndoAware(false);
 1562                 axis->setLabelsDateTimeFormat(d->xRangeDateTimeFormat);
 1563                 axis->setUndoAware(true);
 1564             }
 1565         }
 1566     } else {
 1567         setUndoAware(false);
 1568         if (orientation == Axis::Orientation::Horizontal)
 1569             setXRangeFormat(RangeFormat::Numeric);
 1570         else
 1571             setYRangeFormat(RangeFormat::Numeric);
 1572         setUndoAware(true);
 1573     }
 1574 }
 1575 
 1576 void CartesianPlot::childRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child) {
 1577     Q_UNUSED(parent);
 1578     Q_UNUSED(before);
 1579     if (m_legend == child) {
 1580         if (m_menusInitialized)
 1581             addLegendAction->setEnabled(true);
 1582         m_legend = nullptr;
 1583     } else {
 1584         const auto* curve = qobject_cast<const XYCurve*>(child);
 1585         if (curve) {
 1586             updateLegend();
 1587             emit curveRemoved(curve);
 1588         }
 1589     }
 1590 }
 1591 
 1592 /*!
 1593  * \brief CartesianPlot::childHovered
 1594  * Unhover all curves, when another child is hovered. The hover handling for the curves is done in their parent (CartesianPlot),
 1595  * because the hover should set when the curve is hovered and not just the bounding rect (for more see hoverMoveEvent)
 1596  */
 1597 void CartesianPlot::childHovered() {
 1598     Q_D(CartesianPlot);
 1599     bool curveSender = dynamic_cast<XYCurve*>(QObject::sender()) != nullptr;
 1600     if (!d->isSelected()) {
 1601         if (d->m_hovered)
 1602             d->m_hovered = false;
 1603         d->update();
 1604     }
 1605     if (!curveSender) {
 1606         for (auto curve: children<XYCurve>())
 1607             curve->setHover(false);
 1608     }
 1609 }
 1610 
 1611 void CartesianPlot::updateLegend() {
 1612     if (m_legend)
 1613         m_legend->retransform();
 1614 }
 1615 
 1616 /*!
 1617     called when in one of the curves the data was changed.
 1618     Autoscales the coordinate system and the x-axes, when "auto-scale" is active.
 1619 */
 1620 void CartesianPlot::dataChanged() {
 1621     if (project() && project()->isLoading())
 1622         return;
 1623 
 1624     Q_D(CartesianPlot);
 1625     d->curvesXMinMaxIsDirty = true;
 1626     d->curvesYMinMaxIsDirty = true;
 1627     bool updated = false;
 1628     if (d->autoScaleX && d->autoScaleY)
 1629         updated = scaleAuto();
 1630     else if (d->autoScaleX)
 1631         updated = scaleAutoX();
 1632     else if (d->autoScaleY)
 1633         updated = scaleAutoY();
 1634 
 1635     if (!updated || !QObject::sender()) {
 1636         //even if the plot ranges were not changed, either no auto scale active or the new data
 1637         //is within the current ranges and no change of the ranges is required,
 1638         //retransform the curve in order to show the changes
 1639         auto* curve = dynamic_cast<XYCurve*>(QObject::sender());
 1640         if (curve)
 1641             curve->retransform();
 1642         else {
 1643             auto* hist = dynamic_cast<Histogram*>(QObject::sender());
 1644             if (hist)
 1645                 hist->retransform();
 1646             else {
 1647                 //no sender available, the function was called directly in the file filter (live data source got new data)
 1648                 //or in Project::load() -> retransform all available curves since we don't know which curves are affected.
 1649                 //TODO: this logic can be very expensive
 1650                 for (auto* c : children<XYCurve>()) {
 1651                     c->recalcLogicalPoints();
 1652                     c->retransform();
 1653                 }
 1654             }
 1655         }
 1656     }
 1657 }
 1658 
 1659 /*!
 1660     called when in one of the curves the x-data was changed.
 1661     Autoscales the coordinate system and the x-axes, when "auto-scale" is active.
 1662 */
 1663 void CartesianPlot::xDataChanged() {
 1664     if (project() && project()->isLoading())
 1665         return;
 1666 
 1667     Q_D(CartesianPlot);
 1668     if (d->suppressRetransform)
 1669         return;
 1670 
 1671     d->curvesXMinMaxIsDirty = true;
 1672     bool updated = false;
 1673     if (d->autoScaleX)
 1674         updated = this->scaleAutoX();
 1675 
 1676     if (!updated) {
 1677         //even if the plot ranges were not changed, either no auto scale active or the new data
 1678         //is within the current ranges and no change of the ranges is required,
 1679         //retransform the curve in order to show the changes
 1680         auto* curve = dynamic_cast<XYCurve*>(QObject::sender());
 1681         if (curve)
 1682             curve->retransform();
 1683         else {
 1684             auto* hist = dynamic_cast<Histogram*>(QObject::sender());
 1685             if (hist)
 1686                 hist->retransform();
 1687         }
 1688     }
 1689 
 1690     //in case there is only one curve and its column mode was changed, check whether we start plotting datetime data
 1691     if (children<XYCurve>().size() == 1) {
 1692         auto* curve = dynamic_cast<XYCurve*>(QObject::sender());
 1693         if (curve) {
 1694             const AbstractColumn* col = curve->xColumn();
 1695             if (col->columnMode() == AbstractColumn::ColumnMode::DateTime && d->xRangeFormat != RangeFormat::DateTime) {
 1696                 setUndoAware(false);
 1697                 setXRangeFormat(RangeFormat::DateTime);
 1698                 setUndoAware(true);
 1699             }
 1700         }
 1701     }
 1702     emit curveDataChanged(dynamic_cast<XYCurve*>(QObject::sender()));
 1703 }
 1704 
 1705 /*!
 1706     called when in one of the curves the x-data was changed.
 1707     Autoscales the coordinate system and the x-axes, when "auto-scale" is active.
 1708 */
 1709 void CartesianPlot::yDataChanged() {
 1710     if (project() && project()->isLoading())
 1711         return;
 1712 
 1713     Q_D(CartesianPlot);
 1714     if (d->suppressRetransform)
 1715         return;
 1716 
 1717     d->curvesYMinMaxIsDirty = true;
 1718     bool updated = false;
 1719     if (d->autoScaleY)
 1720         updated = this->scaleAutoY();
 1721 
 1722     if (!updated) {
 1723         //even if the plot ranges were not changed, either no auto scale active or the new data
 1724         //is within the current ranges and no change of the ranges is required,
 1725         //retransform the curve in order to show the changes
 1726         auto* curve = dynamic_cast<XYCurve*>(QObject::sender());
 1727         if (curve)
 1728             curve->retransform();
 1729         else {
 1730             auto* hist = dynamic_cast<Histogram*>(QObject::sender());
 1731             if (hist)
 1732                 hist->retransform();
 1733         }
 1734     }
 1735 
 1736     //in case there is only one curve and its column mode was changed, check whether we start plotting datetime data
 1737     if (children<XYCurve>().size() == 1) {
 1738         auto* curve = dynamic_cast<XYCurve*>(QObject::sender());
 1739         if (curve) {
 1740             const AbstractColumn* col = curve->yColumn();
 1741             if (col->columnMode() == AbstractColumn::ColumnMode::DateTime && d->yRangeFormat != RangeFormat::DateTime) {
 1742                 setUndoAware(false);
 1743                 setYRangeFormat(RangeFormat::DateTime);
 1744                 setUndoAware(true);
 1745             }
 1746         }
 1747     }
 1748     emit curveDataChanged(dynamic_cast<XYCurve*>(QObject::sender()));
 1749 }
 1750 
 1751 void CartesianPlot::curveVisibilityChanged() {
 1752     Q_D(CartesianPlot);
 1753     d->curvesXMinMaxIsDirty = true;
 1754     d->curvesYMinMaxIsDirty = true;
 1755     updateLegend();
 1756     if (d->autoScaleX && d->autoScaleY)
 1757         this->scaleAuto();
 1758     else if (d->autoScaleX)
 1759         this->scaleAutoX();
 1760     else if (d->autoScaleY)
 1761         this->scaleAutoY();
 1762 
 1763     emit curveVisibilityChangedSignal();
 1764 }
 1765 
 1766 void CartesianPlot::curveLinePenChanged(QPen pen) {
 1767     const auto* curve = qobject_cast<const XYCurve*>(QObject::sender());
 1768     emit curveLinePenChanged(pen, curve->name());
 1769 }
 1770 
 1771 void CartesianPlot::setMouseMode(MouseMode mouseMode) {
 1772     Q_D(CartesianPlot);
 1773 
 1774     d->mouseMode = mouseMode;
 1775     d->setHandlesChildEvents(mouseMode != MouseMode::Selection);
 1776 
 1777     QList<QGraphicsItem*> items = d->childItems();
 1778     if (d->mouseMode == MouseMode::Selection) {
 1779         d->setZoomSelectionBandShow(false);
 1780         d->setCursor(Qt::ArrowCursor);
 1781         for (auto* item : items)
 1782             item->setFlag(QGraphicsItem::ItemStacksBehindParent, false);
 1783     } else {
 1784         for (auto* item : items)
 1785             item->setFlag(QGraphicsItem::ItemStacksBehindParent, true);
 1786     }
 1787 
 1788     //when doing zoom selection, prevent the graphics item from being movable
 1789     //if it's currently movable (no worksheet layout available)
 1790     const auto* worksheet = dynamic_cast<const Worksheet*>(parentAspect());
 1791     if (worksheet) {
 1792         if (mouseMode == MouseMode::Selection) {
 1793             if (worksheet->layout() != Worksheet::Layout::NoLayout)
 1794                 graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false);
 1795             else
 1796                 graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true);
 1797         } else   //zoom m_selection
 1798             graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false);
 1799     }
 1800 
 1801     emit mouseModeChanged(mouseMode);
 1802 }
 1803 
 1804 void CartesianPlot::setLocked(bool locked) {
 1805     Q_D(CartesianPlot);
 1806     d->locked = locked;
 1807 }
 1808 
 1809 bool CartesianPlot::isLocked() const {
 1810     Q_D(const CartesianPlot);
 1811     return d->locked;
 1812 }
 1813 
 1814 bool CartesianPlot::scaleAutoX() {
 1815     Q_D(CartesianPlot);
 1816     if (d->curvesXMinMaxIsDirty) {
 1817         calculateCurvesXMinMax(false);
 1818 
 1819         /*
 1820         //take the size of the error bar cap into account if error bars with caps are plotted
 1821         double errorBarsCapSize = -1;
 1822         for (auto* curve : this->children<const XYCurve>()) {
 1823             if (curve->yErrorType() == XYCurve::ErrorType::NoError)
 1824                 continue;
 1825 
 1826             if (curve->errorBarsType() != XYCurve::ErrorBarsType::WithEnds)
 1827                 continue;
 1828 
 1829             if ( (curve->yErrorType() == XYCurve::ErrorType::Symmetric && curve->yErrorPlusColumn())
 1830                 || (curve->yErrorType() == XYCurve::ErrorType::Asymmetric && (curve->yErrorPlusColumn() && curve->yErrorMinusColumn())) )
 1831                 errorBarsCapSize = qMax(errorBarsCapSize, curve->errorBarsCapSize());
 1832         }
 1833 
 1834         if (errorBarsCapSize > 0) {
 1835             // must be done, because retransformScales uses xMin/xMax
 1836             if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY)
 1837                 d->xMin = d->curvesXMin;
 1838 
 1839             if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY)
 1840                 d->xMax = d->curvesXMax;
 1841             // When the previous scale is completely different. The mapTo functions scale with wrong values. To prevent
 1842             // this a rescale must be done.
 1843             // The errorBarsCapSize is in Scene coordinates. So this value must be transformed into a logical value. Due
 1844             // to nonlinear scalings it cannot only be multiplied with a scaling factor and depends on the position of the
 1845             // column value
 1846             // dirty hack: call setIsLoading(true) to suppress the call of retransform() in retransformScales() since a
 1847             // retransform is already done at the end of this function
 1848             setIsLoading(true);
 1849             d->retransformScales();
 1850             setIsLoading(false);
 1851             QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMin, 0), AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 1852             point.setX(point.x() - errorBarsCapSize/2.);
 1853             point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 1854             // Problem is, when the scaling is not linear (for example log(x)) and the minimum is 0. In this
 1855             // case mapLogicalToScene returns (0,0) which is smaller than the curves minimum
 1856             if (point.x() < d->curvesXMin)
 1857                 d->curvesXMin = point.x();
 1858 
 1859             point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMax, 0), AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 1860             point.setX(point.x() + errorBarsCapSize/2.);
 1861             point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 1862             if (point.x() > d->curvesXMax)
 1863                 d->curvesXMax = point.x();
 1864         }
 1865         */
 1866         d->curvesYMinMaxIsDirty = true;
 1867         d->curvesXMinMaxIsDirty = false;
 1868     }
 1869 
 1870     bool update = false;
 1871     if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) {
 1872         d->xMin = d->curvesXMin;
 1873         update = true;
 1874     }
 1875 
 1876     if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) {
 1877         d->xMax = d->curvesXMax;
 1878         update = true;
 1879     }
 1880 
 1881     if (update) {
 1882         if (d->xMax == d->xMin) {
 1883             //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value
 1884             if (d->xMax != 0) {
 1885                 d->xMax = d->xMax*1.1;
 1886                 d->xMin = d->xMin*0.9;
 1887             } else {
 1888                 d->xMax = 0.1;
 1889                 d->xMin = -0.1;
 1890             }
 1891         } else {
 1892             double offset = (d->xMax - d->xMin)*d->autoScaleOffsetFactor;
 1893             d->xMin -= offset;
 1894             d->xMax += offset;
 1895         }
 1896         d->retransformScales();
 1897     }
 1898 
 1899     return update;
 1900 }
 1901 
 1902 bool CartesianPlot::scaleAutoY() {
 1903     Q_D(CartesianPlot);
 1904 
 1905     if (d->curvesYMinMaxIsDirty) {
 1906         calculateCurvesYMinMax(false); // loop over all curves
 1907 
 1908         /*
 1909         //take the size of the error bar cap into account if error bars with caps are plotted
 1910         double errorBarsCapSize = -1;
 1911         for (auto* curve : this->children<const XYCurve>()) {
 1912             if (curve->xErrorType() == XYCurve::ErrorType::NoError)
 1913                 continue;
 1914 
 1915             if (curve->errorBarsType() != XYCurve::ErrorBarsType::WithEnds)
 1916                 continue;
 1917 
 1918             if ( (curve->xErrorType() == XYCurve::ErrorType::Symmetric && curve->xErrorPlusColumn())
 1919                 || (curve->xErrorType() == XYCurve::ErrorType::Asymmetric && (curve->xErrorPlusColumn() && curve->xErrorMinusColumn())) )
 1920                 errorBarsCapSize = qMax(errorBarsCapSize, curve->errorBarsCapSize());
 1921         }
 1922 
 1923         if (errorBarsCapSize > 0) {
 1924             if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY)
 1925                 d->yMin = d->curvesYMin;
 1926 
 1927             if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY)
 1928                 d->yMax = d->curvesYMax;
 1929             setIsLoading(true);
 1930             d->retransformScales();
 1931             setIsLoading(false);
 1932             QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMin), AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 1933             point.setY(point.y() + errorBarsCapSize);
 1934             point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 1935             if (point.y() < d->curvesYMin)
 1936                 d->curvesYMin = point.y();
 1937 
 1938             point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMax), AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 1939             point.setY(point.y() - errorBarsCapSize);
 1940             point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 1941             if (point.y() > d->curvesYMax)
 1942                 d->curvesYMax = point.y();
 1943         }
 1944         */
 1945 
 1946         d->curvesXMinMaxIsDirty = true;
 1947         d->curvesYMinMaxIsDirty = false;
 1948     }
 1949 
 1950     bool update = false;
 1951     if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) {
 1952         d->yMin = d->curvesYMin;
 1953         update = true;
 1954     }
 1955 
 1956     if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) {
 1957         d->yMax = d->curvesYMax;
 1958         update = true;
 1959     }
 1960     if (update) {
 1961         if (d->yMax == d->yMin) {
 1962             //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value
 1963             if (d->yMax != 0) {
 1964                 d->yMax = d->yMax*1.1;
 1965                 d->yMin = d->yMin*0.9;
 1966             } else {
 1967                 d->yMax = 0.1;
 1968                 d->yMin = -0.1;
 1969             }
 1970         } else {
 1971             double offset = (d->yMax - d->yMin)*d->autoScaleOffsetFactor;
 1972             d->yMin -= offset;
 1973             d->yMax += offset;
 1974         }
 1975         d->retransformScales();
 1976     }
 1977 
 1978     return update;
 1979 }
 1980 
 1981 void CartesianPlot::scaleAutoTriggered() {
 1982     QAction* action = dynamic_cast<QAction*>(QObject::sender());
 1983     if (!action)
 1984         return;
 1985 
 1986     if (action == scaleAutoAction)
 1987         scaleAuto();
 1988     else if (action == scaleAutoXAction)
 1989         setAutoScaleX(true);
 1990     else if (action == scaleAutoYAction)
 1991         setAutoScaleY(true);
 1992 }
 1993 
 1994 bool CartesianPlot::scaleAuto() {
 1995     Q_D(CartesianPlot);
 1996 
 1997     if (d->curvesXMinMaxIsDirty) {
 1998         calculateCurvesXMinMax();
 1999 
 2000         /*
 2001         //take the size of the error bar cap into account if error bars with caps are plotted
 2002         double errorBarsCapSize = -1;
 2003         for (auto* curve : this->children<const XYCurve>()) {
 2004             if (curve->yErrorType() == XYCurve::ErrorType::NoError)
 2005                 continue;
 2006 
 2007             if (curve->errorBarsType() != XYCurve::ErrorBarsType::WithEnds)
 2008                 continue;
 2009 
 2010             if ( (curve->yErrorType() == XYCurve::ErrorType::Symmetric && curve->yErrorPlusColumn())
 2011                 || (curve->yErrorType() == XYCurve::ErrorType::Asymmetric && (curve->yErrorPlusColumn() && curve->yErrorMinusColumn())) )
 2012                 errorBarsCapSize = qMax(errorBarsCapSize, curve->errorBarsCapSize());
 2013         }
 2014 
 2015         if (errorBarsCapSize > 0) {
 2016             if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY)
 2017                 d->xMin = d->curvesXMin;
 2018 
 2019             if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY)
 2020                 d->xMax = d->curvesXMax;
 2021             setIsLoading(true);
 2022             d->retransformScales();
 2023             setIsLoading(false);
 2024             QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMin, 0), AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 2025             point.setX(point.x() - errorBarsCapSize);
 2026             point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 2027             if (point.x() < d->curvesXMin)
 2028                 d->curvesXMin = point.x();
 2029 
 2030             point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMax, 0), AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 2031             point.setX(point.x() + errorBarsCapSize);
 2032             point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 2033             if (point.x() > d->curvesXMax)
 2034                 d->curvesXMax = point.x();
 2035         }
 2036         */
 2037         d->curvesXMinMaxIsDirty = false;
 2038     }
 2039 
 2040     if (d->curvesYMinMaxIsDirty) {
 2041         calculateCurvesYMinMax();
 2042 
 2043         /*
 2044         //take the size of the error bar cap into account if error bars with caps are plotted
 2045         double errorBarsCapSize = -1;
 2046         for (auto* curve : this->children<const XYCurve>()) {
 2047             if (curve->xErrorType() == XYCurve::ErrorType::NoError)
 2048                 continue;
 2049 
 2050             if (curve->errorBarsType() != XYCurve::ErrorBarsType::WithEnds)
 2051                 continue;
 2052 
 2053             if ( (curve->xErrorType() == XYCurve::ErrorType::Symmetric && curve->xErrorPlusColumn())
 2054                 || (curve->xErrorType() == XYCurve::ErrorType::Asymmetric && (curve->xErrorPlusColumn() && curve->xErrorMinusColumn())) )
 2055                 errorBarsCapSize = qMax(errorBarsCapSize, curve->errorBarsCapSize());
 2056         }
 2057 
 2058         if (errorBarsCapSize > 0) {
 2059             if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY)
 2060                 d->yMin = d->curvesYMin;
 2061 
 2062             if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY)
 2063                 d->yMax = d->curvesYMax;
 2064             setIsLoading(true);
 2065             d->retransformScales();
 2066             setIsLoading(false);
 2067             QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMin), AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 2068             point.setY(point.y() + errorBarsCapSize);
 2069             point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 2070             if (point.y() < d->curvesYMin)
 2071                 d->curvesYMin = point.y();
 2072 
 2073             point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMax), AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 2074             point.setY(point.y() - errorBarsCapSize);
 2075             point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 2076             if (point.y() > d->curvesYMax)
 2077                 d->curvesYMax = point.y();
 2078         }
 2079         */
 2080         d->curvesYMinMaxIsDirty = false;
 2081     }
 2082 
 2083     bool updateX = false;
 2084     bool updateY = false;
 2085     if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) {
 2086         d->xMin = d->curvesXMin;
 2087         updateX = true;
 2088     }
 2089 
 2090     if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) {
 2091         d->xMax = d->curvesXMax;
 2092         updateX = true;
 2093     }
 2094 
 2095     if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) {
 2096         d->yMin = d->curvesYMin;
 2097         updateY = true;
 2098     }
 2099 
 2100     if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) {
 2101         d->yMax = d->curvesYMax;
 2102         updateY = true;
 2103     }
 2104     DEBUG(Q_FUNC_INFO << ", xmin/xmax = " << d->xMin << '/' << d->xMax << ", ymin/ymax = " << d->yMin << '/' << d->yMax);
 2105 
 2106     if (updateX || updateY) {
 2107         if (updateX) {
 2108             if (d->xMax == d->xMin) {
 2109                 //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value
 2110                 if (d->xMax != 0) {
 2111                     d->xMax = d->xMax*1.1;
 2112                     d->xMin = d->xMin*0.9;
 2113                 } else {
 2114                     d->xMax = 0.1;
 2115                     d->xMin = -0.1;
 2116                 }
 2117             } else {
 2118                 double offset = (d->xMax - d->xMin)*d->autoScaleOffsetFactor;
 2119                 d->xMin -= offset;
 2120                 d->xMax += offset;
 2121             }
 2122             setAutoScaleX(true);
 2123         }
 2124         if (updateY) {
 2125             if (d->yMax == d->yMin) {
 2126                 //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value
 2127                 if (d->yMax != 0) {
 2128                     d->yMax = d->yMax*1.1;
 2129                     d->yMin = d->yMin*0.9;
 2130                 } else {
 2131                     d->yMax = 0.1;
 2132                     d->yMin = -0.1;
 2133                 }
 2134             } else {
 2135                 double offset = (d->yMax - d->yMin)*d->autoScaleOffsetFactor;
 2136                 d->yMin -= offset;
 2137                 d->yMax += offset;
 2138             }
 2139             setAutoScaleY(true);
 2140         }
 2141         d->retransformScales();
 2142     }
 2143 
 2144     return (updateX || updateY);
 2145 }
 2146 
 2147 /*!
 2148  * Calculates and sets curves y min and max. This function does not respect the range
 2149  * of the y axis
 2150  */
 2151 void CartesianPlot::calculateCurvesXMinMax(bool completeRange) {
 2152     Q_D(CartesianPlot);
 2153 
 2154     d->curvesXMin = INFINITY;
 2155     d->curvesXMax = -INFINITY;
 2156 
 2157     //loop over all xy-curves and determine the maximum and minimum x-values
 2158     for (const auto* curve : this->children<const XYCurve>()) {
 2159         if (!curve->isVisible())
 2160             continue;
 2161 
 2162         auto* xColumn = curve->xColumn();
 2163         if (!xColumn)
 2164             continue;
 2165 
 2166         double min = d->curvesXMin;
 2167         double max = d->curvesXMax;
 2168 
 2169         int start =0;
 2170         int end = 0;
 2171         if (d->rangeType == RangeType::Free && curve->yColumn()
 2172                 && !completeRange) {
 2173             curve->yColumn()->indicesMinMax(yMin(), yMax(), start, end);
 2174             if (end < curve->yColumn()->rowCount())
 2175                 end ++;
 2176         } else {
 2177             switch (d->rangeType) {
 2178             case RangeType::Free:
 2179                 start = 0;
 2180                 end = xColumn->rowCount();
 2181                 break;
 2182             case RangeType::Last:
 2183                 start = xColumn->rowCount() - d->rangeLastValues;
 2184                 end = xColumn->rowCount();
 2185                 break;
 2186             case RangeType::First:
 2187                 start = 0;
 2188                 end = d->rangeFirstValues;
 2189                 break;
 2190             }
 2191         }
 2192 
 2193         curve->minMaxX(start, end, min, max, true);
 2194         if (min < d->curvesXMin)
 2195             d->curvesXMin = min;
 2196 
 2197         if (max > d->curvesXMax)
 2198             d->curvesXMax = max;
 2199     }
 2200 
 2201     //loop over all histograms and determine the maximum and minimum x-values
 2202     for (const auto* curve : this->children<const Histogram>()) {
 2203         if (!curve->isVisible())
 2204             continue;
 2205         if (!curve->dataColumn())
 2206             continue;
 2207 
 2208         const double min = curve->getXMinimum();
 2209         if (d->curvesXMin > min)
 2210             d->curvesXMin = min;
 2211 
 2212         const double max = curve->getXMaximum();
 2213         if (max > d->curvesXMax)
 2214             d->curvesXMax = max;
 2215     }
 2216 }
 2217 
 2218 /*!
 2219  * Calculates and sets curves y min and max. This function does not respect the range
 2220  * of the x axis
 2221  */
 2222 void CartesianPlot::calculateCurvesYMinMax(bool completeRange) {
 2223     Q_D(CartesianPlot);
 2224 
 2225     d->curvesYMin = INFINITY;
 2226     d->curvesYMax = -INFINITY;
 2227 
 2228     double min = d->curvesYMin;
 2229     double max = d->curvesYMax;
 2230 
 2231 
 2232     //loop over all xy-curves and determine the maximum and minimum y-values
 2233     for (const auto* curve : this->children<const XYCurve>()) {
 2234         if (!curve->isVisible())
 2235             continue;
 2236 
 2237         auto* yColumn = curve->yColumn();
 2238         if (!yColumn)
 2239             continue;
 2240 
 2241         int start =0;
 2242         int end = 0;
 2243         if (d->rangeType == RangeType::Free && curve->xColumn() &&
 2244                 !completeRange) {
 2245             curve->xColumn()->indicesMinMax(xMin(), xMax(), start, end);
 2246             if (end < curve->xColumn()->rowCount())
 2247                 end ++; // because minMaxY excludes indexMax
 2248         } else {
 2249             switch (d->rangeType) {
 2250                 case RangeType::Free:
 2251                     start = 0;
 2252                     end = yColumn->rowCount();
 2253                     break;
 2254                 case RangeType::Last:
 2255                     start = yColumn->rowCount() - d->rangeLastValues;
 2256                     end = yColumn->rowCount();
 2257                     break;
 2258                 case RangeType::First:
 2259                     start = 0;
 2260                     end = d->rangeFirstValues;
 2261                     break;
 2262             }
 2263         }
 2264 
 2265         curve->minMaxY(start, end, min, max, true);
 2266 
 2267         if (min < d->curvesYMin)
 2268             d->curvesYMin = min;
 2269 
 2270         if (max > d->curvesYMax)
 2271             d->curvesYMax = max;
 2272     }
 2273 
 2274     //loop over all histograms and determine the maximum y-value
 2275     for (const auto* curve : this->children<const Histogram>()) {
 2276         if (!curve->isVisible())
 2277             continue;
 2278 
 2279         const double min = curve->getYMinimum();
 2280         if (d->curvesYMin > min)
 2281             d->curvesYMin = min;
 2282 
 2283         const double max = curve->getYMaximum();
 2284         if (max > d->curvesYMax)
 2285             d->curvesYMax = max;
 2286     }
 2287 }
 2288 
 2289 void CartesianPlot::zoomIn() {
 2290     Q_D(CartesianPlot);
 2291 
 2292     setUndoAware(false);
 2293     setAutoScaleX(false);
 2294     setAutoScaleY(false);
 2295     setUndoAware(true);
 2296     d->curvesXMinMaxIsDirty = true;
 2297     d->curvesYMinMaxIsDirty = true;
 2298     zoom(true, true); //zoom in x
 2299     zoom(false, true); //zoom in y
 2300     d->retransformScales();
 2301 }
 2302 
 2303 void CartesianPlot::zoomOut() {
 2304     Q_D(CartesianPlot);
 2305 
 2306     setUndoAware(false);
 2307     setAutoScaleX(false);
 2308     setAutoScaleY(false);
 2309     setUndoAware(true);
 2310     d->curvesXMinMaxIsDirty = true;
 2311     d->curvesYMinMaxIsDirty = true;
 2312     zoom(true, false); //zoom out x
 2313     zoom(false, false); //zoom out y
 2314     d->retransformScales();
 2315 }
 2316 
 2317 void CartesianPlot::zoomInX() {
 2318     Q_D(CartesianPlot);
 2319 
 2320     setUndoAware(false);
 2321     setAutoScaleX(false);
 2322     setUndoAware(true);
 2323     d->curvesYMinMaxIsDirty = true;
 2324     zoom(true, true); //zoom in x
 2325     if (d->autoScaleY && autoScaleY())
 2326         return;
 2327 
 2328     d->retransformScales();
 2329 }
 2330 
 2331 void CartesianPlot::zoomOutX() {
 2332     Q_D(CartesianPlot);
 2333 
 2334     setUndoAware(false);
 2335     setAutoScaleX(false);
 2336     setUndoAware(true);
 2337     d->curvesYMinMaxIsDirty = true;
 2338     zoom(true, false); //zoom out x
 2339 
 2340     if (d->autoScaleY && autoScaleY())
 2341         return;
 2342 
 2343     d->retransformScales();
 2344 }
 2345 
 2346 void CartesianPlot::zoomInY() {
 2347     Q_D(CartesianPlot);
 2348 
 2349     setUndoAware(false);
 2350     setAutoScaleY(false);
 2351     setUndoAware(true);
 2352     d->curvesYMinMaxIsDirty = true;
 2353     zoom(false, true); //zoom in y
 2354 
 2355     if (d->autoScaleX && autoScaleX())
 2356         return;
 2357 
 2358     d->retransformScales();
 2359 }
 2360 
 2361 void CartesianPlot::zoomOutY() {
 2362     Q_D(CartesianPlot);
 2363 
 2364     setUndoAware(false);
 2365     setAutoScaleY(false);
 2366     setUndoAware(true);
 2367     d->curvesYMinMaxIsDirty = true;
 2368     zoom(false, false); //zoom out y
 2369 
 2370     if (d->autoScaleX && autoScaleX())
 2371         return;
 2372 
 2373     d->retransformScales();
 2374 }
 2375 
 2376 /*!
 2377  * helper function called in other zoom*() functions
 2378  * and doing the actual change of the data ranges.
 2379  * @param x if set to \true the x-range is modified, the y-range for \c false
 2380  * @param in the "zoom in" is performed if set to \c \true, "zoom out" for \c false
 2381  */
 2382 void CartesianPlot::zoom(bool x, bool in) {
 2383     Q_D(CartesianPlot);
 2384 
 2385     double min;
 2386     double max;
 2387     CartesianPlot::Scale scale;
 2388     if (x) {
 2389         min = d->xMin;
 2390         max = d->xMax;
 2391         scale = d->xScale;
 2392     } else {
 2393         min = d->yMin;
 2394         max = d->yMax;
 2395         scale = d->yScale;
 2396     }
 2397 
 2398     double factor = m_zoomFactor;
 2399     if (in)
 2400         factor = 1/factor;
 2401 
 2402     switch (scale) {
 2403     case Scale::Linear: {
 2404         double oldRange = max - min;
 2405         double newRange = (max - min) * factor;
 2406         max = max + (newRange - oldRange) / 2;
 2407         min = min - (newRange - oldRange) / 2;
 2408         break;
 2409     }
 2410     case Scale::Log10:
 2411     case Scale::Log10Abs: {
 2412         double oldRange = log10(max) - log10(min);
 2413         double newRange = (log10(max) - log10(min)) * factor;
 2414         max = max * pow(10, (newRange - oldRange) / 2.);
 2415         min = min / pow(10, (newRange - oldRange) / 2.);
 2416         break;
 2417     }
 2418     case Scale::Log2:
 2419     case Scale::Log2Abs: {
 2420         double oldRange = log2(max) - log2(min);
 2421         double newRange = (log2(max) - log2(min)) * factor;
 2422         max = max * pow(2, (newRange - oldRange) / 2.);
 2423         min = min / pow(2, (newRange - oldRange) / 2.);
 2424         break;
 2425     }
 2426     case Scale::Ln:
 2427     case Scale::LnAbs: {
 2428         double oldRange = log(max) - log(min);
 2429         double newRange = (log(max) - log(min)) * factor;
 2430         max = max * exp((newRange - oldRange) / 2.);
 2431         min = min / exp((newRange - oldRange) / 2.);
 2432         break;
 2433     }
 2434     case Scale::Sqrt:
 2435     case Scale::X2:
 2436         break;
 2437     }
 2438 
 2439     if (!std::isnan(min) && !std::isnan(max) && std::isfinite(min) && std::isfinite(max)) {
 2440         if (x) {
 2441             d->xMin = min;
 2442             d->xMax = max;
 2443         } else {
 2444             d->yMin = min;
 2445             d->yMax = max;
 2446         }
 2447     }
 2448 }
 2449 
 2450 /*!
 2451  * helper function called in other shift*() functions
 2452  * and doing the actual change of the data ranges.
 2453  * @param x if set to \true the x-range is modified, the y-range for \c false
 2454  * @param leftOrDown the "shift left" for x or "shift dows" for y is performed if set to \c \true,
 2455  * "shift right" or "shift up" for \c false
 2456  */
 2457 void CartesianPlot::shift(bool x, bool leftOrDown) {
 2458     Q_D(CartesianPlot);
 2459 
 2460     double min;
 2461     double max;
 2462     CartesianPlot::Scale scale;
 2463     double offset = 0.0;
 2464     double factor = 0.1;
 2465     if (x) {
 2466         min = d->xMin;
 2467         max = d->xMax;
 2468         scale = d->xScale;
 2469     } else {
 2470         min = d->yMin;
 2471         max = d->yMax;
 2472         scale = d->yScale;
 2473     }
 2474 
 2475     if (leftOrDown)
 2476         factor *= -1.;
 2477 
 2478     switch (scale) {
 2479     case Scale::Linear: {
 2480         offset = (max - min) * factor;
 2481         min += offset;
 2482         max += offset;
 2483         break;
 2484     }
 2485     case Scale::Log10:
 2486     case Scale::Log10Abs: {
 2487         offset = (log10(max) - log10(min)) * factor;
 2488         min *= pow(10, offset);
 2489         max *= pow(10, offset);
 2490         break;
 2491     }
 2492     case Scale::Log2:
 2493     case Scale::Log2Abs: {
 2494         offset = (log2(max) - log2(min)) * factor;
 2495         min *= pow(2, offset);
 2496         max *= pow(2, offset);
 2497         break;
 2498     }
 2499     case Scale::Ln:
 2500     case Scale::LnAbs: {
 2501         offset = (log(max) - log(min)) * factor;
 2502         min *= exp(offset);
 2503         max *= exp(offset);
 2504         break;
 2505     }
 2506     case Scale::Sqrt:
 2507     case Scale::X2:
 2508         break;
 2509     }
 2510 
 2511     if (!std::isnan(min) && !std::isnan(max) && std::isfinite(min) && std::isfinite(max)) {
 2512         if (x) {
 2513             d->xMin = min;
 2514             d->xMax = max;
 2515         } else {
 2516             d->yMin = min;
 2517             d->yMax = max;
 2518         }
 2519     }
 2520 }
 2521 
 2522 void CartesianPlot::shiftLeftX() {
 2523     Q_D(CartesianPlot);
 2524 
 2525     setUndoAware(false);
 2526     setAutoScaleX(false);
 2527     setUndoAware(true);
 2528     d->curvesYMinMaxIsDirty = true;
 2529     shift(true, true);
 2530 
 2531     if (d->autoScaleY && scaleAutoY())
 2532         return;
 2533 
 2534     d->retransformScales();
 2535 }
 2536 
 2537 void CartesianPlot::shiftRightX() {
 2538     Q_D(CartesianPlot);
 2539 
 2540     setUndoAware(false);
 2541     setAutoScaleX(false);
 2542     setUndoAware(true);
 2543     d->curvesYMinMaxIsDirty = true;
 2544     shift(true, false);
 2545 
 2546     if (d->autoScaleY && scaleAutoY())
 2547         return;
 2548 
 2549     d->retransformScales();
 2550 }
 2551 
 2552 void CartesianPlot::shiftUpY() {
 2553     Q_D(CartesianPlot);
 2554 
 2555     setUndoAware(false);
 2556     setAutoScaleY(false);
 2557     setUndoAware(true);
 2558     d->curvesXMinMaxIsDirty = true;
 2559     shift(false, false);
 2560 
 2561     if (d->autoScaleX && scaleAutoX())
 2562         return;
 2563 
 2564     d->retransformScales();
 2565 }
 2566 
 2567 void CartesianPlot::shiftDownY() {
 2568     Q_D(CartesianPlot);
 2569 
 2570     setUndoAware(false);
 2571     setAutoScaleY(false);
 2572     setUndoAware(true);
 2573     d->curvesXMinMaxIsDirty = true;
 2574     shift(false, true);
 2575 
 2576     if (d->autoScaleX && scaleAutoX())
 2577         return;
 2578 
 2579     d->retransformScales();
 2580 }
 2581 
 2582 void CartesianPlot::cursor() {
 2583     Q_D(CartesianPlot);
 2584     d->retransformScales();
 2585 }
 2586 
 2587 void CartesianPlot::mousePressZoomSelectionMode(QPointF logicPos) {
 2588     Q_D(CartesianPlot);
 2589     d->mousePressZoomSelectionMode(logicPos);
 2590 }
 2591 void CartesianPlot::mousePressCursorMode(int cursorNumber, QPointF logicPos) {
 2592     Q_D(CartesianPlot);
 2593     d->mousePressCursorMode(cursorNumber, logicPos);
 2594 }
 2595 void CartesianPlot::mouseMoveZoomSelectionMode(QPointF logicPos) {
 2596     Q_D(CartesianPlot);
 2597     d->mouseMoveZoomSelectionMode(logicPos);
 2598 }
 2599 void CartesianPlot::mouseMoveCursorMode(int cursorNumber, QPointF logicPos) {
 2600     Q_D(CartesianPlot);
 2601     d->mouseMoveCursorMode(cursorNumber, logicPos);
 2602 }
 2603 
 2604 void CartesianPlot::mouseReleaseZoomSelectionMode() {
 2605     Q_D(CartesianPlot);
 2606     d->mouseReleaseZoomSelectionMode();
 2607 }
 2608 
 2609 void CartesianPlot::mouseHoverZoomSelectionMode(QPointF logicPos) {
 2610     Q_D(CartesianPlot);
 2611     d->mouseHoverZoomSelectionMode(logicPos);
 2612 }
 2613 
 2614 void CartesianPlot::mouseHoverOutsideDataRect() {
 2615     Q_D(CartesianPlot);
 2616     d->mouseHoverOutsideDataRect();
 2617 }
 2618 
 2619 //##############################################################################
 2620 //######  SLOTs for changes triggered via QActions in the context menu  ########
 2621 //##############################################################################
 2622 void CartesianPlot::visibilityChanged() {
 2623     Q_D(CartesianPlot);
 2624     this->setVisible(!d->isVisible());
 2625 }
 2626 
 2627 //#####################################################################
 2628 //################### Private implementation ##########################
 2629 //#####################################################################
 2630 CartesianPlotPrivate::CartesianPlotPrivate(CartesianPlot* plot) : AbstractPlotPrivate(plot), q(plot) {
 2631     setData(0, static_cast<int>(WorksheetElement::WorksheetElementName::NameCartesianPlot));
 2632     m_cursor0Text.prepare();
 2633     m_cursor1Text.prepare();
 2634 }
 2635 
 2636 /*!
 2637     updates the position of plot rectangular in scene coordinates to \c r and recalculates the scales.
 2638     The size of the plot corresponds to the size of the plot area, the area which is filled with the background color etc.
 2639     and which can pose the parent item for several sub-items (like TextLabel).
 2640     Note, the size of the area used to define the coordinate system doesn't need to be equal to this plot area.
 2641     Also, the size (=bounding box) of CartesianPlot can be greater than the size of the plot area.
 2642  */
 2643 void CartesianPlotPrivate::retransform() {
 2644     if (suppressRetransform)
 2645         return;
 2646 
 2647     PERFTRACE("CartesianPlotPrivate::retransform()");
 2648     prepareGeometryChange();
 2649     setPos(rect.x() + rect.width()/2, rect.y() + rect.height()/2);
 2650 
 2651     updateDataRect();
 2652     retransformScales();
 2653 
 2654     //plotArea position is always (0, 0) in parent's coordinates, don't need to update here
 2655     q->plotArea()->setRect(rect);
 2656 
 2657     //call retransform() for the title and the legend (if available)
 2658     //when a predefined position relative to the (Left, Centered etc.) is used,
 2659     //the actual position needs to be updated on plot's geometry changes.
 2660     if (q->title())
 2661         q->title()->retransform();
 2662     if (q->m_legend)
 2663         q->m_legend->retransform();
 2664 
 2665     WorksheetElementContainerPrivate::recalcShapeAndBoundingRect();
 2666 }
 2667 
 2668 void CartesianPlotPrivate::retransformScales() {
 2669     DEBUG(Q_FUNC_INFO << ", xmin/xmax = " << xMin << '/'<< xMax << ", ymin/ymax = " << yMin << '/' << yMax);
 2670     PERFTRACE("CartesianPlotPrivate::retransformScales()");
 2671 
 2672     QVector<CartesianScale*> scales;
 2673 
 2674     //check ranges for log-scales
 2675     if (xScale != CartesianPlot::Scale::Linear)
 2676         checkXRange();
 2677 
 2678     //check whether we have x-range breaks - the first break, if available, should be valid
 2679     bool hasValidBreak = (xRangeBreakingEnabled && !xRangeBreaks.list.isEmpty() && xRangeBreaks.list.first().isValid());
 2680 
 2681     static const int breakGap = 20;
 2682     double sceneStart, sceneEnd, logicalStart, logicalEnd;
 2683 
 2684     //create x-scales
 2685     int plotSceneStart = dataRect.x();
 2686     int plotSceneEnd = dataRect.x() + dataRect.width();
 2687     if (!hasValidBreak) {
 2688         //no breaks available -> range goes from the plot beginning to the end of the plot
 2689         sceneStart = plotSceneStart;
 2690         sceneEnd = plotSceneEnd;
 2691         logicalStart = xMin;
 2692         logicalEnd = xMax;
 2693 
 2694         //TODO: how should we handle the case sceneStart == sceneEnd?
 2695         //(to reproduce, create plots and adjust the spacing/pading to get zero size for the plots)
 2696         if (sceneStart != sceneEnd)
 2697             scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd);
 2698     } else {
 2699         int sceneEndLast = plotSceneStart;
 2700         int logicalEndLast = xMin;
 2701         for (const auto& rb : xRangeBreaks.list) {
 2702             if (!rb.isValid())
 2703                 break;
 2704 
 2705             //current range goes from the end of the previous one (or from the plot beginning) to curBreak.start
 2706             sceneStart = sceneEndLast;
 2707             if (&rb == &xRangeBreaks.list.first()) sceneStart += breakGap;
 2708             sceneEnd = plotSceneStart + (plotSceneEnd-plotSceneStart) * rb.position;
 2709             logicalStart = logicalEndLast;
 2710             logicalEnd = rb.start;
 2711 
 2712             if (sceneStart != sceneEnd)
 2713                 scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd);
 2714 
 2715             sceneEndLast = sceneEnd;
 2716             logicalEndLast = rb.end;
 2717         }
 2718 
 2719         //add the remaining range going from the last available range break to the end of the plot (=end of the x-data range)
 2720         sceneStart = sceneEndLast+breakGap;
 2721         sceneEnd = plotSceneEnd;
 2722         logicalStart = logicalEndLast;
 2723         logicalEnd = xMax;
 2724 
 2725         if (sceneStart != sceneEnd)
 2726             scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd);
 2727     }
 2728 
 2729     cSystem->setXScales(scales);
 2730 
 2731     //check ranges for log-scales
 2732     if (yScale != CartesianPlot::Scale::Linear)
 2733         checkYRange();
 2734 
 2735     //check whether we have y-range breaks - the first break, if available, should be valid
 2736     hasValidBreak = (yRangeBreakingEnabled && !yRangeBreaks.list.isEmpty() && yRangeBreaks.list.first().isValid());
 2737 
 2738     //create y-scales
 2739     scales.clear();
 2740     plotSceneStart = dataRect.y() + dataRect.height();
 2741     plotSceneEnd = dataRect.y();
 2742     if (!hasValidBreak) {
 2743         //no breaks available -> range goes from the plot beginning to the end of the plot
 2744         sceneStart = plotSceneStart;
 2745         sceneEnd = plotSceneEnd;
 2746         logicalStart = yMin;
 2747         logicalEnd = yMax;
 2748 
 2749         if (sceneStart != sceneEnd)
 2750             scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd);
 2751     } else {
 2752         int sceneEndLast = plotSceneStart;
 2753         int logicalEndLast = yMin;
 2754         for (const auto& rb : yRangeBreaks.list) {
 2755             if (!rb.isValid())
 2756                 break;
 2757 
 2758             //current range goes from the end of the previous one (or from the plot beginning) to curBreak.start
 2759             sceneStart = sceneEndLast;
 2760             if (&rb == &yRangeBreaks.list.first()) sceneStart -= breakGap;
 2761             sceneEnd = plotSceneStart + (plotSceneEnd-plotSceneStart) * rb.position;
 2762             logicalStart = logicalEndLast;
 2763             logicalEnd = rb.start;
 2764 
 2765             if (sceneStart != sceneEnd)
 2766                 scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd);
 2767 
 2768             sceneEndLast = sceneEnd;
 2769             logicalEndLast = rb.end;
 2770         }
 2771 
 2772         //add the remaining range going from the last available range break to the end of the plot (=end of the y-data range)
 2773         sceneStart = sceneEndLast-breakGap;
 2774         sceneEnd = plotSceneEnd;
 2775         logicalStart = logicalEndLast;
 2776         logicalEnd = yMax;
 2777 
 2778         if (sceneStart != sceneEnd)
 2779             scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd);
 2780     }
 2781 
 2782     cSystem->setYScales(scales);
 2783 
 2784     //calculate the changes in x and y and save the current values for xMin, xMax, yMin, yMax
 2785     double deltaXMin = 0;
 2786     double deltaXMax = 0;
 2787     double deltaYMin = 0;
 2788     double deltaYMax = 0;
 2789 
 2790     if (xMin != xMinPrev) {
 2791         deltaXMin = xMin - xMinPrev;
 2792         emit q->xMinChanged(xMin);
 2793     }
 2794 
 2795     if (xMax != xMaxPrev) {
 2796         deltaXMax = xMax - xMaxPrev;
 2797         emit q->xMaxChanged(xMax);
 2798     }
 2799 
 2800     if (yMin != yMinPrev) {
 2801         deltaYMin = yMin - yMinPrev;
 2802         emit q->yMinChanged(yMin);
 2803     }
 2804 
 2805     if (yMax != yMaxPrev) {
 2806         deltaYMax = yMax - yMaxPrev;
 2807         emit q->yMaxChanged(yMax);
 2808     }
 2809 
 2810     xMinPrev = xMin;
 2811     xMaxPrev = xMax;
 2812     yMinPrev = yMin;
 2813     yMaxPrev = yMax;
 2814     //adjust auto-scale axes
 2815     for (auto* axis : q->children<Axis>()) {
 2816         if (!axis->autoScale())
 2817             continue;
 2818 
 2819         if (axis->orientation() == Axis::Orientation::Horizontal) {
 2820             if (deltaXMax != 0) {
 2821                 axis->setUndoAware(false);
 2822                 axis->setSuppressRetransform(true);
 2823                 axis->setEnd(xMax);
 2824                 axis->setUndoAware(true);
 2825                 axis->setSuppressRetransform(false);
 2826             }
 2827             if (deltaXMin != 0) {
 2828                 axis->setUndoAware(false);
 2829                 axis->setSuppressRetransform(true);
 2830                 axis->setStart(xMin);
 2831                 axis->setUndoAware(true);
 2832                 axis->setSuppressRetransform(false);
 2833             }
 2834             //TODO;
 2835 //          if (axis->position() == Axis::Position::Custom && deltaYMin != 0) {
 2836 //              axis->setOffset(axis->offset() + deltaYMin, false);
 2837 //          }
 2838         } else {
 2839             if (deltaYMax != 0) {
 2840                 axis->setUndoAware(false);
 2841                 axis->setSuppressRetransform(true);
 2842                 axis->setEnd(yMax);
 2843                 axis->setUndoAware(true);
 2844                 axis->setSuppressRetransform(false);
 2845             }
 2846             if (deltaYMin != 0) {
 2847                 axis->setUndoAware(false);
 2848                 axis->setSuppressRetransform(true);
 2849                 axis->setStart(yMin);
 2850                 axis->setUndoAware(true);
 2851                 axis->setSuppressRetransform(false);
 2852             }
 2853 
 2854             //TODO;
 2855 //          if (axis->position() == Axis::Position::Custom && deltaXMin != 0) {
 2856 //              axis->setOffset(axis->offset() + deltaXMin, false);
 2857 //          }
 2858         }
 2859     }
 2860     // call retransform() on the parent to trigger the update of all axes and curves.
 2861     //no need to do this on load since all plots are retransformed again after the project is loaded.
 2862     if (!q->isLoading())
 2863         q->retransform();
 2864 }
 2865 
 2866 /*
 2867  * calculates the rectangular of the are showing the actual data (plot's rect minus padding),
 2868  * in plot's coordinates.
 2869  */
 2870 void CartesianPlotPrivate::updateDataRect() {
 2871     dataRect = mapRectFromScene(rect);
 2872 
 2873     double paddingLeft = horizontalPadding;
 2874     double paddingRight = rightPadding;
 2875     double paddingTop = verticalPadding;
 2876     double paddingBottom = bottomPadding;
 2877     if (symmetricPadding) {
 2878         paddingRight = horizontalPadding;
 2879         paddingBottom = verticalPadding;
 2880     }
 2881 
 2882     dataRect.setX(dataRect.x() + paddingLeft);
 2883     dataRect.setY(dataRect.y() + paddingTop);
 2884 
 2885     double newHeight = dataRect.height() - paddingBottom;
 2886     if (newHeight < 0)
 2887         newHeight = 0;
 2888     dataRect.setHeight(newHeight);
 2889 
 2890     double newWidth = dataRect.width() - paddingRight;
 2891     if (newWidth < 0)
 2892         newWidth = 0;
 2893     dataRect.setWidth(newWidth);
 2894 }
 2895 
 2896 void CartesianPlotPrivate::rangeChanged() {
 2897     curvesXMinMaxIsDirty = true;
 2898     curvesYMinMaxIsDirty = true;
 2899     if (autoScaleX && autoScaleY)
 2900         q->scaleAuto();
 2901     else if (autoScaleX)
 2902         q->scaleAutoX();
 2903     else if (autoScaleY)
 2904         q->scaleAutoY();
 2905 }
 2906 
 2907 void CartesianPlotPrivate::xRangeFormatChanged() {
 2908     for (auto* axis : q->children<Axis>()) {
 2909         if (axis->orientation() == Axis::Orientation::Horizontal)
 2910             axis->retransformTickLabelStrings();
 2911     }
 2912 }
 2913 
 2914 void CartesianPlotPrivate::yRangeFormatChanged() {
 2915     for (auto* axis : q->children<Axis>()) {
 2916         if (axis->orientation() == Axis::Orientation::Vertical)
 2917             axis->retransformTickLabelStrings();
 2918     }
 2919 }
 2920 
 2921 /*!
 2922  * don't allow any negative values for the x range when log or sqrt scalings are used
 2923  */
 2924 void CartesianPlotPrivate::checkXRange() {
 2925     double min = 0.01;
 2926 
 2927     if (xMin <= 0.0) {
 2928         (min < xMax*min) ? xMin = min : xMin = xMax*min;
 2929         emit q->xMinChanged(xMin);
 2930     } else if (xMax <= 0.0) {
 2931         (-min > xMin*min) ? xMax = -min : xMax = xMin*min;
 2932         emit q->xMaxChanged(xMax);
 2933     }
 2934 }
 2935 
 2936 /*!
 2937  * don't allow any negative values for the y range when log or sqrt scalings are used
 2938  */
 2939 void CartesianPlotPrivate::checkYRange() {
 2940     double min = 0.01;
 2941 
 2942     if (yMin <= 0.0) {
 2943         (min < yMax*min) ? yMin = min : yMin = yMax*min;
 2944         emit q->yMinChanged(yMin);
 2945     } else if (yMax <= 0.0) {
 2946         (-min > yMin*min) ? yMax = -min : yMax = yMin*min;
 2947         emit q->yMaxChanged(yMax);
 2948     }
 2949 }
 2950 
 2951 CartesianScale* CartesianPlotPrivate::createScale(CartesianPlot::Scale type, double sceneStart, double sceneEnd, double logicalStart, double logicalEnd) {
 2952     DEBUG(Q_FUNC_INFO << ", scene start/end = " << sceneStart << '/' << sceneEnd << ", logical start/end = " << logicalStart << '/' << logicalEnd);
 2953 //  Interval<double> interval (logicalStart-0.01, logicalEnd+0.01); //TODO: move this to CartesianScale
 2954     Interval<double> interval (std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max());
 2955 //  Interval<double> interval (logicalStart, logicalEnd);
 2956     if (type == CartesianPlot::Scale::Linear)
 2957         return CartesianScale::createLinearScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd);
 2958     else
 2959         return CartesianScale::createLogScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd, type);
 2960 }
 2961 
 2962 /*!
 2963  * Reimplemented from QGraphicsItem.
 2964  */
 2965 QVariant CartesianPlotPrivate::itemChange(GraphicsItemChange change, const QVariant &value) {
 2966     if (change == QGraphicsItem::ItemPositionChange) {
 2967         const QPointF& itemPos = value.toPointF();//item's center point in parent's coordinates;
 2968         const qreal x = itemPos.x();
 2969         const qreal y = itemPos.y();
 2970 
 2971         //calculate the new rect and forward the changes to the frontend
 2972         QRectF newRect;
 2973         const qreal w = rect.width();
 2974         const qreal h = rect.height();
 2975         newRect.setX(x-w/2);
 2976         newRect.setY(y-h/2);
 2977         newRect.setWidth(w);
 2978         newRect.setHeight(h);
 2979         emit q->rectChanged(newRect);
 2980     }
 2981     return QGraphicsItem::itemChange(change, value);
 2982 }
 2983 
 2984 //##############################################################################
 2985 //##################################  Events  ##################################
 2986 //##############################################################################
 2987 
 2988 /*!
 2989  * \brief CartesianPlotPrivate::mousePressEvent
 2990  * In this function only basic stuff is done. The mousePressEvent is forwarded to the Worksheet, which
 2991  * has access to all cartesian plots and can apply the changes to all plots if the option "applyToAll"
 2992  * is set. The worksheet calls then the corresponding mousepressZoomMode/CursorMode function in this class
 2993  * This is done for mousePress, mouseMove and mouseRelease event
 2994  * This function sends a signal with the logical position, because this is the only value which is the same
 2995  * in all plots. Using the scene coordinates is not possible
 2996  * \param event
 2997  */
 2998 void CartesianPlotPrivate::mousePressEvent(QGraphicsSceneMouseEvent *event) {
 2999 
 3000     if (mouseMode == CartesianPlot::MouseMode::ZoomSelection || mouseMode == CartesianPlot::MouseMode::ZoomXSelection || mouseMode == CartesianPlot::MouseMode::ZoomYSelection)
 3001         emit q->mousePressZoomSelectionModeSignal(cSystem->mapSceneToLogical(event->pos(), AbstractCoordinateSystem::MappingFlag::Limit));
 3002     else if (mouseMode == CartesianPlot::MouseMode::Cursor) {
 3003         setCursor(Qt::SizeHorCursor);
 3004         QPointF logicalPos = cSystem->mapSceneToLogical(event->pos(), AbstractCoordinateSystem::MappingFlag::Limit);
 3005         double cursorPenWidth2 = cursorPen.width()/2.;
 3006         if (cursorPenWidth2 < 10.)
 3007             cursorPenWidth2 = 10.;
 3008         if (cursor0Enable && qAbs(event->pos().x()-cSystem->mapLogicalToScene(QPointF(cursor0Pos.x(),yMin)).x()) < cursorPenWidth2) {
 3009             selectedCursor = 0;
 3010         } else if (cursor1Enable && qAbs(event->pos().x()-cSystem->mapLogicalToScene(QPointF(cursor1Pos.x(),yMin)).x()) < cursorPenWidth2) {
 3011             selectedCursor = 1;
 3012         } else if (QApplication::keyboardModifiers() & Qt::ControlModifier){
 3013             cursor1Enable = true;
 3014             selectedCursor = 1;
 3015             emit q->cursor1EnableChanged(cursor1Enable);
 3016         } else {
 3017             cursor0Enable = true;
 3018             selectedCursor = 0;
 3019             emit q->cursor0EnableChanged(cursor0Enable);
 3020         }
 3021         emit q->mousePressCursorModeSignal(selectedCursor, logicalPos);
 3022 
 3023     } else {
 3024         if (!locked && dataRect.contains(event->pos())) {
 3025             panningStarted = true;
 3026             m_panningStart = event->pos();
 3027             setCursor(Qt::ClosedHandCursor);
 3028         }
 3029     }
 3030     QGraphicsItem::mousePressEvent(event);
 3031 }
 3032 
 3033 void CartesianPlotPrivate::mousePressZoomSelectionMode(QPointF logicalPos) {
 3034     if (mouseMode == CartesianPlot::MouseMode::ZoomSelection) {
 3035 
 3036         if (logicalPos.x() < xMin)
 3037             logicalPos.setX(xMin);
 3038 
 3039         if (logicalPos.x() > xMax)
 3040             logicalPos.setX(xMax);
 3041 
 3042         if (logicalPos.y() < yMin)
 3043             logicalPos.setY(yMin);
 3044 
 3045         if (logicalPos.y() > yMax)
 3046             logicalPos.setY(yMax);
 3047 
 3048         m_selectionStart = cSystem->mapLogicalToScene(logicalPos, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 3049 
 3050     } else if (mouseMode == CartesianPlot::MouseMode::ZoomXSelection) {
 3051         logicalPos.setY(yMin); // must be done, because the other plots can have other ranges, value must be in the scenes
 3052         m_selectionStart.setX(cSystem->mapLogicalToScene(logicalPos, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping).x());
 3053         m_selectionStart.setY(dataRect.y());
 3054     } else if (mouseMode == CartesianPlot::MouseMode::ZoomYSelection) {
 3055         logicalPos.setX(xMin); // must be done, because the other plots can have other ranges, value must be in the scenes
 3056         m_selectionStart.setX(dataRect.x());
 3057         m_selectionStart.setY(cSystem->mapLogicalToScene(logicalPos, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping).y());
 3058     }
 3059     m_selectionEnd = m_selectionStart;
 3060     m_selectionBandIsShown = true;
 3061 }
 3062 
 3063 void CartesianPlotPrivate::mousePressCursorMode(int cursorNumber, QPointF logicalPos) {
 3064 
 3065     cursorNumber == 0 ? cursor0Enable = true : cursor1Enable = true;
 3066 
 3067     QPointF p1(logicalPos.x(), yMin);
 3068     QPointF p2(logicalPos.x(), yMax);
 3069 
 3070     if (cursorNumber == 0) {
 3071         cursor0Pos.setX(logicalPos.x());
 3072         cursor0Pos.setY(0);
 3073     } else {
 3074         cursor1Pos.setX(logicalPos.x());
 3075         cursor1Pos.setY(0);
 3076     }
 3077     update();
 3078 }
 3079 
 3080 void CartesianPlotPrivate::updateCursor() {
 3081     update();
 3082 }
 3083 
 3084 void CartesianPlotPrivate::setZoomSelectionBandShow(bool show) {
 3085     m_selectionBandIsShown = show;
 3086 }
 3087 
 3088 void CartesianPlotPrivate::mouseMoveEvent(QGraphicsSceneMouseEvent* event) {
 3089     if (mouseMode == CartesianPlot::MouseMode::Selection) {
 3090         if (panningStarted && dataRect.contains(event->pos()) ) {
 3091             //don't retransform on small mouse movement deltas
 3092             const int deltaXScene = (m_panningStart.x() - event->pos().x());
 3093             const int deltaYScene = (m_panningStart.y() - event->pos().y());
 3094             if (abs(deltaXScene) < 5 && abs(deltaYScene) < 5)
 3095                 return;
 3096 
 3097             const QPointF logicalEnd = cSystem->mapSceneToLogical(event->pos());
 3098             const QPointF logicalStart = cSystem->mapSceneToLogical(m_panningStart);
 3099 
 3100             //handle the change in x
 3101             switch (xScale) {
 3102             case CartesianPlot::Scale::Linear: {
 3103                 const float deltaX = (logicalStart.x() - logicalEnd.x());
 3104                 xMax += deltaX;
 3105                 xMin += deltaX;
 3106                 break;
 3107             }
 3108             case CartesianPlot::Scale::Log10:
 3109             case CartesianPlot::Scale::Log10Abs: {
 3110                 const float deltaX = log10(logicalStart.x()) - log10(logicalEnd.x());
 3111                 xMin *= pow(10, deltaX);
 3112                 xMax *= pow(10, deltaX);
 3113                 break;
 3114             }
 3115             case CartesianPlot::Scale::Log2:
 3116             case CartesianPlot::Scale::Log2Abs: {
 3117                 const float deltaX = log2(logicalStart.x()) - log2(logicalEnd.x());
 3118                 xMin *= pow(2, deltaX);
 3119                 xMax *= pow(2, deltaX);
 3120                 break;
 3121             }
 3122             case CartesianPlot::Scale::Ln:
 3123             case CartesianPlot::Scale::LnAbs: {
 3124                 const float deltaX = log(logicalStart.x()) - log(logicalEnd.x());
 3125                 xMin *= exp(deltaX);
 3126                 xMax *= exp(deltaX);
 3127                 break;
 3128             }
 3129             case CartesianPlot::Scale::Sqrt:
 3130             case CartesianPlot::Scale::X2:
 3131                 break;
 3132             }
 3133 
 3134             //handle the change in y
 3135             switch (yScale) {
 3136             case CartesianPlot::Scale::Linear: {
 3137                 const float deltaY = (logicalStart.y() - logicalEnd.y());
 3138                 yMax += deltaY;
 3139                 yMin += deltaY;
 3140                 break;
 3141             }
 3142             case CartesianPlot::Scale::Log10:
 3143             case CartesianPlot::Scale::Log10Abs: {
 3144                 const float deltaY = log10(logicalStart.y()) - log10(logicalEnd.y());
 3145                 yMin *= pow(10, deltaY);
 3146                 yMax *= pow(10, deltaY);
 3147                 break;
 3148             }
 3149             case CartesianPlot::Scale::Log2:
 3150             case CartesianPlot::Scale::Log2Abs: {
 3151                 const float deltaY = log2(logicalStart.y()) - log2(logicalEnd.y());
 3152                 yMin *= pow(2, deltaY);
 3153                 yMax *= pow(2, deltaY);
 3154                 break;
 3155             }
 3156             case CartesianPlot::Scale::Ln:
 3157             case CartesianPlot::Scale::LnAbs: {
 3158                 const float deltaY = log(logicalStart.y()) - log(logicalEnd.y());
 3159                 yMin *= exp(deltaY);
 3160                 yMax *= exp(deltaY);
 3161                 break;
 3162             }
 3163             case CartesianPlot::Scale::Sqrt:
 3164             case CartesianPlot::Scale::X2:
 3165                 break;
 3166             }
 3167 
 3168             q->setUndoAware(false);
 3169             q->setAutoScaleX(false);
 3170             q->setAutoScaleY(false);
 3171             q->setUndoAware(true);
 3172 
 3173             retransformScales();
 3174             m_panningStart = event->pos();
 3175         } else
 3176             QGraphicsItem::mouseMoveEvent(event);
 3177     } else if (mouseMode == CartesianPlot::MouseMode::ZoomSelection || mouseMode == CartesianPlot::MouseMode::ZoomXSelection || mouseMode == CartesianPlot::MouseMode::ZoomYSelection) {
 3178         QGraphicsItem::mouseMoveEvent(event);
 3179         if ( !boundingRect().contains(event->pos()) ) {
 3180             q->info(QString());
 3181             return;
 3182         }
 3183         emit q->mouseMoveZoomSelectionModeSignal(cSystem->mapSceneToLogical(event->pos(), AbstractCoordinateSystem::MappingFlag::Limit));
 3184 
 3185     } else if (mouseMode == CartesianPlot::MouseMode::Cursor) {
 3186         QGraphicsItem::mouseMoveEvent(event);
 3187         if (!boundingRect().contains(event->pos())) {
 3188             q->info(i18n("Not inside of the bounding rect"));
 3189             return;
 3190         }
 3191         QPointF logicalPos = cSystem->mapSceneToLogical(event->pos(), AbstractCoordinateSystem::MappingFlag::Limit);
 3192 
 3193         // updating treeview data and cursor position
 3194         // updatign cursor position is done in Worksheet, because
 3195         // multiple plots must be updated
 3196         emit q->mouseMoveCursorModeSignal(selectedCursor, logicalPos);
 3197     }
 3198 }
 3199 
 3200 void CartesianPlotPrivate::mouseMoveZoomSelectionMode(QPointF logicalPos) {
 3201     QString info;
 3202     QPointF logicalStart = cSystem->mapSceneToLogical(m_selectionStart, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 3203     if (mouseMode == CartesianPlot::MouseMode::ZoomSelection) {
 3204         m_selectionEnd = cSystem->mapLogicalToScene(logicalPos, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 3205         QPointF logicalEnd = logicalPos;
 3206         if (xRangeFormat == CartesianPlot::RangeFormat::Numeric)
 3207             info = QString::fromUtf8("Δx=") + QString::number(logicalEnd.x()-logicalStart.x());
 3208         else
 3209             info = i18n("from x=%1 to x=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.x()).toString(xRangeDateTimeFormat),
 3210                         QDateTime::fromMSecsSinceEpoch(logicalEnd.x()).toString(xRangeDateTimeFormat));
 3211 
 3212         info += QLatin1String(", ");
 3213         if (yRangeFormat == CartesianPlot::RangeFormat::Numeric)
 3214             info += QString::fromUtf8("Δy=") + QString::number(logicalEnd.y()-logicalStart.y());
 3215         else
 3216             info += i18n("from y=%1 to y=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.y()).toString(xRangeDateTimeFormat),
 3217                          QDateTime::fromMSecsSinceEpoch(logicalEnd.y()).toString(xRangeDateTimeFormat));
 3218     } else if (mouseMode == CartesianPlot::MouseMode::ZoomXSelection) {
 3219         logicalPos.setY(yMin); // must be done, because the other plots can have other ranges, value must be in the scenes
 3220         m_selectionEnd.setX(cSystem->mapLogicalToScene(logicalPos, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping).x());//event->pos().x());
 3221         m_selectionEnd.setY(dataRect.bottom());
 3222         QPointF logicalEnd = logicalPos;
 3223         if (xRangeFormat == CartesianPlot::RangeFormat::Numeric)
 3224             info = QString::fromUtf8("Δx=") + QString::number(logicalEnd.x()-logicalStart.x());
 3225         else
 3226             info = i18n("from x=%1 to x=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.x()).toString(xRangeDateTimeFormat),
 3227                         QDateTime::fromMSecsSinceEpoch(logicalEnd.x()).toString(xRangeDateTimeFormat));
 3228     } else if (mouseMode == CartesianPlot::MouseMode::ZoomYSelection) {
 3229         m_selectionEnd.setX(dataRect.right());
 3230         logicalPos.setX(xMin); // must be done, because the other plots can have other ranges, value must be in the scenes
 3231         m_selectionEnd.setY(cSystem->mapLogicalToScene(logicalPos, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping).y());//event->pos().y());
 3232         QPointF logicalEnd = logicalPos;
 3233         if (yRangeFormat == CartesianPlot::RangeFormat::Numeric)
 3234             info = QString::fromUtf8("Δy=") + QString::number(logicalEnd.y()-logicalStart.y());
 3235         else
 3236             info = i18n("from y=%1 to y=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.y()).toString(xRangeDateTimeFormat),
 3237                         QDateTime::fromMSecsSinceEpoch(logicalEnd.y()).toString(xRangeDateTimeFormat));
 3238     }
 3239     q->info(info);
 3240     update();
 3241 }
 3242 
 3243 void CartesianPlotPrivate::mouseMoveCursorMode(int cursorNumber, QPointF logicalPos) {
 3244 
 3245     QPointF p1(logicalPos.x(), 0);
 3246     cursorNumber == 0 ? cursor0Pos = p1 : cursor1Pos = p1;
 3247 
 3248     QString info;
 3249     if (xRangeFormat == CartesianPlot::RangeFormat::Numeric)
 3250         info = QString::fromUtf8("x=") + QString::number(logicalPos.x());
 3251     else
 3252         info = i18n("x=%1", QDateTime::fromMSecsSinceEpoch(logicalPos.x()).toString(xRangeDateTimeFormat));
 3253     q->info(info);
 3254     update();
 3255 }
 3256 
 3257 void CartesianPlotPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) {
 3258     setCursor(Qt::ArrowCursor);
 3259     if (mouseMode == CartesianPlot::MouseMode::Selection) {
 3260         panningStarted = false;
 3261 
 3262         //TODO: why do we do this all the time?!?!
 3263         const QPointF& itemPos = pos();//item's center point in parent's coordinates;
 3264         const qreal x = itemPos.x();
 3265         const qreal y = itemPos.y();
 3266 
 3267         //calculate the new rect and set it
 3268         QRectF newRect;
 3269         const qreal w = rect.width();
 3270         const qreal h = rect.height();
 3271         newRect.setX(x-w/2);
 3272         newRect.setY(y-h/2);
 3273         newRect.setWidth(w);
 3274         newRect.setHeight(h);
 3275 
 3276         suppressRetransform = true;
 3277         q->setRect(newRect);
 3278         suppressRetransform = false;
 3279 
 3280         QGraphicsItem::mouseReleaseEvent(event);
 3281     } else if (mouseMode == CartesianPlot::MouseMode::ZoomSelection || mouseMode == CartesianPlot::MouseMode::ZoomXSelection || mouseMode == CartesianPlot::MouseMode::ZoomYSelection) {
 3282         emit q->mouseReleaseZoomSelectionModeSignal();
 3283     }
 3284 }
 3285 
 3286 void CartesianPlotPrivate::mouseReleaseZoomSelectionMode() {
 3287     //don't zoom if very small region was selected, avoid occasional/unwanted zooming
 3288     if ( qAbs(m_selectionEnd.x()-m_selectionStart.x()) < 20 || qAbs(m_selectionEnd.y()-m_selectionStart.y()) < 20 ) {
 3289         m_selectionBandIsShown = false;
 3290         return;
 3291     }
 3292     bool retransformPlot = true;
 3293 
 3294     //determine the new plot ranges
 3295     QPointF logicalZoomStart = cSystem->mapSceneToLogical(m_selectionStart, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 3296     QPointF logicalZoomEnd = cSystem->mapSceneToLogical(m_selectionEnd, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 3297     if (m_selectionEnd.x() > m_selectionStart.x()) {
 3298         xMin = logicalZoomStart.x();
 3299         xMax = logicalZoomEnd.x();
 3300     } else {
 3301         xMin = logicalZoomEnd.x();
 3302         xMax = logicalZoomStart.x();
 3303     }
 3304 
 3305     if (m_selectionEnd.y() > m_selectionStart.y()) {
 3306         yMin = logicalZoomEnd.y();
 3307         yMax = logicalZoomStart.y();
 3308     } else {
 3309         yMin = logicalZoomStart.y();
 3310         yMax = logicalZoomEnd.y();
 3311     }
 3312 
 3313     if (mouseMode == CartesianPlot::MouseMode::ZoomSelection) {
 3314         curvesXMinMaxIsDirty = true;
 3315         curvesYMinMaxIsDirty = true;
 3316         q->setAutoScaleX(false);
 3317         q->setAutoScaleY(false);
 3318     } else if (mouseMode == CartesianPlot::MouseMode::ZoomXSelection) {
 3319         curvesYMinMaxIsDirty = true;
 3320         q->setAutoScaleX(false);
 3321         if (q->autoScaleY() && q->scaleAutoY())
 3322             retransformPlot = false;
 3323     } else if (mouseMode == CartesianPlot::MouseMode::ZoomYSelection) {
 3324         curvesXMinMaxIsDirty = true;
 3325         q->setAutoScaleY(false);
 3326         if (q->autoScaleX() && q->scaleAutoX())
 3327             retransformPlot = false;
 3328     }
 3329 
 3330     if (retransformPlot)
 3331         retransformScales();
 3332 
 3333     m_selectionBandIsShown = false;
 3334 }
 3335 
 3336 void CartesianPlotPrivate::wheelEvent(QGraphicsSceneWheelEvent* event) {
 3337     if (locked)
 3338         return;
 3339 
 3340     //determine first, which axes are selected and zoom only in the corresponding direction.
 3341     //zoom the entire plot if no axes selected.
 3342     bool zoomX = false;
 3343     bool zoomY = false;
 3344     for (auto* axis : q->children<Axis>()) {
 3345         if (!axis->graphicsItem()->isSelected() && !axis->isHovered())
 3346             continue;
 3347 
 3348         if (axis->orientation() == Axis::Orientation::Horizontal)
 3349             zoomX  = true;
 3350         else
 3351             zoomY = true;
 3352     }
 3353 
 3354     if (event->delta() > 0) {
 3355         if (!zoomX && !zoomY) {
 3356             //no special axis selected -> zoom in everything
 3357             q->zoomIn();
 3358         } else {
 3359             if (zoomX) q->zoomInX();
 3360             if (zoomY) q->zoomInY();
 3361         }
 3362     } else {
 3363         if (!zoomX && !zoomY) {
 3364             //no special axis selected -> zoom in everything
 3365             q->zoomOut();
 3366         } else {
 3367             if (zoomX) q->zoomOutX();
 3368             if (zoomY) q->zoomOutY();
 3369         }
 3370     }
 3371 }
 3372 
 3373 void CartesianPlotPrivate::keyPressEvent(QKeyEvent* event) {
 3374     if (event->key() == Qt::Key_Escape) {
 3375         setCursor(Qt::ArrowCursor);
 3376         q->setMouseMode(CartesianPlot::MouseMode::Selection);
 3377         m_selectionBandIsShown = false;
 3378     } else if (event->key() == Qt::Key_Left || event->key() == Qt::Key_Right
 3379         || event->key() == Qt::Key_Up ||event->key() == Qt::Key_Down) {
 3380 
 3381         const auto* worksheet = static_cast<const Worksheet*>(q->parentAspect());
 3382         if (worksheet->layout() == Worksheet::Layout::NoLayout) {
 3383             const int delta = 5;
 3384             QRectF rect = q->rect();
 3385 
 3386             if (event->key() == Qt::Key_Left) {
 3387                 rect.setX(rect.x() - delta);
 3388                 rect.setWidth(rect.width() - delta);
 3389             } else if (event->key() == Qt::Key_Right) {
 3390                 rect.setX(rect.x() + delta);
 3391                 rect.setWidth(rect.width() + delta);
 3392             } else if (event->key() == Qt::Key_Up) {
 3393                 rect.setY(rect.y() - delta);
 3394                 rect.setHeight(rect.height() - delta);
 3395             } else if (event->key() == Qt::Key_Down) {
 3396                 rect.setY(rect.y() + delta);
 3397                 rect.setHeight(rect.height() + delta);
 3398             }
 3399 
 3400             q->setRect(rect);
 3401         }
 3402 
 3403     }
 3404     QGraphicsItem::keyPressEvent(event);
 3405 }
 3406 
 3407 void CartesianPlotPrivate::hoverMoveEvent(QGraphicsSceneHoverEvent* event) {
 3408     QPointF point = event->pos();
 3409     QString info;
 3410     if (dataRect.contains(point)) {
 3411         QPointF logicalPoint = cSystem->mapSceneToLogical(point);
 3412 
 3413         if ((mouseMode == CartesianPlot::MouseMode::ZoomSelection) ||
 3414             mouseMode == CartesianPlot::MouseMode::Selection) {
 3415             info = "x=";
 3416             if (xRangeFormat == CartesianPlot::RangeFormat::Numeric)
 3417                  info += QString::number(logicalPoint.x());
 3418             else
 3419                 info += QDateTime::fromMSecsSinceEpoch(logicalPoint.x()).toString(xRangeDateTimeFormat);
 3420 
 3421             info += ", y=";
 3422             if (yRangeFormat == CartesianPlot::RangeFormat::Numeric)
 3423                 info += QString::number(logicalPoint.y());
 3424             else
 3425                 info += QDateTime::fromMSecsSinceEpoch(logicalPoint.y()).toString(yRangeDateTimeFormat);
 3426         }
 3427 
 3428         if (mouseMode == CartesianPlot::MouseMode::ZoomSelection && !m_selectionBandIsShown) {
 3429             emit q->mouseHoverZoomSelectionModeSignal(logicalPoint);
 3430         } else if (mouseMode == CartesianPlot::MouseMode::ZoomXSelection && !m_selectionBandIsShown) {
 3431             info = "x=";
 3432             if (xRangeFormat == CartesianPlot::RangeFormat::Numeric)
 3433                  info += QString::number(logicalPoint.x());
 3434             else
 3435                 info += QDateTime::fromMSecsSinceEpoch(logicalPoint.x()).toString(xRangeDateTimeFormat);
 3436             emit q->mouseHoverZoomSelectionModeSignal(logicalPoint);
 3437         } else if (mouseMode == CartesianPlot::MouseMode::ZoomYSelection && !m_selectionBandIsShown) {
 3438             info = "y=";
 3439             if (yRangeFormat == CartesianPlot::RangeFormat::Numeric)
 3440                 info += QString::number(logicalPoint.y());
 3441             else
 3442                 info += QDateTime::fromMSecsSinceEpoch(logicalPoint.y()).toString(yRangeDateTimeFormat);
 3443             emit q->mouseHoverZoomSelectionModeSignal(logicalPoint);
 3444         } else if (mouseMode == CartesianPlot::MouseMode::Selection) {
 3445             // hover the nearest curve to the mousepointer
 3446             // hovering curves is implemented in the parent, because no ignoreEvent() exists
 3447             // for it. Checking all curves and hover the first
 3448             bool curve_hovered = false;
 3449             const auto& curves = q->children<Curve>();
 3450             for (int i=curves.count() - 1; i >= 0; i--){ // because the last curve is above the other curves
 3451                 if (curve_hovered){ // if a curve is already hovered, disable hover for the rest
 3452                     curves[i]->setHover(false);
 3453                     continue;
 3454                 }
 3455                 if (curves[i]->activateCurve(event->pos())) {
 3456                     curves[i]->setHover(true);
 3457                     curve_hovered = true;
 3458                     continue;
 3459                 }
 3460                 curves[i]->setHover(false);
 3461             }
 3462         } else if (mouseMode == CartesianPlot::MouseMode::Cursor) {
 3463             info = "x=";
 3464             if (yRangeFormat == CartesianPlot::RangeFormat::Numeric)
 3465                 info += QString::number(logicalPoint.x());
 3466             else
 3467                 info += QDateTime::fromMSecsSinceEpoch(logicalPoint.x()).toString(xRangeDateTimeFormat);
 3468 
 3469             double cursorPenWidth2 = cursorPen.width()/2.;
 3470             if (cursorPenWidth2 < 10.)
 3471                 cursorPenWidth2 = 10.;
 3472             if ((cursor0Enable && qAbs(point.x()-cSystem->mapLogicalToScene(QPointF(cursor0Pos.x(),yMin)).x()) < cursorPenWidth2) ||
 3473                     (cursor1Enable && qAbs(point.x()-cSystem->mapLogicalToScene(QPointF(cursor1Pos.x(),yMin)).x()) < cursorPenWidth2))
 3474                 setCursor(Qt::SizeHorCursor);
 3475             else
 3476                 setCursor(Qt::ArrowCursor);
 3477 
 3478             update();
 3479         }
 3480     } else
 3481         emit q->mouseHoverOutsideDataRectSignal();
 3482 
 3483     q->info(info);
 3484 
 3485     QGraphicsItem::hoverMoveEvent(event);
 3486 }
 3487 
 3488 void CartesianPlotPrivate::mouseHoverOutsideDataRect() {
 3489     m_insideDataRect = false;
 3490     update();
 3491 }
 3492 
 3493 void CartesianPlotPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) {
 3494     QVector<XYCurve*> curves = q->children<XYCurve>();
 3495     for (auto* curve : curves)
 3496         curve->setHover(false);
 3497 
 3498     m_hovered = false;
 3499     QGraphicsItem::hoverLeaveEvent(event);
 3500 }
 3501 
 3502 void CartesianPlotPrivate::mouseHoverZoomSelectionMode(QPointF logicPos) {
 3503     m_insideDataRect = true;
 3504 
 3505     if (mouseMode == CartesianPlot::MouseMode::ZoomSelection && !m_selectionBandIsShown) {
 3506 
 3507     } else if (mouseMode == CartesianPlot::MouseMode::ZoomXSelection && !m_selectionBandIsShown) {
 3508         QPointF p1(logicPos.x(), yMin);
 3509         QPointF p2(logicPos.x(), yMax);
 3510         m_selectionStartLine.setP1(cSystem->mapLogicalToScene(p1, CartesianCoordinateSystem::MappingFlag::Limit));
 3511         m_selectionStartLine.setP2(cSystem->mapLogicalToScene(p2, CartesianCoordinateSystem::MappingFlag::Limit));
 3512     } else if (mouseMode == CartesianPlot::MouseMode::ZoomYSelection && !m_selectionBandIsShown) {
 3513         QPointF p1(xMin, logicPos.y());
 3514         QPointF p2(xMax, logicPos.y());
 3515         m_selectionStartLine.setP1(cSystem->mapLogicalToScene(p1, CartesianCoordinateSystem::MappingFlag::Limit));
 3516         m_selectionStartLine.setP2(cSystem->mapLogicalToScene(p2, CartesianCoordinateSystem::MappingFlag::Limit));
 3517     }
 3518     update(); // because if previous another selection mode was selected, the lines must be deleted
 3519 }
 3520 
 3521 void CartesianPlotPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
 3522     Q_UNUSED(option)
 3523     Q_UNUSED(widget)
 3524 
 3525     if (!isVisible())
 3526         return;
 3527 
 3528     if (!m_printing) {
 3529         painter->save();
 3530 
 3531         painter->setPen(cursorPen);
 3532         QFont font = painter->font();
 3533         font.setPointSize(font.pointSize() * 4);
 3534         painter->setFont(font);
 3535 
 3536         QPointF p1 = cSystem->mapLogicalToScene(QPointF(cursor0Pos.x(),yMin));
 3537         if (cursor0Enable && p1 != QPointF(0,0)){
 3538             QPointF p2 = cSystem->mapLogicalToScene(QPointF(cursor0Pos.x(),yMax));
 3539             painter->drawLine(p1,p2);
 3540             QPointF textPos = p2;
 3541             textPos.setX(p2.x() - m_cursor0Text.size().width()/2);
 3542             textPos.setY(p2.y() - m_cursor0Text.size().height());
 3543             if (textPos.y() < boundingRect().y())
 3544                 textPos.setY(boundingRect().y());
 3545             painter->drawStaticText(textPos, m_cursor0Text);
 3546         }
 3547 
 3548         p1 = cSystem->mapLogicalToScene(QPointF(cursor1Pos.x(),yMin));
 3549         if (cursor1Enable && p1 != QPointF(0,0)){
 3550             QPointF p2 = cSystem->mapLogicalToScene(QPointF(cursor1Pos.x(),yMax));
 3551             painter->drawLine(p1,p2);
 3552             QPointF textPos = p2;
 3553             // TODO: Moving this stuff into other function to not calculate it every time
 3554             textPos.setX(p2.x() - m_cursor1Text.size().width()/2);
 3555             textPos.setY(p2.y() - m_cursor1Text.size().height());
 3556             if (textPos.y() < boundingRect().y())
 3557                 textPos.setY(boundingRect().y());
 3558             painter->drawStaticText(textPos, m_cursor1Text);
 3559         }
 3560 
 3561         painter->restore();
 3562     }
 3563 
 3564     painter->setPen(QPen(Qt::black, 3));
 3565     if ((mouseMode == CartesianPlot::MouseMode::ZoomXSelection || mouseMode == CartesianPlot::MouseMode::ZoomYSelection)
 3566             && (!m_selectionBandIsShown) && m_insideDataRect)
 3567         painter->drawLine(m_selectionStartLine);
 3568 
 3569     if (m_selectionBandIsShown) {
 3570         QPointF selectionStart = m_selectionStart;
 3571         if (m_selectionStart.x() > dataRect.right())
 3572             selectionStart.setX(dataRect.right());
 3573         if (m_selectionStart.x() < dataRect.left())
 3574             selectionStart.setX(dataRect.left());
 3575         if (m_selectionStart.y() > dataRect.bottom())
 3576             selectionStart.setY(dataRect.bottom());
 3577         if (m_selectionStart.y() < dataRect.top())
 3578             selectionStart.setY(dataRect.top());
 3579 
 3580         QPointF selectionEnd = m_selectionEnd;
 3581         if (m_selectionEnd.x() > dataRect.right())
 3582             selectionEnd.setX(dataRect.right());
 3583         if (m_selectionEnd.x() < dataRect.left())
 3584             selectionEnd.setX(dataRect.left());
 3585         if (m_selectionEnd.y() > dataRect.bottom())
 3586             selectionEnd.setY(dataRect.bottom());
 3587         if (m_selectionEnd.y() < dataRect.top())
 3588             selectionEnd.setY(dataRect.top());
 3589         painter->save();
 3590         painter->setPen(QPen(Qt::black, 5));
 3591         painter->drawRect(QRectF(selectionStart, selectionEnd));
 3592         painter->setBrush(Qt::blue);
 3593         painter->setOpacity(0.2);
 3594         painter->drawRect(QRectF(selectionStart, selectionEnd));
 3595         painter->restore();
 3596     }
 3597 }
 3598 
 3599 //##############################################################################
 3600 //##################  Serialization/Deserialization  ###########################
 3601 //##############################################################################
 3602 
 3603 //! Save as XML
 3604 void CartesianPlot::save(QXmlStreamWriter* writer) const {
 3605     Q_D(const CartesianPlot);
 3606 
 3607     writer->writeStartElement( "cartesianPlot" );
 3608     writeBasicAttributes(writer);
 3609     writeCommentElement(writer);
 3610 
 3611     //applied theme
 3612     if (!d->theme.isEmpty()) {
 3613         writer->writeStartElement( "theme" );
 3614         writer->writeAttribute("name", d->theme);
 3615         writer->writeEndElement();
 3616     }
 3617 
 3618     //cursor
 3619     writer->writeStartElement( "cursor" );
 3620     WRITE_QPEN(d->cursorPen);
 3621     writer->writeEndElement();
 3622     //geometry
 3623     writer->writeStartElement( "geometry" );
 3624     writer->writeAttribute( "x", QString::number(d->rect.x()) );
 3625     writer->writeAttribute( "y", QString::number(d->rect.y()) );
 3626     writer->writeAttribute( "width", QString::number(d->rect.width()) );
 3627     writer->writeAttribute( "height", QString::number(d->rect.height()) );
 3628     writer->writeAttribute( "visible", QString::number(d->isVisible()) );
 3629     writer->writeEndElement();
 3630 
 3631     //coordinate system and padding
 3632     writer->writeStartElement( "coordinateSystem" );
 3633     writer->writeAttribute( "autoScaleX", QString::number(d->autoScaleX) );
 3634     writer->writeAttribute( "autoScaleY", QString::number(d->autoScaleY) );
 3635     writer->writeAttribute( "xMin", QString::number(d->xMin, 'g', 16));
 3636     writer->writeAttribute( "xMax", QString::number(d->xMax, 'g', 16) );
 3637     writer->writeAttribute( "yMin", QString::number(d->yMin, 'g', 16) );
 3638     writer->writeAttribute( "yMax", QString::number(d->yMax, 'g', 16) );
 3639     writer->writeAttribute( "xScale", QString::number(static_cast<int>(d->xScale)) );
 3640     writer->writeAttribute( "yScale", QString::number(static_cast<int>(d->yScale)) );
 3641     writer->writeAttribute( "xRangeFormat", QString::number(static_cast<int>(d->xRangeFormat)) );
 3642     writer->writeAttribute( "yRangeFormat", QString::number(static_cast<int>(d->yRangeFormat)) );
 3643     writer->writeAttribute( "xRangeDateTimeFormat", d->xRangeDateTimeFormat );
 3644     writer->writeAttribute( "yRangeDateTimeFormat", d->yRangeDateTimeFormat );
 3645     writer->writeAttribute( "horizontalPadding", QString::number(d->horizontalPadding) );
 3646     writer->writeAttribute( "verticalPadding", QString::number(d->verticalPadding) );
 3647     writer->writeAttribute( "rightPadding", QString::number(d->rightPadding) );
 3648     writer->writeAttribute( "bottomPadding", QString::number(d->bottomPadding) );
 3649     writer->writeAttribute( "symmetricPadding", QString::number(d->symmetricPadding));
 3650     writer->writeEndElement();
 3651 
 3652     //x-scale breaks
 3653     if (d->xRangeBreakingEnabled || !d->xRangeBreaks.list.isEmpty()) {
 3654         writer->writeStartElement("xRangeBreaks");
 3655         writer->writeAttribute( "enabled", QString::number(d->xRangeBreakingEnabled) );
 3656         for (const auto& rb : d->xRangeBreaks.list) {
 3657             writer->writeStartElement("xRangeBreak");
 3658             writer->writeAttribute("start", QString::number(rb.start));
 3659             writer->writeAttribute("end", QString::number(rb.end));
 3660             writer->writeAttribute("position", QString::number(rb.position));
 3661             writer->writeAttribute("style", QString::number(static_cast<int>(rb.style)));
 3662             writer->writeEndElement();
 3663         }
 3664         writer->writeEndElement();
 3665     }
 3666 
 3667     //y-scale breaks
 3668     if (d->yRangeBreakingEnabled || !d->yRangeBreaks.list.isEmpty()) {
 3669         writer->writeStartElement("yRangeBreaks");
 3670         writer->writeAttribute( "enabled", QString::number(d->yRangeBreakingEnabled) );
 3671         for (const auto& rb : d->yRangeBreaks.list) {
 3672             writer->writeStartElement("yRangeBreak");
 3673             writer->writeAttribute("start", QString::number(rb.start));
 3674             writer->writeAttribute("end", QString::number(rb.end));
 3675             writer->writeAttribute("position", QString::number(rb.position));
 3676             writer->writeAttribute("style", QString::number(static_cast<int>(rb.style)));
 3677             writer->writeEndElement();
 3678         }
 3679         writer->writeEndElement();
 3680     }
 3681 
 3682     //serialize all children (plot area, title text label, axes and curves)
 3683     for (auto* elem : children<WorksheetElement>(ChildIndexFlag::IncludeHidden))
 3684         elem->save(writer);
 3685 
 3686     writer->writeEndElement(); // close "cartesianPlot" section
 3687 }
 3688 
 3689 
 3690 //! Load from XML
 3691 bool CartesianPlot::load(XmlStreamReader* reader, bool preview) {
 3692     Q_D(CartesianPlot);
 3693 
 3694     if (!readBasicAttributes(reader))
 3695         return false;
 3696 
 3697     KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used");
 3698     QXmlStreamAttributes attribs;
 3699     QString str;
 3700     bool titleLabelRead = false;
 3701 
 3702     while (!reader->atEnd()) {
 3703         reader->readNext();
 3704         if (reader->isEndElement() && reader->name() == "cartesianPlot")
 3705             break;
 3706 
 3707         if (!reader->isStartElement())
 3708             continue;
 3709 
 3710         if (reader->name() == "comment") {
 3711             if (!readCommentElement(reader))
 3712                 return false;
 3713         } else if (!preview && reader->name() == "theme") {
 3714             attribs = reader->attributes();
 3715             d->theme = attribs.value("name").toString();
 3716         } else if (!preview && reader->name() == "cursor") {
 3717             attribs = reader->attributes();
 3718             QPen pen;
 3719             pen.setWidth(attribs.value("width").toInt());
 3720             pen.setStyle(static_cast<Qt::PenStyle>(attribs.value("style").toInt()));
 3721             QColor color;
 3722             color.setRed(attribs.value("color_r").toInt());
 3723             color.setGreen(attribs.value("color_g").toInt());
 3724             color.setBlue(attribs.value("color_b").toInt());
 3725             pen.setColor(color);
 3726             d->cursorPen = pen;
 3727         } else if (!preview && reader->name() == "geometry") {
 3728             attribs = reader->attributes();
 3729 
 3730             str = attribs.value("x").toString();
 3731             if (str.isEmpty())
 3732                 reader->raiseWarning(attributeWarning.subs("x").toString());
 3733             else
 3734                 d->rect.setX( str.toDouble() );
 3735 
 3736             str = attribs.value("y").toString();
 3737             if (str.isEmpty())
 3738                 reader->raiseWarning(attributeWarning.subs("y").toString());
 3739             else
 3740                 d->rect.setY( str.toDouble() );
 3741 
 3742             str = attribs.value("width").toString();
 3743             if (str.isEmpty())
 3744                 reader->raiseWarning(attributeWarning.subs("width").toString());
 3745             else
 3746                 d->rect.setWidth( str.toDouble() );
 3747 
 3748             str = attribs.value("height").toString();
 3749             if (str.isEmpty())
 3750                 reader->raiseWarning(attributeWarning.subs("height").toString());
 3751             else
 3752                 d->rect.setHeight( str.toDouble() );
 3753 
 3754             str = attribs.value("visible").toString();
 3755             if (str.isEmpty())
 3756                 reader->raiseWarning(attributeWarning.subs("visible").toString());
 3757             else
 3758                 d->setVisible(str.toInt());
 3759         } else if (!preview && reader->name() == "coordinateSystem") {
 3760             attribs = reader->attributes();
 3761 
 3762             READ_INT_VALUE("autoScaleX", autoScaleX, bool);
 3763             READ_INT_VALUE("autoScaleY", autoScaleY, bool);
 3764 
 3765             str = attribs.value("xMin").toString();
 3766             if (str.isEmpty())
 3767                 reader->raiseWarning(attributeWarning.subs("xMin").toString());
 3768             else {
 3769                 d->xMin = str.toDouble();
 3770                 d->xMinPrev = d->xMin;
 3771             }
 3772 
 3773             str = attribs.value("xMax").toString();
 3774             if (str.isEmpty())
 3775                 reader->raiseWarning(attributeWarning.subs("xMax").toString());
 3776             else {
 3777                 d->xMax = str.toDouble();
 3778                 d->xMaxPrev = d->xMax;
 3779             }
 3780 
 3781             str = attribs.value("yMin").toString();
 3782             if (str.isEmpty())
 3783                 reader->raiseWarning(attributeWarning.subs("yMin").toString());
 3784             else {
 3785                 d->yMin = str.toDouble();
 3786                 d->yMinPrev = d->yMin;
 3787             }
 3788 
 3789             str = attribs.value("yMax").toString();
 3790             if (str.isEmpty())
 3791                 reader->raiseWarning(attributeWarning.subs("yMax").toString());
 3792             else {
 3793                 d->yMax = str.toDouble();
 3794                 d->yMaxPrev = d->yMax;
 3795             }
 3796 
 3797             READ_INT_VALUE("xScale", xScale, CartesianPlot::Scale);
 3798             READ_INT_VALUE("yScale", yScale, CartesianPlot::Scale);
 3799 
 3800             READ_INT_VALUE("xRangeFormat", xRangeFormat, CartesianPlot::RangeFormat);
 3801             READ_INT_VALUE("yRangeFormat", yRangeFormat, CartesianPlot::RangeFormat);
 3802 
 3803             str = attribs.value("xRangeDateTimeFormat").toString();
 3804             if (!str.isEmpty())
 3805                 d->xRangeDateTimeFormat = str;
 3806 
 3807             str = attribs.value("yRangeDateTimeFormat").toString();
 3808             if (!str.isEmpty())
 3809                 d->yRangeDateTimeFormat = str;
 3810 
 3811             READ_DOUBLE_VALUE("horizontalPadding", horizontalPadding);
 3812             READ_DOUBLE_VALUE("verticalPadding", verticalPadding);
 3813             READ_DOUBLE_VALUE("rightPadding", rightPadding);
 3814             READ_DOUBLE_VALUE("bottomPadding", bottomPadding);
 3815             READ_INT_VALUE("symmetricPadding", symmetricPadding, bool);
 3816         } else if (!preview && reader->name() == "xRangeBreaks") {
 3817             //delete default rang break
 3818             d->xRangeBreaks.list.clear();
 3819 
 3820             attribs = reader->attributes();
 3821             READ_INT_VALUE("enabled", xRangeBreakingEnabled, bool);
 3822         } else if (!preview && reader->name() == "xRangeBreak") {
 3823             attribs = reader->attributes();
 3824 
 3825             RangeBreak b;
 3826             str = attribs.value("start").toString();
 3827             if (str.isEmpty())
 3828                 reader->raiseWarning(attributeWarning.subs("start").toString());
 3829             else
 3830                 b.start = str.toDouble();
 3831 
 3832             str = attribs.value("end").toString();
 3833             if (str.isEmpty())
 3834                 reader->raiseWarning(attributeWarning.subs("end").toString());
 3835             else
 3836                 b.end = str.toDouble();
 3837 
 3838             str = attribs.value("position").toString();
 3839             if (str.isEmpty())
 3840                 reader->raiseWarning(attributeWarning.subs("position").toString());
 3841             else
 3842                 b.position = str.toDouble();
 3843 
 3844             str = attribs.value("style").toString();
 3845             if (str.isEmpty())
 3846                 reader->raiseWarning(attributeWarning.subs("style").toString());
 3847             else
 3848                 b.style = CartesianPlot::RangeBreakStyle(str.toInt());
 3849 
 3850             d->xRangeBreaks.list << b;
 3851         } else if (!preview && reader->name() == "yRangeBreaks") {
 3852             //delete default rang break
 3853             d->yRangeBreaks.list.clear();
 3854 
 3855             attribs = reader->attributes();
 3856             READ_INT_VALUE("enabled", yRangeBreakingEnabled, bool);
 3857         } else if (!preview && reader->name() == "yRangeBreak") {
 3858             attribs = reader->attributes();
 3859 
 3860             RangeBreak b;
 3861             str = attribs.value("start").toString();
 3862             if (str.isEmpty())
 3863                 reader->raiseWarning(attributeWarning.subs("start").toString());
 3864             else
 3865                 b.start = str.toDouble();
 3866 
 3867             str = attribs.value("end").toString();
 3868             if (str.isEmpty())
 3869                 reader->raiseWarning(attributeWarning.subs("end").toString());
 3870             else
 3871                 b.end = str.toDouble();
 3872 
 3873             str = attribs.value("position").toString();
 3874             if (str.isEmpty())
 3875                 reader->raiseWarning(attributeWarning.subs("position").toString());
 3876             else
 3877                 b.position = str.toDouble();
 3878 
 3879             str = attribs.value("style").toString();
 3880             if (str.isEmpty())
 3881                 reader->raiseWarning(attributeWarning.subs("style").toString());
 3882             else
 3883                 b.style = CartesianPlot::RangeBreakStyle(str.toInt());
 3884 
 3885             d->yRangeBreaks.list << b;
 3886         } else if (reader->name() == "textLabel") {
 3887             if (!titleLabelRead) {
 3888                 //the first text label is always the title label
 3889                 m_title->load(reader, preview);
 3890                 titleLabelRead = true;
 3891 
 3892                 //TODO: the name is read in m_title->load() but we overwrite it here
 3893                 //since the old projects don't have this " - Title" appendix yet that we add in init().
 3894                 //can be removed in couple of releases
 3895                 m_title->setName(name() + QLatin1String(" - ") + i18n("Title"));
 3896             } else {
 3897                 TextLabel* label = new TextLabel("text label");
 3898                 if (label->load(reader, preview)) {
 3899                     addChildFast(label);
 3900                     label->setParentGraphicsItem(graphicsItem());
 3901                 } else {
 3902                     delete label;
 3903                     return false;
 3904                 }
 3905             }
 3906         } else if (reader->name() == "image") {
 3907             auto* image = new Image(QString());
 3908             if (!image->load(reader, preview)) {
 3909                 delete image;
 3910                 return false;
 3911             } else
 3912                 addChildFast(image);
 3913         } else if (reader->name() == "plotArea")
 3914             m_plotArea->load(reader, preview);
 3915         else if (reader->name() == "axis") {
 3916             auto* axis = new Axis(QString());
 3917             if (axis->load(reader, preview))
 3918                 addChildFast(axis);
 3919             else {
 3920                 delete axis;
 3921                 return false;
 3922             }
 3923         } else if (reader->name() == "xyCurve") {
 3924             auto* curve = new XYCurve(QString());
 3925             if (curve->load(reader, preview))
 3926                 addChildFast(curve);
 3927             else {
 3928                 removeChild(curve);
 3929                 return false;
 3930             }
 3931         } else if (reader->name() == "xyEquationCurve") {
 3932             auto* curve = new XYEquationCurve(QString());
 3933             if (curve->load(reader, preview))
 3934                 addChildFast(curve);
 3935             else {
 3936                 removeChild(curve);
 3937                 return false;
 3938             }
 3939         } else if (reader->name() == "xyDataReductionCurve") {
 3940             auto* curve = new XYDataReductionCurve(QString());
 3941             if (curve->load(reader, preview))
 3942                 addChildFast(curve);
 3943             else {
 3944                 removeChild(curve);
 3945                 return false;
 3946             }
 3947         } else if (reader->name() == "xyDifferentiationCurve") {
 3948             auto* curve = new XYDifferentiationCurve(QString());
 3949             if (curve->load(reader, preview))
 3950                 addChildFast(curve);
 3951             else {
 3952                 removeChild(curve);
 3953                 return false;
 3954             }
 3955         } else if (reader->name() == "xyIntegrationCurve") {
 3956             auto* curve = new XYIntegrationCurve(QString());
 3957             if (curve->load(reader, preview))
 3958                 addChildFast(curve);
 3959             else {
 3960                 removeChild(curve);
 3961                 return false;
 3962             }
 3963         } else if (reader->name() == "xyInterpolationCurve") {
 3964             auto* curve = new XYInterpolationCurve(QString());
 3965             if (curve->load(reader, preview))
 3966                 addChildFast(curve);
 3967             else {
 3968                 removeChild(curve);
 3969                 return false;
 3970             }
 3971         } else if (reader->name() == "xySmoothCurve") {
 3972             auto* curve = new XYSmoothCurve(QString());
 3973             if (curve->load(reader, preview))
 3974                 addChildFast(curve);
 3975             else {
 3976                 removeChild(curve);
 3977                 return false;
 3978             }
 3979         } else if (reader->name() == "xyFitCurve") {
 3980             auto* curve = new XYFitCurve(QString());
 3981             if (curve->load(reader, preview))
 3982                 addChildFast(curve);
 3983             else {
 3984                 removeChild(curve);
 3985                 return false;
 3986             }
 3987         } else if (reader->name() == "xyFourierFilterCurve") {
 3988             auto* curve = new XYFourierFilterCurve(QString());
 3989             if (curve->load(reader, preview))
 3990                 addChildFast(curve);
 3991             else {
 3992                 removeChild(curve);
 3993                 return false;
 3994             }
 3995         } else if (reader->name() == "xyFourierTransformCurve") {
 3996             auto* curve = new XYFourierTransformCurve(QString());
 3997             if (curve->load(reader, preview))
 3998                 addChildFast(curve);
 3999             else {
 4000                 removeChild(curve);
 4001                 return false;
 4002             }
 4003         } else if (reader->name() == "xyConvolutionCurve") {
 4004             auto* curve = new XYConvolutionCurve(QString());
 4005             if (curve->load(reader, preview))
 4006                 addChildFast(curve);
 4007             else {
 4008                 removeChild(curve);
 4009                 return false;
 4010             }
 4011         } else if (reader->name() == "xyCorrelationCurve") {
 4012             auto* curve = new XYCorrelationCurve(QString());
 4013             if (curve->load(reader, preview))
 4014                 addChildFast(curve);
 4015             else {
 4016                 removeChild(curve);
 4017                 return false;
 4018             }
 4019         } else if (reader->name() == "cartesianPlotLegend") {
 4020             m_legend = new CartesianPlotLegend(this, QString());
 4021             if (m_legend->load(reader, preview))
 4022                 addChildFast(m_legend);
 4023             else {
 4024                 delete m_legend;
 4025                 return false;
 4026             }
 4027         } else if (reader->name() == "customPoint") {
 4028             auto* point = new CustomPoint(this, QString());
 4029             if (point->load(reader, preview))
 4030                 addChildFast(point);
 4031             else {
 4032                 delete point;
 4033                 return false;
 4034             }
 4035         } else if (reader->name() == "referenceLine") {
 4036             auto* line = new ReferenceLine(this, QString());
 4037             if (line->load(reader, preview))
 4038                 addChildFast(line);
 4039             else {
 4040                 delete line;
 4041                 return false;
 4042             }
 4043         } else if (reader->name() == "Histogram") {
 4044             auto* curve = new Histogram("Histogram");
 4045             if (curve->load(reader, preview))
 4046                 addChildFast(curve);
 4047             else {
 4048                 removeChild(curve);
 4049                 return false;
 4050             }
 4051         } else { // unknown element
 4052             reader->raiseWarning(i18n("unknown cartesianPlot element '%1'", reader->name().toString()));
 4053             if (!reader->skipToEndElement()) return false;
 4054         }
 4055     }
 4056 
 4057     if (preview)
 4058         return true;
 4059 
 4060     d->retransform();
 4061 
 4062     //if a theme was used, initialize the color palette
 4063     if (!d->theme.isEmpty()) {
 4064         //TODO: check whether the theme config really exists
 4065         KConfig config( ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig );
 4066         this->setColorPalette(config);
 4067     } else {
 4068         //initialize the color palette with default colors
 4069         this->setColorPalette(KConfig());
 4070     }
 4071 
 4072     return true;
 4073 }
 4074 
 4075 //##############################################################################
 4076 //#########################  Theme management ##################################
 4077 //##############################################################################
 4078 void CartesianPlot::loadTheme(const QString& theme) {
 4079     if (!theme.isEmpty()) {
 4080         KConfig config(ThemeHandler::themeFilePath(theme), KConfig::SimpleConfig);
 4081         loadThemeConfig(config);
 4082     } else {
 4083         KConfig config;
 4084         loadThemeConfig(config);
 4085     }
 4086 }
 4087 
 4088 void CartesianPlot::loadThemeConfig(const KConfig& config) {
 4089     Q_D(CartesianPlot);
 4090 
 4091     QString theme = QString();
 4092     if (config.hasGroup(QLatin1String("Theme"))) {
 4093         theme = config.name();
 4094 
 4095         // theme path is saved with UNIX dir separator
 4096         theme = theme.right(theme.length() - theme.lastIndexOf(QLatin1Char('/')) - 1);
 4097         DEBUG(Q_FUNC_INFO << ", set theme to " << STDSTRING(theme));
 4098     }
 4099 
 4100     //loadThemeConfig() can be called from
 4101     //1. CartesianPlot::setTheme() when the user changes the theme for the plot
 4102     //2. Worksheet::setTheme() -> Worksheet::loadTheme() when the user changes the theme for the worksheet
 4103     //In the second case (i.e. when d->theme is not equal to theme yet),
 4104     ///we need to put the new theme name on the undo-stack.
 4105     if (theme != d->theme)
 4106         exec(new CartesianPlotSetThemeCmd(d, theme, ki18n("%1: set theme")));
 4107 
 4108     //load the color palettes for the curves
 4109     this->setColorPalette(config);
 4110 
 4111     //load the theme for all the children
 4112     for (auto* child : children<WorksheetElement>(ChildIndexFlag::IncludeHidden))
 4113         child->loadThemeConfig(config);
 4114 
 4115     d->update(this->rect());
 4116 }
 4117 
 4118 void CartesianPlot::saveTheme(KConfig &config) {
 4119     const QVector<Axis*>& axisElements = children<Axis>(ChildIndexFlag::IncludeHidden);
 4120     const QVector<PlotArea*>& plotAreaElements = children<PlotArea>(ChildIndexFlag::IncludeHidden);
 4121     const QVector<TextLabel*>& textLabelElements = children<TextLabel>(ChildIndexFlag::IncludeHidden);
 4122 
 4123     axisElements.at(0)->saveThemeConfig(config);
 4124     plotAreaElements.at(0)->saveThemeConfig(config);
 4125     textLabelElements.at(0)->saveThemeConfig(config);
 4126 
 4127     for (auto *child : children<XYCurve>(ChildIndexFlag::IncludeHidden))
 4128         child->saveThemeConfig(config);
 4129 }
 4130 
 4131 //Generating colors from 5-color theme palette
 4132 void CartesianPlot::setColorPalette(const KConfig& config) {
 4133     if (config.hasGroup(QLatin1String("Theme"))) {
 4134         KConfigGroup group = config.group(QLatin1String("Theme"));
 4135 
 4136         //read the five colors defining the palette
 4137         m_themeColorPalette.clear();
 4138         m_themeColorPalette.append(group.readEntry("ThemePaletteColor1", QColor()));
 4139         m_themeColorPalette.append(group.readEntry("ThemePaletteColor2", QColor()));
 4140         m_themeColorPalette.append(group.readEntry("ThemePaletteColor3", QColor()));
 4141         m_themeColorPalette.append(group.readEntry("ThemePaletteColor4", QColor()));
 4142         m_themeColorPalette.append(group.readEntry("ThemePaletteColor5", QColor()));
 4143     } else {
 4144         //no theme is available, provide 5 "default colors"
 4145         m_themeColorPalette.clear();
 4146         m_themeColorPalette.append(QColor(25, 25, 25));
 4147         m_themeColorPalette.append(QColor(0, 0, 127));
 4148         m_themeColorPalette.append(QColor(127 ,0, 0));
 4149         m_themeColorPalette.append(QColor(0, 127, 0));
 4150         m_themeColorPalette.append(QColor(85, 0, 127));
 4151     }
 4152 
 4153     //generate 30 additional shades if the color palette contains more than one color
 4154     if (m_themeColorPalette.at(0) != m_themeColorPalette.at(1)) {
 4155         QColor c;
 4156 
 4157         //3 factors to create shades from theme's palette
 4158         std::array<float, 3> fac = {0.25f, 0.45f, 0.65f};
 4159 
 4160         //Generate 15 lighter shades
 4161         for (int i = 0; i < 5; i++) {
 4162             for (int j = 1; j < 4; j++) {
 4163                 c.setRed( m_themeColorPalette.at(i).red()*(1-fac[j-1]) );
 4164                 c.setGreen( m_themeColorPalette.at(i).green()*(1-fac[j-1]) );
 4165                 c.setBlue( m_themeColorPalette.at(i).blue()*(1-fac[j-1]) );
 4166                 m_themeColorPalette.append(c);
 4167             }
 4168         }
 4169 
 4170         //Generate 15 darker shades
 4171         for (int i = 0; i < 5; i++) {
 4172             for (int j = 4; j < 7; j++) {
 4173                 c.setRed( m_themeColorPalette.at(i).red()+((255-m_themeColorPalette.at(i).red())*fac[j-4]) );
 4174                 c.setGreen( m_themeColorPalette.at(i).green()+((255-m_themeColorPalette.at(i).green())*fac[j-4]) );
 4175                 c.setBlue( m_themeColorPalette.at(i).blue()+((255-m_themeColorPalette.at(i).blue())*fac[j-4]) );
 4176                 m_themeColorPalette.append(c);
 4177             }
 4178         }
 4179     }
 4180 }
 4181 
 4182 const QList<QColor>& CartesianPlot::themeColorPalette() const {
 4183     return m_themeColorPalette;
 4184 }