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