"Fossies" - the Fresh Open Source Software Archive

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


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

    1 /***************************************************************************
    2     File                 : Axis.cpp
    3     Project              : LabPlot
    4     Description          : Axis for cartesian coordinate systems.
    5     --------------------------------------------------------------------
    6     Copyright            : (C) 2011-2018 Alexander Semke (alexander.semke@web.de)
    7     Copyright            : (C) 2013-2020 Stefan Gerlach  (stefan.gerlach@uni.kn)
    8 
    9  ***************************************************************************/
   10 
   11 /***************************************************************************
   12  *                                                                         *
   13  *  This program is free software; you can redistribute it and/or modify   *
   14  *  it under the terms of the GNU General Public License as published by   *
   15  *  the Free Software Foundation; either version 2 of the License, or      *
   16  *  (at your option) any later version.                                    *
   17  *                                                                         *
   18  *  This program is distributed in the hope that it will be useful,        *
   19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
   20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
   21  *  GNU General Public License for more details.                           *
   22  *                                                                         *
   23  *   You should have received a copy of the GNU General Public License     *
   24  *   along with this program; if not, write to the Free Software           *
   25  *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
   26  *   Boston, MA  02110-1301  USA                                           *
   27  *                                                                         *
   28  ***************************************************************************/
   29 
   30 #include "backend/worksheet/plots/cartesian/Axis.h"
   31 #include "backend/worksheet/plots/cartesian/AxisPrivate.h"
   32 #include "backend/worksheet/Worksheet.h"
   33 #include "backend/worksheet/TextLabel.h"
   34 #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h"
   35 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
   36 #include "backend/core/AbstractColumn.h"
   37 #include "backend/lib/commandtemplates.h"
   38 #include "backend/lib/XmlStreamReader.h"
   39 #include "backend/lib/macros.h"
   40 // #include "backend/lib/trace.h"
   41 #include "kdefrontend/GuiTools.h"
   42 
   43 #include <KConfig>
   44 #include <KLocalizedString>
   45 
   46 #include <QGraphicsSceneContextMenuEvent>
   47 #include <QMenu>
   48 #include <QPainter>
   49 #include <QTextDocument>
   50 
   51 extern "C" {
   52 #include <gsl/gsl_math.h>
   53 #include "backend/nsl/nsl_math.h"
   54 }
   55 
   56 /**
   57  * \class AxisGrid
   58  * \brief Helper class to get the axis grid drawn with the z-Value=0.
   59  *
   60  * The painting of the grid lines is separated from the painting of the axis itself.
   61  * This allows to use a different z-values for the grid lines (z=0, drawn below all other objects )
   62  * and for the axis (z=FLT_MAX, drawn on top of all other objects)
   63  *
   64  *  \ingroup worksheet
   65  */
   66 class AxisGrid : public QGraphicsItem {
   67 public:
   68     explicit AxisGrid(AxisPrivate* a) {
   69         axis = a;
   70         setFlag(QGraphicsItem::ItemIsSelectable, false);
   71         setFlag(QGraphicsItem::ItemIsFocusable, false);
   72         setAcceptHoverEvents(false);
   73     }
   74 
   75     QRectF boundingRect() const override {
   76         QPainterPath gridShape;
   77         gridShape.addPath(WorksheetElement::shapeFromPath(axis->majorGridPath, axis->majorGridPen));
   78         gridShape.addPath(WorksheetElement::shapeFromPath(axis->minorGridPath, axis->minorGridPen));
   79         QRectF boundingRectangle = gridShape.boundingRect();
   80         return boundingRectangle;
   81     }
   82 
   83     void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override {
   84         Q_UNUSED(option)
   85         Q_UNUSED(widget)
   86 
   87         if (!axis->isVisible()) return;
   88         if (axis->linePath.isEmpty()) return;
   89 
   90         //draw major grid
   91         if (axis->majorGridPen.style() != Qt::NoPen) {
   92             painter->setOpacity(axis->majorGridOpacity);
   93             painter->setPen(axis->majorGridPen);
   94             painter->setBrush(Qt::NoBrush);
   95             painter->drawPath(axis->majorGridPath);
   96         }
   97 
   98         //draw minor grid
   99         if (axis->minorGridPen.style() != Qt::NoPen) {
  100             painter->setOpacity(axis->minorGridOpacity);
  101             painter->setPen(axis->minorGridPen);
  102             painter->setBrush(Qt::NoBrush);
  103             painter->drawPath(axis->minorGridPath);
  104         }
  105     }
  106 
  107 private:
  108     AxisPrivate* axis;
  109 };
  110 
  111 /**
  112  * \class Axis
  113  * \brief Axis for cartesian coordinate systems.
  114  *
  115  *  \ingroup worksheet
  116  */
  117 Axis::Axis(const QString& name, Orientation orientation)
  118         : WorksheetElement(name, AspectType::Axis), d_ptr(new AxisPrivate(this)) {
  119     d_ptr->orientation = orientation;
  120     init();
  121 }
  122 
  123 Axis::Axis(const QString& name, Orientation orientation, AxisPrivate* dd)
  124         : WorksheetElement(name, AspectType::Axis), d_ptr(dd) {
  125     d_ptr->orientation = orientation;
  126     init();
  127 }
  128 
  129 void Axis::finalizeAdd() {
  130     Q_D(Axis);
  131     d->plot = dynamic_cast<CartesianPlot*>(parentAspect());
  132     Q_ASSERT(d->plot);
  133     d->cSystem = dynamic_cast<const CartesianCoordinateSystem*>(d->plot->coordinateSystem());
  134 }
  135 
  136 void Axis::init() {
  137     Q_D(Axis);
  138 
  139     KConfig config;
  140     KConfigGroup group = config.group("Axis");
  141 
  142     d->autoScale = true;
  143     d->position = Axis::Position::Custom;
  144     d->offset = group.readEntry("PositionOffset", 0);
  145     d->scale = (Scale) group.readEntry("Scale", static_cast<int>(Scale::Linear));
  146     d->autoScale = group.readEntry("AutoScale", true);
  147     d->start = group.readEntry("Start", 0);
  148     d->end = group.readEntry("End", 10);
  149     d->zeroOffset = group.readEntry("ZeroOffset", 0);
  150     d->scalingFactor = group.readEntry("ScalingFactor", 1.0);
  151 
  152     d->linePen.setStyle( (Qt::PenStyle) group.readEntry("LineStyle", (int) Qt::SolidLine) );
  153     d->linePen.setWidthF( group.readEntry("LineWidth", Worksheet::convertToSceneUnits( 1.0, Worksheet::Unit::Point) ) );
  154     d->lineOpacity = group.readEntry("LineOpacity", 1.0);
  155     d->arrowType = (Axis::ArrowType) group.readEntry("ArrowType", static_cast<int>(ArrowType::NoArrow));
  156     d->arrowPosition = (Axis::ArrowPosition) group.readEntry("ArrowPosition", static_cast<int>(ArrowPosition::Right));
  157     d->arrowSize = group.readEntry("ArrowSize", Worksheet::convertToSceneUnits(10, Worksheet::Unit::Point));
  158 
  159     // axis title
  160     d->title = new TextLabel(this->name(), TextLabel::Type::AxisTitle);
  161     connect( d->title, &TextLabel::changed, this, &Axis::labelChanged);
  162     addChild(d->title);
  163     d->title->setHidden(true);
  164     d->title->graphicsItem()->setParentItem(d);
  165     d->title->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false);
  166     d->title->graphicsItem()->setFlag(QGraphicsItem::ItemIsFocusable, false);
  167     d->title->graphicsItem()->setAcceptHoverEvents(false);
  168     d->title->setText(this->name());
  169     if (d->orientation == Orientation::Vertical) d->title->setRotationAngle(90);
  170     d->titleOffsetX = Worksheet::convertToSceneUnits(2, Worksheet::Unit::Point); //distance to the axis tick labels
  171     d->titleOffsetY = Worksheet::convertToSceneUnits(2, Worksheet::Unit::Point); //distance to the axis tick labels
  172 
  173     d->majorTicksDirection = (Axis::TicksDirection) group.readEntry("MajorTicksDirection", (int) Axis::ticksOut);
  174     d->majorTicksType = (TicksType) group.readEntry("MajorTicksType", static_cast<int>(TicksType::TotalNumber));
  175     d->majorTicksNumber = group.readEntry("MajorTicksNumber", 11);
  176     d->majorTicksSpacing = group.readEntry("MajorTicksIncrement", 0.0); // set to 0, so axisdock determines the value to not have to many labels the first time switched to Spacing
  177 
  178     d->majorTicksPen.setStyle((Qt::PenStyle) group.readEntry("MajorTicksLineStyle", (int)Qt::SolidLine) );
  179     d->majorTicksPen.setColor( group.readEntry("MajorTicksColor", QColor(Qt::black) ) );
  180     d->majorTicksPen.setWidthF( group.readEntry("MajorTicksWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point) ) );
  181     d->majorTicksLength = group.readEntry("MajorTicksLength", Worksheet::convertToSceneUnits(6.0, Worksheet::Unit::Point));
  182     d->majorTicksOpacity = group.readEntry("MajorTicksOpacity", 1.0);
  183 
  184     d->minorTicksDirection = (TicksDirection) group.readEntry("MinorTicksDirection", (int) Axis::ticksOut);
  185     d->minorTicksType = (TicksType) group.readEntry("MinorTicksType", static_cast<int>(TicksType::TotalNumber));
  186     d->minorTicksNumber = group.readEntry("MinorTicksNumber", 1);
  187     d->minorTicksIncrement = group.readEntry("MinorTicksIncrement", 0.0);   // see MajorTicksIncrement
  188     d->minorTicksPen.setStyle((Qt::PenStyle) group.readEntry("MinorTicksLineStyle", (int)Qt::SolidLine) );
  189     d->minorTicksPen.setColor( group.readEntry("MinorTicksColor", QColor(Qt::black) ) );
  190     d->minorTicksPen.setWidthF( group.readEntry("MinorTicksWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point) ) );
  191     d->minorTicksLength = group.readEntry("MinorTicksLength", Worksheet::convertToSceneUnits(3.0, Worksheet::Unit::Point));
  192     d->minorTicksOpacity = group.readEntry("MinorTicksOpacity", 1.0);
  193 
  194     //Labels
  195     d->labelsFormat = (LabelsFormat) group.readEntry("LabelsFormat", static_cast<int>(LabelsFormat::Decimal));
  196     d->labelsAutoPrecision = group.readEntry("LabelsAutoPrecision", true);
  197     d->labelsPrecision = group.readEntry("LabelsPrecision", 1);
  198     d->labelsDateTimeFormat = group.readEntry("LabelsDateTimeFormat", "yyyy-MM-dd hh:mm:ss");
  199     d->labelsPosition = (LabelsPosition) group.readEntry("LabelsPosition", (int) LabelsPosition::Out);
  200     d->labelsOffset = group.readEntry("LabelsOffset",  Worksheet::convertToSceneUnits( 5.0, Worksheet::Unit::Point ));
  201     d->labelsRotationAngle = group.readEntry("LabelsRotation", 0);
  202     d->labelsFont = group.readEntry("LabelsFont", QFont());
  203     d->labelsFont.setPixelSize( Worksheet::convertToSceneUnits( 10.0, Worksheet::Unit::Point ) );
  204     d->labelsColor = group.readEntry("LabelsFontColor", QColor(Qt::black));
  205     d->labelsBackgroundType = (LabelsBackgroundType) group.readEntry("LabelsBackgroundType", static_cast<int>(LabelsBackgroundType::Transparent));
  206     d->labelsBackgroundColor = group.readEntry("LabelsBackgroundColor", QColor(Qt::white));
  207     d->labelsPrefix =  group.readEntry("LabelsPrefix", "" );
  208     d->labelsSuffix =  group.readEntry("LabelsSuffix", "" );
  209     d->labelsOpacity = group.readEntry("LabelsOpacity", 1.0);
  210 
  211     //major grid
  212     d->majorGridPen.setStyle( (Qt::PenStyle) group.readEntry("MajorGridStyle", (int) Qt::SolidLine) );
  213     d->majorGridPen.setColor(group.readEntry("MajorGridColor", QColor(Qt::gray)) );
  214     d->majorGridPen.setWidthF( group.readEntry("MajorGridWidth", Worksheet::convertToSceneUnits( 1.0, Worksheet::Unit::Point ) ) );
  215     d->majorGridOpacity = group.readEntry("MajorGridOpacity", 1.0);
  216 
  217     //minor grid
  218     d->minorGridPen.setStyle( (Qt::PenStyle) group.readEntry("MinorGridStyle", (int) Qt::DotLine) );
  219     d->minorGridPen.setColor(group.readEntry("MinorGridColor", QColor(Qt::gray)) );
  220     d->minorGridPen.setWidthF( group.readEntry("MinorGridWidth", Worksheet::convertToSceneUnits( 1.0, Worksheet::Unit::Point ) ) );
  221     d->minorGridOpacity = group.readEntry("MinorGridOpacity", 1.0);
  222 }
  223 
  224 /*!
  225  * For the most frequently edited properties, create Actions and ActionGroups for the context menu.
  226  * For some ActionGroups the actual actions are created in \c GuiTool,
  227  */
  228 void Axis::initActions() {
  229     visibilityAction = new QAction(QIcon::fromTheme("view-visible"), i18n("Visible"), this);
  230     visibilityAction->setCheckable(true);
  231     connect(visibilityAction, &QAction::triggered, this, &Axis::visibilityChangedSlot);
  232 
  233     //Orientation
  234     orientationActionGroup = new QActionGroup(this);
  235     orientationActionGroup->setExclusive(true);
  236     connect(orientationActionGroup, &QActionGroup::triggered, this, &Axis::orientationChangedSlot);
  237 
  238     orientationHorizontalAction = new QAction(QIcon::fromTheme("labplot-axis-horizontal"), i18n("Horizontal"), orientationActionGroup);
  239     orientationHorizontalAction->setCheckable(true);
  240 
  241     orientationVerticalAction = new QAction(QIcon::fromTheme("labplot-axis-vertical"), i18n("Vertical"), orientationActionGroup);
  242     orientationVerticalAction->setCheckable(true);
  243 
  244     //Line
  245     lineStyleActionGroup = new QActionGroup(this);
  246     lineStyleActionGroup->setExclusive(true);
  247     connect(lineStyleActionGroup, &QActionGroup::triggered, this, &Axis::lineStyleChanged);
  248 
  249     lineColorActionGroup = new QActionGroup(this);
  250     lineColorActionGroup->setExclusive(true);
  251     connect(lineColorActionGroup, &QActionGroup::triggered, this, &Axis::lineColorChanged);
  252 
  253     //Ticks
  254     //TODO
  255 }
  256 
  257 void Axis::initMenus() {
  258     this->initActions();
  259 
  260     //Orientation
  261     orientationMenu = new QMenu(i18n("Orientation"));
  262     orientationMenu->setIcon(QIcon::fromTheme("labplot-axis-horizontal"));
  263     orientationMenu->addAction(orientationHorizontalAction);
  264     orientationMenu->addAction(orientationVerticalAction);
  265 
  266     //Line
  267     lineMenu = new QMenu(i18n("Line"));
  268     lineMenu->setIcon(QIcon::fromTheme("draw-line"));
  269     lineStyleMenu = new QMenu(i18n("Style"), lineMenu);
  270     lineStyleMenu->setIcon(QIcon::fromTheme("object-stroke-style"));
  271     lineMenu->setIcon(QIcon::fromTheme("draw-line"));
  272     lineMenu->addMenu( lineStyleMenu );
  273 
  274     lineColorMenu = new QMenu(i18n("Color"), lineMenu);
  275     lineColorMenu->setIcon(QIcon::fromTheme("fill-color"));
  276     GuiTools::fillColorMenu( lineColorMenu, lineColorActionGroup );
  277     lineMenu->addMenu( lineColorMenu );
  278 }
  279 
  280 QMenu* Axis::createContextMenu() {
  281     if (!orientationMenu)
  282         initMenus();
  283 
  284     Q_D(const Axis);
  285     QMenu* menu = WorksheetElement::createContextMenu();
  286     QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action"
  287 
  288     visibilityAction->setChecked(isVisible());
  289     menu->insertAction(firstAction, visibilityAction);
  290 
  291     //Orientation
  292     if (d->orientation == Orientation::Horizontal)
  293         orientationHorizontalAction->setChecked(true);
  294     else
  295         orientationVerticalAction->setChecked(true);
  296 
  297     menu->insertMenu(firstAction, orientationMenu);
  298 
  299     //Line styles
  300     GuiTools::updatePenStyles( lineStyleMenu, lineStyleActionGroup, d->linePen.color() );
  301     GuiTools::selectPenStyleAction(lineStyleActionGroup, d->linePen.style() );
  302 
  303     GuiTools::selectColorAction(lineColorActionGroup, d->linePen.color() );
  304 
  305     menu->insertMenu(firstAction, lineMenu);
  306     menu->insertSeparator(firstAction);
  307 
  308     return menu;
  309 }
  310 
  311 /*!
  312     Returns an icon to be used in the project explorer.
  313 */
  314 QIcon Axis::icon() const{
  315     Q_D(const Axis);
  316     QIcon ico;
  317     if (d->orientation == Orientation::Horizontal)
  318         ico = QIcon::fromTheme("labplot-axis-horizontal");
  319     else
  320         ico = QIcon::fromTheme("labplot-axis-vertical");
  321 
  322     return ico;
  323 }
  324 
  325 Axis::~Axis() {
  326     if (orientationMenu) {
  327         delete orientationMenu;
  328         delete lineMenu;
  329     }
  330 
  331     //no need to delete d->title, since it was added with addChild in init();
  332 
  333     //no need to delete the d-pointer here - it inherits from QGraphicsItem
  334     //and is deleted during the cleanup in QGraphicsScene
  335 }
  336 
  337 QGraphicsItem *Axis::graphicsItem() const {
  338     return d_ptr;
  339 }
  340 
  341 /*!
  342  * overrides the implementation in WorksheetElement and sets the z-value to the maximal possible,
  343  * axes are drawn on top of all other object in the plot.
  344  */
  345 void Axis::setZValue(qreal) {
  346     Q_D(Axis);
  347     d->setZValue(std::numeric_limits<double>::max());
  348     d->gridItem->setParentItem(d->parentItem());
  349     d->gridItem->setZValue(0);
  350 }
  351 
  352 void Axis::retransform() {
  353     Q_D(Axis);
  354     d->retransform();
  355 }
  356 
  357 void Axis::retransformTickLabelStrings() {
  358     Q_D(Axis);
  359     d->retransformTickLabelStrings();
  360 }
  361 
  362 void Axis::setSuppressRetransform(bool value) {
  363     Q_D(Axis);
  364     d->suppressRetransform = value;
  365 }
  366 
  367 void Axis::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) {
  368     Q_D(Axis);
  369     Q_UNUSED(pageResize);
  370 
  371     double ratio = 0;
  372     if (horizontalRatio > 1.0 || verticalRatio > 1.0)
  373         ratio = qMax(horizontalRatio, verticalRatio);
  374     else
  375         ratio = qMin(horizontalRatio, verticalRatio);
  376 
  377     QPen pen = d->linePen;
  378     pen.setWidthF(pen.widthF() * ratio);
  379     d->linePen = pen;
  380 
  381     d->majorTicksLength *= ratio; // ticks are perpendicular to axis line -> verticalRatio relevant
  382     d->minorTicksLength *= ratio;
  383     d->labelsFont.setPixelSize( d->labelsFont.pixelSize() * ratio ); //TODO: take into account rotated labels
  384     d->labelsOffset *= ratio;
  385     d->title->handleResize(horizontalRatio, verticalRatio, pageResize);
  386 }
  387 
  388 /* ============================ getter methods ================= */
  389 BASIC_SHARED_D_READER_IMPL(Axis, bool, autoScale, autoScale)
  390 BASIC_SHARED_D_READER_IMPL(Axis, Axis::Orientation, orientation, orientation)
  391 BASIC_SHARED_D_READER_IMPL(Axis, Axis::Position, position, position)
  392 BASIC_SHARED_D_READER_IMPL(Axis, Axis::Scale, scale, scale)
  393 BASIC_SHARED_D_READER_IMPL(Axis, double, offset, offset)
  394 BASIC_SHARED_D_READER_IMPL(Axis, double, start, start)
  395 BASIC_SHARED_D_READER_IMPL(Axis, double, end, end)
  396 BASIC_SHARED_D_READER_IMPL(Axis, qreal, scalingFactor, scalingFactor)
  397 BASIC_SHARED_D_READER_IMPL(Axis, qreal, zeroOffset, zeroOffset)
  398 
  399 BASIC_SHARED_D_READER_IMPL(Axis, TextLabel*, title, title)
  400 BASIC_SHARED_D_READER_IMPL(Axis, qreal, titleOffsetX, titleOffsetX)
  401 BASIC_SHARED_D_READER_IMPL(Axis, qreal, titleOffsetY, titleOffsetY)
  402 
  403 CLASS_SHARED_D_READER_IMPL(Axis, QPen, linePen, linePen)
  404 BASIC_SHARED_D_READER_IMPL(Axis, qreal, lineOpacity, lineOpacity)
  405 BASIC_SHARED_D_READER_IMPL(Axis, Axis::ArrowType, arrowType, arrowType)
  406 BASIC_SHARED_D_READER_IMPL(Axis, Axis::ArrowPosition, arrowPosition, arrowPosition)
  407 BASIC_SHARED_D_READER_IMPL(Axis, qreal, arrowSize, arrowSize)
  408 
  409 BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksDirection, majorTicksDirection, majorTicksDirection)
  410 BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksType, majorTicksType, majorTicksType)
  411 BASIC_SHARED_D_READER_IMPL(Axis, int, majorTicksNumber, majorTicksNumber)
  412 BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorTicksSpacing, majorTicksSpacing)
  413 BASIC_SHARED_D_READER_IMPL(Axis, const AbstractColumn*, majorTicksColumn, majorTicksColumn)
  414 QString& Axis::majorTicksColumnPath() const { return d_ptr->majorTicksColumnPath; }
  415 BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorTicksLength, majorTicksLength)
  416 CLASS_SHARED_D_READER_IMPL(Axis, QPen, majorTicksPen, majorTicksPen)
  417 BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorTicksOpacity, majorTicksOpacity)
  418 
  419 BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksDirection, minorTicksDirection, minorTicksDirection)
  420 BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksType, minorTicksType, minorTicksType)
  421 BASIC_SHARED_D_READER_IMPL(Axis, int, minorTicksNumber, minorTicksNumber)
  422 BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorTicksSpacing, minorTicksIncrement)
  423 BASIC_SHARED_D_READER_IMPL(Axis, const AbstractColumn*, minorTicksColumn, minorTicksColumn)
  424 QString& Axis::minorTicksColumnPath() const { return d_ptr->minorTicksColumnPath; }
  425 BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorTicksLength, minorTicksLength)
  426 CLASS_SHARED_D_READER_IMPL(Axis, QPen, minorTicksPen, minorTicksPen)
  427 BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorTicksOpacity, minorTicksOpacity)
  428 
  429 BASIC_SHARED_D_READER_IMPL(Axis, Axis::LabelsFormat, labelsFormat, labelsFormat);
  430 BASIC_SHARED_D_READER_IMPL(Axis, bool, labelsAutoPrecision, labelsAutoPrecision);
  431 BASIC_SHARED_D_READER_IMPL(Axis, int, labelsPrecision, labelsPrecision);
  432 BASIC_SHARED_D_READER_IMPL(Axis, QString, labelsDateTimeFormat, labelsDateTimeFormat);
  433 BASIC_SHARED_D_READER_IMPL(Axis, Axis::LabelsPosition, labelsPosition, labelsPosition);
  434 BASIC_SHARED_D_READER_IMPL(Axis, qreal, labelsOffset, labelsOffset);
  435 BASIC_SHARED_D_READER_IMPL(Axis, qreal, labelsRotationAngle, labelsRotationAngle);
  436 CLASS_SHARED_D_READER_IMPL(Axis, QColor, labelsColor, labelsColor);
  437 CLASS_SHARED_D_READER_IMPL(Axis, QFont, labelsFont, labelsFont);
  438 BASIC_SHARED_D_READER_IMPL(Axis, Axis::LabelsBackgroundType, labelsBackgroundType, labelsBackgroundType);
  439 CLASS_SHARED_D_READER_IMPL(Axis, QColor, labelsBackgroundColor, labelsBackgroundColor);
  440 CLASS_SHARED_D_READER_IMPL(Axis, QString, labelsPrefix, labelsPrefix);
  441 CLASS_SHARED_D_READER_IMPL(Axis, QString, labelsSuffix, labelsSuffix);
  442 BASIC_SHARED_D_READER_IMPL(Axis, qreal, labelsOpacity, labelsOpacity);
  443 
  444 CLASS_SHARED_D_READER_IMPL(Axis, QPen, majorGridPen, majorGridPen)
  445 BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorGridOpacity, majorGridOpacity)
  446 CLASS_SHARED_D_READER_IMPL(Axis, QPen, minorGridPen, minorGridPen)
  447 BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorGridOpacity, minorGridOpacity)
  448 
  449 /* ============================ setter methods and undo commands ================= */
  450 STD_SETTER_CMD_IMPL_F_S(Axis, SetAutoScale, bool, autoScale, retransform);
  451 void Axis::setAutoScale(bool autoScale) {
  452     Q_D(Axis);
  453     if (autoScale != d->autoScale) {
  454         exec(new AxisSetAutoScaleCmd(d, autoScale, ki18n("%1: set axis auto scaling")));
  455 
  456         if (autoScale) {
  457             auto* plot = qobject_cast<CartesianPlot*>(parentAspect());
  458             if (!plot)
  459                 return;
  460 
  461             if (d->orientation == Axis::Orientation::Horizontal) {
  462                 d->end = plot->xMax();
  463                 d->start = plot->xMin();
  464             } else {
  465                 d->end = plot->yMax();
  466                 d->start = plot->yMin();
  467             }
  468             retransform();
  469             emit endChanged(d->end);
  470             emit startChanged(d->start);
  471         }
  472     }
  473 }
  474 
  475 STD_SWAP_METHOD_SETTER_CMD_IMPL(Axis, SetVisible, bool, swapVisible);
  476 void Axis::setVisible(bool on) {
  477     Q_D(Axis);
  478     exec(new AxisSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible")));
  479 }
  480 
  481 bool Axis::isVisible() const {
  482     Q_D(const Axis);
  483     return d->isVisible();
  484 }
  485 
  486 void Axis::setDefault(bool value) {
  487     Q_D(Axis);
  488     d->isDefault = value;
  489 }
  490 
  491 bool Axis::isDefault() const {
  492     Q_D(const Axis);
  493     return d->isDefault;
  494 }
  495 
  496 void Axis::setPrinting(bool on) {
  497     Q_D(Axis);
  498     d->setPrinting(on);
  499 }
  500 
  501 bool Axis::isHovered() const {
  502     Q_D(const Axis);
  503     return d->isHovered();
  504 }
  505 
  506 STD_SETTER_CMD_IMPL_F_S(Axis, SetOrientation, Axis::Orientation, orientation, retransform);
  507 void Axis::setOrientation(Orientation orientation) {
  508     Q_D(Axis);
  509     if (orientation != d->orientation)
  510         exec(new AxisSetOrientationCmd(d, orientation, ki18n("%1: set axis orientation")));
  511 }
  512 
  513 STD_SETTER_CMD_IMPL_F_S(Axis, SetPosition, Axis::Position, position, retransform);
  514 void Axis::setPosition(Position position) {
  515     Q_D(Axis);
  516     if (position != d->position)
  517         exec(new AxisSetPositionCmd(d, position, ki18n("%1: set axis position")));
  518 }
  519 
  520 STD_SETTER_CMD_IMPL_F_S(Axis, SetScaling, Axis::Scale, scale, retransformTicks);
  521 void Axis::setScale(Scale scale) {
  522     Q_D(Axis);
  523     if (scale != d->scale)
  524         exec(new AxisSetScalingCmd(d, scale, ki18n("%1: set axis scale")));
  525 }
  526 
  527 STD_SETTER_CMD_IMPL_F(Axis, SetOffset, double, offset, retransform);
  528 void Axis::setOffset(double offset, bool undo) {
  529     Q_D(Axis);
  530     if (offset != d->offset) {
  531         if (undo) {
  532             exec(new AxisSetOffsetCmd(d, offset, ki18n("%1: set axis offset")));
  533         } else {
  534             d->offset = offset;
  535             //don't need to call retransform() afterward
  536             //since the only usage of this call is in CartesianPlot, where retransform is called for all children anyway.
  537         }
  538         emit positionChanged(offset);
  539     }
  540 }
  541 
  542 STD_SETTER_CMD_IMPL_F_S(Axis, SetStart, double, start, retransform);
  543 void Axis::setStart(double start) {
  544     Q_D(Axis);
  545     if (start != d->start)
  546         exec(new AxisSetStartCmd(d, start, ki18n("%1: set axis start")));
  547 }
  548 
  549 STD_SETTER_CMD_IMPL_F_S(Axis, SetEnd, double, end, retransform);
  550 void Axis::setEnd(double end) {
  551     Q_D(Axis);
  552     if (end != d->end)
  553         exec(new AxisSetEndCmd(d, end, ki18n("%1: set axis end")));
  554 }
  555 
  556 STD_SETTER_CMD_IMPL_F_S(Axis, SetZeroOffset, qreal, zeroOffset, retransform);
  557 void Axis::setZeroOffset(qreal zeroOffset) {
  558     Q_D(Axis);
  559     if (zeroOffset != d->zeroOffset)
  560         exec(new AxisSetZeroOffsetCmd(d, zeroOffset, ki18n("%1: set axis zero offset")));
  561 }
  562 
  563 STD_SETTER_CMD_IMPL_F_S(Axis, SetScalingFactor, qreal, scalingFactor, retransform);
  564 void Axis::setScalingFactor(qreal scalingFactor) {
  565     Q_D(Axis);
  566     if (scalingFactor != d->scalingFactor)
  567         exec(new AxisSetScalingFactorCmd(d, scalingFactor, ki18n("%1: set axis scaling factor")));
  568 }
  569 
  570 //Title
  571 STD_SETTER_CMD_IMPL_F_S(Axis, SetTitleOffsetX, qreal, titleOffsetX, retransform);
  572 void Axis::setTitleOffsetX(qreal offset) {
  573     Q_D(Axis);
  574     if (offset != d->titleOffsetX)
  575         exec(new AxisSetTitleOffsetXCmd(d, offset, ki18n("%1: set title offset")));
  576 }
  577 
  578 STD_SETTER_CMD_IMPL_F_S(Axis, SetTitleOffsetY, qreal, titleOffsetY, retransform);
  579 void Axis::setTitleOffsetY(qreal offset) {
  580     Q_D(Axis);
  581     if (offset != d->titleOffsetY)
  582         exec(new AxisSetTitleOffsetYCmd(d, offset, ki18n("%1: set title offset")));
  583 }
  584 
  585 //Line
  586 STD_SETTER_CMD_IMPL_F_S(Axis, SetLinePen, QPen, linePen, recalcShapeAndBoundingRect);
  587 void Axis::setLinePen(const QPen &pen) {
  588     Q_D(Axis);
  589     if (pen != d->linePen)
  590         exec(new AxisSetLinePenCmd(d, pen, ki18n("%1: set line style")));
  591 }
  592 
  593 STD_SETTER_CMD_IMPL_F_S(Axis, SetLineOpacity, qreal, lineOpacity, update);
  594 void Axis::setLineOpacity(qreal opacity) {
  595     Q_D(Axis);
  596     if (opacity != d->lineOpacity)
  597         exec(new AxisSetLineOpacityCmd(d, opacity, ki18n("%1: set line opacity")));
  598 }
  599 
  600 STD_SETTER_CMD_IMPL_F_S(Axis, SetArrowType, Axis::ArrowType, arrowType, retransformArrow);
  601 void Axis::setArrowType(ArrowType type) {
  602     Q_D(Axis);
  603     if (type != d->arrowType)
  604         exec(new AxisSetArrowTypeCmd(d, type, ki18n("%1: set arrow type")));
  605 }
  606 
  607 STD_SETTER_CMD_IMPL_F_S(Axis, SetArrowPosition, Axis::ArrowPosition, arrowPosition, retransformArrow);
  608 void Axis::setArrowPosition(ArrowPosition position) {
  609     Q_D(Axis);
  610     if (position != d->arrowPosition)
  611         exec(new AxisSetArrowPositionCmd(d, position, ki18n("%1: set arrow position")));
  612 }
  613 
  614 STD_SETTER_CMD_IMPL_F_S(Axis, SetArrowSize, qreal, arrowSize, retransformArrow);
  615 void Axis::setArrowSize(qreal arrowSize) {
  616     Q_D(Axis);
  617     if (arrowSize != d->arrowSize)
  618         exec(new AxisSetArrowSizeCmd(d, arrowSize, ki18n("%1: set arrow size")));
  619 }
  620 
  621 //Major ticks
  622 STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksDirection, Axis::TicksDirection, majorTicksDirection, retransformTicks);
  623 void Axis::setMajorTicksDirection(TicksDirection majorTicksDirection) {
  624     Q_D(Axis);
  625     if (majorTicksDirection != d->majorTicksDirection)
  626         exec(new AxisSetMajorTicksDirectionCmd(d, majorTicksDirection, ki18n("%1: set major ticks direction")));
  627 }
  628 
  629 STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksType, Axis::TicksType, majorTicksType, retransformTicks);
  630 void Axis::setMajorTicksType(TicksType majorTicksType) {
  631     Q_D(Axis);
  632     if (majorTicksType!= d->majorTicksType)
  633         exec(new AxisSetMajorTicksTypeCmd(d, majorTicksType, ki18n("%1: set major ticks type")));
  634 }
  635 STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksNumber, int, majorTicksNumber, retransformTicks);
  636 void Axis::setMajorTicksNumber(int majorTicksNumber) {
  637     Q_D(Axis);
  638     if (majorTicksNumber != d->majorTicksNumber)
  639         exec(new AxisSetMajorTicksNumberCmd(d, majorTicksNumber, ki18n("%1: set the total number of the major ticks")));
  640 }
  641 
  642 STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksSpacing, qreal, majorTicksSpacing, retransformTicks);
  643 void Axis::setMajorTicksSpacing(qreal majorTicksSpacing) {
  644     Q_D(Axis);
  645     if (majorTicksSpacing != d->majorTicksSpacing)
  646         exec(new AxisSetMajorTicksSpacingCmd(d, majorTicksSpacing, ki18n("%1: set the spacing of the major ticks")));
  647 }
  648 
  649 STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksColumn, const AbstractColumn*, majorTicksColumn, retransformTicks)
  650 void Axis::setMajorTicksColumn(const AbstractColumn* column) {
  651     Q_D(Axis);
  652     if (column != d->majorTicksColumn) {
  653         exec(new AxisSetMajorTicksColumnCmd(d, column, ki18n("%1: assign major ticks' values")));
  654 
  655         if (column) {
  656             connect(column, &AbstractColumn::dataChanged, this, &Axis::retransformTicks);
  657             connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved,
  658                     this, &Axis::majorTicksColumnAboutToBeRemoved);
  659             //TODO: add disconnect in the undo-function
  660         }
  661     }
  662 }
  663 
  664 STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksPen, QPen, majorTicksPen, recalcShapeAndBoundingRect);
  665 void Axis::setMajorTicksPen(const QPen& pen) {
  666     Q_D(Axis);
  667     if (pen != d->majorTicksPen)
  668         exec(new AxisSetMajorTicksPenCmd(d, pen, ki18n("%1: set major ticks style")));
  669 }
  670 
  671 STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksLength, qreal, majorTicksLength, retransformTicks);
  672 void Axis::setMajorTicksLength(qreal majorTicksLength) {
  673     Q_D(Axis);
  674     if (majorTicksLength != d->majorTicksLength)
  675         exec(new AxisSetMajorTicksLengthCmd(d, majorTicksLength, ki18n("%1: set major ticks length")));
  676 }
  677 
  678 STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksOpacity, qreal, majorTicksOpacity, update);
  679 void Axis::setMajorTicksOpacity(qreal opacity) {
  680     Q_D(Axis);
  681     if (opacity != d->majorTicksOpacity)
  682         exec(new AxisSetMajorTicksOpacityCmd(d, opacity, ki18n("%1: set major ticks opacity")));
  683 }
  684 
  685 //Minor ticks
  686 STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksDirection, Axis::TicksDirection, minorTicksDirection, retransformTicks);
  687 void Axis::setMinorTicksDirection(TicksDirection minorTicksDirection) {
  688     Q_D(Axis);
  689     if (minorTicksDirection != d->minorTicksDirection)
  690         exec(new AxisSetMinorTicksDirectionCmd(d, minorTicksDirection, ki18n("%1: set minor ticks direction")));
  691 }
  692 
  693 STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksType, Axis::TicksType, minorTicksType, retransformTicks);
  694 void Axis::setMinorTicksType(TicksType minorTicksType) {
  695     Q_D(Axis);
  696     if (minorTicksType!= d->minorTicksType)
  697         exec(new AxisSetMinorTicksTypeCmd(d, minorTicksType, ki18n("%1: set minor ticks type")));
  698 }
  699 
  700 STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksNumber, int, minorTicksNumber, retransformTicks);
  701 void Axis::setMinorTicksNumber(int minorTicksNumber) {
  702     Q_D(Axis);
  703     if (minorTicksNumber != d->minorTicksNumber)
  704         exec(new AxisSetMinorTicksNumberCmd(d, minorTicksNumber, ki18n("%1: set the total number of the minor ticks")));
  705 }
  706 
  707 STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksSpacing, qreal, minorTicksIncrement, retransformTicks);
  708 void Axis::setMinorTicksSpacing(qreal minorTicksSpacing) {
  709     Q_D(Axis);
  710     if (minorTicksSpacing != d->minorTicksIncrement)
  711         exec(new AxisSetMinorTicksSpacingCmd(d, minorTicksSpacing, ki18n("%1: set the spacing of the minor ticks")));
  712 }
  713 
  714 STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksColumn, const AbstractColumn*, minorTicksColumn, retransformTicks)
  715 void Axis::setMinorTicksColumn(const AbstractColumn* column) {
  716     Q_D(Axis);
  717     if (column != d->minorTicksColumn) {
  718         exec(new AxisSetMinorTicksColumnCmd(d, column, ki18n("%1: assign minor ticks' values")));
  719 
  720         if (column) {
  721             connect(column, &AbstractColumn::dataChanged, this, &Axis::retransformTicks);
  722             connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved,
  723                     this, &Axis::minorTicksColumnAboutToBeRemoved);
  724             //TODO: add disconnect in the undo-function
  725         }
  726     }
  727 }
  728 
  729 STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksPen, QPen, minorTicksPen, recalcShapeAndBoundingRect);
  730 void Axis::setMinorTicksPen(const QPen& pen) {
  731     Q_D(Axis);
  732     if (pen != d->minorTicksPen)
  733         exec(new AxisSetMinorTicksPenCmd(d, pen, ki18n("%1: set minor ticks style")));
  734 }
  735 
  736 STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksLength, qreal, minorTicksLength, retransformTicks);
  737 void Axis::setMinorTicksLength(qreal minorTicksLength) {
  738     Q_D(Axis);
  739     if (minorTicksLength != d->minorTicksLength)
  740         exec(new AxisSetMinorTicksLengthCmd(d, minorTicksLength, ki18n("%1: set minor ticks length")));
  741 }
  742 
  743 STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksOpacity, qreal, minorTicksOpacity, update);
  744 void Axis::setMinorTicksOpacity(qreal opacity) {
  745     Q_D(Axis);
  746     if (opacity != d->minorTicksOpacity)
  747         exec(new AxisSetMinorTicksOpacityCmd(d, opacity, ki18n("%1: set minor ticks opacity")));
  748 }
  749 
  750 //Labels
  751 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsFormat, Axis::LabelsFormat, labelsFormat, retransformTicks);
  752 void Axis::setLabelsFormat(LabelsFormat labelsFormat) {
  753     Q_D(Axis);
  754     if (labelsFormat != d->labelsFormat) {
  755 
  756         //TODO: this part is not undo/redo-aware
  757         if (labelsFormat == LabelsFormat::Decimal)
  758             d->labelsFormatDecimalOverruled = true;
  759         else
  760             d->labelsFormatDecimalOverruled = false;
  761 
  762         exec(new AxisSetLabelsFormatCmd(d, labelsFormat, ki18n("%1: set labels format")));
  763     }
  764 }
  765 
  766 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsAutoPrecision, bool, labelsAutoPrecision, retransformTickLabelStrings);
  767 void Axis::setLabelsAutoPrecision(bool labelsAutoPrecision) {
  768     Q_D(Axis);
  769     if (labelsAutoPrecision != d->labelsAutoPrecision)
  770         exec(new AxisSetLabelsAutoPrecisionCmd(d, labelsAutoPrecision, ki18n("%1: set labels precision")));
  771 }
  772 
  773 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsPrecision, int, labelsPrecision, retransformTickLabelStrings);
  774 void Axis::setLabelsPrecision(int labelsPrecision) {
  775     Q_D(Axis);
  776     if (labelsPrecision != d->labelsPrecision)
  777         exec(new AxisSetLabelsPrecisionCmd(d, labelsPrecision, ki18n("%1: set labels precision")));
  778 }
  779 
  780 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsDateTimeFormat, QString, labelsDateTimeFormat, retransformTickLabelStrings);
  781 void Axis::setLabelsDateTimeFormat(const QString& format) {
  782     Q_D(Axis);
  783     if (format != d->labelsDateTimeFormat)
  784         exec(new AxisSetLabelsDateTimeFormatCmd(d, format, ki18n("%1: set labels datetime format")));
  785 }
  786 
  787 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsPosition, Axis::LabelsPosition, labelsPosition, retransformTickLabelPositions);
  788 void Axis::setLabelsPosition(LabelsPosition labelsPosition) {
  789     Q_D(Axis);
  790     if (labelsPosition != d->labelsPosition)
  791         exec(new AxisSetLabelsPositionCmd(d, labelsPosition, ki18n("%1: set labels position")));
  792 }
  793 
  794 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsOffset, double, labelsOffset, retransformTickLabelPositions);
  795 void Axis::setLabelsOffset(double offset) {
  796     Q_D(Axis);
  797     if (offset != d->labelsOffset)
  798         exec(new AxisSetLabelsOffsetCmd(d, offset, ki18n("%1: set label offset")));
  799 }
  800 
  801 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsRotationAngle, qreal, labelsRotationAngle, retransformTickLabelPositions);
  802 void Axis::setLabelsRotationAngle(qreal angle) {
  803     Q_D(Axis);
  804     if (angle != d->labelsRotationAngle)
  805         exec(new AxisSetLabelsRotationAngleCmd(d, angle, ki18n("%1: set label rotation angle")));
  806 }
  807 
  808 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsColor, QColor, labelsColor, update);
  809 void Axis::setLabelsColor(const QColor& color) {
  810     Q_D(Axis);
  811     if (color != d->labelsColor)
  812         exec(new AxisSetLabelsColorCmd(d, color, ki18n("%1: set label color")));
  813 }
  814 
  815 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsFont, QFont, labelsFont, retransformTickLabelStrings);
  816 void Axis::setLabelsFont(const QFont& font) {
  817     Q_D(Axis);
  818     if (font != d->labelsFont)
  819         exec(new AxisSetLabelsFontCmd(d, font, ki18n("%1: set label font")));
  820 }
  821 
  822 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsBackgroundType, Axis::LabelsBackgroundType, labelsBackgroundType, update);
  823 void Axis::setLabelsBackgroundType(LabelsBackgroundType type) {
  824     Q_D(Axis);
  825     if (type != d->labelsBackgroundType)
  826         exec(new AxisSetLabelsBackgroundTypeCmd(d, type, ki18n("%1: set labels background type")));
  827 }
  828 
  829 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsBackgroundColor, QColor, labelsBackgroundColor, update);
  830 void Axis::setLabelsBackgroundColor(const QColor& color) {
  831     Q_D(Axis);
  832     if (color != d->labelsBackgroundColor)
  833         exec(new AxisSetLabelsBackgroundColorCmd(d, color, ki18n("%1: set label background color")));
  834 }
  835 
  836 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsPrefix, QString, labelsPrefix, retransformTickLabelStrings);
  837 void Axis::setLabelsPrefix(const QString& prefix) {
  838     Q_D(Axis);
  839     if (prefix != d->labelsPrefix)
  840         exec(new AxisSetLabelsPrefixCmd(d, prefix, ki18n("%1: set label prefix")));
  841 }
  842 
  843 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsSuffix, QString, labelsSuffix, retransformTickLabelStrings);
  844 void Axis::setLabelsSuffix(const QString& suffix) {
  845     Q_D(Axis);
  846     if (suffix != d->labelsSuffix)
  847         exec(new AxisSetLabelsSuffixCmd(d, suffix, ki18n("%1: set label suffix")));
  848 }
  849 
  850 STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsOpacity, qreal, labelsOpacity, update);
  851 void Axis::setLabelsOpacity(qreal opacity) {
  852     Q_D(Axis);
  853     if (opacity != d->labelsOpacity)
  854         exec(new AxisSetLabelsOpacityCmd(d, opacity, ki18n("%1: set labels opacity")));
  855 }
  856 
  857 //Major grid
  858 STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorGridPen, QPen, majorGridPen, retransformMajorGrid);
  859 void Axis::setMajorGridPen(const QPen& pen) {
  860     Q_D(Axis);
  861     if (pen != d->majorGridPen)
  862         exec(new AxisSetMajorGridPenCmd(d, pen, ki18n("%1: set major grid style")));
  863 }
  864 
  865 STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorGridOpacity, qreal, majorGridOpacity, updateGrid);
  866 void Axis::setMajorGridOpacity(qreal opacity) {
  867     Q_D(Axis);
  868     if (opacity != d->majorGridOpacity)
  869         exec(new AxisSetMajorGridOpacityCmd(d, opacity, ki18n("%1: set major grid opacity")));
  870 }
  871 
  872 //Minor grid
  873 STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorGridPen, QPen, minorGridPen, retransformMinorGrid);
  874 void Axis::setMinorGridPen(const QPen& pen) {
  875     Q_D(Axis);
  876     if (pen != d->minorGridPen)
  877         exec(new AxisSetMinorGridPenCmd(d, pen, ki18n("%1: set minor grid style")));
  878 }
  879 
  880 STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorGridOpacity, qreal, minorGridOpacity, updateGrid);
  881 void Axis::setMinorGridOpacity(qreal opacity) {
  882     Q_D(Axis);
  883     if (opacity != d->minorGridOpacity)
  884         exec(new AxisSetMinorGridOpacityCmd(d, opacity, ki18n("%1: set minor grid opacity")));
  885 }
  886 
  887 //##############################################################################
  888 //####################################  SLOTs   ################################
  889 //##############################################################################
  890 void Axis::labelChanged() {
  891     Q_D(Axis);
  892     d->recalcShapeAndBoundingRect();
  893 }
  894 
  895 void Axis::retransformTicks() {
  896     Q_D(Axis);
  897     d->retransformTicks();
  898 }
  899 
  900 void Axis::majorTicksColumnAboutToBeRemoved(const AbstractAspect* aspect) {
  901     Q_D(Axis);
  902     if (aspect == d->majorTicksColumn) {
  903         d->majorTicksColumn = nullptr;
  904         d->retransformTicks();
  905     }
  906 }
  907 
  908 void Axis::minorTicksColumnAboutToBeRemoved(const AbstractAspect* aspect) {
  909     Q_D(Axis);
  910     if (aspect == d->minorTicksColumn) {
  911         d->minorTicksColumn = nullptr;
  912         d->retransformTicks();
  913     }
  914 }
  915 
  916 //##############################################################################
  917 //######  SLOTs for changes triggered via QActions in the context menu  ########
  918 //##############################################################################
  919 void Axis::orientationChangedSlot(QAction* action) {
  920     if (action == orientationHorizontalAction)
  921         this->setOrientation(Axis::Orientation::Horizontal);
  922     else
  923         this->setOrientation(Axis::Orientation::Vertical);
  924 }
  925 
  926 void Axis::lineStyleChanged(QAction* action) {
  927     Q_D(const Axis);
  928     QPen pen = d->linePen;
  929     pen.setStyle(GuiTools::penStyleFromAction(lineStyleActionGroup, action));
  930     this->setLinePen(pen);
  931 }
  932 
  933 void Axis::lineColorChanged(QAction* action) {
  934     Q_D(const Axis);
  935     QPen pen = d->linePen;
  936     pen.setColor(GuiTools::colorFromAction(lineColorActionGroup, action));
  937     this->setLinePen(pen);
  938 }
  939 
  940 void Axis::visibilityChangedSlot() {
  941     Q_D(const Axis);
  942     this->setVisible(!d->isVisible());
  943 }
  944 
  945 //#####################################################################
  946 //################### Private implementation ##########################
  947 //#####################################################################
  948 AxisPrivate::AxisPrivate(Axis* owner) : gridItem(new AxisGrid(this)), q(owner) {
  949     setFlag(QGraphicsItem::ItemIsSelectable, true);
  950     setFlag(QGraphicsItem::ItemIsFocusable, true);
  951     setAcceptHoverEvents(true);
  952 }
  953 
  954 QString AxisPrivate::name() const{
  955     return q->name();
  956 }
  957 
  958 bool AxisPrivate::swapVisible(bool on) {
  959     bool oldValue = isVisible();
  960 
  961     //When making a graphics item invisible, it gets deselected in the scene.
  962     //In this case we don't want to deselect the item in the project explorer.
  963     //We need to supress the deselection in the view.
  964     auto* worksheet = static_cast<Worksheet*>(q->parent(AspectType::Worksheet));
  965     worksheet->suppressSelectionChangedEvent(true);
  966     setVisible(on);
  967     gridItem->setVisible(on);
  968     worksheet->suppressSelectionChangedEvent(false);
  969 
  970     emit q->visibilityChanged(on);
  971     return oldValue;
  972 }
  973 
  974 QRectF AxisPrivate::boundingRect() const{
  975     return boundingRectangle;
  976 }
  977 
  978 /*!
  979   Returns the shape of the XYCurve as a QPainterPath in local coordinates
  980 */
  981 QPainterPath AxisPrivate::shape() const{
  982     return axisShape;
  983 }
  984 
  985 /*!
  986     recalculates the position of the axis on the worksheet
  987  */
  988 void AxisPrivate::retransform() {
  989     if (suppressRetransform || !plot)
  990         return;
  991 
  992 //  PERFTRACE(name().toLatin1() + ", AxisPrivate::retransform()");
  993     m_suppressRecalc = true;
  994     retransformLine();
  995     m_suppressRecalc = false;
  996     recalcShapeAndBoundingRect();
  997 }
  998 
  999 void AxisPrivate::retransformLine() {
 1000     if (suppressRetransform)
 1001         return;
 1002 
 1003     linePath = QPainterPath();
 1004     lines.clear();
 1005 
 1006     QPointF startPoint;
 1007     QPointF endPoint;
 1008 
 1009     if (orientation == Axis::Orientation::Horizontal) {
 1010         if (position == Axis::Position::Top)
 1011             offset = plot->yMax();
 1012         else if (position == Axis::Position::Bottom)
 1013             offset = plot->yMin();
 1014         else if (position == Axis::Position::Centered)
 1015             offset = plot->yMin() + (plot->yMax()-plot->yMin())/2;
 1016 
 1017         startPoint.setX(start);
 1018         startPoint.setY(offset);
 1019         endPoint.setX(end);
 1020         endPoint.setY(offset);
 1021     } else { // vertical
 1022         if (position == Axis::Position::Left)
 1023             offset = plot->xMin();
 1024         else if (position == Axis::Position::Right)
 1025             offset = plot->xMax();
 1026         else if (position == Axis::Position::Centered)
 1027             offset = plot->xMin() + (plot->xMax()-plot->xMin())/2;
 1028 
 1029         startPoint.setX(offset);
 1030         startPoint.setY(start);
 1031         endPoint.setY(end);
 1032         endPoint.setX(offset);
 1033     }
 1034 
 1035     lines.append(QLineF(startPoint, endPoint));
 1036     lines = cSystem->mapLogicalToScene(lines, AbstractCoordinateSystem::MappingFlag::MarkGaps);
 1037     for (const auto& line : lines) {
 1038         linePath.moveTo(line.p1());
 1039         linePath.lineTo(line.p2());
 1040     }
 1041 
 1042     if (linePath.isEmpty()) {
 1043         recalcShapeAndBoundingRect();
 1044         return;
 1045     } else {
 1046         retransformArrow();
 1047         retransformTicks();
 1048     }
 1049 }
 1050 
 1051 void AxisPrivate::retransformArrow() {
 1052     if (suppressRetransform)
 1053         return;
 1054 
 1055     arrowPath = QPainterPath();
 1056     if (arrowType == Axis::ArrowType::NoArrow || lines.isEmpty()) {
 1057         recalcShapeAndBoundingRect();
 1058         return;
 1059     }
 1060 
 1061     if (arrowPosition == Axis::ArrowPosition::Right || arrowPosition == Axis::ArrowPosition::Both) {
 1062         const QPointF& endPoint = lines.at(lines.size()-1).p2();
 1063         this->addArrow(endPoint, 1);
 1064     }
 1065 
 1066     if (arrowPosition == Axis::ArrowPosition::Left || arrowPosition == Axis::ArrowPosition::Both) {
 1067         const QPointF& endPoint = lines.at(0).p1();
 1068         this->addArrow(endPoint, -1);
 1069     }
 1070 
 1071     recalcShapeAndBoundingRect();
 1072 }
 1073 
 1074 void AxisPrivate::addArrow(QPointF startPoint, int direction) {
 1075     static const double cos_phi = cos(M_PI/6.);
 1076 
 1077     if (orientation == Axis::Orientation::Horizontal) {
 1078         QPointF endPoint = QPointF(startPoint.x() + direction*arrowSize, startPoint.y());
 1079         arrowPath.moveTo(startPoint);
 1080         arrowPath.lineTo(endPoint);
 1081 
 1082         switch (arrowType) {
 1083             case Axis::ArrowType::NoArrow:
 1084                 break;
 1085             case Axis::ArrowType::SimpleSmall:
 1086                 arrowPath.moveTo(endPoint);
 1087                 arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()-arrowSize/4*cos_phi));
 1088                 arrowPath.moveTo(endPoint);
 1089                 arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()+arrowSize/4*cos_phi));
 1090                 break;
 1091             case Axis::ArrowType::SimpleBig:
 1092                 arrowPath.moveTo(endPoint);
 1093                 arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()-arrowSize/2*cos_phi));
 1094                 arrowPath.moveTo(endPoint);
 1095                 arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()+arrowSize/2*cos_phi));
 1096                 break;
 1097             case Axis::ArrowType::FilledSmall:
 1098                 arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()-arrowSize/4*cos_phi));
 1099                 arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()+arrowSize/4*cos_phi));
 1100                 arrowPath.lineTo(endPoint);
 1101                 break;
 1102             case Axis::ArrowType::FilledBig:
 1103                 arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()-arrowSize/2*cos_phi));
 1104                 arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()+arrowSize/2*cos_phi));
 1105                 arrowPath.lineTo(endPoint);
 1106                 break;
 1107             case Axis::ArrowType::SemiFilledSmall:
 1108                 arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()-arrowSize/4*cos_phi));
 1109                 arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/8, endPoint.y()));
 1110                 arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()+arrowSize/4*cos_phi));
 1111                 arrowPath.lineTo(endPoint);
 1112                 break;
 1113             case Axis::ArrowType::SemiFilledBig:
 1114                 arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()-arrowSize/2*cos_phi));
 1115                 arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()));
 1116                 arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()+arrowSize/2*cos_phi));
 1117                 arrowPath.lineTo(endPoint);
 1118                 break;
 1119         }
 1120     } else { //vertical orientation
 1121         QPointF endPoint = QPointF(startPoint.x(), startPoint.y()-direction*arrowSize);
 1122         arrowPath.moveTo(startPoint);
 1123         arrowPath.lineTo(endPoint);
 1124 
 1125         switch (arrowType) {
 1126             case Axis::ArrowType::NoArrow:
 1127                 break;
 1128             case Axis::ArrowType::SimpleSmall:
 1129                 arrowPath.moveTo(endPoint);
 1130                 arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4));
 1131                 arrowPath.moveTo(endPoint);
 1132                 arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4));
 1133                 break;
 1134             case Axis::ArrowType::SimpleBig:
 1135                 arrowPath.moveTo(endPoint);
 1136                 arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2));
 1137                 arrowPath.moveTo(endPoint);
 1138                 arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2));
 1139                 break;
 1140             case Axis::ArrowType::FilledSmall:
 1141                 arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4));
 1142                 arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4));
 1143                 arrowPath.lineTo(endPoint);
 1144                 break;
 1145             case Axis::ArrowType::FilledBig:
 1146                 arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2));
 1147                 arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2));
 1148                 arrowPath.lineTo(endPoint);
 1149                 break;
 1150             case Axis::ArrowType::SemiFilledSmall:
 1151                 arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4));
 1152                 arrowPath.lineTo(QPointF(endPoint.x(), endPoint.y()+direction*arrowSize/8));
 1153                 arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4));
 1154                 arrowPath.lineTo(endPoint);
 1155                 break;
 1156             case Axis::ArrowType::SemiFilledBig:
 1157                 arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2));
 1158                 arrowPath.lineTo(QPointF(endPoint.x(), endPoint.y()+direction*arrowSize/4));
 1159                 arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2));
 1160                 arrowPath.lineTo(endPoint);
 1161                 break;
 1162         }
 1163     }
 1164 }
 1165 
 1166 //! helper function for retransformTicks()
 1167 bool AxisPrivate::transformAnchor(QPointF* anchorPoint) {
 1168     QVector<QPointF> points;
 1169     points.append(*anchorPoint);
 1170     points = cSystem->mapLogicalToScene(points);
 1171 
 1172     if (points.count() != 1) { // point is not mappable or in a coordinate gap
 1173         return false;
 1174     } else {
 1175         *anchorPoint = points.at(0);
 1176         return true;
 1177     }
 1178 }
 1179 
 1180 /*!
 1181     recalculates the position of the axis ticks.
 1182  */
 1183 void AxisPrivate::retransformTicks() {
 1184     if (suppressRetransform)
 1185         return;
 1186 
 1187     //TODO: check that start and end are > 0 for log and >=0 for sqrt, etc.
 1188 
 1189     majorTicksPath = QPainterPath();
 1190     minorTicksPath = QPainterPath();
 1191     majorTickPoints.clear();
 1192     minorTickPoints.clear();
 1193     tickLabelValues.clear();
 1194 
 1195     if ( majorTicksNumber < 1 || (majorTicksDirection == Axis::noTicks && minorTicksDirection == Axis::noTicks) ) {
 1196         retransformTickLabelPositions(); //this calls recalcShapeAndBoundingRect()
 1197         return;
 1198     }
 1199 
 1200     //determine the increment for the major ticks
 1201     double majorTicksIncrement = 0;
 1202     int tmpMajorTicksNumber = 0;
 1203     if (majorTicksType == Axis::TicksType::TotalNumber) {
 1204         //the total number of major ticks is given - > determine the increment
 1205         tmpMajorTicksNumber = majorTicksNumber;
 1206         switch (scale) {
 1207             case Axis::Scale::Linear:
 1208                 majorTicksIncrement = end-start;
 1209                 break;
 1210             case Axis::Scale::Log10:
 1211                 majorTicksIncrement = log10(end)-log10(start);
 1212                 break;
 1213             case Axis::Scale::Log2:
 1214                 majorTicksIncrement = log2(end)-log2(start);
 1215                 break;
 1216             case Axis::Scale::Ln:
 1217                 majorTicksIncrement = log(end)-log(start);
 1218                 break;
 1219             case Axis::Scale::Sqrt:
 1220                 majorTicksIncrement = sqrt(end)-sqrt(start);
 1221                 break;
 1222             case Axis::Scale::X2:
 1223                 majorTicksIncrement = end*end - start*start;
 1224         }
 1225         if (majorTicksNumber > 1)
 1226             majorTicksIncrement /= majorTicksNumber - 1;
 1227     } else if (majorTicksType == Axis::TicksType::Spacing) {
 1228         //the increment of the major ticks is given -> determine the number
 1229         majorTicksIncrement = majorTicksSpacing * GSL_SIGN(end-start);
 1230         switch (scale) {
 1231             case Axis::Scale::Linear:
 1232                 tmpMajorTicksNumber = qRound((end-start)/majorTicksIncrement + 1);
 1233                 break;
 1234             case Axis::Scale::Log10:
 1235                 tmpMajorTicksNumber = qRound((log10(end)-log10(start))/majorTicksIncrement + 1);
 1236                 break;
 1237             case Axis::Scale::Log2:
 1238                 tmpMajorTicksNumber = qRound((log2(end)-log2(start))/majorTicksIncrement + 1);
 1239                 break;
 1240             case Axis::Scale::Ln:
 1241                 tmpMajorTicksNumber = qRound((log(end)-log(start))/majorTicksIncrement + 1);
 1242                 break;
 1243             case Axis::Scale::Sqrt:
 1244                 tmpMajorTicksNumber = qRound((sqrt(end)-sqrt(start))/majorTicksIncrement + 1);
 1245                 break;
 1246             case Axis::Scale::X2:
 1247                 tmpMajorTicksNumber = qRound((end*end - start*start)/majorTicksIncrement + 1);
 1248         }
 1249     } else { //custom column was provided
 1250         if (majorTicksColumn) {
 1251             tmpMajorTicksNumber = majorTicksColumn->rowCount();
 1252         } else {
 1253             retransformTickLabelPositions(); //this calls recalcShapeAndBoundingRect()
 1254             return;
 1255         }
 1256     }
 1257 
 1258     int tmpMinorTicksNumber;
 1259     if (minorTicksType == Axis::TicksType::TotalNumber)
 1260         tmpMinorTicksNumber = minorTicksNumber;
 1261     else if (minorTicksType == Axis::TicksType::Spacing)
 1262         tmpMinorTicksNumber = fabs(end - start)/ (majorTicksNumber - 1)/minorTicksIncrement - 1;
 1263     else
 1264         (minorTicksColumn) ? tmpMinorTicksNumber = minorTicksColumn->rowCount() : tmpMinorTicksNumber = 0;
 1265 
 1266     QPointF anchorPoint;
 1267     QPointF startPoint;
 1268     QPointF endPoint;
 1269     qreal majorTickPos = 0.0;
 1270     qreal minorTickPos;
 1271     qreal nextMajorTickPos = 0.0;
 1272     const int xDirection = cSystem->xDirection();
 1273     const int yDirection = cSystem->yDirection();
 1274     const double middleX = plot->xMin() + (plot->xMax() - plot->xMin())/2;
 1275     const double middleY = plot->yMin() + (plot->yMax() - plot->yMin())/2;
 1276     bool valid;
 1277 
 1278     //DEBUG("tmpMajorTicksNumber = " << tmpMajorTicksNumber)
 1279     for (int iMajor = 0; iMajor < tmpMajorTicksNumber; iMajor++) {
 1280         //DEBUG("major tick " << iMajor)
 1281         //calculate major tick's position
 1282         if (majorTicksType != Axis::TicksType::CustomColumn) {
 1283             switch (scale) {
 1284                 case Axis::Scale::Linear:
 1285                     majorTickPos = start + majorTicksIncrement * iMajor;
 1286                     nextMajorTickPos = majorTickPos + majorTicksIncrement;
 1287                     break;
 1288                 case Axis::Scale::Log10:
 1289                     majorTickPos = start * pow(10, majorTicksIncrement*iMajor);
 1290                     nextMajorTickPos = majorTickPos * pow(10, majorTicksIncrement);
 1291                     break;
 1292                 case Axis::Scale::Log2:
 1293                     majorTickPos = start * pow(2, majorTicksIncrement*iMajor);
 1294                     nextMajorTickPos = majorTickPos * pow(2, majorTicksIncrement);
 1295                     break;
 1296                 case Axis::Scale::Ln:
 1297                     majorTickPos = start * exp(majorTicksIncrement*iMajor);
 1298                     nextMajorTickPos = majorTickPos * exp(majorTicksIncrement);
 1299                     break;
 1300                 case Axis::Scale::Sqrt:
 1301                     majorTickPos = pow(sqrt(start) + majorTicksIncrement*iMajor, 2);
 1302                     nextMajorTickPos = pow(sqrt(start) + majorTicksIncrement*(iMajor+1), 2);
 1303                     break;
 1304                 case Axis::Scale::X2:
 1305                     majorTickPos = sqrt(start*start + majorTicksIncrement*iMajor);
 1306                     nextMajorTickPos = sqrt(start*start + majorTicksIncrement*(iMajor+1));
 1307                     break;
 1308             }
 1309         } else {    // custom column
 1310             if (!majorTicksColumn->isValid(iMajor) || majorTicksColumn->isMasked(iMajor))
 1311                 continue;
 1312             majorTickPos = majorTicksColumn->valueAt(iMajor);
 1313             // set next major tick pos for minor ticks
 1314             if (iMajor < tmpMajorTicksNumber - 1) {
 1315                 if (majorTicksColumn->isValid(iMajor+1) && !majorTicksColumn->isMasked(iMajor+1))
 1316                     nextMajorTickPos = majorTicksColumn->valueAt(iMajor+1);
 1317             } else  // last major tick
 1318                 tmpMinorTicksNumber = 0;
 1319         }
 1320 
 1321         //calculate start and end points for major tick's line
 1322         if (majorTicksDirection != Axis::noTicks) {
 1323             if (orientation == Axis::Orientation::Horizontal) {
 1324                 anchorPoint.setX(majorTickPos);
 1325                 anchorPoint.setY(offset);
 1326                 valid = transformAnchor(&anchorPoint);
 1327                 if (valid) {
 1328                     if (offset < middleY) {
 1329                         startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn)  ? yDirection * majorTicksLength  : 0);
 1330                         endPoint   = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut) ? -yDirection * majorTicksLength : 0);
 1331                     } else {
 1332                         startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut)  ? yDirection * majorTicksLength  : 0);
 1333                         endPoint   = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn) ? -yDirection * majorTicksLength : 0);
 1334                     }
 1335                 }
 1336             } else { // vertical
 1337                 anchorPoint.setY(majorTickPos);
 1338                 anchorPoint.setX(offset);
 1339                 valid = transformAnchor(&anchorPoint);
 1340 
 1341                 if (valid) {
 1342                     if (offset < middleX) {
 1343                         startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn)  ? xDirection * majorTicksLength  : 0, 0);
 1344                         endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? -xDirection * majorTicksLength : 0, 0);
 1345                     } else {
 1346                         startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? xDirection * majorTicksLength : 0, 0);
 1347                         endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn)  ? -xDirection *  majorTicksLength  : 0, 0);
 1348                     }
 1349                 }
 1350             }
 1351 
 1352             double value = scalingFactor * majorTickPos + zeroOffset;
 1353 
 1354             //if custom column is used, we can have duplicated values in it and we need only unique values
 1355             if (majorTicksType == Axis::TicksType::CustomColumn && tickLabelValues.indexOf(value) != -1)
 1356                 valid = false;
 1357 
 1358             //add major tick's line to the painter path
 1359             if (valid) {
 1360                 majorTicksPath.moveTo(startPoint);
 1361                 majorTicksPath.lineTo(endPoint);
 1362                 majorTickPoints << anchorPoint;
 1363                 tickLabelValues << value;
 1364             }
 1365         }
 1366 
 1367         //minor ticks
 1368         //DEBUG("   tmpMinorTicksNumber = " << tmpMinorTicksNumber)
 1369         if (Axis::noTicks != minorTicksDirection && tmpMajorTicksNumber > 1 && tmpMinorTicksNumber > 0 && iMajor < tmpMajorTicksNumber - 1 && nextMajorTickPos != majorTickPos) {
 1370             //minor ticks are placed at equidistant positions independent of the selected scaling for the major ticks positions
 1371             double minorTicksIncrement = (nextMajorTickPos - majorTickPos)/(tmpMinorTicksNumber + 1);
 1372             //DEBUG("   nextMajorTickPos = " << nextMajorTickPos)
 1373             //DEBUG("   majorTickPos = " << majorTickPos)
 1374             //DEBUG("   minorTicksIncrement = " << minorTicksIncrement)
 1375 
 1376             for (int iMinor = 0; iMinor < tmpMinorTicksNumber; iMinor++) {
 1377                 //calculate minor tick's position
 1378                 if (minorTicksType != Axis::TicksType::CustomColumn) {
 1379                     minorTickPos = majorTickPos + (iMinor + 1) * minorTicksIncrement;
 1380                 } else {
 1381                     if (!minorTicksColumn->isValid(iMinor) || minorTicksColumn->isMasked(iMinor))
 1382                         continue;
 1383                     minorTickPos = minorTicksColumn->valueAt(iMinor);
 1384 
 1385                     //in the case a custom column is used for the minor ticks, we draw them _once_ for the whole range of the axis.
 1386                     //execute the minor ticks loop only once.
 1387                     if (iMajor > 0)
 1388                         break;
 1389                 }
 1390                 //DEBUG("       minorTickPos = " << minorTickPos)
 1391 
 1392                 //calculate start and end points for minor tick's line
 1393                 if (orientation == Axis::Orientation::Horizontal) {
 1394                     anchorPoint.setX(minorTickPos);
 1395                     anchorPoint.setY(offset);
 1396                     valid = transformAnchor(&anchorPoint);
 1397 
 1398                     if (valid) {
 1399                         if (offset < middleY) {
 1400                             startPoint = anchorPoint + QPointF(0, (minorTicksDirection & Axis::ticksIn)  ? yDirection * minorTicksLength  : 0);
 1401                             endPoint   = anchorPoint + QPointF(0, (minorTicksDirection & Axis::ticksOut) ? -yDirection * minorTicksLength : 0);
 1402                         } else {
 1403                             startPoint = anchorPoint + QPointF(0, (minorTicksDirection & Axis::ticksOut)  ? yDirection * minorTicksLength  : 0);
 1404                             endPoint   = anchorPoint + QPointF(0, (minorTicksDirection & Axis::ticksIn) ? -yDirection * minorTicksLength : 0);
 1405                         }
 1406                     }
 1407                 } else { // vertical
 1408                     anchorPoint.setY(minorTickPos);
 1409                     anchorPoint.setX(offset);
 1410                     valid = transformAnchor(&anchorPoint);
 1411 
 1412                     if (valid) {
 1413                         if (offset < middleX) {
 1414                             startPoint = anchorPoint + QPointF((minorTicksDirection & Axis::ticksIn)  ? xDirection * minorTicksLength  : 0, 0);
 1415                             endPoint   = anchorPoint + QPointF((minorTicksDirection & Axis::ticksOut) ? -xDirection * minorTicksLength : 0, 0);
 1416                         } else {
 1417                             startPoint = anchorPoint + QPointF((minorTicksDirection & Axis::ticksOut)  ? xDirection * minorTicksLength  : 0, 0);
 1418                             endPoint   = anchorPoint + QPointF((minorTicksDirection & Axis::ticksIn) ? -xDirection * minorTicksLength : 0, 0);
 1419                         }
 1420                     }
 1421                 }
 1422 
 1423                 //add minor tick's line to the painter path
 1424                 if (valid) {
 1425                     minorTicksPath.moveTo(startPoint);
 1426                     minorTicksPath.lineTo(endPoint);
 1427                     minorTickPoints << anchorPoint;
 1428                 }
 1429             }
 1430         }
 1431     }
 1432 
 1433     //tick positions where changed -> update the position of the tick labels and grid lines
 1434     retransformTickLabelStrings();
 1435     retransformMajorGrid();
 1436     retransformMinorGrid();
 1437 }
 1438 
 1439 /*!
 1440     creates the tick label strings starting with the most optimal
 1441     (=the smallest possible number of float digits) precision for the floats
 1442 */
 1443 void AxisPrivate::retransformTickLabelStrings() {
 1444     DEBUG(Q_FUNC_INFO)
 1445     if (suppressRetransform)
 1446         return;
 1447 
 1448     if (labelsAutoPrecision) {
 1449         //check, whether we need to increase the current precision
 1450         int newPrecision = upperLabelsPrecision(labelsPrecision, labelsFormat);
 1451         if (newPrecision != labelsPrecision) {
 1452             labelsPrecision = newPrecision;
 1453             emit q->labelsPrecisionChanged(labelsPrecision);
 1454         } else {
 1455             //check, whether we can reduce the current precision
 1456             newPrecision = lowerLabelsPrecision(labelsPrecision, labelsFormat);
 1457             if (newPrecision != labelsPrecision) {
 1458                 labelsPrecision = newPrecision;
 1459                 emit q->labelsPrecisionChanged(labelsPrecision);
 1460             }
 1461         }
 1462     }
 1463     //DEBUG("labelsPrecision =" << labelsPrecision);
 1464 
 1465     //automatically switch from 'decimal' to 'scientific' format for big numbers (>10^4)
 1466     //and back to decimal when the numbers get smaller after the auto-switch again
 1467     if (labelsFormat == Axis::LabelsFormat::Decimal && !labelsFormatDecimalOverruled) {
 1468         for (auto value : tickLabelValues) {
 1469             if (std::abs(value) > 1e4) {
 1470                 labelsFormat = Axis::LabelsFormat::ScientificE;
 1471                 emit q->labelsFormatChanged(labelsFormat);
 1472                 labelsFormatAutoChanged = true;
 1473                 break;
 1474             }
 1475         }
 1476     } else if (labelsFormatAutoChanged ) {
 1477         //check whether we still have big numbers
 1478         bool changeBack = true;
 1479         for (auto value : tickLabelValues) {
 1480             if (std::abs(value) > 1e4) {
 1481                 changeBack = false;
 1482                 break;
 1483             }
 1484         }
 1485 
 1486         if (changeBack) {
 1487             labelsFormatAutoChanged = false;
 1488             labelsFormat = Axis::LabelsFormat::Decimal;
 1489             emit q->labelsFormatChanged(labelsFormat);
 1490         }
 1491     }
 1492 
 1493     tickLabelStrings.clear();
 1494     QString str;
 1495     SET_NUMBER_LOCALE
 1496     if ( (orientation == Axis::Orientation::Horizontal && plot->xRangeFormat() == CartesianPlot::RangeFormat::Numeric)
 1497         || (orientation == Axis::Orientation::Vertical && plot->yRangeFormat() == CartesianPlot::RangeFormat::Numeric) ) {
 1498         if (labelsFormat == Axis::LabelsFormat::Decimal) {
 1499             QString nullStr = numberLocale.toString(0., 'f', labelsPrecision);
 1500             for (const auto value : tickLabelValues) {
 1501                 str = numberLocale.toString(value, 'f', labelsPrecision);
 1502                 if (str == "-" + nullStr) str = nullStr;
 1503                 str = labelsPrefix + str + labelsSuffix;
 1504                 tickLabelStrings << str;
 1505             }
 1506         } else if (labelsFormat == Axis::LabelsFormat::ScientificE) {
 1507             QString nullStr = numberLocale.toString(0., 'e', labelsPrecision);
 1508             for (const auto value : tickLabelValues) {
 1509                 if (value == 0) // just show "0"
 1510                     str = numberLocale.toString(value, 'f', 0);
 1511                 else
 1512                     str = numberLocale.toString(value, 'e', labelsPrecision);
 1513                 if (str == "-" + nullStr) str = nullStr;
 1514                 str = labelsPrefix + str + labelsSuffix;
 1515                 tickLabelStrings << str;
 1516             }
 1517         } else if (labelsFormat == Axis::LabelsFormat::Powers10) {
 1518             for (const auto value : tickLabelValues) {
 1519                 if (value == 0) // just show "0"
 1520                     str = numberLocale.toString(value, 'f', 0);
 1521                 else {
 1522                     str = "10<sup>" + numberLocale.toString(log10(qAbs(value)), 'f', labelsPrecision) + "</sup>";
 1523                     if (value < 0)
 1524                         str.prepend("-");
 1525                 }
 1526                 str = labelsPrefix + str + labelsSuffix;
 1527                 tickLabelStrings << str;
 1528             }
 1529         } else if (labelsFormat == Axis::LabelsFormat::Powers2) {
 1530             for (const auto value : tickLabelValues) {
 1531                 if (value == 0) // just show "0"
 1532                     str = numberLocale.toString(value, 'f', 0);
 1533                 else  {
 1534                     str = "2<span style=\"vertical-align:super\">" + numberLocale.toString(log2(qAbs(value)), 'f', labelsPrecision) + "</span>";
 1535                     if (value < 0)
 1536                         str.prepend("-");
 1537                 }
 1538                 str = labelsPrefix + str + labelsSuffix;
 1539                 tickLabelStrings << str;
 1540             }
 1541         } else if (labelsFormat == Axis::LabelsFormat::PowersE) {
 1542             for (const auto value : tickLabelValues) {
 1543                 if (value == 0) // just show "0"
 1544                     str = numberLocale.toString(value, 'f', 0);
 1545                 else {
 1546                     str = "e<span style=\"vertical-align:super\">" + numberLocale.toString(log(qAbs(value)), 'f', labelsPrecision) + "</span>";
 1547                     if (value < 0)
 1548                         str.prepend("-");
 1549                 }
 1550                 str = labelsPrefix + str + labelsSuffix;
 1551                 tickLabelStrings << str;
 1552             }
 1553         } else if (labelsFormat == Axis::LabelsFormat::MultipliesPi) {
 1554             for (const auto value : tickLabelValues) {
 1555                 if (value == 0) // just show "0"
 1556                     str = numberLocale.toString(value, 'f', 0);
 1557                 else
 1558                     str = "<span>" + numberLocale.toString(value / M_PI, 'f', labelsPrecision) + "</span>" + QChar(0x03C0);
 1559                 str = labelsPrefix + str + labelsSuffix;
 1560                 tickLabelStrings << str;
 1561             }
 1562         }
 1563     } else {
 1564         for (const auto value : tickLabelValues) {
 1565             QDateTime dateTime;
 1566             dateTime.setMSecsSinceEpoch(value);
 1567             str = dateTime.toString(labelsDateTimeFormat);
 1568             str = labelsPrefix + str + labelsSuffix;
 1569             tickLabelStrings << str;
 1570         }
 1571     }
 1572 
 1573     //recalculate the position of the tick labels
 1574     retransformTickLabelPositions();
 1575 }
 1576 
 1577 /*!
 1578     returns the smallest upper limit for the precision
 1579     where no duplicates for the tick label float occur.
 1580  */
 1581 int AxisPrivate::upperLabelsPrecision(const int precision, const Axis::LabelsFormat format) {
 1582     DEBUG(Q_FUNC_INFO << ", precision = " << precision);
 1583 
 1584     // avoid problems with zero range axis
 1585     if (tickLabelValues.isEmpty() || qFuzzyCompare(tickLabelValues.constFirst(), tickLabelValues.constLast())) {
 1586         DEBUG(Q_FUNC_INFO << ", zero range axis detected. ticklabel values: ")
 1587         QDEBUG(Q_FUNC_INFO << tickLabelValues)
 1588         return 0;
 1589     }
 1590 
 1591     //round float to the current precision and look for duplicates.
 1592     //if there are duplicates, increase the precision.
 1593     QVector<double> tempValues;
 1594     switch (format) {
 1595     case Axis::LabelsFormat::Decimal:
 1596     case Axis::LabelsFormat::MultipliesPi:
 1597         for (const auto value : tickLabelValues)
 1598             tempValues.append( nsl_math_round_places(value, precision) );
 1599         break;
 1600     case Axis::LabelsFormat::ScientificE:
 1601         for (const auto value : tickLabelValues)
 1602             tempValues.append( nsl_math_round_precision(value, precision) );
 1603         break;
 1604     case Axis::LabelsFormat::Powers10:
 1605         for (const auto value : tickLabelValues)
 1606             tempValues.append( nsl_math_round_places(log10(qAbs(value)), precision) );
 1607         break;
 1608     case Axis::LabelsFormat::Powers2:
 1609         for (const auto value : tickLabelValues)
 1610             tempValues.append( nsl_math_round_places(log2(qAbs(value)), precision) );
 1611         break;
 1612     case Axis::LabelsFormat::PowersE:
 1613         for (const auto value : tickLabelValues)
 1614             tempValues.append( nsl_math_round_places(log(qAbs(value)), precision) );
 1615     }
 1616 
 1617     for (int i = 0; i < tempValues.size(); ++i) {
 1618         for (int j = 0; j < tempValues.size(); ++j) {
 1619             if (i == j)
 1620                 continue;
 1621 
 1622             //duplicate for the current precision found, increase the precision and check again
 1623             if (tempValues.at(i) == tempValues.at(j))
 1624                 return upperLabelsPrecision(precision + 1, format);
 1625         }
 1626     }
 1627 
 1628     //no duplicates for the current precision found: return the current value
 1629     DEBUG(Q_FUNC_INFO << ", upper precision = " << precision);
 1630     return precision;
 1631 }
 1632 
 1633 /*!
 1634     returns highest lower limit for the precision
 1635     where no duplicates for the tick label float occur.
 1636 */
 1637 int AxisPrivate::lowerLabelsPrecision(const int precision, const Axis::LabelsFormat format) {
 1638     DEBUG(Q_FUNC_INFO << ", precision = " << precision);
 1639     //round value to the current precision and look for duplicates.
 1640     //if there are duplicates, decrease the precision.
 1641     QVector<double> tempValues;
 1642     switch (format) {
 1643     case Axis::LabelsFormat::Decimal:
 1644     case Axis::LabelsFormat::MultipliesPi:
 1645         for (auto value : tickLabelValues)
 1646             tempValues.append( nsl_math_round_places(value, precision-1) );
 1647         break;
 1648     case Axis::LabelsFormat::ScientificE:
 1649         for (auto value : tickLabelValues)
 1650             tempValues.append( nsl_math_round_precision(value, precision-1) );
 1651         break;
 1652     case Axis::LabelsFormat::Powers10:
 1653         for (auto value : tickLabelValues)
 1654             tempValues.append( nsl_math_round_places(log10(qAbs(value)), precision-1) );
 1655         break;
 1656     case Axis::LabelsFormat::Powers2:
 1657         for (auto value : tickLabelValues)
 1658             tempValues.append( nsl_math_round_places(log2(qAbs(value)), precision-1) );
 1659         break;
 1660     case Axis::LabelsFormat::PowersE:
 1661         for (auto value : tickLabelValues)
 1662             tempValues.append( nsl_math_round_places(log(qAbs(value)), precision-1) );
 1663     }
 1664 
 1665 
 1666     //check whether we have duplicates with reduced precision
 1667     //-> current precision cannot be reduced, return the current value
 1668     for (int i = 0; i < tempValues.size(); ++i) {
 1669         for (int j = 0; j < tempValues.size(); ++j) {
 1670             if (i == j) continue;
 1671             if (tempValues.at(i) == tempValues.at(j))
 1672                 return precision;
 1673         }
 1674     }
 1675 
 1676     if (precision == 0) {
 1677         bool hasDoubles = false;
 1678         for (auto value : tickLabelValues) {
 1679             if (floor(value) != value) {
 1680                 hasDoubles = true;
 1681                 break;
 1682             }
 1683         }
 1684 
 1685         //if we have double values we don't want to show them as integers, keep at least one float digit.
 1686         if (hasDoubles)
 1687             return 1;
 1688         else
 1689             return 0;
 1690     } else {
 1691         //no duplicates found, reduce further, and check again
 1692         return lowerLabelsPrecision(precision - 1, format);
 1693     }
 1694 }
 1695 
 1696 /*!
 1697     recalculates the position of the tick labels.
 1698     Called when the geometry related properties (position, offset, font size, suffix, prefix) of the labels are changed.
 1699  */
 1700 void AxisPrivate::retransformTickLabelPositions() {
 1701     tickLabelPoints.clear();
 1702     if (majorTicksDirection == Axis::noTicks || labelsPosition == Axis::LabelsPosition::NoLabels) {
 1703         recalcShapeAndBoundingRect();
 1704         return;
 1705     }
 1706 
 1707     QFontMetrics fm(labelsFont);
 1708     double width = 0;
 1709     double height = fm.ascent();
 1710     QPointF pos;
 1711     const double middleX = plot->xMin() + (plot->xMax() - plot->xMin())/2;
 1712     const double middleY = plot->yMin() + (plot->yMax() - plot->yMin())/2;
 1713     const int xDirection = cSystem->xDirection();
 1714     const int yDirection = cSystem->yDirection();
 1715 
 1716     QPointF startPoint, endPoint, anchorPoint;
 1717 
 1718     QTextDocument td;
 1719     td.setDefaultFont(labelsFont);
 1720     const double cosine = cos(labelsRotationAngle * M_PI / 180.); // calculate only one time
 1721     const double sine = sin(labelsRotationAngle * M_PI / 180.); // calculate only one time
 1722     for ( int i = 0; i < majorTickPoints.size(); i++ ) {
 1723         if ((orientation == Axis::Orientation::Horizontal && plot->xRangeFormat() == CartesianPlot::RangeFormat::Numeric) ||
 1724                 (orientation == Axis::Orientation::Vertical && plot->yRangeFormat() == CartesianPlot::RangeFormat::Numeric)) {
 1725             if (labelsFormat == Axis::LabelsFormat::Decimal || labelsFormat == Axis::LabelsFormat::ScientificE) {
 1726                 width = fm.boundingRect(tickLabelStrings.at(i)).width();
 1727             } else {
 1728                 td.setHtml(tickLabelStrings.at(i));
 1729                 width = td.size().width();
 1730                 height = td.size().height();
 1731             }
 1732         } else { // Datetime
 1733             width = fm.boundingRect(tickLabelStrings.at(i)).width();
 1734         }
 1735 
 1736         const double diffx = cosine * width;
 1737         const double diffy = sine * width;
 1738         anchorPoint = majorTickPoints.at(i);
 1739 
 1740         //center align all labels with respect to the end point of the tick line
 1741         if (orientation == Axis::Orientation::Horizontal) {
 1742             if (offset < middleY) {
 1743                 startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn)  ? yDirection * majorTicksLength  : 0);
 1744                 endPoint   = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut) ? -yDirection * majorTicksLength : 0);
 1745             } else {
 1746                 startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut)  ? yDirection * majorTicksLength  : 0);
 1747                 endPoint   = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn) ? -yDirection * majorTicksLength : 0);
 1748             }
 1749 
 1750             // for rotated labels (angle is not zero), align label's corner at the position of the tick
 1751             if (fabs(fabs(labelsRotationAngle) - 180.) < 1.e-2) { // +-180°
 1752                 if (labelsPosition == Axis::LabelsPosition::Out) {
 1753                     pos.setX(endPoint.x() + width/2);
 1754                     pos.setY(endPoint.y() + labelsOffset );
 1755                 } else {
 1756                     pos.setX(startPoint.x() + width/2);
 1757                     pos.setY(startPoint.y() - height - labelsOffset);
 1758                 }
 1759             } else if (labelsRotationAngle <= -0.01) { // [-0.01°, -180°)
 1760                 if (labelsPosition == Axis::LabelsPosition::Out) {
 1761                     pos.setX(endPoint.x() + sine * height/2);
 1762                     pos.setY(endPoint.y() + labelsOffset + cosine * height/2);
 1763                 } else {
 1764                     pos.setX(startPoint.x() + sine * height/2 - diffx);
 1765                     pos.setY(startPoint.y() - labelsOffset + cosine * height/2 + diffy);
 1766                 }
 1767             } else if (labelsRotationAngle >= 0.01) { // [0.01°, 180°)
 1768                 if (labelsPosition == Axis::LabelsPosition::Out) {
 1769                     pos.setX(endPoint.x() - diffx + sine * height/2);
 1770                     pos.setY(endPoint.y() + labelsOffset + diffy + cosine * height/2);
 1771                 } else {
 1772                     pos.setX(startPoint.x() + sine * height/2);
 1773                     pos.setY(startPoint.y() - labelsOffset + cosine * height/2);
 1774                 }
 1775             } else {    // 0°
 1776                 if (labelsPosition == Axis::LabelsPosition::Out) {
 1777                     pos.setX(endPoint.x() - width/2);
 1778                     pos.setY(endPoint.y() + height + labelsOffset);
 1779                 } else {
 1780                     pos.setX(startPoint.x() - width/2);
 1781                     pos.setY(startPoint.y() - labelsOffset);
 1782                 }
 1783             }
 1784         // ---------------------- vertical -------------------------
 1785         } else {
 1786             if (offset < middleX) {
 1787                 startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn)  ? xDirection * majorTicksLength  : 0, 0);
 1788                 endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? -xDirection * majorTicksLength : 0, 0);
 1789             } else {
 1790                 startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? xDirection * majorTicksLength : 0, 0);
 1791                 endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn)  ? -xDirection *  majorTicksLength  : 0, 0);
 1792             }
 1793 
 1794             if (fabs(labelsRotationAngle - 90.) < 1.e-2) { // +90°
 1795                 if (labelsPosition == Axis::LabelsPosition::Out) {
 1796                     pos.setX(endPoint.x() - labelsOffset);
 1797                     pos.setY(endPoint.y() + width/2 );
 1798                 } else {
 1799                     pos.setX(startPoint.x() + labelsOffset);
 1800                     pos.setY(startPoint.y() + width/2);
 1801                 }
 1802             } else if (fabs(labelsRotationAngle + 90.) < 1.e-2) { // -90°
 1803                 if (labelsPosition == Axis::LabelsPosition::Out) {
 1804                     pos.setX(endPoint.x() - labelsOffset - height);
 1805                     pos.setY(endPoint.y() - width/2);
 1806                 } else {
 1807                     pos.setX(startPoint.x() + labelsOffset);
 1808                     pos.setY(startPoint.y() - width/2);
 1809                 }
 1810             } else if (fabs(fabs(labelsRotationAngle) - 180.) < 1.e-2) { // +-180°
 1811                 if (labelsPosition == Axis::LabelsPosition::Out) {
 1812                     pos.setX(endPoint.x() - labelsOffset);
 1813                     pos.setY(endPoint.y() - height/2);
 1814                 } else {
 1815                     pos.setX(startPoint.x() + labelsOffset + width);
 1816                     pos.setY(startPoint.y() - height/2);
 1817                 }
 1818             } else if (fabs(labelsRotationAngle) >= 0.01 && fabs(labelsRotationAngle) <= 89.99) { // [0.01°, 90°)
 1819                 if (labelsPosition == Axis::LabelsPosition::Out) {
 1820                     // left
 1821                     pos.setX(endPoint.x() - labelsOffset - diffx + sine * height/2);
 1822                     pos.setY(endPoint.y() + cosine * height/2 + diffy);
 1823                 } else {
 1824                     pos.setX(startPoint.x() + labelsOffset + sine * height/2);
 1825                     pos.setY(startPoint.y() + cosine * height/2);
 1826                 }
 1827             } else if (fabs(labelsRotationAngle) >= 90.01 && fabs(labelsRotationAngle) <= 179.99) { // [90.01, 180)
 1828                 if (labelsPosition == Axis::LabelsPosition::Out) {
 1829                     // left
 1830                     pos.setX(endPoint.x() - labelsOffset + sine * height/2);
 1831                     pos.setY(endPoint.y() + cosine * height/2);
 1832                 } else {
 1833                     pos.setX(startPoint.x() + labelsOffset - diffx + sine * height/2);
 1834                     pos.setY(startPoint.y() + diffy + cosine * height/2);
 1835                 }
 1836             } else { // 0°
 1837                 if (labelsPosition == Axis::LabelsPosition::Out) {
 1838                     pos.setX(endPoint.x() - width - labelsOffset);
 1839                     pos.setY(endPoint.y() + height/2);
 1840                 } else {
 1841                     pos.setX(startPoint.x() + labelsOffset);
 1842                     pos.setY(startPoint.y() + height/2);
 1843                 }
 1844             }
 1845         }
 1846         tickLabelPoints << pos;
 1847     }
 1848 
 1849     recalcShapeAndBoundingRect();
 1850 }
 1851 
 1852 void AxisPrivate::retransformMajorGrid() {
 1853     if (suppressRetransform)
 1854         return;
 1855 
 1856     majorGridPath = QPainterPath();
 1857     if (majorGridPen.style() == Qt::NoPen || majorTickPoints.size() == 0) {
 1858         recalcShapeAndBoundingRect();
 1859         return;
 1860     }
 1861 
 1862     //major tick points are already in scene coordinates, convert them back to logical...
 1863     //TODO: mapping should work without SuppressPageClipping-flag, check float comparisons in the map-function.
 1864     //Currently, grid lines disappear somtimes without this flag
 1865     QVector<QPointF> logicalMajorTickPoints = cSystem->mapSceneToLogical(majorTickPoints, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 1866 
 1867     if (logicalMajorTickPoints.isEmpty())
 1868         return;
 1869 
 1870     //TODO:
 1871     //when iterating over all grid lines, skip the first and the last points for auto scaled axes,
 1872     //since we don't want to paint any grid lines at the plot boundaries
 1873     bool skipLowestTick, skipUpperTick;
 1874     if (orientation == Axis::Orientation::Horizontal) { //horizontal axis
 1875         skipLowestTick = qFuzzyCompare(logicalMajorTickPoints.at(0).x(), plot->xMin());
 1876         skipUpperTick = qFuzzyCompare(logicalMajorTickPoints.at(logicalMajorTickPoints.size()-1).x(), plot->xMax());
 1877     } else {
 1878         skipLowestTick = qFuzzyCompare(logicalMajorTickPoints.at(0).y(), plot->yMin());
 1879         skipUpperTick = qFuzzyCompare(logicalMajorTickPoints.at(logicalMajorTickPoints.size()-1).y(), plot->yMax());
 1880     }
 1881 
 1882     int start, end;
 1883     if (skipLowestTick) {
 1884         if (logicalMajorTickPoints.size() > 1)
 1885             start = 1;
 1886         else
 1887             start = 0;
 1888     } else {
 1889         start = 0;
 1890     }
 1891 
 1892     if (skipUpperTick) {
 1893         if (logicalMajorTickPoints.size() > 1)
 1894             end = logicalMajorTickPoints.size() - 1;
 1895         else
 1896             end = 0;
 1897 
 1898     } else {
 1899         end = logicalMajorTickPoints.size();
 1900     }
 1901 
 1902     QVector<QLineF> lines;
 1903     if (orientation == Axis::Orientation::Horizontal) { //horizontal axis
 1904         double yMin = plot->yMin();
 1905         double yMax = plot->yMax();
 1906 
 1907         for (int i = start; i < end; ++i) {
 1908             const QPointF& point = logicalMajorTickPoints.at(i);
 1909             lines.append( QLineF(point.x(), yMin, point.x(), yMax) );
 1910         }
 1911     } else { //vertical axis
 1912         double xMin = plot->xMin();
 1913         double xMax = plot->xMax();
 1914 
 1915         //skip the first and the last points, since we don't want to paint any grid lines at the plot boundaries
 1916         for (int i = start; i < end; ++i) {
 1917             const QPointF& point = logicalMajorTickPoints.at(i);
 1918             lines.append( QLineF(xMin, point.y(), xMax, point.y()) );
 1919         }
 1920     }
 1921 
 1922     lines = cSystem->mapLogicalToScene(lines, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 1923     for (const auto& line : lines) {
 1924         majorGridPath.moveTo(line.p1());
 1925         majorGridPath.lineTo(line.p2());
 1926     }
 1927 
 1928     recalcShapeAndBoundingRect();
 1929 }
 1930 
 1931 void AxisPrivate::retransformMinorGrid() {
 1932     if (suppressRetransform)
 1933         return;
 1934 
 1935     minorGridPath = QPainterPath();
 1936     if (minorGridPen.style() == Qt::NoPen) {
 1937         recalcShapeAndBoundingRect();
 1938         return;
 1939     }
 1940 
 1941     //minor tick points are already in scene coordinates, convert them back to logical...
 1942     //TODO: mapping should work without SuppressPageClipping-flag, check float comparisons in the map-function.
 1943     //Currently, grid lines disappear somtimes without this flag
 1944     QVector<QPointF> logicalMinorTickPoints = cSystem->mapSceneToLogical(minorTickPoints, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 1945 
 1946     QVector<QLineF> lines;
 1947     if (orientation == Axis::Orientation::Horizontal) { //horizontal axis
 1948         double yMin = plot->yMin();
 1949         double yMax = plot->yMax();
 1950 
 1951         for (const auto point : logicalMinorTickPoints)
 1952             lines.append( QLineF(point.x(), yMin, point.x(), yMax) );
 1953     } else { //vertical axis
 1954         double xMin = plot->xMin();
 1955         double xMax = plot->xMax();
 1956 
 1957         for (const auto point: logicalMinorTickPoints)
 1958             lines.append( QLineF(xMin, point.y(), xMax, point.y()) );
 1959     }
 1960 
 1961     lines = cSystem->mapLogicalToScene(lines, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
 1962     for (const auto& line : lines) {
 1963         minorGridPath.moveTo(line.p1());
 1964         minorGridPath.lineTo(line.p2());
 1965     }
 1966 
 1967     recalcShapeAndBoundingRect();
 1968 }
 1969 
 1970 /*!
 1971  * called when the opacity of the grid was changes, update the grid graphics item
 1972  */
 1973 //TODO: this function is only needed for loaded projects where update() doesn't seem to be enough
 1974 //and we have to call gridItem->update() explicitly.
 1975 //This is not required for newly created plots/axes. Why is this difference?
 1976 void AxisPrivate::updateGrid() {
 1977     gridItem->update();
 1978 }
 1979 
 1980 void AxisPrivate::recalcShapeAndBoundingRect() {
 1981     if (m_suppressRecalc)
 1982         return;
 1983 
 1984     prepareGeometryChange();
 1985 
 1986     if (linePath.isEmpty()) {
 1987         axisShape = QPainterPath();
 1988         boundingRectangle = QRectF();
 1989         title->setPositionInvalid(true);
 1990         if (plot) plot->prepareGeometryChange();
 1991         return;
 1992     } else {
 1993         title->setPositionInvalid(false);
 1994     }
 1995 
 1996     axisShape = WorksheetElement::shapeFromPath(linePath, linePen);
 1997     axisShape.addPath(WorksheetElement::shapeFromPath(arrowPath, linePen));
 1998     axisShape.addPath(WorksheetElement::shapeFromPath(majorTicksPath, majorTicksPen));
 1999     axisShape.addPath(WorksheetElement::shapeFromPath(minorTicksPath, minorTicksPen));
 2000 
 2001     QPainterPath tickLabelsPath = QPainterPath();
 2002     if (labelsPosition != Axis::LabelsPosition::NoLabels) {
 2003         QTransform trafo;
 2004         QPainterPath tempPath;
 2005         QFontMetrics fm(labelsFont);
 2006         QTextDocument td;
 2007         td.setDefaultFont(labelsFont);
 2008         for (int i = 0; i < tickLabelPoints.size(); i++) {
 2009             tempPath = QPainterPath();
 2010             if (labelsFormat == Axis::LabelsFormat::Decimal || labelsFormat == Axis::LabelsFormat::ScientificE) {
 2011                 tempPath.addRect(fm.boundingRect(tickLabelStrings.at(i)));
 2012             } else {
 2013                 td.setHtml(tickLabelStrings.at(i));
 2014                 tempPath.addRect(QRectF(0, -td.size().height(), td.size().width(), td.size().height()));
 2015             }
 2016 
 2017             trafo.reset();
 2018             trafo.translate( tickLabelPoints.at(i).x(), tickLabelPoints.at(i).y() );
 2019 
 2020             trafo.rotate(-labelsRotationAngle);
 2021             tempPath = trafo.map(tempPath);
 2022 
 2023             tickLabelsPath.addPath(WorksheetElement::shapeFromPath(tempPath, linePen));
 2024         }
 2025         axisShape.addPath(WorksheetElement::shapeFromPath(tickLabelsPath, QPen()));
 2026     }
 2027 
 2028     //add title label, if available
 2029     if ( title->isVisible() && !title->text().text.isEmpty() ) {
 2030         const QRectF& titleRect = title->graphicsItem()->boundingRect();
 2031         if (titleRect.width() != 0.0 &&  titleRect.height() != 0.0) {
 2032             //determine the new position of the title label:
 2033             //we calculate the new position here and not in retransform(),
 2034             //since it depends on the size and position of the tick labels, tickLabelsPath, available here.
 2035             QRectF rect = linePath.boundingRect();
 2036             qreal offsetX = titleOffsetX; //the distance to the axis line
 2037             qreal offsetY = titleOffsetY; //the distance to the axis line
 2038             if (orientation == Axis::Orientation::Horizontal) {
 2039                 offsetY -= titleRect.height()/2;
 2040                 if (labelsPosition == Axis::LabelsPosition::Out)
 2041                     offsetY -= labelsOffset + tickLabelsPath.boundingRect().height();
 2042                 title->setPosition( QPointF( (rect.topLeft().x() + rect.topRight().x())/2 + titleOffsetX, rect.bottomLeft().y() - offsetY ) );
 2043             } else {
 2044                 offsetX -= titleRect.width()/2;
 2045                 if (labelsPosition == Axis::LabelsPosition::Out)
 2046                     offsetX -= labelsOffset+ tickLabelsPath.boundingRect().width();
 2047                 title->setPosition( QPointF( rect.topLeft().x() + offsetX, (rect.topLeft().y() + rect.bottomLeft().y())/2 - titleOffsetY) );
 2048             }
 2049             axisShape.addPath(WorksheetElement::shapeFromPath(title->graphicsItem()->mapToParent(title->graphicsItem()->shape()), linePen));
 2050         }
 2051     }
 2052 
 2053     boundingRectangle = axisShape.boundingRect();
 2054 
 2055     //if the axis goes beyond the current bounding box of the plot (too high offset is used, too long labels etc.)
 2056     //request a prepareGeometryChange() for the plot in order to properly keep track of geometry changes
 2057     if (plot)
 2058         plot->prepareGeometryChange();
 2059 }
 2060 
 2061 /*!
 2062     paints the content of the axis. Reimplemented from \c QGraphicsItem.
 2063     \sa QGraphicsItem::paint()
 2064  */
 2065 void AxisPrivate::paint(QPainter *painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
 2066     Q_UNUSED(option)
 2067     Q_UNUSED(widget)
 2068 
 2069     if (!isVisible())
 2070         return;
 2071 
 2072     if (linePath.isEmpty())
 2073         return;
 2074 
 2075     //draw the line
 2076     if (linePen.style() != Qt::NoPen) {
 2077         painter->setOpacity(lineOpacity);
 2078         painter->setPen(linePen);
 2079         painter->setBrush(Qt::SolidPattern);
 2080         painter->drawPath(linePath);
 2081 
 2082         //draw the arrow
 2083         if (arrowType != Axis::ArrowType::NoArrow)
 2084             painter->drawPath(arrowPath);
 2085     }
 2086 
 2087     //draw the major ticks
 2088     if (majorTicksDirection != Axis::noTicks) {
 2089         painter->setOpacity(majorTicksOpacity);
 2090         painter->setPen(majorTicksPen);
 2091         painter->setBrush(Qt::NoBrush);
 2092         painter->drawPath(majorTicksPath);
 2093     }
 2094 
 2095     //draw the minor ticks
 2096     if (minorTicksDirection != Axis::noTicks) {
 2097         painter->setOpacity(minorTicksOpacity);
 2098         painter->setPen(minorTicksPen);
 2099         painter->setBrush(Qt::NoBrush);
 2100         painter->drawPath(minorTicksPath);
 2101     }
 2102 
 2103     // draw tick labels
 2104     if (labelsPosition != Axis::LabelsPosition::NoLabels) {
 2105         painter->setOpacity(labelsOpacity);
 2106         painter->setPen(QPen(labelsColor));
 2107         painter->setFont(labelsFont);
 2108         QTextDocument doc;
 2109         doc.setDefaultFont(labelsFont);
 2110         QFontMetrics fm(labelsFont);
 2111         if ((orientation == Axis::Orientation::Horizontal && plot->xRangeFormat() == CartesianPlot::RangeFormat::Numeric) ||
 2112                 (orientation == Axis::Orientation::Vertical && plot->yRangeFormat() == CartesianPlot::RangeFormat::Numeric)) {
 2113             //QDEBUG(Q_FUNC_INFO << ", axis tick label strings: " << tickLabelStrings)
 2114             for (int i = 0; i < tickLabelPoints.size(); i++) {
 2115                 painter->translate(tickLabelPoints.at(i));
 2116                 painter->save();
 2117                 painter->rotate(-labelsRotationAngle);
 2118 
 2119                 if (labelsFormat == Axis::LabelsFormat::Decimal || labelsFormat == Axis::LabelsFormat::ScientificE) {
 2120                     if (labelsBackgroundType != Axis::LabelsBackgroundType::Transparent) {
 2121                         const QRect& rect = fm.boundingRect(tickLabelStrings.at(i));
 2122                         painter->fillRect(rect, labelsBackgroundColor);
 2123                     }
 2124                     painter->drawText(QPoint(0, 0), tickLabelStrings.at(i));
 2125                 } else {
 2126                     QString style("p {color: %1;}");
 2127                     doc.setDefaultStyleSheet(style.arg(labelsColor.name()));
 2128                     doc.setHtml("<p>" + tickLabelStrings.at(i) + "</p>");
 2129                     QSizeF size = doc.size();
 2130                     int height = size.height();
 2131                     if (labelsBackgroundType != Axis::LabelsBackgroundType::Transparent) {
 2132                         int width = size.width();
 2133                         painter->fillRect(0, -height, width, height, labelsBackgroundColor);
 2134                     }
 2135                     painter->translate(0, -height);
 2136                     doc.drawContents(painter);
 2137                 }
 2138                 painter->restore();
 2139                 painter->translate(-tickLabelPoints.at(i));
 2140             }
 2141         } else { // datetime
 2142             for (int i = 0; i < tickLabelPoints.size(); i++) {
 2143                 painter->translate(tickLabelPoints.at(i));
 2144                 painter->save();
 2145                 painter->rotate(-labelsRotationAngle);
 2146                 if (labelsBackgroundType != Axis::LabelsBackgroundType::Transparent) {
 2147                     const QRect& rect = fm.boundingRect(tickLabelStrings.at(i));
 2148                     painter->fillRect(rect, labelsBackgroundColor);
 2149                 }
 2150                 painter->drawText(QPoint(0, 0), tickLabelStrings.at(i));
 2151                 painter->restore();
 2152                 painter->translate(-tickLabelPoints.at(i));
 2153             }
 2154         }
 2155     }
 2156 
 2157     if (m_hovered && !isSelected() && !m_printing) {
 2158         painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine));
 2159         painter->drawPath(axisShape);
 2160     }
 2161 
 2162     if (isSelected() && !m_printing) {
 2163         painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine));
 2164         painter->drawPath(axisShape);
 2165     }
 2166 }
 2167 
 2168 void AxisPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) {
 2169     q->createContextMenu()->exec(event->screenPos());
 2170 }
 2171 
 2172 void AxisPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) {
 2173     if (!isSelected()) {
 2174         m_hovered = true;
 2175         emit q->hovered();
 2176         update(axisShape.boundingRect());
 2177     }
 2178 }
 2179 
 2180 void AxisPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) {
 2181     if (m_hovered) {
 2182         m_hovered = false;
 2183         emit q->unhovered();
 2184         update(axisShape.boundingRect());
 2185     }
 2186 }
 2187 
 2188 void AxisPrivate::mousePressEvent(QGraphicsSceneMouseEvent* event) {
 2189     auto* plot = static_cast<CartesianPlot*>(q->parentAspect());
 2190     if (!plot->isLocked()) {
 2191         m_panningStarted = true;
 2192         m_panningStart = event->pos();
 2193     } else
 2194         QGraphicsItem::mousePressEvent(event);
 2195 }
 2196 
 2197 void AxisPrivate::mouseMoveEvent(QGraphicsSceneMouseEvent* event) {
 2198     if (m_panningStarted) {
 2199         if (orientation == WorksheetElement::Orientation::Horizontal) {
 2200             setCursor(Qt::SizeHorCursor);
 2201             const int deltaXScene = (m_panningStart.x() - event->pos().x());
 2202             if (abs(deltaXScene) < 5)
 2203                 return;
 2204 
 2205             auto* plot = static_cast<CartesianPlot*>(q->parentAspect());
 2206             if (deltaXScene > 0)
 2207                 plot->shiftRightX();
 2208             else
 2209                 plot->shiftLeftX();
 2210         } else {
 2211             setCursor(Qt::SizeVerCursor);
 2212             const int deltaYScene = (m_panningStart.y() - event->pos().y());
 2213             if (abs(deltaYScene) < 5)
 2214                 return;
 2215 
 2216             auto* plot = static_cast<CartesianPlot*>(q->parentAspect());
 2217             if (deltaYScene > 0)
 2218                 plot->shiftUpY();
 2219             else
 2220                 plot->shiftDownY();
 2221         }
 2222 
 2223         m_panningStart = event->pos();
 2224     }
 2225 }
 2226 
 2227 
 2228 void AxisPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) {
 2229     setCursor(Qt::ArrowCursor);
 2230     m_panningStarted = false;
 2231     QGraphicsItem::mouseReleaseEvent(event);
 2232 }
 2233 
 2234 void AxisPrivate::setPrinting(bool on) {
 2235     m_printing = on;
 2236 }
 2237 
 2238 bool AxisPrivate::isHovered() const {
 2239     return m_hovered;
 2240 }
 2241 
 2242 //##############################################################################
 2243 //##################  Serialization/Deserialization  ###########################
 2244 //##############################################################################
 2245 //! Save as XML
 2246 void Axis::save(QXmlStreamWriter* writer) const {
 2247     Q_D(const Axis);
 2248 
 2249     writer->writeStartElement("axis");
 2250     writeBasicAttributes(writer);
 2251     writeCommentElement(writer);
 2252 
 2253     //general
 2254     writer->writeStartElement( "general" );
 2255     writer->writeAttribute( "autoScale", QString::number(d->autoScale) );
 2256     writer->writeAttribute( "orientation", QString::number(static_cast<int>(d->orientation)) );
 2257     writer->writeAttribute( "position", QString::number(static_cast<int>(d->position)) );
 2258     writer->writeAttribute( "scale", QString::number(static_cast<int>(d->scale)) );
 2259     writer->writeAttribute( "offset", QString::number(d->offset) );
 2260     writer->writeAttribute( "start", QString::number(d->start) );
 2261     writer->writeAttribute( "end", QString::number(d->end) );
 2262     writer->writeAttribute( "scalingFactor", QString::number(d->scalingFactor) );
 2263     writer->writeAttribute( "zeroOffset", QString::number(d->zeroOffset) );
 2264     writer->writeAttribute( "titleOffsetX", QString::number(d->titleOffsetX) );
 2265     writer->writeAttribute( "titleOffsetY", QString::number(d->titleOffsetY) );
 2266     writer->writeAttribute( "visible", QString::number(d->isVisible()) );
 2267     writer->writeEndElement();
 2268 
 2269     //label
 2270     d->title->save(writer);
 2271 
 2272     //line
 2273     writer->writeStartElement( "line" );
 2274     WRITE_QPEN(d->linePen);
 2275     writer->writeAttribute( "opacity", QString::number(d->lineOpacity) );
 2276     writer->writeAttribute( "arrowType", QString::number(static_cast<int>(d->arrowType)) );
 2277     writer->writeAttribute( "arrowPosition", QString::number(static_cast<int>(d->arrowPosition)) );
 2278     writer->writeAttribute( "arrowSize", QString::number(d->arrowSize) );
 2279     writer->writeEndElement();
 2280 
 2281     //major ticks
 2282     writer->writeStartElement( "majorTicks" );
 2283     writer->writeAttribute( "direction", QString::number(d->majorTicksDirection) );
 2284     writer->writeAttribute( "type", QString::number(static_cast<int>(d->majorTicksType)) );
 2285     writer->writeAttribute( "number", QString::number(d->majorTicksNumber) );
 2286     writer->writeAttribute( "increment", QString::number(d->majorTicksSpacing) );
 2287     WRITE_COLUMN(d->majorTicksColumn, majorTicksColumn);
 2288     writer->writeAttribute( "length", QString::number(d->majorTicksLength) );
 2289     WRITE_QPEN(d->majorTicksPen);
 2290     writer->writeAttribute( "opacity", QString::number(d->majorTicksOpacity) );
 2291     writer->writeEndElement();
 2292 
 2293     //minor ticks
 2294     writer->writeStartElement( "minorTicks" );
 2295     writer->writeAttribute( "direction", QString::number(d->minorTicksDirection) );
 2296     writer->writeAttribute( "type", QString::number(static_cast<int>(d->minorTicksType)) );
 2297     writer->writeAttribute( "number", QString::number(d->minorTicksNumber) );
 2298     writer->writeAttribute( "increment", QString::number(d->minorTicksIncrement) );
 2299     WRITE_COLUMN(d->minorTicksColumn, minorTicksColumn);
 2300     writer->writeAttribute( "length", QString::number(d->minorTicksLength) );
 2301     WRITE_QPEN(d->minorTicksPen);
 2302     writer->writeAttribute( "opacity", QString::number(d->minorTicksOpacity) );
 2303     writer->writeEndElement();
 2304 
 2305     //extra ticks
 2306 
 2307     //labels
 2308     writer->writeStartElement( "labels" );
 2309     writer->writeAttribute( "position", QString::number(static_cast<int>(d->labelsPosition)) );
 2310     writer->writeAttribute( "offset", QString::number(d->labelsOffset) );
 2311     writer->writeAttribute( "rotation", QString::number(d->labelsRotationAngle) );
 2312     writer->writeAttribute( "format", QString::number(static_cast<int>(d->labelsFormat)) );
 2313     writer->writeAttribute( "precision", QString::number(d->labelsPrecision) );
 2314     writer->writeAttribute( "autoPrecision", QString::number(d->labelsAutoPrecision) );
 2315     writer->writeAttribute( "dateTimeFormat", d->labelsDateTimeFormat );
 2316     WRITE_QCOLOR(d->labelsColor);
 2317     WRITE_QFONT(d->labelsFont);
 2318     writer->writeAttribute( "prefix", d->labelsPrefix );
 2319     writer->writeAttribute( "suffix", d->labelsSuffix );
 2320     writer->writeAttribute( "opacity", QString::number(d->labelsOpacity) );
 2321     writer->writeAttribute( "backgroundType", QString::number(static_cast<int>(d->labelsBackgroundType)) );
 2322     writer->writeAttribute( "backgroundColor_r", QString::number(d->labelsBackgroundColor.red()) );
 2323     writer->writeAttribute( "backgroundColor_g", QString::number(d->labelsBackgroundColor.green()) );
 2324     writer->writeAttribute( "backgroundColor_b", QString::number(d->labelsBackgroundColor.blue()) );
 2325     writer->writeEndElement();
 2326 
 2327     //grid
 2328     writer->writeStartElement( "majorGrid" );
 2329     WRITE_QPEN(d->majorGridPen);
 2330     writer->writeAttribute( "opacity", QString::number(d->majorGridOpacity) );
 2331     writer->writeEndElement();
 2332 
 2333     writer->writeStartElement( "minorGrid" );
 2334     WRITE_QPEN(d->minorGridPen);
 2335     writer->writeAttribute( "opacity", QString::number(d->minorGridOpacity) );
 2336     writer->writeEndElement();
 2337 
 2338     writer->writeEndElement(); // close "axis" section
 2339 }
 2340 
 2341 //! Load from XML
 2342 bool Axis::load(XmlStreamReader* reader, bool preview) {
 2343     Q_D(Axis);
 2344 
 2345     if (!readBasicAttributes(reader))
 2346         return false;
 2347 
 2348     KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used");
 2349     QXmlStreamAttributes attribs;
 2350     QString str;
 2351 
 2352     while (!reader->atEnd()) {
 2353         reader->readNext();
 2354         if (reader->isEndElement() && reader->name() == "axis")
 2355             break;
 2356 
 2357         if (!reader->isStartElement())
 2358             continue;
 2359 
 2360         if (!preview && reader->name() == "comment") {
 2361             if (!readCommentElement(reader)) return false;
 2362         } else if (!preview && reader->name() == "general") {
 2363             attribs = reader->attributes();
 2364 
 2365             READ_INT_VALUE("autoScale", autoScale, bool);
 2366             READ_INT_VALUE("orientation", orientation, Orientation);
 2367             READ_INT_VALUE("position", position, Axis::Position);
 2368             READ_INT_VALUE("scale", scale, Axis::Scale);
 2369             READ_DOUBLE_VALUE("offset", offset);
 2370             READ_DOUBLE_VALUE("start", start);
 2371             READ_DOUBLE_VALUE("end", end);
 2372             READ_DOUBLE_VALUE("scalingFactor", scalingFactor);
 2373             READ_DOUBLE_VALUE("zeroOffset", zeroOffset);
 2374             READ_DOUBLE_VALUE("titleOffsetX", titleOffsetX);
 2375             READ_DOUBLE_VALUE("titleOffsetY", titleOffsetY);
 2376 
 2377             str = attribs.value("visible").toString();
 2378             if (str.isEmpty())
 2379                 reader->raiseWarning(attributeWarning.subs("visible").toString());
 2380             else
 2381                 d->setVisible(str.toInt());
 2382         } else if (reader->name() == "textLabel") {
 2383             d->title->load(reader, preview);
 2384         } else if (!preview && reader->name() == "line") {
 2385             attribs = reader->attributes();
 2386 
 2387             READ_QPEN(d->linePen);
 2388             READ_DOUBLE_VALUE("opacity", lineOpacity);
 2389             READ_INT_VALUE("arrowType", arrowType, Axis::ArrowType);
 2390             READ_INT_VALUE("arrowPosition", arrowPosition, Axis::ArrowPosition);
 2391             READ_DOUBLE_VALUE("arrowSize", arrowSize);
 2392         } else if (!preview && reader->name() == "majorTicks") {
 2393             attribs = reader->attributes();
 2394 
 2395             READ_INT_VALUE("direction", majorTicksDirection, Axis::TicksDirection);
 2396             READ_INT_VALUE("type", majorTicksType, Axis::TicksType);
 2397             READ_INT_VALUE("number", majorTicksNumber, int);
 2398             READ_DOUBLE_VALUE("increment", majorTicksSpacing);
 2399             READ_COLUMN(majorTicksColumn);
 2400             READ_DOUBLE_VALUE("length", majorTicksLength);
 2401             READ_QPEN(d->majorTicksPen);
 2402             READ_DOUBLE_VALUE("opacity", majorTicksOpacity);
 2403         } else if (!preview && reader->name() == "minorTicks") {
 2404             attribs = reader->attributes();
 2405 
 2406             READ_INT_VALUE("direction", minorTicksDirection, Axis::TicksDirection);
 2407             READ_INT_VALUE("type", minorTicksType, Axis::TicksType);
 2408             READ_INT_VALUE("number", minorTicksNumber, int);
 2409             READ_DOUBLE_VALUE("increment", minorTicksIncrement);
 2410             READ_COLUMN(minorTicksColumn);
 2411             READ_DOUBLE_VALUE("length", minorTicksLength);
 2412             READ_QPEN(d->minorTicksPen);
 2413             READ_DOUBLE_VALUE("opacity", minorTicksOpacity);
 2414         } else if (!preview && reader->name() == "labels") {
 2415             attribs = reader->attributes();
 2416 
 2417             READ_INT_VALUE("position", labelsPosition, Axis::LabelsPosition);
 2418             READ_DOUBLE_VALUE("offset", labelsOffset);
 2419             READ_DOUBLE_VALUE("rotation", labelsRotationAngle);
 2420             READ_INT_VALUE("format", labelsFormat, Axis::LabelsFormat);
 2421             READ_INT_VALUE("precision", labelsPrecision, int);
 2422             READ_INT_VALUE("autoPrecision", labelsAutoPrecision, bool);
 2423             d->labelsDateTimeFormat = attribs.value("dateTimeFormat").toString();
 2424             READ_QCOLOR(d->labelsColor);
 2425             READ_QFONT(d->labelsFont);
 2426 
 2427             //don't produce any warning if no prefix or suffix is set (empty string is allowed here in xml)
 2428             d->labelsPrefix = attribs.value("prefix").toString();
 2429             d->labelsSuffix = attribs.value("suffix").toString();
 2430 
 2431             READ_DOUBLE_VALUE("opacity", labelsOpacity);
 2432 
 2433             READ_INT_VALUE("backgroundType", labelsBackgroundType, Axis::LabelsBackgroundType);
 2434             str = attribs.value("backgroundColor_r").toString();
 2435             if(!str.isEmpty())
 2436                 d->labelsBackgroundColor.setRed(str.toInt());
 2437 
 2438             str = attribs.value("backgroundColor_g").toString();
 2439             if(!str.isEmpty())
 2440                 d->labelsBackgroundColor.setGreen(str.toInt());
 2441 
 2442             str = attribs.value("backgroundColor_b").toString();
 2443             if(!str.isEmpty())
 2444                 d->labelsBackgroundColor.setBlue(str.toInt());
 2445         } else if (!preview && reader->name() == "majorGrid") {
 2446             attribs = reader->attributes();
 2447 
 2448             READ_QPEN(d->majorGridPen);
 2449             READ_DOUBLE_VALUE("opacity", majorGridOpacity);
 2450         } else if (!preview && reader->name() == "minorGrid") {
 2451             attribs = reader->attributes();
 2452 
 2453             READ_QPEN(d->minorGridPen);
 2454             READ_DOUBLE_VALUE("opacity", minorGridOpacity);
 2455         } else { // unknown element
 2456             reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString()));
 2457             if (!reader->skipToEndElement()) return false;
 2458         }
 2459     }
 2460 
 2461     return true;
 2462 }
 2463 
 2464 //##############################################################################
 2465 //#########################  Theme management ##################################
 2466 //##############################################################################
 2467 void Axis::loadThemeConfig(const KConfig& config) {
 2468     const KConfigGroup& group = config.group("Axis");
 2469 
 2470     //we don't want to show the major and minor grid lines for non-first horizontal/vertical axes
 2471     //determine the index of the axis among other axes having the same orientation
 2472     bool firstAxis = true;
 2473     for (const auto* axis : parentAspect()->children<Axis>()) {
 2474         if (orientation() == axis->orientation()) {
 2475             if (axis == this) {
 2476                 break;
 2477             } else {
 2478                 firstAxis = false;
 2479                 break;
 2480             }
 2481         }
 2482     }
 2483 
 2484     QPen p;
 2485 
 2486     // Tick label
 2487     this->setLabelsColor(group.readEntry("LabelsFontColor", QColor(Qt::black)));
 2488     this->setLabelsOpacity(group.readEntry("LabelsOpacity", 1.0));
 2489 
 2490     //use plot area color for the background color of the labels
 2491     const KConfigGroup& groupPlot = config.group("CartesianPlot");
 2492     this->setLabelsBackgroundColor(groupPlot.readEntry("BackgroundFirstColor", QColor(Qt::white)));
 2493 
 2494     //Line
 2495     this->setLineOpacity(group.readEntry("LineOpacity", 1.0));
 2496     p.setStyle((Qt::PenStyle)group.readEntry("LineStyle", (int)Qt::SolidLine));
 2497     p.setColor(group.readEntry("LineColor", QColor(Qt::black)));
 2498     p.setWidthF(group.readEntry("LineWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point)));
 2499     this->setLinePen(p);
 2500 
 2501     //Major grid
 2502     if (firstAxis) {
 2503         p.setStyle((Qt::PenStyle)group.readEntry("MajorGridStyle", (int)Qt::SolidLine));
 2504         p.setColor(group.readEntry("MajorGridColor", QColor(Qt::gray)));
 2505         p.setWidthF(group.readEntry("MajorGridWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point)));
 2506     } else
 2507         p.setStyle(Qt::NoPen);
 2508     this->setMajorGridPen(p);
 2509     this->setMajorGridOpacity(group.readEntry("MajorGridOpacity", 1.0));
 2510 
 2511     //Major ticks
 2512     p.setStyle((Qt::PenStyle)group.readEntry("MajorTicksLineStyle", (int)Qt::SolidLine));
 2513     p.setColor(group.readEntry("MajorTicksColor", QColor(Qt::black)));
 2514     p.setWidthF(group.readEntry("MajorTicksWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point)));
 2515     this->setMajorTicksPen(p);
 2516     this->setMajorTicksOpacity(group.readEntry("MajorTicksOpacity", 1.0));
 2517 
 2518     //Minor grid
 2519     if (firstAxis) {
 2520         p.setStyle((Qt::PenStyle)group.readEntry("MinorGridStyle", (int)Qt::DotLine));
 2521         p.setColor(group.readEntry("MinorGridColor", QColor(Qt::gray)));
 2522         p.setWidthF(group.readEntry("MinorGridWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point)));
 2523     } else
 2524         p.setStyle(Qt::NoPen);
 2525     this->setMinorGridOpacity(group.readEntry("MinorGridOpacity", 1.0));
 2526     this->setMinorGridPen(p);
 2527 
 2528     //Minor ticks
 2529     p.setStyle((Qt::PenStyle)group.readEntry("MinorTicksLineStyle", (int)Qt::SolidLine));
 2530     p.setColor(group.readEntry("MinorTicksColor", QColor(Qt::black)));
 2531     p.setWidthF(group.readEntry("MinorTicksWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point)));
 2532     this->setMinorTicksPen(p);
 2533     this->setMinorTicksOpacity(group.readEntry("MinorTicksOpacity", 1.0));
 2534 
 2535     //load the theme for the title label
 2536     Q_D(Axis);
 2537     d->title->loadThemeConfig(config);
 2538 }
 2539 
 2540 void Axis::saveThemeConfig(const KConfig& config) {
 2541     KConfigGroup group = config.group("Axis");
 2542 
 2543     // Tick label
 2544     group.writeEntry("LabelsFontColor", this->labelsColor());
 2545     group.writeEntry("LabelsOpacity", this->labelsOpacity());
 2546     group.writeEntry("LabelsBackgroundColor", this->labelsBackgroundColor());
 2547 
 2548     //Line
 2549     group.writeEntry("LineOpacity", this->lineOpacity());
 2550     group.writeEntry("LineColor", this->linePen().color());
 2551     group.writeEntry("LineStyle", (int) this->linePen().style());
 2552     group.writeEntry("LineWidth", this->linePen().widthF());
 2553 
 2554     //Major ticks
 2555     group.writeEntry("MajorGridOpacity", this->majorGridOpacity());
 2556     group.writeEntry("MajorGridColor", this->majorGridPen().color());
 2557     group.writeEntry("MajorGridStyle", (int) this->majorGridPen().style());
 2558     group.writeEntry("MajorGridWidth", this->majorGridPen().widthF());
 2559     group.writeEntry("MajorTicksColor", this->majorTicksPen().color());
 2560     group.writeEntry("MajorTicksLineStyle", (int) this->majorTicksPen().style());
 2561     group.writeEntry("MajorTicksWidth", this->majorTicksPen().widthF());
 2562     group.writeEntry("MajorTicksOpacity", this->majorTicksOpacity());
 2563     group.writeEntry("MajorTicksType", (int)this->majorTicksType());
 2564 
 2565     //Minor ticks
 2566     group.writeEntry("MinorGridOpacity", this->minorGridOpacity());
 2567     group.writeEntry("MinorGridColor", this->minorGridPen().color());
 2568     group.writeEntry("MinorGridStyle", (int) this->minorGridPen().style());
 2569     group.writeEntry("MinorGridWidth", this->minorGridPen().widthF());
 2570     group.writeEntry("MinorTicksColor", this->minorTicksPen().color());
 2571     group.writeEntry("MinorTicksLineStyle", (int) this->minorTicksPen().style());
 2572     group.writeEntry("MinorTicksWidth", this->minorTicksPen().widthF());
 2573     group.writeEntry("MinorTicksOpacity", this->minorTicksOpacity());
 2574     group.writeEntry("MinorTicksType", (int)this->minorTicksType());
 2575 
 2576     //same the theme config for the title label
 2577     Q_D(Axis);
 2578     d->title->saveThemeConfig(config);
 2579 }