##// END OF EJS Templates
Implements spectrograms display (1)...
Alexandre Leroux -
r905:aa0c1de0bcf6
parent child
Show More
@@ -1,482 +1,483
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 196 /// @return the values dataset
197 197 std::shared_ptr<ArrayData<Dim> > valuesData() { return m_ValuesData; }
198 198 const std::shared_ptr<ArrayData<Dim> > valuesData() const { return m_ValuesData; }
199 199
200 200 /// @sa IDataSeries::valuesUnit()
201 201 Unit valuesUnit() const override { return m_ValuesUnit; }
202 202
203 203 int nbPoints() const override { return m_ValuesData->totalSize(); }
204 204
205 205 void clear()
206 206 {
207 207 m_XAxisData->clear();
208 208 m_ValuesData->clear();
209 209 }
210 210
211 211 bool isEmpty() const noexcept { return m_XAxisData->size() == 0; }
212 212
213 213 /// Merges into the data series an other data series.
214 214 ///
215 215 /// The two dataseries:
216 216 /// - must be of the same dimension
217 217 /// - must have the same y-axis (if defined)
218 218 ///
219 219 /// If the prerequisites are not valid, the method does nothing
220 220 ///
221 221 /// @remarks the data series to merge with is cleared after the operation
222 222 void merge(IDataSeries *dataSeries) override
223 223 {
224 224 dataSeries->lockWrite();
225 225 lockWrite();
226 226
227 227 if (auto other = dynamic_cast<DataSeries<Dim> *>(dataSeries)) {
228 228 if (m_YAxis == other->m_YAxis) {
229 229 DataSeriesMergeHelper::merge(*other, *this);
230 230 }
231 231 else {
232 232 qCWarning(LOG_DataSeries())
233 233 << QObject::tr("Can't merge data series that have not the same y-axis");
234 234 }
235 235 }
236 236 else {
237 237 qCWarning(LOG_DataSeries())
238 238 << QObject::tr("Detection of a type of IDataSeries we cannot merge with !");
239 239 }
240 240 unlock();
241 241 dataSeries->unlock();
242 242 }
243 243
244 244 void purge(double min, double max) override
245 245 {
246 246 // Nothing to purge if series is empty
247 247 if (isEmpty()) {
248 248 return;
249 249 }
250 250
251 251 if (min > max) {
252 252 std::swap(min, max);
253 253 }
254 254
255 255 // Nothing to purge if series min/max are inside purge range
256 256 auto xMin = cbegin()->x();
257 257 auto xMax = (--cend())->x();
258 258 if (xMin >= min && xMax <= max) {
259 259 return;
260 260 }
261 261
262 262 auto lowerIt = std::lower_bound(
263 263 begin(), end(), min, [](const auto &it, const auto &val) { return it.x() < val; });
264 264 erase(begin(), lowerIt);
265 265 auto upperIt = std::upper_bound(
266 266 begin(), end(), max, [](const auto &val, const auto &it) { return val < it.x(); });
267 267 erase(upperIt, end());
268 268 }
269 269
270 270 // ///////// //
271 271 // Iterators //
272 272 // ///////// //
273 273
274 274 DataSeriesIterator begin() override
275 275 {
276 276 return DataSeriesIterator{DataSeriesIteratorValue{
277 277 std::make_unique<dataseries_detail::IteratorValue<Dim, false> >(*this, true)}};
278 278 }
279 279
280 280 DataSeriesIterator end() override
281 281 {
282 282 return DataSeriesIterator{DataSeriesIteratorValue{
283 283 std::make_unique<dataseries_detail::IteratorValue<Dim, false> >(*this, false)}};
284 284 }
285 285
286 286 DataSeriesIterator cbegin() const override
287 287 {
288 288 return DataSeriesIterator{DataSeriesIteratorValue{
289 289 std::make_unique<dataseries_detail::IteratorValue<Dim, true> >(*this, true)}};
290 290 }
291 291
292 292 DataSeriesIterator cend() const override
293 293 {
294 294 return DataSeriesIterator{DataSeriesIteratorValue{
295 295 std::make_unique<dataseries_detail::IteratorValue<Dim, true> >(*this, false)}};
296 296 }
297 297
298 298 void erase(DataSeriesIterator first, DataSeriesIterator last)
299 299 {
300 300 auto firstImpl
301 301 = dynamic_cast<dataseries_detail::IteratorValue<Dim, false> *>(first->impl());
302 302 auto lastImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, false> *>(last->impl());
303 303
304 304 if (firstImpl && lastImpl) {
305 305 m_XAxisData->erase(firstImpl->m_XIt, lastImpl->m_XIt);
306 306 m_ValuesData->erase(firstImpl->m_ValuesIt, lastImpl->m_ValuesIt);
307 307 }
308 308 }
309 309
310 310 void insert(DataSeriesIterator first, DataSeriesIterator last, bool prepend = false)
311 311 {
312 312 auto firstImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, true> *>(first->impl());
313 313 auto lastImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, true> *>(last->impl());
314 314
315 315 if (firstImpl && lastImpl) {
316 316 m_XAxisData->insert(firstImpl->m_XIt, lastImpl->m_XIt, prepend);
317 317 m_ValuesData->insert(firstImpl->m_ValuesIt, lastImpl->m_ValuesIt, prepend);
318 318 }
319 319 }
320 320
321 321 /// @sa IDataSeries::minXAxisData()
322 322 DataSeriesIterator minXAxisData(double minXAxisData) const override
323 323 {
324 324 return std::lower_bound(
325 325 cbegin(), cend(), minXAxisData,
326 326 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
327 327 }
328 328
329 329 /// @sa IDataSeries::maxXAxisData()
330 330 DataSeriesIterator maxXAxisData(double maxXAxisData) const override
331 331 {
332 332 // Gets the first element that greater than max value
333 333 auto it = std::upper_bound(
334 334 cbegin(), cend(), maxXAxisData,
335 335 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
336 336
337 337 return it == cbegin() ? cend() : --it;
338 338 }
339 339
340 340 std::pair<DataSeriesIterator, DataSeriesIterator> xAxisRange(double minXAxisData,
341 341 double maxXAxisData) const override
342 342 {
343 343 if (minXAxisData > maxXAxisData) {
344 344 std::swap(minXAxisData, maxXAxisData);
345 345 }
346 346
347 347 auto begin = cbegin();
348 348 auto end = cend();
349 349
350 350 auto lowerIt = std::lower_bound(
351 351 begin, end, minXAxisData,
352 352 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
353 353 auto upperIt = std::upper_bound(
354 354 lowerIt, end, maxXAxisData,
355 355 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
356 356
357 357 return std::make_pair(lowerIt, upperIt);
358 358 }
359 359
360 360 std::pair<DataSeriesIterator, DataSeriesIterator>
361 361 valuesBounds(double minXAxisData, double maxXAxisData) const override
362 362 {
363 363 // Places iterators to the correct x-axis range
364 364 auto xAxisRangeIts = xAxisRange(minXAxisData, maxXAxisData);
365 365
366 366 // Returns end iterators if the range is empty
367 367 if (xAxisRangeIts.first == xAxisRangeIts.second) {
368 368 return std::make_pair(cend(), cend());
369 369 }
370 370
371 371 // Gets the iterator on the min of all values data
372 372 auto minIt = std::min_element(
373 373 xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) {
374 374 return SortUtils::minCompareWithNaN(it1.minValue(), it2.minValue());
375 375 });
376 376
377 377 // Gets the iterator on the max of all values data
378 378 auto maxIt = std::max_element(
379 379 xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) {
380 380 return SortUtils::maxCompareWithNaN(it1.maxValue(), it2.maxValue());
381 381 });
382 382
383 383 return std::make_pair(minIt, maxIt);
384 384 }
385 385
386 /// @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; }
389
386 390 // /////// //
387 391 // Mutexes //
388 392 // /////// //
389 393
390 394 virtual void lockRead() { m_Lock.lockForRead(); }
391 395 virtual void lockWrite() { m_Lock.lockForWrite(); }
392 396 virtual void unlock() { m_Lock.unlock(); }
393 397
394 398 protected:
395 399 /// Protected ctor (DataSeries is abstract).
396 400 ///
397 401 /// Data vectors must be consistent with each other, otherwise an exception will be thrown (@sa
398 402 /// class description for consistent rules)
399 403 /// @remarks data series is automatically sorted on its x-axis data
400 404 /// @throws std::invalid_argument if the data are inconsistent with each other
401 405 explicit DataSeries(std::shared_ptr<ArrayData<1> > xAxisData, const Unit &xAxisUnit,
402 406 std::shared_ptr<ArrayData<Dim> > valuesData, const Unit &valuesUnit,
403 407 OptionalAxis yAxis = OptionalAxis{})
404 408 : m_XAxisData{xAxisData},
405 409 m_XAxisUnit{xAxisUnit},
406 410 m_ValuesData{valuesData},
407 411 m_ValuesUnit{valuesUnit},
408 412 m_YAxis{std::move(yAxis)}
409 413 {
410 414 if (m_XAxisData->size() != m_ValuesData->size()) {
411 415 throw std::invalid_argument{
412 416 "The number of values by component must be equal to the number of x-axis data"};
413 417 }
414 418
415 419 // Validates y-axis (if defined)
416 420 if (yAxis.isDefined() && (yAxis.size() != m_ValuesData->componentCount())) {
417 421 throw std::invalid_argument{
418 422 "As the y-axis is defined, the number of value components must be equal to the "
419 423 "number of y-axis data"};
420 424 }
421 425
422 426 // Sorts data if it's not the case
423 427 const auto &xAxisCData = m_XAxisData->cdata();
424 428 if (!std::is_sorted(xAxisCData.cbegin(), xAxisCData.cend())) {
425 429 sort();
426 430 }
427 431 }
428 432
429 433 /// Copy ctor
430 434 explicit DataSeries(const DataSeries<Dim> &other)
431 435 : m_XAxisData{std::make_shared<ArrayData<1> >(*other.m_XAxisData)},
432 436 m_XAxisUnit{other.m_XAxisUnit},
433 437 m_ValuesData{std::make_shared<ArrayData<Dim> >(*other.m_ValuesData)},
434 438 m_ValuesUnit{other.m_ValuesUnit},
435 439 m_YAxis{other.m_YAxis}
436 440 {
437 441 // Since a series is ordered from its construction and is always ordered, it is not
438 442 // necessary to call the sort method here ('other' is sorted)
439 443 }
440 444
441 /// @return the y-axis associated to the data series
442 OptionalAxis yAxis() const { return m_YAxis; }
443
444 445 /// Assignment operator
445 446 template <int D>
446 447 DataSeries &operator=(DataSeries<D> other)
447 448 {
448 449 std::swap(m_XAxisData, other.m_XAxisData);
449 450 std::swap(m_XAxisUnit, other.m_XAxisUnit);
450 451 std::swap(m_ValuesData, other.m_ValuesData);
451 452 std::swap(m_ValuesUnit, other.m_ValuesUnit);
452 453 std::swap(m_YAxis, other.m_YAxis);
453 454
454 455 return *this;
455 456 }
456 457
457 458 private:
458 459 /**
459 460 * Sorts data series on its x-axis data
460 461 */
461 462 void sort() noexcept
462 463 {
463 464 auto permutation = SortUtils::sortPermutation(*m_XAxisData, std::less<double>());
464 465 m_XAxisData = m_XAxisData->sort(permutation);
465 466 m_ValuesData = m_ValuesData->sort(permutation);
466 467 }
467 468
468 469 // x-axis
469 470 std::shared_ptr<ArrayData<1> > m_XAxisData;
470 471 Unit m_XAxisUnit;
471 472
472 473 // values
473 474 std::shared_ptr<ArrayData<Dim> > m_ValuesData;
474 475 Unit m_ValuesUnit;
475 476
476 477 // y-axis (optional)
477 478 OptionalAxis m_YAxis;
478 479
479 480 QReadWriteLock m_Lock;
480 481 };
481 482
482 483 #endif // SCIQLOP_DATASERIES_H
@@ -1,247 +1,269
1 1 #include "Visualization/VisualizationGraphHelper.h"
2 2 #include "Visualization/qcustomplot.h"
3 3
4 4 #include <Common/ColorUtils.h>
5 5
6 6 #include <Data/ScalarSeries.h>
7 #include <Data/SpectrogramSeries.h>
7 8 #include <Data/VectorSeries.h>
8 9
9 10 #include <Variable/Variable.h>
10 11
11 12 Q_LOGGING_CATEGORY(LOG_VisualizationGraphHelper, "VisualizationGraphHelper")
12 13
13 14 namespace {
14 15
15 16 class SqpDataContainer : public QCPGraphDataContainer {
16 17 public:
17 18 void appendGraphData(const QCPGraphData &data) { mData.append(data); }
18 19 };
19 20
20 21 /**
21 22 * Struct used to create plottables, depending on the type of the data series from which to create
22 23 * them
23 24 * @tparam T the data series' type
24 25 * @remarks Default implementation can't create plottables
25 26 */
26 27 template <typename T, typename Enabled = void>
27 28 struct PlottablesCreator {
28 29 static PlottablesMap createPlottables(T &, QCustomPlot &)
29 30 {
30 31 qCCritical(LOG_DataSeries())
31 32 << QObject::tr("Can't create plottables: unmanaged data series type");
32 33 return {};
33 34 }
34 35 };
35 36
36 37 /**
37 38 * Specialization of PlottablesCreator for scalars and vectors
38 39 * @sa ScalarSeries
39 40 * @sa VectorSeries
40 41 */
41 42 template <typename T>
42 43 struct PlottablesCreator<T,
43 44 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
44 45 or std::is_base_of<VectorSeries, T>::value> > {
45 46 static PlottablesMap createPlottables(T &dataSeries, QCustomPlot &plot)
46 47 {
47 48 PlottablesMap result{};
48 49
49 50 // Gets the number of components of the data series
50 51 dataSeries.lockRead();
51 52 auto componentCount = dataSeries.valuesData()->componentCount();
52 53 dataSeries.unlock();
53 54
54 55 auto colors = ColorUtils::colors(Qt::blue, Qt::red, componentCount);
55 56
56 57 // For each component of the data series, creates a QCPGraph to add to the plot
57 58 for (auto i = 0; i < componentCount; ++i) {
58 59 auto graph = plot.addGraph();
59 60 graph->setPen(QPen{colors.at(i)});
60 61
61 62 result.insert({i, graph});
62 63 }
63 64
64 65 plot.replot();
65 66
66 67 return result;
67 68 }
68 69 };
69 70
70 71 /**
72 * Specialization of PlottablesCreator for spectrograms
73 * @sa SpectrogramSeries
74 */
75 template <typename T>
76 struct PlottablesCreator<T,
77 typename std::enable_if_t<std::is_base_of<SpectrogramSeries, T>::value> > {
78 static PlottablesMap createPlottables(T &dataSeries, QCustomPlot &plot)
79 {
80 PlottablesMap result{};
81 result.insert({0, new QCPColorMap{plot.xAxis, plot.yAxis}});
82
83 plot.replot();
84
85 return result;
86 }
87 };
88
89 /**
71 90 * Struct used to update plottables, depending on the type of the data series from which to update
72 91 * them
73 92 * @tparam T the data series' type
74 93 * @remarks Default implementation can't update plottables
75 94 */
76 95 template <typename T, typename Enabled = void>
77 96 struct PlottablesUpdater {
78 97 static void setPlotYAxisRange(T &, const SqpRange &, QCustomPlot &)
79 98 {
80 99 qCCritical(LOG_DataSeries())
81 100 << QObject::tr("Can't set plot y-axis range: unmanaged data series type");
82 101 }
83 102
84 103 static void updatePlottables(T &, PlottablesMap &, const SqpRange &, bool)
85 104 {
86 105 qCCritical(LOG_DataSeries())
87 106 << QObject::tr("Can't update plottables: unmanaged data series type");
88 107 }
89 108 };
90 109
91 110 /**
92 111 * Specialization of PlottablesUpdater for scalars and vectors
93 112 * @sa ScalarSeries
94 113 * @sa VectorSeries
95 114 */
96 115 template <typename T>
97 116 struct PlottablesUpdater<T,
98 117 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
99 118 or std::is_base_of<VectorSeries, T>::value> > {
100 119 static void setPlotYAxisRange(T &dataSeries, const SqpRange &xAxisRange, QCustomPlot &plot)
101 120 {
102 121 auto minValue = 0., maxValue = 0.;
103 122
104 123 dataSeries.lockRead();
105 124 auto valuesBounds = dataSeries.valuesBounds(xAxisRange.m_TStart, xAxisRange.m_TEnd);
106 125 auto end = dataSeries.cend();
107 126 if (valuesBounds.first != end && valuesBounds.second != end) {
108 127 auto rangeValue = [](const auto &value) { return std::isnan(value) ? 0. : value; };
109 128
110 129 minValue = rangeValue(valuesBounds.first->minValue());
111 130 maxValue = rangeValue(valuesBounds.second->maxValue());
112 131 }
113 132 dataSeries.unlock();
114 133
115 134 plot.yAxis->setRange(QCPRange{minValue, maxValue});
116 135 }
117 136
118 137 static void updatePlottables(T &dataSeries, PlottablesMap &plottables, const SqpRange &range,
119 138 bool rescaleAxes)
120 139 {
121 140
122 141 // For each plottable to update, resets its data
123 142 std::map<int, QSharedPointer<SqpDataContainer> > dataContainers{};
124 143 for (const auto &plottable : plottables) {
125 144 if (auto graph = dynamic_cast<QCPGraph *>(plottable.second)) {
126 145 auto dataContainer = QSharedPointer<SqpDataContainer>::create();
127 146 graph->setData(dataContainer);
128 147
129 148 dataContainers.insert({plottable.first, dataContainer});
130 149 }
131 150 }
132 151 dataSeries.lockRead();
133 152
134 153 // - Gets the data of the series included in the current range
135 154 // - Updates each plottable by adding, for each data item, a point that takes x-axis data
136 155 // and value data. The correct value is retrieved according to the index of the component
137 156 auto subDataIts = dataSeries.xAxisRange(range.m_TStart, range.m_TEnd);
138 157 for (auto it = subDataIts.first; it != subDataIts.second; ++it) {
139 158 for (const auto &dataContainer : dataContainers) {
140 159 auto componentIndex = dataContainer.first;
141 160 dataContainer.second->appendGraphData(
142 161 QCPGraphData(it->x(), it->value(componentIndex)));
143 162 }
144 163 }
145 164
146 165 dataSeries.unlock();
147 166
148 167 if (!plottables.empty()) {
149 168 auto plot = plottables.begin()->second->parentPlot();
150 169
151 170 if (rescaleAxes) {
152 171 plot->rescaleAxes();
153 172 }
154 173
155 174 plot->replot();
156 175 }
157 176 }
158 177 };
159 178
160 179 /**
161 180 * Helper used to create/update plottables
162 181 */
163 182 struct IPlottablesHelper {
164 183 virtual ~IPlottablesHelper() noexcept = default;
165 184 virtual PlottablesMap create(QCustomPlot &plot) const = 0;
166 185 virtual void setYAxisRange(const SqpRange &xAxisRange, QCustomPlot &plot) const = 0;
167 186 virtual void update(PlottablesMap &plottables, const SqpRange &range,
168 187 bool rescaleAxes = false) const = 0;
169 188 };
170 189
171 190 /**
172 191 * Default implementation of IPlottablesHelper, which takes data series to create/update plottables
173 192 * @tparam T the data series' type
174 193 */
175 194 template <typename T>
176 195 struct PlottablesHelper : public IPlottablesHelper {
177 196 explicit PlottablesHelper(T &dataSeries) : m_DataSeries{dataSeries} {}
178 197
179 198 PlottablesMap create(QCustomPlot &plot) const override
180 199 {
181 200 return PlottablesCreator<T>::createPlottables(m_DataSeries, plot);
182 201 }
183 202
184 203 void update(PlottablesMap &plottables, const SqpRange &range, bool rescaleAxes) const override
185 204 {
186 205 PlottablesUpdater<T>::updatePlottables(m_DataSeries, plottables, range, rescaleAxes);
187 206 }
188 207
189 208 void setYAxisRange(const SqpRange &xAxisRange, QCustomPlot &plot) const override
190 209 {
191 210 return PlottablesUpdater<T>::setPlotYAxisRange(m_DataSeries, xAxisRange, plot);
192 211 }
193 212
194 213 T &m_DataSeries;
195 214 };
196 215
197 216 /// Creates IPlottablesHelper according to a data series
198 217 std::unique_ptr<IPlottablesHelper> createHelper(std::shared_ptr<IDataSeries> dataSeries) noexcept
199 218 {
200 219 if (auto scalarSeries = std::dynamic_pointer_cast<ScalarSeries>(dataSeries)) {
201 220 return std::make_unique<PlottablesHelper<ScalarSeries> >(*scalarSeries);
202 221 }
222 else if (auto spectrogramSeries = std::dynamic_pointer_cast<SpectrogramSeries>(dataSeries)) {
223 return std::make_unique<PlottablesHelper<SpectrogramSeries> >(*spectrogramSeries);
224 }
203 225 else if (auto vectorSeries = std::dynamic_pointer_cast<VectorSeries>(dataSeries)) {
204 226 return std::make_unique<PlottablesHelper<VectorSeries> >(*vectorSeries);
205 227 }
206 228 else {
207 229 return std::make_unique<PlottablesHelper<IDataSeries> >(*dataSeries);
208 230 }
209 231 }
210 232
211 233 } // namespace
212 234
213 235 PlottablesMap VisualizationGraphHelper::create(std::shared_ptr<Variable> variable,
214 236 QCustomPlot &plot) noexcept
215 237 {
216 238 if (variable) {
217 239 auto helper = createHelper(variable->dataSeries());
218 240 auto plottables = helper->create(plot);
219 241 return plottables;
220 242 }
221 243 else {
222 244 qCDebug(LOG_VisualizationGraphHelper())
223 245 << QObject::tr("Can't create graph plottables : the variable is null");
224 246 return PlottablesMap{};
225 247 }
226 248 }
227 249
228 250 void VisualizationGraphHelper::setYAxisRange(std::shared_ptr<Variable> variable,
229 251 QCustomPlot &plot) noexcept
230 252 {
231 253 if (variable) {
232 254 auto helper = createHelper(variable->dataSeries());
233 255 helper->setYAxisRange(variable->range(), plot);
234 256 }
235 257 else {
236 258 qCDebug(LOG_VisualizationGraphHelper())
237 259 << QObject::tr("Can't set y-axis range of plot: the variable is null");
238 260 }
239 261 }
240 262
241 263 void VisualizationGraphHelper::updateData(PlottablesMap &plottables,
242 264 std::shared_ptr<IDataSeries> dataSeries,
243 265 const SqpRange &dateTime)
244 266 {
245 267 auto helper = createHelper(dataSeries);
246 268 helper->update(plottables, dateTime);
247 269 }
General Comments 0
You need to be logged in to leave comments. Login now