##// END OF EJS Templates
Merge pull request 233 from SCIQLOP-Initialisation develop...
leroux -
r619:3ec53f32541f merge
parent child
Show More
@@ -1,116 +1,117
1 1 #ifndef SCIQLOP_SORTUTILS_H
2 2 #define SCIQLOP_SORTUTILS_H
3 3
4 4 #include <algorithm>
5 #include <cmath>
5 6 #include <numeric>
6 7 #include <vector>
7 8
8 9 /**
9 10 * Utility class with methods for sorting data
10 11 */
11 12 struct SortUtils {
12 13 /**
13 14 * Generates a vector representing the index of insertion of each data of a container if this
14 15 * one had to be sorted according to a comparison function.
15 16 *
16 17 * For example:
17 18 * If the container is a vector {1; 4; 2; 5; 3} and the comparison function is std::less, the
18 19 * result would be : {0; 3; 1; 4; 2}
19 20 *
20 21 * @tparam Container the type of the container.
21 22 * @tparam Compare the type of the comparison function
22 23 * @param container the container from which to generate the result. The container must have a
23 24 * at() method that returns a value associated to an index
24 25 * @param compare the comparison function
25 26 */
26 27 template <typename Container, typename Compare>
27 28 static std::vector<int> sortPermutation(const Container &container, const Compare &compare)
28 29 {
29 30 auto permutation = std::vector<int>{};
30 31 permutation.resize(container.size());
31 32
32 33 std::iota(permutation.begin(), permutation.end(), 0);
33 34 std::sort(permutation.begin(), permutation.end(),
34 35 [&](int i, int j) { return compare(container.at(i), container.at(j)); });
35 36 return permutation;
36 37 }
37 38
38 39 /**
39 40 * Sorts a container according to indices passed in parameter
40 41 * @param container the container sorted
41 42 * @param sortPermutation the indices used to sort the container
42 43 * @return the container sorted
43 44 * @warning no verification is made on validity of sortPermutation (i.e. the vector has unique
44 45 * indices and its range is [0 ; vector.size()[ )
45 46 */
46 47 template <typename Container>
47 48 static Container sort(const Container &container, const std::vector<int> &sortPermutation)
48 49 {
49 50 if (container.size() != sortPermutation.size()) {
50 51 return Container{};
51 52 }
52 53
53 54 // Inits result
54 55 auto sortedData = Container{};
55 56 sortedData.resize(container.size());
56 57
57 58 std::transform(sortPermutation.cbegin(), sortPermutation.cend(), sortedData.begin(),
58 59 [&container](int i) { return container.at(i); });
59 60
60 61 return sortedData;
61 62 }
62 63
63 64 /**
64 65 * Compares two values that can be NaN. This method is intended to be used as a compare function
65 66 * for searching min value by excluding NaN values.
66 67 *
67 68 * Examples of use:
68 69 * - f({1, 3, 2, 4, 5}) will return 1
69 70 * - f({NaN, 3, 2, 4, 5}) will return 2 (NaN is excluded)
70 71 * - f({NaN, NaN, 3, NaN, NaN}) will return 3 (NaN are excluded)
71 72 * - f({NaN, NaN, NaN, NaN, NaN}) will return NaN (no existing value)
72 73 *
73 74 * @param v1 first value
74 75 * @param v2 second value
75 76 * @return true if v1 < v2, false otherwise
76 77 * @sa std::min_element
77 78 */
78 79 template <typename T>
79 80 static bool minCompareWithNaN(const T &v1, const T &v2)
80 81 {
81 82 // Table used with NaN values:
82 83 // NaN < v2 -> false
83 84 // v1 < NaN -> true
84 85 // NaN < NaN -> false
85 86 // v1 < v2 -> v1 < v2
86 87 return std::isnan(v1) ? false : std::isnan(v2) || (v1 < v2);
87 88 }
88 89
89 90 /**
90 91 * Compares two values that can be NaN. This method is intended to be used as a compare function
91 92 * for searching max value by excluding NaN values.
92 93 *
93 94 * Examples of use:
94 95 * - f({1, 3, 2, 4, 5}) will return 5
95 96 * - f({1, 3, 2, 4, NaN}) will return 4 (NaN is excluded)
96 97 * - f({NaN, NaN, 3, NaN, NaN}) will return 3 (NaN are excluded)
97 98 * - f({NaN, NaN, NaN, NaN, NaN}) will return NaN (no existing value)
98 99 *
99 100 * @param v1 first value
100 101 * @param v2 second value
101 102 * @return true if v1 < v2, false otherwise
102 103 * @sa std::max_element
103 104 */
104 105 template <typename T>
105 106 static bool maxCompareWithNaN(const T &v1, const T &v2)
106 107 {
107 108 // Table used with NaN values:
108 109 // NaN < v2 -> true
109 110 // v1 < NaN -> false
110 111 // NaN < NaN -> false
111 112 // v1 < v2 -> v1 < v2
112 113 return std::isnan(v1) ? true : !std::isnan(v2) && (v1 < v2);
113 114 }
114 115 };
115 116
116 117 #endif // SCIQLOP_SORTUTILS_H
@@ -1,520 +1,522
1 1 #include "Data/DataSeries.h"
2 2 #include "Data/ScalarSeries.h"
3 3 #include "Data/VectorSeries.h"
4 4
5 #include <cmath>
6
5 7 #include <QObject>
6 8 #include <QtTest>
7 9
8 10 Q_DECLARE_METATYPE(std::shared_ptr<ScalarSeries>)
9 11 Q_DECLARE_METATYPE(std::shared_ptr<VectorSeries>)
10 12
11 13 class TestDataSeries : public QObject {
12 14 Q_OBJECT
13 15 private:
14 16 template <typename T>
15 17 void testValuesBoundsStructure()
16 18 {
17 19 // ////////////// //
18 20 // Test structure //
19 21 // ////////////// //
20 22
21 23 // Data series to get values bounds
22 24 QTest::addColumn<std::shared_ptr<T> >("dataSeries");
23 25
24 26 // x-axis range
25 27 QTest::addColumn<double>("minXAxis");
26 28 QTest::addColumn<double>("maxXAxis");
27 29
28 30 // Expected results
29 31 QTest::addColumn<bool>(
30 32 "expectedOK"); // Test is expected to be ok (i.e. method doesn't return end iterators)
31 33 QTest::addColumn<double>("expectedMinValue");
32 34 QTest::addColumn<double>("expectedMaxValue");
33 35 }
34 36
35 37 template <typename T>
36 38 void testValuesBounds()
37 39 {
38 40 QFETCH(std::shared_ptr<T>, dataSeries);
39 41 QFETCH(double, minXAxis);
40 42 QFETCH(double, maxXAxis);
41 43
42 44 QFETCH(bool, expectedOK);
43 45 QFETCH(double, expectedMinValue);
44 46 QFETCH(double, expectedMaxValue);
45 47
46 48 auto minMaxIts = dataSeries->valuesBounds(minXAxis, maxXAxis);
47 49 auto end = dataSeries->cend();
48 50
49 51 // Checks iterators with expected result
50 52 QCOMPARE(expectedOK, minMaxIts.first != end && minMaxIts.second != end);
51 53
52 54 if (expectedOK) {
53 55 auto compare = [](const auto &v1, const auto &v2) {
54 56 return (std::isnan(v1) && std::isnan(v2)) || v1 == v2;
55 57 };
56 58
57 59 QVERIFY(compare(expectedMinValue, minMaxIts.first->minValue()));
58 60 QVERIFY(compare(expectedMaxValue, minMaxIts.second->maxValue()));
59 61 }
60 62 }
61 63
62 64 private slots:
63 65 /// Input test data
64 66 /// @sa testCtor()
65 67 void testCtor_data();
66 68
67 69 /// Tests construction of a data series
68 70 void testCtor();
69 71
70 72 /// Input test data
71 73 /// @sa testMerge()
72 74 void testMerge_data();
73 75
74 76 /// Tests merge of two data series
75 77 void testMerge();
76 78
77 79 /// Input test data
78 80 /// @sa testMinXAxisData()
79 81 void testMinXAxisData_data();
80 82
81 83 /// Tests get min x-axis data of a data series
82 84 void testMinXAxisData();
83 85
84 86 /// Input test data
85 87 /// @sa testMaxXAxisData()
86 88 void testMaxXAxisData_data();
87 89
88 90 /// Tests get max x-axis data of a data series
89 91 void testMaxXAxisData();
90 92
91 93 /// Input test data
92 94 /// @sa testXAxisRange()
93 95 void testXAxisRange_data();
94 96
95 97 /// Tests get x-axis range of a data series
96 98 void testXAxisRange();
97 99
98 100 /// Input test data
99 101 /// @sa testValuesBoundsScalar()
100 102 void testValuesBoundsScalar_data();
101 103
102 104 /// Tests get values bounds of a scalar series
103 105 void testValuesBoundsScalar();
104 106
105 107 /// Input test data
106 108 /// @sa testValuesBoundsVector()
107 109 void testValuesBoundsVector_data();
108 110
109 111 /// Tests get values bounds of a vector series
110 112 void testValuesBoundsVector();
111 113 };
112 114
113 115 void TestDataSeries::testCtor_data()
114 116 {
115 117 // ////////////// //
116 118 // Test structure //
117 119 // ////////////// //
118 120
119 121 // x-axis data
120 122 QTest::addColumn<QVector<double> >("xAxisData");
121 123 // values data
122 124 QTest::addColumn<QVector<double> >("valuesData");
123 125
124 126 // expected x-axis data
125 127 QTest::addColumn<QVector<double> >("expectedXAxisData");
126 128 // expected values data
127 129 QTest::addColumn<QVector<double> >("expectedValuesData");
128 130
129 131 // ////////// //
130 132 // Test cases //
131 133 // ////////// //
132 134
133 135 QTest::newRow("invalidData (different sizes of vectors)")
134 136 << QVector<double>{1., 2., 3., 4., 5.} << QVector<double>{100., 200., 300.}
135 137 << QVector<double>{} << QVector<double>{};
136 138
137 139 QTest::newRow("sortedData") << QVector<double>{1., 2., 3., 4., 5.}
138 140 << QVector<double>{100., 200., 300., 400., 500.}
139 141 << QVector<double>{1., 2., 3., 4., 5.}
140 142 << QVector<double>{100., 200., 300., 400., 500.};
141 143
142 144 QTest::newRow("unsortedData") << QVector<double>{5., 4., 3., 2., 1.}
143 145 << QVector<double>{100., 200., 300., 400., 500.}
144 146 << QVector<double>{1., 2., 3., 4., 5.}
145 147 << QVector<double>{500., 400., 300., 200., 100.};
146 148
147 149 QTest::newRow("unsortedData2")
148 150 << QVector<double>{1., 4., 3., 5., 2.} << QVector<double>{100., 200., 300., 400., 500.}
149 151 << QVector<double>{1., 2., 3., 4., 5.} << QVector<double>{100., 500., 300., 200., 400.};
150 152 }
151 153
152 154 void TestDataSeries::testCtor()
153 155 {
154 156 // Creates series
155 157 QFETCH(QVector<double>, xAxisData);
156 158 QFETCH(QVector<double>, valuesData);
157 159
158 160 auto series = std::make_shared<ScalarSeries>(std::move(xAxisData), std::move(valuesData),
159 161 Unit{}, Unit{});
160 162
161 163 // Validates results : we check that the data series is sorted on its x-axis data
162 164 QFETCH(QVector<double>, expectedXAxisData);
163 165 QFETCH(QVector<double>, expectedValuesData);
164 166
165 167 auto seriesXAxisData = series->xAxisData()->data();
166 168 auto seriesValuesData = series->valuesData()->data();
167 169
168 170 QVERIFY(
169 171 std::equal(expectedXAxisData.cbegin(), expectedXAxisData.cend(), seriesXAxisData.cbegin()));
170 172 QVERIFY(std::equal(expectedValuesData.cbegin(), expectedValuesData.cend(),
171 173 seriesValuesData.cbegin()));
172 174 }
173 175
174 176 namespace {
175 177
176 178 std::shared_ptr<ScalarSeries> createScalarSeries(QVector<double> xAxisData,
177 179 QVector<double> valuesData)
178 180 {
179 181 return std::make_shared<ScalarSeries>(std::move(xAxisData), std::move(valuesData), Unit{},
180 182 Unit{});
181 183 }
182 184
183 185 std::shared_ptr<VectorSeries> createVectorSeries(QVector<double> xAxisData,
184 186 QVector<double> xValuesData,
185 187 QVector<double> yValuesData,
186 188 QVector<double> zValuesData)
187 189 {
188 190 return std::make_shared<VectorSeries>(std::move(xAxisData), std::move(xValuesData),
189 191 std::move(yValuesData), std::move(zValuesData), Unit{},
190 192 Unit{});
191 193 }
192 194
193 195 } // namespace
194 196
195 197 void TestDataSeries::testMerge_data()
196 198 {
197 199 // ////////////// //
198 200 // Test structure //
199 201 // ////////////// //
200 202
201 203 // Data series to merge
202 204 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
203 205 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries2");
204 206
205 207 // Expected values in the first data series after merge
206 208 QTest::addColumn<QVector<double> >("expectedXAxisData");
207 209 QTest::addColumn<QVector<double> >("expectedValuesData");
208 210
209 211 // ////////// //
210 212 // Test cases //
211 213 // ////////// //
212 214
213 215 QTest::newRow("sortedMerge")
214 216 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
215 217 << createScalarSeries({6., 7., 8., 9., 10.}, {600., 700., 800., 900., 1000.})
216 218 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
217 219 << QVector<double>{100., 200., 300., 400., 500., 600., 700., 800., 900., 1000.};
218 220
219 221 QTest::newRow("unsortedMerge")
220 222 << createScalarSeries({6., 7., 8., 9., 10.}, {600., 700., 800., 900., 1000.})
221 223 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
222 224 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
223 225 << QVector<double>{100., 200., 300., 400., 500., 600., 700., 800., 900., 1000.};
224 226
225 227 QTest::newRow("unsortedMerge2")
226 228 << createScalarSeries({1., 2., 8., 9., 10}, {100., 200., 300., 400., 500.})
227 229 << createScalarSeries({3., 4., 5., 6., 7.}, {600., 700., 800., 900., 1000.})
228 230 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
229 231 << QVector<double>{100., 200., 600., 700., 800., 900., 1000., 300., 400., 500.};
230 232
231 233 QTest::newRow("unsortedMerge3")
232 234 << createScalarSeries({3., 5., 8., 7., 2}, {100., 200., 300., 400., 500.})
233 235 << createScalarSeries({6., 4., 9., 10., 1.}, {600., 700., 800., 900., 1000.})
234 236 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
235 237 << QVector<double>{1000., 500., 100., 700., 200., 600., 400., 300., 800., 900.};
236 238 }
237 239
238 240 void TestDataSeries::testMerge()
239 241 {
240 242 // Merges series
241 243 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
242 244 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries2);
243 245
244 246 dataSeries->merge(dataSeries2.get());
245 247
246 248 // Validates results : we check that the merge is valid and the data series is sorted on its
247 249 // x-axis data
248 250 QFETCH(QVector<double>, expectedXAxisData);
249 251 QFETCH(QVector<double>, expectedValuesData);
250 252
251 253 auto seriesXAxisData = dataSeries->xAxisData()->data();
252 254 auto seriesValuesData = dataSeries->valuesData()->data();
253 255
254 256 QVERIFY(
255 257 std::equal(expectedXAxisData.cbegin(), expectedXAxisData.cend(), seriesXAxisData.cbegin()));
256 258 QVERIFY(std::equal(expectedValuesData.cbegin(), expectedValuesData.cend(),
257 259 seriesValuesData.cbegin()));
258 260 }
259 261
260 262 void TestDataSeries::testMinXAxisData_data()
261 263 {
262 264 // ////////////// //
263 265 // Test structure //
264 266 // ////////////// //
265 267
266 268 // Data series to get min data
267 269 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
268 270
269 271 // Min data
270 272 QTest::addColumn<double>("min");
271 273
272 274 // Expected results
273 275 QTest::addColumn<bool>(
274 276 "expectedOK"); // if true, expects to have a result (i.e. the iterator != end iterator)
275 277 QTest::addColumn<double>(
276 278 "expectedMin"); // Expected value when method doesn't return end iterator
277 279
278 280 // ////////// //
279 281 // Test cases //
280 282 // ////////// //
281 283
282 284 QTest::newRow("minData1") << createScalarSeries({1., 2., 3., 4., 5.},
283 285 {100., 200., 300., 400., 500.})
284 286 << 0. << true << 1.;
285 287 QTest::newRow("minData2") << createScalarSeries({1., 2., 3., 4., 5.},
286 288 {100., 200., 300., 400., 500.})
287 289 << 1. << true << 1.;
288 290 QTest::newRow("minData3") << createScalarSeries({1., 2., 3., 4., 5.},
289 291 {100., 200., 300., 400., 500.})
290 292 << 1.1 << true << 2.;
291 293 QTest::newRow("minData4") << createScalarSeries({1., 2., 3., 4., 5.},
292 294 {100., 200., 300., 400., 500.})
293 295 << 5. << true << 5.;
294 296 QTest::newRow("minData5") << createScalarSeries({1., 2., 3., 4., 5.},
295 297 {100., 200., 300., 400., 500.})
296 298 << 5.1 << false << std::numeric_limits<double>::quiet_NaN();
297 299 QTest::newRow("minData6") << createScalarSeries({}, {}) << 1.1 << false
298 300 << std::numeric_limits<double>::quiet_NaN();
299 301 }
300 302
301 303 void TestDataSeries::testMinXAxisData()
302 304 {
303 305 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
304 306 QFETCH(double, min);
305 307
306 308 QFETCH(bool, expectedOK);
307 309 QFETCH(double, expectedMin);
308 310
309 311 auto it = dataSeries->minXAxisData(min);
310 312
311 313 QCOMPARE(expectedOK, it != dataSeries->cend());
312 314
313 315 // If the method doesn't return a end iterator, checks with expected value
314 316 if (expectedOK) {
315 317 QCOMPARE(expectedMin, it->x());
316 318 }
317 319 }
318 320
319 321 void TestDataSeries::testMaxXAxisData_data()
320 322 {
321 323 // ////////////// //
322 324 // Test structure //
323 325 // ////////////// //
324 326
325 327 // Data series to get max data
326 328 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
327 329
328 330 // Max data
329 331 QTest::addColumn<double>("max");
330 332
331 333 // Expected results
332 334 QTest::addColumn<bool>(
333 335 "expectedOK"); // if true, expects to have a result (i.e. the iterator != end iterator)
334 336 QTest::addColumn<double>(
335 337 "expectedMax"); // Expected value when method doesn't return end iterator
336 338
337 339 // ////////// //
338 340 // Test cases //
339 341 // ////////// //
340 342
341 343 QTest::newRow("maxData1") << createScalarSeries({1., 2., 3., 4., 5.},
342 344 {100., 200., 300., 400., 500.})
343 345 << 6. << true << 5.;
344 346 QTest::newRow("maxData2") << createScalarSeries({1., 2., 3., 4., 5.},
345 347 {100., 200., 300., 400., 500.})
346 348 << 5. << true << 5.;
347 349 QTest::newRow("maxData3") << createScalarSeries({1., 2., 3., 4., 5.},
348 350 {100., 200., 300., 400., 500.})
349 351 << 4.9 << true << 4.;
350 352 QTest::newRow("maxData4") << createScalarSeries({1., 2., 3., 4., 5.},
351 353 {100., 200., 300., 400., 500.})
352 354 << 1.1 << true << 1.;
353 355 QTest::newRow("maxData5") << createScalarSeries({1., 2., 3., 4., 5.},
354 356 {100., 200., 300., 400., 500.})
355 357 << 1. << true << 1.;
356 358 QTest::newRow("maxData6") << createScalarSeries({}, {}) << 1.1 << false
357 359 << std::numeric_limits<double>::quiet_NaN();
358 360 }
359 361
360 362 void TestDataSeries::testMaxXAxisData()
361 363 {
362 364 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
363 365 QFETCH(double, max);
364 366
365 367 QFETCH(bool, expectedOK);
366 368 QFETCH(double, expectedMax);
367 369
368 370 auto it = dataSeries->maxXAxisData(max);
369 371
370 372 QCOMPARE(expectedOK, it != dataSeries->cend());
371 373
372 374 // If the method doesn't return a end iterator, checks with expected value
373 375 if (expectedOK) {
374 376 QCOMPARE(expectedMax, it->x());
375 377 }
376 378 }
377 379
378 380 void TestDataSeries::testXAxisRange_data()
379 381 {
380 382 // ////////////// //
381 383 // Test structure //
382 384 // ////////////// //
383 385
384 386 // Data series to get x-axis range
385 387 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
386 388
387 389 // Min/max values
388 390 QTest::addColumn<double>("min");
389 391 QTest::addColumn<double>("max");
390 392
391 393 // Expected values
392 394 QTest::addColumn<QVector<double> >("expectedXAxisData");
393 395 QTest::addColumn<QVector<double> >("expectedValuesData");
394 396
395 397 // ////////// //
396 398 // Test cases //
397 399 // ////////// //
398 400
399 401 QTest::newRow("xAxisRange1") << createScalarSeries({1., 2., 3., 4., 5.},
400 402 {100., 200., 300., 400., 500.})
401 403 << -1. << 3.2 << QVector<double>{1., 2., 3.}
402 404 << QVector<double>{100., 200., 300.};
403 405 QTest::newRow("xAxisRange2") << createScalarSeries({1., 2., 3., 4., 5.},
404 406 {100., 200., 300., 400., 500.})
405 407 << 1. << 4. << QVector<double>{1., 2., 3., 4.}
406 408 << QVector<double>{100., 200., 300., 400.};
407 409 QTest::newRow("xAxisRange3") << createScalarSeries({1., 2., 3., 4., 5.},
408 410 {100., 200., 300., 400., 500.})
409 411 << 1. << 3.9 << QVector<double>{1., 2., 3.}
410 412 << QVector<double>{100., 200., 300.};
411 413 QTest::newRow("xAxisRange4") << createScalarSeries({1., 2., 3., 4., 5.},
412 414 {100., 200., 300., 400., 500.})
413 415 << 0. << 0.9 << QVector<double>{} << QVector<double>{};
414 416 QTest::newRow("xAxisRange5") << createScalarSeries({1., 2., 3., 4., 5.},
415 417 {100., 200., 300., 400., 500.})
416 418 << 0. << 1. << QVector<double>{1.} << QVector<double>{100.};
417 419 QTest::newRow("xAxisRange6") << createScalarSeries({1., 2., 3., 4., 5.},
418 420 {100., 200., 300., 400., 500.})
419 421 << 2.1 << 6. << QVector<double>{3., 4., 5.}
420 422 << QVector<double>{300., 400., 500.};
421 423 QTest::newRow("xAxisRange7") << createScalarSeries({1., 2., 3., 4., 5.},
422 424 {100., 200., 300., 400., 500.})
423 425 << 6. << 9. << QVector<double>{} << QVector<double>{};
424 426 QTest::newRow("xAxisRange8") << createScalarSeries({1., 2., 3., 4., 5.},
425 427 {100., 200., 300., 400., 500.})
426 428 << 5. << 9. << QVector<double>{5.} << QVector<double>{500.};
427 429 }
428 430
429 431 void TestDataSeries::testXAxisRange()
430 432 {
431 433 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
432 434 QFETCH(double, min);
433 435 QFETCH(double, max);
434 436
435 437 QFETCH(QVector<double>, expectedXAxisData);
436 438 QFETCH(QVector<double>, expectedValuesData);
437 439
438 440 auto bounds = dataSeries->xAxisRange(min, max);
439 441 QVERIFY(std::equal(bounds.first, bounds.second, expectedXAxisData.cbegin(),
440 442 expectedXAxisData.cend(),
441 443 [](const auto &it, const auto &expectedX) { return it.x() == expectedX; }));
442 444 QVERIFY(std::equal(
443 445 bounds.first, bounds.second, expectedValuesData.cbegin(), expectedValuesData.cend(),
444 446 [](const auto &it, const auto &expectedVal) { return it.value() == expectedVal; }));
445 447 }
446 448
447 449 void TestDataSeries::testValuesBoundsScalar_data()
448 450 {
449 451 testValuesBoundsStructure<ScalarSeries>();
450 452
451 453 // ////////// //
452 454 // Test cases //
453 455 // ////////// //
454 456 auto nan = std::numeric_limits<double>::quiet_NaN();
455 457
456 458 QTest::newRow("scalarBounds1")
457 459 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 0. << 6.
458 460 << true << 100. << 500.;
459 461 QTest::newRow("scalarBounds2")
460 462 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 2. << 4.
461 463 << true << 200. << 400.;
462 464 QTest::newRow("scalarBounds3")
463 465 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 0. << 0.5
464 466 << false << nan << nan;
465 467 QTest::newRow("scalarBounds4")
466 468 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 5.1 << 6.
467 469 << false << nan << nan;
468 QTest::newRow("scalarBounds5")
469 << createScalarSeries({1.}, {100.}) << 0. << 2. << true << 100. << 100.;
470 QTest::newRow("scalarBounds5") << createScalarSeries({1.}, {100.}) << 0. << 2. << true << 100.
471 << 100.;
470 472 QTest::newRow("scalarBounds6") << createScalarSeries({}, {}) << 0. << 2. << false << nan << nan;
471 473
472 474 // Tests with NaN values: NaN values are not included in min/max search
473 475 QTest::newRow("scalarBounds7")
474 476 << createScalarSeries({1., 2., 3., 4., 5.}, {nan, 200., 300., 400., nan}) << 0. << 6.
475 477 << true << 200. << 400.;
476 478 QTest::newRow("scalarBounds8")
477 479 << createScalarSeries({1., 2., 3., 4., 5.}, {nan, nan, nan, nan, nan}) << 0. << 6. << true
478 480 << std::numeric_limits<double>::quiet_NaN() << std::numeric_limits<double>::quiet_NaN();
479 481 }
480 482
481 483 void TestDataSeries::testValuesBoundsScalar()
482 484 {
483 485 testValuesBounds<ScalarSeries>();
484 486 }
485 487
486 488 void TestDataSeries::testValuesBoundsVector_data()
487 489 {
488 490 testValuesBoundsStructure<VectorSeries>();
489 491
490 492 // ////////// //
491 493 // Test cases //
492 494 // ////////// //
493 495 auto nan = std::numeric_limits<double>::quiet_NaN();
494 496
495 497 QTest::newRow("vectorBounds1")
496 498 << createVectorSeries({1., 2., 3., 4., 5.}, {10., 15., 20., 13., 12.},
497 499 {35., 24., 10., 9., 0.3}, {13., 14., 12., 9., 24.})
498 500 << 0. << 6. << true << 0.3 << 35.; // min/max in same component
499 501 QTest::newRow("vectorBounds2")
500 502 << createVectorSeries({1., 2., 3., 4., 5.}, {2.3, 15., 20., 13., 12.},
501 503 {35., 24., 10., 9., 4.}, {13., 14., 12., 9., 24.})
502 504 << 0. << 6. << true << 2.3 << 35.; // min/max in same entry
503 505 QTest::newRow("vectorBounds3")
504 506 << createVectorSeries({1., 2., 3., 4., 5.}, {2.3, 15., 20., 13., 12.},
505 507 {35., 24., 10., 9., 4.}, {13., 14., 12., 9., 24.})
506 508 << 2. << 3. << true << 10. << 24.;
507 509
508 510 // Tests with NaN values: NaN values are not included in min/max search
509 511 QTest::newRow("vectorBounds4")
510 512 << createVectorSeries({1., 2.}, {nan, nan}, {nan, nan}, {nan, nan}) << 0. << 6. << true
511 513 << nan << nan;
512 514 }
513 515
514 516 void TestDataSeries::testValuesBoundsVector()
515 517 {
516 518 testValuesBounds<VectorSeries>();
517 519 }
518 520
519 521 QTEST_MAIN(TestDataSeries)
520 522 #include "TestDataSeries.moc"
@@ -1,256 +1,257
1 1 #include "Visualization/VisualizationZoneWidget.h"
2 2
3 3
4 4 #include "Visualization/IVisualizationWidgetVisitor.h"
5 5 #include "Visualization/VisualizationGraphWidget.h"
6 6 #include "ui_VisualizationZoneWidget.h"
7 7
8 8 #include <Data/SqpRange.h>
9 9 #include <Variable/Variable.h>
10 10 #include <Variable/VariableController.h>
11 11
12 #include <cmath>
12 13 #include <QUuid>
13 14 #include <SqpApplication.h>
14 15
15 16 Q_LOGGING_CATEGORY(LOG_VisualizationZoneWidget, "VisualizationZoneWidget")
16 17
17 18 namespace {
18 19
19 20 /// Minimum height for graph added in zones (in pixels)
20 21 const auto GRAPH_MINIMUM_HEIGHT = 300;
21 22
22 23 /// Generates a default name for a new graph, according to the number of graphs already displayed in
23 24 /// the zone
24 25 QString defaultGraphName(const QLayout &layout)
25 26 {
26 27 auto count = 0;
27 28 for (auto i = 0; i < layout.count(); ++i) {
28 29 if (dynamic_cast<VisualizationGraphWidget *>(layout.itemAt(i)->widget())) {
29 30 count++;
30 31 }
31 32 }
32 33
33 34 return QObject::tr("Graph %1").arg(count + 1);
34 35 }
35 36
36 37 } // namespace
37 38
38 39 struct VisualizationZoneWidget::VisualizationZoneWidgetPrivate {
39 40
40 41 explicit VisualizationZoneWidgetPrivate() : m_SynchronisationGroupId{QUuid::createUuid()} {}
41 42 QUuid m_SynchronisationGroupId;
42 43 };
43 44
44 45 VisualizationZoneWidget::VisualizationZoneWidget(const QString &name, QWidget *parent)
45 46 : QWidget{parent},
46 47 ui{new Ui::VisualizationZoneWidget},
47 48 impl{spimpl::make_unique_impl<VisualizationZoneWidgetPrivate>()}
48 49 {
49 50 ui->setupUi(this);
50 51
51 52 ui->zoneNameLabel->setText(name);
52 53
53 54 // 'Close' options : widget is deleted when closed
54 55 setAttribute(Qt::WA_DeleteOnClose);
55 56 connect(ui->closeButton, &QToolButton::clicked, this, &VisualizationZoneWidget::close);
56 57 ui->closeButton->setIcon(sqpApp->style()->standardIcon(QStyle::SP_TitleBarCloseButton));
57 58
58 59 // Synchronisation id
59 60 QMetaObject::invokeMethod(&sqpApp->variableController(), "onAddSynchronizationGroupId",
60 61 Qt::QueuedConnection, Q_ARG(QUuid, impl->m_SynchronisationGroupId));
61 62 }
62 63
63 64 VisualizationZoneWidget::~VisualizationZoneWidget()
64 65 {
65 66 delete ui;
66 67 }
67 68
68 69 void VisualizationZoneWidget::addGraph(VisualizationGraphWidget *graphWidget)
69 70 {
70 71 ui->visualizationZoneFrame->layout()->addWidget(graphWidget);
71 72 }
72 73
73 74 VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptr<Variable> variable)
74 75 {
75 76 auto graphWidget = new VisualizationGraphWidget{
76 77 defaultGraphName(*ui->visualizationZoneFrame->layout()), this};
77 78
78 79
79 80 // Set graph properties
80 81 graphWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
81 82 graphWidget->setMinimumHeight(GRAPH_MINIMUM_HEIGHT);
82 83
83 84
84 85 // Lambda to synchronize zone widget
85 86 auto synchronizeZoneWidget = [this, graphWidget](const SqpRange &graphRange,
86 87 const SqpRange &oldGraphRange) {
87 88
88 89 auto zoomType = VariableController::getZoomType(graphRange, oldGraphRange);
89 90 auto frameLayout = ui->visualizationZoneFrame->layout();
90 91 for (auto i = 0; i < frameLayout->count(); ++i) {
91 92 auto graphChild
92 93 = dynamic_cast<VisualizationGraphWidget *>(frameLayout->itemAt(i)->widget());
93 94 if (graphChild && (graphChild != graphWidget)) {
94 95
95 96 auto graphChildRange = graphChild->graphRange();
96 97 switch (zoomType) {
97 98 case AcquisitionZoomType::ZoomIn: {
98 99 auto deltaLeft = graphRange.m_TStart - oldGraphRange.m_TStart;
99 100 auto deltaRight = oldGraphRange.m_TEnd - graphRange.m_TEnd;
100 101 graphChildRange.m_TStart += deltaLeft;
101 102 graphChildRange.m_TEnd -= deltaRight;
102 103 qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: ZoomIn");
103 104 qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: deltaLeft")
104 105 << deltaLeft;
105 106 qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: deltaRight")
106 107 << deltaRight;
107 108 qCCritical(LOG_VisualizationZoneWidget())
108 109 << tr("TORM: dt") << graphRange.m_TEnd - graphRange.m_TStart;
109 110
110 111 break;
111 112 }
112 113
113 114 case AcquisitionZoomType::ZoomOut: {
114 115 qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: ZoomOut");
115 116 auto deltaLeft = oldGraphRange.m_TStart - graphRange.m_TStart;
116 117 auto deltaRight = graphRange.m_TEnd - oldGraphRange.m_TEnd;
117 118 qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: deltaLeft")
118 119 << deltaLeft;
119 120 qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: deltaRight")
120 121 << deltaRight;
121 122 qCCritical(LOG_VisualizationZoneWidget())
122 123 << tr("TORM: dt") << graphRange.m_TEnd - graphRange.m_TStart;
123 124 graphChildRange.m_TStart -= deltaLeft;
124 125 graphChildRange.m_TEnd += deltaRight;
125 126 break;
126 127 }
127 128 case AcquisitionZoomType::PanRight: {
128 129 qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: PanRight");
129 130 auto deltaRight = graphRange.m_TEnd - oldGraphRange.m_TEnd;
130 131 graphChildRange.m_TStart += deltaRight;
131 132 graphChildRange.m_TEnd += deltaRight;
132 133 qCCritical(LOG_VisualizationZoneWidget())
133 134 << tr("TORM: dt") << graphRange.m_TEnd - graphRange.m_TStart;
134 135 break;
135 136 }
136 137 case AcquisitionZoomType::PanLeft: {
137 138 qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: PanLeft");
138 139 auto deltaLeft = oldGraphRange.m_TStart - graphRange.m_TStart;
139 140 graphChildRange.m_TStart -= deltaLeft;
140 141 graphChildRange.m_TEnd -= deltaLeft;
141 142 break;
142 143 }
143 144 case AcquisitionZoomType::Unknown: {
144 145 qCCritical(LOG_VisualizationZoneWidget())
145 146 << tr("Impossible to synchronize: zoom type unknown");
146 147 break;
147 148 }
148 149 default:
149 150 qCCritical(LOG_VisualizationZoneWidget())
150 151 << tr("Impossible to synchronize: zoom type not take into account");
151 152 // No action
152 153 break;
153 154 }
154 155 graphChild->enableAcquisition(false);
155 156 qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: Range before: ")
156 157 << graphChild->graphRange();
157 158 qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: Range after : ")
158 159 << graphChildRange;
159 160 qCCritical(LOG_VisualizationZoneWidget())
160 161 << tr("TORM: child dt") << graphChildRange.m_TEnd - graphChildRange.m_TStart;
161 162 graphChild->setGraphRange(graphChildRange);
162 163 graphChild->enableAcquisition(true);
163 164 }
164 165 }
165 166 };
166 167
167 168 // connection for synchronization
168 169 connect(graphWidget, &VisualizationGraphWidget::synchronize, synchronizeZoneWidget);
169 170 connect(graphWidget, &VisualizationGraphWidget::variableAdded, this,
170 171 &VisualizationZoneWidget::onVariableAdded);
171 172
172 173 auto range = SqpRange{};
173 174
174 175 // Apply visitor to graph children
175 176 auto layout = ui->visualizationZoneFrame->layout();
176 177 if (layout->count() > 0) {
177 178 // Case of a new graph in a existant zone
178 179 if (auto visualizationGraphWidget
179 180 = dynamic_cast<VisualizationGraphWidget *>(layout->itemAt(0)->widget())) {
180 181 range = visualizationGraphWidget->graphRange();
181 182 }
182 183 }
183 184 else {
184 185 // Case of a new graph as the first of the zone
185 186 range = variable->range();
186 187 }
187 188
188 189 this->addGraph(graphWidget);
189 190
190 191 graphWidget->addVariable(variable, range);
191 192
192 193 // get y using variable range
193 194 if (auto dataSeries = variable->dataSeries()) {
194 195 auto valuesBounds = dataSeries->valuesBounds(range.m_TStart, range.m_TEnd);
195 196 auto end = dataSeries->cend();
196 197 if (valuesBounds.first != end && valuesBounds.second != end) {
197 198 auto rangeValue = [](const auto &value) { return std::isnan(value) ? 0. : value; };
198 199
199 200 auto minValue = rangeValue(valuesBounds.first->minValue());
200 201 auto maxValue = rangeValue(valuesBounds.second->maxValue());
201 202
202 203 graphWidget->setYRange(SqpRange{minValue, maxValue});
203 204 }
204 205 }
205 206
206 207 return graphWidget;
207 208 }
208 209
209 210 void VisualizationZoneWidget::accept(IVisualizationWidgetVisitor *visitor)
210 211 {
211 212 if (visitor) {
212 213 visitor->visitEnter(this);
213 214
214 215 // Apply visitor to graph children
215 216 auto layout = ui->visualizationZoneFrame->layout();
216 217 for (auto i = 0; i < layout->count(); ++i) {
217 218 if (auto item = layout->itemAt(i)) {
218 219 // Widgets different from graphs are not visited (no action)
219 220 if (auto visualizationGraphWidget
220 221 = dynamic_cast<VisualizationGraphWidget *>(item->widget())) {
221 222 visualizationGraphWidget->accept(visitor);
222 223 }
223 224 }
224 225 }
225 226
226 227 visitor->visitLeave(this);
227 228 }
228 229 else {
229 230 qCCritical(LOG_VisualizationZoneWidget()) << tr("Can't visit widget : the visitor is null");
230 231 }
231 232 }
232 233
233 234 bool VisualizationZoneWidget::canDrop(const Variable &variable) const
234 235 {
235 236 // A tab can always accomodate a variable
236 237 Q_UNUSED(variable);
237 238 return true;
238 239 }
239 240
240 241 bool VisualizationZoneWidget::contains(const Variable &variable) const
241 242 {
242 243 Q_UNUSED(variable);
243 244 return false;
244 245 }
245 246
246 247 QString VisualizationZoneWidget::name() const
247 248 {
248 249 return ui->zoneNameLabel->text();
249 250 }
250 251
251 252 void VisualizationZoneWidget::onVariableAdded(std::shared_ptr<Variable> variable)
252 253 {
253 254 QMetaObject::invokeMethod(&sqpApp->variableController(), "onAddSynchronized",
254 255 Qt::QueuedConnection, Q_ARG(std::shared_ptr<Variable>, variable),
255 256 Q_ARG(QUuid, impl->m_SynchronisationGroupId));
256 257 }
General Comments 0
You need to be logged in to leave comments. Login now