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

0001 /*
0002     File                 : XYCurve.cpp
0003     Project              : LabPlot
0004     Description          : A xy-curve
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2010-2024 Alexander Semke <alexander.semke@web.de>
0007     SPDX-FileCopyrightText: 2013-2021 Stefan Gerlach <stefan.gerlach@uni.kn>
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 /*!
0012   \class XYCurve
0013   \brief A 2D-curve, provides an interface for editing many properties of the curve.
0014 
0015   \ingroup worksheet
0016 */
0017 
0018 #include "XYCurve.h"
0019 #include "XYCurvePrivate.h"
0020 #include "backend/core/AbstractColumn.h"
0021 #include "backend/core/Project.h"
0022 #include "backend/core/Settings.h"
0023 #include "backend/core/column/Column.h"
0024 #include "backend/gsl/errors.h"
0025 #include "backend/lib/XmlStreamReader.h"
0026 #include "backend/lib/commandtemplates.h"
0027 #include "backend/lib/macrosCurve.h"
0028 #include "backend/lib/trace.h"
0029 #include "backend/spreadsheet/Spreadsheet.h"
0030 #include "backend/worksheet/Background.h"
0031 #include "backend/worksheet/Line.h"
0032 #include "backend/worksheet/Worksheet.h"
0033 #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h"
0034 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
0035 #include "backend/worksheet/plots/cartesian/ErrorBarStyle.h"
0036 #include "backend/worksheet/plots/cartesian/Symbol.h"
0037 #include "tools/ImageTools.h"
0038 
0039 #include <KConfig>
0040 #include <KConfigGroup>
0041 #include <KLocalizedString>
0042 
0043 #include <QGraphicsSceneMouseEvent>
0044 #include <QMenu>
0045 #include <QPainter>
0046 #include <QScreen>
0047 
0048 #include <gsl/gsl_errno.h>
0049 #include <gsl/gsl_math.h>
0050 #include <gsl/gsl_spline.h>
0051 
0052 using Dimension = CartesianCoordinateSystem::Dimension;
0053 
0054 CURVE_COLUMN_CONNECT(XYCurve, X, x, recalcLogicalPoints)
0055 CURVE_COLUMN_CONNECT(XYCurve, Y, y, recalcLogicalPoints)
0056 CURVE_COLUMN_CONNECT(XYCurve, Values, values, recalcLogicalPoints)
0057 
0058 XYCurve::XYCurve(const QString& name, AspectType type)
0059     : Plot(name, new XYCurvePrivate(this), type) {
0060     init();
0061 }
0062 
0063 XYCurve::XYCurve(const QString& name, XYCurvePrivate* dd, AspectType type)
0064     : Plot(name, dd, type) {
0065     init();
0066 }
0067 
0068 // no need to delete the d-pointer here - it inherits from QGraphicsItem
0069 // and is deleted during the cleanup in QGraphicsScene
0070 XYCurve::~XYCurve() = default;
0071 
0072 void XYCurve::init() {
0073     Q_D(XYCurve);
0074 
0075     KConfig config;
0076     KConfigGroup group = config.group(QStringLiteral("XYCurve"));
0077 
0078     d->lineType = (LineType)group.readEntry(QStringLiteral("LineType"), static_cast<int>(LineType::Line));
0079     d->lineIncreasingXOnly = group.readEntry(QStringLiteral("LineIncreasingXOnly"), false);
0080     d->lineSkipGaps = group.readEntry(QStringLiteral("SkipLineGaps"), false);
0081     d->lineInterpolationPointsCount = group.readEntry(QStringLiteral("LineInterpolationPointsCount"), 1);
0082 
0083     d->line = new Line(QString());
0084     d->line->setCreateXmlElement(false);
0085     d->line->setHidden(true);
0086     addChild(d->line);
0087     d->line->init(group);
0088     connect(d->line, &Line::updatePixmapRequested, [=] {
0089         d->updatePixmap();
0090         Q_EMIT appearanceChanged();
0091     });
0092     connect(d->line, &Line::updateRequested, [=] {
0093         d->recalcShapeAndBoundingRect();
0094         Q_EMIT appearanceChanged();
0095     });
0096 
0097     d->dropLine = new Line(QString());
0098     d->dropLine->setPrefix(QStringLiteral("DropLine"));
0099     d->dropLine->setHidden(true);
0100     addChild(d->dropLine);
0101     d->dropLine->init(group);
0102     connect(d->dropLine, &Line::dropLineTypeChanged, [=] {
0103         d->updateDropLines();
0104     });
0105     connect(d->dropLine, &Line::updatePixmapRequested, [=] {
0106         d->updatePixmap();
0107     });
0108     connect(d->dropLine, &Line::updateRequested, [=] {
0109         d->recalcShapeAndBoundingRect();
0110     });
0111 
0112     // initialize the symbol
0113     d->symbol = new Symbol(QString());
0114     addChild(d->symbol);
0115     d->symbol->setHidden(true);
0116     d->symbol->init(group);
0117     connect(d->symbol, &Symbol::updateRequested, [=] {
0118         d->updateSymbols();
0119         Q_EMIT appearanceChanged();
0120     });
0121     connect(d->symbol, &Symbol::updatePixmapRequested, [=] {
0122         d->updatePixmap();
0123         Q_EMIT appearanceChanged();
0124     });
0125 
0126     // values
0127     d->valuesType = (ValuesType)group.readEntry(QStringLiteral("ValuesType"), static_cast<int>(ValuesType::NoValues));
0128     d->valuesPosition = (ValuesPosition)group.readEntry(QStringLiteral("ValuesPosition"), static_cast<int>(ValuesPosition::Above));
0129     d->valuesDistance = group.readEntry(QStringLiteral("ValuesDistance"), Worksheet::convertToSceneUnits(5, Worksheet::Unit::Point));
0130     d->valuesRotationAngle = group.readEntry(QStringLiteral("ValuesRotation"), 0.0);
0131     d->valuesOpacity = group.readEntry(QStringLiteral("ValuesOpacity"), 1.0);
0132     d->valuesNumericFormat = group.readEntry(QStringLiteral("ValuesNumericFormat"), QStringLiteral("f")).at(0).toLatin1();
0133     d->valuesPrecision = group.readEntry(QStringLiteral("ValuesNumericFormat"), 2);
0134     d->valuesDateTimeFormat = group.readEntry(QStringLiteral("ValuesDateTimeFormat"), QStringLiteral("yyyy-MM-dd"));
0135     d->valuesPrefix = group.readEntry(QStringLiteral("ValuesPrefix"), QStringLiteral(""));
0136     d->valuesSuffix = group.readEntry(QStringLiteral("ValuesSuffix"), QStringLiteral(""));
0137     d->valuesFont = group.readEntry(QStringLiteral("ValuesFont"), QFont());
0138     d->valuesFont.setPixelSize(Worksheet::convertToSceneUnits(8, Worksheet::Unit::Point));
0139     d->valuesColor = group.readEntry(QStringLiteral("ValuesColor"), QColor(Qt::black));
0140 
0141     // Background/Filling
0142     d->background = new Background(QString());
0143     d->background->setPrefix(QStringLiteral("Filling"));
0144     d->background->setPositionAvailable(true);
0145     addChild(d->background);
0146     d->background->setHidden(true);
0147     d->background->init(group);
0148     connect(d->background, &Background::updateRequested, [=] {
0149         d->updatePixmap();
0150     });
0151     connect(d->background, &Background::updatePositionRequested, [=] {
0152         d->updateFilling();
0153     });
0154 
0155     // error bars
0156     d->xErrorBar = new ErrorBar(QString());
0157     d->xErrorBar->setPrefix(QStringLiteral("X"));
0158     addChild(d->xErrorBar);
0159     d->xErrorBar->setHidden(true);
0160     d->xErrorBar->init(group);
0161     connect(d->xErrorBar, &ErrorBar::updateRequested, [=] {
0162         d->updateErrorBars();
0163     });
0164 
0165     d->yErrorBar = new ErrorBar(QString());
0166     d->yErrorBar->setPrefix(QStringLiteral("Y"));
0167     addChild(d->yErrorBar);
0168     d->yErrorBar->setHidden(true);
0169     d->yErrorBar->init(group);
0170     connect(d->yErrorBar, &ErrorBar::updateRequested, [=] {
0171         d->updateErrorBars();
0172     });
0173 
0174     d->errorBarStyle = new ErrorBarStyle(QString());
0175     addChild(d->errorBarStyle);
0176     d->errorBarStyle->setHidden(true);
0177     d->errorBarStyle->init(group);
0178     connect(d->errorBarStyle, &ErrorBarStyle::updateRequested, [=] {
0179         d->updateErrorBars();
0180     });
0181     connect(d->errorBarStyle, &ErrorBarStyle::updatePixmapRequested, [=] {
0182         d->updatePixmap();
0183     });
0184 
0185     // marginal plots (rug, histogram, boxplot)
0186     d->rugEnabled = group.readEntry(QStringLiteral("RugEnabled"), false);
0187     d->rugOrientation = (WorksheetElement::Orientation)group.readEntry(QStringLiteral("RugOrientation"), (int)WorksheetElement::Orientation::Both);
0188     d->rugLength = group.readEntry(QStringLiteral("RugLength"), Worksheet::convertToSceneUnits(5, Worksheet::Unit::Point));
0189     d->rugWidth = group.readEntry(QStringLiteral("RugWidth"), 0.0);
0190     d->rugOffset = group.readEntry(QStringLiteral("RugOffset"), 0.0);
0191 }
0192 
0193 void XYCurve::initActions() {
0194     navigateToAction = new QAction(QIcon::fromTheme(QStringLiteral("go-next-view")), QString(), this);
0195     connect(navigateToAction, SIGNAL(triggered(bool)), this, SLOT(navigateTo()));
0196 
0197     m_menusInitialized = true;
0198 }
0199 
0200 QMenu* XYCurve::createContextMenu() {
0201     if (!m_menusInitialized)
0202         initActions();
0203 
0204     QMenu* menu = WorksheetElement::createContextMenu();
0205     QAction* visibilityAction = this->visibilityAction(); // skip the first action because of the "title-action", second is visible
0206 
0207     //"data analysis" menu
0208     //  auto* plot = static_cast<CartesianPlot*>(parentAspect());
0209     menu->insertMenu(visibilityAction, m_plot->analysisMenu());
0210     menu->insertSeparator(visibilityAction);
0211 
0212     //"Navigate to spreadsheet"-action, show only if x- or y-columns have data from a spreadsheet
0213     AbstractAspect* parentSpreadsheet = nullptr;
0214     if (xColumn() && dynamic_cast<Spreadsheet*>(xColumn()->parentAspect()))
0215         parentSpreadsheet = xColumn()->parentAspect();
0216     else if (yColumn() && dynamic_cast<Spreadsheet*>(yColumn()->parentAspect()))
0217         parentSpreadsheet = yColumn()->parentAspect();
0218 
0219     if (parentSpreadsheet) {
0220         navigateToAction->setText(i18n("Navigate to \"%1\"", parentSpreadsheet->name()));
0221         navigateToAction->setData(parentSpreadsheet->path());
0222         menu->insertAction(visibilityAction, navigateToAction);
0223         menu->insertSeparator(visibilityAction);
0224     }
0225 
0226     // if the context menu is called on an item that is not selected yet, select it
0227     if (!graphicsItem()->isSelected())
0228         graphicsItem()->setSelected(true);
0229 
0230     return menu;
0231 }
0232 
0233 /*!
0234     Returns an icon to be used in the project explorer.
0235 */
0236 QIcon XYCurve::icon() const {
0237     return QIcon::fromTheme(QStringLiteral("labplot-xy-curve"));
0238 }
0239 
0240 // ##############################################################################
0241 // ##########################  getter methods  ##################################
0242 // ##############################################################################
0243 // data source
0244 const AbstractColumn* XYCurve::column(const Dimension dim) const {
0245     switch (dim) {
0246     case Dimension::X:
0247         return xColumn();
0248     case Dimension::Y:
0249         return yColumn();
0250     }
0251     return nullptr;
0252 }
0253 BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, xColumn, xColumn)
0254 BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, yColumn, yColumn)
0255 BASIC_SHARED_D_READER_IMPL(XYCurve, QString, xColumnPath, xColumnPath)
0256 BASIC_SHARED_D_READER_IMPL(XYCurve, QString, yColumnPath, yColumnPath)
0257 
0258 // line
0259 BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::LineType, lineType, lineType)
0260 BASIC_SHARED_D_READER_IMPL(XYCurve, bool, lineSkipGaps, lineSkipGaps)
0261 BASIC_SHARED_D_READER_IMPL(XYCurve, bool, lineIncreasingXOnly, lineIncreasingXOnly)
0262 BASIC_SHARED_D_READER_IMPL(XYCurve, int, lineInterpolationPointsCount, lineInterpolationPointsCount)
0263 
0264 Line* XYCurve::line() const {
0265     Q_D(const XYCurve);
0266     return d->line;
0267 }
0268 
0269 Line* XYCurve::dropLine() const {
0270     Q_D(const XYCurve);
0271     return d->dropLine;
0272 }
0273 
0274 // symbols
0275 Symbol* XYCurve::symbol() const {
0276     Q_D(const XYCurve);
0277     return d->symbol;
0278 }
0279 
0280 // values
0281 BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ValuesType, valuesType, valuesType)
0282 BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, valuesColumn, valuesColumn)
0283 BASIC_SHARED_D_READER_IMPL(XYCurve, QString, valuesColumnPath, valuesColumnPath)
0284 
0285 BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ValuesPosition, valuesPosition, valuesPosition)
0286 BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, valuesDistance, valuesDistance)
0287 BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, valuesRotationAngle, valuesRotationAngle)
0288 BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, valuesOpacity, valuesOpacity)
0289 BASIC_SHARED_D_READER_IMPL(XYCurve, char, valuesNumericFormat, valuesNumericFormat)
0290 BASIC_SHARED_D_READER_IMPL(XYCurve, int, valuesPrecision, valuesPrecision)
0291 BASIC_SHARED_D_READER_IMPL(XYCurve, QString, valuesDateTimeFormat, valuesDateTimeFormat)
0292 BASIC_SHARED_D_READER_IMPL(XYCurve, QString, valuesPrefix, valuesPrefix)
0293 BASIC_SHARED_D_READER_IMPL(XYCurve, QString, valuesSuffix, valuesSuffix)
0294 BASIC_SHARED_D_READER_IMPL(XYCurve, QColor, valuesColor, valuesColor)
0295 BASIC_SHARED_D_READER_IMPL(XYCurve, QFont, valuesFont, valuesFont)
0296 
0297 // Filling
0298 Background* XYCurve::background() const {
0299     Q_D(const XYCurve);
0300     return d->background;
0301 }
0302 
0303 // error bars
0304 ErrorBar* XYCurve::xErrorBar() const {
0305     Q_D(const XYCurve);
0306     return d->xErrorBar;
0307 }
0308 
0309 ErrorBar* XYCurve::yErrorBar() const {
0310     Q_D(const XYCurve);
0311     return d->yErrorBar;
0312 }
0313 
0314 ErrorBarStyle* XYCurve::errorBarStyle() const {
0315     Q_D(const XYCurve);
0316     return d->errorBarStyle;
0317 }
0318 
0319 // margin plots
0320 BASIC_SHARED_D_READER_IMPL(XYCurve, bool, rugEnabled, rugEnabled)
0321 BASIC_SHARED_D_READER_IMPL(XYCurve, WorksheetElement::Orientation, rugOrientation, rugOrientation)
0322 BASIC_SHARED_D_READER_IMPL(XYCurve, double, rugLength, rugLength)
0323 BASIC_SHARED_D_READER_IMPL(XYCurve, double, rugWidth, rugWidth)
0324 BASIC_SHARED_D_READER_IMPL(XYCurve, double, rugOffset, rugOffset)
0325 
0326 /*!
0327  * return \c true if the data in the source columns (x, y) used in the analysis curves, \c false otherwise
0328  */
0329 bool XYCurve::isSourceDataChangedSinceLastRecalc() const {
0330     Q_D(const XYCurve);
0331     return d->sourceDataChangedSinceLastRecalc;
0332 }
0333 
0334 double XYCurve::minimum(const Dimension) const {
0335     // TODO
0336     return NAN;
0337 }
0338 
0339 double XYCurve::maximum(const Dimension) const {
0340     // TODO
0341     return NAN;
0342 }
0343 
0344 bool XYCurve::hasData() const {
0345     Q_D(const XYCurve);
0346     return (d->xColumn != nullptr || d->yColumn != nullptr);
0347 }
0348 
0349 bool XYCurve::usingColumn(const Column* column) const {
0350     Q_D(const XYCurve);
0351     return (d->xColumn == column || d->yColumn == column || (d->xErrorBar->type() == ErrorBar::Type::Symmetric && d->xErrorBar->plusColumn() == column)
0352             || (d->xErrorBar->type() == ErrorBar::Type::Asymmetric && (d->xErrorBar->plusColumn() == column || d->xErrorBar->minusColumn() == column))
0353             || (d->yErrorBar->type() == ErrorBar::Type::Symmetric && d->yErrorBar->plusColumn() == column)
0354             || (d->yErrorBar->type() == ErrorBar::Type::Asymmetric && (d->yErrorBar->plusColumn() == column || d->yErrorBar->minusColumn() == column))
0355             || (d->valuesType == ValuesType::CustomColumn && d->valuesColumn == column));
0356 }
0357 
0358 void XYCurve::updateColumnDependencies(const AbstractColumn* column) {
0359     Q_D(XYCurve);
0360     const QString& columnPath = column->path();
0361     setUndoAware(false);
0362 
0363     if (d->xColumn == column) // the column is the same and was just renamed -> update the column path
0364         d->xColumnPath = columnPath;
0365     else if (d->xColumnPath == columnPath) // another column was renamed to the current path -> set and connect to the new column
0366         setXColumn(column);
0367 
0368     if (d->yColumn == column)
0369         d->yColumnPath = columnPath;
0370     else if (d->yColumnPath == columnPath)
0371         setYColumn(column);
0372 
0373     if (d->valuesColumn == column)
0374         d->valuesColumnPath = columnPath;
0375     else if (d->valuesColumnPath == columnPath)
0376         setValuesColumn(column);
0377 
0378     if (d->valuesColumnPath == columnPath)
0379         setValuesColumn(column);
0380 
0381     if (d->xErrorBar->plusColumn() == column)
0382         d->xErrorBar->plusColumnPath() = columnPath;
0383     else if (d->xErrorBar->plusColumnPath() == columnPath)
0384         d->yErrorBar->setPlusColumn(column);
0385 
0386     if (d->xErrorBar->minusColumn() == column)
0387         d->xErrorBar->minusColumnPath() = columnPath;
0388     else if (d->xErrorBar->minusColumnPath() == columnPath)
0389         d->xErrorBar->setMinusColumn(column);
0390 
0391     if (d->yErrorBar->plusColumn() == column)
0392         d->yErrorBar->plusColumnPath() = columnPath;
0393     else if (d->yErrorBar->plusColumnPath() == columnPath)
0394         d->yErrorBar->setPlusColumn(column);
0395 
0396     if (d->yErrorBar->minusColumn() == column)
0397         d->yErrorBar->minusColumnPath() = columnPath;
0398     else if (d->yErrorBar->minusColumnPath() == columnPath)
0399         d->yErrorBar->setMinusColumn(column);
0400 
0401     setUndoAware(true);
0402 }
0403 
0404 QColor XYCurve::color() const {
0405     Q_D(const XYCurve);
0406     if (d->lineType != XYCurve::LineType::NoLine)
0407         return d->line->pen().color();
0408     else if (d->symbol->style() != Symbol::Style::NoSymbols)
0409         return d->symbol->pen().color();
0410     return QColor();
0411 }
0412 
0413 // ##############################################################################
0414 // #################  setter methods and undo commands ##########################
0415 // ##############################################################################
0416 
0417 // 1) add XYCurveSetXColumnCmd as friend class to XYCurve
0418 // 2) add XYCURVE_COLUMN_CONNECT(x) as private method to XYCurve
0419 // 3) define all missing slots
0420 CURVE_COLUMN_SETTER_CMD_IMPL_F_S(XYCurve, X, x, recalcLogicalPoints)
0421 void XYCurve::setXColumn(const AbstractColumn* column) {
0422     Q_D(XYCurve);
0423     if (column != d->xColumn)
0424         exec(new XYCurveSetXColumnCmd(d, column, ki18n("%1: x-data source changed")));
0425 }
0426 
0427 CURVE_COLUMN_SETTER_CMD_IMPL_F_S(XYCurve, Y, y, recalcLogicalPoints)
0428 void XYCurve::setYColumn(const AbstractColumn* column) {
0429     Q_D(XYCurve);
0430     if (column != d->yColumn)
0431         exec(new XYCurveSetYColumnCmd(d, column, ki18n("%1: y-data source changed")));
0432 }
0433 
0434 void XYCurve::setXColumnPath(const QString& path) {
0435     Q_D(XYCurve);
0436     d->xColumnPath = path;
0437 }
0438 
0439 void XYCurve::setYColumnPath(const QString& path) {
0440     Q_D(XYCurve);
0441     d->yColumnPath = path;
0442 }
0443 
0444 // Line
0445 STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineType, XYCurve::LineType, lineType, updateLines)
0446 void XYCurve::setLineType(LineType type) {
0447     Q_D(XYCurve);
0448     if (type != d->lineType)
0449         exec(new XYCurveSetLineTypeCmd(d, type, ki18n("%1: line type changed")));
0450 }
0451 
0452 STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineSkipGaps, bool, lineSkipGaps, updateLines)
0453 void XYCurve::setLineSkipGaps(bool skip) {
0454     Q_D(XYCurve);
0455     if (skip != d->lineSkipGaps)
0456         exec(new XYCurveSetLineSkipGapsCmd(d, skip, ki18n("%1: set skip line gaps")));
0457 }
0458 
0459 STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineIncreasingXOnly, bool, lineIncreasingXOnly, updateLines)
0460 void XYCurve::setLineIncreasingXOnly(bool incr) {
0461     Q_D(XYCurve);
0462     if (incr != d->lineIncreasingXOnly)
0463         exec(new XYCurveSetLineIncreasingXOnlyCmd(d, incr, ki18n("%1: set increasing X")));
0464 }
0465 
0466 STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineInterpolationPointsCount, int, lineInterpolationPointsCount, updateLines)
0467 void XYCurve::setLineInterpolationPointsCount(int count) {
0468     Q_D(XYCurve);
0469     if (count != d->lineInterpolationPointsCount)
0470         exec(new XYCurveSetLineInterpolationPointsCountCmd(d, count, ki18n("%1: set the number of interpolation points")));
0471 }
0472 
0473 // Values-Tab
0474 STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesType, XYCurve::ValuesType, valuesType, updateValues)
0475 void XYCurve::setValuesType(XYCurve::ValuesType type) {
0476     Q_D(XYCurve);
0477     if (type != d->valuesType)
0478         exec(new XYCurveSetValuesTypeCmd(d, type, ki18n("%1: set values type")));
0479 }
0480 
0481 CURVE_COLUMN_SETTER_CMD_IMPL_F_S(XYCurve, Values, values, updateValues)
0482 void XYCurve::setValuesColumn(const AbstractColumn* column) {
0483     Q_D(XYCurve);
0484     if (column != d->valuesColumn) {
0485         exec(new XYCurveSetValuesColumnCmd(d, column, ki18n("%1: set values column")));
0486 
0487         // no need to recalculate the points on value labels changes
0488         disconnect(column, &AbstractColumn::dataChanged, this, &XYCurve::recalcLogicalPoints);
0489 
0490         if (column)
0491             connect(column, &AbstractColumn::dataChanged, this, &XYCurve::updateValues);
0492     }
0493 }
0494 
0495 void XYCurve::setValuesColumnPath(const QString& path) {
0496     Q_D(XYCurve);
0497     d->valuesColumnPath = path;
0498 }
0499 
0500 STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesPosition, XYCurve::ValuesPosition, valuesPosition, updateValues)
0501 void XYCurve::setValuesPosition(ValuesPosition position) {
0502     Q_D(XYCurve);
0503     if (position != d->valuesPosition)
0504         exec(new XYCurveSetValuesPositionCmd(d, position, ki18n("%1: set values position")));
0505 }
0506 
0507 STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesDistance, qreal, valuesDistance, updateValues)
0508 void XYCurve::setValuesDistance(qreal distance) {
0509     Q_D(XYCurve);
0510     if (distance != d->valuesDistance)
0511         exec(new XYCurveSetValuesDistanceCmd(d, distance, ki18n("%1: set values distance")));
0512 }
0513 
0514 STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesRotationAngle, qreal, valuesRotationAngle, updateValues)
0515 void XYCurve::setValuesRotationAngle(qreal angle) {
0516     Q_D(XYCurve);
0517     if (!qFuzzyCompare(1 + angle, 1 + d->valuesRotationAngle))
0518         exec(new XYCurveSetValuesRotationAngleCmd(d, angle, ki18n("%1: rotate values")));
0519 }
0520 
0521 STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesOpacity, qreal, valuesOpacity, updatePixmap)
0522 void XYCurve::setValuesOpacity(qreal opacity) {
0523     Q_D(XYCurve);
0524     if (opacity != d->valuesOpacity)
0525         exec(new XYCurveSetValuesOpacityCmd(d, opacity, ki18n("%1: set values opacity")));
0526 }
0527 
0528 STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesNumericFormat, char, valuesNumericFormat, updateValues)
0529 void XYCurve::setValuesNumericFormat(char format) {
0530     Q_D(XYCurve);
0531     if (format != d->valuesNumericFormat)
0532         exec(new XYCurveSetValuesNumericFormatCmd(d, format, ki18n("%1: set values numeric format")));
0533 }
0534 
0535 STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesPrecision, int, valuesPrecision, updateValues)
0536 void XYCurve::setValuesPrecision(int precision) {
0537     Q_D(XYCurve);
0538     if (precision != d->valuesPrecision)
0539         exec(new XYCurveSetValuesPrecisionCmd(d, precision, ki18n("%1: set values precision")));
0540 }
0541 
0542 STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesDateTimeFormat, QString, valuesDateTimeFormat, updateValues)
0543 void XYCurve::setValuesDateTimeFormat(const QString& format) {
0544     Q_D(XYCurve);
0545     if (format != d->valuesDateTimeFormat)
0546         exec(new XYCurveSetValuesDateTimeFormatCmd(d, format, ki18n("%1: set values datetime format")));
0547 }
0548 
0549 STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesPrefix, QString, valuesPrefix, updateValues)
0550 void XYCurve::setValuesPrefix(const QString& prefix) {
0551     Q_D(XYCurve);
0552     if (prefix != d->valuesPrefix)
0553         exec(new XYCurveSetValuesPrefixCmd(d, prefix, ki18n("%1: set values prefix")));
0554 }
0555 
0556 STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesSuffix, QString, valuesSuffix, updateValues)
0557 void XYCurve::setValuesSuffix(const QString& suffix) {
0558     Q_D(XYCurve);
0559     if (suffix != d->valuesSuffix)
0560         exec(new XYCurveSetValuesSuffixCmd(d, suffix, ki18n("%1: set values suffix")));
0561 }
0562 
0563 STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesFont, QFont, valuesFont, updateValues)
0564 void XYCurve::setValuesFont(const QFont& font) {
0565     Q_D(XYCurve);
0566     if (font != d->valuesFont)
0567         exec(new XYCurveSetValuesFontCmd(d, font, ki18n("%1: set values font")));
0568 }
0569 
0570 STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesColor, QColor, valuesColor, updatePixmap)
0571 void XYCurve::setValuesColor(const QColor& color) {
0572     Q_D(XYCurve);
0573     if (color != d->valuesColor)
0574         exec(new XYCurveSetValuesColorCmd(d, color, ki18n("%1: set values color")));
0575 }
0576 
0577 // margin plots
0578 STD_SETTER_CMD_IMPL_F_S(XYCurve, SetRugEnabled, bool, rugEnabled, updateRug)
0579 void XYCurve::setRugEnabled(bool enabled) {
0580     Q_D(XYCurve);
0581     if (enabled != d->rugEnabled)
0582         exec(new XYCurveSetRugEnabledCmd(d, enabled, ki18n("%1: change rug enabled")));
0583 }
0584 
0585 STD_SETTER_CMD_IMPL_F_S(XYCurve, SetRugOrientation, WorksheetElement::Orientation, rugOrientation, updateRug)
0586 void XYCurve::setRugOrientation(WorksheetElement::Orientation orientation) {
0587     Q_D(XYCurve);
0588     if (orientation != d->rugOrientation)
0589         exec(new XYCurveSetRugOrientationCmd(d, orientation, ki18n("%1: set rug orientation")));
0590 }
0591 
0592 STD_SETTER_CMD_IMPL_F_S(XYCurve, SetRugWidth, double, rugWidth, updatePixmap)
0593 void XYCurve::setRugWidth(double width) {
0594     Q_D(XYCurve);
0595     if (width != d->rugWidth)
0596         exec(new XYCurveSetRugWidthCmd(d, width, ki18n("%1: change rug width")));
0597 }
0598 
0599 STD_SETTER_CMD_IMPL_F_S(XYCurve, SetRugLength, double, rugLength, updateRug)
0600 void XYCurve::setRugLength(double length) {
0601     Q_D(XYCurve);
0602     if (length != d->rugLength)
0603         exec(new XYCurveSetRugLengthCmd(d, length, ki18n("%1: change rug length")));
0604 }
0605 
0606 STD_SETTER_CMD_IMPL_F_S(XYCurve, SetRugOffset, double, rugOffset, updateRug)
0607 void XYCurve::setRugOffset(double offset) {
0608     Q_D(XYCurve);
0609     if (offset != d->rugOffset)
0610         exec(new XYCurveSetRugOffsetCmd(d, offset, ki18n("%1: change rug offset")));
0611 }
0612 
0613 // ##############################################################################
0614 // #################################  SLOTS  ####################################
0615 // ##############################################################################
0616 void XYCurve::retransform() {
0617     Q_D(XYCurve);
0618     d->retransform();
0619 }
0620 
0621 void XYCurve::recalcLogicalPoints() {
0622     Q_D(XYCurve);
0623     d->recalcLogicalPoints();
0624 }
0625 
0626 void XYCurve::updateValues() {
0627     Q_D(XYCurve);
0628     d->updateValues();
0629 }
0630 
0631 void XYCurve::updateErrorBars() {
0632     Q_D(XYCurve);
0633     d->updateErrorBars();
0634 }
0635 
0636 // TODO
0637 void XYCurve::handleResize(double horizontalRatio, double verticalRatio, bool /*pageResize*/) {
0638     Q_D(const XYCurve);
0639 
0640     d->symbol->setSize(d->symbol->size() * horizontalRatio);
0641 
0642     QPen pen = d->symbol->pen();
0643     pen.setWidthF(pen.widthF() * (horizontalRatio + verticalRatio) / 2.);
0644     d->symbol->setPen(pen);
0645 
0646     double width = d->line->width() * (horizontalRatio + verticalRatio) / 2.;
0647     d->line->setWidth(width);
0648 
0649     // setValuesDistance(d->distance*);
0650     QFont font = d->valuesFont;
0651     font.setPointSizeF(font.pointSizeF() * horizontalRatio);
0652     setValuesFont(font);
0653 }
0654 
0655 // Finds index where x is located and returns the value "index" after the found value
0656 /*!
0657  * Find nearest x value from a value xpos and his y value
0658  * @param xpos position for which the next index xpos should be found
0659  * @param offset Offset from the index where xpos is. Positive is after the found index, negative is before the found index
0660  * @param x x value at the found index
0661  * @param y y value at the found index
0662  * @param valueFound True when value found, otherwise false
0663  */
0664 int XYCurve::getNextValue(double xpos, int offset, double& x, double& y, bool& valueFound) const {
0665     valueFound = false;
0666     auto properties = xColumn()->properties();
0667     if (properties == AbstractColumn::Properties::MonotonicDecreasing)
0668         offset *= -1;
0669 
0670     int index = xColumn()->indexForValue(xpos);
0671     if (index < 0)
0672         return -1;
0673     if (offset > 0 && index + offset < xColumn()->rowCount())
0674         index += offset;
0675     else if (offset > 0)
0676         index = xColumn()->rowCount() - 1;
0677     else if ((offset < 0 && index + offset > 0))
0678         index += offset;
0679     else
0680         index = 0;
0681 
0682     auto xMode = xColumn()->columnMode();
0683 
0684     if (xMode == AbstractColumn::ColumnMode::Double || xMode == AbstractColumn::ColumnMode::Integer)
0685         x = xColumn()->valueAt(index);
0686     else if (xMode == AbstractColumn::ColumnMode::DateTime || xMode == AbstractColumn::ColumnMode::Day || xMode == AbstractColumn::ColumnMode::Month)
0687         x = xColumn()->dateTimeAt(index).toMSecsSinceEpoch();
0688     else
0689         return index;
0690 
0691     auto yMode = yColumn()->columnMode();
0692 
0693     if (yMode == AbstractColumn::ColumnMode::Double || yMode == AbstractColumn::ColumnMode::Integer)
0694         y = yColumn()->valueAt(index);
0695     else if (yMode == AbstractColumn::ColumnMode::DateTime || yMode == AbstractColumn::ColumnMode::Day || yMode == AbstractColumn::ColumnMode::Month)
0696         y = yColumn()->dateTimeAt(index).toMSecsSinceEpoch();
0697     else
0698         return index;
0699 
0700     valueFound = true;
0701     return index;
0702 }
0703 
0704 void XYCurve::xColumnAboutToBeRemoved(const AbstractAspect* aspect) {
0705     Q_D(XYCurve);
0706     if (aspect == d->xColumn) {
0707         d->xColumn = nullptr;
0708         d->m_logicalPoints.clear();
0709         d->retransform();
0710     }
0711 }
0712 
0713 void XYCurve::yColumnAboutToBeRemoved(const AbstractAspect* aspect) {
0714     Q_D(XYCurve);
0715     if (aspect == d->yColumn) {
0716         d->yColumn = nullptr;
0717         d->m_logicalPoints.clear();
0718         d->retransform();
0719     }
0720 }
0721 
0722 void XYCurve::valuesColumnAboutToBeRemoved(const AbstractAspect* aspect) {
0723     Q_D(XYCurve);
0724     if (aspect == d->valuesColumn) {
0725         d->valuesColumn = nullptr;
0726         d->updateValues();
0727     }
0728 }
0729 
0730 // ##############################################################################
0731 // ######  SLOTs for changes triggered via QActions in the context menu  ########
0732 // ##############################################################################
0733 
0734 void XYCurve::navigateTo() {
0735     project()->navigateTo(navigateToAction->data().toString());
0736 }
0737 
0738 // ##############################################################################
0739 // ######################### Private implementation #############################
0740 // ##############################################################################
0741 XYCurvePrivate::XYCurvePrivate(XYCurve* owner)
0742     : PlotPrivate(owner)
0743     , q(owner) {
0744     setFlag(QGraphicsItem::ItemIsSelectable, true);
0745     setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
0746     setAcceptHoverEvents(false);
0747 }
0748 
0749 void XYCurvePrivate::calculateScenePoints() {
0750     if (!q->plot() || !m_scenePointsDirty || !xColumn)
0751         return;
0752 #if PERFTRACE_CURVES
0753     PERFTRACE(QLatin1String(Q_FUNC_INFO) + QStringLiteral(", curve ") + name());
0754 #endif
0755 
0756     m_scenePoints.clear();
0757 
0758     // calculate the scene coordinates
0759     //  This condition cannot be used, because m_logicalPoints is also used in updateErrorBars(), updateDropLines() and in updateFilling()
0760     //  TODO: check updateErrorBars() and updateDropLines() and if they aren't available don't calculate this part
0761     // if (symbolsStyle != Symbol::Style::NoSymbols || valuesType != XYCurve::NoValues ) {
0762     {
0763 #if PERFTRACE_CURVES
0764         PERFTRACE(QLatin1String(Q_FUNC_INFO) + QStringLiteral(", curve ") + name() + QStringLiteral(", map logical points to scene coordinates"));
0765 #endif
0766 
0767         const int numberOfPoints = m_logicalPoints.size();
0768         DEBUG(Q_FUNC_INFO << ", number of logical points = " << numberOfPoints)
0769         // for (auto p : m_logicalPoints)
0770         //  QDEBUG(Q_FUNC_INFO << ", logical points: " << QString::number(p.x(), 'g', 12) << " = " << QDateTime::fromMSecsSinceEpoch(p.x(), Qt::UTC))
0771 
0772         if (numberOfPoints > 0) {
0773             const auto dataRect{plot()->dataRect()};
0774             // this is the old method considering DPI
0775             DEBUG(Q_FUNC_INFO << ", plot->dataRect() width/height = " << dataRect.width() << '/' << dataRect.height());
0776             DEBUG(Q_FUNC_INFO << ", logical DPI X/Y = " << QApplication::primaryScreen()->logicalDotsPerInchX() << '/'
0777                               << QApplication::primaryScreen()->logicalDotsPerInchY())
0778             DEBUG(Q_FUNC_INFO << ", physical DPI X/Y = " << QApplication::primaryScreen()->physicalDotsPerInchX() << '/'
0779                               << QApplication::primaryScreen()->physicalDotsPerInchY())
0780 
0781             // new method
0782             const int numberOfPixelX = dataRect.width();
0783             const int numberOfPixelY = dataRect.height();
0784 
0785             if (numberOfPixelX <= 0 || numberOfPixelY <= 0) {
0786                 DEBUG(Q_FUNC_INFO << ", number of pixel X <= 0 or number of pixel Y <= 0!")
0787                 return;
0788             }
0789             DEBUG(" numberOfPixelX/numberOfPixelY = " << numberOfPixelX << '/' << numberOfPixelY)
0790 
0791             // eliminate multiple scene points (size (numberOfPixelX + 1) * (numberOfPixelY + 1)) TODO: why "+1"
0792             QVector<QVector<bool>> scenePointsUsed(numberOfPixelX + 1);
0793             for (auto& col : scenePointsUsed)
0794                 col.resize(numberOfPixelY + 1);
0795 
0796             const auto columnProperties = xColumn->properties();
0797             int startIndex, endIndex;
0798             if (columnProperties == AbstractColumn::Properties::MonotonicDecreasing || columnProperties == AbstractColumn::Properties::MonotonicIncreasing) {
0799                 DEBUG(Q_FUNC_INFO << ", column monotonic")
0800                 if (!q->cSystem->isValid())
0801                     return;
0802                 double xMin = q->cSystem->mapSceneToLogical(dataRect.topLeft()).x();
0803                 double xMax = q->cSystem->mapSceneToLogical(dataRect.bottomRight()).x();
0804                 DEBUG(Q_FUNC_INFO << ", xMin/xMax = " << xMin << '/' << xMax)
0805 
0806                 startIndex = Column::indexForValue(xMin, m_logicalPoints, columnProperties);
0807                 endIndex = Column::indexForValue(xMax, m_logicalPoints, columnProperties);
0808 
0809                 if (startIndex > endIndex && endIndex >= 0)
0810                     std::swap(startIndex, endIndex);
0811 
0812                 if (startIndex < 0)
0813                     startIndex = 0;
0814                 if (endIndex < 0)
0815                     endIndex = numberOfPoints - 1;
0816 
0817             } else {
0818                 DEBUG(Q_FUNC_INFO << ", column not monotonic")
0819                 startIndex = 0;
0820                 endIndex = numberOfPoints - 1;
0821             }
0822             //} // (symbolsStyle != Symbol::NoSymbols || valuesType != XYCurve::NoValues )
0823 
0824             m_pointVisible.resize(numberOfPoints);
0825             q->cSystem->mapLogicalToScene(startIndex, endIndex, m_logicalPoints, m_scenePoints, m_pointVisible);
0826             // for (auto p : m_logicalPoints)
0827             //  QDEBUG(Q_FUNC_INFO << ", logical points: " << QString::number(p.x(), 'g', 12) << " = " << QDateTime::fromMSecsSinceEpoch(p.x(), Qt::UTC))
0828         }
0829     }
0830     //} // (symbolsStyle != Symbol::Style::NoSymbols || valuesType != XYCurve::NoValues )
0831     m_scenePointsDirty = false;
0832 }
0833 
0834 /*!
0835   called when the size of the plot or its data ranges (manual changes, zooming, etc.) were changed.
0836   recalculates the position of the scene points to be drawn.
0837   triggers the update of lines, drop lines, symbols etc.
0838 */
0839 void XYCurvePrivate::retransform() {
0840     const bool suppressed = !isVisible() || q->isLoading() || suppressRetransform || !plot();
0841     DEBUG("\n" << Q_FUNC_INFO << ", name = " << STDSTRING(name()) << ", suppressRetransform = " << suppressRetransform);
0842     trackRetransformCalled(suppressed);
0843     if (suppressed)
0844         return;
0845 
0846     m_scenePointsDirty = true;
0847     m_scenePoints.clear(); // free memory
0848 
0849     DEBUG(Q_FUNC_INFO << ", x/y column = " << xColumn << "/" << yColumn);
0850     // Q_ASSERT(xColumn != nullptr);
0851     if (!xColumn || !yColumn) {
0852         DEBUG(Q_FUNC_INFO << ", WARNING: xColumn or yColumn not available");
0853         linePath = QPainterPath();
0854         dropLinePath = QPainterPath();
0855         symbolsPath = QPainterPath();
0856         valuesPath = QPainterPath();
0857         errorBarsPath = QPainterPath();
0858         rugPath = QPainterPath();
0859         m_shape = QPainterPath();
0860         m_lines.clear();
0861         m_valuePoints.clear();
0862         m_valueStrings.clear();
0863         m_fillPolygons.clear();
0864         recalcShapeAndBoundingRect();
0865         return;
0866     }
0867 
0868     suppressRecalc = true;
0869     updateLines();
0870     updateDropLines();
0871     updateSymbols();
0872     updateRug();
0873     updateValues();
0874     suppressRecalc = false;
0875     updateErrorBars();
0876 }
0877 
0878 /*!
0879  * called if the x- or y-data was changed.
0880  * copies the valid data points from the x- and y-columns into the internal container
0881  */
0882 void XYCurvePrivate::recalcLogicalPoints() {
0883     PERFTRACE(QLatin1String(Q_FUNC_INFO) + QStringLiteral(", curve ") + name());
0884 
0885     m_pointVisible.clear();
0886     m_logicalPoints.clear();
0887     connectedPointsLogical.clear();
0888     validPointsIndicesLogical.clear();
0889 
0890     if (!xColumn || !yColumn)
0891         return;
0892 
0893     auto xColMode = xColumn->columnMode();
0894     auto yColMode = yColumn->columnMode();
0895     const int rows = xColumn->rowCount();
0896     m_logicalPoints.reserve(rows);
0897 
0898     // take only valid and non masked points
0899     for (int row = 0; row < rows; row++) {
0900         if (xColumn->isValid(row) && yColumn->isValid(row) && (!xColumn->isMasked(row)) && (!yColumn->isMasked(row))) {
0901             QPointF tempPoint;
0902 
0903             switch (xColMode) {
0904             case AbstractColumn::ColumnMode::Double:
0905                 tempPoint.setX(xColumn->doubleAt(row));
0906                 break;
0907             case AbstractColumn::ColumnMode::Integer:
0908                 tempPoint.setX(xColumn->integerAt(row));
0909                 break;
0910             case AbstractColumn::ColumnMode::BigInt:
0911                 tempPoint.setX(xColumn->bigIntAt(row));
0912                 break;
0913             case AbstractColumn::ColumnMode::DateTime:
0914                 tempPoint.setX(xColumn->dateTimeAt(row).toMSecsSinceEpoch());
0915                 break;
0916             case AbstractColumn::ColumnMode::Text:
0917             case AbstractColumn::ColumnMode::Month:
0918             case AbstractColumn::ColumnMode::Day:
0919                 break;
0920             }
0921 
0922             switch (yColMode) {
0923             case AbstractColumn::ColumnMode::Double:
0924                 tempPoint.setY(yColumn->doubleAt(row));
0925                 break;
0926             case AbstractColumn::ColumnMode::Integer:
0927                 tempPoint.setY(yColumn->integerAt(row));
0928                 break;
0929             case AbstractColumn::ColumnMode::BigInt:
0930                 tempPoint.setY(yColumn->bigIntAt(row));
0931                 break;
0932             case AbstractColumn::ColumnMode::DateTime:
0933                 tempPoint.setY(yColumn->dateTimeAt(row).toMSecsSinceEpoch());
0934                 break;
0935             case AbstractColumn::ColumnMode::Text:
0936             case AbstractColumn::ColumnMode::Month:
0937             case AbstractColumn::ColumnMode::Day:
0938                 break;
0939             }
0940 
0941             m_logicalPoints.append(tempPoint);
0942             // TODO: append, resize-reserve
0943             connectedPointsLogical.push_back(true);
0944             validPointsIndicesLogical.push_back(row);
0945         } else {
0946             if (!connectedPointsLogical.empty())
0947                 connectedPointsLogical[connectedPointsLogical.size() - 1] = false;
0948         }
0949     }
0950 
0951     m_pointVisible.resize(m_logicalPoints.size());
0952 }
0953 
0954 /*!
0955  * Adds a line, which connects two points, but only if they don't lie on the same xAxis pixel.
0956  * If they lie on the same x pixel, draw a vertical line between the minimum and maximum y value. So all points are included
0957  * This function can be used for all axis scalings (linear, log, sqrt, ...). For the linear case use the function above, because it's optimized for the linear
0958  * case
0959  * @param p next point
0960  * @param x
0961  * @param minY
0962  * @param maxY
0963  * @param lastPoint remember last point in case of overlap
0964  * @param pixelDiff x pixel distance between two points
0965  * @param pixelCount pixel count
0966  */
0967 void XYCurvePrivate::addLine(QPointF p,
0968                              double& x,
0969                              double& minY,
0970                              double& maxY,
0971                              QPointF& lastPoint,
0972                              int& pixelDiff,
0973                              int numberOfPixelX,
0974                              double minDiffX,
0975                              RangeT::Scale scale,
0976                              bool& prevPixelDiffZero) {
0977     if (scale == RangeT::Scale::Linear) {
0978         pixelDiff = (std::round(p.x() / minDiffX) - x) != 0; // only relevant if greater zero or not
0979         addUniqueLine(p, minY, maxY, lastPoint, pixelDiff, m_lines, prevPixelDiffZero);
0980         if (pixelDiff > 0) // set x to next pixel
0981             x = std::round(p.x() / minDiffX);
0982     } else {
0983         // for nonlinear scaling the pixel distance must be calculated for every point
0984         static const double preCalc = (double)plot()->dataRect().width() / numberOfPixelX;
0985         bool visible;
0986         QPointF pScene = q->cSystem->mapLogicalToScene(p, visible, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping);
0987 
0988         // if the point is not valid, don't create a line
0989         if (!visible)
0990             return;
0991 
0992         // using only the difference between the points is not sufficient, because
0993         // p0 is updated always independent if new line added or not
0994         const int p1Pixel = std::round((pScene.x() - plot()->dataRect().x()) / preCalc);
0995         pixelDiff = p1Pixel - x;
0996 
0997         addUniqueLine(p, minY, maxY, lastPoint, pixelDiff, m_lines, prevPixelDiffZero);
0998 
0999         if (pixelDiff > 0) // set x to next pixel
1000             x = std::round((pScene.x() - plot()->dataRect().x()) / preCalc);
1001     }
1002 }
1003 
1004 /*!
1005  * \brief XYCurvePrivate::addUniqueLine
1006  * This function is called from the other two addLine() functions to avoid duplication
1007  * @param p next point
1008  * @param minY
1009  * @param maxY
1010  * @param lastPoint remember last point in case of overlap
1011  * @param pixelDiff pixel distance in x between two points
1012  */
1013 void XYCurvePrivate::addUniqueLine(QPointF p, double& minY, double& maxY, QPointF& lastPoint, int& pixelDiff, QVector<QLineF>& lines, bool& prevPixelDiffZero) {
1014     if (pixelDiff == 0) {
1015         maxY = std::max(p.y(), maxY);
1016         minY = std::min(p.y(), minY);
1017         prevPixelDiffZero = true;
1018     } else {
1019         if (prevPixelDiffZero) {
1020             // If previously more than one point lied on the same pixel
1021             // a vertical line will be drawn which connects all points
1022             // on this x value. So we don't have to draw a lot of lines,
1023             // but only one
1024 
1025             // Not needed, because the line would be that small that it will never be visible
1026             // if (m_lines.count() > 0) {
1027             //  auto p_temp = m_lines.last().p2();
1028             //  m_lines.append(QLineF(p_temp.x(), p_temp.y(), x, p_temp.y()));
1029             // }
1030             if (maxY != minY)
1031                 lines.append(QLineF(lastPoint.x(), minY, lastPoint.x(), maxY));
1032             lines.append(QLineF(lastPoint, p));
1033         } else if (!std::isnan(lastPoint.x()) && !std::isnan(lastPoint.y()))
1034             lines.append(QLineF(lastPoint, p));
1035         prevPixelDiffZero = false;
1036         minY = p.y();
1037         maxY = p.y();
1038         // TODO: needed?
1039         //          if (p1.y() >= minY && p1.y() <= maxY && pixelDiff == 1)
1040         //              return;
1041     }
1042     lastPoint = p;
1043 }
1044 
1045 /*!
1046   recalculates the painter path for the lines connecting the data points.
1047   Called each time when the type of this connection is changed.
1048 TODO: At the moment also the points which are outside of the scene are added. This algorithm can be improved by omitting all
1049   lines where both points are outside of the scene
1050 */
1051 void XYCurvePrivate::updateLines() {
1052 #if PERFTRACE_CURVES
1053     PERFTRACE(QLatin1String(Q_FUNC_INFO) + QStringLiteral(", curve ") + name());
1054 #endif
1055     linePath = QPainterPath();
1056     m_lines.clear();
1057     if (lineType == XYCurve::LineType::NoLine) {
1058         DEBUG(Q_FUNC_INFO << ", nothing to do, since line type is XYCurve::LineType::NoLine");
1059         updateFilling();
1060         recalcShapeAndBoundingRect();
1061         return;
1062     }
1063 
1064     int numberOfPoints{static_cast<int>(m_logicalPoints.size())};
1065     if (numberOfPoints <= 1) {
1066         DEBUG(Q_FUNC_INFO << ", nothing to do, since not enough data points available");
1067         recalcShapeAndBoundingRect();
1068         return;
1069     }
1070 
1071     const QRectF pageRect = plot()->dataRect();
1072     // old method using DPI
1073     // const double widthDatarectInch = Worksheet::convertFromSceneUnits(plot()->dataRect().width(), Worksheet::Unit::Inch);
1074     // float heightDatarectInch = Worksheet::convertFromSceneUnits(plot()->dataRect().height(), Worksheet::Unit::Inch);
1075     // const int numberOfPixelX = ceil(widthDatarectInch * QApplication::desktop()->physicalDpiX());
1076     const int numberOfPixelX = pageRect.width();
1077 
1078     // calculate the lines connecting the data points
1079     {
1080 #if PERFTRACE_CURVES
1081         PERFTRACE(QLatin1String(Q_FUNC_INFO) + QStringLiteral(", curve ") + name() + QStringLiteral(", calculate the lines connecting the data points"));
1082 #endif
1083 
1084         // find index for xMin and xMax to not loop through all values
1085         int startIndex, endIndex;
1086         auto columnProperties = q->xColumn()->properties();
1087         if (columnProperties == AbstractColumn::Properties::MonotonicDecreasing || columnProperties == AbstractColumn::Properties::MonotonicIncreasing) {
1088             DEBUG(Q_FUNC_INFO << ", monotonic")
1089             if (!q->cSystem->isValid())
1090                 return;
1091             const double xMin = q->cSystem->mapSceneToLogical(pageRect.topLeft()).x();
1092             const double xMax = q->cSystem->mapSceneToLogical(pageRect.bottomRight()).x();
1093 
1094             startIndex = Column::indexForValue(xMin, m_logicalPoints, columnProperties);
1095             endIndex = Column::indexForValue(xMax, m_logicalPoints, columnProperties);
1096 
1097             if (startIndex > endIndex)
1098                 std::swap(startIndex, endIndex);
1099 
1100             startIndex--; // use one value before
1101             endIndex++;
1102             if (startIndex < 0)
1103                 startIndex = 0;
1104             if (endIndex < 0 || endIndex >= numberOfPoints)
1105                 endIndex = numberOfPoints - 1;
1106 
1107             numberOfPoints = endIndex - startIndex + 1;
1108         } else {
1109             DEBUG(Q_FUNC_INFO << ", non monotonic")
1110             startIndex = 0;
1111             endIndex = numberOfPoints - 1;
1112         }
1113         DEBUG(Q_FUNC_INFO << ", start/endIndex = " << startIndex << '/' << endIndex)
1114 
1115         QPointF tempPoint1, tempPoint2; // used as temporaryPoints to interpolate datapoints if set
1116         if (columnProperties == AbstractColumn::Properties::Constant) {
1117             DEBUG(Q_FUNC_INFO << ", CONSTANT column")
1118             auto cs = plot()->coordinateSystem(q->coordinateSystemIndex());
1119             const auto xRange{plot()->range(Dimension::X, cs->index(Dimension::X))};
1120             const auto yRange{plot()->range(Dimension::Y, cs->index(Dimension::Y))};
1121             tempPoint1 = QPointF(xRange.start(), yRange.start());
1122             tempPoint2 = QPointF(xRange.start(), yRange.end());
1123             m_lines.append(QLineF(tempPoint1, tempPoint2));
1124         } else {
1125             QPointF lastPoint{NAN, NAN}; // last x value
1126             int pixelDiff = 0;
1127             bool prevPixelDiffZero = false;
1128             double minY{INFINITY}, maxY{-INFINITY};
1129             QPointF p0, p1;
1130             const auto xIndex{q->cSystem->index(Dimension::X)};
1131             const auto xRange{plot()->range(Dimension::X, xIndex)};
1132             double minDiffX;
1133             const RangeT::Scale scale = plot()->xRangeScale(xIndex);
1134 
1135             // setting initial point
1136             double xPos = NAN;
1137             if (scale == RangeT::Scale::Linear)
1138                 minDiffX = (xRange.end() - xRange.start()) / numberOfPixelX;
1139             else {
1140                 // For nonlinear x achses, the linear scene coordinates are used
1141                 minDiffX = plot()->dataRect().width() / numberOfPixelX;
1142             }
1143 
1144             // determine first point
1145             for (int i{startIndex}; i < endIndex; i++) {
1146                 if (!lineSkipGaps && !connectedPointsLogical.at(i))
1147                     continue;
1148 
1149                 p0 = m_logicalPoints.at(i);
1150                 startIndex = i + 1;
1151 
1152                 if (lineType != XYCurve::LineType::SplineCubicNatural && lineType != XYCurve::LineType::SplineCubicPeriodic
1153                     && lineType != XYCurve::LineType::SplineAkimaNatural && lineType != XYCurve::LineType::SplineAkimaPeriodic) {
1154                     if (scale == RangeT::Scale::Linear) {
1155                         xPos = std::round(p0.x() / minDiffX);
1156                         lastPoint = p0;
1157                         minY = p0.y();
1158                         maxY = p0.y();
1159                     } else {
1160                         bool visible;
1161                         QPointF pScene = q->cSystem->mapLogicalToScene(p0, visible, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping);
1162                         if (!visible)
1163                             continue;
1164                         xPos = std::round((pScene.x() - plot()->dataRect().x()) / ((double)plot()->dataRect().width() * numberOfPixelX));
1165                         lastPoint = p0;
1166                     }
1167                 }
1168                 break;
1169             }
1170 
1171             switch (lineType) {
1172             case XYCurve::LineType::NoLine:
1173                 break;
1174             case XYCurve::LineType::Line: {
1175 #if PERFTRACE_CURVES
1176                 PERFTRACE(name() + QLatin1String(Q_FUNC_INFO) + QStringLiteral(", find relevant lines"));
1177 #endif
1178                 for (int i{startIndex}; i <= endIndex; i++) {
1179                     p1 = m_logicalPoints.at(i);
1180                     if (!lineSkipGaps && (i > startIndex && !connectedPointsLogical.at(i - 1))) {
1181                         if (pixelDiff == 0)
1182                             m_lines.append(QLineF(QPointF(p0.x(), minY), QPointF(p0.x(), maxY)));
1183                         prevPixelDiffZero = false;
1184                         p0 = p1;
1185                         lastPoint = p1;
1186                         continue;
1187                     }
1188 
1189                     if (lineIncreasingXOnly && (p1.x() < p0.x())) // skip points
1190                         continue;
1191                     addLine(p1, xPos, minY, maxY, lastPoint, pixelDiff, numberOfPixelX, minDiffX, scale, prevPixelDiffZero);
1192                     p0 = p1;
1193                 }
1194 
1195                 if (pixelDiff == 0)
1196                     m_lines.append(QLineF(QPointF(p0.x(), minY), QPointF(p0.x(), maxY)));
1197 
1198                 break;
1199             }
1200             case XYCurve::LineType::StartHorizontal: {
1201                 for (int i{startIndex}; i <= endIndex; i++) {
1202                     p1 = m_logicalPoints.at(i);
1203                     if (!lineSkipGaps && (i > startIndex && !connectedPointsLogical.at(i - 1))) {
1204                         // if (pixelDiff == 0) // not needed, because last line will be always a vertical
1205                         m_lines.append(QLineF(QPointF(p0.x(), minY), QPointF(p0.x(), maxY)));
1206                         prevPixelDiffZero = false;
1207                         p0 = p1;
1208                         lastPoint = p1;
1209                         continue;
1210                     }
1211                     if (lineIncreasingXOnly && (p1.x() < p0.x()))
1212                         continue;
1213 
1214                     tempPoint1 = QPointF(p1.x(), p0.y()); // horizontal line
1215                     addLine(tempPoint1, xPos, minY, maxY, lastPoint, pixelDiff, numberOfPixelX, minDiffX, scale, prevPixelDiffZero);
1216                     addLine(p1, xPos, minY, maxY, lastPoint, pixelDiff, numberOfPixelX, minDiffX, scale, prevPixelDiffZero);
1217                     p0 = p1;
1218                 }
1219                 // last line is a vertical line so it must be drawn manually (pixelDiff is 0)
1220                 m_lines.append(QLineF(QPointF(p0.x(), minY), QPointF(p0.x(), maxY)));
1221                 break;
1222             }
1223             case XYCurve::LineType::StartVertical: {
1224                 for (int i{startIndex}; i <= endIndex; i++) {
1225                     p1 = m_logicalPoints.at(i);
1226                     if (!lineSkipGaps && (i > startIndex && !connectedPointsLogical.at(i - 1))) {
1227                         if (pixelDiff == 0)
1228                             m_lines.append(QLineF(QPointF(p0.x(), minY), QPointF(p0.x(), maxY)));
1229                         prevPixelDiffZero = false;
1230                         p0 = p1;
1231                         lastPoint = p1;
1232                         continue;
1233                     }
1234                     if (lineIncreasingXOnly && (p1.x() < p0.x()))
1235                         continue;
1236                     tempPoint1 = QPointF(p0.x(), p1.y());
1237                     addLine(tempPoint1, xPos, minY, maxY, lastPoint, pixelDiff, numberOfPixelX, minDiffX, scale, prevPixelDiffZero);
1238                     addLine(p1, xPos, minY, maxY, lastPoint, pixelDiff, numberOfPixelX, minDiffX, scale, prevPixelDiffZero);
1239                     p0 = p1;
1240                 }
1241 
1242                 if (pixelDiff == 0)
1243                     m_lines.append(QLineF(QPointF(p0.x(), minY), QPointF(p0.x(), maxY)));
1244 
1245                 break;
1246             }
1247             case XYCurve::LineType::MidpointHorizontal: {
1248                 for (int i{startIndex}; i <= endIndex; i++) {
1249                     p1 = m_logicalPoints.at(i);
1250                     if (!lineSkipGaps && (i > startIndex && !connectedPointsLogical.at(i - 1))) {
1251                         if (pixelDiff == 0)
1252                             m_lines.append(QLineF(QPointF(p0.x(), minY), QPointF(p0.x(), maxY)));
1253                         prevPixelDiffZero = false;
1254                         p0 = p1;
1255                         lastPoint = p1;
1256                         continue;
1257                     }
1258                     if (lineIncreasingXOnly && (p1.x() < p0.x()))
1259                         continue;
1260                     tempPoint1 = QPointF(p0.x() + (p1.x() - p0.x()) / 2., p0.y()); // horizontal until mid at p0.y level
1261                     tempPoint2 = QPointF(p0.x() + (p1.x() - p0.x()) / 2., p1.y()); // vertical line
1262                     addLine(tempPoint1, xPos, minY, maxY, lastPoint, pixelDiff, numberOfPixelX, minDiffX, scale, prevPixelDiffZero);
1263                     addLine(tempPoint2, xPos, minY, maxY, lastPoint, pixelDiff, numberOfPixelX, minDiffX, scale, prevPixelDiffZero);
1264                     addLine(p1, xPos, minY, maxY, lastPoint, pixelDiff, numberOfPixelX, minDiffX, scale, prevPixelDiffZero);
1265                     p0 = p1;
1266                 }
1267 
1268                 if (pixelDiff == 0)
1269                     m_lines.append(QLineF(QPointF(p0.x(), minY), QPointF(p0.x(), maxY)));
1270 
1271                 break;
1272             }
1273             case XYCurve::LineType::MidpointVertical: {
1274                 for (int i{startIndex}; i <= endIndex; i++) {
1275                     p1 = m_logicalPoints.at(i);
1276                     if (!lineSkipGaps && (i > startIndex && !connectedPointsLogical.at(i - 1))) {
1277                         // if (pixelDiff == 0) // last line will be always a vertical line
1278                         m_lines.append(QLineF(QPointF(p0.x(), minY), QPointF(p0.x(), maxY)));
1279                         prevPixelDiffZero = false;
1280                         p0 = p1;
1281                         lastPoint = p1;
1282                         continue;
1283                     }
1284                     if (lineIncreasingXOnly && (p1.x() < p0.x()))
1285                         continue;
1286                     tempPoint1 = QPointF(p0.x(), p0.y() + (p1.y() - p0.y()) / 2.);
1287                     tempPoint2 = QPointF(p1.x(), p0.y() + (p1.y() - p0.y()) / 2.);
1288                     addLine(tempPoint1, xPos, minY, maxY, lastPoint, pixelDiff, numberOfPixelX, minDiffX, scale, prevPixelDiffZero);
1289                     addLine(tempPoint2, xPos, minY, maxY, lastPoint, pixelDiff, numberOfPixelX, minDiffX, scale, prevPixelDiffZero);
1290                     addLine(p1, xPos, minY, maxY, lastPoint, pixelDiff, numberOfPixelX, minDiffX, scale, prevPixelDiffZero);
1291                     p0 = p1;
1292                 }
1293                 // last line is a vertical line so it must be drawn manually (pixelDiff is 0)
1294                 m_lines.append(QLineF(QPointF(p0.x(), minY), QPointF(p0.x(), maxY)));
1295                 break;
1296             }
1297             case XYCurve::LineType::Segments2:
1298             case XYCurve::LineType::Segments3: {
1299                 int skip{0};
1300                 int skip_index = 1;
1301                 if (lineType == XYCurve::LineType::Segments2)
1302                     skip_index = 1;
1303                 else if (lineType == XYCurve::LineType::Segments3)
1304                     skip_index = 2;
1305                 else
1306                     DEBUG("WARNING: unhandled case!!!!");
1307 
1308                 for (int i{startIndex}; i <= endIndex; i++) {
1309                     p1 = m_logicalPoints.at(i);
1310                     if (skip < skip_index) {
1311                         if (!lineSkipGaps && (i > startIndex && !connectedPointsLogical.at(i - 1))) {
1312                             if (pixelDiff == 0)
1313                                 m_lines.append(QLineF(QPointF(p0.x(), minY), QPointF(p0.x(), maxY)));
1314                             prevPixelDiffZero = false;
1315                             p0 = p1;
1316                             lastPoint = p1;
1317                             skip = 0;
1318                             continue;
1319                         }
1320 
1321                         if (lineIncreasingXOnly && (p1.x() < p0.x())) {
1322                             skip = 0;
1323                             continue;
1324                         }
1325                         addLine(p1, xPos, minY, maxY, lastPoint, pixelDiff, numberOfPixelX, minDiffX, scale, prevPixelDiffZero);
1326                         skip++;
1327                     } else {
1328                         skip = 0;
1329                         if (!lineSkipGaps && (i > startIndex && !connectedPointsLogical.at(i - 1))) {
1330                             if (pixelDiff == 0)
1331                                 m_lines.append(QLineF(QPointF(p0.x(), minY), QPointF(p0.x(), maxY)));
1332                             prevPixelDiffZero = false;
1333                             skip += 2; // if one point is missing, 2 lines are skipped
1334                         }
1335                         lastPoint = p1;
1336                     }
1337                     p0 = p1;
1338                 }
1339 
1340                 if (pixelDiff == 0 && skip != skip_index)
1341                     m_lines.append(QLineF(QPointF(p0.x(), minY), QPointF(p0.x(), maxY)));
1342 
1343                 break;
1344             }
1345             case XYCurve::LineType::SplineCubicNatural:
1346             case XYCurve::LineType::SplineCubicPeriodic:
1347             case XYCurve::LineType::SplineAkimaNatural:
1348             case XYCurve::LineType::SplineAkimaPeriodic: {
1349                 std::unique_ptr<double[]> x(new double[numberOfPoints]());
1350                 std::unique_ptr<double[]> y(new double[numberOfPoints]());
1351                 int validPoints = 1; // p0 is valid
1352                 x[0] = p0.x();
1353                 y[0] = p0.y();
1354                 for (int i{startIndex}; i <= endIndex; i++) {
1355                     p1 = m_logicalPoints.at(i);
1356                     if (!lineSkipGaps && (i > startIndex && !connectedPointsLogical.at(i - 1))) {
1357                         if (pixelDiff == 0)
1358                             m_lines.append(QLineF(QPointF(p0.x(), minY), QPointF(p0.x(), maxY)));
1359                         p0 = p1;
1360                         lastPoint = p1;
1361                         continue;
1362                     }
1363                     if (lineIncreasingXOnly && (p1.x() < p0.x()))
1364                         continue;
1365                     validPoints++;
1366                     x[i] = p1.x();
1367                     y[i] = p1.y();
1368                 }
1369                 numberOfPoints = validPoints;
1370 
1371                 gsl_interp_accel* acc = gsl_interp_accel_alloc();
1372                 gsl_spline* spline{nullptr};
1373                 gsl_set_error_handler_off();
1374                 switch (lineType) {
1375                 case XYCurve::LineType::SplineCubicNatural:
1376                     spline = gsl_spline_alloc(gsl_interp_cspline, numberOfPoints);
1377                     break;
1378                 case XYCurve::LineType::SplineCubicPeriodic:
1379                     spline = gsl_spline_alloc(gsl_interp_cspline_periodic, numberOfPoints);
1380                     break;
1381                 case XYCurve::LineType::SplineAkimaNatural:
1382                     spline = gsl_spline_alloc(gsl_interp_akima, numberOfPoints);
1383                     break;
1384                 case XYCurve::LineType::SplineAkimaPeriodic:
1385                     spline = gsl_spline_alloc(gsl_interp_akima_periodic, numberOfPoints);
1386                     break;
1387                 case XYCurve::LineType::NoLine:
1388                 case XYCurve::LineType::Line:
1389                 case XYCurve::LineType::StartHorizontal:
1390                 case XYCurve::LineType::StartVertical:
1391                 case XYCurve::LineType::MidpointHorizontal:
1392                 case XYCurve::LineType::MidpointVertical:
1393                 case XYCurve::LineType::Segments2:
1394                 case XYCurve::LineType::Segments3:
1395                     break;
1396                 }
1397 
1398                 if (!spline) {
1399                     QString msg;
1400                     if ((lineType == XYCurve::LineType::SplineAkimaNatural || lineType == XYCurve::LineType::SplineAkimaPeriodic) && numberOfPoints < 5)
1401                         msg = i18n("Error: Akima spline interpolation requires a minimum of 5 points.");
1402                     else
1403                         msg = i18n("Error: Could not initialize the spline function.");
1404                     Q_EMIT q->info(msg);
1405 
1406                     recalcShapeAndBoundingRect();
1407                     gsl_interp_accel_free(acc);
1408                     return;
1409                 }
1410 
1411                 int status = gsl_spline_init(spline, x.get(), y.get(), numberOfPoints);
1412                 if (status != 0) {
1413                     // TODO: check in gsl/interp.c when GSL_EINVAL is thrown
1414                     QString gslError;
1415                     if (status == GSL_EINVAL)
1416                         gslError = i18n("x values must be monotonically increasing.");
1417                     else
1418                         gslError = gslErrorToString(status);
1419                     Q_EMIT q->info(i18n("Error: %1", gslError));
1420 
1421                     recalcShapeAndBoundingRect();
1422                     gsl_spline_free(spline);
1423                     gsl_interp_accel_free(acc);
1424                     return;
1425                 }
1426 
1427                 // create interpolating points
1428                 // TODO: QVector
1429                 std::vector<double> xinterp, yinterp;
1430                 for (int i{0}; i < numberOfPoints - 1; i++) {
1431                     const double x1 = x[i];
1432                     const double x2 = x[i + 1];
1433                     const double step = std::abs(x2 - x1) / (lineInterpolationPointsCount + 1);
1434 
1435                     for (int j{0}; j < (lineInterpolationPointsCount + 1); j++) {
1436                         const double xi = x1 + j * step;
1437                         const double yi = gsl_spline_eval(spline, xi, acc);
1438                         xinterp.push_back(xi);
1439                         yinterp.push_back(yi);
1440                     }
1441                 }
1442 
1443                 if (!xinterp.empty()) {
1444                     for (unsigned int i{0}; i < xinterp.size() - 1; i++) {
1445                         p0 = QPointF(xinterp[i], yinterp[i]);
1446                         addLine(p0, xPos, minY, maxY, lastPoint, pixelDiff, numberOfPixelX, minDiffX, scale, prevPixelDiffZero);
1447                     }
1448 
1449                     addLine(QPointF(x[numberOfPoints - 1], y[numberOfPoints - 1]),
1450                             xPos,
1451                             minY,
1452                             maxY,
1453                             lastPoint,
1454                             pixelDiff,
1455                             numberOfPixelX,
1456                             minDiffX,
1457                             scale,
1458                             prevPixelDiffZero);
1459                 }
1460 
1461                 gsl_spline_free(spline);
1462                 gsl_interp_accel_free(acc);
1463                 break;
1464             }
1465             }
1466         }
1467     }
1468 
1469     // map the lines to scene coordinates
1470     {
1471 #if PERFTRACE_CURVES
1472         PERFTRACE(QLatin1String(Q_FUNC_INFO) + QStringLiteral(", curve ") + name() + QStringLiteral(", map lines to scene coordinates"));
1473 #endif
1474         Q_EMIT q->linesUpdated(q, m_lines);
1475         m_lines = q->cSystem->mapLogicalToScene(m_lines);
1476     }
1477 
1478     {
1479 #if PERFTRACE_CURVES
1480         PERFTRACE(QLatin1String(Q_FUNC_INFO) + QStringLiteral(", curve ") + name() + QStringLiteral(", calculate new line path"));
1481 #endif
1482         // new line path
1483         if (!m_lines.isEmpty()) {
1484             linePath.moveTo(m_lines.constFirst().p1());
1485             QPointF prevP2;
1486             for (const auto& line : qAsConst(m_lines)) {
1487                 if (prevP2 != line.p1())
1488                     linePath.moveTo(line.p1());
1489                 linePath.lineTo(line.p2());
1490                 prevP2 = line.p2();
1491             }
1492         }
1493     }
1494 
1495     updateFilling();
1496     recalcShapeAndBoundingRect();
1497 }
1498 
1499 /*!
1500   recalculates the painter path for the drop lines.
1501   Called each time when the type of the drop lines is changed.
1502 */
1503 void XYCurvePrivate::updateDropLines() {
1504     dropLinePath = QPainterPath();
1505     if (dropLine->dropLineType() == XYCurve::DropLineType::NoDropLine) {
1506         recalcShapeAndBoundingRect();
1507         return;
1508     }
1509 
1510     // calculate the scene points to get the values for m_pointsVisible
1511     calculateScenePoints();
1512 
1513     // calculate drop lines
1514     QVector<QLineF> dlines;
1515     auto cs = plot()->coordinateSystem(q->coordinateSystemIndex());
1516     const double xMin = plot()->range(Dimension::X, cs->index(Dimension::X)).start();
1517     const double yMin = plot()->range(Dimension::Y, cs->index(Dimension::Y)).start();
1518 
1519     int i = 0;
1520     switch (dropLine->dropLineType()) {
1521     case XYCurve::DropLineType::NoDropLine:
1522         break;
1523     case XYCurve::DropLineType::X:
1524         for (const auto& point : qAsConst(m_logicalPoints)) {
1525             if (!m_pointVisible.at(i++))
1526                 continue;
1527             dlines.append(QLineF(point, QPointF(point.x(), yMin)));
1528         }
1529         break;
1530     case XYCurve::DropLineType::Y:
1531         for (const auto& point : qAsConst(m_logicalPoints)) {
1532             if (!m_pointVisible.at(i++))
1533                 continue;
1534             dlines.append(QLineF(point, QPointF(xMin, point.y())));
1535         }
1536         break;
1537     case XYCurve::DropLineType::XY:
1538         for (const auto& point : qAsConst(m_logicalPoints)) {
1539             if (!m_pointVisible.at(i++))
1540                 continue;
1541             dlines.append(QLineF(point, QPointF(point.x(), yMin)));
1542             dlines.append(QLineF(point, QPointF(xMin, point.y())));
1543         }
1544         break;
1545     case XYCurve::DropLineType::XZeroBaseline:
1546         for (const auto& point : qAsConst(m_logicalPoints)) {
1547             if (!m_pointVisible.at(i++))
1548                 continue;
1549             dlines.append(QLineF(point, QPointF(point.x(), 0)));
1550         }
1551         break;
1552     case XYCurve::DropLineType::XMinBaseline:
1553         for (const auto& point : qAsConst(m_logicalPoints)) {
1554             if (!m_pointVisible.at(i++))
1555                 continue;
1556             dlines.append(QLineF(point, QPointF(point.x(), yColumn->minimum())));
1557         }
1558         break;
1559     case XYCurve::DropLineType::XMaxBaseline:
1560         for (const auto& point : qAsConst(m_logicalPoints)) {
1561             if (!m_pointVisible.at(i++))
1562                 continue;
1563             dlines.append(QLineF(point, QPointF(point.x(), yColumn->maximum())));
1564         }
1565         break;
1566     }
1567 
1568     // map the drop lines to scene coordinates
1569     dlines = q->cSystem->mapLogicalToScene(dlines);
1570 
1571     // new painter path for the drop lines
1572     for (const auto& line : qAsConst(dlines)) {
1573         dropLinePath.moveTo(line.p1());
1574         dropLinePath.lineTo(line.p2());
1575     }
1576 
1577     recalcShapeAndBoundingRect();
1578 }
1579 
1580 void XYCurvePrivate::updateSymbols() {
1581 #if PERFTRACE_CURVES
1582     PERFTRACE(QLatin1String(Q_FUNC_INFO) + QStringLiteral(", curve ") + name());
1583 #endif
1584     symbolsPath = QPainterPath();
1585     if (symbol->style() != Symbol::Style::NoSymbols) {
1586         QPainterPath path = Symbol::stylePath(symbol->style());
1587 
1588         QTransform trafo;
1589         trafo.scale(symbol->size(), symbol->size());
1590         path = trafo.map(path);
1591         trafo.reset();
1592 
1593         if (symbol->rotationAngle() != 0.) {
1594             trafo.rotate(symbol->rotationAngle());
1595             path = trafo.map(path);
1596         }
1597         calculateScenePoints();
1598         for (const auto& point : qAsConst(m_scenePoints)) {
1599             trafo.reset();
1600             trafo.translate(point.x(), point.y());
1601             symbolsPath.addPath(trafo.map(path));
1602         }
1603     }
1604 
1605     recalcShapeAndBoundingRect();
1606 }
1607 
1608 void XYCurvePrivate::updateRug() {
1609     rugPath = QPainterPath();
1610 
1611     if (!rugEnabled || !plot()) {
1612         recalcShapeAndBoundingRect();
1613         return;
1614     }
1615 
1616     QVector<QPointF> points;
1617     auto cs = plot()->coordinateSystem(q->coordinateSystemIndex());
1618     const double xMin = plot()->range(Dimension::X, cs->index(Dimension::X)).start();
1619     const double yMin = plot()->range(Dimension::Y, cs->index(Dimension::Y)).start();
1620 
1621     // vertical rug
1622     if (rugOrientation == WorksheetElement::Orientation::Vertical || rugOrientation == WorksheetElement::Orientation::Both) {
1623         for (const auto& point : qAsConst(m_logicalPoints))
1624             points << QPointF(xMin, point.y());
1625 
1626         // map the points to scene coordinates
1627         points = q->cSystem->mapLogicalToScene(points);
1628 
1629         // path for the vertical rug lines
1630         for (const auto& point : qAsConst(points)) {
1631             rugPath.moveTo(point.x() + rugOffset, point.y());
1632             rugPath.lineTo(point.x() + rugOffset + rugLength, point.y());
1633         }
1634     }
1635 
1636     // horizontal rug
1637     if (rugOrientation == WorksheetElement::Orientation::Horizontal || rugOrientation == WorksheetElement::Orientation::Both) {
1638         points.clear();
1639         for (const auto& point : qAsConst(m_logicalPoints))
1640             points << QPointF(point.x(), yMin);
1641 
1642         // map the points to scene coordinates
1643         points = q->cSystem->mapLogicalToScene(points);
1644 
1645         // path for the horizontal rug lines
1646         for (const auto& point : qAsConst(points)) {
1647             rugPath.moveTo(point.x(), point.y() - rugOffset);
1648             rugPath.lineTo(point.x(), point.y() - rugOffset - rugLength);
1649         }
1650     }
1651 
1652     recalcShapeAndBoundingRect();
1653 }
1654 
1655 /*!
1656   recreates the value strings to be shown and recalculates their draw position.
1657 */
1658 void XYCurvePrivate::updateValues() {
1659 #if PERFTRACE_CURVES
1660     PERFTRACE(QLatin1String(Q_FUNC_INFO) + QLatin1String(", curve ") + name());
1661 #endif
1662     valuesPath = QPainterPath();
1663     m_valuePoints.clear();
1664     m_valueStrings.clear();
1665 
1666     const int numberOfPoints = m_logicalPoints.size();
1667     if (valuesType == XYCurve::ValuesType::NoValues || numberOfPoints == 0) {
1668         recalcShapeAndBoundingRect();
1669         return;
1670     }
1671     m_valuePoints.reserve(numberOfPoints);
1672     m_valueStrings.reserve(numberOfPoints);
1673 
1674     calculateScenePoints();
1675 
1676     // determine the value string for all points that are currently visible in the plot
1677     int i{0};
1678     auto cs = plot()->coordinateSystem(q->coordinateSystemIndex());
1679     const auto numberLocale = QLocale();
1680     switch (valuesType) {
1681     case XYCurve::ValuesType::NoValues:
1682     case XYCurve::ValuesType::X: {
1683         auto xRangeFormat{plot()->range(Dimension::X, cs->index(Dimension::X)).format()};
1684         int precision = valuesPrecision;
1685         if (xColumn->columnMode() == AbstractColumn::ColumnMode::Integer || xColumn->columnMode() == AbstractColumn::ColumnMode::BigInt)
1686             precision = 0;
1687         for (const auto& point : qAsConst(m_logicalPoints)) {
1688             if (!m_pointVisible.at(i++))
1689                 continue;
1690             QString value;
1691             if (xRangeFormat == RangeT::Format::Numeric)
1692                 value = numberLocale.toString(point.x(), valuesNumericFormat, precision);
1693             else
1694                 value = QDateTime::fromMSecsSinceEpoch(point.x(), Qt::UTC).toString(valuesDateTimeFormat);
1695             m_valueStrings << valuesPrefix + value + valuesSuffix;
1696         }
1697         break;
1698     }
1699     case XYCurve::ValuesType::Y: {
1700         auto rangeFormat{plot()->range(Dimension::Y, cs->index(Dimension::Y)).format()};
1701         int precision = valuesPrecision;
1702         if (yColumn->columnMode() == AbstractColumn::ColumnMode::Integer || yColumn->columnMode() == AbstractColumn::ColumnMode::BigInt)
1703             precision = 0;
1704         for (const auto& point : qAsConst(m_logicalPoints)) {
1705             if (!m_pointVisible.at(i++))
1706                 continue;
1707             QString value;
1708             if (rangeFormat == RangeT::Format::Numeric)
1709                 value = numberLocale.toString(point.y(), valuesNumericFormat, precision);
1710             else
1711                 value = QDateTime::fromMSecsSinceEpoch(point.y(), Qt::UTC).toString(valuesDateTimeFormat);
1712             m_valueStrings << valuesPrefix + value + valuesSuffix;
1713         }
1714         break;
1715     }
1716     case XYCurve::ValuesType::XY:
1717     case XYCurve::ValuesType::XYBracketed: {
1718         auto xRangeFormat{plot()->range(Dimension::X, cs->index(Dimension::X)).format()};
1719         auto yRangeFormat{plot()->range(Dimension::Y, cs->index(Dimension::Y)).format()};
1720 
1721         int xPrecision = valuesPrecision;
1722         if (xColumn->columnMode() == AbstractColumn::ColumnMode::Integer || xColumn->columnMode() == AbstractColumn::ColumnMode::BigInt)
1723             xPrecision = 0;
1724 
1725         int yPrecision = valuesPrecision;
1726         if (yColumn->columnMode() == AbstractColumn::ColumnMode::Integer || yColumn->columnMode() == AbstractColumn::ColumnMode::BigInt)
1727             yPrecision = 0;
1728 
1729         for (const auto& point : qAsConst(m_logicalPoints)) {
1730             if (!m_pointVisible.at(i++))
1731                 continue;
1732             QString value;
1733             if (valuesType == XYCurve::ValuesType::XYBracketed)
1734                 value = QLatin1Char('(');
1735             if (xRangeFormat == RangeT::Format::Numeric)
1736                 value += numberLocale.toString(point.x(), valuesNumericFormat, xPrecision);
1737             else
1738                 value += QDateTime::fromMSecsSinceEpoch(point.x(), Qt::UTC).toString(valuesDateTimeFormat);
1739 
1740             if (yRangeFormat == RangeT::Format::Numeric)
1741                 value += QLatin1Char(',') + numberLocale.toString(point.y(), valuesNumericFormat, yPrecision);
1742             else
1743                 value += QLatin1Char(',') + QDateTime::fromMSecsSinceEpoch(point.y(), Qt::UTC).toString(valuesDateTimeFormat);
1744 
1745             if (valuesType == XYCurve::ValuesType::XYBracketed)
1746                 value += QLatin1Char(')');
1747 
1748             m_valueStrings << valuesPrefix + value + valuesSuffix;
1749         }
1750         break;
1751     }
1752     case XYCurve::ValuesType::CustomColumn: {
1753         if (!valuesColumn) {
1754             recalcShapeAndBoundingRect();
1755             return;
1756         }
1757 
1758         const int endRow{std::min(std::min(xColumn->rowCount(), yColumn->rowCount()), valuesColumn->rowCount())};
1759         auto xColMode{xColumn->columnMode()};
1760         auto vColMode{valuesColumn->columnMode()};
1761 
1762         // need to check x range
1763         auto cs = plot()->coordinateSystem(q->coordinateSystemIndex());
1764         auto xRange = plot()->range(Dimension::X, cs->index(Dimension::X));
1765 
1766         size_t index = 0; // index of valid points (logicalPoints)
1767         for (int i = 0; i < endRow; ++i) {
1768             // ignore value labels for invalid data points
1769             // otherwise the assignment to the data points get lost
1770             if (!xColumn->isValid(i) || xColumn->isMasked(i) || !yColumn->isValid(i) || yColumn->isMasked(i) || !m_pointVisible.at(index++))
1771                 continue;
1772 
1773             if (!valuesColumn->isValid(i) || valuesColumn->isMasked(i)) {
1774                 m_valueStrings << QString();
1775                 continue;
1776             }
1777 
1778             // check if inside x range
1779             switch (xColMode) {
1780             case AbstractColumn::ColumnMode::Double:
1781             case AbstractColumn::ColumnMode::Integer:
1782             case AbstractColumn::ColumnMode::BigInt:
1783                 if (!xRange.contains(xColumn->valueAt(i)))
1784                     continue;
1785                 break;
1786             case AbstractColumn::ColumnMode::DateTime:
1787             case AbstractColumn::ColumnMode::Month:
1788             case AbstractColumn::ColumnMode::Day:
1789                 if (xColumn->dateTimeAt(i) < QDateTime::fromMSecsSinceEpoch(xRange.start(), Qt::UTC)
1790                     || xColumn->dateTimeAt(i) > QDateTime::fromMSecsSinceEpoch(xRange.end(), Qt::UTC))
1791                     continue;
1792                 break;
1793             case AbstractColumn::ColumnMode::Text:
1794                 break;
1795             }
1796 
1797             switch (vColMode) {
1798             case AbstractColumn::ColumnMode::Double:
1799                 m_valueStrings << valuesPrefix + numberLocale.toString(valuesColumn->valueAt(i), valuesNumericFormat, valuesPrecision) + valuesSuffix;
1800                 break;
1801             case AbstractColumn::ColumnMode::Integer:
1802             case AbstractColumn::ColumnMode::BigInt:
1803                 m_valueStrings << valuesPrefix + numberLocale.toString(valuesColumn->valueAt(i)) + valuesSuffix;
1804                 break;
1805             case AbstractColumn::ColumnMode::Text:
1806                 m_valueStrings << valuesPrefix + valuesColumn->textAt(i) + valuesSuffix;
1807                 break;
1808             case AbstractColumn::ColumnMode::DateTime:
1809             case AbstractColumn::ColumnMode::Month:
1810             case AbstractColumn::ColumnMode::Day:
1811                 m_valueStrings << valuesPrefix + valuesColumn->dateTimeAt(i).toString(valuesDateTimeFormat) + valuesSuffix;
1812                 break;
1813             }
1814         }
1815     }
1816     }
1817     m_valueStrings.squeeze();
1818 
1819     // Calculate the coordinates where to paint the value strings.
1820     // The coordinates depend on the actual size of the string.
1821     QPointF tempPoint;
1822     QFontMetrics fm(valuesFont);
1823     const int h{fm.ascent()};
1824 
1825     i = 0;
1826     for (const auto& string : qAsConst(m_valueStrings)) {
1827         // catch case with more label strings than scene points (should not happen even with custom column)
1828         if (i >= m_scenePoints.size())
1829             break;
1830         const int w{fm.boundingRect(string).width()};
1831         const double x{m_scenePoints.at(i).x()};
1832         const double y{m_scenePoints.at(i).y()};
1833         i++;
1834 
1835         switch (valuesPosition) {
1836         case XYCurve::ValuesPosition::Above:
1837             tempPoint = QPointF(x - w / 2., y - valuesDistance);
1838             break;
1839         case XYCurve::ValuesPosition::Under:
1840             tempPoint = QPointF(x - w / 2., y + valuesDistance + h / 2.);
1841             break;
1842         case XYCurve::ValuesPosition::Left:
1843             tempPoint = QPointF(x - valuesDistance - w - 1., y);
1844             break;
1845         case XYCurve::ValuesPosition::Right:
1846             tempPoint = QPointF(x + valuesDistance - 1., y);
1847             break;
1848         }
1849         m_valuePoints.append(tempPoint);
1850     }
1851     m_valuePoints.squeeze();
1852 
1853     QTransform trafo;
1854     QPainterPath path;
1855     i = 0;
1856     for (const auto& point : qAsConst(m_valuePoints)) {
1857         path = QPainterPath();
1858         path.addText(QPoint(0, 0), valuesFont, m_valueStrings.at(i++));
1859 
1860         trafo.reset();
1861         trafo.translate(point.x(), point.y());
1862         if (valuesRotationAngle != 0)
1863             trafo.rotate(-valuesRotationAngle);
1864 
1865         valuesPath.addPath(trafo.map(path));
1866     }
1867 
1868     recalcShapeAndBoundingRect();
1869 }
1870 
1871 void XYCurvePrivate::updateFilling() {
1872     if (suppressRetransform)
1873         return;
1874 
1875     m_fillPolygons.clear();
1876 
1877     // don't try to calculate the filling polygons if
1878     //  - no filling was enabled
1879     //  - the number of visible points on the scene is too high
1880     //  - no scene points available, everything outside of the plot region or no scene points calculated yet
1881     auto fillingPosition = background->position();
1882     if (fillingPosition == Background::Position::No) {
1883         recalcShapeAndBoundingRect();
1884         return;
1885     }
1886     calculateScenePoints(); // TODO: find other way
1887     if (m_scenePoints.size() > 1000 || m_scenePoints.isEmpty()) {
1888         recalcShapeAndBoundingRect();
1889         return;
1890     }
1891 
1892     QVector<QLineF> fillLines;
1893 
1894     // if there're no interpolation lines available (XYCurve::NoLine selected), create line-interpolation,
1895     // use already available lines otherwise.
1896     if (!m_lines.isEmpty())
1897         fillLines = m_lines;
1898     else {
1899         for (int i = 0; i < m_logicalPoints.size() - 1; i++) {
1900             if (!lineSkipGaps && !connectedPointsLogical[i])
1901                 continue;
1902             fillLines.append(QLineF(m_logicalPoints.at(i), m_logicalPoints.at(i + 1)));
1903         }
1904 
1905         // no lines available (no points), nothing to do
1906         if (fillLines.isEmpty())
1907             return;
1908 
1909         fillLines = q->cSystem->mapLogicalToScene(fillLines);
1910 
1911         // no lines available (no points) after mapping, nothing to do
1912         if (fillLines.isEmpty())
1913             return;
1914     }
1915 
1916     // create polygon(s):
1917     // 1. Depending on the current zoom-level, only a subset of the curve may be visible in the plot
1918     // and more of the filling area should be shown than the area defined by the start and end points of the currently visible points.
1919     // We check first whether the curve crosses the boundaries of the plot and determine new start and end points and put them to the boundaries.
1920     // 2. Furthermore, depending on the current filling type we determine the end point (x- or y-coordinate) where all polygons are closed at the end.
1921     QPolygonF pol;
1922     QPointF start = fillLines.at(0).p1(); // starting point of the current polygon, initialize with the first visible point
1923     QPointF end = fillLines.at(fillLines.size() - 1).p2(); // end point of the current polygon, initialize with the last visible point
1924     const QPointF& first = m_logicalPoints.at(0); // first point of the curve, may not be visible currently
1925     const QPointF& last = m_logicalPoints.at(m_logicalPoints.size() - 1); // last point of the curve, may not be visible currently
1926     QPointF edge;
1927     double xEnd{0.}, yEnd{0.};
1928     auto cs = plot()->coordinateSystem(q->coordinateSystemIndex());
1929     const auto xRange{plot()->range(Dimension::X, cs->index(Dimension::X))};
1930     const auto yRange{plot()->range(Dimension::Y, cs->index(Dimension::Y))};
1931     const double xMin{xRange.start()}, xMax{xRange.end()};
1932     const double yMin{yRange.start()}, yMax{yRange.end()};
1933     bool visible;
1934     if (fillingPosition == Background::Position::Above) {
1935         edge = q->cSystem->mapLogicalToScene(QPointF(xMin, yMin), visible);
1936 
1937         // start point
1938         if (nsl_math_essentially_equal(start.y(), edge.y())) {
1939             if (first.x() < xMin)
1940                 start = edge;
1941             else if (first.x() > xMax)
1942                 start = q->cSystem->mapLogicalToScene(QPointF(xMax, yMin), visible);
1943             else
1944                 start = q->cSystem->mapLogicalToScene(QPointF(first.x(), yMin), visible);
1945         }
1946 
1947         // end point
1948         if (nsl_math_essentially_equal(end.y(), edge.y())) {
1949             if (last.x() < xMin)
1950                 end = edge;
1951             else if (last.x() > xMax)
1952                 end = q->cSystem->mapLogicalToScene(QPointF(xMax, yMin), visible);
1953             else
1954                 end = q->cSystem->mapLogicalToScene(QPointF(last.x(), yMin), visible);
1955         }
1956 
1957         // coordinate at which to close all polygons
1958         yEnd = q->cSystem->mapLogicalToScene(QPointF(xMin, yMax), visible).y();
1959     } else if (fillingPosition == Background::Position::Below) {
1960         edge = q->cSystem->mapLogicalToScene(QPointF(xMin, yMax), visible);
1961 
1962         // start point
1963         if (nsl_math_essentially_equal(start.y(), edge.y())) {
1964             if (first.x() < xMin)
1965                 start = edge;
1966             else if (first.x() > xMax)
1967                 start = q->cSystem->mapLogicalToScene(QPointF(xMax, yMax), visible);
1968             else
1969                 start = q->cSystem->mapLogicalToScene(QPointF(first.x(), yMax), visible);
1970         }
1971 
1972         // end point
1973         if (nsl_math_essentially_equal(end.y(), edge.y())) {
1974             if (last.x() < xMin)
1975                 end = edge;
1976             else if (last.x() > xMax)
1977                 end = q->cSystem->mapLogicalToScene(QPointF(xMax, yMax), visible);
1978             else
1979                 end = q->cSystem->mapLogicalToScene(QPointF(last.x(), yMax), visible);
1980         }
1981 
1982         // coordinate at which to close all polygons
1983         yEnd = q->cSystem->mapLogicalToScene(QPointF(xMin, yMin), visible).y();
1984     } else if (fillingPosition == Background::Position::ZeroBaseline) {
1985         edge = q->cSystem->mapLogicalToScene(QPointF(xMin, yMax), visible);
1986 
1987         // start point
1988         if (nsl_math_essentially_equal(start.y(), edge.y())) {
1989             if (yMax > 0) {
1990                 if (first.x() < xMin)
1991                     start = edge;
1992                 else if (first.x() > xRange.end())
1993                     start = q->cSystem->mapLogicalToScene(QPointF(xMax, yMax), visible);
1994                 else
1995                     start = q->cSystem->mapLogicalToScene(QPointF(first.x(), yMax), visible);
1996             } else {
1997                 if (first.x() < xMin)
1998                     start = edge;
1999                 else if (first.x() > xMax)
2000                     start = q->cSystem->mapLogicalToScene(QPointF(xMax, yMin), visible);
2001                 else
2002                     start = q->cSystem->mapLogicalToScene(QPointF(first.x(), yMin), visible);
2003             }
2004         }
2005 
2006         // end point
2007         if (nsl_math_essentially_equal(end.y(), edge.y())) {
2008             if (yMax > 0) {
2009                 if (last.x() < xMin)
2010                     end = edge;
2011                 else if (last.x() > xMax)
2012                     end = q->cSystem->mapLogicalToScene(QPointF(xMax, yMax), visible);
2013                 else
2014                     end = q->cSystem->mapLogicalToScene(QPointF(last.x(), yMax), visible);
2015             } else {
2016                 if (last.x() < xMin)
2017                     end = edge;
2018                 else if (last.x() > xMax)
2019                     end = q->cSystem->mapLogicalToScene(QPointF(xMax, yMin), visible);
2020                 else
2021                     end = q->cSystem->mapLogicalToScene(QPointF(last.x(), yMin), visible);
2022             }
2023         }
2024 
2025         yEnd = q->cSystem->mapLogicalToScene(QPointF(xMin, yMin > 0 ? yMin : 0), visible).y();
2026     } else if (fillingPosition == Background::Position::Left) {
2027         edge = q->cSystem->mapLogicalToScene(QPointF(xMax, yMin), visible);
2028 
2029         // start point
2030         if (nsl_math_essentially_equal(start.x(), edge.x())) {
2031             if (first.y() < yMin)
2032                 start = edge;
2033             else if (first.y() > yMax)
2034                 start = q->cSystem->mapLogicalToScene(QPointF(xMax, yMax), visible);
2035             else
2036                 start = q->cSystem->mapLogicalToScene(QPointF(xMax, first.y()), visible);
2037         }
2038 
2039         // end point
2040         if (nsl_math_essentially_equal(end.x(), edge.x())) {
2041             if (last.y() < yMin)
2042                 end = edge;
2043             else if (last.y() > yMax)
2044                 end = q->cSystem->mapLogicalToScene(QPointF(xMax, yMax), visible);
2045             else
2046                 end = q->cSystem->mapLogicalToScene(QPointF(xMax, last.y()), visible);
2047         }
2048 
2049         // coordinate at which to close all polygons
2050         xEnd = q->cSystem->mapLogicalToScene(QPointF(xMin, yMin), visible).x();
2051     } else { // FillingRight
2052         edge = q->cSystem->mapLogicalToScene(QPointF(xMin, yMin), visible);
2053 
2054         // start point
2055         if (nsl_math_essentially_equal(start.x(), edge.x())) {
2056             if (first.y() < yMin)
2057                 start = edge;
2058             else if (first.y() > yMax)
2059                 start = q->cSystem->mapLogicalToScene(QPointF(xMin, yMax), visible);
2060             else
2061                 start = q->cSystem->mapLogicalToScene(QPointF(xMin, first.y()), visible);
2062         }
2063 
2064         // end point
2065         if (nsl_math_essentially_equal(end.x(), edge.x())) {
2066             if (last.y() < yMin)
2067                 end = edge;
2068             else if (last.y() > yMax)
2069                 end = q->cSystem->mapLogicalToScene(QPointF(xMin, yMax), visible);
2070             else
2071                 end = q->cSystem->mapLogicalToScene(QPointF(xMin, last.y()), visible);
2072         }
2073 
2074         // coordinate at which to close all polygons
2075         xEnd = q->cSystem->mapLogicalToScene(QPointF(xMax, yMin), visible).x();
2076     }
2077 
2078     if (start != fillLines.at(0).p1())
2079         pol << start;
2080 
2081     QPointF p1, p2;
2082     for (int i = 0; i < fillLines.size(); ++i) {
2083         const QLineF& line = fillLines.at(i);
2084         p1 = line.p1();
2085         p2 = line.p2();
2086         if (i != 0 && p1 != fillLines.at(i - 1).p2()) {
2087             // the first point of the current line is not equal to the last point of the previous line
2088             //->check whether we have a break in between.
2089             const bool gap = false; // TODO
2090             if (!gap) {
2091                 //-> we have no break in the curve -> connect the points by a horizontal/vertical line
2092                 pol << fillLines.at(i - 1).p2() << p1;
2093             } else {
2094                 //-> we have a break in the curve -> close the polygon, add it to the polygon list and start a new polygon
2095                 if (fillingPosition == Background::Position::Above || fillingPosition == Background::Position::Below
2096                     || fillingPosition == Background::Position::ZeroBaseline) {
2097                     pol << QPointF(fillLines.at(i - 1).p2().x(), yEnd);
2098                     pol << QPointF(start.x(), yEnd);
2099                 } else {
2100                     pol << QPointF(xEnd, fillLines.at(i - 1).p2().y());
2101                     pol << QPointF(xEnd, start.y());
2102                 }
2103 
2104                 m_fillPolygons << pol;
2105                 pol.clear();
2106                 start = p1;
2107             }
2108         }
2109         pol << p1 << p2;
2110     }
2111 
2112     if (p2 != end)
2113         pol << end;
2114 
2115     // close the last polygon
2116     if (fillingPosition == Background::Position::Above || fillingPosition == Background::Position::Below
2117         || fillingPosition == Background::Position::ZeroBaseline) {
2118         pol << QPointF(end.x(), yEnd);
2119         pol << QPointF(start.x(), yEnd);
2120     } else {
2121         pol << QPointF(xEnd, end.y());
2122         pol << QPointF(xEnd, start.y());
2123     }
2124 
2125     m_fillPolygons << pol;
2126     recalcShapeAndBoundingRect();
2127 }
2128 
2129 /*!
2130  * Find y value which corresponds to a @p x . @p valueFound indicates, if value was found.
2131  * When monotonic increasing or decreasing a different algorithm will be used, which needs less steps (mean) (log_2(rowCount)) to find the value.
2132  * @param x
2133  * @param valueFound
2134  * @return
2135  */
2136 double XYCurve::y(double x, bool& valueFound) const {
2137     if (!yColumn() || !xColumn()) {
2138         valueFound = false;
2139         return NAN;
2140     }
2141 
2142     const int index = xColumn()->indexForValue(x);
2143     if (index < 0) {
2144         valueFound = false;
2145         return NAN;
2146     }
2147 
2148     valueFound = true;
2149     if (yColumn()->isNumeric())
2150         return yColumn()->valueAt(index);
2151     else {
2152         valueFound = false;
2153         return NAN;
2154     }
2155 }
2156 
2157 /*!
2158  * @param x :value for which y should be found
2159  * @param valueFound: returns true if y value found, otherwise false
2160  * @param x_new: exact x value where y value is
2161  * @return y value from x value
2162  */
2163 double XYCurve::y(double x, double& x_new, bool& valueFound) const {
2164     int index = xColumn()->indexForValue(x);
2165     if (index < 0) {
2166         valueFound = false;
2167         return NAN;
2168     }
2169 
2170     AbstractColumn::ColumnMode xColumnMode = xColumn()->columnMode();
2171     if (xColumn()->isNumeric())
2172         x_new = xColumn()->valueAt(index);
2173     else if (xColumnMode == AbstractColumn::ColumnMode::DateTime || xColumnMode == AbstractColumn::ColumnMode::Day
2174              || xColumnMode == AbstractColumn::ColumnMode::Month)
2175         x_new = xColumn()->dateTimeAt(index).toMSecsSinceEpoch();
2176     else {
2177         // any other type implemented
2178         valueFound = false;
2179         return NAN;
2180     }
2181 
2182     valueFound = true;
2183     if (yColumn()->isNumeric())
2184         return yColumn()->valueAt(index);
2185     else {
2186         valueFound = false;
2187         return NAN;
2188     }
2189 }
2190 
2191 /*!
2192  * Find y DateTime which corresponds to a @p x . @p valueFound indicates, if value was found.
2193  * When monotonic increasing or decreasing a different algorithm will be used, which needs less steps (mean) (log_2(rowCount)) to find the value.
2194  * @param x
2195  * @param valueFound
2196  * @return Return found value
2197  */
2198 QDateTime XYCurve::yDateTime(double x, bool& valueFound) const {
2199     if (!yColumn() || !xColumn()) {
2200         valueFound = false;
2201         return {};
2202     }
2203 
2204     auto yColumnMode = yColumn()->columnMode();
2205     const int index = xColumn()->indexForValue(x);
2206     if (index < 0) {
2207         valueFound = false;
2208         return {};
2209     }
2210 
2211     valueFound = true;
2212     if (yColumnMode == AbstractColumn::ColumnMode::Day || yColumnMode == AbstractColumn::ColumnMode::Month
2213         || yColumnMode == AbstractColumn::ColumnMode::DateTime)
2214         return yColumn()->dateTimeAt(index);
2215 
2216     valueFound = false;
2217     return {};
2218 }
2219 
2220 bool XYCurve::minMax(const Dimension dim, const Range<int>& indexRange, Range<double>& r, bool includeErrorBars) const {
2221     Q_D(const XYCurve);
2222     switch (dim) {
2223     case Dimension::X:
2224         return minMax(xColumn(), yColumn(), d->xErrorBar->type(), d->xErrorBar->plusColumn(), d->xErrorBar->minusColumn(), indexRange, r, includeErrorBars);
2225     case Dimension::Y:
2226         return minMax(yColumn(), xColumn(), d->yErrorBar->type(), d->yErrorBar->plusColumn(), d->yErrorBar->minusColumn(), indexRange, r, includeErrorBars);
2227     }
2228     return false;
2229 }
2230 
2231 /*!
2232  * Calculates the minimum \p min and maximum \p max of a curve with optionally respecting the error bars
2233  * This function does not check if the values are out of range
2234  * \p indexMax is not included
2235  * \p column1
2236  * \p column2
2237  * \p errorType
2238  * \p errorPlusColumn
2239  * \p errorMinusColumn
2240  * \p indexRange [min, max]
2241  * \p min
2242  * \p max
2243  * \p includeErrorBars If true respect the error bars in the min/max calculation
2244  */
2245 bool XYCurve::minMax(const AbstractColumn* column1,
2246                      const AbstractColumn* column2,
2247                      const ErrorBar::Type errorType,
2248                      const AbstractColumn* errorPlusColumn,
2249                      const AbstractColumn* errorMinusColumn,
2250                      const Range<int>& indexRange,
2251                      Range<double>& range,
2252                      bool includeErrorBars) const {
2253 #ifdef PERFTRACE_AUTOSCALE
2254     PERFTRACE(name() + QLatin1String(Q_FUNC_INFO));
2255 #endif
2256     // when property is increasing or decreasing there is a benefit in finding minimum and maximum
2257     // for property == AbstractColumn::Properties::No it must be iterated over all values so it does not matter if this function or the below one is used
2258     // if the property of the second column is not AbstractColumn::Properties::No means, that all values are valid and not masked
2259     // DEBUG(Q_FUNC_INFO << "\n, column 1 min/max = " << column1->minimum() << "/" << column1->maximum())
2260     if ((!includeErrorBars || errorType == ErrorBar::Type::NoError) && column1->properties() != AbstractColumn::Properties::No && column2
2261         && column2->properties() != AbstractColumn::Properties::No) {
2262         auto min = column1->minimum(indexRange.start(), indexRange.end());
2263         auto max = column1->maximum(indexRange.start(), indexRange.end());
2264         DEBUG(Q_FUNC_INFO << "\n, column 1 min/max in index range = " << min << "/" << max)
2265         // TODO: Range
2266         range.setRange(min, max);
2267         return true;
2268     }
2269 
2270     if (column1->rowCount() == 0)
2271         return false;
2272 
2273     range.setRange(INFINITY, -INFINITY);
2274     // DEBUG(Q_FUNC_INFO << ", calculate range for index range " << indexRange.start() << " .. " << indexRange.end())
2275 
2276     for (int i = indexRange.start(); i <= indexRange.end(); ++i) {
2277         if (!column1->isValid(i) || column1->isMasked(i) || (column2 && (!column2->isValid(i) || column2->isMasked(i))))
2278             continue;
2279 
2280         if ((errorPlusColumn && i >= errorPlusColumn->rowCount()) || (errorMinusColumn && i >= errorMinusColumn->rowCount()))
2281             continue;
2282 
2283         double value;
2284         if (column1->isNumeric())
2285             value = column1->valueAt(i);
2286         else if (column1->columnMode() == AbstractColumn::ColumnMode::DateTime || column1->columnMode() == AbstractColumn::ColumnMode::Month
2287                  || column1->columnMode() == AbstractColumn::ColumnMode::Day)
2288             value = column1->dateTimeAt(i).toMSecsSinceEpoch();
2289         else
2290             return false;
2291 
2292         if (errorType == ErrorBar::Type::NoError) {
2293             if (value < range.start())
2294                 range.start() = value;
2295 
2296             if (value > range.end())
2297                 range.end() = value;
2298         } else {
2299             // determine the values for the errors
2300             double errorPlus, errorMinus;
2301             if (errorPlusColumn && errorPlusColumn->isValid(i) && !errorPlusColumn->isMasked(i))
2302                 if (errorPlusColumn->isNumeric())
2303                     errorPlus = errorPlusColumn->valueAt(i);
2304                 else if (errorPlusColumn->columnMode() == AbstractColumn::ColumnMode::DateTime
2305                          || errorPlusColumn->columnMode() == AbstractColumn::ColumnMode::Month
2306                          || errorPlusColumn->columnMode() == AbstractColumn::ColumnMode::Day)
2307                     errorPlus = errorPlusColumn->dateTimeAt(i).toMSecsSinceEpoch();
2308                 else
2309                     return false;
2310             else
2311                 errorPlus = 0;
2312 
2313             if (errorType == ErrorBar::Type::Symmetric)
2314                 errorMinus = errorPlus;
2315             else {
2316                 if (errorMinusColumn && errorMinusColumn->isValid(i) && !errorMinusColumn->isMasked(i))
2317                     if (errorMinusColumn->isNumeric())
2318                         errorMinus = errorMinusColumn->valueAt(i);
2319                     else if (errorMinusColumn->columnMode() == AbstractColumn::ColumnMode::DateTime
2320                              || errorMinusColumn->columnMode() == AbstractColumn::ColumnMode::Month
2321                              || errorMinusColumn->columnMode() == AbstractColumn::ColumnMode::Day)
2322                         errorMinus = errorMinusColumn->dateTimeAt(i).toMSecsSinceEpoch();
2323                     else
2324                         return false;
2325                 else
2326                     errorMinus = 0;
2327             }
2328 
2329             if (value - errorMinus < range.start())
2330                 range.start() = value - errorMinus;
2331 
2332             if (value + errorPlus > range.end())
2333                 range.end() = value + errorPlus;
2334         }
2335         // DEBUG(Q_FUNC_INFO << ", range = " << range.toStdString())
2336     }
2337     return true;
2338 }
2339 
2340 bool XYCurvePrivate::activatePlot(QPointF mouseScenePos, double maxDist) {
2341     if (!isVisible())
2342         return false;
2343 
2344     int rowCount{0};
2345     if (lineType != XYCurve::LineType::NoLine)
2346         rowCount = m_lines.count();
2347     else if (symbol->style() != Symbol::Style::NoSymbols) {
2348         calculateScenePoints();
2349         rowCount = m_scenePoints.size();
2350     } else
2351         return false;
2352 
2353     if (rowCount == 0)
2354         return false;
2355 
2356     if (maxDist < 0)
2357         maxDist = (line->pen().width() < 10) ? 10. : line->pen().width();
2358 
2359     const double maxDistSquare = gsl_pow_2(maxDist);
2360 
2361     auto properties = q->xColumn()->properties();
2362     if (properties == AbstractColumn::Properties::No || properties == AbstractColumn::Properties::NonMonotonic) {
2363         // assumption: points exist if no line. otherwise previously returned false
2364         if (lineType == XYCurve::LineType::NoLine) {
2365             calculateScenePoints();
2366             QPointF curvePosPrevScene = m_scenePoints.at(0);
2367             QPointF curvePosScene = curvePosPrevScene;
2368             for (int row = 0; row < rowCount; row++) {
2369                 if (gsl_pow_2(mouseScenePos.x() - curvePosScene.x()) + gsl_pow_2(mouseScenePos.y() - curvePosScene.y()) <= maxDistSquare)
2370                     return true;
2371 
2372                 curvePosPrevScene = curvePosScene;
2373                 curvePosScene = m_scenePoints.at(row);
2374             }
2375         } else {
2376             for (int row = 0; row < rowCount; row++) {
2377                 QLineF line = m_lines.at(row);
2378                 if (pointLiesNearLine(line.p1(), line.p2(), mouseScenePos, maxDist))
2379                     return true;
2380             }
2381         }
2382 
2383     } else if (properties == AbstractColumn::Properties::MonotonicIncreasing || properties == AbstractColumn::Properties::MonotonicDecreasing) {
2384         bool increase{true};
2385         if (properties == AbstractColumn::Properties::MonotonicDecreasing)
2386             increase = false;
2387 
2388         double x{mouseScenePos.x() - maxDist};
2389         int index{0};
2390 
2391         QPointF curvePosScene;
2392         QPointF curvePosPrevScene;
2393 
2394         if (lineType == XYCurve::LineType::NoLine) {
2395             calculateScenePoints();
2396             curvePosScene = m_scenePoints.at(index);
2397             curvePosPrevScene = curvePosScene;
2398             index = Column::indexForValue(x, m_scenePoints, static_cast<AbstractColumn::Properties>(properties));
2399         } else
2400             index = Column::indexForValue(x, m_lines, static_cast<AbstractColumn::Properties>(properties));
2401 
2402         if (index >= 1)
2403             index--; // use one before so it is secured that I'm before point.x()
2404         else if (index == -1)
2405             return false;
2406 
2407         const double xMax{mouseScenePos.x() + maxDist};
2408         bool stop{false};
2409         while (true) {
2410             // assumption: points exist if no line. otherwise previously returned false
2411             if (lineType == XYCurve::LineType::NoLine) { // check points only if no line otherwise check only the lines
2412                 if (curvePosScene.x() > xMax)
2413                     stop = true; // one more time if bigger
2414                 if (gsl_hypot(mouseScenePos.x() - curvePosScene.x(), mouseScenePos.y() - curvePosScene.y()) <= maxDist)
2415                     return true;
2416             } else {
2417                 if (m_lines.at(index).p1().x() > xMax)
2418                     stop = true; // one more time if bigger
2419 
2420                 QLineF line = m_lines.at(index);
2421                 if (pointLiesNearLine(line.p1(), line.p2(), mouseScenePos, maxDist))
2422                     return true;
2423             }
2424 
2425             if (stop || (index >= rowCount - 1 && increase) || (index <= 0 && !increase))
2426                 break;
2427 
2428             if (increase)
2429                 index++;
2430             else
2431                 index--;
2432 
2433             if (lineType == XYCurve::LineType::NoLine) {
2434                 calculateScenePoints();
2435                 curvePosPrevScene = curvePosScene;
2436                 curvePosScene = m_scenePoints.at(index);
2437             }
2438         }
2439     }
2440 
2441     return false;
2442 }
2443 
2444 /*!
2445  * \brief XYCurve::pointLiesNearLine
2446  * Calculates if a point \p pos lies near than maxDist to the line created by the points \p p1 and \p p2
2447  * https://stackoverflow.com/questions/11604680/point-laying-near-line
2448  * \p p1 first point of the line
2449  * \p p2 second point of the line
2450  * \p pos Position to check
2451  * \p maxDist Maximal distance away from the curve, which is valid
2452  * \return Return true if point lies next to the line
2453  */
2454 bool XYCurvePrivate::pointLiesNearLine(const QPointF p1, const QPointF p2, const QPointF pos, const double maxDist) const {
2455     const double dx12{p2.x() - p1.x()};
2456     const double dy12{p2.y() - p1.y()};
2457     const double vecLength{gsl_hypot(dx12, dy12)};
2458 
2459     const double dx1m{pos.x() - p1.x()};
2460     const double dy1m{pos.y() - p1.y()};
2461     if (vecLength == 0.) {
2462         if (gsl_hypot(dx1m, dy1m) <= maxDist)
2463             return true;
2464         return false;
2465     }
2466     QPointF unitvec(dx12 / vecLength, dy12 / vecLength);
2467 
2468     const double dist_segm{std::abs(dx1m * unitvec.y() - dy1m * unitvec.x())};
2469     const double scalarProduct{dx1m * unitvec.x() + dy1m * unitvec.y()};
2470 
2471     if (scalarProduct > 0) {
2472         if (scalarProduct < vecLength && dist_segm < maxDist)
2473             return true;
2474     }
2475     return false;
2476 }
2477 
2478 // TODO: curvePosScene.x() >= mouseScenePos.x() &&
2479 // curvePosPrevScene.x() < mouseScenePos.x()
2480 // should not be here
2481 bool XYCurvePrivate::pointLiesNearCurve(const QPointF mouseScenePos,
2482                                         const QPointF curvePosPrevScene,
2483                                         const QPointF curvePosScene,
2484                                         const int index,
2485                                         const double maxDist) const {
2486     if (q->lineType() != XYCurve::LineType::NoLine && curvePosScene.x() >= mouseScenePos.x() && curvePosPrevScene.x() < mouseScenePos.x()) {
2487         if (q->lineType() == XYCurve::LineType::Line) {
2488             // point is not in the near of the point, but it can be in the near of the connection line of two points
2489             if (pointLiesNearLine(curvePosPrevScene, curvePosScene, mouseScenePos, maxDist))
2490                 return true;
2491         } else if (q->lineType() == XYCurve::LineType::StartHorizontal) {
2492             QPointF tempPoint = curvePosPrevScene;
2493             tempPoint.setX(curvePosScene.x());
2494             if (pointLiesNearLine(curvePosPrevScene, tempPoint, mouseScenePos, maxDist))
2495                 return true;
2496             if (pointLiesNearLine(tempPoint, curvePosScene, mouseScenePos, maxDist))
2497                 return true;
2498         } else if (q->lineType() == XYCurve::LineType::StartVertical) {
2499             QPointF tempPoint = curvePosPrevScene;
2500             tempPoint.setY(curvePosScene.y());
2501             if (pointLiesNearLine(curvePosPrevScene, tempPoint, mouseScenePos, maxDist))
2502                 return true;
2503             if (pointLiesNearLine(tempPoint, curvePosScene, mouseScenePos, maxDist))
2504                 return true;
2505         } else if (q->lineType() == XYCurve::LineType::MidpointHorizontal) {
2506             QPointF tempPoint = curvePosPrevScene;
2507             tempPoint.setX(curvePosPrevScene.x() + (curvePosScene.x() - curvePosPrevScene.x()) / 2);
2508             if (pointLiesNearLine(curvePosPrevScene, tempPoint, mouseScenePos, maxDist))
2509                 return true;
2510             QPointF tempPoint2(tempPoint.x(), curvePosScene.y());
2511             if (pointLiesNearLine(tempPoint, tempPoint2, mouseScenePos, maxDist))
2512                 return true;
2513 
2514             if (pointLiesNearLine(tempPoint2, curvePosScene, mouseScenePos, maxDist))
2515                 return true;
2516         } else if (q->lineType() == XYCurve::LineType::MidpointVertical) {
2517             QPointF tempPoint = curvePosPrevScene;
2518             tempPoint.setY(curvePosPrevScene.y() + (curvePosScene.y() - curvePosPrevScene.y()) / 2);
2519             if (pointLiesNearLine(curvePosPrevScene, tempPoint, mouseScenePos, maxDist))
2520                 return true;
2521             QPointF tempPoint2(tempPoint.y(), curvePosScene.x());
2522             if (pointLiesNearLine(tempPoint, tempPoint2, mouseScenePos, maxDist))
2523                 return true;
2524 
2525             if (pointLiesNearLine(tempPoint2, curvePosScene, mouseScenePos, maxDist))
2526                 return true;
2527         } else if (q->lineType() == XYCurve::LineType::SplineAkimaNatural || q->lineType() == XYCurve::LineType::SplineCubicNatural
2528                    || q->lineType() == XYCurve::LineType::SplineAkimaPeriodic || q->lineType() == XYCurve::LineType::SplineCubicPeriodic) {
2529             for (int i = 0; i < q->lineInterpolationPointsCount() + 1; i++) {
2530                 QLineF line = m_lines.at(index * (q->lineInterpolationPointsCount() + 1) + i);
2531                 QPointF p1{line.p1()}; // cSystem->mapLogicalToScene(line.p1());
2532                 QPointF p2{line.p2()}; // cSystem->mapLogicalToScene(line.p2());
2533                 if (pointLiesNearLine(p1, p2, mouseScenePos, maxDist))
2534                     return true;
2535             }
2536         } else {
2537             // point is not in the near of the point, but it can be in the near of the connection line of two points
2538             if (pointLiesNearLine(curvePosPrevScene, curvePosScene, mouseScenePos, maxDist))
2539                 return true;
2540         }
2541     }
2542     return false;
2543 }
2544 
2545 void XYCurvePrivate::updateErrorBars() {
2546     errorBarsPath = QPainterPath();
2547     if (xErrorBar->type() == ErrorBar::Type::NoError && yErrorBar->type() == ErrorBar::Type::NoError) {
2548         recalcShapeAndBoundingRect();
2549         return;
2550     }
2551 
2552     QVector<QLineF> elines;
2553     QVector<QPointF> pointsErrorBarAnchorX;
2554     QVector<QPointF> pointsErrorBarAnchorY;
2555     const auto errorBarStyleType = errorBarStyle->type();
2556 
2557     calculateScenePoints();
2558 
2559     for (int i = 0; i < m_logicalPoints.size(); ++i) {
2560         if (!m_pointVisible.at(i))
2561             continue;
2562 
2563         const QPointF& point{m_logicalPoints.at(i)};
2564         const int index{validPointsIndicesLogical.at(i)};
2565         double errorPlus, errorMinus;
2566 
2567         // error bars for x
2568         const auto xErrorType = xErrorBar->type();
2569         const auto* xErrorPlusColumn = xErrorBar->plusColumn();
2570         const auto* xErrorMinusColumn = xErrorBar->minusColumn();
2571         if (xErrorType != ErrorBar::Type::NoError) {
2572             // determine the values for the errors
2573             if (xErrorPlusColumn && xErrorPlusColumn->isValid(index) && !xErrorPlusColumn->isMasked(index))
2574                 errorPlus = xErrorPlusColumn->valueAt(index);
2575             else
2576                 errorPlus = 0;
2577 
2578             if (xErrorType == ErrorBar::Type::Symmetric)
2579                 errorMinus = errorPlus;
2580             else {
2581                 if (xErrorMinusColumn && xErrorMinusColumn->isValid(index) && !xErrorMinusColumn->isMasked(index))
2582                     errorMinus = xErrorMinusColumn->valueAt(index);
2583                 else
2584                     errorMinus = 0;
2585             }
2586 
2587             // draw the error bars
2588             if (errorMinus != 0. || errorPlus != 0.)
2589                 elines.append(QLineF(QPointF(point.x() - errorMinus, point.y()), QPointF(point.x() + errorPlus, point.y())));
2590 
2591             // determine the end points of the errors bars in logical coordinates to draw later the cap
2592             if (errorBarStyleType == ErrorBarStyle::Type::WithEnds) {
2593                 if (errorMinus != 0.)
2594                     pointsErrorBarAnchorX << QPointF(point.x() - errorMinus, point.y());
2595                 if (errorPlus != 0.)
2596                     pointsErrorBarAnchorX << QPointF(point.x() + errorPlus, point.y());
2597             }
2598         }
2599 
2600         // error bars for y
2601         const auto yErrorType = yErrorBar->type();
2602         const auto* yErrorPlusColumn = yErrorBar->plusColumn();
2603         const auto* yErrorMinusColumn = yErrorBar->minusColumn();
2604         if (yErrorType != ErrorBar::Type::NoError) {
2605             // determine the values for the errors
2606             if (yErrorPlusColumn && yErrorPlusColumn->isValid(index) && !yErrorPlusColumn->isMasked(index))
2607                 errorPlus = yErrorPlusColumn->valueAt(index);
2608             else
2609                 errorPlus = 0;
2610 
2611             if (yErrorType == ErrorBar::Type::Symmetric)
2612                 errorMinus = errorPlus;
2613             else {
2614                 if (yErrorMinusColumn && yErrorMinusColumn->isValid(index) && !yErrorMinusColumn->isMasked(index))
2615                     errorMinus = yErrorMinusColumn->valueAt(index);
2616                 else
2617                     errorMinus = 0;
2618             }
2619 
2620             // draw the error bars
2621             if (errorMinus != 0. || errorPlus != 0.)
2622                 elines.append(QLineF(QPointF(point.x(), point.y() + errorPlus), QPointF(point.x(), point.y() - errorMinus)));
2623 
2624             // determine the end points of the errors bars in logical coordinates to draw later the cap
2625             if (errorBarStyleType == ErrorBarStyle::Type::WithEnds) {
2626                 if (errorMinus != 0.)
2627                     pointsErrorBarAnchorY << QPointF(point.x(), point.y() + errorPlus);
2628                 if (errorPlus != 0.)
2629                     pointsErrorBarAnchorY << QPointF(point.x(), point.y() - errorMinus);
2630             }
2631         }
2632     }
2633 
2634     // map the error bars to scene coordinates
2635     elines = q->cSystem->mapLogicalToScene(elines);
2636 
2637     // new painter path for the error bars
2638     for (const auto& line : qAsConst(elines)) {
2639         errorBarsPath.moveTo(line.p1());
2640         errorBarsPath.lineTo(line.p2());
2641     }
2642 
2643     // add caps for x error bars
2644     const auto errorBarsCapSize = errorBarStyle->capSize();
2645     if (!pointsErrorBarAnchorX.isEmpty()) {
2646         pointsErrorBarAnchorX = q->cSystem->mapLogicalToScene(pointsErrorBarAnchorX);
2647         for (const auto& point : qAsConst(pointsErrorBarAnchorX)) {
2648             errorBarsPath.moveTo(QPointF(point.x(), point.y() - errorBarsCapSize / 2.));
2649             errorBarsPath.lineTo(QPointF(point.x(), point.y() + errorBarsCapSize / 2.));
2650         }
2651     }
2652 
2653     // add caps for y error bars
2654     if (!pointsErrorBarAnchorY.isEmpty()) {
2655         pointsErrorBarAnchorY = q->cSystem->mapLogicalToScene(pointsErrorBarAnchorY);
2656         for (const auto& point : qAsConst(pointsErrorBarAnchorY)) {
2657             errorBarsPath.moveTo(QPointF(point.x() - errorBarsCapSize / 2., point.y()));
2658             errorBarsPath.lineTo(QPointF(point.x() + errorBarsCapSize / 2., point.y()));
2659         }
2660     }
2661 
2662     recalcShapeAndBoundingRect();
2663 }
2664 
2665 /*!
2666   recalculates the outer bounds and the shape of the curve.
2667 */
2668 void XYCurvePrivate::recalcShapeAndBoundingRect() {
2669     DEBUG(Q_FUNC_INFO << ", suppressRecalc = " << suppressRecalc);
2670     if (suppressRecalc)
2671         return;
2672 
2673 #if PERFTRACE_CURVES
2674     PERFTRACE(QLatin1String(Q_FUNC_INFO) + QLatin1String(", curve ") + name());
2675 #endif
2676 
2677     prepareGeometryChange();
2678     m_shape = QPainterPath();
2679     if (lineType != XYCurve::LineType::NoLine)
2680         m_shape.addPath(WorksheetElement::shapeFromPath(linePath, line->pen()));
2681 
2682     if (dropLine->dropLineType() != XYCurve::DropLineType::NoDropLine)
2683         m_shape.addPath(WorksheetElement::shapeFromPath(dropLinePath, dropLine->pen()));
2684 
2685     if (symbol->style() != Symbol::Style::NoSymbols)
2686         m_shape.addPath(symbolsPath);
2687 
2688     m_shape.addPath(rugPath);
2689 
2690     if (valuesType != XYCurve::ValuesType::NoValues)
2691         m_shape.addPath(valuesPath);
2692 
2693     if (xErrorBar->type() != ErrorBar::Type::NoError || yErrorBar->type() != ErrorBar::Type::NoError)
2694         m_shape.addPath(WorksheetElement::shapeFromPath(errorBarsPath, errorBarStyle->line()->pen()));
2695 
2696     m_boundingRectangle = m_shape.boundingRect();
2697 
2698     for (const auto& pol : qAsConst(m_fillPolygons))
2699         m_boundingRectangle = m_boundingRectangle.united(pol.boundingRect());
2700 
2701     // TODO: when the selection is painted, line intersections are visible.
2702     // simplified() removes those artifacts but is horrible slow for curves with large number of points.
2703     // search for an alternative.
2704     // m_shape = m_shape.simplified();
2705 
2706     updatePixmap();
2707 }
2708 
2709 void XYCurvePrivate::draw(QPainter* painter) {
2710 #if PERFTRACE_CURVES
2711     PERFTRACE(QLatin1String(Q_FUNC_INFO) + QLatin1String(", curve ") + name());
2712 #endif
2713 
2714     // draw filling
2715     if (background->position() != Background::Position::No) {
2716         painter->setOpacity(background->opacity());
2717         painter->setPen(Qt::SolidLine);
2718         for (const auto& polygon : qAsConst(m_fillPolygons))
2719             drawFillingPollygon(polygon, painter, background);
2720     }
2721 
2722     // draw lines
2723     if (lineType != XYCurve::LineType::NoLine) {
2724         painter->setOpacity(line->opacity());
2725         painter->setPen(line->pen());
2726         painter->setBrush(Qt::NoBrush);
2727         if (line->pen().style() == Qt::SolidLine && !q->isPrinting()) {
2728             // Much fast than drawPath but has problems
2729             // with different styles
2730             // When exporting to svg or pdf, this creates for every line
2731             // it's own path in the saved file which is not desired. We
2732             // would like to have one complete path for a curve not many paths
2733             for (auto& line : m_lines)
2734                 painter->drawLine(line);
2735         } else {
2736             painter->drawPath(linePath);
2737         }
2738     }
2739 
2740     // draw drop lines
2741     if (dropLine->dropLineType() != XYCurve::DropLineType::NoDropLine) {
2742         painter->setOpacity(dropLine->opacity());
2743         painter->setPen(dropLine->pen());
2744         painter->setBrush(Qt::NoBrush);
2745         painter->drawPath(dropLinePath);
2746     }
2747 
2748     // draw error bars
2749     if ((xErrorBar->type() != ErrorBar::Type::NoError) || (yErrorBar->type() != ErrorBar::Type::NoError)) {
2750         painter->setOpacity(errorBarStyle->line()->opacity());
2751         painter->setPen(errorBarStyle->line()->pen());
2752         painter->setBrush(Qt::NoBrush);
2753         painter->drawPath(errorBarsPath);
2754     }
2755 
2756     // draw symbols
2757     if (symbol->style() != Symbol::Style::NoSymbols) {
2758         calculateScenePoints();
2759         symbol->draw(painter, m_scenePoints);
2760     }
2761 
2762     // draw values
2763     if (valuesType != XYCurve::ValuesType::NoValues) {
2764         painter->setOpacity(valuesOpacity);
2765         painter->setPen(QPen(valuesColor));
2766         painter->setFont(valuesFont);
2767         drawValues(painter);
2768     }
2769 
2770     // draw rug
2771     if (rugEnabled) {
2772         QPen pen;
2773         pen.setColor(symbol->brush().color());
2774         pen.setWidthF(rugWidth);
2775         painter->setPen(pen);
2776         painter->setOpacity(symbol->opacity());
2777         painter->drawPath(rugPath);
2778     }
2779 }
2780 
2781 void XYCurvePrivate::updatePixmap() {
2782     DEBUG(Q_FUNC_INFO << ", suppressRecalc = " << suppressRecalc);
2783     if (suppressRecalc)
2784         return;
2785 
2786     m_hoverEffectImageIsDirty = true;
2787     m_selectionEffectImageIsDirty = true;
2788     if (m_boundingRectangle.width() == 0 || m_boundingRectangle.height() == 0) {
2789         DEBUG(Q_FUNC_INFO << ", boundingRectangle.width() or boundingRectangle.height() == 0");
2790         m_pixmap = QPixmap();
2791         return;
2792     }
2793     QPixmap pixmap(ceil(m_boundingRectangle.width()), ceil(m_boundingRectangle.height()));
2794     pixmap.fill(Qt::transparent);
2795     QPainter painter(&pixmap);
2796     painter.setRenderHint(QPainter::Antialiasing, true);
2797     painter.translate(-m_boundingRectangle.topLeft());
2798 
2799     draw(&painter);
2800     painter.end();
2801     m_pixmap = pixmap;
2802 
2803     update();
2804     Q_EMIT q->changed();
2805 }
2806 
2807 QVariant XYCurvePrivate::itemChange(GraphicsItemChange change, const QVariant& value) {
2808     // signalize, that the curve was selected. Will be used to create a new InfoElement (Marker)
2809     if (change == QGraphicsItem::ItemSelectedChange) {
2810         if (value.toBool() && q->cSystem && q->cSystem->isValid()) {
2811             Q_EMIT q->selected(q->cSystem->mapSceneToLogical(mousePos).x());
2812         }
2813     }
2814     return QGraphicsItem::itemChange(change, value);
2815 }
2816 
2817 /*!
2818   Reimplementation of QGraphicsItem::paint(). This function does the actual painting of the curve.
2819   \sa QGraphicsItem::paint().
2820 */
2821 void XYCurvePrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget*) {
2822     if (!isVisible())
2823         return;
2824 
2825     painter->setPen(Qt::NoPen);
2826     painter->setBrush(Qt::NoBrush);
2827     painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
2828 
2829     if (!q->isPrinting() && Settings::group(QStringLiteral("Settings_Worksheet")).readEntry<bool>("DoubleBuffering", true))
2830         painter->drawPixmap(m_boundingRectangle.topLeft(), m_pixmap); // draw the cached pixmap (fast)
2831     else
2832         draw(painter); // draw directly again (slow)
2833 
2834     if (isHovered() && !isSelected() && !q->isPrinting()) {
2835         if (m_hoverEffectImageIsDirty) {
2836             QPixmap pix = m_pixmap;
2837             QPainter p(&pix);
2838             p.setCompositionMode(QPainter::CompositionMode_SourceIn); // source (shadow) pixels merged with the alpha channel of the destination (m_pixmap)
2839             p.fillRect(pix.rect(), QApplication::palette().color(QPalette::Shadow));
2840             p.end();
2841 
2842             m_hoverEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5);
2843             m_hoverEffectImageIsDirty = false;
2844         }
2845 
2846         painter->drawImage(m_boundingRectangle.topLeft(), m_hoverEffectImage, m_pixmap.rect());
2847         return;
2848     }
2849 
2850     if (isSelected() && !q->isPrinting()) {
2851         if (m_selectionEffectImageIsDirty) {
2852             QPixmap pix = m_pixmap;
2853             QPainter p(&pix);
2854             p.setCompositionMode(QPainter::CompositionMode_SourceIn);
2855             p.fillRect(pix.rect(), QApplication::palette().color(QPalette::Highlight));
2856             p.end();
2857 
2858             m_selectionEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5);
2859             m_selectionEffectImageIsDirty = false;
2860         }
2861 
2862         painter->drawImage(m_boundingRectangle.topLeft(), m_selectionEffectImage, m_pixmap.rect());
2863     }
2864 }
2865 
2866 void XYCurvePrivate::drawValues(QPainter* painter) {
2867     // QDEBUG(Q_FUNC_INFO << ", value strings = " << m_valueStrings)
2868     int i = 0;
2869     for (const auto& point : qAsConst(m_valuePoints)) {
2870         painter->translate(point);
2871         if (valuesRotationAngle != 0.)
2872             painter->rotate(-valuesRotationAngle);
2873 
2874         painter->drawText(QPoint(0, 0), m_valueStrings.at(i++));
2875 
2876         if (valuesRotationAngle != 0.)
2877             painter->rotate(valuesRotationAngle);
2878         painter->translate(-point);
2879     }
2880 }
2881 
2882 /*!
2883  * checks if the mousePress event was done near the histogram shape
2884  * and selects the graphics item if it is the case.
2885  * \p event
2886  */
2887 void XYCurvePrivate::mousePressEvent(QGraphicsSceneMouseEvent* event) {
2888     if (plot()->mouseMode() != CartesianPlot::MouseMode::Selection) {
2889         event->ignore();
2890         return QGraphicsItem::mousePressEvent(event);
2891     }
2892     mousePos = event->pos();
2893 
2894     if (q->activatePlot(event->pos())) {
2895         setSelected(true);
2896         return;
2897     }
2898 
2899     event->ignore();
2900     setSelected(false);
2901     QGraphicsItem::mousePressEvent(event);
2902 }
2903 
2904 // ##############################################################################
2905 // ##################  Serialization/Deserialization  ###########################
2906 // ##############################################################################
2907 //! Save as XML
2908 void XYCurve::save(QXmlStreamWriter* writer) const {
2909     Q_D(const XYCurve);
2910 
2911     writer->writeStartElement(QStringLiteral("xyCurve"));
2912     writeBasicAttributes(writer);
2913     writeCommentElement(writer);
2914 
2915     // general
2916     writer->writeStartElement(QStringLiteral("general"));
2917 
2918     // if the data columns are valid, write their current paths.
2919     // if not, write the last used paths so the columns can be restored later
2920     // when the columns with the same path are added again to the project
2921     if (d->xColumn)
2922         writer->writeAttribute(QStringLiteral("xColumn"), d->xColumn->path());
2923     else
2924         writer->writeAttribute(QStringLiteral("xColumn"), d->xColumnPath);
2925 
2926     if (d->yColumn)
2927         writer->writeAttribute(QStringLiteral("yColumn"), d->yColumn->path());
2928     else
2929         writer->writeAttribute(QStringLiteral("yColumn"), d->yColumnPath);
2930 
2931     writer->writeAttribute(QStringLiteral("plotRangeIndex"), QString::number(m_cSystemIndex));
2932     writer->writeAttribute(QStringLiteral("legendVisible"), QString::number(d->legendVisible));
2933     writer->writeAttribute(QStringLiteral("visible"), QString::number(d->isVisible()));
2934     writer->writeEndElement();
2935 
2936     // Line
2937     writer->writeStartElement(QStringLiteral("lines"));
2938     writer->writeAttribute(QStringLiteral("type"), QString::number(static_cast<int>(d->lineType)));
2939     writer->writeAttribute(QStringLiteral("skipGaps"), QString::number(d->lineSkipGaps));
2940     writer->writeAttribute(QStringLiteral("increasingXOnly"), QString::number(d->lineIncreasingXOnly));
2941     writer->writeAttribute(QStringLiteral("interpolationPointsCount"), QString::number(d->lineInterpolationPointsCount));
2942     d->line->save(writer);
2943     writer->writeEndElement();
2944 
2945     // Drop lines
2946     d->dropLine->save(writer);
2947 
2948     // Symbols
2949     d->symbol->save(writer);
2950 
2951     // Values
2952     writer->writeStartElement(QStringLiteral("values"));
2953     writer->writeAttribute(QStringLiteral("type"), QString::number(static_cast<int>(d->valuesType)));
2954     writer->writeAttribute(QStringLiteral("valuesColumn"), d->valuesColumnPath);
2955     writer->writeAttribute(QStringLiteral("position"), QString::number(static_cast<int>(d->valuesPosition)));
2956     writer->writeAttribute(QStringLiteral("distance"), QString::number(d->valuesDistance));
2957     writer->writeAttribute(QStringLiteral("rotation"), QString::number(d->valuesRotationAngle));
2958     writer->writeAttribute(QStringLiteral("opacity"), QString::number(d->valuesOpacity));
2959     writer->writeAttribute(QStringLiteral("numericFormat"), QChar::fromLatin1(d->valuesNumericFormat));
2960     writer->writeAttribute(QStringLiteral("dateTimeFormat"), d->valuesDateTimeFormat);
2961     writer->writeAttribute(QStringLiteral("precision"), QString::number(d->valuesPrecision));
2962     writer->writeAttribute(QStringLiteral("prefix"), d->valuesPrefix);
2963     writer->writeAttribute(QStringLiteral("suffix"), d->valuesSuffix);
2964     WRITE_QCOLOR(d->valuesColor);
2965     WRITE_QFONT(d->valuesFont);
2966     writer->writeEndElement();
2967 
2968     // Filling
2969     d->background->save(writer);
2970 
2971     // Error bars
2972     writer->writeStartElement(QStringLiteral("errorBars"));
2973     d->xErrorBar->save(writer);
2974     d->yErrorBar->save(writer);
2975     d->errorBarStyle->save(writer);
2976     writer->writeEndElement();
2977 
2978     // margin plots
2979     writer->writeStartElement(QStringLiteral("margins"));
2980     writer->writeAttribute(QStringLiteral("rugEnabled"), QString::number(d->rugEnabled));
2981     writer->writeAttribute(QStringLiteral("rugOrientation"), QString::number(static_cast<int>(d->rugOrientation)));
2982     writer->writeAttribute(QStringLiteral("rugLength"), QString::number(d->rugLength));
2983     writer->writeAttribute(QStringLiteral("rugWidth"), QString::number(d->rugWidth));
2984     writer->writeAttribute(QStringLiteral("rugOffset"), QString::number(d->rugOffset));
2985     writer->writeEndElement();
2986 
2987     writer->writeEndElement(); // close "xyCurve" section
2988 }
2989 
2990 //! Load from XML
2991 bool XYCurve::load(XmlStreamReader* reader, bool preview) {
2992     Q_D(XYCurve);
2993 
2994     if (!readBasicAttributes(reader))
2995         return false;
2996 
2997     QXmlStreamAttributes attribs;
2998     QString str;
2999 
3000     while (!reader->atEnd()) {
3001         reader->readNext();
3002         if (reader->isEndElement() && reader->name() == QLatin1String("xyCurve"))
3003             break;
3004 
3005         if (!reader->isStartElement())
3006             continue;
3007 
3008         if (reader->name() == QLatin1String("comment")) {
3009             if (!readCommentElement(reader))
3010                 return false;
3011         } else if (reader->name() == QLatin1String("general")) {
3012             attribs = reader->attributes();
3013             READ_COLUMN(xColumn);
3014             READ_COLUMN(yColumn);
3015             READ_INT_VALUE("legendVisible", legendVisible, bool);
3016 
3017             str = attribs.value(QStringLiteral("visible")).toString();
3018             if (str.isEmpty())
3019                 reader->raiseMissingAttributeWarning(QStringLiteral("visible"));
3020             else
3021                 d->setVisible(str.toInt());
3022             READ_INT_VALUE_DIRECT("plotRangeIndex", m_cSystemIndex, int);
3023         } else if (!preview && reader->name() == QLatin1String("lines")) {
3024             attribs = reader->attributes();
3025 
3026             READ_INT_VALUE("type", lineType, LineType);
3027             READ_INT_VALUE("skipGaps", lineSkipGaps, bool);
3028             READ_INT_VALUE("increasingXOnly", lineIncreasingXOnly, bool);
3029             READ_INT_VALUE("interpolationPointsCount", lineInterpolationPointsCount, int);
3030             d->line->load(reader, preview);
3031         } else if (!preview && reader->name() == QLatin1String("dropLines")) {
3032             d->dropLine->load(reader, preview);
3033         } else if (!preview && reader->name() == QLatin1String("symbols")) {
3034             d->symbol->load(reader, preview);
3035         } else if (!preview && reader->name() == QLatin1String("values")) {
3036             attribs = reader->attributes();
3037 
3038             READ_INT_VALUE("type", valuesType, ValuesType);
3039             READ_COLUMN(valuesColumn);
3040 
3041             READ_INT_VALUE("position", valuesPosition, ValuesPosition);
3042             READ_DOUBLE_VALUE("distance", valuesDistance);
3043             READ_DOUBLE_VALUE("rotation", valuesRotationAngle);
3044             READ_DOUBLE_VALUE("opacity", valuesOpacity);
3045 
3046             str = attribs.value(QStringLiteral("numericFormat")).toString();
3047             if (str.isEmpty())
3048                 reader->raiseMissingAttributeWarning(QStringLiteral("numericFormat"));
3049             else
3050                 d->valuesNumericFormat = *(str.toLatin1().data());
3051 
3052             READ_STRING_VALUE("dateTimeFormat", valuesDateTimeFormat);
3053             READ_INT_VALUE("precision", valuesPrecision, int);
3054 
3055             // don't produce any warning if no prefix or suffix is set (empty string is allowed here in xml)
3056             d->valuesPrefix = attribs.value(QStringLiteral("prefix")).toString();
3057             d->valuesSuffix = attribs.value(QStringLiteral("suffix")).toString();
3058 
3059             READ_QCOLOR(d->valuesColor);
3060             READ_QFONT(d->valuesFont);
3061         } else if (!preview && reader->name() == QLatin1String("filling"))
3062             d->background->load(reader, preview);
3063         else if (reader->name() == QLatin1String("errorBars")) {
3064             d->xErrorBar->load(reader, preview);
3065             d->yErrorBar->load(reader, preview);
3066             d->errorBarStyle->load(reader, preview);
3067         } else if (!preview && reader->name() == QLatin1String("margins")) {
3068             attribs = reader->attributes();
3069 
3070             READ_INT_VALUE("rugEnabled", rugEnabled, bool);
3071             READ_INT_VALUE("rugOrientation", rugOrientation, Orientation);
3072             READ_DOUBLE_VALUE("rugLength", rugLength);
3073             READ_DOUBLE_VALUE("rugWidth", rugWidth);
3074             READ_DOUBLE_VALUE("rugOffset", rugOffset);
3075         } else { // unknown element
3076             reader->raiseUnknownElementWarning();
3077             if (!reader->skipToEndElement())
3078                 return false;
3079         }
3080     }
3081 
3082     return true;
3083 }
3084 
3085 // ##############################################################################
3086 // #########################  Theme management ##################################
3087 // ##############################################################################
3088 void XYCurve::loadThemeConfig(const KConfig& config) {
3089     KConfigGroup group = config.group(QStringLiteral("XYCurve"));
3090 
3091     const auto* plot = dynamic_cast<const CartesianPlot*>(parentAspect());
3092     if (!plot)
3093         return;
3094     const int index = plot->curveChildIndex(this);
3095     const QColor themeColor = plot->themeColorPalette(index);
3096 
3097     Q_D(XYCurve);
3098     d->suppressRecalc = true;
3099 
3100     d->line->loadThemeConfig(group, themeColor);
3101     d->dropLine->loadThemeConfig(group, themeColor);
3102     d->symbol->loadThemeConfig(group, themeColor);
3103     d->background->loadThemeConfig(group);
3104     d->errorBarStyle->loadThemeConfig(group, themeColor);
3105 
3106     // Values
3107     this->setValuesOpacity(group.readEntry(QStringLiteral("ValuesOpacity"), 1.0));
3108     this->setValuesColor(group.readEntry(QStringLiteral("ValuesColor"), themeColor));
3109 
3110     // margins
3111     if (plot->theme() == QLatin1String("Tufte")) {
3112         if (d->xColumn && d->xColumn->rowCount() < 100) {
3113             setRugEnabled(true);
3114             setRugOrientation(WorksheetElement::Orientation::Both);
3115         }
3116     } else
3117         setRugEnabled(false);
3118 
3119     d->suppressRecalc = false;
3120     d->recalcShapeAndBoundingRect();
3121 }
3122 
3123 void XYCurve::saveThemeConfig(const KConfig& config) {
3124     KConfigGroup group = config.group(QStringLiteral("XYCurve"));
3125     Q_D(const XYCurve);
3126 
3127     d->line->saveThemeConfig(group);
3128     d->dropLine->saveThemeConfig(group);
3129     d->background->saveThemeConfig(group);
3130     d->symbol->saveThemeConfig(group);
3131     d->errorBarStyle->saveThemeConfig(group);
3132 
3133     // Values
3134     group.writeEntry(QStringLiteral("ValuesOpacity"), this->valuesOpacity());
3135     group.writeEntry(QStringLiteral("ValuesColor"), (QColor)this->valuesColor());
3136     group.writeEntry(QStringLiteral("ValuesFont"), this->valuesFont());
3137 
3138     const int index = parentAspect()->indexOfChild<XYCurve>(this);
3139     if (index < 5) {
3140         KConfigGroup themeGroup = config.group(QStringLiteral("Theme"));
3141         for (int i = index; i < 5; i++) {
3142             QString s = QStringLiteral("ThemePaletteColor") + QString::number(i + 1);
3143             themeGroup.writeEntry(s, (QColor)d->line->pen().color());
3144         }
3145     }
3146 }