diff --git a/core/include/Common/SortUtils.h b/core/include/Common/SortUtils.h index b79705c..f30fef1 100644 --- a/core/include/Common/SortUtils.h +++ b/core/include/Common/SortUtils.h @@ -59,6 +59,58 @@ struct SortUtils { return sortedData; } + + /** + * Compares two values that can be NaN. This method is intended to be used as a compare function + * for searching min value by excluding NaN values. + * + * Examples of use: + * - f({1, 3, 2, 4, 5}) will return 1 + * - f({NaN, 3, 2, 4, 5}) will return 2 (NaN is excluded) + * - f({NaN, NaN, 3, NaN, NaN}) will return 3 (NaN are excluded) + * - f({NaN, NaN, NaN, NaN, NaN}) will return NaN (no existing value) + * + * @param v1 first value + * @param v2 second value + * @return true if v1 < v2, false otherwise + * @sa std::min_element + */ + template + static bool minCompareWithNaN(const T &v1, const T &v2) + { + // Table used with NaN values: + // NaN < v2 -> false + // v1 < NaN -> true + // NaN < NaN -> false + // v1 < v2 -> v1 < v2 + return std::isnan(v1) ? false : std::isnan(v2) || (v1 < v2); + } + + /** + * Compares two values that can be NaN. This method is intended to be used as a compare function + * for searching max value by excluding NaN values. + * + * Examples of use: + * - f({1, 3, 2, 4, 5}) will return 5 + * - f({1, 3, 2, 4, NaN}) will return 4 (NaN is excluded) + * - f({NaN, NaN, 3, NaN, NaN}) will return 3 (NaN are excluded) + * - f({NaN, NaN, NaN, NaN, NaN}) will return NaN (no existing value) + * + * @param v1 first value + * @param v2 second value + * @return true if v1 < v2, false otherwise + * @sa std::max_element + */ + template + static bool maxCompareWithNaN(const T &v1, const T &v2) + { + // Table used with NaN values: + // NaN < v2 -> true + // v1 < NaN -> false + // NaN < NaN -> false + // v1 < v2 -> v1 < v2 + return std::isnan(v1) ? true : !std::isnan(v2) && (v1 < v2); + } }; #endif // SCIQLOP_SORTUTILS_H diff --git a/core/include/Data/ArrayData.h b/core/include/Data/ArrayData.h index f33f0a5..8097f53 100644 --- a/core/include/Data/ArrayData.h +++ b/core/include/Data/ArrayData.h @@ -70,6 +70,26 @@ public: double at(int index) const { return *m_Its.at(index); } double first() const { return *m_Its.front(); } + /// @return the min value among all components + double min() const + { + auto end = m_Its.cend(); + auto it = std::min_element(m_Its.cbegin(), end, [](const auto &it1, const auto &it2) { + return SortUtils::minCompareWithNaN(*it1, *it2); + }); + return it != end ? **it : std::numeric_limits::quiet_NaN(); + } + + /// @return the max value among all components + double max() const + { + auto end = m_Its.cend(); + auto it = std::max_element(m_Its.cbegin(), end, [](const auto &it1, const auto &it2) { + return SortUtils::maxCompareWithNaN(*it1, *it2); + }); + return it != end ? **it : std::numeric_limits::quiet_NaN(); + } + void next() { for (auto &it : m_Its) { diff --git a/core/include/Data/DataSeries.h b/core/include/Data/DataSeries.h index da79366..1606810 100644 --- a/core/include/Data/DataSeries.h +++ b/core/include/Data/DataSeries.h @@ -65,6 +65,8 @@ public: double x() const override { return m_XIt->at(0); } double value() const override { return m_ValuesIt->at(0); } double value(int componentIndex) const override { return m_ValuesIt->at(componentIndex); } + double minValue() const override { return m_ValuesIt->min(); } + double maxValue() const override { return m_ValuesIt->max(); } private: ArrayData<1>::Iterator m_XIt; @@ -191,16 +193,16 @@ public: std::make_unique >(*this, false)}}; } - /// @sa IDataSeries::minData() - DataSeriesIterator minData(double minXAxisData) const override + /// @sa IDataSeries::minXAxisData() + DataSeriesIterator minXAxisData(double minXAxisData) const override { return std::lower_bound( cbegin(), cend(), minXAxisData, [](const auto &itValue, const auto &value) { return itValue.x() < value; }); } - /// @sa IDataSeries::maxData() - DataSeriesIterator maxData(double maxXAxisData) const override + /// @sa IDataSeries::maxXAxisData() + DataSeriesIterator maxXAxisData(double maxXAxisData) const override { // Gets the first element that greater than max value auto it = std::upper_bound( @@ -210,27 +212,52 @@ public: return it == cbegin() ? cend() : --it; } - std::pair subData(double min, double max) const override + std::pair xAxisRange(double minXAxisData, + double maxXAxisData) const override { - if (min > max) { - std::swap(min, max); + if (minXAxisData > maxXAxisData) { + std::swap(minXAxisData, maxXAxisData); } auto begin = cbegin(); auto end = cend(); - auto lowerIt - = std::lower_bound(begin, end, min, [](const auto &itValue, const auto &value) { - return itValue.x() < value; - }); - auto upperIt - = std::upper_bound(begin, end, max, [](const auto &value, const auto &itValue) { - return value < itValue.x(); - }); + auto lowerIt = std::lower_bound( + begin, end, minXAxisData, + [](const auto &itValue, const auto &value) { return itValue.x() < value; }); + auto upperIt = std::upper_bound( + begin, end, maxXAxisData, + [](const auto &value, const auto &itValue) { return value < itValue.x(); }); return std::make_pair(lowerIt, upperIt); } + std::pair + valuesBounds(double minXAxisData, double maxXAxisData) const override + { + // Places iterators to the correct x-axis range + auto xAxisRangeIts = xAxisRange(minXAxisData, maxXAxisData); + + // Returns end iterators if the range is empty + if (xAxisRangeIts.first == xAxisRangeIts.second) { + return std::make_pair(cend(), cend()); + } + + // Gets the iterator on the min of all values data + auto minIt = std::min_element( + xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) { + return SortUtils::minCompareWithNaN(it1.minValue(), it2.minValue()); + }); + + // Gets the iterator on the max of all values data + auto maxIt = std::max_element( + xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) { + return SortUtils::maxCompareWithNaN(it1.maxValue(), it2.maxValue()); + }); + + return std::make_pair(minIt, maxIt); + } + // /////// // // Mutexes // // /////// // diff --git a/core/include/Data/DataSeriesIterator.h b/core/include/Data/DataSeriesIterator.h index ada5796..78c05af 100644 --- a/core/include/Data/DataSeriesIterator.h +++ b/core/include/Data/DataSeriesIterator.h @@ -24,6 +24,8 @@ public: virtual double x() const = 0; virtual double value() const = 0; virtual double value(int componentIndex) const = 0; + virtual double minValue() const = 0; + virtual double maxValue() const = 0; }; explicit DataSeriesIteratorValue(std::unique_ptr impl); @@ -43,6 +45,10 @@ public: double value() const; /// Gets value data depending on an index double value(int componentIndex) const; + /// Gets min of all values data + double minValue() const; + /// Gets max of all values data + double maxValue() const; private: std::unique_ptr m_Impl; diff --git a/core/include/Data/IDataSeries.h b/core/include/Data/IDataSeries.h index a9059c4..33fc095 100644 --- a/core/include/Data/IDataSeries.h +++ b/core/include/Data/IDataSeries.h @@ -72,14 +72,22 @@ public: /// @return the iterator to the first entry of the data series whose x-axis data is greater than /// or equal to the value passed in parameter, or the end iterator if there is no matching value - virtual DataSeriesIterator minData(double minXAxisData) const = 0; + virtual DataSeriesIterator minXAxisData(double minXAxisData) const = 0; /// @return the iterator to the last entry of the data series whose x-axis data is less than or /// equal to the value passed in parameter, or the end iterator if there is no matching value - virtual DataSeriesIterator maxData(double maxXAxisData) const = 0; - - virtual std::pair subData(double min, - double max) const = 0; + virtual DataSeriesIterator maxXAxisData(double maxXAxisData) const = 0; + + /// @return the iterators pointing to the range of data whose x-axis values are between min and + /// max passed in parameters + virtual std::pair + xAxisRange(double minXAxisData, double maxXAxisData) const = 0; + + /// @return two iterators pointing to the data that have respectively the min and the max value + /// data of a data series' range. The search is performed for a given x-axis range. + /// @sa xAxisRange() + virtual std::pair + valuesBounds(double minXAxisData, double maxXAxisData) const = 0; // /////// // // Mutexes // diff --git a/core/src/Data/DataSeriesIterator.cpp b/core/src/Data/DataSeriesIterator.cpp index ce39a3d..87a2222 100644 --- a/core/src/Data/DataSeriesIterator.cpp +++ b/core/src/Data/DataSeriesIterator.cpp @@ -47,6 +47,16 @@ double DataSeriesIteratorValue::value(int componentIndex) const return m_Impl->value(componentIndex); } +double DataSeriesIteratorValue::minValue() const +{ + return m_Impl->minValue(); +} + +double DataSeriesIteratorValue::maxValue() const +{ + return m_Impl->maxValue(); +} + DataSeriesIterator::DataSeriesIterator(DataSeriesIteratorValue value) : m_CurrentValue{std::move(value)} { diff --git a/core/src/Data/ScalarSeries.cpp b/core/src/Data/ScalarSeries.cpp index 7b5b64a..49bec2e 100644 --- a/core/src/Data/ScalarSeries.cpp +++ b/core/src/Data/ScalarSeries.cpp @@ -18,7 +18,7 @@ std::shared_ptr ScalarSeries::subDataSeries(const SqpRange &range) auto subValuesData = QVector(); this->lockRead(); { - auto bounds = subData(range.m_TStart, range.m_TEnd); + auto bounds = xAxisRange(range.m_TStart, range.m_TEnd); for (auto it = bounds.first; it != bounds.second; ++it) { subXAxisData.append(it->x()); subValuesData.append(it->value()); diff --git a/core/src/Data/VectorSeries.cpp b/core/src/Data/VectorSeries.cpp index 06432e5..a8091b2 100644 --- a/core/src/Data/VectorSeries.cpp +++ b/core/src/Data/VectorSeries.cpp @@ -24,7 +24,7 @@ std::shared_ptr VectorSeries::subDataSeries(const SqpRange &range) this->lockRead(); { - auto bounds = subData(range.m_TStart, range.m_TEnd); + auto bounds = xAxisRange(range.m_TStart, range.m_TEnd); for (auto it = bounds.first; it != bounds.second; ++it) { subXAxisData.append(it->x()); subXValuesData.append(it->value(0)); diff --git a/core/src/Variable/VariableModel.cpp b/core/src/Variable/VariableModel.cpp index 2bce556..0093876 100644 --- a/core/src/Variable/VariableModel.cpp +++ b/core/src/Variable/VariableModel.cpp @@ -174,11 +174,11 @@ QVariant VariableModel::data(const QModelIndex &index, int role) const case TSTART_COLUMN: // Shows the min value of the data series above the range tstart return dateTimeVariant([min = variable->range().m_TStart]( - const auto &dataSeries) { return dataSeries.minData(min); }); + const auto &dataSeries) { return dataSeries.minXAxisData(min); }); case TEND_COLUMN: // Shows the max value of the data series under the range tend return dateTimeVariant([max = variable->range().m_TEnd]( - const auto &dataSeries) { return dataSeries.maxData(max); }); + const auto &dataSeries) { return dataSeries.maxXAxisData(max); }); case UNIT_COLUMN: return variable->metadata().value(QStringLiteral("units")); case MISSION_COLUMN: diff --git a/core/tests/Data/TestDataSeries.cpp b/core/tests/Data/TestDataSeries.cpp index d6c75b8..aa381ca 100644 --- a/core/tests/Data/TestDataSeries.cpp +++ b/core/tests/Data/TestDataSeries.cpp @@ -1,13 +1,64 @@ #include "Data/DataSeries.h" #include "Data/ScalarSeries.h" +#include "Data/VectorSeries.h" #include #include Q_DECLARE_METATYPE(std::shared_ptr) +Q_DECLARE_METATYPE(std::shared_ptr) class TestDataSeries : public QObject { Q_OBJECT +private: + template + void testValuesBoundsStructure() + { + // ////////////// // + // Test structure // + // ////////////// // + + // Data series to get values bounds + QTest::addColumn >("dataSeries"); + + // x-axis range + QTest::addColumn("minXAxis"); + QTest::addColumn("maxXAxis"); + + // Expected results + QTest::addColumn( + "expectedOK"); // Test is expected to be ok (i.e. method doesn't return end iterators) + QTest::addColumn("expectedMinValue"); + QTest::addColumn("expectedMaxValue"); + } + + template + void testValuesBounds() + { + QFETCH(std::shared_ptr, dataSeries); + QFETCH(double, minXAxis); + QFETCH(double, maxXAxis); + + QFETCH(bool, expectedOK); + QFETCH(double, expectedMinValue); + QFETCH(double, expectedMaxValue); + + auto minMaxIts = dataSeries->valuesBounds(minXAxis, maxXAxis); + auto end = dataSeries->cend(); + + // Checks iterators with expected result + QCOMPARE(expectedOK, minMaxIts.first != end && minMaxIts.second != end); + + if (expectedOK) { + auto compare = [](const auto &v1, const auto &v2) { + return (std::isnan(v1) && std::isnan(v2)) || v1 == v2; + }; + + QVERIFY(compare(expectedMinValue, minMaxIts.first->minValue())); + QVERIFY(compare(expectedMaxValue, minMaxIts.second->maxValue())); + } + } + private slots: /// Input test data /// @sa testCtor() @@ -24,25 +75,39 @@ private slots: void testMerge(); /// Input test data - /// @sa testMinData() - void testMinData_data(); + /// @sa testMinXAxisData() + void testMinXAxisData_data(); + + /// Tests get min x-axis data of a data series + void testMinXAxisData(); + + /// Input test data + /// @sa testMaxXAxisData() + void testMaxXAxisData_data(); + + /// Tests get max x-axis data of a data series + void testMaxXAxisData(); + + /// Input test data + /// @sa testXAxisRange() + void testXAxisRange_data(); - /// Tests get min data of a data series - void testMinData(); + /// Tests get x-axis range of a data series + void testXAxisRange(); /// Input test data - /// @sa testMaxData() - void testMaxData_data(); + /// @sa testValuesBoundsScalar() + void testValuesBoundsScalar_data(); - /// Tests get max data of a data series - void testMaxData(); + /// Tests get values bounds of a scalar series + void testValuesBoundsScalar(); /// Input test data - /// @sa testSubdata() - void testSubdata_data(); + /// @sa testValuesBoundsVector() + void testValuesBoundsVector_data(); - /// Tests get subdata of two data series - void testSubdata(); + /// Tests get values bounds of a vector series + void testValuesBoundsVector(); }; void TestDataSeries::testCtor_data() @@ -108,12 +173,23 @@ void TestDataSeries::testCtor() namespace { -std::shared_ptr createSeries(QVector xAxisData, QVector valuesData) +std::shared_ptr createScalarSeries(QVector xAxisData, + QVector valuesData) { return std::make_shared(std::move(xAxisData), std::move(valuesData), Unit{}, Unit{}); } +std::shared_ptr createVectorSeries(QVector xAxisData, + QVector xValuesData, + QVector yValuesData, + QVector zValuesData) +{ + return std::make_shared(std::move(xAxisData), std::move(xValuesData), + std::move(yValuesData), std::move(zValuesData), Unit{}, + Unit{}); +} + } // namespace void TestDataSeries::testMerge_data() @@ -135,26 +211,26 @@ void TestDataSeries::testMerge_data() // ////////// // QTest::newRow("sortedMerge") - << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) - << createSeries({6., 7., 8., 9., 10.}, {600., 700., 800., 900., 1000.}) + << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) + << createScalarSeries({6., 7., 8., 9., 10.}, {600., 700., 800., 900., 1000.}) << QVector{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.} << QVector{100., 200., 300., 400., 500., 600., 700., 800., 900., 1000.}; QTest::newRow("unsortedMerge") - << createSeries({6., 7., 8., 9., 10.}, {600., 700., 800., 900., 1000.}) - << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) + << createScalarSeries({6., 7., 8., 9., 10.}, {600., 700., 800., 900., 1000.}) + << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << QVector{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.} << QVector{100., 200., 300., 400., 500., 600., 700., 800., 900., 1000.}; QTest::newRow("unsortedMerge2") - << createSeries({1., 2., 8., 9., 10}, {100., 200., 300., 400., 500.}) - << createSeries({3., 4., 5., 6., 7.}, {600., 700., 800., 900., 1000.}) + << createScalarSeries({1., 2., 8., 9., 10}, {100., 200., 300., 400., 500.}) + << createScalarSeries({3., 4., 5., 6., 7.}, {600., 700., 800., 900., 1000.}) << QVector{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.} << QVector{100., 200., 600., 700., 800., 900., 1000., 300., 400., 500.}; QTest::newRow("unsortedMerge3") - << createSeries({3., 5., 8., 7., 2}, {100., 200., 300., 400., 500.}) - << createSeries({6., 4., 9., 10., 1.}, {600., 700., 800., 900., 1000.}) + << createScalarSeries({3., 5., 8., 7., 2}, {100., 200., 300., 400., 500.}) + << createScalarSeries({6., 4., 9., 10., 1.}, {600., 700., 800., 900., 1000.}) << QVector{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.} << QVector{1000., 500., 100., 700., 200., 600., 400., 300., 800., 900.}; } @@ -181,7 +257,7 @@ void TestDataSeries::testMerge() seriesValuesData.cbegin())); } -void TestDataSeries::testMinData_data() +void TestDataSeries::testMinXAxisData_data() { // ////////////// // // Test structure // @@ -203,21 +279,26 @@ void TestDataSeries::testMinData_data() // Test cases // // ////////// // - QTest::newRow("minData1") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) + QTest::newRow("minData1") << createScalarSeries({1., 2., 3., 4., 5.}, + {100., 200., 300., 400., 500.}) << 0. << true << 1.; - QTest::newRow("minData2") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) + QTest::newRow("minData2") << createScalarSeries({1., 2., 3., 4., 5.}, + {100., 200., 300., 400., 500.}) << 1. << true << 1.; - QTest::newRow("minData3") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) + QTest::newRow("minData3") << createScalarSeries({1., 2., 3., 4., 5.}, + {100., 200., 300., 400., 500.}) << 1.1 << true << 2.; - QTest::newRow("minData4") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) + QTest::newRow("minData4") << createScalarSeries({1., 2., 3., 4., 5.}, + {100., 200., 300., 400., 500.}) << 5. << true << 5.; - QTest::newRow("minData5") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) + QTest::newRow("minData5") << createScalarSeries({1., 2., 3., 4., 5.}, + {100., 200., 300., 400., 500.}) << 5.1 << false << std::numeric_limits::quiet_NaN(); - QTest::newRow("minData6") << createSeries({}, {}) << 1.1 << false + QTest::newRow("minData6") << createScalarSeries({}, {}) << 1.1 << false << std::numeric_limits::quiet_NaN(); } -void TestDataSeries::testMinData() +void TestDataSeries::testMinXAxisData() { QFETCH(std::shared_ptr, dataSeries); QFETCH(double, min); @@ -225,7 +306,7 @@ void TestDataSeries::testMinData() QFETCH(bool, expectedOK); QFETCH(double, expectedMin); - auto it = dataSeries->minData(min); + auto it = dataSeries->minXAxisData(min); QCOMPARE(expectedOK, it != dataSeries->cend()); @@ -235,7 +316,7 @@ void TestDataSeries::testMinData() } } -void TestDataSeries::testMaxData_data() +void TestDataSeries::testMaxXAxisData_data() { // ////////////// // // Test structure // @@ -257,21 +338,26 @@ void TestDataSeries::testMaxData_data() // Test cases // // ////////// // - QTest::newRow("maxData1") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) + QTest::newRow("maxData1") << createScalarSeries({1., 2., 3., 4., 5.}, + {100., 200., 300., 400., 500.}) << 6. << true << 5.; - QTest::newRow("maxData2") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) + QTest::newRow("maxData2") << createScalarSeries({1., 2., 3., 4., 5.}, + {100., 200., 300., 400., 500.}) << 5. << true << 5.; - QTest::newRow("maxData3") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) + QTest::newRow("maxData3") << createScalarSeries({1., 2., 3., 4., 5.}, + {100., 200., 300., 400., 500.}) << 4.9 << true << 4.; - QTest::newRow("maxData4") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) + QTest::newRow("maxData4") << createScalarSeries({1., 2., 3., 4., 5.}, + {100., 200., 300., 400., 500.}) << 1.1 << true << 1.; - QTest::newRow("maxData5") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) + QTest::newRow("maxData5") << createScalarSeries({1., 2., 3., 4., 5.}, + {100., 200., 300., 400., 500.}) << 1. << true << 1.; - QTest::newRow("maxData6") << createSeries({}, {}) << 1.1 << false + QTest::newRow("maxData6") << createScalarSeries({}, {}) << 1.1 << false << std::numeric_limits::quiet_NaN(); } -void TestDataSeries::testMaxData() +void TestDataSeries::testMaxXAxisData() { QFETCH(std::shared_ptr, dataSeries); QFETCH(double, max); @@ -279,7 +365,7 @@ void TestDataSeries::testMaxData() QFETCH(bool, expectedOK); QFETCH(double, expectedMax); - auto it = dataSeries->maxData(max); + auto it = dataSeries->maxXAxisData(max); QCOMPARE(expectedOK, it != dataSeries->cend()); @@ -289,20 +375,20 @@ void TestDataSeries::testMaxData() } } -void TestDataSeries::testSubdata_data() +void TestDataSeries::testXAxisRange_data() { // ////////////// // // Test structure // // ////////////// // - // Data series to get subdata + // Data series to get x-axis range QTest::addColumn >("dataSeries"); // Min/max values QTest::addColumn("min"); QTest::addColumn("max"); - // Expected values after subdata + // Expected values QTest::addColumn >("expectedXAxisData"); QTest::addColumn >("expectedValuesData"); @@ -310,29 +396,37 @@ void TestDataSeries::testSubdata_data() // Test cases // // ////////// // - QTest::newRow("subData1") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) - << -1. << 3.2 << QVector{1., 2., 3.} - << QVector{100., 200., 300.}; - QTest::newRow("subData2") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) - << 1. << 4. << QVector{1., 2., 3., 4.} - << QVector{100., 200., 300., 400.}; - QTest::newRow("subData3") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) - << 1. << 3.9 << QVector{1., 2., 3.} - << QVector{100., 200., 300.}; - QTest::newRow("subData4") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) - << 0. << 0.9 << QVector{} << QVector{}; - QTest::newRow("subData5") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) - << 0. << 1. << QVector{1.} << QVector{100.}; - QTest::newRow("subData6") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) - << 2.1 << 6. << QVector{3., 4., 5.} - << QVector{300., 400., 500.}; - QTest::newRow("subData7") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) - << 6. << 9. << QVector{} << QVector{}; - QTest::newRow("subData8") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) - << 5. << 9. << QVector{5.} << QVector{500.}; + QTest::newRow("xAxisRange1") << createScalarSeries({1., 2., 3., 4., 5.}, + {100., 200., 300., 400., 500.}) + << -1. << 3.2 << QVector{1., 2., 3.} + << QVector{100., 200., 300.}; + QTest::newRow("xAxisRange2") << createScalarSeries({1., 2., 3., 4., 5.}, + {100., 200., 300., 400., 500.}) + << 1. << 4. << QVector{1., 2., 3., 4.} + << QVector{100., 200., 300., 400.}; + QTest::newRow("xAxisRange3") << createScalarSeries({1., 2., 3., 4., 5.}, + {100., 200., 300., 400., 500.}) + << 1. << 3.9 << QVector{1., 2., 3.} + << QVector{100., 200., 300.}; + QTest::newRow("xAxisRange4") << createScalarSeries({1., 2., 3., 4., 5.}, + {100., 200., 300., 400., 500.}) + << 0. << 0.9 << QVector{} << QVector{}; + QTest::newRow("xAxisRange5") << createScalarSeries({1., 2., 3., 4., 5.}, + {100., 200., 300., 400., 500.}) + << 0. << 1. << QVector{1.} << QVector{100.}; + QTest::newRow("xAxisRange6") << createScalarSeries({1., 2., 3., 4., 5.}, + {100., 200., 300., 400., 500.}) + << 2.1 << 6. << QVector{3., 4., 5.} + << QVector{300., 400., 500.}; + QTest::newRow("xAxisRange7") << createScalarSeries({1., 2., 3., 4., 5.}, + {100., 200., 300., 400., 500.}) + << 6. << 9. << QVector{} << QVector{}; + QTest::newRow("xAxisRange8") << createScalarSeries({1., 2., 3., 4., 5.}, + {100., 200., 300., 400., 500.}) + << 5. << 9. << QVector{5.} << QVector{500.}; } -void TestDataSeries::testSubdata() +void TestDataSeries::testXAxisRange() { QFETCH(std::shared_ptr, dataSeries); QFETCH(double, min); @@ -341,7 +435,7 @@ void TestDataSeries::testSubdata() QFETCH(QVector, expectedXAxisData); QFETCH(QVector, expectedValuesData); - auto bounds = dataSeries->subData(min, max); + auto bounds = dataSeries->xAxisRange(min, max); QVERIFY(std::equal(bounds.first, bounds.second, expectedXAxisData.cbegin(), expectedXAxisData.cend(), [](const auto &it, const auto &expectedX) { return it.x() == expectedX; })); @@ -350,5 +444,77 @@ void TestDataSeries::testSubdata() [](const auto &it, const auto &expectedVal) { return it.value() == expectedVal; })); } +void TestDataSeries::testValuesBoundsScalar_data() +{ + testValuesBoundsStructure(); + + // ////////// // + // Test cases // + // ////////// // + auto nan = std::numeric_limits::quiet_NaN(); + + QTest::newRow("scalarBounds1") + << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 0. << 6. + << true << 100. << 500.; + QTest::newRow("scalarBounds2") + << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 2. << 4. + << true << 200. << 400.; + QTest::newRow("scalarBounds3") + << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 0. << 0.5 + << false << nan << nan; + QTest::newRow("scalarBounds4") + << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 5.1 << 6. + << false << nan << nan; + QTest::newRow("scalarBounds5") + << createScalarSeries({1.}, {100.}) << 0. << 2. << true << 100. << 100.; + QTest::newRow("scalarBounds6") << createScalarSeries({}, {}) << 0. << 2. << false << nan << nan; + + // Tests with NaN values: NaN values are not included in min/max search + QTest::newRow("scalarBounds7") + << createScalarSeries({1., 2., 3., 4., 5.}, {nan, 200., 300., 400., nan}) << 0. << 6. + << true << 200. << 400.; + QTest::newRow("scalarBounds8") + << createScalarSeries({1., 2., 3., 4., 5.}, {nan, nan, nan, nan, nan}) << 0. << 6. << true + << std::numeric_limits::quiet_NaN() << std::numeric_limits::quiet_NaN(); +} + +void TestDataSeries::testValuesBoundsScalar() +{ + testValuesBounds(); +} + +void TestDataSeries::testValuesBoundsVector_data() +{ + testValuesBoundsStructure(); + + // ////////// // + // Test cases // + // ////////// // + auto nan = std::numeric_limits::quiet_NaN(); + + QTest::newRow("vectorBounds1") + << createVectorSeries({1., 2., 3., 4., 5.}, {10., 15., 20., 13., 12.}, + {35., 24., 10., 9., 0.3}, {13., 14., 12., 9., 24.}) + << 0. << 6. << true << 0.3 << 35.; // min/max in same component + QTest::newRow("vectorBounds2") + << createVectorSeries({1., 2., 3., 4., 5.}, {2.3, 15., 20., 13., 12.}, + {35., 24., 10., 9., 4.}, {13., 14., 12., 9., 24.}) + << 0. << 6. << true << 2.3 << 35.; // min/max in same entry + QTest::newRow("vectorBounds3") + << createVectorSeries({1., 2., 3., 4., 5.}, {2.3, 15., 20., 13., 12.}, + {35., 24., 10., 9., 4.}, {13., 14., 12., 9., 24.}) + << 2. << 3. << true << 10. << 24.; + + // Tests with NaN values: NaN values are not included in min/max search + QTest::newRow("vectorBounds4") + << createVectorSeries({1., 2.}, {nan, nan}, {nan, nan}, {nan, nan}) << 0. << 6. << true + << nan << nan; +} + +void TestDataSeries::testValuesBoundsVector() +{ + testValuesBounds(); +} + QTEST_MAIN(TestDataSeries) #include "TestDataSeries.moc" diff --git a/gui/src/Visualization/VisualizationGraphHelper.cpp b/gui/src/Visualization/VisualizationGraphHelper.cpp index bd80ba7..64edc65 100644 --- a/gui/src/Visualization/VisualizationGraphHelper.cpp +++ b/gui/src/Visualization/VisualizationGraphHelper.cpp @@ -149,7 +149,7 @@ struct PlottablesUpdateraddGraph(graphWidget); graphWidget->addVariable(variable, range); - // TODO: get y using variable range - graphWidget->setYRange(SqpRange{-10, 10}); + + // get y using variable range + if (auto dataSeries = variable->dataSeries()) { + auto valuesBounds = dataSeries->valuesBounds(range.m_TStart, range.m_TEnd); + auto end = dataSeries->cend(); + if (valuesBounds.first != end && valuesBounds.second != end) { + auto rangeValue = [](const auto &value) { return std::isnan(value) ? 0. : value; }; + + auto minValue = rangeValue(valuesBounds.first->minValue()); + auto maxValue = rangeValue(valuesBounds.second->maxValue()); + + graphWidget->setYRange(SqpRange{minValue, maxValue}); + } + } return graphWidget; } diff --git a/plugins/amda/tests/TestAmdaAcquisition.cpp b/plugins/amda/tests/TestAmdaAcquisition.cpp index e9b9f1a..5df8fc8 100644 --- a/plugins/amda/tests/TestAmdaAcquisition.cpp +++ b/plugins/amda/tests/TestAmdaAcquisition.cpp @@ -45,7 +45,7 @@ bool compareDataSeries(std::shared_ptr candidate, SqpRange candidat if (candidateDS && referenceDS) { auto itRefs - = referenceDS->subData(candidateCacheRange.m_TStart, candidateCacheRange.m_TEnd); + = referenceDS->xAxisRange(candidateCacheRange.m_TStart, candidateCacheRange.m_TEnd); qDebug() << " DISTANCE" << std::distance(candidateDS->cbegin(), candidateDS->cend()) << std::distance(itRefs.first, itRefs.second);