##// END OF EJS Templates
Updates access to y-axis properties of the data series (2)...
Alexandre Leroux -
r1031:958f5923ee65
parent child
Show More
@@ -1,488 +1,507
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 #include <Data/OptionalAxis.h>
12 12
13 13 #include <QLoggingCategory>
14 14 #include <QReadLocker>
15 15 #include <QReadWriteLock>
16 16 #include <memory>
17 17
18 18 // We don't use the Qt macro since the log is used in the header file, which causes multiple log
19 19 // definitions with inheritance. Inline method is used instead
20 20 inline const QLoggingCategory &LOG_DataSeries()
21 21 {
22 22 static const QLoggingCategory category{"DataSeries"};
23 23 return category;
24 24 }
25 25
26 26 template <int Dim>
27 27 class DataSeries;
28 28
29 29 namespace dataseries_detail {
30 30
31 31 template <int Dim, bool IsConst>
32 32 class IteratorValue : public DataSeriesIteratorValue::Impl {
33 33 public:
34 34 friend class DataSeries<Dim>;
35 35
36 36 template <bool IC = IsConst, typename = std::enable_if_t<IC == false> >
37 37 explicit IteratorValue(DataSeries<Dim> &dataSeries, bool begin)
38 38 : m_XIt(begin ? dataSeries.xAxisData()->begin() : dataSeries.xAxisData()->end()),
39 m_ValuesIt(begin ? dataSeries.valuesData()->begin() : dataSeries.valuesData()->end())
39 m_ValuesIt(begin ? dataSeries.valuesData()->begin() : dataSeries.valuesData()->end()),
40 m_YItBegin{dataSeries.yAxis().begin()},
41 m_YItEnd{dataSeries.yAxis().end()}
40 42 {
41 43 }
42 44
43 45 template <bool IC = IsConst, typename = std::enable_if_t<IC == true> >
44 46 explicit IteratorValue(const DataSeries<Dim> &dataSeries, bool begin)
45 47 : m_XIt(begin ? dataSeries.xAxisData()->cbegin() : dataSeries.xAxisData()->cend()),
46 48 m_ValuesIt(begin ? dataSeries.valuesData()->cbegin()
47 : dataSeries.valuesData()->cend())
49 : dataSeries.valuesData()->cend()),
50 m_YItBegin{dataSeries.yAxis().cbegin()},
51 m_YItEnd{dataSeries.yAxis().cend()}
48 52 {
49 53 }
50 54
51 55 IteratorValue(const IteratorValue &other) = default;
52 56
53 57 std::unique_ptr<DataSeriesIteratorValue::Impl> clone() const override
54 58 {
55 59 return std::make_unique<IteratorValue<Dim, IsConst> >(*this);
56 60 }
57 61
58 62 int distance(const DataSeriesIteratorValue::Impl &other) const override try {
59 63 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
60 64 return m_XIt->distance(*otherImpl.m_XIt);
61 65 }
62 66 catch (const std::bad_cast &) {
63 67 return 0;
64 68 }
65 69
66 70 bool equals(const DataSeriesIteratorValue::Impl &other) const override try {
67 71 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
68 return std::tie(m_XIt, m_ValuesIt) == std::tie(otherImpl.m_XIt, otherImpl.m_ValuesIt);
72 return std::tie(m_XIt, m_ValuesIt, m_YItBegin, m_YItEnd)
73 == std::tie(otherImpl.m_XIt, otherImpl.m_ValuesIt, otherImpl.m_YItBegin,
74 otherImpl.m_YItEnd);
69 75 }
70 76 catch (const std::bad_cast &) {
71 77 return false;
72 78 }
73 79
74 80 bool lowerThan(const DataSeriesIteratorValue::Impl &other) const override try {
75 81 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
76 82 return m_XIt->lowerThan(*otherImpl.m_XIt);
77 83 }
78 84 catch (const std::bad_cast &) {
79 85 return false;
80 86 }
81 87
82 88 std::unique_ptr<DataSeriesIteratorValue::Impl> advance(int offset) const override
83 89 {
84 90 auto result = clone();
85 91 result->next(offset);
86 92 return result;
87 93 }
88 94
89 95 void next(int offset) override
90 96 {
91 97 m_XIt->next(offset);
92 98 m_ValuesIt->next(offset);
93 99 }
94 100
95 101 void prev() override
96 102 {
97 103 --m_XIt;
98 104 --m_ValuesIt;
99 105 }
100 106
101 107 double x() const override { return m_XIt->at(0); }
108 std::vector<double> y() const override
109 {
110 std::vector<double> result{};
111 std::transform(m_YItBegin, m_YItEnd, std::back_inserter(result),
112 [](const auto &it) { return it.first(); });
113
114 return result;
115 }
116
102 117 double value() const override { return m_ValuesIt->at(0); }
103 118 double value(int componentIndex) const override { return m_ValuesIt->at(componentIndex); }
104 119 double minValue() const override { return m_ValuesIt->min(); }
105 120 double maxValue() const override { return m_ValuesIt->max(); }
106 121 QVector<double> values() const override { return m_ValuesIt->values(); }
107 122
108 123 void swap(DataSeriesIteratorValue::Impl &other) override
109 124 {
110 125 auto &otherImpl = dynamic_cast<IteratorValue &>(other);
111 126 m_XIt->impl()->swap(*otherImpl.m_XIt->impl());
112 127 m_ValuesIt->impl()->swap(*otherImpl.m_ValuesIt->impl());
128 m_YItBegin->impl()->swap(*otherImpl.m_YItBegin->impl());
129 m_YItEnd->impl()->swap(*otherImpl.m_YItEnd->impl());
113 130 }
114 131
115 132 private:
116 133 ArrayDataIterator m_XIt;
117 134 ArrayDataIterator m_ValuesIt;
135 ArrayDataIterator m_YItBegin;
136 ArrayDataIterator m_YItEnd;
118 137 };
119 138 } // namespace dataseries_detail
120 139
121 140 /**
122 141 * @brief The DataSeries class is the base (abstract) implementation of IDataSeries.
123 142 *
124 143 * The DataSeries represents values on one or two axes, according to these rules:
125 144 * - the x-axis is always defined
126 145 * - an y-axis can be defined or not. If set, additional consistency checks apply to the values (see
127 146 * below)
128 147 * - the values are defined on one or two dimensions. In the case of 2-dim values, the data is
129 148 * distributed into components (for example, a vector defines three components)
130 149 * - New values can be added to the series, on the x-axis.
131 150 * - Once initialized to the series creation, the y-axis (if defined) is no longer modifiable
132 151 * - Data representing values and axes are associated with a unit
133 152 * - The data series is always sorted in ascending order on the x-axis.
134 153 *
135 154 * Consistency checks are carried out between the axes and the values. These controls are provided
136 155 * throughout the DataSeries lifecycle:
137 156 * - the number of data on the x-axis must be equal to the number of values (in the case of
138 157 * 2-dim ArrayData for values, the test is performed on the number of values per component)
139 158 * - if the y-axis is defined, the number of components of the ArrayData for values must equal the
140 159 * number of data on the y-axis.
141 160 *
142 161 * Examples:
143 162 * 1)
144 163 * - x-axis: [1 ; 2 ; 3]
145 164 * - y-axis: not defined
146 165 * - values: [10 ; 20 ; 30] (1-dim ArrayData)
147 166 * => the DataSeries is valid, as x-axis and values have the same number of data
148 167 *
149 168 * 2)
150 169 * - x-axis: [1 ; 2 ; 3]
151 170 * - y-axis: not defined
152 171 * - values: [10 ; 20 ; 30 ; 40] (1-dim ArrayData)
153 172 * => the DataSeries is invalid, as x-axis and values haven't the same number of data
154 173 *
155 174 * 3)
156 175 * - x-axis: [1 ; 2 ; 3]
157 176 * - y-axis: not defined
158 177 * - values: [10 ; 20 ; 30
159 178 * 40 ; 50 ; 60] (2-dim ArrayData)
160 179 * => the DataSeries is valid, as x-axis has 3 data and values contains 2 components with 3
161 180 * data each
162 181 *
163 182 * 4)
164 183 * - x-axis: [1 ; 2 ; 3]
165 184 * - y-axis: [1 ; 2]
166 185 * - values: [10 ; 20 ; 30
167 186 * 40 ; 50 ; 60] (2-dim ArrayData)
168 187 * => the DataSeries is valid, as:
169 188 * - x-axis has 3 data and values contains 2 components with 3 data each AND
170 189 * - y-axis has 2 data and values contains 2 components
171 190 *
172 191 * 5)
173 192 * - x-axis: [1 ; 2 ; 3]
174 193 * - y-axis: [1 ; 2 ; 3]
175 194 * - values: [10 ; 20 ; 30
176 195 * 40 ; 50 ; 60] (2-dim ArrayData)
177 196 * => the DataSeries is invalid, as:
178 197 * - x-axis has 3 data and values contains 2 components with 3 data each BUT
179 198 * - y-axis has 3 data and values contains only 2 components
180 199 *
181 200 * @tparam Dim The dimension of the values data
182 201 *
183 202 */
184 203 template <int Dim>
185 204 class SCIQLOP_CORE_EXPORT DataSeries : public IDataSeries {
186 205 friend class DataSeriesMergeHelper;
187 206
188 207 public:
189 208 /// @sa IDataSeries::xAxisData()
190 209 std::shared_ptr<ArrayData<1> > xAxisData() override { return m_XAxisData; }
191 210 const std::shared_ptr<ArrayData<1> > xAxisData() const { return m_XAxisData; }
192 211
193 212 /// @sa IDataSeries::xAxisUnit()
194 213 Unit xAxisUnit() const override { return m_XAxisUnit; }
195 214
196 215 /// @sa IDataSeries::yAxisUnit()
197 216 Unit yAxisUnit() const override { return m_YAxis.unit(); }
198 217
199 218 /// @return the values dataset
200 219 std::shared_ptr<ArrayData<Dim> > valuesData() { return m_ValuesData; }
201 220 const std::shared_ptr<ArrayData<Dim> > valuesData() const { return m_ValuesData; }
202 221
203 222 /// @sa IDataSeries::valuesUnit()
204 223 Unit valuesUnit() const override { return m_ValuesUnit; }
205 224
206 225 int nbPoints() const override { return m_ValuesData->totalSize(); }
207 226
208 227 std::pair<double, double> yBounds() const override { return m_YAxis.bounds(); }
209 228
210 229 void clear()
211 230 {
212 231 m_XAxisData->clear();
213 232 m_ValuesData->clear();
214 233 }
215 234
216 235 bool isEmpty() const noexcept { return m_XAxisData->size() == 0; }
217 236
218 237 /// Merges into the data series an other data series.
219 238 ///
220 239 /// The two dataseries:
221 240 /// - must be of the same dimension
222 241 /// - must have the same y-axis (if defined)
223 242 ///
224 243 /// If the prerequisites are not valid, the method does nothing
225 244 ///
226 245 /// @remarks the data series to merge with is cleared after the operation
227 246 void merge(IDataSeries *dataSeries) override
228 247 {
229 248 dataSeries->lockWrite();
230 249 lockWrite();
231 250
232 251 if (auto other = dynamic_cast<DataSeries<Dim> *>(dataSeries)) {
233 252 if (m_YAxis == other->m_YAxis) {
234 253 DataSeriesMergeHelper::merge(*other, *this);
235 254 }
236 255 else {
237 256 qCWarning(LOG_DataSeries())
238 257 << QObject::tr("Can't merge data series that have not the same y-axis");
239 258 }
240 259 }
241 260 else {
242 261 qCWarning(LOG_DataSeries())
243 262 << QObject::tr("Detection of a type of IDataSeries we cannot merge with !");
244 263 }
245 264 unlock();
246 265 dataSeries->unlock();
247 266 }
248 267
249 268 void purge(double min, double max) override
250 269 {
251 270 // Nothing to purge if series is empty
252 271 if (isEmpty()) {
253 272 return;
254 273 }
255 274
256 275 if (min > max) {
257 276 std::swap(min, max);
258 277 }
259 278
260 279 // Nothing to purge if series min/max are inside purge range
261 280 auto xMin = cbegin()->x();
262 281 auto xMax = (--cend())->x();
263 282 if (xMin >= min && xMax <= max) {
264 283 return;
265 284 }
266 285
267 286 auto lowerIt = std::lower_bound(
268 287 begin(), end(), min, [](const auto &it, const auto &val) { return it.x() < val; });
269 288 erase(begin(), lowerIt);
270 289 auto upperIt = std::upper_bound(
271 290 begin(), end(), max, [](const auto &val, const auto &it) { return val < it.x(); });
272 291 erase(upperIt, end());
273 292 }
274 293
275 294 // ///////// //
276 295 // Iterators //
277 296 // ///////// //
278 297
279 298 DataSeriesIterator begin() override
280 299 {
281 300 return DataSeriesIterator{DataSeriesIteratorValue{
282 301 std::make_unique<dataseries_detail::IteratorValue<Dim, false> >(*this, true)}};
283 302 }
284 303
285 304 DataSeriesIterator end() override
286 305 {
287 306 return DataSeriesIterator{DataSeriesIteratorValue{
288 307 std::make_unique<dataseries_detail::IteratorValue<Dim, false> >(*this, false)}};
289 308 }
290 309
291 310 DataSeriesIterator cbegin() const override
292 311 {
293 312 return DataSeriesIterator{DataSeriesIteratorValue{
294 313 std::make_unique<dataseries_detail::IteratorValue<Dim, true> >(*this, true)}};
295 314 }
296 315
297 316 DataSeriesIterator cend() const override
298 317 {
299 318 return DataSeriesIterator{DataSeriesIteratorValue{
300 319 std::make_unique<dataseries_detail::IteratorValue<Dim, true> >(*this, false)}};
301 320 }
302 321
303 322 void erase(DataSeriesIterator first, DataSeriesIterator last)
304 323 {
305 324 auto firstImpl
306 325 = dynamic_cast<dataseries_detail::IteratorValue<Dim, false> *>(first->impl());
307 326 auto lastImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, false> *>(last->impl());
308 327
309 328 if (firstImpl && lastImpl) {
310 329 m_XAxisData->erase(firstImpl->m_XIt, lastImpl->m_XIt);
311 330 m_ValuesData->erase(firstImpl->m_ValuesIt, lastImpl->m_ValuesIt);
312 331 }
313 332 }
314 333
315 334 void insert(DataSeriesIterator first, DataSeriesIterator last, bool prepend = false)
316 335 {
317 336 auto firstImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, true> *>(first->impl());
318 337 auto lastImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, true> *>(last->impl());
319 338
320 339 if (firstImpl && lastImpl) {
321 340 m_XAxisData->insert(firstImpl->m_XIt, lastImpl->m_XIt, prepend);
322 341 m_ValuesData->insert(firstImpl->m_ValuesIt, lastImpl->m_ValuesIt, prepend);
323 342 }
324 343 }
325 344
326 345 /// @sa IDataSeries::minXAxisData()
327 346 DataSeriesIterator minXAxisData(double minXAxisData) const override
328 347 {
329 348 return std::lower_bound(
330 349 cbegin(), cend(), minXAxisData,
331 350 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
332 351 }
333 352
334 353 /// @sa IDataSeries::maxXAxisData()
335 354 DataSeriesIterator maxXAxisData(double maxXAxisData) const override
336 355 {
337 356 // Gets the first element that greater than max value
338 357 auto it = std::upper_bound(
339 358 cbegin(), cend(), maxXAxisData,
340 359 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
341 360
342 361 return it == cbegin() ? cend() : --it;
343 362 }
344 363
345 364 std::pair<DataSeriesIterator, DataSeriesIterator> xAxisRange(double minXAxisData,
346 365 double maxXAxisData) const override
347 366 {
348 367 if (minXAxisData > maxXAxisData) {
349 368 std::swap(minXAxisData, maxXAxisData);
350 369 }
351 370
352 371 auto begin = cbegin();
353 372 auto end = cend();
354 373
355 374 auto lowerIt = std::lower_bound(
356 375 begin, end, minXAxisData,
357 376 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
358 377 auto upperIt = std::upper_bound(
359 378 lowerIt, end, maxXAxisData,
360 379 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
361 380
362 381 return std::make_pair(lowerIt, upperIt);
363 382 }
364 383
365 384 std::pair<DataSeriesIterator, DataSeriesIterator>
366 385 valuesBounds(double minXAxisData, double maxXAxisData) const override
367 386 {
368 387 // Places iterators to the correct x-axis range
369 388 auto xAxisRangeIts = xAxisRange(minXAxisData, maxXAxisData);
370 389
371 390 // Returns end iterators if the range is empty
372 391 if (xAxisRangeIts.first == xAxisRangeIts.second) {
373 392 return std::make_pair(cend(), cend());
374 393 }
375 394
376 395 // Gets the iterator on the min of all values data
377 396 auto minIt = std::min_element(
378 397 xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) {
379 398 return SortUtils::minCompareWithNaN(it1.minValue(), it2.minValue());
380 399 });
381 400
382 401 // Gets the iterator on the max of all values data
383 402 auto maxIt = std::max_element(
384 403 xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) {
385 404 return SortUtils::maxCompareWithNaN(it1.maxValue(), it2.maxValue());
386 405 });
387 406
388 407 return std::make_pair(minIt, maxIt);
389 408 }
390 409
391 410 /// @return the y-axis associated to the data series
392 411 const OptionalAxis &yAxis() const { return m_YAxis; }
393 412 OptionalAxis &yAxis() { return m_YAxis; }
394 413
395 414 // /////// //
396 415 // Mutexes //
397 416 // /////// //
398 417
399 418 virtual void lockRead() { m_Lock.lockForRead(); }
400 419 virtual void lockWrite() { m_Lock.lockForWrite(); }
401 420 virtual void unlock() { m_Lock.unlock(); }
402 421
403 422 protected:
404 423 /// Protected ctor (DataSeries is abstract).
405 424 ///
406 425 /// Data vectors must be consistent with each other, otherwise an exception will be thrown (@sa
407 426 /// class description for consistent rules)
408 427 /// @remarks data series is automatically sorted on its x-axis data
409 428 /// @throws std::invalid_argument if the data are inconsistent with each other
410 429 explicit DataSeries(std::shared_ptr<ArrayData<1> > xAxisData, const Unit &xAxisUnit,
411 430 std::shared_ptr<ArrayData<Dim> > valuesData, const Unit &valuesUnit,
412 431 OptionalAxis yAxis = OptionalAxis{})
413 432 : m_XAxisData{xAxisData},
414 433 m_XAxisUnit{xAxisUnit},
415 434 m_ValuesData{valuesData},
416 435 m_ValuesUnit{valuesUnit},
417 436 m_YAxis{std::move(yAxis)}
418 437 {
419 438 if (m_XAxisData->size() != m_ValuesData->size()) {
420 439 throw std::invalid_argument{
421 440 "The number of values by component must be equal to the number of x-axis data"};
422 441 }
423 442
424 443 // Validates y-axis (if defined)
425 444 if (yAxis.isDefined() && (yAxis.size() != m_ValuesData->componentCount())) {
426 445 throw std::invalid_argument{
427 446 "As the y-axis is defined, the number of value components must be equal to the "
428 447 "number of y-axis data"};
429 448 }
430 449
431 450 // Sorts data if it's not the case
432 451 const auto &xAxisCData = m_XAxisData->cdata();
433 452 if (!std::is_sorted(xAxisCData.cbegin(), xAxisCData.cend())) {
434 453 sort();
435 454 }
436 455 }
437 456
438 457 /// Copy ctor
439 458 explicit DataSeries(const DataSeries<Dim> &other)
440 459 : m_XAxisData{std::make_shared<ArrayData<1> >(*other.m_XAxisData)},
441 460 m_XAxisUnit{other.m_XAxisUnit},
442 461 m_ValuesData{std::make_shared<ArrayData<Dim> >(*other.m_ValuesData)},
443 462 m_ValuesUnit{other.m_ValuesUnit},
444 463 m_YAxis{other.m_YAxis}
445 464 {
446 465 // Since a series is ordered from its construction and is always ordered, it is not
447 466 // necessary to call the sort method here ('other' is sorted)
448 467 }
449 468
450 469 /// Assignment operator
451 470 template <int D>
452 471 DataSeries &operator=(DataSeries<D> other)
453 472 {
454 473 std::swap(m_XAxisData, other.m_XAxisData);
455 474 std::swap(m_XAxisUnit, other.m_XAxisUnit);
456 475 std::swap(m_ValuesData, other.m_ValuesData);
457 476 std::swap(m_ValuesUnit, other.m_ValuesUnit);
458 477 std::swap(m_YAxis, other.m_YAxis);
459 478
460 479 return *this;
461 480 }
462 481
463 482 private:
464 483 /**
465 484 * Sorts data series on its x-axis data
466 485 */
467 486 void sort() noexcept
468 487 {
469 488 auto permutation = SortUtils::sortPermutation(*m_XAxisData, std::less<double>());
470 489 m_XAxisData = m_XAxisData->sort(permutation);
471 490 m_ValuesData = m_ValuesData->sort(permutation);
472 491 }
473 492
474 493 // x-axis
475 494 std::shared_ptr<ArrayData<1> > m_XAxisData;
476 495 Unit m_XAxisUnit;
477 496
478 497 // values
479 498 std::shared_ptr<ArrayData<Dim> > m_ValuesData;
480 499 Unit m_ValuesUnit;
481 500
482 501 // y-axis (optional)
483 502 OptionalAxis m_YAxis;
484 503
485 504 QReadWriteLock m_Lock;
486 505 };
487 506
488 507 #endif // SCIQLOP_DATASERIES_H
@@ -1,78 +1,81
1 1 #ifndef SCIQLOP_DATASERIESITERATOR_H
2 2 #define SCIQLOP_DATASERIESITERATOR_H
3 3
4 4 #include "CoreGlobal.h"
5 5 #include "Data/SqpIterator.h"
6 6
7 7 #include <QVector>
8 8 #include <memory>
9 9
10 10 /**
11 11 * @brief The DataSeriesIteratorValue class represents the current value of a data series iterator.
12 12 * It offers standard access methods for the data in the series (x-axis, values), but it is up to
13 13 * each series to define its own implementation of how to retrieve this data, by implementing the
14 14 * DataSeriesIteratorValue::Impl interface
15 15 *
16 16 * @sa DataSeriesIterator
17 17 */
18 18 class SCIQLOP_CORE_EXPORT DataSeriesIteratorValue {
19 19 public:
20 20 struct Impl {
21 21 virtual ~Impl() noexcept = default;
22 22 virtual std::unique_ptr<Impl> clone() const = 0;
23 23 virtual int distance(const Impl &other) const = 0;
24 24 virtual bool equals(const Impl &other) const = 0;
25 25 virtual bool lowerThan(const Impl &other) const = 0;
26 26 virtual std::unique_ptr<Impl> advance(int offset) const = 0;
27 27 virtual void next(int offset) = 0;
28 28 virtual void prev() = 0;
29 29 virtual double x() const = 0;
30 virtual std::vector<double> y() const = 0;
30 31 virtual double value() const = 0;
31 32 virtual double value(int componentIndex) const = 0;
32 33 virtual double minValue() const = 0;
33 34 virtual double maxValue() const = 0;
34 35 virtual QVector<double> values() const = 0;
35 36
36 37 virtual void swap(Impl &other) = 0;
37 38 };
38 39
39 40 explicit DataSeriesIteratorValue(std::unique_ptr<Impl> impl);
40 41 DataSeriesIteratorValue(const DataSeriesIteratorValue &other);
41 42 DataSeriesIteratorValue &operator=(DataSeriesIteratorValue other);
42 43
43 44 int distance(const DataSeriesIteratorValue &other) const;
44 45 bool equals(const DataSeriesIteratorValue &other) const;
45 46 bool lowerThan(const DataSeriesIteratorValue &other) const;
46 47
47 48 DataSeriesIteratorValue advance(int offset) const;
48 49 /// Advances to the next value
49 50 void next(int offset = 1);
50 51 /// Moves back to the previous value
51 52 void prev();
52 53 /// Gets x-axis data
53 54 double x() const;
55 /// Gets y-axis data
56 std::vector<double> y() const;
54 57 /// Gets value data
55 58 double value() const;
56 59 /// Gets value data depending on an index
57 60 double value(int componentIndex) const;
58 61 /// Gets min of all values data
59 62 double minValue() const;
60 63 /// Gets max of all values data
61 64 double maxValue() const;
62 65 /// Gets all values data
63 66 QVector<double> values() const;
64 67
65 68 Impl *impl();
66 69
67 70 friend void swap(DataSeriesIteratorValue &lhs, DataSeriesIteratorValue &rhs)
68 71 {
69 72 std::swap(lhs.m_Impl, rhs.m_Impl);
70 73 }
71 74
72 75 private:
73 76 std::unique_ptr<Impl> m_Impl;
74 77 };
75 78
76 79 using DataSeriesIterator = SqpIterator<DataSeriesIteratorValue>;
77 80
78 81 #endif // SCIQLOP_DATASERIESITERATOR_H
@@ -1,62 +1,66
1 1 #ifndef SCIQLOP_OPTIONALAXIS_H
2 2 #define SCIQLOP_OPTIONALAXIS_H
3 3
4 #include <Data/ArrayDataIterator.h>
5
4 6 #include "CoreGlobal.h"
5 7 #include "Unit.h"
6 8
7 9 #include <memory>
8 10
9 11 template <int Dim>
10 12 class ArrayData;
11 13
12 14 /**
13 15 * @brief The OptionalAxis class defines an optional data axis for a series of data.
14 16 *
15 17 * An optional data axis is an axis that can be defined or not for a data series. If defined, it
16 18 * contains a unit and data (1-dim ArrayData). It is then possible to access the data or the unit.
17 19 * In the case of an undefined axis, the axis has no data and no unit. The methods for accessing the
18 20 * data or the unit are always callable but will return undefined values.
19 21 *
20 22 * @sa DataSeries
21 23 * @sa ArrayData
22 24 */
23 25 class SCIQLOP_CORE_EXPORT OptionalAxis {
24 26 public:
25 27 /// Ctor for an undefined axis
26 28 explicit OptionalAxis();
27 29 /// Ctor for a defined axis
28 30 /// @param data the axis' data
29 31 /// @param unit the axis' unit
30 32 /// @throws std::invalid_argument if no data is associated to the axis
31 33 explicit OptionalAxis(std::shared_ptr<ArrayData<1> > data, Unit unit);
32 34
33 35 /// Copy ctor
34 36 OptionalAxis(const OptionalAxis &other);
35 37 /// Assignment operator
36 38 OptionalAxis &operator=(OptionalAxis other);
37 39
38 40 /// @return the flag that indicates if the axis is defined or not
39 41 bool isDefined() const;
40 42
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
45 43 ///@return the min and max values of the data on the axis, NaN values if there is no data
46 44 std::pair<double, double> bounds() const;
47 45
48 46 /// @return the number of data on the axis, 0 if the axis is not defined
49 47 int size() const;
50 48 /// @return the unit of the axis, an empty unit if the axis is not defined
51 49 Unit unit() const;
52 50
53 51 bool operator==(const OptionalAxis &other);
54 52 bool operator!=(const OptionalAxis &other);
55 53
54 // Iterators on data
55 ArrayDataIterator begin();
56 ArrayDataIterator end();
57 ArrayDataIterator cbegin() const;
58 ArrayDataIterator cend() const;
59
56 60 private:
57 61 bool m_Defined; ///< Axis is defined or not
58 62 std::shared_ptr<ArrayData<1> > m_Data; ///< Axis' data
59 63 Unit m_Unit; ///< Axis' unit
60 64 };
61 65
62 66 #endif // SCIQLOP_OPTIONALAXIS_H
@@ -1,83 +1,88
1 1 #include "Data/DataSeriesIterator.h"
2 2
3 3 DataSeriesIteratorValue::DataSeriesIteratorValue(
4 4 std::unique_ptr<DataSeriesIteratorValue::Impl> impl)
5 5 : m_Impl{std::move(impl)}
6 6 {
7 7 }
8 8
9 9 DataSeriesIteratorValue::DataSeriesIteratorValue(const DataSeriesIteratorValue &other)
10 10 : m_Impl{other.m_Impl->clone()}
11 11 {
12 12 }
13 13
14 14 DataSeriesIteratorValue &DataSeriesIteratorValue::operator=(DataSeriesIteratorValue other)
15 15 {
16 16 m_Impl->swap(*other.m_Impl);
17 17 return *this;
18 18 }
19 19
20 20 int DataSeriesIteratorValue::distance(const DataSeriesIteratorValue &other) const
21 21 {
22 22 return m_Impl->distance(*other.m_Impl);
23 23 }
24 24
25 25 bool DataSeriesIteratorValue::equals(const DataSeriesIteratorValue &other) const
26 26 {
27 27 return m_Impl->equals(*other.m_Impl);
28 28 }
29 29
30 30 bool DataSeriesIteratorValue::lowerThan(const DataSeriesIteratorValue &other) const
31 31 {
32 32 return m_Impl->lowerThan(*other.m_Impl);
33 33 }
34 34
35 35 DataSeriesIteratorValue DataSeriesIteratorValue::advance(int offset) const
36 36 {
37 37 return DataSeriesIteratorValue{m_Impl->advance(offset)};
38 38 }
39 39
40 40 void DataSeriesIteratorValue::next(int offset)
41 41 {
42 42 m_Impl->next(offset);
43 43 }
44 44
45 45 void DataSeriesIteratorValue::prev()
46 46 {
47 47 m_Impl->prev();
48 48 }
49 49
50 50 double DataSeriesIteratorValue::x() const
51 51 {
52 52 return m_Impl->x();
53 53 }
54 54
55 std::vector<double> DataSeriesIteratorValue::y() const
56 {
57 return m_Impl->y();
58 }
59
55 60 double DataSeriesIteratorValue::value() const
56 61 {
57 62 return m_Impl->value();
58 63 }
59 64
60 65 double DataSeriesIteratorValue::value(int componentIndex) const
61 66 {
62 67 return m_Impl->value(componentIndex);
63 68 }
64 69
65 70 double DataSeriesIteratorValue::minValue() const
66 71 {
67 72 return m_Impl->minValue();
68 73 }
69 74
70 75 double DataSeriesIteratorValue::maxValue() const
71 76 {
72 77 return m_Impl->maxValue();
73 78 }
74 79
75 80 QVector<double> DataSeriesIteratorValue::values() const
76 81 {
77 82 return m_Impl->values();
78 83 }
79 84
80 85 DataSeriesIteratorValue::Impl *DataSeriesIteratorValue::impl()
81 86 {
82 87 return m_Impl.get();
83 88 }
@@ -1,99 +1,107
1 1 #include <Data/OptionalAxis.h>
2 2
3 3 #include "Data/ArrayData.h"
4 4
5 OptionalAxis::OptionalAxis() : m_Defined{false}, m_Data{nullptr}, m_Unit{}
5 OptionalAxis::OptionalAxis()
6 : m_Defined{false}, m_Data{std::make_shared<ArrayData<1> >(std::vector<double>{})}, m_Unit{}
6 7 {
7 8 }
8 9
9 10 OptionalAxis::OptionalAxis(std::shared_ptr<ArrayData<1> > data, Unit unit)
10 11 : m_Defined{true}, m_Data{data}, m_Unit{std::move(unit)}
11 12 {
12 13 if (m_Data == nullptr) {
13 14 throw std::invalid_argument{"Data can't be null for a defined axis"};
14 15 }
15 16 }
16 17
17 18 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}
19 : m_Defined{other.m_Defined}, m_Data{other.m_Data}, m_Unit{other.m_Unit}
21 20 {
22 21 }
23 22
24 23 OptionalAxis &OptionalAxis::operator=(OptionalAxis other)
25 24 {
26 25 std::swap(m_Defined, other.m_Defined);
27 26 std::swap(m_Data, other.m_Data);
28 27 std::swap(m_Unit, other.m_Unit);
29 28
30 29 return *this;
31 30 }
32 31
33 32 bool OptionalAxis::isDefined() const
34 33 {
35 34 return m_Defined;
36 35 }
37 36
38 double OptionalAxis::at(int index) const
39 {
40 if (m_Defined) {
41 return (index >= 0 && index < m_Data->size()) ? m_Data->at(index)
42 : std::numeric_limits<double>::quiet_NaN();
43 }
44 else {
45 return std::numeric_limits<double>::quiet_NaN();
46 }
47 }
48
49 37 std::pair<double, double> OptionalAxis::bounds() const
50 38 {
51 39 if (!m_Defined || m_Data->size() == 0) {
52 40 return std::make_pair(std::numeric_limits<double>::quiet_NaN(),
53 41 std::numeric_limits<double>::quiet_NaN());
54 42 }
55 43 else {
56 44
57 45 auto minIt = std::min_element(
58 46 m_Data->cbegin(), m_Data->cend(), [](const auto &it1, const auto &it2) {
59 47 return SortUtils::minCompareWithNaN(it1.first(), it2.first());
60 48 });
61 49
62 50 // Gets the iterator on the max of all values data
63 51 auto maxIt = std::max_element(
64 52 m_Data->cbegin(), m_Data->cend(), [](const auto &it1, const auto &it2) {
65 53 return SortUtils::maxCompareWithNaN(it1.first(), it2.first());
66 54 });
67 55
68 56 return std::make_pair(minIt->first(), maxIt->first());
69 57 }
70 58 }
71 59
72 60 int OptionalAxis::size() const
73 61 {
74 62 return m_Defined ? m_Data->size() : 0;
75 63 }
76 64
77 65 Unit OptionalAxis::unit() const
78 66 {
79 67 return m_Defined ? m_Unit : Unit{};
80 68 }
81 69
82 70 bool OptionalAxis::operator==(const OptionalAxis &other)
83 71 {
84 72 // Axis not defined
85 73 if (!m_Defined) {
86 74 return !other.m_Defined;
87 75 }
88 76
89 77 // Axis defined
90 78 return m_Unit == other.m_Unit
91 79 && std::equal(
92 80 m_Data->cbegin(), m_Data->cend(), other.m_Data->cbegin(), other.m_Data->cend(),
93 81 [](const auto &it1, const auto &it2) { return it1.values() == it2.values(); });
94 82 }
95 83
96 84 bool OptionalAxis::operator!=(const OptionalAxis &other)
97 85 {
98 86 return !(*this == other);
99 87 }
88
89 ArrayDataIterator OptionalAxis::begin()
90 {
91 return m_Data->begin();
92 }
93
94 ArrayDataIterator OptionalAxis::end()
95 {
96 return m_Data->end();
97 }
98
99 ArrayDataIterator OptionalAxis::cbegin() const
100 {
101 return m_Data->cbegin();
102 }
103
104 ArrayDataIterator OptionalAxis::cend() const
105 {
106 return m_Data->cend();
107 }
@@ -1,150 +1,113
1 1 #include <Data/ArrayData.h>
2 2 #include <Data/OptionalAxis.h>
3 3
4 4 #include <QObject>
5 5 #include <QtTest>
6 6
7 7 Q_DECLARE_METATYPE(OptionalAxis)
8 8
9 9 class TestOptionalAxis : public QObject {
10 10 Q_OBJECT
11 11
12 12 private slots:
13 13 /// Tests the creation of a undefined axis
14 14 void testNotDefinedAxisCtor();
15 15
16 16 /// Tests the creation of a undefined axis
17 17 void testDefinedAxisCtor_data();
18 18 void testDefinedAxisCtor();
19 19
20 /// Tests @sa OptionalAxis::at() method
21 void testAt_data();
22 void testAt();
23
24 20 /// Tests @sa OptionalAxis::size() method
25 21 void testSize_data();
26 22 void testSize();
27 23
28 24 /// Tests @sa OptionalAxis::unit() method
29 25 void testUnit_data();
30 26 void testUnit();
31 27 };
32 28
33 29 void TestOptionalAxis::testNotDefinedAxisCtor()
34 30 {
35 31 OptionalAxis notDefinedAxis{};
36 32 QVERIFY(!notDefinedAxis.isDefined());
37 33 }
38 34
39 35 void TestOptionalAxis::testDefinedAxisCtor_data()
40 36 {
41 37 QTest::addColumn<bool>("noData"); // If set to true, nullptr is passed as data of the axis
42 38 QTest::addColumn<std::vector<double> >(
43 39 "data"); // Values assigned to the axis when 'noData' flag is set to false
44 40 QTest::addColumn<Unit>("unit"); // Unit assigned to the axis
45 41
46 42 QTest::newRow("validData") << false << std::vector<double>{1, 2, 3} << Unit{"Hz"};
47 43 QTest::newRow("invalidData") << true << std::vector<double>{} << Unit{"Hz"};
48 44 }
49 45
50 46 void TestOptionalAxis::testDefinedAxisCtor()
51 47 {
52 48 QFETCH(bool, noData);
53 49 QFETCH(Unit, unit);
54 50
55 51 // When there is no data, we expect that the constructor returns exception
56 52 if (noData) {
57 53 QVERIFY_EXCEPTION_THROWN(OptionalAxis(nullptr, unit), std::invalid_argument);
58 54 }
59 55 else {
60 56 QFETCH(std::vector<double>, data);
61 57
62 58 OptionalAxis axis{std::make_shared<ArrayData<1> >(data), unit};
63 59 QVERIFY(axis.isDefined());
64 60 }
65 61 }
66 62
67 void TestOptionalAxis::testAt_data()
68 {
69 QTest::addColumn<OptionalAxis>("axis"); // Axis used for test case (defined or not)
70 QTest::addColumn<int>("index"); // Index to test in the axis
71 QTest::addColumn<double>("expectedValue"); // Expected axis value for the index
72
73 OptionalAxis definedAxis{std::make_shared<ArrayData<1> >(std::vector<double>{1, 2, 3}),
74 Unit{"Hz"}};
75
76 QTest::newRow("data1") << definedAxis << 0 << 1.;
77 QTest::newRow("data2") << definedAxis << 1 << 2.;
78 QTest::newRow("data3") << definedAxis << 2 << 3.;
79 QTest::newRow("data4 (index out of bounds)")
80 << definedAxis << 3
81 << std::numeric_limits<double>::quiet_NaN(); // Expects NaN for out of bounds index
82 QTest::newRow("data5 (index out of bounds)")
83 << definedAxis << -1
84 << std::numeric_limits<double>::quiet_NaN(); // Expects NaN for out of bounds index
85 QTest::newRow("data6 (axis not defined)")
86 << OptionalAxis{} << 0
87 << std::numeric_limits<double>::quiet_NaN(); // Expects NaN for undefined axis
88 }
89
90 void TestOptionalAxis::testAt()
91 {
92 QFETCH(OptionalAxis, axis);
93 QFETCH(int, index);
94 QFETCH(double, expectedValue);
95
96 auto value = axis.at(index);
97 QVERIFY((std::isnan(value) && std::isnan(expectedValue)) || value == expectedValue);
98 }
99
100 63 void TestOptionalAxis::testSize_data()
101 64 {
102 65 QTest::addColumn<OptionalAxis>("axis"); // Axis used for test case (defined or not)
103 66 QTest::addColumn<int>("expectedSize"); // Expected number of data in the axis
104 67
105 68 // Lambda that creates default defined axis (with the values passed in parameter)
106 69 auto axis = [](std::vector<double> values) {
107 70 return OptionalAxis{std::make_shared<ArrayData<1> >(std::move(values)), Unit{"Hz"}};
108 71 };
109 72
110 73 QTest::newRow("data1") << axis({}) << 0;
111 74 QTest::newRow("data2") << axis({1, 2, 3}) << 3;
112 75 QTest::newRow("data3") << axis({1, 2, 3, 4}) << 4;
113 76 QTest::newRow("data4 (axis not defined)") << OptionalAxis{}
114 77 << 0; // Expects 0 for undefined axis
115 78 }
116 79
117 80 void TestOptionalAxis::testSize()
118 81 {
119 82 QFETCH(OptionalAxis, axis);
120 83 QFETCH(int, expectedSize);
121 84
122 85 QCOMPARE(axis.size(), expectedSize);
123 86 }
124 87
125 88 void TestOptionalAxis::testUnit_data()
126 89 {
127 90 QTest::addColumn<OptionalAxis>("axis"); // Axis used for test case (defined or not)
128 91 QTest::addColumn<Unit>("expectedUnit"); // Expected unit for the axis
129 92
130 93 // Lambda that creates default defined axis (with the unit passed in parameter)
131 94 auto axis = [](Unit unit) {
132 95 return OptionalAxis{std::make_shared<ArrayData<1> >(std::vector<double>{1, 2, 3}), unit};
133 96 };
134 97
135 98 QTest::newRow("data1") << axis(Unit{"Hz"}) << Unit{"Hz"};
136 99 QTest::newRow("data2") << axis(Unit{"t", true}) << Unit{"t", true};
137 100 QTest::newRow("data3 (axis not defined)") << OptionalAxis{}
138 101 << Unit{}; // Expects default unit for undefined axis
139 102 }
140 103
141 104 void TestOptionalAxis::testUnit()
142 105 {
143 106 QFETCH(OptionalAxis, axis);
144 107 QFETCH(Unit, expectedUnit);
145 108
146 109 QCOMPARE(axis.unit(), expectedUnit);
147 110 }
148 111
149 112 QTEST_MAIN(TestOptionalAxis)
150 113 #include "TestOptionalAxis.moc"
@@ -1,580 +1,579
1 1 #include "AmdaResultParser.h"
2 2
3 3 #include <Data/ScalarSeries.h>
4 4 #include <Data/SpectrogramSeries.h>
5 5 #include <Data/VectorSeries.h>
6 6
7 7 #include <QObject>
8 8 #include <QtTest>
9 9
10 10 namespace {
11 11
12 12 /// Path for the tests
13 13 const auto TESTS_RESOURCES_PATH
14 14 = QFileInfo{QString{AMDA_TESTS_RESOURCES_DIR}, "TestAmdaResultParser"}.absoluteFilePath();
15 15
16 16 QDateTime dateTime(int year, int month, int day, int hours, int minutes, int seconds)
17 17 {
18 18 return QDateTime{{year, month, day}, {hours, minutes, seconds}, Qt::UTC};
19 19 }
20 20
21 21 QString inputFilePath(const QString &inputFileName)
22 22 {
23 23 return QFileInfo{TESTS_RESOURCES_PATH, inputFileName}.absoluteFilePath();
24 24 }
25 25
26 26 template <typename T>
27 27 struct ExpectedResults {
28 28
29 29 ExpectedResults &setParsingOK(bool parsingOK)
30 30 {
31 31 m_ParsingOK = parsingOK;
32 32 return *this;
33 33 }
34 34
35 35 ExpectedResults &setXAxisUnit(Unit xAxisUnit)
36 36 {
37 37 m_XAxisUnit = std::move(xAxisUnit);
38 38 return *this;
39 39 }
40 40
41 41 ExpectedResults &setXAxisData(const QVector<QDateTime> &xAxisData)
42 42 {
43 43 m_XAxisData.clear();
44 44
45 45 // Converts QVector<QDateTime> to QVector<double>
46 46 std::transform(xAxisData.cbegin(), xAxisData.cend(), std::back_inserter(m_XAxisData),
47 47 [](const auto &dateTime) { return dateTime.toMSecsSinceEpoch() / 1000.; });
48 48
49 49 return *this;
50 50 }
51 51
52 52 ExpectedResults &setValuesUnit(Unit valuesUnit)
53 53 {
54 54 m_ValuesUnit = std::move(valuesUnit);
55 55 return *this;
56 56 }
57 57
58 58 ExpectedResults &setValuesData(QVector<double> valuesData)
59 59 {
60 60 m_ValuesData.clear();
61 61 m_ValuesData.push_back(std::move(valuesData));
62 62 return *this;
63 63 }
64 64
65 65 ExpectedResults &setValuesData(QVector<QVector<double> > valuesData)
66 66 {
67 67 m_ValuesData = std::move(valuesData);
68 68 return *this;
69 69 }
70 70
71 71 ExpectedResults &setYAxisEnabled(bool yAxisEnabled)
72 72 {
73 73 m_YAxisEnabled = yAxisEnabled;
74 74 return *this;
75 75 }
76 76
77 77 ExpectedResults &setYAxisUnit(Unit yAxisUnit)
78 78 {
79 79 m_YAxisUnit = std::move(yAxisUnit);
80 80 return *this;
81 81 }
82 82
83 83 ExpectedResults &setYAxisData(QVector<double> yAxisData)
84 84 {
85 85 m_YAxisData = std::move(yAxisData);
86 86 return *this;
87 87 }
88 88
89 89 /**
90 90 * Validates a DataSeries compared to the expected results
91 91 * @param results the DataSeries to validate
92 92 */
93 93 void validate(std::shared_ptr<IDataSeries> results)
94 94 {
95 95 if (m_ParsingOK) {
96 96 auto dataSeries = dynamic_cast<T *>(results.get());
97 97 if (dataSeries == nullptr) {
98 98
99 99 // No unit detected, parsink ok but data is nullptr
100 100 // TODO, improve the test to verify that the data is null
101 101 return;
102 102 }
103 103
104 104 // Checks units
105 105 QVERIFY(dataSeries->xAxisUnit() == m_XAxisUnit);
106 106 QVERIFY(dataSeries->valuesUnit() == m_ValuesUnit);
107 107
108 108 auto verifyRange = [dataSeries](const auto &expectedData, const auto &equalFun) {
109 109 QVERIFY(std::equal(dataSeries->cbegin(), dataSeries->cend(), expectedData.cbegin(),
110 110 expectedData.cend(),
111 111 [&equalFun](const auto &dataSeriesIt, const auto &expectedX) {
112 112 return equalFun(dataSeriesIt, expectedX);
113 113 }));
114 114 };
115 115
116 116 // Checks x-axis data
117 117 verifyRange(m_XAxisData, [](const auto &seriesIt, const auto &value) {
118 118 return seriesIt.x() == value;
119 119 });
120 120
121 121 // Checks values data of each component
122 122 for (auto i = 0; i < m_ValuesData.size(); ++i) {
123 123 verifyRange(m_ValuesData.at(i), [i](const auto &seriesIt, const auto &value) {
124 124 auto itValue = seriesIt.value(i);
125 125 return (std::isnan(itValue) && std::isnan(value)) || seriesIt.value(i) == value;
126 126 });
127 127 }
128 128
129 129 // Checks y-axis (if defined)
130 130 auto yAxis = dataSeries->yAxis();
131 131 QCOMPARE(yAxis.isDefined(), m_YAxisEnabled);
132 132
133 133 if (m_YAxisEnabled) {
134 134 // Unit
135 135 QCOMPARE(yAxis.unit(), m_YAxisUnit);
136 136
137 137 // Data
138 auto yAxisSize = yAxis.size();
139 QCOMPARE(yAxisSize, m_YAxisData.size());
140 for (auto i = 0; i < yAxisSize; ++i) {
141 QCOMPARE(yAxis.at(i), m_YAxisData.at(i));
142 }
138 QVERIFY(std::equal(yAxis.cbegin(), yAxis.cend(), m_YAxisData.cbegin(),
139 m_YAxisData.cend(), [](const auto &it, const auto &expectedVal) {
140 return it.first() == expectedVal;
141 }));
143 142 }
144 143 }
145 144 else {
146 145 QVERIFY(results == nullptr);
147 146 }
148 147 }
149 148
150 149 // Parsing was successfully completed
151 150 bool m_ParsingOK{false};
152 151 // Expected x-axis unit
153 152 Unit m_XAxisUnit{};
154 153 // Expected x-axis data
155 154 QVector<double> m_XAxisData{};
156 155 // Expected values unit
157 156 Unit m_ValuesUnit{};
158 157 // Expected values data
159 158 QVector<QVector<double> > m_ValuesData{};
160 159 // Expected data series has y-axis
161 160 bool m_YAxisEnabled{false};
162 161 // Expected y-axis unit (if axis defined)
163 162 Unit m_YAxisUnit{};
164 163 // Expected y-axis data (if axis defined)
165 164 QVector<double> m_YAxisData{};
166 165 };
167 166
168 167 } // namespace
169 168
170 169 Q_DECLARE_METATYPE(ExpectedResults<ScalarSeries>)
171 170 Q_DECLARE_METATYPE(ExpectedResults<SpectrogramSeries>)
172 171 Q_DECLARE_METATYPE(ExpectedResults<VectorSeries>)
173 172
174 173 class TestAmdaResultParser : public QObject {
175 174 Q_OBJECT
176 175 private:
177 176 template <typename T>
178 177 void testReadDataStructure()
179 178 {
180 179 // ////////////// //
181 180 // Test structure //
182 181 // ////////////// //
183 182
184 183 // Name of TXT file to read
185 184 QTest::addColumn<QString>("inputFileName");
186 185 // Expected results
187 186 QTest::addColumn<ExpectedResults<T> >("expectedResults");
188 187 }
189 188
190 189 template <typename T>
191 190 void testRead(AmdaResultParser::ValueType valueType)
192 191 {
193 192 QFETCH(QString, inputFileName);
194 193 QFETCH(ExpectedResults<T>, expectedResults);
195 194
196 195 // Parses file
197 196 auto filePath = inputFilePath(inputFileName);
198 197 auto results = AmdaResultParser::readTxt(filePath, valueType);
199 198
200 199 // ///////////////// //
201 200 // Validates results //
202 201 // ///////////////// //
203 202 expectedResults.validate(results);
204 203 }
205 204
206 205 private slots:
207 206 /// Input test data
208 207 /// @sa testReadScalarTxt()
209 208 void testReadScalarTxt_data();
210 209
211 210 /// Tests parsing scalar series of a TXT file
212 211 void testReadScalarTxt();
213 212
214 213 /// Input test data
215 214 /// @sa testReadSpectrogramTxt()
216 215 void testReadSpectrogramTxt_data();
217 216
218 217 /// Tests parsing spectrogram series of a TXT file
219 218 void testReadSpectrogramTxt();
220 219
221 220 /// Input test data
222 221 /// @sa testReadVectorTxt()
223 222 void testReadVectorTxt_data();
224 223
225 224 /// Tests parsing vector series of a TXT file
226 225 void testReadVectorTxt();
227 226 };
228 227
229 228 void TestAmdaResultParser::testReadScalarTxt_data()
230 229 {
231 230 testReadDataStructure<ScalarSeries>();
232 231
233 232 // ////////// //
234 233 // Test cases //
235 234 // ////////// //
236 235
237 236 // Valid files
238 237 QTest::newRow("Valid file")
239 238 << QStringLiteral("ValidScalar1.txt")
240 239 << ExpectedResults<ScalarSeries>{}
241 240 .setParsingOK(true)
242 241 .setXAxisUnit(Unit{"nT", true})
243 242 .setXAxisData({dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
244 243 dateTime(2013, 9, 23, 9, 2, 30), dateTime(2013, 9, 23, 9, 3, 30),
245 244 dateTime(2013, 9, 23, 9, 4, 30), dateTime(2013, 9, 23, 9, 5, 30),
246 245 dateTime(2013, 9, 23, 9, 6, 30), dateTime(2013, 9, 23, 9, 7, 30),
247 246 dateTime(2013, 9, 23, 9, 8, 30), dateTime(2013, 9, 23, 9, 9, 30)})
248 247 .setValuesData({-2.83950, -2.71850, -2.52150, -2.57633, -2.58050, -2.48325, -2.63025,
249 248 -2.55800, -2.43250, -2.42200});
250 249
251 250 QTest::newRow("Valid file (value of first line is invalid but it is converted to NaN")
252 251 << QStringLiteral("WrongValue.txt")
253 252 << ExpectedResults<ScalarSeries>{}
254 253 .setParsingOK(true)
255 254 .setXAxisUnit(Unit{"nT", true})
256 255 .setXAxisData({dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
257 256 dateTime(2013, 9, 23, 9, 2, 30)})
258 257 .setValuesData({std::numeric_limits<double>::quiet_NaN(), -2.71850, -2.52150});
259 258
260 259 QTest::newRow("Valid file that contains NaN values")
261 260 << QStringLiteral("NaNValue.txt")
262 261 << ExpectedResults<ScalarSeries>{}
263 262 .setParsingOK(true)
264 263 .setXAxisUnit(Unit{("nT"), true})
265 264 .setXAxisData({dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
266 265 dateTime(2013, 9, 23, 9, 2, 30)})
267 266 .setValuesData({std::numeric_limits<double>::quiet_NaN(), -2.71850, -2.52150});
268 267
269 268 // Valid files but with some invalid lines (wrong unit, wrong values, etc.)
270 269 QTest::newRow("No unit file")
271 270 << QStringLiteral("NoUnit.txt")
272 271 << ExpectedResults<ScalarSeries>{}.setParsingOK(true).setXAxisUnit(Unit{"", true});
273 272
274 273 QTest::newRow("Wrong unit file")
275 274 << QStringLiteral("WrongUnit.txt")
276 275 << ExpectedResults<ScalarSeries>{}
277 276 .setParsingOK(true)
278 277 .setXAxisUnit(Unit{"", true})
279 278 .setXAxisData({dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
280 279 dateTime(2013, 9, 23, 9, 2, 30)})
281 280 .setValuesData({-2.83950, -2.71850, -2.52150});
282 281
283 282 QTest::newRow("Wrong results file (date of first line is invalid")
284 283 << QStringLiteral("WrongDate.txt")
285 284 << ExpectedResults<ScalarSeries>{}
286 285 .setParsingOK(true)
287 286 .setXAxisUnit(Unit{"nT", true})
288 287 .setXAxisData({dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)})
289 288 .setValuesData({-2.71850, -2.52150});
290 289
291 290 QTest::newRow("Wrong results file (too many values for first line")
292 291 << QStringLiteral("TooManyValues.txt")
293 292 << ExpectedResults<ScalarSeries>{}
294 293 .setParsingOK(true)
295 294 .setXAxisUnit(Unit{"nT", true})
296 295 .setXAxisData({dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)})
297 296 .setValuesData({-2.71850, -2.52150});
298 297
299 298 QTest::newRow("Wrong results file (x of first line is NaN")
300 299 << QStringLiteral("NaNX.txt")
301 300 << ExpectedResults<ScalarSeries>{}
302 301 .setParsingOK(true)
303 302 .setXAxisUnit(Unit{"nT", true})
304 303 .setXAxisData({dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)})
305 304 .setValuesData({-2.71850, -2.52150});
306 305
307 306 QTest::newRow("Invalid file type (vector)")
308 307 << QStringLiteral("ValidVector1.txt")
309 308 << ExpectedResults<ScalarSeries>{}.setParsingOK(true).setXAxisUnit(Unit{"nT", true});
310 309
311 310 // Invalid files
312 311 QTest::newRow("Invalid file (unexisting file)")
313 312 << QStringLiteral("UnexistingFile.txt")
314 313 << ExpectedResults<ScalarSeries>{}.setParsingOK(false);
315 314
316 315 QTest::newRow("Invalid file (file not found on server)")
317 316 << QStringLiteral("FileNotFound.txt")
318 317 << ExpectedResults<ScalarSeries>{}.setParsingOK(false);
319 318 }
320 319
321 320 void TestAmdaResultParser::testReadScalarTxt()
322 321 {
323 322 testRead<ScalarSeries>(AmdaResultParser::ValueType::SCALAR);
324 323 }
325 324
326 325 void TestAmdaResultParser::testReadSpectrogramTxt_data()
327 326 {
328 327 testReadDataStructure<SpectrogramSeries>();
329 328
330 329 // ////////// //
331 330 // Test cases //
332 331 // ////////// //
333 332
334 333 // Valid files
335 334 QTest::newRow("Valid file (three bands)")
336 335 << QStringLiteral("spectro/ValidSpectrogram1.txt")
337 336 << ExpectedResults<SpectrogramSeries>{}
338 337 .setParsingOK(true)
339 338 .setXAxisUnit(Unit{"t", true})
340 339 .setXAxisData({dateTime(2012, 11, 6, 9, 14, 35), dateTime(2012, 11, 6, 9, 16, 10),
341 340 dateTime(2012, 11, 6, 9, 17, 45), dateTime(2012, 11, 6, 9, 19, 20),
342 341 dateTime(2012, 11, 6, 9, 20, 55)})
343 342 .setYAxisEnabled(true)
344 343 .setYAxisUnit(Unit{"eV"})
345 344 .setYAxisData({5.75, 7.6, 10.05}) // middle of the intervals of each band
346 345 .setValuesUnit(Unit{"eV/(cm^2-s-sr-eV)"})
347 346 .setValuesData(QVector<QVector<double> >{
348 347 {16313.780, 12631.465, 8223.368, 27595.301, 12820.613},
349 348 {15405.838, 11957.925, 15026.249, 25617.533, 11179.109},
350 349 {8946.475, 18133.158, 10875.621, 24051.619, 19283.221}});
351 350
352 351 auto fourBandsResult
353 352 = ExpectedResults<SpectrogramSeries>{}
354 353 .setParsingOK(true)
355 354 .setXAxisUnit(Unit{"t", true})
356 355 .setXAxisData({dateTime(2012, 11, 6, 9, 14, 35), dateTime(2012, 11, 6, 9, 16, 10),
357 356 dateTime(2012, 11, 6, 9, 17, 45), dateTime(2012, 11, 6, 9, 19, 20),
358 357 dateTime(2012, 11, 6, 9, 20, 55)})
359 358 .setYAxisEnabled(true)
360 359 .setYAxisUnit(Unit{"eV"})
361 360 .setYAxisData({5.75, 7.6, 10.05, 13.}) // middle of the intervals of each band
362 361 .setValuesUnit(Unit{"eV/(cm^2-s-sr-eV)"})
363 362 .setValuesData(QVector<QVector<double> >{
364 363 {16313.780, 12631.465, 8223.368, 27595.301, 12820.613},
365 364 {15405.838, 11957.925, 15026.249, 25617.533, 11179.109},
366 365 {8946.475, 18133.158, 10875.621, 24051.619, 19283.221},
367 366 {20907.664, 32076.725, 13008.381, 13142.759, 23226.998}});
368 367
369 368 QTest::newRow("Valid file (four bands)")
370 369 << QStringLiteral("spectro/ValidSpectrogram2.txt") << fourBandsResult;
371 370 QTest::newRow("Valid file (four unsorted bands)")
372 371 << QStringLiteral("spectro/ValidSpectrogram3.txt")
373 372 << fourBandsResult; // Bands and values are sorted
374 373
375 374 auto nan = std::numeric_limits<double>::quiet_NaN();
376 375
377 376 auto nanValuesResult
378 377 = ExpectedResults<SpectrogramSeries>{}
379 378 .setParsingOK(true)
380 379 .setXAxisUnit(Unit{"t", true})
381 380 .setXAxisData({dateTime(2012, 11, 6, 9, 14, 35), dateTime(2012, 11, 6, 9, 16, 10),
382 381 dateTime(2012, 11, 6, 9, 17, 45), dateTime(2012, 11, 6, 9, 19, 20),
383 382 dateTime(2012, 11, 6, 9, 20, 55)})
384 383 .setYAxisEnabled(true)
385 384 .setYAxisUnit(Unit{"eV"})
386 385 .setYAxisData({5.75, 7.6, 10.05, 13.}) // middle of the intervals of each band
387 386 .setValuesUnit(Unit{"eV/(cm^2-s-sr-eV)"})
388 387 .setValuesData(
389 388 QVector<QVector<double> >{{nan, 12631.465, 8223.368, 27595.301, 12820.613},
390 389 {15405.838, nan, nan, 25617.533, 11179.109},
391 390 {8946.475, 18133.158, 10875.621, 24051.619, 19283.221},
392 391 {nan, nan, nan, nan, nan}});
393 392
394 393 QTest::newRow("Valid file (containing NaN values)")
395 394 << QStringLiteral("spectro/ValidSpectrogramNaNValues.txt") << nanValuesResult;
396 395 QTest::newRow("Valid file (containing fill values)")
397 396 << QStringLiteral("spectro/ValidSpectrogramFillValues.txt")
398 397 << nanValuesResult; // Fill values are replaced by NaN values in the data series
399 398
400 399 QTest::newRow("Valid file (containing data holes, resolution = 3 minutes)")
401 400 << QStringLiteral("spectro/ValidSpectrogramDataHoles.txt")
402 401 << ExpectedResults<SpectrogramSeries>{}
403 402 .setParsingOK(true)
404 403 .setXAxisUnit(Unit{"t", true})
405 404 .setXAxisData({dateTime(2011, 12, 10, 12, 10, 54), //
406 405 dateTime(2011, 12, 10, 12, 13, 54), // Data hole
407 406 dateTime(2011, 12, 10, 12, 16, 54), // Data hole
408 407 dateTime(2011, 12, 10, 12, 17, 23), //
409 408 dateTime(2011, 12, 10, 12, 20, 23), // Data hole
410 409 dateTime(2011, 12, 10, 12, 23, 23), // Data hole
411 410 dateTime(2011, 12, 10, 12, 23, 51), //
412 411 dateTime(2011, 12, 10, 12, 26, 51), // Data hole
413 412 dateTime(2011, 12, 10, 12, 29, 51), // Data hole
414 413 dateTime(2011, 12, 10, 12, 30, 19), //
415 414 dateTime(2011, 12, 10, 12, 33, 19), // Data hole
416 415 dateTime(2011, 12, 10, 12, 35, 04), //
417 416 dateTime(2011, 12, 10, 12, 36, 41), //
418 417 dateTime(2011, 12, 10, 12, 38, 18), //
419 418 dateTime(2011, 12, 10, 12, 39, 55)})
420 419 .setYAxisEnabled(true)
421 420 .setYAxisUnit(Unit{"eV"})
422 421 .setYAxisData({16485.85, 20996.1}) // middle of the intervals of each band
423 422 .setValuesUnit(Unit{"eV/(cm^2-s-sr-eV)"})
424 423 .setValuesData(QVector<QVector<double> >{{2577578.000, //
425 424 nan, // Data hole
426 425 nan, // Data hole
427 426 2314121.500, //
428 427 nan, // Data hole
429 428 nan, // Data hole
430 429 2063608.750, //
431 430 nan, // Data hole
432 431 nan, // Data hole
433 432 2234525.500, //
434 433 nan, // Data hole
435 434 1670215.250, //
436 435 1689243.250, //
437 436 1654617.125, //
438 437 1504983.750},
439 438 {2336016.000, //
440 439 nan, // Data hole
441 440 nan, // Data hole
442 441 1712093.125, //
443 442 nan, // Data hole
444 443 nan, // Data hole
445 444 1614491.625, //
446 445 nan, // Data hole
447 446 nan, // Data hole
448 447 1764516.500, //
449 448 nan, // Data hole
450 449 1688078.500, //
451 450 1743183.500, //
452 451 1733603.250, //
453 452 1708356.500}});
454 453
455 454 QTest::newRow(
456 455 "Valid file (containing data holes at the beginning and the end, resolution = 4 minutes)")
457 456 << QStringLiteral("spectro/ValidSpectrogramDataHoles2.txt")
458 457 << ExpectedResults<SpectrogramSeries>{}
459 458 .setParsingOK(true)
460 459 .setXAxisUnit(Unit{"t", true})
461 460 .setXAxisData({
462 461 dateTime(2011, 12, 10, 12, 2, 54), // Data hole
463 462 dateTime(2011, 12, 10, 12, 6, 54), // Data hole
464 463 dateTime(2011, 12, 10, 12, 10, 54), //
465 464 dateTime(2011, 12, 10, 12, 14, 54), // Data hole
466 465 dateTime(2011, 12, 10, 12, 17, 23), //
467 466 dateTime(2011, 12, 10, 12, 21, 23), // Data hole
468 467 dateTime(2011, 12, 10, 12, 23, 51), //
469 468 dateTime(2011, 12, 10, 12, 27, 51), // Data hole
470 469 dateTime(2011, 12, 10, 12, 30, 19), //
471 470 dateTime(2011, 12, 10, 12, 34, 19), // Data hole
472 471 dateTime(2011, 12, 10, 12, 35, 04), //
473 472 dateTime(2011, 12, 10, 12, 36, 41), //
474 473 dateTime(2011, 12, 10, 12, 38, 18), //
475 474 dateTime(2011, 12, 10, 12, 39, 55),
476 475 dateTime(2011, 12, 10, 12, 43, 55), // Data hole
477 476 dateTime(2011, 12, 10, 12, 47, 55), // Data hole
478 477 dateTime(2011, 12, 10, 12, 51, 55), // Data hole
479 478 dateTime(2011, 12, 10, 12, 55, 55), // Data hole
480 479 dateTime(2011, 12, 10, 12, 59, 55) // Data hole
481 480 })
482 481 .setYAxisEnabled(true)
483 482 .setYAxisUnit(Unit{"eV"})
484 483 .setYAxisData({16485.85, 20996.1}) // middle of the intervals of each band
485 484 .setValuesUnit(Unit{"eV/(cm^2-s-sr-eV)"})
486 485 .setValuesData(QVector<QVector<double> >{{
487 486 nan, // Data hole
488 487 nan, // Data hole
489 488 2577578.000, //
490 489 nan, // Data hole
491 490 2314121.500, //
492 491 nan, // Data hole
493 492 2063608.750, //
494 493 nan, // Data hole
495 494 2234525.500, //
496 495 nan, // Data hole
497 496 1670215.250, //
498 497 1689243.250, //
499 498 1654617.125, //
500 499 1504983.750, //
501 500 nan, // Data hole
502 501 nan, // Data hole
503 502 nan, // Data hole
504 503 nan, // Data hole
505 504 nan // Data hole
506 505 },
507 506 {
508 507 nan, // Data hole
509 508 nan, // Data hole
510 509 2336016.000, //
511 510 nan, // Data hole
512 511 1712093.125, //
513 512 nan, // Data hole
514 513 1614491.625, //
515 514 nan, // Data hole
516 515 1764516.500, //
517 516 nan, // Data hole
518 517 1688078.500, //
519 518 1743183.500, //
520 519 1733603.250, //
521 520 1708356.500, //
522 521 nan, // Data hole
523 522 nan, // Data hole
524 523 nan, // Data hole
525 524 nan, // Data hole
526 525 nan // Data hole
527 526 }});
528 527
529 528 // Invalid files
530 529 QTest::newRow("Invalid file (inconsistent bands)")
531 530 << QStringLiteral("spectro/InvalidSpectrogramWrongBands.txt")
532 531 << ExpectedResults<SpectrogramSeries>{}.setParsingOK(false);
533 532 }
534 533
535 534 void TestAmdaResultParser::testReadSpectrogramTxt()
536 535 {
537 536 testRead<SpectrogramSeries>(AmdaResultParser::ValueType::SPECTROGRAM);
538 537 }
539 538
540 539 void TestAmdaResultParser::testReadVectorTxt_data()
541 540 {
542 541 testReadDataStructure<VectorSeries>();
543 542
544 543 // ////////// //
545 544 // Test cases //
546 545 // ////////// //
547 546
548 547 // Valid files
549 548 QTest::newRow("Valid file")
550 549 << QStringLiteral("ValidVector1.txt")
551 550 << ExpectedResults<VectorSeries>{}
552 551 .setParsingOK(true)
553 552 .setXAxisUnit(Unit{"nT", true})
554 553 .setXAxisData({dateTime(2013, 7, 2, 9, 13, 50), dateTime(2013, 7, 2, 9, 14, 6),
555 554 dateTime(2013, 7, 2, 9, 14, 22), dateTime(2013, 7, 2, 9, 14, 38),
556 555 dateTime(2013, 7, 2, 9, 14, 54), dateTime(2013, 7, 2, 9, 15, 10),
557 556 dateTime(2013, 7, 2, 9, 15, 26), dateTime(2013, 7, 2, 9, 15, 42),
558 557 dateTime(2013, 7, 2, 9, 15, 58), dateTime(2013, 7, 2, 9, 16, 14)})
559 558 .setValuesData(
560 559 {{-0.332, -1.011, -1.457, -1.293, -1.217, -1.443, -1.278, -1.202, -1.22, -1.259},
561 560 {3.206, 2.999, 2.785, 2.736, 2.612, 2.564, 2.892, 2.862, 2.859, 2.764},
562 561 {0.058, 0.496, 1.018, 1.485, 1.662, 1.505, 1.168, 1.244, 1.15, 1.358}});
563 562
564 563 // Valid files but with some invalid lines (wrong unit, wrong values, etc.)
565 564 QTest::newRow("Invalid file type (scalar)")
566 565 << QStringLiteral("ValidScalar1.txt")
567 566 << ExpectedResults<VectorSeries>{}
568 567 .setParsingOK(true)
569 568 .setXAxisUnit(Unit{"nT", true})
570 569 .setXAxisData({})
571 570 .setValuesData(QVector<QVector<double> >{{}, {}, {}});
572 571 }
573 572
574 573 void TestAmdaResultParser::testReadVectorTxt()
575 574 {
576 575 testRead<VectorSeries>(AmdaResultParser::ValueType::VECTOR);
577 576 }
578 577
579 578 QTEST_MAIN(TestAmdaResultParser)
580 579 #include "TestAmdaResultParser.moc"
General Comments 0
You need to be logged in to leave comments. Login now