DataSeries.h
482 lines
| 16.6 KiB
| text/x-c
|
CLexer
Alexandre Leroux
|
r125 | #ifndef SCIQLOP_DATASERIES_H | ||
#define SCIQLOP_DATASERIES_H | ||||
Alexandre Leroux
|
r562 | #include "CoreGlobal.h" | ||
Alexandre Leroux
|
r450 | #include <Common/SortUtils.h> | ||
Alexandre Leroux
|
r125 | #include <Data/ArrayData.h> | ||
Alexandre Leroux
|
r668 | #include <Data/DataSeriesMergeHelper.h> | ||
Alexandre Leroux
|
r125 | #include <Data/IDataSeries.h> | ||
Alexandre Leroux
|
r861 | #include <Data/OptionalAxis.h> | ||
Alexandre Leroux
|
r125 | |||
r243 | #include <QLoggingCategory> | |||
r364 | #include <QReadLocker> | |||
#include <QReadWriteLock> | ||||
Alexandre Leroux
|
r125 | #include <memory> | ||
Alexandre Leroux
|
r562 | // 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; | ||||
} | ||||
r243 | ||||
Alexandre Leroux
|
r596 | template <int Dim> | ||
class DataSeries; | ||||
namespace dataseries_detail { | ||||
Alexandre Leroux
|
r673 | template <int Dim, bool IsConst> | ||
Alexandre Leroux
|
r596 | class IteratorValue : public DataSeriesIteratorValue::Impl { | ||
public: | ||||
Alexandre Leroux
|
r673 | friend class DataSeries<Dim>; | ||
template <bool IC = IsConst, typename = std::enable_if_t<IC == false> > | ||||
explicit IteratorValue(DataSeries<Dim> &dataSeries, bool begin) | ||||
: m_XIt(begin ? dataSeries.xAxisData()->begin() : dataSeries.xAxisData()->end()), | ||||
m_ValuesIt(begin ? dataSeries.valuesData()->begin() : dataSeries.valuesData()->end()) | ||||
{ | ||||
} | ||||
template <bool IC = IsConst, typename = std::enable_if_t<IC == true> > | ||||
Alexandre Leroux
|
r596 | 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()) | ||||
{ | ||||
} | ||||
Alexandre Leroux
|
r673 | |||
Alexandre Leroux
|
r596 | IteratorValue(const IteratorValue &other) = default; | ||
std::unique_ptr<DataSeriesIteratorValue::Impl> clone() const override | ||||
{ | ||||
Alexandre Leroux
|
r673 | return std::make_unique<IteratorValue<Dim, IsConst> >(*this); | ||
Alexandre Leroux
|
r596 | } | ||
Alexandre Leroux
|
r689 | int distance(const DataSeriesIteratorValue::Impl &other) const override try { | ||
const auto &otherImpl = dynamic_cast<const IteratorValue &>(other); | ||||
return m_XIt->distance(*otherImpl.m_XIt); | ||||
} | ||||
catch (const std::bad_cast &) { | ||||
return 0; | ||||
} | ||||
Alexandre Leroux
|
r596 | 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; | ||||
} | ||||
Alexandre Leroux
|
r689 | bool lowerThan(const DataSeriesIteratorValue::Impl &other) const override try { | ||
const auto &otherImpl = dynamic_cast<const IteratorValue &>(other); | ||||
return m_XIt->lowerThan(*otherImpl.m_XIt); | ||||
} | ||||
catch (const std::bad_cast &) { | ||||
return false; | ||||
} | ||||
std::unique_ptr<DataSeriesIteratorValue::Impl> advance(int offset) const override | ||||
{ | ||||
auto result = clone(); | ||||
Alexandre Leroux
|
r697 | result->next(offset); | ||
Alexandre Leroux
|
r689 | return result; | ||
} | ||||
Alexandre Leroux
|
r697 | void next(int offset) override | ||
Alexandre Leroux
|
r596 | { | ||
Alexandre Leroux
|
r697 | m_XIt->next(offset); | ||
m_ValuesIt->next(offset); | ||||
Alexandre Leroux
|
r596 | } | ||
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
|
r609 | double minValue() const override { return m_ValuesIt->min(); } | ||
double maxValue() const override { return m_ValuesIt->max(); } | ||||
Alexandre Leroux
|
r667 | QVector<double> values() const override { return m_ValuesIt->values(); } | ||
Alexandre Leroux
|
r596 | |||
Alexandre Leroux
|
r674 | void swap(DataSeriesIteratorValue::Impl &other) override | ||
{ | ||||
auto &otherImpl = dynamic_cast<IteratorValue &>(other); | ||||
m_XIt->impl()->swap(*otherImpl.m_XIt->impl()); | ||||
m_ValuesIt->impl()->swap(*otherImpl.m_ValuesIt->impl()); | ||||
} | ||||
Alexandre Leroux
|
r596 | private: | ||
Alexandre Leroux
|
r641 | ArrayDataIterator m_XIt; | ||
ArrayDataIterator m_ValuesIt; | ||||
Alexandre Leroux
|
r596 | }; | ||
} // namespace dataseries_detail | ||||
Alexandre Leroux
|
r125 | /** | ||
* @brief The DataSeries class is the base (abstract) implementation of IDataSeries. | ||||
* | ||||
Alexandre Leroux
|
r862 | * The DataSeries represents values on one or two axes, according to these rules: | ||
* - the x-axis is always defined | ||||
* - an y-axis can be defined or not. If set, additional consistency checks apply to the values (see | ||||
* below) | ||||
* - the values are defined on one or two dimensions. In the case of 2-dim values, the data is | ||||
* distributed into components (for example, a vector defines three components) | ||||
* - New values can be added to the series, on the x-axis. | ||||
* - Once initialized to the series creation, the y-axis (if defined) is no longer modifiable | ||||
* - Data representing values and axes are associated with a unit | ||||
* - The data series is always sorted in ascending order on the x-axis. | ||||
Alexandre Leroux
|
r449 | * | ||
Alexandre Leroux
|
r862 | * Consistency checks are carried out between the axes and the values. These controls are provided | ||
* throughout the DataSeries lifecycle: | ||||
* - the number of data on the x-axis must be equal to the number of values (in the case of | ||||
* 2-dim ArrayData for values, the test is performed on the number of values per component) | ||||
* - if the y-axis is defined, the number of components of the ArrayData for values must equal the | ||||
* number of data on the y-axis. | ||||
* | ||||
* Examples: | ||||
* 1) | ||||
* - x-axis: [1 ; 2 ; 3] | ||||
* - y-axis: not defined | ||||
* - values: [10 ; 20 ; 30] (1-dim ArrayData) | ||||
* => the DataSeries is valid, as x-axis and values have the same number of data | ||||
* | ||||
* 2) | ||||
* - x-axis: [1 ; 2 ; 3] | ||||
* - y-axis: not defined | ||||
* - values: [10 ; 20 ; 30 ; 40] (1-dim ArrayData) | ||||
* => the DataSeries is invalid, as x-axis and values haven't the same number of data | ||||
* | ||||
* 3) | ||||
* - x-axis: [1 ; 2 ; 3] | ||||
* - y-axis: not defined | ||||
* - values: [10 ; 20 ; 30 | ||||
* 40 ; 50 ; 60] (2-dim ArrayData) | ||||
* => the DataSeries is valid, as x-axis has 3 data and values contains 2 components with 3 | ||||
* data each | ||||
* | ||||
* 4) | ||||
* - x-axis: [1 ; 2 ; 3] | ||||
* - y-axis: [1 ; 2] | ||||
* - values: [10 ; 20 ; 30 | ||||
* 40 ; 50 ; 60] (2-dim ArrayData) | ||||
* => the DataSeries is valid, as: | ||||
* - x-axis has 3 data and values contains 2 components with 3 data each AND | ||||
* - y-axis has 2 data and values contains 2 components | ||||
* | ||||
* 5) | ||||
* - x-axis: [1 ; 2 ; 3] | ||||
* - y-axis: [1 ; 2 ; 3] | ||||
* - values: [10 ; 20 ; 30 | ||||
* 40 ; 50 ; 60] (2-dim ArrayData) | ||||
* => the DataSeries is invalid, as: | ||||
* - x-axis has 3 data and values contains 2 components with 3 data each BUT | ||||
* - y-axis has 3 data and values contains only 2 components | ||||
Alexandre Leroux
|
r125 | * | ||
* @tparam Dim The dimension of the values data | ||||
* | ||||
*/ | ||||
template <int Dim> | ||||
Alexandre Leroux
|
r562 | class SCIQLOP_CORE_EXPORT DataSeries : public IDataSeries { | ||
Alexandre Leroux
|
r668 | friend class DataSeriesMergeHelper; | ||
Alexandre Leroux
|
r125 | public: | ||
/// @sa IDataSeries::xAxisData() | ||||
std::shared_ptr<ArrayData<1> > xAxisData() override { return m_XAxisData; } | ||||
Alexandre Leroux
|
r310 | const std::shared_ptr<ArrayData<1> > xAxisData() const { return m_XAxisData; } | ||
Alexandre Leroux
|
r125 | |||
/// @sa IDataSeries::xAxisUnit() | ||||
Alexandre Leroux
|
r177 | Unit xAxisUnit() const override { return m_XAxisUnit; } | ||
Alexandre Leroux
|
r125 | |||
/// @return the values dataset | ||||
Alexandre Leroux
|
r310 | std::shared_ptr<ArrayData<Dim> > valuesData() { return m_ValuesData; } | ||
const std::shared_ptr<ArrayData<Dim> > valuesData() const { return m_ValuesData; } | ||||
Alexandre Leroux
|
r125 | |||
/// @sa IDataSeries::valuesUnit() | ||||
Alexandre Leroux
|
r177 | Unit valuesUnit() const override { return m_ValuesUnit; } | ||
Alexandre Leroux
|
r125 | |||
Alexandre Leroux
|
r858 | int nbPoints() const override { return m_ValuesData->totalSize(); } | ||
r539 | ||||
r364 | void clear() | |||
{ | ||||
m_XAxisData->clear(); | ||||
m_ValuesData->clear(); | ||||
} | ||||
Alexandre Leroux
|
r668 | bool isEmpty() const noexcept { return m_XAxisData->size() == 0; } | ||
Alexandre Leroux
|
r861 | /// Merges into the data series an other data series. | ||
/// | ||||
/// The two dataseries: | ||||
/// - must be of the same dimension | ||||
/// - must have the same y-axis (if defined) | ||||
/// | ||||
/// If the prerequisites are not valid, the method does nothing | ||||
/// | ||||
Alexandre Leroux
|
r451 | /// @remarks the data series to merge with is cleared after the operation | ||
r233 | void merge(IDataSeries *dataSeries) override | |||
{ | ||||
Alexandre Leroux
|
r451 | dataSeries->lockWrite(); | ||
lockWrite(); | ||||
if (auto other = dynamic_cast<DataSeries<Dim> *>(dataSeries)) { | ||||
Alexandre Leroux
|
r861 | if (m_YAxis == other->m_YAxis) { | ||
DataSeriesMergeHelper::merge(*other, *this); | ||||
} | ||||
else { | ||||
qCWarning(LOG_DataSeries()) | ||||
<< QObject::tr("Can't merge data series that have not the same y-axis"); | ||||
} | ||||
r233 | } | |||
r243 | else { | |||
qCWarning(LOG_DataSeries()) | ||||
Alexandre Leroux
|
r451 | << QObject::tr("Detection of a type of IDataSeries we cannot merge with !"); | ||
r243 | } | |||
Alexandre Leroux
|
r451 | unlock(); | ||
dataSeries->unlock(); | ||||
r233 | } | |||
Alexandre Leroux
|
r676 | void purge(double min, double max) override | ||
{ | ||||
Alexandre Leroux
|
r690 | // Nothing to purge if series is empty | ||
if (isEmpty()) { | ||||
return; | ||||
} | ||||
Alexandre Leroux
|
r676 | if (min > max) { | ||
std::swap(min, max); | ||||
} | ||||
Alexandre Leroux
|
r690 | // Nothing to purge if series min/max are inside purge range | ||
auto xMin = cbegin()->x(); | ||||
auto xMax = (--cend())->x(); | ||||
if (xMin >= min && xMax <= max) { | ||||
return; | ||||
} | ||||
Alexandre Leroux
|
r676 | |||
Alexandre Leroux
|
r690 | auto lowerIt = std::lower_bound( | ||
begin(), end(), min, [](const auto &it, const auto &val) { return it.x() < val; }); | ||||
erase(begin(), lowerIt); | ||||
auto upperIt = std::upper_bound( | ||||
begin(), end(), max, [](const auto &val, const auto &it) { return val < it.x(); }); | ||||
erase(upperIt, end()); | ||||
Alexandre Leroux
|
r676 | } | ||
Alexandre Leroux
|
r556 | // ///////// // | ||
// Iterators // | ||||
// ///////// // | ||||
Alexandre Leroux
|
r673 | DataSeriesIterator begin() override | ||
{ | ||||
return DataSeriesIterator{DataSeriesIteratorValue{ | ||||
std::make_unique<dataseries_detail::IteratorValue<Dim, false> >(*this, true)}}; | ||||
} | ||||
DataSeriesIterator end() override | ||||
{ | ||||
return DataSeriesIterator{DataSeriesIteratorValue{ | ||||
std::make_unique<dataseries_detail::IteratorValue<Dim, false> >(*this, false)}}; | ||||
} | ||||
Alexandre Leroux
|
r596 | DataSeriesIterator cbegin() const override | ||
{ | ||||
return DataSeriesIterator{DataSeriesIteratorValue{ | ||||
Alexandre Leroux
|
r673 | std::make_unique<dataseries_detail::IteratorValue<Dim, true> >(*this, true)}}; | ||
Alexandre Leroux
|
r596 | } | ||
Alexandre Leroux
|
r556 | |||
Alexandre Leroux
|
r596 | DataSeriesIterator cend() const override | ||
{ | ||||
return DataSeriesIterator{DataSeriesIteratorValue{ | ||||
Alexandre Leroux
|
r673 | std::make_unique<dataseries_detail::IteratorValue<Dim, true> >(*this, false)}}; | ||
Alexandre Leroux
|
r596 | } | ||
Alexandre Leroux
|
r556 | |||
Alexandre Leroux
|
r675 | void erase(DataSeriesIterator first, DataSeriesIterator last) | ||
{ | ||||
auto firstImpl | ||||
= dynamic_cast<dataseries_detail::IteratorValue<Dim, false> *>(first->impl()); | ||||
auto lastImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, false> *>(last->impl()); | ||||
if (firstImpl && lastImpl) { | ||||
m_XAxisData->erase(firstImpl->m_XIt, lastImpl->m_XIt); | ||||
m_ValuesData->erase(firstImpl->m_ValuesIt, lastImpl->m_ValuesIt); | ||||
} | ||||
} | ||||
Alexandre Leroux
|
r696 | void insert(DataSeriesIterator first, DataSeriesIterator last, bool prepend = false) | ||
{ | ||||
auto firstImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, true> *>(first->impl()); | ||||
auto lastImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, true> *>(last->impl()); | ||||
if (firstImpl && lastImpl) { | ||||
m_XAxisData->insert(firstImpl->m_XIt, lastImpl->m_XIt, prepend); | ||||
m_ValuesData->insert(firstImpl->m_ValuesIt, lastImpl->m_ValuesIt, prepend); | ||||
} | ||||
} | ||||
Alexandre Leroux
|
r604 | /// @sa IDataSeries::minXAxisData() | ||
DataSeriesIterator minXAxisData(double minXAxisData) const override | ||||
Alexandre Leroux
|
r599 | { | ||
return std::lower_bound( | ||||
cbegin(), cend(), minXAxisData, | ||||
[](const auto &itValue, const auto &value) { return itValue.x() < value; }); | ||||
} | ||||
Alexandre Leroux
|
r604 | /// @sa IDataSeries::maxXAxisData() | ||
DataSeriesIterator maxXAxisData(double maxXAxisData) const override | ||||
Alexandre Leroux
|
r600 | { | ||
// 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
|
r605 | std::pair<DataSeriesIterator, DataSeriesIterator> xAxisRange(double minXAxisData, | ||
double maxXAxisData) const override | ||||
Alexandre Leroux
|
r557 | { | ||
Alexandre Leroux
|
r605 | if (minXAxisData > maxXAxisData) { | ||
std::swap(minXAxisData, maxXAxisData); | ||||
Alexandre Leroux
|
r557 | } | ||
auto begin = cbegin(); | ||||
auto end = cend(); | ||||
Alexandre Leroux
|
r605 | auto lowerIt = std::lower_bound( | ||
begin, end, minXAxisData, | ||||
[](const auto &itValue, const auto &value) { return itValue.x() < value; }); | ||||
auto upperIt = std::upper_bound( | ||||
Alexandre Leroux
|
r696 | lowerIt, end, maxXAxisData, | ||
Alexandre Leroux
|
r605 | [](const auto &value, const auto &itValue) { return value < itValue.x(); }); | ||
Alexandre Leroux
|
r557 | |||
return std::make_pair(lowerIt, upperIt); | ||||
} | ||||
Alexandre Leroux
|
r608 | 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
|
r609 | // 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
|
r608 | |||
Alexandre Leroux
|
r609 | // 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
|
r608 | } | ||
Alexandre Leroux
|
r556 | // /////// // | ||
// Mutexes // | ||||
// /////// // | ||||
r364 | virtual void lockRead() { m_Lock.lockForRead(); } | |||
virtual void lockWrite() { m_Lock.lockForWrite(); } | ||||
virtual void unlock() { m_Lock.unlock(); } | ||||
Alexandre Leroux
|
r125 | protected: | ||
Alexandre Leroux
|
r861 | /// Protected ctor (DataSeries is abstract). | ||
/// | ||||
/// Data vectors must be consistent with each other, otherwise an exception will be thrown (@sa | ||||
/// class description for consistent rules) | ||||
Alexandre Leroux
|
r449 | /// @remarks data series is automatically sorted on its x-axis data | ||
Alexandre Leroux
|
r861 | /// @throws std::invalid_argument if the data are inconsistent with each other | ||
Alexandre Leroux
|
r188 | explicit DataSeries(std::shared_ptr<ArrayData<1> > xAxisData, const Unit &xAxisUnit, | ||
Alexandre Leroux
|
r861 | std::shared_ptr<ArrayData<Dim> > valuesData, const Unit &valuesUnit, | ||
OptionalAxis yAxis = OptionalAxis{}) | ||||
Alexandre Leroux
|
r125 | : m_XAxisData{xAxisData}, | ||
Alexandre Leroux
|
r188 | m_XAxisUnit{xAxisUnit}, | ||
Alexandre Leroux
|
r125 | m_ValuesData{valuesData}, | ||
Alexandre Leroux
|
r861 | m_ValuesUnit{valuesUnit}, | ||
m_YAxis{std::move(yAxis)} | ||||
Alexandre Leroux
|
r125 | { | ||
Alexandre Leroux
|
r449 | if (m_XAxisData->size() != m_ValuesData->size()) { | ||
Alexandre Leroux
|
r861 | throw std::invalid_argument{ | ||
"The number of values by component must be equal to the number of x-axis data"}; | ||||
} | ||||
// Validates y-axis (if defined) | ||||
if (yAxis.isDefined() && (yAxis.size() != m_ValuesData->componentCount())) { | ||||
throw std::invalid_argument{ | ||||
"As the y-axis is defined, the number of value components must be equal to the " | ||||
"number of y-axis data"}; | ||||
Alexandre Leroux
|
r449 | } | ||
// 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
|
r125 | } | ||
Alexandre Leroux
|
r310 | /// 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)}, | ||||
Alexandre Leroux
|
r861 | m_ValuesUnit{other.m_ValuesUnit}, | ||
m_YAxis{other.m_YAxis} | ||||
Alexandre Leroux
|
r310 | { | ||
Alexandre Leroux
|
r449 | // 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
|
r310 | } | ||
Alexandre Leroux
|
r861 | /// @return the y-axis associated to the data series | ||
OptionalAxis yAxis() const { return m_YAxis; } | ||||
Alexandre Leroux
|
r310 | /// 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); | ||||
Alexandre Leroux
|
r861 | std::swap(m_YAxis, other.m_YAxis); | ||
Alexandre Leroux
|
r310 | |||
return *this; | ||||
} | ||||
Alexandre Leroux
|
r125 | private: | ||
Alexandre Leroux
|
r449 | /** | ||
* Sorts data series on its x-axis data | ||||
*/ | ||||
void sort() noexcept | ||||
{ | ||||
Alexandre Leroux
|
r450 | auto permutation = SortUtils::sortPermutation(*m_XAxisData, std::less<double>()); | ||
m_XAxisData = m_XAxisData->sort(permutation); | ||||
m_ValuesData = m_ValuesData->sort(permutation); | ||||
Alexandre Leroux
|
r449 | } | ||
Alexandre Leroux
|
r861 | // x-axis | ||
Alexandre Leroux
|
r125 | std::shared_ptr<ArrayData<1> > m_XAxisData; | ||
Alexandre Leroux
|
r177 | Unit m_XAxisUnit; | ||
Alexandre Leroux
|
r861 | |||
// values | ||||
Alexandre Leroux
|
r125 | std::shared_ptr<ArrayData<Dim> > m_ValuesData; | ||
Alexandre Leroux
|
r177 | Unit m_ValuesUnit; | ||
r364 | ||||
Alexandre Leroux
|
r861 | // y-axis (optional) | ||
OptionalAxis m_YAxis; | ||||
r364 | QReadWriteLock m_Lock; | |||
Alexandre Leroux
|
r125 | }; | ||
#endif // SCIQLOP_DATASERIES_H | ||||