##// END OF EJS Templates
Minor changes...
Alexandre Leroux -
r864:551a1137cf04
parent child
Show More
@@ -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
@@ -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,391
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 11
12 12 #include <QLoggingCategory>
13 13 #include <QReadLocker>
14 14 #include <QReadWriteLock>
15 15 #include <memory>
16 16
17 17 // We don't use the Qt macro since the log is used in the header file, which causes multiple log
18 18 // definitions with inheritance. Inline method is used instead
19 19 inline const QLoggingCategory &LOG_DataSeries()
20 20 {
21 21 static const QLoggingCategory category{"DataSeries"};
22 22 return category;
23 23 }
24 24
25 25 template <int Dim>
26 26 class DataSeries;
27 27
28 28 namespace dataseries_detail {
29 29
30 30 template <int Dim, bool IsConst>
31 31 class IteratorValue : public DataSeriesIteratorValue::Impl {
32 32 public:
33 33 friend class DataSeries<Dim>;
34 34
35 35 template <bool IC = IsConst, typename = std::enable_if_t<IC == false> >
36 36 explicit IteratorValue(DataSeries<Dim> &dataSeries, bool begin)
37 37 : m_XIt(begin ? dataSeries.xAxisData()->begin() : dataSeries.xAxisData()->end()),
38 38 m_ValuesIt(begin ? dataSeries.valuesData()->begin() : dataSeries.valuesData()->end())
39 39 {
40 40 }
41 41
42 42 template <bool IC = IsConst, typename = std::enable_if_t<IC == true> >
43 43 explicit IteratorValue(const DataSeries<Dim> &dataSeries, bool begin)
44 44 : m_XIt(begin ? dataSeries.xAxisData()->cbegin() : dataSeries.xAxisData()->cend()),
45 45 m_ValuesIt(begin ? dataSeries.valuesData()->cbegin()
46 46 : dataSeries.valuesData()->cend())
47 47 {
48 48 }
49 49
50 50 IteratorValue(const IteratorValue &other) = default;
51 51
52 52 std::unique_ptr<DataSeriesIteratorValue::Impl> clone() const override
53 53 {
54 54 return std::make_unique<IteratorValue<Dim, IsConst> >(*this);
55 55 }
56 56
57 57 int distance(const DataSeriesIteratorValue::Impl &other) const override try {
58 58 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
59 59 return m_XIt->distance(*otherImpl.m_XIt);
60 60 }
61 61 catch (const std::bad_cast &) {
62 62 return 0;
63 63 }
64 64
65 65 bool equals(const DataSeriesIteratorValue::Impl &other) const override try {
66 66 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
67 67 return std::tie(m_XIt, m_ValuesIt) == std::tie(otherImpl.m_XIt, otherImpl.m_ValuesIt);
68 68 }
69 69 catch (const std::bad_cast &) {
70 70 return false;
71 71 }
72 72
73 73 bool lowerThan(const DataSeriesIteratorValue::Impl &other) const override try {
74 74 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
75 75 return m_XIt->lowerThan(*otherImpl.m_XIt);
76 76 }
77 77 catch (const std::bad_cast &) {
78 78 return false;
79 79 }
80 80
81 81 std::unique_ptr<DataSeriesIteratorValue::Impl> advance(int offset) const override
82 82 {
83 83 auto result = clone();
84 84 result->next(offset);
85 85 return result;
86 86 }
87 87
88 88 void next(int offset) override
89 89 {
90 90 m_XIt->next(offset);
91 91 m_ValuesIt->next(offset);
92 92 }
93 93
94 94 void prev() override
95 95 {
96 96 --m_XIt;
97 97 --m_ValuesIt;
98 98 }
99 99
100 100 double x() const override { return m_XIt->at(0); }
101 101 double value() const override { return m_ValuesIt->at(0); }
102 102 double value(int componentIndex) const override { return m_ValuesIt->at(componentIndex); }
103 103 double minValue() const override { return m_ValuesIt->min(); }
104 104 double maxValue() const override { return m_ValuesIt->max(); }
105 105 QVector<double> values() const override { return m_ValuesIt->values(); }
106 106
107 107 void swap(DataSeriesIteratorValue::Impl &other) override
108 108 {
109 109 auto &otherImpl = dynamic_cast<IteratorValue &>(other);
110 110 m_XIt->impl()->swap(*otherImpl.m_XIt->impl());
111 111 m_ValuesIt->impl()->swap(*otherImpl.m_ValuesIt->impl());
112 112 }
113 113
114 114 private:
115 115 ArrayDataIterator m_XIt;
116 116 ArrayDataIterator m_ValuesIt;
117 117 };
118 118 } // namespace dataseries_detail
119 119
120 120 /**
121 121 * @brief The DataSeries class is the base (abstract) implementation of IDataSeries.
122 122 *
123 123 * It proposes to set a dimension for the values ​​data.
124 124 *
125 125 * A DataSeries is always sorted on its x-axis data.
126 126 *
127 127 * @tparam Dim The dimension of the values data
128 128 *
129 129 */
130 130 template <int Dim>
131 131 class SCIQLOP_CORE_EXPORT DataSeries : public IDataSeries {
132 132 friend class DataSeriesMergeHelper;
133 133
134 134 public:
135 135 /// @sa IDataSeries::xAxisData()
136 136 std::shared_ptr<ArrayData<1> > xAxisData() override { return m_XAxisData; }
137 137 const std::shared_ptr<ArrayData<1> > xAxisData() const { return m_XAxisData; }
138 138
139 139 /// @sa IDataSeries::xAxisUnit()
140 140 Unit xAxisUnit() const override { return m_XAxisUnit; }
141 141
142 142 /// @return the values dataset
143 143 std::shared_ptr<ArrayData<Dim> > valuesData() { return m_ValuesData; }
144 144 const std::shared_ptr<ArrayData<Dim> > valuesData() const { return m_ValuesData; }
145 145
146 146 /// @sa IDataSeries::valuesUnit()
147 147 Unit valuesUnit() const override { return m_ValuesUnit; }
148 148
149 int nbPoints() const override { return m_XAxisData->totalSize() + m_ValuesData->totalSize(); }
149 int nbPoints() const override { return m_ValuesData->totalSize(); }
150 150
151 151 void clear()
152 152 {
153 153 m_XAxisData->clear();
154 154 m_ValuesData->clear();
155 155 }
156 156
157 157 bool isEmpty() const noexcept { return m_XAxisData->size() == 0; }
158 158
159 159 /// Merges into the data series an other data series
160 160 /// @remarks the data series to merge with is cleared after the operation
161 161 void merge(IDataSeries *dataSeries) override
162 162 {
163 163 dataSeries->lockWrite();
164 164 lockWrite();
165 165
166 166 if (auto other = dynamic_cast<DataSeries<Dim> *>(dataSeries)) {
167 167 DataSeriesMergeHelper::merge(*other, *this);
168 168 }
169 169 else {
170 170 qCWarning(LOG_DataSeries())
171 171 << QObject::tr("Detection of a type of IDataSeries we cannot merge with !");
172 172 }
173 173 unlock();
174 174 dataSeries->unlock();
175 175 }
176 176
177 177 void purge(double min, double max) override
178 178 {
179 179 // Nothing to purge if series is empty
180 180 if (isEmpty()) {
181 181 return;
182 182 }
183 183
184 184 if (min > max) {
185 185 std::swap(min, max);
186 186 }
187 187
188 188 // Nothing to purge if series min/max are inside purge range
189 189 auto xMin = cbegin()->x();
190 190 auto xMax = (--cend())->x();
191 191 if (xMin >= min && xMax <= max) {
192 192 return;
193 193 }
194 194
195 195 auto lowerIt = std::lower_bound(
196 196 begin(), end(), min, [](const auto &it, const auto &val) { return it.x() < val; });
197 197 erase(begin(), lowerIt);
198 198 auto upperIt = std::upper_bound(
199 199 begin(), end(), max, [](const auto &val, const auto &it) { return val < it.x(); });
200 200 erase(upperIt, end());
201 201 }
202 202
203 203 // ///////// //
204 204 // Iterators //
205 205 // ///////// //
206 206
207 207 DataSeriesIterator begin() override
208 208 {
209 209 return DataSeriesIterator{DataSeriesIteratorValue{
210 210 std::make_unique<dataseries_detail::IteratorValue<Dim, false> >(*this, true)}};
211 211 }
212 212
213 213 DataSeriesIterator end() override
214 214 {
215 215 return DataSeriesIterator{DataSeriesIteratorValue{
216 216 std::make_unique<dataseries_detail::IteratorValue<Dim, false> >(*this, false)}};
217 217 }
218 218
219 219 DataSeriesIterator cbegin() const override
220 220 {
221 221 return DataSeriesIterator{DataSeriesIteratorValue{
222 222 std::make_unique<dataseries_detail::IteratorValue<Dim, true> >(*this, true)}};
223 223 }
224 224
225 225 DataSeriesIterator cend() const override
226 226 {
227 227 return DataSeriesIterator{DataSeriesIteratorValue{
228 228 std::make_unique<dataseries_detail::IteratorValue<Dim, true> >(*this, false)}};
229 229 }
230 230
231 231 void erase(DataSeriesIterator first, DataSeriesIterator last)
232 232 {
233 233 auto firstImpl
234 234 = dynamic_cast<dataseries_detail::IteratorValue<Dim, false> *>(first->impl());
235 235 auto lastImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, false> *>(last->impl());
236 236
237 237 if (firstImpl && lastImpl) {
238 238 m_XAxisData->erase(firstImpl->m_XIt, lastImpl->m_XIt);
239 239 m_ValuesData->erase(firstImpl->m_ValuesIt, lastImpl->m_ValuesIt);
240 240 }
241 241 }
242 242
243 243 void insert(DataSeriesIterator first, DataSeriesIterator last, bool prepend = false)
244 244 {
245 245 auto firstImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, true> *>(first->impl());
246 246 auto lastImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, true> *>(last->impl());
247 247
248 248 if (firstImpl && lastImpl) {
249 249 m_XAxisData->insert(firstImpl->m_XIt, lastImpl->m_XIt, prepend);
250 250 m_ValuesData->insert(firstImpl->m_ValuesIt, lastImpl->m_ValuesIt, prepend);
251 251 }
252 252 }
253 253
254 254 /// @sa IDataSeries::minXAxisData()
255 255 DataSeriesIterator minXAxisData(double minXAxisData) const override
256 256 {
257 257 return std::lower_bound(
258 258 cbegin(), cend(), minXAxisData,
259 259 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
260 260 }
261 261
262 262 /// @sa IDataSeries::maxXAxisData()
263 263 DataSeriesIterator maxXAxisData(double maxXAxisData) const override
264 264 {
265 265 // Gets the first element that greater than max value
266 266 auto it = std::upper_bound(
267 267 cbegin(), cend(), maxXAxisData,
268 268 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
269 269
270 270 return it == cbegin() ? cend() : --it;
271 271 }
272 272
273 273 std::pair<DataSeriesIterator, DataSeriesIterator> xAxisRange(double minXAxisData,
274 274 double maxXAxisData) const override
275 275 {
276 276 if (minXAxisData > maxXAxisData) {
277 277 std::swap(minXAxisData, maxXAxisData);
278 278 }
279 279
280 280 auto begin = cbegin();
281 281 auto end = cend();
282 282
283 283 auto lowerIt = std::lower_bound(
284 284 begin, end, minXAxisData,
285 285 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
286 286 auto upperIt = std::upper_bound(
287 287 lowerIt, end, maxXAxisData,
288 288 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
289 289
290 290 return std::make_pair(lowerIt, upperIt);
291 291 }
292 292
293 293 std::pair<DataSeriesIterator, DataSeriesIterator>
294 294 valuesBounds(double minXAxisData, double maxXAxisData) const override
295 295 {
296 296 // Places iterators to the correct x-axis range
297 297 auto xAxisRangeIts = xAxisRange(minXAxisData, maxXAxisData);
298 298
299 299 // Returns end iterators if the range is empty
300 300 if (xAxisRangeIts.first == xAxisRangeIts.second) {
301 301 return std::make_pair(cend(), cend());
302 302 }
303 303
304 304 // Gets the iterator on the min of all values data
305 305 auto minIt = std::min_element(
306 306 xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) {
307 307 return SortUtils::minCompareWithNaN(it1.minValue(), it2.minValue());
308 308 });
309 309
310 310 // Gets the iterator on the max of all values data
311 311 auto maxIt = std::max_element(
312 312 xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) {
313 313 return SortUtils::maxCompareWithNaN(it1.maxValue(), it2.maxValue());
314 314 });
315 315
316 316 return std::make_pair(minIt, maxIt);
317 317 }
318 318
319 319 // /////// //
320 320 // Mutexes //
321 321 // /////// //
322 322
323 323 virtual void lockRead() { m_Lock.lockForRead(); }
324 324 virtual void lockWrite() { m_Lock.lockForWrite(); }
325 325 virtual void unlock() { m_Lock.unlock(); }
326 326
327 327 protected:
328 328 /// Protected ctor (DataSeries is abstract). The vectors must have the same size, otherwise a
329 329 /// DataSeries with no values will be created.
330 330 /// @remarks data series is automatically sorted on its x-axis data
331 331 explicit DataSeries(std::shared_ptr<ArrayData<1> > xAxisData, const Unit &xAxisUnit,
332 332 std::shared_ptr<ArrayData<Dim> > valuesData, const Unit &valuesUnit)
333 333 : m_XAxisData{xAxisData},
334 334 m_XAxisUnit{xAxisUnit},
335 335 m_ValuesData{valuesData},
336 336 m_ValuesUnit{valuesUnit}
337 337 {
338 338 if (m_XAxisData->size() != m_ValuesData->size()) {
339 339 clear();
340 340 }
341 341
342 342 // Sorts data if it's not the case
343 343 const auto &xAxisCData = m_XAxisData->cdata();
344 344 if (!std::is_sorted(xAxisCData.cbegin(), xAxisCData.cend())) {
345 345 sort();
346 346 }
347 347 }
348 348
349 349 /// Copy ctor
350 350 explicit DataSeries(const DataSeries<Dim> &other)
351 351 : m_XAxisData{std::make_shared<ArrayData<1> >(*other.m_XAxisData)},
352 352 m_XAxisUnit{other.m_XAxisUnit},
353 353 m_ValuesData{std::make_shared<ArrayData<Dim> >(*other.m_ValuesData)},
354 354 m_ValuesUnit{other.m_ValuesUnit}
355 355 {
356 356 // Since a series is ordered from its construction and is always ordered, it is not
357 357 // necessary to call the sort method here ('other' is sorted)
358 358 }
359 359
360 360 /// Assignment operator
361 361 template <int D>
362 362 DataSeries &operator=(DataSeries<D> other)
363 363 {
364 364 std::swap(m_XAxisData, other.m_XAxisData);
365 365 std::swap(m_XAxisUnit, other.m_XAxisUnit);
366 366 std::swap(m_ValuesData, other.m_ValuesData);
367 367 std::swap(m_ValuesUnit, other.m_ValuesUnit);
368 368
369 369 return *this;
370 370 }
371 371
372 372 private:
373 373 /**
374 374 * Sorts data series on its x-axis data
375 375 */
376 376 void sort() noexcept
377 377 {
378 378 auto permutation = SortUtils::sortPermutation(*m_XAxisData, std::less<double>());
379 379 m_XAxisData = m_XAxisData->sort(permutation);
380 380 m_ValuesData = m_ValuesData->sort(permutation);
381 381 }
382 382
383 383 std::shared_ptr<ArrayData<1> > m_XAxisData;
384 384 Unit m_XAxisUnit;
385 385 std::shared_ptr<ArrayData<Dim> > m_ValuesData;
386 386 Unit m_ValuesUnit;
387 387
388 388 QReadWriteLock m_Lock;
389 389 };
390 390
391 391 #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,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"
General Comments 0
You need to be logged in to leave comments. Login now