File indexing completed on 2024-04-28 03:48:09

0001 /*
0002     File                 : XYCurveTest.cpp
0003     Project              : LabPlot
0004     Description          : Tests for XYCurve
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2021 Martin Marmsoler <martin.marmsoler@gmail.com>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "RetransformTest.h"
0012 
0013 #include "backend/core/Project.h"
0014 #include "backend/core/column/Column.h"
0015 #include "backend/datasources/filters/AsciiFilter.h"
0016 #include "backend/spreadsheet/Spreadsheet.h"
0017 #include "backend/worksheet/Worksheet.h"
0018 #include "backend/worksheet/plots/cartesian/AxisPrivate.h"
0019 #include "backend/worksheet/plots/cartesian/BarPlot.h"
0020 #include "backend/worksheet/plots/cartesian/BoxPlot.h"
0021 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
0022 #include "backend/worksheet/plots/cartesian/Histogram.h"
0023 #include "backend/worksheet/plots/cartesian/XYCurve.h"
0024 #include "backend/worksheet/plots/cartesian/XYCurvePrivate.h"
0025 #include "backend/worksheet/plots/cartesian/XYEquationCurve.h"
0026 #include "commonfrontend/worksheet/WorksheetView.h"
0027 #include "kdefrontend/dockwidgets/AxisDock.h"
0028 #include "kdefrontend/dockwidgets/CartesianPlotDock.h"
0029 #include "kdefrontend/dockwidgets/XYCurveDock.h"
0030 
0031 #include <QAction>
0032 
0033 #define COMPARE(actual, expected, message)                                                                                                                     \
0034     do {                                                                                                                                                       \
0035         if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) {                                                                      \
0036             qDebug() << message;                                                                                                                               \
0037             return;                                                                                                                                            \
0038         }                                                                                                                                                      \
0039     } while (false)
0040 
0041 using Dimension = CartesianCoordinateSystem::Dimension;
0042 
0043 void RetransformTest::initTestCase() {
0044     // needed in order to have the signals triggered by SignallingUndoCommand, see LabPlot.cpp
0045     // TODO: redesign/remove this
0046     qRegisterMetaType<const AbstractAspect*>("const AbstractAspect*");
0047     qRegisterMetaType<const AbstractColumn*>("const AbstractColumn*");
0048 }
0049 
0050 void RetransformTest::TestLoadProject() {
0051     RetransformCallCounter c;
0052     Project project;
0053 
0054     // Does not work during load.
0055     // connect(&project, &Project::aspectAdded, &c, &RetransformCallCounter::aspectAdded);
0056 
0057     project.load(QFINDTESTDATA(QLatin1String("data/p1.lml")));
0058 
0059     QHash<QString, int> h = {{QStringLiteral("Project/Worksheet/xy-plot"), 1},
0060                              {QStringLiteral("Project/Worksheet/xy-plot/x"), 1},
0061                              {QStringLiteral("Project/Worksheet/xy-plot/y"), 1},
0062                              {QStringLiteral("Project/Worksheet/xy-plot/sin"), 1},
0063                              {QStringLiteral("Project/Worksheet/xy-plot/cos"), 1},
0064                              {QStringLiteral("Project/Worksheet/xy-plot/tan"), 1},
0065                              {QStringLiteral("Project/Worksheet/xy-plot/y-axis"), 1},
0066                              {QStringLiteral("Project/Worksheet/xy-plot/legend"), 1},
0067                              {QStringLiteral("Project/Worksheet/xy-plot/plotImage"), 1},
0068                              {QStringLiteral("Project/Worksheet/xy-plot/plotText"), 1},
0069                              {QStringLiteral("Project/Worksheet/Text Label"), 1},
0070                              {QStringLiteral("Project/Worksheet/Image"), 1},
0071                              {QStringLiteral("Project/Worksheet/plot2"), 1},
0072                              {QStringLiteral("Project/Worksheet/plot2/x"), 1},
0073                              {QStringLiteral("Project/Worksheet/plot2/y"), 1},
0074                              {QStringLiteral("Project/Worksheet/plot2/xy-curve"), 1}};
0075 
0076     auto children = project.children(AspectType::AbstractAspect, AbstractAspect::ChildIndexFlag::Recursive);
0077     for (auto& child : children) {
0078         int expectedCallCount = 0;
0079         const QString& path = child->path();
0080         if (h.contains(path))
0081             expectedCallCount = h.value(path);
0082         COMPARE(c.callCount(child), expectedCallCount, path);
0083     }
0084 }
0085 
0086 // Problem in this project was that the second axis labels are not loaded. Zooming in/out once shows the correct range
0087 void RetransformTest::TestLoadProject2() {
0088     QLocale::setDefault(QLocale::C); // . as decimal separator
0089     RetransformCallCounter c;
0090     Project project;
0091 
0092     project.load(QFINDTESTDATA(QLatin1String("data/bars_dis_004.lml")));
0093 
0094     QHash<QString, int> h = {{QStringLiteral("Project/Worksheet - Spreadsheet/Plot - Spreadsheet"), 1},
0095                              {QStringLiteral("Project/Worksheet - Spreadsheet/Plot - Spreadsheet/x"), 1},
0096                              {QStringLiteral("Project/Worksheet - Spreadsheet/Plot - Spreadsheet/y"), 1},
0097                              {QStringLiteral("Project/Worksheet - Spreadsheet/Plot - Spreadsheet/x2"), 1},
0098                              {QStringLiteral("Project/Worksheet - Spreadsheet/Plot - Spreadsheet/y2"), 1},
0099                              {QStringLiteral("Project/Worksheet - Spreadsheet/Plot - Spreadsheet/Frequency"), 1},
0100                              {QStringLiteral("Project/Worksheet - Spreadsheet/Plot - Spreadsheet/Cumulative"), 1},
0101                              {QStringLiteral("Project/Worksheet - Spreadsheet/Plot - Spreadsheet/legend"), 1}};
0102 
0103     auto children = project.children(AspectType::AbstractAspect, AbstractAspect::ChildIndexFlag::Recursive);
0104     for (auto& child : children) {
0105         int expectedCallCount = 0;
0106         const QString& path = child->path();
0107         if (h.contains(path))
0108             expectedCallCount = h.value(path);
0109         COMPARE(c.callCount(child), expectedCallCount, path);
0110     }
0111 
0112     // check axis ranges
0113     auto axes = project.children(AspectType::Axis, AbstractAspect::ChildIndexFlag::Recursive);
0114     QCOMPARE(axes.length(), 4);
0115     auto* xAxis = axes.at(0);
0116     auto* xAxis2 = axes.at(1);
0117     auto* yAxis1 = axes.at(2);
0118     auto* yAxis2 = axes.at(3);
0119 
0120     QCOMPARE(xAxis->name(), QStringLiteral("x"));
0121     QCOMPARE(xAxis2->name(), QStringLiteral("x2"));
0122     QCOMPARE(yAxis1->name(), QStringLiteral("y"));
0123     QCOMPARE(yAxis2->name(), QStringLiteral("y2"));
0124 
0125     // xAxis2 does not have any labels
0126     QVector<QString> refString = {QStringLiteral("161.2"),
0127                                   QStringLiteral("166.7"),
0128                                   QStringLiteral("172.2"),
0129                                   QStringLiteral("177.8"),
0130                                   QStringLiteral("183.3"),
0131                                   QStringLiteral("188.8"),
0132                                   QStringLiteral("194.4")};
0133     COMPARE_STRING_VECTORS(static_cast<Axis*>(xAxis)->tickLabelStrings(), refString);
0134     QVector<double> ref = {0, 20, 40, 60, 80, 100};
0135     COMPARE_DOUBLE_VECTORS(static_cast<Axis*>(yAxis1)->tickLabelValues(), ref);
0136     ref = {0, 0.2, 0.4, 0.6, 0.8, 1.0};
0137     COMPARE_DOUBLE_VECTORS(static_cast<Axis*>(yAxis2)->tickLabelValues(), ref);
0138 }
0139 
0140 void RetransformTest::TestResizeWindows() {
0141     RetransformCallCounter c;
0142     Project project;
0143     project.load(QFINDTESTDATA(QLatin1String("data/p1.lml")));
0144 
0145     const auto& worksheets = project.children(AspectType::Worksheet);
0146     QCOMPARE(worksheets.count(), 1);
0147     auto worksheet = static_cast<Worksheet*>(worksheets.at(0));
0148     auto* view = static_cast<WorksheetView*>(worksheet->view());
0149 
0150     view->resize(100, 100);
0151     view->processResize();
0152 
0153     for (const auto& child : project.children(AspectType::AbstractAspect, AbstractAspect::ChildIndexFlag::Recursive))
0154         connect(child, &AbstractAspect::retransformCalledSignal, &c, &RetransformCallCounter::aspectRetransformed);
0155 
0156     view->resize(1000, 1000);
0157     view->processResize();
0158 
0159     // Check that every element is exactly called once
0160     QHash<QString, int> h = {{QStringLiteral("Project/Worksheet/xy-plot"), 1},
0161                              {QStringLiteral("Project/Worksheet/xy-plot/x"), 1},
0162                              {QStringLiteral("Project/Worksheet/xy-plot/y"), 1},
0163                              {QStringLiteral("Project/Worksheet/xy-plot/sin"), 1},
0164                              {QStringLiteral("Project/Worksheet/xy-plot/cos"), 1},
0165                              {QStringLiteral("Project/Worksheet/xy-plot/tan"), 1},
0166                              {QStringLiteral("Project/Worksheet/xy-plot/y-axis"), 1},
0167                              {QStringLiteral("Project/Worksheet/xy-plot/legend"), 1},
0168                              {QStringLiteral("Project/Worksheet/xy-plot/plotImage"), 1},
0169                              {QStringLiteral("Project/Worksheet/xy-plot/plotText"), 1},
0170                              //{QStringLiteral("Project/Worksheet/Text Label"), 1}, // TODO: turn on when fixed
0171                              //{QStringLiteral("Project/Worksheet/Image"), 1},  // TODO: turn on when fixed
0172                              {QStringLiteral("Project/Worksheet/plot2"), 1},
0173                              {QStringLiteral("Project/Worksheet/plot2/x"), 1},
0174                              {QStringLiteral("Project/Worksheet/plot2/y"), 1},
0175                              {QStringLiteral("Project/Worksheet/plot2/xy-curve"), 1}};
0176 
0177     QCOMPARE(c.elementLogCount(false), h.count());
0178     QHash<QString, int>::const_iterator i;
0179     for (i = h.constBegin(); i != h.constEnd(); ++i)
0180         QCOMPARE(c.callCount(i.key()), 1);
0181 }
0182 
0183 /*!
0184  * \brief RetransformTest::TestZoomSelectionAutoscale
0185  * Check that retransform and retransform scale is called correctly during zoom and autoscale. Check
0186  * also the number of calls of the retransforms
0187  */
0188 void RetransformTest::TestZoomSelectionAutoscale() {
0189     RetransformCallCounter c;
0190     Project project;
0191 
0192     project.load(QFINDTESTDATA(QLatin1String("data/p1.lml")));
0193     auto children = project.children(AspectType::AbstractAspect, AbstractAspect::ChildIndexFlag::Recursive);
0194 
0195     // Spreadsheet "Spreadsheet"
0196     // Column "x"
0197     // Column "sin"
0198     // Column "cos"
0199     // Column "tan"
0200     // Worksheet "Worksheet"
0201     // CartesianPlot "xy-plot"
0202     // Axis "x"
0203     // Axis "y"
0204     // XYCurve "sin"
0205     // XYCurve "cos"
0206     // XYCurve "tan"
0207     // Axis "y-axis"
0208     // Legend "legend"
0209     // TextLabel "plotText"
0210     // Image "plotImage"
0211     // TextLabel "Text Label"
0212     // Image "Image"
0213     // --- Second plot
0214     // CartesianPlot "plot2"
0215     // Axis "x"
0216     // Axis "y"
0217     // XYCurve "xy-curve"
0218     QCOMPARE(children.length(), 22);
0219     for (const auto& child : children)
0220         connect(child, &AbstractAspect::retransformCalledSignal, &c, &RetransformCallCounter::aspectRetransformed);
0221 
0222     for (const auto& plot : project.children(AspectType::CartesianPlot, AbstractAspect::ChildIndexFlag::Recursive)) {
0223         connect(static_cast<CartesianPlot*>(plot), &CartesianPlot::scaleRetransformed, &c, &RetransformCallCounter::retransformScaleCalled);
0224     }
0225 
0226     auto* worksheet = project.child<Worksheet>(0);
0227     QVERIFY(worksheet);
0228 
0229     auto* view = static_cast<WorksheetView*>(worksheet->view());
0230     QVERIFY(view);
0231 
0232     auto* plot = worksheet->child<CartesianPlot>(0);
0233     QVERIFY(plot);
0234     QCOMPARE(plot->name(), QLatin1String("xy-plot"));
0235 
0236     auto* plot2 = worksheet->child<CartesianPlot>(1);
0237     QVERIFY(plot2);
0238     QCOMPARE(plot2->name(), QLatin1String("plot2"));
0239 
0240     QCOMPARE(plot->childCount<XYCurve>(), 3);
0241     QCOMPARE(plot->child<XYCurve>(0)->name(), QStringLiteral("sin"));
0242     QCOMPARE(plot->child<XYCurve>(0)->coordinateSystemIndex(), 0);
0243     QCOMPARE(plot->child<XYCurve>(1)->name(), QStringLiteral("cos"));
0244     QCOMPARE(plot->child<XYCurve>(1)->coordinateSystemIndex(), 0);
0245     QCOMPARE(plot->child<XYCurve>(2)->name(), QStringLiteral("tan"));
0246     QCOMPARE(plot->child<XYCurve>(2)->coordinateSystemIndex(), 1);
0247 
0248     QAction a(nullptr);
0249     a.setData(static_cast<int>(CartesianPlot::MouseMode::ZoomXSelection));
0250     view->cartesianPlotMouseModeChanged(&a);
0251 
0252     QCOMPARE(c.elementLogCount(false), 0);
0253     QVERIFY(c.calledExact(0, false));
0254 
0255     Q_EMIT plot->mousePressZoomSelectionModeSignal(QPointF(0.2, -150));
0256     Q_EMIT plot->mouseMoveZoomSelectionModeSignal(QPointF(0.6, 100));
0257     Q_EMIT plot->mouseReleaseZoomSelectionModeSignal();
0258 
0259     // x and y are called only once
0260     QCOMPARE(c.logsXScaleRetransformed.count(), 2); // 2 plots with each one x axis
0261     QCOMPARE(c.logsXScaleRetransformed.at(0).plot, plot);
0262     QCOMPARE(c.logsXScaleRetransformed.at(0).index, 0);
0263     QCOMPARE(c.logsXScaleRetransformed.at(1).plot, plot2);
0264     QCOMPARE(c.logsXScaleRetransformed.at(1).index, 0);
0265     QCOMPARE(c.logsYScaleRetransformed.count(),
0266              3); // there are two vertical ranges (sin, cos and tan range) for the first plot and one y axis for the second plot
0267     QCOMPARE(c.logsYScaleRetransformed.at(0).plot, plot);
0268     QCOMPARE(c.logsYScaleRetransformed.at(0).index, 0);
0269     QCOMPARE(c.logsYScaleRetransformed.at(1).plot, plot);
0270     QCOMPARE(c.logsYScaleRetransformed.at(1).index, 1);
0271     QCOMPARE(c.logsYScaleRetransformed.at(2).plot, plot2);
0272     QCOMPARE(c.logsYScaleRetransformed.at(2).index, 0);
0273 
0274     // Check that every element is exactly called once
0275     // plot it self does not change so retransform is not called on cartesianplotPrivate
0276     QStringList list = {
0277         QStringLiteral("Project/Worksheet/xy-plot/x"),
0278         QStringLiteral("Project/Worksheet/xy-plot/y"),
0279         QStringLiteral("Project/Worksheet/xy-plot/sin"),
0280         QStringLiteral("Project/Worksheet/xy-plot/cos"),
0281         QStringLiteral("Project/Worksheet/xy-plot/tan"),
0282         QStringLiteral("Project/Worksheet/xy-plot/y-axis"),
0283         // not neccesary to retransform legend, but is difficult to
0284         // distinguish so let it in, because it does not cost that much performance
0285         QStringLiteral("Project/Worksheet/xy-plot/legend"),
0286         QStringLiteral("Project/Worksheet/xy-plot/plotText"),
0287         QStringLiteral("Project/Worksheet/xy-plot/plotImage"),
0288         // second plot starting
0289         QStringLiteral("Project/Worksheet/plot2/x"),
0290         QStringLiteral("Project/Worksheet/plot2/y"),
0291         QStringLiteral("Project/Worksheet/plot2/xy-curve"),
0292     };
0293     QCOMPARE(c.elementLogCount(false), list.count());
0294     for (auto& s : list)
0295         QCOMPARE(c.callCount(s), 1);
0296 
0297     c.resetRetransformCount();
0298     view->selectItem(plot->graphicsItem());
0299     a.setData(static_cast<int>(CartesianPlot::NavigationOperation::ScaleAutoX));
0300     view->cartesianPlotNavigationChanged(&a);
0301 
0302     QCOMPARE(c.elementLogCount(false), list.count());
0303     for (auto& s : list)
0304         QCOMPARE(c.callCount(s), 1);
0305 
0306     // x and y are called only once
0307     QCOMPARE(c.logsXScaleRetransformed.count(), 2); // 2 plots with each one x axis
0308     QCOMPARE(c.logsXScaleRetransformed.at(0).plot, plot);
0309     QCOMPARE(c.logsXScaleRetransformed.at(0).index, 0); // first x axis of first plot
0310     QCOMPARE(c.logsXScaleRetransformed.at(1).plot, plot2);
0311     QCOMPARE(c.logsXScaleRetransformed.at(1).index, 0); // first x axis of second plot
0312     QCOMPARE(c.logsYScaleRetransformed.count(),
0313              3); // there are two vertical ranges (sin, cos and tan range) for the first plot and one y axis for the second plot
0314     QCOMPARE(c.logsYScaleRetransformed.at(0).plot, plot);
0315     QCOMPARE(c.logsYScaleRetransformed.at(0).index, 0); // first y axis of first plot
0316     QCOMPARE(c.logsYScaleRetransformed.at(1).plot, plot);
0317     QCOMPARE(c.logsYScaleRetransformed.at(1).index, 1); // second y axis of first plot
0318     QCOMPARE(c.logsYScaleRetransformed.at(2).plot, plot2);
0319     QCOMPARE(c.logsYScaleRetransformed.at(2).index, 0); // first y axis of second plot
0320 }
0321 
0322 /*!
0323  * \brief RetransformTest::TestZoomAutoscaleSingleYRange
0324  * Having two coordinatesystems cSystem1 and cSystem2 with a common x Range
0325  * cSystem1 has automatic scaling of y Range turned on, cSystem2 not
0326  * When zoom x Selection is done, the y Range of cSystem1 shall be autoscaled,
0327  * but not the y Range of cSystem2
0328  * Nice extends should not apply!
0329  */
0330 void RetransformTest::TestZoomAutoscaleSingleYRange() {
0331     Project project;
0332 
0333     auto* worksheet = new Worksheet(QStringLiteral("Worksheet"));
0334     project.addChild(worksheet);
0335 
0336     auto* plot = new CartesianPlot(QStringLiteral("plot"));
0337     worksheet->addChild(plot);
0338     plot->setType(CartesianPlot::Type::TwoAxes); // Otherwise no axis are created
0339     plot->setNiceExtend(true); // Important must be on!
0340 
0341     // Create new cSystem2
0342     Range<double> yRange;
0343     yRange.setFormat(RangeT::Format::Numeric);
0344     plot->addYRange(yRange);
0345     CartesianCoordinateSystem* cSystem2 = new CartesianCoordinateSystem(plot);
0346     cSystem2->setIndex(Dimension::X, 0);
0347     cSystem2->setIndex(Dimension::Y, 1);
0348     plot->addCoordinateSystem(cSystem2);
0349 
0350     // Generate data and
0351     Spreadsheet* sheet = new Spreadsheet(QStringLiteral("Spreadsheet"), false);
0352     project.addChild(sheet);
0353     sheet->setColumnCount(3);
0354     sheet->setRowCount(11);
0355     sheet->column(0)->setColumnMode(AbstractColumn::ColumnMode::Double);
0356     sheet->column(1)->setColumnMode(AbstractColumn::ColumnMode::Double);
0357     sheet->column(2)->setColumnMode(AbstractColumn::ColumnMode::Double);
0358 
0359     for (int i = 0; i < sheet->rowCount(); i++) {
0360         sheet->column(0)->setValueAt(i, i);
0361         sheet->column(1)->setValueAt(i, i + 1000 + 0.3);
0362         sheet->column(2)->setValueAt(i, -i + 0.1); // This 0.1 is important!
0363     }
0364 
0365     auto* curve1 = new XYCurve(QStringLiteral("curve1"));
0366     plot->addChild(curve1);
0367     curve1->setCoordinateSystemIndex(0);
0368     curve1->setXColumn(sheet->column(0));
0369     curve1->setYColumn(sheet->column(1));
0370     QCOMPARE(plot->rangeFormat(Dimension::X, 0), RangeT::Format::Numeric);
0371     QCOMPARE(plot->rangeFormat(Dimension::Y, 0), RangeT::Format::Numeric);
0372     QCOMPARE(plot->rangeFormat(Dimension::Y, 1), RangeT::Format::Numeric);
0373 
0374     auto* curve2 = new XYCurve(QStringLiteral("curve2"));
0375     plot->addChild(curve2);
0376     curve2->setCoordinateSystemIndex(1);
0377     curve2->setXColumn(sheet->column(0));
0378     curve2->setYColumn(sheet->column(2));
0379     QCOMPARE(plot->rangeFormat(Dimension::X, 0), RangeT::Format::Numeric);
0380     QCOMPARE(plot->rangeFormat(Dimension::Y, 0), RangeT::Format::Numeric);
0381     QCOMPARE(plot->rangeFormat(Dimension::Y, 1), RangeT::Format::Numeric);
0382 
0383     CHECK_RANGE(plot, curve1, Dimension::X, 0., 10.);
0384     CHECK_RANGE(plot, curve1, Dimension::Y, 1000., 1011.); // Nice extend applied
0385     CHECK_RANGE(plot, curve2, Dimension::X, 0., 10.);
0386     CHECK_RANGE(plot, curve2, Dimension::Y, -10., 1.);
0387 
0388     plot->enableAutoScale(Dimension::Y, 1, false); // disable autoscale for second y range
0389 
0390     auto r = plot->range(Dimension::Y, 1);
0391     r.setStart(-9.9);
0392     r.setEnd(0.1);
0393     plot->setRange(Dimension::Y, 1, r);
0394 
0395     CHECK_RANGE(plot, curve1, Dimension::X, 0., 10.);
0396     CHECK_RANGE(plot, curve1, Dimension::Y, 1000., 1011.);
0397     CHECK_RANGE(plot, curve2, Dimension::X, 0., 10.);
0398     CHECK_RANGE(plot, curve2, Dimension::Y, -9.9, 0.1);
0399 
0400     QAction a(nullptr);
0401     a.setData(static_cast<int>(CartesianPlot::MouseMode::ZoomXSelection));
0402     auto* view = static_cast<WorksheetView*>(worksheet->view());
0403     QVERIFY(view);
0404     view->initActions();
0405     view->cartesianPlotMouseModeChanged(&a);
0406 
0407     view->setCartesianPlotActionMode(Worksheet::CartesianPlotActionMode::ApplyActionToAllX);
0408 
0409     // Zoom selection
0410     Q_EMIT plot->mousePressZoomSelectionModeSignal(QPointF(2, 0));
0411     Q_EMIT plot->mouseMoveZoomSelectionModeSignal(QPointF(3, 100));
0412     Q_EMIT plot->mouseReleaseZoomSelectionModeSignal();
0413 
0414     CHECK_RANGE(plot, curve1, Dimension::X, 2., 3.);
0415     CHECK_RANGE(plot, curve1, Dimension::Y, 1002.2, 1003.3); // Nice Extend applied
0416     CHECK_RANGE(plot, curve2, Dimension::X, 2., 3.);
0417     CHECK_RANGE(plot, curve2, Dimension::Y, -9.9, 0.1); // Not changed, because autoscale is turned off
0418 }
0419 
0420 /*!
0421  * \brief RetransformTest::TestZoomAutoscaleSingleXRange
0422  * Having two coordinatesystems cSystem1 and cSystem2 with a common y Range
0423  * cSystem1 has automatic scaling of x Range turned on, cSystem2 not
0424  * When zoom y Selection is done, the x Range of cSystem1 shall be autoscaled,
0425  * but not the x Range of cSystem2
0426  * Nice extends should not apply!
0427  */
0428 void RetransformTest::TestZoomAutoscaleSingleXRange() {
0429     Project project;
0430 
0431     auto* worksheet = new Worksheet(QStringLiteral("Worksheet"));
0432     project.addChild(worksheet);
0433 
0434     auto* plot = new CartesianPlot(QStringLiteral("plot"));
0435     worksheet->addChild(plot);
0436     plot->setType(CartesianPlot::Type::TwoAxes); // Otherwise no axis are created
0437     plot->setNiceExtend(true); // Important must be on!
0438 
0439     // Create new cSystem2
0440     Range<double> xRange;
0441     xRange.setFormat(RangeT::Format::Numeric);
0442     plot->addXRange(xRange);
0443     CartesianCoordinateSystem* cSystem2 = new CartesianCoordinateSystem(plot);
0444     cSystem2->setIndex(Dimension::X, 1);
0445     cSystem2->setIndex(Dimension::Y, 0);
0446     plot->addCoordinateSystem(cSystem2);
0447 
0448     // Generate data and
0449     Spreadsheet* sheet = new Spreadsheet(QStringLiteral("Spreadsheet"), false);
0450     project.addChild(sheet);
0451     sheet->setColumnCount(3);
0452     sheet->setRowCount(11);
0453     sheet->column(0)->setColumnMode(AbstractColumn::ColumnMode::Double);
0454     sheet->column(1)->setColumnMode(AbstractColumn::ColumnMode::Double);
0455     sheet->column(2)->setColumnMode(AbstractColumn::ColumnMode::Double);
0456 
0457     for (int i = 0; i < sheet->rowCount(); i++) {
0458         sheet->column(0)->setValueAt(i, i);
0459         sheet->column(1)->setValueAt(i, i + 1000 + 0.3);
0460         sheet->column(2)->setValueAt(i, -i + 0.1); // This 0.1 is important!
0461     }
0462 
0463     auto* curve1 = new XYCurve(QStringLiteral("curve1"));
0464     plot->addChild(curve1);
0465     curve1->setCoordinateSystemIndex(0);
0466     curve1->setXColumn(sheet->column(1));
0467     curve1->setYColumn(sheet->column(0));
0468     QCOMPARE(plot->rangeFormat(Dimension::X, 0), RangeT::Format::Numeric);
0469     QCOMPARE(plot->rangeFormat(Dimension::Y, 0), RangeT::Format::Numeric);
0470     QCOMPARE(plot->rangeFormat(Dimension::X, 1), RangeT::Format::Numeric);
0471 
0472     auto* curve2 = new XYCurve(QStringLiteral("curve2"));
0473     plot->addChild(curve2);
0474     curve2->setCoordinateSystemIndex(1);
0475     curve2->setXColumn(sheet->column(2));
0476     curve2->setYColumn(sheet->column(0));
0477     QCOMPARE(plot->rangeFormat(Dimension::X, 0), RangeT::Format::Numeric);
0478     QCOMPARE(plot->rangeFormat(Dimension::Y, 0), RangeT::Format::Numeric);
0479     QCOMPARE(plot->rangeFormat(Dimension::X, 1), RangeT::Format::Numeric);
0480 
0481     CHECK_RANGE(plot, curve1, Dimension::Y, 0., 10.);
0482     CHECK_RANGE(plot, curve1, Dimension::X, 1000., 1011.); // Nice extend applied
0483     CHECK_RANGE(plot, curve2, Dimension::Y, 0., 10.);
0484     CHECK_RANGE(plot, curve2, Dimension::X, -10., 1.);
0485 
0486     plot->enableAutoScale(Dimension::X, 1, false); // disable autoscale for second y range
0487 
0488     auto r = plot->range(Dimension::X, 1);
0489     r.setStart(-9.9);
0490     r.setEnd(0.1);
0491     plot->setRange(Dimension::X, 1, r);
0492 
0493     CHECK_RANGE(plot, curve1, Dimension::Y, 0., 10.);
0494     CHECK_RANGE(plot, curve1, Dimension::X, 1000., 1011.);
0495     CHECK_RANGE(plot, curve2, Dimension::Y, 0., 10.);
0496     CHECK_RANGE(plot, curve2, Dimension::X, -9.9, 0.1);
0497 
0498     QAction a(nullptr);
0499     a.setData(static_cast<int>(CartesianPlot::MouseMode::ZoomYSelection));
0500     auto* view = static_cast<WorksheetView*>(worksheet->view());
0501     QVERIFY(view);
0502     view->initActions();
0503     view->cartesianPlotMouseModeChanged(&a);
0504 
0505     view->setCartesianPlotActionMode(Worksheet::CartesianPlotActionMode::ApplyActionToAllY);
0506 
0507     // Zoom selection
0508     Q_EMIT plot->mousePressZoomSelectionModeSignal(QPointF(0, 2));
0509     Q_EMIT plot->mouseMoveZoomSelectionModeSignal(QPointF(100, 3));
0510     Q_EMIT plot->mouseReleaseZoomSelectionModeSignal();
0511 
0512     CHECK_RANGE(plot, curve1, Dimension::Y, 2., 3.);
0513     CHECK_RANGE(plot, curve1, Dimension::X, 1002.2, 1003.3); // Nice Extend applied
0514     CHECK_RANGE(plot, curve2, Dimension::Y, 2., 3.);
0515     CHECK_RANGE(plot, curve2, Dimension::X, -9.9, 0.1); // Not changed, because autoscale is turned off
0516 }
0517 
0518 /*!
0519  * \brief RetransformTest::TestPadding
0520  * Check that during a padding change retransform and retransform scale is called
0521  */
0522 void RetransformTest::TestPadding() {
0523     RetransformCallCounter c;
0524     Project project;
0525 
0526     project.load(QFINDTESTDATA(QLatin1String("data/p1.lml")));
0527     auto children = project.children(AspectType::AbstractAspect, AbstractAspect::ChildIndexFlag::Recursive);
0528 
0529     // Spreadsheet "Spreadsheet"
0530     // Column "x"
0531     // Column "sin"
0532     // Column "cos"
0533     // Column "tan"
0534     // Worksheet "Worksheet"
0535     // CartesianPlot "xy-plot"
0536     // Axis "x"
0537     // Axis "y"
0538     // XYCurve "sin"
0539     // XYCurve "cos"
0540     // XYCurve "tan"
0541     // Axis "y-axis"
0542     // Legend "legend"
0543     // TextLabel "plotText"
0544     // Image "plotImage"
0545     // TextLabel "Text Label"
0546     // Image "Image"
0547     // --- Second plot
0548     // CartesianPlot "plot2"
0549     // Axis "x"
0550     // Axis "y"
0551     // XYCurve "xy-curve"
0552     QCOMPARE(children.length(), 22);
0553     for (const auto& child : children)
0554         connect(child, &AbstractAspect::retransformCalledSignal, &c, &RetransformCallCounter::aspectRetransformed);
0555 
0556     for (const auto& plot : project.children(AspectType::CartesianPlot, AbstractAspect::ChildIndexFlag::Recursive))
0557         connect(static_cast<CartesianPlot*>(plot), &CartesianPlot::scaleRetransformed, &c, &RetransformCallCounter::retransformScaleCalled);
0558 
0559     auto* worksheet = project.child<Worksheet>(0);
0560     QVERIFY(worksheet);
0561 
0562     auto* view = static_cast<WorksheetView*>(worksheet->view());
0563     QVERIFY(view);
0564 
0565     auto* plot = worksheet->child<CartesianPlot>(0);
0566     QVERIFY(plot);
0567     QCOMPARE(plot->name(), QLatin1String("xy-plot"));
0568 
0569     // Check that every element is exactly called once
0570     // TODO: set to 6. legend should not retransform
0571     // plot it self does not change so retransform is not called on cartesianplotPrivate
0572     QStringList list = {QStringLiteral("Project/Worksheet/xy-plot"), // datarect changed, so plot must also be retransformed
0573                         QStringLiteral("Project/Worksheet/xy-plot/x"),
0574                         QStringLiteral("Project/Worksheet/xy-plot/y"),
0575                         QStringLiteral("Project/Worksheet/xy-plot/sin"),
0576                         QStringLiteral("Project/Worksheet/xy-plot/cos"),
0577                         QStringLiteral("Project/Worksheet/xy-plot/tan"),
0578                         QStringLiteral("Project/Worksheet/xy-plot/y-axis"),
0579                         QStringLiteral("Project/Worksheet/xy-plot/legend"),
0580                         QStringLiteral("Project/Worksheet/xy-plot/plotText"),
0581                         QStringLiteral("Project/Worksheet/xy-plot/plotImage")};
0582     double hPad = plot->horizontalPadding();
0583     plot->setHorizontalPadding(hPad + 10);
0584 
0585     QCOMPARE(c.elementLogCount(false), list.count());
0586     for (auto& s : list)
0587         QCOMPARE(c.callCount(s), 1);
0588 
0589     // x and y are called only once
0590     QCOMPARE(c.logsXScaleRetransformed.count(), 1);
0591     QCOMPARE(c.logsXScaleRetransformed.at(0).plot, plot);
0592     QCOMPARE(c.logsXScaleRetransformed.at(0).index, 0);
0593     QCOMPARE(c.logsYScaleRetransformed.count(), 2); // there are two vertical ranges (sin,cos and tan range)
0594     QCOMPARE(c.logsYScaleRetransformed.at(0).plot, plot);
0595     QCOMPARE(c.logsYScaleRetransformed.at(0).index, 0);
0596     QCOMPARE(c.logsYScaleRetransformed.at(1).plot, plot);
0597     QCOMPARE(c.logsYScaleRetransformed.at(1).index, 1);
0598 
0599     c.resetRetransformCount();
0600 
0601     list = QStringList({// data rect of the plot does not change, so retransforming the
0602                         // plot is not needed
0603                         QStringLiteral("Project/Worksheet/xy-plot/x"),
0604                         QStringLiteral("Project/Worksheet/xy-plot/y"),
0605                         QStringLiteral("Project/Worksheet/xy-plot/sin"),
0606                         QStringLiteral("Project/Worksheet/xy-plot/cos"),
0607                         QStringLiteral("Project/Worksheet/xy-plot/tan"),
0608                         QStringLiteral("Project/Worksheet/xy-plot/y-axis"),
0609                         QStringLiteral("Project/Worksheet/xy-plot/legend"),
0610                         QStringLiteral("Project/Worksheet/xy-plot/plotText"),
0611                         QStringLiteral("Project/Worksheet/xy-plot/plotImage")});
0612     plot->navigate(-1, CartesianPlot::NavigationOperation::ScaleAuto);
0613 
0614     QCOMPARE(c.elementLogCount(false), list.count());
0615     for (auto& s : list)
0616         QCOMPARE(c.callCount(s), 1);
0617 
0618     // x and y are already scaled due to the change of padding
0619     QCOMPARE(c.logsXScaleRetransformed.count(), 0);
0620     QCOMPARE(c.logsYScaleRetransformed.count(), 0);
0621 }
0622 
0623 void RetransformTest::TestCopyPastePlot() {
0624     Project project;
0625     auto* ws = new Worksheet(QStringLiteral("Worksheet"));
0626     QVERIFY(ws != nullptr);
0627     project.addChild(ws);
0628 
0629     auto* p = new CartesianPlot(QStringLiteral("plot"));
0630     p->setType(CartesianPlot::Type::TwoAxes); // Otherwise no axis are created
0631     QVERIFY(p != nullptr);
0632     ws->addChild(p);
0633 
0634     auto* ws2 = new Worksheet(QStringLiteral("Worksheet2"));
0635     QVERIFY(ws2 != nullptr);
0636     project.addChild(ws2);
0637 
0638     RetransformCallCounter c;
0639 
0640     p->copy();
0641     ws2->paste(true);
0642 
0643     auto plots = ws2->children(AspectType::CartesianPlot);
0644     QCOMPARE(plots.count(), 1);
0645 
0646     // Check that the plot was retransformed after pasting
0647     QCOMPARE(c.callCount(plots.at(0)), 1);
0648 }
0649 
0650 void RetransformTest::TestAddCurve() {
0651     Project project;
0652     auto* ws = new Worksheet(QStringLiteral("Worksheet"));
0653     QVERIFY(ws != nullptr);
0654     project.addChild(ws);
0655 
0656     auto* p = new CartesianPlot(QStringLiteral("plot"));
0657     p->setType(CartesianPlot::Type::TwoAxes); // Otherwise no axis are created
0658     QVERIFY(p != nullptr);
0659     ws->addChild(p);
0660 
0661     RetransformCallCounter c;
0662 
0663     p->addChild(new XYEquationCurve(QLatin1String("curve")));
0664 
0665     // check that plot will be recalculated if a curve will be added
0666     QCOMPARE(c.callCount(p), 1);
0667 
0668     auto children = project.children(AspectType::AbstractAspect, AbstractAspect::ChildIndexFlag::Recursive);
0669 
0670     // Project/Worksheet
0671     // Project/Worksheet/plot
0672     // Project/Worksheet/plot/x
0673     // Project/Worksheet/plot/y
0674     // Project/Worksheet/plot/f(x) // equation curve
0675     QCOMPARE(children.length(), 5);
0676     for (const auto& child : children)
0677         connect(child, &AbstractAspect::retransformCalledSignal, &c, &RetransformCallCounter::aspectRetransformed);
0678 
0679     for (const auto& plot : project.children(AspectType::CartesianPlot, AbstractAspect::ChildIndexFlag::Recursive))
0680         connect(static_cast<CartesianPlot*>(plot), &CartesianPlot::scaleRetransformed, &c, &RetransformCallCounter::retransformScaleCalled);
0681 
0682     c.resetRetransformCount();
0683 
0684     auto equationCurves = p->children(AspectType::XYEquationCurve);
0685     QCOMPARE(equationCurves.count(), 1);
0686     auto* equationCurve = static_cast<XYEquationCurve*>(equationCurves.at(0));
0687     XYEquationCurve::EquationData data;
0688     data.count = 100;
0689     data.expression1 = QStringLiteral("x");
0690     data.expression2 = QString();
0691     data.min = QStringLiteral("0");
0692     data.max = QStringLiteral("10");
0693     data.type = XYEquationCurve::EquationType::Cartesian;
0694     equationCurve->setEquationData(data);
0695 
0696     auto list =
0697         QStringList({QStringLiteral("Project/Worksheet/plot/x"), QStringLiteral("Project/Worksheet/plot/y"), QStringLiteral("Project/Worksheet/plot/f(x)")});
0698     QCOMPARE(c.elementLogCount(false), list.count());
0699     QCOMPARE(c.callCount(list.at(0)), 1);
0700     QCOMPARE(c.callCount(list.at(1)), 1);
0701     QCOMPARE(c.callCount(list.at(2)), 0);
0702 
0703     // x and y are called only once
0704     QCOMPARE(c.logsXScaleRetransformed.count(), 1);
0705     QCOMPARE(c.logsXScaleRetransformed.at(0).plot, p);
0706     QCOMPARE(c.logsXScaleRetransformed.at(0).index, 0);
0707     QCOMPARE(c.logsYScaleRetransformed.count(), 1);
0708     QCOMPARE(c.logsYScaleRetransformed.at(0).plot, p);
0709     QCOMPARE(c.logsYScaleRetransformed.at(0).index, 0);
0710 }
0711 
0712 void RetransformTest::TestBarPlotOrientation() {
0713     RetransformCallCounter c;
0714     Project project;
0715 
0716     project.load(QFINDTESTDATA(QLatin1String("data/barplot_test.lml")));
0717     auto children = project.children(AspectType::AbstractAspect, AbstractAspect::ChildIndexFlag::Recursive);
0718 
0719     // Spreadsheet "Spreadsheet"
0720     // Column "labels"
0721     // Column "1"
0722     // Column "2"
0723     // Column "3"
0724     // Column "4"
0725     // Worksheet "Worksheet"
0726     // CartesianPlot "xy-plot"
0727     // Axis "x"
0728     // Axis "x2"
0729     // Axis "y"
0730     // Axis "y2"
0731     // BarPlot "BarPlot"
0732     QCOMPARE(children.length(), 13);
0733     for (const auto& child : children)
0734         connect(child, &AbstractAspect::retransformCalledSignal, &c, &RetransformCallCounter::aspectRetransformed);
0735 
0736     for (const auto& plot : project.children(AspectType::CartesianPlot, AbstractAspect::ChildIndexFlag::Recursive))
0737         connect(static_cast<CartesianPlot*>(plot), &CartesianPlot::scaleRetransformed, &c, &RetransformCallCounter::retransformScaleCalled);
0738 
0739     auto barplots = project.children(AspectType::BarPlot, AbstractAspect::ChildIndexFlag::Recursive);
0740     QCOMPARE(barplots.length(), 1);
0741     auto barplot = static_cast<BarPlot*>(barplots.at(0));
0742     QCOMPARE(barplot->name(), QStringLiteral("Bar Plot"));
0743 
0744     // Trigger retransform
0745     barplot->setOrientation(BarPlot::Orientation::Horizontal);
0746 
0747     auto* worksheet = project.child<Worksheet>(0);
0748     QVERIFY(worksheet);
0749     auto* plot = worksheet->child<CartesianPlot>(0);
0750     QVERIFY(plot);
0751     QCOMPARE(plot->name(), QLatin1String("xy-plot"));
0752 
0753     // x and y are called only once
0754     QCOMPARE(c.logsXScaleRetransformed.count(), 1); // one plot with 2 x-Axes but both are using the same range so 1
0755     QCOMPARE(c.logsXScaleRetransformed.at(0).plot, plot);
0756     QCOMPARE(c.logsXScaleRetransformed.at(0).index, 0);
0757     QCOMPARE(c.logsYScaleRetransformed.count(), 1); // one plot with 2 x-Axes but both are using the same range so 1
0758     QCOMPARE(c.logsYScaleRetransformed.at(0).plot, plot);
0759     QCOMPARE(c.logsYScaleRetransformed.at(0).index, 0);
0760 }
0761 
0762 void RetransformTest::TestZoom() {
0763     RetransformCallCounter c;
0764     Project project;
0765 
0766     auto* sheet = new Spreadsheet(QStringLiteral("Spreadsheet"), false);
0767     sheet->setColumnCount(2);
0768     sheet->setRowCount(100);
0769 
0770     QVector<int> xData;
0771     QVector<double> yData;
0772     for (int i = 0; i < 100; i++) {
0773         xData.append(i);
0774         yData.append(i);
0775     }
0776     auto* xColumn = sheet->column(0);
0777     xColumn->setColumnMode(AbstractColumn::ColumnMode::Integer);
0778     xColumn->replaceInteger(0, xData);
0779     auto* yColumn = sheet->column(1);
0780     yColumn->setColumnMode(AbstractColumn::ColumnMode::Double);
0781     yColumn->replaceValues(0, yData);
0782 
0783     project.addChild(sheet);
0784 
0785     auto* worksheet = new Worksheet(QStringLiteral("Worksheet - Spreadsheet"));
0786     project.addChild(worksheet);
0787 
0788     auto* p = new CartesianPlot(QStringLiteral("Plot - Spreadsheet"));
0789     p->setType(CartesianPlot::Type::FourAxes); // Otherwise no axis are created
0790     worksheet->addChild(p);
0791 
0792     auto* curve = new XYCurve(QStringLiteral("curve"));
0793     p->addChild(curve);
0794     curve->setXColumn(xColumn);
0795     curve->setYColumn(yColumn);
0796 
0797     auto children = project.children(AspectType::AbstractAspect, AbstractAspect::ChildIndexFlag::Recursive);
0798 
0799     // Spreadsheet "Spreadsheet"
0800     // Column "1"
0801     // Column "2"
0802     // Worksheet "Worksheet-Spreadsheet"
0803     // CartesianPlot "Plot-Spreadsheet"
0804     // Axis "x"
0805     // Axis "x2"
0806     // Axis "y"
0807     // Axis "y2"
0808     // XYCurve "curve"
0809     QCOMPARE(children.length(), 10);
0810     for (const auto& child : children)
0811         connect(child, &AbstractAspect::retransformCalledSignal, &c, &RetransformCallCounter::aspectRetransformed);
0812 
0813     auto plots = project.children(AspectType::CartesianPlot, AbstractAspect::ChildIndexFlag::Recursive);
0814     QCOMPARE(plots.length(), 1);
0815     auto* plot = static_cast<CartesianPlot*>(plots[0]);
0816     connect(static_cast<CartesianPlot*>(plot), &CartesianPlot::scaleRetransformed, &c, &RetransformCallCounter::retransformScaleCalled);
0817 
0818     plot->zoomInX();
0819 
0820     // x and y are called only once
0821     QCOMPARE(c.logsXScaleRetransformed.count(), 1); // one plot with 2 x-Axes but both are using the same range so 1
0822     QCOMPARE(c.logsXScaleRetransformed.at(0).plot, plot);
0823     QCOMPARE(c.logsXScaleRetransformed.at(0).index, 0);
0824     QCOMPARE(c.logsYScaleRetransformed.count(), 1); // one plot with 2 x-Axes but both are using the same range so 1
0825     QCOMPARE(c.logsYScaleRetransformed.at(0).plot, plot);
0826     QCOMPARE(c.logsYScaleRetransformed.at(0).index, 0);
0827 }
0828 
0829 void RetransformTest::TestImportCSV() {
0830     Project project;
0831     auto* ws = new Worksheet(QStringLiteral("Worksheet"));
0832     QVERIFY(ws != nullptr);
0833     project.addChild(ws);
0834 
0835     Spreadsheet* spreadsheet = new Spreadsheet(QStringLiteral("test"), false);
0836     spreadsheet->setColumnCount(2);
0837     spreadsheet->setRowCount(3);
0838 
0839     auto* xCol = spreadsheet->column(0);
0840     xCol->replaceValues(0, QVector<double>({1, 2, 3}));
0841 
0842     auto* yCol = spreadsheet->column(1);
0843     yCol->replaceValues(0, QVector<double>({2, 3, 4}));
0844 
0845     QCOMPARE(spreadsheet->rowCount(), 3);
0846     QCOMPARE(spreadsheet->columnCount(), 2);
0847 
0848     project.addChild(spreadsheet);
0849     auto* p = new CartesianPlot(QStringLiteral("plot"));
0850     p->setType(CartesianPlot::Type::TwoAxes); // Otherwise no axis are created
0851     QVERIFY(p != nullptr);
0852     ws->addChild(p);
0853 
0854     auto* curve = new XYCurve(QStringLiteral("xy-curve"));
0855     curve->setXColumn(xCol);
0856     curve->setYColumn(yCol);
0857     p->addChild(curve);
0858 
0859     auto children = project.children(AspectType::AbstractAspect, AbstractAspect::ChildIndexFlag::Recursive);
0860 
0861     RetransformCallCounter c;
0862     // Worksheet "Worksheet"
0863     // CartesianPlot "plot"
0864     // Axis "x"
0865     // Axis "y"
0866     // XYCurve "xy-curve"
0867     // Spreadsheet "test"
0868     // Column "1"
0869     // Column "2"
0870     QCOMPARE(children.length(), 8);
0871     for (const auto& child : children) {
0872         qDebug() << child->name();
0873         connect(child, &AbstractAspect::retransformCalledSignal, &c, &RetransformCallCounter::aspectRetransformed);
0874     }
0875 
0876     for (const auto& plot : project.children(AspectType::CartesianPlot, AbstractAspect::ChildIndexFlag::Recursive))
0877         connect(static_cast<CartesianPlot*>(plot), &CartesianPlot::scaleRetransformed, &c, &RetransformCallCounter::retransformScaleCalled);
0878 
0879     // check axis ranges
0880     auto axes = p->children(AspectType::Axis, AbstractAspect::ChildIndexFlag::Recursive);
0881     QCOMPARE(axes.length(), 2);
0882     auto* xAxis = axes.at(0);
0883     QVector<double> ref = {1, 1.5, 2, 2.5, 3};
0884     COMPARE_DOUBLE_VECTORS(static_cast<Axis*>(xAxis)->tickLabelValues(), ref);
0885     auto* yAxis = axes.at(1);
0886     ref = {2, 2.5, 3, 3.5, 4};
0887     COMPARE_DOUBLE_VECTORS(static_cast<Axis*>(yAxis)->tickLabelValues(), ref);
0888 
0889     QTemporaryFile file;
0890     QCOMPARE(file.open(), true);
0891     QVERIFY(file.write("1,2\n10,10\n20,20\n30,30\n") > 0);
0892     file.close();
0893 
0894     AsciiFilter filter;
0895     filter.setHeaderLine(1);
0896     filter.readDataFromFile(file.fileName(), spreadsheet, AbstractFileFilter::ImportMode::Replace);
0897 
0898     QCOMPARE(spreadsheet->rowCount(), 3);
0899     QCOMPARE(spreadsheet->columnCount(), 2);
0900 
0901     xCol = spreadsheet->column(0);
0902     QCOMPARE(xCol->name(), QStringLiteral("1"));
0903     QCOMPARE(xCol->valueAt(0), 10);
0904     QCOMPARE(xCol->valueAt(1), 20);
0905     QCOMPARE(xCol->valueAt(2), 30);
0906 
0907     yCol = spreadsheet->column(1);
0908     QCOMPARE(yCol->name(), QStringLiteral("2"));
0909     QCOMPARE(yCol->valueAt(0), 10);
0910     QCOMPARE(yCol->valueAt(1), 20);
0911     QCOMPARE(yCol->valueAt(2), 30);
0912 
0913     ref = {10, 15, 20, 25, 30};
0914     auto tickLabelValues = static_cast<Axis*>(xAxis)->tickLabelValues();
0915     COMPARE_DOUBLE_VECTORS(tickLabelValues, ref);
0916     ref = {10, 15, 20, 25, 30};
0917     COMPARE_DOUBLE_VECTORS(static_cast<Axis*>(yAxis)->tickLabelValues(), ref);
0918 
0919     // x and y are called only once
0920     QCOMPARE(c.logsXScaleRetransformed.count(), 1); // one plot with 1 x-Axis
0921     QCOMPARE(c.logsXScaleRetransformed.at(0).plot, p);
0922     QCOMPARE(c.logsXScaleRetransformed.at(0).index, 0);
0923     QCOMPARE(c.logsYScaleRetransformed.count(), 1); // one plot with 1 x-Axis
0924     QCOMPARE(c.logsYScaleRetransformed.at(0).plot, p);
0925     QCOMPARE(c.logsYScaleRetransformed.at(0).index, 0);
0926 
0927     auto list = QStringList(
0928         {QStringLiteral("Project/Worksheet/plot/x"), QStringLiteral("Project/Worksheet/plot/y"), QStringLiteral("Project/Worksheet/plot/xy-curve")});
0929     QCOMPARE(c.elementLogCount(false), list.count());
0930     for (auto& s : list) {
0931         qDebug() << s;
0932         QCOMPARE(c.callCount(s), 1);
0933     }
0934 }
0935 
0936 void RetransformTest::TestSetScale() {
0937     RetransformCallCounter c;
0938     Project project;
0939 
0940     auto* sheet = new Spreadsheet(QStringLiteral("Spreadsheet"), false);
0941     sheet->setColumnCount(2);
0942     sheet->setRowCount(100);
0943 
0944     QVector<int> xData;
0945     QVector<double> yData;
0946     for (int i = 0; i < 100; i++) {
0947         xData.append(i);
0948         yData.append(i);
0949     }
0950     auto* xColumn = sheet->column(0);
0951     xColumn->setColumnMode(AbstractColumn::ColumnMode::Integer);
0952     xColumn->replaceInteger(0, xData);
0953     auto* yColumn = sheet->column(1);
0954     yColumn->setColumnMode(AbstractColumn::ColumnMode::Double);
0955     yColumn->replaceValues(0, yData);
0956 
0957     project.addChild(sheet);
0958 
0959     auto* worksheet = new Worksheet(QStringLiteral("Worksheet"));
0960     project.addChild(worksheet);
0961 
0962     auto* p = new CartesianPlot(QStringLiteral("Plot"));
0963     p->setType(CartesianPlot::Type::FourAxes); // Otherwise no axis are created
0964     worksheet->addChild(p);
0965 
0966     auto* curve = new XYCurve(QStringLiteral("curve"));
0967     p->addChild(curve);
0968     curve->setXColumn(xColumn);
0969     curve->setYColumn(yColumn);
0970 
0971     auto children = project.children(AspectType::AbstractAspect, AbstractAspect::ChildIndexFlag::Recursive);
0972 
0973     // Spreadsheet "Spreadsheet"
0974     // Column "1"
0975     // Column "2"
0976     // Worksheet "Worksheet"
0977     // CartesianPlot "Plot"
0978     // Axis "x"
0979     // Axis "x2"
0980     // Axis "y"
0981     // Axis "y2"
0982     // XYCurve "curve"
0983     QCOMPARE(children.length(), 10);
0984     for (const auto& child : children)
0985         connect(child, &AbstractAspect::retransformCalledSignal, &c, &RetransformCallCounter::aspectRetransformed);
0986 
0987     auto plots = project.children(AspectType::CartesianPlot, AbstractAspect::ChildIndexFlag::Recursive);
0988     QCOMPARE(plots.length(), 1);
0989     auto* plot = static_cast<CartesianPlot*>(plots[0]);
0990     connect(static_cast<CartesianPlot*>(plot), &CartesianPlot::scaleRetransformed, &c, &RetransformCallCounter::retransformScaleCalled);
0991 
0992     c.resetRetransformCount();
0993 
0994     auto list = QStringList({// data rect of the plot does not change, so retransforming the
0995                              // plot is not needed
0996                              QStringLiteral("Project/Worksheet/Plot/x"),
0997                              QStringLiteral("Project/Worksheet/Plot/x2"),
0998                              QStringLiteral("Project/Worksheet/Plot/y"),
0999                              QStringLiteral("Project/Worksheet/Plot/y2"),
1000                              QStringLiteral("Project/Worksheet/Plot/curve")});
1001 
1002     plot->setRangeScale(Dimension::X, 0, RangeT::Scale::Log10);
1003 
1004     QCOMPARE(c.elementLogCount(false), list.count());
1005     for (auto& s : list)
1006         QCOMPARE(c.callCount(s), 1);
1007 
1008     // x and y are called only once
1009     QCOMPARE(c.logsXScaleRetransformed.count(), 1);
1010     QCOMPARE(c.logsXScaleRetransformed.at(0).plot, plot);
1011     QCOMPARE(c.logsXScaleRetransformed.at(0).index, 0);
1012     QCOMPARE(c.logsYScaleRetransformed.count(), 0);
1013 }
1014 
1015 void RetransformTest::TestChangePlotRange() {
1016     RetransformCallCounter c;
1017     Project project;
1018 
1019     auto* sheet = new Spreadsheet(QStringLiteral("Spreadsheet"), false);
1020     sheet->setColumnCount(2);
1021     sheet->setRowCount(100);
1022 
1023     QVector<int> xData;
1024     QVector<double> yData;
1025     for (int i = 0; i < 100; i++) {
1026         xData.append(i);
1027         yData.append(i);
1028     }
1029     auto* xColumn = sheet->column(0);
1030     xColumn->setColumnMode(AbstractColumn::ColumnMode::Integer);
1031     xColumn->replaceInteger(0, xData);
1032     auto* yColumn = sheet->column(1);
1033     yColumn->setColumnMode(AbstractColumn::ColumnMode::Double);
1034     yColumn->replaceValues(0, yData);
1035 
1036     project.addChild(sheet);
1037 
1038     auto* worksheet = new Worksheet(QStringLiteral("Worksheet"));
1039     project.addChild(worksheet);
1040 
1041     auto* p = new CartesianPlot(QStringLiteral("Plot"));
1042     p->setType(CartesianPlot::Type::TwoAxes); // Otherwise no axis are created
1043     worksheet->addChild(p);
1044 
1045     auto* curve = new XYCurve(QStringLiteral("curve"));
1046     p->addChild(curve);
1047     curve->setXColumn(xColumn);
1048     curve->setYColumn(yColumn);
1049 
1050     auto children = project.children(AspectType::AbstractAspect, AbstractAspect::ChildIndexFlag::Recursive);
1051 
1052     // Spreadsheet "Spreadsheet"
1053     // Column "1"
1054     // Column "2"
1055     // Worksheet "Worksheet"
1056     // CartesianPlot "Plot"
1057     // Axis "x"
1058     // Axis "y"
1059     // XYCurve "curve"
1060     QCOMPARE(children.length(), 8);
1061     for (const auto& child : children)
1062         connect(child, &AbstractAspect::retransformCalledSignal, &c, &RetransformCallCounter::aspectRetransformed);
1063 
1064     auto plots = project.children(AspectType::CartesianPlot, AbstractAspect::ChildIndexFlag::Recursive);
1065     QCOMPARE(plots.length(), 1);
1066     auto* plot = static_cast<CartesianPlot*>(plots[0]);
1067     connect(static_cast<CartesianPlot*>(plot), &CartesianPlot::scaleRetransformed, &c, &RetransformCallCounter::retransformScaleCalled);
1068 
1069     c.resetRetransformCount();
1070 
1071     auto list = QStringList({// data rect of the plot does not change, so retransforming the
1072                              // plot is not needed
1073                              QStringLiteral("Project/Worksheet/Plot/x"),
1074                              QStringLiteral("Project/Worksheet/Plot/y"),
1075                              QStringLiteral("Project/Worksheet/Plot/curve")});
1076 
1077     CartesianPlotDock dock(nullptr);
1078     dock.setPlots({plot});
1079     dock.addXRange();
1080 
1081     QCOMPARE(plot->rangeCount(Dimension::X), 2);
1082 
1083     dock.autoScaleChanged(Dimension::X, 1, false);
1084     QCOMPARE(plot->autoScale(Dimension::X, 1), false);
1085 
1086     dock.minChanged(Dimension::X, 1, 10);
1087     dock.maxChanged(Dimension::X, 1, 20);
1088 
1089     // check axis ranges
1090     auto axes = project.children(AspectType::Axis, AbstractAspect::ChildIndexFlag::Recursive);
1091     QCOMPARE(axes.length(), 2);
1092     auto* xAxis = static_cast<Axis*>(axes.at(0));
1093     auto* yAxis = static_cast<Axis*>(axes.at(1));
1094 
1095     QCOMPARE(xAxis->name(), QStringLiteral("x"));
1096     QCOMPARE(yAxis->name(), QStringLiteral("y"));
1097 
1098     //  QVector<QString> refString = {"0", "20", "20", "177.8", "183.3", "188.8", "194.4"};
1099     //  COMPARE_STRING_VECTORS(static_cast<Axis*>(xAxis)->tickLabelStrings(), refString);
1100     QVector<double> ref = {0, 20, 40, 60, 80, 100};
1101     COMPARE_DOUBLE_VECTORS(xAxis->tickLabelValues(), ref);
1102     ref = {0, 20, 40, 60, 80, 100};
1103     COMPARE_DOUBLE_VECTORS(yAxis->tickLabelValues(), ref);
1104 
1105     int linesUpdatedCounter = 0;
1106     connect(curve, &XYCurve::linesUpdated, [&linesUpdatedCounter](const XYCurve*, const QVector<QLineF> lines) {
1107         // One point before and one point after is used therefore it is not 10, 20
1108         // se XYCurvePrivate::updateLines() startIndex--; and endIndex++;
1109         QCOMPARE(lines.at(0).p1().x(), 9);
1110         QCOMPARE(lines.last().p2().x(), 21);
1111         linesUpdatedCounter++;
1112     });
1113     // switch in first csystem from the first x range to the second x range
1114     dock.PlotRangeChanged(0, Dimension::X, 1);
1115 
1116     ref = {10, 12, 14, 16, 18, 20};
1117     COMPARE_DOUBLE_VECTORS(static_cast<Axis*>(xAxis)->tickLabelValues(), ref);
1118     ref = {0, 20, 40, 60, 80, 100};
1119     COMPARE_DOUBLE_VECTORS(static_cast<Axis*>(yAxis)->tickLabelValues(), ref);
1120 
1121     auto dataRect = plot->dataRect();
1122 
1123     // check that lines are starting at the beginning of the datarect
1124     auto line = xAxis->d_func()->lines.at(0);
1125     QCOMPARE(line.p1().x(), dataRect.left());
1126     QCOMPARE(line.p2().x(), dataRect.right());
1127 
1128     auto lines = curve->d_func()->m_lines;
1129     QCOMPARE(lines.at(0).p1().x(), dataRect.left());
1130     QCOMPARE(lines.last().p2().x(), dataRect.right());
1131 
1132     QCOMPARE(linesUpdatedCounter, 1);
1133 }
1134 
1135 void RetransformTest::TestChangePlotRangeElement() {
1136     // Change the plotrange of one of the elements
1137 
1138     RetransformCallCounter c;
1139     Project project;
1140 
1141     auto* sheet = new Spreadsheet(QStringLiteral("Spreadsheet"), false);
1142     sheet->setColumnCount(2);
1143     sheet->setRowCount(100);
1144 
1145     QVector<int> xData;
1146     QVector<double> yData;
1147     for (int i = 0; i < 100; i++) {
1148         xData.append(i);
1149         yData.append(i);
1150     }
1151     auto* xColumn = sheet->column(0);
1152     xColumn->setColumnMode(AbstractColumn::ColumnMode::Integer);
1153     xColumn->replaceInteger(0, xData);
1154     auto* yColumn = sheet->column(1);
1155     yColumn->setColumnMode(AbstractColumn::ColumnMode::Double);
1156     yColumn->replaceValues(0, yData);
1157 
1158     project.addChild(sheet);
1159 
1160     auto* worksheet = new Worksheet(QStringLiteral("Worksheet"));
1161     project.addChild(worksheet);
1162 
1163     auto* p = new CartesianPlot(QStringLiteral("Plot"));
1164     p->setType(CartesianPlot::Type::FourAxes); // Otherwise no axis are created
1165     worksheet->addChild(p);
1166 
1167     auto* curve = new XYCurve(QStringLiteral("curve"));
1168     p->addChild(curve);
1169     curve->setXColumn(xColumn);
1170     curve->setYColumn(yColumn);
1171 
1172     auto children = project.children(AspectType::AbstractAspect, AbstractAspect::ChildIndexFlag::Recursive);
1173 
1174     // Spreadsheet "Spreadsheet"
1175     // Column "1"
1176     // Column "2"
1177     // Worksheet "Worksheet"
1178     // CartesianPlot "Plot"
1179     // Axis "x"
1180     // Axis "x2"
1181     // Axis "y"
1182     // Axis "y2"
1183     // XYCurve "curve"
1184     QCOMPARE(children.length(), 10);
1185     for (const auto& child : children)
1186         connect(child, &AbstractAspect::retransformCalledSignal, &c, &RetransformCallCounter::aspectRetransformed);
1187 
1188     auto plots = project.children(AspectType::CartesianPlot, AbstractAspect::ChildIndexFlag::Recursive);
1189     QCOMPARE(plots.length(), 1);
1190     auto* plot = static_cast<CartesianPlot*>(plots[0]);
1191     connect(static_cast<CartesianPlot*>(plot), &CartesianPlot::scaleRetransformed, &c, &RetransformCallCounter::retransformScaleCalled);
1192 
1193     // check axes
1194     auto axes = project.children(AspectType::Axis, AbstractAspect::ChildIndexFlag::Recursive);
1195     QCOMPARE(axes.length(), 4);
1196     auto* xAxis1 = static_cast<Axis*>(axes.at(0));
1197     auto* xAxis2 = static_cast<Axis*>(axes.at(1));
1198     auto* yAxis1 = static_cast<Axis*>(axes.at(2));
1199     auto* yAxis2 = static_cast<Axis*>(axes.at(3));
1200     QCOMPARE(xAxis1->name(), QStringLiteral("x"));
1201     QCOMPARE(xAxis2->name(), QStringLiteral("x2"));
1202     QCOMPARE(yAxis1->name(), QStringLiteral("y"));
1203     QCOMPARE(yAxis2->name(), QStringLiteral("y2"));
1204 
1205     auto list = QStringList({// data rect of the plot does not change, so retransforming the
1206                              // plot is not needed
1207                              QStringLiteral("Project/Worksheet/Plot/x"),
1208                              QStringLiteral("Project/Worksheet/Plot/y"),
1209                              QStringLiteral("Project/Worksheet/Plot/x2"),
1210                              QStringLiteral("Project/Worksheet/Plot/y2"),
1211                              QStringLiteral("Project/Worksheet/Plot/curve")});
1212 
1213     CartesianPlotDock dock(nullptr);
1214     dock.setPlots({plot});
1215     dock.addYRange();
1216     QCOMPARE(plot->rangeCount(Dimension::Y), 2);
1217     dock.addPlotRange();
1218     QCOMPARE(plot->coordinateSystemCount(), 2);
1219 
1220     dock.autoScaleChanged(Dimension::Y, 1, false);
1221     QCOMPARE(plot->autoScale(Dimension::Y, 1), false);
1222     dock.minChanged(Dimension::Y, 1, 10);
1223     dock.maxChanged(Dimension::Y, 1, 20);
1224 
1225     // Csystem1:
1226     // x 0..1
1227     // y 0..1
1228     // Csystem2:
1229     // x 0..1
1230     // y 10..20
1231 
1232     c.resetRetransformCount();
1233 
1234     dock.PlotRangeChanged(1, Dimension::Y, 1);
1235 
1236     QCOMPARE(c.logsXScaleRetransformed.count(), 0);
1237     QCOMPARE(c.logsYScaleRetransformed.count(), 1);
1238     QCOMPARE(c.logsYScaleRetransformed.at(0).index, 1);
1239 
1240     QCOMPARE(plot->range(Dimension::X, 0).start(), 0);
1241     QCOMPARE(plot->range(Dimension::X, 0).end(), 100);
1242     QCOMPARE(plot->range(Dimension::Y, 0).start(), 0);
1243     QCOMPARE(plot->range(Dimension::Y, 0).end(), 100);
1244     QCOMPARE(plot->range(Dimension::Y, 1).start(), 10);
1245     QCOMPARE(plot->range(Dimension::Y, 1).end(), 20);
1246 
1247     c.resetRetransformCount();
1248 
1249     {
1250         AxisDock d(nullptr);
1251         d.setAxes({yAxis2});
1252         QCOMPARE(yAxis2->coordinateSystemIndex(), 0);
1253         d.plotRangeChanged(1); // change plotrange of yAxis2 from 0 to 1
1254         QCOMPARE(yAxis2->coordinateSystemIndex(), 1);
1255     }
1256 
1257     // the y scale of cSystem1 and the y scale of CSystem2 shall be retransformed, because
1258     // an element switches from y1 to y2
1259     // xScale shall not be retransformed because it is common for both cSystems
1260     QCOMPARE(c.logsXScaleRetransformed.count(), 0);
1261     QCOMPARE(c.logsYScaleRetransformed.count(), 0);
1262     //  QCOMPARE(c.logsYScaleRetransformed.at(0).index, 0); // range did not change so retransformScale was not called
1263     //  QCOMPARE(c.logsYScaleRetransformed.at(1).index, 1); // range did not change so retransformScale was not called
1264 
1265     const auto dataRect = plot->dataRect();
1266 
1267     // Check that both lines go from bottom to top
1268     {
1269         QCOMPARE(yAxis1->d_func()->lines.count(), 1);
1270         const auto line = yAxis1->d_func()->lines.at(0);
1271         QCOMPARE(line.p1().y(), dataRect.bottom());
1272         QCOMPARE(line.p2().y(), dataRect.top());
1273     }
1274 
1275     {
1276         QCOMPARE(yAxis2->d_func()->lines.count(), 1);
1277         const auto line = yAxis2->d_func()->lines.at(0);
1278         QCOMPARE(line.p1().y(), dataRect.bottom());
1279         QCOMPARE(line.p2().y(), dataRect.top());
1280     }
1281 }
1282 
1283 void RetransformTest::TestChangePlotRangeElement2() {
1284     // Change the plotrange of one of the elements
1285 
1286     RetransformCallCounter c;
1287     Project project;
1288 
1289     auto* sheet = new Spreadsheet(QStringLiteral("Spreadsheet"), false);
1290     sheet->setColumnCount(4);
1291     sheet->setRowCount(100);
1292 
1293     project.addChild(sheet);
1294 
1295     auto* worksheet = new Worksheet(QStringLiteral("Worksheet"));
1296     project.addChild(worksheet);
1297 
1298     auto* p = new CartesianPlot(QStringLiteral("Plot"));
1299     p->setType(CartesianPlot::Type::FourAxes); // Otherwise no axis are created
1300     worksheet->addChild(p);
1301     p->setNiceExtend(false);
1302 
1303     auto* curve = new XYCurve(QStringLiteral("curve"));
1304     p->addChild(curve);
1305     {
1306         QVector<int> xData;
1307         QVector<double> yData;
1308         for (int i = 1; i < 101; i++) {
1309             xData.append(i);
1310             yData.append(i);
1311         }
1312         auto* xColumn = sheet->column(0);
1313         xColumn->setColumnMode(AbstractColumn::ColumnMode::Integer);
1314         xColumn->replaceInteger(0, xData);
1315         auto* yColumn = sheet->column(1);
1316         yColumn->setColumnMode(AbstractColumn::ColumnMode::Double);
1317         yColumn->replaceValues(0, yData);
1318         curve->setXColumn(xColumn);
1319         curve->setYColumn(yColumn);
1320     }
1321 
1322     auto* curve2 = new XYCurve(QStringLiteral("curve2"));
1323     p->addChild(curve2);
1324     {
1325         QVector<int> xData;
1326         QVector<double> yData;
1327         for (int i = 1; i < 31; i++) { // different to the above
1328             xData.append(i);
1329             yData.append(i);
1330         }
1331         auto* xColumn = sheet->column(2);
1332         xColumn->setColumnMode(AbstractColumn::ColumnMode::Integer);
1333         xColumn->replaceInteger(0, xData);
1334         auto* yColumn = sheet->column(3);
1335         yColumn->setColumnMode(AbstractColumn::ColumnMode::Double);
1336         yColumn->replaceValues(0, yData);
1337         curve2->setXColumn(xColumn);
1338         curve2->setYColumn(yColumn);
1339     }
1340 
1341     // SAVE_PROJECT("TestChangePlotRangeElement2.lml");
1342 
1343     QCOMPARE(p->range(Dimension::X, 0).start(), 1);
1344     QCOMPARE(p->range(Dimension::X, 0).end(), 100);
1345     QCOMPARE(p->range(Dimension::Y, 0).start(), 1);
1346     QCOMPARE(p->range(Dimension::Y, 0).end(), 100);
1347 
1348     auto children = project.children(AspectType::AbstractAspect, AbstractAspect::ChildIndexFlag::Recursive);
1349 
1350     // Spreadsheet "Spreadsheet"
1351     // Column "1"
1352     // Column "2"
1353     // Column "3"
1354     // Column "4"
1355     // Worksheet "Worksheet"
1356     // CartesianPlot "Plot"
1357     // Axis "x"
1358     // Axis "x2"
1359     // Axis "y"
1360     // Axis "y2"
1361     // XYCurve "curve"
1362     // XYCurve "curve2"
1363     QCOMPARE(children.length(), 13);
1364     for (const auto& child : children)
1365         connect(child, &AbstractAspect::retransformCalledSignal, &c, &RetransformCallCounter::aspectRetransformed);
1366 
1367     auto plots = project.children(AspectType::CartesianPlot, AbstractAspect::ChildIndexFlag::Recursive);
1368     QCOMPARE(plots.length(), 1);
1369     auto* plot = static_cast<CartesianPlot*>(plots[0]);
1370     connect(static_cast<CartesianPlot*>(plot), &CartesianPlot::scaleRetransformed, &c, &RetransformCallCounter::retransformScaleCalled);
1371 
1372     // check axis ranges
1373     auto axes = project.children(AspectType::Axis, AbstractAspect::ChildIndexFlag::Recursive);
1374     QCOMPARE(axes.length(), 4);
1375     auto* xAxis1 = static_cast<Axis*>(axes.at(0));
1376     auto* xAxis2 = static_cast<Axis*>(axes.at(1));
1377     auto* yAxis1 = static_cast<Axis*>(axes.at(2));
1378     auto* yAxis2 = static_cast<Axis*>(axes.at(3));
1379     QCOMPARE(xAxis1->name(), QStringLiteral("x"));
1380     QCOMPARE(xAxis2->name(), QStringLiteral("x2"));
1381     QCOMPARE(yAxis1->name(), QStringLiteral("y"));
1382     QCOMPARE(yAxis2->name(), QStringLiteral("y2"));
1383 
1384     auto list = QStringList({// data rect of the plot does not change, so retransforming the
1385                              // plot is not needed
1386                              QStringLiteral("Project/Worksheet/Plot/x"),
1387                              QStringLiteral("Project/Worksheet/Plot/y"),
1388                              QStringLiteral("Project/Worksheet/Plot/x2"),
1389                              QStringLiteral("Project/Worksheet/Plot/y2"),
1390                              QStringLiteral("Project/Worksheet/Plot/curve"),
1391                              QStringLiteral("Project/Worksheet/Plot/curve2")});
1392 
1393     CartesianPlotDock dock(nullptr);
1394     dock.setPlots({plot});
1395     dock.addYRange();
1396     QCOMPARE(plot->rangeCount(Dimension::Y), 2);
1397     dock.addPlotRange();
1398     QCOMPARE(plot->coordinateSystemCount(), 2);
1399 
1400     c.resetRetransformCount();
1401 
1402     dock.PlotRangeChanged(1, Dimension::Y, 1); // switch for second csystem to y range 2
1403 
1404     // No update of the scales, because the range did not change
1405     QCOMPARE(c.logsXScaleRetransformed.count(), 0);
1406     QCOMPARE(c.logsYScaleRetransformed.count(), 1);
1407     QCOMPARE(c.logsYScaleRetransformed.at(0).index, 1); // The respective coordinate system must be rescaled
1408 
1409     c.resetRetransformCount();
1410 
1411     XYCurveDock curveDock(nullptr);
1412     curveDock.setupGeneral();
1413     curveDock.setCurves({curve2});
1414     QCOMPARE(curve2->coordinateSystemIndex(), 0);
1415     curveDock.plotRangeChanged(1); // set curve range to other
1416     QCOMPARE(curve2->coordinateSystemIndex(), 1);
1417 
1418     // CSystem1:
1419     // x 1..100
1420     // y 1..100
1421     // CSystem2:
1422     // x 1..100 // bigger 100 > 30 (same xrange as for CSystem1
1423     // y 1..30
1424 
1425     QCOMPARE(plot->range(Dimension::X, 0).start(), 1);
1426     QCOMPARE(plot->range(Dimension::X, 0).end(), 100);
1427     QCOMPARE(plot->range(Dimension::Y, 0).start(), 1);
1428     QCOMPARE(plot->range(Dimension::Y, 0).end(), 100);
1429     QCOMPARE(plot->range(Dimension::Y, 1).start(), 1);
1430     QCOMPARE(plot->range(Dimension::Y, 1).end(), 30);
1431 
1432     // the y scale of cSystem1 and the y scale of CSystem2 shall be retransformed, because
1433     // cruve2 switches from y1 to y2
1434     // xScale shall not be retransformed because it did not change
1435     QCOMPARE(c.logsXScaleRetransformed.count(), 0);
1436     QCOMPARE(c.logsYScaleRetransformed.count(), 1);
1437     // QCOMPARE(c.logsYScaleRetransformed.at(0).index, 0); // did not change, because range is already at 1..100
1438     QCOMPARE(c.logsYScaleRetransformed.at(0).index, 1);
1439 
1440     const auto dataRect = plot->dataRect();
1441 
1442     // Check that both lines go from bottom to top
1443     {
1444         QCOMPARE(yAxis1->d_func()->lines.count(), 1);
1445         const auto line = yAxis1->d_func()->lines.at(0);
1446         QCOMPARE(line.p1().y(), dataRect.bottom());
1447         QCOMPARE(line.p2().y(), dataRect.top());
1448     }
1449 
1450     {
1451         QCOMPARE(yAxis2->d_func()->lines.count(), 1);
1452         const auto line = yAxis2->d_func()->lines.at(0);
1453         QCOMPARE(line.p1().y(), dataRect.bottom());
1454         QCOMPARE(line.p2().y(), dataRect.top());
1455     }
1456 
1457     // Now change cSystemIndex of second yAxis too
1458     c.resetRetransformCount();
1459 
1460     {
1461         AxisDock d(nullptr);
1462         d.setAxes({yAxis2});
1463         d.plotRangeChanged(1); // change from cSystem1 to cSystem2
1464     }
1465 
1466     // the y scale of cSystem1 and the y scale of CSystem2 shall be retransformed, because
1467     // yAxis2 switches from y1 to y2
1468     // xScale shall not be retransformed because it did not change
1469     QCOMPARE(c.logsXScaleRetransformed.count(), 0);
1470     QCOMPARE(c.logsYScaleRetransformed.count(), 0);
1471     //  QCOMPARE(c.logsYScaleRetransformed.at(0).index, 0); // range did not change, so retransformScale gets not called
1472     //  QCOMPARE(c.logsYScaleRetransformed.at(0).index, 1); // range did not change, so retransformScale gets not called
1473 
1474     {
1475         QCOMPARE(yAxis1->d_func()->lines.count(), 1);
1476         const auto line = yAxis1->d_func()->lines.at(0);
1477         QCOMPARE(line.p1().y(), dataRect.bottom());
1478         QCOMPARE(line.p2().y(), dataRect.top());
1479     }
1480 
1481     {
1482         QCOMPARE(yAxis2->d_func()->lines.count(), 1);
1483         const auto line = yAxis2->d_func()->lines.at(0);
1484         QCOMPARE(line.p1().y(), dataRect.bottom());
1485         QCOMPARE(line.p2().y(), dataRect.top());
1486     }
1487 }
1488 
1489 void RetransformTest::TestChangePlotRangeElement3() {
1490     // Change the plotrange of one of the elements
1491     // This time curve1 changes csystem
1492     // --> yRange1 and yRange2 have to be transformed
1493 
1494     RetransformCallCounter c;
1495     Project project;
1496 
1497     auto* sheet = new Spreadsheet(QStringLiteral("Spreadsheet"), false);
1498     sheet->setColumnCount(4);
1499     sheet->setRowCount(100);
1500 
1501     project.addChild(sheet);
1502 
1503     auto* worksheet = new Worksheet(QStringLiteral("Worksheet"));
1504     project.addChild(worksheet);
1505 
1506     auto* p = new CartesianPlot(QStringLiteral("Plot"));
1507     p->setType(CartesianPlot::Type::FourAxes); // Otherwise no axis are created
1508     worksheet->addChild(p);
1509     p->setNiceExtend(false);
1510 
1511     auto* curve = new XYCurve(QStringLiteral("curve"));
1512     p->addChild(curve);
1513     {
1514         QVector<int> xData;
1515         QVector<double> yData;
1516         for (int i = 1; i < 101; i++) {
1517             xData.append(i);
1518             yData.append(i);
1519         }
1520         auto* xColumn = sheet->column(0);
1521         xColumn->setColumnMode(AbstractColumn::ColumnMode::Integer);
1522         xColumn->replaceInteger(0, xData);
1523         auto* yColumn = sheet->column(1);
1524         yColumn->setColumnMode(AbstractColumn::ColumnMode::Double);
1525         yColumn->replaceValues(0, yData);
1526         curve->setXColumn(xColumn);
1527         curve->setYColumn(yColumn);
1528     }
1529 
1530     auto* curve2 = new XYCurve(QStringLiteral("curve2"));
1531     p->addChild(curve2);
1532     {
1533         QVector<int> xData;
1534         QVector<double> yData;
1535         for (int i = 1; i < 31; i++) { // different to the above
1536             xData.append(i);
1537             yData.append(i);
1538         }
1539         auto* xColumn = sheet->column(2);
1540         xColumn->setColumnMode(AbstractColumn::ColumnMode::Integer);
1541         xColumn->replaceInteger(0, xData);
1542         auto* yColumn = sheet->column(3);
1543         yColumn->setColumnMode(AbstractColumn::ColumnMode::Double);
1544         yColumn->replaceValues(0, yData);
1545         curve2->setXColumn(xColumn);
1546         curve2->setYColumn(yColumn);
1547     }
1548 
1549     // SAVE_PROJECT("TestChangePlotRangeElement2.lml");
1550 
1551     QCOMPARE(p->range(Dimension::X, 0).start(), 1);
1552     QCOMPARE(p->range(Dimension::X, 0).end(), 100);
1553     QCOMPARE(p->range(Dimension::Y, 0).start(), 1);
1554     QCOMPARE(p->range(Dimension::Y, 0).end(), 100);
1555 
1556     auto children = project.children(AspectType::AbstractAspect, AbstractAspect::ChildIndexFlag::Recursive);
1557 
1558     // Spreadsheet "Spreadsheet"
1559     // Column "1"
1560     // Column "2"
1561     // Column "3"
1562     // Column "4"
1563     // Worksheet "Worksheet"
1564     // CartesianPlot "Plot"
1565     // Axis "x"
1566     // Axis "x2"
1567     // Axis "y"
1568     // Axis "y2"
1569     // XYCurve "curve"
1570     // XYCurve "curve2"
1571     QCOMPARE(children.length(), 13);
1572     for (const auto& child : children)
1573         connect(child, &AbstractAspect::retransformCalledSignal, &c, &RetransformCallCounter::aspectRetransformed);
1574 
1575     auto plots = project.children(AspectType::CartesianPlot, AbstractAspect::ChildIndexFlag::Recursive);
1576     QCOMPARE(plots.length(), 1);
1577     auto* plot = static_cast<CartesianPlot*>(plots[0]);
1578     connect(static_cast<CartesianPlot*>(plot), &CartesianPlot::scaleRetransformed, &c, &RetransformCallCounter::retransformScaleCalled);
1579 
1580     // check axis ranges
1581     auto axes = project.children(AspectType::Axis, AbstractAspect::ChildIndexFlag::Recursive);
1582     QCOMPARE(axes.length(), 4);
1583     auto* xAxis1 = static_cast<Axis*>(axes.at(0));
1584     auto* xAxis2 = static_cast<Axis*>(axes.at(1));
1585     auto* yAxis1 = static_cast<Axis*>(axes.at(2));
1586     auto* yAxis2 = static_cast<Axis*>(axes.at(3));
1587     QCOMPARE(xAxis1->name(), QStringLiteral("x"));
1588     QCOMPARE(xAxis2->name(), QStringLiteral("x2"));
1589     QCOMPARE(yAxis1->name(), QStringLiteral("y"));
1590     QCOMPARE(yAxis2->name(), QStringLiteral("y2"));
1591 
1592     auto list = QStringList({// data rect of the plot does not change, so retransforming the
1593                              // plot is not needed
1594                              QStringLiteral("Project/Worksheet/Plot/x"),
1595                              QStringLiteral("Project/Worksheet/Plot/y"),
1596                              QStringLiteral("Project/Worksheet/Plot/x2"),
1597                              QStringLiteral("Project/Worksheet/Plot/y2"),
1598                              QStringLiteral("Project/Worksheet/Plot/curve"),
1599                              QStringLiteral("Project/Worksheet/Plot/curve2")});
1600 
1601     CartesianPlotDock dock(nullptr);
1602     dock.setPlots({plot});
1603     dock.addYRange();
1604     QCOMPARE(plot->rangeCount(Dimension::Y), 2);
1605     dock.addPlotRange();
1606     QCOMPARE(plot->coordinateSystemCount(), 2);
1607 
1608     c.resetRetransformCount();
1609 
1610     dock.PlotRangeChanged(1, Dimension::Y, 1); // switch for second csystem to y range 2
1611 
1612     // No update of the scales, because the range did not change
1613     QCOMPARE(c.logsXScaleRetransformed.count(), 0);
1614     QCOMPARE(c.logsYScaleRetransformed.count(), 1);
1615     QCOMPARE(c.logsYScaleRetransformed.at(0).index, 1); // The respective coordinate system must be rescaled
1616 
1617     c.resetRetransformCount();
1618 
1619     XYCurveDock curveDock(nullptr);
1620     curveDock.setupGeneral();
1621     curveDock.setCurves({curve});
1622     QCOMPARE(curve->coordinateSystemIndex(), 0);
1623     curveDock.plotRangeChanged(1); // set curve range to other
1624     QCOMPARE(curve->coordinateSystemIndex(), 1);
1625 
1626     // CSystem1:
1627     // x 1..100
1628     // y 1..30
1629     // CSystem2:
1630     // x 1..100
1631     // y 1..100
1632 
1633     QCOMPARE(plot->range(Dimension::X, 0).start(), 1);
1634     QCOMPARE(plot->range(Dimension::X, 0).end(), 100);
1635     QCOMPARE(plot->range(Dimension::Y, 0).start(), 1);
1636     QCOMPARE(plot->range(Dimension::Y, 0).end(), 30);
1637     QCOMPARE(plot->range(Dimension::Y, 1).start(), 1);
1638     QCOMPARE(plot->range(Dimension::Y, 1).end(), 100);
1639 
1640     // the y scale of cSystem1 and the y scale of CSystem2 shall be retransformed, because
1641     // cruve2 switches from y1 to y2
1642     // xScale shall not be retransformed because it did not change
1643     QCOMPARE(c.logsXScaleRetransformed.count(), 0);
1644     QCOMPARE(c.logsYScaleRetransformed.count(), 2);
1645     QCOMPARE(c.logsYScaleRetransformed.at(0).index, 0);
1646     QCOMPARE(c.logsYScaleRetransformed.at(1).index, 1);
1647 
1648     const auto dataRect = plot->dataRect();
1649 
1650     // Check that both lines go from bottom to top
1651     {
1652         QCOMPARE(yAxis1->d_func()->lines.count(), 1);
1653         const auto line = yAxis1->d_func()->lines.at(0);
1654         QCOMPARE(line.p1().y(), dataRect.bottom());
1655         QCOMPARE(line.p2().y(), dataRect.top());
1656     }
1657 
1658     {
1659         QCOMPARE(yAxis2->d_func()->lines.count(), 1);
1660         const auto line = yAxis2->d_func()->lines.at(0);
1661         QCOMPARE(line.p1().y(), dataRect.bottom());
1662         QCOMPARE(line.p2().y(), dataRect.top());
1663     }
1664 
1665     // Now change cSystemIndex of second yAxis too
1666     c.resetRetransformCount();
1667 
1668     {
1669         AxisDock d(nullptr);
1670         d.setAxes({yAxis2});
1671         d.plotRangeChanged(1); // change from cSystem1 to cSystem2
1672     }
1673 
1674     // the y scale of cSystem1 and the y scale of CSystem2 shall be retransformed, because
1675     // yAxis2 switches from y1 to y2
1676     // xScale shall not be retransformed because it did not change
1677     QCOMPARE(c.logsXScaleRetransformed.count(), 0);
1678     QCOMPARE(c.logsYScaleRetransformed.count(), 0);
1679     //  QCOMPARE(c.logsYScaleRetransformed.at(0).index, 0); // range did not change, so retransformScale gets not called
1680     //  QCOMPARE(c.logsYScaleRetransformed.at(0).index, 1); // range did not change, so retransformScale gets not called
1681 
1682     {
1683         QCOMPARE(yAxis1->d_func()->lines.count(), 1);
1684         const auto line = yAxis1->d_func()->lines.at(0);
1685         QCOMPARE(line.p1().y(), dataRect.bottom());
1686         QCOMPARE(line.p2().y(), dataRect.top());
1687     }
1688 
1689     {
1690         QCOMPARE(yAxis2->d_func()->lines.count(), 1);
1691         const auto line = yAxis2->d_func()->lines.at(0);
1692         QCOMPARE(line.p1().y(), dataRect.bottom());
1693         QCOMPARE(line.p2().y(), dataRect.top());
1694     }
1695 }
1696 // ##############################################################################
1697 // ####### Tests checking the retransform behavior on plot shape changes  #######
1698 // ##############################################################################
1699 /*!
1700  * recalculation of plots without changing the min and max values of the data ranges.
1701  */
1702 void RetransformTest::testPlotRecalcNoRetransform() {
1703     // prepare the data
1704     Spreadsheet sheet(QStringLiteral("test"), false);
1705     sheet.setColumnCount(1);
1706     sheet.setRowCount(100);
1707     auto* column = sheet.column(0);
1708     column->setValueAt(0, 2.);
1709     column->setValueAt(1, 4.);
1710     column->setValueAt(2, 6.);
1711     QVector<const AbstractColumn*> dataColumns;
1712     dataColumns << column;
1713 
1714     // prepare the worksheet + plots
1715     RetransformCallCounter c;
1716     auto* ws = new Worksheet(QStringLiteral("worksheet"));
1717     auto* p = new CartesianPlot(QStringLiteral("plot"));
1718     p->setType(CartesianPlot::Type::TwoAxes); // Otherwise no axis are created
1719     ws->addChild(p);
1720     c.aspectAdded(p);
1721     const auto& axes = p->children<Axis>();
1722     QCOMPARE(axes.count(), 2);
1723     c.aspectAdded(axes.at(0));
1724     c.aspectAdded(axes.at(1));
1725 
1726     auto* barPlot = new BarPlot(QStringLiteral("barPlot"));
1727     barPlot->setDataColumns(dataColumns);
1728     p->addChild(barPlot);
1729     c.aspectAdded(barPlot);
1730 
1731     auto* boxPlot = new BoxPlot(QStringLiteral("boxPlot"));
1732     boxPlot->setDataColumns(dataColumns);
1733     p->addChild(boxPlot);
1734     c.aspectAdded(boxPlot);
1735 
1736     auto* histPlot = new Histogram(QStringLiteral("histPlot"));
1737     histPlot->setDataColumn(column);
1738     p->addChild(histPlot);
1739     c.aspectAdded(histPlot);
1740 
1741     // call recalc() in the created plots which is called at runtime when modifying the data
1742     // or any plot properties affecting the shape of the plot.
1743     // since the data was not changed and no properties were changed affecting plot ranges
1744     // like the orientation of a box plot changing min and max values for x and y, etc.,
1745     // there shouldn't be any retransform calls in the parent plot area
1746     c.resetRetransformCount();
1747     barPlot->recalc();
1748     {
1749         QCOMPARE(c.elementLogCount(false), 1); // only barplot
1750         const auto stat = c.statistic(false);
1751         QCOMPARE(stat.contains(barPlot->path()), true);
1752         QVERIFY(c.calledExact(1, false));
1753         QCOMPARE(c.logsXScaleRetransformed.count(), 0);
1754         QCOMPARE(c.logsYScaleRetransformed.count(), 0);
1755     }
1756 
1757     c.resetRetransformCount();
1758 
1759     boxPlot->recalc();
1760     {
1761         QCOMPARE(c.elementLogCount(false), 1); // only boxPlot
1762         const auto stat = c.statistic(false);
1763         QCOMPARE(stat.contains(boxPlot->path()), true);
1764         QVERIFY(c.calledExact(1, false));
1765         QCOMPARE(c.logsXScaleRetransformed.count(), 0);
1766         QCOMPARE(c.logsYScaleRetransformed.count(), 0);
1767     }
1768 
1769     c.resetRetransformCount();
1770 
1771     histPlot->recalc();
1772 
1773     {
1774         QCOMPARE(c.elementLogCount(false), 1); // only histPlot
1775         const auto stat = c.statistic(false);
1776         QCOMPARE(stat.contains(histPlot->path()), true);
1777         QVERIFY(c.calledExact(1, false));
1778         QCOMPARE(c.logsXScaleRetransformed.count(), 0);
1779         QCOMPARE(c.logsYScaleRetransformed.count(), 0);
1780     }
1781 }
1782 
1783 /*!
1784  * recalculation of plots with changing the min and max values of the data ranges.
1785  */
1786 void RetransformTest::testPlotRecalcRetransform() {
1787     // prepare the data
1788     Spreadsheet sheet(QStringLiteral("test"), false);
1789     sheet.setColumnCount(1);
1790     sheet.setRowCount(100);
1791     auto* column = sheet.column(0);
1792     column->setValueAt(0, 2.);
1793     column->setValueAt(1, 4.);
1794     column->setValueAt(2, 6.);
1795     QVector<const AbstractColumn*> dataColumns;
1796     dataColumns << column;
1797 
1798     RetransformCallCounter c;
1799 
1800     // prepare the worksheet + plots
1801     auto* ws = new Worksheet(QStringLiteral("worksheet"));
1802     auto* p = new CartesianPlot(QStringLiteral("plot"));
1803     p->setType(CartesianPlot::Type::TwoAxes); // Otherwise no axis are created
1804     ws->addChild(p);
1805     c.aspectAdded(p);
1806     const auto& axes = p->children<Axis>();
1807     QCOMPARE(axes.count(), 2);
1808     c.aspectAdded(axes.at(0));
1809     c.aspectAdded(axes.at(1));
1810 
1811     auto* barPlot = new BarPlot(QStringLiteral("barPlot"));
1812     barPlot->setDataColumns(dataColumns);
1813     p->addChild(barPlot);
1814     c.aspectAdded(barPlot);
1815 
1816     auto* boxPlot = new BoxPlot(QStringLiteral("boxPlot"));
1817     boxPlot->setDataColumns(dataColumns);
1818     p->addChild(boxPlot);
1819     c.aspectAdded(boxPlot);
1820 
1821     auto* histPlot = new Histogram(QStringLiteral("histPlot"));
1822     histPlot->setDataColumn(column);
1823     p->addChild(histPlot);
1824     c.aspectAdded(histPlot);
1825 
1826     c.resetRetransformCount();
1827 
1828     // modify one of the plots so its min and max values are changed.
1829     // this should trigger the recalculation of the data ranges in the parent plot area
1830     // and a retransform call for all its children
1831     QCOMPARE(histPlot->orientation(), Histogram::Orientation::Vertical);
1832     histPlot->setOrientation(Histogram::Orientation::Horizontal);
1833 
1834     {
1835         const auto stat = c.statistic(false);
1836         QCOMPARE(stat.count(), 5); // barPlot, boxPlot, histPlot, xAxis and yAxis are inside
1837         QCOMPARE(stat.contains(barPlot->path()), true);
1838         QCOMPARE(stat.contains(boxPlot->path()), true);
1839         QCOMPARE(stat.contains(histPlot->path()), true);
1840         QCOMPARE(stat.contains(axes.at(0)->path()), true);
1841         QCOMPARE(stat.contains(axes.at(1)->path()), true);
1842         QVERIFY(c.calledExact(1, false));
1843         QCOMPARE(c.logsXScaleRetransformed.count(), 1);
1844         QCOMPARE(c.logsYScaleRetransformed.count(), 0); // y range did not change, because boxplot and barplot  are still vertical
1845     }
1846 
1847     {
1848         c.resetRetransformCount();
1849         QCOMPARE(barPlot->orientation(), BarPlot::Orientation::Vertical);
1850         barPlot->setOrientation(BarPlot::Orientation::Horizontal);
1851         const auto stat = c.statistic(false);
1852         QCOMPARE(stat.count(), 5); // barPlot, boxPlot, histPlot, xAxis and yAxis are inside
1853         QCOMPARE(stat.contains(barPlot->path()), true);
1854         QCOMPARE(stat.contains(boxPlot->path()), true);
1855         QCOMPARE(stat.contains(histPlot->path()), true);
1856         QCOMPARE(stat.contains(axes.at(0)->path()), true);
1857         QCOMPARE(stat.contains(axes.at(1)->path()), true);
1858         QVERIFY(c.calledExact(1, false));
1859         QCOMPARE(c.logsXScaleRetransformed.count(), 1);
1860         QCOMPARE(c.logsYScaleRetransformed.count(), 0); // y range did not change, because boxplot  is still vertical
1861     }
1862 
1863     {
1864         c.resetRetransformCount();
1865         QCOMPARE(boxPlot->orientation(), BoxPlot::Orientation::Vertical);
1866         boxPlot->setOrientation(BoxPlot::Orientation::Horizontal);
1867 
1868         const auto stat = c.statistic(false);
1869         QCOMPARE(stat.count(), 5); // barPlot, boxPlot, histPlot, xAxis and yAxis are inside
1870         QCOMPARE(stat.contains(barPlot->path()), true);
1871         QCOMPARE(stat.contains(boxPlot->path()), true);
1872         QCOMPARE(stat.contains(histPlot->path()), true);
1873         QCOMPARE(stat.contains(axes.at(0)->path()), true);
1874         QCOMPARE(stat.contains(axes.at(1)->path()), true);
1875         QVERIFY(c.calledExact(1, false));
1876         QCOMPARE(c.logsXScaleRetransformed.count(), 1);
1877         QCOMPARE(c.logsYScaleRetransformed.count(), 1); // y range changes
1878     }
1879 }
1880 
1881 // ############################################################################################
1882 // ############################################################################################
1883 // ############################################################################################
1884 
1885 /*!
1886  * \brief RetransformCallCounter::statistic
1887  * Returns a statistic how often retransform was called for a specific element
1888  * They key is the path of the element and the value is the value how often
1889  * retransform was called
1890  * \param includeSuppressed. If true all retransforms even the suppressed once will
1891  * be counted. If false only the retransforms which are really executed are counted
1892  * \return
1893  */
1894 QHash<QString, int> RetransformCallCounter::statistic(bool includeSuppressed) {
1895     QHash<QString, int> result;
1896     for (auto& log : logsRetransformed) {
1897         const auto& path = log.aspect->path();
1898         if (!includeSuppressed && log.suppressed)
1899             continue;
1900 
1901         if (!result.contains(path))
1902             result.insert(path, 1);
1903         else
1904             result.insert(path, result.take(path) + 1);
1905     }
1906     return result;
1907 }
1908 
1909 /*!
1910  * \brief RetransformCallCounter::elementLogCount
1911  * Counts the number of different elements which got at least one retransform
1912  * \param includeSuppressed
1913  * \return
1914  */
1915 int RetransformCallCounter::elementLogCount(bool includeSuppressed) {
1916     return statistic(includeSuppressed).count();
1917 }
1918 
1919 /*!
1920  * \brief RetransformCallCounter::calledExact
1921  * Checks if all elements are retransformed \p requiredCallCount times
1922  * \param requiredCallCount
1923  * \param includeSuppressed
1924  * \return True if all elements are retransformed \p requiredCallCount times, else false
1925  */
1926 bool RetransformCallCounter::calledExact(int requiredCallCount, bool includeSuppressed) {
1927     const auto& result = statistic(includeSuppressed);
1928     QHash<QString, int>::const_iterator i;
1929     for (i = result.constBegin(); i != result.constEnd(); ++i) {
1930         if (i.value() != requiredCallCount) {
1931             qDebug() << "Expected CallCount: " << requiredCallCount << ", Current: " << i.value() << ". " << i.key();
1932             return false;
1933         }
1934     }
1935     return true;
1936 }
1937 
1938 /*!
1939  * \brief RetransformCallCounter::callCount
1940  * Returns the call count of a specific element defined by \p path
1941  * \param path The path of the element element->path()
1942  * \return
1943  */
1944 int RetransformCallCounter::callCount(const QString& path) {
1945     const auto& result = statistic(false);
1946     if (!result.contains(path))
1947         return 0;
1948 
1949     return result.value(path);
1950 }
1951 
1952 /*!
1953  * \brief RetransformCallCounter::callCount
1954  * Returns the number of retransform called for a specific object. This counter contains
1955  * all retransforms from the beginning when the object was created and not yet connected
1956  * to the RetransformCallCounter object. This is usefull when checking the retransform
1957  * counts during loading of a project or during creation of an aspect
1958  * \param aspect
1959  * \return
1960  */
1961 int RetransformCallCounter::callCount(const AbstractAspect* aspect) {
1962     return aspect->retransformCalled();
1963 }
1964 
1965 /*!
1966  * \brief RetransformCallCounter::resetRetransformCount
1967  * Reset all counters
1968  */
1969 void RetransformCallCounter::resetRetransformCount() {
1970     logsRetransformed.clear();
1971     logsXScaleRetransformed.clear();
1972     logsYScaleRetransformed.clear();
1973 }
1974 
1975 /*!
1976  * \brief RetransformCallCounter::aspectRetransformed
1977  * Slot called whenever an aspects retransform was called after RetransformCallCounter::aspectAdded()
1978  * was called on the object.
1979  * \param sender
1980  * \param suppressed
1981  */
1982 void RetransformCallCounter::aspectRetransformed(const AbstractAspect* sender, bool suppressed) {
1983     logsRetransformed.append({sender, suppressed});
1984 }
1985 
1986 /*!
1987  * \brief RetransformCallCounter::retransformScaleCalled
1988  * Slot called whenever an aspects retransformScale was called after RetransformCallCounter::aspectAdded()
1989  * was called on the object.
1990  * \param sender
1991  * \param suppressed
1992  */
1993 void RetransformCallCounter::retransformScaleCalled(const CartesianPlot* plot, const Dimension dim, int index) {
1994     switch (dim) {
1995     case Dimension::X:
1996         logsXScaleRetransformed.append({plot, index});
1997         break;
1998     case Dimension::Y:
1999         logsYScaleRetransformed.append({plot, index});
2000         break;
2001     }
2002 }
2003 
2004 /*!
2005  * \brief RetransformCallCounter::aspectAdded
2006  * Connect RetransformCallCounter to the aspects signals to count the retransform calls
2007  * \param aspect
2008  */
2009 void RetransformCallCounter::aspectAdded(const AbstractAspect* aspect) {
2010     connect(aspect, &AbstractAspect::retransformCalledSignal, this, &RetransformCallCounter::aspectRetransformed);
2011     auto* plot = dynamic_cast<const CartesianPlot*>(aspect);
2012     if (plot)
2013         connect(plot, &CartesianPlot::scaleRetransformed, this, &RetransformCallCounter::retransformScaleCalled);
2014 }
2015 
2016 // Test change data
2017 
2018 QTEST_MAIN(RetransformTest)