DataSeries.h
333 lines
| 10.8 KiB
| text/x-c
|
CLexer
Alexandre Leroux
|
r117 | #ifndef SCIQLOP_DATASERIES_H | ||
#define SCIQLOP_DATASERIES_H | ||||
Alexandre Leroux
|
r529 | #include "CoreGlobal.h" | ||
Alexandre Leroux
|
r416 | #include <Common/SortUtils.h> | ||
Alexandre Leroux
|
r117 | #include <Data/ArrayData.h> | ||
#include <Data/IDataSeries.h> | ||||
r227 | #include <QLoggingCategory> | |||
r336 | #include <QReadLocker> | |||
#include <QReadWriteLock> | ||||
Alexandre Leroux
|
r117 | #include <memory> | ||
Alexandre Leroux
|
r529 | // 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; | ||||
} | ||||
r227 | ||||
Alexandre Leroux
|
r558 | template <int Dim> | ||
class DataSeries; | ||||
namespace dataseries_detail { | ||||
template <int Dim> | ||||
class IteratorValue : public DataSeriesIteratorValue::Impl { | ||||
public: | ||||
explicit IteratorValue(const DataSeries<Dim> &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<DataSeriesIteratorValue::Impl> clone() const override | ||||
{ | ||||
return std::make_unique<IteratorValue<Dim> >(*this); | ||||
} | ||||
bool equals(const DataSeriesIteratorValue::Impl &other) const override try { | ||||
const auto &otherImpl = dynamic_cast<const IteratorValue &>(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); } | ||||
Alexandre Leroux
|
r570 | double minValue() const override { return m_ValuesIt->min(); } | ||
double maxValue() const override { return m_ValuesIt->max(); } | ||||
Alexandre Leroux
|
r558 | |||
private: | ||||
Alexandre Leroux
|
r598 | ArrayDataIterator m_XIt; | ||
ArrayDataIterator m_ValuesIt; | ||||
Alexandre Leroux
|
r558 | }; | ||
} // namespace dataseries_detail | ||||
Alexandre Leroux
|
r117 | /** | ||
* @brief The DataSeries class is the base (abstract) implementation of IDataSeries. | ||||
* | ||||
Alexandre Leroux
|
r415 | * It proposes to set a dimension for the values data. | ||
* | ||||
* A DataSeries is always sorted on its x-axis data. | ||||
Alexandre Leroux
|
r117 | * | ||
* @tparam Dim The dimension of the values data | ||||
* | ||||
*/ | ||||
template <int Dim> | ||||
Alexandre Leroux
|
r529 | class SCIQLOP_CORE_EXPORT DataSeries : public IDataSeries { | ||
Alexandre Leroux
|
r117 | public: | ||
/// @sa IDataSeries::xAxisData() | ||||
std::shared_ptr<ArrayData<1> > xAxisData() override { return m_XAxisData; } | ||||
Alexandre Leroux
|
r287 | const std::shared_ptr<ArrayData<1> > xAxisData() const { return m_XAxisData; } | ||
Alexandre Leroux
|
r117 | |||
/// @sa IDataSeries::xAxisUnit() | ||||
Alexandre Leroux
|
r164 | Unit xAxisUnit() const override { return m_XAxisUnit; } | ||
Alexandre Leroux
|
r117 | |||
/// @return the values dataset | ||||
Alexandre Leroux
|
r287 | std::shared_ptr<ArrayData<Dim> > valuesData() { return m_ValuesData; } | ||
const std::shared_ptr<ArrayData<Dim> > valuesData() const { return m_ValuesData; } | ||||
Alexandre Leroux
|
r117 | |||
/// @sa IDataSeries::valuesUnit() | ||||
Alexandre Leroux
|
r164 | Unit valuesUnit() const override { return m_ValuesUnit; } | ||
Alexandre Leroux
|
r117 | |||
r510 | ||||
SqpRange range() const override | ||||
{ | ||||
if (!m_XAxisData->cdata().isEmpty()) { | ||||
return SqpRange{m_XAxisData->cdata().first(), m_XAxisData->cdata().last()}; | ||||
} | ||||
return SqpRange{}; | ||||
} | ||||
r336 | void clear() | |||
{ | ||||
m_XAxisData->clear(); | ||||
m_ValuesData->clear(); | ||||
} | ||||
Alexandre Leroux
|
r417 | /// Merges into the data series an other data series | ||
/// @remarks the data series to merge with is cleared after the operation | ||||
r217 | void merge(IDataSeries *dataSeries) override | |||
{ | ||||
Alexandre Leroux
|
r417 | dataSeries->lockWrite(); | ||
lockWrite(); | ||||
if (auto other = dynamic_cast<DataSeries<Dim> *>(dataSeries)) { | ||||
const auto &otherXAxisData = other->xAxisData()->cdata(); | ||||
const auto &xAxisData = m_XAxisData->cdata(); | ||||
// As data series are sorted, we can improve performances of merge, by call the sort | ||||
// method only if the two data series overlap. | ||||
if (!otherXAxisData.empty()) { | ||||
auto firstValue = otherXAxisData.front(); | ||||
auto lastValue = otherXAxisData.back(); | ||||
auto xAxisDataBegin = xAxisData.cbegin(); | ||||
auto xAxisDataEnd = xAxisData.cend(); | ||||
bool prepend; | ||||
bool sortNeeded; | ||||
if (std::lower_bound(xAxisDataBegin, xAxisDataEnd, firstValue) == xAxisDataEnd) { | ||||
// Other data series if after data series | ||||
prepend = false; | ||||
sortNeeded = false; | ||||
} | ||||
else if (std::upper_bound(xAxisDataBegin, xAxisDataEnd, lastValue) | ||||
== xAxisDataBegin) { | ||||
// Other data series if before data series | ||||
prepend = true; | ||||
sortNeeded = false; | ||||
} | ||||
else { | ||||
// The two data series overlap | ||||
prepend = false; | ||||
sortNeeded = true; | ||||
} | ||||
// Makes the merge | ||||
m_XAxisData->add(*other->xAxisData(), prepend); | ||||
m_ValuesData->add(*other->valuesData(), prepend); | ||||
if (sortNeeded) { | ||||
sort(); | ||||
} | ||||
} | ||||
// Clears the other data series | ||||
other->clear(); | ||||
r217 | } | |||
r227 | else { | |||
qCWarning(LOG_DataSeries()) | ||||
Alexandre Leroux
|
r417 | << QObject::tr("Detection of a type of IDataSeries we cannot merge with !"); | ||
r227 | } | |||
Alexandre Leroux
|
r417 | unlock(); | ||
dataSeries->unlock(); | ||||
r217 | } | |||
Alexandre Leroux
|
r524 | // ///////// // | ||
// Iterators // | ||||
// ///////// // | ||||
Alexandre Leroux
|
r558 | DataSeriesIterator cbegin() const override | ||
{ | ||||
return DataSeriesIterator{DataSeriesIteratorValue{ | ||||
std::make_unique<dataseries_detail::IteratorValue<Dim> >(*this, true)}}; | ||||
} | ||||
Alexandre Leroux
|
r524 | |||
Alexandre Leroux
|
r558 | DataSeriesIterator cend() const override | ||
{ | ||||
return DataSeriesIterator{DataSeriesIteratorValue{ | ||||
std::make_unique<dataseries_detail::IteratorValue<Dim> >(*this, false)}}; | ||||
} | ||||
Alexandre Leroux
|
r524 | |||
Alexandre Leroux
|
r565 | /// @sa IDataSeries::minXAxisData() | ||
DataSeriesIterator minXAxisData(double minXAxisData) const override | ||||
Alexandre Leroux
|
r561 | { | ||
return std::lower_bound( | ||||
cbegin(), cend(), minXAxisData, | ||||
[](const auto &itValue, const auto &value) { return itValue.x() < value; }); | ||||
} | ||||
Alexandre Leroux
|
r565 | /// @sa IDataSeries::maxXAxisData() | ||
DataSeriesIterator maxXAxisData(double maxXAxisData) const override | ||||
Alexandre Leroux
|
r562 | { | ||
// 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; | ||||
} | ||||
Alexandre Leroux
|
r566 | std::pair<DataSeriesIterator, DataSeriesIterator> xAxisRange(double minXAxisData, | ||
double maxXAxisData) const override | ||||
Alexandre Leroux
|
r525 | { | ||
Alexandre Leroux
|
r566 | if (minXAxisData > maxXAxisData) { | ||
std::swap(minXAxisData, maxXAxisData); | ||||
Alexandre Leroux
|
r525 | } | ||
auto begin = cbegin(); | ||||
auto end = cend(); | ||||
Alexandre Leroux
|
r566 | 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(); }); | ||||
Alexandre Leroux
|
r525 | |||
return std::make_pair(lowerIt, upperIt); | ||||
} | ||||
Alexandre Leroux
|
r569 | std::pair<DataSeriesIterator, DataSeriesIterator> | ||
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()); | ||||
} | ||||
Alexandre Leroux
|
r570 | // 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()); | ||||
}); | ||||
Alexandre Leroux
|
r569 | |||
Alexandre Leroux
|
r570 | // 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); | ||||
Alexandre Leroux
|
r569 | } | ||
Alexandre Leroux
|
r524 | // /////// // | ||
// Mutexes // | ||||
// /////// // | ||||
r336 | virtual void lockRead() { m_Lock.lockForRead(); } | |||
virtual void lockWrite() { m_Lock.lockForWrite(); } | ||||
virtual void unlock() { m_Lock.unlock(); } | ||||
Alexandre Leroux
|
r117 | protected: | ||
Alexandre Leroux
|
r415 | /// 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 | ||||
Alexandre Leroux
|
r175 | explicit DataSeries(std::shared_ptr<ArrayData<1> > xAxisData, const Unit &xAxisUnit, | ||
std::shared_ptr<ArrayData<Dim> > valuesData, const Unit &valuesUnit) | ||||
Alexandre Leroux
|
r117 | : m_XAxisData{xAxisData}, | ||
Alexandre Leroux
|
r175 | m_XAxisUnit{xAxisUnit}, | ||
Alexandre Leroux
|
r117 | m_ValuesData{valuesData}, | ||
Alexandre Leroux
|
r175 | m_ValuesUnit{valuesUnit} | ||
Alexandre Leroux
|
r117 | { | ||
Alexandre Leroux
|
r415 | 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(); | ||||
} | ||||
Alexandre Leroux
|
r117 | } | ||
Alexandre Leroux
|
r287 | /// Copy ctor | ||
explicit DataSeries(const DataSeries<Dim> &other) | ||||
: m_XAxisData{std::make_shared<ArrayData<1> >(*other.m_XAxisData)}, | ||||
m_XAxisUnit{other.m_XAxisUnit}, | ||||
m_ValuesData{std::make_shared<ArrayData<Dim> >(*other.m_ValuesData)}, | ||||
m_ValuesUnit{other.m_ValuesUnit} | ||||
{ | ||||
Alexandre Leroux
|
r415 | // 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) | ||||
Alexandre Leroux
|
r287 | } | ||
/// Assignment operator | ||||
template <int D> | ||||
DataSeries &operator=(DataSeries<D> 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; | ||||
} | ||||
Alexandre Leroux
|
r117 | private: | ||
Alexandre Leroux
|
r415 | /** | ||
* Sorts data series on its x-axis data | ||||
*/ | ||||
void sort() noexcept | ||||
{ | ||||
Alexandre Leroux
|
r416 | auto permutation = SortUtils::sortPermutation(*m_XAxisData, std::less<double>()); | ||
m_XAxisData = m_XAxisData->sort(permutation); | ||||
m_ValuesData = m_ValuesData->sort(permutation); | ||||
Alexandre Leroux
|
r415 | } | ||
Alexandre Leroux
|
r117 | std::shared_ptr<ArrayData<1> > m_XAxisData; | ||
Alexandre Leroux
|
r164 | Unit m_XAxisUnit; | ||
Alexandre Leroux
|
r117 | std::shared_ptr<ArrayData<Dim> > m_ValuesData; | ||
Alexandre Leroux
|
r164 | Unit m_ValuesUnit; | ||
r336 | ||||
QReadWriteLock m_Lock; | ||||
Alexandre Leroux
|
r117 | }; | ||
#endif // SCIQLOP_DATASERIES_H | ||||