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