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

Why an exception? A simple clear() on the data and maybe a method isValid() might be enough.

359 "The number of values by component must be equal to the number of x-axis data"};
360 }
361
362 // Validates y-axis (if defined)
363 if (yAxis.isDefined() && (yAxis.size() != m_ValuesData->componentCount())) {
364 throw std::invalid_argument{
365 "As the y-axis is defined, the number of value components must be equal to the "
366 "number of y-axis data"};
340 367 }
341 368
342 369 // Sorts data if it's not the case
343 370 const auto &xAxisCData = m_XAxisData->cdata();
344 371 if (!std::is_sorted(xAxisCData.cbegin(), xAxisCData.cend())) {
345 372 sort();
346 373 }
347 374 }
348 375
349 376 /// Copy ctor
350 377 explicit DataSeries(const DataSeries<Dim> &other)
351 378 : m_XAxisData{std::make_shared<ArrayData<1> >(*other.m_XAxisData)},
352 379 m_XAxisUnit{other.m_XAxisUnit},
353 380 m_ValuesData{std::make_shared<ArrayData<Dim> >(*other.m_ValuesData)},
354 m_ValuesUnit{other.m_ValuesUnit}
381 m_ValuesUnit{other.m_ValuesUnit},
382 m_YAxis{other.m_YAxis}
355 383 {
356 384 // Since a series is ordered from its construction and is always ordered, it is not
357 385 // necessary to call the sort method here ('other' is sorted)
358 386 }
359 387
388 /// @return the y-axis associated to the data series
389 OptionalAxis yAxis() const { return m_YAxis; }
390
360 391 /// Assignment operator
361 392 template <int D>
362 393 DataSeries &operator=(DataSeries<D> other)
363 394 {
364 395 std::swap(m_XAxisData, other.m_XAxisData);
365 396 std::swap(m_XAxisUnit, other.m_XAxisUnit);
366 397 std::swap(m_ValuesData, other.m_ValuesData);
367 398 std::swap(m_ValuesUnit, other.m_ValuesUnit);
399 std::swap(m_YAxis, other.m_YAxis);
368 400
369 401 return *this;
370 402 }
371 403
372 404 private:
373 405 /**
374 406 * Sorts data series on its x-axis data
375 407 */
376 408 void sort() noexcept
377 409 {
378 410 auto permutation = SortUtils::sortPermutation(*m_XAxisData, std::less<double>());
379 411 m_XAxisData = m_XAxisData->sort(permutation);
380 412 m_ValuesData = m_ValuesData->sort(permutation);
381 413 }
382 414
415 // x-axis
383 416 std::shared_ptr<ArrayData<1> > m_XAxisData;
384 417 Unit m_XAxisUnit;
418
419 // values
385 420 std::shared_ptr<ArrayData<Dim> > m_ValuesData;
386 421 Unit m_ValuesUnit;
387 422
423 // y-axis (optional)
424 OptionalAxis m_YAxis;
425
388 426 QReadWriteLock m_Lock;
389 427 };
390 428
391 429 #endif // SCIQLOP_DATASERIES_H
General Comments 0
You need to be logged in to leave comments. Login now