##// END OF EJS Templates
Handles double click on color scale...
Alexandre Leroux -
r1002:4ab6e8aca534
parent child
Show More
@@ -1,39 +1,40
1 1 #ifndef SCIQLOP_VISUALIZATIONGRAPHRENDERINGDELEGATE_H
2 2 #define SCIQLOP_VISUALIZATIONGRAPHRENDERINGDELEGATE_H
3 3
4 4 #include <Common/spimpl.h>
5 5
6 6 #include <Visualization/VisualizationDefs.h>
7 7
8 8 class IDataSeries;
9 9 class QCustomPlot;
10 10 class QMouseEvent;
11 11 class Unit;
12 12 class VisualizationGraphWidget;
13 13
14 14 class VisualizationGraphRenderingDelegate {
15 15 public:
16 16 /// Ctor
17 17 /// @param graphWidget the graph widget to which the delegate is associated
18 18 /// @remarks the graph widget must exist throughout the life cycle of the delegate
19 19 explicit VisualizationGraphRenderingDelegate(VisualizationGraphWidget &graphWidget);
20 20
21 void onMouseDoubleClick(QMouseEvent *event) noexcept;
21 22 void onMouseMove(QMouseEvent *event) noexcept;
22 23
23 24 /// Sets properties of the plot's axes from the data series passed as parameter
24 25 void setAxesProperties(std::shared_ptr<IDataSeries> dataSeries) noexcept;
25 26
26 27 /// Sets rendering properties of the plottables passed as parameter, from the data series that
27 28 /// generated these
28 29 void setPlottablesProperties(std::shared_ptr<IDataSeries> dataSeries,
29 30 PlottablesMap &plottables) noexcept;
30 31
31 32 /// Shows or hides graph overlay (name, close button, etc.)
32 33 void showGraphOverlay(bool show) noexcept;
33 34
34 35 private:
35 36 class VisualizationGraphRenderingDelegatePrivate;
36 37 spimpl::unique_impl_ptr<VisualizationGraphRenderingDelegatePrivate> impl;
37 38 };
38 39
39 40 #endif // SCIQLOP_VISUALIZATIONGRAPHRENDERINGDELEGATE_H
@@ -1,121 +1,123
1 1 #ifndef SCIQLOP_VISUALIZATIONGRAPHWIDGET_H
2 2 #define SCIQLOP_VISUALIZATIONGRAPHWIDGET_H
3 3
4 4 #include "Visualization/IVisualizationWidget.h"
5 5 #include "Visualization/VisualizationDragWidget.h"
6 6
7 7 #include <QLoggingCategory>
8 8 #include <QWidget>
9 9
10 10 #include <memory>
11 11
12 12 #include <Common/spimpl.h>
13 13
14 14 Q_DECLARE_LOGGING_CATEGORY(LOG_VisualizationGraphWidget)
15 15
16 16 class QCPRange;
17 17 class QCustomPlot;
18 18 class SqpRange;
19 19 class Variable;
20 20 class VisualizationZoneWidget;
21 21
22 22 namespace Ui {
23 23 class VisualizationGraphWidget;
24 24 } // namespace Ui
25 25
26 26 class VisualizationGraphWidget : public VisualizationDragWidget, public IVisualizationWidget {
27 27 Q_OBJECT
28 28
29 29 friend class QCustomPlotSynchronizer;
30 30 friend class VisualizationGraphRenderingDelegate;
31 31
32 32 public:
33 33 explicit VisualizationGraphWidget(const QString &name = {}, QWidget *parent = 0);
34 34 virtual ~VisualizationGraphWidget();
35 35
36 36 VisualizationZoneWidget *parentZoneWidget() const noexcept;
37 37
38 38 /// If acquisition isn't enable, requestDataLoading signal cannot be emit
39 39 void enableAcquisition(bool enable);
40 40
41 41 void addVariable(std::shared_ptr<Variable> variable, SqpRange range);
42 42
43 43 /// Removes a variable from the graph
44 44 void removeVariable(std::shared_ptr<Variable> variable) noexcept;
45 45
46 46 /// Returns the list of all variables used in the graph
47 47 QList<std::shared_ptr<Variable> > variables() const;
48 48
49 49 /// Sets the y-axis range based on the data of a variable
50 50 void setYRange(std::shared_ptr<Variable> variable);
51 51 SqpRange graphRange() const noexcept;
52 52 void setGraphRange(const SqpRange &range);
53 53
54 54 // IVisualizationWidget interface
55 55 void accept(IVisualizationWidgetVisitor *visitor) override;
56 56 bool canDrop(const Variable &variable) const override;
57 57 bool contains(const Variable &variable) const override;
58 58 QString name() const override;
59 59
60 60 // VisualisationDragWidget
61 61 QMimeData *mimeData() const override;
62 62 bool isDragAllowed() const override;
63 63 void highlightForMerge(bool highlighted) override;
64 64
65 65 // Cursors
66 66 /// Adds or moves the vertical cursor at the specified value on the x-axis
67 67 void addVerticalCursor(double time);
68 68 /// Adds or moves the vertical cursor at the specified value on the x-axis
69 69 void addVerticalCursorAtViewportPosition(double position);
70 70 void removeVerticalCursor();
71 71 /// Adds or moves the vertical cursor at the specified value on the y-axis
72 72 void addHorizontalCursor(double value);
73 73 /// Adds or moves the vertical cursor at the specified value on the y-axis
74 74 void addHorizontalCursorAtViewportPosition(double position);
75 75 void removeHorizontalCursor();
76 76
77 77 signals:
78 78 void synchronize(const SqpRange &range, const SqpRange &oldRange);
79 79 void requestDataLoading(QVector<std::shared_ptr<Variable> > variable, const SqpRange &range,
80 80 bool synchronise);
81 81
82 82 /// Signal emitted when the variable is about to be removed from the graph
83 83 void variableAboutToBeRemoved(std::shared_ptr<Variable> var);
84 84 /// Signal emitted when the variable has been added to the graph
85 85 void variableAdded(std::shared_ptr<Variable> var);
86 86
87 87 protected:
88 88 void closeEvent(QCloseEvent *event) override;
89 89 void enterEvent(QEvent *event) override;
90 90 void leaveEvent(QEvent *event) override;
91 91
92 92 QCustomPlot &plot() noexcept;
93 93
94 94 private:
95 95 Ui::VisualizationGraphWidget *ui;
96 96
97 97 class VisualizationGraphWidgetPrivate;
98 98 spimpl::unique_impl_ptr<VisualizationGraphWidgetPrivate> impl;
99 99
100 100 private slots:
101 101 /// Slot called when right clicking on the graph (displays a menu)
102 102 void onGraphMenuRequested(const QPoint &pos) noexcept;
103 103
104 104 /// Rescale the X axe to range parameter
105 105 void onRangeChanged(const QCPRange &t1, const QCPRange &t2);
106 106
107 /// Slot called when a mouse double click was made
108 void onMouseDoubleClick(QMouseEvent *event) noexcept;
107 109 /// Slot called when a mouse move was made
108 110 void onMouseMove(QMouseEvent *event) noexcept;
109 111 /// Slot called when a mouse wheel was made, to perform some processing before the zoom is done
110 112 void onMouseWheel(QWheelEvent *event) noexcept;
111 113 /// Slot called when a mouse press was made, to activate the calibration of a graph
112 114 void onMousePress(QMouseEvent *event) noexcept;
113 115 /// Slot called when a mouse release was made, to deactivate the calibration of a graph
114 116 void onMouseRelease(QMouseEvent *event) noexcept;
115 117
116 118 void onDataCacheVariableUpdated();
117 119
118 120 void onUpdateVarDisplaying(std::shared_ptr<Variable> variable, const SqpRange &range);
119 121 };
120 122
121 123 #endif // SCIQLOP_VISUALIZATIONGRAPHWIDGET_H
@@ -1,245 +1,255
1 1 #include "Visualization/VisualizationGraphRenderingDelegate.h"
2 2 #include "Visualization/AxisRenderingUtils.h"
3 #include "Visualization/ColorScaleEditor.h"
3 4 #include "Visualization/PlottablesRenderingUtils.h"
4 5 #include "Visualization/VisualizationGraphWidget.h"
5 6 #include "Visualization/qcustomplot.h"
6 7
7 8 #include <Common/DateUtils.h>
8 9
9 10 #include <Data/IDataSeries.h>
10 11
11 12 #include <SqpApplication.h>
12 13
13 14 namespace {
14 15
15 16 /// Name of the axes layer in QCustomPlot
16 17 const auto AXES_LAYER = QStringLiteral("axes");
17 18
18 19 /// Icon used to show x-axis properties
19 20 const auto HIDE_AXIS_ICON_PATH = QStringLiteral(":/icones/down.png");
20 21
21 22 /// Name of the overlay layer in QCustomPlot
22 23 const auto OVERLAY_LAYER = QStringLiteral("overlay");
23 24
24 25 /// Pixmap used to show x-axis properties
25 26 const auto SHOW_AXIS_ICON_PATH = QStringLiteral(":/icones/up.png");
26 27
27 28 const auto TOOLTIP_FORMAT = QStringLiteral("key: %1\nvalue: %2");
28 29
29 30 /// Offset used to shift the tooltip of the mouse
30 31 const auto TOOLTIP_OFFSET = QPoint{20, 20};
31 32
32 33 /// Tooltip display rectangle (the tooltip is hidden when the mouse leaves this rectangle)
33 34 const auto TOOLTIP_RECT = QRect{10, 10, 10, 10};
34 35
35 36 /// Timeout after which the tooltip is displayed
36 37 const auto TOOLTIP_TIMEOUT = 500;
37 38
38 39 void initPointTracerStyle(QCPItemTracer &tracer) noexcept
39 40 {
40 41 tracer.setInterpolating(false);
41 42 tracer.setStyle(QCPItemTracer::tsCircle);
42 43 tracer.setSize(3);
43 44 tracer.setPen(QPen(Qt::black));
44 45 tracer.setBrush(Qt::black);
45 46 }
46 47
47 48 QPixmap pixmap(const QString &iconPath) noexcept
48 49 {
49 50 return QIcon{iconPath}.pixmap(QSize{16, 16});
50 51 }
51 52
52 53 void initClosePixmapStyle(QCPItemPixmap &pixmap) noexcept
53 54 {
54 55 // Icon
55 56 pixmap.setPixmap(
56 57 sqpApp->style()->standardIcon(QStyle::SP_TitleBarCloseButton).pixmap(QSize{16, 16}));
57 58
58 59 // Position
59 60 pixmap.topLeft->setType(QCPItemPosition::ptAxisRectRatio);
60 61 pixmap.topLeft->setCoords(1, 0);
61 62 pixmap.setClipToAxisRect(false);
62 63
63 64 // Can be selected
64 65 pixmap.setSelectable(true);
65 66 }
66 67
67 68 void initXAxisPixmapStyle(QCPItemPixmap &itemPixmap) noexcept
68 69 {
69 70 // Icon
70 71 itemPixmap.setPixmap(pixmap(HIDE_AXIS_ICON_PATH));
71 72
72 73 // Position
73 74 itemPixmap.topLeft->setType(QCPItemPosition::ptAxisRectRatio);
74 75 itemPixmap.topLeft->setCoords(0, 1);
75 76 itemPixmap.setClipToAxisRect(false);
76 77
77 78 // Can be selected
78 79 itemPixmap.setSelectable(true);
79 80 }
80 81
81 82 void initTitleTextStyle(QCPItemText &text) noexcept
82 83 {
83 84 // Font and background styles
84 85 text.setColor(Qt::gray);
85 86 text.setBrush(Qt::white);
86 87
87 88 // Position
88 89 text.setPositionAlignment(Qt::AlignTop | Qt::AlignLeft);
89 90 text.position->setType(QCPItemPosition::ptAxisRectRatio);
90 91 text.position->setCoords(0.5, 0);
91 92 }
92 93
93 94 } // namespace
94 95
95 96 struct VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegatePrivate {
96 97 explicit VisualizationGraphRenderingDelegatePrivate(VisualizationGraphWidget &graphWidget)
97 98 : m_Plot{graphWidget.plot()},
98 99 m_PointTracer{new QCPItemTracer{&m_Plot}},
99 100 m_TracerTimer{},
100 101 m_ClosePixmap{new QCPItemPixmap{&m_Plot}},
101 102 m_TitleText{new QCPItemText{&m_Plot}},
102 103 m_XAxisPixmap{new QCPItemPixmap{&m_Plot}},
103 104 m_ShowXAxis{true},
104 105 m_XAxisLabel{},
105 106 m_ColorScale{new QCPColorScale{&m_Plot}}
106 107 {
107 108 initPointTracerStyle(*m_PointTracer);
108 109
109 110 m_TracerTimer.setInterval(TOOLTIP_TIMEOUT);
110 111 m_TracerTimer.setSingleShot(true);
111 112
112 113 // Inits "close button" in plot overlay
113 114 m_ClosePixmap->setLayer(OVERLAY_LAYER);
114 115 initClosePixmapStyle(*m_ClosePixmap);
115 116
116 117 // Connects pixmap selection to graph widget closing
117 118 QObject::connect(m_ClosePixmap, &QCPItemPixmap::selectionChanged,
118 119 [&graphWidget](bool selected) {
119 120 if (selected) {
120 121 graphWidget.close();
121 122 }
122 123 });
123 124
124 125 // Inits graph name in plot overlay
125 126 m_TitleText->setLayer(OVERLAY_LAYER);
126 127 m_TitleText->setText(graphWidget.name());
127 128 initTitleTextStyle(*m_TitleText);
128 129
129 130 // Inits "show x-axis button" in plot overlay
130 131 m_XAxisPixmap->setLayer(OVERLAY_LAYER);
131 132 initXAxisPixmapStyle(*m_XAxisPixmap);
132 133
133 134 // Connects pixmap selection to graph x-axis showing/hiding
134 135 QObject::connect(m_XAxisPixmap, &QCPItemPixmap::selectionChanged, [this]() {
135 136 if (m_XAxisPixmap->selected()) {
136 137 // Changes the selection state and refreshes the x-axis
137 138 m_ShowXAxis = !m_ShowXAxis;
138 139 updateXAxisState();
139 140 m_Plot.layer(AXES_LAYER)->replot();
140 141
141 142 // Deselects the x-axis pixmap and updates icon
142 143 m_XAxisPixmap->setSelected(false);
143 144 m_XAxisPixmap->setPixmap(
144 145 pixmap(m_ShowXAxis ? HIDE_AXIS_ICON_PATH : SHOW_AXIS_ICON_PATH));
145 146 m_Plot.layer(OVERLAY_LAYER)->replot();
146 147 }
147 148 });
148 149 }
149 150
150 151 /// Updates state of x-axis according to the current selection of x-axis pixmap
151 152 /// @remarks the method doesn't call plot refresh
152 153 void updateXAxisState() noexcept
153 154 {
154 155 m_Plot.xAxis->setTickLabels(m_ShowXAxis);
155 156 m_Plot.xAxis->setLabel(m_ShowXAxis ? m_XAxisLabel : QString{});
156 157 }
157 158
158 159 QCustomPlot &m_Plot;
159 160 QCPItemTracer *m_PointTracer;
160 161 QTimer m_TracerTimer;
161 162 QCPItemPixmap *m_ClosePixmap; /// Graph's close button
162 163 QCPItemText *m_TitleText; /// Graph's title
163 164 QCPItemPixmap *m_XAxisPixmap;
164 165 bool m_ShowXAxis; /// X-axis properties are shown or hidden
165 166 QString m_XAxisLabel;
166 167 QCPColorScale *m_ColorScale; /// Color scale used for some types of graphs (as spectrograms)
167 168 };
168 169
169 170 VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegate(
170 171 VisualizationGraphWidget &graphWidget)
171 172 : impl{spimpl::make_unique_impl<VisualizationGraphRenderingDelegatePrivate>(graphWidget)}
172 173 {
173 174 }
174 175
176 void VisualizationGraphRenderingDelegate::onMouseDoubleClick(QMouseEvent *event) noexcept
177 {
178 // Opens color scale editor if color scale is double clicked
179 if (auto colorScale
180 = dynamic_cast<QCPColorScale *>(impl->m_Plot.layoutElementAt(event->pos()))) {
181 ColorScaleEditor{}.exec();
182 }
183 }
184
175 185 void VisualizationGraphRenderingDelegate::onMouseMove(QMouseEvent *event) noexcept
176 186 {
177 187 // Cancels pending refresh
178 188 impl->m_TracerTimer.disconnect();
179 189
180 190 // Reinits tracers
181 191 impl->m_PointTracer->setGraph(nullptr);
182 192 impl->m_PointTracer->setVisible(false);
183 193 impl->m_Plot.replot();
184 194
185 195 // Gets the graph under the mouse position
186 196 auto eventPos = event->pos();
187 197 if (auto graph = qobject_cast<QCPGraph *>(impl->m_Plot.plottableAt(eventPos))) {
188 198 auto mouseKey = graph->keyAxis()->pixelToCoord(eventPos.x());
189 199 auto graphData = graph->data();
190 200
191 201 // Gets the closest data point to the mouse
192 202 auto graphDataIt = graphData->findBegin(mouseKey);
193 203 if (graphDataIt != graphData->constEnd()) {
194 204 auto key = formatValue(graphDataIt->key, *graph->keyAxis());
195 205 auto value = formatValue(graphDataIt->value, *graph->valueAxis());
196 206
197 207 // Displays point tracer
198 208 impl->m_PointTracer->setGraph(graph);
199 209 impl->m_PointTracer->setGraphKey(graphDataIt->key);
200 210 impl->m_PointTracer->setLayer(
201 211 impl->m_Plot.layer("main")); // Tracer is set on top of the plot's main layer
202 212 impl->m_PointTracer->setVisible(true);
203 213 impl->m_Plot.replot();
204 214
205 215 // Starts timer to show tooltip after timeout
206 216 auto showTooltip = [ tooltip = TOOLTIP_FORMAT.arg(key, value), eventPos, this ]()
207 217 {
208 218 QToolTip::showText(impl->m_Plot.mapToGlobal(eventPos) + TOOLTIP_OFFSET, tooltip,
209 219 &impl->m_Plot, TOOLTIP_RECT);
210 220 };
211 221
212 222 QObject::connect(&impl->m_TracerTimer, &QTimer::timeout, showTooltip);
213 223 impl->m_TracerTimer.start();
214 224 }
215 225 }
216 226 }
217 227
218 228 void VisualizationGraphRenderingDelegate::setAxesProperties(
219 229 std::shared_ptr<IDataSeries> dataSeries) noexcept
220 230 {
221 231 // Stores x-axis label to be able to retrieve it when x-axis pixmap is unselected
222 232 impl->m_XAxisLabel = dataSeries->xAxisUnit().m_Name;
223 233
224 234 auto axisHelper = IAxisHelperFactory::create(dataSeries);
225 235 axisHelper->setProperties(impl->m_Plot, *impl->m_ColorScale);
226 236
227 237 // Updates x-axis state
228 238 impl->updateXAxisState();
229 239
230 240 impl->m_Plot.layer(AXES_LAYER)->replot();
231 241 }
232 242
233 243 void VisualizationGraphRenderingDelegate::setPlottablesProperties(
234 244 std::shared_ptr<IDataSeries> dataSeries, PlottablesMap &plottables) noexcept
235 245 {
236 246 auto plottablesHelper = IPlottablesHelperFactory::create(dataSeries);
237 247 plottablesHelper->setProperties(plottables);
238 248 }
239 249
240 250 void VisualizationGraphRenderingDelegate::showGraphOverlay(bool show) noexcept
241 251 {
242 252 auto overlay = impl->m_Plot.layer(OVERLAY_LAYER);
243 253 overlay->setVisible(show);
244 254 overlay->replot();
245 255 }
@@ -1,597 +1,604
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/VisualizationZoneWidget.h"
8 8 #include "ui_VisualizationGraphWidget.h"
9 9
10 10 #include <Common/MimeTypesDef.h>
11 11 #include <Data/ArrayData.h>
12 12 #include <Data/IDataSeries.h>
13 13 #include <DragAndDrop/DragDropHelper.h>
14 14 #include <Settings/SqpSettingsDefs.h>
15 15 #include <SqpApplication.h>
16 16 #include <Time/TimeController.h>
17 17 #include <Variable/Variable.h>
18 18 #include <Variable/VariableController.h>
19 19
20 20 #include <unordered_map>
21 21
22 22 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
23 23
24 24 namespace {
25 25
26 26 /// Key pressed to enable zoom on horizontal axis
27 27 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::ControlModifier;
28 28
29 29 /// Key pressed to enable zoom on vertical axis
30 30 const auto VERTICAL_ZOOM_MODIFIER = Qt::ShiftModifier;
31 31
32 32 /// Speed of a step of a wheel event for a pan, in percentage of the axis range
33 33 const auto PAN_SPEED = 5;
34 34
35 35 /// Key pressed to enable a calibration pan
36 36 const auto VERTICAL_PAN_MODIFIER = Qt::AltModifier;
37 37
38 38 /// Minimum size for the zoom box, in percentage of the axis range
39 39 const auto ZOOM_BOX_MIN_SIZE = 0.8;
40 40
41 41 /// Format of the dates appearing in the label of a cursor
42 42 const auto CURSOR_LABELS_DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd\nhh:mm:ss:zzz");
43 43
44 44 } // namespace
45 45
46 46 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate {
47 47
48 48 explicit VisualizationGraphWidgetPrivate(const QString &name)
49 49 : m_Name{name},
50 50 m_DoAcquisition{true},
51 51 m_IsCalibration{false},
52 52 m_RenderingDelegate{nullptr}
53 53 {
54 54 }
55 55
56 56 QString m_Name;
57 57 // 1 variable -> n qcpplot
58 58 std::map<std::shared_ptr<Variable>, PlottablesMap> m_VariableToPlotMultiMap;
59 59 bool m_DoAcquisition;
60 60 bool m_IsCalibration;
61 61 /// Delegate used to attach rendering features to the plot
62 62 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
63 63
64 64 QCPItemRect *m_DrawingRect = nullptr;
65 65 std::unique_ptr<VisualizationCursorItem> m_HorizontalCursor = nullptr;
66 66 std::unique_ptr<VisualizationCursorItem> m_VerticalCursor = nullptr;
67 67
68 68 void configureDrawingRect()
69 69 {
70 70 if (m_DrawingRect) {
71 71 QPen p;
72 72 p.setWidth(2);
73 73 m_DrawingRect->setPen(p);
74 74 }
75 75 }
76 76
77 77 void startDrawingRect(const QPoint &pos, QCustomPlot &plot)
78 78 {
79 79 removeDrawingRect(plot);
80 80
81 81 auto axisPos = posToAxisPos(pos, plot);
82 82
83 83 m_DrawingRect = new QCPItemRect{&plot};
84 84 configureDrawingRect();
85 85
86 86 m_DrawingRect->topLeft->setCoords(axisPos);
87 87 m_DrawingRect->bottomRight->setCoords(axisPos);
88 88 }
89 89
90 90 void removeDrawingRect(QCustomPlot &plot)
91 91 {
92 92 if (m_DrawingRect) {
93 93 plot.removeItem(m_DrawingRect); // the item is deleted by QCustomPlot
94 94 m_DrawingRect = nullptr;
95 95 plot.replot(QCustomPlot::rpQueuedReplot);
96 96 }
97 97 }
98 98
99 99 QPointF posToAxisPos(const QPoint &pos, QCustomPlot &plot) const
100 100 {
101 101 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
102 102 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
103 103 return QPointF{axisX->pixelToCoord(pos.x()), axisY->pixelToCoord(pos.y())};
104 104 }
105 105
106 106 bool pointIsInAxisRect(const QPointF &axisPoint, QCustomPlot &plot) const
107 107 {
108 108 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
109 109 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
110 110
111 111 return axisX->range().contains(axisPoint.x()) && axisY->range().contains(axisPoint.y());
112 112 }
113 113 };
114 114
115 115 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
116 116 : VisualizationDragWidget{parent},
117 117 ui{new Ui::VisualizationGraphWidget},
118 118 impl{spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name)}
119 119 {
120 120 ui->setupUi(this);
121 121
122 122 // 'Close' options : widget is deleted when closed
123 123 setAttribute(Qt::WA_DeleteOnClose);
124 124
125 125 // Set qcpplot properties :
126 126 // - Drag (on x-axis) and zoom are enabled
127 127 // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation
128 128 ui->widget->setInteractions(QCP::iRangeZoom | QCP::iSelectItems);
129 129
130 130 // The delegate must be initialized after the ui as it uses the plot
131 131 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
132 132
133 133 // Init the cursors
134 134 impl->m_HorizontalCursor = std::make_unique<VisualizationCursorItem>(&plot());
135 135 impl->m_HorizontalCursor->setOrientation(Qt::Horizontal);
136 136 impl->m_VerticalCursor = std::make_unique<VisualizationCursorItem>(&plot());
137 137 impl->m_VerticalCursor->setOrientation(Qt::Vertical);
138 138
139 139 connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress);
140 140 connect(ui->widget, &QCustomPlot::mouseRelease, this,
141 141 &VisualizationGraphWidget::onMouseRelease);
142 142 connect(ui->widget, &QCustomPlot::mouseMove, this, &VisualizationGraphWidget::onMouseMove);
143 143 connect(ui->widget, &QCustomPlot::mouseWheel, this, &VisualizationGraphWidget::onMouseWheel);
144 connect(ui->widget, &QCustomPlot::mouseDoubleClick, this,
145 &VisualizationGraphWidget::onMouseDoubleClick);
144 146 connect(ui->widget->xAxis, static_cast<void (QCPAxis::*)(const QCPRange &, const QCPRange &)>(
145 147 &QCPAxis::rangeChanged),
146 148 this, &VisualizationGraphWidget::onRangeChanged, Qt::DirectConnection);
147 149
148 150 // Activates menu when right clicking on the graph
149 151 ui->widget->setContextMenuPolicy(Qt::CustomContextMenu);
150 152 connect(ui->widget, &QCustomPlot::customContextMenuRequested, this,
151 153 &VisualizationGraphWidget::onGraphMenuRequested);
152 154
153 155 connect(this, &VisualizationGraphWidget::requestDataLoading, &sqpApp->variableController(),
154 156 &VariableController::onRequestDataLoading);
155 157
156 158 connect(&sqpApp->variableController(), &VariableController::updateVarDisplaying, this,
157 159 &VisualizationGraphWidget::onUpdateVarDisplaying);
158 160
159 161 #ifdef Q_OS_MAC
160 162 plot().setPlottingHint(QCP::phFastPolylines, true);
161 163 #endif
162 164 }
163 165
164 166
165 167 VisualizationGraphWidget::~VisualizationGraphWidget()
166 168 {
167 169 delete ui;
168 170 }
169 171
170 172 VisualizationZoneWidget *VisualizationGraphWidget::parentZoneWidget() const noexcept
171 173 {
172 174 auto parent = parentWidget();
173 175 while (parent != nullptr && !qobject_cast<VisualizationZoneWidget *>(parent)) {
174 176 parent = parent->parentWidget();
175 177 }
176 178
177 179 return qobject_cast<VisualizationZoneWidget *>(parent);
178 180 }
179 181
180 182 void VisualizationGraphWidget::enableAcquisition(bool enable)
181 183 {
182 184 impl->m_DoAcquisition = enable;
183 185 }
184 186
185 187 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable> variable, SqpRange range)
186 188 {
187 189 // Uses delegate to create the qcpplot components according to the variable
188 190 auto createdPlottables = VisualizationGraphHelper::create(variable, *ui->widget);
189 191
190 192 if (auto dataSeries = variable->dataSeries()) {
191 193 // Set axes properties according to the units of the data series
192 194 impl->m_RenderingDelegate->setAxesProperties(dataSeries);
193 195
194 196 // Sets rendering properties for the new plottables
195 197 // Warning: this method must be called after setAxesProperties(), as it can access to some
196 198 // axes properties that have to be initialized
197 199 impl->m_RenderingDelegate->setPlottablesProperties(dataSeries, createdPlottables);
198 200 }
199 201
200 202 impl->m_VariableToPlotMultiMap.insert({variable, std::move(createdPlottables)});
201 203
202 204 connect(variable.get(), SIGNAL(updated()), this, SLOT(onDataCacheVariableUpdated()));
203 205
204 206 this->enableAcquisition(false);
205 207 this->setGraphRange(range);
206 208 this->enableAcquisition(true);
207 209
208 210 emit requestDataLoading(QVector<std::shared_ptr<Variable> >() << variable, range, false);
209 211
210 212 emit variableAdded(variable);
211 213 }
212 214
213 215 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable> variable) noexcept
214 216 {
215 217 // Each component associated to the variable :
216 218 // - is removed from qcpplot (which deletes it)
217 219 // - is no longer referenced in the map
218 220 auto variableIt = impl->m_VariableToPlotMultiMap.find(variable);
219 221 if (variableIt != impl->m_VariableToPlotMultiMap.cend()) {
220 222 emit variableAboutToBeRemoved(variable);
221 223
222 224 auto &plottablesMap = variableIt->second;
223 225
224 226 for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend();
225 227 plottableIt != plottableEnd;) {
226 228 ui->widget->removePlottable(plottableIt->second);
227 229 plottableIt = plottablesMap.erase(plottableIt);
228 230 }
229 231
230 232 impl->m_VariableToPlotMultiMap.erase(variableIt);
231 233 }
232 234
233 235 // Updates graph
234 236 ui->widget->replot();
235 237 }
236 238
237 239 QList<std::shared_ptr<Variable> > VisualizationGraphWidget::variables() const
238 240 {
239 241 auto variables = QList<std::shared_ptr<Variable> >{};
240 242 for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap);
241 243 it != std::cend(impl->m_VariableToPlotMultiMap); ++it) {
242 244 variables << it->first;
243 245 }
244 246
245 247 return variables;
246 248 }
247 249
248 250 void VisualizationGraphWidget::setYRange(std::shared_ptr<Variable> variable)
249 251 {
250 252 if (!variable) {
251 253 qCCritical(LOG_VisualizationGraphWidget()) << "Can't set y-axis range: variable is null";
252 254 return;
253 255 }
254 256
255 257 VisualizationGraphHelper::setYAxisRange(variable, *ui->widget);
256 258 }
257 259
258 260 SqpRange VisualizationGraphWidget::graphRange() const noexcept
259 261 {
260 262 auto graphRange = ui->widget->xAxis->range();
261 263 return SqpRange{graphRange.lower, graphRange.upper};
262 264 }
263 265
264 266 void VisualizationGraphWidget::setGraphRange(const SqpRange &range)
265 267 {
266 268 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange START");
267 269 ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd);
268 270 ui->widget->replot();
269 271 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange END");
270 272 }
271 273
272 274 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor)
273 275 {
274 276 if (visitor) {
275 277 visitor->visit(this);
276 278 }
277 279 else {
278 280 qCCritical(LOG_VisualizationGraphWidget())
279 281 << tr("Can't visit widget : the visitor is null");
280 282 }
281 283 }
282 284
283 285 bool VisualizationGraphWidget::canDrop(const Variable &variable) const
284 286 {
285 287 /// @todo : for the moment, a graph can always accomodate a variable
286 288 Q_UNUSED(variable);
287 289 return true;
288 290 }
289 291
290 292 bool VisualizationGraphWidget::contains(const Variable &variable) const
291 293 {
292 294 // Finds the variable among the keys of the map
293 295 auto variablePtr = &variable;
294 296 auto findVariable
295 297 = [variablePtr](const auto &entry) { return variablePtr == entry.first.get(); };
296 298
297 299 auto end = impl->m_VariableToPlotMultiMap.cend();
298 300 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
299 301 return it != end;
300 302 }
301 303
302 304 QString VisualizationGraphWidget::name() const
303 305 {
304 306 return impl->m_Name;
305 307 }
306 308
307 309 QMimeData *VisualizationGraphWidget::mimeData() const
308 310 {
309 311 auto mimeData = new QMimeData;
310 312 mimeData->setData(MIME_TYPE_GRAPH, QByteArray{});
311 313
312 314 auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange());
313 315 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
314 316
315 317 return mimeData;
316 318 }
317 319
318 320 bool VisualizationGraphWidget::isDragAllowed() const
319 321 {
320 322 return true;
321 323 }
322 324
323 325 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
324 326 {
325 327 if (highlighted) {
326 328 plot().setBackground(QBrush(QColor("#BBD5EE")));
327 329 }
328 330 else {
329 331 plot().setBackground(QBrush(Qt::white));
330 332 }
331 333
332 334 plot().update();
333 335 }
334 336
335 337 void VisualizationGraphWidget::addVerticalCursor(double time)
336 338 {
337 339 impl->m_VerticalCursor->setPosition(time);
338 340 impl->m_VerticalCursor->setVisible(true);
339 341
340 342 auto text
341 343 = DateUtils::dateTime(time).toString(CURSOR_LABELS_DATETIME_FORMAT).replace(' ', '\n');
342 344 impl->m_VerticalCursor->setLabelText(text);
343 345 }
344 346
345 347 void VisualizationGraphWidget::addVerticalCursorAtViewportPosition(double position)
346 348 {
347 349 impl->m_VerticalCursor->setAbsolutePosition(position);
348 350 impl->m_VerticalCursor->setVisible(true);
349 351
350 352 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
351 353 auto text
352 354 = DateUtils::dateTime(axis->pixelToCoord(position)).toString(CURSOR_LABELS_DATETIME_FORMAT);
353 355 impl->m_VerticalCursor->setLabelText(text);
354 356 }
355 357
356 358 void VisualizationGraphWidget::removeVerticalCursor()
357 359 {
358 360 impl->m_VerticalCursor->setVisible(false);
359 361 plot().replot(QCustomPlot::rpQueuedReplot);
360 362 }
361 363
362 364 void VisualizationGraphWidget::addHorizontalCursor(double value)
363 365 {
364 366 impl->m_HorizontalCursor->setPosition(value);
365 367 impl->m_HorizontalCursor->setVisible(true);
366 368 impl->m_HorizontalCursor->setLabelText(QString::number(value));
367 369 }
368 370
369 371 void VisualizationGraphWidget::addHorizontalCursorAtViewportPosition(double position)
370 372 {
371 373 impl->m_HorizontalCursor->setAbsolutePosition(position);
372 374 impl->m_HorizontalCursor->setVisible(true);
373 375
374 376 auto axis = plot().axisRect()->axis(QCPAxis::atLeft);
375 377 impl->m_HorizontalCursor->setLabelText(QString::number(axis->pixelToCoord(position)));
376 378 }
377 379
378 380 void VisualizationGraphWidget::removeHorizontalCursor()
379 381 {
380 382 impl->m_HorizontalCursor->setVisible(false);
381 383 plot().replot(QCustomPlot::rpQueuedReplot);
382 384 }
383 385
384 386 void VisualizationGraphWidget::closeEvent(QCloseEvent *event)
385 387 {
386 388 Q_UNUSED(event);
387 389
388 390 // Prevents that all variables will be removed from graph when it will be closed
389 391 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
390 392 emit variableAboutToBeRemoved(variableEntry.first);
391 393 }
392 394 }
393 395
394 396 void VisualizationGraphWidget::enterEvent(QEvent *event)
395 397 {
396 398 Q_UNUSED(event);
397 399 impl->m_RenderingDelegate->showGraphOverlay(true);
398 400 }
399 401
400 402 void VisualizationGraphWidget::leaveEvent(QEvent *event)
401 403 {
402 404 Q_UNUSED(event);
403 405 impl->m_RenderingDelegate->showGraphOverlay(false);
404 406
405 407 if (auto parentZone = parentZoneWidget()) {
406 408 parentZone->notifyMouseLeaveGraph(this);
407 409 }
408 410 else {
409 411 qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget";
410 412 }
411 413 }
412 414
413 415 QCustomPlot &VisualizationGraphWidget::plot() noexcept
414 416 {
415 417 return *ui->widget;
416 418 }
417 419
418 420 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept
419 421 {
420 422 QMenu graphMenu{};
421 423
422 424 // Iterates on variables (unique keys)
423 425 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
424 426 end = impl->m_VariableToPlotMultiMap.cend();
425 427 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
426 428 // 'Remove variable' action
427 429 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
428 430 [ this, var = it->first ]() { removeVariable(var); });
429 431 }
430 432
431 433 if (!graphMenu.isEmpty()) {
432 434 graphMenu.exec(QCursor::pos());
433 435 }
434 436 }
435 437
436 438 void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange &t2)
437 439 {
438 440 qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: VisualizationGraphWidget::onRangeChanged")
439 441 << QThread::currentThread()->objectName() << "DoAcqui"
440 442 << impl->m_DoAcquisition;
441 443
442 444 auto graphRange = SqpRange{t1.lower, t1.upper};
443 445 auto oldGraphRange = SqpRange{t2.lower, t2.upper};
444 446
445 447 if (impl->m_DoAcquisition) {
446 448 QVector<std::shared_ptr<Variable> > variableUnderGraphVector;
447 449
448 450 for (auto it = impl->m_VariableToPlotMultiMap.begin(),
449 451 end = impl->m_VariableToPlotMultiMap.end();
450 452 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
451 453 variableUnderGraphVector.push_back(it->first);
452 454 }
453 455 emit requestDataLoading(std::move(variableUnderGraphVector), graphRange,
454 456 !impl->m_IsCalibration);
455 457
456 458 if (!impl->m_IsCalibration) {
457 459 qCDebug(LOG_VisualizationGraphWidget())
458 460 << tr("TORM: VisualizationGraphWidget::Synchronize notify !!")
459 461 << QThread::currentThread()->objectName() << graphRange << oldGraphRange;
460 462 emit synchronize(graphRange, oldGraphRange);
461 463 }
462 464 }
463 465
464 466 auto pos = mapFromGlobal(QCursor::pos());
465 467 auto axisPos = impl->posToAxisPos(pos, plot());
466 468 if (auto parentZone = parentZoneWidget()) {
467 469 if (impl->pointIsInAxisRect(axisPos, plot())) {
468 470 parentZone->notifyMouseMoveInGraph(pos, axisPos, this);
469 471 }
470 472 else {
471 473 parentZone->notifyMouseLeaveGraph(this);
472 474 }
473 475 }
474 476 else {
475 477 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
476 478 }
477 479 }
478 480
481 void VisualizationGraphWidget::onMouseDoubleClick(QMouseEvent *event) noexcept
482 {
483 impl->m_RenderingDelegate->onMouseDoubleClick(event);
484 }
485
479 486 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
480 487 {
481 488 // Handles plot rendering when mouse is moving
482 489 impl->m_RenderingDelegate->onMouseMove(event);
483 490
484 491 auto axisPos = impl->posToAxisPos(event->pos(), plot());
485 492
486 493 if (impl->m_DrawingRect) {
487 494 impl->m_DrawingRect->bottomRight->setCoords(axisPos);
488 495 }
489 496
490 497 if (auto parentZone = parentZoneWidget()) {
491 498 if (impl->pointIsInAxisRect(axisPos, plot())) {
492 499 parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this);
493 500 }
494 501 else {
495 502 parentZone->notifyMouseLeaveGraph(this);
496 503 }
497 504 }
498 505 else {
499 506 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
500 507 }
501 508
502 509 VisualizationDragWidget::mouseMoveEvent(event);
503 510 }
504 511
505 512 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
506 513 {
507 514 auto value = event->angleDelta().x() + event->angleDelta().y();
508 515 if (value != 0) {
509 516
510 517 auto direction = value > 0 ? 1.0 : -1.0;
511 518 auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER);
512 519 auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER);
513 520 impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER);
514 521
515 522 auto zoomOrientations = QFlags<Qt::Orientation>{};
516 523 zoomOrientations.setFlag(Qt::Horizontal, isZoomX);
517 524 zoomOrientations.setFlag(Qt::Vertical, isZoomY);
518 525
519 526 ui->widget->axisRect()->setRangeZoom(zoomOrientations);
520 527
521 528 if (!isZoomX && !isZoomY) {
522 529 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
523 530 auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0));
524 531
525 532 axis->setRange(axis->range() + diff);
526 533
527 534 if (plot().noAntialiasingOnDrag()) {
528 535 plot().setNotAntialiasedElements(QCP::aeAll);
529 536 }
530 537
531 538 plot().replot(QCustomPlot::rpQueuedReplot);
532 539 }
533 540 }
534 541 }
535 542
536 543 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
537 544 {
538 545 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox) {
539 546 impl->startDrawingRect(event->pos(), plot());
540 547 }
541 548
542 549 VisualizationDragWidget::mousePressEvent(event);
543 550 }
544 551
545 552 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
546 553 {
547 554 if (impl->m_DrawingRect) {
548 555
549 556 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
550 557 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
551 558
552 559 auto newAxisXRange = QCPRange{impl->m_DrawingRect->topLeft->coords().x(),
553 560 impl->m_DrawingRect->bottomRight->coords().x()};
554 561
555 562 auto newAxisYRange = QCPRange{impl->m_DrawingRect->topLeft->coords().y(),
556 563 impl->m_DrawingRect->bottomRight->coords().y()};
557 564
558 565 impl->removeDrawingRect(plot());
559 566
560 567 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
561 568 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) {
562 569 axisX->setRange(newAxisXRange);
563 570 axisY->setRange(newAxisYRange);
564 571
565 572 plot().replot(QCustomPlot::rpQueuedReplot);
566 573 }
567 574 }
568 575
569 576 impl->m_IsCalibration = false;
570 577 }
571 578
572 579 void VisualizationGraphWidget::onDataCacheVariableUpdated()
573 580 {
574 581 auto graphRange = ui->widget->xAxis->range();
575 582 auto dateTime = SqpRange{graphRange.lower, graphRange.upper};
576 583
577 584 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
578 585 auto variable = variableEntry.first;
579 586 qCDebug(LOG_VisualizationGraphWidget())
580 587 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
581 588 qCDebug(LOG_VisualizationGraphWidget())
582 589 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
583 590 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range())) {
584 591 VisualizationGraphHelper::updateData(variableEntry.second, variable->dataSeries(),
585 592 variable->range());
586 593 }
587 594 }
588 595 }
589 596
590 597 void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr<Variable> variable,
591 598 const SqpRange &range)
592 599 {
593 600 auto it = impl->m_VariableToPlotMultiMap.find(variable);
594 601 if (it != impl->m_VariableToPlotMultiMap.end()) {
595 602 VisualizationGraphHelper::updateData(it->second, variable->dataSeries(), range);
596 603 }
597 604 }
General Comments 0
You need to be logged in to leave comments. Login now