##// END OF EJS Templates
Add continuous auto scale for color scale on spectrograms...
jeandet -
r1466:15fc74768e17
parent child
Show More
@@ -1,545 +1,545
1 1 #include "Visualization/VisualizationGraphHelper.h"
2 2 #include "Visualization/qcustomplot.h"
3 3
4 4 #include <Data/ScalarTimeSerie.h>
5 5 #include <Data/SpectrogramTimeSerie.h>
6 6 #include <Data/TimeSeriesUtils.h>
7 7 #include <Data/VectorTimeSerie.h>
8 8
9 9 #include <Common/cpp_utils.h>
10 10 #include <Variable/Variable2.h>
11 11 #include <algorithm>
12 12
13 13 Q_LOGGING_CATEGORY(LOG_VisualizationGraphHelper, "VisualizationGraphHelper")
14 14
15 15 namespace
16 16 {
17 17
18 18 class SqpDataContainer : public QCPGraphDataContainer
19 19 {
20 20 public:
21 21 void appendGraphData(const QCPGraphData& data) { mData.append(data); }
22 22 };
23 23
24 24 /**
25 25 * Struct used to create plottables, depending on the type of the data series from which to create
26 26 * them
27 27 * @tparam T the data series' type
28 28 * @remarks Default implementation can't create plottables
29 29 */
30 30 template <typename T, typename Enabled = void>
31 31 struct PlottablesCreator
32 32 {
33 33 static PlottablesMap createPlottables(QCustomPlot&, const std::shared_ptr<T>& dataSeries)
34 34 {
35 35 return {};
36 36 }
37 37 };
38 38
39 39 PlottablesMap createGraphs(QCustomPlot& plot, int nbGraphs)
40 40 {
41 41 PlottablesMap result {};
42 42
43 43 // Creates {nbGraphs} QCPGraph to add to the plot
44 44 for (auto i = 0; i < nbGraphs; ++i)
45 45 {
46 46 auto graph = plot.addGraph();
47 47 result.insert({ i, graph });
48 48 }
49 49
50 50 plot.replot();
51 51
52 52 return result;
53 53 }
54 54
55 55 /**
56 56 * Specialization of PlottablesCreator for scalars
57 57 * @sa ScalarSeries
58 58 */
59 59 template <typename T>
60 60 struct PlottablesCreator<T, typename std::enable_if_t<std::is_base_of<ScalarTimeSerie, T>::value>>
61 61 {
62 62 static PlottablesMap createPlottables(QCustomPlot& plot, const std::shared_ptr<T>& dataSeries)
63 63 {
64 64 return createGraphs(plot, 1);
65 65 }
66 66 };
67 67
68 68 /**
69 69 * Specialization of PlottablesCreator for vectors
70 70 * @sa VectorSeries
71 71 */
72 72 template <typename T>
73 73 struct PlottablesCreator<T, typename std::enable_if_t<std::is_base_of<VectorTimeSerie, T>::value>>
74 74 {
75 75 static PlottablesMap createPlottables(QCustomPlot& plot, const std::shared_ptr<T>& dataSeries)
76 76 {
77 77 return createGraphs(plot, 3);
78 78 }
79 79 };
80 80
81 81 /**
82 82 * Specialization of PlottablesCreator for MultiComponentTimeSeries
83 83 * @sa VectorSeries
84 84 */
85 85 template <typename T>
86 86 struct PlottablesCreator<T,
87 87 typename std::enable_if_t<std::is_base_of<MultiComponentTimeSerie, T>::value>>
88 88 {
89 89 static PlottablesMap createPlottables(QCustomPlot& plot, const std::shared_ptr<T>& dataSeries)
90 90 {
91 91 return createGraphs(plot, dataSeries->size(1));
92 92 }
93 93 };
94 94
95 95 /**
96 96 * Specialization of PlottablesCreator for spectrograms
97 97 * @sa SpectrogramSeries
98 98 */
99 99 template <typename T>
100 100 struct PlottablesCreator<T,
101 101 typename std::enable_if_t<std::is_base_of<SpectrogramTimeSerie, T>::value>>
102 102 {
103 103 static PlottablesMap createPlottables(QCustomPlot& plot, const std::shared_ptr<T>& dataSeries)
104 104 {
105 105 PlottablesMap result {};
106 106 result.insert({ 0, new QCPColorMap { plot.xAxis, plot.yAxis } });
107 107
108 108 plot.replot();
109 109
110 110 return result;
111 111 }
112 112 };
113 113
114 114 /**
115 115 * Struct used to update plottables, depending on the type of the data series from which to update
116 116 * them
117 117 * @tparam T the data series' type
118 118 * @remarks Default implementation can't update plottables
119 119 */
120 120 template <typename T, typename Enabled = void>
121 121 struct PlottablesUpdater
122 122 {
123 123 static void setPlotYAxisRange(T&, const DateTimeRange&, QCustomPlot&)
124 124 {
125 125 qCCritical(LOG_VisualizationGraphHelper())
126 126 << QObject::tr("Can't set plot y-axis range: unmanaged data series type");
127 127 }
128 128
129 129 static void updatePlottables(T&, PlottablesMap&, const DateTimeRange&, bool)
130 130 {
131 131 qCCritical(LOG_VisualizationGraphHelper())
132 132 << QObject::tr("Can't update plottables: unmanaged data series type");
133 133 }
134 134 };
135 135
136 136 /**
137 137 * Specialization of PlottablesUpdater for scalars and vectors
138 138 * @sa ScalarSeries
139 139 * @sa VectorSeries
140 140 */
141 141 template <typename T>
142 142 struct PlottablesUpdater<T, typename std::enable_if_t<std::is_base_of<ScalarTimeSerie, T>::value>>
143 143 {
144 144 static void setPlotYAxisRange(T& dataSeries, const DateTimeRange& xAxisRange, QCustomPlot& plot)
145 145 {
146 146 auto minValue = 0., maxValue = 0.;
147 147 if (auto serie = dynamic_cast<ScalarTimeSerie*>(&dataSeries))
148 148 {
149 149 if (serie->size())
150 150 {
151 151 maxValue = (*std::max_element(std::begin(*serie), std::end(*serie))).v();
152 152 minValue = (*std::min_element(std::begin(*serie), std::end(*serie))).v();
153 153 }
154 154 }
155 155 plot.yAxis->setRange(QCPRange { minValue, maxValue });
156 156 }
157 157
158 158 static void updatePlottables(
159 159 T& dataSeries, PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes)
160 160 {
161 161
162 162 // For each plottable to update, resets its data
163 163 for (const auto& plottable : plottables)
164 164 {
165 165 if (auto graph = dynamic_cast<QCPGraph*>(plottable.second))
166 166 {
167 167 auto dataContainer = QSharedPointer<SqpDataContainer>::create();
168 168 if (auto serie = dynamic_cast<ScalarTimeSerie*>(&dataSeries))
169 169 {
170 170 std::for_each(
171 171 std::begin(*serie), std::end(*serie), [&dataContainer](const auto& value) {
172 172 dataContainer->appendGraphData(QCPGraphData(value.t(), value.v()));
173 173 });
174 174 }
175 175 graph->setData(dataContainer);
176 176 }
177 177 }
178 178
179 179 if (!plottables.empty())
180 180 {
181 181 auto plot = plottables.begin()->second->parentPlot();
182 182
183 183 if (rescaleAxes)
184 184 {
185 185 plot->rescaleAxes();
186 186 }
187 187 }
188 188 }
189 189 };
190 190
191 191
192 192 template <typename T>
193 193 struct PlottablesUpdater<T, typename std::enable_if_t<std::is_base_of<VectorTimeSerie, T>::value>>
194 194 {
195 195 static void setPlotYAxisRange(T& dataSeries, const DateTimeRange& xAxisRange, QCustomPlot& plot)
196 196 {
197 197 double minValue = 0., maxValue = 0.;
198 198 if (auto serie = dynamic_cast<VectorTimeSerie*>(&dataSeries))
199 199 {
200 200 std::for_each(
201 201 std::begin(*serie), std::end(*serie), [&minValue, &maxValue](const auto& v) {
202 202 minValue = std::min({ minValue, v.v().x, v.v().y, v.v().z });
203 203 maxValue = std::max({ maxValue, v.v().x, v.v().y, v.v().z });
204 204 });
205 205 }
206 206
207 207 plot.yAxis->setRange(QCPRange { minValue, maxValue });
208 208 }
209 209
210 210 static void updatePlottables(
211 211 T& dataSeries, PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes)
212 212 {
213 213
214 214 // For each plottable to update, resets its data
215 215 for (const auto& plottable : plottables)
216 216 {
217 217 if (auto graph = dynamic_cast<QCPGraph*>(plottable.second))
218 218 {
219 219 auto dataContainer = QSharedPointer<SqpDataContainer>::create();
220 220 if (auto serie = dynamic_cast<VectorTimeSerie*>(&dataSeries))
221 221 {
222 222 switch (plottable.first)
223 223 {
224 224 case 0:
225 225 std::for_each(std::begin(*serie), std::end(*serie),
226 226 [&dataContainer](const auto& value) {
227 227 dataContainer->appendGraphData(
228 228 QCPGraphData(value.t(), value.v().x));
229 229 });
230 230 break;
231 231 case 1:
232 232 std::for_each(std::begin(*serie), std::end(*serie),
233 233 [&dataContainer](const auto& value) {
234 234 dataContainer->appendGraphData(
235 235 QCPGraphData(value.t(), value.v().y));
236 236 });
237 237 break;
238 238 case 2:
239 239 std::for_each(std::begin(*serie), std::end(*serie),
240 240 [&dataContainer](const auto& value) {
241 241 dataContainer->appendGraphData(
242 242 QCPGraphData(value.t(), value.v().z));
243 243 });
244 244 break;
245 245 default:
246 246 break;
247 247 }
248 248 }
249 249 graph->setData(dataContainer);
250 250 }
251 251 }
252 252
253 253 if (!plottables.empty())
254 254 {
255 255 auto plot = plottables.begin()->second->parentPlot();
256 256
257 257 if (rescaleAxes)
258 258 {
259 259 plot->rescaleAxes();
260 260 }
261 261 }
262 262 }
263 263 };
264 264
265 265
266 266 template <typename T>
267 267 struct PlottablesUpdater<T,
268 268 typename std::enable_if_t<std::is_base_of<MultiComponentTimeSerie, T>::value>>
269 269 {
270 270 static void setPlotYAxisRange(T& dataSeries, const DateTimeRange& xAxisRange, QCustomPlot& plot)
271 271 {
272 272 double minValue = 0., maxValue = 0.;
273 273 if (auto serie = dynamic_cast<MultiComponentTimeSerie*>(&dataSeries))
274 274 {
275 275 std::for_each(
276 276 std::begin(*serie), std::end(*serie), [&minValue, &maxValue](const auto& v) {
277 277 minValue = std::min(minValue, std::min_element(v.begin(), v.end())->v());
278 278 maxValue = std::max(maxValue, std::max_element(v.begin(), v.end())->v());
279 279 });
280 280 }
281 281 plot.yAxis->setRange(QCPRange { minValue, maxValue });
282 282 }
283 283
284 284 static void updatePlottables(
285 285 T& dataSeries, PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes)
286 286 {
287 287 for (const auto& plottable : plottables)
288 288 {
289 289 if (auto graph = dynamic_cast<QCPGraph*>(plottable.second))
290 290 {
291 291 auto dataContainer = QSharedPointer<SqpDataContainer>::create();
292 292 if (auto serie = dynamic_cast<MultiComponentTimeSerie*>(&dataSeries))
293 293 {
294 294 // TODO
295 295 std::for_each(std::begin(*serie), std::end(*serie),
296 296 [&dataContainer, component = plottable.first](const auto& value) {
297 297 dataContainer->appendGraphData(
298 298 QCPGraphData(value.t(), value[component]));
299 299 });
300 300 }
301 301 graph->setData(dataContainer);
302 302 }
303 303 }
304 304
305 305 if (!plottables.empty())
306 306 {
307 307 auto plot = plottables.begin()->second->parentPlot();
308 308
309 309 if (rescaleAxes)
310 310 {
311 311 plot->rescaleAxes();
312 312 }
313 313 }
314 314 }
315 315 };
316 316
317 317 /**
318 318 * Specialization of PlottablesUpdater for spectrograms
319 319 * @sa SpectrogramSeries
320 320 */
321 321 template <typename T>
322 322 struct PlottablesUpdater<T,
323 323 typename std::enable_if_t<std::is_base_of<SpectrogramTimeSerie, T>::value>>
324 324 {
325 325 static void setPlotYAxisRange(T& dataSeries, const DateTimeRange& xAxisRange, QCustomPlot& plot)
326 326 {
327 327 auto [minValue, maxValue] = dataSeries.axis_range(1);
328 328 std::cout << "min=" << minValue << " max=" << maxValue << std::endl;
329 329 plot.yAxis->setRange(QCPRange { minValue, maxValue });
330 330 }
331 331
332 332 static void updatePlottables(
333 333 T& dataSeries, PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes)
334 334 {
335 335 if (plottables.empty())
336 336 {
337 337 qCDebug(LOG_VisualizationGraphHelper())
338 338 << QObject::tr("Can't update spectrogram: no colormap has been associated");
339 339 return;
340 340 }
341 341
342 342 // Gets the colormap to update (normally there is only one colormap)
343 343 Q_ASSERT(plottables.size() == 1);
344 344 auto colormap = dynamic_cast<QCPColorMap*>(plottables.at(0));
345 345 Q_ASSERT(colormap != nullptr);
346 346 auto plot = colormap->parentPlot();
347 347 auto [minValue, maxValue] = dataSeries.axis_range(1);
348 348 plot->yAxis->setRange(QCPRange { minValue, maxValue });
349 349 if (auto serie = dynamic_cast<SpectrogramTimeSerie*>(&dataSeries))
350 350 {
351 351 if (serie->size(0) > 2)
352 352 {
353 353 const auto& xAxis = serie->axis(0);
354 354 auto yAxis = serie->axis(1); // copy for in place reverse order
355 355 std::reverse(std::begin(yAxis), std::end(yAxis));
356 356 auto xAxisProperties = TimeSeriesUtils::axis_analysis<TimeSeriesUtils::IsLinear,
357 357 TimeSeriesUtils::CheckMedian>(xAxis);
358 358 auto yAxisProperties = TimeSeriesUtils::axis_analysis<TimeSeriesUtils::IsLog,
359 359 TimeSeriesUtils::DontCheckMedian>(yAxis);
360 360
361 361 int colormap_h_size = std::min(32000,
362 362 static_cast<int>(xAxisProperties.range / xAxisProperties.max_resolution));
363 363 auto colormap_v_size
364 364 = static_cast<int>(yAxisProperties.range / yAxisProperties.max_resolution);
365 365
366 366 colormap->data()->setSize(colormap_h_size, colormap_v_size);
367 367 colormap->data()->setRange(
368 368 QCPRange { serie->begin()->t(), (serie->end() - 1)->t() },
369 369 { minValue, maxValue });
370 370
371 371 std::vector<std::pair<int, int>> y_access_pattern;
372 372 for (int y_index = 0, cel_index = 0; y_index < colormap_v_size; y_index++)
373 373 {
374 374 double current_y = pow(
375 375 10., (yAxisProperties.max_resolution * y_index) + std::log10(minValue));
376 376 if (current_y > yAxis[cel_index])
377 377 cel_index++;
378 378 y_access_pattern.push_back({ y_index, yAxis.size() - 1 - cel_index });
379 379 }
380 380
381 381 auto line = serie->begin();
382 382 double current_time = xAxis[0];
383 383 int x_index = 0;
384 384
385 385 while (x_index < colormap_h_size)
386 386 {
387 387 if (current_time > (line + 1)->t())
388 388 {
389 389 line++;
390 390 }
391 391 if ((current_time - xAxis[0])
392 392 > (x_index * xAxisProperties.range / colormap_h_size))
393 393 {
394 394 x_index++;
395 395 }
396 396 if (line->t() <= (current_time + xAxisProperties.max_resolution))
397 397 {
398 398 std::for_each(std::cbegin(y_access_pattern), std::cend(y_access_pattern),
399 399 [&colormap, &line, x_index](const auto& acc) {
400 400 colormap->data()->setCell(x_index, acc.first, (*line)[acc.second]);
401 401 });
402 402 }
403 403 else
404 404 {
405 405 for (int y_index = 0; y_index < colormap_v_size; y_index++)
406 406 {
407 407 colormap->data()->setCell(x_index, y_index, std::nan(""));
408 408 }
409 409 }
410 410 current_time += xAxisProperties.max_resolution;
411 411 }
412 412 }
413
413 colormap->rescaleDataRange(true);
414 414 if (rescaleAxes)
415 415 {
416 416 plot->rescaleAxes();
417 417 }
418 418 }
419 419 }
420 420 };
421 421
422 422 /**
423 423 * Helper used to create/update plottables
424 424 */
425 425 struct IPlottablesHelper
426 426 {
427 427 virtual ~IPlottablesHelper() noexcept = default;
428 428 virtual PlottablesMap create(QCustomPlot& plot) const = 0;
429 429 virtual void setYAxisRange(const DateTimeRange& xAxisRange, QCustomPlot& plot) const = 0;
430 430 virtual void update(
431 431 PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes = false) const = 0;
432 432 };
433 433
434 434 /**
435 435 * Default implementation of IPlottablesHelper, which takes data series to create/update
436 436 * plottables
437 437 * @tparam T the data series' type
438 438 */
439 439 template <typename T>
440 440 struct PlottablesHelper : public IPlottablesHelper
441 441 {
442 442 explicit PlottablesHelper(std::shared_ptr<T> dataSeries) : m_DataSeries { dataSeries } {}
443 443
444 444 PlottablesMap create(QCustomPlot& plot) const override
445 445 {
446 446 return PlottablesCreator<T>::createPlottables(plot, m_DataSeries);
447 447 }
448 448
449 449 void update(
450 450 PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes) const override
451 451 {
452 452 if (m_DataSeries)
453 453 {
454 454 PlottablesUpdater<T>::updatePlottables(*m_DataSeries, plottables, range, rescaleAxes);
455 455 }
456 456 else
457 457 {
458 458 qCCritical(LOG_VisualizationGraphHelper()) << "Can't update plottables: inconsistency "
459 459 "between the type of data series and the "
460 460 "type supposed";
461 461 }
462 462 }
463 463
464 464 void setYAxisRange(const DateTimeRange& xAxisRange, QCustomPlot& plot) const override
465 465 {
466 466 if (m_DataSeries)
467 467 {
468 468 PlottablesUpdater<T>::setPlotYAxisRange(*m_DataSeries, xAxisRange, plot);
469 469 }
470 470 else
471 471 {
472 472 qCCritical(LOG_VisualizationGraphHelper()) << "Can't update plottables: inconsistency "
473 473 "between the type of data series and the "
474 474 "type supposed";
475 475 }
476 476 }
477 477
478 478 std::shared_ptr<T> m_DataSeries;
479 479 };
480 480
481 481 /// Creates IPlottablesHelper according to the type of data series a variable holds
482 482 std::unique_ptr<IPlottablesHelper> createHelper(std::shared_ptr<Variable2> variable) noexcept
483 483 {
484 484 switch (variable->type())
485 485 {
486 486 case DataSeriesType::SCALAR:
487 487 return std::make_unique<PlottablesHelper<ScalarTimeSerie>>(
488 488 std::dynamic_pointer_cast<ScalarTimeSerie>(variable->data()));
489 489 case DataSeriesType::SPECTROGRAM:
490 490 return std::make_unique<PlottablesHelper<SpectrogramTimeSerie>>(
491 491 std::dynamic_pointer_cast<SpectrogramTimeSerie>(variable->data()));
492 492 case DataSeriesType::VECTOR:
493 493 return std::make_unique<PlottablesHelper<VectorTimeSerie>>(
494 494 std::dynamic_pointer_cast<VectorTimeSerie>(variable->data()));
495 495 case DataSeriesType::MULTICOMPONENT:
496 496 return std::make_unique<PlottablesHelper<MultiComponentTimeSerie>>(
497 497 std::dynamic_pointer_cast<MultiComponentTimeSerie>(variable->data()));
498 498 default:
499 499 // Creates default helper
500 500 break;
501 501 }
502 502
503 503 return std::make_unique<PlottablesHelper<TimeSeries::ITimeSerie>>(nullptr);
504 504 }
505 505
506 506 } // namespace
507 507
508 508 PlottablesMap VisualizationGraphHelper::create(
509 509 std::shared_ptr<Variable2> variable, QCustomPlot& plot) noexcept
510 510 {
511 511 if (variable)
512 512 {
513 513 auto helper = createHelper(variable);
514 514 auto plottables = helper->create(plot);
515 515 return plottables;
516 516 }
517 517 else
518 518 {
519 519 qCDebug(LOG_VisualizationGraphHelper())
520 520 << QObject::tr("Can't create graph plottables : the variable is null");
521 521 return PlottablesMap {};
522 522 }
523 523 }
524 524
525 525 void VisualizationGraphHelper::setYAxisRange(
526 526 std::shared_ptr<Variable2> variable, QCustomPlot& plot) noexcept
527 527 {
528 528 if (variable)
529 529 {
530 530 auto helper = createHelper(variable);
531 531 helper->setYAxisRange(variable->range(), plot);
532 532 }
533 533 else
534 534 {
535 535 qCDebug(LOG_VisualizationGraphHelper())
536 536 << QObject::tr("Can't set y-axis range of plot: the variable is null");
537 537 }
538 538 }
539 539
540 540 void VisualizationGraphHelper::updateData(
541 541 PlottablesMap& plottables, std::shared_ptr<Variable2> variable, const DateTimeRange& dateTime)
542 542 {
543 543 auto helper = createHelper(variable);
544 544 helper->update(plottables, dateTime);
545 545 }
General Comments 0
You need to be logged in to leave comments. Login now