##// END OF EJS Templates
Merge branch 'feature/SpectrogramSeries' into develop
Alexandre Leroux -
r867:73c43e70e278 merge
parent child
Show More
@@ -0,0 +1,58
1 #ifndef SCIQLOP_OPTIONALAXIS_H
2 #define SCIQLOP_OPTIONALAXIS_H
3
4 #include "CoreGlobal.h"
5 #include "Unit.h"
6
7 #include <memory>
8
9 template <int Dim>
10 class ArrayData;
11
12 /**
13 * @brief The OptionalAxis class defines an optional data axis for a series of data.
14 *
15 * An optional data axis is an axis that can be defined or not for a data series. If defined, it
16 * contains a unit and data (1-dim ArrayData). It is then possible to access the data or the unit.
17 * In the case of an undefined axis, the axis has no data and no unit. The methods for accessing the
18 * data or the unit are always callable but will return undefined values.
19 *
20 * @sa DataSeries
21 * @sa ArrayData
22 */
23 class SCIQLOP_CORE_EXPORT OptionalAxis {
24 public:
25 /// Ctor for an undefined axis
26 explicit OptionalAxis();
27 /// Ctor for a defined axis
28 /// @param data the axis' data
29 /// @param unit the axis' unit
30 /// @throws std::invalid_argument if no data is associated to the axis
31 explicit OptionalAxis(std::shared_ptr<ArrayData<1> > data, Unit unit);
32
33 /// Copy ctor
34 OptionalAxis(const OptionalAxis &other);
35 /// Assignment operator
36 OptionalAxis &operator=(OptionalAxis other);
37
38 /// @return the flag that indicates if the axis is defined or not
39 bool isDefined() const;
40
41 /// @return gets the data at the index passed in parameter, NaN if the index is outside the
42 /// bounds of the axis, or if the axis is undefined
43 double at(int index) const;
44 /// @return the number of data on the axis, 0 if the axis is not defined
45 int size() const;
46 /// @return the unit of the axis, an empty unit if the axis is not defined
47 Unit unit() const;
48
49 bool operator==(const OptionalAxis &other);
50 bool operator!=(const OptionalAxis &other);
51
52 private:
53 bool m_Defined; ///< Axis is defined or not
54 std::shared_ptr<ArrayData<1> > m_Data; ///< Axis' data
55 Unit m_Unit; ///< Axis' unit
56 };
57
58 #endif // SCIQLOP_OPTIONALAXIS_H
@@ -0,0 +1,33
1 #ifndef SCIQLOP_SPECTROGRAMSERIES_H
2 #define SCIQLOP_SPECTROGRAMSERIES_H
3
4 #include "CoreGlobal.h"
5
6 #include <Data/DataSeries.h>
7
8 /**
9 * @brief The SpectrogramSeries class is the implementation for a data series representing a
10 * spectrogram.
11 *
12 * It defines values on a x-axis and a y-axis.
13 */
14 class SCIQLOP_CORE_EXPORT SpectrogramSeries : public DataSeries<2> {
15 public:
16 /// Ctor
17 explicit SpectrogramSeries(std::vector<double> xAxisData, std::vector<double> yAxisData,
18 std::vector<double> valuesData, const Unit &xAxisUnit,
19 const Unit &yAxisUnit, const Unit &valuesUnit);
20
21 /// Ctor directly with the y-axis
22 explicit SpectrogramSeries(std::shared_ptr<ArrayData<1> > xAxisData, const Unit &xAxisUnit,
23 std::shared_ptr<ArrayData<2> > valuesData, const Unit &valuesUnit,
24 OptionalAxis yAxis);
25
26 /// @sa DataSeries::clone()
27 std::unique_ptr<IDataSeries> clone() const override;
28
29 /// @sa DataSeries::subDataSeries()
30 std::shared_ptr<IDataSeries> subDataSeries(const SqpRange &range) override;
31 };
32
33 #endif // SCIQLOP_SPECTROGRAMSERIES_H
@@ -0,0 +1,23
1 #ifndef SCIQLOP_UNIT_H
2 #define SCIQLOP_UNIT_H
3
4 #include <QString>
5 #include <tuple>
6
7 struct Unit {
8 explicit Unit(const QString &name = {}, bool timeUnit = false)
9 : m_Name{name}, m_TimeUnit{timeUnit}
10 {
11 }
12
13 inline bool operator==(const Unit &other) const
14 {
15 return std::tie(m_Name, m_TimeUnit) == std::tie(other.m_Name, other.m_TimeUnit);
16 }
17 inline bool operator!=(const Unit &other) const { return !(*this == other); }
18
19 QString m_Name; ///< Unit name
20 bool m_TimeUnit; ///< The unit is a unit of time (UTC)
21 };
22
23 #endif // SCIQLOP_UNIT_H
@@ -0,0 +1,74
1 #include <Data/OptionalAxis.h>
2
3 #include "Data/ArrayData.h"
4
5 OptionalAxis::OptionalAxis() : m_Defined{false}, m_Data{nullptr}, m_Unit{}
6 {
7 }
8
9 OptionalAxis::OptionalAxis(std::shared_ptr<ArrayData<1> > data, Unit unit)
10 : m_Defined{true}, m_Data{data}, m_Unit{std::move(unit)}
11 {
12 if (m_Data == nullptr) {
13 throw std::invalid_argument{"Data can't be null for a defined axis"};
14 }
15 }
16
17 OptionalAxis::OptionalAxis(const OptionalAxis &other)
18 : m_Defined{other.m_Defined},
19 m_Data{other.m_Data ? std::make_shared<ArrayData<1> >(*other.m_Data) : nullptr},
20 m_Unit{other.m_Unit}
21 {
22 }
23
24 OptionalAxis &OptionalAxis::operator=(OptionalAxis other)
25 {
26 std::swap(m_Defined, other.m_Defined);
27 std::swap(m_Data, other.m_Data);
28 std::swap(m_Unit, other.m_Unit);
29 }
30
31 bool OptionalAxis::isDefined() const
32 {
33 return m_Defined;
34 }
35
36 double OptionalAxis::at(int index) const
37 {
38 if (m_Defined) {
39 return (index >= 0 && index < m_Data->size()) ? m_Data->at(index)
40 : std::numeric_limits<double>::quiet_NaN();
41 }
42 else {
43 return std::numeric_limits<double>::quiet_NaN();
44 }
45 }
46
47 int OptionalAxis::size() const
48 {
49 return m_Defined ? m_Data->size() : 0;
50 }
51
52 Unit OptionalAxis::unit() const
53 {
54 return m_Defined ? m_Unit : Unit{};
55 }
56
57 bool OptionalAxis::operator==(const OptionalAxis &other)
58 {
59 // Axis not defined
60 if (!m_Defined) {
61 return !other.m_Defined;
62 }
63
64 // Axis defined
65 return m_Unit == other.m_Unit
66 && std::equal(
67 m_Data->cbegin(), m_Data->cend(), other.m_Data->cbegin(), other.m_Data->cend(),
68 [](const auto &it1, const auto &it2) { return it1.values() == it2.values(); });
69 }
70
71 bool OptionalAxis::operator!=(const OptionalAxis &other)
72 {
73 return !(*this == other);
74 }
@@ -0,0 +1,45
1 #include <Data/SpectrogramSeries.h>
2
3 SpectrogramSeries::SpectrogramSeries(std::vector<double> xAxisData, std::vector<double> yAxisData,
4 std::vector<double> valuesData, const Unit &xAxisUnit,
5 const Unit &yAxisUnit, const Unit &valuesUnit)
6 : SpectrogramSeries{
7 std::make_shared<ArrayData<1> >(std::move(xAxisData)), xAxisUnit,
8 std::make_shared<ArrayData<2> >(std::move(valuesData), yAxisData.size()), valuesUnit,
9 OptionalAxis{std::make_shared<ArrayData<1> >(std::move(yAxisData)), yAxisUnit}}
10 {
11 }
12
13 SpectrogramSeries::SpectrogramSeries(std::shared_ptr<ArrayData<1> > xAxisData,
14 const Unit &xAxisUnit,
15 std::shared_ptr<ArrayData<2> > valuesData,
16 const Unit &valuesUnit, OptionalAxis yAxis)
17 : DataSeries{std::move(xAxisData), xAxisUnit, std::move(valuesData), valuesUnit,
18 std::move(yAxis)}
19 {
20 }
21
22 std::unique_ptr<IDataSeries> SpectrogramSeries::clone() const
23 {
24 return std::make_unique<SpectrogramSeries>(*this);
25 }
26
27 std::shared_ptr<IDataSeries> SpectrogramSeries::subDataSeries(const SqpRange &range)
28 {
29 auto subXAxisData = std::vector<double>();
30 auto subValuesData = QVector<double>(); // Uses QVector to append easily values to it
31 this->lockRead();
32 auto bounds = xAxisRange(range.m_TStart, range.m_TEnd);
33 for (auto it = bounds.first; it != bounds.second; ++it) {
34 subXAxisData.push_back(it->x());
35 subValuesData.append(it->values());
36 }
37
38 auto yAxis = this->yAxis();
39 this->unlock();
40
41 return std::make_shared<SpectrogramSeries>(
42 std::make_shared<ArrayData<1> >(std::move(subXAxisData)), this->xAxisUnit(),
43 std::make_shared<ArrayData<2> >(subValuesData.toStdVector(), yAxis.size()),
44 this->valuesUnit(), std::move(yAxis));
45 }
@@ -0,0 +1,90
1 #include "DataSeriesBuilders.h"
2
3 #include <Data/ScalarSeries.h>
4 #include <Data/SpectrogramSeries.h>
5 #include <Data/VectorSeries.h>
6 #include <Data/Unit.h>
7
8 // ///////////// //
9 // ScalarBuilder //
10 // ///////////// //
11
12 ScalarBuilder &ScalarBuilder::setX(std::vector<double> xData)
13 {
14 m_XAxisData = std::move(xData);
15 return *this;
16 }
17
18 ScalarBuilder &ScalarBuilder::setValues(std::vector<double> valuesData)
19 {
20 m_ValuesData =std::move(valuesData);
21 return *this;
22 }
23
24 std::shared_ptr<ScalarSeries> ScalarBuilder::build()
25 {
26 return std::make_shared<ScalarSeries>(std::move(m_XAxisData), std::move(m_ValuesData), Unit{},
27 Unit{});
28 }
29
30 // ////////////////// //
31 // SpectrogramBuilder //
32 // ////////////////// //
33
34 SpectrogramBuilder &SpectrogramBuilder::setX(std::vector<double> xData)
35 {
36 m_XAxisData = std::move(xData);
37 return *this;
38 }
39
40 SpectrogramBuilder &SpectrogramBuilder::setY(std::vector<double> yData)
41 {
42 m_YAxisData =std::move(yData);
43 return *this;
44 }
45
46 SpectrogramBuilder &SpectrogramBuilder::setValues(std::vector<double> valuesData)
47 {
48 m_ValuesData =std::move(valuesData);
49 return *this;
50 }
51
52 std::shared_ptr<SpectrogramSeries> SpectrogramBuilder::build()
53 {
54 return std::make_shared<SpectrogramSeries>(std::move(m_XAxisData), std::move(m_YAxisData), std::move(m_ValuesData), Unit{},
55 Unit{}, Unit{});
56 }
57
58 // ///////////// //
59 // VectorBuilder //
60 // ///////////// //
61
62 VectorBuilder &VectorBuilder::setX(std::vector<double> xData)
63 {
64 m_XAxisData = std::move(xData);
65 return *this;
66 }
67
68 VectorBuilder &VectorBuilder::setXValues(std::vector<double> xValuesData)
69 {
70 m_XValuesData =std::move(xValuesData);
71 return *this;
72 }
73
74 VectorBuilder &VectorBuilder::setYValues(std::vector<double> yValuesData)
75 {
76 m_YValuesData =std::move(yValuesData);
77 return *this;
78 }
79
80 VectorBuilder &VectorBuilder::setZValues(std::vector<double> zValuesData)
81 {
82 m_ZValuesData =std::move(zValuesData);
83 return *this;
84 }
85
86 std::shared_ptr<VectorSeries> VectorBuilder::build()
87 {
88 return std::make_shared<VectorSeries>(std::move(m_XAxisData), std::move(m_XValuesData), std::move(m_YValuesData), std::move(m_ZValuesData), Unit{},
89 Unit{});
90 }
@@ -0,0 +1,74
1 #ifndef SCIQLOP_DATASERIESBUILDERS_H
2 #define SCIQLOP_DATASERIESBUILDERS_H
3
4 #include <memory>
5 #include <vector>
6
7 class ScalarSeries;
8 class SpectrogramSeries;
9 class VectorSeries;
10
11 /**
12 * @brief The ScalarBuilder class aims to facilitate the creation of a ScalarSeries for unit tests
13 * @sa ScalarSeries
14 */
15 class ScalarBuilder {
16 public:
17 /// Sets x-axis data of the series
18 ScalarBuilder & setX(std::vector<double> xData);
19 /// Sets values data of the series
20 ScalarBuilder & setValues(std::vector<double> valuesData);
21 /// Creates the series
22 std::shared_ptr<ScalarSeries> build();
23
24 private:
25 std::vector<double> m_XAxisData{};
26 std::vector<double> m_ValuesData{};
27 };
28
29 /**
30 * @brief The SpectrogramBuilder class aims to facilitate the creation of a SpectrogramSeries for unit tests
31 * @sa SpectrogramSeries
32 */
33 class SpectrogramBuilder {
34 public:
35 /// Sets x-axis data of the series
36 SpectrogramBuilder & setX(std::vector<double> xData);
37 /// Sets y-axis data of the series
38 SpectrogramBuilder & setY(std::vector<double> yData);
39 /// Sets values data of the series
40 SpectrogramBuilder & setValues(std::vector<double> valuesData);
41 /// Creates the series
42 std::shared_ptr<SpectrogramSeries> build();
43
44 private:
45 std::vector<double> m_XAxisData{};
46 std::vector<double> m_YAxisData{};
47 std::vector<double> m_ValuesData{};
48 };
49
50 /**
51 * @brief The VectorBuilder class aims to facilitate the creation of a VectorSeries for unit tests
52 * @sa VectorSeries
53 */
54 class VectorBuilder {
55 public:
56 /// Sets x-axis data of the series
57 VectorBuilder & setX(std::vector<double> xData);
58 /// Sets x-values data of the series
59 VectorBuilder & setXValues(std::vector<double> xValuesData);
60 /// Sets y-values data of the series
61 VectorBuilder & setYValues(std::vector<double> yValuesData);
62 /// Sets z-values data of the series
63 VectorBuilder & setZValues(std::vector<double> zValuesData);
64 /// Creates the series
65 std::shared_ptr<VectorSeries> build();
66
67 private:
68 std::vector<double> m_XAxisData{};
69 std::vector<double> m_XValuesData{};
70 std::vector<double> m_YValuesData{};
71 std::vector<double> m_ZValuesData{};
72 };
73
74 #endif // SCIQLOP_DATASERIESBUILDERS_H
@@ -0,0 +1,24
1 #include "DataSeriesUtils.h"
2
3 void validateRange(DataSeriesIterator first, DataSeriesIterator last, const DataContainer &xData,
4 const DataContainer &valuesData)
5 {
6 QVERIFY(std::equal(first, last, xData.cbegin(), xData.cend(),
7 [](const auto &it, const auto &expectedX) { return it.x() == expectedX; }));
8 QVERIFY(std::equal(
9 first, last, valuesData.cbegin(), valuesData.cend(),
10 [](const auto &it, const auto &expectedVal) { return it.value() == expectedVal; }));
11 }
12
13 void validateRange(DataSeriesIterator first, DataSeriesIterator last, const DataContainer &xData, const std::vector<DataContainer> &valuesData)
14 {
15 QVERIFY(std::equal(first, last, xData.cbegin(), xData.cend(),
16 [](const auto &it, const auto &expectedX) { return it.x() == expectedX; }));
17 for (auto i = 0; i < valuesData.size(); ++i) {
18 auto componentData = valuesData.at(i);
19
20 QVERIFY(std::equal(
21 first, last, componentData.cbegin(), componentData.cend(),
22 [i](const auto &it, const auto &expectedVal) { return it.value(i) == expectedVal; }));
23 }
24 }
@@ -0,0 +1,373
1 /**
2 * The DataSeriesUtils file contains a set of utility methods that can be used to test the operations on a DataSeries.
3 *
4 * Most of these methods are template methods to adapt to any series (scalars, vectors, spectrograms...)
5 *
6 * @sa DataSeries
7 */
8 #ifndef SCIQLOP_DATASERIESUTILS_H
9 #define SCIQLOP_DATASERIESUTILS_H
10
11 #include <Data/DataSeriesIterator.h>
12 #include <Data/ScalarSeries.h>
13 #include <Data/SpectrogramSeries.h>
14 #include <Data/VectorSeries.h>
15
16 #include <memory>
17 #include <QtTest>
18
19 /// Underlying data in ArrayData
20 using DataContainer = std::vector<double>;
21
22 Q_DECLARE_METATYPE(std::shared_ptr<ScalarSeries>)
23 Q_DECLARE_METATYPE(std::shared_ptr<SpectrogramSeries>)
24 Q_DECLARE_METATYPE(std::shared_ptr<VectorSeries>)
25
26 /**
27 * Checks that the range of a 1-dim data series contains the expected x-axis data and values data
28 * @param first the iterator on the beginning of the range to check
29 * @param last the iterator on the end of the range to check
30 * @param xData expected x-axis data for the range
31 * @param valuesData expected values data for the range
32 */
33 void validateRange(DataSeriesIterator first, DataSeriesIterator last, const DataContainer &xData,
34 const DataContainer &valuesData);
35
36 /**
37 * Checks that the range of a 2-dim data series contains the expected x-axis data and values data
38 * @param first the iterator on the beginning of the range to check
39 * @param last the iterator on the end of the range to check
40 * @param xData expected x-axis data for the range
41 * @param valuesData expected values data for the range
42 */
43 void validateRange(DataSeriesIterator first, DataSeriesIterator last, const DataContainer &xData,
44 const std::vector<DataContainer> &valuesData);
45
46 /**
47 * Sets the structure of unit tests concerning merge of two data series
48 * @tparam DataSeriesType the type of data series to merge
49 * @tparam ExpectedValuesType the type of values expected after merge
50 * @sa testMerge_t()
51 */
52 template <typename DataSeriesType, typename ExpectedValuesType>
53 void testMerge_struct() {
54 // Data series to merge
55 QTest::addColumn<std::shared_ptr<DataSeriesType> >("dataSeries");
56 QTest::addColumn<std::shared_ptr<DataSeriesType> >("dataSeries2");
57
58 // Expected values in the first data series after merge
59 QTest::addColumn<DataContainer>("expectedXAxisData");
60 QTest::addColumn<ExpectedValuesType>("expectedValuesData");
61 }
62
63 /**
64 * Unit test concerning merge of two data series
65 * @sa testMerge_struct()
66 */
67 template <typename DataSeriesType, typename ExpectedValuesType>
68 void testMerge_t(){
69 // Merges series
70 QFETCH(std::shared_ptr<DataSeriesType>, dataSeries);
71 QFETCH(std::shared_ptr<DataSeriesType>, dataSeries2);
72
73 dataSeries->merge(dataSeries2.get());
74
75 // Validates results : we check that the merge is valid
76 QFETCH(DataContainer, expectedXAxisData);
77 QFETCH(ExpectedValuesType, expectedValuesData);
78
79 validateRange(dataSeries->cbegin(), dataSeries->cend(), expectedXAxisData, expectedValuesData);
80 }
81
82 /**
83 * Sets the structure of unit tests concerning merge of two data series that are of a different type
84 * @tparam SourceType the type of data series with which to make the merge
85 * @tparam DestType the type of data series in which to make the merge
86 * @sa testMergeDifferentTypes_t()
87 */
88 template <typename SourceType, typename DestType>
89 void testMergeDifferentTypes_struct()
90 {
91 // Data series to merge
92 QTest::addColumn<std::shared_ptr<DestType> >("dest");
93 QTest::addColumn<std::shared_ptr<SourceType> >("source");
94
95 // Expected values in the dest data series after merge
96 QTest::addColumn<DataContainer>("expectedXAxisData");
97 QTest::addColumn<DataContainer>("expectedValuesData");
98 }
99
100 /**
101 * Unit test concerning merge of two data series that are of a different type
102 * @sa testMergeDifferentTypes_struct()
103 */
104 template <typename SourceType, typename DestType>
105 void testMergeDifferentTypes_t()
106 {
107 // Merges series
108 QFETCH(std::shared_ptr<SourceType>, source);
109 QFETCH(std::shared_ptr<DestType>, dest);
110
111 dest->merge(source.get());
112
113 // Validates results : we check that the merge is valid and the data series is sorted on its
114 // x-axis data
115 QFETCH(DataContainer, expectedXAxisData);
116 QFETCH(DataContainer, expectedValuesData);
117
118 validateRange(dest->cbegin(), dest->cend(), expectedXAxisData, expectedValuesData);
119 }
120
121 /**
122 * Sets the structure of unit tests concerning getting the min x-axis data of a data series
123 * @tparam T the type of data series on which to make the operation
124 * @sa testMinXAxisData_t()
125 */
126 template <typename T>
127 void testMinXAxisData_struct(){
128 // Data series to get min data
129 QTest::addColumn<std::shared_ptr<T> >("dataSeries");
130
131 // Min data
132 QTest::addColumn<double>("min");
133
134 // Expected results
135 QTest::addColumn<bool>(
136 "expectedOK"); // if true, expects to have a result (i.e. the iterator != end iterator)
137 QTest::addColumn<double>(
138 "expectedMin"); // Expected value when method doesn't return end iterator
139 }
140
141 /**
142 * Unit test concerning getting the min x-axis data of a data series
143 * @sa testMinXAxisData_struct()
144 */
145 template <typename T>
146 void testMinXAxisData_t()
147 {
148 QFETCH(std::shared_ptr<T>, dataSeries);
149 QFETCH(double, min);
150
151 QFETCH(bool, expectedOK);
152 QFETCH(double, expectedMin);
153
154 auto it = dataSeries->minXAxisData(min);
155
156 QCOMPARE(expectedOK, it != dataSeries->cend());
157
158 // If the method doesn't return a end iterator, checks with expected value
159 if (expectedOK) {
160 QCOMPARE(expectedMin, it->x());
161 }
162 }
163
164 /**
165 * Sets the structure of unit tests concerning getting the max x-axis data of a data series
166 * @tparam T the type of data series on which to make the operation
167 * @sa testMaxXAxisData_t()
168 */
169 template <typename T>
170 void testMaxXAxisData_struct(){
171 // Data series to get max data
172 QTest::addColumn<std::shared_ptr<T> >("dataSeries");
173
174 // Max data
175 QTest::addColumn<double>("max");
176
177 // Expected results
178 QTest::addColumn<bool>(
179 "expectedOK"); // if true, expects to have a result (i.e. the iterator != end iterator)
180 QTest::addColumn<double>(
181 "expectedMax"); // Expected value when method doesn't return end iterator
182
183 }
184
185 /**
186 * Unit test concerning getting the max x-axis data of a data series
187 * @sa testMaxXAxisData_struct()
188 */
189 template <typename T>
190 void testMaxXAxisData_t()
191 {
192 QFETCH(std::shared_ptr<T>, dataSeries);
193 QFETCH(double, max);
194
195 QFETCH(bool, expectedOK);
196 QFETCH(double, expectedMax);
197
198 auto it = dataSeries->maxXAxisData(max);
199
200 QCOMPARE(expectedOK, it != dataSeries->cend());
201
202 // If the method doesn't return a end iterator, checks with expected value
203 if (expectedOK) {
204 QCOMPARE(expectedMax, it->x());
205 }
206 }
207
208 /**
209 * Sets the structure of unit tests concerning getting the purge of a data series
210 * @tparam T the type of data series on which to make the operation
211 * @sa testMinXAxisData_t()
212 */
213 template <typename T>
214 void testPurge_struct()
215 {
216 // Data series to purge
217 QTest::addColumn<std::shared_ptr<T> >("dataSeries");
218 QTest::addColumn<double>("min");
219 QTest::addColumn<double>("max");
220
221 // Expected values after purge
222 QTest::addColumn<DataContainer>("expectedXAxisData");
223 QTest::addColumn<std::vector<DataContainer> >("expectedValuesData");
224 }
225
226 /**
227 * Unit test concerning getting the purge of a data series
228 * @sa testPurge_struct()
229 */
230 template <typename T>
231 void testPurge_t(){
232 QFETCH(std::shared_ptr<T>, dataSeries);
233 QFETCH(double, min);
234 QFETCH(double, max);
235
236 dataSeries->purge(min, max);
237
238 // Validates results
239 QFETCH(DataContainer, expectedXAxisData);
240 QFETCH(std::vector<DataContainer>, expectedValuesData);
241
242 validateRange(dataSeries->cbegin(), dataSeries->cend(), expectedXAxisData,
243 expectedValuesData);
244 }
245
246 /**
247 * Sets the structure of unit tests concerning getting subdata of a data series
248 * @tparam DataSeriesType the type of data series on which to make the operation
249 * @tparam ExpectedValuesType the type of values expected after the operation
250 * @sa testSubDataSeries_t()
251 */
252 template <typename DataSeriesType, typename ExpectedValuesType>
253 void testSubDataSeries_struct() {
254 // Data series from which extract the subdata series
255 QTest::addColumn<std::shared_ptr<DataSeriesType> >("dataSeries");
256 // Range to extract
257 QTest::addColumn<SqpRange>("range");
258
259 // Expected values for the subdata series
260 QTest::addColumn<DataContainer>("expectedXAxisData");
261 QTest::addColumn<ExpectedValuesType>("expectedValuesData");
262 }
263
264 /**
265 * Unit test concerning getting subdata of a data series
266 * @sa testSubDataSeries_struct()
267 */
268 template <typename DataSeriesType, typename ExpectedValuesType>
269 void testSubDataSeries_t(){
270 QFETCH(std::shared_ptr<DataSeriesType>, dataSeries);
271 QFETCH(SqpRange, range);
272
273 // Makes the operation
274 auto subDataSeries = std::dynamic_pointer_cast<DataSeriesType>(dataSeries->subDataSeries(range));
275 QVERIFY(subDataSeries != nullptr);
276
277 // Validates results
278 QFETCH(DataContainer, expectedXAxisData);
279 QFETCH(ExpectedValuesType, expectedValuesData);
280
281 validateRange(subDataSeries->cbegin(), subDataSeries->cend(), expectedXAxisData, expectedValuesData);
282 }
283
284 /**
285 * Sets the structure of unit tests concerning getting the range of a data series
286 * @tparam T the type of data series on which to make the operation
287 * @sa testXAxisRange_t()
288 */
289 template <typename T>
290 void testXAxisRange_struct(){
291 // Data series to get x-axis range
292 QTest::addColumn<std::shared_ptr<T> >("dataSeries");
293
294 // Min/max values
295 QTest::addColumn<double>("min");
296 QTest::addColumn<double>("max");
297
298 // Expected values
299 QTest::addColumn<DataContainer>("expectedXAxisData");
300 QTest::addColumn<DataContainer>("expectedValuesData");
301 }
302
303 /**
304 * Unit test concerning getting the range of a data series
305 * @sa testXAxisRange_struct()
306 */
307 template <typename T>
308 void testXAxisRange_t(){
309 QFETCH(std::shared_ptr<T>, dataSeries);
310 QFETCH(double, min);
311 QFETCH(double, max);
312
313 QFETCH(DataContainer, expectedXAxisData);
314 QFETCH(DataContainer, expectedValuesData);
315
316 auto bounds = dataSeries->xAxisRange(min, max);
317 validateRange(bounds.first, bounds.second, expectedXAxisData, expectedValuesData);
318 }
319
320 /**
321 * Sets the structure of unit tests concerning getting values bounds of a data series
322 * @tparam T the type of data series on which to make the operation
323 * @sa testValuesBounds_t()
324 */
325 template <typename T>
326 void testValuesBounds_struct()
327 {
328 // Data series to get values bounds
329 QTest::addColumn<std::shared_ptr<T> >("dataSeries");
330
331 // x-axis range
332 QTest::addColumn<double>("minXAxis");
333 QTest::addColumn<double>("maxXAxis");
334
335 // Expected results
336 QTest::addColumn<bool>(
337 "expectedOK"); // Test is expected to be ok (i.e. method doesn't return end iterators)
338 QTest::addColumn<double>("expectedMinValue");
339 QTest::addColumn<double>("expectedMaxValue");
340 }
341
342 /**
343 * Unit test concerning getting values bounds of a data series
344 * @sa testValuesBounds_struct()
345 */
346 template <typename T>
347 void testValuesBounds_t()
348 {
349 QFETCH(std::shared_ptr<T>, dataSeries);
350 QFETCH(double, minXAxis);
351 QFETCH(double, maxXAxis);
352
353 QFETCH(bool, expectedOK);
354 QFETCH(double, expectedMinValue);
355 QFETCH(double, expectedMaxValue);
356
357 auto minMaxIts = dataSeries->valuesBounds(minXAxis, maxXAxis);
358 auto end = dataSeries->cend();
359
360 // Checks iterators with expected result
361 QCOMPARE(expectedOK, minMaxIts.first != end && minMaxIts.second != end);
362
363 if (expectedOK) {
364 auto compare = [](const auto &v1, const auto &v2) {
365 return (std::isnan(v1) && std::isnan(v2)) || v1 == v2;
366 };
367
368 QVERIFY(compare(expectedMinValue, minMaxIts.first->minValue()));
369 QVERIFY(compare(expectedMaxValue, minMaxIts.second->maxValue()));
370 }
371 }
372
373 #endif // SCIQLOP_DATASERIESUTILS_H
@@ -0,0 +1,151
1 #include <Data/ArrayData.h>
2 #include <Data/OptionalAxis.h>
3
4 #include <QObject>
5 #include <QtTest>
6
7 Q_DECLARE_METATYPE(OptionalAxis)
8 Q_DECLARE_METATYPE(Unit)
9
10 class TestOptionalAxis : public QObject {
11 Q_OBJECT
12
13 private slots:
14 /// Tests the creation of a undefined axis
15 void testNotDefinedAxisCtor();
16
17 /// Tests the creation of a undefined axis
18 void testDefinedAxisCtor_data();
19 void testDefinedAxisCtor();
20
21 /// Tests @sa OptionalAxis::at() method
22 void testAt_data();
23 void testAt();
24
25 /// Tests @sa OptionalAxis::size() method
26 void testSize_data();
27 void testSize();
28
29 /// Tests @sa OptionalAxis::unit() method
30 void testUnit_data();
31 void testUnit();
32 };
33
34 void TestOptionalAxis::testNotDefinedAxisCtor()
35 {
36 OptionalAxis notDefinedAxis{};
37 QVERIFY(!notDefinedAxis.isDefined());
38 }
39
40 void TestOptionalAxis::testDefinedAxisCtor_data()
41 {
42 QTest::addColumn<bool>("noData"); // If set to true, nullptr is passed as data of the axis
43 QTest::addColumn<std::vector<double> >(
44 "data"); // Values assigned to the axis when 'noData' flag is set to false
45 QTest::addColumn<Unit>("unit"); // Unit assigned to the axis
46
47 QTest::newRow("validData") << false << std::vector<double>{1, 2, 3} << Unit{"Hz"};
48 QTest::newRow("invalidData") << true << std::vector<double>{} << Unit{"Hz"};
49 }
50
51 void TestOptionalAxis::testDefinedAxisCtor()
52 {
53 QFETCH(bool, noData);
54 QFETCH(Unit, unit);
55
56 // When there is no data, we expect that the constructor returns exception
57 if (noData) {
58 QVERIFY_EXCEPTION_THROWN(OptionalAxis(nullptr, unit), std::invalid_argument);
59 }
60 else {
61 QFETCH(std::vector<double>, data);
62
63 OptionalAxis axis{std::make_shared<ArrayData<1> >(data), unit};
64 QVERIFY(axis.isDefined());
65 }
66 }
67
68 void TestOptionalAxis::testAt_data()
69 {
70 QTest::addColumn<OptionalAxis>("axis"); // Axis used for test case (defined or not)
71 QTest::addColumn<int>("index"); // Index to test in the axis
72 QTest::addColumn<double>("expectedValue"); // Expected axis value for the index
73
74 OptionalAxis definedAxis{std::make_shared<ArrayData<1> >(std::vector<double>{1, 2, 3}),
75 Unit{"Hz"}};
76
77 QTest::newRow("data1") << definedAxis << 0 << 1.;
78 QTest::newRow("data2") << definedAxis << 1 << 2.;
79 QTest::newRow("data3") << definedAxis << 2 << 3.;
80 QTest::newRow("data4 (index out of bounds)")
81 << definedAxis << 3
82 << std::numeric_limits<double>::quiet_NaN(); // Expects NaN for out of bounds index
83 QTest::newRow("data5 (index out of bounds)")
84 << definedAxis << -1
85 << std::numeric_limits<double>::quiet_NaN(); // Expects NaN for out of bounds index
86 QTest::newRow("data6 (axis not defined)")
87 << OptionalAxis{} << 0
88 << std::numeric_limits<double>::quiet_NaN(); // Expects NaN for undefined axis
89 }
90
91 void TestOptionalAxis::testAt()
92 {
93 QFETCH(OptionalAxis, axis);
94 QFETCH(int, index);
95 QFETCH(double, expectedValue);
96
97 auto value = axis.at(index);
98 QVERIFY((std::isnan(value) && std::isnan(expectedValue)) || value == expectedValue);
99 }
100
101 void TestOptionalAxis::testSize_data()
102 {
103 QTest::addColumn<OptionalAxis>("axis"); // Axis used for test case (defined or not)
104 QTest::addColumn<int>("expectedSize"); // Expected number of data in the axis
105
106 // Lambda that creates default defined axis (with the values passed in parameter)
107 auto axis = [](std::vector<double> values) {
108 return OptionalAxis{std::make_shared<ArrayData<1> >(std::move(values)), Unit{"Hz"}};
109 };
110
111 QTest::newRow("data1") << axis({}) << 0;
112 QTest::newRow("data2") << axis({1, 2, 3}) << 3;
113 QTest::newRow("data3") << axis({1, 2, 3, 4}) << 4;
114 QTest::newRow("data4 (axis not defined)")
115 << OptionalAxis{} << 0; // Expects 0 for undefined axis
116 }
117
118 void TestOptionalAxis::testSize()
119 {
120 QFETCH(OptionalAxis, axis);
121 QFETCH(int, expectedSize);
122
123 QCOMPARE(axis.size(), expectedSize);
124 }
125
126 void TestOptionalAxis::testUnit_data()
127 {
128 QTest::addColumn<OptionalAxis>("axis"); // Axis used for test case (defined or not)
129 QTest::addColumn<Unit>("expectedUnit"); // Expected unit for the axis
130
131 // Lambda that creates default defined axis (with the unit passed in parameter)
132 auto axis = [](Unit unit) {
133 return OptionalAxis{std::make_shared<ArrayData<1> >(std::vector<double>{1, 2, 3}), unit};
134 };
135
136 QTest::newRow("data1") << axis(Unit{"Hz"}) << Unit{"Hz"};
137 QTest::newRow("data2") << axis(Unit{"t", true}) << Unit{"t", true};
138 QTest::newRow("data3 (axis not defined)")
139 << OptionalAxis{} << Unit{}; // Expects default unit for undefined axis
140 }
141
142 void TestOptionalAxis::testUnit()
143 {
144 QFETCH(OptionalAxis, axis);
145 QFETCH(Unit, expectedUnit);
146
147 QCOMPARE(axis.unit(), expectedUnit);
148 }
149
150 QTEST_MAIN(TestOptionalAxis)
151 #include "TestOptionalAxis.moc"
@@ -0,0 +1,426
1 #include "Data/ScalarSeries.h"
2
3 #include "DataSeriesBuilders.h"
4 #include "DataSeriesUtils.h"
5
6 #include <QObject>
7 #include <QtTest>
8
9 /**
10 * @brief The TestScalarSeries class defines unit tests on scalar series.
11 *
12 * Most of these unit tests use generic tests defined for DataSeries (@sa DataSeriesUtils)
13 */
14 class TestScalarSeries : public QObject {
15 Q_OBJECT
16 private slots:
17 /// Tests construction of a scalar series
18 void testCtor_data();
19 void testCtor();
20
21 /// Tests merge of two scalar series
22 void testMerge_data();
23 void testMerge();
24
25 /// Tests merge of a vector series in a scalar series
26 void testMergeWithVector_data();
27 void testMergeWithVector();
28
29 /// Tests get min x-axis data of a scalar series
30 void testMinXAxisData_data();
31 void testMinXAxisData();
32
33 /// Tests get max x-axis data of a scalar series
34 void testMaxXAxisData_data();
35 void testMaxXAxisData();
36
37 /// Tests purge of a scalar series
38 void testPurge_data();
39 void testPurge();
40
41 /// Tests get x-axis range of a scalar series
42 void testXAxisRange_data();
43 void testXAxisRange();
44
45 /// Tests get values bounds of a scalar series
46 void testValuesBounds_data();
47 void testValuesBounds();
48 };
49
50 void TestScalarSeries::testCtor_data()
51 {
52 // x-axis data
53 QTest::addColumn<DataContainer>("xAxisData");
54 // values data
55 QTest::addColumn<DataContainer>("valuesData");
56
57 // construction expected to be valid
58 QTest::addColumn<bool>("expectOK");
59 // expected x-axis data (when construction is valid)
60 QTest::addColumn<DataContainer>("expectedXAxisData");
61 // expected values data (when construction is valid)
62 QTest::addColumn<DataContainer>("expectedValuesData");
63
64 QTest::newRow("invalidData (different sizes of vectors)")
65 << DataContainer{1., 2., 3., 4., 5.} << DataContainer{100., 200., 300.} << false
66 << DataContainer{} << DataContainer{};
67
68 QTest::newRow("sortedData") << DataContainer{1., 2., 3., 4., 5.}
69 << DataContainer{100., 200., 300., 400., 500.} << true
70 << DataContainer{1., 2., 3., 4., 5.}
71 << DataContainer{100., 200., 300., 400., 500.};
72
73 QTest::newRow("unsortedData") << DataContainer{5., 4., 3., 2., 1.}
74 << DataContainer{100., 200., 300., 400., 500.} << true
75 << DataContainer{1., 2., 3., 4., 5.}
76 << DataContainer{500., 400., 300., 200., 100.};
77
78 QTest::newRow("unsortedData2")
79 << DataContainer{1., 4., 3., 5., 2.} << DataContainer{100., 200., 300., 400., 500.} << true
80 << DataContainer{1., 2., 3., 4., 5.} << DataContainer{100., 500., 300., 200., 400.};
81 }
82
83 void TestScalarSeries::testCtor()
84 {
85 // Creates series
86 QFETCH(DataContainer, xAxisData);
87 QFETCH(DataContainer, valuesData);
88 QFETCH(bool, expectOK);
89
90 if (expectOK) {
91 auto series = std::make_shared<ScalarSeries>(std::move(xAxisData), std::move(valuesData),
92 Unit{}, Unit{});
93
94 // Validates results : we check that the data series is sorted on its x-axis data
95 QFETCH(DataContainer, expectedXAxisData);
96 QFETCH(DataContainer, expectedValuesData);
97
98 validateRange(series->cbegin(), series->cend(), expectedXAxisData, expectedValuesData);
99 }
100 else {
101 QVERIFY_EXCEPTION_THROWN(std::make_shared<ScalarSeries>(
102 std::move(xAxisData), std::move(valuesData), Unit{}, Unit{}),
103 std::invalid_argument);
104 }
105 }
106
107 void TestScalarSeries::testMerge_data()
108 {
109 testMerge_struct<ScalarSeries, DataContainer>();
110
111 QTest::newRow("sortedMerge") << ScalarBuilder{}
112 .setX({1., 2., 3., 4., 5.})
113 .setValues({100., 200., 300., 400., 500.})
114 .build()
115 << ScalarBuilder{}
116 .setX({6., 7., 8., 9., 10.})
117 .setValues({600., 700., 800., 900., 1000.})
118 .build()
119 << DataContainer{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
120 << DataContainer{100., 200., 300., 400., 500.,
121 600., 700., 800., 900., 1000.};
122
123 QTest::newRow("unsortedMerge")
124 << ScalarBuilder{}
125 .setX({6., 7., 8., 9., 10.})
126 .setValues({600., 700., 800., 900., 1000.})
127 .build()
128 << ScalarBuilder{}
129 .setX({1., 2., 3., 4., 5.})
130 .setValues({100., 200., 300., 400., 500.})
131 .build()
132 << DataContainer{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
133 << DataContainer{100., 200., 300., 400., 500., 600., 700., 800., 900., 1000.};
134
135 QTest::newRow("unsortedMerge2 (merge not made because source is in the bounds of dest)")
136 << ScalarBuilder{}
137 .setX({1., 2., 8., 9., 10})
138 .setValues({100., 200., 800., 900., 1000.})
139 .build()
140 << ScalarBuilder{}
141 .setX({3., 4., 5., 6., 7.})
142 .setValues({300., 400., 500., 600., 700.})
143 .build()
144 << DataContainer{1., 2., 8., 9., 10.} << DataContainer{100., 200., 800., 900., 1000.};
145
146 QTest::newRow("unsortedMerge3")
147 << ScalarBuilder{}
148 .setX({3., 4., 5., 7., 8})
149 .setValues({300., 400., 500., 700., 800.})
150 .build()
151 << ScalarBuilder{}
152 .setX({1., 2., 3., 7., 10.})
153 .setValues({100., 200., 333., 777., 1000.})
154 .build()
155 << DataContainer{1., 2., 3., 4., 5., 7., 8., 10.}
156 << DataContainer{100., 200., 300., 400., 500., 700., 800., 1000.};
157
158 QTest::newRow("emptySource") << ScalarBuilder{}
159 .setX({3., 4., 5., 7., 8})
160 .setValues({300., 400., 500., 700., 800.})
161 .build()
162 << ScalarBuilder{}.setX({}).setValues({}).build()
163 << DataContainer{3., 4., 5., 7., 8.}
164 << DataContainer{300., 400., 500., 700., 800.};
165 }
166
167 void TestScalarSeries::testMerge()
168 {
169 testMerge_t<ScalarSeries, DataContainer>();
170 }
171
172 void TestScalarSeries::testMergeWithVector_data()
173 {
174 testMergeDifferentTypes_struct<VectorSeries, ScalarSeries>();
175
176 QTest::newRow("mergeVectorInScalar")
177 << ScalarBuilder{}
178 .setX({1., 2., 3., 4., 5.})
179 .setValues({100., 200., 300., 400., 500.})
180 .build()
181 << VectorBuilder{}
182 .setX({6., 7., 8., 9., 10.})
183 .setXValues({600., 700., 800., 900., 1000.})
184 .setYValues({610., 710., 810., 910., 1010.})
185 .setZValues({620., 720., 820., 920., 1020.})
186 .build()
187 << DataContainer{1., 2., 3., 4., 5.} << DataContainer{100., 200., 300., 400., 500.};
188 }
189
190 void TestScalarSeries::testMergeWithVector()
191 {
192 testMergeDifferentTypes_t<VectorSeries, ScalarSeries>();
193 }
194
195 void TestScalarSeries::testMinXAxisData_data()
196 {
197 testMinXAxisData_struct<ScalarSeries>();
198
199 QTest::newRow("minData1") << ScalarBuilder{}
200 .setX({1., 2., 3., 4., 5.})
201 .setValues({100., 200., 300., 400., 500.})
202 .build()
203 << 0. << true << 1.;
204 QTest::newRow("minData2") << ScalarBuilder{}
205 .setX({1., 2., 3., 4., 5.})
206 .setValues({100., 200., 300., 400., 500.})
207 .build()
208 << 1. << true << 1.;
209 QTest::newRow("minData3") << ScalarBuilder{}
210 .setX({1., 2., 3., 4., 5.})
211 .setValues({100., 200., 300., 400., 500.})
212 .build()
213 << 1.1 << true << 2.;
214 QTest::newRow("minData4") << ScalarBuilder{}
215 .setX({1., 2., 3., 4., 5.})
216 .setValues({100., 200., 300., 400., 500.})
217 .build()
218 << 5. << true << 5.;
219 QTest::newRow("minData5") << ScalarBuilder{}
220 .setX({1., 2., 3., 4., 5.})
221 .setValues({100., 200., 300., 400., 500.})
222 .build()
223 << 5.1 << false << std::numeric_limits<double>::quiet_NaN();
224 QTest::newRow("minData6") << ScalarBuilder{}.setX({}).setValues({}).build() << 1.1 << false
225 << std::numeric_limits<double>::quiet_NaN();
226 }
227
228 void TestScalarSeries::testMinXAxisData()
229 {
230 testMinXAxisData_t<ScalarSeries>();
231 }
232
233 void TestScalarSeries::testMaxXAxisData_data()
234 {
235 testMaxXAxisData_struct<ScalarSeries>();
236
237 QTest::newRow("maxData1") << ScalarBuilder{}
238 .setX({1., 2., 3., 4., 5.})
239 .setValues({100., 200., 300., 400., 500.})
240 .build()
241 << 6. << true << 5.;
242 QTest::newRow("maxData2") << ScalarBuilder{}
243 .setX({1., 2., 3., 4., 5.})
244 .setValues({100., 200., 300., 400., 500.})
245 .build()
246 << 5. << true << 5.;
247 QTest::newRow("maxData3") << ScalarBuilder{}
248 .setX({1., 2., 3., 4., 5.})
249 .setValues({100., 200., 300., 400., 500.})
250 .build()
251 << 4.9 << true << 4.;
252 QTest::newRow("maxData4") << ScalarBuilder{}
253 .setX({1., 2., 3., 4., 5.})
254 .setValues({100., 200., 300., 400., 500.})
255 .build()
256 << 1.1 << true << 1.;
257 QTest::newRow("maxData5") << ScalarBuilder{}
258 .setX({1., 2., 3., 4., 5.})
259 .setValues({100., 200., 300., 400., 500.})
260 .build()
261 << 1. << true << 1.;
262 QTest::newRow("maxData6") << ScalarBuilder{}.setX({}).setValues({}).build() << 1.1 << false
263 << std::numeric_limits<double>::quiet_NaN();
264 }
265
266 void TestScalarSeries::testMaxXAxisData()
267 {
268 testMaxXAxisData_t<ScalarSeries>();
269 }
270
271 void TestScalarSeries::testPurge_data()
272 {
273 testPurge_struct<ScalarSeries>();
274
275 QTest::newRow("purgeScalar") << ScalarBuilder{}
276 .setX({1., 2., 3., 4., 5.})
277 .setValues({100., 200., 300., 400., 500.})
278 .build()
279 << 2. << 4. << DataContainer{2., 3., 4.}
280 << std::vector<DataContainer>{{200., 300., 400.}};
281 QTest::newRow("purgeScalar1 (min/max swap)")
282 << ScalarBuilder{}
283 .setX({1., 2., 3., 4., 5.})
284 .setValues({100., 200., 300., 400., 500.})
285 .build()
286 << 4. << 2. << DataContainer{2., 3., 4.} << std::vector<DataContainer>{{200., 300., 400.}};
287 QTest::newRow("purgeScalar2") << ScalarBuilder{}
288 .setX({1., 2., 3., 4., 5.})
289 .setValues({100., 200., 300., 400., 500.})
290 .build()
291 << 0. << 2.5 << DataContainer{1., 2.}
292 << std::vector<DataContainer>{{100., 200.}};
293 QTest::newRow("purgeScalar3") << ScalarBuilder{}
294 .setX({1., 2., 3., 4., 5.})
295 .setValues({100., 200., 300., 400., 500.})
296 .build()
297 << 3.5 << 7. << DataContainer{4., 5.}
298 << std::vector<DataContainer>{{400., 500.}};
299 QTest::newRow("purgeScalar4") << ScalarBuilder{}
300 .setX({1., 2., 3., 4., 5.})
301 .setValues({100., 200., 300., 400., 500.})
302 .build()
303 << 0. << 7. << DataContainer{1., 2., 3., 4., 5.}
304 << std::vector<DataContainer>{{100., 200., 300., 400., 500.}};
305 QTest::newRow("purgeScalar5") << ScalarBuilder{}
306 .setX({1., 2., 3., 4., 5.})
307 .setValues({100., 200., 300., 400., 500.})
308 .build()
309 << 5.5 << 7. << DataContainer{} << std::vector<DataContainer>{{}};
310 }
311
312 void TestScalarSeries::testPurge()
313 {
314 testPurge_t<ScalarSeries>();
315 }
316
317 void TestScalarSeries::testXAxisRange_data()
318 {
319 testXAxisRange_struct<ScalarSeries>();
320
321 QTest::newRow("xAxisRange") << ScalarBuilder{}
322 .setX({1., 2., 3., 4., 5.})
323 .setValues({100., 200., 300., 400., 500.})
324 .build()
325 << -1. << 3.2 << DataContainer{1., 2., 3.}
326 << DataContainer{100., 200., 300.};
327 QTest::newRow("xAxisRange1 (min/max swap)")
328 << ScalarBuilder{}
329 .setX({1., 2., 3., 4., 5.})
330 .setValues({100., 200., 300., 400., 500.})
331 .build()
332 << 3.2 << -1. << DataContainer{1., 2., 3.} << DataContainer{100., 200., 300.};
333 QTest::newRow("xAxisRange2") << ScalarBuilder{}
334 .setX({1., 2., 3., 4., 5.})
335 .setValues({100., 200., 300., 400., 500.})
336 .build()
337 << 1. << 4. << DataContainer{1., 2., 3., 4.}
338 << DataContainer{100., 200., 300., 400.};
339 QTest::newRow("xAxisRange3") << ScalarBuilder{}
340 .setX({1., 2., 3., 4., 5.})
341 .setValues({100., 200., 300., 400., 500.})
342 .build()
343 << 1. << 3.9 << DataContainer{1., 2., 3.}
344 << DataContainer{100., 200., 300.};
345 QTest::newRow("xAxisRange4") << ScalarBuilder{}
346 .setX({1., 2., 3., 4., 5.})
347 .setValues({100., 200., 300., 400., 500.})
348 .build()
349 << 0. << 0.9 << DataContainer{} << DataContainer{};
350 QTest::newRow("xAxisRange5") << ScalarBuilder{}
351 .setX({1., 2., 3., 4., 5.})
352 .setValues({100., 200., 300., 400., 500.})
353 .build()
354 << 0. << 1. << DataContainer{1.} << DataContainer{100.};
355 QTest::newRow("xAxisRange6") << ScalarBuilder{}
356 .setX({1., 2., 3., 4., 5.})
357 .setValues({100., 200., 300., 400., 500.})
358 .build()
359 << 2.1 << 6. << DataContainer{3., 4., 5.}
360 << DataContainer{300., 400., 500.};
361 QTest::newRow("xAxisRange7") << ScalarBuilder{}
362 .setX({1., 2., 3., 4., 5.})
363 .setValues({100., 200., 300., 400., 500.})
364 .build()
365 << 6. << 9. << DataContainer{} << DataContainer{};
366 QTest::newRow("xAxisRange8") << ScalarBuilder{}
367 .setX({1., 2., 3., 4., 5.})
368 .setValues({100., 200., 300., 400., 500.})
369 .build()
370 << 5. << 9. << DataContainer{5.} << DataContainer{500.};
371 }
372
373 void TestScalarSeries::testXAxisRange()
374 {
375 testXAxisRange_t<ScalarSeries>();
376 }
377
378 void TestScalarSeries::testValuesBounds_data()
379 {
380 testValuesBounds_struct<ScalarSeries>();
381
382 auto nan = std::numeric_limits<double>::quiet_NaN();
383
384 QTest::newRow("scalarBounds1") << ScalarBuilder{}
385 .setX({1., 2., 3., 4., 5.})
386 .setValues({100., 200., 300., 400., 500.})
387 .build()
388 << 0. << 6. << true << 100. << 500.;
389 QTest::newRow("scalarBounds2") << ScalarBuilder{}
390 .setX({1., 2., 3., 4., 5.})
391 .setValues({100., 200., 300., 400., 500.})
392 .build()
393 << 2. << 4. << true << 200. << 400.;
394 QTest::newRow("scalarBounds3") << ScalarBuilder{}
395 .setX({1., 2., 3., 4., 5.})
396 .setValues({100., 200., 300., 400., 500.})
397 .build()
398 << 0. << 0.5 << false << nan << nan;
399 QTest::newRow("scalarBounds4") << ScalarBuilder{}
400 .setX({1., 2., 3., 4., 5.})
401 .setValues({100., 200., 300., 400., 500.})
402 .build()
403 << 5.1 << 6. << false << nan << nan;
404 QTest::newRow("scalarBounds5")
405 << ScalarBuilder{}.setX({1.}).setValues({100.}).build() << 0. << 2. << true << 100. << 100.;
406 QTest::newRow("scalarBounds6")
407 << ScalarBuilder{}.setX({}).setValues({}).build() << 0. << 2. << false << nan << nan;
408
409 // Tests with NaN values: NaN values are not included in min/max search
410 QTest::newRow("scalarBounds7") << ScalarBuilder{}
411 .setX({1., 2., 3., 4., 5.})
412 .setValues({nan, 200., 300., 400., nan})
413 .build()
414 << 0. << 6. << true << 200. << 400.;
415 QTest::newRow("scalarBounds8")
416 << ScalarBuilder{}.setX({1., 2., 3., 4., 5.}).setValues({nan, nan, nan, nan, nan}).build()
417 << 0. << 6. << true << nan << nan;
418 }
419
420 void TestScalarSeries::testValuesBounds()
421 {
422 testValuesBounds_t<ScalarSeries>();
423 }
424
425 QTEST_MAIN(TestScalarSeries)
426 #include "TestScalarSeries.moc"
@@ -0,0 +1,199
1 #include "Data/SpectrogramSeries.h"
2
3 #include "DataSeriesBuilders.h"
4 #include "DataSeriesUtils.h"
5
6 #include <QObject>
7 #include <QtTest>
8
9 namespace {
10
11 // Aliases used to facilitate reading of test inputs
12 using X = DataContainer;
13 using Y = DataContainer;
14 using Values = DataContainer;
15 using Components = std::vector<DataContainer>;
16
17 } // namespace
18
19 /**
20 * @brief The TestSpectrogramSeries class defines unit tests on spectrogram series.
21 *
22 * Most of these unit tests use generic tests defined for DataSeries (@sa DataSeriesUtils)
23 */
24 class TestSpectrogramSeries : public QObject {
25 Q_OBJECT
26 private slots:
27
28 /// Tests construction of a spectrogram series
29 void testCtor_data();
30 void testCtor();
31
32 /// Tests merge of two spectrogram series
33 void testMerge_data();
34 void testMerge();
35
36 /// @todo ALX: test subdataseries
37 /// Tests get subdata of a spectrogram series
38 void testSubDataSeries_data();
39 void testSubDataSeries();
40 };
41
42 void TestSpectrogramSeries::testCtor_data()
43 {
44 // x-axis data
45 QTest::addColumn<X>("xAxisData");
46 // y-axis data
47 QTest::addColumn<Y>("yAxisData");
48 // values data
49 QTest::addColumn<Values>("valuesData");
50
51 // construction expected to be valid
52 QTest::addColumn<bool>("expectOK");
53 // expected x-axis data (when construction is valid)
54 QTest::addColumn<X>("expectedXAxisData");
55 // expected components data (when construction is valid)
56 QTest::addColumn<Components>("expectedComponentsData");
57
58 QTest::newRow(
59 "invalidData (number of values by component aren't equal to the number of x-axis data)")
60 << X{1., 2., 3., 4., 5.} << Y{1., 2., 3.} << Values{1., 2., 3.} << false << X{}
61 << Components{};
62
63 QTest::newRow("invalidData (number of components aren't equal to the number of y-axis data)")
64 << X{1., 2., 3., 4., 5.} << Y{1., 2.} // 2 y-axis data
65 << Values{1., 2., 3., 4., 5.} // 1 component
66 << false << X{} << Components{};
67
68 QTest::newRow("sortedData") << X{1., 2., 3., 4., 5.} << Y{1., 2.} // 2 y-axis data
69 << Values{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.} // 2 components
70 << true << X{1., 2., 3., 4., 5.}
71 << Components{{1., 3., 5., 7., 9.}, {2., 4., 6., 8., 10.}};
72
73 QTest::newRow("unsortedData") << X{5., 4., 3., 2., 1.} << Y{1., 2.}
74 << Values{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.} << true
75 << X{1., 2., 3., 4., 5.}
76 << Components{{9., 7., 5., 3., 1.}, {10., 8., 6., 4., 2.}};
77 }
78
79 void TestSpectrogramSeries::testCtor()
80 {
81 // Creates series
82 QFETCH(X, xAxisData);
83 QFETCH(Y, yAxisData);
84 QFETCH(Values, valuesData);
85 QFETCH(bool, expectOK);
86
87 if (expectOK) {
88 auto series = SpectrogramBuilder{}
89 .setX(std::move(xAxisData))
90 .setY(std::move(yAxisData))
91 .setValues(std::move(valuesData))
92 .build();
93
94 // Validates results
95 QFETCH(X, expectedXAxisData);
96 QFETCH(Components, expectedComponentsData);
97 validateRange(series->cbegin(), series->cend(), expectedXAxisData, expectedComponentsData);
98 }
99 else {
100 QVERIFY_EXCEPTION_THROWN(SpectrogramBuilder{}
101 .setX(std::move(xAxisData))
102 .setY(std::move(yAxisData))
103 .setValues(std::move(valuesData))
104 .build(),
105 std::invalid_argument);
106 }
107 }
108
109 void TestSpectrogramSeries::testMerge_data()
110 {
111 testMerge_struct<SpectrogramSeries, Components>();
112
113 QTest::newRow("sortedMerge") << SpectrogramBuilder{}
114 .setX({1., 2., 3.})
115 .setY({1., 2.})
116 .setValues({10., 11., 20., 21., 30., 31})
117 .build()
118 << SpectrogramBuilder{}
119 .setX({4., 5., 6.})
120 .setY({1., 2.})
121 .setValues({40., 41., 50., 51., 60., 61})
122 .build()
123 << DataContainer{1., 2., 3., 4., 5., 6.}
124 << Components{{10., 20., 30., 40., 50., 60.},
125 {11., 21., 31., 41., 51., 61}};
126
127 QTest::newRow(
128 "unsortedMerge (merge not made because the two data series have different y-axes)")
129 << SpectrogramBuilder{}
130 .setX({4., 5., 6.})
131 .setY({1., 2.})
132 .setValues({40., 41., 50., 51., 60., 61})
133 .build()
134 << SpectrogramBuilder{}
135 .setX({1., 2., 3.})
136 .setY({3., 4.})
137 .setValues({10., 11., 20., 21., 30., 31})
138 .build()
139 << DataContainer{4., 5., 6.} << Components{{40., 50., 60.}, {41., 51., 61}};
140
141 QTest::newRow(
142 "unsortedMerge (unsortedMerge (merge is made because the two data series have the same "
143 "y-axis)")
144 << SpectrogramBuilder{}
145 .setX({4., 5., 6.})
146 .setY({1., 2.})
147 .setValues({40., 41., 50., 51., 60., 61})
148 .build()
149 << SpectrogramBuilder{}
150 .setX({1., 2., 3.})
151 .setY({1., 2.})
152 .setValues({10., 11., 20., 21., 30., 31})
153 .build()
154 << DataContainer{1., 2., 3., 4., 5., 6.}
155 << Components{{10., 20., 30., 40., 50., 60.}, {11., 21., 31., 41., 51., 61}};
156 }
157
158 void TestSpectrogramSeries::testMerge()
159 {
160 testMerge_t<SpectrogramSeries, Components>();
161 }
162
163 void TestSpectrogramSeries::testSubDataSeries_data()
164 {
165 testSubDataSeries_struct<SpectrogramSeries, Components>();
166
167 QTest::newRow("subDataSeries (the range includes all data)")
168 << SpectrogramBuilder{}
169 .setX({1., 2., 3.})
170 .setY({1., 2.})
171 .setValues({10., 11., 20., 21., 30., 31})
172 .build()
173 << SqpRange{0., 5.} << DataContainer{1., 2., 3.}
174 << Components{{10., 20., 30.}, {11., 21., 31.}};
175
176 QTest::newRow("subDataSeries (the range includes no data)")
177 << SpectrogramBuilder{}
178 .setX({1., 2., 3.})
179 .setY({1., 2.})
180 .setValues({10., 11., 20., 21., 30., 31})
181 .build()
182 << SqpRange{4., 5.} << DataContainer{} << Components{{}, {}};
183
184 QTest::newRow("subDataSeries (the range includes some data)")
185 << SpectrogramBuilder{}
186 .setX({1., 2., 3.})
187 .setY({1., 2.})
188 .setValues({10., 11., 20., 21., 30., 31})
189 .build()
190 << SqpRange{1.1, 3} << DataContainer{2., 3.} << Components{{20., 30.}, {21., 31.}};
191 }
192
193 void TestSpectrogramSeries::testSubDataSeries()
194 {
195 testSubDataSeries_t<SpectrogramSeries, Components>();
196 }
197
198 QTEST_MAIN(TestSpectrogramSeries)
199 #include "TestSpectrogramSeries.moc"
@@ -0,0 +1,90
1 #include "Data/VectorSeries.h"
2
3 #include "DataSeriesBuilders.h"
4 #include "DataSeriesUtils.h"
5
6 #include <QObject>
7 #include <QtTest>
8
9 /**
10 * @brief The TestVectorSeries class defines unit tests on vector series.
11 *
12 * Most of these unit tests use generic tests defined for DataSeries (@sa DataSeriesUtils)
13 */
14 class TestVectorSeries : public QObject {
15 Q_OBJECT
16 private slots:
17 /// Tests purge of a vector series
18 void testPurge_data();
19 void testPurge();
20
21 /// Tests get values bounds of a vector series
22 void testValuesBounds_data();
23 void testValuesBounds();
24 };
25
26 void TestVectorSeries::testPurge_data()
27 {
28 testPurge_struct<VectorSeries>();
29
30 QTest::newRow("purgeVector") << VectorBuilder{}
31 .setX({1., 2., 3., 4., 5.})
32 .setXValues({6., 7., 8., 9., 10.})
33 .setYValues({11., 12., 13., 14., 15.})
34 .setZValues({16., 17., 18., 19., 20.})
35 .build()
36 << 2. << 4. << DataContainer{2., 3., 4.}
37 << std::vector<DataContainer>{
38 {7., 8., 9.}, {12., 13., 14.}, {17., 18., 19.}};
39 }
40
41 void TestVectorSeries::testPurge()
42 {
43 testPurge_t<VectorSeries>();
44 }
45
46 void TestVectorSeries::testValuesBounds_data()
47 {
48 testValuesBounds_struct<VectorSeries>();
49
50 auto nan = std::numeric_limits<double>::quiet_NaN();
51
52 QTest::newRow("vectorBounds1") << VectorBuilder{}
53 .setX({1., 2., 3., 4., 5.})
54 .setXValues({10., 15., 20., 13., 12.})
55 .setYValues({35., 24., 10., 9., 0.3})
56 .setZValues({13., 14., 12., 9., 24.})
57 .build()
58 << 0. << 6. << true << 0.3 << 35.; // min/max in same component
59 QTest::newRow("vectorBounds2") << VectorBuilder{}
60 .setX({1., 2., 3., 4., 5.})
61 .setXValues({2.3, 15., 20., 13., 12.})
62 .setYValues({35., 24., 10., 9., 4.})
63 .setZValues({13., 14., 12., 9., 24.})
64 .build()
65 << 0. << 6. << true << 2.3 << 35.; // min/max in same entry
66 QTest::newRow("vectorBounds3") << VectorBuilder{}
67 .setX({1., 2., 3., 4., 5.})
68 .setXValues({2.3, 15., 20., 13., 12.})
69 .setYValues({35., 24., 10., 9., 4.})
70 .setZValues({13., 14., 12., 9., 24.})
71 .build()
72 << 2. << 3. << true << 10. << 24.;
73
74 // Tests with NaN values: NaN values are not included in min/max search
75 QTest::newRow("vectorBounds4") << VectorBuilder{}
76 .setX({1., 2.})
77 .setXValues({nan, nan})
78 .setYValues({nan, nan})
79 .setZValues({nan, nan})
80 .build()
81 << 0. << 6. << true << nan << nan;
82 }
83
84 void TestVectorSeries::testValuesBounds()
85 {
86 testValuesBounds_t<VectorSeries>();
87 }
88
89 QTEST_MAIN(TestVectorSeries)
90 #include "TestVectorSeries.moc"
@@ -1,374 +1,373
1 1 #ifndef SCIQLOP_ARRAYDATA_H
2 2 #define SCIQLOP_ARRAYDATA_H
3 3
4 4 #include "Data/ArrayDataIterator.h"
5 5 #include <Common/SortUtils.h>
6 6
7 7 #include <QReadLocker>
8 8 #include <QReadWriteLock>
9 9 #include <QVector>
10 10
11 11 #include <memory>
12 12
13 13 template <int Dim>
14 14 class ArrayData;
15 15
16 16 using DataContainer = std::vector<double>;
17 17
18 18 namespace arraydata_detail {
19 19
20 20 /// Struct used to sort ArrayData
21 21 template <int Dim>
22 22 struct Sort {
23 23 static std::shared_ptr<ArrayData<Dim> > sort(const DataContainer &data, int nbComponents,
24 24 const std::vector<int> &sortPermutation)
25 25 {
26 26 return std::make_shared<ArrayData<Dim> >(
27 27 SortUtils::sort(data, nbComponents, sortPermutation), nbComponents);
28 28 }
29 29 };
30 30
31 31 /// Specialization for uni-dimensional ArrayData
32 32 template <>
33 33 struct Sort<1> {
34 34 static std::shared_ptr<ArrayData<1> > sort(const DataContainer &data, int nbComponents,
35 35 const std::vector<int> &sortPermutation)
36 36 {
37 37 Q_UNUSED(nbComponents)
38 38 return std::make_shared<ArrayData<1> >(SortUtils::sort(data, 1, sortPermutation));
39 39 }
40 40 };
41 41
42 42 template <int Dim, bool IsConst>
43 43 class IteratorValue;
44 44
45 45 template <int Dim, bool IsConst>
46 46 struct IteratorValueBuilder {
47 47 };
48 48
49 49 template <int Dim>
50 50 struct IteratorValueBuilder<Dim, true> {
51 51 using DataContainerIterator = DataContainer::const_iterator;
52 52
53 53 static void swap(IteratorValue<Dim, true> &o1, IteratorValue<Dim, true> &o2) {}
54 54 };
55 55
56 56 template <int Dim>
57 57 struct IteratorValueBuilder<Dim, false> {
58 58 using DataContainerIterator = DataContainer::iterator;
59 59
60 60 static void swap(IteratorValue<Dim, false> &o1, IteratorValue<Dim, false> &o2)
61 61 {
62 62 for (auto i = 0; i < o1.m_NbComponents; ++i) {
63 63 std::iter_swap(o1.m_It + i, o2.m_It + i);
64 64 }
65 65 }
66 66 };
67 67
68 68 template <int Dim, bool IsConst>
69 69 class IteratorValue : public ArrayDataIteratorValue::Impl {
70 70 public:
71 71 friend class ArrayData<Dim>;
72 72 friend class IteratorValueBuilder<Dim, IsConst>;
73 73
74 74 using DataContainerIterator =
75 75 typename IteratorValueBuilder<Dim, IsConst>::DataContainerIterator;
76 76
77 77 template <bool IC = IsConst, typename = std::enable_if_t<IC == true> >
78 78 explicit IteratorValue(const DataContainer &container, int nbComponents, bool begin)
79 79 : m_It{begin ? container.cbegin() : container.cend()}, m_NbComponents{nbComponents}
80 80 {
81 81 }
82 82
83 83 template <bool IC = IsConst, typename = std::enable_if_t<IC == false> >
84 84 explicit IteratorValue(DataContainer &container, int nbComponents, bool begin)
85 85 : m_It{begin ? container.begin() : container.end()}, m_NbComponents{nbComponents}
86 86 {
87 87 }
88 88
89 89 IteratorValue(const IteratorValue &other) = default;
90 90
91 91 std::unique_ptr<ArrayDataIteratorValue::Impl> clone() const override
92 92 {
93 93 return std::make_unique<IteratorValue<Dim, IsConst> >(*this);
94 94 }
95 95
96 96 int distance(const ArrayDataIteratorValue::Impl &other) const override try {
97 /// @todo ALX : validate
98 97 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
99 98 return std::distance(otherImpl.m_It, m_It) / m_NbComponents;
100 99 }
101 100 catch (const std::bad_cast &) {
102 101 return 0;
103 102 }
104 103
105 104 bool equals(const ArrayDataIteratorValue::Impl &other) const override try {
106 105 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
107 106 return std::tie(m_It, m_NbComponents) == std::tie(otherImpl.m_It, otherImpl.m_NbComponents);
108 107 }
109 108 catch (const std::bad_cast &) {
110 109 return false;
111 110 }
112 111
113 112 bool lowerThan(const ArrayDataIteratorValue::Impl &other) const override try {
114 113 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
115 114 return m_It < otherImpl.m_It;
116 115 }
117 116 catch (const std::bad_cast &) {
118 117 return false;
119 118 }
120 119
121 120 std::unique_ptr<ArrayDataIteratorValue::Impl> advance(int offset) const override
122 121 {
123 122 auto result = clone();
124 123 result->next(offset);
125 124 return result;
126 125 }
127 126
128 127 void next(int offset) override { std::advance(m_It, offset * m_NbComponents); }
129 128 void prev() override { std::advance(m_It, -m_NbComponents); }
130 129
131 130 double at(int componentIndex) const override { return *(m_It + componentIndex); }
132 131 double first() const override { return *m_It; }
133 132 double min() const override
134 133 {
135 134 auto values = this->values();
136 135 auto end = values.cend();
137 136 auto it = std::min_element(values.cbegin(), end, [](const auto &v1, const auto &v2) {
138 137 return SortUtils::minCompareWithNaN(v1, v2);
139 138 });
140 139
141 140 return it != end ? *it : std::numeric_limits<double>::quiet_NaN();
142 141 }
143 142 double max() const override
144 143 {
145 144 auto values = this->values();
146 145 auto end = values.cend();
147 146 auto it = std::max_element(values.cbegin(), end, [](const auto &v1, const auto &v2) {
148 147 return SortUtils::maxCompareWithNaN(v1, v2);
149 148 });
150 149 return it != end ? *it : std::numeric_limits<double>::quiet_NaN();
151 150 }
152 151
153 152 QVector<double> values() const override
154 153 {
155 154 auto result = QVector<double>{};
156 155 for (auto i = 0; i < m_NbComponents; ++i) {
157 156 result.push_back(*(m_It + i));
158 157 }
159 158
160 159 return result;
161 160 }
162 161
163 162 void swap(ArrayDataIteratorValue::Impl &other) override
164 163 {
165 164 auto &otherImpl = dynamic_cast<IteratorValue &>(other);
166 165 IteratorValueBuilder<Dim, IsConst>::swap(*this, otherImpl);
167 166 }
168 167
169 168 private:
170 169 DataContainerIterator m_It;
171 170 int m_NbComponents;
172 171 };
173 172
174 173 } // namespace arraydata_detail
175 174
176 175 /**
177 176 * @brief The ArrayData class represents a dataset for a data series.
178 177 *
179 178 * A dataset can be unidimensional or two-dimensional. This property is determined by the Dim
180 179 * template-parameter. In a case of a two-dimensional dataset, each dataset component has the same
181 180 * number of values
182 181 *
183 182 * @tparam Dim the dimension of the ArrayData (one or two)
184 183 * @sa IDataSeries
185 184 */
186 185 template <int Dim>
187 186 class ArrayData {
188 187 public:
189 188 // ///// //
190 189 // Ctors //
191 190 // ///// //
192 191
193 192 /**
194 193 * Ctor for a unidimensional ArrayData
195 194 * @param data the data the ArrayData will hold
196 195 */
197 196 template <int D = Dim, typename = std::enable_if_t<D == 1> >
198 197 explicit ArrayData(DataContainer data) : m_Data{std::move(data)}, m_NbComponents{1}
199 198 {
200 199 }
201 200
202 201 /**
203 202 * Ctor for a two-dimensional ArrayData. The number of components (number of lines) must be
204 203 * greater than 2 and must be a divisor of the total number of data in the vector
205 204 * @param data the data the ArrayData will hold
206 205 * @param nbComponents the number of components
207 206 * @throws std::invalid_argument if the number of components is less than 2 or is not a divisor
208 207 * of the size of the data
209 208 */
210 209 template <int D = Dim, typename = std::enable_if_t<D == 2> >
211 210 explicit ArrayData(DataContainer data, int nbComponents)
212 211 : m_Data{std::move(data)}, m_NbComponents{nbComponents}
213 212 {
214 213 if (nbComponents < 2) {
215 214 throw std::invalid_argument{
216 215 QString{"A multidimensional ArrayData must have at least 2 components (found: %1)"}
217 216 .arg(nbComponents)
218 217 .toStdString()};
219 218 }
220 219
221 220 if (m_Data.size() % m_NbComponents != 0) {
222 221 throw std::invalid_argument{QString{
223 222 "The number of components (%1) is inconsistent with the total number of data (%2)"}
224 223 .arg(m_Data.size(), nbComponents)
225 224 .toStdString()};
226 225 }
227 226 }
228 227
229 228 /// Copy ctor
230 229 explicit ArrayData(const ArrayData &other)
231 230 {
232 231 QReadLocker otherLocker{&other.m_Lock};
233 232 m_Data = other.m_Data;
234 233 m_NbComponents = other.m_NbComponents;
235 234 }
236 235
237 236 // /////////////// //
238 237 // General methods //
239 238 // /////////////// //
240 239
241 240 /**
242 241 * Merges into the array data an other array data. The two array datas must have the same number
243 242 * of components so the merge can be done
244 243 * @param other the array data to merge with
245 244 * @param prepend if true, the other array data is inserted at the beginning, otherwise it is
246 245 * inserted at the end
247 246 */
248 247 void add(const ArrayData<Dim> &other, bool prepend = false)
249 248 {
250 249 QWriteLocker locker{&m_Lock};
251 250 QReadLocker otherLocker{&other.m_Lock};
252 251
253 252 if (m_NbComponents != other.componentCount()) {
254 253 return;
255 254 }
256 255
257 256 insert(other.cbegin(), other.cend(), prepend);
258 257 }
259 258
260 259 void clear()
261 260 {
262 261 QWriteLocker locker{&m_Lock};
263 262 m_Data.clear();
264 263 }
265 264
266 265 int componentCount() const noexcept { return m_NbComponents; }
267 266
268 267 /// @return the size (i.e. number of values) of a single component
269 268 /// @remarks in a case of a two-dimensional ArrayData, each component has the same size
270 269 int size() const
271 270 {
272 271 QReadLocker locker{&m_Lock};
273 272 return m_Data.size() / m_NbComponents;
274 273 }
275 274
276 275 /// @return the total size (i.e. number of values) of the array data
277 276 int totalSize() const
278 277 {
279 278 QReadLocker locker{&m_Lock};
280 279 return m_Data.size();
281 280 }
282 281
283 282 std::shared_ptr<ArrayData<Dim> > sort(const std::vector<int> &sortPermutation)
284 283 {
285 284 QReadLocker locker{&m_Lock};
286 285 return arraydata_detail::Sort<Dim>::sort(m_Data, m_NbComponents, sortPermutation);
287 286 }
288 287
289 288 // ///////// //
290 289 // Iterators //
291 290 // ///////// //
292 291
293 292 ArrayDataIterator begin()
294 293 {
295 294 return ArrayDataIterator{
296 295 ArrayDataIteratorValue{std::make_unique<arraydata_detail::IteratorValue<Dim, false> >(
297 296 m_Data, m_NbComponents, true)}};
298 297 }
299 298
300 299 ArrayDataIterator end()
301 300 {
302 301 return ArrayDataIterator{
303 302 ArrayDataIteratorValue{std::make_unique<arraydata_detail::IteratorValue<Dim, false> >(
304 303 m_Data, m_NbComponents, false)}};
305 304 }
306 305
307 306 ArrayDataIterator cbegin() const
308 307 {
309 308 return ArrayDataIterator{
310 309 ArrayDataIteratorValue{std::make_unique<arraydata_detail::IteratorValue<Dim, true> >(
311 310 m_Data, m_NbComponents, true)}};
312 311 }
313 312
314 313 ArrayDataIterator cend() const
315 314 {
316 315 return ArrayDataIterator{
317 316 ArrayDataIteratorValue{std::make_unique<arraydata_detail::IteratorValue<Dim, true> >(
318 317 m_Data, m_NbComponents, false)}};
319 318 }
320 319
321 320 void erase(ArrayDataIterator first, ArrayDataIterator last)
322 321 {
323 322 auto firstImpl = dynamic_cast<arraydata_detail::IteratorValue<Dim, false> *>(first->impl());
324 323 auto lastImpl = dynamic_cast<arraydata_detail::IteratorValue<Dim, false> *>(last->impl());
325 324
326 325 if (firstImpl && lastImpl) {
327 326 m_Data.erase(firstImpl->m_It, lastImpl->m_It);
328 327 }
329 328 }
330 329
331 330 void insert(ArrayDataIterator first, ArrayDataIterator last, bool prepend = false)
332 331 {
333 332 auto firstImpl = dynamic_cast<arraydata_detail::IteratorValue<Dim, true> *>(first->impl());
334 333 auto lastImpl = dynamic_cast<arraydata_detail::IteratorValue<Dim, true> *>(last->impl());
335 334
336 335 if (firstImpl && lastImpl) {
337 336 auto insertIt = prepend ? m_Data.begin() : m_Data.end();
338 337
339 338 m_Data.insert(insertIt, firstImpl->m_It, lastImpl->m_It);
340 339 }
341 340 }
342 341
343 342 /**
344 343 * @return the data at a specified index
345 344 * @remarks index must be a valid position
346 345 */
347 346 double at(int index) const noexcept
348 347 {
349 348 QReadLocker locker{&m_Lock};
350 349 return m_Data.at(index);
351 350 }
352 351
353 352 // ///////////// //
354 353 // 1-dim methods //
355 354 // ///////////// //
356 355
357 356 /**
358 357 * @return the data as a vector, as a const reference
359 358 * @remarks this method is only available for a unidimensional ArrayData
360 359 */
361 360 template <int D = Dim, typename = std::enable_if_t<D == 1> >
362 361 DataContainer cdata() const noexcept
363 362 {
364 363 return m_Data;
365 364 }
366 365
367 366 private:
368 367 DataContainer m_Data;
369 368 /// Number of components (lines). Is always 1 in a 1-dim ArrayData
370 369 int m_NbComponents;
371 370 mutable QReadWriteLock m_Lock;
372 371 };
373 372
374 373 #endif // SCIQLOP_ARRAYDATA_H
@@ -1,391 +1,482
1 1 #ifndef SCIQLOP_DATASERIES_H
2 2 #define SCIQLOP_DATASERIES_H
3 3
4 4 #include "CoreGlobal.h"
5 5
6 6 #include <Common/SortUtils.h>
7 7
8 8 #include <Data/ArrayData.h>
9 9 #include <Data/DataSeriesMergeHelper.h>
10 10 #include <Data/IDataSeries.h>
11 #include <Data/OptionalAxis.h>
11 12
12 13 #include <QLoggingCategory>
13 14 #include <QReadLocker>
14 15 #include <QReadWriteLock>
15 16 #include <memory>
16 17
17 18 // We don't use the Qt macro since the log is used in the header file, which causes multiple log
18 19 // definitions with inheritance. Inline method is used instead
19 20 inline const QLoggingCategory &LOG_DataSeries()
20 21 {
21 22 static const QLoggingCategory category{"DataSeries"};
22 23 return category;
23 24 }
24 25
25 26 template <int Dim>
26 27 class DataSeries;
27 28
28 29 namespace dataseries_detail {
29 30
30 31 template <int Dim, bool IsConst>
31 32 class IteratorValue : public DataSeriesIteratorValue::Impl {
32 33 public:
33 34 friend class DataSeries<Dim>;
34 35
35 36 template <bool IC = IsConst, typename = std::enable_if_t<IC == false> >
36 37 explicit IteratorValue(DataSeries<Dim> &dataSeries, bool begin)
37 38 : m_XIt(begin ? dataSeries.xAxisData()->begin() : dataSeries.xAxisData()->end()),
38 39 m_ValuesIt(begin ? dataSeries.valuesData()->begin() : dataSeries.valuesData()->end())
39 40 {
40 41 }
41 42
42 43 template <bool IC = IsConst, typename = std::enable_if_t<IC == true> >
43 44 explicit IteratorValue(const DataSeries<Dim> &dataSeries, bool begin)
44 45 : m_XIt(begin ? dataSeries.xAxisData()->cbegin() : dataSeries.xAxisData()->cend()),
45 46 m_ValuesIt(begin ? dataSeries.valuesData()->cbegin()
46 47 : dataSeries.valuesData()->cend())
47 48 {
48 49 }
49 50
50 51 IteratorValue(const IteratorValue &other) = default;
51 52
52 53 std::unique_ptr<DataSeriesIteratorValue::Impl> clone() const override
53 54 {
54 55 return std::make_unique<IteratorValue<Dim, IsConst> >(*this);
55 56 }
56 57
57 58 int distance(const DataSeriesIteratorValue::Impl &other) const override try {
58 59 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
59 60 return m_XIt->distance(*otherImpl.m_XIt);
60 61 }
61 62 catch (const std::bad_cast &) {
62 63 return 0;
63 64 }
64 65
65 66 bool equals(const DataSeriesIteratorValue::Impl &other) const override try {
66 67 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
67 68 return std::tie(m_XIt, m_ValuesIt) == std::tie(otherImpl.m_XIt, otherImpl.m_ValuesIt);
68 69 }
69 70 catch (const std::bad_cast &) {
70 71 return false;
71 72 }
72 73
73 74 bool lowerThan(const DataSeriesIteratorValue::Impl &other) const override try {
74 75 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
75 76 return m_XIt->lowerThan(*otherImpl.m_XIt);
76 77 }
77 78 catch (const std::bad_cast &) {
78 79 return false;
79 80 }
80 81
81 82 std::unique_ptr<DataSeriesIteratorValue::Impl> advance(int offset) const override
82 83 {
83 84 auto result = clone();
84 85 result->next(offset);
85 86 return result;
86 87 }
87 88
88 89 void next(int offset) override
89 90 {
90 91 m_XIt->next(offset);
91 92 m_ValuesIt->next(offset);
92 93 }
93 94
94 95 void prev() override
95 96 {
96 97 --m_XIt;
97 98 --m_ValuesIt;
98 99 }
99 100
100 101 double x() const override { return m_XIt->at(0); }
101 102 double value() const override { return m_ValuesIt->at(0); }
102 103 double value(int componentIndex) const override { return m_ValuesIt->at(componentIndex); }
103 104 double minValue() const override { return m_ValuesIt->min(); }
104 105 double maxValue() const override { return m_ValuesIt->max(); }
105 106 QVector<double> values() const override { return m_ValuesIt->values(); }
106 107
107 108 void swap(DataSeriesIteratorValue::Impl &other) override
108 109 {
109 110 auto &otherImpl = dynamic_cast<IteratorValue &>(other);
110 111 m_XIt->impl()->swap(*otherImpl.m_XIt->impl());
111 112 m_ValuesIt->impl()->swap(*otherImpl.m_ValuesIt->impl());
112 113 }
113 114
114 115 private:
115 116 ArrayDataIterator m_XIt;
116 117 ArrayDataIterator m_ValuesIt;
117 118 };
118 119 } // namespace dataseries_detail
119 120
120 121 /**
121 122 * @brief The DataSeries class is the base (abstract) implementation of IDataSeries.
122 123 *
123 * It proposes to set a dimension for the values ​​data.
124 * The DataSeries represents values on one or two axes, according to these rules:
125 * - the x-axis is always defined
126 * - an y-axis can be defined or not. If set, additional consistency checks apply to the values (see
127 * below)
128 * - the values are defined on one or two dimensions. In the case of 2-dim values, the data is
129 * distributed into components (for example, a vector defines three components)
130 * - New values can be added to the series, on the x-axis.
131 * - Once initialized to the series creation, the y-axis (if defined) is no longer modifiable
132 * - Data representing values and axes are associated with a unit
133 * - The data series is always sorted in ascending order on the x-axis.
124 134 *
125 * A DataSeries is always sorted on its x-axis data.
135 * Consistency checks are carried out between the axes and the values. These controls are provided
136 * throughout the DataSeries lifecycle:
137 * - the number of data on the x-axis must be equal to the number of values (in the case of
138 * 2-dim ArrayData for values, the test is performed on the number of values per component)
139 * - if the y-axis is defined, the number of components of the ArrayData for values must equal the
140 * number of data on the y-axis.
141 *
142 * Examples:
143 * 1)
144 * - x-axis: [1 ; 2 ; 3]
145 * - y-axis: not defined
146 * - values: [10 ; 20 ; 30] (1-dim ArrayData)
147 * => the DataSeries is valid, as x-axis and values have the same number of data
148 *
149 * 2)
150 * - x-axis: [1 ; 2 ; 3]
151 * - y-axis: not defined
152 * - values: [10 ; 20 ; 30 ; 40] (1-dim ArrayData)
153 * => the DataSeries is invalid, as x-axis and values haven't the same number of data
154 *
155 * 3)
156 * - x-axis: [1 ; 2 ; 3]
157 * - y-axis: not defined
158 * - values: [10 ; 20 ; 30
159 * 40 ; 50 ; 60] (2-dim ArrayData)
160 * => the DataSeries is valid, as x-axis has 3 data and values contains 2 components with 3
161 * data each
162 *
163 * 4)
164 * - x-axis: [1 ; 2 ; 3]
165 * - y-axis: [1 ; 2]
166 * - values: [10 ; 20 ; 30
167 * 40 ; 50 ; 60] (2-dim ArrayData)
168 * => the DataSeries is valid, as:
169 * - x-axis has 3 data and values contains 2 components with 3 data each AND
170 * - y-axis has 2 data and values contains 2 components
171 *
172 * 5)
173 * - x-axis: [1 ; 2 ; 3]
174 * - y-axis: [1 ; 2 ; 3]
175 * - values: [10 ; 20 ; 30
176 * 40 ; 50 ; 60] (2-dim ArrayData)
177 * => the DataSeries is invalid, as:
178 * - x-axis has 3 data and values contains 2 components with 3 data each BUT
179 * - y-axis has 3 data and values contains only 2 components
126 180 *
127 181 * @tparam Dim The dimension of the values data
128 182 *
129 183 */
130 184 template <int Dim>
131 185 class SCIQLOP_CORE_EXPORT DataSeries : public IDataSeries {
132 186 friend class DataSeriesMergeHelper;
133 187
134 188 public:
135 189 /// @sa IDataSeries::xAxisData()
136 190 std::shared_ptr<ArrayData<1> > xAxisData() override { return m_XAxisData; }
137 191 const std::shared_ptr<ArrayData<1> > xAxisData() const { return m_XAxisData; }
138 192
139 193 /// @sa IDataSeries::xAxisUnit()
140 194 Unit xAxisUnit() const override { return m_XAxisUnit; }
141 195
142 196 /// @return the values dataset
143 197 std::shared_ptr<ArrayData<Dim> > valuesData() { return m_ValuesData; }
144 198 const std::shared_ptr<ArrayData<Dim> > valuesData() const { return m_ValuesData; }
145 199
146 200 /// @sa IDataSeries::valuesUnit()
147 201 Unit valuesUnit() const override { return m_ValuesUnit; }
148 202
149 int nbPoints() const override { return m_XAxisData->totalSize() + m_ValuesData->totalSize(); }
203 int nbPoints() const override { return m_ValuesData->totalSize(); }
150 204
151 205 void clear()
152 206 {
153 207 m_XAxisData->clear();
154 208 m_ValuesData->clear();
155 209 }
156 210
157 211 bool isEmpty() const noexcept { return m_XAxisData->size() == 0; }
158 212
159 /// Merges into the data series an other data series
213 /// Merges into the data series an other data series.
214 ///
215 /// The two dataseries:
216 /// - must be of the same dimension
217 /// - must have the same y-axis (if defined)
218 ///
219 /// If the prerequisites are not valid, the method does nothing
220 ///
160 221 /// @remarks the data series to merge with is cleared after the operation
161 222 void merge(IDataSeries *dataSeries) override
162 223 {
163 224 dataSeries->lockWrite();
164 225 lockWrite();
165 226
166 227 if (auto other = dynamic_cast<DataSeries<Dim> *>(dataSeries)) {
228 if (m_YAxis == other->m_YAxis) {
167 229 DataSeriesMergeHelper::merge(*other, *this);
168 230 }
169 231 else {
170 232 qCWarning(LOG_DataSeries())
233 << QObject::tr("Can't merge data series that have not the same y-axis");
234 }
235 }
236 else {
237 qCWarning(LOG_DataSeries())
171 238 << QObject::tr("Detection of a type of IDataSeries we cannot merge with !");
172 239 }
173 240 unlock();
174 241 dataSeries->unlock();
175 242 }
176 243
177 244 void purge(double min, double max) override
178 245 {
179 246 // Nothing to purge if series is empty
180 247 if (isEmpty()) {
181 248 return;
182 249 }
183 250
184 251 if (min > max) {
185 252 std::swap(min, max);
186 253 }
187 254
188 255 // Nothing to purge if series min/max are inside purge range
189 256 auto xMin = cbegin()->x();
190 257 auto xMax = (--cend())->x();
191 258 if (xMin >= min && xMax <= max) {
192 259 return;
193 260 }
194 261
195 262 auto lowerIt = std::lower_bound(
196 263 begin(), end(), min, [](const auto &it, const auto &val) { return it.x() < val; });
197 264 erase(begin(), lowerIt);
198 265 auto upperIt = std::upper_bound(
199 266 begin(), end(), max, [](const auto &val, const auto &it) { return val < it.x(); });
200 267 erase(upperIt, end());
201 268 }
202 269
203 270 // ///////// //
204 271 // Iterators //
205 272 // ///////// //
206 273
207 274 DataSeriesIterator begin() override
208 275 {
209 276 return DataSeriesIterator{DataSeriesIteratorValue{
210 277 std::make_unique<dataseries_detail::IteratorValue<Dim, false> >(*this, true)}};
211 278 }
212 279
213 280 DataSeriesIterator end() override
214 281 {
215 282 return DataSeriesIterator{DataSeriesIteratorValue{
216 283 std::make_unique<dataseries_detail::IteratorValue<Dim, false> >(*this, false)}};
217 284 }
218 285
219 286 DataSeriesIterator cbegin() const override
220 287 {
221 288 return DataSeriesIterator{DataSeriesIteratorValue{
222 289 std::make_unique<dataseries_detail::IteratorValue<Dim, true> >(*this, true)}};
223 290 }
224 291
225 292 DataSeriesIterator cend() const override
226 293 {
227 294 return DataSeriesIterator{DataSeriesIteratorValue{
228 295 std::make_unique<dataseries_detail::IteratorValue<Dim, true> >(*this, false)}};
229 296 }
230 297
231 298 void erase(DataSeriesIterator first, DataSeriesIterator last)
232 299 {
233 300 auto firstImpl
234 301 = dynamic_cast<dataseries_detail::IteratorValue<Dim, false> *>(first->impl());
235 302 auto lastImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, false> *>(last->impl());
236 303
237 304 if (firstImpl && lastImpl) {
238 305 m_XAxisData->erase(firstImpl->m_XIt, lastImpl->m_XIt);
239 306 m_ValuesData->erase(firstImpl->m_ValuesIt, lastImpl->m_ValuesIt);
240 307 }
241 308 }
242 309
243 310 void insert(DataSeriesIterator first, DataSeriesIterator last, bool prepend = false)
244 311 {
245 312 auto firstImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, true> *>(first->impl());
246 313 auto lastImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, true> *>(last->impl());
247 314
248 315 if (firstImpl && lastImpl) {
249 316 m_XAxisData->insert(firstImpl->m_XIt, lastImpl->m_XIt, prepend);
250 317 m_ValuesData->insert(firstImpl->m_ValuesIt, lastImpl->m_ValuesIt, prepend);
251 318 }
252 319 }
253 320
254 321 /// @sa IDataSeries::minXAxisData()
255 322 DataSeriesIterator minXAxisData(double minXAxisData) const override
256 323 {
257 324 return std::lower_bound(
258 325 cbegin(), cend(), minXAxisData,
259 326 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
260 327 }
261 328
262 329 /// @sa IDataSeries::maxXAxisData()
263 330 DataSeriesIterator maxXAxisData(double maxXAxisData) const override
264 331 {
265 332 // Gets the first element that greater than max value
266 333 auto it = std::upper_bound(
267 334 cbegin(), cend(), maxXAxisData,
268 335 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
269 336
270 337 return it == cbegin() ? cend() : --it;
271 338 }
272 339
273 340 std::pair<DataSeriesIterator, DataSeriesIterator> xAxisRange(double minXAxisData,
274 341 double maxXAxisData) const override
275 342 {
276 343 if (minXAxisData > maxXAxisData) {
277 344 std::swap(minXAxisData, maxXAxisData);
278 345 }
279 346
280 347 auto begin = cbegin();
281 348 auto end = cend();
282 349
283 350 auto lowerIt = std::lower_bound(
284 351 begin, end, minXAxisData,
285 352 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
286 353 auto upperIt = std::upper_bound(
287 354 lowerIt, end, maxXAxisData,
288 355 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
289 356
290 357 return std::make_pair(lowerIt, upperIt);
291 358 }
292 359
293 360 std::pair<DataSeriesIterator, DataSeriesIterator>
294 361 valuesBounds(double minXAxisData, double maxXAxisData) const override
295 362 {
296 363 // Places iterators to the correct x-axis range
297 364 auto xAxisRangeIts = xAxisRange(minXAxisData, maxXAxisData);
298 365
299 366 // Returns end iterators if the range is empty
300 367 if (xAxisRangeIts.first == xAxisRangeIts.second) {
301 368 return std::make_pair(cend(), cend());
302 369 }
303 370
304 371 // Gets the iterator on the min of all values data
305 372 auto minIt = std::min_element(
306 373 xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) {
307 374 return SortUtils::minCompareWithNaN(it1.minValue(), it2.minValue());
308 375 });
309 376
310 377 // Gets the iterator on the max of all values data
311 378 auto maxIt = std::max_element(
312 379 xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) {
313 380 return SortUtils::maxCompareWithNaN(it1.maxValue(), it2.maxValue());
314 381 });
315 382
316 383 return std::make_pair(minIt, maxIt);
317 384 }
318 385
319 386 // /////// //
320 387 // Mutexes //
321 388 // /////// //
322 389
323 390 virtual void lockRead() { m_Lock.lockForRead(); }
324 391 virtual void lockWrite() { m_Lock.lockForWrite(); }
325 392 virtual void unlock() { m_Lock.unlock(); }
326 393
327 394 protected:
328 /// Protected ctor (DataSeries is abstract). The vectors must have the same size, otherwise a
329 /// DataSeries with no values will be created.
395 /// Protected ctor (DataSeries is abstract).
396 ///
397 /// Data vectors must be consistent with each other, otherwise an exception will be thrown (@sa
398 /// class description for consistent rules)
330 399 /// @remarks data series is automatically sorted on its x-axis data
400 /// @throws std::invalid_argument if the data are inconsistent with each other
331 401 explicit DataSeries(std::shared_ptr<ArrayData<1> > xAxisData, const Unit &xAxisUnit,
332 std::shared_ptr<ArrayData<Dim> > valuesData, const Unit &valuesUnit)
402 std::shared_ptr<ArrayData<Dim> > valuesData, const Unit &valuesUnit,
403 OptionalAxis yAxis = OptionalAxis{})
333 404 : m_XAxisData{xAxisData},
334 405 m_XAxisUnit{xAxisUnit},
335 406 m_ValuesData{valuesData},
336 m_ValuesUnit{valuesUnit}
407 m_ValuesUnit{valuesUnit},
408 m_YAxis{std::move(yAxis)}
337 409 {
338 410 if (m_XAxisData->size() != m_ValuesData->size()) {
339 clear();
411 throw std::invalid_argument{
412 "The number of values by component must be equal to the number of x-axis data"};
413 }
414
415 // Validates y-axis (if defined)
416 if (yAxis.isDefined() && (yAxis.size() != m_ValuesData->componentCount())) {
417 throw std::invalid_argument{
418 "As the y-axis is defined, the number of value components must be equal to the "
419 "number of y-axis data"};
340 420 }
341 421
342 422 // Sorts data if it's not the case
343 423 const auto &xAxisCData = m_XAxisData->cdata();
344 424 if (!std::is_sorted(xAxisCData.cbegin(), xAxisCData.cend())) {
345 425 sort();
346 426 }
347 427 }
348 428
349 429 /// Copy ctor
350 430 explicit DataSeries(const DataSeries<Dim> &other)
351 431 : m_XAxisData{std::make_shared<ArrayData<1> >(*other.m_XAxisData)},
352 432 m_XAxisUnit{other.m_XAxisUnit},
353 433 m_ValuesData{std::make_shared<ArrayData<Dim> >(*other.m_ValuesData)},
354 m_ValuesUnit{other.m_ValuesUnit}
434 m_ValuesUnit{other.m_ValuesUnit},
435 m_YAxis{other.m_YAxis}
355 436 {
356 437 // Since a series is ordered from its construction and is always ordered, it is not
357 438 // necessary to call the sort method here ('other' is sorted)
358 439 }
359 440
441 /// @return the y-axis associated to the data series
442 OptionalAxis yAxis() const { return m_YAxis; }
443
360 444 /// Assignment operator
361 445 template <int D>
362 446 DataSeries &operator=(DataSeries<D> other)
363 447 {
364 448 std::swap(m_XAxisData, other.m_XAxisData);
365 449 std::swap(m_XAxisUnit, other.m_XAxisUnit);
366 450 std::swap(m_ValuesData, other.m_ValuesData);
367 451 std::swap(m_ValuesUnit, other.m_ValuesUnit);
452 std::swap(m_YAxis, other.m_YAxis);
368 453
369 454 return *this;
370 455 }
371 456
372 457 private:
373 458 /**
374 459 * Sorts data series on its x-axis data
375 460 */
376 461 void sort() noexcept
377 462 {
378 463 auto permutation = SortUtils::sortPermutation(*m_XAxisData, std::less<double>());
379 464 m_XAxisData = m_XAxisData->sort(permutation);
380 465 m_ValuesData = m_ValuesData->sort(permutation);
381 466 }
382 467
468 // x-axis
383 469 std::shared_ptr<ArrayData<1> > m_XAxisData;
384 470 Unit m_XAxisUnit;
471
472 // values
385 473 std::shared_ptr<ArrayData<Dim> > m_ValuesData;
386 474 Unit m_ValuesUnit;
387 475
476 // y-axis (optional)
477 OptionalAxis m_YAxis;
478
388 479 QReadWriteLock m_Lock;
389 480 };
390 481
391 482 #endif // SCIQLOP_DATASERIES_H
@@ -1,111 +1,96
1 1 #ifndef SCIQLOP_IDATASERIES_H
2 2 #define SCIQLOP_IDATASERIES_H
3 3
4 4 #include <Common/MetaTypes.h>
5 5 #include <Data/DataSeriesIterator.h>
6 6 #include <Data/SqpRange.h>
7 #include <Data/Unit.h>
7 8
8 9 #include <memory>
9 10
10 11 #include <QString>
11 12
12 13 template <int Dim>
13 14 class ArrayData;
14 15
15 struct Unit {
16 explicit Unit(const QString &name = {}, bool timeUnit = false)
17 : m_Name{name}, m_TimeUnit{timeUnit}
18 {
19 }
20
21 inline bool operator==(const Unit &other) const
22 {
23 return std::tie(m_Name, m_TimeUnit) == std::tie(other.m_Name, other.m_TimeUnit);
24 }
25 inline bool operator!=(const Unit &other) const { return !(*this == other); }
26
27 QString m_Name; ///< Unit name
28 bool m_TimeUnit; ///< The unit is a unit of time (UTC)
29 };
30
31 16 /**
32 17 * @brief The IDataSeries aims to declare a data series.
33 18 *
34 19 * A data series is an entity that contains at least :
35 20 * - one dataset representing the x-axis
36 21 * - one dataset representing the values
37 22 *
38 23 * Each dataset is represented by an ArrayData, and is associated with a unit.
39 24 *
40 25 * An ArrayData can be unidimensional or two-dimensional, depending on the implementation of the
41 26 * IDataSeries. The x-axis dataset is always unidimensional.
42 27 *
43 28 * @sa ArrayData
44 29 */
45 30 class IDataSeries {
46 31 public:
47 32 virtual ~IDataSeries() noexcept = default;
48 33
49 34 /// Returns the x-axis dataset
50 35 virtual std::shared_ptr<ArrayData<1> > xAxisData() = 0;
51 36
52 37 /// Returns the x-axis dataset (as const)
53 38 virtual const std::shared_ptr<ArrayData<1> > xAxisData() const = 0;
54 39
55 40 virtual Unit xAxisUnit() const = 0;
56 41
57 42 virtual Unit valuesUnit() const = 0;
58 43
59 44 virtual void merge(IDataSeries *dataSeries) = 0;
60 45 /// Removes from data series all entries whose value on the x-axis is not between min and max
61 46 virtual void purge(double min, double max) = 0;
62 47
63 48 /// @todo Review the name and signature of this method
64 49 virtual std::shared_ptr<IDataSeries> subDataSeries(const SqpRange &range) = 0;
65 50
66 51 virtual std::unique_ptr<IDataSeries> clone() const = 0;
67 52
68 53 /// @return the total number of points contained in the data series
69 54 virtual int nbPoints() const = 0;
70 55
71 56 // ///////// //
72 57 // Iterators //
73 58 // ///////// //
74 59
75 60 virtual DataSeriesIterator cbegin() const = 0;
76 61 virtual DataSeriesIterator cend() const = 0;
77 62 virtual DataSeriesIterator begin() = 0;
78 63 virtual DataSeriesIterator end() = 0;
79 64
80 65 /// @return the iterator to the first entry of the data series whose x-axis data is greater than
81 66 /// or equal to the value passed in parameter, or the end iterator if there is no matching value
82 67 virtual DataSeriesIterator minXAxisData(double minXAxisData) const = 0;
83 68
84 69 /// @return the iterator to the last entry of the data series whose x-axis data is less than or
85 70 /// equal to the value passed in parameter, or the end iterator if there is no matching value
86 71 virtual DataSeriesIterator maxXAxisData(double maxXAxisData) const = 0;
87 72
88 73 /// @return the iterators pointing to the range of data whose x-axis values are between min and
89 74 /// max passed in parameters
90 75 virtual std::pair<DataSeriesIterator, DataSeriesIterator>
91 76 xAxisRange(double minXAxisData, double maxXAxisData) const = 0;
92 77
93 78 /// @return two iterators pointing to the data that have respectively the min and the max value
94 79 /// data of a data series' range. The search is performed for a given x-axis range.
95 80 /// @sa xAxisRange()
96 81 virtual std::pair<DataSeriesIterator, DataSeriesIterator>
97 82 valuesBounds(double minXAxisData, double maxXAxisData) const = 0;
98 83
99 84 // /////// //
100 85 // Mutexes //
101 86 // /////// //
102 87
103 88 virtual void lockRead() = 0;
104 89 virtual void lockWrite() = 0;
105 90 virtual void unlock() = 0;
106 91 };
107 92
108 93 // Required for using shared_ptr in signals/slots
109 94 SCIQLOP_REGISTER_META_TYPE(IDATASERIES_PTR_REGISTRY, std::shared_ptr<IDataSeries>)
110 95
111 96 #endif // SCIQLOP_IDATASERIES_H
@@ -1,63 +1,65
1 1
2 2 core_moc_headers = [
3 3 'include/Data/IDataProvider.h',
4 4 'include/DataSource/DataSourceController.h',
5 5 'include/DataSource/DataSourceItemAction.h',
6 6 'include/Network/NetworkController.h',
7 7 'include/Time/TimeController.h',
8 8 'include/Variable/Variable.h',
9 9 'include/Variable/VariableCacheController.h',
10 10 'include/Variable/VariableController.h',
11 11 'include/Variable/VariableAcquisitionWorker.h',
12 12 'include/Variable/VariableCacheStrategy.h',
13 13 'include/Variable/VariableSynchronizationGroup.h',
14 14 'include/Variable/VariableModel.h',
15 15 'include/Visualization/VisualizationController.h'
16 16 ]
17 17
18 18
19 19 core_moc_files = qt5.preprocess(moc_headers : core_moc_headers)
20 20
21 21 core_sources = [
22 22 'src/Common/DateUtils.cpp',
23 23 'src/Common/StringUtils.cpp',
24 24 'src/Common/MimeTypesDef.cpp',
25 25 'src/Data/ScalarSeries.cpp',
26 'src/Data/SpectrogramSeries.cpp',
26 27 'src/Data/DataSeriesIterator.cpp',
27 28 'src/Data/ArrayDataIterator.cpp',
28 29 'src/Data/VectorSeries.cpp',
30 'src/Data/OptionalAxis.cpp',
29 31 'src/DataSource/DataSourceController.cpp',
30 32 'src/DataSource/DataSourceItem.cpp',
31 33 'src/DataSource/DataSourceItemAction.cpp',
32 34 'src/Network/NetworkController.cpp',
33 35 'src/Plugin/PluginManager.cpp',
34 36 'src/Settings/SqpSettingsDefs.cpp',
35 37 'src/Time/TimeController.cpp',
36 38 'src/Variable/Variable.cpp',
37 39 'src/Variable/VariableCacheController.cpp',
38 40 'src/Variable/VariableController.cpp',
39 41 'src/Variable/VariableAcquisitionWorker.cpp',
40 42 'src/Variable/VariableSynchronizationGroup.cpp',
41 43 'src/Variable/VariableModel.cpp',
42 44 'src/Visualization/VisualizationController.cpp'
43 45 ]
44 46
45 47 core_inc = include_directories(['include', '../plugin/include'])
46 48
47 49 sciqlop_core_lib = library('sciqlopcore',
48 50 core_sources,
49 51 core_moc_files,
50 52 cpp_args : '-DCORE_LIB',
51 53 include_directories : core_inc,
52 54 dependencies : [qt5core, qt5network],
53 55 install : true
54 56 )
55 57
56 58
57 59 sciqlop_core = declare_dependency(link_with : sciqlop_core_lib,
58 60 include_directories : core_inc,
59 61 dependencies : [qt5core, qt5network])
60 62
61 63
62 64 subdir('tests')
63 65
@@ -1,409 +1,409
1 1 #include <Variable/Variable.h>
2 2
3 3 #include <Data/ScalarSeries.h>
4 4
5 5 #include <QObject>
6 6 #include <QtTest>
7 7
8 8 #include <memory>
9 9
10 10 namespace {
11 11
12 12 /// Generates a date in double
13 13 auto date = [](int year, int month, int day, int hours, int minutes, int seconds) {
14 14 return DateUtils::secondsSinceEpoch(
15 15 QDateTime{{year, month, day}, {hours, minutes, seconds}, Qt::UTC});
16 16 };
17 17
18 18 /// Generates a series of test data for a range
19 19 std::shared_ptr<ScalarSeries> dataSeries(const SqpRange &range)
20 20 {
21 21 auto xAxisData = std::vector<double>{};
22 22 auto valuesData = std::vector<double>{};
23 23
24 24 auto value = 0;
25 25 for (auto x = range.m_TStart; x <= range.m_TEnd; ++x, ++value) {
26 26 xAxisData.push_back(x);
27 27 valuesData.push_back(value);
28 28 }
29 29
30 30 return std::make_shared<ScalarSeries>(std::move(xAxisData), std::move(valuesData), Unit{},
31 31 Unit{});
32 32 }
33 33
34 34 } // namespace
35 35
36 36 Q_DECLARE_METATYPE(std::shared_ptr<ScalarSeries>)
37 37
38 38 class TestVariable : public QObject {
39 39 Q_OBJECT
40 40
41 41 private slots:
42 42 void testClone_data();
43 43 void testClone();
44 44
45 45 void testNotInCacheRangeList();
46 46 void testInCacheRangeList();
47 47
48 48 void testNbPoints_data();
49 49 void testNbPoints();
50 50
51 51 void testRealRange_data();
52 52 void testRealRange();
53 53 };
54 54
55 55 void TestVariable::testClone_data()
56 56 {
57 57 // ////////////// //
58 58 // Test structure //
59 59 // ////////////// //
60 60
61 61 QTest::addColumn<QString>("name");
62 62 QTest::addColumn<QVariantHash>("metadata");
63 63 QTest::addColumn<SqpRange>("range");
64 64 QTest::addColumn<SqpRange>("cacheRange");
65 65 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
66 66
67 67 // ////////// //
68 68 // Test cases //
69 69 // ////////// //
70 70
71 71 auto cacheRange = SqpRange{date(2017, 1, 1, 12, 0, 0), date(2017, 1, 1, 13, 0, 0)};
72 72 QTest::newRow("clone1") << QStringLiteral("var1")
73 73 << QVariantHash{{"data1", 1}, {"data2", "abc"}}
74 74 << SqpRange{date(2017, 1, 1, 12, 30, 0), (date(2017, 1, 1, 12, 45, 0))}
75 75 << cacheRange << dataSeries(cacheRange);
76 76 }
77 77
78 78 void TestVariable::testClone()
79 79 {
80 80 // Creates variable
81 81 QFETCH(QString, name);
82 82 QFETCH(QVariantHash, metadata);
83 83 QFETCH(SqpRange, range);
84 84 QFETCH(SqpRange, cacheRange);
85 85 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
86 86
87 87 Variable variable{name, metadata};
88 88 variable.setRange(range);
89 89 variable.setCacheRange(cacheRange);
90 90 variable.mergeDataSeries(dataSeries);
91 91
92 92 // Clones variable
93 93 auto clone = variable.clone();
94 94
95 95 // Checks cloned variable's state
96 96 QCOMPARE(clone->name(), name);
97 97 QCOMPARE(clone->metadata(), metadata);
98 98 QCOMPARE(clone->range(), range);
99 99 QCOMPARE(clone->cacheRange(), cacheRange);
100 100
101 101 // Compares data series
102 102 if (dataSeries != nullptr) {
103 103 QVERIFY(clone->dataSeries() != nullptr);
104 104 QVERIFY(std::equal(dataSeries->cbegin(), dataSeries->cend(), clone->dataSeries()->cbegin(),
105 105 clone->dataSeries()->cend(), [](const auto &it1, const auto &it2) {
106 106 return it1.x() == it2.x() && it1.value() == it2.value();
107 107 }));
108 108 }
109 109 else {
110 110 QVERIFY(clone->dataSeries() == nullptr);
111 111 }
112 112 }
113 113
114 114 void TestVariable::testNotInCacheRangeList()
115 115 {
116 116 auto varRS = QDateTime{QDate{2017, 01, 01}, QTime{2, 3, 20, 0}};
117 117 auto varRE = QDateTime{QDate{2017, 01, 01}, QTime{2, 3, 40, 0}};
118 118
119 119 auto sqpR = SqpRange{DateUtils::secondsSinceEpoch(varRS), DateUtils::secondsSinceEpoch(varRE)};
120 120
121 121 auto varCRS = QDateTime{QDate{2017, 01, 01}, QTime{2, 3, 0, 0}};
122 122 auto varCRE = QDateTime{QDate{2017, 01, 01}, QTime{2, 4, 0, 0}};
123 123
124 124 auto sqpCR
125 125 = SqpRange{DateUtils::secondsSinceEpoch(varCRS), DateUtils::secondsSinceEpoch(varCRE)};
126 126
127 127 Variable var{"Var test"};
128 128 var.setRange(sqpR);
129 129 var.setCacheRange(sqpCR);
130 130
131 131 // 1: [ts,te] < varTS
132 132 auto ts = QDateTime{QDate{2017, 01, 01}, QTime{2, 0, 0, 0}};
133 133 auto te = QDateTime{QDate{2017, 01, 01}, QTime{2, 1, 0, 0}};
134 134 auto sqp = SqpRange{DateUtils::secondsSinceEpoch(ts), DateUtils::secondsSinceEpoch(te)};
135 135
136 136 auto notInCach = var.provideNotInCacheRangeList(sqp);
137 137
138 138 QCOMPARE(notInCach.size(), 1);
139 139
140 140 auto notInCachRange = notInCach.first();
141 141
142 142 QCOMPARE(notInCachRange.m_TStart, DateUtils::secondsSinceEpoch(ts));
143 143 QCOMPARE(notInCachRange.m_TEnd, DateUtils::secondsSinceEpoch(te));
144 144
145 145 // 2: ts < varTS < te < varTE
146 146 ts = QDateTime{QDate{2017, 01, 01}, QTime{2, 0, 0, 0}};
147 147 te = QDateTime{QDate{2017, 01, 01}, QTime{2, 3, 30, 0}};
148 148 sqp = SqpRange{DateUtils::secondsSinceEpoch(ts), DateUtils::secondsSinceEpoch(te)};
149 149 notInCach = var.provideNotInCacheRangeList(sqp);
150 150 QCOMPARE(notInCach.size(), 1);
151 151 notInCachRange = notInCach.first();
152 152 QCOMPARE(notInCachRange.m_TStart, DateUtils::secondsSinceEpoch(ts));
153 153 QCOMPARE(notInCachRange.m_TEnd, DateUtils::secondsSinceEpoch(varCRS));
154 154
155 155 // 3: varTS < ts < te < varTE
156 156 ts = QDateTime{QDate{2017, 01, 01}, QTime{2, 3, 20, 0}};
157 157 te = QDateTime{QDate{2017, 01, 01}, QTime{2, 3, 30, 0}};
158 158 sqp = SqpRange{DateUtils::secondsSinceEpoch(ts), DateUtils::secondsSinceEpoch(te)};
159 159 notInCach = var.provideNotInCacheRangeList(sqp);
160 160 QCOMPARE(notInCach.size(), 0);
161 161
162 162
163 163 // 4: varTS < ts < varTE < te
164 164 ts = QDateTime{QDate{2017, 01, 01}, QTime{2, 3, 20, 0}};
165 165 te = QDateTime{QDate{2017, 01, 01}, QTime{2, 5, 0, 0}};
166 166 sqp = SqpRange{DateUtils::secondsSinceEpoch(ts), DateUtils::secondsSinceEpoch(te)};
167 167 notInCach = var.provideNotInCacheRangeList(sqp);
168 168 QCOMPARE(notInCach.size(), 1);
169 169 notInCachRange = notInCach.first();
170 170 QCOMPARE(notInCachRange.m_TStart, DateUtils::secondsSinceEpoch(varCRE));
171 171 QCOMPARE(notInCachRange.m_TEnd, DateUtils::secondsSinceEpoch(te));
172 172
173 173 // 5: varTS < varTE < ts < te
174 174 ts = QDateTime{QDate{2017, 01, 01}, QTime{2, 4, 20, 0}};
175 175 te = QDateTime{QDate{2017, 01, 01}, QTime{2, 5, 0, 0}};
176 176 sqp = SqpRange{DateUtils::secondsSinceEpoch(ts), DateUtils::secondsSinceEpoch(te)};
177 177 notInCach = var.provideNotInCacheRangeList(sqp);
178 178 QCOMPARE(notInCach.size(), 1);
179 179 notInCachRange = notInCach.first();
180 180 QCOMPARE(notInCachRange.m_TStart, DateUtils::secondsSinceEpoch(ts));
181 181 QCOMPARE(notInCachRange.m_TEnd, DateUtils::secondsSinceEpoch(te));
182 182
183 183 // 6: ts <varTS < varTE < te
184 184 ts = QDateTime{QDate{2017, 01, 01}, QTime{2, 1, 0, 0}};
185 185 te = QDateTime{QDate{2017, 01, 01}, QTime{2, 5, 0, 0}};
186 186 sqp = SqpRange{DateUtils::secondsSinceEpoch(ts), DateUtils::secondsSinceEpoch(te)};
187 187 notInCach = var.provideNotInCacheRangeList(sqp);
188 188 QCOMPARE(notInCach.size(), 2);
189 189 notInCachRange = notInCach.first();
190 190 QCOMPARE(notInCachRange.m_TStart, DateUtils::secondsSinceEpoch(ts));
191 191 QCOMPARE(notInCachRange.m_TEnd, DateUtils::secondsSinceEpoch(varCRS));
192 192 notInCachRange = notInCach[1];
193 193 QCOMPARE(notInCachRange.m_TStart, DateUtils::secondsSinceEpoch(varCRE));
194 194 QCOMPARE(notInCachRange.m_TEnd, DateUtils::secondsSinceEpoch(te));
195 195 }
196 196
197 197
198 198 void TestVariable::testInCacheRangeList()
199 199 {
200 200 auto varRS = QDateTime{QDate{2017, 01, 01}, QTime{2, 3, 20, 0}};
201 201 auto varRE = QDateTime{QDate{2017, 01, 01}, QTime{2, 3, 40, 0}};
202 202
203 203 auto sqpR = SqpRange{DateUtils::secondsSinceEpoch(varRS), DateUtils::secondsSinceEpoch(varRE)};
204 204
205 205 auto varCRS = QDateTime{QDate{2017, 01, 01}, QTime{2, 3, 0, 0}};
206 206 auto varCRE = QDateTime{QDate{2017, 01, 01}, QTime{2, 4, 0, 0}};
207 207 auto sqpCR
208 208 = SqpRange{DateUtils::secondsSinceEpoch(varCRS), DateUtils::secondsSinceEpoch(varCRE)};
209 209
210 210 Variable var{"Var test"};
211 211 var.setRange(sqpR);
212 212 var.setCacheRange(sqpCR);
213 213
214 214 // 1: [ts,te] < varTS
215 215 auto ts = QDateTime{QDate{2017, 01, 01}, QTime{2, 0, 0, 0}};
216 216 auto te = QDateTime{QDate{2017, 01, 01}, QTime{2, 1, 0, 0}};
217 217 auto sqp = SqpRange{DateUtils::secondsSinceEpoch(ts), DateUtils::secondsSinceEpoch(te)};
218 218
219 219 auto notInCach = var.provideInCacheRangeList(sqp);
220 220
221 221 QCOMPARE(notInCach.size(), 0);
222 222
223 223 // 2: ts < varTS < te < varTE
224 224 ts = QDateTime{QDate{2017, 01, 01}, QTime{2, 0, 0, 0}};
225 225 te = QDateTime{QDate{2017, 01, 01}, QTime{2, 3, 30, 0}};
226 226 sqp = SqpRange{DateUtils::secondsSinceEpoch(ts), DateUtils::secondsSinceEpoch(te)};
227 227 notInCach = var.provideInCacheRangeList(sqp);
228 228 QCOMPARE(notInCach.size(), 1);
229 229 auto notInCachRange = notInCach.first();
230 230 QCOMPARE(notInCachRange.m_TStart, DateUtils::secondsSinceEpoch(varCRS));
231 231 QCOMPARE(notInCachRange.m_TEnd, DateUtils::secondsSinceEpoch(te));
232 232
233 233 // 3: varTS < ts < te < varTE
234 234 ts = QDateTime{QDate{2017, 01, 01}, QTime{2, 3, 20, 0}};
235 235 te = QDateTime{QDate{2017, 01, 01}, QTime{2, 3, 30, 0}};
236 236 sqp = SqpRange{DateUtils::secondsSinceEpoch(ts), DateUtils::secondsSinceEpoch(te)};
237 237 notInCach = var.provideInCacheRangeList(sqp);
238 238 QCOMPARE(notInCach.size(), 1);
239 239 notInCachRange = notInCach.first();
240 240 QCOMPARE(notInCachRange.m_TStart, DateUtils::secondsSinceEpoch(ts));
241 241 QCOMPARE(notInCachRange.m_TEnd, DateUtils::secondsSinceEpoch(te));
242 242
243 243 // 4: varTS < ts < varTE < te
244 244 ts = QDateTime{QDate{2017, 01, 01}, QTime{2, 3, 20, 0}};
245 245 te = QDateTime{QDate{2017, 01, 01}, QTime{2, 5, 0, 0}};
246 246 sqp = SqpRange{DateUtils::secondsSinceEpoch(ts), DateUtils::secondsSinceEpoch(te)};
247 247 notInCach = var.provideInCacheRangeList(sqp);
248 248 QCOMPARE(notInCach.size(), 1);
249 249 notInCachRange = notInCach.first();
250 250 QCOMPARE(notInCachRange.m_TStart, DateUtils::secondsSinceEpoch(ts));
251 251 QCOMPARE(notInCachRange.m_TEnd, DateUtils::secondsSinceEpoch(varCRE));
252 252
253 253 // 5: varTS < varTE < ts < te
254 254 ts = QDateTime{QDate{2017, 01, 01}, QTime{2, 4, 20, 0}};
255 255 te = QDateTime{QDate{2017, 01, 01}, QTime{2, 5, 0, 0}};
256 256 sqp = SqpRange{DateUtils::secondsSinceEpoch(ts), DateUtils::secondsSinceEpoch(te)};
257 257 notInCach = var.provideInCacheRangeList(sqp);
258 258 QCOMPARE(notInCach.size(), 0);
259 259
260 260 // 6: ts <varTS < varTE < te
261 261 ts = QDateTime{QDate{2017, 01, 01}, QTime{2, 1, 0, 0}};
262 262 te = QDateTime{QDate{2017, 01, 01}, QTime{2, 5, 0, 0}};
263 263 sqp = SqpRange{DateUtils::secondsSinceEpoch(ts), DateUtils::secondsSinceEpoch(te)};
264 264 notInCach = var.provideInCacheRangeList(sqp);
265 265 QCOMPARE(notInCach.size(), 1);
266 266 notInCachRange = notInCach.first();
267 267 QCOMPARE(notInCachRange.m_TStart, DateUtils::secondsSinceEpoch(varCRS));
268 268 QCOMPARE(notInCachRange.m_TEnd, DateUtils::secondsSinceEpoch(varCRE));
269 269 }
270 270
271 271 namespace {
272 272
273 273 /// Struct used to represent an operation for @sa TestVariable::testNbPoints()
274 274 struct NbPointsOperation {
275 275 SqpRange m_CacheRange; /// Range to set for the variable
276 276 std::shared_ptr<ScalarSeries> m_DataSeries; /// Series to merge in the variable
277 277 int m_ExpectedNbPoints; /// Number of points in the variable expected after operation
278 278 };
279 279
280 280 using NbPointsOperations = std::vector<NbPointsOperation>;
281 281
282 282 } // namespace
283 283
284 284 Q_DECLARE_METATYPE(NbPointsOperations)
285 285
286 286 void TestVariable::testNbPoints_data()
287 287 {
288 288 // ////////////// //
289 289 // Test structure //
290 290 // ////////////// //
291 291
292 292 QTest::addColumn<NbPointsOperations>("operations");
293 293
294 294 // ////////// //
295 295 // Test cases //
296 296 // ////////// //
297 297 NbPointsOperations operations{};
298 298
299 // Sets cache range (expected nb points = series xAxis data + series values data)
299 // Sets cache range (expected nb points = values data)
300 300 auto cacheRange = SqpRange{date(2017, 1, 1, 12, 0, 0), date(2017, 1, 1, 12, 0, 9)};
301 operations.push_back({cacheRange, dataSeries(cacheRange), 20});
301 operations.push_back({cacheRange, dataSeries(cacheRange), 10});
302 302
303 303 // Doubles cache but don't add data series (expected nb points don't change)
304 304 cacheRange = SqpRange{date(2017, 1, 1, 12, 0, 0), date(2017, 1, 1, 12, 0, 19)};
305 operations.push_back({cacheRange, dataSeries(INVALID_RANGE), 20});
305 operations.push_back({cacheRange, dataSeries(INVALID_RANGE), 10});
306 306
307 307 // Doubles cache and data series (expected nb points change)
308 308 cacheRange = SqpRange{date(2017, 1, 1, 12, 0, 0), date(2017, 1, 1, 12, 0, 19)};
309 operations.push_back({cacheRange, dataSeries(cacheRange), 40});
309 operations.push_back({cacheRange, dataSeries(cacheRange), 20});
310 310
311 311 // Decreases cache (expected nb points decreases as the series is purged)
312 312 cacheRange = SqpRange{date(2017, 1, 1, 12, 0, 5), date(2017, 1, 1, 12, 0, 9)};
313 operations.push_back({cacheRange, dataSeries(INVALID_RANGE), 10});
313 operations.push_back({cacheRange, dataSeries(INVALID_RANGE), 5});
314 314
315 315 QTest::newRow("nbPoints1") << operations;
316 316 }
317 317
318 318 void TestVariable::testNbPoints()
319 319 {
320 320 // Creates variable
321 321 Variable variable{"var"};
322 322 QCOMPARE(variable.nbPoints(), 0);
323 323
324 324 QFETCH(NbPointsOperations, operations);
325 325 for (const auto &operation : operations) {
326 326 // Sets cache range and merge data series
327 327 variable.setCacheRange(operation.m_CacheRange);
328 328 if (operation.m_DataSeries != nullptr) {
329 329 variable.mergeDataSeries(operation.m_DataSeries);
330 330 }
331 331
332 332 // Checks nb points
333 333 QCOMPARE(variable.nbPoints(), operation.m_ExpectedNbPoints);
334 334 }
335 335 }
336 336
337 337 namespace {
338 338
339 339 /// Struct used to represent a range operation on a variable
340 340 /// @sa TestVariable::testRealRange()
341 341 struct RangeOperation {
342 342 SqpRange m_CacheRange; /// Range to set for the variable
343 343 std::shared_ptr<ScalarSeries> m_DataSeries; /// Series to merge in the variable
344 344 SqpRange m_ExpectedRealRange; /// Real Range expected after operation on the variable
345 345 };
346 346
347 347 using RangeOperations = std::vector<RangeOperation>;
348 348
349 349 } // namespace
350 350
351 351 Q_DECLARE_METATYPE(RangeOperations)
352 352
353 353 void TestVariable::testRealRange_data()
354 354 {
355 355 // ////////////// //
356 356 // Test structure //
357 357 // ////////////// //
358 358
359 359 QTest::addColumn<RangeOperations>("operations");
360 360
361 361 // ////////// //
362 362 // Test cases //
363 363 // ////////// //
364 364 RangeOperations operations{};
365 365
366 366 // Inits cache range and data series (expected real range = cache range)
367 367 auto cacheRange = SqpRange{date(2017, 1, 1, 12, 0, 0), date(2017, 1, 1, 13, 0, 0)};
368 368 operations.push_back({cacheRange, dataSeries(cacheRange), cacheRange});
369 369
370 370 // Changes cache range and updates data series (expected real range = cache range)
371 371 cacheRange = SqpRange{date(2017, 1, 1, 14, 0, 0), date(2017, 1, 1, 15, 0, 0)};
372 372 operations.push_back({cacheRange, dataSeries(cacheRange), cacheRange});
373 373
374 374 // Changes cache range and update data series but with a lower range (expected real range =
375 375 // data series range)
376 376 cacheRange = SqpRange{date(2017, 1, 1, 12, 0, 0), date(2017, 1, 1, 16, 0, 0)};
377 377 auto dataSeriesRange = SqpRange{date(2017, 1, 1, 14, 0, 0), date(2017, 1, 1, 15, 0, 0)};
378 378 operations.push_back({cacheRange, dataSeries(dataSeriesRange), dataSeriesRange});
379 379
380 380 // Changes cache range but DON'T update data series (expected real range = cache range
381 381 // before operation)
382 382 cacheRange = SqpRange{date(2017, 1, 1, 10, 0, 0), date(2017, 1, 1, 17, 0, 0)};
383 383 operations.push_back({cacheRange, nullptr, dataSeriesRange});
384 384
385 385 QTest::newRow("realRange1") << operations;
386 386 }
387 387
388 388 void TestVariable::testRealRange()
389 389 {
390 390 // Creates variable (real range is invalid)
391 391 Variable variable{"var"};
392 392 QCOMPARE(variable.realRange(), INVALID_RANGE);
393 393
394 394 QFETCH(RangeOperations, operations);
395 395 for (const auto &operation : operations) {
396 396 // Sets cache range and merge data series
397 397 variable.setCacheRange(operation.m_CacheRange);
398 398 if (operation.m_DataSeries != nullptr) {
399 399 variable.mergeDataSeries(operation.m_DataSeries);
400 400 }
401 401
402 402 // Checks real range
403 403 QCOMPARE(variable.realRange(), operation.m_ExpectedRealRange);
404 404 }
405 405 }
406 406
407 407
408 408 QTEST_MAIN(TestVariable)
409 409 #include "TestVariable.moc"
@@ -1,20 +1,23
1 1
2 2
3 3 tests = [
4 4 [['Common/TestStringUtils.cpp'],'test_string_utils','StringUtils test'],
5 [['Data/TestDataSeries.cpp'],'test_data','DataSeries test'],
5 [['Data/TestScalarSeries.cpp'],'test_scalar','ScalarSeries test'],
6 [['Data/TestSpectrogramSeries.cpp'],'test_spectrogram','SpectrogramSeries test'],
7 [['Data/TestVectorSeries.cpp'],'test_vector','VectorSeries test'],
6 8 [['Data/TestOneDimArrayData.cpp'],'test_1d','One Dim Array test'],
9 [['Data/TestOptionalAxis.cpp'],'test_optional_axis','OptionalAxis test'],
7 10 [['Data/TestTwoDimArrayData.cpp'],'test_2d','Two Dim Array test'],
8 11 [['DataSource/TestDataSourceController.cpp'],'test_data_source','DataSourceController test'],
9 12 [['Variable/TestVariableCacheController.cpp'],'test_variable_cache','VariableCacheController test'],
10 13 [['Variable/TestVariable.cpp'],'test_variable','Variable test'],
11 14 [['Variable/TestVariableSync.cpp'],'test_variable_sync','Variable synchronization test']
12 15 ]
13 16
14 17 foreach unit_test : tests
15 18 test_moc_files = qt5.preprocess(moc_sources : unit_test[0])
16 19 test_exe = executable(unit_test[1],unit_test[0] , test_moc_files,
17 20 dependencies : [sciqlop_core, qt5test])
18 21 test(unit_test[2], test_exe, args: ['-teamcity', '-o', '@0@.teamcity.txt'.format(unit_test[1])])
19 22 endforeach
20 23
1 NO CONTENT: file was removed
This diff has been collapsed as it changes many lines, (707 lines changed) Show them Hide them
General Comments 0
You need to be logged in to leave comments. Login now