diff --git a/core/include/Data/ArrayData.h b/core/include/Data/ArrayData.h index 5cd885b..f33f0a5 100644 --- a/core/include/Data/ArrayData.h +++ b/core/include/Data/ArrayData.h @@ -77,6 +77,13 @@ public: } } + void prev() + { + for (auto &it : m_Its) { + --it; + } + } + bool operator==(const IteratorValue &other) const { return m_Its == other.m_Its; } private: @@ -105,6 +112,12 @@ public: return *this; } + Iterator &operator--() + { + m_CurrentValue.prev(); + return *this; + } + pointer operator->() const { return &m_CurrentValue; } reference operator*() const { return m_CurrentValue; } diff --git a/core/include/Data/DataSeries.h b/core/include/Data/DataSeries.h index f2fcd2a..da79366 100644 --- a/core/include/Data/DataSeries.h +++ b/core/include/Data/DataSeries.h @@ -21,6 +21,57 @@ inline const QLoggingCategory &LOG_DataSeries() return category; } +template +class DataSeries; + +namespace dataseries_detail { + +template +class IteratorValue : public DataSeriesIteratorValue::Impl { +public: + explicit IteratorValue(const DataSeries &dataSeries, bool begin) + : m_XIt(begin ? dataSeries.xAxisData()->cbegin() : dataSeries.xAxisData()->cend()), + m_ValuesIt(begin ? dataSeries.valuesData()->cbegin() + : dataSeries.valuesData()->cend()) + { + } + IteratorValue(const IteratorValue &other) = default; + + std::unique_ptr clone() const override + { + return std::make_unique >(*this); + } + + bool equals(const DataSeriesIteratorValue::Impl &other) const override try { + const auto &otherImpl = dynamic_cast(other); + return std::tie(m_XIt, m_ValuesIt) == std::tie(otherImpl.m_XIt, otherImpl.m_ValuesIt); + } + catch (const std::bad_cast &) { + return false; + } + + void next() override + { + ++m_XIt; + ++m_ValuesIt; + } + + void prev() override + { + --m_XIt; + --m_ValuesIt; + } + + 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); } + +private: + ArrayData<1>::Iterator m_XIt; + typename ArrayData::Iterator m_ValuesIt; +}; +} // namespace dataseries_detail + /** * @brief The DataSeries class is the base (abstract) implementation of IDataSeries. * @@ -34,71 +85,6 @@ inline const QLoggingCategory &LOG_DataSeries() template class SCIQLOP_CORE_EXPORT DataSeries : public IDataSeries { public: - class IteratorValue { - public: - explicit IteratorValue(const DataSeries &dataSeries, bool begin) - : m_XIt(begin ? dataSeries.xAxisData()->cbegin() : dataSeries.xAxisData()->cend()), - m_ValuesIt(begin ? dataSeries.valuesData()->cbegin() - : dataSeries.valuesData()->cend()) - { - } - - double x() const { return m_XIt->at(0); } - double value() const { return m_ValuesIt->at(0); } - double value(int componentIndex) const { return m_ValuesIt->at(componentIndex); } - - void next() - { - ++m_XIt; - ++m_ValuesIt; - } - - bool operator==(const IteratorValue &other) const - { - return std::tie(m_XIt, m_ValuesIt) == std::tie(other.m_XIt, other.m_ValuesIt); - } - - private: - ArrayData<1>::Iterator m_XIt; - typename ArrayData::Iterator m_ValuesIt; - }; - - class Iterator { - public: - using iterator_category = std::forward_iterator_tag; - using value_type = const IteratorValue; - using difference_type = std::ptrdiff_t; - using pointer = value_type *; - using reference = value_type &; - - Iterator(const DataSeries &dataSeries, bool begin) : m_CurrentValue{dataSeries, begin} {} - virtual ~Iterator() noexcept = default; - Iterator(const Iterator &) = default; - Iterator(Iterator &&) = default; - Iterator &operator=(const Iterator &) = default; - Iterator &operator=(Iterator &&) = default; - - Iterator &operator++() - { - m_CurrentValue.next(); - return *this; - } - - pointer operator->() const { return &m_CurrentValue; } - - reference operator*() const { return m_CurrentValue; } - - bool operator==(const Iterator &other) const - { - return m_CurrentValue == other.m_CurrentValue; - } - - bool operator!=(const Iterator &other) const { return !(*this == other); } - - private: - IteratorValue m_CurrentValue; - }; - /// @sa IDataSeries::xAxisData() std::shared_ptr > xAxisData() override { return m_XAxisData; } const std::shared_ptr > xAxisData() const { return m_XAxisData; } @@ -193,11 +179,38 @@ public: // Iterators // // ///////// // - Iterator cbegin() const { return Iterator{*this, true}; } + DataSeriesIterator cbegin() const override + { + return DataSeriesIterator{DataSeriesIteratorValue{ + std::make_unique >(*this, true)}}; + } + + DataSeriesIterator cend() const override + { + return DataSeriesIterator{DataSeriesIteratorValue{ + std::make_unique >(*this, false)}}; + } + + /// @sa IDataSeries::minData() + DataSeriesIterator minData(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 + { + // Gets the first element that greater than max value + auto it = std::upper_bound( + cbegin(), cend(), maxXAxisData, + [](const auto &value, const auto &itValue) { return value < itValue.x(); }); - Iterator cend() const { return Iterator{*this, false}; } + return it == cbegin() ? cend() : --it; + } - std::pair subData(double min, double max) const + std::pair subData(double min, double max) const override { if (min > max) { std::swap(min, max); diff --git a/core/include/Data/DataSeriesIterator.h b/core/include/Data/DataSeriesIterator.h new file mode 100644 index 0000000..ada5796 --- /dev/null +++ b/core/include/Data/DataSeriesIterator.h @@ -0,0 +1,82 @@ +#ifndef SCIQLOP_DATASERIESITERATOR_H +#define SCIQLOP_DATASERIESITERATOR_H + +#include "CoreGlobal.h" + +#include + +/** + * @brief The DataSeriesIteratorValue class represents the current value of a data series iterator. + * It offers standard access methods for the data in the series (x-axis, values), but it is up to + * each series to define its own implementation of how to retrieve this data, by implementing the + * DataSeriesIteratorValue::Impl interface + * + * @sa DataSeriesIterator + */ +class SCIQLOP_CORE_EXPORT DataSeriesIteratorValue { +public: + struct Impl { + virtual ~Impl() noexcept = default; + virtual std::unique_ptr clone() const = 0; + virtual bool equals(const Impl &other) const = 0; + virtual void next() = 0; + virtual void prev() = 0; + virtual double x() const = 0; + virtual double value() const = 0; + virtual double value(int componentIndex) const = 0; + }; + + explicit DataSeriesIteratorValue(std::unique_ptr impl); + DataSeriesIteratorValue(const DataSeriesIteratorValue &other); + DataSeriesIteratorValue(DataSeriesIteratorValue &&other) = default; + DataSeriesIteratorValue &operator=(DataSeriesIteratorValue other); + + bool equals(const DataSeriesIteratorValue &other) const; + + /// Advances to the next value + void next(); + /// Moves back to the previous value + void prev(); + /// Gets x-axis data + double x() const; + /// Gets value data + double value() const; + /// Gets value data depending on an index + double value(int componentIndex) const; + +private: + std::unique_ptr m_Impl; +}; + +/** + * @brief The DataSeriesIterator class represents an iterator used for data series. It defines all + * operators needed for a standard forward iterator + * @sa http://www.cplusplus.com/reference/iterator/ + */ +class SCIQLOP_CORE_EXPORT DataSeriesIterator { +public: + using iterator_category = std::forward_iterator_tag; + using value_type = const DataSeriesIteratorValue; + using difference_type = std::ptrdiff_t; + using pointer = value_type *; + using reference = value_type &; + + explicit DataSeriesIterator(DataSeriesIteratorValue value); + virtual ~DataSeriesIterator() noexcept = default; + DataSeriesIterator(const DataSeriesIterator &) = default; + DataSeriesIterator(DataSeriesIterator &&) = default; + DataSeriesIterator &operator=(const DataSeriesIterator &) = default; + DataSeriesIterator &operator=(DataSeriesIterator &&) = default; + + DataSeriesIterator &operator++(); + DataSeriesIterator &operator--(); + pointer operator->() const { return &m_CurrentValue; } + reference operator*() const { return m_CurrentValue; } + bool operator==(const DataSeriesIterator &other) const; + bool operator!=(const DataSeriesIterator &other) const; + +private: + DataSeriesIteratorValue m_CurrentValue; +}; + +#endif // SCIQLOP_DATASERIESITERATOR_H diff --git a/core/include/Data/IDataSeries.h b/core/include/Data/IDataSeries.h index 7435fa3..a9059c4 100644 --- a/core/include/Data/IDataSeries.h +++ b/core/include/Data/IDataSeries.h @@ -2,6 +2,7 @@ #define SCIQLOP_IDATASERIES_H #include +#include #include #include @@ -62,6 +63,28 @@ public: virtual std::unique_ptr clone() const = 0; virtual SqpRange range() const = 0; + // ///////// // + // Iterators // + // ///////// // + + virtual DataSeriesIterator cbegin() const = 0; + virtual DataSeriesIterator cend() const = 0; + + /// @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; + + /// @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; + + // /////// // + // Mutexes // + // /////// // + virtual void lockRead() = 0; virtual void lockWrite() = 0; virtual void unlock() = 0; diff --git a/core/src/Data/DataSeriesIterator.cpp b/core/src/Data/DataSeriesIterator.cpp new file mode 100644 index 0000000..ce39a3d --- /dev/null +++ b/core/src/Data/DataSeriesIterator.cpp @@ -0,0 +1,75 @@ +#include "Data/DataSeriesIterator.h" + +DataSeriesIteratorValue::DataSeriesIteratorValue( + std::unique_ptr impl) + : m_Impl{std::move(impl)} +{ +} + +DataSeriesIteratorValue::DataSeriesIteratorValue(const DataSeriesIteratorValue &other) + : m_Impl{other.m_Impl->clone()} +{ +} + +DataSeriesIteratorValue &DataSeriesIteratorValue::operator=(DataSeriesIteratorValue other) +{ + std::swap(m_Impl, other.m_Impl); + return *this; +} + +bool DataSeriesIteratorValue::equals(const DataSeriesIteratorValue &other) const +{ + return m_Impl->equals(*other.m_Impl); +} + +void DataSeriesIteratorValue::next() +{ + m_Impl->next(); +} + +void DataSeriesIteratorValue::prev() +{ + m_Impl->prev(); +} + +double DataSeriesIteratorValue::x() const +{ + return m_Impl->x(); +} + +double DataSeriesIteratorValue::value() const +{ + return m_Impl->value(); +} + +double DataSeriesIteratorValue::value(int componentIndex) const +{ + return m_Impl->value(componentIndex); +} + +DataSeriesIterator::DataSeriesIterator(DataSeriesIteratorValue value) + : m_CurrentValue{std::move(value)} +{ +} + +DataSeriesIterator &DataSeriesIterator::operator++() +{ + m_CurrentValue.next(); + return *this; +} + +DataSeriesIterator &DataSeriesIterator::operator--() +{ + m_CurrentValue.prev(); + return *this; +} + +bool DataSeriesIterator::operator==(const DataSeriesIterator &other) const +{ + return m_CurrentValue.equals(other.m_CurrentValue); +} + +bool DataSeriesIterator::operator!=(const DataSeriesIterator &other) const +{ + return !(*this == other); +} diff --git a/core/src/Variable/VariableModel.cpp b/core/src/Variable/VariableModel.cpp index a3065ba..2bce556 100644 --- a/core/src/Variable/VariableModel.cpp +++ b/core/src/Variable/VariableModel.cpp @@ -154,18 +154,31 @@ QVariant VariableModel::data(const QModelIndex &index, int role) const if (role == Qt::DisplayRole) { if (auto variable = impl->m_Variables.at(index.row()).get()) { /// Lambda function that builds the variant to return for a time value - auto dateTimeVariant = [](double secs) { - auto dateTime = DateUtils::dateTime(secs); - return dateTime.toString(DATETIME_FORMAT); + /// @param getValueFun function used to get for a data series the iterator on the entry + /// that contains the time value to display + auto dateTimeVariant = [variable](const auto &getValueFun) { + if (auto dataSeries = variable->dataSeries()) { + auto it = getValueFun(*dataSeries); + return (it != dataSeries->cend()) + ? DateUtils::dateTime(it->x()).toString(DATETIME_FORMAT) + : QVariant{}; + } + else { + return QVariant{}; + } }; switch (index.column()) { case NAME_COLUMN: return variable->name(); case TSTART_COLUMN: - return dateTimeVariant(variable->range().m_TStart); + // 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); }); case TEND_COLUMN: - return dateTimeVariant(variable->range().m_TEnd); + // 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); }); 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 a8f6525..d6c75b8 100644 --- a/core/tests/Data/TestDataSeries.cpp +++ b/core/tests/Data/TestDataSeries.cpp @@ -24,6 +24,20 @@ private slots: void testMerge(); /// Input test data + /// @sa testMinData() + void testMinData_data(); + + /// Tests get min data of a data series + void testMinData(); + + /// Input test data + /// @sa testMaxData() + void testMaxData_data(); + + /// Tests get max data of a data series + void testMaxData(); + + /// Input test data /// @sa testSubdata() void testSubdata_data(); @@ -167,6 +181,114 @@ void TestDataSeries::testMerge() seriesValuesData.cbegin())); } +void TestDataSeries::testMinData_data() +{ + // ////////////// // + // Test structure // + // ////////////// // + + // Data series to get min data + QTest::addColumn >("dataSeries"); + + // Min data + QTest::addColumn("min"); + + // Expected results + QTest::addColumn( + "expectedOK"); // if true, expects to have a result (i.e. the iterator != end iterator) + QTest::addColumn( + "expectedMin"); // Expected value when method doesn't return end iterator + + // ////////// // + // Test cases // + // ////////// // + + QTest::newRow("minData1") << createSeries({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.}) + << 1. << true << 1.; + QTest::newRow("minData3") << createSeries({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.}) + << 5. << true << 5.; + QTest::newRow("minData5") << createSeries({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 + << std::numeric_limits::quiet_NaN(); +} + +void TestDataSeries::testMinData() +{ + QFETCH(std::shared_ptr, dataSeries); + QFETCH(double, min); + + QFETCH(bool, expectedOK); + QFETCH(double, expectedMin); + + auto it = dataSeries->minData(min); + + QCOMPARE(expectedOK, it != dataSeries->cend()); + + // If the method doesn't return a end iterator, checks with expected value + if (expectedOK) { + QCOMPARE(expectedMin, it->x()); + } +} + +void TestDataSeries::testMaxData_data() +{ + // ////////////// // + // Test structure // + // ////////////// // + + // Data series to get max data + QTest::addColumn >("dataSeries"); + + // Max data + QTest::addColumn("max"); + + // Expected results + QTest::addColumn( + "expectedOK"); // if true, expects to have a result (i.e. the iterator != end iterator) + QTest::addColumn( + "expectedMax"); // Expected value when method doesn't return end iterator + + // ////////// // + // Test cases // + // ////////// // + + QTest::newRow("maxData1") << createSeries({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.}) + << 5. << true << 5.; + QTest::newRow("maxData3") << createSeries({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.}) + << 1.1 << true << 1.; + QTest::newRow("maxData5") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) + << 1. << true << 1.; + QTest::newRow("maxData6") << createSeries({}, {}) << 1.1 << false + << std::numeric_limits::quiet_NaN(); +} + +void TestDataSeries::testMaxData() +{ + QFETCH(std::shared_ptr, dataSeries); + QFETCH(double, max); + + QFETCH(bool, expectedOK); + QFETCH(double, expectedMax); + + auto it = dataSeries->maxData(max); + + QCOMPARE(expectedOK, it != dataSeries->cend()); + + // If the method doesn't return a end iterator, checks with expected value + if (expectedOK) { + QCOMPARE(expectedMax, it->x()); + } +} + void TestDataSeries::testSubdata_data() { // ////////////// // diff --git a/core/vera-exclusions/exclusions.txt b/core/vera-exclusions/exclusions.txt index d8f594e..f1bd7ef 100644 --- a/core/vera-exclusions/exclusions.txt +++ b/core/vera-exclusions/exclusions.txt @@ -3,6 +3,8 @@ Common/spimpl\.h:\d+:.* # Ignore false positive relative to two class definitions in a same file DataSourceItem\.h:\d+:.*IPSIS_S01.* +DataSeries\.h:\d+:.*IPSIS_S01.* +DataSeriesIterator\.h:\d+:.*IPSIS_S01.* # Ignore false positive relative to a template class ArrayData\.h:\d+:.*IPSIS_S04_VARIABLE.*found: (D) @@ -11,6 +13,7 @@ ArrayData\.h:\d+:.*IPSIS_S06.*found: (D) ArrayData\.h:\d+:.*IPSIS_S06.*found: (Dim) DataSeries\.h:\d+:.*IPSIS_S04_METHOD.*found: LOG_DataSeries DataSeries\.h:\d+:.*IPSIS_S04_VARIABLE.* +DataSeries\.h:\d+:.*IPSIS_S04_NAMESPACE.*found: (dataseries_detail) # Ignore false positive relative to iterators ArrayData\.h:\d+:.*IPSIS_S04_VARIABLE.*found: (forward_iterator_tag) @@ -27,16 +30,20 @@ ArrayData\.h:\d+:.*IPSIS_S06.*found: (ptrdiff_t) ArrayData\.h:\d+:.*IPSIS_S06.*found: (pointer) ArrayData\.h:\d+:.*IPSIS_S06.*found: (reference) ArrayData\.h:\d+:.*IPSIS_S06.*found: (value_type) -DataSeries\.h:\d+:.*IPSIS_S05.* -DataSeries\.h:\d+:.*IPSIS_S06.*found: (iterator_category) -DataSeries\.h:\d+:.*IPSIS_S06.*found: (forward_iterator_tag) -DataSeries\.h:\d+:.*IPSIS_S06.*found: (value_type) -DataSeries\.h:\d+:.*IPSIS_S06.*found: (IteratorValue) -DataSeries\.h:\d+:.*IPSIS_S06.*found: (difference_type) -DataSeries\.h:\d+:.*IPSIS_S06.*found: (ptrdiff_t) -DataSeries\.h:\d+:.*IPSIS_S06.*found: (pointer) -DataSeries\.h:\d+:.*IPSIS_S06.*found: (reference) -DataSeries\.h:\d+:.*IPSIS_S06.*found: (value_type) +DataSeriesIterator\.h:\d+:.*IPSIS_S04_VARIABLE.*found: (forward_iterator_tag) +DataSeriesIterator\.h:\d+:.*IPSIS_S04_VARIABLE.*found: (DataSeriesIteratorValue) +DataSeriesIterator\.h:\d+:.*IPSIS_S04_VARIABLE.*found: (ptrdiff_t) +DataSeriesIterator\.h:\d+:.*IPSIS_S04_VARIABLE.*found: (value_type) +DataSeriesIterator\.h:\d+:.*IPSIS_S05.* +DataSeriesIterator\.h:\d+:.*IPSIS_S06.*found: (iterator_category) +DataSeriesIterator\.h:\d+:.*IPSIS_S06.*found: (forward_iterator_tag) +DataSeriesIterator\.h:\d+:.*IPSIS_S06.*found: (value_type) +DataSeriesIterator\.h:\d+:.*IPSIS_S06.*found: (DataSeriesIteratorValue) +DataSeriesIterator\.h:\d+:.*IPSIS_S06.*found: (difference_type) +DataSeriesIterator\.h:\d+:.*IPSIS_S06.*found: (ptrdiff_t) +DataSeriesIterator\.h:\d+:.*IPSIS_S06.*found: (pointer) +DataSeriesIterator\.h:\d+:.*IPSIS_S06.*found: (reference) +DataSeriesIterator\.h:\d+:.*IPSIS_S06.*found: (value_type) # Ignore false positive relative to an alias DataSourceItemAction\.h:\d+:.*IPSIS_S06.*found: (ExecuteFunction)