##// END OF EJS Templates
Implements zoom box interaction mode
trabillard -
r1002:6b01e312fcf2
parent child
Show More
@@ -1,416 +1,490
1 1 #include "Visualization/VisualizationGraphWidget.h"
2 2 #include "Visualization/IVisualizationWidgetVisitor.h"
3 3 #include "Visualization/VisualizationDefs.h"
4 4 #include "Visualization/VisualizationGraphHelper.h"
5 5 #include "Visualization/VisualizationGraphRenderingDelegate.h"
6 6 #include "Visualization/VisualizationZoneWidget.h"
7 7 #include "ui_VisualizationGraphWidget.h"
8 8
9 9 #include <Common/MimeTypesDef.h>
10 10 #include <Data/ArrayData.h>
11 11 #include <Data/IDataSeries.h>
12 12 #include <DragAndDrop/DragDropHelper.h>
13 13 #include <Settings/SqpSettingsDefs.h>
14 14 #include <SqpApplication.h>
15 15 #include <Time/TimeController.h>
16 16 #include <Variable/Variable.h>
17 17 #include <Variable/VariableController.h>
18 18
19 19 #include <unordered_map>
20 20
21 21 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
22 22
23 23 namespace {
24 24
25 25 /// Key pressed to enable zoom on horizontal axis
26 26 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::ControlModifier;
27 27
28 28 /// Key pressed to enable zoom on vertical axis
29 29 const auto VERTICAL_ZOOM_MODIFIER = Qt::ShiftModifier;
30 30
31 31 /// Speed of a step of a wheel event for a pan, in percentage of the axis range
32 32 const auto PAN_SPEED = 5;
33 33
34 34 /// Key pressed to enable a calibration pan
35 35 const auto VERTICAL_PAN_MODIFIER = Qt::AltModifier;
36 36
37 /// Minimum size for the zoom box, in percentage of the axis range
38 const auto ZOOM_BOX_MIN_SIZE = 0.8;
39
37 40 } // namespace
38 41
39 42 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate {
40 43
41 44 explicit VisualizationGraphWidgetPrivate(const QString &name)
42 45 : m_Name{name},
43 46 m_DoAcquisition{true},
44 47 m_IsCalibration{false},
45 48 m_RenderingDelegate{nullptr}
46 49 {
47 50 }
48 51
49 52 QString m_Name;
50 53 // 1 variable -> n qcpplot
51 54 std::map<std::shared_ptr<Variable>, PlottablesMap> m_VariableToPlotMultiMap;
52 55 bool m_DoAcquisition;
53 56 bool m_IsCalibration;
54 57 /// Delegate used to attach rendering features to the plot
55 58 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
59
60 QCPItemRect *m_DrawingRect = nullptr;
61
62 void configureDrawingRect()
63 {
64 if (m_DrawingRect) {
65 QPen p;
66 p.setWidth(2);
67 m_DrawingRect->setPen(p);
68 }
69 }
70
71 void startDrawingRect(const QPoint &pos, QCustomPlot &plot)
72 {
73 removeDrawingRect(plot);
74
75 auto axisPos = posToAxisPos(pos, plot);
76
77 m_DrawingRect = new QCPItemRect{&plot};
78 configureDrawingRect();
79
80 m_DrawingRect->topLeft->setCoords(axisPos);
81 m_DrawingRect->bottomRight->setCoords(axisPos);
82 }
83
84 void removeDrawingRect(QCustomPlot &plot)
85 {
86 if (m_DrawingRect) {
87 plot.removeItem(m_DrawingRect); // the item is deleted by QCustomPlot
88 m_DrawingRect = nullptr;
89 plot.replot(QCustomPlot::rpQueuedReplot);
90 }
91 }
92
93 QPointF posToAxisPos(const QPoint &pos, QCustomPlot &plot) const
94 {
95 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
96 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
97 return QPointF{axisX->pixelToCoord(pos.x()), axisY->pixelToCoord(pos.y())};
98 }
56 99 };
57 100
58 101 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
59 102 : VisualizationDragWidget{parent},
60 103 ui{new Ui::VisualizationGraphWidget},
61 104 impl{spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name)}
62 105 {
63 106 ui->setupUi(this);
64 107
65 108 // 'Close' options : widget is deleted when closed
66 109 setAttribute(Qt::WA_DeleteOnClose);
67 110
68 111 // Set qcpplot properties :
69 112 // - Drag (on x-axis) and zoom are enabled
70 113 // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation
71 114 ui->widget->setInteractions(QCP::iRangeZoom | QCP::iSelectItems);
72 115
73 116 // The delegate must be initialized after the ui as it uses the plot
74 117 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
75 118
76 119 connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress);
77 120 connect(ui->widget, &QCustomPlot::mouseRelease, this,
78 121 &VisualizationGraphWidget::onMouseRelease);
79 122 connect(ui->widget, &QCustomPlot::mouseMove, this, &VisualizationGraphWidget::onMouseMove);
80 123 connect(ui->widget, &QCustomPlot::mouseWheel, this, &VisualizationGraphWidget::onMouseWheel);
81 124 connect(ui->widget->xAxis, static_cast<void (QCPAxis::*)(const QCPRange &, const QCPRange &)>(
82 125 &QCPAxis::rangeChanged),
83 126 this, &VisualizationGraphWidget::onRangeChanged, Qt::DirectConnection);
84 127
85 128 // Activates menu when right clicking on the graph
86 129 ui->widget->setContextMenuPolicy(Qt::CustomContextMenu);
87 130 connect(ui->widget, &QCustomPlot::customContextMenuRequested, this,
88 131 &VisualizationGraphWidget::onGraphMenuRequested);
89 132
90 133 connect(this, &VisualizationGraphWidget::requestDataLoading, &sqpApp->variableController(),
91 134 &VariableController::onRequestDataLoading);
92 135
93 136 connect(&sqpApp->variableController(), &VariableController::updateVarDisplaying, this,
94 137 &VisualizationGraphWidget::onUpdateVarDisplaying);
95 138 }
96 139
97 140
98 141 VisualizationGraphWidget::~VisualizationGraphWidget()
99 142 {
100 143 delete ui;
101 144 }
102 145
103 146 VisualizationZoneWidget *VisualizationGraphWidget::parentZoneWidget() const noexcept
104 147 {
105 148 auto parent = parentWidget();
106 149 while (parent != nullptr && !qobject_cast<VisualizationZoneWidget *>(parent)) {
107 150 parent = parent->parentWidget();
108 151 }
109 152
110 153 return qobject_cast<VisualizationZoneWidget *>(parent);
111 154 }
112 155
113 156 void VisualizationGraphWidget::enableAcquisition(bool enable)
114 157 {
115 158 impl->m_DoAcquisition = enable;
116 159 }
117 160
118 161 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable> variable, SqpRange range)
119 162 {
120 163 // Uses delegate to create the qcpplot components according to the variable
121 164 auto createdPlottables = VisualizationGraphHelper::create(variable, *ui->widget);
122 165
123 166 if (auto dataSeries = variable->dataSeries()) {
124 167 // Set axes properties according to the units of the data series
125 168 impl->m_RenderingDelegate->setAxesProperties(dataSeries);
126 169
127 170 // Sets rendering properties for the new plottables
128 171 // Warning: this method must be called after setAxesProperties(), as it can access to some
129 172 // axes properties that have to be initialized
130 173 impl->m_RenderingDelegate->setPlottablesProperties(dataSeries, createdPlottables);
131 174 }
132 175
133 176 impl->m_VariableToPlotMultiMap.insert({variable, std::move(createdPlottables)});
134 177
135 178 connect(variable.get(), SIGNAL(updated()), this, SLOT(onDataCacheVariableUpdated()));
136 179
137 180 this->enableAcquisition(false);
138 181 this->setGraphRange(range);
139 182 this->enableAcquisition(true);
140 183
141 184 emit requestDataLoading(QVector<std::shared_ptr<Variable> >() << variable, range, false);
142 185
143 186 emit variableAdded(variable);
144 187 }
145 188
146 189 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable> variable) noexcept
147 190 {
148 191 // Each component associated to the variable :
149 192 // - is removed from qcpplot (which deletes it)
150 193 // - is no longer referenced in the map
151 194 auto variableIt = impl->m_VariableToPlotMultiMap.find(variable);
152 195 if (variableIt != impl->m_VariableToPlotMultiMap.cend()) {
153 196 emit variableAboutToBeRemoved(variable);
154 197
155 198 auto &plottablesMap = variableIt->second;
156 199
157 200 for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend();
158 201 plottableIt != plottableEnd;) {
159 202 ui->widget->removePlottable(plottableIt->second);
160 203 plottableIt = plottablesMap.erase(plottableIt);
161 204 }
162 205
163 206 impl->m_VariableToPlotMultiMap.erase(variableIt);
164 207 }
165 208
166 209 // Updates graph
167 210 ui->widget->replot();
168 211 }
169 212
170 213 QList<std::shared_ptr<Variable> > VisualizationGraphWidget::variables() const
171 214 {
172 215 auto variables = QList<std::shared_ptr<Variable> >{};
173 216 for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap);
174 217 it != std::cend(impl->m_VariableToPlotMultiMap); ++it) {
175 218 variables << it->first;
176 219 }
177 220
178 221 return variables;
179 222 }
180 223
181 224 void VisualizationGraphWidget::setYRange(std::shared_ptr<Variable> variable)
182 225 {
183 226 if (!variable) {
184 227 qCCritical(LOG_VisualizationGraphWidget()) << "Can't set y-axis range: variable is null";
185 228 return;
186 229 }
187 230
188 231 VisualizationGraphHelper::setYAxisRange(variable, *ui->widget);
189 232 }
190 233
191 234 SqpRange VisualizationGraphWidget::graphRange() const noexcept
192 235 {
193 236 auto graphRange = ui->widget->xAxis->range();
194 237 return SqpRange{graphRange.lower, graphRange.upper};
195 238 }
196 239
197 240 void VisualizationGraphWidget::setGraphRange(const SqpRange &range)
198 241 {
199 242 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange START");
200 243 ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd);
201 244 ui->widget->replot();
202 245 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange END");
203 246 }
204 247
205 248 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor)
206 249 {
207 250 if (visitor) {
208 251 visitor->visit(this);
209 252 }
210 253 else {
211 254 qCCritical(LOG_VisualizationGraphWidget())
212 255 << tr("Can't visit widget : the visitor is null");
213 256 }
214 257 }
215 258
216 259 bool VisualizationGraphWidget::canDrop(const Variable &variable) const
217 260 {
218 261 /// @todo : for the moment, a graph can always accomodate a variable
219 262 Q_UNUSED(variable);
220 263 return true;
221 264 }
222 265
223 266 bool VisualizationGraphWidget::contains(const Variable &variable) const
224 267 {
225 268 // Finds the variable among the keys of the map
226 269 auto variablePtr = &variable;
227 270 auto findVariable
228 271 = [variablePtr](const auto &entry) { return variablePtr == entry.first.get(); };
229 272
230 273 auto end = impl->m_VariableToPlotMultiMap.cend();
231 274 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
232 275 return it != end;
233 276 }
234 277
235 278 QString VisualizationGraphWidget::name() const
236 279 {
237 280 return impl->m_Name;
238 281 }
239 282
240 283 QMimeData *VisualizationGraphWidget::mimeData() const
241 284 {
242 285 auto mimeData = new QMimeData;
243 286 mimeData->setData(MIME_TYPE_GRAPH, QByteArray{});
244 287
245 288 auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange());
246 289 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
247 290
248 291 return mimeData;
249 292 }
250 293
251 294 bool VisualizationGraphWidget::isDragAllowed() const
252 295 {
253 296 return true;
254 297 }
255 298
256 299 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
257 300 {
258 301 if (highlighted) {
259 302 plot().setBackground(QBrush(QColor("#BBD5EE")));
260 303 }
261 304 else {
262 305 plot().setBackground(QBrush(Qt::white));
263 306 }
264 307
265 308 plot().update();
266 309 }
267 310
268 311 void VisualizationGraphWidget::closeEvent(QCloseEvent *event)
269 312 {
270 313 Q_UNUSED(event);
271 314
272 315 // Prevents that all variables will be removed from graph when it will be closed
273 316 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
274 317 emit variableAboutToBeRemoved(variableEntry.first);
275 318 }
276 319 }
277 320
278 321 void VisualizationGraphWidget::enterEvent(QEvent *event)
279 322 {
280 323 Q_UNUSED(event);
281 324 impl->m_RenderingDelegate->showGraphOverlay(true);
282 325 }
283 326
284 327 void VisualizationGraphWidget::leaveEvent(QEvent *event)
285 328 {
286 329 Q_UNUSED(event);
287 330 impl->m_RenderingDelegate->showGraphOverlay(false);
288 331 }
289 332
290 333 QCustomPlot &VisualizationGraphWidget::plot() noexcept
291 334 {
292 335 return *ui->widget;
293 336 }
294 337
295 338 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept
296 339 {
297 340 QMenu graphMenu{};
298 341
299 342 // Iterates on variables (unique keys)
300 343 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
301 344 end = impl->m_VariableToPlotMultiMap.cend();
302 345 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
303 346 // 'Remove variable' action
304 347 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
305 348 [ this, var = it->first ]() { removeVariable(var); });
306 349 }
307 350
308 351 if (!graphMenu.isEmpty()) {
309 352 graphMenu.exec(QCursor::pos());
310 353 }
311 354 }
312 355
313 356 void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange &t2)
314 357 {
315 358 qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: VisualizationGraphWidget::onRangeChanged")
316 359 << QThread::currentThread()->objectName() << "DoAcqui"
317 360 << impl->m_DoAcquisition;
318 361
319 362 auto graphRange = SqpRange{t1.lower, t1.upper};
320 363 auto oldGraphRange = SqpRange{t2.lower, t2.upper};
321 364
322 365 if (impl->m_DoAcquisition) {
323 366 QVector<std::shared_ptr<Variable> > variableUnderGraphVector;
324 367
325 368 for (auto it = impl->m_VariableToPlotMultiMap.begin(),
326 369 end = impl->m_VariableToPlotMultiMap.end();
327 370 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
328 371 variableUnderGraphVector.push_back(it->first);
329 372 }
330 373 emit requestDataLoading(std::move(variableUnderGraphVector), graphRange,
331 374 !impl->m_IsCalibration);
332 375
333 376 if (!impl->m_IsCalibration) {
334 377 qCDebug(LOG_VisualizationGraphWidget())
335 378 << tr("TORM: VisualizationGraphWidget::Synchronize notify !!")
336 379 << QThread::currentThread()->objectName() << graphRange << oldGraphRange;
337 380 emit synchronize(graphRange, oldGraphRange);
338 381 }
339 382 }
340 383 }
341 384
342 385 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
343 386 {
344 387 // Handles plot rendering when mouse is moving
345 388 impl->m_RenderingDelegate->onMouseMove(event);
346 389
390 if (impl->m_DrawingRect) {
391 auto axisPos = impl->posToAxisPos(event->pos(), plot());
392 impl->m_DrawingRect->bottomRight->setCoords(axisPos);
393 }
394
347 395 VisualizationDragWidget::mouseMoveEvent(event);
348 396 }
349 397
350 398 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
351 399 {
352 400 auto value = event->angleDelta().x() + event->angleDelta().y();
353 401 if (value != 0) {
354 402
355 403 auto direction = value > 0 ? 1.0 : -1.0;
356 404 auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER);
357 405 auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER);
358 406 impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER);
359 407
360 408 auto zoomOrientations = QFlags<Qt::Orientation>{};
361 409 zoomOrientations.setFlag(Qt::Horizontal, isZoomX);
362 410 zoomOrientations.setFlag(Qt::Vertical, isZoomY);
363 411
364 412 ui->widget->axisRect()->setRangeZoom(zoomOrientations);
365 413
366 414 if (!isZoomX && !isZoomY) {
367 415 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
368 416 auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0));
369 417
370 418 axis->setRange(axis->range() + diff);
371 419
372 420 if (plot().noAntialiasingOnDrag()) {
373 421 plot().setNotAntialiasedElements(QCP::aeAll);
374 422 }
375 423
376 424 plot().replot(QCustomPlot::rpQueuedReplot);
377 425 }
378 426 }
379 427 }
380 428
381 429 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
382 430 {
431 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox) {
432 impl->startDrawingRect(event->pos(), plot());
433 }
434
383 435 VisualizationDragWidget::mousePressEvent(event);
384 436 }
385 437
386 438 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
387 439 {
440 if (impl->m_DrawingRect) {
441
442 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
443 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
444
445 auto newAxisXRange = QCPRange{impl->m_DrawingRect->topLeft->coords().x(),
446 impl->m_DrawingRect->bottomRight->coords().x()};
447
448 auto newAxisYRange = QCPRange{impl->m_DrawingRect->topLeft->coords().y(),
449 impl->m_DrawingRect->bottomRight->coords().y()};
450
451 impl->removeDrawingRect(plot());
452
453 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
454 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) {
455 axisX->setRange(newAxisXRange);
456 axisY->setRange(newAxisYRange);
457
458 plot().replot(QCustomPlot::rpQueuedReplot);
459 }
460 }
461
388 462 impl->m_IsCalibration = false;
389 463 }
390 464
391 465 void VisualizationGraphWidget::onDataCacheVariableUpdated()
392 466 {
393 467 auto graphRange = ui->widget->xAxis->range();
394 468 auto dateTime = SqpRange{graphRange.lower, graphRange.upper};
395 469
396 470 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
397 471 auto variable = variableEntry.first;
398 472 qCDebug(LOG_VisualizationGraphWidget())
399 473 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
400 474 qCDebug(LOG_VisualizationGraphWidget())
401 475 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
402 476 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range())) {
403 477 VisualizationGraphHelper::updateData(variableEntry.second, variable->dataSeries(),
404 478 variable->range());
405 479 }
406 480 }
407 481 }
408 482
409 483 void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr<Variable> variable,
410 484 const SqpRange &range)
411 485 {
412 486 auto it = impl->m_VariableToPlotMultiMap.find(variable);
413 487 if (it != impl->m_VariableToPlotMultiMap.end()) {
414 488 VisualizationGraphHelper::updateData(it->second, variable->dataSeries(), range);
415 489 }
416 490 }
General Comments 1
Under Review
author

Auto status change to "Under Review"

You need to be logged in to leave comments. Login now