#ifndef SCIQLOP_DATASERIESMERGEHELPER_H #define SCIQLOP_DATASERIESMERGEHELPER_H template class DataSeries; namespace detail { /** * Scope that can be used for a merge operation * @tparam FEnd the type of function that will be executed at the end of the scope */ template struct MergeScope { explicit MergeScope(FEnd end) : m_End{end} {} virtual ~MergeScope() noexcept { m_End(); } FEnd m_End; }; /** * Creates a scope for merge operation * @tparam end the function executed at the end of the scope */ template MergeScope scope(FEnd end) { return MergeScope{end}; } /** * Enum used to position a data series relative to another during a merge operation */ enum class MergePosition { LOWER_THAN, GREATER_THAN, EQUAL, OVERLAP }; /** * Computes the position of the first data series relative to the second data series * @param lhs the first data series * @param rhs the second data series * @return the merge position computed * @remarks the data series must not be empty */ template MergePosition mergePosition(DataSeries &lhs, DataSeries &rhs) { Q_ASSERT(!lhs.isEmpty() && !rhs.isEmpty()); // Case lhs < rhs auto lhsLast = --lhs.cend(); auto rhsFirst = rhs.cbegin(); if (lhsLast->x() < rhsFirst->x()) { return MergePosition::LOWER_THAN; } // Case lhs > rhs auto lhsFirst = lhs.cbegin(); auto rhsLast = --rhs.cend(); if (lhsFirst->x() > rhsLast->x()) { return MergePosition::GREATER_THAN; } // Other cases auto equal = std::equal(lhs.cbegin(), lhs.cend(), rhs.cbegin(), rhs.cend(), [](const auto &it1, const auto &it2) { return it1.x() == it2.x() && it1.values() == it2.values(); }); return equal ? MergePosition::EQUAL : MergePosition::OVERLAP; } } // namespace detail /// Helper used to merge two DataSeries /// @sa DataSeries struct DataSeriesMergeHelper { /// Merges the source data series into the dest data series. Data of the source data series are /// consumed template static void merge(DataSeries &source, DataSeries &dest) { // Creates a scope to clear source data series at the end of the merge auto _ = detail::scope([&source]() { source.clear(); }); // Case : source data series is empty -> no merge is made if (source.isEmpty()) { return; } // Case : dest data series is empty -> we simply swap the data if (dest.isEmpty()) { std::swap(dest.m_XAxisData, source.m_XAxisData); std::swap(dest.m_ValuesData, source.m_ValuesData); return; } // Gets the position of the source in relation to the destination auto sourcePosition = detail::mergePosition(source, dest); switch (sourcePosition) { case detail::MergePosition::LOWER_THAN: case detail::MergePosition::GREATER_THAN: { auto prepend = sourcePosition == detail::MergePosition::LOWER_THAN; dest.m_XAxisData->add(*source.m_XAxisData, prepend); dest.m_ValuesData->add(*source.m_ValuesData, prepend); break; } case detail::MergePosition::EQUAL: // the data series equal each other : no merge made break; case detail::MergePosition::OVERLAP: { // the two data series overlap : merge is made auto temp = dest.clone(); if (auto tempSeries = dynamic_cast *>(temp.get())) { // Makes the merge : // - Data are sorted by x-axis values // - If two entries are in the source range and the other range, only one entry // is retained as result // - The results are stored directly in the data series dest.clear(); std::set_union( tempSeries->cbegin(), tempSeries->cend(), source.cbegin(), source.cend(), std::back_inserter(dest), [](const auto &it1, const auto &it2) { return it1.x() < it2.x(); }); } break; } default: Q_ASSERT(false); } } }; #endif // SCIQLOP_DATASERIESMERGEHELPER_H