##// END OF EJS Templates
Settings binding (5)...
Alexandre Leroux -
r434:5e3a35bfa2fe
parent child
Show More
@@ -1,401 +1,412
1 1 #include "Visualization/VisualizationGraphWidget.h"
2 2 #include "Visualization/IVisualizationWidgetVisitor.h"
3 3 #include "Visualization/VisualizationGraphHelper.h"
4 4 #include "ui_VisualizationGraphWidget.h"
5 5
6 6 #include <Data/ArrayData.h>
7 7 #include <Data/IDataSeries.h>
8 #include <Settings/SqpSettingsDefs.h>
8 9 #include <SqpApplication.h>
9 10 #include <Variable/Variable.h>
10 11 #include <Variable/VariableController.h>
11 12
12 13 #include <unordered_map>
13 14
14 15 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
15 16
16 17 namespace {
17 18
18 19 /// Key pressed to enable zoom on horizontal axis
19 20 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::NoModifier;
20 21
21 22 /// Key pressed to enable zoom on vertical axis
22 23 const auto VERTICAL_ZOOM_MODIFIER = Qt::ControlModifier;
23 24
25 /// Gets a tolerance value from application settings. If the setting can't be found, the default
26 /// value passed in parameter is returned
27 double toleranceValue(const QString &key, double defaultValue) noexcept
28 {
29 return QSettings{}.value(key, defaultValue).toDouble();
30 }
31
24 32 } // namespace
25 33
26 34 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate {
27 35
28 36 explicit VisualizationGraphWidgetPrivate() : m_DoSynchronize{true}, m_IsCalibration{false} {}
29 37
30 38
31 39 // Return the operation when range changed
32 40 VisualizationGraphWidgetZoomType getZoomType(const QCPRange &t1, const QCPRange &t2);
33 41
34 42 // 1 variable -> n qcpplot
35 43 std::multimap<std::shared_ptr<Variable>, QCPAbstractPlottable *> m_VariableToPlotMultiMap;
36 44
37 45 bool m_DoSynchronize;
38 46 bool m_IsCalibration;
39 47 };
40 48
41 49 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
42 50 : QWidget{parent},
43 51 ui{new Ui::VisualizationGraphWidget},
44 52 impl{spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>()}
45 53 {
46 54 ui->setupUi(this);
47 55
48 56 ui->graphNameLabel->setText(name);
49 57
50 58 // 'Close' options : widget is deleted when closed
51 59 setAttribute(Qt::WA_DeleteOnClose);
52 60 connect(ui->closeButton, &QToolButton::clicked, this, &VisualizationGraphWidget::close);
53 61 ui->closeButton->setIcon(sqpApp->style()->standardIcon(QStyle::SP_TitleBarCloseButton));
54 62
55 63 // Set qcpplot properties :
56 64 // - Drag (on x-axis) and zoom are enabled
57 65 // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation
58 66 ui->widget->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);
59 67 ui->widget->axisRect()->setRangeDrag(Qt::Horizontal);
60 68 connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress);
61 69 connect(ui->widget, &QCustomPlot::mouseRelease, this,
62 70 &VisualizationGraphWidget::onMouseRelease);
63 71 connect(ui->widget, &QCustomPlot::mouseWheel, this, &VisualizationGraphWidget::onMouseWheel);
64 72 connect(ui->widget->xAxis, static_cast<void (QCPAxis::*)(const QCPRange &, const QCPRange &)>(
65 73 &QCPAxis::rangeChanged),
66 74 this, &VisualizationGraphWidget::onRangeChanged, Qt::DirectConnection);
67 75
68 76 // Activates menu when right clicking on the graph
69 77 ui->widget->setContextMenuPolicy(Qt::CustomContextMenu);
70 78 connect(ui->widget, &QCustomPlot::customContextMenuRequested, this,
71 79 &VisualizationGraphWidget::onGraphMenuRequested);
72 80
73 81 connect(this, &VisualizationGraphWidget::requestDataLoading, &sqpApp->variableController(),
74 82 &VariableController::onRequestDataLoading);
75 83 }
76 84
77 85
78 86 VisualizationGraphWidget::~VisualizationGraphWidget()
79 87 {
80 88 delete ui;
81 89 }
82 90
83 91 void VisualizationGraphWidget::enableSynchronize(bool enable)
84 92 {
85 93 impl->m_DoSynchronize = enable;
86 94 }
87 95
88 96 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable> variable)
89 97 {
90 98 // Uses delegate to create the qcpplot components according to the variable
91 99 auto createdPlottables = VisualizationGraphHelper::create(variable, *ui->widget);
92 100
93 101 for (auto createdPlottable : qAsConst(createdPlottables)) {
94 102 impl->m_VariableToPlotMultiMap.insert({variable, createdPlottable});
95 103 }
96 104
97 105 connect(variable.get(), SIGNAL(updated()), this, SLOT(onDataCacheVariableUpdated()));
98 106 }
99 107 void VisualizationGraphWidget::addVariableUsingGraph(std::shared_ptr<Variable> variable)
100 108 {
101 109
102 110 // when adding a variable, we need to set its time range to the current graph range
103 111 auto grapheRange = ui->widget->xAxis->range();
104 112 auto dateTime = SqpDateTime{grapheRange.lower, grapheRange.upper};
105 113 variable->setDateTime(dateTime);
106 114
107 115 auto variableDateTimeWithTolerance = dateTime;
108 116
109 // add 20% tolerance for each side
110 auto tolerance = 0.2 * (dateTime.m_TEnd - dateTime.m_TStart);
117 // add tolerance for each side
118 auto toleranceFactor
119 = toleranceValue(GENERAL_TOLERANCE_AT_INIT_KEY, GENERAL_TOLERANCE_AT_INIT_DEFAULT_VALUE);
120 auto tolerance = toleranceFactor * (dateTime.m_TEnd - dateTime.m_TStart);
111 121 variableDateTimeWithTolerance.m_TStart -= tolerance;
112 122 variableDateTimeWithTolerance.m_TEnd += tolerance;
113 123
114 124 // Uses delegate to create the qcpplot components according to the variable
115 125 auto createdPlottables = VisualizationGraphHelper::create(variable, *ui->widget);
116 126
117 127 for (auto createdPlottable : qAsConst(createdPlottables)) {
118 128 impl->m_VariableToPlotMultiMap.insert({variable, createdPlottable});
119 129 }
120 130
121 131 connect(variable.get(), SIGNAL(updated()), this, SLOT(onDataCacheVariableUpdated()));
122 132
123 133 // CHangement detected, we need to ask controller to request data loading
124 134 emit requestDataLoading(variable, variableDateTimeWithTolerance);
125 135 }
126 136
127 137 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable> variable) noexcept
128 138 {
129 139 // Each component associated to the variable :
130 140 // - is removed from qcpplot (which deletes it)
131 141 // - is no longer referenced in the map
132 142 auto componentsIt = impl->m_VariableToPlotMultiMap.equal_range(variable);
133 143 for (auto it = componentsIt.first; it != componentsIt.second;) {
134 144 ui->widget->removePlottable(it->second);
135 145 it = impl->m_VariableToPlotMultiMap.erase(it);
136 146 }
137 147
138 148 // Updates graph
139 149 ui->widget->replot();
140 150 }
141 151
142 152 void VisualizationGraphWidget::setRange(std::shared_ptr<Variable> variable,
143 153 const SqpDateTime &range)
144 154 {
145 155 // Note: in case of different axes that depends on variable, we could start with a code like
146 156 // that:
147 157 // auto componentsIt = impl->m_VariableToPlotMultiMap.equal_range(variable);
148 158 // for (auto it = componentsIt.first; it != componentsIt.second;) {
149 159 // }
150 160 ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd);
151 161 ui->widget->replot();
152 162 }
153 163
154 164 SqpDateTime VisualizationGraphWidget::graphRange() const noexcept
155 165 {
156 166 auto grapheRange = ui->widget->xAxis->range();
157 167 return SqpDateTime{grapheRange.lower, grapheRange.upper};
158 168 }
159 169
160 170 void VisualizationGraphWidget::setGraphRange(const SqpDateTime &range)
161 171 {
162 172 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange START");
163 173 ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd);
164 174 ui->widget->replot();
165 175 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange END");
166 176 }
167 177
168 178 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor)
169 179 {
170 180 if (visitor) {
171 181 visitor->visit(this);
172 182 }
173 183 else {
174 184 qCCritical(LOG_VisualizationGraphWidget())
175 185 << tr("Can't visit widget : the visitor is null");
176 186 }
177 187 }
178 188
179 189 bool VisualizationGraphWidget::canDrop(const Variable &variable) const
180 190 {
181 191 /// @todo : for the moment, a graph can always accomodate a variable
182 192 Q_UNUSED(variable);
183 193 return true;
184 194 }
185 195
186 196 bool VisualizationGraphWidget::contains(const Variable &variable) const
187 197 {
188 198 // Finds the variable among the keys of the map
189 199 auto variablePtr = &variable;
190 200 auto findVariable
191 201 = [variablePtr](const auto &entry) { return variablePtr == entry.first.get(); };
192 202
193 203 auto end = impl->m_VariableToPlotMultiMap.cend();
194 204 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
195 205 return it != end;
196 206 }
197 207
198 208 QString VisualizationGraphWidget::name() const
199 209 {
200 210 return ui->graphNameLabel->text();
201 211 }
202 212
203 213 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept
204 214 {
205 215 QMenu graphMenu{};
206 216
207 217 // Iterates on variables (unique keys)
208 218 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
209 219 end = impl->m_VariableToPlotMultiMap.cend();
210 220 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
211 221 // 'Remove variable' action
212 222 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
213 223 [ this, var = it->first ]() { removeVariable(var); });
214 224 }
215 225
216 226 if (!graphMenu.isEmpty()) {
217 227 graphMenu.exec(mapToGlobal(pos));
218 228 }
219 229 }
220 230
221 231 void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange &t2)
222 232 {
223 qCInfo(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::onRangeChanged")
224 << QThread::currentThread()->objectName();
233 qCInfo(LOG_VisualizationGraphWidget())
234 << tr("VisualizationGraphWidget::onRangeChanged") << QThread::currentThread()->objectName();
225 235
226 236 auto dateTimeRange = SqpDateTime{t1.lower, t1.upper};
227 237
228 238 auto zoomType = impl->getZoomType(t1, t2);
229 239 for (auto it = impl->m_VariableToPlotMultiMap.cbegin();
230 240 it != impl->m_VariableToPlotMultiMap.cend(); ++it) {
231 241
232 242 auto variable = it->first;
233 243 auto currentDateTime = dateTimeRange;
234 244
235 auto toleranceFactor = 0.2;
245 auto toleranceFactor = toleranceValue(GENERAL_TOLERANCE_AT_UPDATE_KEY,
246 GENERAL_TOLERANCE_AT_UPDATE_DEFAULT_VALUE);
236 247 auto tolerance = toleranceFactor * (currentDateTime.m_TEnd - currentDateTime.m_TStart);
237 248 auto variableDateTimeWithTolerance = currentDateTime;
238 249 variableDateTimeWithTolerance.m_TStart -= tolerance;
239 250 variableDateTimeWithTolerance.m_TEnd += tolerance;
240 251
241 252 qCDebug(LOG_VisualizationGraphWidget()) << "r" << currentDateTime;
242 253 qCDebug(LOG_VisualizationGraphWidget()) << "t" << variableDateTimeWithTolerance;
243 254 qCDebug(LOG_VisualizationGraphWidget()) << "v" << variable->dateTime();
244 255 // If new range with tol is upper than variable datetime parameters. we need to request new
245 256 // data
246 257 if (!variable->contains(variableDateTimeWithTolerance)) {
247 258
248 259 auto variableDateTimeWithTolerance = currentDateTime;
249 260 if (!variable->isInside(currentDateTime)) {
250 261 auto variableDateTime = variable->dateTime();
251 262 if (variable->contains(variableDateTimeWithTolerance)) {
252 263 qCDebug(LOG_VisualizationGraphWidget())
253 264 << tr("TORM: Detection zoom in that need request:");
254 265 // add tolerance for each side
255 266 tolerance
256 267 = toleranceFactor * (currentDateTime.m_TEnd - currentDateTime.m_TStart);
257 268 variableDateTimeWithTolerance.m_TStart -= tolerance;
258 269 variableDateTimeWithTolerance.m_TEnd += tolerance;
259 270 }
260 271 else if (variableDateTime.m_TStart < currentDateTime.m_TStart) {
261 272 qCInfo(LOG_VisualizationGraphWidget()) << tr("TORM: Detection pan to right:");
262 273
263 274 auto diffEndToKeepDelta = currentDateTime.m_TEnd - variableDateTime.m_TEnd;
264 275 currentDateTime.m_TStart = variableDateTime.m_TStart + diffEndToKeepDelta;
265 276 // Tolerance have to be added to the right
266 277 // add tolerance for right (end) side
267 278 tolerance
268 279 = toleranceFactor * (currentDateTime.m_TEnd - currentDateTime.m_TStart);
269 280 variableDateTimeWithTolerance.m_TEnd += tolerance;
270 281 }
271 282 else if (variableDateTime.m_TEnd > currentDateTime.m_TEnd) {
272 283 qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: Detection pan to left: ");
273 284 auto diffStartToKeepDelta
274 285 = variableDateTime.m_TStart - currentDateTime.m_TStart;
275 286 currentDateTime.m_TEnd = variableDateTime.m_TEnd - diffStartToKeepDelta;
276 287 // Tolerance have to be added to the left
277 288 // add tolerance for left (start) side
278 289 tolerance
279 290 = toleranceFactor * (currentDateTime.m_TEnd - currentDateTime.m_TStart);
280 291 variableDateTimeWithTolerance.m_TStart -= tolerance;
281 292 }
282 293 else {
283 294 qCCritical(LOG_VisualizationGraphWidget())
284 295 << tr("Detection anormal zoom detection: ");
285 296 }
286 297 }
287 298 else {
288 299 qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: Detection zoom out: ");
289 300 // add tolerance for each side
290 301 tolerance = toleranceFactor * (currentDateTime.m_TEnd - currentDateTime.m_TStart);
291 302 variableDateTimeWithTolerance.m_TStart -= tolerance;
292 303 variableDateTimeWithTolerance.m_TEnd += tolerance;
293 304 zoomType = VisualizationGraphWidgetZoomType::ZoomOut;
294 305 }
295 306 if (!variable->contains(dateTimeRange)) {
296 307 qCDebug(LOG_VisualizationGraphWidget())
297 308 << "TORM: Modif on variable datetime detected" << currentDateTime;
298 309 variable->setDateTime(currentDateTime);
299 310 }
300 311
301 312 qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: Request data detection: ");
302 313 // CHangement detected, we need to ask controller to request data loading
303 314 emit requestDataLoading(variable, variableDateTimeWithTolerance);
304 315 }
305 316 else {
306 317 qCInfo(LOG_VisualizationGraphWidget())
307 318 << tr("TORM: Detection zoom in that doesn't need request: ");
308 319 zoomType = VisualizationGraphWidgetZoomType::ZoomIn;
309 320 }
310 321 }
311 322
312 323 if (impl->m_DoSynchronize && !impl->m_IsCalibration) {
313 324 auto oldDateTime = SqpDateTime{t2.lower, t2.upper};
314 325 qCDebug(LOG_VisualizationGraphWidget())
315 326 << tr("TORM: VisualizationGraphWidget::Synchronize notify !!")
316 327 << QThread::currentThread()->objectName();
317 328 emit synchronize(dateTimeRange, oldDateTime, zoomType);
318 329 }
319 330 }
320 331
321 332 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
322 333 {
323 334 auto zoomOrientations = QFlags<Qt::Orientation>{};
324 335
325 336 // Lambda that enables a zoom orientation if the key modifier related to this orientation
326 337 // has
327 338 // been pressed
328 339 auto enableOrientation
329 340 = [&zoomOrientations, event](const auto &orientation, const auto &modifier) {
330 341 auto orientationEnabled = event->modifiers().testFlag(modifier);
331 342 zoomOrientations.setFlag(orientation, orientationEnabled);
332 343 };
333 344 enableOrientation(Qt::Vertical, VERTICAL_ZOOM_MODIFIER);
334 345 enableOrientation(Qt::Horizontal, HORIZONTAL_ZOOM_MODIFIER);
335 346
336 347 ui->widget->axisRect()->setRangeZoom(zoomOrientations);
337 348 }
338 349
339 350 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
340 351 {
341 352 impl->m_IsCalibration = event->modifiers().testFlag(Qt::ControlModifier);
342 353 }
343 354
344 355 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
345 356 {
346 357 impl->m_IsCalibration = false;
347 358 }
348 359
349 360 void VisualizationGraphWidget::onDataCacheVariableUpdated()
350 361 {
351 362 // NOTE:
352 363 // We don't want to call the method for each component of a variable unitarily, but for
353 364 // all
354 365 // its components at once (eg its three components in the case of a vector).
355 366
356 367 // The unordered_multimap does not do this easily, so the question is whether to:
357 368 // - use an ordered_multimap and the algos of std to group the values by key
358 369 // - use a map (unique keys) and store as values directly the list of components
359 370
360 371 auto grapheRange = ui->widget->xAxis->range();
361 372 auto dateTime = SqpDateTime{grapheRange.lower, grapheRange.upper};
362 373
363 374 for (auto it = impl->m_VariableToPlotMultiMap.cbegin();
364 375 it != impl->m_VariableToPlotMultiMap.cend(); ++it) {
365 376 auto variable = it->first;
366 377 qCDebug(LOG_VisualizationGraphWidget())
367 378 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S"
368 379 << variable->dateTime();
369 380 qCDebug(LOG_VisualizationGraphWidget())
370 381 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
371 382 if (dateTime.contains(variable->dateTime()) || dateTime.intersect(variable->dateTime())) {
372 383
373 384 VisualizationGraphHelper::updateData(QVector<QCPAbstractPlottable *>{} << it->second,
374 385 variable->dataSeries(), variable->dateTime());
375 386 }
376 387 }
377 388 }
378 389
379 390 VisualizationGraphWidgetZoomType
380 391 VisualizationGraphWidget::VisualizationGraphWidgetPrivate::getZoomType(const QCPRange &t1,
381 392 const QCPRange &t2)
382 393 {
383 394 // t1.lower <= t2.lower && t2.upper <= t1.upper
384 395 auto zoomType = VisualizationGraphWidgetZoomType::Unknown;
385 396 if (t1.lower <= t2.lower && t2.upper <= t1.upper) {
386 397 zoomType = VisualizationGraphWidgetZoomType::ZoomOut;
387 398 }
388 399 else if (t1.lower > t2.lower && t1.upper > t2.upper) {
389 400 zoomType = VisualizationGraphWidgetZoomType::PanRight;
390 401 }
391 402 else if (t1.lower < t2.lower && t1.upper < t2.upper) {
392 403 zoomType = VisualizationGraphWidgetZoomType::PanLeft;
393 404 }
394 405 else if (t1.lower > t2.lower && t2.upper > t1.upper) {
395 406 zoomType = VisualizationGraphWidgetZoomType::ZoomIn;
396 407 }
397 408 else {
398 409 qCCritical(LOG_VisualizationGraphWidget()) << "getZoomType: Unknown type detected";
399 410 }
400 411 return zoomType;
401 412 }
General Comments 0
You need to be logged in to leave comments. Login now