#ifndef SCIQLOP_DATASERIES_H #define SCIQLOP_DATASERIES_H #include "CoreGlobal.h" #include #include #include #include #include #include #include #include // We don't use the Qt macro since the log is used in the header file, which causes multiple log // definitions with inheritance. Inline method is used instead inline const QLoggingCategory &LOG_DataSeries() { static const QLoggingCategory category{"DataSeries"}; return category; } template class DataSeries; namespace dataseries_detail { template class IteratorValue : public DataSeriesIteratorValue::Impl { public: friend class DataSeries; template > explicit IteratorValue(DataSeries &dataSeries, bool begin) : m_XIt(begin ? dataSeries.xAxisData()->begin() : dataSeries.xAxisData()->end()), m_ValuesIt(begin ? dataSeries.valuesData()->begin() : dataSeries.valuesData()->end()) { } template > 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); } double minValue() const override { return m_ValuesIt->min(); } double maxValue() const override { return m_ValuesIt->max(); } QVector values() const override { return m_ValuesIt->values(); } void swap(DataSeriesIteratorValue::Impl &other) override { auto &otherImpl = dynamic_cast(other); m_XIt->impl()->swap(*otherImpl.m_XIt->impl()); m_ValuesIt->impl()->swap(*otherImpl.m_ValuesIt->impl()); } private: ArrayDataIterator m_XIt; ArrayDataIterator m_ValuesIt; }; } // namespace dataseries_detail /** * @brief The DataSeries class is the base (abstract) implementation of IDataSeries. * * It proposes to set a dimension for the values ​​data. * * A DataSeries is always sorted on its x-axis data. * * @tparam Dim The dimension of the values data * */ template class SCIQLOP_CORE_EXPORT DataSeries : public IDataSeries { friend class DataSeriesMergeHelper; public: /// Tag needed to define the push_back() method /// @sa push_back() using value_type = DataSeriesIteratorValue; /// @sa IDataSeries::xAxisData() std::shared_ptr > xAxisData() override { return m_XAxisData; } const std::shared_ptr > xAxisData() const { return m_XAxisData; } /// @sa IDataSeries::xAxisUnit() Unit xAxisUnit() const override { return m_XAxisUnit; } /// @return the values dataset std::shared_ptr > valuesData() { return m_ValuesData; } const std::shared_ptr > valuesData() const { return m_ValuesData; } /// @sa IDataSeries::valuesUnit() Unit valuesUnit() const override { return m_ValuesUnit; } SqpRange range() const override { if (!m_XAxisData->cdata().isEmpty()) { return SqpRange{m_XAxisData->cdata().first(), m_XAxisData->cdata().last()}; } return SqpRange{}; } void clear() { m_XAxisData->clear(); m_ValuesData->clear(); } bool isEmpty() const noexcept { return m_XAxisData->size() == 0; } /// Merges into the data series an other data series /// @remarks the data series to merge with is cleared after the operation void merge(IDataSeries *dataSeries) override { dataSeries->lockWrite(); lockWrite(); if (auto other = dynamic_cast *>(dataSeries)) { DataSeriesMergeHelper::merge(*other, *this); } else { qCWarning(LOG_DataSeries()) << QObject::tr("Detection of a type of IDataSeries we cannot merge with !"); } unlock(); dataSeries->unlock(); } void purge(double min, double max) override { if (min > max) { std::swap(min, max); } lockWrite(); auto it = std::remove_if( begin(), end(), [min, max](const auto &it) { return it.x() < min || it.x() > max; }); erase(it, end()); unlock(); } // ///////// // // Iterators // // ///////// // DataSeriesIterator begin() override { return DataSeriesIterator{DataSeriesIteratorValue{ std::make_unique >(*this, true)}}; } DataSeriesIterator end() override { return DataSeriesIterator{DataSeriesIteratorValue{ std::make_unique >(*this, false)}}; } DataSeriesIterator cbegin() const override { return DataSeriesIterator{DataSeriesIteratorValue{ std::make_unique >(*this, true)}}; } DataSeriesIterator cend() const override { return DataSeriesIterator{DataSeriesIteratorValue{ std::make_unique >(*this, false)}}; } void erase(DataSeriesIterator first, DataSeriesIterator last) { auto firstImpl = dynamic_cast *>(first->impl()); auto lastImpl = dynamic_cast *>(last->impl()); if (firstImpl && lastImpl) { m_XAxisData->erase(firstImpl->m_XIt, lastImpl->m_XIt); m_ValuesData->erase(firstImpl->m_ValuesIt, lastImpl->m_ValuesIt); } } /// @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::maxXAxisData() DataSeriesIterator maxXAxisData(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(); }); return it == cbegin() ? cend() : --it; } std::pair xAxisRange(double minXAxisData, double maxXAxisData) const override { if (minXAxisData > maxXAxisData) { std::swap(minXAxisData, maxXAxisData); } auto begin = cbegin(); auto end = cend(); 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 // // /////// // virtual void lockRead() { m_Lock.lockForRead(); } virtual void lockWrite() { m_Lock.lockForWrite(); } virtual void unlock() { m_Lock.unlock(); } // ///// // // Other // // ///// // /// Inserts at the end of the data series the value of the iterator passed as a parameter. This /// method is intended to be used in the context of generating a back insert iterator /// @param iteratorValue the iterator value containing the values to insert /// @sa http://en.cppreference.com/w/cpp/iterator/back_inserter /// @sa merge() /// @sa value_type void push_back(const value_type &iteratorValue) { m_XAxisData->push_back(QVector{iteratorValue.x()}); m_ValuesData->push_back(iteratorValue.values()); } protected: /// Protected ctor (DataSeries is abstract). The vectors must have the same size, otherwise a /// DataSeries with no values will be created. /// @remarks data series is automatically sorted on its x-axis data explicit DataSeries(std::shared_ptr > xAxisData, const Unit &xAxisUnit, std::shared_ptr > valuesData, const Unit &valuesUnit) : m_XAxisData{xAxisData}, m_XAxisUnit{xAxisUnit}, m_ValuesData{valuesData}, m_ValuesUnit{valuesUnit} { if (m_XAxisData->size() != m_ValuesData->size()) { clear(); } // Sorts data if it's not the case const auto &xAxisCData = m_XAxisData->cdata(); if (!std::is_sorted(xAxisCData.cbegin(), xAxisCData.cend())) { sort(); } } /// Copy ctor explicit DataSeries(const DataSeries &other) : m_XAxisData{std::make_shared >(*other.m_XAxisData)}, m_XAxisUnit{other.m_XAxisUnit}, m_ValuesData{std::make_shared >(*other.m_ValuesData)}, m_ValuesUnit{other.m_ValuesUnit} { // Since a series is ordered from its construction and is always ordered, it is not // necessary to call the sort method here ('other' is sorted) } /// Assignment operator template DataSeries &operator=(DataSeries other) { std::swap(m_XAxisData, other.m_XAxisData); std::swap(m_XAxisUnit, other.m_XAxisUnit); std::swap(m_ValuesData, other.m_ValuesData); std::swap(m_ValuesUnit, other.m_ValuesUnit); return *this; } private: /** * Sorts data series on its x-axis data */ void sort() noexcept { auto permutation = SortUtils::sortPermutation(*m_XAxisData, std::less()); m_XAxisData = m_XAxisData->sort(permutation); m_ValuesData = m_ValuesData->sort(permutation); } std::shared_ptr > m_XAxisData; Unit m_XAxisUnit; std::shared_ptr > m_ValuesData; Unit m_ValuesUnit; QReadWriteLock m_Lock; }; #endif // SCIQLOP_DATASERIES_H