##// END OF EJS Templates
Merge branch 'feature/ValueRange' into develop
Alexandre Leroux -
r614:d53842017d03 merge
parent child
Show More
@@ -1,64 +1,116
1 #ifndef SCIQLOP_SORTUTILS_H
1 #ifndef SCIQLOP_SORTUTILS_H
2 #define SCIQLOP_SORTUTILS_H
2 #define SCIQLOP_SORTUTILS_H
3
3
4 #include <algorithm>
4 #include <algorithm>
5 #include <numeric>
5 #include <numeric>
6 #include <vector>
6 #include <vector>
7
7
8 /**
8 /**
9 * Utility class with methods for sorting data
9 * Utility class with methods for sorting data
10 */
10 */
11 struct SortUtils {
11 struct SortUtils {
12 /**
12 /**
13 * Generates a vector representing the index of insertion of each data of a container if this
13 * Generates a vector representing the index of insertion of each data of a container if this
14 * one had to be sorted according to a comparison function.
14 * one had to be sorted according to a comparison function.
15 *
15 *
16 * For example:
16 * For example:
17 * If the container is a vector {1; 4; 2; 5; 3} and the comparison function is std::less, the
17 * If the container is a vector {1; 4; 2; 5; 3} and the comparison function is std::less, the
18 * result would be : {0; 3; 1; 4; 2}
18 * result would be : {0; 3; 1; 4; 2}
19 *
19 *
20 * @tparam Container the type of the container.
20 * @tparam Container the type of the container.
21 * @tparam Compare the type of the comparison function
21 * @tparam Compare the type of the comparison function
22 * @param container the container from which to generate the result. The container must have a
22 * @param container the container from which to generate the result. The container must have a
23 * at() method that returns a value associated to an index
23 * at() method that returns a value associated to an index
24 * @param compare the comparison function
24 * @param compare the comparison function
25 */
25 */
26 template <typename Container, typename Compare>
26 template <typename Container, typename Compare>
27 static std::vector<int> sortPermutation(const Container &container, const Compare &compare)
27 static std::vector<int> sortPermutation(const Container &container, const Compare &compare)
28 {
28 {
29 auto permutation = std::vector<int>{};
29 auto permutation = std::vector<int>{};
30 permutation.resize(container.size());
30 permutation.resize(container.size());
31
31
32 std::iota(permutation.begin(), permutation.end(), 0);
32 std::iota(permutation.begin(), permutation.end(), 0);
33 std::sort(permutation.begin(), permutation.end(),
33 std::sort(permutation.begin(), permutation.end(),
34 [&](int i, int j) { return compare(container.at(i), container.at(j)); });
34 [&](int i, int j) { return compare(container.at(i), container.at(j)); });
35 return permutation;
35 return permutation;
36 }
36 }
37
37
38 /**
38 /**
39 * Sorts a container according to indices passed in parameter
39 * Sorts a container according to indices passed in parameter
40 * @param container the container sorted
40 * @param container the container sorted
41 * @param sortPermutation the indices used to sort the container
41 * @param sortPermutation the indices used to sort the container
42 * @return the container sorted
42 * @return the container sorted
43 * @warning no verification is made on validity of sortPermutation (i.e. the vector has unique
43 * @warning no verification is made on validity of sortPermutation (i.e. the vector has unique
44 * indices and its range is [0 ; vector.size()[ )
44 * indices and its range is [0 ; vector.size()[ )
45 */
45 */
46 template <typename Container>
46 template <typename Container>
47 static Container sort(const Container &container, const std::vector<int> &sortPermutation)
47 static Container sort(const Container &container, const std::vector<int> &sortPermutation)
48 {
48 {
49 if (container.size() != sortPermutation.size()) {
49 if (container.size() != sortPermutation.size()) {
50 return Container{};
50 return Container{};
51 }
51 }
52
52
53 // Inits result
53 // Inits result
54 auto sortedData = Container{};
54 auto sortedData = Container{};
55 sortedData.resize(container.size());
55 sortedData.resize(container.size());
56
56
57 std::transform(sortPermutation.cbegin(), sortPermutation.cend(), sortedData.begin(),
57 std::transform(sortPermutation.cbegin(), sortPermutation.cend(), sortedData.begin(),
58 [&container](int i) { return container.at(i); });
58 [&container](int i) { return container.at(i); });
59
59
60 return sortedData;
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 #endif // SCIQLOP_SORTUTILS_H
116 #endif // SCIQLOP_SORTUTILS_H
@@ -1,328 +1,348
1 #ifndef SCIQLOP_ARRAYDATA_H
1 #ifndef SCIQLOP_ARRAYDATA_H
2 #define SCIQLOP_ARRAYDATA_H
2 #define SCIQLOP_ARRAYDATA_H
3
3
4 #include <Common/SortUtils.h>
4 #include <Common/SortUtils.h>
5
5
6 #include <QReadLocker>
6 #include <QReadLocker>
7 #include <QReadWriteLock>
7 #include <QReadWriteLock>
8 #include <QVector>
8 #include <QVector>
9
9
10 #include <memory>
10 #include <memory>
11
11
12 template <int Dim>
12 template <int Dim>
13 class ArrayData;
13 class ArrayData;
14
14
15 using DataContainer = QVector<QVector<double> >;
15 using DataContainer = QVector<QVector<double> >;
16
16
17 namespace arraydata_detail {
17 namespace arraydata_detail {
18
18
19 /// Struct used to sort ArrayData
19 /// Struct used to sort ArrayData
20 template <int Dim>
20 template <int Dim>
21 struct Sort {
21 struct Sort {
22 static std::shared_ptr<ArrayData<Dim> > sort(const DataContainer &data,
22 static std::shared_ptr<ArrayData<Dim> > sort(const DataContainer &data,
23 const std::vector<int> &sortPermutation)
23 const std::vector<int> &sortPermutation)
24 {
24 {
25 auto nbComponents = data.size();
25 auto nbComponents = data.size();
26 auto sortedData = DataContainer(nbComponents);
26 auto sortedData = DataContainer(nbComponents);
27
27
28 for (auto i = 0; i < nbComponents; ++i) {
28 for (auto i = 0; i < nbComponents; ++i) {
29 sortedData[i] = SortUtils::sort(data.at(i), sortPermutation);
29 sortedData[i] = SortUtils::sort(data.at(i), sortPermutation);
30 }
30 }
31
31
32 return std::make_shared<ArrayData<Dim> >(std::move(sortedData));
32 return std::make_shared<ArrayData<Dim> >(std::move(sortedData));
33 }
33 }
34 };
34 };
35
35
36 /// Specialization for uni-dimensional ArrayData
36 /// Specialization for uni-dimensional ArrayData
37 template <>
37 template <>
38 struct Sort<1> {
38 struct Sort<1> {
39 static std::shared_ptr<ArrayData<1> > sort(const DataContainer &data,
39 static std::shared_ptr<ArrayData<1> > sort(const DataContainer &data,
40 const std::vector<int> &sortPermutation)
40 const std::vector<int> &sortPermutation)
41 {
41 {
42 return std::make_shared<ArrayData<1> >(SortUtils::sort(data.at(0), sortPermutation));
42 return std::make_shared<ArrayData<1> >(SortUtils::sort(data.at(0), sortPermutation));
43 }
43 }
44 };
44 };
45
45
46 } // namespace arraydata_detail
46 } // namespace arraydata_detail
47
47
48 /**
48 /**
49 * @brief The ArrayData class represents a dataset for a data series.
49 * @brief The ArrayData class represents a dataset for a data series.
50 *
50 *
51 * A dataset can be unidimensional or two-dimensional. This property is determined by the Dim
51 * A dataset can be unidimensional or two-dimensional. This property is determined by the Dim
52 * template-parameter. In a case of a two-dimensional dataset, each dataset component has the same
52 * template-parameter. In a case of a two-dimensional dataset, each dataset component has the same
53 * number of values
53 * number of values
54 *
54 *
55 * @tparam Dim the dimension of the ArrayData (one or two)
55 * @tparam Dim the dimension of the ArrayData (one or two)
56 * @sa IDataSeries
56 * @sa IDataSeries
57 */
57 */
58 template <int Dim>
58 template <int Dim>
59 class ArrayData {
59 class ArrayData {
60 public:
60 public:
61 class IteratorValue {
61 class IteratorValue {
62 public:
62 public:
63 explicit IteratorValue(const DataContainer &container, bool begin) : m_Its{}
63 explicit IteratorValue(const DataContainer &container, bool begin) : m_Its{}
64 {
64 {
65 for (auto i = 0; i < container.size(); ++i) {
65 for (auto i = 0; i < container.size(); ++i) {
66 m_Its.push_back(begin ? container.at(i).cbegin() : container.at(i).cend());
66 m_Its.push_back(begin ? container.at(i).cbegin() : container.at(i).cend());
67 }
67 }
68 }
68 }
69
69
70 double at(int index) const { return *m_Its.at(index); }
70 double at(int index) const { return *m_Its.at(index); }
71 double first() const { return *m_Its.front(); }
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 void next()
93 void next()
74 {
94 {
75 for (auto &it : m_Its) {
95 for (auto &it : m_Its) {
76 ++it;
96 ++it;
77 }
97 }
78 }
98 }
79
99
80 void prev()
100 void prev()
81 {
101 {
82 for (auto &it : m_Its) {
102 for (auto &it : m_Its) {
83 --it;
103 --it;
84 }
104 }
85 }
105 }
86
106
87 bool operator==(const IteratorValue &other) const { return m_Its == other.m_Its; }
107 bool operator==(const IteratorValue &other) const { return m_Its == other.m_Its; }
88
108
89 private:
109 private:
90 std::vector<DataContainer::value_type::const_iterator> m_Its;
110 std::vector<DataContainer::value_type::const_iterator> m_Its;
91 };
111 };
92
112
93 class Iterator {
113 class Iterator {
94 public:
114 public:
95 using iterator_category = std::forward_iterator_tag;
115 using iterator_category = std::forward_iterator_tag;
96 using value_type = const IteratorValue;
116 using value_type = const IteratorValue;
97 using difference_type = std::ptrdiff_t;
117 using difference_type = std::ptrdiff_t;
98 using pointer = value_type *;
118 using pointer = value_type *;
99 using reference = value_type &;
119 using reference = value_type &;
100
120
101 Iterator(const DataContainer &container, bool begin) : m_CurrentValue{container, begin} {}
121 Iterator(const DataContainer &container, bool begin) : m_CurrentValue{container, begin} {}
102
122
103 virtual ~Iterator() noexcept = default;
123 virtual ~Iterator() noexcept = default;
104 Iterator(const Iterator &) = default;
124 Iterator(const Iterator &) = default;
105 Iterator(Iterator &&) = default;
125 Iterator(Iterator &&) = default;
106 Iterator &operator=(const Iterator &) = default;
126 Iterator &operator=(const Iterator &) = default;
107 Iterator &operator=(Iterator &&) = default;
127 Iterator &operator=(Iterator &&) = default;
108
128
109 Iterator &operator++()
129 Iterator &operator++()
110 {
130 {
111 m_CurrentValue.next();
131 m_CurrentValue.next();
112 return *this;
132 return *this;
113 }
133 }
114
134
115 Iterator &operator--()
135 Iterator &operator--()
116 {
136 {
117 m_CurrentValue.prev();
137 m_CurrentValue.prev();
118 return *this;
138 return *this;
119 }
139 }
120
140
121 pointer operator->() const { return &m_CurrentValue; }
141 pointer operator->() const { return &m_CurrentValue; }
122 reference operator*() const { return m_CurrentValue; }
142 reference operator*() const { return m_CurrentValue; }
123
143
124 bool operator==(const Iterator &other) const
144 bool operator==(const Iterator &other) const
125 {
145 {
126 return m_CurrentValue == other.m_CurrentValue;
146 return m_CurrentValue == other.m_CurrentValue;
127 }
147 }
128
148
129 bool operator!=(const Iterator &other) const { return !(*this == other); }
149 bool operator!=(const Iterator &other) const { return !(*this == other); }
130
150
131 private:
151 private:
132 IteratorValue m_CurrentValue;
152 IteratorValue m_CurrentValue;
133 };
153 };
134
154
135 // ///// //
155 // ///// //
136 // Ctors //
156 // Ctors //
137 // ///// //
157 // ///// //
138
158
139 /**
159 /**
140 * Ctor for a unidimensional ArrayData
160 * Ctor for a unidimensional ArrayData
141 * @param data the data the ArrayData will hold
161 * @param data the data the ArrayData will hold
142 */
162 */
143 template <int D = Dim, typename = std::enable_if_t<D == 1> >
163 template <int D = Dim, typename = std::enable_if_t<D == 1> >
144 explicit ArrayData(QVector<double> data) : m_Data{1, QVector<double>{}}
164 explicit ArrayData(QVector<double> data) : m_Data{1, QVector<double>{}}
145 {
165 {
146 m_Data[0] = std::move(data);
166 m_Data[0] = std::move(data);
147 }
167 }
148
168
149 /**
169 /**
150 * Ctor for a two-dimensional ArrayData. The number of components (number of vectors) must be
170 * Ctor for a two-dimensional ArrayData. The number of components (number of vectors) must be
151 * greater than 2 and each component must have the same number of values
171 * greater than 2 and each component must have the same number of values
152 * @param data the data the ArrayData will hold
172 * @param data the data the ArrayData will hold
153 * @throws std::invalid_argument if the number of components is less than 2
173 * @throws std::invalid_argument if the number of components is less than 2
154 * @remarks if the number of values is not the same for each component, no value is set
174 * @remarks if the number of values is not the same for each component, no value is set
155 */
175 */
156 template <int D = Dim, typename = std::enable_if_t<D == 2> >
176 template <int D = Dim, typename = std::enable_if_t<D == 2> >
157 explicit ArrayData(DataContainer data)
177 explicit ArrayData(DataContainer data)
158 {
178 {
159 auto nbComponents = data.size();
179 auto nbComponents = data.size();
160 if (nbComponents < 2) {
180 if (nbComponents < 2) {
161 throw std::invalid_argument{
181 throw std::invalid_argument{
162 QString{"A multidimensional ArrayData must have at least 2 components (found: %1"}
182 QString{"A multidimensional ArrayData must have at least 2 components (found: %1"}
163 .arg(data.size())
183 .arg(data.size())
164 .toStdString()};
184 .toStdString()};
165 }
185 }
166
186
167 auto nbValues = data.front().size();
187 auto nbValues = data.front().size();
168 if (std::all_of(data.cbegin(), data.cend(), [nbValues](const auto &component) {
188 if (std::all_of(data.cbegin(), data.cend(), [nbValues](const auto &component) {
169 return component.size() == nbValues;
189 return component.size() == nbValues;
170 })) {
190 })) {
171 m_Data = std::move(data);
191 m_Data = std::move(data);
172 }
192 }
173 else {
193 else {
174 m_Data = DataContainer{nbComponents, QVector<double>{}};
194 m_Data = DataContainer{nbComponents, QVector<double>{}};
175 }
195 }
176 }
196 }
177
197
178 /// Copy ctor
198 /// Copy ctor
179 explicit ArrayData(const ArrayData &other)
199 explicit ArrayData(const ArrayData &other)
180 {
200 {
181 QReadLocker otherLocker{&other.m_Lock};
201 QReadLocker otherLocker{&other.m_Lock};
182 m_Data = other.m_Data;
202 m_Data = other.m_Data;
183 }
203 }
184
204
185 // /////////////// //
205 // /////////////// //
186 // General methods //
206 // General methods //
187 // /////////////// //
207 // /////////////// //
188
208
189 /**
209 /**
190 * Merges into the array data an other array data. The two array datas must have the same number
210 * Merges into the array data an other array data. The two array datas must have the same number
191 * of components so the merge can be done
211 * of components so the merge can be done
192 * @param other the array data to merge with
212 * @param other the array data to merge with
193 * @param prepend if true, the other array data is inserted at the beginning, otherwise it is
213 * @param prepend if true, the other array data is inserted at the beginning, otherwise it is
194 * inserted at the end
214 * inserted at the end
195 */
215 */
196 void add(const ArrayData<Dim> &other, bool prepend = false)
216 void add(const ArrayData<Dim> &other, bool prepend = false)
197 {
217 {
198 QWriteLocker locker{&m_Lock};
218 QWriteLocker locker{&m_Lock};
199 QReadLocker otherLocker{&other.m_Lock};
219 QReadLocker otherLocker{&other.m_Lock};
200
220
201 auto nbComponents = m_Data.size();
221 auto nbComponents = m_Data.size();
202 if (nbComponents != other.m_Data.size()) {
222 if (nbComponents != other.m_Data.size()) {
203 return;
223 return;
204 }
224 }
205
225
206 for (auto componentIndex = 0; componentIndex < nbComponents; ++componentIndex) {
226 for (auto componentIndex = 0; componentIndex < nbComponents; ++componentIndex) {
207 if (prepend) {
227 if (prepend) {
208 const auto &otherData = other.data(componentIndex);
228 const auto &otherData = other.data(componentIndex);
209 const auto otherDataSize = otherData.size();
229 const auto otherDataSize = otherData.size();
210
230
211 auto &data = m_Data[componentIndex];
231 auto &data = m_Data[componentIndex];
212 data.insert(data.begin(), otherDataSize, 0.);
232 data.insert(data.begin(), otherDataSize, 0.);
213
233
214 for (auto i = 0; i < otherDataSize; ++i) {
234 for (auto i = 0; i < otherDataSize; ++i) {
215 data.replace(i, otherData.at(i));
235 data.replace(i, otherData.at(i));
216 }
236 }
217 }
237 }
218 else {
238 else {
219 m_Data[componentIndex] += other.data(componentIndex);
239 m_Data[componentIndex] += other.data(componentIndex);
220 }
240 }
221 }
241 }
222 }
242 }
223
243
224 void clear()
244 void clear()
225 {
245 {
226 QWriteLocker locker{&m_Lock};
246 QWriteLocker locker{&m_Lock};
227
247
228 auto nbComponents = m_Data.size();
248 auto nbComponents = m_Data.size();
229 for (auto i = 0; i < nbComponents; ++i) {
249 for (auto i = 0; i < nbComponents; ++i) {
230 m_Data[i].clear();
250 m_Data[i].clear();
231 }
251 }
232 }
252 }
233
253
234 int componentCount() const noexcept { return m_Data.size(); }
254 int componentCount() const noexcept { return m_Data.size(); }
235
255
236 /**
256 /**
237 * @return the data of a component
257 * @return the data of a component
238 * @param componentIndex the index of the component to retrieve the data
258 * @param componentIndex the index of the component to retrieve the data
239 * @return the component's data, empty vector if the index is invalid
259 * @return the component's data, empty vector if the index is invalid
240 */
260 */
241 QVector<double> data(int componentIndex) const noexcept
261 QVector<double> data(int componentIndex) const noexcept
242 {
262 {
243 QReadLocker locker{&m_Lock};
263 QReadLocker locker{&m_Lock};
244
264
245 return (componentIndex >= 0 && componentIndex < m_Data.size()) ? m_Data.at(componentIndex)
265 return (componentIndex >= 0 && componentIndex < m_Data.size()) ? m_Data.at(componentIndex)
246 : QVector<double>{};
266 : QVector<double>{};
247 }
267 }
248
268
249 /// @return the size (i.e. number of values) of a single component
269 /// @return the size (i.e. number of values) of a single component
250 /// @remarks in a case of a two-dimensional ArrayData, each component has the same size
270 /// @remarks in a case of a two-dimensional ArrayData, each component has the same size
251 int size() const
271 int size() const
252 {
272 {
253 QReadLocker locker{&m_Lock};
273 QReadLocker locker{&m_Lock};
254 return m_Data[0].size();
274 return m_Data[0].size();
255 }
275 }
256
276
257 std::shared_ptr<ArrayData<Dim> > sort(const std::vector<int> &sortPermutation)
277 std::shared_ptr<ArrayData<Dim> > sort(const std::vector<int> &sortPermutation)
258 {
278 {
259 QReadLocker locker{&m_Lock};
279 QReadLocker locker{&m_Lock};
260 return arraydata_detail::Sort<Dim>::sort(m_Data, sortPermutation);
280 return arraydata_detail::Sort<Dim>::sort(m_Data, sortPermutation);
261 }
281 }
262
282
263 // ///////// //
283 // ///////// //
264 // Iterators //
284 // Iterators //
265 // ///////// //
285 // ///////// //
266
286
267 Iterator cbegin() const { return Iterator{m_Data, true}; }
287 Iterator cbegin() const { return Iterator{m_Data, true}; }
268 Iterator cend() const { return Iterator{m_Data, false}; }
288 Iterator cend() const { return Iterator{m_Data, false}; }
269
289
270 // ///////////// //
290 // ///////////// //
271 // 1-dim methods //
291 // 1-dim methods //
272 // ///////////// //
292 // ///////////// //
273
293
274 /**
294 /**
275 * @return the data at a specified index
295 * @return the data at a specified index
276 * @remarks index must be a valid position
296 * @remarks index must be a valid position
277 * @remarks this method is only available for a unidimensional ArrayData
297 * @remarks this method is only available for a unidimensional ArrayData
278 */
298 */
279 template <int D = Dim, typename = std::enable_if_t<D == 1> >
299 template <int D = Dim, typename = std::enable_if_t<D == 1> >
280 double at(int index) const noexcept
300 double at(int index) const noexcept
281 {
301 {
282 QReadLocker locker{&m_Lock};
302 QReadLocker locker{&m_Lock};
283 return m_Data[0].at(index);
303 return m_Data[0].at(index);
284 }
304 }
285
305
286 /**
306 /**
287 * @return the data as a vector, as a const reference
307 * @return the data as a vector, as a const reference
288 * @remarks this method is only available for a unidimensional ArrayData
308 * @remarks this method is only available for a unidimensional ArrayData
289 */
309 */
290 template <int D = Dim, typename = std::enable_if_t<D == 1> >
310 template <int D = Dim, typename = std::enable_if_t<D == 1> >
291 const QVector<double> &cdata() const noexcept
311 const QVector<double> &cdata() const noexcept
292 {
312 {
293 QReadLocker locker{&m_Lock};
313 QReadLocker locker{&m_Lock};
294 return m_Data.at(0);
314 return m_Data.at(0);
295 }
315 }
296
316
297 /**
317 /**
298 * @return the data as a vector
318 * @return the data as a vector
299 * @remarks this method is only available for a unidimensional ArrayData
319 * @remarks this method is only available for a unidimensional ArrayData
300 */
320 */
301 template <int D = Dim, typename = std::enable_if_t<D == 1> >
321 template <int D = Dim, typename = std::enable_if_t<D == 1> >
302 QVector<double> data() const noexcept
322 QVector<double> data() const noexcept
303 {
323 {
304 QReadLocker locker{&m_Lock};
324 QReadLocker locker{&m_Lock};
305 return m_Data[0];
325 return m_Data[0];
306 }
326 }
307
327
308 // ///////////// //
328 // ///////////// //
309 // 2-dim methods //
329 // 2-dim methods //
310 // ///////////// //
330 // ///////////// //
311
331
312 /**
332 /**
313 * @return the data
333 * @return the data
314 * @remarks this method is only available for a two-dimensional ArrayData
334 * @remarks this method is only available for a two-dimensional ArrayData
315 */
335 */
316 template <int D = Dim, typename = std::enable_if_t<D == 2> >
336 template <int D = Dim, typename = std::enable_if_t<D == 2> >
317 DataContainer data() const noexcept
337 DataContainer data() const noexcept
318 {
338 {
319 QReadLocker locker{&m_Lock};
339 QReadLocker locker{&m_Lock};
320 return m_Data;
340 return m_Data;
321 }
341 }
322
342
323 private:
343 private:
324 DataContainer m_Data;
344 DataContainer m_Data;
325 mutable QReadWriteLock m_Lock;
345 mutable QReadWriteLock m_Lock;
326 };
346 };
327
347
328 #endif // SCIQLOP_ARRAYDATA_H
348 #endif // SCIQLOP_ARRAYDATA_H
@@ -1,306 +1,333
1 #ifndef SCIQLOP_DATASERIES_H
1 #ifndef SCIQLOP_DATASERIES_H
2 #define SCIQLOP_DATASERIES_H
2 #define SCIQLOP_DATASERIES_H
3
3
4 #include "CoreGlobal.h"
4 #include "CoreGlobal.h"
5
5
6 #include <Common/SortUtils.h>
6 #include <Common/SortUtils.h>
7
7
8 #include <Data/ArrayData.h>
8 #include <Data/ArrayData.h>
9 #include <Data/IDataSeries.h>
9 #include <Data/IDataSeries.h>
10
10
11 #include <QLoggingCategory>
11 #include <QLoggingCategory>
12 #include <QReadLocker>
12 #include <QReadLocker>
13 #include <QReadWriteLock>
13 #include <QReadWriteLock>
14 #include <memory>
14 #include <memory>
15
15
16 // We don't use the Qt macro since the log is used in the header file, which causes multiple log
16 // We don't use the Qt macro since the log is used in the header file, which causes multiple log
17 // definitions with inheritance. Inline method is used instead
17 // definitions with inheritance. Inline method is used instead
18 inline const QLoggingCategory &LOG_DataSeries()
18 inline const QLoggingCategory &LOG_DataSeries()
19 {
19 {
20 static const QLoggingCategory category{"DataSeries"};
20 static const QLoggingCategory category{"DataSeries"};
21 return category;
21 return category;
22 }
22 }
23
23
24 template <int Dim>
24 template <int Dim>
25 class DataSeries;
25 class DataSeries;
26
26
27 namespace dataseries_detail {
27 namespace dataseries_detail {
28
28
29 template <int Dim>
29 template <int Dim>
30 class IteratorValue : public DataSeriesIteratorValue::Impl {
30 class IteratorValue : public DataSeriesIteratorValue::Impl {
31 public:
31 public:
32 explicit IteratorValue(const DataSeries<Dim> &dataSeries, bool begin)
32 explicit IteratorValue(const DataSeries<Dim> &dataSeries, bool begin)
33 : m_XIt(begin ? dataSeries.xAxisData()->cbegin() : dataSeries.xAxisData()->cend()),
33 : m_XIt(begin ? dataSeries.xAxisData()->cbegin() : dataSeries.xAxisData()->cend()),
34 m_ValuesIt(begin ? dataSeries.valuesData()->cbegin()
34 m_ValuesIt(begin ? dataSeries.valuesData()->cbegin()
35 : dataSeries.valuesData()->cend())
35 : dataSeries.valuesData()->cend())
36 {
36 {
37 }
37 }
38 IteratorValue(const IteratorValue &other) = default;
38 IteratorValue(const IteratorValue &other) = default;
39
39
40 std::unique_ptr<DataSeriesIteratorValue::Impl> clone() const override
40 std::unique_ptr<DataSeriesIteratorValue::Impl> clone() const override
41 {
41 {
42 return std::make_unique<IteratorValue<Dim> >(*this);
42 return std::make_unique<IteratorValue<Dim> >(*this);
43 }
43 }
44
44
45 bool equals(const DataSeriesIteratorValue::Impl &other) const override try {
45 bool equals(const DataSeriesIteratorValue::Impl &other) const override try {
46 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
46 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
47 return std::tie(m_XIt, m_ValuesIt) == std::tie(otherImpl.m_XIt, otherImpl.m_ValuesIt);
47 return std::tie(m_XIt, m_ValuesIt) == std::tie(otherImpl.m_XIt, otherImpl.m_ValuesIt);
48 }
48 }
49 catch (const std::bad_cast &) {
49 catch (const std::bad_cast &) {
50 return false;
50 return false;
51 }
51 }
52
52
53 void next() override
53 void next() override
54 {
54 {
55 ++m_XIt;
55 ++m_XIt;
56 ++m_ValuesIt;
56 ++m_ValuesIt;
57 }
57 }
58
58
59 void prev() override
59 void prev() override
60 {
60 {
61 --m_XIt;
61 --m_XIt;
62 --m_ValuesIt;
62 --m_ValuesIt;
63 }
63 }
64
64
65 double x() const override { return m_XIt->at(0); }
65 double x() const override { return m_XIt->at(0); }
66 double value() const override { return m_ValuesIt->at(0); }
66 double value() const override { return m_ValuesIt->at(0); }
67 double value(int componentIndex) const override { return m_ValuesIt->at(componentIndex); }
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 private:
71 private:
70 ArrayData<1>::Iterator m_XIt;
72 ArrayData<1>::Iterator m_XIt;
71 typename ArrayData<Dim>::Iterator m_ValuesIt;
73 typename ArrayData<Dim>::Iterator m_ValuesIt;
72 };
74 };
73 } // namespace dataseries_detail
75 } // namespace dataseries_detail
74
76
75 /**
77 /**
76 * @brief The DataSeries class is the base (abstract) implementation of IDataSeries.
78 * @brief The DataSeries class is the base (abstract) implementation of IDataSeries.
77 *
79 *
78 * It proposes to set a dimension for the values ​​data.
80 * It proposes to set a dimension for the values ​​data.
79 *
81 *
80 * A DataSeries is always sorted on its x-axis data.
82 * A DataSeries is always sorted on its x-axis data.
81 *
83 *
82 * @tparam Dim The dimension of the values data
84 * @tparam Dim The dimension of the values data
83 *
85 *
84 */
86 */
85 template <int Dim>
87 template <int Dim>
86 class SCIQLOP_CORE_EXPORT DataSeries : public IDataSeries {
88 class SCIQLOP_CORE_EXPORT DataSeries : public IDataSeries {
87 public:
89 public:
88 /// @sa IDataSeries::xAxisData()
90 /// @sa IDataSeries::xAxisData()
89 std::shared_ptr<ArrayData<1> > xAxisData() override { return m_XAxisData; }
91 std::shared_ptr<ArrayData<1> > xAxisData() override { return m_XAxisData; }
90 const std::shared_ptr<ArrayData<1> > xAxisData() const { return m_XAxisData; }
92 const std::shared_ptr<ArrayData<1> > xAxisData() const { return m_XAxisData; }
91
93
92 /// @sa IDataSeries::xAxisUnit()
94 /// @sa IDataSeries::xAxisUnit()
93 Unit xAxisUnit() const override { return m_XAxisUnit; }
95 Unit xAxisUnit() const override { return m_XAxisUnit; }
94
96
95 /// @return the values dataset
97 /// @return the values dataset
96 std::shared_ptr<ArrayData<Dim> > valuesData() { return m_ValuesData; }
98 std::shared_ptr<ArrayData<Dim> > valuesData() { return m_ValuesData; }
97 const std::shared_ptr<ArrayData<Dim> > valuesData() const { return m_ValuesData; }
99 const std::shared_ptr<ArrayData<Dim> > valuesData() const { return m_ValuesData; }
98
100
99 /// @sa IDataSeries::valuesUnit()
101 /// @sa IDataSeries::valuesUnit()
100 Unit valuesUnit() const override { return m_ValuesUnit; }
102 Unit valuesUnit() const override { return m_ValuesUnit; }
101
103
102
104
103 SqpRange range() const override
105 SqpRange range() const override
104 {
106 {
105 if (!m_XAxisData->cdata().isEmpty()) {
107 if (!m_XAxisData->cdata().isEmpty()) {
106 return SqpRange{m_XAxisData->cdata().first(), m_XAxisData->cdata().last()};
108 return SqpRange{m_XAxisData->cdata().first(), m_XAxisData->cdata().last()};
107 }
109 }
108
110
109 return SqpRange{};
111 return SqpRange{};
110 }
112 }
111
113
112 void clear()
114 void clear()
113 {
115 {
114 m_XAxisData->clear();
116 m_XAxisData->clear();
115 m_ValuesData->clear();
117 m_ValuesData->clear();
116 }
118 }
117
119
118 /// Merges into the data series an other data series
120 /// Merges into the data series an other data series
119 /// @remarks the data series to merge with is cleared after the operation
121 /// @remarks the data series to merge with is cleared after the operation
120 void merge(IDataSeries *dataSeries) override
122 void merge(IDataSeries *dataSeries) override
121 {
123 {
122 dataSeries->lockWrite();
124 dataSeries->lockWrite();
123 lockWrite();
125 lockWrite();
124
126
125 if (auto other = dynamic_cast<DataSeries<Dim> *>(dataSeries)) {
127 if (auto other = dynamic_cast<DataSeries<Dim> *>(dataSeries)) {
126 const auto &otherXAxisData = other->xAxisData()->cdata();
128 const auto &otherXAxisData = other->xAxisData()->cdata();
127 const auto &xAxisData = m_XAxisData->cdata();
129 const auto &xAxisData = m_XAxisData->cdata();
128
130
129 // As data series are sorted, we can improve performances of merge, by call the sort
131 // As data series are sorted, we can improve performances of merge, by call the sort
130 // method only if the two data series overlap.
132 // method only if the two data series overlap.
131 if (!otherXAxisData.empty()) {
133 if (!otherXAxisData.empty()) {
132 auto firstValue = otherXAxisData.front();
134 auto firstValue = otherXAxisData.front();
133 auto lastValue = otherXAxisData.back();
135 auto lastValue = otherXAxisData.back();
134
136
135 auto xAxisDataBegin = xAxisData.cbegin();
137 auto xAxisDataBegin = xAxisData.cbegin();
136 auto xAxisDataEnd = xAxisData.cend();
138 auto xAxisDataEnd = xAxisData.cend();
137
139
138 bool prepend;
140 bool prepend;
139 bool sortNeeded;
141 bool sortNeeded;
140
142
141 if (std::lower_bound(xAxisDataBegin, xAxisDataEnd, firstValue) == xAxisDataEnd) {
143 if (std::lower_bound(xAxisDataBegin, xAxisDataEnd, firstValue) == xAxisDataEnd) {
142 // Other data series if after data series
144 // Other data series if after data series
143 prepend = false;
145 prepend = false;
144 sortNeeded = false;
146 sortNeeded = false;
145 }
147 }
146 else if (std::upper_bound(xAxisDataBegin, xAxisDataEnd, lastValue)
148 else if (std::upper_bound(xAxisDataBegin, xAxisDataEnd, lastValue)
147 == xAxisDataBegin) {
149 == xAxisDataBegin) {
148 // Other data series if before data series
150 // Other data series if before data series
149 prepend = true;
151 prepend = true;
150 sortNeeded = false;
152 sortNeeded = false;
151 }
153 }
152 else {
154 else {
153 // The two data series overlap
155 // The two data series overlap
154 prepend = false;
156 prepend = false;
155 sortNeeded = true;
157 sortNeeded = true;
156 }
158 }
157
159
158 // Makes the merge
160 // Makes the merge
159 m_XAxisData->add(*other->xAxisData(), prepend);
161 m_XAxisData->add(*other->xAxisData(), prepend);
160 m_ValuesData->add(*other->valuesData(), prepend);
162 m_ValuesData->add(*other->valuesData(), prepend);
161
163
162 if (sortNeeded) {
164 if (sortNeeded) {
163 sort();
165 sort();
164 }
166 }
165 }
167 }
166
168
167 // Clears the other data series
169 // Clears the other data series
168 other->clear();
170 other->clear();
169 }
171 }
170 else {
172 else {
171 qCWarning(LOG_DataSeries())
173 qCWarning(LOG_DataSeries())
172 << QObject::tr("Detection of a type of IDataSeries we cannot merge with !");
174 << QObject::tr("Detection of a type of IDataSeries we cannot merge with !");
173 }
175 }
174 unlock();
176 unlock();
175 dataSeries->unlock();
177 dataSeries->unlock();
176 }
178 }
177
179
178 // ///////// //
180 // ///////// //
179 // Iterators //
181 // Iterators //
180 // ///////// //
182 // ///////// //
181
183
182 DataSeriesIterator cbegin() const override
184 DataSeriesIterator cbegin() const override
183 {
185 {
184 return DataSeriesIterator{DataSeriesIteratorValue{
186 return DataSeriesIterator{DataSeriesIteratorValue{
185 std::make_unique<dataseries_detail::IteratorValue<Dim> >(*this, true)}};
187 std::make_unique<dataseries_detail::IteratorValue<Dim> >(*this, true)}};
186 }
188 }
187
189
188 DataSeriesIterator cend() const override
190 DataSeriesIterator cend() const override
189 {
191 {
190 return DataSeriesIterator{DataSeriesIteratorValue{
192 return DataSeriesIterator{DataSeriesIteratorValue{
191 std::make_unique<dataseries_detail::IteratorValue<Dim> >(*this, false)}};
193 std::make_unique<dataseries_detail::IteratorValue<Dim> >(*this, false)}};
192 }
194 }
193
195
194 /// @sa IDataSeries::minData()
196 /// @sa IDataSeries::minXAxisData()
195 DataSeriesIterator minData(double minXAxisData) const override
197 DataSeriesIterator minXAxisData(double minXAxisData) const override
196 {
198 {
197 return std::lower_bound(
199 return std::lower_bound(
198 cbegin(), cend(), minXAxisData,
200 cbegin(), cend(), minXAxisData,
199 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
201 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
200 }
202 }
201
203
202 /// @sa IDataSeries::maxData()
204 /// @sa IDataSeries::maxXAxisData()
203 DataSeriesIterator maxData(double maxXAxisData) const override
205 DataSeriesIterator maxXAxisData(double maxXAxisData) const override
204 {
206 {
205 // Gets the first element that greater than max value
207 // Gets the first element that greater than max value
206 auto it = std::upper_bound(
208 auto it = std::upper_bound(
207 cbegin(), cend(), maxXAxisData,
209 cbegin(), cend(), maxXAxisData,
208 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
210 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
209
211
210 return it == cbegin() ? cend() : --it;
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) {
218 if (minXAxisData > maxXAxisData) {
216 std::swap(min, max);
219 std::swap(minXAxisData, maxXAxisData);
217 }
220 }
218
221
219 auto begin = cbegin();
222 auto begin = cbegin();
220 auto end = cend();
223 auto end = cend();
221
224
222 auto lowerIt
225 auto lowerIt = std::lower_bound(
223 = std::lower_bound(begin, end, min, [](const auto &itValue, const auto &value) {
226 begin, end, minXAxisData,
224 return itValue.x() < value;
227 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
225 });
228 auto upperIt = std::upper_bound(
226 auto upperIt
229 begin, end, maxXAxisData,
227 = std::upper_bound(begin, end, max, [](const auto &value, const auto &itValue) {
230 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
228 return value < itValue.x();
229 });
230
231
231 return std::make_pair(lowerIt, upperIt);
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 // Mutexes //
262 // Mutexes //
236 // /////// //
263 // /////// //
237
264
238 virtual void lockRead() { m_Lock.lockForRead(); }
265 virtual void lockRead() { m_Lock.lockForRead(); }
239 virtual void lockWrite() { m_Lock.lockForWrite(); }
266 virtual void lockWrite() { m_Lock.lockForWrite(); }
240 virtual void unlock() { m_Lock.unlock(); }
267 virtual void unlock() { m_Lock.unlock(); }
241
268
242 protected:
269 protected:
243 /// Protected ctor (DataSeries is abstract). The vectors must have the same size, otherwise a
270 /// Protected ctor (DataSeries is abstract). The vectors must have the same size, otherwise a
244 /// DataSeries with no values will be created.
271 /// DataSeries with no values will be created.
245 /// @remarks data series is automatically sorted on its x-axis data
272 /// @remarks data series is automatically sorted on its x-axis data
246 explicit DataSeries(std::shared_ptr<ArrayData<1> > xAxisData, const Unit &xAxisUnit,
273 explicit DataSeries(std::shared_ptr<ArrayData<1> > xAxisData, const Unit &xAxisUnit,
247 std::shared_ptr<ArrayData<Dim> > valuesData, const Unit &valuesUnit)
274 std::shared_ptr<ArrayData<Dim> > valuesData, const Unit &valuesUnit)
248 : m_XAxisData{xAxisData},
275 : m_XAxisData{xAxisData},
249 m_XAxisUnit{xAxisUnit},
276 m_XAxisUnit{xAxisUnit},
250 m_ValuesData{valuesData},
277 m_ValuesData{valuesData},
251 m_ValuesUnit{valuesUnit}
278 m_ValuesUnit{valuesUnit}
252 {
279 {
253 if (m_XAxisData->size() != m_ValuesData->size()) {
280 if (m_XAxisData->size() != m_ValuesData->size()) {
254 clear();
281 clear();
255 }
282 }
256
283
257 // Sorts data if it's not the case
284 // Sorts data if it's not the case
258 const auto &xAxisCData = m_XAxisData->cdata();
285 const auto &xAxisCData = m_XAxisData->cdata();
259 if (!std::is_sorted(xAxisCData.cbegin(), xAxisCData.cend())) {
286 if (!std::is_sorted(xAxisCData.cbegin(), xAxisCData.cend())) {
260 sort();
287 sort();
261 }
288 }
262 }
289 }
263
290
264 /// Copy ctor
291 /// Copy ctor
265 explicit DataSeries(const DataSeries<Dim> &other)
292 explicit DataSeries(const DataSeries<Dim> &other)
266 : m_XAxisData{std::make_shared<ArrayData<1> >(*other.m_XAxisData)},
293 : m_XAxisData{std::make_shared<ArrayData<1> >(*other.m_XAxisData)},
267 m_XAxisUnit{other.m_XAxisUnit},
294 m_XAxisUnit{other.m_XAxisUnit},
268 m_ValuesData{std::make_shared<ArrayData<Dim> >(*other.m_ValuesData)},
295 m_ValuesData{std::make_shared<ArrayData<Dim> >(*other.m_ValuesData)},
269 m_ValuesUnit{other.m_ValuesUnit}
296 m_ValuesUnit{other.m_ValuesUnit}
270 {
297 {
271 // Since a series is ordered from its construction and is always ordered, it is not
298 // Since a series is ordered from its construction and is always ordered, it is not
272 // necessary to call the sort method here ('other' is sorted)
299 // necessary to call the sort method here ('other' is sorted)
273 }
300 }
274
301
275 /// Assignment operator
302 /// Assignment operator
276 template <int D>
303 template <int D>
277 DataSeries &operator=(DataSeries<D> other)
304 DataSeries &operator=(DataSeries<D> other)
278 {
305 {
279 std::swap(m_XAxisData, other.m_XAxisData);
306 std::swap(m_XAxisData, other.m_XAxisData);
280 std::swap(m_XAxisUnit, other.m_XAxisUnit);
307 std::swap(m_XAxisUnit, other.m_XAxisUnit);
281 std::swap(m_ValuesData, other.m_ValuesData);
308 std::swap(m_ValuesData, other.m_ValuesData);
282 std::swap(m_ValuesUnit, other.m_ValuesUnit);
309 std::swap(m_ValuesUnit, other.m_ValuesUnit);
283
310
284 return *this;
311 return *this;
285 }
312 }
286
313
287 private:
314 private:
288 /**
315 /**
289 * Sorts data series on its x-axis data
316 * Sorts data series on its x-axis data
290 */
317 */
291 void sort() noexcept
318 void sort() noexcept
292 {
319 {
293 auto permutation = SortUtils::sortPermutation(*m_XAxisData, std::less<double>());
320 auto permutation = SortUtils::sortPermutation(*m_XAxisData, std::less<double>());
294 m_XAxisData = m_XAxisData->sort(permutation);
321 m_XAxisData = m_XAxisData->sort(permutation);
295 m_ValuesData = m_ValuesData->sort(permutation);
322 m_ValuesData = m_ValuesData->sort(permutation);
296 }
323 }
297
324
298 std::shared_ptr<ArrayData<1> > m_XAxisData;
325 std::shared_ptr<ArrayData<1> > m_XAxisData;
299 Unit m_XAxisUnit;
326 Unit m_XAxisUnit;
300 std::shared_ptr<ArrayData<Dim> > m_ValuesData;
327 std::shared_ptr<ArrayData<Dim> > m_ValuesData;
301 Unit m_ValuesUnit;
328 Unit m_ValuesUnit;
302
329
303 QReadWriteLock m_Lock;
330 QReadWriteLock m_Lock;
304 };
331 };
305
332
306 #endif // SCIQLOP_DATASERIES_H
333 #endif // SCIQLOP_DATASERIES_H
@@ -1,82 +1,88
1 #ifndef SCIQLOP_DATASERIESITERATOR_H
1 #ifndef SCIQLOP_DATASERIESITERATOR_H
2 #define SCIQLOP_DATASERIESITERATOR_H
2 #define SCIQLOP_DATASERIESITERATOR_H
3
3
4 #include "CoreGlobal.h"
4 #include "CoreGlobal.h"
5
5
6 #include <memory>
6 #include <memory>
7
7
8 /**
8 /**
9 * @brief The DataSeriesIteratorValue class represents the current value of a data series iterator.
9 * @brief The DataSeriesIteratorValue class represents the current value of a data series iterator.
10 * It offers standard access methods for the data in the series (x-axis, values), but it is up to
10 * It offers standard access methods for the data in the series (x-axis, values), but it is up to
11 * each series to define its own implementation of how to retrieve this data, by implementing the
11 * each series to define its own implementation of how to retrieve this data, by implementing the
12 * DataSeriesIteratorValue::Impl interface
12 * DataSeriesIteratorValue::Impl interface
13 *
13 *
14 * @sa DataSeriesIterator
14 * @sa DataSeriesIterator
15 */
15 */
16 class SCIQLOP_CORE_EXPORT DataSeriesIteratorValue {
16 class SCIQLOP_CORE_EXPORT DataSeriesIteratorValue {
17 public:
17 public:
18 struct Impl {
18 struct Impl {
19 virtual ~Impl() noexcept = default;
19 virtual ~Impl() noexcept = default;
20 virtual std::unique_ptr<Impl> clone() const = 0;
20 virtual std::unique_ptr<Impl> clone() const = 0;
21 virtual bool equals(const Impl &other) const = 0;
21 virtual bool equals(const Impl &other) const = 0;
22 virtual void next() = 0;
22 virtual void next() = 0;
23 virtual void prev() = 0;
23 virtual void prev() = 0;
24 virtual double x() const = 0;
24 virtual double x() const = 0;
25 virtual double value() const = 0;
25 virtual double value() const = 0;
26 virtual double value(int componentIndex) const = 0;
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 explicit DataSeriesIteratorValue(std::unique_ptr<Impl> impl);
31 explicit DataSeriesIteratorValue(std::unique_ptr<Impl> impl);
30 DataSeriesIteratorValue(const DataSeriesIteratorValue &other);
32 DataSeriesIteratorValue(const DataSeriesIteratorValue &other);
31 DataSeriesIteratorValue(DataSeriesIteratorValue &&other) = default;
33 DataSeriesIteratorValue(DataSeriesIteratorValue &&other) = default;
32 DataSeriesIteratorValue &operator=(DataSeriesIteratorValue other);
34 DataSeriesIteratorValue &operator=(DataSeriesIteratorValue other);
33
35
34 bool equals(const DataSeriesIteratorValue &other) const;
36 bool equals(const DataSeriesIteratorValue &other) const;
35
37
36 /// Advances to the next value
38 /// Advances to the next value
37 void next();
39 void next();
38 /// Moves back to the previous value
40 /// Moves back to the previous value
39 void prev();
41 void prev();
40 /// Gets x-axis data
42 /// Gets x-axis data
41 double x() const;
43 double x() const;
42 /// Gets value data
44 /// Gets value data
43 double value() const;
45 double value() const;
44 /// Gets value data depending on an index
46 /// Gets value data depending on an index
45 double value(int componentIndex) const;
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 private:
53 private:
48 std::unique_ptr<Impl> m_Impl;
54 std::unique_ptr<Impl> m_Impl;
49 };
55 };
50
56
51 /**
57 /**
52 * @brief The DataSeriesIterator class represents an iterator used for data series. It defines all
58 * @brief The DataSeriesIterator class represents an iterator used for data series. It defines all
53 * operators needed for a standard forward iterator
59 * operators needed for a standard forward iterator
54 * @sa http://www.cplusplus.com/reference/iterator/
60 * @sa http://www.cplusplus.com/reference/iterator/
55 */
61 */
56 class SCIQLOP_CORE_EXPORT DataSeriesIterator {
62 class SCIQLOP_CORE_EXPORT DataSeriesIterator {
57 public:
63 public:
58 using iterator_category = std::forward_iterator_tag;
64 using iterator_category = std::forward_iterator_tag;
59 using value_type = const DataSeriesIteratorValue;
65 using value_type = const DataSeriesIteratorValue;
60 using difference_type = std::ptrdiff_t;
66 using difference_type = std::ptrdiff_t;
61 using pointer = value_type *;
67 using pointer = value_type *;
62 using reference = value_type &;
68 using reference = value_type &;
63
69
64 explicit DataSeriesIterator(DataSeriesIteratorValue value);
70 explicit DataSeriesIterator(DataSeriesIteratorValue value);
65 virtual ~DataSeriesIterator() noexcept = default;
71 virtual ~DataSeriesIterator() noexcept = default;
66 DataSeriesIterator(const DataSeriesIterator &) = default;
72 DataSeriesIterator(const DataSeriesIterator &) = default;
67 DataSeriesIterator(DataSeriesIterator &&) = default;
73 DataSeriesIterator(DataSeriesIterator &&) = default;
68 DataSeriesIterator &operator=(const DataSeriesIterator &) = default;
74 DataSeriesIterator &operator=(const DataSeriesIterator &) = default;
69 DataSeriesIterator &operator=(DataSeriesIterator &&) = default;
75 DataSeriesIterator &operator=(DataSeriesIterator &&) = default;
70
76
71 DataSeriesIterator &operator++();
77 DataSeriesIterator &operator++();
72 DataSeriesIterator &operator--();
78 DataSeriesIterator &operator--();
73 pointer operator->() const { return &m_CurrentValue; }
79 pointer operator->() const { return &m_CurrentValue; }
74 reference operator*() const { return m_CurrentValue; }
80 reference operator*() const { return m_CurrentValue; }
75 bool operator==(const DataSeriesIterator &other) const;
81 bool operator==(const DataSeriesIterator &other) const;
76 bool operator!=(const DataSeriesIterator &other) const;
82 bool operator!=(const DataSeriesIterator &other) const;
77
83
78 private:
84 private:
79 DataSeriesIteratorValue m_CurrentValue;
85 DataSeriesIteratorValue m_CurrentValue;
80 };
86 };
81
87
82 #endif // SCIQLOP_DATASERIESITERATOR_H
88 #endif // SCIQLOP_DATASERIESITERATOR_H
@@ -1,96 +1,104
1 #ifndef SCIQLOP_IDATASERIES_H
1 #ifndef SCIQLOP_IDATASERIES_H
2 #define SCIQLOP_IDATASERIES_H
2 #define SCIQLOP_IDATASERIES_H
3
3
4 #include <Common/MetaTypes.h>
4 #include <Common/MetaTypes.h>
5 #include <Data/DataSeriesIterator.h>
5 #include <Data/DataSeriesIterator.h>
6 #include <Data/SqpRange.h>
6 #include <Data/SqpRange.h>
7
7
8 #include <memory>
8 #include <memory>
9
9
10 #include <QString>
10 #include <QString>
11
11
12 template <int Dim>
12 template <int Dim>
13 class ArrayData;
13 class ArrayData;
14
14
15 struct Unit {
15 struct Unit {
16 explicit Unit(const QString &name = {}, bool timeUnit = false)
16 explicit Unit(const QString &name = {}, bool timeUnit = false)
17 : m_Name{name}, m_TimeUnit{timeUnit}
17 : m_Name{name}, m_TimeUnit{timeUnit}
18 {
18 {
19 }
19 }
20
20
21 inline bool operator==(const Unit &other) const
21 inline bool operator==(const Unit &other) const
22 {
22 {
23 return std::tie(m_Name, m_TimeUnit) == std::tie(other.m_Name, other.m_TimeUnit);
23 return std::tie(m_Name, m_TimeUnit) == std::tie(other.m_Name, other.m_TimeUnit);
24 }
24 }
25 inline bool operator!=(const Unit &other) const { return !(*this == other); }
25 inline bool operator!=(const Unit &other) const { return !(*this == other); }
26
26
27 QString m_Name; ///< Unit name
27 QString m_Name; ///< Unit name
28 bool m_TimeUnit; ///< The unit is a unit of time (UTC)
28 bool m_TimeUnit; ///< The unit is a unit of time (UTC)
29 };
29 };
30
30
31 /**
31 /**
32 * @brief The IDataSeries aims to declare a data series.
32 * @brief The IDataSeries aims to declare a data series.
33 *
33 *
34 * A data series is an entity that contains at least :
34 * A data series is an entity that contains at least :
35 * - one dataset representing the x-axis
35 * - one dataset representing the x-axis
36 * - one dataset representing the values
36 * - one dataset representing the values
37 *
37 *
38 * Each dataset is represented by an ArrayData, and is associated with a unit.
38 * Each dataset is represented by an ArrayData, and is associated with a unit.
39 *
39 *
40 * An ArrayData can be unidimensional or two-dimensional, depending on the implementation of the
40 * An ArrayData can be unidimensional or two-dimensional, depending on the implementation of the
41 * IDataSeries. The x-axis dataset is always unidimensional.
41 * IDataSeries. The x-axis dataset is always unidimensional.
42 *
42 *
43 * @sa ArrayData
43 * @sa ArrayData
44 */
44 */
45 class IDataSeries {
45 class IDataSeries {
46 public:
46 public:
47 virtual ~IDataSeries() noexcept = default;
47 virtual ~IDataSeries() noexcept = default;
48
48
49 /// Returns the x-axis dataset
49 /// Returns the x-axis dataset
50 virtual std::shared_ptr<ArrayData<1> > xAxisData() = 0;
50 virtual std::shared_ptr<ArrayData<1> > xAxisData() = 0;
51
51
52 /// Returns the x-axis dataset (as const)
52 /// Returns the x-axis dataset (as const)
53 virtual const std::shared_ptr<ArrayData<1> > xAxisData() const = 0;
53 virtual const std::shared_ptr<ArrayData<1> > xAxisData() const = 0;
54
54
55 virtual Unit xAxisUnit() const = 0;
55 virtual Unit xAxisUnit() const = 0;
56
56
57 virtual Unit valuesUnit() const = 0;
57 virtual Unit valuesUnit() const = 0;
58
58
59 virtual void merge(IDataSeries *dataSeries) = 0;
59 virtual void merge(IDataSeries *dataSeries) = 0;
60 /// @todo Review the name and signature of this method
60 /// @todo Review the name and signature of this method
61 virtual std::shared_ptr<IDataSeries> subDataSeries(const SqpRange &range) = 0;
61 virtual std::shared_ptr<IDataSeries> subDataSeries(const SqpRange &range) = 0;
62
62
63 virtual std::unique_ptr<IDataSeries> clone() const = 0;
63 virtual std::unique_ptr<IDataSeries> clone() const = 0;
64 virtual SqpRange range() const = 0;
64 virtual SqpRange range() const = 0;
65
65
66 // ///////// //
66 // ///////// //
67 // Iterators //
67 // Iterators //
68 // ///////// //
68 // ///////// //
69
69
70 virtual DataSeriesIterator cbegin() const = 0;
70 virtual DataSeriesIterator cbegin() const = 0;
71 virtual DataSeriesIterator cend() const = 0;
71 virtual DataSeriesIterator cend() const = 0;
72
72
73 /// @return the iterator to the first entry of the data series whose x-axis data is greater than
73 /// @return the iterator to the first entry of the data series whose x-axis data is greater than
74 /// or equal to the value passed in parameter, or the end iterator if there is no matching value
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 /// @return the iterator to the last entry of the data series whose x-axis data is less than or
77 /// @return the iterator to the last entry of the data series whose x-axis data is less than or
78 /// equal to the value passed in parameter, or the end iterator if there is no matching value
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;
79 virtual DataSeriesIterator maxXAxisData(double maxXAxisData) const = 0;
80
80
81 virtual std::pair<DataSeriesIterator, DataSeriesIterator> subData(double min,
81 /// @return the iterators pointing to the range of data whose x-axis values are between min and
82 double max) const = 0;
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 // Mutexes //
93 // Mutexes //
86 // /////// //
94 // /////// //
87
95
88 virtual void lockRead() = 0;
96 virtual void lockRead() = 0;
89 virtual void lockWrite() = 0;
97 virtual void lockWrite() = 0;
90 virtual void unlock() = 0;
98 virtual void unlock() = 0;
91 };
99 };
92
100
93 // Required for using shared_ptr in signals/slots
101 // Required for using shared_ptr in signals/slots
94 SCIQLOP_REGISTER_META_TYPE(IDATASERIES_PTR_REGISTRY, std::shared_ptr<IDataSeries>)
102 SCIQLOP_REGISTER_META_TYPE(IDATASERIES_PTR_REGISTRY, std::shared_ptr<IDataSeries>)
95
103
96 #endif // SCIQLOP_IDATASERIES_H
104 #endif // SCIQLOP_IDATASERIES_H
@@ -1,75 +1,85
1 #include "Data/DataSeriesIterator.h"
1 #include "Data/DataSeriesIterator.h"
2
2
3 DataSeriesIteratorValue::DataSeriesIteratorValue(
3 DataSeriesIteratorValue::DataSeriesIteratorValue(
4 std::unique_ptr<DataSeriesIteratorValue::Impl> impl)
4 std::unique_ptr<DataSeriesIteratorValue::Impl> impl)
5 : m_Impl{std::move(impl)}
5 : m_Impl{std::move(impl)}
6 {
6 {
7 }
7 }
8
8
9 DataSeriesIteratorValue::DataSeriesIteratorValue(const DataSeriesIteratorValue &other)
9 DataSeriesIteratorValue::DataSeriesIteratorValue(const DataSeriesIteratorValue &other)
10 : m_Impl{other.m_Impl->clone()}
10 : m_Impl{other.m_Impl->clone()}
11 {
11 {
12 }
12 }
13
13
14 DataSeriesIteratorValue &DataSeriesIteratorValue::operator=(DataSeriesIteratorValue other)
14 DataSeriesIteratorValue &DataSeriesIteratorValue::operator=(DataSeriesIteratorValue other)
15 {
15 {
16 std::swap(m_Impl, other.m_Impl);
16 std::swap(m_Impl, other.m_Impl);
17 return *this;
17 return *this;
18 }
18 }
19
19
20 bool DataSeriesIteratorValue::equals(const DataSeriesIteratorValue &other) const
20 bool DataSeriesIteratorValue::equals(const DataSeriesIteratorValue &other) const
21 {
21 {
22 return m_Impl->equals(*other.m_Impl);
22 return m_Impl->equals(*other.m_Impl);
23 }
23 }
24
24
25 void DataSeriesIteratorValue::next()
25 void DataSeriesIteratorValue::next()
26 {
26 {
27 m_Impl->next();
27 m_Impl->next();
28 }
28 }
29
29
30 void DataSeriesIteratorValue::prev()
30 void DataSeriesIteratorValue::prev()
31 {
31 {
32 m_Impl->prev();
32 m_Impl->prev();
33 }
33 }
34
34
35 double DataSeriesIteratorValue::x() const
35 double DataSeriesIteratorValue::x() const
36 {
36 {
37 return m_Impl->x();
37 return m_Impl->x();
38 }
38 }
39
39
40 double DataSeriesIteratorValue::value() const
40 double DataSeriesIteratorValue::value() const
41 {
41 {
42 return m_Impl->value();
42 return m_Impl->value();
43 }
43 }
44
44
45 double DataSeriesIteratorValue::value(int componentIndex) const
45 double DataSeriesIteratorValue::value(int componentIndex) const
46 {
46 {
47 return m_Impl->value(componentIndex);
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 DataSeriesIterator::DataSeriesIterator(DataSeriesIteratorValue value)
60 DataSeriesIterator::DataSeriesIterator(DataSeriesIteratorValue value)
51 : m_CurrentValue{std::move(value)}
61 : m_CurrentValue{std::move(value)}
52 {
62 {
53 }
63 }
54
64
55 DataSeriesIterator &DataSeriesIterator::operator++()
65 DataSeriesIterator &DataSeriesIterator::operator++()
56 {
66 {
57 m_CurrentValue.next();
67 m_CurrentValue.next();
58 return *this;
68 return *this;
59 }
69 }
60
70
61 DataSeriesIterator &DataSeriesIterator::operator--()
71 DataSeriesIterator &DataSeriesIterator::operator--()
62 {
72 {
63 m_CurrentValue.prev();
73 m_CurrentValue.prev();
64 return *this;
74 return *this;
65 }
75 }
66
76
67 bool DataSeriesIterator::operator==(const DataSeriesIterator &other) const
77 bool DataSeriesIterator::operator==(const DataSeriesIterator &other) const
68 {
78 {
69 return m_CurrentValue.equals(other.m_CurrentValue);
79 return m_CurrentValue.equals(other.m_CurrentValue);
70 }
80 }
71
81
72 bool DataSeriesIterator::operator!=(const DataSeriesIterator &other) const
82 bool DataSeriesIterator::operator!=(const DataSeriesIterator &other) const
73 {
83 {
74 return !(*this == other);
84 return !(*this == other);
75 }
85 }
@@ -1,31 +1,31
1 #include <Data/ScalarSeries.h>
1 #include <Data/ScalarSeries.h>
2
2
3 ScalarSeries::ScalarSeries(QVector<double> xAxisData, QVector<double> valuesData,
3 ScalarSeries::ScalarSeries(QVector<double> xAxisData, QVector<double> valuesData,
4 const Unit &xAxisUnit, const Unit &valuesUnit)
4 const Unit &xAxisUnit, const Unit &valuesUnit)
5 : DataSeries{std::make_shared<ArrayData<1> >(std::move(xAxisData)), xAxisUnit,
5 : DataSeries{std::make_shared<ArrayData<1> >(std::move(xAxisData)), xAxisUnit,
6 std::make_shared<ArrayData<1> >(std::move(valuesData)), valuesUnit}
6 std::make_shared<ArrayData<1> >(std::move(valuesData)), valuesUnit}
7 {
7 {
8 }
8 }
9
9
10 std::unique_ptr<IDataSeries> ScalarSeries::clone() const
10 std::unique_ptr<IDataSeries> ScalarSeries::clone() const
11 {
11 {
12 return std::make_unique<ScalarSeries>(*this);
12 return std::make_unique<ScalarSeries>(*this);
13 }
13 }
14
14
15 std::shared_ptr<IDataSeries> ScalarSeries::subDataSeries(const SqpRange &range)
15 std::shared_ptr<IDataSeries> ScalarSeries::subDataSeries(const SqpRange &range)
16 {
16 {
17 auto subXAxisData = QVector<double>();
17 auto subXAxisData = QVector<double>();
18 auto subValuesData = QVector<double>();
18 auto subValuesData = QVector<double>();
19 this->lockRead();
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 for (auto it = bounds.first; it != bounds.second; ++it) {
22 for (auto it = bounds.first; it != bounds.second; ++it) {
23 subXAxisData.append(it->x());
23 subXAxisData.append(it->x());
24 subValuesData.append(it->value());
24 subValuesData.append(it->value());
25 }
25 }
26 }
26 }
27 this->unlock();
27 this->unlock();
28
28
29 return std::make_shared<ScalarSeries>(subXAxisData, subValuesData, this->xAxisUnit(),
29 return std::make_shared<ScalarSeries>(subXAxisData, subValuesData, this->xAxisUnit(),
30 this->valuesUnit());
30 this->valuesUnit());
31 }
31 }
@@ -1,39 +1,39
1 #include "Data/VectorSeries.h"
1 #include "Data/VectorSeries.h"
2
2
3 VectorSeries::VectorSeries(QVector<double> xAxisData, QVector<double> xValuesData,
3 VectorSeries::VectorSeries(QVector<double> xAxisData, QVector<double> xValuesData,
4 QVector<double> yValuesData, QVector<double> zValuesData,
4 QVector<double> yValuesData, QVector<double> zValuesData,
5 const Unit &xAxisUnit, const Unit &valuesUnit)
5 const Unit &xAxisUnit, const Unit &valuesUnit)
6 : DataSeries{std::make_shared<ArrayData<1> >(std::move(xAxisData)), xAxisUnit,
6 : DataSeries{std::make_shared<ArrayData<1> >(std::move(xAxisData)), xAxisUnit,
7 std::make_shared<ArrayData<2> >(QVector<QVector<double> >{
7 std::make_shared<ArrayData<2> >(QVector<QVector<double> >{
8 std::move(xValuesData), std::move(yValuesData), std::move(zValuesData)}),
8 std::move(xValuesData), std::move(yValuesData), std::move(zValuesData)}),
9 valuesUnit}
9 valuesUnit}
10 {
10 {
11 }
11 }
12
12
13 std::unique_ptr<IDataSeries> VectorSeries::clone() const
13 std::unique_ptr<IDataSeries> VectorSeries::clone() const
14 {
14 {
15 return std::make_unique<VectorSeries>(*this);
15 return std::make_unique<VectorSeries>(*this);
16 }
16 }
17
17
18 std::shared_ptr<IDataSeries> VectorSeries::subDataSeries(const SqpRange &range)
18 std::shared_ptr<IDataSeries> VectorSeries::subDataSeries(const SqpRange &range)
19 {
19 {
20 auto subXAxisData = QVector<double>();
20 auto subXAxisData = QVector<double>();
21 auto subXValuesData = QVector<double>();
21 auto subXValuesData = QVector<double>();
22 auto subYValuesData = QVector<double>();
22 auto subYValuesData = QVector<double>();
23 auto subZValuesData = QVector<double>();
23 auto subZValuesData = QVector<double>();
24
24
25 this->lockRead();
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 for (auto it = bounds.first; it != bounds.second; ++it) {
28 for (auto it = bounds.first; it != bounds.second; ++it) {
29 subXAxisData.append(it->x());
29 subXAxisData.append(it->x());
30 subXValuesData.append(it->value(0));
30 subXValuesData.append(it->value(0));
31 subYValuesData.append(it->value(1));
31 subYValuesData.append(it->value(1));
32 subZValuesData.append(it->value(2));
32 subZValuesData.append(it->value(2));
33 }
33 }
34 }
34 }
35 this->unlock();
35 this->unlock();
36
36
37 return std::make_shared<VectorSeries>(subXAxisData, subXValuesData, subYValuesData,
37 return std::make_shared<VectorSeries>(subXAxisData, subXValuesData, subYValuesData,
38 subZValuesData, this->xAxisUnit(), this->valuesUnit());
38 subZValuesData, this->xAxisUnit(), this->valuesUnit());
39 }
39 }
@@ -1,271 +1,271
1 #include <Variable/Variable.h>
1 #include <Variable/Variable.h>
2 #include <Variable/VariableModel.h>
2 #include <Variable/VariableModel.h>
3
3
4 #include <Common/DateUtils.h>
4 #include <Common/DateUtils.h>
5
5
6 #include <Data/IDataSeries.h>
6 #include <Data/IDataSeries.h>
7
7
8 #include <QSize>
8 #include <QSize>
9 #include <unordered_map>
9 #include <unordered_map>
10
10
11 Q_LOGGING_CATEGORY(LOG_VariableModel, "VariableModel")
11 Q_LOGGING_CATEGORY(LOG_VariableModel, "VariableModel")
12
12
13 namespace {
13 namespace {
14
14
15 // Column indexes
15 // Column indexes
16 const auto NAME_COLUMN = 0;
16 const auto NAME_COLUMN = 0;
17 const auto TSTART_COLUMN = 1;
17 const auto TSTART_COLUMN = 1;
18 const auto TEND_COLUMN = 2;
18 const auto TEND_COLUMN = 2;
19 const auto UNIT_COLUMN = 3;
19 const auto UNIT_COLUMN = 3;
20 const auto MISSION_COLUMN = 4;
20 const auto MISSION_COLUMN = 4;
21 const auto PLUGIN_COLUMN = 5;
21 const auto PLUGIN_COLUMN = 5;
22 const auto NB_COLUMNS = 6;
22 const auto NB_COLUMNS = 6;
23
23
24 // Column properties
24 // Column properties
25 const auto DEFAULT_HEIGHT = 25;
25 const auto DEFAULT_HEIGHT = 25;
26 const auto DEFAULT_WIDTH = 100;
26 const auto DEFAULT_WIDTH = 100;
27
27
28 struct ColumnProperties {
28 struct ColumnProperties {
29 ColumnProperties(const QString &name = {}, int width = DEFAULT_WIDTH,
29 ColumnProperties(const QString &name = {}, int width = DEFAULT_WIDTH,
30 int height = DEFAULT_HEIGHT)
30 int height = DEFAULT_HEIGHT)
31 : m_Name{name}, m_Width{width}, m_Height{height}
31 : m_Name{name}, m_Width{width}, m_Height{height}
32 {
32 {
33 }
33 }
34
34
35 QString m_Name;
35 QString m_Name;
36 int m_Width;
36 int m_Width;
37 int m_Height;
37 int m_Height;
38 };
38 };
39
39
40 const auto COLUMN_PROPERTIES = QHash<int, ColumnProperties>{
40 const auto COLUMN_PROPERTIES = QHash<int, ColumnProperties>{
41 {NAME_COLUMN, {QObject::tr("Name")}}, {TSTART_COLUMN, {QObject::tr("tStart"), 180}},
41 {NAME_COLUMN, {QObject::tr("Name")}}, {TSTART_COLUMN, {QObject::tr("tStart"), 180}},
42 {TEND_COLUMN, {QObject::tr("tEnd"), 180}}, {UNIT_COLUMN, {QObject::tr("Unit")}},
42 {TEND_COLUMN, {QObject::tr("tEnd"), 180}}, {UNIT_COLUMN, {QObject::tr("Unit")}},
43 {MISSION_COLUMN, {QObject::tr("Mission")}}, {PLUGIN_COLUMN, {QObject::tr("Plugin")}}};
43 {MISSION_COLUMN, {QObject::tr("Mission")}}, {PLUGIN_COLUMN, {QObject::tr("Plugin")}}};
44
44
45 /// Format for datetimes
45 /// Format for datetimes
46 const auto DATETIME_FORMAT = QStringLiteral("dd/MM/yyyy \nhh:mm:ss:zzz");
46 const auto DATETIME_FORMAT = QStringLiteral("dd/MM/yyyy \nhh:mm:ss:zzz");
47
47
48
48
49 } // namespace
49 } // namespace
50
50
51 struct VariableModel::VariableModelPrivate {
51 struct VariableModel::VariableModelPrivate {
52 /// Variables created in SciQlop
52 /// Variables created in SciQlop
53 std::vector<std::shared_ptr<Variable> > m_Variables;
53 std::vector<std::shared_ptr<Variable> > m_Variables;
54 std::unordered_map<std::shared_ptr<Variable>, double> m_VariableToProgress;
54 std::unordered_map<std::shared_ptr<Variable>, double> m_VariableToProgress;
55
55
56 /// Return the row index of the variable. -1 if it's not found
56 /// Return the row index of the variable. -1 if it's not found
57 int indexOfVariable(Variable *variable) const noexcept;
57 int indexOfVariable(Variable *variable) const noexcept;
58 };
58 };
59
59
60 VariableModel::VariableModel(QObject *parent)
60 VariableModel::VariableModel(QObject *parent)
61 : QAbstractTableModel{parent}, impl{spimpl::make_unique_impl<VariableModelPrivate>()}
61 : QAbstractTableModel{parent}, impl{spimpl::make_unique_impl<VariableModelPrivate>()}
62 {
62 {
63 }
63 }
64
64
65 std::shared_ptr<Variable> VariableModel::createVariable(const QString &name,
65 std::shared_ptr<Variable> VariableModel::createVariable(const QString &name,
66 const SqpRange &dateTime,
66 const SqpRange &dateTime,
67 const QVariantHash &metadata) noexcept
67 const QVariantHash &metadata) noexcept
68 {
68 {
69 auto insertIndex = rowCount();
69 auto insertIndex = rowCount();
70 beginInsertRows({}, insertIndex, insertIndex);
70 beginInsertRows({}, insertIndex, insertIndex);
71
71
72 auto variable = std::make_shared<Variable>(name, dateTime, metadata);
72 auto variable = std::make_shared<Variable>(name, dateTime, metadata);
73
73
74 impl->m_Variables.push_back(variable);
74 impl->m_Variables.push_back(variable);
75 connect(variable.get(), &Variable::updated, this, &VariableModel::onVariableUpdated);
75 connect(variable.get(), &Variable::updated, this, &VariableModel::onVariableUpdated);
76
76
77 endInsertRows();
77 endInsertRows();
78
78
79 return variable;
79 return variable;
80 }
80 }
81
81
82 void VariableModel::deleteVariable(std::shared_ptr<Variable> variable) noexcept
82 void VariableModel::deleteVariable(std::shared_ptr<Variable> variable) noexcept
83 {
83 {
84 if (!variable) {
84 if (!variable) {
85 qCCritical(LOG_Variable()) << "Can't delete a null variable from the model";
85 qCCritical(LOG_Variable()) << "Can't delete a null variable from the model";
86 return;
86 return;
87 }
87 }
88
88
89 // Finds variable in the model
89 // Finds variable in the model
90 auto begin = impl->m_Variables.cbegin();
90 auto begin = impl->m_Variables.cbegin();
91 auto end = impl->m_Variables.cend();
91 auto end = impl->m_Variables.cend();
92 auto it = std::find(begin, end, variable);
92 auto it = std::find(begin, end, variable);
93 if (it != end) {
93 if (it != end) {
94 auto removeIndex = std::distance(begin, it);
94 auto removeIndex = std::distance(begin, it);
95
95
96 // Deletes variable
96 // Deletes variable
97 beginRemoveRows({}, removeIndex, removeIndex);
97 beginRemoveRows({}, removeIndex, removeIndex);
98 impl->m_Variables.erase(it);
98 impl->m_Variables.erase(it);
99 endRemoveRows();
99 endRemoveRows();
100 }
100 }
101 else {
101 else {
102 qCritical(LOG_VariableModel())
102 qCritical(LOG_VariableModel())
103 << tr("Can't delete variable %1 from the model: the variable is not in the model")
103 << tr("Can't delete variable %1 from the model: the variable is not in the model")
104 .arg(variable->name());
104 .arg(variable->name());
105 }
105 }
106
106
107 // Removes variable from progress map
107 // Removes variable from progress map
108 impl->m_VariableToProgress.erase(variable);
108 impl->m_VariableToProgress.erase(variable);
109 }
109 }
110
110
111
111
112 std::shared_ptr<Variable> VariableModel::variable(int index) const
112 std::shared_ptr<Variable> VariableModel::variable(int index) const
113 {
113 {
114 return (index >= 0 && index < impl->m_Variables.size()) ? impl->m_Variables[index] : nullptr;
114 return (index >= 0 && index < impl->m_Variables.size()) ? impl->m_Variables[index] : nullptr;
115 }
115 }
116
116
117 void VariableModel::setDataProgress(std::shared_ptr<Variable> variable, double progress)
117 void VariableModel::setDataProgress(std::shared_ptr<Variable> variable, double progress)
118 {
118 {
119 if (progress > 0.0) {
119 if (progress > 0.0) {
120 impl->m_VariableToProgress[variable] = progress;
120 impl->m_VariableToProgress[variable] = progress;
121 }
121 }
122 else {
122 else {
123 impl->m_VariableToProgress.erase(variable);
123 impl->m_VariableToProgress.erase(variable);
124 }
124 }
125 auto modelIndex = createIndex(impl->indexOfVariable(variable.get()), NAME_COLUMN);
125 auto modelIndex = createIndex(impl->indexOfVariable(variable.get()), NAME_COLUMN);
126
126
127 emit dataChanged(modelIndex, modelIndex);
127 emit dataChanged(modelIndex, modelIndex);
128 }
128 }
129
129
130 int VariableModel::columnCount(const QModelIndex &parent) const
130 int VariableModel::columnCount(const QModelIndex &parent) const
131 {
131 {
132 Q_UNUSED(parent);
132 Q_UNUSED(parent);
133
133
134 return NB_COLUMNS;
134 return NB_COLUMNS;
135 }
135 }
136
136
137 int VariableModel::rowCount(const QModelIndex &parent) const
137 int VariableModel::rowCount(const QModelIndex &parent) const
138 {
138 {
139 Q_UNUSED(parent);
139 Q_UNUSED(parent);
140
140
141 return impl->m_Variables.size();
141 return impl->m_Variables.size();
142 }
142 }
143
143
144 QVariant VariableModel::data(const QModelIndex &index, int role) const
144 QVariant VariableModel::data(const QModelIndex &index, int role) const
145 {
145 {
146 if (!index.isValid()) {
146 if (!index.isValid()) {
147 return QVariant{};
147 return QVariant{};
148 }
148 }
149
149
150 if (index.row() < 0 || index.row() >= rowCount()) {
150 if (index.row() < 0 || index.row() >= rowCount()) {
151 return QVariant{};
151 return QVariant{};
152 }
152 }
153
153
154 if (role == Qt::DisplayRole) {
154 if (role == Qt::DisplayRole) {
155 if (auto variable = impl->m_Variables.at(index.row()).get()) {
155 if (auto variable = impl->m_Variables.at(index.row()).get()) {
156 /// Lambda function that builds the variant to return for a time value
156 /// Lambda function that builds the variant to return for a time value
157 /// @param getValueFun function used to get for a data series the iterator on the entry
157 /// @param getValueFun function used to get for a data series the iterator on the entry
158 /// that contains the time value to display
158 /// that contains the time value to display
159 auto dateTimeVariant = [variable](const auto &getValueFun) {
159 auto dateTimeVariant = [variable](const auto &getValueFun) {
160 if (auto dataSeries = variable->dataSeries()) {
160 if (auto dataSeries = variable->dataSeries()) {
161 auto it = getValueFun(*dataSeries);
161 auto it = getValueFun(*dataSeries);
162 return (it != dataSeries->cend())
162 return (it != dataSeries->cend())
163 ? DateUtils::dateTime(it->x()).toString(DATETIME_FORMAT)
163 ? DateUtils::dateTime(it->x()).toString(DATETIME_FORMAT)
164 : QVariant{};
164 : QVariant{};
165 }
165 }
166 else {
166 else {
167 return QVariant{};
167 return QVariant{};
168 }
168 }
169 };
169 };
170
170
171 switch (index.column()) {
171 switch (index.column()) {
172 case NAME_COLUMN:
172 case NAME_COLUMN:
173 return variable->name();
173 return variable->name();
174 case TSTART_COLUMN:
174 case TSTART_COLUMN:
175 // Shows the min value of the data series above the range tstart
175 // Shows the min value of the data series above the range tstart
176 return dateTimeVariant([min = variable->range().m_TStart](
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 case TEND_COLUMN:
178 case TEND_COLUMN:
179 // Shows the max value of the data series under the range tend
179 // Shows the max value of the data series under the range tend
180 return dateTimeVariant([max = variable->range().m_TEnd](
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 case UNIT_COLUMN:
182 case UNIT_COLUMN:
183 return variable->metadata().value(QStringLiteral("units"));
183 return variable->metadata().value(QStringLiteral("units"));
184 case MISSION_COLUMN:
184 case MISSION_COLUMN:
185 return variable->metadata().value(QStringLiteral("mission"));
185 return variable->metadata().value(QStringLiteral("mission"));
186 case PLUGIN_COLUMN:
186 case PLUGIN_COLUMN:
187 return variable->metadata().value(QStringLiteral("plugin"));
187 return variable->metadata().value(QStringLiteral("plugin"));
188 default:
188 default:
189 // No action
189 // No action
190 break;
190 break;
191 }
191 }
192
192
193 qWarning(LOG_VariableModel())
193 qWarning(LOG_VariableModel())
194 << tr("Can't get data (unknown column %1)").arg(index.column());
194 << tr("Can't get data (unknown column %1)").arg(index.column());
195 }
195 }
196 else {
196 else {
197 qWarning(LOG_VariableModel()) << tr("Can't get data (no variable)");
197 qWarning(LOG_VariableModel()) << tr("Can't get data (no variable)");
198 }
198 }
199 }
199 }
200 else if (role == VariableRoles::ProgressRole) {
200 else if (role == VariableRoles::ProgressRole) {
201 if (auto variable = impl->m_Variables.at(index.row())) {
201 if (auto variable = impl->m_Variables.at(index.row())) {
202
202
203 auto it = impl->m_VariableToProgress.find(variable);
203 auto it = impl->m_VariableToProgress.find(variable);
204 if (it != impl->m_VariableToProgress.cend()) {
204 if (it != impl->m_VariableToProgress.cend()) {
205 return it->second;
205 return it->second;
206 }
206 }
207 }
207 }
208 }
208 }
209
209
210 return QVariant{};
210 return QVariant{};
211 }
211 }
212
212
213 QVariant VariableModel::headerData(int section, Qt::Orientation orientation, int role) const
213 QVariant VariableModel::headerData(int section, Qt::Orientation orientation, int role) const
214 {
214 {
215 if (role != Qt::DisplayRole && role != Qt::SizeHintRole) {
215 if (role != Qt::DisplayRole && role != Qt::SizeHintRole) {
216 return QVariant{};
216 return QVariant{};
217 }
217 }
218
218
219 if (orientation == Qt::Horizontal) {
219 if (orientation == Qt::Horizontal) {
220 auto propertiesIt = COLUMN_PROPERTIES.find(section);
220 auto propertiesIt = COLUMN_PROPERTIES.find(section);
221 if (propertiesIt != COLUMN_PROPERTIES.cend()) {
221 if (propertiesIt != COLUMN_PROPERTIES.cend()) {
222 // Role is either DisplayRole or SizeHintRole
222 // Role is either DisplayRole or SizeHintRole
223 return (role == Qt::DisplayRole)
223 return (role == Qt::DisplayRole)
224 ? QVariant{propertiesIt->m_Name}
224 ? QVariant{propertiesIt->m_Name}
225 : QVariant{QSize{propertiesIt->m_Width, propertiesIt->m_Height}};
225 : QVariant{QSize{propertiesIt->m_Width, propertiesIt->m_Height}};
226 }
226 }
227 else {
227 else {
228 qWarning(LOG_VariableModel())
228 qWarning(LOG_VariableModel())
229 << tr("Can't get header data (unknown column %1)").arg(section);
229 << tr("Can't get header data (unknown column %1)").arg(section);
230 }
230 }
231 }
231 }
232
232
233 return QVariant{};
233 return QVariant{};
234 }
234 }
235
235
236 void VariableModel::abortProgress(const QModelIndex &index)
236 void VariableModel::abortProgress(const QModelIndex &index)
237 {
237 {
238 if (auto variable = impl->m_Variables.at(index.row())) {
238 if (auto variable = impl->m_Variables.at(index.row())) {
239 emit abortProgessRequested(variable);
239 emit abortProgessRequested(variable);
240 }
240 }
241 }
241 }
242
242
243 void VariableModel::onVariableUpdated() noexcept
243 void VariableModel::onVariableUpdated() noexcept
244 {
244 {
245 // Finds variable that has been updated in the model
245 // Finds variable that has been updated in the model
246 if (auto updatedVariable = dynamic_cast<Variable *>(sender())) {
246 if (auto updatedVariable = dynamic_cast<Variable *>(sender())) {
247 auto updatedVariableIndex = impl->indexOfVariable(updatedVariable);
247 auto updatedVariableIndex = impl->indexOfVariable(updatedVariable);
248
248
249 if (updatedVariableIndex > -1) {
249 if (updatedVariableIndex > -1) {
250 emit dataChanged(createIndex(updatedVariableIndex, 0),
250 emit dataChanged(createIndex(updatedVariableIndex, 0),
251 createIndex(updatedVariableIndex, columnCount() - 1));
251 createIndex(updatedVariableIndex, columnCount() - 1));
252 }
252 }
253 }
253 }
254 }
254 }
255
255
256 int VariableModel::VariableModelPrivate::indexOfVariable(Variable *variable) const noexcept
256 int VariableModel::VariableModelPrivate::indexOfVariable(Variable *variable) const noexcept
257 {
257 {
258 auto begin = std::cbegin(m_Variables);
258 auto begin = std::cbegin(m_Variables);
259 auto end = std::cend(m_Variables);
259 auto end = std::cend(m_Variables);
260 auto it
260 auto it
261 = std::find_if(begin, end, [variable](const auto &var) { return var.get() == variable; });
261 = std::find_if(begin, end, [variable](const auto &var) { return var.get() == variable; });
262
262
263 if (it != end) {
263 if (it != end) {
264 // Gets the index of the variable in the model: we assume here that views have the same
264 // Gets the index of the variable in the model: we assume here that views have the same
265 // order as the model
265 // order as the model
266 return std::distance(begin, it);
266 return std::distance(begin, it);
267 }
267 }
268 else {
268 else {
269 return -1;
269 return -1;
270 }
270 }
271 }
271 }
@@ -1,354 +1,520
1 #include "Data/DataSeries.h"
1 #include "Data/DataSeries.h"
2 #include "Data/ScalarSeries.h"
2 #include "Data/ScalarSeries.h"
3 #include "Data/VectorSeries.h"
3
4
4 #include <QObject>
5 #include <QObject>
5 #include <QtTest>
6 #include <QtTest>
6
7
7 Q_DECLARE_METATYPE(std::shared_ptr<ScalarSeries>)
8 Q_DECLARE_METATYPE(std::shared_ptr<ScalarSeries>)
9 Q_DECLARE_METATYPE(std::shared_ptr<VectorSeries>)
8
10
9 class TestDataSeries : public QObject {
11 class TestDataSeries : public QObject {
10 Q_OBJECT
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 private slots:
62 private slots:
12 /// Input test data
63 /// Input test data
13 /// @sa testCtor()
64 /// @sa testCtor()
14 void testCtor_data();
65 void testCtor_data();
15
66
16 /// Tests construction of a data series
67 /// Tests construction of a data series
17 void testCtor();
68 void testCtor();
18
69
19 /// Input test data
70 /// Input test data
20 /// @sa testMerge()
71 /// @sa testMerge()
21 void testMerge_data();
72 void testMerge_data();
22
73
23 /// Tests merge of two data series
74 /// Tests merge of two data series
24 void testMerge();
75 void testMerge();
25
76
26 /// Input test data
77 /// Input test data
27 /// @sa testMinData()
78 /// @sa testMinXAxisData()
28 void testMinData_data();
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
95 /// Tests get x-axis range of a data series
31 void testMinData();
96 void testXAxisRange();
32
97
33 /// Input test data
98 /// Input test data
34 /// @sa testMaxData()
99 /// @sa testValuesBoundsScalar()
35 void testMaxData_data();
100 void testValuesBoundsScalar_data();
36
101
37 /// Tests get max data of a data series
102 /// Tests get values bounds of a scalar series
38 void testMaxData();
103 void testValuesBoundsScalar();
39
104
40 /// Input test data
105 /// Input test data
41 /// @sa testSubdata()
106 /// @sa testValuesBoundsVector()
42 void testSubdata_data();
107 void testValuesBoundsVector_data();
43
108
44 /// Tests get subdata of two data series
109 /// Tests get values bounds of a vector series
45 void testSubdata();
110 void testValuesBoundsVector();
46 };
111 };
47
112
48 void TestDataSeries::testCtor_data()
113 void TestDataSeries::testCtor_data()
49 {
114 {
50 // ////////////// //
115 // ////////////// //
51 // Test structure //
116 // Test structure //
52 // ////////////// //
117 // ////////////// //
53
118
54 // x-axis data
119 // x-axis data
55 QTest::addColumn<QVector<double> >("xAxisData");
120 QTest::addColumn<QVector<double> >("xAxisData");
56 // values data
121 // values data
57 QTest::addColumn<QVector<double> >("valuesData");
122 QTest::addColumn<QVector<double> >("valuesData");
58
123
59 // expected x-axis data
124 // expected x-axis data
60 QTest::addColumn<QVector<double> >("expectedXAxisData");
125 QTest::addColumn<QVector<double> >("expectedXAxisData");
61 // expected values data
126 // expected values data
62 QTest::addColumn<QVector<double> >("expectedValuesData");
127 QTest::addColumn<QVector<double> >("expectedValuesData");
63
128
64 // ////////// //
129 // ////////// //
65 // Test cases //
130 // Test cases //
66 // ////////// //
131 // ////////// //
67
132
68 QTest::newRow("invalidData (different sizes of vectors)")
133 QTest::newRow("invalidData (different sizes of vectors)")
69 << QVector<double>{1., 2., 3., 4., 5.} << QVector<double>{100., 200., 300.}
134 << QVector<double>{1., 2., 3., 4., 5.} << QVector<double>{100., 200., 300.}
70 << QVector<double>{} << QVector<double>{};
135 << QVector<double>{} << QVector<double>{};
71
136
72 QTest::newRow("sortedData") << QVector<double>{1., 2., 3., 4., 5.}
137 QTest::newRow("sortedData") << QVector<double>{1., 2., 3., 4., 5.}
73 << QVector<double>{100., 200., 300., 400., 500.}
138 << QVector<double>{100., 200., 300., 400., 500.}
74 << QVector<double>{1., 2., 3., 4., 5.}
139 << QVector<double>{1., 2., 3., 4., 5.}
75 << QVector<double>{100., 200., 300., 400., 500.};
140 << QVector<double>{100., 200., 300., 400., 500.};
76
141
77 QTest::newRow("unsortedData") << QVector<double>{5., 4., 3., 2., 1.}
142 QTest::newRow("unsortedData") << QVector<double>{5., 4., 3., 2., 1.}
78 << QVector<double>{100., 200., 300., 400., 500.}
143 << QVector<double>{100., 200., 300., 400., 500.}
79 << QVector<double>{1., 2., 3., 4., 5.}
144 << QVector<double>{1., 2., 3., 4., 5.}
80 << QVector<double>{500., 400., 300., 200., 100.};
145 << QVector<double>{500., 400., 300., 200., 100.};
81
146
82 QTest::newRow("unsortedData2")
147 QTest::newRow("unsortedData2")
83 << QVector<double>{1., 4., 3., 5., 2.} << QVector<double>{100., 200., 300., 400., 500.}
148 << QVector<double>{1., 4., 3., 5., 2.} << QVector<double>{100., 200., 300., 400., 500.}
84 << QVector<double>{1., 2., 3., 4., 5.} << QVector<double>{100., 500., 300., 200., 400.};
149 << QVector<double>{1., 2., 3., 4., 5.} << QVector<double>{100., 500., 300., 200., 400.};
85 }
150 }
86
151
87 void TestDataSeries::testCtor()
152 void TestDataSeries::testCtor()
88 {
153 {
89 // Creates series
154 // Creates series
90 QFETCH(QVector<double>, xAxisData);
155 QFETCH(QVector<double>, xAxisData);
91 QFETCH(QVector<double>, valuesData);
156 QFETCH(QVector<double>, valuesData);
92
157
93 auto series = std::make_shared<ScalarSeries>(std::move(xAxisData), std::move(valuesData),
158 auto series = std::make_shared<ScalarSeries>(std::move(xAxisData), std::move(valuesData),
94 Unit{}, Unit{});
159 Unit{}, Unit{});
95
160
96 // Validates results : we check that the data series is sorted on its x-axis data
161 // Validates results : we check that the data series is sorted on its x-axis data
97 QFETCH(QVector<double>, expectedXAxisData);
162 QFETCH(QVector<double>, expectedXAxisData);
98 QFETCH(QVector<double>, expectedValuesData);
163 QFETCH(QVector<double>, expectedValuesData);
99
164
100 auto seriesXAxisData = series->xAxisData()->data();
165 auto seriesXAxisData = series->xAxisData()->data();
101 auto seriesValuesData = series->valuesData()->data();
166 auto seriesValuesData = series->valuesData()->data();
102
167
103 QVERIFY(
168 QVERIFY(
104 std::equal(expectedXAxisData.cbegin(), expectedXAxisData.cend(), seriesXAxisData.cbegin()));
169 std::equal(expectedXAxisData.cbegin(), expectedXAxisData.cend(), seriesXAxisData.cbegin()));
105 QVERIFY(std::equal(expectedValuesData.cbegin(), expectedValuesData.cend(),
170 QVERIFY(std::equal(expectedValuesData.cbegin(), expectedValuesData.cend(),
106 seriesValuesData.cbegin()));
171 seriesValuesData.cbegin()));
107 }
172 }
108
173
109 namespace {
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 return std::make_shared<ScalarSeries>(std::move(xAxisData), std::move(valuesData), Unit{},
179 return std::make_shared<ScalarSeries>(std::move(xAxisData), std::move(valuesData), Unit{},
114 Unit{});
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 } // namespace
193 } // namespace
118
194
119 void TestDataSeries::testMerge_data()
195 void TestDataSeries::testMerge_data()
120 {
196 {
121 // ////////////// //
197 // ////////////// //
122 // Test structure //
198 // Test structure //
123 // ////////////// //
199 // ////////////// //
124
200
125 // Data series to merge
201 // Data series to merge
126 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
202 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
127 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries2");
203 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries2");
128
204
129 // Expected values in the first data series after merge
205 // Expected values in the first data series after merge
130 QTest::addColumn<QVector<double> >("expectedXAxisData");
206 QTest::addColumn<QVector<double> >("expectedXAxisData");
131 QTest::addColumn<QVector<double> >("expectedValuesData");
207 QTest::addColumn<QVector<double> >("expectedValuesData");
132
208
133 // ////////// //
209 // ////////// //
134 // Test cases //
210 // Test cases //
135 // ////////// //
211 // ////////// //
136
212
137 QTest::newRow("sortedMerge")
213 QTest::newRow("sortedMerge")
138 << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
214 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
139 << createSeries({6., 7., 8., 9., 10.}, {600., 700., 800., 900., 1000.})
215 << createScalarSeries({6., 7., 8., 9., 10.}, {600., 700., 800., 900., 1000.})
140 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
216 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
141 << QVector<double>{100., 200., 300., 400., 500., 600., 700., 800., 900., 1000.};
217 << QVector<double>{100., 200., 300., 400., 500., 600., 700., 800., 900., 1000.};
142
218
143 QTest::newRow("unsortedMerge")
219 QTest::newRow("unsortedMerge")
144 << createSeries({6., 7., 8., 9., 10.}, {600., 700., 800., 900., 1000.})
220 << createScalarSeries({6., 7., 8., 9., 10.}, {600., 700., 800., 900., 1000.})
145 << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
221 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
146 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
222 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
147 << QVector<double>{100., 200., 300., 400., 500., 600., 700., 800., 900., 1000.};
223 << QVector<double>{100., 200., 300., 400., 500., 600., 700., 800., 900., 1000.};
148
224
149 QTest::newRow("unsortedMerge2")
225 QTest::newRow("unsortedMerge2")
150 << createSeries({1., 2., 8., 9., 10}, {100., 200., 300., 400., 500.})
226 << createScalarSeries({1., 2., 8., 9., 10}, {100., 200., 300., 400., 500.})
151 << createSeries({3., 4., 5., 6., 7.}, {600., 700., 800., 900., 1000.})
227 << createScalarSeries({3., 4., 5., 6., 7.}, {600., 700., 800., 900., 1000.})
152 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
228 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
153 << QVector<double>{100., 200., 600., 700., 800., 900., 1000., 300., 400., 500.};
229 << QVector<double>{100., 200., 600., 700., 800., 900., 1000., 300., 400., 500.};
154
230
155 QTest::newRow("unsortedMerge3")
231 QTest::newRow("unsortedMerge3")
156 << createSeries({3., 5., 8., 7., 2}, {100., 200., 300., 400., 500.})
232 << createScalarSeries({3., 5., 8., 7., 2}, {100., 200., 300., 400., 500.})
157 << createSeries({6., 4., 9., 10., 1.}, {600., 700., 800., 900., 1000.})
233 << createScalarSeries({6., 4., 9., 10., 1.}, {600., 700., 800., 900., 1000.})
158 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
234 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
159 << QVector<double>{1000., 500., 100., 700., 200., 600., 400., 300., 800., 900.};
235 << QVector<double>{1000., 500., 100., 700., 200., 600., 400., 300., 800., 900.};
160 }
236 }
161
237
162 void TestDataSeries::testMerge()
238 void TestDataSeries::testMerge()
163 {
239 {
164 // Merges series
240 // Merges series
165 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
241 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
166 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries2);
242 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries2);
167
243
168 dataSeries->merge(dataSeries2.get());
244 dataSeries->merge(dataSeries2.get());
169
245
170 // Validates results : we check that the merge is valid and the data series is sorted on its
246 // Validates results : we check that the merge is valid and the data series is sorted on its
171 // x-axis data
247 // x-axis data
172 QFETCH(QVector<double>, expectedXAxisData);
248 QFETCH(QVector<double>, expectedXAxisData);
173 QFETCH(QVector<double>, expectedValuesData);
249 QFETCH(QVector<double>, expectedValuesData);
174
250
175 auto seriesXAxisData = dataSeries->xAxisData()->data();
251 auto seriesXAxisData = dataSeries->xAxisData()->data();
176 auto seriesValuesData = dataSeries->valuesData()->data();
252 auto seriesValuesData = dataSeries->valuesData()->data();
177
253
178 QVERIFY(
254 QVERIFY(
179 std::equal(expectedXAxisData.cbegin(), expectedXAxisData.cend(), seriesXAxisData.cbegin()));
255 std::equal(expectedXAxisData.cbegin(), expectedXAxisData.cend(), seriesXAxisData.cbegin()));
180 QVERIFY(std::equal(expectedValuesData.cbegin(), expectedValuesData.cend(),
256 QVERIFY(std::equal(expectedValuesData.cbegin(), expectedValuesData.cend(),
181 seriesValuesData.cbegin()));
257 seriesValuesData.cbegin()));
182 }
258 }
183
259
184 void TestDataSeries::testMinData_data()
260 void TestDataSeries::testMinXAxisData_data()
185 {
261 {
186 // ////////////// //
262 // ////////////// //
187 // Test structure //
263 // Test structure //
188 // ////////////// //
264 // ////////////// //
189
265
190 // Data series to get min data
266 // Data series to get min data
191 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
267 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
192
268
193 // Min data
269 // Min data
194 QTest::addColumn<double>("min");
270 QTest::addColumn<double>("min");
195
271
196 // Expected results
272 // Expected results
197 QTest::addColumn<bool>(
273 QTest::addColumn<bool>(
198 "expectedOK"); // if true, expects to have a result (i.e. the iterator != end iterator)
274 "expectedOK"); // if true, expects to have a result (i.e. the iterator != end iterator)
199 QTest::addColumn<double>(
275 QTest::addColumn<double>(
200 "expectedMin"); // Expected value when method doesn't return end iterator
276 "expectedMin"); // Expected value when method doesn't return end iterator
201
277
202 // ////////// //
278 // ////////// //
203 // Test cases //
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 << 0. << true << 1.;
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 << 1. << true << 1.;
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 << 1.1 << true << 2.;
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 << 5. << true << 5.;
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 << 5.1 << false << std::numeric_limits<double>::quiet_NaN();
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 << std::numeric_limits<double>::quiet_NaN();
298 << std::numeric_limits<double>::quiet_NaN();
218 }
299 }
219
300
220 void TestDataSeries::testMinData()
301 void TestDataSeries::testMinXAxisData()
221 {
302 {
222 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
303 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
223 QFETCH(double, min);
304 QFETCH(double, min);
224
305
225 QFETCH(bool, expectedOK);
306 QFETCH(bool, expectedOK);
226 QFETCH(double, expectedMin);
307 QFETCH(double, expectedMin);
227
308
228 auto it = dataSeries->minData(min);
309 auto it = dataSeries->minXAxisData(min);
229
310
230 QCOMPARE(expectedOK, it != dataSeries->cend());
311 QCOMPARE(expectedOK, it != dataSeries->cend());
231
312
232 // If the method doesn't return a end iterator, checks with expected value
313 // If the method doesn't return a end iterator, checks with expected value
233 if (expectedOK) {
314 if (expectedOK) {
234 QCOMPARE(expectedMin, it->x());
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 // Test structure //
322 // Test structure //
242 // ////////////// //
323 // ////////////// //
243
324
244 // Data series to get max data
325 // Data series to get max data
245 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
326 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
246
327
247 // Max data
328 // Max data
248 QTest::addColumn<double>("max");
329 QTest::addColumn<double>("max");
249
330
250 // Expected results
331 // Expected results
251 QTest::addColumn<bool>(
332 QTest::addColumn<bool>(
252 "expectedOK"); // if true, expects to have a result (i.e. the iterator != end iterator)
333 "expectedOK"); // if true, expects to have a result (i.e. the iterator != end iterator)
253 QTest::addColumn<double>(
334 QTest::addColumn<double>(
254 "expectedMax"); // Expected value when method doesn't return end iterator
335 "expectedMax"); // Expected value when method doesn't return end iterator
255
336
256 // ////////// //
337 // ////////// //
257 // Test cases //
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 << 6. << true << 5.;
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 << 5. << true << 5.;
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 << 4.9 << true << 4.;
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 << 1.1 << true << 1.;
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 << 1. << true << 1.;
355 << 1. << true << 1.;
270 QTest::newRow("maxData6") << createSeries({}, {}) << 1.1 << false
356 QTest::newRow("maxData6") << createScalarSeries({}, {}) << 1.1 << false
271 << std::numeric_limits<double>::quiet_NaN();
357 << std::numeric_limits<double>::quiet_NaN();
272 }
358 }
273
359
274 void TestDataSeries::testMaxData()
360 void TestDataSeries::testMaxXAxisData()
275 {
361 {
276 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
362 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
277 QFETCH(double, max);
363 QFETCH(double, max);
278
364
279 QFETCH(bool, expectedOK);
365 QFETCH(bool, expectedOK);
280 QFETCH(double, expectedMax);
366 QFETCH(double, expectedMax);
281
367
282 auto it = dataSeries->maxData(max);
368 auto it = dataSeries->maxXAxisData(max);
283
369
284 QCOMPARE(expectedOK, it != dataSeries->cend());
370 QCOMPARE(expectedOK, it != dataSeries->cend());
285
371
286 // If the method doesn't return a end iterator, checks with expected value
372 // If the method doesn't return a end iterator, checks with expected value
287 if (expectedOK) {
373 if (expectedOK) {
288 QCOMPARE(expectedMax, it->x());
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 // Test structure //
381 // Test structure //
296 // ////////////// //
382 // ////////////// //
297
383
298 // Data series to get subdata
384 // Data series to get x-axis range
299 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
385 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
300
386
301 // Min/max values
387 // Min/max values
302 QTest::addColumn<double>("min");
388 QTest::addColumn<double>("min");
303 QTest::addColumn<double>("max");
389 QTest::addColumn<double>("max");
304
390
305 // Expected values after subdata
391 // Expected values
306 QTest::addColumn<QVector<double> >("expectedXAxisData");
392 QTest::addColumn<QVector<double> >("expectedXAxisData");
307 QTest::addColumn<QVector<double> >("expectedValuesData");
393 QTest::addColumn<QVector<double> >("expectedValuesData");
308
394
309 // ////////// //
395 // ////////// //
310 // Test cases //
396 // Test cases //
311 // ////////// //
397 // ////////// //
312
398
313 QTest::newRow("subData1") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
399 QTest::newRow("xAxisRange1") << createScalarSeries({1., 2., 3., 4., 5.},
314 << -1. << 3.2 << QVector<double>{1., 2., 3.}
400 {100., 200., 300., 400., 500.})
315 << QVector<double>{100., 200., 300.};
401 << -1. << 3.2 << QVector<double>{1., 2., 3.}
316 QTest::newRow("subData2") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
402 << QVector<double>{100., 200., 300.};
317 << 1. << 4. << QVector<double>{1., 2., 3., 4.}
403 QTest::newRow("xAxisRange2") << createScalarSeries({1., 2., 3., 4., 5.},
318 << QVector<double>{100., 200., 300., 400.};
404 {100., 200., 300., 400., 500.})
319 QTest::newRow("subData3") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
405 << 1. << 4. << QVector<double>{1., 2., 3., 4.}
320 << 1. << 3.9 << QVector<double>{1., 2., 3.}
406 << QVector<double>{100., 200., 300., 400.};
321 << QVector<double>{100., 200., 300.};
407 QTest::newRow("xAxisRange3") << createScalarSeries({1., 2., 3., 4., 5.},
322 QTest::newRow("subData4") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
408 {100., 200., 300., 400., 500.})
323 << 0. << 0.9 << QVector<double>{} << QVector<double>{};
409 << 1. << 3.9 << QVector<double>{1., 2., 3.}
324 QTest::newRow("subData5") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
410 << QVector<double>{100., 200., 300.};
325 << 0. << 1. << QVector<double>{1.} << QVector<double>{100.};
411 QTest::newRow("xAxisRange4") << createScalarSeries({1., 2., 3., 4., 5.},
326 QTest::newRow("subData6") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
412 {100., 200., 300., 400., 500.})
327 << 2.1 << 6. << QVector<double>{3., 4., 5.}
413 << 0. << 0.9 << QVector<double>{} << QVector<double>{};
328 << QVector<double>{300., 400., 500.};
414 QTest::newRow("xAxisRange5") << createScalarSeries({1., 2., 3., 4., 5.},
329 QTest::newRow("subData7") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
415 {100., 200., 300., 400., 500.})
330 << 6. << 9. << QVector<double>{} << QVector<double>{};
416 << 0. << 1. << QVector<double>{1.} << QVector<double>{100.};
331 QTest::newRow("subData8") << createSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
417 QTest::newRow("xAxisRange6") << createScalarSeries({1., 2., 3., 4., 5.},
332 << 5. << 9. << QVector<double>{5.} << QVector<double>{500.};
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 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
431 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
338 QFETCH(double, min);
432 QFETCH(double, min);
339 QFETCH(double, max);
433 QFETCH(double, max);
340
434
341 QFETCH(QVector<double>, expectedXAxisData);
435 QFETCH(QVector<double>, expectedXAxisData);
342 QFETCH(QVector<double>, expectedValuesData);
436 QFETCH(QVector<double>, expectedValuesData);
343
437
344 auto bounds = dataSeries->subData(min, max);
438 auto bounds = dataSeries->xAxisRange(min, max);
345 QVERIFY(std::equal(bounds.first, bounds.second, expectedXAxisData.cbegin(),
439 QVERIFY(std::equal(bounds.first, bounds.second, expectedXAxisData.cbegin(),
346 expectedXAxisData.cend(),
440 expectedXAxisData.cend(),
347 [](const auto &it, const auto &expectedX) { return it.x() == expectedX; }));
441 [](const auto &it, const auto &expectedX) { return it.x() == expectedX; }));
348 QVERIFY(std::equal(
442 QVERIFY(std::equal(
349 bounds.first, bounds.second, expectedValuesData.cbegin(), expectedValuesData.cend(),
443 bounds.first, bounds.second, expectedValuesData.cbegin(), expectedValuesData.cend(),
350 [](const auto &it, const auto &expectedVal) { return it.value() == expectedVal; }));
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 QTEST_MAIN(TestDataSeries)
519 QTEST_MAIN(TestDataSeries)
354 #include "TestDataSeries.moc"
520 #include "TestDataSeries.moc"
@@ -1,243 +1,243
1 #include "Visualization/VisualizationGraphHelper.h"
1 #include "Visualization/VisualizationGraphHelper.h"
2 #include "Visualization/qcustomplot.h"
2 #include "Visualization/qcustomplot.h"
3
3
4 #include <Common/ColorUtils.h>
4 #include <Common/ColorUtils.h>
5
5
6 #include <Data/ScalarSeries.h>
6 #include <Data/ScalarSeries.h>
7 #include <Data/VectorSeries.h>
7 #include <Data/VectorSeries.h>
8
8
9 #include <Variable/Variable.h>
9 #include <Variable/Variable.h>
10
10
11 Q_LOGGING_CATEGORY(LOG_VisualizationGraphHelper, "VisualizationGraphHelper")
11 Q_LOGGING_CATEGORY(LOG_VisualizationGraphHelper, "VisualizationGraphHelper")
12
12
13 namespace {
13 namespace {
14
14
15 class SqpDataContainer : public QCPGraphDataContainer {
15 class SqpDataContainer : public QCPGraphDataContainer {
16 public:
16 public:
17 void appendGraphData(const QCPGraphData &data) { mData.append(data); }
17 void appendGraphData(const QCPGraphData &data) { mData.append(data); }
18 };
18 };
19
19
20
20
21 /// Format for datetimes on a axis
21 /// Format for datetimes on a axis
22 const auto DATETIME_TICKER_FORMAT = QStringLiteral("yyyy/MM/dd \nhh:mm:ss");
22 const auto DATETIME_TICKER_FORMAT = QStringLiteral("yyyy/MM/dd \nhh:mm:ss");
23
23
24 /// Generates the appropriate ticker for an axis, depending on whether the axis displays time or
24 /// Generates the appropriate ticker for an axis, depending on whether the axis displays time or
25 /// non-time data
25 /// non-time data
26 QSharedPointer<QCPAxisTicker> axisTicker(bool isTimeAxis)
26 QSharedPointer<QCPAxisTicker> axisTicker(bool isTimeAxis)
27 {
27 {
28 if (isTimeAxis) {
28 if (isTimeAxis) {
29 auto dateTicker = QSharedPointer<QCPAxisTickerDateTime>::create();
29 auto dateTicker = QSharedPointer<QCPAxisTickerDateTime>::create();
30 dateTicker->setDateTimeFormat(DATETIME_TICKER_FORMAT);
30 dateTicker->setDateTimeFormat(DATETIME_TICKER_FORMAT);
31 dateTicker->setDateTimeSpec(Qt::UTC);
31 dateTicker->setDateTimeSpec(Qt::UTC);
32
32
33 return dateTicker;
33 return dateTicker;
34 }
34 }
35 else {
35 else {
36 // default ticker
36 // default ticker
37 return QSharedPointer<QCPAxisTicker>::create();
37 return QSharedPointer<QCPAxisTicker>::create();
38 }
38 }
39 }
39 }
40
40
41 /// Sets axes properties according to the properties of a data series
41 /// Sets axes properties according to the properties of a data series
42 template <int Dim>
42 template <int Dim>
43 void setAxesProperties(const DataSeries<Dim> &dataSeries, QCustomPlot &plot) noexcept
43 void setAxesProperties(const DataSeries<Dim> &dataSeries, QCustomPlot &plot) noexcept
44 {
44 {
45 /// @todo : for the moment, no control is performed on the axes: the units and the tickers
45 /// @todo : for the moment, no control is performed on the axes: the units and the tickers
46 /// are fixed for the default x-axis and y-axis of the plot, and according to the new graph
46 /// are fixed for the default x-axis and y-axis of the plot, and according to the new graph
47 auto setAxisProperties = [](auto axis, const auto &unit) {
47 auto setAxisProperties = [](auto axis, const auto &unit) {
48 // label (unit name)
48 // label (unit name)
49 axis->setLabel(unit.m_Name);
49 axis->setLabel(unit.m_Name);
50
50
51 // ticker (depending on the type of unit)
51 // ticker (depending on the type of unit)
52 axis->setTicker(axisTicker(unit.m_TimeUnit));
52 axis->setTicker(axisTicker(unit.m_TimeUnit));
53 };
53 };
54 setAxisProperties(plot.xAxis, dataSeries.xAxisUnit());
54 setAxisProperties(plot.xAxis, dataSeries.xAxisUnit());
55 setAxisProperties(plot.yAxis, dataSeries.valuesUnit());
55 setAxisProperties(plot.yAxis, dataSeries.valuesUnit());
56 }
56 }
57
57
58 /**
58 /**
59 * Struct used to create plottables, depending on the type of the data series from which to create
59 * Struct used to create plottables, depending on the type of the data series from which to create
60 * them
60 * them
61 * @tparam T the data series' type
61 * @tparam T the data series' type
62 * @remarks Default implementation can't create plottables
62 * @remarks Default implementation can't create plottables
63 */
63 */
64 template <typename T, typename Enabled = void>
64 template <typename T, typename Enabled = void>
65 struct PlottablesCreator {
65 struct PlottablesCreator {
66 static PlottablesMap createPlottables(T &, QCustomPlot &)
66 static PlottablesMap createPlottables(T &, QCustomPlot &)
67 {
67 {
68 qCCritical(LOG_DataSeries())
68 qCCritical(LOG_DataSeries())
69 << QObject::tr("Can't create plottables: unmanaged data series type");
69 << QObject::tr("Can't create plottables: unmanaged data series type");
70 return {};
70 return {};
71 }
71 }
72 };
72 };
73
73
74 /**
74 /**
75 * Specialization of PlottablesCreator for scalars and vectors
75 * Specialization of PlottablesCreator for scalars and vectors
76 * @sa ScalarSeries
76 * @sa ScalarSeries
77 * @sa VectorSeries
77 * @sa VectorSeries
78 */
78 */
79 template <typename T>
79 template <typename T>
80 struct PlottablesCreator<T,
80 struct PlottablesCreator<T,
81 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
81 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
82 or std::is_base_of<VectorSeries, T>::value> > {
82 or std::is_base_of<VectorSeries, T>::value> > {
83 static PlottablesMap createPlottables(T &dataSeries, QCustomPlot &plot)
83 static PlottablesMap createPlottables(T &dataSeries, QCustomPlot &plot)
84 {
84 {
85 PlottablesMap result{};
85 PlottablesMap result{};
86
86
87 // Gets the number of components of the data series
87 // Gets the number of components of the data series
88 auto componentCount = dataSeries.valuesData()->componentCount();
88 auto componentCount = dataSeries.valuesData()->componentCount();
89
89
90 auto colors = ColorUtils::colors(Qt::blue, Qt::red, componentCount);
90 auto colors = ColorUtils::colors(Qt::blue, Qt::red, componentCount);
91
91
92 // For each component of the data series, creates a QCPGraph to add to the plot
92 // For each component of the data series, creates a QCPGraph to add to the plot
93 for (auto i = 0; i < componentCount; ++i) {
93 for (auto i = 0; i < componentCount; ++i) {
94 auto graph = plot.addGraph();
94 auto graph = plot.addGraph();
95 graph->setPen(QPen{colors.at(i)});
95 graph->setPen(QPen{colors.at(i)});
96
96
97 result.insert({i, graph});
97 result.insert({i, graph});
98 }
98 }
99
99
100 // Axes properties
100 // Axes properties
101 setAxesProperties(dataSeries, plot);
101 setAxesProperties(dataSeries, plot);
102
102
103 plot.replot();
103 plot.replot();
104
104
105 return result;
105 return result;
106 }
106 }
107 };
107 };
108
108
109 /**
109 /**
110 * Struct used to update plottables, depending on the type of the data series from which to update
110 * Struct used to update plottables, depending on the type of the data series from which to update
111 * them
111 * them
112 * @tparam T the data series' type
112 * @tparam T the data series' type
113 * @remarks Default implementation can't update plottables
113 * @remarks Default implementation can't update plottables
114 */
114 */
115 template <typename T, typename Enabled = void>
115 template <typename T, typename Enabled = void>
116 struct PlottablesUpdater {
116 struct PlottablesUpdater {
117 static void updatePlottables(T &, PlottablesMap &, const SqpRange &, bool)
117 static void updatePlottables(T &, PlottablesMap &, const SqpRange &, bool)
118 {
118 {
119 qCCritical(LOG_DataSeries())
119 qCCritical(LOG_DataSeries())
120 << QObject::tr("Can't update plottables: unmanaged data series type");
120 << QObject::tr("Can't update plottables: unmanaged data series type");
121 }
121 }
122 };
122 };
123
123
124 /**
124 /**
125 * Specialization of PlottablesUpdater for scalars and vectors
125 * Specialization of PlottablesUpdater for scalars and vectors
126 * @sa ScalarSeries
126 * @sa ScalarSeries
127 * @sa VectorSeries
127 * @sa VectorSeries
128 */
128 */
129 template <typename T>
129 template <typename T>
130 struct PlottablesUpdater<T,
130 struct PlottablesUpdater<T,
131 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
131 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
132 or std::is_base_of<VectorSeries, T>::value> > {
132 or std::is_base_of<VectorSeries, T>::value> > {
133 static void updatePlottables(T &dataSeries, PlottablesMap &plottables, const SqpRange &range,
133 static void updatePlottables(T &dataSeries, PlottablesMap &plottables, const SqpRange &range,
134 bool rescaleAxes)
134 bool rescaleAxes)
135 {
135 {
136 dataSeries.lockRead();
136 dataSeries.lockRead();
137
137
138 // For each plottable to update, resets its data
138 // For each plottable to update, resets its data
139 std::map<int, QSharedPointer<SqpDataContainer> > dataContainers{};
139 std::map<int, QSharedPointer<SqpDataContainer> > dataContainers{};
140 for (const auto &plottable : plottables) {
140 for (const auto &plottable : plottables) {
141 if (auto graph = dynamic_cast<QCPGraph *>(plottable.second)) {
141 if (auto graph = dynamic_cast<QCPGraph *>(plottable.second)) {
142 auto dataContainer = QSharedPointer<SqpDataContainer>::create();
142 auto dataContainer = QSharedPointer<SqpDataContainer>::create();
143 graph->setData(dataContainer);
143 graph->setData(dataContainer);
144
144
145 dataContainers.insert({plottable.first, dataContainer});
145 dataContainers.insert({plottable.first, dataContainer});
146 }
146 }
147 }
147 }
148
148
149 // - Gets the data of the series included in the current range
149 // - Gets the data of the series included in the current range
150 // - Updates each plottable by adding, for each data item, a point that takes x-axis data
150 // - Updates each plottable by adding, for each data item, a point that takes x-axis data
151 // and value data. The correct value is retrieved according to the index of the component
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 for (auto it = subDataIts.first; it != subDataIts.second; ++it) {
153 for (auto it = subDataIts.first; it != subDataIts.second; ++it) {
154 for (const auto &dataContainer : dataContainers) {
154 for (const auto &dataContainer : dataContainers) {
155 auto componentIndex = dataContainer.first;
155 auto componentIndex = dataContainer.first;
156 dataContainer.second->appendGraphData(
156 dataContainer.second->appendGraphData(
157 QCPGraphData(it->x(), it->value(componentIndex)));
157 QCPGraphData(it->x(), it->value(componentIndex)));
158 }
158 }
159 }
159 }
160
160
161 dataSeries.unlock();
161 dataSeries.unlock();
162
162
163 if (!plottables.empty()) {
163 if (!plottables.empty()) {
164 auto plot = plottables.begin()->second->parentPlot();
164 auto plot = plottables.begin()->second->parentPlot();
165
165
166 if (rescaleAxes) {
166 if (rescaleAxes) {
167 plot->rescaleAxes();
167 plot->rescaleAxes();
168 }
168 }
169
169
170 plot->replot();
170 plot->replot();
171 }
171 }
172 }
172 }
173 };
173 };
174
174
175 /**
175 /**
176 * Helper used to create/update plottables
176 * Helper used to create/update plottables
177 */
177 */
178 struct IPlottablesHelper {
178 struct IPlottablesHelper {
179 virtual ~IPlottablesHelper() noexcept = default;
179 virtual ~IPlottablesHelper() noexcept = default;
180 virtual PlottablesMap create(QCustomPlot &plot) const = 0;
180 virtual PlottablesMap create(QCustomPlot &plot) const = 0;
181 virtual void update(PlottablesMap &plottables, const SqpRange &range,
181 virtual void update(PlottablesMap &plottables, const SqpRange &range,
182 bool rescaleAxes = false) const = 0;
182 bool rescaleAxes = false) const = 0;
183 };
183 };
184
184
185 /**
185 /**
186 * Default implementation of IPlottablesHelper, which takes data series to create/update plottables
186 * Default implementation of IPlottablesHelper, which takes data series to create/update plottables
187 * @tparam T the data series' type
187 * @tparam T the data series' type
188 */
188 */
189 template <typename T>
189 template <typename T>
190 struct PlottablesHelper : public IPlottablesHelper {
190 struct PlottablesHelper : public IPlottablesHelper {
191 explicit PlottablesHelper(T &dataSeries) : m_DataSeries{dataSeries} {}
191 explicit PlottablesHelper(T &dataSeries) : m_DataSeries{dataSeries} {}
192
192
193 PlottablesMap create(QCustomPlot &plot) const override
193 PlottablesMap create(QCustomPlot &plot) const override
194 {
194 {
195 return PlottablesCreator<T>::createPlottables(m_DataSeries, plot);
195 return PlottablesCreator<T>::createPlottables(m_DataSeries, plot);
196 }
196 }
197
197
198 void update(PlottablesMap &plottables, const SqpRange &range, bool rescaleAxes) const override
198 void update(PlottablesMap &plottables, const SqpRange &range, bool rescaleAxes) const override
199 {
199 {
200 PlottablesUpdater<T>::updatePlottables(m_DataSeries, plottables, range, rescaleAxes);
200 PlottablesUpdater<T>::updatePlottables(m_DataSeries, plottables, range, rescaleAxes);
201 }
201 }
202
202
203 T &m_DataSeries;
203 T &m_DataSeries;
204 };
204 };
205
205
206 /// Creates IPlottablesHelper according to a data series
206 /// Creates IPlottablesHelper according to a data series
207 std::unique_ptr<IPlottablesHelper> createHelper(std::shared_ptr<IDataSeries> dataSeries) noexcept
207 std::unique_ptr<IPlottablesHelper> createHelper(std::shared_ptr<IDataSeries> dataSeries) noexcept
208 {
208 {
209 if (auto scalarSeries = std::dynamic_pointer_cast<ScalarSeries>(dataSeries)) {
209 if (auto scalarSeries = std::dynamic_pointer_cast<ScalarSeries>(dataSeries)) {
210 return std::make_unique<PlottablesHelper<ScalarSeries> >(*scalarSeries);
210 return std::make_unique<PlottablesHelper<ScalarSeries> >(*scalarSeries);
211 }
211 }
212 else if (auto vectorSeries = std::dynamic_pointer_cast<VectorSeries>(dataSeries)) {
212 else if (auto vectorSeries = std::dynamic_pointer_cast<VectorSeries>(dataSeries)) {
213 return std::make_unique<PlottablesHelper<VectorSeries> >(*vectorSeries);
213 return std::make_unique<PlottablesHelper<VectorSeries> >(*vectorSeries);
214 }
214 }
215 else {
215 else {
216 return std::make_unique<PlottablesHelper<IDataSeries> >(*dataSeries);
216 return std::make_unique<PlottablesHelper<IDataSeries> >(*dataSeries);
217 }
217 }
218 }
218 }
219
219
220 } // namespace
220 } // namespace
221
221
222 PlottablesMap VisualizationGraphHelper::create(std::shared_ptr<Variable> variable,
222 PlottablesMap VisualizationGraphHelper::create(std::shared_ptr<Variable> variable,
223 QCustomPlot &plot) noexcept
223 QCustomPlot &plot) noexcept
224 {
224 {
225 if (variable) {
225 if (variable) {
226 auto helper = createHelper(variable->dataSeries());
226 auto helper = createHelper(variable->dataSeries());
227 auto plottables = helper->create(plot);
227 auto plottables = helper->create(plot);
228 return plottables;
228 return plottables;
229 }
229 }
230 else {
230 else {
231 qCDebug(LOG_VisualizationGraphHelper())
231 qCDebug(LOG_VisualizationGraphHelper())
232 << QObject::tr("Can't create graph plottables : the variable is null");
232 << QObject::tr("Can't create graph plottables : the variable is null");
233 return PlottablesMap{};
233 return PlottablesMap{};
234 }
234 }
235 }
235 }
236
236
237 void VisualizationGraphHelper::updateData(PlottablesMap &plottables,
237 void VisualizationGraphHelper::updateData(PlottablesMap &plottables,
238 std::shared_ptr<IDataSeries> dataSeries,
238 std::shared_ptr<IDataSeries> dataSeries,
239 const SqpRange &dateTime)
239 const SqpRange &dateTime)
240 {
240 {
241 auto helper = createHelper(dataSeries);
241 auto helper = createHelper(dataSeries);
242 helper->update(plottables, dateTime);
242 helper->update(plottables, dateTime);
243 }
243 }
General Comments 0
You need to be logged in to leave comments. Login now