##// END OF EJS Templates
Updates VisualizationGraphHelper to use variable's type instead of dataseries
Alexandre Leroux -
r1280:5b0b3c10ce1e
parent child
Show More
@@ -1,41 +1,41
1 1 #ifndef SCIQLOP_VISUALIZATIONGRAPHHELPER_H
2 2 #define SCIQLOP_VISUALIZATIONGRAPHHELPER_H
3 3
4 4 #include "Visualization/VisualizationDefs.h"
5 5
6 6 #include <Data/SqpRange.h>
7 7
8 8 #include <QLoggingCategory>
9 9 #include <QVector>
10 10
11 11 #include <memory>
12 12
13 13 Q_DECLARE_LOGGING_CATEGORY(LOG_VisualizationGraphHelper)
14 14
15 15 class IDataSeries;
16 16 class QCPAbstractPlottable;
17 17 class QCustomPlot;
18 18 class Variable;
19 19
20 20 /**
21 21 * @brief The VisualizationGraphHelper class aims to create the QCustomPlot components relative to a
22 22 * variable, depending on the data series of this variable
23 23 */
24 24 struct VisualizationGraphHelper {
25 25 /**
26 26 * Creates (if possible) the QCustomPlot components relative to the variable passed in
27 27 * parameter, and adds these to the plot passed in parameter.
28 28 * @param variable the variable for which to create the components
29 29 * @param plot the plot in which to add the created components. It takes ownership of these
30 30 * components.
31 31 * @return the list of the components created
32 32 */
33 33 static PlottablesMap create(std::shared_ptr<Variable> variable, QCustomPlot &plot) noexcept;
34 34
35 static void updateData(PlottablesMap &plottables, std::shared_ptr<IDataSeries> dataSeries,
35 static void updateData(PlottablesMap &plottables, std::shared_ptr<Variable> variable,
36 36 const SqpRange &dateTime);
37 37
38 38 static void setYAxisRange(std::shared_ptr<Variable> variable, QCustomPlot &plot) noexcept;
39 39 };
40 40
41 41 #endif // SCIQLOP_VISUALIZATIONGRAPHHELPER_H
@@ -1,340 +1,361
1 1 #include "Visualization/VisualizationGraphHelper.h"
2 2 #include "Visualization/qcustomplot.h"
3 3
4 4 #include <Data/DataSeriesUtils.h>
5 5 #include <Data/ScalarSeries.h>
6 6 #include <Data/SpectrogramSeries.h>
7 7 #include <Data/VectorSeries.h>
8 8
9 9 #include <Variable/Variable.h>
10 10
11 11 Q_LOGGING_CATEGORY(LOG_VisualizationGraphHelper, "VisualizationGraphHelper")
12 12
13 13 namespace {
14 14
15 15 class SqpDataContainer : public QCPGraphDataContainer {
16 16 public:
17 17 void appendGraphData(const QCPGraphData &data) { mData.append(data); }
18 18 };
19 19
20 20 /**
21 21 * Struct used to create plottables, depending on the type of the data series from which to create
22 22 * them
23 23 * @tparam T the data series' type
24 24 * @remarks Default implementation can't create plottables
25 25 */
26 26 template <typename T, typename Enabled = void>
27 27 struct PlottablesCreator {
28 static PlottablesMap createPlottables(T &, QCustomPlot &)
28 static PlottablesMap createPlottables(QCustomPlot &)
29 29 {
30 30 qCCritical(LOG_DataSeries())
31 31 << QObject::tr("Can't create plottables: unmanaged data series type");
32 32 return {};
33 33 }
34 34 };
35 35
36 PlottablesMap createGraphs(QCustomPlot &plot, int nbGraphs)
37 {
38 PlottablesMap result{};
39
40 // Creates {nbGraphs} QCPGraph to add to the plot
41 for (auto i = 0; i < nbGraphs; ++i) {
42 auto graph = plot.addGraph();
43 result.insert({i, graph});
44 }
45
46 plot.replot();
47
48 return result;
49 }
50
36 51 /**
37 * Specialization of PlottablesCreator for scalars and vectors
52 * Specialization of PlottablesCreator for scalars
38 53 * @sa ScalarSeries
39 * @sa VectorSeries
40 54 */
41 55 template <typename T>
42 struct PlottablesCreator<T,
43 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
44 or std::is_base_of<VectorSeries, T>::value> > {
45 static PlottablesMap createPlottables(T &dataSeries, QCustomPlot &plot)
46 {
47 PlottablesMap result{};
48
49 // Gets the number of components of the data series
50 dataSeries.lockRead();
51 auto componentCount = dataSeries.valuesData()->componentCount();
52 dataSeries.unlock();
53
54 // For each component of the data series, creates a QCPGraph to add to the plot
55 for (auto i = 0; i < componentCount; ++i) {
56 auto graph = plot.addGraph();
57 result.insert({i, graph});
58 }
59
60 plot.replot();
56 struct PlottablesCreator<T, typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value> > {
57 static PlottablesMap createPlottables(QCustomPlot &plot) { return createGraphs(plot, 1); }
58 };
61 59
62 return result;
63 }
60 /**
61 * Specialization of PlottablesCreator for vectors
62 * @sa VectorSeries
63 */
64 template <typename T>
65 struct PlottablesCreator<T, typename std::enable_if_t<std::is_base_of<VectorSeries, T>::value> > {
66 static PlottablesMap createPlottables(QCustomPlot &plot) { return createGraphs(plot, 3); }
64 67 };
65 68
66 69 /**
67 70 * Specialization of PlottablesCreator for spectrograms
68 71 * @sa SpectrogramSeries
69 72 */
70 73 template <typename T>
71 74 struct PlottablesCreator<T,
72 75 typename std::enable_if_t<std::is_base_of<SpectrogramSeries, T>::value> > {
73 static PlottablesMap createPlottables(T &dataSeries, QCustomPlot &plot)
76 static PlottablesMap createPlottables(QCustomPlot &plot)
74 77 {
75 78 PlottablesMap result{};
76 79 result.insert({0, new QCPColorMap{plot.xAxis, plot.yAxis}});
77 80
78 81 plot.replot();
79 82
80 83 return result;
81 84 }
82 85 };
83 86
84 87 /**
85 88 * Struct used to update plottables, depending on the type of the data series from which to update
86 89 * them
87 90 * @tparam T the data series' type
88 91 * @remarks Default implementation can't update plottables
89 92 */
90 93 template <typename T, typename Enabled = void>
91 94 struct PlottablesUpdater {
92 95 static void setPlotYAxisRange(T &, const SqpRange &, QCustomPlot &)
93 96 {
94 97 qCCritical(LOG_VisualizationGraphHelper())
95 98 << QObject::tr("Can't set plot y-axis range: unmanaged data series type");
96 99 }
97 100
98 101 static void updatePlottables(T &, PlottablesMap &, const SqpRange &, bool)
99 102 {
100 103 qCCritical(LOG_VisualizationGraphHelper())
101 104 << QObject::tr("Can't update plottables: unmanaged data series type");
102 105 }
103 106 };
104 107
105 108 /**
106 109 * Specialization of PlottablesUpdater for scalars and vectors
107 110 * @sa ScalarSeries
108 111 * @sa VectorSeries
109 112 */
110 113 template <typename T>
111 114 struct PlottablesUpdater<T,
112 115 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
113 116 or std::is_base_of<VectorSeries, T>::value> > {
114 117 static void setPlotYAxisRange(T &dataSeries, const SqpRange &xAxisRange, QCustomPlot &plot)
115 118 {
116 119 auto minValue = 0., maxValue = 0.;
117 120
118 121 dataSeries.lockRead();
119 122 auto valuesBounds = dataSeries.valuesBounds(xAxisRange.m_TStart, xAxisRange.m_TEnd);
120 123 auto end = dataSeries.cend();
121 124 if (valuesBounds.first != end && valuesBounds.second != end) {
122 125 auto rangeValue = [](const auto &value) { return std::isnan(value) ? 0. : value; };
123 126
124 127 minValue = rangeValue(valuesBounds.first->minValue());
125 128 maxValue = rangeValue(valuesBounds.second->maxValue());
126 129 }
127 130 dataSeries.unlock();
128 131
129 132 plot.yAxis->setRange(QCPRange{minValue, maxValue});
130 133 }
131 134
132 135 static void updatePlottables(T &dataSeries, PlottablesMap &plottables, const SqpRange &range,
133 136 bool rescaleAxes)
134 137 {
135 138
136 139 // For each plottable to update, resets its data
137 140 std::map<int, QSharedPointer<SqpDataContainer> > dataContainers{};
138 141 for (const auto &plottable : plottables) {
139 142 if (auto graph = dynamic_cast<QCPGraph *>(plottable.second)) {
140 143 auto dataContainer = QSharedPointer<SqpDataContainer>::create();
141 144 graph->setData(dataContainer);
142 145
143 146 dataContainers.insert({plottable.first, dataContainer});
144 147 }
145 148 }
146 149 dataSeries.lockRead();
147 150
148 151 // - Gets the data of the series included in the current range
149 152 // - Updates each plottable by adding, for each data item, a point that takes x-axis data
150 153 // and value data. The correct value is retrieved according to the index of the component
151 154 auto subDataIts = dataSeries.xAxisRange(range.m_TStart, range.m_TEnd);
152 155 for (auto it = subDataIts.first; it != subDataIts.second; ++it) {
153 156 for (const auto &dataContainer : dataContainers) {
154 157 auto componentIndex = dataContainer.first;
155 158 dataContainer.second->appendGraphData(
156 159 QCPGraphData(it->x(), it->value(componentIndex)));
157 160 }
158 161 }
159 162
160 163 dataSeries.unlock();
161 164
162 165 if (!plottables.empty()) {
163 166 auto plot = plottables.begin()->second->parentPlot();
164 167
165 168 if (rescaleAxes) {
166 169 plot->rescaleAxes();
167 170 }
168 171 }
169 172 }
170 173 };
171 174
172 175 /**
173 176 * Specialization of PlottablesUpdater for spectrograms
174 177 * @sa SpectrogramSeries
175 178 */
176 179 template <typename T>
177 180 struct PlottablesUpdater<T,
178 181 typename std::enable_if_t<std::is_base_of<SpectrogramSeries, T>::value> > {
179 182 static void setPlotYAxisRange(T &dataSeries, const SqpRange &xAxisRange, QCustomPlot &plot)
180 183 {
181 184 double min, max;
182 185 std::tie(min, max) = dataSeries.yBounds();
183 186
184 187 if (!std::isnan(min) && !std::isnan(max)) {
185 188 plot.yAxis->setRange(QCPRange{min, max});
186 189 }
187 190 }
188 191
189 192 static void updatePlottables(T &dataSeries, PlottablesMap &plottables, const SqpRange &range,
190 193 bool rescaleAxes)
191 194 {
192 195 if (plottables.empty()) {
193 196 qCDebug(LOG_VisualizationGraphHelper())
194 197 << QObject::tr("Can't update spectrogram: no colormap has been associated");
195 198 return;
196 199 }
197 200
198 201 // Gets the colormap to update (normally there is only one colormap)
199 202 Q_ASSERT(plottables.size() == 1);
200 203 auto colormap = dynamic_cast<QCPColorMap *>(plottables.at(0));
201 204 Q_ASSERT(colormap != nullptr);
202 205
203 206 dataSeries.lockRead();
204 207
205 208 // Processing spectrogram data for display in QCustomPlot
206 209 auto its = dataSeries.xAxisRange(range.m_TStart, range.m_TEnd);
207 210
208 211 // Computes logarithmic y-axis resolution for the spectrogram
209 212 auto yData = its.first->y();
210 213 auto yResolution = DataSeriesUtils::resolution(yData.begin(), yData.end(), true);
211 214
212 215 // Generates mesh for colormap
213 216 auto mesh = DataSeriesUtils::regularMesh(
214 217 its.first, its.second, DataSeriesUtils::Resolution{dataSeries.xResolution()},
215 218 yResolution);
216 219
217 220 dataSeries.unlock();
218 221
219 222 colormap->data()->setSize(mesh.m_NbX, mesh.m_NbY);
220 223 if (!mesh.isEmpty()) {
221 224 colormap->data()->setRange(
222 225 QCPRange{mesh.m_XMin, mesh.xMax()},
223 226 // y-axis range is converted to linear values
224 227 QCPRange{std::pow(10, mesh.m_YMin), std::pow(10, mesh.yMax())});
225 228
226 229 // Sets values
227 230 auto index = 0;
228 231 for (auto it = mesh.m_Data.begin(), end = mesh.m_Data.end(); it != end; ++it, ++index) {
229 232 auto xIndex = index % mesh.m_NbX;
230 233 auto yIndex = index / mesh.m_NbX;
231 234
232 235 colormap->data()->setCell(xIndex, yIndex, *it);
233 236
234 237 // Makes the NaN values to be transparent in the colormap
235 238 if (std::isnan(*it)) {
236 239 colormap->data()->setAlpha(xIndex, yIndex, 0);
237 240 }
238 241 }
239 242 }
240 243
241 244 // Rescales axes
242 245 auto plot = colormap->parentPlot();
243 246
244 247 if (rescaleAxes) {
245 248 plot->rescaleAxes();
246 249 }
247 250 }
248 251 };
249 252
250 253 /**
251 254 * Helper used to create/update plottables
252 255 */
253 256 struct IPlottablesHelper {
254 257 virtual ~IPlottablesHelper() noexcept = default;
255 258 virtual PlottablesMap create(QCustomPlot &plot) const = 0;
256 259 virtual void setYAxisRange(const SqpRange &xAxisRange, QCustomPlot &plot) const = 0;
257 260 virtual void update(PlottablesMap &plottables, const SqpRange &range,
258 261 bool rescaleAxes = false) const = 0;
259 262 };
260 263
261 264 /**
262 265 * Default implementation of IPlottablesHelper, which takes data series to create/update plottables
263 266 * @tparam T the data series' type
264 267 */
265 268 template <typename T>
266 269 struct PlottablesHelper : public IPlottablesHelper {
267 explicit PlottablesHelper(T &dataSeries) : m_DataSeries{dataSeries} {}
270 explicit PlottablesHelper(std::shared_ptr<T> dataSeries) : m_DataSeries{dataSeries} {}
268 271
269 272 PlottablesMap create(QCustomPlot &plot) const override
270 273 {
271 return PlottablesCreator<T>::createPlottables(m_DataSeries, plot);
274 return PlottablesCreator<T>::createPlottables(plot);
272 275 }
273 276
274 277 void update(PlottablesMap &plottables, const SqpRange &range, bool rescaleAxes) const override
275 278 {
276 PlottablesUpdater<T>::updatePlottables(m_DataSeries, plottables, range, rescaleAxes);
279 if (m_DataSeries) {
280 PlottablesUpdater<T>::updatePlottables(*m_DataSeries, plottables, range, rescaleAxes);
281 }
282 else {
283 qCCritical(LOG_VisualizationGraphHelper()) << "Can't update plottables: inconsistency "
284 "between the type of data series and the "
285 "type supposed";
286 }
277 287 }
278 288
279 289 void setYAxisRange(const SqpRange &xAxisRange, QCustomPlot &plot) const override
280 290 {
281 return PlottablesUpdater<T>::setPlotYAxisRange(m_DataSeries, xAxisRange, plot);
291 if (m_DataSeries) {
292 PlottablesUpdater<T>::setPlotYAxisRange(*m_DataSeries, xAxisRange, plot);
293 }
294 else {
295 qCCritical(LOG_VisualizationGraphHelper()) << "Can't update plottables: inconsistency "
296 "between the type of data series and the "
297 "type supposed";
298 }
282 299 }
283 300
284 T &m_DataSeries;
301 std::shared_ptr<T> m_DataSeries;
285 302 };
286 303
287 /// Creates IPlottablesHelper according to a data series
288 std::unique_ptr<IPlottablesHelper> createHelper(std::shared_ptr<IDataSeries> dataSeries) noexcept
304 /// Creates IPlottablesHelper according to the type of data series a variable holds
305 std::unique_ptr<IPlottablesHelper> createHelper(std::shared_ptr<Variable> variable) noexcept
289 306 {
290 if (auto scalarSeries = std::dynamic_pointer_cast<ScalarSeries>(dataSeries)) {
291 return std::make_unique<PlottablesHelper<ScalarSeries> >(*scalarSeries);
292 }
293 else if (auto spectrogramSeries = std::dynamic_pointer_cast<SpectrogramSeries>(dataSeries)) {
294 return std::make_unique<PlottablesHelper<SpectrogramSeries> >(*spectrogramSeries);
295 }
296 else if (auto vectorSeries = std::dynamic_pointer_cast<VectorSeries>(dataSeries)) {
297 return std::make_unique<PlottablesHelper<VectorSeries> >(*vectorSeries);
298 }
299 else {
300 return std::make_unique<PlottablesHelper<IDataSeries> >(*dataSeries);
307 switch (variable->type()) {
308 case DataSeriesType::SCALAR:
309 return std::make_unique<PlottablesHelper<ScalarSeries> >(
310 std::dynamic_pointer_cast<ScalarSeries>(variable->dataSeries()));
311 case DataSeriesType::SPECTROGRAM:
312 return std::make_unique<PlottablesHelper<SpectrogramSeries> >(
313 std::dynamic_pointer_cast<SpectrogramSeries>(variable->dataSeries()));
314 case DataSeriesType::VECTOR:
315 return std::make_unique<PlottablesHelper<VectorSeries> >(
316 std::dynamic_pointer_cast<VectorSeries>(variable->dataSeries()));
317 default:
318 // Creates default helper
319 break;
301 320 }
321
322 return std::make_unique<PlottablesHelper<IDataSeries> >(nullptr);
302 323 }
303 324
304 325 } // namespace
305 326
306 327 PlottablesMap VisualizationGraphHelper::create(std::shared_ptr<Variable> variable,
307 328 QCustomPlot &plot) noexcept
308 329 {
309 330 if (variable) {
310 auto helper = createHelper(variable->dataSeries());
331 auto helper = createHelper(variable);
311 332 auto plottables = helper->create(plot);
312 333 return plottables;
313 334 }
314 335 else {
315 336 qCDebug(LOG_VisualizationGraphHelper())
316 337 << QObject::tr("Can't create graph plottables : the variable is null");
317 338 return PlottablesMap{};
318 339 }
319 340 }
320 341
321 342 void VisualizationGraphHelper::setYAxisRange(std::shared_ptr<Variable> variable,
322 343 QCustomPlot &plot) noexcept
323 344 {
324 345 if (variable) {
325 auto helper = createHelper(variable->dataSeries());
346 auto helper = createHelper(variable);
326 347 helper->setYAxisRange(variable->range(), plot);
327 348 }
328 349 else {
329 350 qCDebug(LOG_VisualizationGraphHelper())
330 351 << QObject::tr("Can't set y-axis range of plot: the variable is null");
331 352 }
332 353 }
333 354
334 355 void VisualizationGraphHelper::updateData(PlottablesMap &plottables,
335 std::shared_ptr<IDataSeries> dataSeries,
356 std::shared_ptr<Variable> variable,
336 357 const SqpRange &dateTime)
337 358 {
338 auto helper = createHelper(dataSeries);
359 auto helper = createHelper(variable);
339 360 helper->update(plottables, dateTime);
340 361 }
@@ -1,1004 +1,1004
1 1 #include "Visualization/VisualizationGraphWidget.h"
2 2 #include "Visualization/IVisualizationWidgetVisitor.h"
3 3 #include "Visualization/VisualizationCursorItem.h"
4 4 #include "Visualization/VisualizationDefs.h"
5 5 #include "Visualization/VisualizationGraphHelper.h"
6 6 #include "Visualization/VisualizationGraphRenderingDelegate.h"
7 7 #include "Visualization/VisualizationMultiZoneSelectionDialog.h"
8 8 #include "Visualization/VisualizationSelectionZoneItem.h"
9 9 #include "Visualization/VisualizationSelectionZoneManager.h"
10 10 #include "Visualization/VisualizationWidget.h"
11 11 #include "Visualization/VisualizationZoneWidget.h"
12 12 #include "ui_VisualizationGraphWidget.h"
13 13
14 14 #include <Actions/ActionsGuiController.h>
15 15 #include <Common/MimeTypesDef.h>
16 16 #include <Data/ArrayData.h>
17 17 #include <Data/IDataSeries.h>
18 18 #include <Data/SpectrogramSeries.h>
19 19 #include <DragAndDrop/DragDropGuiController.h>
20 20 #include <Settings/SqpSettingsDefs.h>
21 21 #include <SqpApplication.h>
22 22 #include <Time/TimeController.h>
23 23 #include <Variable/Variable.h>
24 24 #include <Variable/VariableController.h>
25 25
26 26 #include <unordered_map>
27 27
28 28 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
29 29
30 30 namespace {
31 31
32 32 /// Key pressed to enable drag&drop in all modes
33 33 const auto DRAG_DROP_MODIFIER = Qt::AltModifier;
34 34
35 35 /// Key pressed to enable zoom on horizontal axis
36 36 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::ControlModifier;
37 37
38 38 /// Key pressed to enable zoom on vertical axis
39 39 const auto VERTICAL_ZOOM_MODIFIER = Qt::ShiftModifier;
40 40
41 41 /// Speed of a step of a wheel event for a pan, in percentage of the axis range
42 42 const auto PAN_SPEED = 5;
43 43
44 44 /// Key pressed to enable a calibration pan
45 45 const auto VERTICAL_PAN_MODIFIER = Qt::AltModifier;
46 46
47 47 /// Key pressed to enable multi selection of selection zones
48 48 const auto MULTI_ZONE_SELECTION_MODIFIER = Qt::ControlModifier;
49 49
50 50 /// Minimum size for the zoom box, in percentage of the axis range
51 51 const auto ZOOM_BOX_MIN_SIZE = 0.8;
52 52
53 53 /// Format of the dates appearing in the label of a cursor
54 54 const auto CURSOR_LABELS_DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd\nhh:mm:ss:zzz");
55 55
56 56 } // namespace
57 57
58 58 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate {
59 59
60 60 explicit VisualizationGraphWidgetPrivate(const QString &name)
61 61 : m_Name{name},
62 62 m_Flags{GraphFlag::EnableAll},
63 63 m_IsCalibration{false},
64 64 m_RenderingDelegate{nullptr}
65 65 {
66 66 }
67 67
68 void updateData(PlottablesMap &plottables, std::shared_ptr<IDataSeries> dataSeries,
68 void updateData(PlottablesMap &plottables, std::shared_ptr<Variable> variable,
69 69 const SqpRange &range)
70 70 {
71 VisualizationGraphHelper::updateData(plottables, dataSeries, range);
71 VisualizationGraphHelper::updateData(plottables, variable, range);
72 72
73 73 // Prevents that data has changed to update rendering
74 74 m_RenderingDelegate->onPlotUpdated();
75 75 }
76 76
77 77 QString m_Name;
78 78 // 1 variable -> n qcpplot
79 79 std::map<std::shared_ptr<Variable>, PlottablesMap> m_VariableToPlotMultiMap;
80 80 GraphFlags m_Flags;
81 81 bool m_IsCalibration;
82 82 /// Delegate used to attach rendering features to the plot
83 83 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
84 84
85 85 QCPItemRect *m_DrawingZoomRect = nullptr;
86 86 QStack<QPair<QCPRange, QCPRange> > m_ZoomStack;
87 87
88 88 std::unique_ptr<VisualizationCursorItem> m_HorizontalCursor = nullptr;
89 89 std::unique_ptr<VisualizationCursorItem> m_VerticalCursor = nullptr;
90 90
91 91 VisualizationSelectionZoneItem *m_DrawingZone = nullptr;
92 92 VisualizationSelectionZoneItem *m_HoveredZone = nullptr;
93 93 QVector<VisualizationSelectionZoneItem *> m_SelectionZones;
94 94
95 95 bool m_HasMovedMouse = false; // Indicates if the mouse moved in a releaseMouse even
96 96
97 97 void startDrawingRect(const QPoint &pos, QCustomPlot &plot)
98 98 {
99 99 removeDrawingRect(plot);
100 100
101 101 auto axisPos = posToAxisPos(pos, plot);
102 102
103 103 m_DrawingZoomRect = new QCPItemRect{&plot};
104 104 QPen p;
105 105 p.setWidth(2);
106 106 m_DrawingZoomRect->setPen(p);
107 107
108 108 m_DrawingZoomRect->topLeft->setCoords(axisPos);
109 109 m_DrawingZoomRect->bottomRight->setCoords(axisPos);
110 110 }
111 111
112 112 void removeDrawingRect(QCustomPlot &plot)
113 113 {
114 114 if (m_DrawingZoomRect) {
115 115 plot.removeItem(m_DrawingZoomRect); // the item is deleted by QCustomPlot
116 116 m_DrawingZoomRect = nullptr;
117 117 plot.replot(QCustomPlot::rpQueuedReplot);
118 118 }
119 119 }
120 120
121 121 void startDrawingZone(const QPoint &pos, VisualizationGraphWidget *graph)
122 122 {
123 123 endDrawingZone(graph);
124 124
125 125 auto axisPos = posToAxisPos(pos, graph->plot());
126 126
127 127 m_DrawingZone = new VisualizationSelectionZoneItem{&graph->plot()};
128 128 m_DrawingZone->setRange(axisPos.x(), axisPos.x());
129 129 m_DrawingZone->setEditionEnabled(false);
130 130 }
131 131
132 132 void endDrawingZone(VisualizationGraphWidget *graph)
133 133 {
134 134 if (m_DrawingZone) {
135 135 auto drawingZoneRange = m_DrawingZone->range();
136 136 if (qAbs(drawingZoneRange.m_TEnd - drawingZoneRange.m_TStart) > 0) {
137 137 m_DrawingZone->setEditionEnabled(true);
138 138 addSelectionZone(m_DrawingZone);
139 139 }
140 140 else {
141 141 graph->plot().removeItem(m_DrawingZone); // the item is deleted by QCustomPlot
142 142 }
143 143
144 144 graph->plot().replot(QCustomPlot::rpQueuedReplot);
145 145 m_DrawingZone = nullptr;
146 146 }
147 147 }
148 148
149 149 void setSelectionZonesEditionEnabled(bool value)
150 150 {
151 151 for (auto s : m_SelectionZones) {
152 152 s->setEditionEnabled(value);
153 153 }
154 154 }
155 155
156 156 void addSelectionZone(VisualizationSelectionZoneItem *zone) { m_SelectionZones << zone; }
157 157
158 158 VisualizationSelectionZoneItem *selectionZoneAt(const QPoint &pos,
159 159 const QCustomPlot &plot) const
160 160 {
161 161 VisualizationSelectionZoneItem *selectionZoneItemUnderCursor = nullptr;
162 162 auto minDistanceToZone = -1;
163 163 for (auto zone : m_SelectionZones) {
164 164 auto distanceToZone = zone->selectTest(pos, false);
165 165 if ((minDistanceToZone < 0 || distanceToZone <= minDistanceToZone)
166 166 && distanceToZone >= 0 && distanceToZone < plot.selectionTolerance()) {
167 167 selectionZoneItemUnderCursor = zone;
168 168 }
169 169 }
170 170
171 171 return selectionZoneItemUnderCursor;
172 172 }
173 173
174 174 QVector<VisualizationSelectionZoneItem *> selectionZonesAt(const QPoint &pos,
175 175 const QCustomPlot &plot) const
176 176 {
177 177 QVector<VisualizationSelectionZoneItem *> zones;
178 178 for (auto zone : m_SelectionZones) {
179 179 auto distanceToZone = zone->selectTest(pos, false);
180 180 if (distanceToZone >= 0 && distanceToZone < plot.selectionTolerance()) {
181 181 zones << zone;
182 182 }
183 183 }
184 184
185 185 return zones;
186 186 }
187 187
188 188 void moveSelectionZoneOnTop(VisualizationSelectionZoneItem *zone, QCustomPlot &plot)
189 189 {
190 190 if (!m_SelectionZones.isEmpty() && m_SelectionZones.last() != zone) {
191 191 zone->moveToTop();
192 192 m_SelectionZones.removeAll(zone);
193 193 m_SelectionZones.append(zone);
194 194 }
195 195 }
196 196
197 197 QPointF posToAxisPos(const QPoint &pos, QCustomPlot &plot) const
198 198 {
199 199 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
200 200 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
201 201 return QPointF{axisX->pixelToCoord(pos.x()), axisY->pixelToCoord(pos.y())};
202 202 }
203 203
204 204 bool pointIsInAxisRect(const QPointF &axisPoint, QCustomPlot &plot) const
205 205 {
206 206 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
207 207 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
208 208 return axisX->range().contains(axisPoint.x()) && axisY->range().contains(axisPoint.y());
209 209 }
210 210 };
211 211
212 212 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
213 213 : VisualizationDragWidget{parent},
214 214 ui{new Ui::VisualizationGraphWidget},
215 215 impl{spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name)}
216 216 {
217 217 ui->setupUi(this);
218 218
219 219 // 'Close' options : widget is deleted when closed
220 220 setAttribute(Qt::WA_DeleteOnClose);
221 221
222 222 // Set qcpplot properties :
223 223 // - zoom is enabled
224 224 // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation
225 225 ui->widget->setInteractions(QCP::iRangeZoom);
226 226 ui->widget->axisRect()->setRangeDrag(Qt::Horizontal | Qt::Vertical);
227 227
228 228 // The delegate must be initialized after the ui as it uses the plot
229 229 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
230 230
231 231 // Init the cursors
232 232 impl->m_HorizontalCursor = std::make_unique<VisualizationCursorItem>(&plot());
233 233 impl->m_HorizontalCursor->setOrientation(Qt::Horizontal);
234 234 impl->m_VerticalCursor = std::make_unique<VisualizationCursorItem>(&plot());
235 235 impl->m_VerticalCursor->setOrientation(Qt::Vertical);
236 236
237 237 connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress);
238 238 connect(ui->widget, &QCustomPlot::mouseRelease, this,
239 239 &VisualizationGraphWidget::onMouseRelease);
240 240 connect(ui->widget, &QCustomPlot::mouseMove, this, &VisualizationGraphWidget::onMouseMove);
241 241 connect(ui->widget, &QCustomPlot::mouseWheel, this, &VisualizationGraphWidget::onMouseWheel);
242 242 connect(ui->widget, &QCustomPlot::mouseDoubleClick, this,
243 243 &VisualizationGraphWidget::onMouseDoubleClick);
244 244 connect(ui->widget->xAxis, static_cast<void (QCPAxis::*)(const QCPRange &, const QCPRange &)>(
245 245 &QCPAxis::rangeChanged),
246 246 this, &VisualizationGraphWidget::onRangeChanged, Qt::DirectConnection);
247 247
248 248 // Activates menu when right clicking on the graph
249 249 ui->widget->setContextMenuPolicy(Qt::CustomContextMenu);
250 250 connect(ui->widget, &QCustomPlot::customContextMenuRequested, this,
251 251 &VisualizationGraphWidget::onGraphMenuRequested);
252 252
253 253 connect(this, &VisualizationGraphWidget::requestDataLoading, &sqpApp->variableController(),
254 254 &VariableController::onRequestDataLoading);
255 255
256 256 connect(&sqpApp->variableController(), &VariableController::updateVarDisplaying, this,
257 257 &VisualizationGraphWidget::onUpdateVarDisplaying);
258 258
259 259 #ifdef Q_OS_MAC
260 260 plot().setPlottingHint(QCP::phFastPolylines, true);
261 261 #endif
262 262 }
263 263
264 264
265 265 VisualizationGraphWidget::~VisualizationGraphWidget()
266 266 {
267 267 delete ui;
268 268 }
269 269
270 270 VisualizationZoneWidget *VisualizationGraphWidget::parentZoneWidget() const noexcept
271 271 {
272 272 auto parent = parentWidget();
273 273 while (parent != nullptr && !qobject_cast<VisualizationZoneWidget *>(parent)) {
274 274 parent = parent->parentWidget();
275 275 }
276 276
277 277 return qobject_cast<VisualizationZoneWidget *>(parent);
278 278 }
279 279
280 280 VisualizationWidget *VisualizationGraphWidget::parentVisualizationWidget() const
281 281 {
282 282 auto parent = parentWidget();
283 283 while (parent != nullptr && !qobject_cast<VisualizationWidget *>(parent)) {
284 284 parent = parent->parentWidget();
285 285 }
286 286
287 287 return qobject_cast<VisualizationWidget *>(parent);
288 288 }
289 289
290 290 void VisualizationGraphWidget::setFlags(GraphFlags flags)
291 291 {
292 292 impl->m_Flags = std::move(flags);
293 293 }
294 294
295 295 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable> variable, SqpRange range)
296 296 {
297 297 // Uses delegate to create the qcpplot components according to the variable
298 298 auto createdPlottables = VisualizationGraphHelper::create(variable, *ui->widget);
299 299
300 300 if (auto dataSeries = variable->dataSeries()) {
301 301 // Set axes properties according to the units of the data series
302 302 impl->m_RenderingDelegate->setAxesProperties(dataSeries);
303 303
304 304 // Sets rendering properties for the new plottables
305 305 // Warning: this method must be called after setAxesProperties(), as it can access to some
306 306 // axes properties that have to be initialized
307 307 impl->m_RenderingDelegate->setPlottablesProperties(dataSeries, createdPlottables);
308 308 }
309 309
310 310 impl->m_VariableToPlotMultiMap.insert({variable, std::move(createdPlottables)});
311 311
312 312 connect(variable.get(), SIGNAL(updated()), this, SLOT(onDataCacheVariableUpdated()));
313 313
314 314 this->setFlags(GraphFlag::DisableAll);
315 315 this->setGraphRange(range);
316 316 this->setFlags(GraphFlag::EnableAll);
317 317
318 318 emit requestDataLoading(QVector<std::shared_ptr<Variable> >() << variable, range, false);
319 319
320 320 emit variableAdded(variable);
321 321 }
322 322
323 323 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable> variable) noexcept
324 324 {
325 325 // Each component associated to the variable :
326 326 // - is removed from qcpplot (which deletes it)
327 327 // - is no longer referenced in the map
328 328 auto variableIt = impl->m_VariableToPlotMultiMap.find(variable);
329 329 if (variableIt != impl->m_VariableToPlotMultiMap.cend()) {
330 330 emit variableAboutToBeRemoved(variable);
331 331
332 332 auto &plottablesMap = variableIt->second;
333 333
334 334 for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend();
335 335 plottableIt != plottableEnd;) {
336 336 ui->widget->removePlottable(plottableIt->second);
337 337 plottableIt = plottablesMap.erase(plottableIt);
338 338 }
339 339
340 340 impl->m_VariableToPlotMultiMap.erase(variableIt);
341 341 }
342 342
343 343 // Updates graph
344 344 ui->widget->replot();
345 345 }
346 346
347 347 QList<std::shared_ptr<Variable> > VisualizationGraphWidget::variables() const
348 348 {
349 349 auto variables = QList<std::shared_ptr<Variable> >{};
350 350 for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap);
351 351 it != std::cend(impl->m_VariableToPlotMultiMap); ++it) {
352 352 variables << it->first;
353 353 }
354 354
355 355 return variables;
356 356 }
357 357
358 358 void VisualizationGraphWidget::setYRange(std::shared_ptr<Variable> variable)
359 359 {
360 360 if (!variable) {
361 361 qCCritical(LOG_VisualizationGraphWidget()) << "Can't set y-axis range: variable is null";
362 362 return;
363 363 }
364 364
365 365 VisualizationGraphHelper::setYAxisRange(variable, *ui->widget);
366 366 }
367 367
368 368 SqpRange VisualizationGraphWidget::graphRange() const noexcept
369 369 {
370 370 auto graphRange = ui->widget->xAxis->range();
371 371 return SqpRange{graphRange.lower, graphRange.upper};
372 372 }
373 373
374 374 void VisualizationGraphWidget::setGraphRange(const SqpRange &range)
375 375 {
376 376 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange START");
377 377 ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd);
378 378 ui->widget->replot();
379 379 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange END");
380 380 }
381 381
382 382 QVector<SqpRange> VisualizationGraphWidget::selectionZoneRanges() const
383 383 {
384 384 QVector<SqpRange> ranges;
385 385 for (auto zone : impl->m_SelectionZones) {
386 386 ranges << zone->range();
387 387 }
388 388
389 389 return ranges;
390 390 }
391 391
392 392 void VisualizationGraphWidget::addSelectionZones(const QVector<SqpRange> &ranges)
393 393 {
394 394 for (const auto &range : ranges) {
395 395 // note: ownership is transfered to QCustomPlot
396 396 auto zone = new VisualizationSelectionZoneItem(&plot());
397 397 zone->setRange(range.m_TStart, range.m_TEnd);
398 398 impl->addSelectionZone(zone);
399 399 }
400 400
401 401 plot().replot(QCustomPlot::rpQueuedReplot);
402 402 }
403 403
404 404 void VisualizationGraphWidget::removeSelectionZone(VisualizationSelectionZoneItem *selectionZone)
405 405 {
406 406 parentVisualizationWidget()->selectionZoneManager().setSelected(selectionZone, false);
407 407
408 408 if (impl->m_HoveredZone == selectionZone) {
409 409 impl->m_HoveredZone = nullptr;
410 410 setCursor(Qt::ArrowCursor);
411 411 }
412 412
413 413 impl->m_SelectionZones.removeAll(selectionZone);
414 414 plot().removeItem(selectionZone);
415 415 plot().replot(QCustomPlot::rpQueuedReplot);
416 416 }
417 417
418 418 void VisualizationGraphWidget::undoZoom()
419 419 {
420 420 auto zoom = impl->m_ZoomStack.pop();
421 421 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
422 422 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
423 423
424 424 axisX->setRange(zoom.first);
425 425 axisY->setRange(zoom.second);
426 426
427 427 plot().replot(QCustomPlot::rpQueuedReplot);
428 428 }
429 429
430 430 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor)
431 431 {
432 432 if (visitor) {
433 433 visitor->visit(this);
434 434 }
435 435 else {
436 436 qCCritical(LOG_VisualizationGraphWidget())
437 437 << tr("Can't visit widget : the visitor is null");
438 438 }
439 439 }
440 440
441 441 bool VisualizationGraphWidget::canDrop(const Variable &variable) const
442 442 {
443 443 auto isSpectrogram = [](const auto &variable) {
444 444 return std::dynamic_pointer_cast<SpectrogramSeries>(variable.dataSeries()) != nullptr;
445 445 };
446 446
447 447 // - A spectrogram series can't be dropped on graph with existing plottables
448 448 // - No data series can be dropped on graph with existing spectrogram series
449 449 return isSpectrogram(variable)
450 450 ? impl->m_VariableToPlotMultiMap.empty()
451 451 : std::none_of(
452 452 impl->m_VariableToPlotMultiMap.cbegin(), impl->m_VariableToPlotMultiMap.cend(),
453 453 [isSpectrogram](const auto &entry) { return isSpectrogram(*entry.first); });
454 454 }
455 455
456 456 bool VisualizationGraphWidget::contains(const Variable &variable) const
457 457 {
458 458 // Finds the variable among the keys of the map
459 459 auto variablePtr = &variable;
460 460 auto findVariable
461 461 = [variablePtr](const auto &entry) { return variablePtr == entry.first.get(); };
462 462
463 463 auto end = impl->m_VariableToPlotMultiMap.cend();
464 464 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
465 465 return it != end;
466 466 }
467 467
468 468 QString VisualizationGraphWidget::name() const
469 469 {
470 470 return impl->m_Name;
471 471 }
472 472
473 473 QMimeData *VisualizationGraphWidget::mimeData(const QPoint &position) const
474 474 {
475 475 auto mimeData = new QMimeData;
476 476
477 477 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(position, plot());
478 478 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
479 479 && selectionZoneItemUnderCursor) {
480 480 mimeData->setData(MIME_TYPE_TIME_RANGE, TimeController::mimeDataForTimeRange(
481 481 selectionZoneItemUnderCursor->range()));
482 482 mimeData->setData(MIME_TYPE_SELECTION_ZONE, TimeController::mimeDataForTimeRange(
483 483 selectionZoneItemUnderCursor->range()));
484 484 }
485 485 else {
486 486 mimeData->setData(MIME_TYPE_GRAPH, QByteArray{});
487 487
488 488 auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange());
489 489 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
490 490 }
491 491
492 492 return mimeData;
493 493 }
494 494
495 495 QPixmap VisualizationGraphWidget::customDragPixmap(const QPoint &dragPosition)
496 496 {
497 497 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(dragPosition, plot());
498 498 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
499 499 && selectionZoneItemUnderCursor) {
500 500
501 501 auto zoneTopLeft = selectionZoneItemUnderCursor->topLeft->pixelPosition();
502 502 auto zoneBottomRight = selectionZoneItemUnderCursor->bottomRight->pixelPosition();
503 503
504 504 auto zoneSize = QSizeF{qAbs(zoneBottomRight.x() - zoneTopLeft.x()),
505 505 qAbs(zoneBottomRight.y() - zoneTopLeft.y())}
506 506 .toSize();
507 507
508 508 auto pixmap = QPixmap(zoneSize);
509 509 render(&pixmap, QPoint(), QRegion{QRect{zoneTopLeft.toPoint(), zoneSize}});
510 510
511 511 return pixmap;
512 512 }
513 513
514 514 return QPixmap();
515 515 }
516 516
517 517 bool VisualizationGraphWidget::isDragAllowed() const
518 518 {
519 519 return true;
520 520 }
521 521
522 522 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
523 523 {
524 524 if (highlighted) {
525 525 plot().setBackground(QBrush(QColor("#BBD5EE")));
526 526 }
527 527 else {
528 528 plot().setBackground(QBrush(Qt::white));
529 529 }
530 530
531 531 plot().update();
532 532 }
533 533
534 534 void VisualizationGraphWidget::addVerticalCursor(double time)
535 535 {
536 536 impl->m_VerticalCursor->setPosition(time);
537 537 impl->m_VerticalCursor->setVisible(true);
538 538
539 539 auto text
540 540 = DateUtils::dateTime(time).toString(CURSOR_LABELS_DATETIME_FORMAT).replace(' ', '\n');
541 541 impl->m_VerticalCursor->setLabelText(text);
542 542 }
543 543
544 544 void VisualizationGraphWidget::addVerticalCursorAtViewportPosition(double position)
545 545 {
546 546 impl->m_VerticalCursor->setAbsolutePosition(position);
547 547 impl->m_VerticalCursor->setVisible(true);
548 548
549 549 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
550 550 auto text
551 551 = DateUtils::dateTime(axis->pixelToCoord(position)).toString(CURSOR_LABELS_DATETIME_FORMAT);
552 552 impl->m_VerticalCursor->setLabelText(text);
553 553 }
554 554
555 555 void VisualizationGraphWidget::removeVerticalCursor()
556 556 {
557 557 impl->m_VerticalCursor->setVisible(false);
558 558 plot().replot(QCustomPlot::rpQueuedReplot);
559 559 }
560 560
561 561 void VisualizationGraphWidget::addHorizontalCursor(double value)
562 562 {
563 563 impl->m_HorizontalCursor->setPosition(value);
564 564 impl->m_HorizontalCursor->setVisible(true);
565 565 impl->m_HorizontalCursor->setLabelText(QString::number(value));
566 566 }
567 567
568 568 void VisualizationGraphWidget::addHorizontalCursorAtViewportPosition(double position)
569 569 {
570 570 impl->m_HorizontalCursor->setAbsolutePosition(position);
571 571 impl->m_HorizontalCursor->setVisible(true);
572 572
573 573 auto axis = plot().axisRect()->axis(QCPAxis::atLeft);
574 574 impl->m_HorizontalCursor->setLabelText(QString::number(axis->pixelToCoord(position)));
575 575 }
576 576
577 577 void VisualizationGraphWidget::removeHorizontalCursor()
578 578 {
579 579 impl->m_HorizontalCursor->setVisible(false);
580 580 plot().replot(QCustomPlot::rpQueuedReplot);
581 581 }
582 582
583 583 void VisualizationGraphWidget::closeEvent(QCloseEvent *event)
584 584 {
585 585 Q_UNUSED(event);
586 586
587 587 // Prevents that all variables will be removed from graph when it will be closed
588 588 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
589 589 emit variableAboutToBeRemoved(variableEntry.first);
590 590 }
591 591 }
592 592
593 593 void VisualizationGraphWidget::enterEvent(QEvent *event)
594 594 {
595 595 Q_UNUSED(event);
596 596 impl->m_RenderingDelegate->showGraphOverlay(true);
597 597 }
598 598
599 599 void VisualizationGraphWidget::leaveEvent(QEvent *event)
600 600 {
601 601 Q_UNUSED(event);
602 602 impl->m_RenderingDelegate->showGraphOverlay(false);
603 603
604 604 if (auto parentZone = parentZoneWidget()) {
605 605 parentZone->notifyMouseLeaveGraph(this);
606 606 }
607 607 else {
608 608 qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget";
609 609 }
610 610
611 611 if (impl->m_HoveredZone) {
612 612 impl->m_HoveredZone->setHovered(false);
613 613 impl->m_HoveredZone = nullptr;
614 614 }
615 615 }
616 616
617 617 QCustomPlot &VisualizationGraphWidget::plot() const noexcept
618 618 {
619 619 return *ui->widget;
620 620 }
621 621
622 622 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept
623 623 {
624 624 QMenu graphMenu{};
625 625
626 626 // Iterates on variables (unique keys)
627 627 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
628 628 end = impl->m_VariableToPlotMultiMap.cend();
629 629 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
630 630 // 'Remove variable' action
631 631 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
632 632 [ this, var = it->first ]() { removeVariable(var); });
633 633 }
634 634
635 635 if (!impl->m_ZoomStack.isEmpty()) {
636 636 if (!graphMenu.isEmpty()) {
637 637 graphMenu.addSeparator();
638 638 }
639 639
640 640 graphMenu.addAction(tr("Undo Zoom"), [this]() { undoZoom(); });
641 641 }
642 642
643 643 // Selection Zone Actions
644 644 auto selectionZoneItem = impl->selectionZoneAt(pos, plot());
645 645 if (selectionZoneItem) {
646 646 auto selectedItems = parentVisualizationWidget()->selectionZoneManager().selectedItems();
647 647 selectedItems.removeAll(selectionZoneItem);
648 648 selectedItems.prepend(selectionZoneItem); // Put the current selection zone first
649 649
650 650 auto zoneActions = sqpApp->actionsGuiController().selectionZoneActions();
651 651 if (!zoneActions.isEmpty() && !graphMenu.isEmpty()) {
652 652 graphMenu.addSeparator();
653 653 }
654 654
655 655 QHash<QString, QMenu *> subMenus;
656 656 QHash<QString, bool> subMenusEnabled;
657 657
658 658 for (auto zoneAction : zoneActions) {
659 659
660 660 auto isEnabled = zoneAction->isEnabled(selectedItems);
661 661
662 662 auto menu = &graphMenu;
663 663 for (auto subMenuName : zoneAction->subMenuList()) {
664 664 if (!subMenus.contains(subMenuName)) {
665 665 menu = menu->addMenu(subMenuName);
666 666 subMenus[subMenuName] = menu;
667 667 subMenusEnabled[subMenuName] = isEnabled;
668 668 }
669 669 else {
670 670 menu = subMenus.value(subMenuName);
671 671 if (isEnabled) {
672 672 // The sub menu is enabled if at least one of its actions is enabled
673 673 subMenusEnabled[subMenuName] = true;
674 674 }
675 675 }
676 676 }
677 677
678 678 auto action = menu->addAction(zoneAction->name());
679 679 action->setEnabled(isEnabled);
680 680 action->setShortcut(zoneAction->displayedShortcut());
681 681 QObject::connect(action, &QAction::triggered,
682 682 [zoneAction, selectedItems]() { zoneAction->execute(selectedItems); });
683 683 }
684 684
685 685 for (auto it = subMenus.cbegin(); it != subMenus.cend(); ++it) {
686 686 it.value()->setEnabled(subMenusEnabled[it.key()]);
687 687 }
688 688 }
689 689
690 690 if (!graphMenu.isEmpty()) {
691 691 graphMenu.exec(QCursor::pos());
692 692 }
693 693 }
694 694
695 695 void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange &t2)
696 696 {
697 697 qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: VisualizationGraphWidget::onRangeChanged")
698 698 << QThread::currentThread()->objectName() << "DoAcqui"
699 699 << impl->m_Flags.testFlag(GraphFlag::EnableAcquisition);
700 700
701 701 auto graphRange = SqpRange{t1.lower, t1.upper};
702 702 auto oldGraphRange = SqpRange{t2.lower, t2.upper};
703 703
704 704 if (impl->m_Flags.testFlag(GraphFlag::EnableAcquisition)) {
705 705 QVector<std::shared_ptr<Variable> > variableUnderGraphVector;
706 706
707 707 for (auto it = impl->m_VariableToPlotMultiMap.begin(),
708 708 end = impl->m_VariableToPlotMultiMap.end();
709 709 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
710 710 variableUnderGraphVector.push_back(it->first);
711 711 }
712 712 emit requestDataLoading(std::move(variableUnderGraphVector), graphRange,
713 713 !impl->m_IsCalibration);
714 714 }
715 715
716 716 if (impl->m_Flags.testFlag(GraphFlag::EnableSynchronization) && !impl->m_IsCalibration) {
717 717 qCDebug(LOG_VisualizationGraphWidget())
718 718 << tr("TORM: VisualizationGraphWidget::Synchronize notify !!")
719 719 << QThread::currentThread()->objectName() << graphRange << oldGraphRange;
720 720 emit synchronize(graphRange, oldGraphRange);
721 721 }
722 722
723 723 auto pos = mapFromGlobal(QCursor::pos());
724 724 auto axisPos = impl->posToAxisPos(pos, plot());
725 725 if (auto parentZone = parentZoneWidget()) {
726 726 if (impl->pointIsInAxisRect(axisPos, plot())) {
727 727 parentZone->notifyMouseMoveInGraph(pos, axisPos, this);
728 728 }
729 729 else {
730 730 parentZone->notifyMouseLeaveGraph(this);
731 731 }
732 732 }
733 733 else {
734 734 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
735 735 }
736 736
737 737 // Quits calibration
738 738 impl->m_IsCalibration = false;
739 739 }
740 740
741 741 void VisualizationGraphWidget::onMouseDoubleClick(QMouseEvent *event) noexcept
742 742 {
743 743 impl->m_RenderingDelegate->onMouseDoubleClick(event);
744 744 }
745 745
746 746 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
747 747 {
748 748 // Handles plot rendering when mouse is moving
749 749 impl->m_RenderingDelegate->onMouseMove(event);
750 750
751 751 auto axisPos = impl->posToAxisPos(event->pos(), plot());
752 752
753 753 // Zoom box and zone drawing
754 754 if (impl->m_DrawingZoomRect) {
755 755 impl->m_DrawingZoomRect->bottomRight->setCoords(axisPos);
756 756 }
757 757 else if (impl->m_DrawingZone) {
758 758 impl->m_DrawingZone->setEnd(axisPos.x());
759 759 }
760 760
761 761 // Cursor
762 762 if (auto parentZone = parentZoneWidget()) {
763 763 if (impl->pointIsInAxisRect(axisPos, plot())) {
764 764 parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this);
765 765 }
766 766 else {
767 767 parentZone->notifyMouseLeaveGraph(this);
768 768 }
769 769 }
770 770 else {
771 771 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
772 772 }
773 773
774 774 // Search for the selection zone under the mouse
775 775 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot());
776 776 if (selectionZoneItemUnderCursor && !impl->m_DrawingZone
777 777 && sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones) {
778 778
779 779 // Sets the appropriate cursor shape
780 780 auto cursorShape = selectionZoneItemUnderCursor->curshorShapeForPosition(event->pos());
781 781 setCursor(cursorShape);
782 782
783 783 // Manages the hovered zone
784 784 if (selectionZoneItemUnderCursor != impl->m_HoveredZone) {
785 785 if (impl->m_HoveredZone) {
786 786 impl->m_HoveredZone->setHovered(false);
787 787 }
788 788 selectionZoneItemUnderCursor->setHovered(true);
789 789 impl->m_HoveredZone = selectionZoneItemUnderCursor;
790 790 plot().replot(QCustomPlot::rpQueuedReplot);
791 791 }
792 792 }
793 793 else {
794 794 // There is no zone under the mouse or the interaction mode is not "selection zones"
795 795 if (impl->m_HoveredZone) {
796 796 impl->m_HoveredZone->setHovered(false);
797 797 impl->m_HoveredZone = nullptr;
798 798 }
799 799
800 800 setCursor(Qt::ArrowCursor);
801 801 }
802 802
803 803 impl->m_HasMovedMouse = true;
804 804 VisualizationDragWidget::mouseMoveEvent(event);
805 805 }
806 806
807 807 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
808 808 {
809 809 auto value = event->angleDelta().x() + event->angleDelta().y();
810 810 if (value != 0) {
811 811
812 812 auto direction = value > 0 ? 1.0 : -1.0;
813 813 auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER);
814 814 auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER);
815 815 impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER);
816 816
817 817 auto zoomOrientations = QFlags<Qt::Orientation>{};
818 818 zoomOrientations.setFlag(Qt::Horizontal, isZoomX);
819 819 zoomOrientations.setFlag(Qt::Vertical, isZoomY);
820 820
821 821 ui->widget->axisRect()->setRangeZoom(zoomOrientations);
822 822
823 823 if (!isZoomX && !isZoomY) {
824 824 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
825 825 auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0));
826 826
827 827 axis->setRange(axis->range() + diff);
828 828
829 829 if (plot().noAntialiasingOnDrag()) {
830 830 plot().setNotAntialiasedElements(QCP::aeAll);
831 831 }
832 832
833 833 plot().replot(QCustomPlot::rpQueuedReplot);
834 834 }
835 835 }
836 836 }
837 837
838 838 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
839 839 {
840 840 auto isDragDropClick = event->modifiers().testFlag(DRAG_DROP_MODIFIER);
841 841 auto isSelectionZoneMode
842 842 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
843 843 auto isLeftClick = event->buttons().testFlag(Qt::LeftButton);
844 844
845 845 if (!isDragDropClick && isLeftClick) {
846 846 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox) {
847 847 // Starts a zoom box
848 848 impl->startDrawingRect(event->pos(), plot());
849 849 }
850 850 else if (isSelectionZoneMode && impl->m_DrawingZone == nullptr) {
851 851 // Starts a new selection zone
852 852 auto zoneAtPos = impl->selectionZoneAt(event->pos(), plot());
853 853 if (!zoneAtPos) {
854 854 impl->startDrawingZone(event->pos(), this);
855 855 }
856 856 }
857 857 }
858 858
859 859 // Allows mouse panning only in default mode
860 860 plot().setInteraction(QCP::iRangeDrag, sqpApp->plotsInteractionMode()
861 861 == SqpApplication::PlotsInteractionMode::None
862 862 && !isDragDropClick);
863 863
864 864 // Allows zone edition only in selection zone mode without drag&drop
865 865 impl->setSelectionZonesEditionEnabled(isSelectionZoneMode && !isDragDropClick);
866 866
867 867 // Selection / Deselection
868 868 if (isSelectionZoneMode) {
869 869 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
870 870 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot());
871 871
872 872
873 873 if (selectionZoneItemUnderCursor && !selectionZoneItemUnderCursor->selected()
874 874 && !isMultiSelectionClick) {
875 875 parentVisualizationWidget()->selectionZoneManager().select(
876 876 {selectionZoneItemUnderCursor});
877 877 }
878 878 else if (!selectionZoneItemUnderCursor && !isMultiSelectionClick && isLeftClick) {
879 879 parentVisualizationWidget()->selectionZoneManager().clearSelection();
880 880 }
881 881 else {
882 882 // No selection change
883 883 }
884 884
885 885 if (selectionZoneItemUnderCursor && isLeftClick) {
886 886 selectionZoneItemUnderCursor->setAssociatedEditedZones(
887 887 parentVisualizationWidget()->selectionZoneManager().selectedItems());
888 888 }
889 889 }
890 890
891 891
892 892 impl->m_HasMovedMouse = false;
893 893 VisualizationDragWidget::mousePressEvent(event);
894 894 }
895 895
896 896 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
897 897 {
898 898 if (impl->m_DrawingZoomRect) {
899 899
900 900 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
901 901 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
902 902
903 903 auto newAxisXRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().x(),
904 904 impl->m_DrawingZoomRect->bottomRight->coords().x()};
905 905
906 906 auto newAxisYRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().y(),
907 907 impl->m_DrawingZoomRect->bottomRight->coords().y()};
908 908
909 909 impl->removeDrawingRect(plot());
910 910
911 911 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
912 912 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) {
913 913 impl->m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
914 914 axisX->setRange(newAxisXRange);
915 915 axisY->setRange(newAxisYRange);
916 916
917 917 plot().replot(QCustomPlot::rpQueuedReplot);
918 918 }
919 919 }
920 920
921 921 impl->endDrawingZone(this);
922 922
923 923 // Selection / Deselection
924 924 auto isSelectionZoneMode
925 925 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
926 926 if (isSelectionZoneMode) {
927 927 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
928 928 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot());
929 929 if (selectionZoneItemUnderCursor && event->button() == Qt::LeftButton
930 930 && !impl->m_HasMovedMouse) {
931 931
932 932 auto zonesUnderCursor = impl->selectionZonesAt(event->pos(), plot());
933 933 if (zonesUnderCursor.count() > 1) {
934 934 // There are multiple zones under the mouse.
935 935 // Performs the selection with a selection dialog.
936 936 VisualizationMultiZoneSelectionDialog dialog{this};
937 937 dialog.setZones(zonesUnderCursor);
938 938 dialog.move(mapToGlobal(event->pos() - QPoint(dialog.width() / 2, 20)));
939 939 dialog.activateWindow();
940 940 dialog.raise();
941 941 if (dialog.exec() == QDialog::Accepted) {
942 942 auto selection = dialog.selectedZones();
943 943
944 944 if (!isMultiSelectionClick) {
945 945 parentVisualizationWidget()->selectionZoneManager().clearSelection();
946 946 }
947 947
948 948 for (auto it = selection.cbegin(); it != selection.cend(); ++it) {
949 949 auto zone = it.key();
950 950 auto isSelected = it.value();
951 951 parentVisualizationWidget()->selectionZoneManager().setSelected(zone,
952 952 isSelected);
953 953
954 954 if (isSelected) {
955 955 // Puts the zone on top of the stack so it can be moved or resized
956 956 impl->moveSelectionZoneOnTop(zone, plot());
957 957 }
958 958 }
959 959 }
960 960 }
961 961 else {
962 962 if (!isMultiSelectionClick) {
963 963 parentVisualizationWidget()->selectionZoneManager().select(
964 964 {selectionZoneItemUnderCursor});
965 965 impl->moveSelectionZoneOnTop(selectionZoneItemUnderCursor, plot());
966 966 }
967 967 else {
968 968 parentVisualizationWidget()->selectionZoneManager().setSelected(
969 969 selectionZoneItemUnderCursor, !selectionZoneItemUnderCursor->selected()
970 970 || event->button() == Qt::RightButton);
971 971 }
972 972 }
973 973 }
974 974 else {
975 975 // No selection change
976 976 }
977 977 }
978 978 }
979 979
980 980 void VisualizationGraphWidget::onDataCacheVariableUpdated()
981 981 {
982 982 auto graphRange = ui->widget->xAxis->range();
983 983 auto dateTime = SqpRange{graphRange.lower, graphRange.upper};
984 984
985 985 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
986 986 auto variable = variableEntry.first;
987 987 qCDebug(LOG_VisualizationGraphWidget())
988 988 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
989 989 qCDebug(LOG_VisualizationGraphWidget())
990 990 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
991 991 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range())) {
992 impl->updateData(variableEntry.second, variable->dataSeries(), variable->range());
992 impl->updateData(variableEntry.second, variable, variable->range());
993 993 }
994 994 }
995 995 }
996 996
997 997 void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr<Variable> variable,
998 998 const SqpRange &range)
999 999 {
1000 1000 auto it = impl->m_VariableToPlotMultiMap.find(variable);
1001 1001 if (it != impl->m_VariableToPlotMultiMap.end()) {
1002 impl->updateData(it->second, variable->dataSeries(), range);
1002 impl->updateData(it->second, variable, range);
1003 1003 }
1004 1004 }
General Comments 0
You need to be logged in to leave comments. Login now