##// END OF EJS Templates
Updates access to y-axis properties of the data series (1)...
Alexandre Leroux -
r988:99f1718ec669
parent child
Show More
@@ -1,483 +1,488
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 124 * The DataSeries represents values on one or two axes, according to these rules:
125 125 * - the x-axis is always defined
126 126 * - an y-axis can be defined or not. If set, additional consistency checks apply to the values (see
127 127 * below)
128 128 * - the values are defined on one or two dimensions. In the case of 2-dim values, the data is
129 129 * distributed into components (for example, a vector defines three components)
130 130 * - New values can be added to the series, on the x-axis.
131 131 * - Once initialized to the series creation, the y-axis (if defined) is no longer modifiable
132 132 * - Data representing values and axes are associated with a unit
133 133 * - The data series is always sorted in ascending order on the x-axis.
134 134 *
135 135 * Consistency checks are carried out between the axes and the values. These controls are provided
136 136 * throughout the DataSeries lifecycle:
137 137 * - the number of data on the x-axis must be equal to the number of values (in the case of
138 138 * 2-dim ArrayData for values, the test is performed on the number of values per component)
139 139 * - if the y-axis is defined, the number of components of the ArrayData for values must equal the
140 140 * number of data on the y-axis.
141 141 *
142 142 * Examples:
143 143 * 1)
144 144 * - x-axis: [1 ; 2 ; 3]
145 145 * - y-axis: not defined
146 146 * - values: [10 ; 20 ; 30] (1-dim ArrayData)
147 147 * => the DataSeries is valid, as x-axis and values have the same number of data
148 148 *
149 149 * 2)
150 150 * - x-axis: [1 ; 2 ; 3]
151 151 * - y-axis: not defined
152 152 * - values: [10 ; 20 ; 30 ; 40] (1-dim ArrayData)
153 153 * => the DataSeries is invalid, as x-axis and values haven't the same number of data
154 154 *
155 155 * 3)
156 156 * - x-axis: [1 ; 2 ; 3]
157 157 * - y-axis: not defined
158 158 * - values: [10 ; 20 ; 30
159 159 * 40 ; 50 ; 60] (2-dim ArrayData)
160 160 * => the DataSeries is valid, as x-axis has 3 data and values contains 2 components with 3
161 161 * data each
162 162 *
163 163 * 4)
164 164 * - x-axis: [1 ; 2 ; 3]
165 165 * - y-axis: [1 ; 2]
166 166 * - values: [10 ; 20 ; 30
167 167 * 40 ; 50 ; 60] (2-dim ArrayData)
168 168 * => the DataSeries is valid, as:
169 169 * - x-axis has 3 data and values contains 2 components with 3 data each AND
170 170 * - y-axis has 2 data and values contains 2 components
171 171 *
172 172 * 5)
173 173 * - x-axis: [1 ; 2 ; 3]
174 174 * - y-axis: [1 ; 2 ; 3]
175 175 * - values: [10 ; 20 ; 30
176 176 * 40 ; 50 ; 60] (2-dim ArrayData)
177 177 * => the DataSeries is invalid, as:
178 178 * - x-axis has 3 data and values contains 2 components with 3 data each BUT
179 179 * - y-axis has 3 data and values contains only 2 components
180 180 *
181 181 * @tparam Dim The dimension of the values data
182 182 *
183 183 */
184 184 template <int Dim>
185 185 class SCIQLOP_CORE_EXPORT DataSeries : public IDataSeries {
186 186 friend class DataSeriesMergeHelper;
187 187
188 188 public:
189 189 /// @sa IDataSeries::xAxisData()
190 190 std::shared_ptr<ArrayData<1> > xAxisData() override { return m_XAxisData; }
191 191 const std::shared_ptr<ArrayData<1> > xAxisData() const { return m_XAxisData; }
192 192
193 193 /// @sa IDataSeries::xAxisUnit()
194 194 Unit xAxisUnit() const override { return m_XAxisUnit; }
195 195
196 /// @sa IDataSeries::yAxisUnit()
197 Unit yAxisUnit() const override { return m_YAxis.unit(); }
198
196 199 /// @return the values dataset
197 200 std::shared_ptr<ArrayData<Dim> > valuesData() { return m_ValuesData; }
198 201 const std::shared_ptr<ArrayData<Dim> > valuesData() const { return m_ValuesData; }
199 202
200 203 /// @sa IDataSeries::valuesUnit()
201 204 Unit valuesUnit() const override { return m_ValuesUnit; }
202 205
203 206 int nbPoints() const override { return m_ValuesData->totalSize(); }
204 207
208 std::pair<double, double> yBounds() const override { return m_YAxis.bounds(); }
209
205 210 void clear()
206 211 {
207 212 m_XAxisData->clear();
208 213 m_ValuesData->clear();
209 214 }
210 215
211 216 bool isEmpty() const noexcept { return m_XAxisData->size() == 0; }
212 217
213 218 /// Merges into the data series an other data series.
214 219 ///
215 220 /// The two dataseries:
216 221 /// - must be of the same dimension
217 222 /// - must have the same y-axis (if defined)
218 223 ///
219 224 /// If the prerequisites are not valid, the method does nothing
220 225 ///
221 226 /// @remarks the data series to merge with is cleared after the operation
222 227 void merge(IDataSeries *dataSeries) override
223 228 {
224 229 dataSeries->lockWrite();
225 230 lockWrite();
226 231
227 232 if (auto other = dynamic_cast<DataSeries<Dim> *>(dataSeries)) {
228 233 if (m_YAxis == other->m_YAxis) {
229 234 DataSeriesMergeHelper::merge(*other, *this);
230 235 }
231 236 else {
232 237 qCWarning(LOG_DataSeries())
233 238 << QObject::tr("Can't merge data series that have not the same y-axis");
234 239 }
235 240 }
236 241 else {
237 242 qCWarning(LOG_DataSeries())
238 243 << QObject::tr("Detection of a type of IDataSeries we cannot merge with !");
239 244 }
240 245 unlock();
241 246 dataSeries->unlock();
242 247 }
243 248
244 249 void purge(double min, double max) override
245 250 {
246 251 // Nothing to purge if series is empty
247 252 if (isEmpty()) {
248 253 return;
249 254 }
250 255
251 256 if (min > max) {
252 257 std::swap(min, max);
253 258 }
254 259
255 260 // Nothing to purge if series min/max are inside purge range
256 261 auto xMin = cbegin()->x();
257 262 auto xMax = (--cend())->x();
258 263 if (xMin >= min && xMax <= max) {
259 264 return;
260 265 }
261 266
262 267 auto lowerIt = std::lower_bound(
263 268 begin(), end(), min, [](const auto &it, const auto &val) { return it.x() < val; });
264 269 erase(begin(), lowerIt);
265 270 auto upperIt = std::upper_bound(
266 271 begin(), end(), max, [](const auto &val, const auto &it) { return val < it.x(); });
267 272 erase(upperIt, end());
268 273 }
269 274
270 275 // ///////// //
271 276 // Iterators //
272 277 // ///////// //
273 278
274 279 DataSeriesIterator begin() override
275 280 {
276 281 return DataSeriesIterator{DataSeriesIteratorValue{
277 282 std::make_unique<dataseries_detail::IteratorValue<Dim, false> >(*this, true)}};
278 283 }
279 284
280 285 DataSeriesIterator end() override
281 286 {
282 287 return DataSeriesIterator{DataSeriesIteratorValue{
283 288 std::make_unique<dataseries_detail::IteratorValue<Dim, false> >(*this, false)}};
284 289 }
285 290
286 291 DataSeriesIterator cbegin() const override
287 292 {
288 293 return DataSeriesIterator{DataSeriesIteratorValue{
289 294 std::make_unique<dataseries_detail::IteratorValue<Dim, true> >(*this, true)}};
290 295 }
291 296
292 297 DataSeriesIterator cend() const override
293 298 {
294 299 return DataSeriesIterator{DataSeriesIteratorValue{
295 300 std::make_unique<dataseries_detail::IteratorValue<Dim, true> >(*this, false)}};
296 301 }
297 302
298 303 void erase(DataSeriesIterator first, DataSeriesIterator last)
299 304 {
300 305 auto firstImpl
301 306 = dynamic_cast<dataseries_detail::IteratorValue<Dim, false> *>(first->impl());
302 307 auto lastImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, false> *>(last->impl());
303 308
304 309 if (firstImpl && lastImpl) {
305 310 m_XAxisData->erase(firstImpl->m_XIt, lastImpl->m_XIt);
306 311 m_ValuesData->erase(firstImpl->m_ValuesIt, lastImpl->m_ValuesIt);
307 312 }
308 313 }
309 314
310 315 void insert(DataSeriesIterator first, DataSeriesIterator last, bool prepend = false)
311 316 {
312 317 auto firstImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, true> *>(first->impl());
313 318 auto lastImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, true> *>(last->impl());
314 319
315 320 if (firstImpl && lastImpl) {
316 321 m_XAxisData->insert(firstImpl->m_XIt, lastImpl->m_XIt, prepend);
317 322 m_ValuesData->insert(firstImpl->m_ValuesIt, lastImpl->m_ValuesIt, prepend);
318 323 }
319 324 }
320 325
321 326 /// @sa IDataSeries::minXAxisData()
322 327 DataSeriesIterator minXAxisData(double minXAxisData) const override
323 328 {
324 329 return std::lower_bound(
325 330 cbegin(), cend(), minXAxisData,
326 331 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
327 332 }
328 333
329 334 /// @sa IDataSeries::maxXAxisData()
330 335 DataSeriesIterator maxXAxisData(double maxXAxisData) const override
331 336 {
332 337 // Gets the first element that greater than max value
333 338 auto it = std::upper_bound(
334 339 cbegin(), cend(), maxXAxisData,
335 340 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
336 341
337 342 return it == cbegin() ? cend() : --it;
338 343 }
339 344
340 345 std::pair<DataSeriesIterator, DataSeriesIterator> xAxisRange(double minXAxisData,
341 346 double maxXAxisData) const override
342 347 {
343 348 if (minXAxisData > maxXAxisData) {
344 349 std::swap(minXAxisData, maxXAxisData);
345 350 }
346 351
347 352 auto begin = cbegin();
348 353 auto end = cend();
349 354
350 355 auto lowerIt = std::lower_bound(
351 356 begin, end, minXAxisData,
352 357 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
353 358 auto upperIt = std::upper_bound(
354 359 lowerIt, end, maxXAxisData,
355 360 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
356 361
357 362 return std::make_pair(lowerIt, upperIt);
358 363 }
359 364
360 365 std::pair<DataSeriesIterator, DataSeriesIterator>
361 366 valuesBounds(double minXAxisData, double maxXAxisData) const override
362 367 {
363 368 // Places iterators to the correct x-axis range
364 369 auto xAxisRangeIts = xAxisRange(minXAxisData, maxXAxisData);
365 370
366 371 // Returns end iterators if the range is empty
367 372 if (xAxisRangeIts.first == xAxisRangeIts.second) {
368 373 return std::make_pair(cend(), cend());
369 374 }
370 375
371 376 // Gets the iterator on the min of all values data
372 377 auto minIt = std::min_element(
373 378 xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) {
374 379 return SortUtils::minCompareWithNaN(it1.minValue(), it2.minValue());
375 380 });
376 381
377 382 // Gets the iterator on the max of all values data
378 383 auto maxIt = std::max_element(
379 384 xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) {
380 385 return SortUtils::maxCompareWithNaN(it1.maxValue(), it2.maxValue());
381 386 });
382 387
383 388 return std::make_pair(minIt, maxIt);
384 389 }
385 390
386 391 /// @return the y-axis associated to the data series
387 /// @todo pass getter as protected and use iterators to access the y-axis data
388 OptionalAxis yAxis() const { return m_YAxis; }
392 const OptionalAxis &yAxis() const { return m_YAxis; }
393 OptionalAxis &yAxis() { return m_YAxis; }
389 394
390 395 // /////// //
391 396 // Mutexes //
392 397 // /////// //
393 398
394 399 virtual void lockRead() { m_Lock.lockForRead(); }
395 400 virtual void lockWrite() { m_Lock.lockForWrite(); }
396 401 virtual void unlock() { m_Lock.unlock(); }
397 402
398 403 protected:
399 404 /// Protected ctor (DataSeries is abstract).
400 405 ///
401 406 /// Data vectors must be consistent with each other, otherwise an exception will be thrown (@sa
402 407 /// class description for consistent rules)
403 408 /// @remarks data series is automatically sorted on its x-axis data
404 409 /// @throws std::invalid_argument if the data are inconsistent with each other
405 410 explicit DataSeries(std::shared_ptr<ArrayData<1> > xAxisData, const Unit &xAxisUnit,
406 411 std::shared_ptr<ArrayData<Dim> > valuesData, const Unit &valuesUnit,
407 412 OptionalAxis yAxis = OptionalAxis{})
408 413 : m_XAxisData{xAxisData},
409 414 m_XAxisUnit{xAxisUnit},
410 415 m_ValuesData{valuesData},
411 416 m_ValuesUnit{valuesUnit},
412 417 m_YAxis{std::move(yAxis)}
413 418 {
414 419 if (m_XAxisData->size() != m_ValuesData->size()) {
415 420 throw std::invalid_argument{
416 421 "The number of values by component must be equal to the number of x-axis data"};
417 422 }
418 423
419 424 // Validates y-axis (if defined)
420 425 if (yAxis.isDefined() && (yAxis.size() != m_ValuesData->componentCount())) {
421 426 throw std::invalid_argument{
422 427 "As the y-axis is defined, the number of value components must be equal to the "
423 428 "number of y-axis data"};
424 429 }
425 430
426 431 // Sorts data if it's not the case
427 432 const auto &xAxisCData = m_XAxisData->cdata();
428 433 if (!std::is_sorted(xAxisCData.cbegin(), xAxisCData.cend())) {
429 434 sort();
430 435 }
431 436 }
432 437
433 438 /// Copy ctor
434 439 explicit DataSeries(const DataSeries<Dim> &other)
435 440 : m_XAxisData{std::make_shared<ArrayData<1> >(*other.m_XAxisData)},
436 441 m_XAxisUnit{other.m_XAxisUnit},
437 442 m_ValuesData{std::make_shared<ArrayData<Dim> >(*other.m_ValuesData)},
438 443 m_ValuesUnit{other.m_ValuesUnit},
439 444 m_YAxis{other.m_YAxis}
440 445 {
441 446 // Since a series is ordered from its construction and is always ordered, it is not
442 447 // necessary to call the sort method here ('other' is sorted)
443 448 }
444 449
445 450 /// Assignment operator
446 451 template <int D>
447 452 DataSeries &operator=(DataSeries<D> other)
448 453 {
449 454 std::swap(m_XAxisData, other.m_XAxisData);
450 455 std::swap(m_XAxisUnit, other.m_XAxisUnit);
451 456 std::swap(m_ValuesData, other.m_ValuesData);
452 457 std::swap(m_ValuesUnit, other.m_ValuesUnit);
453 458 std::swap(m_YAxis, other.m_YAxis);
454 459
455 460 return *this;
456 461 }
457 462
458 463 private:
459 464 /**
460 465 * Sorts data series on its x-axis data
461 466 */
462 467 void sort() noexcept
463 468 {
464 469 auto permutation = SortUtils::sortPermutation(*m_XAxisData, std::less<double>());
465 470 m_XAxisData = m_XAxisData->sort(permutation);
466 471 m_ValuesData = m_ValuesData->sort(permutation);
467 472 }
468 473
469 474 // x-axis
470 475 std::shared_ptr<ArrayData<1> > m_XAxisData;
471 476 Unit m_XAxisUnit;
472 477
473 478 // values
474 479 std::shared_ptr<ArrayData<Dim> > m_ValuesData;
475 480 Unit m_ValuesUnit;
476 481
477 482 // y-axis (optional)
478 483 OptionalAxis m_YAxis;
479 484
480 485 QReadWriteLock m_Lock;
481 486 };
482 487
483 488 #endif // SCIQLOP_DATASERIES_H
@@ -1,96 +1,102
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 7 #include <Data/Unit.h>
8 8
9 9 #include <memory>
10 10
11 11 #include <QString>
12 12
13 13 template <int Dim>
14 14 class ArrayData;
15 15
16 16 /**
17 17 * @brief The IDataSeries aims to declare a data series.
18 18 *
19 19 * A data series is an entity that contains at least :
20 20 * - one dataset representing the x-axis
21 21 * - one dataset representing the values
22 22 *
23 23 * Each dataset is represented by an ArrayData, and is associated with a unit.
24 24 *
25 25 * An ArrayData can be unidimensional or two-dimensional, depending on the implementation of the
26 26 * IDataSeries. The x-axis dataset is always unidimensional.
27 27 *
28 28 * @sa ArrayData
29 29 */
30 30 class IDataSeries {
31 31 public:
32 32 virtual ~IDataSeries() noexcept = default;
33 33
34 34 /// Returns the x-axis dataset
35 35 virtual std::shared_ptr<ArrayData<1> > xAxisData() = 0;
36 36
37 37 /// Returns the x-axis dataset (as const)
38 38 virtual const std::shared_ptr<ArrayData<1> > xAxisData() const = 0;
39 39
40 40 virtual Unit xAxisUnit() const = 0;
41 41
42 /// @return the y-axis unit, if axis is defined, default unit otherwise
43 virtual Unit yAxisUnit() const = 0;
44
42 45 virtual Unit valuesUnit() const = 0;
43 46
44 47 virtual void merge(IDataSeries *dataSeries) = 0;
45 48 /// Removes from data series all entries whose value on the x-axis is not between min and max
46 49 virtual void purge(double min, double max) = 0;
47 50
48 51 /// @todo Review the name and signature of this method
49 52 virtual std::shared_ptr<IDataSeries> subDataSeries(const SqpRange &range) = 0;
50 53
51 54 virtual std::unique_ptr<IDataSeries> clone() const = 0;
52 55
53 56 /// @return the total number of points contained in the data series
54 57 virtual int nbPoints() const = 0;
55 58
59 /// @return the bounds of the y-axis axis (if defined)
60 virtual std::pair<double, double> yBounds() const = 0;
61
56 62 // ///////// //
57 63 // Iterators //
58 64 // ///////// //
59 65
60 66 virtual DataSeriesIterator cbegin() const = 0;
61 67 virtual DataSeriesIterator cend() const = 0;
62 68 virtual DataSeriesIterator begin() = 0;
63 69 virtual DataSeriesIterator end() = 0;
64 70
65 71 /// @return the iterator to the first entry of the data series whose x-axis data is greater than
66 72 /// or equal to the value passed in parameter, or the end iterator if there is no matching value
67 73 virtual DataSeriesIterator minXAxisData(double minXAxisData) const = 0;
68 74
69 75 /// @return the iterator to the last entry of the data series whose x-axis data is less than or
70 76 /// equal to the value passed in parameter, or the end iterator if there is no matching value
71 77 virtual DataSeriesIterator maxXAxisData(double maxXAxisData) const = 0;
72 78
73 79 /// @return the iterators pointing to the range of data whose x-axis values are between min and
74 80 /// max passed in parameters
75 81 virtual std::pair<DataSeriesIterator, DataSeriesIterator>
76 82 xAxisRange(double minXAxisData, double maxXAxisData) const = 0;
77 83
78 84 /// @return two iterators pointing to the data that have respectively the min and the max value
79 85 /// data of a data series' range. The search is performed for a given x-axis range.
80 86 /// @sa xAxisRange()
81 87 virtual std::pair<DataSeriesIterator, DataSeriesIterator>
82 88 valuesBounds(double minXAxisData, double maxXAxisData) const = 0;
83 89
84 90 // /////// //
85 91 // Mutexes //
86 92 // /////// //
87 93
88 94 virtual void lockRead() = 0;
89 95 virtual void lockWrite() = 0;
90 96 virtual void unlock() = 0;
91 97 };
92 98
93 99 // Required for using shared_ptr in signals/slots
94 100 SCIQLOP_REGISTER_META_TYPE(IDATASERIES_PTR_REGISTRY, std::shared_ptr<IDataSeries>)
95 101
96 102 #endif // SCIQLOP_IDATASERIES_H
@@ -1,173 +1,172
1 1 #include "Visualization/AxisRenderingUtils.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 <Visualization/qcustomplot.h>
8 8
9 9 Q_LOGGING_CATEGORY(LOG_AxisRenderingUtils, "AxisRenderingUtils")
10 10
11 11 namespace {
12 12
13 13 const auto DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd hh:mm:ss:zzz");
14 14
15 15 /// Format for datetimes on a axis
16 16 const auto DATETIME_TICKER_FORMAT = QStringLiteral("yyyy/MM/dd \nhh:mm:ss");
17 17
18 18 /// Generates the appropriate ticker for an axis, depending on whether the axis displays time or
19 19 /// non-time data
20 20 QSharedPointer<QCPAxisTicker> axisTicker(bool isTimeAxis, QCPAxis::ScaleType scaleType)
21 21 {
22 22 if (isTimeAxis) {
23 23 auto dateTicker = QSharedPointer<QCPAxisTickerDateTime>::create();
24 24 dateTicker->setDateTimeFormat(DATETIME_TICKER_FORMAT);
25 25 dateTicker->setDateTimeSpec(Qt::UTC);
26 26
27 27 return dateTicker;
28 28 }
29 29 else if (scaleType == QCPAxis::stLogarithmic) {
30 30 return QSharedPointer<QCPAxisTickerLog>::create();
31 31 }
32 32 else {
33 33 // default ticker
34 34 return QSharedPointer<QCPAxisTicker>::create();
35 35 }
36 36 }
37 37
38 38 /**
39 39 * Sets properties of the axis passed as parameter
40 40 * @param axis the axis to set
41 41 * @param unit the unit to set for the axis
42 42 * @param scaleType the scale type to set for the axis
43 43 */
44 44 void setAxisProperties(QCPAxis &axis, const Unit &unit,
45 45 QCPAxis::ScaleType scaleType = QCPAxis::stLinear)
46 46 {
47 47 // label (unit name)
48 48 axis.setLabel(unit.m_Name);
49 49
50 50 // scale type
51 51 axis.setScaleType(scaleType);
52 52 if (scaleType == QCPAxis::stLogarithmic) {
53 53 // Scientific notation
54 54 axis.setNumberPrecision(0);
55 55 axis.setNumberFormat("eb");
56 56 }
57 57
58 58 // ticker (depending on the type of unit)
59 59 axis.setTicker(axisTicker(unit.m_TimeUnit, scaleType));
60 60 }
61 61
62 62 /**
63 63 * Delegate used to set axes properties
64 64 */
65 65 template <typename T, typename Enabled = void>
66 66 struct AxisSetter {
67 67 static void setProperties(T &, QCustomPlot &, QCPColorScale &)
68 68 {
69 69 // Default implementation does nothing
70 70 qCCritical(LOG_AxisRenderingUtils()) << "Can't set axis properties: unmanaged type of data";
71 71 }
72 72 };
73 73
74 74 /**
75 75 * Specialization of AxisSetter for scalars and vectors
76 76 * @sa ScalarSeries
77 77 * @sa VectorSeries
78 78 */
79 79 template <typename T>
80 80 struct AxisSetter<T, typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
81 81 or std::is_base_of<VectorSeries, T>::value> > {
82 82 static void setProperties(T &dataSeries, QCustomPlot &plot, QCPColorScale &)
83 83 {
84 84 dataSeries.lockRead();
85 85 auto xAxisUnit = dataSeries.xAxisUnit();
86 86 auto valuesUnit = dataSeries.valuesUnit();
87 87 dataSeries.unlock();
88 88
89 89 setAxisProperties(*plot.xAxis, xAxisUnit);
90 90 setAxisProperties(*plot.yAxis, valuesUnit);
91 91 }
92 92 };
93 93
94 94 /**
95 95 * Specialization of AxisSetter for spectrograms
96 96 * @sa SpectrogramSeries
97 97 */
98 98 template <typename T>
99 99 struct AxisSetter<T, typename std::enable_if_t<std::is_base_of<SpectrogramSeries, T>::value> > {
100 100 static void setProperties(T &dataSeries, QCustomPlot &plot, QCPColorScale &colorScale)
101 101 {
102 102 dataSeries.lockRead();
103 103 auto xAxisUnit = dataSeries.xAxisUnit();
104 /// @todo ALX: use iterators here
105 auto yAxisUnit = dataSeries.yAxis().unit();
104 auto yAxisUnit = dataSeries.yAxisUnit();
106 105 auto valuesUnit = dataSeries.valuesUnit();
107 106 dataSeries.unlock();
108 107
109 108 setAxisProperties(*plot.xAxis, xAxisUnit);
110 109 setAxisProperties(*plot.yAxis, yAxisUnit, QCPAxis::stLogarithmic);
111 110
112 111 // Displays color scale in plot
113 112 plot.plotLayout()->insertRow(0);
114 113 plot.plotLayout()->addElement(0, 0, &colorScale);
115 114 colorScale.setType(QCPAxis::atTop);
116 115 colorScale.setMinimumMargins(QMargins{0, 0, 0, 0});
117 116
118 117 // Aligns color scale with axes
119 118 auto marginGroups = plot.axisRect()->marginGroups();
120 119 for (auto it = marginGroups.begin(), end = marginGroups.end(); it != end; ++it) {
121 120 colorScale.setMarginGroup(it.key(), it.value());
122 121 }
123 122
124 123 // Set color scale properties
125 124 setAxisProperties(*colorScale.axis(), valuesUnit, QCPAxis::stLogarithmic);
126 125 }
127 126 };
128 127
129 128 /**
130 129 * Default implementation of IAxisHelper, which takes data series to set axes properties
131 130 * @tparam T the data series' type
132 131 */
133 132 template <typename T>
134 133 struct AxisHelper : public IAxisHelper {
135 134 explicit AxisHelper(T &dataSeries) : m_DataSeries{dataSeries} {}
136 135
137 136 void setProperties(QCustomPlot &plot, QCPColorScale &colorScale) override
138 137 {
139 138 AxisSetter<T>::setProperties(m_DataSeries, plot, colorScale);
140 139 }
141 140
142 141 T &m_DataSeries;
143 142 };
144 143
145 144 } // namespace
146 145
147 146 QString formatValue(double value, const QCPAxis &axis)
148 147 {
149 148 // If the axis is a time axis, formats the value as a date
150 149 if (auto axisTicker = qSharedPointerDynamicCast<QCPAxisTickerDateTime>(axis.ticker())) {
151 150 return DateUtils::dateTime(value, axisTicker->dateTimeSpec()).toString(DATETIME_FORMAT);
152 151 }
153 152 else {
154 153 return QString::number(value);
155 154 }
156 155 }
157 156
158 157 std::unique_ptr<IAxisHelper>
159 158 IAxisHelperFactory::create(std::shared_ptr<IDataSeries> dataSeries) noexcept
160 159 {
161 160 if (auto scalarSeries = std::dynamic_pointer_cast<ScalarSeries>(dataSeries)) {
162 161 return std::make_unique<AxisHelper<ScalarSeries> >(*scalarSeries);
163 162 }
164 163 else if (auto spectrogramSeries = std::dynamic_pointer_cast<SpectrogramSeries>(dataSeries)) {
165 164 return std::make_unique<AxisHelper<SpectrogramSeries> >(*spectrogramSeries);
166 165 }
167 166 else if (auto vectorSeries = std::dynamic_pointer_cast<VectorSeries>(dataSeries)) {
168 167 return std::make_unique<AxisHelper<VectorSeries> >(*vectorSeries);
169 168 }
170 169 else {
171 170 return std::make_unique<AxisHelper<IDataSeries> >(*dataSeries);
172 171 }
173 172 }
@@ -1,345 +1,344
1 1 #include "Visualization/VisualizationGraphHelper.h"
2 2 #include "Visualization/qcustomplot.h"
3 3
4 4 #include <Data/ScalarSeries.h>
5 5 #include <Data/SpectrogramSeries.h>
6 6 #include <Data/VectorSeries.h>
7 7
8 8 #include <Variable/Variable.h>
9 9
10 10 Q_LOGGING_CATEGORY(LOG_VisualizationGraphHelper, "VisualizationGraphHelper")
11 11
12 12 namespace {
13 13
14 14 class SqpDataContainer : public QCPGraphDataContainer {
15 15 public:
16 16 void appendGraphData(const QCPGraphData &data) { mData.append(data); }
17 17 };
18 18
19 19 /**
20 20 * Struct used to create plottables, depending on the type of the data series from which to create
21 21 * them
22 22 * @tparam T the data series' type
23 23 * @remarks Default implementation can't create plottables
24 24 */
25 25 template <typename T, typename Enabled = void>
26 26 struct PlottablesCreator {
27 27 static PlottablesMap createPlottables(T &, QCustomPlot &)
28 28 {
29 29 qCCritical(LOG_DataSeries())
30 30 << QObject::tr("Can't create plottables: unmanaged data series type");
31 31 return {};
32 32 }
33 33 };
34 34
35 35 /**
36 36 * Specialization of PlottablesCreator for scalars and vectors
37 37 * @sa ScalarSeries
38 38 * @sa VectorSeries
39 39 */
40 40 template <typename T>
41 41 struct PlottablesCreator<T,
42 42 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
43 43 or std::is_base_of<VectorSeries, T>::value> > {
44 44 static PlottablesMap createPlottables(T &dataSeries, QCustomPlot &plot)
45 45 {
46 46 PlottablesMap result{};
47 47
48 48 // Gets the number of components of the data series
49 49 dataSeries.lockRead();
50 50 auto componentCount = dataSeries.valuesData()->componentCount();
51 51 dataSeries.unlock();
52 52
53 53 // For each component of the data series, creates a QCPGraph to add to the plot
54 54 for (auto i = 0; i < componentCount; ++i) {
55 55 auto graph = plot.addGraph();
56 56 result.insert({i, graph});
57 57 }
58 58
59 59 plot.replot();
60 60
61 61 return result;
62 62 }
63 63 };
64 64
65 65 /**
66 66 * Specialization of PlottablesCreator for spectrograms
67 67 * @sa SpectrogramSeries
68 68 */
69 69 template <typename T>
70 70 struct PlottablesCreator<T,
71 71 typename std::enable_if_t<std::is_base_of<SpectrogramSeries, T>::value> > {
72 72 static PlottablesMap createPlottables(T &dataSeries, QCustomPlot &plot)
73 73 {
74 74 PlottablesMap result{};
75 75 result.insert({0, new QCPColorMap{plot.xAxis, plot.yAxis}});
76 76
77 77 plot.replot();
78 78
79 79 return result;
80 80 }
81 81 };
82 82
83 83 /**
84 84 * Struct used to update plottables, depending on the type of the data series from which to update
85 85 * them
86 86 * @tparam T the data series' type
87 87 * @remarks Default implementation can't update plottables
88 88 */
89 89 template <typename T, typename Enabled = void>
90 90 struct PlottablesUpdater {
91 91 static void setPlotYAxisRange(T &, const SqpRange &, QCustomPlot &)
92 92 {
93 93 qCCritical(LOG_VisualizationGraphHelper())
94 94 << QObject::tr("Can't set plot y-axis range: unmanaged data series type");
95 95 }
96 96
97 97 static void updatePlottables(T &, PlottablesMap &, const SqpRange &, bool)
98 98 {
99 99 qCCritical(LOG_VisualizationGraphHelper())
100 100 << QObject::tr("Can't update plottables: unmanaged data series type");
101 101 }
102 102 };
103 103
104 104 /**
105 105 * Specialization of PlottablesUpdater for scalars and vectors
106 106 * @sa ScalarSeries
107 107 * @sa VectorSeries
108 108 */
109 109 template <typename T>
110 110 struct PlottablesUpdater<T,
111 111 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
112 112 or std::is_base_of<VectorSeries, T>::value> > {
113 113 static void setPlotYAxisRange(T &dataSeries, const SqpRange &xAxisRange, QCustomPlot &plot)
114 114 {
115 115 auto minValue = 0., maxValue = 0.;
116 116
117 117 dataSeries.lockRead();
118 118 auto valuesBounds = dataSeries.valuesBounds(xAxisRange.m_TStart, xAxisRange.m_TEnd);
119 119 auto end = dataSeries.cend();
120 120 if (valuesBounds.first != end && valuesBounds.second != end) {
121 121 auto rangeValue = [](const auto &value) { return std::isnan(value) ? 0. : value; };
122 122
123 123 minValue = rangeValue(valuesBounds.first->minValue());
124 124 maxValue = rangeValue(valuesBounds.second->maxValue());
125 125 }
126 126 dataSeries.unlock();
127 127
128 128 plot.yAxis->setRange(QCPRange{minValue, maxValue});
129 129 }
130 130
131 131 static void updatePlottables(T &dataSeries, PlottablesMap &plottables, const SqpRange &range,
132 132 bool rescaleAxes)
133 133 {
134 134
135 135 // For each plottable to update, resets its data
136 136 std::map<int, QSharedPointer<SqpDataContainer> > dataContainers{};
137 137 for (const auto &plottable : plottables) {
138 138 if (auto graph = dynamic_cast<QCPGraph *>(plottable.second)) {
139 139 auto dataContainer = QSharedPointer<SqpDataContainer>::create();
140 140 graph->setData(dataContainer);
141 141
142 142 dataContainers.insert({plottable.first, dataContainer});
143 143 }
144 144 }
145 145 dataSeries.lockRead();
146 146
147 147 // - Gets the data of the series included in the current range
148 148 // - Updates each plottable by adding, for each data item, a point that takes x-axis data
149 149 // and value data. The correct value is retrieved according to the index of the component
150 150 auto subDataIts = dataSeries.xAxisRange(range.m_TStart, range.m_TEnd);
151 151 for (auto it = subDataIts.first; it != subDataIts.second; ++it) {
152 152 for (const auto &dataContainer : dataContainers) {
153 153 auto componentIndex = dataContainer.first;
154 154 dataContainer.second->appendGraphData(
155 155 QCPGraphData(it->x(), it->value(componentIndex)));
156 156 }
157 157 }
158 158
159 159 dataSeries.unlock();
160 160
161 161 if (!plottables.empty()) {
162 162 auto plot = plottables.begin()->second->parentPlot();
163 163
164 164 if (rescaleAxes) {
165 165 plot->rescaleAxes();
166 166 }
167 167
168 168 plot->replot();
169 169 }
170 170 }
171 171 };
172 172
173 173 /**
174 174 * Specialization of PlottablesUpdater for spectrograms
175 175 * @sa SpectrogramSeries
176 176 */
177 177 template <typename T>
178 178 struct PlottablesUpdater<T,
179 179 typename std::enable_if_t<std::is_base_of<SpectrogramSeries, T>::value> > {
180 180 static void setPlotYAxisRange(T &dataSeries, const SqpRange &xAxisRange, QCustomPlot &plot)
181 181 {
182 182 double min, max;
183 /// @todo ALX: use iterators here
184 std::tie(min, max) = dataSeries.yAxis().bounds();
183 std::tie(min, max) = dataSeries.yBounds();
185 184
186 185 if (!std::isnan(min) && !std::isnan(max)) {
187 186 plot.yAxis->setRange(QCPRange{min, max});
188 187 }
189 188 }
190 189
191 190 static void updatePlottables(T &dataSeries, PlottablesMap &plottables, const SqpRange &range,
192 191 bool rescaleAxes)
193 192 {
194 193 if (plottables.empty()) {
195 194 qCDebug(LOG_VisualizationGraphHelper())
196 195 << QObject::tr("Can't update spectrogram: no colormap has been associated");
197 196 return;
198 197 }
199 198
200 199 // Gets the colormap to update (normally there is only one colormap)
201 200 Q_ASSERT(plottables.size() == 1);
202 201 auto colormap = dynamic_cast<QCPColorMap *>(plottables.at(0));
203 202 Q_ASSERT(colormap != nullptr);
204 203
205 204 dataSeries.lockRead();
206 205
207 206 auto its = dataSeries.xAxisRange(range.m_TStart, range.m_TEnd);
208 207 /// @todo ALX: use iterators here
209 208 auto yAxis = dataSeries.yAxis();
210 209
211 210 // Gets properties of x-axis and y-axis to set size and range of the colormap
212 211 auto nbX = std::distance(its.first, its.second);
213 212 auto xMin = nbX != 0 ? its.first->x() : 0.;
214 213 auto xMax = nbX != 0 ? (its.second - 1)->x() : 0.;
215 214
216 215 auto nbY = yAxis.size();
217 216 auto yMin = 0., yMax = 0.;
218 217 if (nbY != 0) {
219 218 std::tie(yMin, yMax) = yAxis.bounds();
220 219 }
221 220
222 221 colormap->data()->setSize(nbX, nbY);
223 222 colormap->data()->setRange(QCPRange{xMin, xMax}, QCPRange{yMin, yMax});
224 223
225 224 // Sets values
226 225 auto xIndex = 0;
227 226 for (auto it = its.first; it != its.second; ++it, ++xIndex) {
228 227 for (auto yIndex = 0; yIndex < nbY; ++yIndex) {
229 228 auto value = it->value(yIndex);
230 229
231 230 colormap->data()->setCell(xIndex, yIndex, value);
232 231
233 232 // Processing spectrogram data for display in QCustomPlot
234 233 /// For the moment, we just make the NaN values to be transparent in the colormap
235 234 /// @todo ALX: complete treatments (mesh generation, etc.)
236 235 if (std::isnan(value)) {
237 236 colormap->data()->setAlpha(xIndex, yIndex, 0);
238 237 }
239 238 }
240 239 }
241 240
242 241 dataSeries.unlock();
243 242
244 243 // Rescales axes
245 244 auto plot = colormap->parentPlot();
246 245
247 246 if (rescaleAxes) {
248 247 plot->rescaleAxes();
249 248 }
250 249
251 250 plot->replot();
252 251 }
253 252 };
254 253
255 254 /**
256 255 * Helper used to create/update plottables
257 256 */
258 257 struct IPlottablesHelper {
259 258 virtual ~IPlottablesHelper() noexcept = default;
260 259 virtual PlottablesMap create(QCustomPlot &plot) const = 0;
261 260 virtual void setYAxisRange(const SqpRange &xAxisRange, QCustomPlot &plot) const = 0;
262 261 virtual void update(PlottablesMap &plottables, const SqpRange &range,
263 262 bool rescaleAxes = false) const = 0;
264 263 };
265 264
266 265 /**
267 266 * Default implementation of IPlottablesHelper, which takes data series to create/update plottables
268 267 * @tparam T the data series' type
269 268 */
270 269 template <typename T>
271 270 struct PlottablesHelper : public IPlottablesHelper {
272 271 explicit PlottablesHelper(T &dataSeries) : m_DataSeries{dataSeries} {}
273 272
274 273 PlottablesMap create(QCustomPlot &plot) const override
275 274 {
276 275 return PlottablesCreator<T>::createPlottables(m_DataSeries, plot);
277 276 }
278 277
279 278 void update(PlottablesMap &plottables, const SqpRange &range, bool rescaleAxes) const override
280 279 {
281 280 PlottablesUpdater<T>::updatePlottables(m_DataSeries, plottables, range, rescaleAxes);
282 281 }
283 282
284 283 void setYAxisRange(const SqpRange &xAxisRange, QCustomPlot &plot) const override
285 284 {
286 285 return PlottablesUpdater<T>::setPlotYAxisRange(m_DataSeries, xAxisRange, plot);
287 286 }
288 287
289 288 T &m_DataSeries;
290 289 };
291 290
292 291 /// Creates IPlottablesHelper according to a data series
293 292 std::unique_ptr<IPlottablesHelper> createHelper(std::shared_ptr<IDataSeries> dataSeries) noexcept
294 293 {
295 294 if (auto scalarSeries = std::dynamic_pointer_cast<ScalarSeries>(dataSeries)) {
296 295 return std::make_unique<PlottablesHelper<ScalarSeries> >(*scalarSeries);
297 296 }
298 297 else if (auto spectrogramSeries = std::dynamic_pointer_cast<SpectrogramSeries>(dataSeries)) {
299 298 return std::make_unique<PlottablesHelper<SpectrogramSeries> >(*spectrogramSeries);
300 299 }
301 300 else if (auto vectorSeries = std::dynamic_pointer_cast<VectorSeries>(dataSeries)) {
302 301 return std::make_unique<PlottablesHelper<VectorSeries> >(*vectorSeries);
303 302 }
304 303 else {
305 304 return std::make_unique<PlottablesHelper<IDataSeries> >(*dataSeries);
306 305 }
307 306 }
308 307
309 308 } // namespace
310 309
311 310 PlottablesMap VisualizationGraphHelper::create(std::shared_ptr<Variable> variable,
312 311 QCustomPlot &plot) noexcept
313 312 {
314 313 if (variable) {
315 314 auto helper = createHelper(variable->dataSeries());
316 315 auto plottables = helper->create(plot);
317 316 return plottables;
318 317 }
319 318 else {
320 319 qCDebug(LOG_VisualizationGraphHelper())
321 320 << QObject::tr("Can't create graph plottables : the variable is null");
322 321 return PlottablesMap{};
323 322 }
324 323 }
325 324
326 325 void VisualizationGraphHelper::setYAxisRange(std::shared_ptr<Variable> variable,
327 326 QCustomPlot &plot) noexcept
328 327 {
329 328 if (variable) {
330 329 auto helper = createHelper(variable->dataSeries());
331 330 helper->setYAxisRange(variable->range(), plot);
332 331 }
333 332 else {
334 333 qCDebug(LOG_VisualizationGraphHelper())
335 334 << QObject::tr("Can't set y-axis range of plot: the variable is null");
336 335 }
337 336 }
338 337
339 338 void VisualizationGraphHelper::updateData(PlottablesMap &plottables,
340 339 std::shared_ptr<IDataSeries> dataSeries,
341 340 const SqpRange &dateTime)
342 341 {
343 342 auto helper = createHelper(dataSeries);
344 343 helper->update(plottables, dateTime);
345 344 }
General Comments 0
You need to be logged in to leave comments. Login now