##// END OF EJS Templates
Adds comments in DataSeries description
Alexandre Leroux -
r868:6830a7435d55
parent child
Show More
@@ -1,429 +1,482
1 1 #ifndef SCIQLOP_DATASERIES_H
2 2 #define SCIQLOP_DATASERIES_H
3 3
4 4 #include "CoreGlobal.h"
5 5
6 6 #include <Common/SortUtils.h>
7 7
8 8 #include <Data/ArrayData.h>
9 9 #include <Data/DataSeriesMergeHelper.h>
10 10 #include <Data/IDataSeries.h>
11 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 39 m_ValuesIt(begin ? dataSeries.valuesData()->begin() : dataSeries.valuesData()->end())
40 40 {
41 41 }
42 42
43 43 template <bool IC = IsConst, typename = std::enable_if_t<IC == true> >
44 44 explicit IteratorValue(const DataSeries<Dim> &dataSeries, bool begin)
45 45 : m_XIt(begin ? dataSeries.xAxisData()->cbegin() : dataSeries.xAxisData()->cend()),
46 46 m_ValuesIt(begin ? dataSeries.valuesData()->cbegin()
47 47 : dataSeries.valuesData()->cend())
48 48 {
49 49 }
50 50
51 51 IteratorValue(const IteratorValue &other) = default;
52 52
53 53 std::unique_ptr<DataSeriesIteratorValue::Impl> clone() const override
54 54 {
55 55 return std::make_unique<IteratorValue<Dim, IsConst> >(*this);
56 56 }
57 57
58 58 int distance(const DataSeriesIteratorValue::Impl &other) const override try {
59 59 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
60 60 return m_XIt->distance(*otherImpl.m_XIt);
61 61 }
62 62 catch (const std::bad_cast &) {
63 63 return 0;
64 64 }
65 65
66 66 bool equals(const DataSeriesIteratorValue::Impl &other) const override try {
67 67 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
68 68 return std::tie(m_XIt, m_ValuesIt) == std::tie(otherImpl.m_XIt, otherImpl.m_ValuesIt);
69 69 }
70 70 catch (const std::bad_cast &) {
71 71 return false;
72 72 }
73 73
74 74 bool lowerThan(const DataSeriesIteratorValue::Impl &other) const override try {
75 75 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
76 76 return m_XIt->lowerThan(*otherImpl.m_XIt);
77 77 }
78 78 catch (const std::bad_cast &) {
79 79 return false;
80 80 }
81 81
82 82 std::unique_ptr<DataSeriesIteratorValue::Impl> advance(int offset) const override
83 83 {
84 84 auto result = clone();
85 85 result->next(offset);
86 86 return result;
87 87 }
88 88
89 89 void next(int offset) override
90 90 {
91 91 m_XIt->next(offset);
92 92 m_ValuesIt->next(offset);
93 93 }
94 94
95 95 void prev() override
96 96 {
97 97 --m_XIt;
98 98 --m_ValuesIt;
99 99 }
100 100
101 101 double x() const override { return m_XIt->at(0); }
102 102 double value() const override { return m_ValuesIt->at(0); }
103 103 double value(int componentIndex) const override { return m_ValuesIt->at(componentIndex); }
104 104 double minValue() const override { return m_ValuesIt->min(); }
105 105 double maxValue() const override { return m_ValuesIt->max(); }
106 106 QVector<double> values() const override { return m_ValuesIt->values(); }
107 107
108 108 void swap(DataSeriesIteratorValue::Impl &other) override
109 109 {
110 110 auto &otherImpl = dynamic_cast<IteratorValue &>(other);
111 111 m_XIt->impl()->swap(*otherImpl.m_XIt->impl());
112 112 m_ValuesIt->impl()->swap(*otherImpl.m_ValuesIt->impl());
113 113 }
114 114
115 115 private:
116 116 ArrayDataIterator m_XIt;
117 117 ArrayDataIterator m_ValuesIt;
118 118 };
119 119 } // namespace dataseries_detail
120 120
121 121 /**
122 122 * @brief The DataSeries class is the base (abstract) implementation of IDataSeries.
123 123 *
124 * It proposes to set a dimension for the values ​​data.
124 * The DataSeries represents values on one or two axes, according to these rules:
125 * - the x-axis is always defined
126 * - an y-axis can be defined or not. If set, additional consistency checks apply to the values (see
127 * below)
128 * - the values are defined on one or two dimensions. In the case of 2-dim values, the data is
129 * distributed into components (for example, a vector defines three components)
130 * - New values can be added to the series, on the x-axis.
131 * - Once initialized to the series creation, the y-axis (if defined) is no longer modifiable
132 * - Data representing values and axes are associated with a unit
133 * - The data series is always sorted in ascending order on the x-axis.
125 134 *
126 * A DataSeries is always sorted on its x-axis data.
135 * Consistency checks are carried out between the axes and the values. These controls are provided
136 * throughout the DataSeries lifecycle:
137 * - the number of data on the x-axis must be equal to the number of values (in the case of
138 * 2-dim ArrayData for values, the test is performed on the number of values per component)
139 * - if the y-axis is defined, the number of components of the ArrayData for values must equal the
140 * number of data on the y-axis.
141 *
142 * Examples:
143 * 1)
144 * - x-axis: [1 ; 2 ; 3]
145 * - y-axis: not defined
146 * - values: [10 ; 20 ; 30] (1-dim ArrayData)
147 * => the DataSeries is valid, as x-axis and values have the same number of data
148 *
149 * 2)
150 * - x-axis: [1 ; 2 ; 3]
151 * - y-axis: not defined
152 * - values: [10 ; 20 ; 30 ; 40] (1-dim ArrayData)
153 * => the DataSeries is invalid, as x-axis and values haven't the same number of data
154 *
155 * 3)
156 * - x-axis: [1 ; 2 ; 3]
157 * - y-axis: not defined
158 * - values: [10 ; 20 ; 30
159 * 40 ; 50 ; 60] (2-dim ArrayData)
160 * => the DataSeries is valid, as x-axis has 3 data and values contains 2 components with 3
161 * data each
162 *
163 * 4)
164 * - x-axis: [1 ; 2 ; 3]
165 * - y-axis: [1 ; 2]
166 * - values: [10 ; 20 ; 30
167 * 40 ; 50 ; 60] (2-dim ArrayData)
168 * => the DataSeries is valid, as:
169 * - x-axis has 3 data and values contains 2 components with 3 data each AND
170 * - y-axis has 2 data and values contains 2 components
171 *
172 * 5)
173 * - x-axis: [1 ; 2 ; 3]
174 * - y-axis: [1 ; 2 ; 3]
175 * - values: [10 ; 20 ; 30
176 * 40 ; 50 ; 60] (2-dim ArrayData)
177 * => the DataSeries is invalid, as:
178 * - x-axis has 3 data and values contains 2 components with 3 data each BUT
179 * - y-axis has 3 data and values contains only 2 components
127 180 *
128 181 * @tparam Dim The dimension of the values data
129 182 *
130 183 */
131 184 template <int Dim>
132 185 class SCIQLOP_CORE_EXPORT DataSeries : public IDataSeries {
133 186 friend class DataSeriesMergeHelper;
134 187
135 188 public:
136 189 /// @sa IDataSeries::xAxisData()
137 190 std::shared_ptr<ArrayData<1> > xAxisData() override { return m_XAxisData; }
138 191 const std::shared_ptr<ArrayData<1> > xAxisData() const { return m_XAxisData; }
139 192
140 193 /// @sa IDataSeries::xAxisUnit()
141 194 Unit xAxisUnit() const override { return m_XAxisUnit; }
142 195
143 196 /// @return the values dataset
144 197 std::shared_ptr<ArrayData<Dim> > valuesData() { return m_ValuesData; }
145 198 const std::shared_ptr<ArrayData<Dim> > valuesData() const { return m_ValuesData; }
146 199
147 200 /// @sa IDataSeries::valuesUnit()
148 201 Unit valuesUnit() const override { return m_ValuesUnit; }
149 202
150 203 int nbPoints() const override { return m_ValuesData->totalSize(); }
151 204
152 205 void clear()
153 206 {
154 207 m_XAxisData->clear();
155 208 m_ValuesData->clear();
156 209 }
157 210
158 211 bool isEmpty() const noexcept { return m_XAxisData->size() == 0; }
159 212
160 213 /// Merges into the data series an other data series.
161 214 ///
162 215 /// The two dataseries:
163 216 /// - must be of the same dimension
164 217 /// - must have the same y-axis (if defined)
165 218 ///
166 219 /// If the prerequisites are not valid, the method does nothing
167 220 ///
168 221 /// @remarks the data series to merge with is cleared after the operation
169 222 void merge(IDataSeries *dataSeries) override
170 223 {
171 224 dataSeries->lockWrite();
172 225 lockWrite();
173 226
174 227 if (auto other = dynamic_cast<DataSeries<Dim> *>(dataSeries)) {
175 228 if (m_YAxis == other->m_YAxis) {
176 229 DataSeriesMergeHelper::merge(*other, *this);
177 230 }
178 231 else {
179 232 qCWarning(LOG_DataSeries())
180 233 << QObject::tr("Can't merge data series that have not the same y-axis");
181 234 }
182 235 }
183 236 else {
184 237 qCWarning(LOG_DataSeries())
185 238 << QObject::tr("Detection of a type of IDataSeries we cannot merge with !");
186 239 }
187 240 unlock();
188 241 dataSeries->unlock();
189 242 }
190 243
191 244 void purge(double min, double max) override
192 245 {
193 246 // Nothing to purge if series is empty
194 247 if (isEmpty()) {
195 248 return;
196 249 }
197 250
198 251 if (min > max) {
199 252 std::swap(min, max);
200 253 }
201 254
202 255 // Nothing to purge if series min/max are inside purge range
203 256 auto xMin = cbegin()->x();
204 257 auto xMax = (--cend())->x();
205 258 if (xMin >= min && xMax <= max) {
206 259 return;
207 260 }
208 261
209 262 auto lowerIt = std::lower_bound(
210 263 begin(), end(), min, [](const auto &it, const auto &val) { return it.x() < val; });
211 264 erase(begin(), lowerIt);
212 265 auto upperIt = std::upper_bound(
213 266 begin(), end(), max, [](const auto &val, const auto &it) { return val < it.x(); });
214 267 erase(upperIt, end());
215 268 }
216 269
217 270 // ///////// //
218 271 // Iterators //
219 272 // ///////// //
220 273
221 274 DataSeriesIterator begin() override
222 275 {
223 276 return DataSeriesIterator{DataSeriesIteratorValue{
224 277 std::make_unique<dataseries_detail::IteratorValue<Dim, false> >(*this, true)}};
225 278 }
226 279
227 280 DataSeriesIterator end() override
228 281 {
229 282 return DataSeriesIterator{DataSeriesIteratorValue{
230 283 std::make_unique<dataseries_detail::IteratorValue<Dim, false> >(*this, false)}};
231 284 }
232 285
233 286 DataSeriesIterator cbegin() const override
234 287 {
235 288 return DataSeriesIterator{DataSeriesIteratorValue{
236 289 std::make_unique<dataseries_detail::IteratorValue<Dim, true> >(*this, true)}};
237 290 }
238 291
239 292 DataSeriesIterator cend() const override
240 293 {
241 294 return DataSeriesIterator{DataSeriesIteratorValue{
242 295 std::make_unique<dataseries_detail::IteratorValue<Dim, true> >(*this, false)}};
243 296 }
244 297
245 298 void erase(DataSeriesIterator first, DataSeriesIterator last)
246 299 {
247 300 auto firstImpl
248 301 = dynamic_cast<dataseries_detail::IteratorValue<Dim, false> *>(first->impl());
249 302 auto lastImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, false> *>(last->impl());
250 303
251 304 if (firstImpl && lastImpl) {
252 305 m_XAxisData->erase(firstImpl->m_XIt, lastImpl->m_XIt);
253 306 m_ValuesData->erase(firstImpl->m_ValuesIt, lastImpl->m_ValuesIt);
254 307 }
255 308 }
256 309
257 310 void insert(DataSeriesIterator first, DataSeriesIterator last, bool prepend = false)
258 311 {
259 312 auto firstImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, true> *>(first->impl());
260 313 auto lastImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, true> *>(last->impl());
261 314
262 315 if (firstImpl && lastImpl) {
263 316 m_XAxisData->insert(firstImpl->m_XIt, lastImpl->m_XIt, prepend);
264 317 m_ValuesData->insert(firstImpl->m_ValuesIt, lastImpl->m_ValuesIt, prepend);
265 318 }
266 319 }
267 320
268 321 /// @sa IDataSeries::minXAxisData()
269 322 DataSeriesIterator minXAxisData(double minXAxisData) const override
270 323 {
271 324 return std::lower_bound(
272 325 cbegin(), cend(), minXAxisData,
273 326 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
274 327 }
275 328
276 329 /// @sa IDataSeries::maxXAxisData()
277 330 DataSeriesIterator maxXAxisData(double maxXAxisData) const override
278 331 {
279 332 // Gets the first element that greater than max value
280 333 auto it = std::upper_bound(
281 334 cbegin(), cend(), maxXAxisData,
282 335 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
283 336
284 337 return it == cbegin() ? cend() : --it;
285 338 }
286 339
287 340 std::pair<DataSeriesIterator, DataSeriesIterator> xAxisRange(double minXAxisData,
288 341 double maxXAxisData) const override
289 342 {
290 343 if (minXAxisData > maxXAxisData) {
291 344 std::swap(minXAxisData, maxXAxisData);
292 345 }
293 346
294 347 auto begin = cbegin();
295 348 auto end = cend();
296 349
297 350 auto lowerIt = std::lower_bound(
298 351 begin, end, minXAxisData,
299 352 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
300 353 auto upperIt = std::upper_bound(
301 354 lowerIt, end, maxXAxisData,
302 355 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
303 356
304 357 return std::make_pair(lowerIt, upperIt);
305 358 }
306 359
307 360 std::pair<DataSeriesIterator, DataSeriesIterator>
308 361 valuesBounds(double minXAxisData, double maxXAxisData) const override
309 362 {
310 363 // Places iterators to the correct x-axis range
311 364 auto xAxisRangeIts = xAxisRange(minXAxisData, maxXAxisData);
312 365
313 366 // Returns end iterators if the range is empty
314 367 if (xAxisRangeIts.first == xAxisRangeIts.second) {
315 368 return std::make_pair(cend(), cend());
316 369 }
317 370
318 371 // Gets the iterator on the min of all values data
319 372 auto minIt = std::min_element(
320 373 xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) {
321 374 return SortUtils::minCompareWithNaN(it1.minValue(), it2.minValue());
322 375 });
323 376
324 377 // Gets the iterator on the max of all values data
325 378 auto maxIt = std::max_element(
326 379 xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) {
327 380 return SortUtils::maxCompareWithNaN(it1.maxValue(), it2.maxValue());
328 381 });
329 382
330 383 return std::make_pair(minIt, maxIt);
331 384 }
332 385
333 386 // /////// //
334 387 // Mutexes //
335 388 // /////// //
336 389
337 390 virtual void lockRead() { m_Lock.lockForRead(); }
338 391 virtual void lockWrite() { m_Lock.lockForWrite(); }
339 392 virtual void unlock() { m_Lock.unlock(); }
340 393
341 394 protected:
342 395 /// Protected ctor (DataSeries is abstract).
343 396 ///
344 397 /// Data vectors must be consistent with each other, otherwise an exception will be thrown (@sa
345 398 /// class description for consistent rules)
346 399 /// @remarks data series is automatically sorted on its x-axis data
347 400 /// @throws std::invalid_argument if the data are inconsistent with each other
348 401 explicit DataSeries(std::shared_ptr<ArrayData<1> > xAxisData, const Unit &xAxisUnit,
349 402 std::shared_ptr<ArrayData<Dim> > valuesData, const Unit &valuesUnit,
350 403 OptionalAxis yAxis = OptionalAxis{})
351 404 : m_XAxisData{xAxisData},
352 405 m_XAxisUnit{xAxisUnit},
353 406 m_ValuesData{valuesData},
354 407 m_ValuesUnit{valuesUnit},
355 408 m_YAxis{std::move(yAxis)}
356 409 {
357 410 if (m_XAxisData->size() != m_ValuesData->size()) {
358 411 throw std::invalid_argument{
359 412 "The number of values by component must be equal to the number of x-axis data"};
360 413 }
361 414
362 415 // Validates y-axis (if defined)
363 416 if (yAxis.isDefined() && (yAxis.size() != m_ValuesData->componentCount())) {
364 417 throw std::invalid_argument{
365 418 "As the y-axis is defined, the number of value components must be equal to the "
366 419 "number of y-axis data"};
367 420 }
368 421
369 422 // Sorts data if it's not the case
370 423 const auto &xAxisCData = m_XAxisData->cdata();
371 424 if (!std::is_sorted(xAxisCData.cbegin(), xAxisCData.cend())) {
372 425 sort();
373 426 }
374 427 }
375 428
376 429 /// Copy ctor
377 430 explicit DataSeries(const DataSeries<Dim> &other)
378 431 : m_XAxisData{std::make_shared<ArrayData<1> >(*other.m_XAxisData)},
379 432 m_XAxisUnit{other.m_XAxisUnit},
380 433 m_ValuesData{std::make_shared<ArrayData<Dim> >(*other.m_ValuesData)},
381 434 m_ValuesUnit{other.m_ValuesUnit},
382 435 m_YAxis{other.m_YAxis}
383 436 {
384 437 // Since a series is ordered from its construction and is always ordered, it is not
385 438 // necessary to call the sort method here ('other' is sorted)
386 439 }
387 440
388 441 /// @return the y-axis associated to the data series
389 442 OptionalAxis yAxis() const { return m_YAxis; }
390 443
391 444 /// Assignment operator
392 445 template <int D>
393 446 DataSeries &operator=(DataSeries<D> other)
394 447 {
395 448 std::swap(m_XAxisData, other.m_XAxisData);
396 449 std::swap(m_XAxisUnit, other.m_XAxisUnit);
397 450 std::swap(m_ValuesData, other.m_ValuesData);
398 451 std::swap(m_ValuesUnit, other.m_ValuesUnit);
399 452 std::swap(m_YAxis, other.m_YAxis);
400 453
401 454 return *this;
402 455 }
403 456
404 457 private:
405 458 /**
406 459 * Sorts data series on its x-axis data
407 460 */
408 461 void sort() noexcept
409 462 {
410 463 auto permutation = SortUtils::sortPermutation(*m_XAxisData, std::less<double>());
411 464 m_XAxisData = m_XAxisData->sort(permutation);
412 465 m_ValuesData = m_ValuesData->sort(permutation);
413 466 }
414 467
415 468 // x-axis
416 469 std::shared_ptr<ArrayData<1> > m_XAxisData;
417 470 Unit m_XAxisUnit;
418 471
419 472 // values
420 473 std::shared_ptr<ArrayData<Dim> > m_ValuesData;
421 474 Unit m_ValuesUnit;
422 475
423 476 // y-axis (optional)
424 477 OptionalAxis m_YAxis;
425 478
426 479 QReadWriteLock m_Lock;
427 480 };
428 481
429 482 #endif // SCIQLOP_DATASERIES_H
General Comments 0
You need to be logged in to leave comments. Login now