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

0001 /*
0002     File                 : Axis.cpp
0003     Project              : LabPlot
0004     Description          : Axis for cartesian coordinate systems.
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2011-2018 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 #include "AxisPrivate.h"
0012 #include "backend/core/AbstractColumn.h"
0013 #include "backend/core/Project.h"
0014 #include "backend/core/Time.h"
0015 #include "backend/core/column/Column.h"
0016 #include "backend/lib/XmlStreamReader.h"
0017 #include "backend/lib/commandtemplates.h"
0018 #include "backend/lib/macros.h"
0019 #include "backend/lib/trace.h"
0020 #include "backend/worksheet/Line.h"
0021 #include "backend/worksheet/TextLabel.h"
0022 #include "backend/worksheet/Worksheet.h"
0023 #include "kdefrontend/GuiTools.h"
0024 
0025 #include "backend/nsl/nsl_math.h"
0026 #include "backend/nsl/nsl_sf_basic.h"
0027 #include <gsl/gsl_math.h>
0028 
0029 #include <KConfig>
0030 #include <KConfigGroup>
0031 #include <KLocalizedString>
0032 
0033 #include <QActionGroup>
0034 #include <QGraphicsSceneMouseEvent>
0035 #include <QMenu>
0036 #include <QPainter>
0037 #include <QTextDocument>
0038 #include <QtMath>
0039 
0040 using Dimension = CartesianCoordinateSystem::Dimension;
0041 
0042 namespace {
0043 constexpr int maxNumberMajorTicks = 100;
0044 constexpr int _maxNumberMajorTicksCustomColumn = 21; // Use one more because one will be subtracted below
0045 constexpr int hoverSelectionEffectPenWidth = 2;
0046 } // Anounymous namespace
0047 
0048 /**
0049  * \class AxisGrid
0050  * \brief Helper class to get the axis grid drawn with the z-Value=0.
0051  *
0052  * The painting of the grid lines is separated from the painting of the axis itself.
0053  * This allows to use a different z-values for the grid lines (z=0, drawn below all other objects )
0054  * and for the axis (z=FLT_MAX, drawn on top of all other objects)
0055  *
0056  *  \ingroup worksheet
0057  */
0058 class AxisGrid : public QGraphicsItem {
0059 public:
0060     explicit AxisGrid(AxisPrivate* a) {
0061         axis = a;
0062         setFlag(QGraphicsItem::ItemIsSelectable, false);
0063         setFlag(QGraphicsItem::ItemIsFocusable, false);
0064         setAcceptHoverEvents(false);
0065     }
0066 
0067     QRectF boundingRect() const override {
0068         QPainterPath gridShape;
0069         gridShape.addPath(WorksheetElement::shapeFromPath(axis->majorGridPath, axis->majorGridLine->pen()));
0070         gridShape.addPath(WorksheetElement::shapeFromPath(axis->minorGridPath, axis->minorGridLine->pen()));
0071         QRectF boundingRectangle = gridShape.boundingRect();
0072         return boundingRectangle;
0073     }
0074 
0075     void paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget*) override {
0076         if (!axis->isVisible() || axis->linePath.isEmpty())
0077             return;
0078 
0079         // draw major grid
0080         if (axis->majorGridLine->pen().style() != Qt::NoPen) {
0081             painter->setOpacity(axis->majorGridLine->opacity());
0082             painter->setPen(axis->majorGridLine->pen());
0083             painter->setBrush(Qt::NoBrush);
0084             painter->drawPath(axis->majorGridPath);
0085         }
0086 
0087         // draw minor grid
0088         if (axis->minorGridLine->pen().style() != Qt::NoPen) {
0089             painter->setOpacity(axis->minorGridLine->opacity());
0090             painter->setPen(axis->minorGridLine->pen());
0091             painter->setBrush(Qt::NoBrush);
0092             painter->drawPath(axis->minorGridPath);
0093         }
0094     }
0095 
0096 private:
0097     AxisPrivate* axis;
0098 };
0099 
0100 /**
0101  * \class Axis
0102  * \brief Axis for cartesian coordinate systems.
0103  *
0104  *  \ingroup worksheet
0105  */
0106 Axis::Axis(const QString& name, Orientation orientation)
0107     : WorksheetElement(name, new AxisPrivate(this), AspectType::Axis) {
0108     init(orientation);
0109 }
0110 
0111 Axis::Axis(const QString& name, Orientation orientation, AxisPrivate* dd)
0112     : WorksheetElement(name, dd, AspectType::Axis) {
0113     init(orientation);
0114 }
0115 
0116 void Axis::init(Orientation orientation) {
0117     Q_D(Axis);
0118 
0119     KConfig config;
0120     KConfigGroup group = config.group(QStringLiteral("Axis"));
0121 
0122     d->orientation = orientation;
0123     d->rangeType = (Axis::RangeType)group.readEntry(QStringLiteral("RangeType"), static_cast<int>(RangeType::Auto));
0124     d->position = Axis::Position::Centered;
0125     d->offset = group.readEntry(QStringLiteral("PositionOffset"), 0);
0126     d->range = Range<double>(group.readEntry(QStringLiteral("Start"), 0.), group.readEntry("End", 10.)); // not auto ticked if already set to 1 here!
0127     d->range.scale() = (RangeT::Scale)group.readEntry(QStringLiteral("Scale"), static_cast<int>(RangeT::Scale::Linear));
0128     d->majorTicksStartType =
0129         static_cast<Axis::TicksStartType>(group.readEntry(QStringLiteral("MajorTicksStartType"), static_cast<bool>(Axis::TicksStartType::Offset)));
0130     d->majorTickStartOffset = group.readEntry(QStringLiteral("MajorTickStartOffset"), 0.0);
0131     d->majorTickStartValue = group.readEntry(QStringLiteral("MajorTickStartValue"), 0.0);
0132     d->scalingFactor = group.readEntry(QStringLiteral("ScalingFactor"), 1.0);
0133     d->zeroOffset = group.readEntry(QStringLiteral("ZeroOffset"), 0);
0134     d->showScaleOffset = group.readEntry(QStringLiteral("ShowScaleOffset"), true);
0135 
0136     // line
0137     d->line = new Line(QString());
0138     d->line->setHidden(true);
0139     d->line->setCreateXmlElement(false); // line properties are written out together with arrow properties in Axis::save()
0140     addChild(d->line);
0141     d->line->init(group);
0142     connect(d->line, &Line::updatePixmapRequested, [=] {
0143         d->update();
0144     });
0145     connect(d->line, &Line::updateRequested, [=] {
0146         d->recalcShapeAndBoundingRect();
0147     });
0148 
0149     d->arrowType = (Axis::ArrowType)group.readEntry(QStringLiteral("ArrowType"), static_cast<int>(ArrowType::NoArrow));
0150     d->arrowPosition = (Axis::ArrowPosition)group.readEntry(QStringLiteral("ArrowPosition"), static_cast<int>(ArrowPosition::Right));
0151     d->arrowSize = group.readEntry(QStringLiteral("ArrowSize"), Worksheet::convertToSceneUnits(10, Worksheet::Unit::Point));
0152 
0153     // axis title
0154     d->title = new TextLabel(this->name(), TextLabel::Type::AxisTitle);
0155     d->title->setText(this->name());
0156     connect(d->title, &TextLabel::changed, this, &Axis::labelChanged);
0157     addChild(d->title);
0158     d->title->setHidden(true);
0159     d->title->graphicsItem()->setParentItem(d);
0160     d->title->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false);
0161     d->title->graphicsItem()->setFlag(QGraphicsItem::ItemIsFocusable, false);
0162     d->title->graphicsItem()->setAcceptHoverEvents(false);
0163     if (d->orientation == Orientation::Vertical) {
0164         d->title->setRotationAngle(90);
0165         d->titleOffsetX = 0; // distance to the axis tick labels
0166         d->titleOffsetY = 0; // centering the title
0167     } else {
0168         d->titleOffsetX = 0; // centering the title
0169         d->titleOffsetY = 0; // distance to the axis tick labels
0170     }
0171 
0172     d->majorTicksDirection = (Axis::TicksDirection)group.readEntry(QStringLiteral("MajorTicksDirection"), (int)Axis::ticksOut);
0173     d->majorTicksType = (TicksType)group.readEntry(QStringLiteral("MajorTicksType"), static_cast<int>(TicksType::TotalNumber));
0174     d->majorTicksNumber = group.readEntry(QStringLiteral("MajorTicksNumber"), 11);
0175     d->majorTicksSpacing = group.readEntry(QStringLiteral("MajorTicksIncrement"),
0176                                            0.0); // set to 0, so axisdock determines the value to not have to many labels the first time switched to Spacing
0177     d->majorTicksLength = group.readEntry(QStringLiteral("MajorTicksLength"), Worksheet::convertToSceneUnits(6.0, Worksheet::Unit::Point));
0178 
0179     d->majorTicksLine = new Line(QString());
0180     d->majorTicksLine->setHidden(true);
0181     d->majorTicksLine->setPrefix(QStringLiteral("MajorTicks"));
0182     d->majorTicksLine->setCreateXmlElement(false);
0183     addChild(d->majorTicksLine);
0184     d->majorTicksLine->init(group);
0185     connect(d->majorTicksLine, &Line::updatePixmapRequested, [=] {
0186         d->update();
0187     });
0188     connect(d->majorTicksLine, &Line::updateRequested, [=] {
0189         d->recalcShapeAndBoundingRect();
0190     });
0191 
0192     d->minorTicksDirection = (TicksDirection)group.readEntry(QStringLiteral("MinorTicksDirection"), (int)Axis::ticksOut);
0193     d->minorTicksType = (TicksType)group.readEntry(QStringLiteral("MinorTicksType"), static_cast<int>(TicksType::TotalNumber));
0194     d->minorTicksNumber = group.readEntry(QStringLiteral("MinorTicksNumber"), 1);
0195     d->minorTicksIncrement = group.readEntry(QStringLiteral("MinorTicksIncrement"), 0.0); // see MajorTicksIncrement
0196     d->minorTicksLength = group.readEntry(QStringLiteral("MinorTicksLength"), Worksheet::convertToSceneUnits(3.0, Worksheet::Unit::Point));
0197 
0198     d->minorTicksLine = new Line(QString());
0199     d->minorTicksLine->setHidden(true);
0200     d->minorTicksLine->setPrefix(QStringLiteral("MinorTicks"));
0201     d->minorTicksLine->setCreateXmlElement(false);
0202     addChild(d->minorTicksLine);
0203     d->minorTicksLine->init(group);
0204     connect(d->minorTicksLine, &Line::updatePixmapRequested, [=] {
0205         d->update();
0206     });
0207     connect(d->minorTicksLine, &Line::updateRequested, [=] {
0208         d->recalcShapeAndBoundingRect();
0209     });
0210 
0211     // Labels
0212     d->labelsFormat = (LabelsFormat)group.readEntry(QStringLiteral("LabelsFormat"), static_cast<int>(LabelsFormat::Decimal));
0213     d->labelsAutoPrecision = group.readEntry(QStringLiteral("LabelsAutoPrecision"), true);
0214     d->labelsPrecision = group.readEntry(QStringLiteral("LabelsPrecision"), 1);
0215     d->labelsDateTimeFormat = group.readEntry(QStringLiteral("LabelsDateTimeFormat"), QStringLiteral("yyyy-MM-dd hh:mm:ss"));
0216     d->labelsPosition = (LabelsPosition)group.readEntry(QStringLiteral("LabelsPosition"), (int)LabelsPosition::Out);
0217     d->labelsOffset = group.readEntry(QStringLiteral("LabelsOffset"), Worksheet::convertToSceneUnits(5.0, Worksheet::Unit::Point));
0218     d->labelsRotationAngle = group.readEntry(QStringLiteral("LabelsRotation"), 0);
0219     d->labelsTextType = (LabelsTextType)group.readEntry(QStringLiteral("LabelsTextType"), static_cast<int>(LabelsTextType::PositionValues));
0220     d->labelsFont = group.readEntry(QStringLiteral("LabelsFont"), QFont());
0221     d->labelsFont.setPixelSize(Worksheet::convertToSceneUnits(10.0, Worksheet::Unit::Point));
0222     d->labelsColor = group.readEntry(QStringLiteral("LabelsFontColor"), QColor(Qt::black));
0223     d->labelsBackgroundType =
0224         (LabelsBackgroundType)group.readEntry(QStringLiteral("LabelsBackgroundType"), static_cast<int>(LabelsBackgroundType::Transparent));
0225     d->labelsBackgroundColor = group.readEntry(QStringLiteral("LabelsBackgroundColor"), QColor(Qt::white));
0226     d->labelsPrefix = group.readEntry(QStringLiteral("LabelsPrefix"), QStringLiteral(""));
0227     d->labelsSuffix = group.readEntry(QStringLiteral("LabelsSuffix"), QStringLiteral(""));
0228     d->labelsOpacity = group.readEntry(QStringLiteral("LabelsOpacity"), 1.0);
0229 
0230     // major grid
0231     d->majorGridLine = new Line(QString());
0232     d->majorGridLine->setPrefix(QStringLiteral("MajorGrid"));
0233     d->majorGridLine->setHidden(true);
0234     addChild(d->majorGridLine);
0235     d->majorGridLine->init(group);
0236     connect(d->majorGridLine, &Line::updatePixmapRequested, [=] {
0237         d->updateGrid();
0238     });
0239     connect(d->majorGridLine, &Line::updateRequested, [=] {
0240         d->retransformMajorGrid();
0241     });
0242 
0243     // minor grid
0244     d->minorGridLine = new Line(QString());
0245     d->minorGridLine->setPrefix(QStringLiteral("MinorGrid"));
0246     d->minorGridLine->setHidden(true);
0247     addChild(d->minorGridLine);
0248     d->minorGridLine->init(group);
0249     connect(d->minorGridLine, &Line::updatePixmapRequested, [=] {
0250         d->updateGrid();
0251     });
0252     connect(d->minorGridLine, &Line::updateRequested, [=] {
0253         d->retransformMinorGrid();
0254     });
0255 
0256     connect(this, &WorksheetElement::coordinateSystemIndexChanged, [this]() {
0257         Q_D(Axis);
0258         d->retransformRange();
0259     });
0260 }
0261 
0262 /*!
0263  * For the most frequently edited properties, create Actions and ActionGroups for the context menu.
0264  * For some ActionGroups the actual actions are created in \c GuiTool,
0265  */
0266 void Axis::initActions() {
0267     // Orientation
0268     orientationActionGroup = new QActionGroup(this);
0269     orientationActionGroup->setExclusive(true);
0270     connect(orientationActionGroup, &QActionGroup::triggered, this, &Axis::orientationChangedSlot);
0271 
0272     orientationHorizontalAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-axis-horizontal")), i18n("Horizontal"), orientationActionGroup);
0273     orientationHorizontalAction->setCheckable(true);
0274 
0275     orientationVerticalAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-axis-vertical")), i18n("Vertical"), orientationActionGroup);
0276     orientationVerticalAction->setCheckable(true);
0277 
0278     // Line
0279     lineStyleActionGroup = new QActionGroup(this);
0280     lineStyleActionGroup->setExclusive(true);
0281     connect(lineStyleActionGroup, &QActionGroup::triggered, this, &Axis::lineStyleChanged);
0282 
0283     lineColorActionGroup = new QActionGroup(this);
0284     lineColorActionGroup->setExclusive(true);
0285     connect(lineColorActionGroup, &QActionGroup::triggered, this, &Axis::lineColorChanged);
0286 
0287     // Ticks
0288     // TODO
0289 }
0290 
0291 void Axis::initMenus() {
0292     this->initActions();
0293 
0294     // Orientation
0295     orientationMenu = new QMenu(i18n("Orientation"));
0296     orientationMenu->setIcon(QIcon::fromTheme(QStringLiteral("labplot-axis-horizontal")));
0297     orientationMenu->addAction(orientationHorizontalAction);
0298     orientationMenu->addAction(orientationVerticalAction);
0299 
0300     // Line
0301     lineMenu = new QMenu(i18n("Line"));
0302     lineMenu->setIcon(QIcon::fromTheme(QStringLiteral("draw-line")));
0303     lineStyleMenu = new QMenu(i18n("Style"), lineMenu);
0304     lineStyleMenu->setIcon(QIcon::fromTheme(QStringLiteral("object-stroke-style")));
0305     lineMenu->setIcon(QIcon::fromTheme(QStringLiteral("draw-line")));
0306     lineMenu->addMenu(lineStyleMenu);
0307 
0308     lineColorMenu = new QMenu(i18n("Color"), lineMenu);
0309     lineColorMenu->setIcon(QIcon::fromTheme(QStringLiteral("fill-color")));
0310     GuiTools::fillColorMenu(lineColorMenu, lineColorActionGroup);
0311     lineMenu->addMenu(lineColorMenu);
0312 }
0313 
0314 QMenu* Axis::createContextMenu() {
0315     if (!orientationMenu)
0316         initMenus();
0317 
0318     Q_D(const Axis);
0319     QMenu* menu = WorksheetElement::createContextMenu();
0320     QAction* visibilityAction = this->visibilityAction();
0321 
0322     // Orientation
0323     if (d->orientation == Orientation::Horizontal)
0324         orientationHorizontalAction->setChecked(true);
0325     else
0326         orientationVerticalAction->setChecked(true);
0327 
0328     menu->insertMenu(visibilityAction, orientationMenu);
0329 
0330     // Line styles
0331     GuiTools::updatePenStyles(lineStyleMenu, lineStyleActionGroup, d->line->pen().color());
0332     GuiTools::selectPenStyleAction(lineStyleActionGroup, d->line->pen().style());
0333 
0334     GuiTools::selectColorAction(lineColorActionGroup, d->line->pen().color());
0335 
0336     menu->insertMenu(visibilityAction, lineMenu);
0337     menu->insertSeparator(visibilityAction);
0338 
0339     return menu;
0340 }
0341 
0342 /*!
0343     Returns an icon to be used in the project explorer.
0344 */
0345 QIcon Axis::icon() const {
0346     Q_D(const Axis);
0347     QIcon icon;
0348     if (d->orientation == Orientation::Horizontal)
0349         icon = QIcon::fromTheme(QStringLiteral("labplot-axis-horizontal"));
0350     else
0351         icon = QIcon::fromTheme(QStringLiteral("labplot-axis-vertical"));
0352 
0353     return icon;
0354 }
0355 
0356 Axis::~Axis() {
0357     if (orientationMenu) {
0358         delete orientationMenu;
0359         delete lineMenu;
0360     }
0361 
0362     // no need to delete d->title, since it was added with addChild in init();
0363 
0364     // no need to delete the d-pointer here - it inherits from QGraphicsItem
0365     // and is deleted during the cleanup in QGraphicsScene
0366 }
0367 
0368 /*!
0369  * overrides the implementation in WorksheetElement and sets the z-value to the maximal possible,
0370  * axes are drawn on top of all other object in the plot.
0371  */
0372 void Axis::setZValue(qreal) {
0373     Q_D(Axis);
0374     d->setZValue(std::numeric_limits<double>::max());
0375     d->gridItem->setParentItem(d->parentItem());
0376     d->gridItem->setZValue(0);
0377 }
0378 
0379 void Axis::retransform() {
0380     Q_D(Axis);
0381     d->retransform();
0382 }
0383 
0384 void Axis::retransformTickLabelStrings() {
0385     Q_D(Axis);
0386     d->retransformTickLabelStrings();
0387 }
0388 
0389 void Axis::setSuppressRetransform(bool value) {
0390     Q_D(Axis);
0391     d->suppressRetransform = value;
0392 }
0393 
0394 void Axis::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) {
0395     Q_D(Axis);
0396 
0397     double ratio = 0;
0398     if (horizontalRatio > 1.0 || verticalRatio > 1.0)
0399         ratio = std::max(horizontalRatio, verticalRatio);
0400     else
0401         ratio = std::min(horizontalRatio, verticalRatio);
0402 
0403     double width = d->line->width() * ratio;
0404     d->line->setWidth(width);
0405 
0406     d->majorTicksLength *= ratio; // ticks are perpendicular to axis line -> verticalRatio relevant
0407     d->minorTicksLength *= ratio;
0408     d->labelsFont.setPixelSize(d->labelsFont.pixelSize() * ratio); // TODO: take into account rotated labels
0409     d->labelsOffset *= ratio;
0410     d->title->handleResize(horizontalRatio, verticalRatio, pageResize);
0411 }
0412 
0413 /* ============================ getter methods ================= */
0414 BASIC_SHARED_D_READER_IMPL(Axis, Axis::RangeType, rangeType, rangeType)
0415 BASIC_SHARED_D_READER_IMPL(Axis, Axis::Orientation, orientation, orientation)
0416 BASIC_SHARED_D_READER_IMPL(Axis, Axis::Position, position, position)
0417 BASIC_SHARED_D_READER_IMPL(Axis, double, offset, offset)
0418 BASIC_SHARED_D_READER_IMPL(Axis, Range<double>, range, range)
0419 BASIC_SHARED_D_READER_IMPL(Axis, bool, rangeScale, rangeScale)
0420 RangeT::Scale Axis::scale() const {
0421     Q_D(const Axis);
0422     if (d->rangeScale)
0423         return d->range.scale();
0424     return d->scale;
0425 }
0426 BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksStartType, majorTicksStartType, majorTicksStartType)
0427 BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorTickStartOffset, majorTickStartOffset)
0428 BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorTickStartValue, majorTickStartValue)
0429 BASIC_SHARED_D_READER_IMPL(Axis, qreal, scalingFactor, scalingFactor)
0430 BASIC_SHARED_D_READER_IMPL(Axis, qreal, zeroOffset, zeroOffset)
0431 BASIC_SHARED_D_READER_IMPL(Axis, bool, showScaleOffset, showScaleOffset)
0432 BASIC_SHARED_D_READER_IMPL(Axis, double, logicalPosition, logicalPosition)
0433 
0434 // title
0435 BASIC_SHARED_D_READER_IMPL(Axis, TextLabel*, title, title)
0436 BASIC_SHARED_D_READER_IMPL(Axis, qreal, titleOffsetX, titleOffsetX)
0437 BASIC_SHARED_D_READER_IMPL(Axis, qreal, titleOffsetY, titleOffsetY)
0438 
0439 // line
0440 Line* Axis::line() const {
0441     Q_D(const Axis);
0442     return d->line;
0443 }
0444 
0445 BASIC_SHARED_D_READER_IMPL(Axis, Axis::ArrowType, arrowType, arrowType)
0446 BASIC_SHARED_D_READER_IMPL(Axis, Axis::ArrowPosition, arrowPosition, arrowPosition)
0447 BASIC_SHARED_D_READER_IMPL(Axis, qreal, arrowSize, arrowSize)
0448 
0449 BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksDirection, majorTicksDirection, majorTicksDirection)
0450 BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksType, majorTicksType, majorTicksType)
0451 BASIC_SHARED_D_READER_IMPL(Axis, bool, majorTicksAutoNumber, majorTicksAutoNumber)
0452 BASIC_SHARED_D_READER_IMPL(Axis, int, majorTicksNumber, majorTicksNumber)
0453 BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorTicksSpacing, majorTicksSpacing)
0454 BASIC_SHARED_D_READER_IMPL(Axis, const AbstractColumn*, majorTicksColumn, majorTicksColumn)
0455 QString& Axis::majorTicksColumnPath() const {
0456     D(Axis);
0457     return d->majorTicksColumnPath;
0458 }
0459 BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorTicksLength, majorTicksLength)
0460 
0461 Line* Axis::majorTicksLine() const {
0462     Q_D(const Axis);
0463     return d->majorTicksLine;
0464 }
0465 
0466 BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksDirection, minorTicksDirection, minorTicksDirection)
0467 BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksType, minorTicksType, minorTicksType)
0468 BASIC_SHARED_D_READER_IMPL(Axis, bool, minorTicksAutoNumber, minorTicksAutoNumber)
0469 BASIC_SHARED_D_READER_IMPL(Axis, int, minorTicksNumber, minorTicksNumber)
0470 BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorTicksSpacing, minorTicksIncrement)
0471 BASIC_SHARED_D_READER_IMPL(Axis, const AbstractColumn*, minorTicksColumn, minorTicksColumn)
0472 QString& Axis::minorTicksColumnPath() const {
0473     D(Axis);
0474     return d->minorTicksColumnPath;
0475 }
0476 BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorTicksLength, minorTicksLength)
0477 
0478 Line* Axis::minorTicksLine() const {
0479     Q_D(const Axis);
0480     return d->minorTicksLine;
0481 }
0482 
0483 BASIC_SHARED_D_READER_IMPL(Axis, Axis::LabelsFormat, labelsFormat, labelsFormat)
0484 BASIC_SHARED_D_READER_IMPL(Axis, bool, labelsFormatAuto, labelsFormatAuto)
0485 BASIC_SHARED_D_READER_IMPL(Axis, bool, labelsAutoPrecision, labelsAutoPrecision)
0486 BASIC_SHARED_D_READER_IMPL(Axis, int, labelsPrecision, labelsPrecision)
0487 BASIC_SHARED_D_READER_IMPL(Axis, QString, labelsDateTimeFormat, labelsDateTimeFormat)
0488 BASIC_SHARED_D_READER_IMPL(Axis, Axis::LabelsPosition, labelsPosition, labelsPosition)
0489 BASIC_SHARED_D_READER_IMPL(Axis, qreal, labelsOffset, labelsOffset)
0490 BASIC_SHARED_D_READER_IMPL(Axis, qreal, labelsRotationAngle, labelsRotationAngle)
0491 BASIC_SHARED_D_READER_IMPL(Axis, Axis::LabelsTextType, labelsTextType, labelsTextType)
0492 BASIC_SHARED_D_READER_IMPL(Axis, const AbstractColumn*, labelsTextColumn, labelsTextColumn)
0493 QString& Axis::labelsTextColumnPath() const {
0494     D(Axis);
0495     return d->labelsTextColumnPath;
0496 }
0497 QVector<double> Axis::tickLabelValues() const {
0498     D(Axis);
0499     return d->tickLabelValues;
0500 }
0501 QVector<QString> Axis::tickLabelStrings() const {
0502     D(Axis);
0503     return d->tickLabelStrings;
0504 }
0505 BASIC_SHARED_D_READER_IMPL(Axis, QColor, labelsColor, labelsColor)
0506 BASIC_SHARED_D_READER_IMPL(Axis, QFont, labelsFont, labelsFont)
0507 BASIC_SHARED_D_READER_IMPL(Axis, Axis::LabelsBackgroundType, labelsBackgroundType, labelsBackgroundType)
0508 BASIC_SHARED_D_READER_IMPL(Axis, QColor, labelsBackgroundColor, labelsBackgroundColor)
0509 BASIC_SHARED_D_READER_IMPL(Axis, QString, labelsPrefix, labelsPrefix)
0510 BASIC_SHARED_D_READER_IMPL(Axis, QString, labelsSuffix, labelsSuffix)
0511 BASIC_SHARED_D_READER_IMPL(Axis, qreal, labelsOpacity, labelsOpacity)
0512 
0513 int Axis::maxNumberMajorTicksCustomColumn() {
0514     return _maxNumberMajorTicksCustomColumn;
0515 }
0516 
0517 // grid
0518 Line* Axis::majorGridLine() const {
0519     Q_D(const Axis);
0520     return d->majorGridLine;
0521 }
0522 
0523 Line* Axis::minorGridLine() const {
0524     Q_D(const Axis);
0525     return d->minorGridLine;
0526 }
0527 
0528 /* ============================ setter methods and undo commands ================= */
0529 STD_SETTER_CMD_IMPL_F(Axis, SetRangeType, Axis::RangeType, rangeType, retransformRange)
0530 void Axis::setRangeType(const Axis::RangeType rangeType) {
0531     Q_D(Axis);
0532     if (rangeType != d->rangeType)
0533         exec(new AxisSetRangeTypeCmd(d, rangeType, ki18n("%1: set axis range type")));
0534 }
0535 
0536 void Axis::setDefault(bool value) {
0537     Q_D(Axis);
0538     d->isDefault = value;
0539 }
0540 
0541 bool Axis::isDefault() const {
0542     Q_D(const Axis);
0543     return d->isDefault;
0544 }
0545 
0546 bool Axis::isNumeric() const {
0547     Q_D(const Axis);
0548     const int xIndex{cSystem->index(Dimension::X)}, yIndex{cSystem->index(Dimension::Y)};
0549     bool numeric = ((d->orientation == Axis::Orientation::Horizontal && m_plot->xRangeFormat(xIndex) == RangeT::Format::Numeric)
0550                     || (d->orientation == Axis::Orientation::Vertical && m_plot->yRangeFormat(yIndex) == RangeT::Format::Numeric));
0551     return numeric;
0552 }
0553 
0554 STD_SETTER_CMD_IMPL_F_S(Axis, SetOrientation, Axis::Orientation, orientation, retransform)
0555 void Axis::setOrientation(Orientation orientation) {
0556     Q_D(Axis);
0557     if (orientation != d->orientation)
0558         exec(new AxisSetOrientationCmd(d, orientation, ki18n("%1: set axis orientation")));
0559 }
0560 
0561 STD_SETTER_CMD_IMPL_F_S(Axis, SetPosition, Axis::Position, position, retransform)
0562 void Axis::setPosition(Position position) {
0563     Q_D(Axis);
0564     if (position != d->position)
0565         exec(new AxisSetPositionCmd(d, position, ki18n("%1: set axis position")));
0566 }
0567 
0568 STD_SETTER_CMD_IMPL_F(Axis, SetOffset, double, offset, retransform)
0569 void Axis::setOffset(double offset, bool undo) {
0570     Q_D(Axis);
0571     if (offset != d->offset) {
0572         if (undo) {
0573             exec(new AxisSetOffsetCmd(d, offset, ki18n("%1: set axis offset")));
0574         } else {
0575             d->offset = offset;
0576             // don't need to call retransform() afterward
0577             // since the only usage of this call is in CartesianPlot, where retransform is called for all children anyway.
0578         }
0579         Q_EMIT positionChanged(offset);
0580     }
0581 }
0582 
0583 STD_SETTER_CMD_IMPL_F_S(Axis, SetRange, Range<double>, range, retransform)
0584 void Axis::setRange(Range<double> range) {
0585     DEBUG(Q_FUNC_INFO << ", range = " << range.toStdString())
0586     Q_D(Axis);
0587     if (range != d->range) {
0588         exec(new AxisSetRangeCmd(d, range, ki18n("%1: set axis range")));
0589         // auto set tick count when changing range (only changed here)
0590         if (d->majorTicksAutoNumber)
0591             setMajorTicksNumber(d->range.autoTickCount(), true);
0592     }
0593 }
0594 void Axis::setStart(double min) {
0595     Q_D(Axis);
0596     Range<double> range = d->range;
0597     const auto scale = range.scale();
0598     if (!(((RangeT::isLogScale(scale)) && min <= 0) || (scale == RangeT::Scale::Sqrt && min < 0))) {
0599         range.setStart(min);
0600         setRange(range);
0601     }
0602     Q_EMIT startChanged(range.start()); // Feedback
0603 }
0604 void Axis::setEnd(double max) {
0605     Q_D(Axis);
0606     Range<double> range = d->range;
0607     const auto scale = range.scale();
0608     if (!((RangeT::isLogScale(scale) && max <= 0) || (scale == RangeT::Scale::Sqrt && max < 0))) {
0609         range.setEnd(max);
0610         setRange(range);
0611     }
0612     Q_EMIT endChanged(range.end()); // Feedback
0613 }
0614 void Axis::setRange(double min, double max) {
0615     Q_D(Axis);
0616     Range<double> range = d->range;
0617     range.setStart(min);
0618     QDEBUG("scale = " << range.scale())
0619     range.setEnd(max);
0620     setRange(range);
0621 }
0622 
0623 STD_SETTER_CMD_IMPL_F_S(Axis, SetRangeScale, bool, rangeScale, retransformTicks)
0624 void Axis::setRangeScale(bool rangeScale) {
0625     Q_D(Axis);
0626     if (rangeScale != d->rangeScale)
0627         exec(new AxisSetRangeScaleCmd(d, rangeScale, ki18n("%1: set range scale")));
0628 }
0629 
0630 STD_SETTER_CMD_IMPL_F_S(Axis, SetScale, RangeT::Scale, scale, retransformTicks)
0631 void Axis::setScale(RangeT::Scale scale) {
0632     Q_D(Axis);
0633     if (scale != d->scale)
0634         exec(new AxisSetScaleCmd(d, scale, ki18n("%1: set scale")));
0635 }
0636 
0637 STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksStartType, Axis::TicksStartType, majorTicksStartType, retransform)
0638 void Axis::setMajorTicksStartType(Axis::TicksStartType type) {
0639     Q_D(Axis);
0640     if (type != d->majorTicksStartType)
0641         exec(new AxisSetMajorTicksStartTypeCmd(d, type, ki18n("%1: set major tick start type")));
0642 }
0643 
0644 STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTickStartOffset, qreal, majorTickStartOffset, retransform)
0645 void Axis::setMajorTickStartOffset(qreal offset) {
0646     Q_D(Axis);
0647     if (offset != d->majorTickStartOffset)
0648         exec(new AxisSetMajorTickStartOffsetCmd(d, offset, ki18n("%1: set major tick start offset")));
0649 }
0650 
0651 STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTickStartValue, qreal, majorTickStartValue, retransform)
0652 void Axis::setMajorTickStartValue(qreal value) {
0653     Q_D(Axis);
0654     // TODO: check if value is invalid
0655     if (value != d->majorTickStartValue)
0656         exec(new AxisSetMajorTickStartValueCmd(d, value, ki18n("%1: set major tick start value")));
0657 }
0658 
0659 STD_SETTER_CMD_IMPL_F_S(Axis, SetScalingFactor, qreal, scalingFactor, retransform)
0660 void Axis::setScalingFactor(qreal scalingFactor) {
0661     Q_D(Axis);
0662     // TODO: check negative values and log-scales?
0663     if (scalingFactor == 0) {
0664         Q_EMIT scalingFactorChanged(d->scalingFactor); // return current scalingfactor as feedback for the spinbox
0665         return;
0666     }
0667     if (scalingFactor != d->scalingFactor)
0668         exec(new AxisSetScalingFactorCmd(d, scalingFactor, ki18n("%1: set axis scaling factor")));
0669 }
0670 
0671 STD_SETTER_CMD_IMPL_F_S(Axis, SetZeroOffset, qreal, zeroOffset, retransform)
0672 void Axis::setZeroOffset(qreal zeroOffset) {
0673     Q_D(Axis);
0674 
0675     // TODO: check for negative values and log scales?
0676     if (zeroOffset != d->zeroOffset)
0677         exec(new AxisSetZeroOffsetCmd(d, zeroOffset, ki18n("%1: set axis zero offset")));
0678 }
0679 STD_SETTER_CMD_IMPL_F_S(Axis, ShowScaleOffset, bool, showScaleOffset, retransform)
0680 void Axis::setShowScaleOffset(bool b) {
0681     Q_D(Axis);
0682     if (b != d->showScaleOffset)
0683         exec(new AxisShowScaleOffsetCmd(d, b, ki18n("%1: show scale and offset")));
0684 }
0685 
0686 STD_SETTER_CMD_IMPL_F_S(Axis, SetLogicalPosition, double, logicalPosition, retransform)
0687 void Axis::setLogicalPosition(double pos) {
0688     Q_D(Axis);
0689     if (pos != d->logicalPosition)
0690         exec(new AxisSetLogicalPositionCmd(d, pos, ki18n("%1: set axis logical position")));
0691 }
0692 
0693 // Title
0694 STD_SETTER_CMD_IMPL_F_S(Axis, SetTitleOffsetX, qreal, titleOffsetX, retransform)
0695 void Axis::setTitleOffsetX(qreal offset) {
0696     Q_D(Axis);
0697     if (offset != d->titleOffsetX)
0698         exec(new AxisSetTitleOffsetXCmd(d, offset, ki18n("%1: set title offset")));
0699 }
0700 
0701 STD_SETTER_CMD_IMPL_F_S(Axis, SetTitleOffsetY, qreal, titleOffsetY, retransform)
0702 void Axis::setTitleOffsetY(qreal offset) {
0703     Q_D(Axis);
0704     if (offset != d->titleOffsetY)
0705         exec(new AxisSetTitleOffsetYCmd(d, offset, ki18n("%1: set title offset")));
0706 }
0707 
0708 // Line
0709 STD_SETTER_CMD_IMPL_F_S(Axis, SetArrowType, Axis::ArrowType, arrowType, retransformArrow)
0710 void Axis::setArrowType(ArrowType type) {
0711     Q_D(Axis);
0712     if (type != d->arrowType)
0713         exec(new AxisSetArrowTypeCmd(d, type, ki18n("%1: set arrow type")));
0714 }
0715 
0716 STD_SETTER_CMD_IMPL_F_S(Axis, SetArrowPosition, Axis::ArrowPosition, arrowPosition, retransformArrow)
0717 void Axis::setArrowPosition(ArrowPosition position) {
0718     Q_D(Axis);
0719     if (position != d->arrowPosition)
0720         exec(new AxisSetArrowPositionCmd(d, position, ki18n("%1: set arrow position")));
0721 }
0722 
0723 STD_SETTER_CMD_IMPL_F_S(Axis, SetArrowSize, qreal, arrowSize, retransformArrow)
0724 void Axis::setArrowSize(qreal arrowSize) {
0725     Q_D(Axis);
0726     if (arrowSize != d->arrowSize)
0727         exec(new AxisSetArrowSizeCmd(d, arrowSize, ki18n("%1: set arrow size")));
0728 }
0729 
0730 // Major ticks
0731 STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksDirection, Axis::TicksDirection, majorTicksDirection, retransformTicks)
0732 void Axis::setMajorTicksDirection(TicksDirection majorTicksDirection) {
0733     Q_D(Axis);
0734     if (majorTicksDirection != d->majorTicksDirection)
0735         exec(new AxisSetMajorTicksDirectionCmd(d, majorTicksDirection, ki18n("%1: set major ticks direction")));
0736 }
0737 
0738 STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksType, Axis::TicksType, majorTicksType, retransformTicks)
0739 void Axis::setMajorTicksType(TicksType majorTicksType) {
0740     Q_D(Axis);
0741     if (majorTicksType != d->majorTicksType)
0742         exec(new AxisSetMajorTicksTypeCmd(d, majorTicksType, ki18n("%1: set major ticks type")));
0743 }
0744 
0745 STD_SETTER_CMD_IMPL_S(Axis, SetMajorTicksNumberNoFinalize, int, majorTicksNumber) // no retransformTicks called
0746 STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksAutoNumber, bool, majorTicksAutoNumber, retransformTicks)
0747 void Axis::setMajorTicksAutoNumber(bool automatic) {
0748     Q_D(Axis);
0749     if (automatic != d->majorTicksAutoNumber) {
0750         auto* parent = new AxisSetMajorTicksAutoNumberCmd(d, automatic, ki18n("%1: enable/disable major automatic tick numbers"));
0751         if (automatic && d->range.autoTickCount() != d->majorTicksNumber)
0752             new AxisSetMajorTicksNumberNoFinalizeCmd(d, d->range.autoTickCount(), ki18n("%1: set the total number of the major ticks"), parent);
0753         exec(parent);
0754     }
0755 }
0756 
0757 STD_SETTER_CMD_IMPL_S(Axis, SetMajorTicksAutoNumberNoFinalize, bool, majorTicksAutoNumber) // no retransformTicks called
0758 STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksNumber, int, majorTicksNumber, retransformTicks)
0759 void Axis::setMajorTicksNumber(int number, bool automatic) {
0760     DEBUG(Q_FUNC_INFO << ", number = " << number)
0761     Q_D(Axis);
0762     if (number > maxNumberMajorTicks) {
0763         // Notifiy the user that the number was invalid
0764         Q_EMIT majorTicksNumberChanged(maxNumberMajorTicks);
0765         return;
0766     } else if (number != d->majorTicksNumber) {
0767         auto* parent = new AxisSetMajorTicksNumberCmd(d, number, ki18n("%1: set the total number of the major ticks"));
0768         if (!automatic)
0769             new AxisSetMajorTicksAutoNumberNoFinalizeCmd(d, false, ki18n("%1: disable major automatic tick numbers"), parent);
0770         exec(parent);
0771     }
0772 }
0773 
0774 STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksSpacing, qreal, majorTicksSpacing, retransformTicks)
0775 void Axis::setMajorTicksSpacing(qreal majorTicksSpacing) {
0776     double range = this->range().length();
0777     DEBUG(Q_FUNC_INFO << ", major spacing = " << majorTicksSpacing << ", range = " << range)
0778     // fix spacing if incorrect (not set or > 100 ticks)
0779     if (majorTicksSpacing == 0. || range / majorTicksSpacing > maxNumberMajorTicks) {
0780         if (majorTicksSpacing == 0.)
0781             majorTicksSpacing = range / (majorTicksNumber() - 1);
0782 
0783         if (range / majorTicksSpacing > maxNumberMajorTicks)
0784             majorTicksSpacing = range / maxNumberMajorTicks;
0785 
0786         Q_EMIT majorTicksSpacingChanged(majorTicksSpacing);
0787         return;
0788     }
0789 
0790     Q_D(Axis);
0791     if (majorTicksSpacing != d->majorTicksSpacing)
0792         exec(new AxisSetMajorTicksSpacingCmd(d, majorTicksSpacing, ki18n("%1: set the spacing of the major ticks")));
0793 }
0794 
0795 STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksColumn, const AbstractColumn*, majorTicksColumn, retransformTicks)
0796 void Axis::setMajorTicksColumn(const AbstractColumn* column) {
0797     Q_D(Axis);
0798     if (column != d->majorTicksColumn) {
0799         exec(new AxisSetMajorTicksColumnCmd(d, column, ki18n("%1: assign major ticks' values")));
0800 
0801         if (column) {
0802             connect(column, &AbstractColumn::dataChanged, this, &Axis::retransformTicks);
0803             connect(column->parentAspect(), &AbstractAspect::childAspectAboutToBeRemoved, this, &Axis::majorTicksColumnAboutToBeRemoved);
0804             // TODO: add disconnect in the undo-function
0805         }
0806     }
0807 }
0808 
0809 STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksLength, qreal, majorTicksLength, retransformTicks)
0810 void Axis::setMajorTicksLength(qreal majorTicksLength) {
0811     Q_D(Axis);
0812     if (majorTicksLength != d->majorTicksLength)
0813         exec(new AxisSetMajorTicksLengthCmd(d, majorTicksLength, ki18n("%1: set major ticks length")));
0814 }
0815 
0816 // Minor ticks
0817 STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksDirection, Axis::TicksDirection, minorTicksDirection, retransformTicks)
0818 void Axis::setMinorTicksDirection(TicksDirection minorTicksDirection) {
0819     Q_D(Axis);
0820     if (minorTicksDirection != d->minorTicksDirection)
0821         exec(new AxisSetMinorTicksDirectionCmd(d, minorTicksDirection, ki18n("%1: set minor ticks direction")));
0822 }
0823 
0824 STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksType, Axis::TicksType, minorTicksType, retransformTicks)
0825 void Axis::setMinorTicksType(TicksType minorTicksType) {
0826     Q_D(Axis);
0827     if (minorTicksType != d->minorTicksType)
0828         exec(new AxisSetMinorTicksTypeCmd(d, minorTicksType, ki18n("%1: set minor ticks type")));
0829 }
0830 
0831 STD_SETTER_CMD_IMPL_S(Axis, SetMinorTicksNumberNoFinalize, int, minorTicksNumber)
0832 STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksAutoNumber, bool, minorTicksAutoNumber, retransformTicks)
0833 void Axis::setMinorTicksAutoNumber(bool automatic) {
0834     Q_D(Axis);
0835     if (automatic != d->minorTicksAutoNumber) {
0836         auto* parent = new AxisSetMinorTicksAutoNumberCmd(d, automatic, ki18n("%1: enable/disable minor automatic tick numbers"));
0837         // TODO: for automatic it is always 1. Is that ok?
0838         if (automatic && 1 != d->minorTicksNumber)
0839             new AxisSetMinorTicksNumberNoFinalizeCmd(d, 1, ki18n("%1: set the total number of the minor ticks"), parent);
0840         exec(parent);
0841     }
0842 }
0843 
0844 STD_SETTER_CMD_IMPL_S(Axis, SetMinorTicksAutoNumberNoFinalize, bool, minorTicksAutoNumber) // no retransformTicks called
0845 STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksNumber, int, minorTicksNumber, retransformTicks)
0846 void Axis::setMinorTicksNumber(int minorTicksNumber) {
0847     DEBUG(Q_FUNC_INFO << ", number = " << minorTicksNumber)
0848     Q_D(Axis);
0849     if (minorTicksNumber != d->minorTicksNumber) {
0850         auto* parent = new AxisSetMinorTicksNumberCmd(d, minorTicksNumber, ki18n("%1: set the total number of the minor ticks"));
0851         new AxisSetMinorTicksAutoNumberNoFinalizeCmd(d, false, ki18n("%1: disable major automatic tick numbers"), parent);
0852         exec(parent);
0853     }
0854 }
0855 
0856 STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksSpacing, qreal, minorTicksIncrement, retransformTicks)
0857 void Axis::setMinorTicksSpacing(qreal minorTicksSpacing) {
0858     Q_D(Axis);
0859     double range = this->range().length();
0860     int numberTicks = 0;
0861 
0862     int majorTicks = majorTicksNumber();
0863     if (minorTicksSpacing > 0.)
0864         numberTicks = range / (majorTicks - 1) / minorTicksSpacing - 1; // recalc
0865 
0866     // set if unset or > 100.
0867     if (minorTicksSpacing == 0. || numberTicks > 100) {
0868         if (minorTicksSpacing == 0.)
0869             minorTicksSpacing = range / (majorTicks - 1) / (minorTicksNumber() + 1);
0870 
0871         numberTicks = range / (majorTicks - 1) / minorTicksSpacing - 1; // recalculate number of ticks
0872 
0873         if (numberTicks > 100) // maximum 100 minor ticks
0874             minorTicksSpacing = range / (majorTicks - 1) / (100 + 1);
0875 
0876         Q_EMIT minorTicksIncrementChanged(minorTicksSpacing);
0877         return;
0878     }
0879 
0880     if (minorTicksSpacing != d->minorTicksIncrement)
0881         exec(new AxisSetMinorTicksSpacingCmd(d, minorTicksSpacing, ki18n("%1: set the spacing of the minor ticks")));
0882 }
0883 
0884 STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksColumn, const AbstractColumn*, minorTicksColumn, retransformTicks)
0885 void Axis::setMinorTicksColumn(const AbstractColumn* column) {
0886     Q_D(Axis);
0887     if (column != d->minorTicksColumn) {
0888         exec(new AxisSetMinorTicksColumnCmd(d, column, ki18n("%1: assign minor ticks' values")));
0889 
0890         if (column) {
0891             connect(column, &AbstractColumn::dataChanged, this, &Axis::retransformTicks);
0892             connect(column->parentAspect(), &AbstractAspect::childAspectAboutToBeRemoved, this, &Axis::minorTicksColumnAboutToBeRemoved);
0893             // TODO: add disconnect in the undo-function
0894         }
0895     }
0896 }
0897 
0898 STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksLength, qreal, minorTicksLength, retransformTicks)
0899 void Axis::setMinorTicksLength(qreal minorTicksLength) {
0900     Q_D(Axis);
0901     if (minorTicksLength != d->minorTicksLength)
0902         exec(new AxisSetMinorTicksLengthCmd(d, minorTicksLength, ki18n("%1: set minor ticks length")));
0903 }
0904 
0905 // Labels
0906 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsFormat, Axis::LabelsFormat, labelsFormat, retransformTicks)
0907 void Axis::setLabelsFormat(LabelsFormat labelsFormat) {
0908     DEBUG(Q_FUNC_INFO << ", format = " << ENUM_TO_STRING(Axis, LabelsFormat, labelsFormat))
0909     Q_D(Axis);
0910     if (labelsFormat != d->labelsFormat)
0911         exec(new AxisSetLabelsFormatCmd(d, labelsFormat, ki18n("%1: set labels format")));
0912 }
0913 
0914 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsFormatAuto, bool, labelsFormatAuto, retransformTicks)
0915 void Axis::setLabelsFormatAuto(bool automatic) {
0916     Q_D(Axis);
0917     if (automatic != d->labelsFormatAuto)
0918         exec(new AxisSetLabelsFormatAutoCmd(d, automatic, ki18n("%1: set labels format automatic")));
0919 }
0920 
0921 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsAutoPrecision, bool, labelsAutoPrecision, retransformTickLabelStrings)
0922 void Axis::setLabelsAutoPrecision(bool labelsAutoPrecision) {
0923     Q_D(Axis);
0924     if (labelsAutoPrecision != d->labelsAutoPrecision)
0925         exec(new AxisSetLabelsAutoPrecisionCmd(d, labelsAutoPrecision, ki18n("%1: set labels precision")));
0926 }
0927 
0928 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsPrecision, int, labelsPrecision, retransformTickLabelStrings)
0929 void Axis::setLabelsPrecision(int labelsPrecision) {
0930     Q_D(Axis);
0931     if (labelsPrecision != d->labelsPrecision)
0932         exec(new AxisSetLabelsPrecisionCmd(d, labelsPrecision, ki18n("%1: set labels precision")));
0933 }
0934 
0935 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsDateTimeFormat, QString, labelsDateTimeFormat, retransformTickLabelStrings)
0936 void Axis::setLabelsDateTimeFormat(const QString& format) {
0937     Q_D(Axis);
0938     if (format != d->labelsDateTimeFormat)
0939         exec(new AxisSetLabelsDateTimeFormatCmd(d, format, ki18n("%1: set labels datetime format")));
0940 }
0941 
0942 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsPosition, Axis::LabelsPosition, labelsPosition, retransformTickLabelPositions)
0943 void Axis::setLabelsPosition(LabelsPosition labelsPosition) {
0944     Q_D(Axis);
0945     if (labelsPosition != d->labelsPosition)
0946         exec(new AxisSetLabelsPositionCmd(d, labelsPosition, ki18n("%1: set labels position")));
0947 }
0948 
0949 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsOffset, double, labelsOffset, retransformTickLabelPositions)
0950 void Axis::setLabelsOffset(double offset) {
0951     Q_D(Axis);
0952     if (offset != d->labelsOffset)
0953         exec(new AxisSetLabelsOffsetCmd(d, offset, ki18n("%1: set label offset")));
0954 }
0955 
0956 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsRotationAngle, qreal, labelsRotationAngle, retransformTickLabelPositions)
0957 void Axis::setLabelsRotationAngle(qreal angle) {
0958     Q_D(Axis);
0959     if (angle != d->labelsRotationAngle)
0960         exec(new AxisSetLabelsRotationAngleCmd(d, angle, ki18n("%1: set label rotation angle")));
0961 }
0962 
0963 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsTextType, Axis::LabelsTextType, labelsTextType, retransformTicks)
0964 void Axis::setLabelsTextType(LabelsTextType type) {
0965     Q_D(Axis);
0966     if (type != d->labelsTextType)
0967         exec(new AxisSetLabelsTextTypeCmd(d, type, ki18n("%1: set labels text type")));
0968 }
0969 
0970 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsTextColumn, const AbstractColumn*, labelsTextColumn, retransformTicks)
0971 void Axis::setLabelsTextColumn(const AbstractColumn* column) {
0972     Q_D(Axis);
0973     if (column != d->labelsTextColumn) {
0974         exec(new AxisSetLabelsTextColumnCmd(d, column, ki18n("%1: set labels text column")));
0975 
0976         if (column) {
0977             connect(column, &AbstractColumn::dataChanged, this, &Axis::retransformTicks);
0978             connect(column->parentAspect(), &AbstractAspect::childAspectAboutToBeRemoved, this, &Axis::retransformTicks);
0979             // TODO: add disconnect in the undo-function
0980         }
0981     }
0982 }
0983 
0984 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsColor, QColor, labelsColor, update)
0985 void Axis::setLabelsColor(const QColor& color) {
0986     Q_D(Axis);
0987     if (color != d->labelsColor)
0988         exec(new AxisSetLabelsColorCmd(d, color, ki18n("%1: set label color")));
0989 }
0990 
0991 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsFont, QFont, labelsFont, retransformTickLabelStrings)
0992 void Axis::setLabelsFont(const QFont& font) {
0993     Q_D(Axis);
0994     if (font != d->labelsFont)
0995         exec(new AxisSetLabelsFontCmd(d, font, ki18n("%1: set label font")));
0996 }
0997 
0998 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsBackgroundType, Axis::LabelsBackgroundType, labelsBackgroundType, update)
0999 void Axis::setLabelsBackgroundType(LabelsBackgroundType type) {
1000     Q_D(Axis);
1001     if (type != d->labelsBackgroundType)
1002         exec(new AxisSetLabelsBackgroundTypeCmd(d, type, ki18n("%1: set labels background type")));
1003 }
1004 
1005 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsBackgroundColor, QColor, labelsBackgroundColor, update)
1006 void Axis::setLabelsBackgroundColor(const QColor& color) {
1007     Q_D(Axis);
1008     if (color != d->labelsBackgroundColor)
1009         exec(new AxisSetLabelsBackgroundColorCmd(d, color, ki18n("%1: set label background color")));
1010 }
1011 
1012 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsPrefix, QString, labelsPrefix, retransformTickLabelStrings)
1013 void Axis::setLabelsPrefix(const QString& prefix) {
1014     Q_D(Axis);
1015     if (prefix != d->labelsPrefix)
1016         exec(new AxisSetLabelsPrefixCmd(d, prefix, ki18n("%1: set label prefix")));
1017 }
1018 
1019 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsSuffix, QString, labelsSuffix, retransformTickLabelStrings)
1020 void Axis::setLabelsSuffix(const QString& suffix) {
1021     Q_D(Axis);
1022     if (suffix != d->labelsSuffix)
1023         exec(new AxisSetLabelsSuffixCmd(d, suffix, ki18n("%1: set label suffix")));
1024 }
1025 
1026 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsOpacity, qreal, labelsOpacity, update)
1027 void Axis::setLabelsOpacity(qreal opacity) {
1028     Q_D(Axis);
1029     if (opacity != d->labelsOpacity)
1030         exec(new AxisSetLabelsOpacityCmd(d, opacity, ki18n("%1: set labels opacity")));
1031 }
1032 
1033 // ##############################################################################
1034 // ####################################  SLOTs   ################################
1035 // ##############################################################################
1036 void Axis::labelChanged() {
1037     Q_D(Axis);
1038     d->recalcShapeAndBoundingRect();
1039 }
1040 
1041 void Axis::retransformTicks() {
1042     Q_D(Axis);
1043     d->retransformTicks();
1044 }
1045 
1046 void Axis::majorTicksColumnAboutToBeRemoved(const AbstractAspect* aspect) {
1047     Q_D(Axis);
1048     if (aspect == d->majorTicksColumn) {
1049         d->majorTicksColumn = nullptr;
1050         d->retransformTicks();
1051     }
1052 }
1053 
1054 void Axis::minorTicksColumnAboutToBeRemoved(const AbstractAspect* aspect) {
1055     Q_D(Axis);
1056     if (aspect == d->minorTicksColumn) {
1057         d->minorTicksColumn = nullptr;
1058         d->retransformTicks();
1059     }
1060 }
1061 
1062 // ##############################################################################
1063 // ######  SLOTs for changes triggered via QActions in the context menu  ########
1064 // ##############################################################################
1065 void Axis::orientationChangedSlot(QAction* action) {
1066     if (action == orientationHorizontalAction)
1067         this->setOrientation(Axis::Orientation::Horizontal);
1068     else
1069         this->setOrientation(Axis::Orientation::Vertical);
1070 }
1071 
1072 void Axis::lineStyleChanged(QAction* action) {
1073     Q_D(const Axis);
1074     d->line->setStyle(GuiTools::penStyleFromAction(lineStyleActionGroup, action));
1075 }
1076 
1077 void Axis::lineColorChanged(QAction* action) {
1078     Q_D(const Axis);
1079     d->line->setColor(GuiTools::colorFromAction(lineColorActionGroup, action));
1080 }
1081 
1082 // #####################################################################
1083 // ################### Private implementation ##########################
1084 // #####################################################################
1085 AxisPrivate::AxisPrivate(Axis* owner)
1086     : WorksheetElementPrivate(owner)
1087     , gridItem(new AxisGrid(this))
1088     , q(owner) {
1089     setFlag(QGraphicsItem::ItemIsSelectable, true);
1090     setFlag(QGraphicsItem::ItemIsFocusable, true);
1091     setAcceptHoverEvents(true);
1092 }
1093 
1094 bool AxisPrivate::swapVisible(bool on) {
1095     bool oldValue = isVisible();
1096 
1097     // When making a graphics item invisible, it gets deselected in the scene.
1098     // In this case we don't want to deselect the item in the project explorer.
1099     // We need to supress the deselection in the view.
1100     auto* worksheet = static_cast<Worksheet*>(q->parent(AspectType::Worksheet));
1101     if (worksheet) {
1102         worksheet->suppressSelectionChangedEvent(true);
1103         setVisible(on);
1104         gridItem->setVisible(on);
1105         worksheet->suppressSelectionChangedEvent(false);
1106     } else
1107         setVisible(on);
1108 
1109     Q_EMIT q->changed();
1110     Q_EMIT q->visibleChanged(on);
1111     return oldValue;
1112 }
1113 
1114 /*!
1115     recalculates the position of the axis on the worksheet
1116  */
1117 void AxisPrivate::retransform() {
1118     DEBUG(Q_FUNC_INFO)
1119     const bool suppress = suppressRetransform || !plot() || q->isLoading();
1120     trackRetransformCalled(suppress);
1121     if (suppress)
1122         return;
1123 
1124     //  PERFTRACE(name().toLatin1() + ", AxisPrivate::retransform()");
1125     suppressRecalc = true;
1126     retransformLine();
1127     suppressRecalc = false;
1128     recalcShapeAndBoundingRect();
1129 }
1130 
1131 void AxisPrivate::retransformRange() {
1132     switch (rangeType) { // also if not changing (like on plot range changes)
1133     case Axis::RangeType::Auto: {
1134         if (orientation == Axis::Orientation::Horizontal)
1135             range = q->m_plot->range(Dimension::X, q->cSystem->index(Dimension::X));
1136         else
1137             range = q->m_plot->range(Dimension::Y, q->cSystem->index(Dimension::Y));
1138 
1139         DEBUG(Q_FUNC_INFO << ", new auto range = " << range.toStdString())
1140         break;
1141     }
1142     case Axis::RangeType::AutoData:
1143         if (orientation == Axis::Orientation::Horizontal)
1144             range = q->m_plot->dataRange(Dimension::X, q->cSystem->index(Dimension::X));
1145         else
1146             range = q->m_plot->dataRange(Dimension::Y, q->cSystem->index(Dimension::Y));
1147 
1148         DEBUG(Q_FUNC_INFO << ", new auto data range = " << range.toStdString())
1149         break;
1150     case Axis::RangeType::Custom:
1151         return;
1152     }
1153 
1154     retransform();
1155     Q_EMIT q->rangeChanged(range);
1156 }
1157 
1158 void AxisPrivate::retransformLine() {
1159     DEBUG(Q_FUNC_INFO << ", \"" << STDSTRING(title->name()) << "\", coordinate system " << q->m_cSystemIndex + 1)
1160     DEBUG(Q_FUNC_INFO << ", x range is x range " << q->cSystem->index(Dimension::X) + 1)
1161     DEBUG(Q_FUNC_INFO << ", y range is y range " << q->cSystem->index(Dimension::Y) + 1)
1162     //  DEBUG(Q_FUNC_INFO << ", x range index check = " << dynamic_cast<const
1163     // CartesianCoordinateSystem*>(plot()->coordinateSystem(q->m_cSystemIndex))->index(Dimension::X)
1164     //)
1165     DEBUG(Q_FUNC_INFO << ", axis range = " << range.toStdString() << " scale = " << ENUM_TO_STRING(RangeT, Scale, q->scale()))
1166 
1167     if (suppressRetransform)
1168         return;
1169 
1170     linePath = QPainterPath();
1171     lines.clear();
1172 
1173     QPointF startPoint, endPoint;
1174     if (orientation == Axis::Orientation::Horizontal) {
1175         if (position == Axis::Position::Logical) {
1176             startPoint = QPointF(range.start(), logicalPosition);
1177             endPoint = QPointF(range.end(), logicalPosition);
1178             lines.append(QLineF(startPoint, endPoint));
1179             // QDEBUG(Q_FUNC_INFO << ", Logical LINE = " << lines)
1180             lines = q->cSystem->mapLogicalToScene(lines, AbstractCoordinateSystem::MappingFlag::MarkGaps);
1181         } else {
1182             WorksheetElement::PositionWrapper wrapper;
1183             if (position == Axis::Position::Top)
1184                 wrapper.verticalPosition = WorksheetElement::VerticalPosition::Top;
1185             else if (position == Axis::Position::Centered)
1186                 wrapper.verticalPosition = WorksheetElement::VerticalPosition::Center;
1187             else // (position == Axis::Position::Bottom) // default
1188                 wrapper.verticalPosition = WorksheetElement::VerticalPosition::Bottom;
1189 
1190             wrapper.point = QPointF(offset, offset);
1191             const auto pos = q->relativePosToParentPos(wrapper);
1192 
1193             Lines ranges{QLineF(QPointF(range.start(), 1.), QPointF(range.end(), 1.))};
1194             // y=1 may be outside clip range: suppress clipping. value must be > 0 for log scales
1195             const auto sceneRange = q->cSystem->mapLogicalToScene(ranges, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping);
1196             // QDEBUG(Q_FUNC_INFO << ", scene range = " << sceneRange)
1197 
1198             if (sceneRange.size() > 0) {
1199                 // std::max/std::min: stay inside rect()
1200                 QRectF rect = q->m_plot->dataRect();
1201                 startPoint = QPointF(std::max(sceneRange.at(0).x1(), rect.x()), pos.y());
1202                 endPoint = QPointF(std::min(sceneRange.at(0).x2(), rect.x() + rect.width()), pos.y());
1203 
1204                 lines.append(QLineF(startPoint, endPoint));
1205                 // QDEBUG(Q_FUNC_INFO << ", Non Logical LINE =" << lines)
1206             }
1207         }
1208     } else { // vertical
1209         if (position == Axis::Position::Logical) {
1210             startPoint = QPointF(logicalPosition, range.start());
1211             endPoint = QPointF(logicalPosition, range.end());
1212             lines.append(QLineF(startPoint, endPoint));
1213             // QDEBUG(Q_FUNC_INFO << ", LOGICAL LINES = " << lines)
1214             lines = q->cSystem->mapLogicalToScene(lines, AbstractCoordinateSystem::MappingFlag::MarkGaps);
1215         } else {
1216             WorksheetElement::PositionWrapper wrapper;
1217             if (position == Axis::Position::Left)
1218                 wrapper.horizontalPosition = WorksheetElement::HorizontalPosition::Left;
1219             else if (position == Axis::Position::Centered)
1220                 wrapper.horizontalPosition = WorksheetElement::HorizontalPosition::Center;
1221             else // (position == Axis::Position::Right) // default
1222                 wrapper.horizontalPosition = WorksheetElement::HorizontalPosition::Right;
1223 
1224             wrapper.point = QPointF(offset, offset);
1225             const auto pos = q->relativePosToParentPos(wrapper);
1226 
1227             Lines ranges{QLineF(QPointF(1., range.start()), QPointF(1., range.end()))};
1228             // x=1 may be outside clip range: suppress clipping. value must be > 0 for log scales
1229             const auto sceneRange = q->cSystem->mapLogicalToScene(ranges, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping);
1230             // QDEBUG(Q_FUNC_INFO << ", scene range = " << sceneRange)
1231             if (sceneRange.size() > 0) {
1232                 // std::max/std::min: stay inside rect()
1233                 QRectF rect = q->m_plot->dataRect();
1234                 startPoint = QPointF(pos.x(), std::min(sceneRange.at(0).y1(), rect.y() + rect.height()));
1235                 endPoint = QPointF(pos.x(), std::max(sceneRange.at(0).y2(), rect.y()));
1236 
1237                 lines.append(QLineF(startPoint, endPoint));
1238                 // QDEBUG(Q_FUNC_INFO << ", Non Logical LINE = " << lines)
1239             }
1240         }
1241     }
1242 
1243     for (const auto& line : qAsConst(lines)) {
1244         linePath.moveTo(line.p1());
1245         linePath.lineTo(line.p2());
1246     }
1247 
1248     if (linePath.isEmpty()) {
1249         DEBUG(Q_FUNC_INFO << ", WARNING: line path is empty")
1250         recalcShapeAndBoundingRect();
1251         return;
1252     } else {
1253         retransformArrow();
1254         retransformTicks();
1255     }
1256 }
1257 
1258 void AxisPrivate::retransformArrow() {
1259     if (suppressRetransform)
1260         return;
1261 
1262     arrowPath = QPainterPath();
1263     if (arrowType == Axis::ArrowType::NoArrow || lines.isEmpty()) {
1264         recalcShapeAndBoundingRect();
1265         return;
1266     }
1267 
1268     if (arrowPosition == Axis::ArrowPosition::Right || arrowPosition == Axis::ArrowPosition::Both) {
1269         const QPointF& endPoint = lines.at(lines.size() - 1).p2();
1270         this->addArrow(endPoint, 1);
1271     }
1272 
1273     if (arrowPosition == Axis::ArrowPosition::Left || arrowPosition == Axis::ArrowPosition::Both) {
1274         const QPointF& endPoint = lines.at(0).p1();
1275         this->addArrow(endPoint, -1);
1276     }
1277 
1278     recalcShapeAndBoundingRect();
1279 }
1280 
1281 void AxisPrivate::addArrow(QPointF startPoint, int direction) {
1282     static const double cos_phi = cos(M_PI / 6.);
1283 
1284     if (orientation == Axis::Orientation::Horizontal) {
1285         QPointF endPoint = QPointF(startPoint.x() + direction * arrowSize, startPoint.y());
1286         arrowPath.moveTo(startPoint);
1287         arrowPath.lineTo(endPoint);
1288 
1289         switch (arrowType) {
1290         case Axis::ArrowType::NoArrow:
1291             break;
1292         case Axis::ArrowType::SimpleSmall:
1293             arrowPath.moveTo(endPoint);
1294             arrowPath.lineTo(QPointF(endPoint.x() - direction * arrowSize / 4, endPoint.y() - arrowSize / 4 * cos_phi));
1295             arrowPath.moveTo(endPoint);
1296             arrowPath.lineTo(QPointF(endPoint.x() - direction * arrowSize / 4, endPoint.y() + arrowSize / 4 * cos_phi));
1297             break;
1298         case Axis::ArrowType::SimpleBig:
1299             arrowPath.moveTo(endPoint);
1300             arrowPath.lineTo(QPointF(endPoint.x() - direction * arrowSize / 2, endPoint.y() - arrowSize / 2 * cos_phi));
1301             arrowPath.moveTo(endPoint);
1302             arrowPath.lineTo(QPointF(endPoint.x() - direction * arrowSize / 2, endPoint.y() + arrowSize / 2 * cos_phi));
1303             break;
1304         case Axis::ArrowType::FilledSmall:
1305             arrowPath.lineTo(QPointF(endPoint.x() - direction * arrowSize / 4, endPoint.y() - arrowSize / 4 * cos_phi));
1306             arrowPath.lineTo(QPointF(endPoint.x() - direction * arrowSize / 4, endPoint.y() + arrowSize / 4 * cos_phi));
1307             arrowPath.lineTo(endPoint);
1308             break;
1309         case Axis::ArrowType::FilledBig:
1310             arrowPath.lineTo(QPointF(endPoint.x() - direction * arrowSize / 2, endPoint.y() - arrowSize / 2 * cos_phi));
1311             arrowPath.lineTo(QPointF(endPoint.x() - direction * arrowSize / 2, endPoint.y() + arrowSize / 2 * cos_phi));
1312             arrowPath.lineTo(endPoint);
1313             break;
1314         case Axis::ArrowType::SemiFilledSmall:
1315             arrowPath.lineTo(QPointF(endPoint.x() - direction * arrowSize / 4, endPoint.y() - arrowSize / 4 * cos_phi));
1316             arrowPath.lineTo(QPointF(endPoint.x() - direction * arrowSize / 8, endPoint.y()));
1317             arrowPath.lineTo(QPointF(endPoint.x() - direction * arrowSize / 4, endPoint.y() + arrowSize / 4 * cos_phi));
1318             arrowPath.lineTo(endPoint);
1319             break;
1320         case Axis::ArrowType::SemiFilledBig:
1321             arrowPath.lineTo(QPointF(endPoint.x() - direction * arrowSize / 2, endPoint.y() - arrowSize / 2 * cos_phi));
1322             arrowPath.lineTo(QPointF(endPoint.x() - direction * arrowSize / 4, endPoint.y()));
1323             arrowPath.lineTo(QPointF(endPoint.x() - direction * arrowSize / 2, endPoint.y() + arrowSize / 2 * cos_phi));
1324             arrowPath.lineTo(endPoint);
1325             break;
1326         }
1327     } else { // vertical orientation
1328         QPointF endPoint = QPointF(startPoint.x(), startPoint.y() - direction * arrowSize);
1329         arrowPath.moveTo(startPoint);
1330         arrowPath.lineTo(endPoint);
1331 
1332         switch (arrowType) {
1333         case Axis::ArrowType::NoArrow:
1334             break;
1335         case Axis::ArrowType::SimpleSmall:
1336             arrowPath.moveTo(endPoint);
1337             arrowPath.lineTo(QPointF(endPoint.x() - arrowSize / 4 * cos_phi, endPoint.y() + direction * arrowSize / 4));
1338             arrowPath.moveTo(endPoint);
1339             arrowPath.lineTo(QPointF(endPoint.x() + arrowSize / 4 * cos_phi, endPoint.y() + direction * arrowSize / 4));
1340             break;
1341         case Axis::ArrowType::SimpleBig:
1342             arrowPath.moveTo(endPoint);
1343             arrowPath.lineTo(QPointF(endPoint.x() - arrowSize / 2 * cos_phi, endPoint.y() + direction * arrowSize / 2));
1344             arrowPath.moveTo(endPoint);
1345             arrowPath.lineTo(QPointF(endPoint.x() + arrowSize / 2 * cos_phi, endPoint.y() + direction * arrowSize / 2));
1346             break;
1347         case Axis::ArrowType::FilledSmall:
1348             arrowPath.lineTo(QPointF(endPoint.x() - arrowSize / 4 * cos_phi, endPoint.y() + direction * arrowSize / 4));
1349             arrowPath.lineTo(QPointF(endPoint.x() + arrowSize / 4 * cos_phi, endPoint.y() + direction * arrowSize / 4));
1350             arrowPath.lineTo(endPoint);
1351             break;
1352         case Axis::ArrowType::FilledBig:
1353             arrowPath.lineTo(QPointF(endPoint.x() - arrowSize / 2 * cos_phi, endPoint.y() + direction * arrowSize / 2));
1354             arrowPath.lineTo(QPointF(endPoint.x() + arrowSize / 2 * cos_phi, endPoint.y() + direction * arrowSize / 2));
1355             arrowPath.lineTo(endPoint);
1356             break;
1357         case Axis::ArrowType::SemiFilledSmall:
1358             arrowPath.lineTo(QPointF(endPoint.x() - arrowSize / 4 * cos_phi, endPoint.y() + direction * arrowSize / 4));
1359             arrowPath.lineTo(QPointF(endPoint.x(), endPoint.y() + direction * arrowSize / 8));
1360             arrowPath.lineTo(QPointF(endPoint.x() + arrowSize / 4 * cos_phi, endPoint.y() + direction * arrowSize / 4));
1361             arrowPath.lineTo(endPoint);
1362             break;
1363         case Axis::ArrowType::SemiFilledBig:
1364             arrowPath.lineTo(QPointF(endPoint.x() - arrowSize / 2 * cos_phi, endPoint.y() + direction * arrowSize / 2));
1365             arrowPath.lineTo(QPointF(endPoint.x(), endPoint.y() + direction * arrowSize / 4));
1366             arrowPath.lineTo(QPointF(endPoint.x() + arrowSize / 2 * cos_phi, endPoint.y() + direction * arrowSize / 2));
1367             arrowPath.lineTo(endPoint);
1368             break;
1369         }
1370     }
1371 }
1372 
1373 //! helper function for retransformTicks()
1374 /*!
1375  * \brief AxisPrivate::transformAnchor
1376  * Transform a position in logical coordinates into scene corrdinates
1377  * \param anchorPoint point which should be converted. Contains the result of the conversion
1378  * if the transformation was valid
1379  * \return true if transformation was successful else false
1380  * Successful means, that the point is inside the coordinate system
1381  */
1382 bool AxisPrivate::transformAnchor(QPointF& anchorPoint) {
1383     QVector<QPointF> points;
1384     points.append(anchorPoint);
1385     points = q->cSystem->mapLogicalToScene(points);
1386 
1387     if (points.count() != 1) { // point is not mappable or in a coordinate gap
1388         return false;
1389     } else {
1390         anchorPoint = points.at(0);
1391         return true;
1392     }
1393 }
1394 
1395 bool AxisPrivate::calculateTickHorizontal(Axis::TicksDirection tickDirection,
1396                                           double ticksLength,
1397                                           double tickStartPos,
1398                                           double dummyOtherDirPos,
1399                                           double otherDirAnchorPoint,
1400                                           double centerValue,
1401                                           int rangeDirection,
1402                                           QPointF& anchorPointOut,
1403                                           QPointF& startPointOut,
1404                                           QPointF& endPointOut) {
1405     bool valid = false;
1406     anchorPointOut.setX(tickStartPos);
1407     anchorPointOut.setY(dummyOtherDirPos); // set dummy logical point, but it must be within the datarect, otherwise valid will be always false
1408     valid = transformAnchor(anchorPointOut);
1409     anchorPointOut.setY(otherDirAnchorPoint);
1410     if (valid) {
1411         // for yDirection == -1 start is above end
1412         if (anchorPointOut.y() >= centerValue) { // below
1413             startPointOut = anchorPointOut + QPointF(0, (tickDirection & Axis::ticksIn) ? rangeDirection * ticksLength : 0);
1414             endPointOut = anchorPointOut + QPointF(0, (tickDirection & Axis::ticksOut) ? -rangeDirection * ticksLength : 0);
1415         } else { // above
1416             startPointOut = anchorPointOut + QPointF(0, (tickDirection & Axis::ticksOut) ? rangeDirection * ticksLength : 0);
1417             endPointOut = anchorPointOut + QPointF(0, (tickDirection & Axis::ticksIn) ? -rangeDirection * ticksLength : 0);
1418         }
1419     }
1420     return valid;
1421 }
1422 
1423 bool AxisPrivate::calculateTickVertical(Axis::TicksDirection tickDirection,
1424                                         double ticksLength,
1425                                         double tickStartPos,
1426                                         double dummyOtherDirPos,
1427                                         double otherDirAnchorPoint,
1428                                         double centerValue,
1429                                         int rangeDirection,
1430                                         QPointF& anchorPointOut,
1431                                         QPointF& startPointOut,
1432                                         QPointF& endPointOut) {
1433     bool valid = false;
1434     anchorPointOut.setY(tickStartPos);
1435     anchorPointOut.setX(dummyOtherDirPos); // set dummy logical point, but it must be within the datarect, otherwise valid will be always false
1436     valid = transformAnchor(anchorPointOut);
1437     anchorPointOut.setX(otherDirAnchorPoint);
1438     if (valid) {
1439         // for xDirection == 1 start is right of end
1440         if (anchorPointOut.x() < centerValue) { // left
1441             startPointOut = anchorPointOut + QPointF((tickDirection & Axis::ticksIn) ? rangeDirection * ticksLength : 0, 0);
1442             endPointOut = anchorPointOut + QPointF((tickDirection & Axis::ticksOut) ? -rangeDirection * ticksLength : 0, 0);
1443         } else { // right
1444             startPointOut = anchorPointOut + QPointF((tickDirection & Axis::ticksOut) ? rangeDirection * ticksLength : 0, 0);
1445             endPointOut = anchorPointOut + QPointF((tickDirection & Axis::ticksIn) ? -rangeDirection * ticksLength : 0, 0);
1446         }
1447     }
1448     return valid;
1449 }
1450 
1451 int AxisPrivate::determineMinorTicksNumber() const {
1452     int tmpMinorTicksNumber = 0;
1453     switch (minorTicksType) {
1454     case Axis::TicksType::TotalNumber:
1455         tmpMinorTicksNumber = minorTicksNumber;
1456         break;
1457     case Axis::TicksType::Spacing:
1458         tmpMinorTicksNumber = range.length() / minorTicksIncrement - 1;
1459         if (majorTicksNumber > 1)
1460             tmpMinorTicksNumber /= majorTicksNumber - 1;
1461         break;
1462     case Axis::TicksType::CustomColumn: // Fall through
1463     case Axis::TicksType::CustomValues:
1464         (minorTicksColumn) ? tmpMinorTicksNumber = minorTicksColumn->rowCount() : tmpMinorTicksNumber = 0;
1465         break;
1466     case Axis::TicksType::ColumnLabels:
1467         break; // not supported
1468     }
1469     return tmpMinorTicksNumber;
1470 }
1471 
1472 /*!
1473     recalculates the position of the axis ticks.
1474  */
1475 void AxisPrivate::retransformTicks() {
1476 #if PERFTRACE_AXIS
1477     PERFTRACE(QLatin1String(Q_FUNC_INFO) + QStringLiteral(", axis ") + name());
1478 #endif
1479     // DEBUG(Q_FUNC_INFO << ' ' << STDSTRING(title->name()))
1480     if (suppressRetransform)
1481         return;
1482 
1483     majorTicksPath = QPainterPath();
1484     minorTicksPath = QPainterPath();
1485     majorTickPoints.clear();
1486     minorTickPoints.clear();
1487     tickLabelValues.clear();
1488     tickLabelValuesString.clear();
1489 
1490     if (!q->cSystem) {
1491         DEBUG(Q_FUNC_INFO << ", WARNING: axis has no coordinate system!")
1492         return;
1493     }
1494 
1495     // determine the increment for the major ticks
1496     double majorTicksIncrement = 0;
1497     int tmpMajorTicksNumber = 0;
1498     double start{range.start()}, end{range.end()};
1499     if (majorTicksStartType == Axis::TicksStartType::Absolute) {
1500         start = majorTickStartValue;
1501 
1502     } else if (majorTicksStartType == Axis::TicksStartType::Offset) {
1503         if (q->isNumeric())
1504             start += majorTickStartOffset;
1505         else {
1506             auto startDt = QDateTime::fromMSecsSinceEpoch(start, Qt::UTC);
1507             startDt.setTimeSpec(Qt::TimeSpec::UTC);
1508             const auto& dt = DateTime::dateTime(majorTickStartOffset);
1509             startDt = startDt.addYears(dt.year);
1510             startDt = startDt.addMonths(dt.month);
1511             startDt = startDt.addDays(dt.day);
1512             startDt = startDt.addMSecs(DateTime::milliseconds(dt.hour, dt.minute, dt.second, dt.millisecond));
1513             start = startDt.toMSecsSinceEpoch();
1514         }
1515     }
1516 
1517     // if type is tick number and tick number is auto: recalculate in case scale has changed
1518     if (majorTicksType == Axis::TicksType::TotalNumber && majorTicksAutoNumber) {
1519         auto r = range;
1520         r.setStart(start);
1521         r.setEnd(end);
1522         majorTicksNumber = r.autoTickCount();
1523     }
1524 
1525     if (majorTicksNumber < 1 || (majorTicksDirection == Axis::noTicks && minorTicksDirection == Axis::noTicks)) {
1526         retransformTickLabelPositions(); // this calls recalcShapeAndBoundingRect()
1527         return;
1528     }
1529 
1530     if (majorTicksType == Axis::TicksType::CustomColumn || majorTicksType == Axis::TicksType::CustomValues) {
1531         if (majorTicksColumn) {
1532             if (majorTicksAutoNumber) {
1533                 tmpMajorTicksNumber = qMin(_maxNumberMajorTicksCustomColumn, majorTicksColumn->rowCount(start, end));
1534                 majorTicksNumber = tmpMajorTicksNumber;
1535                 Q_EMIT q->majorTicksNumberChanged(tmpMajorTicksNumber);
1536             } else
1537                 tmpMajorTicksNumber = majorTicksNumber;
1538             // Do the calculation of the new start/end after recalculating majorTicksNumber, otherwise it could happen that the
1539             // ticks are really near to each other
1540             if (start < end) {
1541                 start = qMax(start, majorTicksColumn->minimum());
1542                 end = qMin(end, majorTicksColumn->maximum());
1543             } else {
1544                 end = qMax(end, majorTicksColumn->minimum());
1545                 start = qMax(start, majorTicksColumn->maximum());
1546             }
1547         } else {
1548             retransformTickLabelPositions(); // this calls recalcShapeAndBoundingRect()
1549             return;
1550         }
1551     } else if (majorTicksType == Axis::TicksType::ColumnLabels) {
1552         const Column* c = dynamic_cast<const Column*>(majorTicksColumn);
1553         if (c && c->valueLabelsInitialized()) {
1554             if (majorTicksAutoNumber) {
1555                 tmpMajorTicksNumber = qMin(_maxNumberMajorTicksCustomColumn, c->valueLabelsCount(start, end));
1556                 majorTicksNumber = tmpMajorTicksNumber;
1557                 Q_EMIT q->majorTicksNumberChanged(tmpMajorTicksNumber);
1558             } else
1559                 tmpMajorTicksNumber = c->valueLabelsCount(start, end);
1560             if (start < end) {
1561                 start = qMax(start, c->valueLabelsMinimum());
1562                 end = qMin(end, c->valueLabelsMaximum());
1563             } else {
1564                 end = qMax(end, c->valueLabelsMinimum());
1565                 start = qMax(start, c->valueLabelsMaximum());
1566             }
1567         } else {
1568             retransformTickLabelPositions(); // this calls recalcShapeAndBoundingRect()
1569             return;
1570         }
1571     }
1572 
1573     QDEBUG(Q_FUNC_INFO << ", ticks type = " << majorTicksType)
1574     switch (majorTicksType) {
1575     case Axis::TicksType::TotalNumber: // total number of major ticks is given - > determine the increment
1576         tmpMajorTicksNumber = majorTicksNumber;
1577         // fall through
1578     case Axis::TicksType::ColumnLabels: // fall through
1579     case Axis::TicksType::CustomColumn: // fall through
1580     case Axis::TicksType::CustomValues:
1581         switch (q->scale()) {
1582         case RangeT::Scale::Linear:
1583             majorTicksIncrement = end - start;
1584             break;
1585         case RangeT::Scale::Log10:
1586             if (start != 0. && end / start > 0.)
1587                 majorTicksIncrement = log10(end / start);
1588             break;
1589         case RangeT::Scale::Log2:
1590             if (start != 0. && end / start > 0.)
1591                 majorTicksIncrement = log2(end / start);
1592             break;
1593         case RangeT::Scale::Ln:
1594             if (start != 0. && end / start > 0.)
1595                 majorTicksIncrement = log(end / start);
1596             break;
1597         case RangeT::Scale::Sqrt:
1598             if (start >= 0. && end >= 0.)
1599                 majorTicksIncrement = std::sqrt(end) - std::sqrt(start);
1600             break;
1601         case RangeT::Scale::Square:
1602             majorTicksIncrement = end * end - start * start;
1603             break;
1604         case RangeT::Scale::Inverse:
1605             if (start != 0. && end != 0.)
1606                 majorTicksIncrement = 1. / start - 1. / end;
1607             break;
1608         }
1609         if (tmpMajorTicksNumber > 1)
1610             majorTicksIncrement /= tmpMajorTicksNumber - 1;
1611         DEBUG(Q_FUNC_INFO << ", major ticks by number. increment = " << majorTicksIncrement << " number = " << majorTicksNumber)
1612         break;
1613     case Axis::TicksType::Spacing:
1614         // the increment of the major ticks is given -> determine the number
1615         // TODO: majorTicksSpacing == 0?
1616         majorTicksIncrement = majorTicksSpacing * GSL_SIGN(end - start);
1617         if (q->isNumeric() || (!q->isNumeric() && q->scale() != RangeT::Scale::Linear)) {
1618             switch (q->scale()) {
1619             case RangeT::Scale::Linear:
1620                 tmpMajorTicksNumber = std::round((end - start) / majorTicksIncrement + 1);
1621                 break;
1622             case RangeT::Scale::Log10:
1623                 if (start != 0. && end / start > 0.)
1624                     tmpMajorTicksNumber = std::round(log10(end / start) / majorTicksIncrement + 1);
1625                 break;
1626             case RangeT::Scale::Log2:
1627                 if (start != 0. && end / start > 0.)
1628                     tmpMajorTicksNumber = std::round(log2(end / start) / majorTicksIncrement + 1);
1629                 break;
1630             case RangeT::Scale::Ln:
1631                 if (start != 0. && end / start > 0.)
1632                     tmpMajorTicksNumber = std::round(std::log(end / start) / majorTicksIncrement + 1);
1633                 break;
1634             case RangeT::Scale::Sqrt:
1635                 if (start >= 0. && end >= 0.)
1636                     tmpMajorTicksNumber = std::round((std::sqrt(end) - std::sqrt(start)) / majorTicksIncrement + 1);
1637                 break;
1638             case RangeT::Scale::Square:
1639                 tmpMajorTicksNumber = std::round((end * end - start * start) / majorTicksIncrement + 1);
1640                 break;
1641             case RangeT::Scale::Inverse:
1642                 if (start != 0. && end != 0.)
1643                     tmpMajorTicksNumber = std::round((1. / start - 1. / end) / majorTicksIncrement + 1);
1644                 break;
1645             }
1646         } else {
1647             // Datetime with linear spacing: Calculation will be done directly where the majorTickPos will be calculated
1648         }
1649         break;
1650     }
1651 
1652     // minor ticks
1653     int tmpMinorTicksNumber = determineMinorTicksNumber();
1654 
1655     //  const int xIndex{ q->cSystem->index(Dimension::X) }, yIndex{ q->cSystem->index(Dimension::Y) };
1656     DEBUG(Q_FUNC_INFO << ", coordinate system " << q->m_cSystemIndex + 1)
1657     auto cs = plot()->coordinateSystem(q->coordinateSystemIndex());
1658     const int xRangeDirection = plot()->range(Dimension::X, cs->index(Dimension::X)).direction();
1659     const int yRangeDirection = plot()->range(Dimension::Y, cs->index(Dimension::Y)).direction();
1660     const int xDirection = q->cSystem->direction(Dimension::X) * xRangeDirection;
1661     const int yDirection = q->cSystem->direction(Dimension::Y) * yRangeDirection;
1662 
1663     // calculate the position of the center point in scene coordinates,
1664     // will be used later to differentiate between "in" and "out" depending
1665     // on the position relative to the center.
1666     const double middleX = plot()->range(Dimension::X, cs->index(Dimension::X)).center();
1667     const double middleY = plot()->range(Dimension::Y, cs->index(Dimension::Y)).center();
1668     QPointF center(middleX, middleY);
1669     bool valid = true;
1670     center = q->cSystem->mapLogicalToScene(center, valid);
1671 
1672     const bool dateTimeSpacing = !q->isNumeric() && q->scale() == RangeT::Scale::Linear && majorTicksType == Axis::TicksType::Spacing;
1673     DateTime::DateTime dt;
1674     QDateTime majorTickPosDateTime;
1675     if (dateTimeSpacing) {
1676         dt = DateTime::dateTime(majorTicksSpacing);
1677         majorTickPosDateTime = QDateTime::fromMSecsSinceEpoch(start, Qt::UTC);
1678     }
1679     const auto dtValid = majorTickPosDateTime.isValid();
1680 
1681     for (int iMajor = 0; iMajor < tmpMajorTicksNumber || (dateTimeSpacing && dtValid); iMajor++) {
1682         //      DEBUG(Q_FUNC_INFO << ", major tick " << iMajor)
1683         qreal majorTickPos = 0.0;
1684         qreal nextMajorTickPos = 0.0;
1685         // calculate major tick's position
1686 
1687         if (!dateTimeSpacing) {
1688             switch (q->scale()) {
1689             case RangeT::Scale::Linear:
1690                 //              DEBUG(Q_FUNC_INFO << ", start = " << start << ", incr = " << majorTicksIncrement << ", i = " << iMajor)
1691                 majorTickPos = start + majorTicksIncrement * iMajor;
1692                 if (std::abs(majorTickPos) < 1.e-15 * majorTicksIncrement) // avoid rounding errors when close to zero
1693                     majorTickPos = 0;
1694                 nextMajorTickPos = majorTickPos + majorTicksIncrement;
1695                 break;
1696             case RangeT::Scale::Log10:
1697                 majorTickPos = start * std::pow(10, majorTicksIncrement * iMajor);
1698                 nextMajorTickPos = majorTickPos * std::pow(10, majorTicksIncrement);
1699                 break;
1700             case RangeT::Scale::Log2:
1701                 majorTickPos = start * std::exp2(majorTicksIncrement * iMajor);
1702                 nextMajorTickPos = majorTickPos * exp2(majorTicksIncrement);
1703                 break;
1704             case RangeT::Scale::Ln:
1705                 majorTickPos = start * std::exp(majorTicksIncrement * iMajor);
1706                 nextMajorTickPos = majorTickPos * exp(majorTicksIncrement);
1707                 break;
1708             case RangeT::Scale::Sqrt:
1709                 majorTickPos = std::pow(std::sqrt(start) + majorTicksIncrement * iMajor, 2);
1710                 nextMajorTickPos = std::pow(std::sqrt(start) + majorTicksIncrement * (iMajor + 1), 2);
1711                 break;
1712             case RangeT::Scale::Square:
1713                 majorTickPos = std::sqrt(start * start + majorTicksIncrement * iMajor);
1714                 nextMajorTickPos = std::sqrt(start * start + majorTicksIncrement * (iMajor + 1));
1715                 break;
1716             case RangeT::Scale::Inverse:
1717                 majorTickPos = 1. / (1. / start + majorTicksIncrement * iMajor);
1718                 nextMajorTickPos = 1. / (1. / start + majorTicksIncrement * (iMajor + 1));
1719                 break;
1720             }
1721         } else {
1722             // Datetime Linear
1723             if (iMajor == 0)
1724                 majorTickPos = start;
1725             else {
1726                 majorTickPosDateTime = majorTickPosDateTime.addYears(dt.year);
1727                 majorTickPosDateTime = majorTickPosDateTime.addMonths(dt.month);
1728                 majorTickPosDateTime = majorTickPosDateTime.addDays(dt.day);
1729                 majorTickPosDateTime = majorTickPosDateTime.addMSecs(DateTime::milliseconds(dt.hour, dt.minute, dt.second, dt.millisecond));
1730                 majorTickPos = majorTickPosDateTime.toMSecsSinceEpoch();
1731             }
1732         }
1733         if (majorTickPos > end || iMajor > maxNumberMajorTicks)
1734             break; // Finish
1735 
1736         int columnIndex = iMajor; // iMajor used if for the labels a custom column is used.
1737         if ((majorTicksType == Axis::TicksType::CustomColumn || majorTicksType == Axis::TicksType::CustomValues)
1738             && (majorTicksColumn->rowCount() >= _maxNumberMajorTicksCustomColumn)) {
1739             // Do not use all values of the column, but just a portion of it
1740             columnIndex = majorTicksColumn->indexForValue(majorTickPos);
1741             Q_ASSERT(columnIndex >= 0);
1742             majorTickPos = majorTicksColumn->valueAt(columnIndex);
1743 
1744             const auto columnIndexNextMajor = majorTicksColumn->indexForValue(nextMajorTickPos);
1745             Q_ASSERT(columnIndexNextMajor >= 0);
1746             nextMajorTickPos = majorTicksColumn->valueAt(columnIndexNextMajor);
1747             if (majorTickPos == nextMajorTickPos && iMajor + 1 < tmpMajorTicksNumber)
1748                 continue; // No need to draw majorTicksPos, because NextMajorTicksPos will completely overlap. Only for the last one
1749         } else if ((majorTicksType == Axis::TicksType::CustomColumn || majorTicksType == Axis::TicksType::CustomValues)) {
1750             majorTickPos = majorTicksColumn->valueAt(columnIndex);
1751             if (majorTicksColumn->rowCount() > columnIndex + 1)
1752                 nextMajorTickPos = majorTicksColumn->valueAt(columnIndex + 1);
1753             else
1754                 nextMajorTickPos = majorTickPos;
1755         } else if (majorTicksType == Axis::TicksType::ColumnLabels) {
1756             const Column* c = dynamic_cast<const Column*>(majorTicksColumn);
1757             Q_ASSERT(tmpMajorTicksNumber > 0);
1758             Q_ASSERT(c);
1759             columnIndex = c->valueLabelsIndexForValue(majorTickPos);
1760             Q_ASSERT(columnIndex >= 0);
1761             majorTickPos = c->valueLabelsValueAt(columnIndex);
1762         }
1763 
1764         qreal otherDirAnchorPoint = 0.0;
1765         if (!lines.isEmpty()) {
1766             if (orientation == Axis::Orientation::Vertical)
1767                 otherDirAnchorPoint = lines.first().p1().x();
1768             else
1769                 otherDirAnchorPoint = lines.first().p1().y();
1770         }
1771 
1772         QPointF anchorPoint, startPoint, endPoint;
1773         // calculate start and end points for major tick's line
1774         if (majorTicksDirection != Axis::noTicks) {
1775             if (orientation == Axis::Orientation::Horizontal) {
1776                 auto startY = q->plot()->range(Dimension::Y, cs->index(Dimension::Y)).start();
1777                 valid = calculateTickHorizontal(majorTicksDirection,
1778                                                 majorTicksLength,
1779                                                 majorTickPos,
1780                                                 startY,
1781                                                 otherDirAnchorPoint,
1782                                                 center.y(),
1783                                                 yDirection,
1784                                                 anchorPoint,
1785                                                 startPoint,
1786                                                 endPoint);
1787             } else { // vertical
1788                 auto startX = q->plot()->range(Dimension::X, cs->index(Dimension::X)).start();
1789                 valid = calculateTickVertical(majorTicksDirection,
1790                                               majorTicksLength,
1791                                               majorTickPos,
1792                                               startX,
1793                                               otherDirAnchorPoint,
1794                                               center.x(),
1795                                               xDirection,
1796                                               anchorPoint,
1797                                               startPoint,
1798                                               endPoint);
1799             }
1800 
1801             const qreal value = scalingFactor * majorTickPos + zeroOffset;
1802             //          DEBUG(Q_FUNC_INFO << ", value = " << value << " " << scalingFactor << " " << majorTickPos << " " << zeroOffset)
1803 
1804             // if custom column is used, we can have duplicated values in it and we need only unique values
1805             if ((majorTicksType == Axis::TicksType::CustomColumn || majorTicksType == Axis::TicksType::ColumnLabels) && tickLabelValues.indexOf(value) != -1)
1806                 valid = false;
1807 
1808             // add major tick's line to the painter path
1809             if (valid) {
1810                 if (majorTicksLine->pen().style() != Qt::NoPen) {
1811                     majorTicksPath.moveTo(startPoint);
1812                     majorTicksPath.lineTo(endPoint);
1813                 }
1814                 majorTickPoints << anchorPoint;
1815                 if (majorTicksType == Axis::TicksType::ColumnLabels) {
1816                     const Column* c = dynamic_cast<const Column*>(majorTicksColumn);
1817                     // majorTicksType == Axis::TicksType::ColumnLabels
1818                     if (c && c->valueLabelsInitialized()) {
1819                         if (columnIndex < c->valueLabelsCount())
1820                             tickLabelValuesString << c->valueLabelAt(columnIndex);
1821                     }
1822                 } else {
1823                     switch (labelsTextType) {
1824                     case Axis::LabelsTextType::PositionValues:
1825                         tickLabelValues << value;
1826                         break;
1827                     case Axis::LabelsTextType::CustomValues: {
1828                         if (labelsTextColumn && columnIndex < labelsTextColumn->rowCount()) {
1829                             switch (labelsTextColumn->columnMode()) {
1830                             case AbstractColumn::ColumnMode::Double:
1831                             case AbstractColumn::ColumnMode::Integer:
1832                             case AbstractColumn::ColumnMode::BigInt:
1833                                 tickLabelValues << labelsTextColumn->valueAt(columnIndex);
1834                                 break;
1835                             case AbstractColumn::ColumnMode::DateTime:
1836                             case AbstractColumn::ColumnMode::Month:
1837                             case AbstractColumn::ColumnMode::Day:
1838                                 tickLabelValues << labelsTextColumn->dateTimeAt(columnIndex).toMSecsSinceEpoch();
1839                                 break;
1840                             case AbstractColumn::ColumnMode::Text:
1841                                 tickLabelValuesString << labelsTextColumn->textAt(columnIndex);
1842                                 break;
1843                             }
1844                         }
1845                     }
1846                     }
1847                 }
1848             }
1849         }
1850 
1851         // minor ticks
1852         // DEBUG("  tmpMinorTicksNumber = " << tmpMinorTicksNumber)
1853         if (Axis::noTicks != minorTicksDirection && tmpMajorTicksNumber > 1 && tmpMinorTicksNumber > 0 && iMajor < tmpMajorTicksNumber - 1
1854             && nextMajorTickPos != majorTickPos) {
1855             // minor ticks are placed at equidistant positions independent of the selected scaling for the major ticks positions
1856             double minorTicksIncrement = (nextMajorTickPos - majorTickPos) / (tmpMinorTicksNumber + 1);
1857             // DEBUG("  nextMajorTickPos = " << nextMajorTickPos)
1858             // DEBUG("  majorTickPos = " << majorTickPos)
1859             // DEBUG("  minorTicksIncrement = " << minorTicksIncrement)
1860 
1861             qreal minorTickPos;
1862             for (int iMinor = 0; iMinor < tmpMinorTicksNumber; iMinor++) {
1863                 // calculate minor tick's position
1864                 if (minorTicksType != Axis::TicksType::CustomColumn) {
1865                     minorTickPos = majorTickPos + (iMinor + 1) * minorTicksIncrement;
1866                 } else {
1867                     if (!minorTicksColumn->isValid(iMinor) || minorTicksColumn->isMasked(iMinor))
1868                         continue;
1869                     minorTickPos = minorTicksColumn->valueAt(iMinor);
1870 
1871                     // in the case a custom column is used for the minor ticks, we draw them _once_ for the whole range of the axis.
1872                     // execute the minor ticks loop only once.
1873                     if (iMajor > 0)
1874                         break;
1875                 }
1876                 // DEBUG("      minorTickPos = " << minorTickPos)
1877 
1878                 // calculate start and end points for minor tick's line (same as major ticks)
1879                 if (orientation == Axis::Orientation::Horizontal) {
1880                     auto startY = q->plot()->range(Dimension::Y, cs->index(Dimension::Y)).start();
1881                     valid = calculateTickHorizontal(minorTicksDirection,
1882                                                     minorTicksLength,
1883                                                     minorTickPos,
1884                                                     startY,
1885                                                     otherDirAnchorPoint,
1886                                                     center.y(),
1887                                                     yDirection,
1888                                                     anchorPoint,
1889                                                     startPoint,
1890                                                     endPoint);
1891                 } else { // vertical
1892                     auto startX = q->plot()->range(Dimension::X, cs->index(Dimension::X)).start();
1893                     valid = calculateTickVertical(minorTicksDirection,
1894                                                   minorTicksLength,
1895                                                   minorTickPos,
1896                                                   startX,
1897                                                   otherDirAnchorPoint,
1898                                                   center.x(),
1899                                                   xDirection,
1900                                                   anchorPoint,
1901                                                   startPoint,
1902                                                   endPoint);
1903                 }
1904 
1905                 // add minor tick's line to the painter path
1906                 if (valid) {
1907                     if (minorTicksLine->pen().style() != Qt::NoPen) {
1908                         minorTicksPath.moveTo(startPoint);
1909                         minorTicksPath.lineTo(endPoint);
1910                     }
1911                     minorTickPoints << anchorPoint;
1912                 }
1913             }
1914         }
1915     }
1916     //  QDEBUG(Q_FUNC_INFO << tickLabelValues)
1917 
1918     // tick positions where changed -> update the position of the tick labels and grid lines
1919     retransformTickLabelStrings();
1920     retransformMajorGrid();
1921     retransformMinorGrid();
1922 }
1923 
1924 /*!
1925     creates the tick label strings starting with the optimal
1926     (=the smallest possible number of digits) precision for the floats
1927 */
1928 void AxisPrivate::retransformTickLabelStrings() {
1929     DEBUG(Q_FUNC_INFO << ' ' << STDSTRING(title->name()) << ", labels precision = " << labelsPrecision << ", labels auto precision = " << labelsAutoPrecision)
1930     if (suppressRetransform)
1931         return;
1932     QDEBUG(Q_FUNC_INFO << ", values = " << tickLabelValues)
1933 
1934     const auto cs = plot()->coordinateSystem(q->coordinateSystemIndex());
1935 
1936     // automatically switch from 'decimal' to 'scientific' format for large and small numbers
1937     // and back to decimal when the numbers get smaller after the auto-switch
1938     DEBUG(Q_FUNC_INFO << ", format = " << ENUM_TO_STRING(Axis, LabelsFormat, labelsFormat))
1939     if (labelsFormatAuto) {
1940         bool largeValue = false;
1941         for (auto value : tickLabelValues) {
1942             // switch to Scientific for large and small values if at least one is
1943             if (std::abs(value) > 1.e4 || (std::abs(value) > 1.e-16 && std::abs(value) < 1e-4)) {
1944                 largeValue = true;
1945                 break;
1946             }
1947         }
1948         if (largeValue)
1949             labelsFormat = Axis::LabelsFormat::Scientific;
1950         else
1951             labelsFormat = Axis::LabelsFormat::Decimal;
1952         Q_EMIT q->labelsFormatChanged(labelsFormat);
1953     }
1954 
1955     // determine labels precision
1956     if (labelsAutoPrecision) {
1957         // do we need to increase the current precision?
1958         int newPrecision = upperLabelsPrecision(labelsPrecision, labelsFormat);
1959         if (newPrecision != labelsPrecision) {
1960             labelsPrecision = newPrecision;
1961             Q_EMIT q->labelsPrecisionChanged(labelsPrecision);
1962         } else {
1963             // can we reduce the current precision?
1964             newPrecision = lowerLabelsPrecision(labelsPrecision, labelsFormat);
1965             if (newPrecision != labelsPrecision) {
1966                 labelsPrecision = newPrecision;
1967                 Q_EMIT q->labelsPrecisionChanged(labelsPrecision);
1968             }
1969         }
1970         DEBUG(Q_FUNC_INFO << ", auto labels precision = " << labelsPrecision)
1971     }
1972 
1973     // category of format
1974     bool numeric = false, datetime = false, text = false;
1975     if (majorTicksType == Axis::TicksType::ColumnLabels)
1976         text = true;
1977     else if (labelsTextType == Axis::LabelsTextType::PositionValues) {
1978         auto xRangeFormat{plot()->range(Dimension::X, cs->index(Dimension::X)).format()};
1979         auto yRangeFormat{plot()->range(Dimension::Y, cs->index(Dimension::Y)).format()};
1980         numeric = ((orientation == Axis::Orientation::Horizontal && xRangeFormat == RangeT::Format::Numeric)
1981                    || (orientation == Axis::Orientation::Vertical && yRangeFormat == RangeT::Format::Numeric));
1982 
1983         if (!numeric)
1984             datetime = true;
1985     } else {
1986         if (labelsTextColumn) {
1987             switch (labelsTextColumn->columnMode()) {
1988             case AbstractColumn::ColumnMode::Double:
1989             case AbstractColumn::ColumnMode::Integer:
1990             case AbstractColumn::ColumnMode::BigInt:
1991                 numeric = true;
1992                 break;
1993             case AbstractColumn::ColumnMode::DateTime:
1994             case AbstractColumn::ColumnMode::Month:
1995             case AbstractColumn::ColumnMode::Day:
1996                 datetime = true;
1997                 break;
1998             case AbstractColumn::ColumnMode::Text:
1999                 text = true;
2000             }
2001         }
2002     }
2003 
2004     tickLabelStrings.clear();
2005     QString str;
2006     const auto numberLocale = QLocale();
2007     if (numeric) {
2008         switch (labelsFormat) {
2009         case Axis::LabelsFormat::Decimal: {
2010             QString nullStr = numberLocale.toString(0., 'f', labelsPrecision);
2011             for (const auto value : qAsConst(tickLabelValues)) {
2012                 // toString() does not round: use NSL function
2013                 if (RangeT::isLogScale(q->scale())) { // don't use same precision for all label on log scales
2014                     const int precision = labelsAutoPrecision ? std::max(labelsPrecision, nsl_math_decimal_places(value) + 1) : labelsPrecision;
2015                     str = numberLocale.toString(value, 'f', precision);
2016                 } else
2017                     str = numberLocale.toString(nsl_math_round_places(value, labelsPrecision), 'f', labelsPrecision);
2018                 if (str == QLatin1String("-") + nullStr)
2019                     str = nullStr;
2020                 str = labelsPrefix + str + labelsSuffix;
2021                 tickLabelStrings << str;
2022             }
2023             break;
2024         }
2025         case Axis::LabelsFormat::ScientificE: {
2026             QString nullStr = numberLocale.toString(0., 'e', labelsPrecision);
2027             for (const auto value : qAsConst(tickLabelValues)) {
2028                 if (value == 0) // just show "0"
2029                     str = numberLocale.toString(value, 'f', 0);
2030                 else {
2031                     int e;
2032                     const double frac = nsl_math_frexp10(value, &e);
2033                     // DEBUG(Q_FUNC_INFO << ", rounded frac * pow (10, e) = " << nsl_math_round_places(frac, labelsPrecision) * pow(10, e))
2034                     str = numberLocale.toString(nsl_math_round_places(frac, labelsPrecision) * gsl_pow_int(10., e), 'e', labelsPrecision);
2035                 }
2036                 if (str == QLatin1String("-") + nullStr)
2037                     str = nullStr; // avoid "-O"
2038                 str = labelsPrefix + str + labelsSuffix;
2039                 tickLabelStrings << str;
2040             }
2041             break;
2042         }
2043         case Axis::LabelsFormat::Powers10: {
2044             for (const auto value : qAsConst(tickLabelValues)) {
2045                 if (value == 0) // just show "0"
2046                     str = numberLocale.toString(value, 'f', 0);
2047                 else {
2048                     str = QStringLiteral("10<sup>")
2049                         + numberLocale.toString(nsl_math_round_places(log10(std::abs(value)), labelsPrecision), 'f', labelsPrecision)
2050                         + QStringLiteral("</sup>");
2051                     if (value < 0)
2052                         str.prepend(QLatin1Char('-'));
2053                 }
2054                 str = labelsPrefix + str + labelsSuffix;
2055                 tickLabelStrings << str;
2056             }
2057             break;
2058         }
2059         case Axis::LabelsFormat::Powers2: {
2060             for (const auto value : qAsConst(tickLabelValues)) {
2061                 if (value == 0) // just show "0"
2062                     str = numberLocale.toString(value, 'f', 0);
2063                 else {
2064                     str = QStringLiteral("2<span style=\"vertical-align:super\">")
2065                         + numberLocale.toString(nsl_math_round_places(log2(std::abs(value)), labelsPrecision), 'f', labelsPrecision)
2066                         + QStringLiteral("</spanlabelsPrecision)>");
2067                     if (value < 0)
2068                         str.prepend(QLatin1Char('-'));
2069                 }
2070                 str = labelsPrefix + str + labelsSuffix;
2071                 tickLabelStrings << str;
2072             }
2073             break;
2074         }
2075         case Axis::LabelsFormat::PowersE: {
2076             for (const auto value : qAsConst(tickLabelValues)) {
2077                 if (value == 0) // just show "0"
2078                     str = numberLocale.toString(value, 'f', 0);
2079                 else {
2080                     str = QStringLiteral("e<span style=\"vertical-align:super\">")
2081                         + numberLocale.toString(nsl_math_round_places(log(std::abs(value)), labelsPrecision), 'f', labelsPrecision) + QStringLiteral("</span>");
2082                     if (value < 0)
2083                         str.prepend(QLatin1Char('-'));
2084                 }
2085                 str = labelsPrefix + str + labelsSuffix;
2086                 tickLabelStrings << str;
2087             }
2088             break;
2089         }
2090         case Axis::LabelsFormat::MultipliesPi: {
2091             for (const auto value : qAsConst(tickLabelValues)) {
2092                 if (value == 0) // just show "0"
2093                     str = numberLocale.toString(value, 'f', 0);
2094                 else if (nsl_math_approximately_equal_eps(value, M_PI, 1.e-3))
2095                     str = QChar(0x03C0);
2096                 else
2097                     str = QStringLiteral("<span>") + numberLocale.toString(nsl_math_round_places(value / M_PI, labelsPrecision), 'f', labelsPrecision)
2098                         + QStringLiteral("</span>") + QChar(0x03C0);
2099                 str = labelsPrefix + str + labelsSuffix;
2100                 tickLabelStrings << str;
2101             }
2102             break;
2103         }
2104         case Axis::LabelsFormat::Scientific: {
2105             for (const auto value : qAsConst(tickLabelValues)) {
2106                 // DEBUG(Q_FUNC_INFO << ", value = " << value << ", precision = " << labelsPrecision)
2107                 if (value == 0) // just show "0"
2108                     str = numberLocale.toString(value, 'f', 0);
2109                 else {
2110                     int e;
2111                     const double frac = nsl_math_frexp10(value, &e);
2112                     if (std::abs(value) < 100. && std::abs(value) > .01) // use normal notation for values near 1, precision reduced by exponent but >= 0
2113                         str = numberLocale.toString(nsl_math_round_places(frac, labelsPrecision) * gsl_pow_int(10., e), 'f', std::max(labelsPrecision - e, 0));
2114                     else {
2115                         // DEBUG(Q_FUNC_INFO << ", nsl rounded = " << nsl_math_round_places(frac, labelsPrecision))
2116                         //  only round fraction
2117                         str = numberLocale.toString(nsl_math_round_places(frac, labelsPrecision), 'f', labelsPrecision);
2118                         str = createScientificRepresentation(str, numberLocale.toString(e));
2119                     }
2120                 }
2121                 str = labelsPrefix + str + labelsSuffix;
2122                 tickLabelStrings << str;
2123             }
2124         }
2125 
2126             DEBUG(Q_FUNC_INFO << ", tick label = " << STDSTRING(str))
2127         }
2128     } else if (datetime) {
2129         for (const auto value : qAsConst(tickLabelValues)) {
2130             QDateTime dateTime;
2131             dateTime.setTimeSpec(Qt::UTC);
2132             dateTime.setMSecsSinceEpoch(value);
2133             str = dateTime.toString(labelsDateTimeFormat);
2134             str = labelsPrefix + str + labelsSuffix;
2135             tickLabelStrings << str;
2136         }
2137     } else if (text) {
2138         for (auto& t : tickLabelValuesString) {
2139             str = labelsPrefix + t + labelsSuffix;
2140             tickLabelStrings << str;
2141         }
2142     }
2143 
2144     QDEBUG(Q_FUNC_INFO << ", strings = " << tickLabelStrings)
2145 
2146     // recalculate the position of the tick labels
2147     retransformTickLabelPositions();
2148 }
2149 
2150 /*!
2151     returns the smallest upper limit for the precision
2152     where no duplicates for the tick label values occur.
2153  */
2154 int AxisPrivate::upperLabelsPrecision(const int precision, const Axis::LabelsFormat format) {
2155     DEBUG(Q_FUNC_INFO << ", precision = " << precision << ", format = " << ENUM_TO_STRING(Axis, LabelsFormat, format));
2156 
2157     // catch out of limit values
2158     if (precision > 6)
2159         return 6;
2160 
2161     // avoid problems with zero range axis
2162     if (tickLabelValues.isEmpty() || qFuzzyCompare(tickLabelValues.constFirst(), tickLabelValues.constLast())) {
2163         DEBUG(Q_FUNC_INFO << ", zero range axis detected.")
2164         return 0;
2165     }
2166 
2167     // round values to the current precision and look for duplicates.
2168     // if there are duplicates, increase the precision.
2169     QVector<double> tempValues;
2170     tempValues.reserve(tickLabelValues.size());
2171 
2172     switch (format) {
2173     case Axis::LabelsFormat::Decimal:
2174         for (const auto value : tickLabelValues)
2175             tempValues.append(nsl_math_round_places(value, precision));
2176         break;
2177     case Axis::LabelsFormat::MultipliesPi:
2178         for (const auto value : tickLabelValues)
2179             tempValues.append(nsl_math_round_places(value / M_PI, precision));
2180         break;
2181     case Axis::LabelsFormat::ScientificE:
2182     case Axis::LabelsFormat::Scientific:
2183         for (const auto value : tickLabelValues) {
2184             int e;
2185             const double frac = nsl_math_frexp10(value, &e);
2186             // DEBUG(Q_FUNC_INFO << ", frac = " << frac << ", exp = " << e)
2187             tempValues.append(nsl_math_round_precision(frac, precision) * gsl_pow_int(10., e));
2188         }
2189         break;
2190     case Axis::LabelsFormat::Powers10:
2191         for (const auto value : tickLabelValues) {
2192             if (value == 0)
2193                 tempValues.append(log10(DBL_MIN));
2194             else {
2195                 // DEBUG(Q_FUNC_INFO << ", rounded value = " << nsl_math_round_places(log10(std::abs(value)), precision))
2196                 tempValues.append(nsl_math_round_places(log10(std::abs(value)), precision));
2197             }
2198         }
2199         break;
2200     case Axis::LabelsFormat::Powers2:
2201         for (const auto value : tickLabelValues) {
2202             if (value == 0)
2203                 tempValues.append(log2(DBL_MIN));
2204             else
2205                 tempValues.append(nsl_math_round_places(log2(std::abs(value)), precision));
2206         }
2207         break;
2208     case Axis::LabelsFormat::PowersE:
2209         for (const auto value : tickLabelValues) {
2210             if (value == 0)
2211                 tempValues.append(log(DBL_MIN));
2212             else
2213                 tempValues.append(nsl_math_round_places(log(std::abs(value)), precision));
2214         }
2215     }
2216     // QDEBUG(Q_FUNC_INFO << ", rounded values: " << tempValues)
2217 
2218     const double scaling = std::abs(tickLabelValues.last() - tickLabelValues.first());
2219     DEBUG(Q_FUNC_INFO << ", scaling = " << scaling)
2220     for (int i = 0; i < tempValues.size(); ++i) {
2221         // check if rounded value differs too much
2222         double relDiff = 0;
2223         // DEBUG(Q_FUNC_INFO << ", round value = " << tempValues.at(i) << ", tick label =  " << tickLabelValues.at(i))
2224         switch (format) {
2225         case Axis::LabelsFormat::Decimal:
2226         case Axis::LabelsFormat::Scientific:
2227         case Axis::LabelsFormat::ScientificE:
2228             relDiff = std::abs(tempValues.at(i) - tickLabelValues.at(i)) / scaling;
2229             break;
2230         case Axis::LabelsFormat::MultipliesPi:
2231             relDiff = std::abs(M_PI * tempValues.at(i) - tickLabelValues.at(i)) / scaling;
2232             break;
2233         case Axis::LabelsFormat::Powers10:
2234             relDiff = std::abs(nsl_sf_exp10(tempValues.at(i)) - tickLabelValues.at(i)) / scaling;
2235             break;
2236         case Axis::LabelsFormat::Powers2:
2237             relDiff = std::abs(exp2(tempValues.at(i)) - tickLabelValues.at(i)) / scaling;
2238             break;
2239         case Axis::LabelsFormat::PowersE:
2240             relDiff = std::abs(exp(tempValues.at(i)) - tickLabelValues.at(i)) / scaling;
2241         }
2242         // DEBUG(Q_FUNC_INFO << ", rel. diff = " << relDiff)
2243         for (int j = 0; j < tempValues.size(); ++j) {
2244             if (i == j)
2245                 continue;
2246 
2247             // if duplicate for the current precision found or differs too much, increase the precision and check again
2248             // DEBUG(Q_FUNC_INFO << ", compare " << tempValues.at(i) << " with " << tempValues.at(j))
2249             if (tempValues.at(i) == tempValues.at(j) || relDiff > 0.01) { // > 1%
2250                 // DEBUG(Q_FUNC_INFO << ", duplicates found : " << tempValues.at(i))
2251                 return upperLabelsPrecision(precision + 1, format);
2252             }
2253         }
2254     }
2255 
2256     // no duplicates for the current precision found: return the current value
2257     DEBUG(Q_FUNC_INFO << ", upper precision = " << precision);
2258     return precision;
2259 }
2260 
2261 /*!
2262     returns highest lower limit for the precision
2263     where no duplicates for the tick label values occur.
2264 */
2265 int AxisPrivate::lowerLabelsPrecision(const int precision, const Axis::LabelsFormat format) {
2266     DEBUG(Q_FUNC_INFO << ", precision = " << precision << ", format = " << ENUM_TO_STRING(Axis, LabelsFormat, format));
2267     // round value to the current precision and look for duplicates.
2268     // if there are duplicates, decrease the precision.
2269 
2270     // no tick labels, no precision
2271     if (tickLabelValues.size() == 0)
2272         return 0;
2273 
2274     QVector<double> tempValues;
2275     tempValues.reserve(tickLabelValues.size());
2276 
2277     switch (format) {
2278     case Axis::LabelsFormat::Decimal:
2279         for (auto value : tickLabelValues)
2280             tempValues.append(nsl_math_round_places(value, precision));
2281         break;
2282     case Axis::LabelsFormat::MultipliesPi:
2283         for (auto value : tickLabelValues)
2284             tempValues.append(nsl_math_round_places(value / M_PI, precision));
2285         break;
2286     case Axis::LabelsFormat::ScientificE:
2287     case Axis::LabelsFormat::Scientific:
2288         for (auto value : tickLabelValues) {
2289             int e;
2290             const double frac = nsl_math_frexp10(value, &e);
2291             // DEBUG(Q_FUNC_INFO << ", frac = " << frac << ", exp = " << e)
2292             // DEBUG(Q_FUNC_INFO << ", rounded frac = " << nsl_math_round_precision(frac, precision))
2293             tempValues.append(nsl_math_round_precision(frac, precision) * gsl_pow_int(10., e));
2294         }
2295         break;
2296     case Axis::LabelsFormat::Powers10:
2297         for (auto value : tickLabelValues) {
2298             if (value == 0)
2299                 tempValues.append(log10(DBL_MIN));
2300             else
2301                 tempValues.append(nsl_math_round_places(log10(std::abs(value)), precision));
2302         }
2303         break;
2304     case Axis::LabelsFormat::Powers2:
2305         for (auto value : tickLabelValues) {
2306             if (value == 0)
2307                 tempValues.append(log2(DBL_MIN));
2308             else
2309                 tempValues.append(nsl_math_round_places(log2(std::abs(value)), precision));
2310         }
2311         break;
2312     case Axis::LabelsFormat::PowersE:
2313         for (auto value : tickLabelValues) {
2314             if (value == 0)
2315                 tempValues.append(log(DBL_MIN));
2316             else
2317                 tempValues.append(nsl_math_round_places(log(std::abs(value)), precision));
2318         }
2319     }
2320     // QDEBUG(Q_FUNC_INFO << ", rounded values = " << tempValues)
2321 
2322     // check whether we have duplicates with reduced precision
2323     //-> current precision cannot be reduced, return the previous value
2324     const double scale = std::abs(tickLabelValues.last() - tickLabelValues.first());
2325     // DEBUG(Q_FUNC_INFO << ", scale = " << scale)
2326     for (int i = 0; i < tempValues.size(); ++i) {
2327         // return if rounded value differs too much
2328         double relDiff = 0;
2329         switch (format) {
2330         case Axis::LabelsFormat::Decimal:
2331         case Axis::LabelsFormat::Scientific:
2332         case Axis::LabelsFormat::ScientificE:
2333             relDiff = std::abs(tempValues.at(i) - tickLabelValues.at(i)) / scale;
2334             break;
2335         case Axis::LabelsFormat::MultipliesPi:
2336             relDiff = std::abs(M_PI * tempValues.at(i) - tickLabelValues.at(i)) / scale;
2337             break;
2338         case Axis::LabelsFormat::Powers10:
2339             relDiff = std::abs(nsl_sf_exp10(tempValues.at(i)) - tickLabelValues.at(i)) / scale;
2340             break;
2341         case Axis::LabelsFormat::Powers2:
2342             relDiff = std::abs(exp2(tempValues.at(i)) - tickLabelValues.at(i)) / scale;
2343             break;
2344         case Axis::LabelsFormat::PowersE:
2345             relDiff = std::abs(exp(tempValues.at(i)) - tickLabelValues.at(i)) / scale;
2346         }
2347         // DEBUG(Q_FUNC_INFO << ", rel. diff = " << relDiff)
2348 
2349         if (relDiff > 0.01) // > 1 %
2350             return precision + 1;
2351         for (int j = 0; j < tempValues.size(); ++j) {
2352             if (i == j)
2353                 continue;
2354             if (tempValues.at(i) == tempValues.at(j))
2355                 return precision + 1;
2356         }
2357     }
2358 
2359     // no duplicates found, reduce further, and check again
2360     if (precision > 0)
2361         return lowerLabelsPrecision(precision - 1, format);
2362 
2363     return 0;
2364 }
2365 
2366 /*!
2367     recalculates the position of the tick labels.
2368     Called when the geometry related properties (position, offset, font size, suffix, prefix) of the labels are changed.
2369  */
2370 void AxisPrivate::retransformTickLabelPositions() {
2371     tickLabelPoints.clear();
2372     if (majorTicksDirection == Axis::noTicks || labelsPosition == Axis::LabelsPosition::NoLabels) {
2373         recalcShapeAndBoundingRect();
2374         return;
2375     }
2376 
2377     QFontMetrics fm(labelsFont);
2378     double width = 0, height = fm.ascent();
2379     QPointF pos;
2380 
2381     //  const int xIndex{ q->cSystem->index(Dimension::X) }, yIndex{ q->cSystem->index(Dimension::Y) };
2382     DEBUG(Q_FUNC_INFO << ' ' << STDSTRING(title->name()) << ", coordinate system index = " << q->m_cSystemIndex)
2383     //  DEBUG(Q_FUNC_INFO << ", x range " << xIndex+1)
2384     //  DEBUG(Q_FUNC_INFO << ", y range " << yIndex+1)
2385     auto cs = plot()->coordinateSystem(q->coordinateSystemIndex());
2386     const double middleX = plot()->range(Dimension::X, cs->index(Dimension::X)).center();
2387     const double middleY = plot()->range(Dimension::Y, cs->index(Dimension::Y)).center();
2388     QPointF center(middleX, middleY);
2389     //  const int xDirection = q->cSystem->direction(Dimension::X);
2390     //  const int yDirection = q->cSystem->direction(Dimension::Y);
2391 
2392     //  QPointF startPoint, endPoint, anchorPoint;
2393 
2394     QTextDocument td;
2395     td.setDefaultFont(labelsFont);
2396     const double cosine = std::cos(qDegreesToRadians(labelsRotationAngle)); // calculate only once
2397     const double sine = std::sin(qDegreesToRadians(labelsRotationAngle)); // calculate only once
2398 
2399     int size = std::min(majorTickPoints.size(), tickLabelStrings.size());
2400     auto xRangeFormat{plot()->range(Dimension::X, cs->index(Dimension::X)).format()};
2401     auto yRangeFormat{plot()->range(Dimension::Y, cs->index(Dimension::Y)).format()};
2402     for (int i = 0; i < size; i++) {
2403         if ((orientation == Axis::Orientation::Horizontal && xRangeFormat == RangeT::Format::Numeric)
2404             || (orientation == Axis::Orientation::Vertical && yRangeFormat == RangeT::Format::Numeric)) {
2405             if (labelsFormat == Axis::LabelsFormat::Decimal || labelsFormat == Axis::LabelsFormat::ScientificE) {
2406                 width = fm.boundingRect(tickLabelStrings.at(i)).width();
2407             } else {
2408                 td.setHtml(tickLabelStrings.at(i));
2409                 width = td.size().width();
2410                 height = td.size().height();
2411             }
2412         } else { // Datetime
2413             width = fm.boundingRect(tickLabelStrings.at(i)).width();
2414         }
2415 
2416         const double diffx = cosine * width;
2417         const double diffy = sine * width;
2418         QPointF anchorPoint = majorTickPoints.at(i);
2419 
2420         // center align all labels with respect to the end point of the tick line
2421         const int xRangeDirection = plot()->range(Dimension::X, cs->index(Dimension::X)).direction();
2422         const int yRangeDirection = plot()->range(Dimension::Y, cs->index(Dimension::Y)).direction();
2423         //      DEBUG(Q_FUNC_INFO << ", x/y range direction = " << xRangeDirection << "/" << yRangeDirection)
2424         const int xDirection = q->cSystem->direction(Dimension::X) * xRangeDirection;
2425         const int yDirection = q->cSystem->direction(Dimension::Y) * yRangeDirection;
2426         //      DEBUG(Q_FUNC_INFO << ", x/y direction = " << xDirection << "/" << yDirection)
2427         QPointF startPoint, endPoint;
2428         if (orientation == Axis::Orientation::Horizontal) {
2429             if (anchorPoint.y() >= center.y()) { // below
2430                 startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn) ? yDirection * majorTicksLength : 0);
2431                 endPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut) ? -yDirection * majorTicksLength : 0);
2432             } else { // above
2433                 startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut) ? yDirection * majorTicksLength : 0);
2434                 endPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn) ? -yDirection * majorTicksLength : 0);
2435             }
2436 
2437             // for rotated labels (angle is not zero), align label's corner at the position of the tick
2438             if (std::abs(std::abs(labelsRotationAngle) - 180.) < 1.e-2) { // +-180°
2439                 if (labelsPosition == Axis::LabelsPosition::Out) {
2440                     pos.setX(endPoint.x() + width / 2);
2441                     pos.setY(endPoint.y() + labelsOffset);
2442                 } else {
2443                     pos.setX(startPoint.x() + width / 2);
2444                     pos.setY(startPoint.y() - height - labelsOffset);
2445                 }
2446             } else if (labelsRotationAngle <= -0.01) { // [-0.01°, -180°)
2447                 if (labelsPosition == Axis::LabelsPosition::Out) {
2448                     pos.setX(endPoint.x() + sine * height / 2);
2449                     pos.setY(endPoint.y() + labelsOffset + cosine * height / 2);
2450                 } else {
2451                     pos.setX(startPoint.x() + sine * height / 2 - diffx);
2452                     pos.setY(startPoint.y() - labelsOffset + cosine * height / 2 + diffy);
2453                 }
2454             } else if (labelsRotationAngle >= 0.01) { // [0.01°, 180°)
2455                 if (labelsPosition == Axis::LabelsPosition::Out) {
2456                     pos.setX(endPoint.x() - diffx + sine * height / 2);
2457                     pos.setY(endPoint.y() + labelsOffset + diffy + cosine * height / 2);
2458                 } else {
2459                     pos.setX(startPoint.x() + sine * height / 2);
2460                     pos.setY(startPoint.y() - labelsOffset + cosine * height / 2);
2461                 }
2462             } else { // 0°
2463                 if (labelsPosition == Axis::LabelsPosition::Out) {
2464                     pos.setX(endPoint.x() - width / 2);
2465                     pos.setY(endPoint.y() + height + labelsOffset);
2466                 } else {
2467                     pos.setX(startPoint.x() - width / 2);
2468                     pos.setY(startPoint.y() - labelsOffset);
2469                 }
2470             }
2471         } else { // ---------------------- vertical -------------------------
2472             if (anchorPoint.x() < center.x()) {
2473                 startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn) ? xDirection * majorTicksLength : 0, 0);
2474                 endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? -xDirection * majorTicksLength : 0, 0);
2475             } else {
2476                 startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? xDirection * majorTicksLength : 0, 0);
2477                 endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn) ? -xDirection * majorTicksLength : 0, 0);
2478             }
2479 
2480             if (std::abs(labelsRotationAngle - 90.) < 1.e-2) { // +90°
2481                 if (labelsPosition == Axis::LabelsPosition::Out) {
2482                     pos.setX(endPoint.x() - labelsOffset);
2483                     pos.setY(endPoint.y() + width / 2);
2484                 } else {
2485                     pos.setX(startPoint.x() + labelsOffset);
2486                     pos.setY(startPoint.y() + width / 2);
2487                 }
2488             } else if (std::abs(labelsRotationAngle + 90.) < 1.e-2) { // -90°
2489                 if (labelsPosition == Axis::LabelsPosition::Out) {
2490                     pos.setX(endPoint.x() - labelsOffset - height);
2491                     pos.setY(endPoint.y() - width / 2);
2492                 } else {
2493                     pos.setX(startPoint.x() + labelsOffset);
2494                     pos.setY(startPoint.y() - width / 2);
2495                 }
2496             } else if (std::abs(std::abs(labelsRotationAngle) - 180.) < 1.e-2) { // +-180°
2497                 if (labelsPosition == Axis::LabelsPosition::Out) {
2498                     pos.setX(endPoint.x() - labelsOffset);
2499                     pos.setY(endPoint.y() - height / 2);
2500                 } else {
2501                     pos.setX(startPoint.x() + labelsOffset + width);
2502                     pos.setY(startPoint.y() - height / 2);
2503                 }
2504             } else if (std::abs(labelsRotationAngle) >= 0.01 && std::abs(labelsRotationAngle) <= 89.99) { // [0.01°, 90°)
2505                 if (labelsPosition == Axis::LabelsPosition::Out) {
2506                     // left
2507                     pos.setX(endPoint.x() - labelsOffset - diffx + sine * height / 2);
2508                     pos.setY(endPoint.y() + cosine * height / 2 + diffy);
2509                 } else {
2510                     pos.setX(startPoint.x() + labelsOffset + sine * height / 2);
2511                     pos.setY(startPoint.y() + cosine * height / 2);
2512                 }
2513             } else if (std::abs(labelsRotationAngle) >= 90.01 && std::abs(labelsRotationAngle) <= 179.99) { // [90.01, 180)
2514                 if (labelsPosition == Axis::LabelsPosition::Out) {
2515                     // left
2516                     pos.setX(endPoint.x() - labelsOffset + sine * height / 2);
2517                     pos.setY(endPoint.y() + cosine * height / 2);
2518                 } else {
2519                     pos.setX(startPoint.x() + labelsOffset - diffx + sine * height / 2);
2520                     pos.setY(startPoint.y() + diffy + cosine * height / 2);
2521                 }
2522             } else { // 0°
2523                 if (labelsPosition == Axis::LabelsPosition::Out) {
2524                     pos.setX(endPoint.x() - width - labelsOffset);
2525                     pos.setY(endPoint.y() + height / 2);
2526                 } else {
2527                     pos.setX(startPoint.x() + labelsOffset);
2528                     pos.setY(startPoint.y() + height / 2);
2529                 }
2530             }
2531         }
2532         tickLabelPoints << pos;
2533     }
2534 
2535     recalcShapeAndBoundingRect();
2536 }
2537 
2538 void AxisPrivate::retransformMajorGrid() {
2539     if (suppressRetransform)
2540         return;
2541 
2542     majorGridPath = QPainterPath();
2543     if (majorGridLine->pen().style() == Qt::NoPen || majorTickPoints.size() == 0) {
2544         recalcShapeAndBoundingRect();
2545         return;
2546     }
2547 
2548     // major tick points are already in scene coordinates, convert them back to logical...
2549     // TODO: mapping should work without SuppressPageClipping-flag, check float comparisons in the map-function.
2550     // Currently, grid lines disappear sometimes without this flag
2551     QVector<QPointF> logicalMajorTickPoints = q->cSystem->mapSceneToLogical(majorTickPoints, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
2552     // for (auto p : logicalMajorTickPoints)
2553     //  QDEBUG(Q_FUNC_INFO << ", logical major tick: " << QString::number(p.x(), 'g', 12) << " = " << QDateTime::fromMSecsSinceEpoch(p.x(), Qt::UTC))
2554 
2555     if (logicalMajorTickPoints.isEmpty())
2556         return;
2557 
2558     DEBUG(Q_FUNC_INFO << ' ' << STDSTRING(title->name()) << ", coordinate system " << q->m_cSystemIndex + 1)
2559     DEBUG(Q_FUNC_INFO << ", x range " << q->cSystem->index(Dimension::X) + 1)
2560     DEBUG(Q_FUNC_INFO << ", y range " << q->cSystem->index(Dimension::Y) + 1)
2561     auto cs = plot()->coordinateSystem(q->coordinateSystemIndex());
2562     const auto xRange{plot()->range(Dimension::X, cs->index(Dimension::X))};
2563     const auto yRange{plot()->range(Dimension::Y, cs->index(Dimension::Y))};
2564 
2565     // TODO:
2566     // when iterating over all grid lines, skip the first and the last points for auto scaled axes,
2567     // since we don't want to paint any grid lines at the plot boundaries
2568     bool skipLowestTick, skipUpperTick;
2569     if (orientation == Axis::Orientation::Horizontal) { // horizontal axis
2570         skipLowestTick = qFuzzyCompare(logicalMajorTickPoints.at(0).x(), xRange.start());
2571         skipUpperTick = qFuzzyCompare(logicalMajorTickPoints.at(logicalMajorTickPoints.size() - 1).x(), xRange.end());
2572     } else {
2573         skipLowestTick = qFuzzyCompare(logicalMajorTickPoints.at(0).y(), yRange.start());
2574         skipUpperTick = qFuzzyCompare(logicalMajorTickPoints.at(logicalMajorTickPoints.size() - 1).y(), yRange.end());
2575     }
2576 
2577     int start, end; // TODO: hides Axis::start, Axis::end!
2578     if (skipLowestTick) {
2579         if (logicalMajorTickPoints.size() > 1)
2580             start = 1;
2581         else
2582             start = 0;
2583     } else {
2584         start = 0;
2585     }
2586 
2587     if (skipUpperTick) {
2588         if (logicalMajorTickPoints.size() > 1)
2589             end = logicalMajorTickPoints.size() - 1;
2590         else
2591             end = 0;
2592 
2593     } else {
2594         end = logicalMajorTickPoints.size();
2595     }
2596 
2597     QVector<QLineF> lines;
2598     if (orientation == Axis::Orientation::Horizontal) { // horizontal axis
2599         for (int i = start; i < end; ++i) {
2600             const QPointF& point = logicalMajorTickPoints.at(i);
2601             lines.append(QLineF(point.x(), yRange.start(), point.x(), yRange.end()));
2602         }
2603     } else { // vertical axis
2604         // skip the first and the last points, since we don't want to paint any grid lines at the plot boundaries
2605         for (int i = start; i < end; ++i) {
2606             const QPointF& point = logicalMajorTickPoints.at(i);
2607             lines.append(QLineF(xRange.start(), point.y(), xRange.end(), point.y()));
2608         }
2609     }
2610 
2611     lines = q->cSystem->mapLogicalToScene(lines, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
2612     for (const auto& line : lines) {
2613         majorGridPath.moveTo(line.p1());
2614         majorGridPath.lineTo(line.p2());
2615     }
2616 
2617     recalcShapeAndBoundingRect();
2618 }
2619 
2620 void AxisPrivate::retransformMinorGrid() {
2621     if (suppressRetransform)
2622         return;
2623 
2624     minorGridPath = QPainterPath();
2625     if (minorGridLine->pen().style() == Qt::NoPen) {
2626         recalcShapeAndBoundingRect();
2627         return;
2628     }
2629 
2630     // minor tick points are already in scene coordinates, convert them back to logical...
2631     // TODO: mapping should work without SuppressPageClipping-flag, check float comparisons in the map-function.
2632     // Currently, grid lines disappear sometimes without this flag
2633     QVector<QPointF> logicalMinorTickPoints = q->cSystem->mapSceneToLogical(minorTickPoints, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
2634 
2635     DEBUG(Q_FUNC_INFO << ' ' << STDSTRING(title->name()) << ", coordinate system " << q->m_cSystemIndex + 1)
2636     DEBUG(Q_FUNC_INFO << ", x range " << q->cSystem->index(Dimension::X) + 1)
2637     DEBUG(Q_FUNC_INFO << ", y range " << q->cSystem->index(Dimension::Y) + 1)
2638 
2639     QVector<QLineF> lines;
2640     auto cs = plot()->coordinateSystem(q->coordinateSystemIndex());
2641     if (orientation == Axis::Orientation::Horizontal) { // horizontal axis
2642         const Range<double> yRange{plot()->range(Dimension::Y, cs->index(Dimension::Y))};
2643 
2644         for (const auto& point : logicalMinorTickPoints)
2645             lines.append(QLineF(point.x(), yRange.start(), point.x(), yRange.end()));
2646     } else { // vertical axis
2647         const Range<double> xRange{plot()->range(Dimension::X, cs->index(Dimension::X))};
2648 
2649         for (const auto& point : logicalMinorTickPoints)
2650             lines.append(QLineF(xRange.start(), point.y(), xRange.end(), point.y()));
2651     }
2652 
2653     lines = q->cSystem->mapLogicalToScene(lines, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
2654     for (const auto& line : lines) {
2655         minorGridPath.moveTo(line.p1());
2656         minorGridPath.lineTo(line.p2());
2657     }
2658 
2659     recalcShapeAndBoundingRect();
2660 }
2661 
2662 /*!
2663  * called when the opacity of the grid was changes, update the grid graphics item
2664  */
2665 // TODO: this function is only needed for loaded projects where update() doesn't seem to be enough
2666 // and we have to call gridItem->update() explicitly.
2667 // This is not required for newly created plots/axes. Why is this difference?
2668 void AxisPrivate::updateGrid() {
2669     gridItem->update();
2670 }
2671 
2672 void AxisPrivate::recalcShapeAndBoundingRect() {
2673     if (suppressRecalc)
2674         return;
2675 
2676     prepareGeometryChange();
2677 
2678     QPainterPath tmpPath; // temp path used to calculate the bounding box for all elements that the axis consists of
2679 
2680     if (linePath.isEmpty()) {
2681         m_boundingRectangle = QRectF();
2682         title->setPositionInvalid(true);
2683         if (plot())
2684             plot()->prepareGeometryChange();
2685         return;
2686     } else {
2687         title->setPositionInvalid(false);
2688     }
2689 
2690     const auto& linePen = line->pen();
2691     tmpPath = WorksheetElement::shapeFromPath(linePath, linePen);
2692     tmpPath.addPath(WorksheetElement::shapeFromPath(arrowPath, linePen));
2693     tmpPath.addPath(WorksheetElement::shapeFromPath(majorTicksPath, majorTicksLine->pen()));
2694     tmpPath.addPath(WorksheetElement::shapeFromPath(minorTicksPath, minorTicksLine->pen()));
2695 
2696     QPainterPath tickLabelsPath = QPainterPath();
2697     if (labelsPosition != Axis::LabelsPosition::NoLabels) {
2698         QTransform trafo;
2699         QPainterPath tempPath;
2700         QFontMetrics fm(labelsFont);
2701         QTextDocument td;
2702         td.setDefaultFont(labelsFont);
2703         for (int i = 0; i < tickLabelPoints.size(); i++) {
2704             tempPath = QPainterPath();
2705             if (labelsFormat == Axis::LabelsFormat::Decimal || labelsFormat == Axis::LabelsFormat::ScientificE) {
2706                 tempPath.addRect(fm.boundingRect(tickLabelStrings.at(i)));
2707             } else {
2708                 td.setHtml(tickLabelStrings.at(i));
2709                 tempPath.addRect(QRectF(0, -td.size().height(), td.size().width(), td.size().height()));
2710             }
2711 
2712             trafo.reset();
2713             trafo.translate(tickLabelPoints.at(i).x(), tickLabelPoints.at(i).y());
2714 
2715             trafo.rotate(-labelsRotationAngle);
2716             tempPath = trafo.map(tempPath);
2717 
2718             tickLabelsPath.addPath(WorksheetElement::shapeFromPath(tempPath, linePen));
2719         }
2720         tmpPath.addPath(WorksheetElement::shapeFromPath(tickLabelsPath, QPen()));
2721     }
2722 
2723     const auto margin = (double)hoverSelectionEffectPenWidth / 2;
2724     const auto axisRect = tmpPath.boundingRect().marginsRemoved(QMarginsF(margin, margin, margin, margin));
2725     tmpPath.addRect(axisRect); // add rect instead of the actual path for ticks - this is done for performance reasons,the calculation for many ticks and long
2726                                // tick texts can be very expensive
2727 
2728     // add title label, if available
2729     QTextDocument doc; // text may be Html, so check if plain text is empty
2730     doc.setHtml(title->text().text);
2731     // QDEBUG(Q_FUNC_INFO << ", title text plain: " << doc.toPlainText())
2732     QPainterPath titlePath;
2733     if (title->isVisible() && !doc.toPlainText().isEmpty()) {
2734         const QRectF& titleRect = title->graphicsItem()->boundingRect();
2735         if (titleRect.size() != QSizeF(0, 0)) {
2736             // determine the new position of the title label:
2737             // we calculate the new position here and not in retransform(),
2738             // since it depends on the size and position of the tick labels, tickLabelsPath, available here.
2739             QRectF rect = linePath.boundingRect();
2740             qreal offsetX = titleOffsetX, offsetY = titleOffsetY; // the distances to the axis line
2741             if (orientation == Axis::Orientation::Horizontal) {
2742                 offsetY -= titleRect.height() * title->scale() / 2.;
2743                 if (labelsPosition == Axis::LabelsPosition::Out)
2744                     offsetY -= labelsOffset + tickLabelsPath.boundingRect().height();
2745                 title->setPosition(QPointF((rect.topLeft().x() + rect.topRight().x()) / 2. + titleOffsetX, rect.bottomLeft().y() - offsetY));
2746             } else {
2747                 offsetX -= titleRect.height() * title->scale() / 2.;
2748                 if (labelsPosition == Axis::LabelsPosition::Out)
2749                     offsetX -= labelsOffset + tickLabelsPath.boundingRect().width();
2750                 title->setPosition(QPointF(rect.topLeft().x() + offsetX, (rect.topLeft().y() + rect.bottomLeft().y()) / 2. - titleOffsetY));
2751             }
2752             titlePath = WorksheetElement::shapeFromPath(title->graphicsItem()->mapToParent(title->graphicsItem()->shape()), linePen);
2753             tmpPath.addPath(titlePath);
2754         }
2755     }
2756 
2757     m_boundingRectangle = tmpPath.boundingRect();
2758     m_shape = QPainterPath();
2759     m_shape.addRect(m_boundingRectangle);
2760 
2761     // if the axis goes beyond the current bounding box of the plot (too high offset is used, too long labels etc.)
2762     // request a prepareGeometryChange() for the plot in order to properly keep track of geometry changes
2763     if (plot())
2764         plot()->prepareGeometryChange();
2765 }
2766 
2767 /*!
2768     paints the content of the axis. Reimplemented from \c QGraphicsItem.
2769     \sa QGraphicsItem::paint()
2770  */
2771 void AxisPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget*) {
2772     if (!isVisible() || linePath.isEmpty())
2773         return;
2774 
2775     // draw the line
2776     if (line->pen().style() != Qt::NoPen) {
2777         painter->setOpacity(line->opacity());
2778         painter->setPen(line->pen());
2779         painter->drawPath(linePath);
2780 
2781         // DUMP_PAINTER_PATH(linePath);
2782 
2783         // draw the arrow
2784         if (arrowType != Axis::ArrowType::NoArrow) {
2785             painter->setBrush(QBrush(line->color(), Qt::SolidPattern));
2786             painter->drawPath(arrowPath);
2787         }
2788     }
2789 
2790     // draw the major ticks
2791     if (majorTicksDirection != Axis::noTicks) {
2792         painter->setOpacity(majorTicksLine->opacity());
2793         painter->setPen(majorTicksLine->pen());
2794         painter->setBrush(Qt::NoBrush);
2795         painter->drawPath(majorTicksPath);
2796     }
2797 
2798     // draw the minor ticks
2799     if (minorTicksDirection != Axis::noTicks) {
2800         painter->setOpacity(minorTicksLine->opacity());
2801         painter->setPen(minorTicksLine->pen());
2802         painter->setBrush(Qt::NoBrush);
2803         painter->drawPath(minorTicksPath);
2804     }
2805 
2806     // draw tick labels
2807     if (labelsPosition != Axis::LabelsPosition::NoLabels) {
2808         auto cs = plot()->coordinateSystem(q->coordinateSystemIndex());
2809         painter->setOpacity(labelsOpacity);
2810         painter->setPen(QPen(labelsColor));
2811         painter->setFont(labelsFont);
2812         QTextDocument doc;
2813         doc.setDefaultFont(labelsFont);
2814         QFontMetrics fm(labelsFont);
2815         auto xRangeFormat{plot()->range(Dimension::X, cs->index(Dimension::X)).format()};
2816         auto yRangeFormat{plot()->range(Dimension::Y, cs->index(Dimension::Y)).format()};
2817         if ((orientation == Axis::Orientation::Horizontal && xRangeFormat == RangeT::Format::Numeric)
2818             || (orientation == Axis::Orientation::Vertical && yRangeFormat == RangeT::Format::Numeric)) {
2819             // QDEBUG(Q_FUNC_INFO << ", axis tick label strings: " << tickLabelStrings)
2820             for (int i = 0; i < tickLabelPoints.size(); i++) {
2821                 painter->translate(tickLabelPoints.at(i));
2822                 painter->save();
2823                 painter->rotate(-labelsRotationAngle);
2824 
2825                 if (labelsFormat == Axis::LabelsFormat::Decimal || labelsFormat == Axis::LabelsFormat::ScientificE) {
2826                     if (labelsBackgroundType != Axis::LabelsBackgroundType::Transparent) {
2827                         const QRect& rect = fm.boundingRect(tickLabelStrings.at(i));
2828                         painter->fillRect(rect, labelsBackgroundColor);
2829                     }
2830                     painter->drawText(QPoint(0, 0), tickLabelStrings.at(i));
2831                 } else {
2832                     const QString style(QStringLiteral("p {color: %1;}"));
2833                     doc.setDefaultStyleSheet(style.arg(labelsColor.name()));
2834                     doc.setHtml(QStringLiteral("<p>") + tickLabelStrings.at(i) + QStringLiteral("</p>"));
2835                     QSizeF size = doc.size();
2836                     int height = size.height();
2837                     if (labelsBackgroundType != Axis::LabelsBackgroundType::Transparent) {
2838                         int width = size.width();
2839                         painter->fillRect(0, -height, width, height, labelsBackgroundColor);
2840                     }
2841                     painter->translate(0, -height);
2842                     doc.drawContents(painter);
2843                 }
2844                 painter->restore();
2845                 painter->translate(-tickLabelPoints.at(i));
2846             }
2847         } else { // datetime
2848             for (int i = 0; i < tickLabelPoints.size(); i++) {
2849                 painter->translate(tickLabelPoints.at(i));
2850                 painter->save();
2851                 painter->rotate(-labelsRotationAngle);
2852                 if (labelsBackgroundType != Axis::LabelsBackgroundType::Transparent) {
2853                     const QRect& rect = fm.boundingRect(tickLabelStrings.at(i));
2854                     painter->fillRect(rect, labelsBackgroundColor);
2855                 }
2856                 painter->drawText(QPoint(0, 0), tickLabelStrings.at(i));
2857                 painter->restore();
2858                 painter->translate(-tickLabelPoints.at(i));
2859             }
2860         }
2861 
2862         // scale + offset label
2863         if (showScaleOffset && tickLabelPoints.size() > 0) {
2864             QString text;
2865             const auto numberLocale = QLocale();
2866             if (scalingFactor != 1)
2867                 text += UTF8_QSTRING("×") + numberLocale.toString(1. / scalingFactor);
2868             if (zeroOffset != 0) {
2869                 if (zeroOffset < 0)
2870                     text += QLatin1String("+");
2871                 text += numberLocale.toString(-zeroOffset);
2872             }
2873 
2874             // used to determinde direction (up/down, left/right)
2875             auto cs = plot()->coordinateSystem(q->coordinateSystemIndex());
2876             const qreal middleX = plot()->range(Dimension::X, cs->index(Dimension::X)).center();
2877             const qreal middleY = plot()->range(Dimension::Y, cs->index(Dimension::Y)).center();
2878             QPointF center(middleX, middleY);
2879             bool valid = true;
2880             center = q->cSystem->mapLogicalToScene(center, valid);
2881 
2882             QPointF lastTickPoint = tickLabelPoints.at(tickLabelPoints.size() - 1);
2883             QPointF labelPosition;
2884             QFontMetrics fm(labelsFont);
2885             if (orientation == Axis::Orientation::Horizontal) {
2886                 if (center.y() < lastTickPoint.y())
2887                     labelPosition = QPointF(-fm.boundingRect(text).width(), 40);
2888                 else
2889                     labelPosition = QPointF(-fm.boundingRect(text).width(), -40);
2890             } else {
2891                 if (center.x() < lastTickPoint.x())
2892                     labelPosition = QPointF(40, 40);
2893                 else
2894                     labelPosition = QPointF(-fm.boundingRect(text).width() - 10, 40);
2895             }
2896             const QPointF offsetLabelPoint = lastTickPoint + labelPosition;
2897             painter->translate(offsetLabelPoint);
2898             // TODO: own format, rotation, etc.
2899             //  painter->save();
2900             painter->drawText(QPoint(0, 0), text);
2901             //  painter->restore();
2902             painter->translate(-offsetLabelPoint);
2903         }
2904     }
2905 
2906     // shape and label
2907     if (m_hovered && !isSelected() && !q->isPrinting()) {
2908         painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), hoverSelectionEffectPenWidth, Qt::SolidLine));
2909         painter->drawPath(m_shape);
2910     }
2911 
2912     if (isSelected() && !q->isPrinting()) {
2913         painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), hoverSelectionEffectPenWidth, Qt::SolidLine));
2914         painter->drawPath(m_shape);
2915     }
2916 
2917 #if DEBUG_AXIS_BOUNDING_RECT
2918     painter->setPen(QColor(Qt::GlobalColor::blue));
2919     painter->drawRect(boundingRect());
2920 #endif
2921 }
2922 
2923 void AxisPrivate::mousePressEvent(QGraphicsSceneMouseEvent* event) {
2924     auto* plot = static_cast<CartesianPlot*>(q->parentAspect());
2925     if (plot->isInteractive()) {
2926         m_panningStarted = true;
2927         m_panningStart = event->pos();
2928     } else
2929         QGraphicsItem::mousePressEvent(event);
2930 }
2931 
2932 void AxisPrivate::mouseMoveEvent(QGraphicsSceneMouseEvent* event) {
2933     if (m_panningStarted) {
2934         Dimension dim = Dimension::X;
2935         int delta = 0;
2936         auto cs = plot()->coordinateSystem(q->coordinateSystemIndex());
2937         if (orientation == WorksheetElement::Orientation::Horizontal) {
2938             setCursor(Qt::SizeHorCursor);
2939             delta = (m_panningStart.x() - event->pos().x());
2940             if (std::abs(delta) < 5.)
2941                 return;
2942             dim = Dimension::X;
2943         } else {
2944             setCursor(Qt::SizeVerCursor);
2945             delta = (m_panningStart.y() - event->pos().y());
2946             if (std::abs(delta) < 5.)
2947                 return;
2948             dim = Dimension::Y;
2949         }
2950 
2951         Q_EMIT q->shiftSignal(delta, dim, cs->index(dim));
2952 
2953         m_panningStart = event->pos();
2954     }
2955 }
2956 
2957 void AxisPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) {
2958     setCursor(Qt::ArrowCursor);
2959     m_panningStarted = false;
2960     QGraphicsItem::mouseReleaseEvent(event);
2961 }
2962 
2963 QString AxisPrivate::createScientificRepresentation(const QString& mantissa, const QString& exponent) {
2964     return mantissa + QStringLiteral("×10<sup>") + exponent + QStringLiteral("</sup>");
2965 }
2966 
2967 // ##############################################################################
2968 // ##################  Serialization/Deserialization  ###########################
2969 // ##############################################################################
2970 //! Save as XML
2971 void Axis::save(QXmlStreamWriter* writer) const {
2972     Q_D(const Axis);
2973 
2974     writer->writeStartElement(QStringLiteral("axis"));
2975     writeBasicAttributes(writer);
2976     writeCommentElement(writer);
2977 
2978     // general
2979     writer->writeStartElement(QStringLiteral("general"));
2980     writer->writeAttribute(QStringLiteral("rangeType"), QString::number(static_cast<int>(d->rangeType)));
2981     writer->writeAttribute(QStringLiteral("orientation"), QString::number(static_cast<int>(d->orientation)));
2982     writer->writeAttribute(QStringLiteral("position"), QString::number(static_cast<int>(d->position)));
2983     writer->writeAttribute(QStringLiteral("scale"), QString::number(static_cast<int>(d->scale)));
2984     writer->writeAttribute(QStringLiteral("rangeScale"), QString::number(static_cast<int>(d->rangeScale)));
2985     writer->writeAttribute(QStringLiteral("offset"), QString::number(d->offset));
2986     writer->writeAttribute(QStringLiteral("logicalPosition"), QString::number(d->logicalPosition));
2987     writer->writeAttribute(QStringLiteral("scaleRange"), QString::number(static_cast<int>(d->range.scale())));
2988     writer->writeAttribute(QStringLiteral("start"), QString::number(d->range.start(), 'g', 12));
2989     writer->writeAttribute(QStringLiteral("end"), QString::number(d->range.end(), 'g', 12));
2990     writer->writeAttribute(QStringLiteral("majorTicksStartType"), QString::number(static_cast<int>(d->majorTicksStartType)));
2991     writer->writeAttribute(QStringLiteral("majorTickStartOffset"), QString::number(d->majorTickStartOffset));
2992     writer->writeAttribute(QStringLiteral("majorTickStartValue"), QString::number(d->majorTickStartValue));
2993     writer->writeAttribute(QStringLiteral("scalingFactor"), QString::number(d->scalingFactor));
2994     writer->writeAttribute(QStringLiteral("zeroOffset"), QString::number(d->zeroOffset));
2995     writer->writeAttribute(QStringLiteral("showScaleOffset"), QString::number(d->showScaleOffset));
2996     writer->writeAttribute(QStringLiteral("titleOffsetX"), QString::number(d->titleOffsetX));
2997     writer->writeAttribute(QStringLiteral("titleOffsetY"), QString::number(d->titleOffsetY));
2998     writer->writeAttribute(QStringLiteral("plotRangeIndex"), QString::number(m_cSystemIndex));
2999     writer->writeAttribute(QStringLiteral("visible"), QString::number(d->isVisible()));
3000     writer->writeEndElement();
3001 
3002     // label
3003     d->title->save(writer);
3004 
3005     // line
3006     writer->writeStartElement(QStringLiteral("line"));
3007     d->line->save(writer);
3008     writer->writeAttribute(QStringLiteral("arrowType"), QString::number(static_cast<int>(d->arrowType)));
3009     writer->writeAttribute(QStringLiteral("arrowPosition"), QString::number(static_cast<int>(d->arrowPosition)));
3010     writer->writeAttribute(QStringLiteral("arrowSize"), QString::number(d->arrowSize));
3011     writer->writeEndElement();
3012 
3013     // major ticks
3014     writer->writeStartElement(QStringLiteral("majorTicks"));
3015     writer->writeAttribute(QStringLiteral("direction"), QString::number(d->majorTicksDirection));
3016     writer->writeAttribute(QStringLiteral("type"), QString::number(static_cast<int>(d->majorTicksType)));
3017     writer->writeAttribute(QStringLiteral("numberAuto"), QString::number(d->majorTicksAutoNumber));
3018     writer->writeAttribute(QStringLiteral("number"), QString::number(d->majorTicksNumber));
3019     writer->writeAttribute(QStringLiteral("increment"), QString::number(d->majorTicksSpacing));
3020     WRITE_COLUMN(d->majorTicksColumn, majorTicksColumn);
3021     writer->writeAttribute(QStringLiteral("length"), QString::number(d->majorTicksLength));
3022     d->majorTicksLine->save(writer);
3023     writer->writeEndElement();
3024 
3025     // minor ticks
3026     writer->writeStartElement(QStringLiteral("minorTicks"));
3027     writer->writeAttribute(QStringLiteral("direction"), QString::number(d->minorTicksDirection));
3028     writer->writeAttribute(QStringLiteral("type"), QString::number(static_cast<int>(d->minorTicksType)));
3029     writer->writeAttribute(QStringLiteral("numberAuto"), QString::number(d->minorTicksAutoNumber));
3030     writer->writeAttribute(QStringLiteral("number"), QString::number(d->minorTicksNumber));
3031     writer->writeAttribute(QStringLiteral("increment"), QString::number(d->minorTicksIncrement));
3032     WRITE_COLUMN(d->minorTicksColumn, minorTicksColumn);
3033     writer->writeAttribute(QStringLiteral("length"), QString::number(d->minorTicksLength));
3034     d->minorTicksLine->save(writer);
3035     writer->writeEndElement();
3036 
3037     // extra ticks
3038 
3039     // labels
3040     writer->writeStartElement(QStringLiteral("labels"));
3041     writer->writeAttribute(QStringLiteral("position"), QString::number(static_cast<int>(d->labelsPosition)));
3042     writer->writeAttribute(QStringLiteral("offset"), QString::number(d->labelsOffset));
3043     writer->writeAttribute(QStringLiteral("rotation"), QString::number(d->labelsRotationAngle));
3044     writer->writeAttribute(QStringLiteral("textType"), QString::number(static_cast<int>(d->labelsTextType)));
3045     WRITE_COLUMN(d->labelsTextColumn, labelsTextColumn);
3046     writer->writeAttribute(QStringLiteral("format"), QString::number(static_cast<int>(d->labelsFormat)));
3047     writer->writeAttribute(QStringLiteral("formatAuto"), QString::number(static_cast<int>(d->labelsFormatAuto)));
3048     writer->writeAttribute(QStringLiteral("precision"), QString::number(d->labelsPrecision));
3049     writer->writeAttribute(QStringLiteral("autoPrecision"), QString::number(d->labelsAutoPrecision));
3050     writer->writeAttribute(QStringLiteral("dateTimeFormat"), d->labelsDateTimeFormat);
3051     WRITE_QCOLOR(d->labelsColor);
3052     WRITE_QFONT(d->labelsFont);
3053     writer->writeAttribute(QStringLiteral("prefix"), d->labelsPrefix);
3054     writer->writeAttribute(QStringLiteral("suffix"), d->labelsSuffix);
3055     writer->writeAttribute(QStringLiteral("opacity"), QString::number(d->labelsOpacity));
3056     writer->writeAttribute(QStringLiteral("backgroundType"), QString::number(static_cast<int>(d->labelsBackgroundType)));
3057     writer->writeAttribute(QStringLiteral("backgroundColor_r"), QString::number(d->labelsBackgroundColor.red()));
3058     writer->writeAttribute(QStringLiteral("backgroundColor_g"), QString::number(d->labelsBackgroundColor.green()));
3059     writer->writeAttribute(QStringLiteral("backgroundColor_b"), QString::number(d->labelsBackgroundColor.blue()));
3060     writer->writeEndElement();
3061 
3062     // grid
3063     d->majorGridLine->save(writer);
3064     d->minorGridLine->save(writer);
3065 
3066     writer->writeEndElement(); // close "axis" section
3067 }
3068 
3069 //! Load from XML
3070 bool Axis::load(XmlStreamReader* reader, bool preview) {
3071     Q_D(Axis);
3072 
3073     if (!readBasicAttributes(reader))
3074         return false;
3075 
3076     QXmlStreamAttributes attribs;
3077     QString str;
3078 
3079     while (!reader->atEnd()) {
3080         reader->readNext();
3081         if (reader->isEndElement() && reader->name() == QLatin1String("axis"))
3082             break;
3083 
3084         if (!reader->isStartElement())
3085             continue;
3086 
3087         if (!preview && reader->name() == QLatin1String("comment")) {
3088             if (!readCommentElement(reader))
3089                 return false;
3090         } else if (!preview && reader->name() == QLatin1String("general")) {
3091             attribs = reader->attributes();
3092             if (Project::xmlVersion() < 5) {
3093                 bool autoScale = attribs.value(QStringLiteral("autoScale")).toInt();
3094                 if (autoScale)
3095                     d->rangeType = Axis::RangeType::Auto;
3096                 else
3097                     d->rangeType = Axis::RangeType::Custom;
3098             } else
3099                 READ_INT_VALUE("rangeType", rangeType, Axis::RangeType);
3100 
3101             READ_INT_VALUE("orientation", orientation, Orientation);
3102             READ_INT_VALUE("position", position, Axis::Position);
3103             // scale
3104             str = attribs.value(QStringLiteral("scale")).toString();
3105             if (str.isEmpty())
3106                 reader->raiseMissingAttributeWarning(QStringLiteral("scale"));
3107             else
3108                 d->scale = static_cast<RangeT::Scale>(str.toInt());
3109 
3110             str = attribs.value(QStringLiteral("rangeScale")).toString();
3111             if (str.isEmpty())
3112                 d->rangeScale = false; // backward compatibility
3113             else
3114                 d->rangeScale = static_cast<bool>(str.toInt());
3115 
3116             str = attribs.value(QStringLiteral("scaleRange")).toString();
3117             if (str.isEmpty())
3118                 d->range.scale() = d->scale; // backward compatibility
3119             else
3120                 d->rangeScale = static_cast<bool>(str.toInt());
3121 
3122             READ_DOUBLE_VALUE("offset", offset);
3123             READ_DOUBLE_VALUE("logicalPosition", logicalPosition);
3124             READ_DOUBLE_VALUE("start", range.start());
3125             READ_DOUBLE_VALUE("end", range.end());
3126             READ_INT_VALUE("majorTicksStartType", majorTicksStartType, TicksStartType);
3127             READ_DOUBLE_VALUE("majorTickStartOffset", majorTickStartOffset);
3128             READ_DOUBLE_VALUE("majorTickStartValue", majorTickStartValue);
3129             READ_DOUBLE_VALUE("scalingFactor", scalingFactor);
3130             READ_DOUBLE_VALUE("zeroOffset", zeroOffset);
3131             READ_INT_VALUE("showScaleOffset", showScaleOffset, bool);
3132             READ_DOUBLE_VALUE("titleOffsetX", titleOffsetX);
3133             READ_DOUBLE_VALUE("titleOffsetY", titleOffsetY);
3134             READ_INT_VALUE_DIRECT("plotRangeIndex", m_cSystemIndex, int);
3135 
3136             if (Project::xmlVersion() < 2) {
3137                 // earlier, offset was only used when the enum  value Custom was used.
3138                 // after the positioning rework, it is possible to specify the offset for
3139                 // all other positions like Left, Right, etc.
3140                 // Also, Custom was renamed to Logical and d->logicalPosition is used now.
3141                 // Adjust the values from older projects.
3142                 if (d->position == Axis::Position::Logical)
3143                     d->logicalPosition = d->offset;
3144                 else
3145                     d->offset = 0.0;
3146             }
3147 
3148             str = attribs.value(QStringLiteral("visible")).toString();
3149             if (str.isEmpty())
3150                 reader->raiseMissingAttributeWarning(QStringLiteral("visible"));
3151             else
3152                 d->setVisible(str.toInt());
3153         } else if (reader->name() == QLatin1String("textLabel")) {
3154             d->title->load(reader, preview);
3155         } else if (!preview && reader->name() == QLatin1String("line")) {
3156             attribs = reader->attributes();
3157             d->line->load(reader, preview);
3158             READ_INT_VALUE("arrowType", arrowType, Axis::ArrowType);
3159             READ_INT_VALUE("arrowPosition", arrowPosition, Axis::ArrowPosition);
3160             READ_DOUBLE_VALUE("arrowSize", arrowSize);
3161         } else if (!preview && reader->name() == QLatin1String("majorTicks")) {
3162             attribs = reader->attributes();
3163 
3164             READ_INT_VALUE("direction", majorTicksDirection, Axis::TicksDirection);
3165             READ_INT_VALUE("type", majorTicksType, Axis::TicksType);
3166             READ_INT_VALUE("numberAuto", majorTicksAutoNumber, bool);
3167             READ_INT_VALUE("number", majorTicksNumber, int);
3168             READ_DOUBLE_VALUE("increment", majorTicksSpacing);
3169             READ_COLUMN(majorTicksColumn);
3170             READ_DOUBLE_VALUE("length", majorTicksLength);
3171             d->majorTicksLine->load(reader, preview);
3172         } else if (!preview && reader->name() == QLatin1String("minorTicks")) {
3173             attribs = reader->attributes();
3174 
3175             READ_INT_VALUE("direction", minorTicksDirection, Axis::TicksDirection);
3176             READ_INT_VALUE("type", minorTicksType, Axis::TicksType);
3177             READ_INT_VALUE("numberAuto", minorTicksAutoNumber, bool);
3178             READ_INT_VALUE("number", minorTicksNumber, int);
3179             READ_DOUBLE_VALUE("increment", minorTicksIncrement);
3180             READ_COLUMN(minorTicksColumn);
3181             READ_DOUBLE_VALUE("length", minorTicksLength);
3182             d->minorTicksLine->load(reader, preview);
3183         } else if (!preview && reader->name() == QLatin1String("labels")) {
3184             attribs = reader->attributes();
3185 
3186             READ_INT_VALUE("position", labelsPosition, Axis::LabelsPosition);
3187             READ_DOUBLE_VALUE("offset", labelsOffset);
3188             READ_DOUBLE_VALUE("rotation", labelsRotationAngle);
3189             READ_INT_VALUE("textType", labelsTextType, Axis::LabelsTextType);
3190             READ_COLUMN(labelsTextColumn);
3191             READ_INT_VALUE("format", labelsFormat, Axis::LabelsFormat);
3192             READ_INT_VALUE("formatAuto", labelsFormatAuto, bool);
3193             READ_INT_VALUE("precision", labelsPrecision, int);
3194             READ_INT_VALUE("autoPrecision", labelsAutoPrecision, bool);
3195             d->labelsDateTimeFormat = attribs.value(QStringLiteral("dateTimeFormat")).toString();
3196             READ_QCOLOR(d->labelsColor);
3197             READ_QFONT(d->labelsFont);
3198 
3199             // don't produce any warning if no prefix or suffix is set (empty string is allowed here in xml)
3200             d->labelsPrefix = attribs.value(QStringLiteral("prefix")).toString();
3201             d->labelsSuffix = attribs.value(QStringLiteral("suffix")).toString();
3202 
3203             READ_DOUBLE_VALUE("opacity", labelsOpacity);
3204 
3205             READ_INT_VALUE("backgroundType", labelsBackgroundType, Axis::LabelsBackgroundType);
3206             str = attribs.value(QStringLiteral("backgroundColor_r")).toString();
3207             if (!str.isEmpty())
3208                 d->labelsBackgroundColor.setRed(str.toInt());
3209 
3210             str = attribs.value(QStringLiteral("backgroundColor_g")).toString();
3211             if (!str.isEmpty())
3212                 d->labelsBackgroundColor.setGreen(str.toInt());
3213 
3214             str = attribs.value(QStringLiteral("backgroundColor_b")).toString();
3215             if (!str.isEmpty())
3216                 d->labelsBackgroundColor.setBlue(str.toInt());
3217         } else if (!preview && reader->name() == QLatin1String("majorGrid")) {
3218             d->majorGridLine->load(reader, preview);
3219         } else if (!preview && reader->name() == QLatin1String("minorGrid")) {
3220             d->minorGridLine->load(reader, preview);
3221         } else { // unknown element
3222             reader->raiseUnknownElementWarning();
3223             if (!reader->skipToEndElement())
3224                 return false;
3225         }
3226     }
3227 
3228     return true;
3229 }
3230 
3231 // ##############################################################################
3232 // #########################  Theme management ##################################
3233 // ##############################################################################
3234 void Axis::loadThemeConfig(const KConfig& config) {
3235     Q_D(Axis);
3236     const KConfigGroup& group = config.group(QStringLiteral("Axis"));
3237 
3238     // we don't want to show the major and minor grid lines for non-first horizontal/vertical axes
3239     // determine the index of the axis among other axes having the same orientation
3240     bool firstAxis = true;
3241     for (const auto* axis : parentAspect()->children<Axis>()) {
3242         if (orientation() == axis->orientation()) {
3243             if (axis == this) {
3244                 break;
3245             } else {
3246                 firstAxis = false;
3247                 break;
3248             }
3249         }
3250     }
3251 
3252     // Tick label
3253     this->setLabelsColor(group.readEntry(QStringLiteral("LabelsFontColor"), QColor(Qt::black)));
3254     this->setLabelsOpacity(group.readEntry(QStringLiteral("LabelsOpacity"), 1.0));
3255 
3256     // use plot area color for the background color of the labels
3257     const KConfigGroup& groupPlot = config.group(QStringLiteral("CartesianPlot"));
3258     this->setLabelsBackgroundColor(groupPlot.readEntry(QStringLiteral("BackgroundFirstColor"), QColor(Qt::white)));
3259 
3260     // Line
3261     d->line->setColor(group.readEntry(QStringLiteral("LineColor"), QColor(Qt::black)));
3262     d->line->setWidth(group.readEntry(QStringLiteral("LineWidth"), Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point)));
3263     d->line->setOpacity(group.readEntry(QStringLiteral("LineOpacity"), 1.0));
3264 
3265     const auto* plot = static_cast<const CartesianPlot*>(parentAspect());
3266     if (firstAxis && plot->theme() == QLatin1String("Tufte")) {
3267         setRangeType(RangeType::AutoData);
3268         d->line->setStyle(Qt::SolidLine);
3269     } else {
3270         // switch back to "Auto" range type when "AutoData" was selected (either because of Tufte or manually selected),
3271         // don't do anything if "Custom" is selected
3272         if (rangeType() == RangeType::AutoData)
3273             setRangeType(RangeType::Auto);
3274 
3275         d->line->setStyle((Qt::PenStyle)group.readEntry(QStringLiteral("LineStyle"), (int)Qt::SolidLine));
3276     }
3277 
3278     // Major grid
3279     if (firstAxis)
3280         d->majorGridLine->setStyle((Qt::PenStyle)group.readEntry(QStringLiteral("MajorGridStyle"), (int)Qt::SolidLine));
3281     else
3282         d->majorGridLine->setStyle(Qt::NoPen);
3283 
3284     d->majorGridLine->setColor(group.readEntry(QStringLiteral("MajorGridColor"), QColor(Qt::gray)));
3285     d->majorGridLine->setWidth(group.readEntry(QStringLiteral("MajorGridWidth"), Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point)));
3286     d->majorGridLine->setOpacity(group.readEntry(QStringLiteral("MajorGridOpacity"), 1.0));
3287 
3288     // Major ticks
3289     this->setMajorTicksDirection((Axis::TicksDirection)group.readEntry(QStringLiteral("MajorTicksDirection"), (int)Axis::ticksIn));
3290     this->setMajorTicksLength(group.readEntry(QStringLiteral("MajorTicksLength"), Worksheet::convertToSceneUnits(6.0, Worksheet::Unit::Point)));
3291     d->majorTicksLine->loadThemeConfig(group);
3292 
3293     // Minor grid
3294     if (firstAxis)
3295         d->minorGridLine->setStyle((Qt::PenStyle)group.readEntry(QStringLiteral("MinorGridStyle"), (int)Qt::DotLine));
3296     else
3297         d->minorGridLine->setStyle(Qt::NoPen);
3298 
3299     d->minorGridLine->setColor(group.readEntry(QStringLiteral("MinorGridColor"), QColor(Qt::gray)));
3300     d->minorGridLine->setWidth(group.readEntry(QStringLiteral("MinorGridWidth"), Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point)));
3301     d->minorGridLine->setOpacity(group.readEntry(QStringLiteral("MinorGridOpacity"), 1.0));
3302 
3303     // Minor ticks
3304     this->setMinorTicksDirection((Axis::TicksDirection)group.readEntry(QStringLiteral("MinorTicksDirection"), (int)Axis::ticksIn));
3305     this->setMinorTicksLength(group.readEntry(QStringLiteral("MinorTicksLength"), Worksheet::convertToSceneUnits(3.0, Worksheet::Unit::Point)));
3306     d->minorTicksLine->loadThemeConfig(group);
3307 
3308     // load the theme for the title label
3309     d->title->loadThemeConfig(config);
3310 }
3311 
3312 void Axis::saveThemeConfig(const KConfig& config) {
3313     Q_D(Axis);
3314     KConfigGroup group = config.group(QStringLiteral("Axis"));
3315 
3316     // Tick label
3317     group.writeEntry(QStringLiteral("LabelsFontColor"), this->labelsColor());
3318     group.writeEntry(QStringLiteral("LabelsOpacity"), this->labelsOpacity());
3319     group.writeEntry(QStringLiteral("LabelsBackgroundColor"), this->labelsBackgroundColor());
3320 
3321     // Line
3322     d->line->saveThemeConfig(group);
3323 
3324     // Major ticks
3325     group.writeEntry(QStringLiteral("MajorTicksType"), (int)this->majorTicksType());
3326     group.writeEntry(QStringLiteral("MajorTicksLength"), d->majorTicksLength);
3327     d->majorTicksLine->saveThemeConfig(group);
3328 
3329     // Minor ticks
3330     group.writeEntry(QStringLiteral("MinorTicksType"), (int)this->minorTicksType());
3331     group.writeEntry(QStringLiteral("MinorTicksLength"), d->majorTicksLength);
3332     d->minorTicksLine->saveThemeConfig(group);
3333 
3334     // grid
3335     d->majorGridLine->saveThemeConfig(group);
3336     d->minorGridLine->saveThemeConfig(group);
3337 
3338     // title labe
3339     d->title->saveThemeConfig(config);
3340 }