##// END OF EJS Templates
Merge pull request 232 from SCIQLOP-Initialisation develop...
perrinel -
r616:69d5aac0e395 merge
parent child
Show More
@@ -1,64 +1,116
1 1 #ifndef SCIQLOP_SORTUTILS_H
2 2 #define SCIQLOP_SORTUTILS_H
3 3
4 4 #include <algorithm>
5 5 #include <numeric>
6 6 #include <vector>
7 7
8 8 /**
9 9 * Utility class with methods for sorting data
10 10 */
11 11 struct SortUtils {
12 12 /**
13 13 * Generates a vector representing the index of insertion of each data of a container if this
14 14 * one had to be sorted according to a comparison function.
15 15 *
16 16 * For example:
17 17 * If the container is a vector {1; 4; 2; 5; 3} and the comparison function is std::less, the
18 18 * result would be : {0; 3; 1; 4; 2}
19 19 *
20 20 * @tparam Container the type of the container.
21 21 * @tparam Compare the type of the comparison function
22 22 * @param container the container from which to generate the result. The container must have a
23 23 * at() method that returns a value associated to an index
24 24 * @param compare the comparison function
25 25 */
26 26 template <typename Container, typename Compare>
27 27 static std::vector<int> sortPermutation(const Container &container, const Compare &compare)
28 28 {
29 29 auto permutation = std::vector<int>{};
30 30 permutation.resize(container.size());
31 31
32 32 std::iota(permutation.begin(), permutation.end(), 0);
33 33 std::sort(permutation.begin(), permutation.end(),
34 34 [&](int i, int j) { return compare(container.at(i), container.at(j)); });
35 35 return permutation;
36 36 }
37 37
38 38 /**
39 39 * Sorts a container according to indices passed in parameter
40 40 * @param container the container sorted
41 41 * @param sortPermutation the indices used to sort the container
42 42 * @return the container sorted
43 43 * @warning no verification is made on validity of sortPermutation (i.e. the vector has unique
44 44 * indices and its range is [0 ; vector.size()[ )
45 45 */
46 46 template <typename Container>
47 47 static Container sort(const Container &container, const std::vector<int> &sortPermutation)
48 48 {
49 49 if (container.size() != sortPermutation.size()) {
50 50 return Container{};
51 51 }
52 52
53 53 // Inits result
54 54 auto sortedData = Container{};
55 55 sortedData.resize(container.size());
56 56
57 57 std::transform(sortPermutation.cbegin(), sortPermutation.cend(), sortedData.begin(),
58 58 [&container](int i) { return container.at(i); });
59 59
60 60 return sortedData;
61 61 }
62
63 /**
64 * Compares two values that can be NaN. This method is intended to be used as a compare function
65 * for searching min value by excluding NaN values.
66 *
67 * Examples of use:
68 * - f({1, 3, 2, 4, 5}) will return 1
69 * - f({NaN, 3, 2, 4, 5}) will return 2 (NaN is excluded)
70 * - f({NaN, NaN, 3, NaN, NaN}) will return 3 (NaN are excluded)
71 * - f({NaN, NaN, NaN, NaN, NaN}) will return NaN (no existing value)
72 *
73 * @param v1 first value
74 * @param v2 second value
75 * @return true if v1 < v2, false otherwise
76 * @sa std::min_element
77 */
78 template <typename T>
79 static bool minCompareWithNaN(const T &v1, const T &v2)
80 {
81 // Table used with NaN values:
82 // NaN < v2 -> false
83 // v1 < NaN -> true
84 // NaN < NaN -> false
85 // v1 < v2 -> v1 < v2
86 return std::isnan(v1) ? false : std::isnan(v2) || (v1 < v2);
87 }
88
89 /**
90 * Compares two values that can be NaN. This method is intended to be used as a compare function
91 * for searching max value by excluding NaN values.
92 *
93 * Examples of use:
94 * - f({1, 3, 2, 4, 5}) will return 5
95 * - f({1, 3, 2, 4, NaN}) will return 4 (NaN is excluded)
96 * - f({NaN, NaN, 3, NaN, NaN}) will return 3 (NaN are excluded)
97 * - f({NaN, NaN, NaN, NaN, NaN}) will return NaN (no existing value)
98 *
99 * @param v1 first value
100 * @param v2 second value
101 * @return true if v1 < v2, false otherwise
102 * @sa std::max_element
103 */
104 template <typename T>
105 static bool maxCompareWithNaN(const T &v1, const T &v2)
106 {
107 // Table used with NaN values:
108 // NaN < v2 -> true
109 // v1 < NaN -> false
110 // NaN < NaN -> false
111 // v1 < v2 -> v1 < v2
112 return std::isnan(v1) ? true : !std::isnan(v2) && (v1 < v2);
113 }
62 114 };
63 115
64 116 #endif // SCIQLOP_SORTUTILS_H
@@ -1,328 +1,348
1 1 #ifndef SCIQLOP_ARRAYDATA_H
2 2 #define SCIQLOP_ARRAYDATA_H
3 3
4 4 #include <Common/SortUtils.h>
5 5
6 6 #include <QReadLocker>
7 7 #include <QReadWriteLock>
8 8 #include <QVector>
9 9
10 10 #include <memory>
11 11
12 12 template <int Dim>
13 13 class ArrayData;
14 14
15 15 using DataContainer = QVector<QVector<double> >;
16 16
17 17 namespace arraydata_detail {
18 18
19 19 /// Struct used to sort ArrayData
20 20 template <int Dim>
21 21 struct Sort {
22 22 static std::shared_ptr<ArrayData<Dim> > sort(const DataContainer &data,
23 23 const std::vector<int> &sortPermutation)
24 24 {
25 25 auto nbComponents = data.size();
26 26 auto sortedData = DataContainer(nbComponents);
27 27
28 28 for (auto i = 0; i < nbComponents; ++i) {
29 29 sortedData[i] = SortUtils::sort(data.at(i), sortPermutation);
30 30 }
31 31
32 32 return std::make_shared<ArrayData<Dim> >(std::move(sortedData));
33 33 }
34 34 };
35 35
36 36 /// Specialization for uni-dimensional ArrayData
37 37 template <>
38 38 struct Sort<1> {
39 39 static std::shared_ptr<ArrayData<1> > sort(const DataContainer &data,
40 40 const std::vector<int> &sortPermutation)
41 41 {
42 42 return std::make_shared<ArrayData<1> >(SortUtils::sort(data.at(0), sortPermutation));
43 43 }
44 44 };
45 45
46 46 } // namespace arraydata_detail
47 47
48 48 /**
49 49 * @brief The ArrayData class represents a dataset for a data series.
50 50 *
51 51 * A dataset can be unidimensional or two-dimensional. This property is determined by the Dim
52 52 * template-parameter. In a case of a two-dimensional dataset, each dataset component has the same
53 53 * number of values
54 54 *
55 55 * @tparam Dim the dimension of the ArrayData (one or two)
56 56 * @sa IDataSeries
57 57 */
58 58 template <int Dim>
59 59 class ArrayData {
60 60 public:
61 61 class IteratorValue {
62 62 public:
63 63 explicit IteratorValue(const DataContainer &container, bool begin) : m_Its{}
64 64 {
65 65 for (auto i = 0; i < container.size(); ++i) {
66 66 m_Its.push_back(begin ? container.at(i).cbegin() : container.at(i).cend());
67 67 }
68 68 }
69 69
70 70 double at(int index) const { return *m_Its.at(index); }
71 71 double first() const { return *m_Its.front(); }
72 72
73 /// @return the min value among all components
74 double min() const
75 {
76 auto end = m_Its.cend();
77 auto it = std::min_element(m_Its.cbegin(), end, [](const auto &it1, const auto &it2) {
78 return SortUtils::minCompareWithNaN(*it1, *it2);
79 });
80 return it != end ? **it : std::numeric_limits<double>::quiet_NaN();
81 }
82
83 /// @return the max value among all components
84 double max() const
85 {
86 auto end = m_Its.cend();
87 auto it = std::max_element(m_Its.cbegin(), end, [](const auto &it1, const auto &it2) {
88 return SortUtils::maxCompareWithNaN(*it1, *it2);
89 });
90 return it != end ? **it : std::numeric_limits<double>::quiet_NaN();
91 }
92
73 93 void next()
74 94 {
75 95 for (auto &it : m_Its) {
76 96 ++it;
77 97 }
78 98 }
79 99
80 100 void prev()
81 101 {
82 102 for (auto &it : m_Its) {
83 103 --it;
84 104 }
85 105 }
86 106
87 107 bool operator==(const IteratorValue &other) const { return m_Its == other.m_Its; }
88 108
89 109 private:
90 110 std::vector<DataContainer::value_type::const_iterator> m_Its;
91 111 };
92 112
93 113 class Iterator {
94 114 public:
95 115 using iterator_category = std::forward_iterator_tag;
96 116 using value_type = const IteratorValue;
97 117 using difference_type = std::ptrdiff_t;
98 118 using pointer = value_type *;
99 119 using reference = value_type &;
100 120
101 121 Iterator(const DataContainer &container, bool begin) : m_CurrentValue{container, begin} {}
102 122
103 123 virtual ~Iterator() noexcept = default;
104 124 Iterator(const Iterator &) = default;
105 125 Iterator(Iterator &&) = default;
106 126 Iterator &operator=(const Iterator &) = default;
107 127 Iterator &operator=(Iterator &&) = default;
108 128
109 129 Iterator &operator++()
110 130 {
111 131 m_CurrentValue.next();
112 132 return *this;
113 133 }
114 134
115 135 Iterator &operator--()
116 136 {
117 137 m_CurrentValue.prev();
118 138 return *this;
119 139 }
120 140
121 141 pointer operator->() const { return &m_CurrentValue; }
122 142 reference operator*() const { return m_CurrentValue; }
123 143
124 144 bool operator==(const Iterator &other) const
125 145 {
126 146 return m_CurrentValue == other.m_CurrentValue;
127 147 }
128 148
129 149 bool operator!=(const Iterator &other) const { return !(*this == other); }
130 150
131 151 private:
132 152 IteratorValue m_CurrentValue;
133 153 };
134 154
135 155 // ///// //
136 156 // Ctors //
137 157 // ///// //
138 158
139 159 /**
140 160 * Ctor for a unidimensional ArrayData
141 161 * @param data the data the ArrayData will hold
142 162 */
143 163 template <int D = Dim, typename = std::enable_if_t<D == 1> >
144 164 explicit ArrayData(QVector<double> data) : m_Data{1, QVector<double>{}}
145 165 {
146 166 m_Data[0] = std::move(data);
147 167 }
148 168
149 169 /**
150 170 * Ctor for a two-dimensional ArrayData. The number of components (number of vectors) must be
151 171 * greater than 2 and each component must have the same number of values
152 172 * @param data the data the ArrayData will hold
153 173 * @throws std::invalid_argument if the number of components is less than 2
154 174 * @remarks if the number of values is not the same for each component, no value is set
155 175 */
156 176 template <int D = Dim, typename = std::enable_if_t<D == 2> >
157 177 explicit ArrayData(DataContainer data)
158 178 {
159 179 auto nbComponents = data.size();
160 180 if (nbComponents < 2) {
161 181 throw std::invalid_argument{
162 182 QString{"A multidimensional ArrayData must have at least 2 components (found: %1"}
163 183 .arg(data.size())
164 184 .toStdString()};
165 185 }
166 186
167 187 auto nbValues = data.front().size();
168 188 if (std::all_of(data.cbegin(), data.cend(), [nbValues](const auto &component) {
169 189 return component.size() == nbValues;
170 190 })) {
171 191 m_Data = std::move(data);
172 192 }
173 193 else {
174 194 m_Data = DataContainer{nbComponents, QVector<double>{}};
175 195 }
176 196 }
177 197
178 198 /// Copy ctor
179 199 explicit ArrayData(const ArrayData &other)
180 200 {
181 201 QReadLocker otherLocker{&other.m_Lock};
182 202 m_Data = other.m_Data;
183 203 }
184 204
185 205 // /////////////// //
186 206 // General methods //
187 207 // /////////////// //
188 208
189 209 /**
190 210 * Merges into the array data an other array data. The two array datas must have the same number
191 211 * of components so the merge can be done
192 212 * @param other the array data to merge with
193 213 * @param prepend if true, the other array data is inserted at the beginning, otherwise it is
194 214 * inserted at the end
195 215 */
196 216 void add(const ArrayData<Dim> &other, bool prepend = false)
197 217 {
198 218 QWriteLocker locker{&m_Lock};
199 219 QReadLocker otherLocker{&other.m_Lock};
200 220
201 221 auto nbComponents = m_Data.size();
202 222 if (nbComponents != other.m_Data.size()) {
203 223 return;
204 224 }
205 225
206 226 for (auto componentIndex = 0; componentIndex < nbComponents; ++componentIndex) {
207 227 if (prepend) {
208 228 const auto &otherData = other.data(componentIndex);
209 229 const auto otherDataSize = otherData.size();
210 230
211 231 auto &data = m_Data[componentIndex];
212 232 data.insert(data.begin(), otherDataSize, 0.);
213 233
214 234 for (auto i = 0; i < otherDataSize; ++i) {
215 235 data.replace(i, otherData.at(i));
216 236 }
217 237 }
218 238 else {
219 239 m_Data[componentIndex] += other.data(componentIndex);
220 240 }
221 241 }
222 242 }
223 243
224 244 void clear()
225 245 {
226 246 QWriteLocker locker{&m_Lock};
227 247
228 248 auto nbComponents = m_Data.size();
229 249 for (auto i = 0; i < nbComponents; ++i) {
230 250 m_Data[i].clear();
231 251 }
232 252 }
233 253
234 254 int componentCount() const noexcept { return m_Data.size(); }
235 255
236 256 /**
237 257 * @return the data of a component
238 258 * @param componentIndex the index of the component to retrieve the data
239 259 * @return the component's data, empty vector if the index is invalid
240 260 */
241 261 QVector<double> data(int componentIndex) const noexcept
242 262 {
243 263 QReadLocker locker{&m_Lock};
244 264
245 265 return (componentIndex >= 0 && componentIndex < m_Data.size()) ? m_Data.at(componentIndex)
246 266 : QVector<double>{};
247 267 }
248 268
249 269 /// @return the size (i.e. number of values) of a single component
250 270 /// @remarks in a case of a two-dimensional ArrayData, each component has the same size
251 271 int size() const
252 272 {
253 273 QReadLocker locker{&m_Lock};
254 274 return m_Data[0].size();
255 275 }
256 276
257 277 std::shared_ptr<ArrayData<Dim> > sort(const std::vector<int> &sortPermutation)
258 278 {
259 279 QReadLocker locker{&m_Lock};
260 280 return arraydata_detail::Sort<Dim>::sort(m_Data, sortPermutation);
261 281 }
262 282
263 283 // ///////// //
264 284 // Iterators //
265 285 // ///////// //
266 286
267 287 Iterator cbegin() const { return Iterator{m_Data, true}; }
268 288 Iterator cend() const { return Iterator{m_Data, false}; }
269 289
270 290 // ///////////// //
271 291 // 1-dim methods //
272 292 // ///////////// //
273 293
274 294 /**
275 295 * @return the data at a specified index
276 296 * @remarks index must be a valid position
277 297 * @remarks this method is only available for a unidimensional ArrayData
278 298 */
279 299 template <int D = Dim, typename = std::enable_if_t<D == 1> >
280 300 double at(int index) const noexcept
281 301 {
282 302 QReadLocker locker{&m_Lock};
283 303 return m_Data[0].at(index);
284 304 }
285 305
286 306 /**
287 307 * @return the data as a vector, as a const reference
288 308 * @remarks this method is only available for a unidimensional ArrayData
289 309 */
290 310 template <int D = Dim, typename = std::enable_if_t<D == 1> >
291 311 const QVector<double> &cdata() const noexcept
292 312 {
293 313 QReadLocker locker{&m_Lock};
294 314 return m_Data.at(0);
295 315 }
296 316
297 317 /**
298 318 * @return the data as a vector
299 319 * @remarks this method is only available for a unidimensional ArrayData
300 320 */
301 321 template <int D = Dim, typename = std::enable_if_t<D == 1> >
302 322 QVector<double> data() const noexcept
303 323 {
304 324 QReadLocker locker{&m_Lock};
305 325 return m_Data[0];
306 326 }
307 327
308 328 // ///////////// //
309 329 // 2-dim methods //
310 330 // ///////////// //
311 331
312 332 /**
313 333 * @return the data
314 334 * @remarks this method is only available for a two-dimensional ArrayData
315 335 */
316 336 template <int D = Dim, typename = std::enable_if_t<D == 2> >
317 337 DataContainer data() const noexcept
318 338 {
319 339 QReadLocker locker{&m_Lock};
320 340 return m_Data;
321 341 }
322 342
323 343 private:
324 344 DataContainer m_Data;
325 345 mutable QReadWriteLock m_Lock;
326 346 };
327 347
328 348 #endif // SCIQLOP_ARRAYDATA_H
@@ -1,306 +1,333
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/IDataSeries.h>
10 10
11 11 #include <QLoggingCategory>
12 12 #include <QReadLocker>
13 13 #include <QReadWriteLock>
14 14 #include <memory>
15 15
16 16 // We don't use the Qt macro since the log is used in the header file, which causes multiple log
17 17 // definitions with inheritance. Inline method is used instead
18 18 inline const QLoggingCategory &LOG_DataSeries()
19 19 {
20 20 static const QLoggingCategory category{"DataSeries"};
21 21 return category;
22 22 }
23 23
24 24 template <int Dim>
25 25 class DataSeries;
26 26
27 27 namespace dataseries_detail {
28 28
29 29 template <int Dim>
30 30 class IteratorValue : public DataSeriesIteratorValue::Impl {
31 31 public:
32 32 explicit IteratorValue(const DataSeries<Dim> &dataSeries, bool begin)
33 33 : m_XIt(begin ? dataSeries.xAxisData()->cbegin() : dataSeries.xAxisData()->cend()),
34 34 m_ValuesIt(begin ? dataSeries.valuesData()->cbegin()
35 35 : dataSeries.valuesData()->cend())
36 36 {
37 37 }
38 38 IteratorValue(const IteratorValue &other) = default;
39 39
40 40 std::unique_ptr<DataSeriesIteratorValue::Impl> clone() const override
41 41 {
42 42 return std::make_unique<IteratorValue<Dim> >(*this);
43 43 }
44 44
45 45 bool equals(const DataSeriesIteratorValue::Impl &other) const override try {
46 46 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
47 47 return std::tie(m_XIt, m_ValuesIt) == std::tie(otherImpl.m_XIt, otherImpl.m_ValuesIt);
48 48 }
49 49 catch (const std::bad_cast &) {
50 50 return false;
51 51 }
52 52
53 53 void next() override
54 54 {
55 55 ++m_XIt;
56 56 ++m_ValuesIt;
57 57 }
58 58
59 59 void prev() override
60 60 {
61 61 --m_XIt;
62 62 --m_ValuesIt;
63 63 }
64 64
65 65 double x() const override { return m_XIt->at(0); }
66 66 double value() const override { return m_ValuesIt->at(0); }
67 67 double value(int componentIndex) const override { return m_ValuesIt->at(componentIndex); }
68 double minValue() const override { return m_ValuesIt->min(); }
69 double maxValue() const override { return m_ValuesIt->max(); }
68 70
69 71 private:
70 72 ArrayData<1>::Iterator m_XIt;
71 73 typename ArrayData<Dim>::Iterator m_ValuesIt;
72 74 };
73 75 } // namespace dataseries_detail
74 76
75 77 /**
76 78 * @brief The DataSeries class is the base (abstract) implementation of IDataSeries.
77 79 *
78 80 * It proposes to set a dimension for the values ​​data.
79 81 *
80 82 * A DataSeries is always sorted on its x-axis data.
81 83 *
82 84 * @tparam Dim The dimension of the values data
83 85 *
84 86 */
85 87 template <int Dim>
86 88 class SCIQLOP_CORE_EXPORT DataSeries : public IDataSeries {
87 89 public:
88 90 /// @sa IDataSeries::xAxisData()
89 91 std::shared_ptr<ArrayData<1> > xAxisData() override { return m_XAxisData; }
90 92 const std::shared_ptr<ArrayData<1> > xAxisData() const { return m_XAxisData; }
91 93
92 94 /// @sa IDataSeries::xAxisUnit()
93 95 Unit xAxisUnit() const override { return m_XAxisUnit; }
94 96
95 97 /// @return the values dataset
96 98 std::shared_ptr<ArrayData<Dim> > valuesData() { return m_ValuesData; }
97 99 const std::shared_ptr<ArrayData<Dim> > valuesData() const { return m_ValuesData; }
98 100
99 101 /// @sa IDataSeries::valuesUnit()
100 102 Unit valuesUnit() const override { return m_ValuesUnit; }
101 103
102 104
103 105 SqpRange range() const override
104 106 {
105 107 if (!m_XAxisData->cdata().isEmpty()) {
106 108 return SqpRange{m_XAxisData->cdata().first(), m_XAxisData->cdata().last()};
107 109 }
108 110
109 111 return SqpRange{};
110 112 }
111 113
112 114 void clear()
113 115 {
114 116 m_XAxisData->clear();
115 117 m_ValuesData->clear();
116 118 }
117 119
118 120 /// Merges into the data series an other data series
119 121 /// @remarks the data series to merge with is cleared after the operation
120 122 void merge(IDataSeries *dataSeries) override
121 123 {
122 124 dataSeries->lockWrite();
123 125 lockWrite();
124 126
125 127 if (auto other = dynamic_cast<DataSeries<Dim> *>(dataSeries)) {
126 128 const auto &otherXAxisData = other->xAxisData()->cdata();
127 129 const auto &xAxisData = m_XAxisData->cdata();
128 130
129 131 // As data series are sorted, we can improve performances of merge, by call the sort
130 132 // method only if the two data series overlap.
131 133 if (!otherXAxisData.empty()) {
132 134 auto firstValue = otherXAxisData.front();
133 135 auto lastValue = otherXAxisData.back();
134 136
135 137 auto xAxisDataBegin = xAxisData.cbegin();
136 138 auto xAxisDataEnd = xAxisData.cend();
137 139
138 140 bool prepend;
139 141 bool sortNeeded;
140 142
141 143 if (std::lower_bound(xAxisDataBegin, xAxisDataEnd, firstValue) == xAxisDataEnd) {
142 144 // Other data series if after data series
143 145 prepend = false;
144 146 sortNeeded = false;
145 147 }
146 148 else if (std::upper_bound(xAxisDataBegin, xAxisDataEnd, lastValue)
147 149 == xAxisDataBegin) {
148 150 // Other data series if before data series
149 151 prepend = true;
150 152 sortNeeded = false;
151 153 }
152 154 else {
153 155 // The two data series overlap
154 156 prepend = false;
155 157 sortNeeded = true;
156 158 }
157 159
158 160 // Makes the merge
159 161 m_XAxisData->add(*other->xAxisData(), prepend);
160 162 m_ValuesData->add(*other->valuesData(), prepend);
161 163
162 164 if (sortNeeded) {
163 165 sort();
164 166 }
165 167 }
166 168
167 169 // Clears the other data series
168 170 other->clear();
169 171 }
170 172 else {
171 173 qCWarning(LOG_DataSeries())
172 174 << QObject::tr("Detection of a type of IDataSeries we cannot merge with !");
173 175 }
174 176 unlock();
175 177 dataSeries->unlock();
176 178 }
177 179
178 180 // ///////// //
179 181 // Iterators //
180 182 // ///////// //
181 183
182 184 DataSeriesIterator cbegin() const override
183 185 {
184 186 return DataSeriesIterator{DataSeriesIteratorValue{
185 187 std::make_unique<dataseries_detail::IteratorValue<Dim> >(*this, true)}};
186 188 }
187 189
188 190 DataSeriesIterator cend() const override
189 191 {
190 192 return DataSeriesIterator{DataSeriesIteratorValue{
191 193 std::make_unique<dataseries_detail::IteratorValue<Dim> >(*this, false)}};
192 194 }
193 195
194 /// @sa IDataSeries::minData()
195 DataSeriesIterator minData(double minXAxisData) const override
196 /// @sa IDataSeries::minXAxisData()
197 DataSeriesIterator minXAxisData(double minXAxisData) const override
196 198 {
197 199 return std::lower_bound(
198 200 cbegin(), cend(), minXAxisData,
199 201 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
200 202 }
201 203
202 /// @sa IDataSeries::maxData()
203 DataSeriesIterator maxData(double maxXAxisData) const override
204 /// @sa IDataSeries::maxXAxisData()
205 DataSeriesIterator maxXAxisData(double maxXAxisData) const override
204 206 {
205 207 // Gets the first element that greater than max value
206 208 auto it = std::upper_bound(
207 209 cbegin(), cend(), maxXAxisData,
208 210 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
209 211
210 212 return it == cbegin() ? cend() : --it;
211 213 }
212 214
213 std::pair<DataSeriesIterator, DataSeriesIterator> subData(double min, double max) const override
215 std::pair<DataSeriesIterator, DataSeriesIterator> xAxisRange(double minXAxisData,
216 double maxXAxisData) const override
214 217 {
215 if (min > max) {
216 std::swap(min, max);
218 if (minXAxisData > maxXAxisData) {
219 std::swap(minXAxisData, maxXAxisData);
217 220 }
218 221
219 222 auto begin = cbegin();
220 223 auto end = cend();
221 224
222 auto lowerIt
223 = std::lower_bound(begin, end, min, [](const auto &itValue, const auto &value) {
224 return itValue.x() < value;
225 });
226 auto upperIt
227 = std::upper_bound(begin, end, max, [](const auto &value, const auto &itValue) {
228 return value < itValue.x();
229 });
225 auto lowerIt = std::lower_bound(
226 begin, end, minXAxisData,
227 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
228 auto upperIt = std::upper_bound(
229 begin, end, maxXAxisData,
230 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
230 231
231 232 return std::make_pair(lowerIt, upperIt);
232 233 }
233 234
235 std::pair<DataSeriesIterator, DataSeriesIterator>
236 valuesBounds(double minXAxisData, double maxXAxisData) const override
237 {
238 // Places iterators to the correct x-axis range
239 auto xAxisRangeIts = xAxisRange(minXAxisData, maxXAxisData);
240
241 // Returns end iterators if the range is empty
242 if (xAxisRangeIts.first == xAxisRangeIts.second) {
243 return std::make_pair(cend(), cend());
244 }
245
246 // Gets the iterator on the min of all values data
247 auto minIt = std::min_element(
248 xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) {
249 return SortUtils::minCompareWithNaN(it1.minValue(), it2.minValue());
250 });
251
252 // Gets the iterator on the max of all values data
253 auto maxIt = std::max_element(
254 xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) {
255 return SortUtils::maxCompareWithNaN(it1.maxValue(), it2.maxValue());
256 });
257
258 return std::make_pair(minIt, maxIt);
259 }
260
234 261 // /////// //
235 262 // Mutexes //
236 263 // /////// //
237 264
238 265 virtual void lockRead() { m_Lock.lockForRead(); }
239 266 virtual void lockWrite() { m_Lock.lockForWrite(); }
240 267 virtual void unlock() { m_Lock.unlock(); }
241 268
242 269 protected:
243 270 /// Protected ctor (DataSeries is abstract). The vectors must have the same size, otherwise a
244 271 /// DataSeries with no values will be created.
245 272 /// @remarks data series is automatically sorted on its x-axis data
246 273 explicit DataSeries(std::shared_ptr<ArrayData<1> > xAxisData, const Unit &xAxisUnit,
247 274 std::shared_ptr<ArrayData<Dim> > valuesData, const Unit &valuesUnit)
248 275 : m_XAxisData{xAxisData},
249 276 m_XAxisUnit{xAxisUnit},
250 277 m_ValuesData{valuesData},
251 278 m_ValuesUnit{valuesUnit}
252 279 {
253 280 if (m_XAxisData->size() != m_ValuesData->size()) {
254 281 clear();
255 282 }
256 283
257 284 // Sorts data if it's not the case
258 285 const auto &xAxisCData = m_XAxisData->cdata();
259 286 if (!std::is_sorted(xAxisCData.cbegin(), xAxisCData.cend())) {
260 287 sort();
261 288 }
262 289 }
263 290
264 291 /// Copy ctor
265 292 explicit DataSeries(const DataSeries<Dim> &other)
266 293 : m_XAxisData{std::make_shared<ArrayData<1> >(*other.m_XAxisData)},
267 294 m_XAxisUnit{other.m_XAxisUnit},
268 295 m_ValuesData{std::make_shared<ArrayData<Dim> >(*other.m_ValuesData)},
269 296 m_ValuesUnit{other.m_ValuesUnit}
270 297 {
271 298 // Since a series is ordered from its construction and is always ordered, it is not
272 299 // necessary to call the sort method here ('other' is sorted)
273 300 }
274 301
275 302 /// Assignment operator
276 303 template <int D>
277 304 DataSeries &operator=(DataSeries<D> other)
278 305 {
279 306 std::swap(m_XAxisData, other.m_XAxisData);
280 307 std::swap(m_XAxisUnit, other.m_XAxisUnit);
281 308 std::swap(m_ValuesData, other.m_ValuesData);
282 309 std::swap(m_ValuesUnit, other.m_ValuesUnit);
283 310
284 311 return *this;
285 312 }
286 313
287 314 private:
288 315 /**
289 316 * Sorts data series on its x-axis data
290 317 */
291 318 void sort() noexcept
292 319 {
293 320 auto permutation = SortUtils::sortPermutation(*m_XAxisData, std::less<double>());
294 321 m_XAxisData = m_XAxisData->sort(permutation);
295 322 m_ValuesData = m_ValuesData->sort(permutation);
296 323 }
297 324
298 325 std::shared_ptr<ArrayData<1> > m_XAxisData;
299 326 Unit m_XAxisUnit;
300 327 std::shared_ptr<ArrayData<Dim> > m_ValuesData;
301 328 Unit m_ValuesUnit;
302 329
303 330 QReadWriteLock m_Lock;
304 331 };
305 332
306 333 #endif // SCIQLOP_DATASERIES_H
@@ -1,82 +1,88
1 1 #ifndef SCIQLOP_DATASERIESITERATOR_H
2 2 #define SCIQLOP_DATASERIESITERATOR_H
3 3
4 4 #include "CoreGlobal.h"
5 5
6 6 #include <memory>
7 7
8 8 /**
9 9 * @brief The DataSeriesIteratorValue class represents the current value of a data series iterator.
10 10 * It offers standard access methods for the data in the series (x-axis, values), but it is up to
11 11 * each series to define its own implementation of how to retrieve this data, by implementing the
12 12 * DataSeriesIteratorValue::Impl interface
13 13 *
14 14 * @sa DataSeriesIterator
15 15 */
16 16 class SCIQLOP_CORE_EXPORT DataSeriesIteratorValue {
17 17 public:
18 18 struct Impl {
19 19 virtual ~Impl() noexcept = default;
20 20 virtual std::unique_ptr<Impl> clone() const = 0;
21 21 virtual bool equals(const Impl &other) const = 0;
22 22 virtual void next() = 0;
23 23 virtual void prev() = 0;
24 24 virtual double x() const = 0;
25 25 virtual double value() const = 0;
26 26 virtual double value(int componentIndex) const = 0;
27 virtual double minValue() const = 0;
28 virtual double maxValue() const = 0;
27 29 };
28 30
29 31 explicit DataSeriesIteratorValue(std::unique_ptr<Impl> impl);
30 32 DataSeriesIteratorValue(const DataSeriesIteratorValue &other);
31 33 DataSeriesIteratorValue(DataSeriesIteratorValue &&other) = default;
32 34 DataSeriesIteratorValue &operator=(DataSeriesIteratorValue other);
33 35
34 36 bool equals(const DataSeriesIteratorValue &other) const;
35 37
36 38 /// Advances to the next value
37 39 void next();
38 40 /// Moves back to the previous value
39 41 void prev();
40 42 /// Gets x-axis data
41 43 double x() const;
42 44 /// Gets value data
43 45 double value() const;
44 46 /// Gets value data depending on an index
45 47 double value(int componentIndex) const;
48 /// Gets min of all values data
49 double minValue() const;
50 /// Gets max of all values data
51 double maxValue() const;
46 52
47 53 private:
48 54 std::unique_ptr<Impl> m_Impl;
49 55 };
50 56
51 57 /**
52 58 * @brief The DataSeriesIterator class represents an iterator used for data series. It defines all
53 59 * operators needed for a standard forward iterator
54 60 * @sa http://www.cplusplus.com/reference/iterator/
55 61 */
56 62 class SCIQLOP_CORE_EXPORT DataSeriesIterator {
57 63 public:
58 64 using iterator_category = std::forward_iterator_tag;
59 65 using value_type = const DataSeriesIteratorValue;
60 66 using difference_type = std::ptrdiff_t;
61 67 using pointer = value_type *;
62 68 using reference = value_type &;
63 69
64 70 explicit DataSeriesIterator(DataSeriesIteratorValue value);
65 71 virtual ~DataSeriesIterator() noexcept = default;
66 72 DataSeriesIterator(const DataSeriesIterator &) = default;
67 73 DataSeriesIterator(DataSeriesIterator &&) = default;
68 74 DataSeriesIterator &operator=(const DataSeriesIterator &) = default;
69 75 DataSeriesIterator &operator=(DataSeriesIterator &&) = default;
70 76
71 77 DataSeriesIterator &operator++();
72 78 DataSeriesIterator &operator--();
73 79 pointer operator->() const { return &m_CurrentValue; }
74 80 reference operator*() const { return m_CurrentValue; }
75 81 bool operator==(const DataSeriesIterator &other) const;
76 82 bool operator!=(const DataSeriesIterator &other) const;
77 83
78 84 private:
79 85 DataSeriesIteratorValue m_CurrentValue;
80 86 };
81 87
82 88 #endif // SCIQLOP_DATASERIESITERATOR_H
@@ -1,96 +1,104
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
8 8 #include <memory>
9 9
10 10 #include <QString>
11 11
12 12 template <int Dim>
13 13 class ArrayData;
14 14
15 15 struct Unit {
16 16 explicit Unit(const QString &name = {}, bool timeUnit = false)
17 17 : m_Name{name}, m_TimeUnit{timeUnit}
18 18 {
19 19 }
20 20
21 21 inline bool operator==(const Unit &other) const
22 22 {
23 23 return std::tie(m_Name, m_TimeUnit) == std::tie(other.m_Name, other.m_TimeUnit);
24 24 }
25 25 inline bool operator!=(const Unit &other) const { return !(*this == other); }
26 26
27 27 QString m_Name; ///< Unit name
28 28 bool m_TimeUnit; ///< The unit is a unit of time (UTC)
29 29 };
30 30
31 31 /**
32 32 * @brief The IDataSeries aims to declare a data series.
33 33 *
34 34 * A data series is an entity that contains at least :
35 35 * - one dataset representing the x-axis
36 36 * - one dataset representing the values
37 37 *
38 38 * Each dataset is represented by an ArrayData, and is associated with a unit.
39 39 *
40 40 * An ArrayData can be unidimensional or two-dimensional, depending on the implementation of the
41 41 * IDataSeries. The x-axis dataset is always unidimensional.
42 42 *
43 43 * @sa ArrayData
44 44 */
45 45 class IDataSeries {
46 46 public:
47 47 virtual ~IDataSeries() noexcept = default;
48 48
49 49 /// Returns the x-axis dataset
50 50 virtual std::shared_ptr<ArrayData<1> > xAxisData() = 0;
51 51
52 52 /// Returns the x-axis dataset (as const)
53 53 virtual const std::shared_ptr<ArrayData<1> > xAxisData() const = 0;
54 54
55 55 virtual Unit xAxisUnit() const = 0;
56 56
57 57 virtual Unit valuesUnit() const = 0;
58 58
59 59 virtual void merge(IDataSeries *dataSeries) = 0;
60 60 /// @todo Review the name and signature of this method
61 61 virtual std::shared_ptr<IDataSeries> subDataSeries(const SqpRange &range) = 0;
62 62
63 63 virtual std::unique_ptr<IDataSeries> clone() const = 0;
64 64 virtual SqpRange range() const = 0;
65 65
66 66 // ///////// //
67 67 // Iterators //
68 68 // ///////// //
69 69
70 70 virtual DataSeriesIterator cbegin() const = 0;
71 71 virtual DataSeriesIterator cend() const = 0;
72 72
73 73 /// @return the iterator to the first entry of the data series whose x-axis data is greater than
74 74 /// or equal to the value passed in parameter, or the end iterator if there is no matching value
75 virtual DataSeriesIterator minData(double minXAxisData) const = 0;
75 virtual DataSeriesIterator minXAxisData(double minXAxisData) const = 0;
76 76
77 77 /// @return the iterator to the last entry of the data series whose x-axis data is less than or
78 78 /// equal to the value passed in parameter, or the end iterator if there is no matching value
79 virtual DataSeriesIterator maxData(double maxXAxisData) const = 0;
80
81 virtual std::pair<DataSeriesIterator, DataSeriesIterator> subData(double min,
82 double max) const = 0;
79 virtual DataSeriesIterator maxXAxisData(double maxXAxisData) const = 0;
80
81 /// @return the iterators pointing to the range of data whose x-axis values are between min and
82 /// max passed in parameters
83 virtual std::pair<DataSeriesIterator, DataSeriesIterator>
84 xAxisRange(double minXAxisData, double maxXAxisData) const = 0;
85
86 /// @return two iterators pointing to the data that have respectively the min and the max value
87 /// data of a data series' range. The search is performed for a given x-axis range.
88 /// @sa xAxisRange()
89 virtual std::pair<DataSeriesIterator, DataSeriesIterator>
90 valuesBounds(double minXAxisData, double maxXAxisData) const = 0;
83 91
84 92 // /////// //
85 93 // Mutexes //
86 94 // /////// //
87 95
88 96 virtual void lockRead() = 0;
89 97 virtual void lockWrite() = 0;
90 98 virtual void unlock() = 0;
91 99 };
92 100
93 101 // Required for using shared_ptr in signals/slots
94 102 SCIQLOP_REGISTER_META_TYPE(IDATASERIES_PTR_REGISTRY, std::shared_ptr<IDataSeries>)
95 103
96 104 #endif // SCIQLOP_IDATASERIES_H
@@ -1,75 +1,85
1 1 #include "Data/DataSeriesIterator.h"
2 2
3 3 DataSeriesIteratorValue::DataSeriesIteratorValue(
4 4 std::unique_ptr<DataSeriesIteratorValue::Impl> impl)
5 5 : m_Impl{std::move(impl)}
6 6 {
7 7 }
8 8
9 9 DataSeriesIteratorValue::DataSeriesIteratorValue(const DataSeriesIteratorValue &other)
10 10 : m_Impl{other.m_Impl->clone()}
11 11 {
12 12 }
13 13
14 14 DataSeriesIteratorValue &DataSeriesIteratorValue::operator=(DataSeriesIteratorValue other)
15 15 {
16 16 std::swap(m_Impl, other.m_Impl);
17 17 return *this;
18 18 }
19 19
20 20 bool DataSeriesIteratorValue::equals(const DataSeriesIteratorValue &other) const
21 21 {
22 22 return m_Impl->equals(*other.m_Impl);
23 23 }
24 24
25 25 void DataSeriesIteratorValue::next()
26 26 {
27 27 m_Impl->next();
28 28 }
29 29
30 30 void DataSeriesIteratorValue::prev()
31 31 {
32 32 m_Impl->prev();
33 33 }
34 34
35 35 double DataSeriesIteratorValue::x() const
36 36 {
37 37 return m_Impl->x();
38 38 }
39 39
40 40 double DataSeriesIteratorValue::value() const
41 41 {
42 42 return m_Impl->value();
43 43 }
44 44
45 45 double DataSeriesIteratorValue::value(int componentIndex) const
46 46 {
47 47 return m_Impl->value(componentIndex);
48 48 }
49 49
50 double DataSeriesIteratorValue::minValue() const
51 {
52 return m_Impl->minValue();
53 }
54
55 double DataSeriesIteratorValue::maxValue() const
56 {
57 return m_Impl->maxValue();
58 }
59
50 60 DataSeriesIterator::DataSeriesIterator(DataSeriesIteratorValue value)
51 61 : m_CurrentValue{std::move(value)}
52 62 {
53 63 }
54 64
55 65 DataSeriesIterator &DataSeriesIterator::operator++()
56 66 {
57 67 m_CurrentValue.next();
58 68 return *this;
59 69 }
60 70
61 71 DataSeriesIterator &DataSeriesIterator::operator--()
62 72 {
63 73 m_CurrentValue.prev();
64 74 return *this;
65 75 }
66 76
67 77 bool DataSeriesIterator::operator==(const DataSeriesIterator &other) const
68 78 {
69 79 return m_CurrentValue.equals(other.m_CurrentValue);
70 80 }
71 81
72 82 bool DataSeriesIterator::operator!=(const DataSeriesIterator &other) const
73 83 {
74 84 return !(*this == other);
75 85 }
@@ -1,31 +1,31
1 1 #include <Data/ScalarSeries.h>
2 2
3 3 ScalarSeries::ScalarSeries(QVector<double> xAxisData, QVector<double> valuesData,
4 4 const Unit &xAxisUnit, const Unit &valuesUnit)
5 5 : DataSeries{std::make_shared<ArrayData<1> >(std::move(xAxisData)), xAxisUnit,
6 6 std::make_shared<ArrayData<1> >(std::move(valuesData)), valuesUnit}
7 7 {
8 8 }
9 9
10 10 std::unique_ptr<IDataSeries> ScalarSeries::clone() const
11 11 {
12 12 return std::make_unique<ScalarSeries>(*this);
13 13 }
14 14
15 15 std::shared_ptr<IDataSeries> ScalarSeries::subDataSeries(const SqpRange &range)
16 16 {
17 17 auto subXAxisData = QVector<double>();
18 18 auto subValuesData = QVector<double>();
19 19 this->lockRead();
20 20 {
21 auto bounds = subData(range.m_TStart, range.m_TEnd);
21 auto bounds = xAxisRange(range.m_TStart, range.m_TEnd);
22 22 for (auto it = bounds.first; it != bounds.second; ++it) {
23 23 subXAxisData.append(it->x());
24 24 subValuesData.append(it->value());
25 25 }
26 26 }
27 27 this->unlock();
28 28
29 29 return std::make_shared<ScalarSeries>(subXAxisData, subValuesData, this->xAxisUnit(),
30 30 this->valuesUnit());
31 31 }
@@ -1,39 +1,39
1 1 #include "Data/VectorSeries.h"
2 2
3 3 VectorSeries::VectorSeries(QVector<double> xAxisData, QVector<double> xValuesData,
4 4 QVector<double> yValuesData, QVector<double> zValuesData,
5 5 const Unit &xAxisUnit, const Unit &valuesUnit)
6 6 : DataSeries{std::make_shared<ArrayData<1> >(std::move(xAxisData)), xAxisUnit,
7 7 std::make_shared<ArrayData<2> >(QVector<QVector<double> >{
8 8 std::move(xValuesData), std::move(yValuesData), std::move(zValuesData)}),
9 9 valuesUnit}
10 10 {
11 11 }
12 12
13 13 std::unique_ptr<IDataSeries> VectorSeries::clone() const
14 14 {
15 15 return std::make_unique<VectorSeries>(*this);
16 16 }
17 17
18 18 std::shared_ptr<IDataSeries> VectorSeries::subDataSeries(const SqpRange &range)
19 19 {
20 20 auto subXAxisData = QVector<double>();
21 21 auto subXValuesData = QVector<double>();
22 22 auto subYValuesData = QVector<double>();
23 23 auto subZValuesData = QVector<double>();
24 24
25 25 this->lockRead();
26 26 {
27 auto bounds = subData(range.m_TStart, range.m_TEnd);
27 auto bounds = xAxisRange(range.m_TStart, range.m_TEnd);
28 28 for (auto it = bounds.first; it != bounds.second; ++it) {
29 29 subXAxisData.append(it->x());
30 30 subXValuesData.append(it->value(0));
31 31 subYValuesData.append(it->value(1));
32 32 subZValuesData.append(it->value(2));
33 33 }
34 34 }
35 35 this->unlock();
36 36
37 37 return std::make_shared<VectorSeries>(subXAxisData, subXValuesData, subYValuesData,
38 38 subZValuesData, this->xAxisUnit(), this->valuesUnit());
39 39 }
@@ -1,271 +1,271
1 1 #include <Variable/Variable.h>
2 2 #include <Variable/VariableModel.h>
3 3
4 4 #include <Common/DateUtils.h>
5 5
6 6 #include <Data/IDataSeries.h>
7 7
8 8 #include <QSize>
9 9 #include <unordered_map>
10 10
11 11 Q_LOGGING_CATEGORY(LOG_VariableModel, "VariableModel")
12 12
13 13 namespace {
14 14
15 15 // Column indexes
16 16 const auto NAME_COLUMN = 0;
17 17 const auto TSTART_COLUMN = 1;
18 18 const auto TEND_COLUMN = 2;
19 19 const auto UNIT_COLUMN = 3;
20 20 const auto MISSION_COLUMN = 4;
21 21 const auto PLUGIN_COLUMN = 5;
22 22 const auto NB_COLUMNS = 6;
23 23
24 24 // Column properties
25 25 const auto DEFAULT_HEIGHT = 25;
26 26 const auto DEFAULT_WIDTH = 100;
27 27
28 28 struct ColumnProperties {
29 29 ColumnProperties(const QString &name = {}, int width = DEFAULT_WIDTH,
30 30 int height = DEFAULT_HEIGHT)
31 31 : m_Name{name}, m_Width{width}, m_Height{height}
32 32 {
33 33 }
34 34
35 35 QString m_Name;
36 36 int m_Width;
37 37 int m_Height;
38 38 };
39 39
40 40 const auto COLUMN_PROPERTIES = QHash<int, ColumnProperties>{
41 41 {NAME_COLUMN, {QObject::tr("Name")}}, {TSTART_COLUMN, {QObject::tr("tStart"), 180}},
42 42 {TEND_COLUMN, {QObject::tr("tEnd"), 180}}, {UNIT_COLUMN, {QObject::tr("Unit")}},
43 43 {MISSION_COLUMN, {QObject::tr("Mission")}}, {PLUGIN_COLUMN, {QObject::tr("Plugin")}}};
44 44
45 45 /// Format for datetimes
46 46 const auto DATETIME_FORMAT = QStringLiteral("dd/MM/yyyy \nhh:mm:ss:zzz");
47 47
48 48
49 49 } // namespace
50 50
51 51 struct VariableModel::VariableModelPrivate {
52 52 /// Variables created in SciQlop
53 53 std::vector<std::shared_ptr<Variable> > m_Variables;
54 54 std::unordered_map<std::shared_ptr<Variable>, double> m_VariableToProgress;
55 55
56 56 /// Return the row index of the variable. -1 if it's not found
57 57 int indexOfVariable(Variable *variable) const noexcept;
58 58 };
59 59
60 60 VariableModel::VariableModel(QObject *parent)
61 61 : QAbstractTableModel{parent}, impl{spimpl::make_unique_impl<VariableModelPrivate>()}
62 62 {
63 63 }
64 64
65 65 std::shared_ptr<Variable> VariableModel::createVariable(const QString &name,
66 66 const SqpRange &dateTime,
67 67 const QVariantHash &metadata) noexcept
68 68 {
69 69 auto insertIndex = rowCount();
70 70 beginInsertRows({}, insertIndex, insertIndex);
71 71
72 72 auto variable = std::make_shared<Variable>(name, dateTime, metadata);
73 73
74 74 impl->m_Variables.push_back(variable);
75 75 connect(variable.get(), &Variable::updated, this, &VariableModel::onVariableUpdated);
76 76
77 77 endInsertRows();
78 78
79 79 return variable;
80 80 }
81 81
82 82 void VariableModel::deleteVariable(std::shared_ptr<Variable> variable) noexcept
83 83 {
84 84 if (!variable) {
85 85 qCCritical(LOG_Variable()) << "Can't delete a null variable from the model";
86 86 return;
87 87 }
88 88
89 89 // Finds variable in the model
90 90 auto begin = impl->m_Variables.cbegin();
91 91 auto end = impl->m_Variables.cend();
92 92 auto it = std::find(begin, end, variable);
93 93 if (it != end) {
94 94 auto removeIndex = std::distance(begin, it);
95 95
96 96 // Deletes variable
97 97 beginRemoveRows({}, removeIndex, removeIndex);
98 98 impl->m_Variables.erase(it);
99 99 endRemoveRows();
100 100 }
101 101 else {
102 102 qCritical(LOG_VariableModel())
103 103 << tr("Can't delete variable %1 from the model: the variable is not in the model")
104 104 .arg(variable->name());
105 105 }
106 106
107 107 // Removes variable from progress map
108 108 impl->m_VariableToProgress.erase(variable);
109 109 }
110 110
111 111
112 112 std::shared_ptr<Variable> VariableModel::variable(int index) const
113 113 {
114 114 return (index >= 0 && index < impl->m_Variables.size()) ? impl->m_Variables[index] : nullptr;
115 115 }
116 116
117 117 void VariableModel::setDataProgress(std::shared_ptr<Variable> variable, double progress)
118 118 {
119 119 if (progress > 0.0) {
120 120 impl->m_VariableToProgress[variable] = progress;
121 121 }
122 122 else {
123 123 impl->m_VariableToProgress.erase(variable);
124 124 }
125 125 auto modelIndex = createIndex(impl->indexOfVariable(variable.get()), NAME_COLUMN);
126 126
127 127 emit dataChanged(modelIndex, modelIndex);
128 128 }
129 129
130 130 int VariableModel::columnCount(const QModelIndex &parent) const
131 131 {
132 132 Q_UNUSED(parent);
133 133
134 134 return NB_COLUMNS;
135 135 }
136 136
137 137 int VariableModel::rowCount(const QModelIndex &parent) const
138 138 {
139 139 Q_UNUSED(parent);
140 140
141 141 return impl->m_Variables.size();
142 142 }
143 143
144 144 QVariant VariableModel::data(const QModelIndex &index, int role) const
145 145 {
146 146 if (!index.isValid()) {
147 147 return QVariant{};
148 148 }
149 149
150 150 if (index.row() < 0 || index.row() >= rowCount()) {
151 151 return QVariant{};
152 152 }
153 153
154 154 if (role == Qt::DisplayRole) {
155 155 if (auto variable = impl->m_Variables.at(index.row()).get()) {
156 156 /// Lambda function that builds the variant to return for a time value
157 157 /// @param getValueFun function used to get for a data series the iterator on the entry
158 158 /// that contains the time value to display
159 159 auto dateTimeVariant = [variable](const auto &getValueFun) {
160 160 if (auto dataSeries = variable->dataSeries()) {
161 161 auto it = getValueFun(*dataSeries);
162 162 return (it != dataSeries->cend())
163 163 ? DateUtils::dateTime(it->x()).toString(DATETIME_FORMAT)
164 164 : QVariant{};
165 165 }
166 166 else {
167 167 return QVariant{};
168 168 }
169 169 };
170 170
171 171 switch (index.column()) {
172 172 case NAME_COLUMN:
173 173 return variable->name();
174 174 case TSTART_COLUMN:
175 175 // Shows the min value of the data series above the range tstart
176 176 return dateTimeVariant([min = variable->range().m_TStart](
177 const auto &dataSeries) { return dataSeries.minData(min); });
177 const auto &dataSeries) { return dataSeries.minXAxisData(min); });
178 178 case TEND_COLUMN:
179 179 // Shows the max value of the data series under the range tend
180 180 return dateTimeVariant([max = variable->range().m_TEnd](
181 const auto &dataSeries) { return dataSeries.maxData(max); });
181 const auto &dataSeries) { return dataSeries.maxXAxisData(max); });
182 182 case UNIT_COLUMN:
183 183 return variable->metadata().value(QStringLiteral("units"));
184 184 case MISSION_COLUMN:
185 185 return variable->metadata().value(QStringLiteral("mission"));
186 186 case PLUGIN_COLUMN:
187 187 return variable->metadata().value(QStringLiteral("plugin"));
188 188 default:
189 189 // No action
190 190 break;
191 191 }
192 192
193 193 qWarning(LOG_VariableModel())
194 194 << tr("Can't get data (unknown column %1)").arg(index.column());
195 195 }
196 196 else {
197 197 qWarning(LOG_VariableModel()) << tr("Can't get data (no variable)");
198 198 }
199 199 }
200 200 else if (role == VariableRoles::ProgressRole) {
201 201 if (auto variable = impl->m_Variables.at(index.row())) {
202 202
203 203 auto it = impl->m_VariableToProgress.find(variable);
204 204 if (it != impl->m_VariableToProgress.cend()) {
205 205 return it->second;
206 206 }
207 207 }
208 208 }
209 209
210 210 return QVariant{};
211 211 }
212 212
213 213 QVariant VariableModel::headerData(int section, Qt::Orientation orientation, int role) const
214 214 {
215 215 if (role != Qt::DisplayRole && role != Qt::SizeHintRole) {
216 216 return QVariant{};
217 217 }
218 218
219 219 if (orientation == Qt::Horizontal) {
220 220 auto propertiesIt = COLUMN_PROPERTIES.find(section);
221 221 if (propertiesIt != COLUMN_PROPERTIES.cend()) {
222 222 // Role is either DisplayRole or SizeHintRole
223 223 return (role == Qt::DisplayRole)
224 224 ? QVariant{propertiesIt->m_Name}
225 225 : QVariant{QSize{propertiesIt->m_Width, propertiesIt->m_Height}};
226 226 }
227 227 else {
228 228 qWarning(LOG_VariableModel())
229 229 << tr("Can't get header data (unknown column %1)").arg(section);
230 230 }
231 231 }
232 232
233 233 return QVariant{};
234 234 }
235 235
236 236 void VariableModel::abortProgress(const QModelIndex &index)
237 237 {
238 238 if (auto variable = impl->m_Variables.at(index.row())) {
239 239 emit abortProgessRequested(variable);
240 240 }
241 241 }
242 242
243 243 void VariableModel::onVariableUpdated() noexcept
244 244 {
245 245 // Finds variable that has been updated in the model
246 246 if (auto updatedVariable = dynamic_cast<Variable *>(sender())) {
247 247 auto updatedVariableIndex = impl->indexOfVariable(updatedVariable);
248 248
249 249 if (updatedVariableIndex > -1) {
250 250 emit dataChanged(createIndex(updatedVariableIndex, 0),
251 251 createIndex(updatedVariableIndex, columnCount() - 1));
252 252 }
253 253 }
254 254 }
255 255
256 256 int VariableModel::VariableModelPrivate::indexOfVariable(Variable *variable) const noexcept
257 257 {
258 258 auto begin = std::cbegin(m_Variables);
259 259 auto end = std::cend(m_Variables);
260 260 auto it
261 261 = std::find_if(begin, end, [variable](const auto &var) { return var.get() == variable; });
262 262
263 263 if (it != end) {
264 264 // Gets the index of the variable in the model: we assume here that views have the same
265 265 // order as the model
266 266 return std::distance(begin, it);
267 267 }
268 268 else {
269 269 return -1;
270 270 }
271 271 }
@@ -1,354 +1,520
1 1 #include "Data/DataSeries.h"
2 2 #include "Data/ScalarSeries.h"
3 #include "Data/VectorSeries.h"
3 4
4 5 #include <QObject>
5 6 #include <QtTest>
6 7
7 8 Q_DECLARE_METATYPE(std::shared_ptr<ScalarSeries>)
9 Q_DECLARE_METATYPE(std::shared_ptr<VectorSeries>)
8 10
9 11 class TestDataSeries : public QObject {
10 12 Q_OBJECT
13 private:
14 template <typename T>
15 void testValuesBoundsStructure()
16 {
17 // ////////////// //
18 // Test structure //
19 // ////////////// //
20
21 // Data series to get values bounds
22 QTest::addColumn<std::shared_ptr<T> >("dataSeries");
23
24 // x-axis range
25 QTest::addColumn<double>("minXAxis");
26 QTest::addColumn<double>("maxXAxis");
27
28 // Expected results
29 QTest::addColumn<bool>(
30 "expectedOK"); // Test is expected to be ok (i.e. method doesn't return end iterators)
31 QTest::addColumn<double>("expectedMinValue");
32 QTest::addColumn<double>("expectedMaxValue");
33 }
34
35 template <typename T>
36 void testValuesBounds()
37 {
38 QFETCH(std::shared_ptr<T>, dataSeries);
39 QFETCH(double, minXAxis);
40 QFETCH(double, maxXAxis);
41
42 QFETCH(bool, expectedOK);
43 QFETCH(double, expectedMinValue);
44 QFETCH(double, expectedMaxValue);
45
46 auto minMaxIts = dataSeries->valuesBounds(minXAxis, maxXAxis);
47 auto end = dataSeries->cend();
48
49 // Checks iterators with expected result
50 QCOMPARE(expectedOK, minMaxIts.first != end && minMaxIts.second != end);
51
52 if (expectedOK) {
53 auto compare = [](const auto &v1, const auto &v2) {
54 return (std::isnan(v1) && std::isnan(v2)) || v1 == v2;
55 };
56
57 QVERIFY(compare(expectedMinValue, minMaxIts.first->minValue()));
58 QVERIFY(compare(expectedMaxValue, minMaxIts.second->maxValue()));
59 }
60 }
61
11 62 private slots:
12 63 /// Input test data
13 64 /// @sa testCtor()
14 65 void testCtor_data();
15 66
16 67 /// Tests construction of a data series
17 68 void testCtor();
18 69
19 70 /// Input test data
20 71 /// @sa testMerge()
21 72 void testMerge_data();
22 73
23 74 /// Tests merge of two data series
24 75 void testMerge();
25 76
26 77 /// Input test data
27 /// @sa testMinData()
28 void testMinData_data();
78 /// @sa testMinXAxisData()
79 void testMinXAxisData_data();
80
81 /// Tests get min x-axis data of a data series
82 void testMinXAxisData();
83
84 /// Input test data
85 /// @sa testMaxXAxisData()
86 void testMaxXAxisData_data();
87
88 /// Tests get max x-axis data of a data series
89 void testMaxXAxisData();
90
91 /// Input test data
92 /// @sa testXAxisRange()
93 void testXAxisRange_data();
29 94
30 /// Tests get min data of a data series
31 void testMinData();
95 /// Tests get x-axis range of a data series
96 void testXAxisRange();
32 97
33 98 /// Input test data
34 /// @sa testMaxData()
35 void testMaxData_data();
99 /// @sa testValuesBoundsScalar()
100 void testValuesBoundsScalar_data();
36 101
37 /// Tests get max data of a data series
38 void testMaxData();
102 /// Tests get values bounds of a scalar series
103 void testValuesBoundsScalar();
39 104
40 105 /// Input test data
41 /// @sa testSubdata()
42 void testSubdata_data();
106 /// @sa testValuesBoundsVector()
107 void testValuesBoundsVector_data();
43 108
44 /// Tests get subdata of two data series
45 void testSubdata();
109 /// Tests get values bounds of a vector series
110 void testValuesBoundsVector();
46 111 };
47 112
48 113 void TestDataSeries::testCtor_data()
49 114 {
50 115 // ////////////// //
51 116 // Test structure //
52 117 // ////////////// //
53 118
54 119 // x-axis data
55 120 QTest::addColumn<QVector<double> >("xAxisData");
56 121 // values data
57 122 QTest::addColumn<QVector<double> >("valuesData");
58 123
59 124 // expected x-axis data
60 125 QTest::addColumn<QVector<double> >("expectedXAxisData");
61 126 // expected values data
62 127 QTest::addColumn<QVector<double> >("expectedValuesData");
63 128
64 129 // ////////// //
65 130 // Test cases //
66 131 // ////////// //
67 132
68 133 QTest::newRow("invalidData (different sizes of vectors)")
69 134 << QVector<double>{1., 2., 3., 4., 5.} << QVector<double>{100., 200., 300.}
70 135 << QVector<double>{} << QVector<double>{};
71 136
72 137 QTest::newRow("sortedData") << QVector<double>{1., 2., 3., 4., 5.}
73 138 << QVector<double>{100., 200., 300., 400., 500.}
74 139 << QVector<double>{1., 2., 3., 4., 5.}
75 140 << QVector<double>{100., 200., 300., 400., 500.};
76 141
77 142 QTest::newRow("unsortedData") << QVector<double>{5., 4., 3., 2., 1.}
78 143 << QVector<double>{100., 200., 300., 400., 500.}
79 144 << QVector<double>{1., 2., 3., 4., 5.}
80 145 << QVector<double>{500., 400., 300., 200., 100.};
81 146
82 147 QTest::newRow("unsortedData2")
83 148 << QVector<double>{1., 4., 3., 5., 2.} << QVector<double>{100., 200., 300., 400., 500.}
84 149 << QVector<double>{1., 2., 3., 4., 5.} << QVector<double>{100., 500., 300., 200., 400.};
85 150 }
86 151
87 152 void TestDataSeries::testCtor()
88 153 {
89 154 // Creates series
90 155 QFETCH(QVector<double>, xAxisData);
91 156 QFETCH(QVector<double>, valuesData);
92 157
93 158 auto series = std::make_shared<ScalarSeries>(std::move(xAxisData), std::move(valuesData),
94 159 Unit{}, Unit{});
95 160
96 161 // Validates results : we check that the data series is sorted on its x-axis data
97 162 QFETCH(QVector<double>, expectedXAxisData);
98 163 QFETCH(QVector<double>, expectedValuesData);
99 164
100 165 auto seriesXAxisData = series->xAxisData()->data();
101 166 auto seriesValuesData = series->valuesData()->data();
102 167
103 168 QVERIFY(
104 169 std::equal(expectedXAxisData.cbegin(), expectedXAxisData.cend(), seriesXAxisData.cbegin()));
105 170 QVERIFY(std::equal(expectedValuesData.cbegin(), expectedValuesData.cend(),
106 171 seriesValuesData.cbegin()));
107 172 }
108 173
109 174 namespace {
110 175
111 std::shared_ptr<ScalarSeries> createSeries(QVector<double> xAxisData, QVector<double> valuesData)
176 std::shared_ptr<ScalarSeries> createScalarSeries(QVector<double> xAxisData,
177 QVector<double> valuesData)
112 178 {
113 179 return std::make_shared<ScalarSeries>(std::move(xAxisData), std::move(valuesData), Unit{},
114 180 Unit{});
115 181 }
116 182
183 std::shared_ptr<VectorSeries> createVectorSeries(QVector<double> xAxisData,
184 QVector<double> xValuesData,
185 QVector<double> yValuesData,
186 QVector<double> zValuesData)
187 {
188 return std::make_shared<VectorSeries>(std::move(xAxisData), std::move(xValuesData),
189 std::move(yValuesData), std::move(zValuesData), Unit{},
190 Unit{});
191 }
192
117 193 } // namespace
118 194
119 195 void TestDataSeries::testMerge_data()
120 196 {
121 197 // ////////////// //
122 198 // Test structure //
123 199 // ////////////// //
124 200
125 201 // Data series to merge
126 202 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
127 203 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries2");
128 204
129 205 // Expected values in the first data series after merge
130 206 QTest::addColumn<QVector<double> >("expectedXAxisData");
131 207 QTest::addColumn<QVector<double> >("expectedValuesData");
132 208
133 209 // ////////// //
134 210 // Test cases //
135 211 // ////////// //
136 212
137 213 QTest::newRow("sortedMerge")
138 << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
139 << createSeries({6., 7., 8., 9., 10.}, {600., 700., 800., 900., 1000.})
214 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
215 << createScalarSeries({6., 7., 8., 9., 10.}, {600., 700., 800., 900., 1000.})
140 216 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
141 217 << QVector<double>{100., 200., 300., 400., 500., 600., 700., 800., 900., 1000.};
142 218
143 219 QTest::newRow("unsortedMerge")
144 << createSeries({6., 7., 8., 9., 10.}, {600., 700., 800., 900., 1000.})
145 << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
220 << createScalarSeries({6., 7., 8., 9., 10.}, {600., 700., 800., 900., 1000.})
221 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
146 222 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
147 223 << QVector<double>{100., 200., 300., 400., 500., 600., 700., 800., 900., 1000.};
148 224
149 225 QTest::newRow("unsortedMerge2")
150 << createSeries({1., 2., 8., 9., 10}, {100., 200., 300., 400., 500.})
151 << createSeries({3., 4., 5., 6., 7.}, {600., 700., 800., 900., 1000.})
226 << createScalarSeries({1., 2., 8., 9., 10}, {100., 200., 300., 400., 500.})
227 << createScalarSeries({3., 4., 5., 6., 7.}, {600., 700., 800., 900., 1000.})
152 228 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
153 229 << QVector<double>{100., 200., 600., 700., 800., 900., 1000., 300., 400., 500.};
154 230
155 231 QTest::newRow("unsortedMerge3")
156 << createSeries({3., 5., 8., 7., 2}, {100., 200., 300., 400., 500.})
157 << createSeries({6., 4., 9., 10., 1.}, {600., 700., 800., 900., 1000.})
232 << createScalarSeries({3., 5., 8., 7., 2}, {100., 200., 300., 400., 500.})
233 << createScalarSeries({6., 4., 9., 10., 1.}, {600., 700., 800., 900., 1000.})
158 234 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
159 235 << QVector<double>{1000., 500., 100., 700., 200., 600., 400., 300., 800., 900.};
160 236 }
161 237
162 238 void TestDataSeries::testMerge()
163 239 {
164 240 // Merges series
165 241 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
166 242 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries2);
167 243
168 244 dataSeries->merge(dataSeries2.get());
169 245
170 246 // Validates results : we check that the merge is valid and the data series is sorted on its
171 247 // x-axis data
172 248 QFETCH(QVector<double>, expectedXAxisData);
173 249 QFETCH(QVector<double>, expectedValuesData);
174 250
175 251 auto seriesXAxisData = dataSeries->xAxisData()->data();
176 252 auto seriesValuesData = dataSeries->valuesData()->data();
177 253
178 254 QVERIFY(
179 255 std::equal(expectedXAxisData.cbegin(), expectedXAxisData.cend(), seriesXAxisData.cbegin()));
180 256 QVERIFY(std::equal(expectedValuesData.cbegin(), expectedValuesData.cend(),
181 257 seriesValuesData.cbegin()));
182 258 }
183 259
184 void TestDataSeries::testMinData_data()
260 void TestDataSeries::testMinXAxisData_data()
185 261 {
186 262 // ////////////// //
187 263 // Test structure //
188 264 // ////////////// //
189 265
190 266 // Data series to get min data
191 267 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
192 268
193 269 // Min data
194 270 QTest::addColumn<double>("min");
195 271
196 272 // Expected results
197 273 QTest::addColumn<bool>(
198 274 "expectedOK"); // if true, expects to have a result (i.e. the iterator != end iterator)
199 275 QTest::addColumn<double>(
200 276 "expectedMin"); // Expected value when method doesn't return end iterator
201 277
202 278 // ////////// //
203 279 // Test cases //
204 280 // ////////// //
205 281
206 QTest::newRow("minData1") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
282 QTest::newRow("minData1") << createScalarSeries({1., 2., 3., 4., 5.},
283 {100., 200., 300., 400., 500.})
207 284 << 0. << true << 1.;
208 QTest::newRow("minData2") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
285 QTest::newRow("minData2") << createScalarSeries({1., 2., 3., 4., 5.},
286 {100., 200., 300., 400., 500.})
209 287 << 1. << true << 1.;
210 QTest::newRow("minData3") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
288 QTest::newRow("minData3") << createScalarSeries({1., 2., 3., 4., 5.},
289 {100., 200., 300., 400., 500.})
211 290 << 1.1 << true << 2.;
212 QTest::newRow("minData4") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
291 QTest::newRow("minData4") << createScalarSeries({1., 2., 3., 4., 5.},
292 {100., 200., 300., 400., 500.})
213 293 << 5. << true << 5.;
214 QTest::newRow("minData5") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
294 QTest::newRow("minData5") << createScalarSeries({1., 2., 3., 4., 5.},
295 {100., 200., 300., 400., 500.})
215 296 << 5.1 << false << std::numeric_limits<double>::quiet_NaN();
216 QTest::newRow("minData6") << createSeries({}, {}) << 1.1 << false
297 QTest::newRow("minData6") << createScalarSeries({}, {}) << 1.1 << false
217 298 << std::numeric_limits<double>::quiet_NaN();
218 299 }
219 300
220 void TestDataSeries::testMinData()
301 void TestDataSeries::testMinXAxisData()
221 302 {
222 303 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
223 304 QFETCH(double, min);
224 305
225 306 QFETCH(bool, expectedOK);
226 307 QFETCH(double, expectedMin);
227 308
228 auto it = dataSeries->minData(min);
309 auto it = dataSeries->minXAxisData(min);
229 310
230 311 QCOMPARE(expectedOK, it != dataSeries->cend());
231 312
232 313 // If the method doesn't return a end iterator, checks with expected value
233 314 if (expectedOK) {
234 315 QCOMPARE(expectedMin, it->x());
235 316 }
236 317 }
237 318
238 void TestDataSeries::testMaxData_data()
319 void TestDataSeries::testMaxXAxisData_data()
239 320 {
240 321 // ////////////// //
241 322 // Test structure //
242 323 // ////////////// //
243 324
244 325 // Data series to get max data
245 326 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
246 327
247 328 // Max data
248 329 QTest::addColumn<double>("max");
249 330
250 331 // Expected results
251 332 QTest::addColumn<bool>(
252 333 "expectedOK"); // if true, expects to have a result (i.e. the iterator != end iterator)
253 334 QTest::addColumn<double>(
254 335 "expectedMax"); // Expected value when method doesn't return end iterator
255 336
256 337 // ////////// //
257 338 // Test cases //
258 339 // ////////// //
259 340
260 QTest::newRow("maxData1") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
341 QTest::newRow("maxData1") << createScalarSeries({1., 2., 3., 4., 5.},
342 {100., 200., 300., 400., 500.})
261 343 << 6. << true << 5.;
262 QTest::newRow("maxData2") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
344 QTest::newRow("maxData2") << createScalarSeries({1., 2., 3., 4., 5.},
345 {100., 200., 300., 400., 500.})
263 346 << 5. << true << 5.;
264 QTest::newRow("maxData3") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
347 QTest::newRow("maxData3") << createScalarSeries({1., 2., 3., 4., 5.},
348 {100., 200., 300., 400., 500.})
265 349 << 4.9 << true << 4.;
266 QTest::newRow("maxData4") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
350 QTest::newRow("maxData4") << createScalarSeries({1., 2., 3., 4., 5.},
351 {100., 200., 300., 400., 500.})
267 352 << 1.1 << true << 1.;
268 QTest::newRow("maxData5") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
353 QTest::newRow("maxData5") << createScalarSeries({1., 2., 3., 4., 5.},
354 {100., 200., 300., 400., 500.})
269 355 << 1. << true << 1.;
270 QTest::newRow("maxData6") << createSeries({}, {}) << 1.1 << false
356 QTest::newRow("maxData6") << createScalarSeries({}, {}) << 1.1 << false
271 357 << std::numeric_limits<double>::quiet_NaN();
272 358 }
273 359
274 void TestDataSeries::testMaxData()
360 void TestDataSeries::testMaxXAxisData()
275 361 {
276 362 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
277 363 QFETCH(double, max);
278 364
279 365 QFETCH(bool, expectedOK);
280 366 QFETCH(double, expectedMax);
281 367
282 auto it = dataSeries->maxData(max);
368 auto it = dataSeries->maxXAxisData(max);
283 369
284 370 QCOMPARE(expectedOK, it != dataSeries->cend());
285 371
286 372 // If the method doesn't return a end iterator, checks with expected value
287 373 if (expectedOK) {
288 374 QCOMPARE(expectedMax, it->x());
289 375 }
290 376 }
291 377
292 void TestDataSeries::testSubdata_data()
378 void TestDataSeries::testXAxisRange_data()
293 379 {
294 380 // ////////////// //
295 381 // Test structure //
296 382 // ////////////// //
297 383
298 // Data series to get subdata
384 // Data series to get x-axis range
299 385 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
300 386
301 387 // Min/max values
302 388 QTest::addColumn<double>("min");
303 389 QTest::addColumn<double>("max");
304 390
305 // Expected values after subdata
391 // Expected values
306 392 QTest::addColumn<QVector<double> >("expectedXAxisData");
307 393 QTest::addColumn<QVector<double> >("expectedValuesData");
308 394
309 395 // ////////// //
310 396 // Test cases //
311 397 // ////////// //
312 398
313 QTest::newRow("subData1") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
314 << -1. << 3.2 << QVector<double>{1., 2., 3.}
315 << QVector<double>{100., 200., 300.};
316 QTest::newRow("subData2") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
317 << 1. << 4. << QVector<double>{1., 2., 3., 4.}
318 << QVector<double>{100., 200., 300., 400.};
319 QTest::newRow("subData3") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
320 << 1. << 3.9 << QVector<double>{1., 2., 3.}
321 << QVector<double>{100., 200., 300.};
322 QTest::newRow("subData4") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
323 << 0. << 0.9 << QVector<double>{} << QVector<double>{};
324 QTest::newRow("subData5") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
325 << 0. << 1. << QVector<double>{1.} << QVector<double>{100.};
326 QTest::newRow("subData6") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
327 << 2.1 << 6. << QVector<double>{3., 4., 5.}
328 << QVector<double>{300., 400., 500.};
329 QTest::newRow("subData7") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
330 << 6. << 9. << QVector<double>{} << QVector<double>{};
331 QTest::newRow("subData8") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
332 << 5. << 9. << QVector<double>{5.} << QVector<double>{500.};
399 QTest::newRow("xAxisRange1") << createScalarSeries({1., 2., 3., 4., 5.},
400 {100., 200., 300., 400., 500.})
401 << -1. << 3.2 << QVector<double>{1., 2., 3.}
402 << QVector<double>{100., 200., 300.};
403 QTest::newRow("xAxisRange2") << createScalarSeries({1., 2., 3., 4., 5.},
404 {100., 200., 300., 400., 500.})
405 << 1. << 4. << QVector<double>{1., 2., 3., 4.}
406 << QVector<double>{100., 200., 300., 400.};
407 QTest::newRow("xAxisRange3") << createScalarSeries({1., 2., 3., 4., 5.},
408 {100., 200., 300., 400., 500.})
409 << 1. << 3.9 << QVector<double>{1., 2., 3.}
410 << QVector<double>{100., 200., 300.};
411 QTest::newRow("xAxisRange4") << createScalarSeries({1., 2., 3., 4., 5.},
412 {100., 200., 300., 400., 500.})
413 << 0. << 0.9 << QVector<double>{} << QVector<double>{};
414 QTest::newRow("xAxisRange5") << createScalarSeries({1., 2., 3., 4., 5.},
415 {100., 200., 300., 400., 500.})
416 << 0. << 1. << QVector<double>{1.} << QVector<double>{100.};
417 QTest::newRow("xAxisRange6") << createScalarSeries({1., 2., 3., 4., 5.},
418 {100., 200., 300., 400., 500.})
419 << 2.1 << 6. << QVector<double>{3., 4., 5.}
420 << QVector<double>{300., 400., 500.};
421 QTest::newRow("xAxisRange7") << createScalarSeries({1., 2., 3., 4., 5.},
422 {100., 200., 300., 400., 500.})
423 << 6. << 9. << QVector<double>{} << QVector<double>{};
424 QTest::newRow("xAxisRange8") << createScalarSeries({1., 2., 3., 4., 5.},
425 {100., 200., 300., 400., 500.})
426 << 5. << 9. << QVector<double>{5.} << QVector<double>{500.};
333 427 }
334 428
335 void TestDataSeries::testSubdata()
429 void TestDataSeries::testXAxisRange()
336 430 {
337 431 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
338 432 QFETCH(double, min);
339 433 QFETCH(double, max);
340 434
341 435 QFETCH(QVector<double>, expectedXAxisData);
342 436 QFETCH(QVector<double>, expectedValuesData);
343 437
344 auto bounds = dataSeries->subData(min, max);
438 auto bounds = dataSeries->xAxisRange(min, max);
345 439 QVERIFY(std::equal(bounds.first, bounds.second, expectedXAxisData.cbegin(),
346 440 expectedXAxisData.cend(),
347 441 [](const auto &it, const auto &expectedX) { return it.x() == expectedX; }));
348 442 QVERIFY(std::equal(
349 443 bounds.first, bounds.second, expectedValuesData.cbegin(), expectedValuesData.cend(),
350 444 [](const auto &it, const auto &expectedVal) { return it.value() == expectedVal; }));
351 445 }
352 446
447 void TestDataSeries::testValuesBoundsScalar_data()
448 {
449 testValuesBoundsStructure<ScalarSeries>();
450
451 // ////////// //
452 // Test cases //
453 // ////////// //
454 auto nan = std::numeric_limits<double>::quiet_NaN();
455
456 QTest::newRow("scalarBounds1")
457 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 0. << 6.
458 << true << 100. << 500.;
459 QTest::newRow("scalarBounds2")
460 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 2. << 4.
461 << true << 200. << 400.;
462 QTest::newRow("scalarBounds3")
463 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 0. << 0.5
464 << false << nan << nan;
465 QTest::newRow("scalarBounds4")
466 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 5.1 << 6.
467 << false << nan << nan;
468 QTest::newRow("scalarBounds5")
469 << createScalarSeries({1.}, {100.}) << 0. << 2. << true << 100. << 100.;
470 QTest::newRow("scalarBounds6") << createScalarSeries({}, {}) << 0. << 2. << false << nan << nan;
471
472 // Tests with NaN values: NaN values are not included in min/max search
473 QTest::newRow("scalarBounds7")
474 << createScalarSeries({1., 2., 3., 4., 5.}, {nan, 200., 300., 400., nan}) << 0. << 6.
475 << true << 200. << 400.;
476 QTest::newRow("scalarBounds8")
477 << createScalarSeries({1., 2., 3., 4., 5.}, {nan, nan, nan, nan, nan}) << 0. << 6. << true
478 << std::numeric_limits<double>::quiet_NaN() << std::numeric_limits<double>::quiet_NaN();
479 }
480
481 void TestDataSeries::testValuesBoundsScalar()
482 {
483 testValuesBounds<ScalarSeries>();
484 }
485
486 void TestDataSeries::testValuesBoundsVector_data()
487 {
488 testValuesBoundsStructure<VectorSeries>();
489
490 // ////////// //
491 // Test cases //
492 // ////////// //
493 auto nan = std::numeric_limits<double>::quiet_NaN();
494
495 QTest::newRow("vectorBounds1")
496 << createVectorSeries({1., 2., 3., 4., 5.}, {10., 15., 20., 13., 12.},
497 {35., 24., 10., 9., 0.3}, {13., 14., 12., 9., 24.})
498 << 0. << 6. << true << 0.3 << 35.; // min/max in same component
499 QTest::newRow("vectorBounds2")
500 << createVectorSeries({1., 2., 3., 4., 5.}, {2.3, 15., 20., 13., 12.},
501 {35., 24., 10., 9., 4.}, {13., 14., 12., 9., 24.})
502 << 0. << 6. << true << 2.3 << 35.; // min/max in same entry
503 QTest::newRow("vectorBounds3")
504 << createVectorSeries({1., 2., 3., 4., 5.}, {2.3, 15., 20., 13., 12.},
505 {35., 24., 10., 9., 4.}, {13., 14., 12., 9., 24.})
506 << 2. << 3. << true << 10. << 24.;
507
508 // Tests with NaN values: NaN values are not included in min/max search
509 QTest::newRow("vectorBounds4")
510 << createVectorSeries({1., 2.}, {nan, nan}, {nan, nan}, {nan, nan}) << 0. << 6. << true
511 << nan << nan;
512 }
513
514 void TestDataSeries::testValuesBoundsVector()
515 {
516 testValuesBounds<VectorSeries>();
517 }
518
353 519 QTEST_MAIN(TestDataSeries)
354 520 #include "TestDataSeries.moc"
@@ -1,243 +1,243
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 7 #include <Data/VectorSeries.h>
8 8
9 9 #include <Variable/Variable.h>
10 10
11 11 Q_LOGGING_CATEGORY(LOG_VisualizationGraphHelper, "VisualizationGraphHelper")
12 12
13 13 namespace {
14 14
15 15 class SqpDataContainer : public QCPGraphDataContainer {
16 16 public:
17 17 void appendGraphData(const QCPGraphData &data) { mData.append(data); }
18 18 };
19 19
20 20
21 21 /// Format for datetimes on a axis
22 22 const auto DATETIME_TICKER_FORMAT = QStringLiteral("yyyy/MM/dd \nhh:mm:ss");
23 23
24 24 /// Generates the appropriate ticker for an axis, depending on whether the axis displays time or
25 25 /// non-time data
26 26 QSharedPointer<QCPAxisTicker> axisTicker(bool isTimeAxis)
27 27 {
28 28 if (isTimeAxis) {
29 29 auto dateTicker = QSharedPointer<QCPAxisTickerDateTime>::create();
30 30 dateTicker->setDateTimeFormat(DATETIME_TICKER_FORMAT);
31 31 dateTicker->setDateTimeSpec(Qt::UTC);
32 32
33 33 return dateTicker;
34 34 }
35 35 else {
36 36 // default ticker
37 37 return QSharedPointer<QCPAxisTicker>::create();
38 38 }
39 39 }
40 40
41 41 /// Sets axes properties according to the properties of a data series
42 42 template <int Dim>
43 43 void setAxesProperties(const DataSeries<Dim> &dataSeries, QCustomPlot &plot) noexcept
44 44 {
45 45 /// @todo : for the moment, no control is performed on the axes: the units and the tickers
46 46 /// are fixed for the default x-axis and y-axis of the plot, and according to the new graph
47 47 auto setAxisProperties = [](auto axis, const auto &unit) {
48 48 // label (unit name)
49 49 axis->setLabel(unit.m_Name);
50 50
51 51 // ticker (depending on the type of unit)
52 52 axis->setTicker(axisTicker(unit.m_TimeUnit));
53 53 };
54 54 setAxisProperties(plot.xAxis, dataSeries.xAxisUnit());
55 55 setAxisProperties(plot.yAxis, dataSeries.valuesUnit());
56 56 }
57 57
58 58 /**
59 59 * Struct used to create plottables, depending on the type of the data series from which to create
60 60 * them
61 61 * @tparam T the data series' type
62 62 * @remarks Default implementation can't create plottables
63 63 */
64 64 template <typename T, typename Enabled = void>
65 65 struct PlottablesCreator {
66 66 static PlottablesMap createPlottables(T &, QCustomPlot &)
67 67 {
68 68 qCCritical(LOG_DataSeries())
69 69 << QObject::tr("Can't create plottables: unmanaged data series type");
70 70 return {};
71 71 }
72 72 };
73 73
74 74 /**
75 75 * Specialization of PlottablesCreator for scalars and vectors
76 76 * @sa ScalarSeries
77 77 * @sa VectorSeries
78 78 */
79 79 template <typename T>
80 80 struct PlottablesCreator<T,
81 81 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
82 82 or std::is_base_of<VectorSeries, T>::value> > {
83 83 static PlottablesMap createPlottables(T &dataSeries, QCustomPlot &plot)
84 84 {
85 85 PlottablesMap result{};
86 86
87 87 // Gets the number of components of the data series
88 88 auto componentCount = dataSeries.valuesData()->componentCount();
89 89
90 90 auto colors = ColorUtils::colors(Qt::blue, Qt::red, componentCount);
91 91
92 92 // For each component of the data series, creates a QCPGraph to add to the plot
93 93 for (auto i = 0; i < componentCount; ++i) {
94 94 auto graph = plot.addGraph();
95 95 graph->setPen(QPen{colors.at(i)});
96 96
97 97 result.insert({i, graph});
98 98 }
99 99
100 100 // Axes properties
101 101 setAxesProperties(dataSeries, plot);
102 102
103 103 plot.replot();
104 104
105 105 return result;
106 106 }
107 107 };
108 108
109 109 /**
110 110 * Struct used to update plottables, depending on the type of the data series from which to update
111 111 * them
112 112 * @tparam T the data series' type
113 113 * @remarks Default implementation can't update plottables
114 114 */
115 115 template <typename T, typename Enabled = void>
116 116 struct PlottablesUpdater {
117 117 static void updatePlottables(T &, PlottablesMap &, const SqpRange &, bool)
118 118 {
119 119 qCCritical(LOG_DataSeries())
120 120 << QObject::tr("Can't update plottables: unmanaged data series type");
121 121 }
122 122 };
123 123
124 124 /**
125 125 * Specialization of PlottablesUpdater for scalars and vectors
126 126 * @sa ScalarSeries
127 127 * @sa VectorSeries
128 128 */
129 129 template <typename T>
130 130 struct PlottablesUpdater<T,
131 131 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
132 132 or std::is_base_of<VectorSeries, T>::value> > {
133 133 static void updatePlottables(T &dataSeries, PlottablesMap &plottables, const SqpRange &range,
134 134 bool rescaleAxes)
135 135 {
136 136 dataSeries.lockRead();
137 137
138 138 // For each plottable to update, resets its data
139 139 std::map<int, QSharedPointer<SqpDataContainer> > dataContainers{};
140 140 for (const auto &plottable : plottables) {
141 141 if (auto graph = dynamic_cast<QCPGraph *>(plottable.second)) {
142 142 auto dataContainer = QSharedPointer<SqpDataContainer>::create();
143 143 graph->setData(dataContainer);
144 144
145 145 dataContainers.insert({plottable.first, dataContainer});
146 146 }
147 147 }
148 148
149 149 // - Gets the data of the series included in the current range
150 150 // - Updates each plottable by adding, for each data item, a point that takes x-axis data
151 151 // and value data. The correct value is retrieved according to the index of the component
152 auto subDataIts = dataSeries.subData(range.m_TStart, range.m_TEnd);
152 auto subDataIts = dataSeries.xAxisRange(range.m_TStart, range.m_TEnd);
153 153 for (auto it = subDataIts.first; it != subDataIts.second; ++it) {
154 154 for (const auto &dataContainer : dataContainers) {
155 155 auto componentIndex = dataContainer.first;
156 156 dataContainer.second->appendGraphData(
157 157 QCPGraphData(it->x(), it->value(componentIndex)));
158 158 }
159 159 }
160 160
161 161 dataSeries.unlock();
162 162
163 163 if (!plottables.empty()) {
164 164 auto plot = plottables.begin()->second->parentPlot();
165 165
166 166 if (rescaleAxes) {
167 167 plot->rescaleAxes();
168 168 }
169 169
170 170 plot->replot();
171 171 }
172 172 }
173 173 };
174 174
175 175 /**
176 176 * Helper used to create/update plottables
177 177 */
178 178 struct IPlottablesHelper {
179 179 virtual ~IPlottablesHelper() noexcept = default;
180 180 virtual PlottablesMap create(QCustomPlot &plot) const = 0;
181 181 virtual void update(PlottablesMap &plottables, const SqpRange &range,
182 182 bool rescaleAxes = false) const = 0;
183 183 };
184 184
185 185 /**
186 186 * Default implementation of IPlottablesHelper, which takes data series to create/update plottables
187 187 * @tparam T the data series' type
188 188 */
189 189 template <typename T>
190 190 struct PlottablesHelper : public IPlottablesHelper {
191 191 explicit PlottablesHelper(T &dataSeries) : m_DataSeries{dataSeries} {}
192 192
193 193 PlottablesMap create(QCustomPlot &plot) const override
194 194 {
195 195 return PlottablesCreator<T>::createPlottables(m_DataSeries, plot);
196 196 }
197 197
198 198 void update(PlottablesMap &plottables, const SqpRange &range, bool rescaleAxes) const override
199 199 {
200 200 PlottablesUpdater<T>::updatePlottables(m_DataSeries, plottables, range, rescaleAxes);
201 201 }
202 202
203 203 T &m_DataSeries;
204 204 };
205 205
206 206 /// Creates IPlottablesHelper according to a data series
207 207 std::unique_ptr<IPlottablesHelper> createHelper(std::shared_ptr<IDataSeries> dataSeries) noexcept
208 208 {
209 209 if (auto scalarSeries = std::dynamic_pointer_cast<ScalarSeries>(dataSeries)) {
210 210 return std::make_unique<PlottablesHelper<ScalarSeries> >(*scalarSeries);
211 211 }
212 212 else if (auto vectorSeries = std::dynamic_pointer_cast<VectorSeries>(dataSeries)) {
213 213 return std::make_unique<PlottablesHelper<VectorSeries> >(*vectorSeries);
214 214 }
215 215 else {
216 216 return std::make_unique<PlottablesHelper<IDataSeries> >(*dataSeries);
217 217 }
218 218 }
219 219
220 220 } // namespace
221 221
222 222 PlottablesMap VisualizationGraphHelper::create(std::shared_ptr<Variable> variable,
223 223 QCustomPlot &plot) noexcept
224 224 {
225 225 if (variable) {
226 226 auto helper = createHelper(variable->dataSeries());
227 227 auto plottables = helper->create(plot);
228 228 return plottables;
229 229 }
230 230 else {
231 231 qCDebug(LOG_VisualizationGraphHelper())
232 232 << QObject::tr("Can't create graph plottables : the variable is null");
233 233 return PlottablesMap{};
234 234 }
235 235 }
236 236
237 237 void VisualizationGraphHelper::updateData(PlottablesMap &plottables,
238 238 std::shared_ptr<IDataSeries> dataSeries,
239 239 const SqpRange &dateTime)
240 240 {
241 241 auto helper = createHelper(dataSeries);
242 242 helper->update(plottables, dateTime);
243 243 }
@@ -1,244 +1,256
1 1 #include "Visualization/VisualizationZoneWidget.h"
2 2
3 3
4 4 #include "Visualization/IVisualizationWidgetVisitor.h"
5 5 #include "Visualization/VisualizationGraphWidget.h"
6 6 #include "ui_VisualizationZoneWidget.h"
7 7
8 8 #include <Data/SqpRange.h>
9 9 #include <Variable/Variable.h>
10 10 #include <Variable/VariableController.h>
11 11
12 12 #include <QUuid>
13 13 #include <SqpApplication.h>
14 14
15 15 Q_LOGGING_CATEGORY(LOG_VisualizationZoneWidget, "VisualizationZoneWidget")
16 16
17 17 namespace {
18 18
19 19 /// Minimum height for graph added in zones (in pixels)
20 20 const auto GRAPH_MINIMUM_HEIGHT = 300;
21 21
22 22 /// Generates a default name for a new graph, according to the number of graphs already displayed in
23 23 /// the zone
24 24 QString defaultGraphName(const QLayout &layout)
25 25 {
26 26 auto count = 0;
27 27 for (auto i = 0; i < layout.count(); ++i) {
28 28 if (dynamic_cast<VisualizationGraphWidget *>(layout.itemAt(i)->widget())) {
29 29 count++;
30 30 }
31 31 }
32 32
33 33 return QObject::tr("Graph %1").arg(count + 1);
34 34 }
35 35
36 36 } // namespace
37 37
38 38 struct VisualizationZoneWidget::VisualizationZoneWidgetPrivate {
39 39
40 40 explicit VisualizationZoneWidgetPrivate() : m_SynchronisationGroupId{QUuid::createUuid()} {}
41 41 QUuid m_SynchronisationGroupId;
42 42 };
43 43
44 44 VisualizationZoneWidget::VisualizationZoneWidget(const QString &name, QWidget *parent)
45 45 : QWidget{parent},
46 46 ui{new Ui::VisualizationZoneWidget},
47 47 impl{spimpl::make_unique_impl<VisualizationZoneWidgetPrivate>()}
48 48 {
49 49 ui->setupUi(this);
50 50
51 51 ui->zoneNameLabel->setText(name);
52 52
53 53 // 'Close' options : widget is deleted when closed
54 54 setAttribute(Qt::WA_DeleteOnClose);
55 55 connect(ui->closeButton, &QToolButton::clicked, this, &VisualizationZoneWidget::close);
56 56 ui->closeButton->setIcon(sqpApp->style()->standardIcon(QStyle::SP_TitleBarCloseButton));
57 57
58 58 // Synchronisation id
59 59 QMetaObject::invokeMethod(&sqpApp->variableController(), "onAddSynchronizationGroupId",
60 60 Qt::QueuedConnection, Q_ARG(QUuid, impl->m_SynchronisationGroupId));
61 61 }
62 62
63 63 VisualizationZoneWidget::~VisualizationZoneWidget()
64 64 {
65 65 delete ui;
66 66 }
67 67
68 68 void VisualizationZoneWidget::addGraph(VisualizationGraphWidget *graphWidget)
69 69 {
70 70 ui->visualizationZoneFrame->layout()->addWidget(graphWidget);
71 71 }
72 72
73 73 VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptr<Variable> variable)
74 74 {
75 75 auto graphWidget = new VisualizationGraphWidget{
76 76 defaultGraphName(*ui->visualizationZoneFrame->layout()), this};
77 77
78 78
79 79 // Set graph properties
80 80 graphWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
81 81 graphWidget->setMinimumHeight(GRAPH_MINIMUM_HEIGHT);
82 82
83 83
84 84 // Lambda to synchronize zone widget
85 85 auto synchronizeZoneWidget = [this, graphWidget](const SqpRange &graphRange,
86 86 const SqpRange &oldGraphRange) {
87 87
88 88 auto zoomType = VariableController::getZoomType(graphRange, oldGraphRange);
89 89 auto frameLayout = ui->visualizationZoneFrame->layout();
90 90 for (auto i = 0; i < frameLayout->count(); ++i) {
91 91 auto graphChild
92 92 = dynamic_cast<VisualizationGraphWidget *>(frameLayout->itemAt(i)->widget());
93 93 if (graphChild && (graphChild != graphWidget)) {
94 94
95 95 auto graphChildRange = graphChild->graphRange();
96 96 switch (zoomType) {
97 97 case AcquisitionZoomType::ZoomIn: {
98 98 auto deltaLeft = graphRange.m_TStart - oldGraphRange.m_TStart;
99 99 auto deltaRight = oldGraphRange.m_TEnd - graphRange.m_TEnd;
100 100 graphChildRange.m_TStart += deltaLeft;
101 101 graphChildRange.m_TEnd -= deltaRight;
102 102 qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: ZoomIn");
103 103 qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: deltaLeft")
104 104 << deltaLeft;
105 105 qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: deltaRight")
106 106 << deltaRight;
107 107 qCCritical(LOG_VisualizationZoneWidget())
108 108 << tr("TORM: dt") << graphRange.m_TEnd - graphRange.m_TStart;
109 109
110 110 break;
111 111 }
112 112
113 113 case AcquisitionZoomType::ZoomOut: {
114 114 qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: ZoomOut");
115 115 auto deltaLeft = oldGraphRange.m_TStart - graphRange.m_TStart;
116 116 auto deltaRight = graphRange.m_TEnd - oldGraphRange.m_TEnd;
117 117 qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: deltaLeft")
118 118 << deltaLeft;
119 119 qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: deltaRight")
120 120 << deltaRight;
121 121 qCCritical(LOG_VisualizationZoneWidget())
122 122 << tr("TORM: dt") << graphRange.m_TEnd - graphRange.m_TStart;
123 123 graphChildRange.m_TStart -= deltaLeft;
124 124 graphChildRange.m_TEnd += deltaRight;
125 125 break;
126 126 }
127 127 case AcquisitionZoomType::PanRight: {
128 128 qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: PanRight");
129 129 auto deltaRight = graphRange.m_TEnd - oldGraphRange.m_TEnd;
130 130 graphChildRange.m_TStart += deltaRight;
131 131 graphChildRange.m_TEnd += deltaRight;
132 132 qCCritical(LOG_VisualizationZoneWidget())
133 133 << tr("TORM: dt") << graphRange.m_TEnd - graphRange.m_TStart;
134 134 break;
135 135 }
136 136 case AcquisitionZoomType::PanLeft: {
137 137 qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: PanLeft");
138 138 auto deltaLeft = oldGraphRange.m_TStart - graphRange.m_TStart;
139 139 graphChildRange.m_TStart -= deltaLeft;
140 140 graphChildRange.m_TEnd -= deltaLeft;
141 141 break;
142 142 }
143 143 case AcquisitionZoomType::Unknown: {
144 144 qCCritical(LOG_VisualizationZoneWidget())
145 145 << tr("Impossible to synchronize: zoom type unknown");
146 146 break;
147 147 }
148 148 default:
149 149 qCCritical(LOG_VisualizationZoneWidget())
150 150 << tr("Impossible to synchronize: zoom type not take into account");
151 151 // No action
152 152 break;
153 153 }
154 154 graphChild->enableAcquisition(false);
155 155 qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: Range before: ")
156 156 << graphChild->graphRange();
157 157 qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: Range after : ")
158 158 << graphChildRange;
159 159 qCCritical(LOG_VisualizationZoneWidget())
160 160 << tr("TORM: child dt") << graphChildRange.m_TEnd - graphChildRange.m_TStart;
161 161 graphChild->setGraphRange(graphChildRange);
162 162 graphChild->enableAcquisition(true);
163 163 }
164 164 }
165 165 };
166 166
167 167 // connection for synchronization
168 168 connect(graphWidget, &VisualizationGraphWidget::synchronize, synchronizeZoneWidget);
169 169 connect(graphWidget, &VisualizationGraphWidget::variableAdded, this,
170 170 &VisualizationZoneWidget::onVariableAdded);
171 171
172 172 auto range = SqpRange{};
173 173
174 174 // Apply visitor to graph children
175 175 auto layout = ui->visualizationZoneFrame->layout();
176 176 if (layout->count() > 0) {
177 177 // Case of a new graph in a existant zone
178 178 if (auto visualizationGraphWidget
179 179 = dynamic_cast<VisualizationGraphWidget *>(layout->itemAt(0)->widget())) {
180 180 range = visualizationGraphWidget->graphRange();
181 181 }
182 182 }
183 183 else {
184 184 // Case of a new graph as the first of the zone
185 185 range = variable->range();
186 186 }
187 187
188 188 this->addGraph(graphWidget);
189 189
190 190 graphWidget->addVariable(variable, range);
191 // TODO: get y using variable range
192 graphWidget->setYRange(SqpRange{-10, 10});
191
192 // get y using variable range
193 if (auto dataSeries = variable->dataSeries()) {
194 auto valuesBounds = dataSeries->valuesBounds(range.m_TStart, range.m_TEnd);
195 auto end = dataSeries->cend();
196 if (valuesBounds.first != end && valuesBounds.second != end) {
197 auto rangeValue = [](const auto &value) { return std::isnan(value) ? 0. : value; };
198
199 auto minValue = rangeValue(valuesBounds.first->minValue());
200 auto maxValue = rangeValue(valuesBounds.second->maxValue());
201
202 graphWidget->setYRange(SqpRange{minValue, maxValue});
203 }
204 }
193 205
194 206 return graphWidget;
195 207 }
196 208
197 209 void VisualizationZoneWidget::accept(IVisualizationWidgetVisitor *visitor)
198 210 {
199 211 if (visitor) {
200 212 visitor->visitEnter(this);
201 213
202 214 // Apply visitor to graph children
203 215 auto layout = ui->visualizationZoneFrame->layout();
204 216 for (auto i = 0; i < layout->count(); ++i) {
205 217 if (auto item = layout->itemAt(i)) {
206 218 // Widgets different from graphs are not visited (no action)
207 219 if (auto visualizationGraphWidget
208 220 = dynamic_cast<VisualizationGraphWidget *>(item->widget())) {
209 221 visualizationGraphWidget->accept(visitor);
210 222 }
211 223 }
212 224 }
213 225
214 226 visitor->visitLeave(this);
215 227 }
216 228 else {
217 229 qCCritical(LOG_VisualizationZoneWidget()) << tr("Can't visit widget : the visitor is null");
218 230 }
219 231 }
220 232
221 233 bool VisualizationZoneWidget::canDrop(const Variable &variable) const
222 234 {
223 235 // A tab can always accomodate a variable
224 236 Q_UNUSED(variable);
225 237 return true;
226 238 }
227 239
228 240 bool VisualizationZoneWidget::contains(const Variable &variable) const
229 241 {
230 242 Q_UNUSED(variable);
231 243 return false;
232 244 }
233 245
234 246 QString VisualizationZoneWidget::name() const
235 247 {
236 248 return ui->zoneNameLabel->text();
237 249 }
238 250
239 251 void VisualizationZoneWidget::onVariableAdded(std::shared_ptr<Variable> variable)
240 252 {
241 253 QMetaObject::invokeMethod(&sqpApp->variableController(), "onAddSynchronized",
242 254 Qt::QueuedConnection, Q_ARG(std::shared_ptr<Variable>, variable),
243 255 Q_ARG(QUuid, impl->m_SynchronisationGroupId));
244 256 }
@@ -1,197 +1,197
1 1 #include "AmdaProvider.h"
2 2 #include "AmdaResultParser.h"
3 3
4 4 #include "SqpApplication.h"
5 5 #include <Data/DataSeries.h>
6 6 #include <Data/IDataSeries.h>
7 7 #include <Data/ScalarSeries.h>
8 8 #include <Time/TimeController.h>
9 9 #include <Variable/Variable.h>
10 10 #include <Variable/VariableController.h>
11 11
12 12 #include <QObject>
13 13 #include <QtTest>
14 14
15 15 #include <memory>
16 16
17 17 // TEST with REF:
18 18 // AmdaData-2012-01-01-12-00-00_2012-01-03-12-00-00
19 19 // imf(0) - Type : Local Parameter @ CDPP/AMDA -
20 20 // Name : bx_gse - Units : nT - Size : 1 -
21 21 // Frame : GSE - Mission : ACE -
22 22 // Instrument : MFI - Dataset : mfi_final-prelim
23 23 // REFERENCE DOWNLOAD FILE =
24 24 // http://amda.irap.omp.eu/php/rest/getParameter.php?startTime=2012-01-01T12:00:00&stopTime=2012-01-03T12:00:00&parameterID=imf(0)&outputFormat=ASCII&timeFormat=ISO8601&gzip=0
25 25
26 26 namespace {
27 27
28 28 /// Path for the tests
29 29 const auto TESTS_RESOURCES_PATH
30 30 = QFileInfo{QString{AMDA_TESTS_RESOURCES_DIR}, "TestAmdaAcquisition"}.absoluteFilePath();
31 31
32 32 const auto TESTS_AMDA_REF_FILE = QString{"AmdaData-2012-01-01-12-00-00_2012-01-03-12-00-00.txt"};
33 33
34 34 template <typename T>
35 35 bool compareDataSeries(std::shared_ptr<IDataSeries> candidate, SqpRange candidateCacheRange,
36 36 std::shared_ptr<IDataSeries> reference)
37 37 {
38 38 auto compareLambda = [](const auto &it1, const auto &it2) {
39 39 return (it1.x() == it2.x()) && (it1.value() == it2.value());
40 40 };
41 41
42 42 auto candidateDS = std::dynamic_pointer_cast<T>(candidate);
43 43 auto referenceDS = std::dynamic_pointer_cast<T>(reference);
44 44
45 45 if (candidateDS && referenceDS) {
46 46
47 47 auto itRefs
48 = referenceDS->subData(candidateCacheRange.m_TStart, candidateCacheRange.m_TEnd);
48 = referenceDS->xAxisRange(candidateCacheRange.m_TStart, candidateCacheRange.m_TEnd);
49 49 qDebug() << " DISTANCE" << std::distance(candidateDS->cbegin(), candidateDS->cend())
50 50 << std::distance(itRefs.first, itRefs.second);
51 51
52 52 // auto xcValue = candidateDS->valuesData()->data();
53 53 // auto dist = std::distance(itRefs.first, itRefs.second);
54 54 // auto it = itRefs.first;
55 55 // for (auto i = 0; i < dist - 1; ++i) {
56 56 // ++it;
57 57 // qInfo() << "END:" << it->value();
58 58 // }
59 59 // qDebug() << "END:" << it->value() << xcValue.last();
60 60
61 61 return std::equal(candidateDS->cbegin(), candidateDS->cend(), itRefs.first, itRefs.second,
62 62 compareLambda);
63 63 }
64 64 else {
65 65 return false;
66 66 }
67 67 }
68 68 }
69 69
70 70 class TestAmdaAcquisition : public QObject {
71 71 Q_OBJECT
72 72
73 73 private slots:
74 74 void testAcquisition();
75 75 };
76 76
77 77 void TestAmdaAcquisition::testAcquisition()
78 78 {
79 79 // READ the ref file:
80 80 auto filePath = QFileInfo{TESTS_RESOURCES_PATH, TESTS_AMDA_REF_FILE}.absoluteFilePath();
81 81 auto results = AmdaResultParser::readTxt(filePath, AmdaResultParser::ValueType::SCALAR);
82 82
83 83 auto provider = std::make_shared<AmdaProvider>();
84 84 auto timeController = std::make_unique<TimeController>();
85 85
86 86 auto varRS = QDateTime{QDate{2012, 01, 02}, QTime{2, 3, 0, 0}};
87 87 auto varRE = QDateTime{QDate{2012, 01, 02}, QTime{2, 4, 0, 0}};
88 88
89 89 auto sqpR = SqpRange{DateUtils::secondsSinceEpoch(varRS), DateUtils::secondsSinceEpoch(varRE)};
90 90
91 91 timeController->onTimeToUpdate(sqpR);
92 92
93 93 QVariantHash metaData;
94 94 metaData.insert("dataType", "scalar");
95 95 metaData.insert("xml:id", "imf(0)");
96 96
97 97 VariableController vc;
98 98 vc.setTimeController(timeController.get());
99 99
100 100 auto var = vc.createVariable("bx_gse", metaData, provider);
101 101
102 102 // 1 : Variable creation
103 103 QCOMPARE(var->range().m_TStart, sqpR.m_TStart);
104 104 QCOMPARE(var->range().m_TEnd, sqpR.m_TEnd);
105 105
106 106 qDebug() << " 1: TIMECONTROLLER" << timeController->dateTime();
107 107 qDebug() << " 1: RANGE " << var->range();
108 108 qDebug() << " 1: CACHERANGE" << var->cacheRange();
109 109
110 110 // wait for 10 sec before asking next request toi permit asynchrone process to finish.
111 111 auto timeToWaitMs = 10000;
112 112
113 113 QEventLoop loop;
114 114 QTimer::singleShot(timeToWaitMs, &loop, &QEventLoop::quit);
115 115 loop.exec();
116 116
117 117 // Tests on acquisition operation
118 118
119 119 int count = 1;
120 120
121 121 auto requestDataLoading = [&vc, var, timeToWaitMs, results, &count](auto tStart, auto tEnd) {
122 122 ++count;
123 123
124 124 auto nextSqpR
125 125 = SqpRange{DateUtils::secondsSinceEpoch(tStart), DateUtils::secondsSinceEpoch(tEnd)};
126 126 vc.onRequestDataLoading(QVector<std::shared_ptr<Variable> >{} << var, nextSqpR,
127 127 var->range(), true);
128 128
129 129 QEventLoop loop;
130 130 QTimer::singleShot(timeToWaitMs, &loop, &QEventLoop::quit);
131 131 loop.exec();
132 132
133 133 qDebug() << count << "RANGE " << var->range();
134 134 qDebug() << count << "CACHERANGE" << var->cacheRange();
135 135
136 136 QCOMPARE(var->range().m_TStart, nextSqpR.m_TStart);
137 137 QCOMPARE(var->range().m_TEnd, nextSqpR.m_TEnd);
138 138
139 139 // Verify dataserie
140 140 QVERIFY(compareDataSeries<ScalarSeries>(var->dataSeries(), var->cacheRange(), results));
141 141
142 142 };
143 143
144 144 // 2 : pan (jump) left for one hour
145 145 auto nextVarRS = QDateTime{QDate{2012, 01, 02}, QTime{2, 1, 0, 0}};
146 146 auto nextVarRE = QDateTime{QDate{2012, 01, 02}, QTime{2, 2, 0, 0}};
147 147 // requestDataLoading(nextVarRS, nextVarRE);
148 148
149 149
150 150 // 3 : pan (jump) right for one hour
151 151 nextVarRS = QDateTime{QDate{2012, 01, 02}, QTime{2, 5, 0, 0}};
152 152 nextVarRE = QDateTime{QDate{2012, 01, 02}, QTime{2, 6, 0, 0}};
153 153 requestDataLoading(nextVarRS, nextVarRE);
154 154
155 155 // 4 : pan (overlay) right for 30 min
156 156 nextVarRS = QDateTime{QDate{2012, 01, 02}, QTime{2, 5, 30, 0}};
157 157 nextVarRE = QDateTime{QDate{2012, 01, 02}, QTime{2, 6, 30, 0}};
158 158 // requestDataLoading(nextVarRS, nextVarRE);
159 159
160 160 // 5 : pan (overlay) left for 30 min
161 161 nextVarRS = QDateTime{QDate{2012, 01, 02}, QTime{2, 5, 0, 0}};
162 162 nextVarRE = QDateTime{QDate{2012, 01, 02}, QTime{2, 6, 0, 0}};
163 163 // requestDataLoading(nextVarRS, nextVarRE);
164 164
165 165 // 6 : pan (overlay) left for 30 min - BIS
166 166 nextVarRS = QDateTime{QDate{2012, 01, 02}, QTime{2, 4, 30, 0}};
167 167 nextVarRE = QDateTime{QDate{2012, 01, 02}, QTime{2, 5, 30, 0}};
168 168 // requestDataLoading(nextVarRS, nextVarRE);
169 169
170 170 // 7 : Zoom in Inside 20 min range
171 171 nextVarRS = QDateTime{QDate{2012, 01, 02}, QTime{2, 4, 50, 0}};
172 172 nextVarRE = QDateTime{QDate{2012, 01, 02}, QTime{2, 5, 10, 0}};
173 173 // requestDataLoading(nextVarRS, nextVarRE);
174 174
175 175 // 8 : Zoom out Inside 2 hours range
176 176 nextVarRS = QDateTime{QDate{2012, 01, 02}, QTime{2, 4, 0, 0}};
177 177 nextVarRE = QDateTime{QDate{2012, 01, 02}, QTime{2, 6, 0, 0}};
178 178 // requestDataLoading(nextVarRS, nextVarRE);
179 179
180 180
181 181 // Close the app after 10 sec
182 182 QTimer::singleShot(timeToWaitMs, &loop, &QEventLoop::quit);
183 183 loop.exec();
184 184 }
185 185
186 186 int main(int argc, char *argv[])
187 187 {
188 188 SqpApplication app(argc, argv);
189 189 app.setAttribute(Qt::AA_Use96Dpi, true);
190 190 TestAmdaAcquisition tc;
191 191 QTEST_SET_MAIN_SOURCE_PATH
192 192 return QTest::qExec(&tc, argc, argv);
193 193 }
194 194
195 195 // QTEST_MAIN(TestAmdaAcquisition)
196 196
197 197 #include "TestAmdaAcquisition.moc"
General Comments 0
You need to be logged in to leave comments. Login now