##// END OF EJS Templates
Display of selection zones on a graph
trabillard -
r1084:dd81862d7e96
parent child
Show More
@@ -0,0 +1,39
1 #ifndef SCIQLOP_VISUALIZATIONSELECTIONZONEITEM_H
2 #define SCIQLOP_VISUALIZATIONSELECTIONZONEITEM_H
3
4 #include <Common/spimpl.h>
5 #include <Data/SqpRange.h>
6 #include <Visualization/qcustomplot.h>
7
8 class VisualizationSelectionZoneItem : public QCPItemRect {
9 public:
10 VisualizationSelectionZoneItem(QCustomPlot *plot);
11 virtual ~VisualizationSelectionZoneItem();
12
13 void setName(const QString &name);
14 QString name() const;
15
16 SqpRange range() const;
17 void setRange(double tstart, double tend);
18 void setStart(double tstart);
19 void setEnd(double tend);
20
21 void setColor(const QColor &color);
22
23 void setEditionEnabled(bool value);
24 bool isEditionEnabled() const;
25
26 Qt::CursorShape curshorShapeForPosition(const QPoint &position) const;
27 void setHovered(bool value);
28
29 protected:
30 void mousePressEvent(QMouseEvent *event, const QVariant &details) override;
31 void mouseMoveEvent(QMouseEvent *event, const QPointF &startPos) override;
32 void mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos) override;
33
34 private:
35 class VisualizationSelectionZoneItemPrivate;
36 spimpl::unique_impl_ptr<VisualizationSelectionZoneItemPrivate> impl;
37 };
38
39 #endif // SCIQLOP_VISUALIZATIONSELECTIONZONEITEM_H
@@ -0,0 +1,271
1 #include "Visualization/VisualizationSelectionZoneItem.h"
2
3 struct VisualizationSelectionZoneItem::VisualizationSelectionZoneItemPrivate {
4
5 QCustomPlot *m_Plot;
6 double m_T1 = 0;
7 double m_T2 = 0;
8 QColor m_Color;
9
10 bool m_IsEditionEnabled = true;
11 double m_MovedOrinalT1 = 0;
12 double m_MovedOrinalT2 = 0;
13
14 QCPItemStraightLine *m_LeftLine;
15 QCPItemStraightLine *m_RightLine;
16 QCPItemText *m_NameLabelItem = nullptr;
17
18 enum class EditionMode { NoEdition, ResizeLeft, ResizeRight, Move };
19 EditionMode m_CurrentEditionMode;
20
21 VisualizationSelectionZoneItemPrivate(QCustomPlot *plot)
22 : m_Plot(plot), m_Color(Qt::blue), m_CurrentEditionMode(EditionMode::NoEdition)
23 {
24 }
25
26 void updatePosition(VisualizationSelectionZoneItem *item)
27 {
28 item->topLeft->setCoords(m_T1, 0);
29 item->bottomRight->setCoords(m_T2, 1);
30 }
31
32 EditionMode getEditionMode(const QPoint &pos, const VisualizationSelectionZoneItem *zoneItem)
33 {
34 auto distanceLeft = m_LeftLine->selectTest(pos, false);
35 auto distanceRight = m_RightLine->selectTest(pos, false);
36 auto distance = zoneItem->selectTest(pos, true);
37
38 if (distanceRight <= distance) {
39 return VisualizationSelectionZoneItemPrivate::EditionMode::ResizeRight;
40 }
41 else if (distanceLeft <= distance) {
42 return VisualizationSelectionZoneItemPrivate::EditionMode::ResizeLeft;
43 }
44
45 return VisualizationSelectionZoneItemPrivate::EditionMode::Move;
46 }
47 };
48
49 VisualizationSelectionZoneItem::VisualizationSelectionZoneItem(QCustomPlot *plot)
50 : QCPItemRect(plot),
51 impl{spimpl::make_unique_impl<VisualizationSelectionZoneItemPrivate>(plot)}
52 {
53 topLeft->setTypeX(QCPItemPosition::ptPlotCoords);
54 topLeft->setTypeY(QCPItemPosition::ptAxisRectRatio);
55 bottomRight->setTypeX(QCPItemPosition::ptPlotCoords);
56 bottomRight->setTypeY(QCPItemPosition::ptAxisRectRatio);
57
58 impl->m_RightLine = new QCPItemStraightLine(plot);
59 impl->m_RightLine->point1->setParentAnchor(topRight);
60 impl->m_RightLine->point2->setParentAnchor(bottomRight);
61 impl->m_RightLine->point1->setTypeX(QCPItemPosition::ptAbsolute);
62 impl->m_RightLine->point1->setTypeY(QCPItemPosition::ptAbsolute);
63 impl->m_RightLine->point2->setTypeX(QCPItemPosition::ptAbsolute);
64 impl->m_RightLine->point2->setTypeY(QCPItemPosition::ptAbsolute);
65
66 impl->m_LeftLine = new QCPItemStraightLine(plot);
67 impl->m_LeftLine->point1->setParentAnchor(topLeft);
68 impl->m_LeftLine->point2->setParentAnchor(bottomLeft);
69 impl->m_LeftLine->point1->setTypeX(QCPItemPosition::ptAbsolute);
70 impl->m_LeftLine->point1->setTypeY(QCPItemPosition::ptAbsolute);
71 impl->m_LeftLine->point2->setTypeX(QCPItemPosition::ptAbsolute);
72 impl->m_LeftLine->point2->setTypeY(QCPItemPosition::ptAbsolute);
73
74 impl->m_RightLine->setSelectable(false);
75 impl->m_LeftLine->setSelectable(false);
76
77 // connect(this, &VisualizationSelectionZoneItem::selectionChanged, impl->m_RightLine,
78 // &QCPItemStraightLine::setSelected);
79 // connect(this, &VisualizationSelectionZoneItem::selectionChanged, impl->m_LeftLine,
80 // &QCPItemStraightLine::setSelected);
81
82 setColor(QColor("#E79D41"));
83 }
84
85 VisualizationSelectionZoneItem::~VisualizationSelectionZoneItem()
86 {
87 impl->m_Plot->removeItem(impl->m_RightLine);
88 impl->m_Plot->removeItem(impl->m_LeftLine);
89 }
90
91 void VisualizationSelectionZoneItem::setName(const QString &name)
92 {
93 if (name.isEmpty() && impl->m_NameLabelItem) {
94 impl->m_Plot->removeItem(impl->m_NameLabelItem);
95 impl->m_NameLabelItem = nullptr;
96 }
97 else if (!impl->m_NameLabelItem) {
98 impl->m_NameLabelItem = new QCPItemText(impl->m_Plot);
99 impl->m_NameLabelItem->setText(name);
100 impl->m_NameLabelItem->setPositionAlignment(Qt::AlignHCenter | Qt::AlignTop);
101 impl->m_NameLabelItem->setColor(impl->m_Color);
102 impl->m_NameLabelItem->position->setParentAnchor(top);
103 }
104 }
105
106 QString VisualizationSelectionZoneItem::name() const
107 {
108 if (!impl->m_NameLabelItem) {
109 return QString();
110 }
111
112 return impl->m_NameLabelItem->text();
113 }
114
115 SqpRange VisualizationSelectionZoneItem::range() const
116 {
117 SqpRange range;
118 range.m_TStart = impl->m_T1 <= impl->m_T2 ? impl->m_T1 : impl->m_T2;
119 range.m_TEnd = impl->m_T1 > impl->m_T2 ? impl->m_T1 : impl->m_T2;
120 return range;
121 }
122
123 void VisualizationSelectionZoneItem::setRange(double tstart, double tend)
124 {
125 impl->m_T1 = tstart;
126 impl->m_T2 = tend;
127 impl->updatePosition(this);
128 }
129
130 void VisualizationSelectionZoneItem::setStart(double tstart)
131 {
132 impl->m_T1 = tstart;
133 impl->updatePosition(this);
134 }
135
136 void VisualizationSelectionZoneItem::setEnd(double tend)
137 {
138 impl->m_T2 = tend;
139 impl->updatePosition(this);
140 }
141
142 void VisualizationSelectionZoneItem::setColor(const QColor &color)
143 {
144 impl->m_Color = color;
145
146 auto brushColor = color;
147 brushColor.setAlpha(40);
148 setBrush(QBrush(brushColor));
149 setPen(QPen(Qt::NoPen));
150
151 auto selectedBrushColor = brushColor;
152 selectedBrushColor.setAlpha(65);
153 setSelectedBrush(QBrush(selectedBrushColor));
154 setSelectedPen(QPen(Qt::NoPen));
155
156 auto linePen = QPen(color);
157 linePen.setStyle(Qt::SolidLine);
158 linePen.setWidth(2);
159
160 auto selectedLinePen = linePen;
161 selectedLinePen.setColor(color.darker(30));
162
163 impl->m_LeftLine->setPen(linePen);
164 impl->m_RightLine->setPen(linePen);
165
166 impl->m_LeftLine->setSelectedPen(selectedLinePen);
167 impl->m_RightLine->setSelectedPen(selectedLinePen);
168 }
169
170 void VisualizationSelectionZoneItem::setEditionEnabled(bool value)
171 {
172 impl->m_IsEditionEnabled = value;
173 setSelectable(value);
174 if (!value) {
175 setSelected(false);
176 }
177 }
178
179 bool VisualizationSelectionZoneItem::isEditionEnabled() const
180 {
181 return impl->m_IsEditionEnabled;
182 }
183
184 Qt::CursorShape
185 VisualizationSelectionZoneItem::curshorShapeForPosition(const QPoint &position) const
186 {
187 auto mode = impl->m_CurrentEditionMode
188 == VisualizationSelectionZoneItemPrivate::EditionMode::NoEdition
189 ? impl->getEditionMode(position, this)
190 : impl->m_CurrentEditionMode;
191 switch (mode) {
192 case VisualizationSelectionZoneItemPrivate::EditionMode::Move:
193 return Qt::SizeAllCursor;
194 case VisualizationSelectionZoneItemPrivate::EditionMode::ResizeLeft:
195 case VisualizationSelectionZoneItemPrivate::EditionMode::ResizeRight: // fallthrough
196 return Qt::SizeHorCursor;
197 default:
198 return Qt::ArrowCursor;
199 }
200 }
201
202 void VisualizationSelectionZoneItem::setHovered(bool value)
203 {
204 if (value) {
205 auto linePen = impl->m_LeftLine->pen();
206 linePen.setStyle(Qt::DotLine);
207 linePen.setWidth(3);
208
209 auto selectedLinePen = impl->m_LeftLine->selectedPen();
210 ;
211 selectedLinePen.setStyle(Qt::DotLine);
212 selectedLinePen.setWidth(3);
213
214 impl->m_LeftLine->setPen(linePen);
215 impl->m_RightLine->setPen(linePen);
216
217 impl->m_LeftLine->setSelectedPen(selectedLinePen);
218 impl->m_RightLine->setSelectedPen(selectedLinePen);
219 }
220 else {
221 setColor(impl->m_Color);
222 }
223 }
224
225 void VisualizationSelectionZoneItem::mousePressEvent(QMouseEvent *event, const QVariant &details)
226 {
227 if (isEditionEnabled()) {
228 impl->m_CurrentEditionMode = impl->getEditionMode(event->pos(), this);
229
230 impl->m_MovedOrinalT1 = impl->m_T1;
231 impl->m_MovedOrinalT2 = impl->m_T2;
232 }
233 else {
234 event->ignore();
235 }
236 }
237
238 void VisualizationSelectionZoneItem::mouseMoveEvent(QMouseEvent *event, const QPointF &startPos)
239 {
240 if (isEditionEnabled()) {
241 auto axis = impl->m_Plot->axisRect()->axis(QCPAxis::atBottom);
242 auto diff = axis->pixelToCoord(event->pos().x()) - axis->pixelToCoord(startPos.x());
243
244 switch (impl->m_CurrentEditionMode) {
245 case VisualizationSelectionZoneItemPrivate::EditionMode::Move:
246 setRange(impl->m_MovedOrinalT1 + diff, impl->m_MovedOrinalT2 + diff);
247 break;
248 case VisualizationSelectionZoneItemPrivate::EditionMode::ResizeLeft:
249 setStart(impl->m_MovedOrinalT1 + diff);
250 break;
251 case VisualizationSelectionZoneItemPrivate::EditionMode::ResizeRight:
252 setEnd(impl->m_MovedOrinalT2 + diff);
253 break;
254 // default:
255 // unknown edition mode
256 }
257 }
258 else {
259 event->ignore();
260 }
261 }
262
263 void VisualizationSelectionZoneItem::mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos)
264 {
265 if (isEditionEnabled()) {
266 impl->m_CurrentEditionMode = VisualizationSelectionZoneItemPrivate::EditionMode::NoEdition;
267 }
268 else {
269 event->ignore();
270 }
271 }
@@ -1,101 +1,102
1 1
2 2 gui_moc_headers = [
3 3 'include/DataSource/DataSourceWidget.h',
4 4 'include/Settings/SqpSettingsDialog.h',
5 5 'include/Settings/SqpSettingsGeneralWidget.h',
6 6 'include/SidePane/SqpSidePane.h',
7 7 'include/SqpApplication.h',
8 8 'include/DragAndDrop/DragDropScroller.h',
9 9 'include/DragAndDrop/DragDropTabSwitcher.h',
10 10 'include/TimeWidget/TimeWidget.h',
11 11 'include/Variable/VariableInspectorWidget.h',
12 12 'include/Variable/RenameVariableDialog.h',
13 13 'include/Visualization/qcustomplot.h',
14 14 'include/Visualization/VisualizationGraphWidget.h',
15 15 'include/Visualization/VisualizationTabWidget.h',
16 16 'include/Visualization/VisualizationWidget.h',
17 17 'include/Visualization/VisualizationZoneWidget.h',
18 18 'include/Visualization/VisualizationDragDropContainer.h',
19 19 'include/Visualization/VisualizationDragWidget.h',
20 20 'include/Visualization/ColorScaleEditor.h'
21 21 ]
22 22
23 23 gui_ui_files = [
24 24 'ui/DataSource/DataSourceWidget.ui',
25 25 'ui/Settings/SqpSettingsDialog.ui',
26 26 'ui/Settings/SqpSettingsGeneralWidget.ui',
27 27 'ui/SidePane/SqpSidePane.ui',
28 28 'ui/TimeWidget/TimeWidget.ui',
29 29 'ui/Variable/VariableInspectorWidget.ui',
30 30 'ui/Variable/RenameVariableDialog.ui',
31 31 'ui/Variable/VariableMenuHeaderWidget.ui',
32 32 'ui/Visualization/VisualizationGraphWidget.ui',
33 33 'ui/Visualization/VisualizationTabWidget.ui',
34 34 'ui/Visualization/VisualizationWidget.ui',
35 35 'ui/Visualization/VisualizationZoneWidget.ui',
36 36 'ui/Visualization/ColorScaleEditor.ui'
37 37 ]
38 38
39 39 gui_qresources = ['resources/sqpguiresources.qrc']
40 40
41 41 gui_moc_files = qt5.preprocess(moc_headers : gui_moc_headers,
42 42 ui_files : gui_ui_files,
43 43 qresources : gui_qresources)
44 44
45 45 gui_sources = [
46 46 'src/SqpApplication.cpp',
47 47 'src/DragAndDrop/DragDropHelper.cpp',
48 48 'src/DragAndDrop/DragDropScroller.cpp',
49 49 'src/DragAndDrop/DragDropTabSwitcher.cpp',
50 50 'src/Common/ColorUtils.cpp',
51 51 'src/Common/VisualizationDef.cpp',
52 52 'src/DataSource/DataSourceTreeWidgetItem.cpp',
53 53 'src/DataSource/DataSourceTreeWidgetHelper.cpp',
54 54 'src/DataSource/DataSourceWidget.cpp',
55 55 'src/DataSource/DataSourceTreeWidget.cpp',
56 56 'src/Settings/SqpSettingsDialog.cpp',
57 57 'src/Settings/SqpSettingsGeneralWidget.cpp',
58 58 'src/SidePane/SqpSidePane.cpp',
59 59 'src/TimeWidget/TimeWidget.cpp',
60 60 'src/Variable/VariableInspectorWidget.cpp',
61 61 'src/Variable/VariableInspectorTableView.cpp',
62 62 'src/Variable/VariableMenuHeaderWidget.cpp',
63 63 'src/Variable/RenameVariableDialog.cpp',
64 64 'src/Visualization/VisualizationGraphHelper.cpp',
65 65 'src/Visualization/VisualizationGraphRenderingDelegate.cpp',
66 66 'src/Visualization/VisualizationGraphWidget.cpp',
67 67 'src/Visualization/VisualizationTabWidget.cpp',
68 68 'src/Visualization/VisualizationWidget.cpp',
69 69 'src/Visualization/VisualizationZoneWidget.cpp',
70 70 'src/Visualization/qcustomplot.cpp',
71 71 'src/Visualization/QCustomPlotSynchronizer.cpp',
72 72 'src/Visualization/operations/FindVariableOperation.cpp',
73 73 'src/Visualization/operations/GenerateVariableMenuOperation.cpp',
74 74 'src/Visualization/operations/MenuBuilder.cpp',
75 75 'src/Visualization/operations/RemoveVariableOperation.cpp',
76 76 'src/Visualization/operations/RescaleAxeOperation.cpp',
77 77 'src/Visualization/VisualizationDragDropContainer.cpp',
78 78 'src/Visualization/VisualizationDragWidget.cpp',
79 79 'src/Visualization/AxisRenderingUtils.cpp',
80 80 'src/Visualization/PlottablesRenderingUtils.cpp',
81 81 'src/Visualization/MacScrollBarStyle.cpp',
82 82 'src/Visualization/VisualizationCursorItem.cpp',
83 83 'src/Visualization/ColorScaleEditor.cpp',
84 84 'src/Visualization/SqpColorScale.cpp',
85 'src/Visualization/QCPColorMapIterator.cpp'
85 'src/Visualization/QCPColorMapIterator.cpp',
86 'src/Visualization/VisualizationSelectionZoneItem.cpp'
86 87 ]
87 88
88 89 gui_inc = include_directories(['include'])
89 90
90 91 sciqlop_gui_lib = library('sciqlopgui',
91 92 gui_sources,
92 93 gui_moc_files,
93 94 include_directories : [gui_inc],
94 95 dependencies : [ qt5printsupport, qt5gui, qt5widgets, qt5svg, sciqlop_core],
95 96 install : true
96 97 )
97 98
98 99 sciqlop_gui = declare_dependency(link_with : sciqlop_gui_lib,
99 100 include_directories : gui_inc,
100 101 dependencies : [qt5printsupport, qt5gui, qt5widgets, qt5svg, sciqlop_core])
101 102
@@ -1,331 +1,333
1 1 #include "Visualization/VisualizationGraphRenderingDelegate.h"
2 2 #include "Visualization/AxisRenderingUtils.h"
3 3 #include "Visualization/ColorScaleEditor.h"
4 4 #include "Visualization/PlottablesRenderingUtils.h"
5 5 #include "Visualization/SqpColorScale.h"
6 6 #include "Visualization/VisualizationGraphWidget.h"
7 7 #include "Visualization/qcustomplot.h"
8 8
9 9 #include <Common/DateUtils.h>
10 10
11 11 #include <Data/IDataSeries.h>
12 12
13 13 #include <SqpApplication.h>
14 14
15 15 namespace {
16 16
17 17 /// Name of the axes layer in QCustomPlot
18 18 const auto AXES_LAYER = QStringLiteral("axes");
19 19
20 20 /// Icon used to show x-axis properties
21 21 const auto HIDE_AXIS_ICON_PATH = QStringLiteral(":/icones/down.png");
22 22
23 23 /// Name of the overlay layer in QCustomPlot
24 24 const auto OVERLAY_LAYER = QStringLiteral("overlay");
25 25
26 26 /// Pixmap used to show x-axis properties
27 27 const auto SHOW_AXIS_ICON_PATH = QStringLiteral(":/icones/up.png");
28 28
29 29 /// Tooltip format for graphs
30 30 const auto GRAPH_TOOLTIP_FORMAT = QStringLiteral("key: %1\nvalue: %2");
31 31
32 32 /// Tooltip format for colormaps
33 33 const auto COLORMAP_TOOLTIP_FORMAT = QStringLiteral("x: %1\ny: %2\nvalue: %3");
34 34
35 35 /// Offset used to shift the tooltip of the mouse
36 36 const auto TOOLTIP_OFFSET = QPoint{20, 20};
37 37
38 38 /// Tooltip display rectangle (the tooltip is hidden when the mouse leaves this rectangle)
39 39 const auto TOOLTIP_RECT = QRect{10, 10, 10, 10};
40 40
41 41 /// Timeout after which the tooltip is displayed
42 42 const auto TOOLTIP_TIMEOUT = 500;
43 43
44 44 void initPointTracerStyle(QCPItemTracer &tracer) noexcept
45 45 {
46 46 tracer.setInterpolating(false);
47 47 tracer.setStyle(QCPItemTracer::tsCircle);
48 48 tracer.setSize(3);
49 49 tracer.setPen(QPen(Qt::black));
50 50 tracer.setBrush(Qt::black);
51 tracer.setSelectable(false);
51 52 }
52 53
53 54 QPixmap pixmap(const QString &iconPath) noexcept
54 55 {
55 56 return QIcon{iconPath}.pixmap(QSize{16, 16});
56 57 }
57 58
58 59 void initClosePixmapStyle(QCPItemPixmap &pixmap) noexcept
59 60 {
60 61 // Icon
61 62 pixmap.setPixmap(
62 63 sqpApp->style()->standardIcon(QStyle::SP_TitleBarCloseButton).pixmap(QSize{16, 16}));
63 64
64 65 // Position
65 66 pixmap.topLeft->setType(QCPItemPosition::ptAxisRectRatio);
66 67 pixmap.topLeft->setCoords(1, 0);
67 68 pixmap.setClipToAxisRect(false);
68 69
69 70 // Can be selected
70 71 pixmap.setSelectable(true);
71 72 }
72 73
73 74 void initXAxisPixmapStyle(QCPItemPixmap &itemPixmap) noexcept
74 75 {
75 76 // Icon
76 77 itemPixmap.setPixmap(pixmap(HIDE_AXIS_ICON_PATH));
77 78
78 79 // Position
79 80 itemPixmap.topLeft->setType(QCPItemPosition::ptAxisRectRatio);
80 81 itemPixmap.topLeft->setCoords(0, 1);
81 82 itemPixmap.setClipToAxisRect(false);
82 83
83 84 // Can be selected
84 85 itemPixmap.setSelectable(true);
85 86 }
86 87
87 88 void initTitleTextStyle(QCPItemText &text) noexcept
88 89 {
89 90 // Font and background styles
90 91 text.setColor(Qt::gray);
91 92 text.setBrush(Qt::white);
92 93
93 94 // Position
94 95 text.setPositionAlignment(Qt::AlignTop | Qt::AlignLeft);
95 96 text.position->setType(QCPItemPosition::ptAxisRectRatio);
96 97 text.position->setCoords(0.5, 0);
98 text.setSelectable(false);
97 99 }
98 100
99 101 /**
100 102 * Returns the cell index (x or y) of a colormap according to the coordinate passed in parameter.
101 103 * This method handles the fact that a colormap axis can be logarithmic or linear.
102 104 * @param colormap the colormap for which to calculate the index
103 105 * @param coord the coord to convert to cell index
104 106 * @param xCoord calculates the x index if true, calculates y index if false
105 107 * @return the cell index
106 108 */
107 109 int colorMapCellIndex(const QCPColorMap &colormap, double coord, bool xCoord)
108 110 {
109 111 // Determines the axis of the colormap according to xCoord, and whether it is logarithmic or not
110 112 auto isLogarithmic = (xCoord ? colormap.keyAxis() : colormap.valueAxis())->scaleType()
111 113 == QCPAxis::stLogarithmic;
112 114
113 115 if (isLogarithmic) {
114 116 // For a logarithmic axis we can't use the conversion method of colormap, so we calculate
115 117 // the index manually based on the position of the coordinate on the axis
116 118
117 119 // Gets the axis range and the number of values between range bounds to calculate the step
118 120 // between each value of the range
119 121 auto range = xCoord ? colormap.data()->keyRange() : colormap.data()->valueRange();
120 122 auto nbValues = (xCoord ? colormap.data()->keySize() : colormap.data()->valueSize()) - 1;
121 123 auto valueStep
122 124 = (std::log10(range.upper) - std::log10(range.lower)) / static_cast<double>(nbValues);
123 125
124 126 // According to the coord position, calculates the closest index in the range
125 127 return std::round((std::log10(coord) - std::log10(range.lower)) / valueStep);
126 128 }
127 129 else {
128 130 // For a linear axis, we use the conversion method of colormap
129 131 int index;
130 132 if (xCoord) {
131 133 colormap.data()->coordToCell(coord, 0., &index, nullptr);
132 134 }
133 135 else {
134 136 colormap.data()->coordToCell(0., coord, nullptr, &index);
135 137 }
136 138
137 139 return index;
138 140 }
139 141 }
140 142
141 143 } // namespace
142 144
143 145 struct VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegatePrivate {
144 146 explicit VisualizationGraphRenderingDelegatePrivate(VisualizationGraphWidget &graphWidget)
145 147 : m_Plot{graphWidget.plot()},
146 148 m_PointTracer{new QCPItemTracer{&m_Plot}},
147 149 m_TracerTimer{},
148 150 m_ClosePixmap{new QCPItemPixmap{&m_Plot}},
149 151 m_TitleText{new QCPItemText{&m_Plot}},
150 152 m_XAxisPixmap{new QCPItemPixmap{&m_Plot}},
151 153 m_ShowXAxis{true},
152 154 m_XAxisLabel{},
153 155 m_ColorScale{SqpColorScale{m_Plot}}
154 156 {
155 157 initPointTracerStyle(*m_PointTracer);
156 158
157 159 m_TracerTimer.setInterval(TOOLTIP_TIMEOUT);
158 160 m_TracerTimer.setSingleShot(true);
159 161
160 162 // Inits "close button" in plot overlay
161 163 m_ClosePixmap->setLayer(OVERLAY_LAYER);
162 164 initClosePixmapStyle(*m_ClosePixmap);
163 165
164 166 // Connects pixmap selection to graph widget closing
165 167 QObject::connect(m_ClosePixmap, &QCPItemPixmap::selectionChanged,
166 168 [&graphWidget](bool selected) {
167 169 if (selected) {
168 170 graphWidget.close();
169 171 }
170 172 });
171 173
172 174 // Inits graph name in plot overlay
173 175 m_TitleText->setLayer(OVERLAY_LAYER);
174 176 m_TitleText->setText(graphWidget.name());
175 177 initTitleTextStyle(*m_TitleText);
176 178
177 179 // Inits "show x-axis button" in plot overlay
178 180 m_XAxisPixmap->setLayer(OVERLAY_LAYER);
179 181 initXAxisPixmapStyle(*m_XAxisPixmap);
180 182
181 183 // Connects pixmap selection to graph x-axis showing/hiding
182 184 QObject::connect(m_XAxisPixmap, &QCPItemPixmap::selectionChanged, [this]() {
183 185 if (m_XAxisPixmap->selected()) {
184 186 // Changes the selection state and refreshes the x-axis
185 187 m_ShowXAxis = !m_ShowXAxis;
186 188 updateXAxisState();
187 189 m_Plot.layer(AXES_LAYER)->replot();
188 190
189 191 // Deselects the x-axis pixmap and updates icon
190 192 m_XAxisPixmap->setSelected(false);
191 193 m_XAxisPixmap->setPixmap(
192 194 pixmap(m_ShowXAxis ? HIDE_AXIS_ICON_PATH : SHOW_AXIS_ICON_PATH));
193 195 m_Plot.layer(OVERLAY_LAYER)->replot();
194 196 }
195 197 });
196 198 }
197 199
198 200 /// Updates state of x-axis according to the current selection of x-axis pixmap
199 201 /// @remarks the method doesn't call plot refresh
200 202 void updateXAxisState() noexcept
201 203 {
202 204 m_Plot.xAxis->setTickLabels(m_ShowXAxis);
203 205 m_Plot.xAxis->setLabel(m_ShowXAxis ? m_XAxisLabel : QString{});
204 206 }
205 207
206 208 QCustomPlot &m_Plot;
207 209 QCPItemTracer *m_PointTracer;
208 210 QTimer m_TracerTimer;
209 211 QCPItemPixmap *m_ClosePixmap; /// Graph's close button
210 212 QCPItemText *m_TitleText; /// Graph's title
211 213 QCPItemPixmap *m_XAxisPixmap;
212 214 bool m_ShowXAxis; /// X-axis properties are shown or hidden
213 215 QString m_XAxisLabel;
214 216 SqpColorScale m_ColorScale; /// Color scale used for some types of graphs (as spectrograms)
215 217 };
216 218
217 219 VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegate(
218 220 VisualizationGraphWidget &graphWidget)
219 221 : impl{spimpl::make_unique_impl<VisualizationGraphRenderingDelegatePrivate>(graphWidget)}
220 222 {
221 223 }
222 224
223 225 void VisualizationGraphRenderingDelegate::onMouseDoubleClick(QMouseEvent *event) noexcept
224 226 {
225 227 // Opens color scale editor if color scale is double clicked
226 228 auto colorScale = static_cast<QCPColorScale *>(impl->m_Plot.layoutElementAt(event->pos()));
227 229 if (impl->m_ColorScale.m_Scale == colorScale) {
228 230 if (ColorScaleEditor{impl->m_ColorScale}.exec() == QDialog::Accepted) {
229 231 impl->m_Plot.replot();
230 232 }
231 233 }
232 234 }
233 235
234 236 void VisualizationGraphRenderingDelegate::onMouseMove(QMouseEvent *event) noexcept
235 237 {
236 238 // Cancels pending refresh
237 239 impl->m_TracerTimer.disconnect();
238 240
239 241 // Reinits tracers
240 242 impl->m_PointTracer->setGraph(nullptr);
241 243 impl->m_PointTracer->setVisible(false);
242 244 impl->m_Plot.replot();
243 245
244 246 QString tooltip{};
245 247
246 248 // Gets the graph under the mouse position
247 249 auto eventPos = event->pos();
248 250 if (auto graph = qobject_cast<QCPGraph *>(impl->m_Plot.plottableAt(eventPos))) {
249 251 auto mouseKey = graph->keyAxis()->pixelToCoord(eventPos.x());
250 252 auto graphData = graph->data();
251 253
252 254 // Gets the closest data point to the mouse
253 255 auto graphDataIt = graphData->findBegin(mouseKey);
254 256 if (graphDataIt != graphData->constEnd()) {
255 257 // Sets tooltip
256 258 auto key = formatValue(graphDataIt->key, *graph->keyAxis());
257 259 auto value = formatValue(graphDataIt->value, *graph->valueAxis());
258 260 tooltip = GRAPH_TOOLTIP_FORMAT.arg(key, value);
259 261
260 262 // Displays point tracer
261 263 impl->m_PointTracer->setGraph(graph);
262 264 impl->m_PointTracer->setGraphKey(graphDataIt->key);
263 265 impl->m_PointTracer->setLayer(
264 266 impl->m_Plot.layer("main")); // Tracer is set on top of the plot's main layer
265 267 impl->m_PointTracer->setVisible(true);
266 268 impl->m_Plot.replot();
267 269 }
268 270 }
269 271 else if (auto colorMap = qobject_cast<QCPColorMap *>(impl->m_Plot.plottableAt(eventPos))) {
270 272 // Gets x and y coords
271 273 auto x = colorMap->keyAxis()->pixelToCoord(eventPos.x());
272 274 auto y = colorMap->valueAxis()->pixelToCoord(eventPos.y());
273 275
274 276 // Calculates x and y cell indexes, and retrieves the underlying value
275 277 auto xCellIndex = colorMapCellIndex(*colorMap, x, true);
276 278 auto yCellIndex = colorMapCellIndex(*colorMap, y, false);
277 279 auto value = colorMap->data()->cell(xCellIndex, yCellIndex);
278 280
279 281 // Sets tooltips
280 282 tooltip = COLORMAP_TOOLTIP_FORMAT.arg(formatValue(x, *colorMap->keyAxis()),
281 283 formatValue(y, *colorMap->valueAxis()),
282 284 formatValue(value, *colorMap->colorScale()->axis()));
283 285 }
284 286
285 287 if (!tooltip.isEmpty()) {
286 288 // Starts timer to show tooltip after timeout
287 289 auto showTooltip = [tooltip, eventPos, this]() {
288 290 QToolTip::showText(impl->m_Plot.mapToGlobal(eventPos) + TOOLTIP_OFFSET, tooltip,
289 291 &impl->m_Plot, TOOLTIP_RECT);
290 292 };
291 293
292 294 QObject::connect(&impl->m_TracerTimer, &QTimer::timeout, showTooltip);
293 295 impl->m_TracerTimer.start();
294 296 }
295 297 }
296 298
297 299 void VisualizationGraphRenderingDelegate::onPlotUpdated() noexcept
298 300 {
299 301 // Updates color scale bounds
300 302 impl->m_ColorScale.updateDataRange();
301 303 impl->m_Plot.replot();
302 304 }
303 305
304 306 void VisualizationGraphRenderingDelegate::setAxesProperties(
305 307 std::shared_ptr<IDataSeries> dataSeries) noexcept
306 308 {
307 309 // Stores x-axis label to be able to retrieve it when x-axis pixmap is unselected
308 310 impl->m_XAxisLabel = dataSeries->xAxisUnit().m_Name;
309 311
310 312 auto axisHelper = IAxisHelperFactory::create(dataSeries);
311 313 axisHelper->setProperties(impl->m_Plot, impl->m_ColorScale);
312 314
313 315 // Updates x-axis state
314 316 impl->updateXAxisState();
315 317
316 318 impl->m_Plot.layer(AXES_LAYER)->replot();
317 319 }
318 320
319 321 void VisualizationGraphRenderingDelegate::setPlottablesProperties(
320 322 std::shared_ptr<IDataSeries> dataSeries, PlottablesMap &plottables) noexcept
321 323 {
322 324 auto plottablesHelper = IPlottablesHelperFactory::create(dataSeries);
323 325 plottablesHelper->setProperties(plottables);
324 326 }
325 327
326 328 void VisualizationGraphRenderingDelegate::showGraphOverlay(bool show) noexcept
327 329 {
328 330 auto overlay = impl->m_Plot.layer(OVERLAY_LAYER);
329 331 overlay->setVisible(show);
330 332 overlay->replot();
331 333 }
@@ -1,621 +1,721
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 #include "Visualization/VisualizationSelectionZoneItem.h"
7 8 #include "Visualization/VisualizationZoneWidget.h"
8 9 #include "ui_VisualizationGraphWidget.h"
9 10
10 11 #include <Common/MimeTypesDef.h>
11 12 #include <Data/ArrayData.h>
12 13 #include <Data/IDataSeries.h>
13 14 #include <Data/SpectrogramSeries.h>
14 15 #include <DragAndDrop/DragDropHelper.h>
15 16 #include <Settings/SqpSettingsDefs.h>
16 17 #include <SqpApplication.h>
17 18 #include <Time/TimeController.h>
18 19 #include <Variable/Variable.h>
19 20 #include <Variable/VariableController.h>
20 21
21 22 #include <unordered_map>
22 23
23 24 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
24 25
25 26 namespace {
26 27
27 28 /// Key pressed to enable zoom on horizontal axis
28 29 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::ControlModifier;
29 30
30 31 /// Key pressed to enable zoom on vertical axis
31 32 const auto VERTICAL_ZOOM_MODIFIER = Qt::ShiftModifier;
32 33
33 34 /// Speed of a step of a wheel event for a pan, in percentage of the axis range
34 35 const auto PAN_SPEED = 5;
35 36
36 37 /// Key pressed to enable a calibration pan
37 38 const auto VERTICAL_PAN_MODIFIER = Qt::AltModifier;
38 39
39 40 /// Minimum size for the zoom box, in percentage of the axis range
40 41 const auto ZOOM_BOX_MIN_SIZE = 0.8;
41 42
42 43 /// Format of the dates appearing in the label of a cursor
43 44 const auto CURSOR_LABELS_DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd\nhh:mm:ss:zzz");
44 45
45 46 } // namespace
46 47
47 48 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate {
48 49
49 50 explicit VisualizationGraphWidgetPrivate(const QString &name)
50 51 : m_Name{name},
51 52 m_DoAcquisition{true},
52 53 m_IsCalibration{false},
53 54 m_RenderingDelegate{nullptr}
54 55 {
55 56 }
56 57
57 58 void updateData(PlottablesMap &plottables, std::shared_ptr<IDataSeries> dataSeries,
58 59 const SqpRange &range)
59 60 {
60 61 VisualizationGraphHelper::updateData(plottables, dataSeries, range);
61 62
62 63 // Prevents that data has changed to update rendering
63 64 m_RenderingDelegate->onPlotUpdated();
64 65 }
65 66
66 67 QString m_Name;
67 68 // 1 variable -> n qcpplot
68 69 std::map<std::shared_ptr<Variable>, PlottablesMap> m_VariableToPlotMultiMap;
69 70 bool m_DoAcquisition;
70 71 bool m_IsCalibration;
71 72 /// Delegate used to attach rendering features to the plot
72 73 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
73 74
74 QCPItemRect *m_DrawingRect = nullptr;
75 QCPItemRect *m_DrawingZoomRect = nullptr;
76
75 77 std::unique_ptr<VisualizationCursorItem> m_HorizontalCursor = nullptr;
76 78 std::unique_ptr<VisualizationCursorItem> m_VerticalCursor = nullptr;
77 79
78 void configureDrawingRect()
79 {
80 if (m_DrawingRect) {
81 QPen p;
82 p.setWidth(2);
83 m_DrawingRect->setPen(p);
84 }
85 }
80 VisualizationSelectionZoneItem *m_DrawingZone = nullptr;
81 VisualizationSelectionZoneItem *m_HoveredZone = nullptr;
82 QVector<VisualizationSelectionZoneItem *> m_SelectionZones;
86 83
87 84 void startDrawingRect(const QPoint &pos, QCustomPlot &plot)
88 85 {
89 86 removeDrawingRect(plot);
90 87
91 88 auto axisPos = posToAxisPos(pos, plot);
92 89
93 m_DrawingRect = new QCPItemRect{&plot};
94 configureDrawingRect();
90 m_DrawingZoomRect = new QCPItemRect{&plot};
91 QPen p;
92 p.setWidth(2);
93 m_DrawingZoomRect->setPen(p);
95 94
96 m_DrawingRect->topLeft->setCoords(axisPos);
97 m_DrawingRect->bottomRight->setCoords(axisPos);
95 m_DrawingZoomRect->topLeft->setCoords(axisPos);
96 m_DrawingZoomRect->bottomRight->setCoords(axisPos);
98 97 }
99 98
100 99 void removeDrawingRect(QCustomPlot &plot)
101 100 {
102 if (m_DrawingRect) {
103 plot.removeItem(m_DrawingRect); // the item is deleted by QCustomPlot
104 m_DrawingRect = nullptr;
101 if (m_DrawingZoomRect) {
102 plot.removeItem(m_DrawingZoomRect); // the item is deleted by QCustomPlot
103 m_DrawingZoomRect = nullptr;
105 104 plot.replot(QCustomPlot::rpQueuedReplot);
106 105 }
107 106 }
108 107
108 void startDrawingZone(const QPoint &pos, QCustomPlot &plot)
109 {
110 endDrawingZone(plot);
111
112 auto axisPos = posToAxisPos(pos, plot);
113
114 m_DrawingZone = new VisualizationSelectionZoneItem{&plot};
115 m_DrawingZone->setRange(axisPos.x(), axisPos.x());
116 m_DrawingZone->setEditionEnabled(false);
117 }
118
119 void endDrawingZone(QCustomPlot &plot)
120 {
121 if (m_DrawingZone) {
122 auto drawingZoneRange = m_DrawingZone->range();
123 if (qAbs(drawingZoneRange.m_TEnd - drawingZoneRange.m_TStart) > 0) {
124 m_DrawingZone->setEditionEnabled(true);
125 m_SelectionZones.append(m_DrawingZone);
126 }
127 else {
128 plot.removeItem(m_DrawingZone); // the item is deleted by QCustomPlot
129 }
130
131 plot.replot(QCustomPlot::rpQueuedReplot);
132 m_DrawingZone = nullptr;
133 }
134 }
135
136 void setSelectionZonesEditionEnabled(bool value)
137 {
138 for (auto s : m_SelectionZones) {
139 s->setEditionEnabled(value);
140 }
141 }
142
109 143 QPointF posToAxisPos(const QPoint &pos, QCustomPlot &plot) const
110 144 {
111 145 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
112 146 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
113 147 return QPointF{axisX->pixelToCoord(pos.x()), axisY->pixelToCoord(pos.y())};
114 148 }
115 149
116 150 bool pointIsInAxisRect(const QPointF &axisPoint, QCustomPlot &plot) const
117 151 {
118 152 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
119 153 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
120
121 154 return axisX->range().contains(axisPoint.x()) && axisY->range().contains(axisPoint.y());
122 155 }
123 156 };
124 157
125 158 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
126 159 : VisualizationDragWidget{parent},
127 160 ui{new Ui::VisualizationGraphWidget},
128 161 impl{spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name)}
129 162 {
130 163 ui->setupUi(this);
131 164
132 165 // 'Close' options : widget is deleted when closed
133 166 setAttribute(Qt::WA_DeleteOnClose);
134 167
135 168 // Set qcpplot properties :
136 // - Drag (on x-axis) and zoom are enabled
169 // - zoom is enabled
137 170 // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation
138 171 ui->widget->setInteractions(QCP::iRangeZoom | QCP::iSelectItems);
139 172
140 173 // The delegate must be initialized after the ui as it uses the plot
141 174 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
142 175
143 176 // Init the cursors
144 177 impl->m_HorizontalCursor = std::make_unique<VisualizationCursorItem>(&plot());
145 178 impl->m_HorizontalCursor->setOrientation(Qt::Horizontal);
146 179 impl->m_VerticalCursor = std::make_unique<VisualizationCursorItem>(&plot());
147 180 impl->m_VerticalCursor->setOrientation(Qt::Vertical);
148 181
149 182 connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress);
150 183 connect(ui->widget, &QCustomPlot::mouseRelease, this,
151 184 &VisualizationGraphWidget::onMouseRelease);
152 185 connect(ui->widget, &QCustomPlot::mouseMove, this, &VisualizationGraphWidget::onMouseMove);
153 186 connect(ui->widget, &QCustomPlot::mouseWheel, this, &VisualizationGraphWidget::onMouseWheel);
154 187 connect(ui->widget, &QCustomPlot::mouseDoubleClick, this,
155 188 &VisualizationGraphWidget::onMouseDoubleClick);
156 189 connect(ui->widget->xAxis, static_cast<void (QCPAxis::*)(const QCPRange &, const QCPRange &)>(
157 190 &QCPAxis::rangeChanged),
158 191 this, &VisualizationGraphWidget::onRangeChanged, Qt::DirectConnection);
159 192
160 193 // Activates menu when right clicking on the graph
161 194 ui->widget->setContextMenuPolicy(Qt::CustomContextMenu);
162 195 connect(ui->widget, &QCustomPlot::customContextMenuRequested, this,
163 196 &VisualizationGraphWidget::onGraphMenuRequested);
164 197
165 198 connect(this, &VisualizationGraphWidget::requestDataLoading, &sqpApp->variableController(),
166 199 &VariableController::onRequestDataLoading);
167 200
168 201 connect(&sqpApp->variableController(), &VariableController::updateVarDisplaying, this,
169 202 &VisualizationGraphWidget::onUpdateVarDisplaying);
170 203
171 204 #ifdef Q_OS_MAC
172 205 plot().setPlottingHint(QCP::phFastPolylines, true);
173 206 #endif
174 207 }
175 208
176 209
177 210 VisualizationGraphWidget::~VisualizationGraphWidget()
178 211 {
179 212 delete ui;
180 213 }
181 214
182 215 VisualizationZoneWidget *VisualizationGraphWidget::parentZoneWidget() const noexcept
183 216 {
184 217 auto parent = parentWidget();
185 218 while (parent != nullptr && !qobject_cast<VisualizationZoneWidget *>(parent)) {
186 219 parent = parent->parentWidget();
187 220 }
188 221
189 222 return qobject_cast<VisualizationZoneWidget *>(parent);
190 223 }
191 224
192 225 void VisualizationGraphWidget::enableAcquisition(bool enable)
193 226 {
194 227 impl->m_DoAcquisition = enable;
195 228 }
196 229
197 230 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable> variable, SqpRange range)
198 231 {
199 232 // Uses delegate to create the qcpplot components according to the variable
200 233 auto createdPlottables = VisualizationGraphHelper::create(variable, *ui->widget);
201 234
202 235 if (auto dataSeries = variable->dataSeries()) {
203 236 // Set axes properties according to the units of the data series
204 237 impl->m_RenderingDelegate->setAxesProperties(dataSeries);
205 238
206 239 // Sets rendering properties for the new plottables
207 240 // Warning: this method must be called after setAxesProperties(), as it can access to some
208 241 // axes properties that have to be initialized
209 242 impl->m_RenderingDelegate->setPlottablesProperties(dataSeries, createdPlottables);
210 243 }
211 244
212 245 impl->m_VariableToPlotMultiMap.insert({variable, std::move(createdPlottables)});
213 246
214 247 connect(variable.get(), SIGNAL(updated()), this, SLOT(onDataCacheVariableUpdated()));
215 248
216 249 this->enableAcquisition(false);
217 250 this->setGraphRange(range);
218 251 this->enableAcquisition(true);
219 252
220 253 emit requestDataLoading(QVector<std::shared_ptr<Variable> >() << variable, range, false);
221 254
222 255 emit variableAdded(variable);
223 256 }
224 257
225 258 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable> variable) noexcept
226 259 {
227 260 // Each component associated to the variable :
228 261 // - is removed from qcpplot (which deletes it)
229 262 // - is no longer referenced in the map
230 263 auto variableIt = impl->m_VariableToPlotMultiMap.find(variable);
231 264 if (variableIt != impl->m_VariableToPlotMultiMap.cend()) {
232 265 emit variableAboutToBeRemoved(variable);
233 266
234 267 auto &plottablesMap = variableIt->second;
235 268
236 269 for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend();
237 270 plottableIt != plottableEnd;) {
238 271 ui->widget->removePlottable(plottableIt->second);
239 272 plottableIt = plottablesMap.erase(plottableIt);
240 273 }
241 274
242 275 impl->m_VariableToPlotMultiMap.erase(variableIt);
243 276 }
244 277
245 278 // Updates graph
246 279 ui->widget->replot();
247 280 }
248 281
249 282 QList<std::shared_ptr<Variable> > VisualizationGraphWidget::variables() const
250 283 {
251 284 auto variables = QList<std::shared_ptr<Variable> >{};
252 285 for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap);
253 286 it != std::cend(impl->m_VariableToPlotMultiMap); ++it) {
254 287 variables << it->first;
255 288 }
256 289
257 290 return variables;
258 291 }
259 292
260 293 void VisualizationGraphWidget::setYRange(std::shared_ptr<Variable> variable)
261 294 {
262 295 if (!variable) {
263 296 qCCritical(LOG_VisualizationGraphWidget()) << "Can't set y-axis range: variable is null";
264 297 return;
265 298 }
266 299
267 300 VisualizationGraphHelper::setYAxisRange(variable, *ui->widget);
268 301 }
269 302
270 303 SqpRange VisualizationGraphWidget::graphRange() const noexcept
271 304 {
272 305 auto graphRange = ui->widget->xAxis->range();
273 306 return SqpRange{graphRange.lower, graphRange.upper};
274 307 }
275 308
276 309 void VisualizationGraphWidget::setGraphRange(const SqpRange &range)
277 310 {
278 311 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange START");
279 312 ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd);
280 313 ui->widget->replot();
281 314 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange END");
282 315 }
283 316
284 317 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor)
285 318 {
286 319 if (visitor) {
287 320 visitor->visit(this);
288 321 }
289 322 else {
290 323 qCCritical(LOG_VisualizationGraphWidget())
291 324 << tr("Can't visit widget : the visitor is null");
292 325 }
293 326 }
294 327
295 328 bool VisualizationGraphWidget::canDrop(const Variable &variable) const
296 329 {
297 330 auto isSpectrogram = [](const auto &variable) {
298 331 return std::dynamic_pointer_cast<SpectrogramSeries>(variable.dataSeries()) != nullptr;
299 332 };
300 333
301 334 // - A spectrogram series can't be dropped on graph with existing plottables
302 335 // - No data series can be dropped on graph with existing spectrogram series
303 336 return isSpectrogram(variable)
304 337 ? impl->m_VariableToPlotMultiMap.empty()
305 338 : std::none_of(
306 339 impl->m_VariableToPlotMultiMap.cbegin(), impl->m_VariableToPlotMultiMap.cend(),
307 340 [isSpectrogram](const auto &entry) { return isSpectrogram(*entry.first); });
308 341 }
309 342
310 343 bool VisualizationGraphWidget::contains(const Variable &variable) const
311 344 {
312 345 // Finds the variable among the keys of the map
313 346 auto variablePtr = &variable;
314 347 auto findVariable
315 348 = [variablePtr](const auto &entry) { return variablePtr == entry.first.get(); };
316 349
317 350 auto end = impl->m_VariableToPlotMultiMap.cend();
318 351 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
319 352 return it != end;
320 353 }
321 354
322 355 QString VisualizationGraphWidget::name() const
323 356 {
324 357 return impl->m_Name;
325 358 }
326 359
327 360 QMimeData *VisualizationGraphWidget::mimeData() const
328 361 {
329 362 auto mimeData = new QMimeData;
330 363 mimeData->setData(MIME_TYPE_GRAPH, QByteArray{});
331 364
332 365 auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange());
333 366 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
334 367
335 368 return mimeData;
336 369 }
337 370
338 371 bool VisualizationGraphWidget::isDragAllowed() const
339 372 {
340 373 return true;
341 374 }
342 375
343 376 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
344 377 {
345 378 if (highlighted) {
346 379 plot().setBackground(QBrush(QColor("#BBD5EE")));
347 380 }
348 381 else {
349 382 plot().setBackground(QBrush(Qt::white));
350 383 }
351 384
352 385 plot().update();
353 386 }
354 387
355 388 void VisualizationGraphWidget::addVerticalCursor(double time)
356 389 {
357 390 impl->m_VerticalCursor->setPosition(time);
358 391 impl->m_VerticalCursor->setVisible(true);
359 392
360 393 auto text
361 394 = DateUtils::dateTime(time).toString(CURSOR_LABELS_DATETIME_FORMAT).replace(' ', '\n');
362 395 impl->m_VerticalCursor->setLabelText(text);
363 396 }
364 397
365 398 void VisualizationGraphWidget::addVerticalCursorAtViewportPosition(double position)
366 399 {
367 400 impl->m_VerticalCursor->setAbsolutePosition(position);
368 401 impl->m_VerticalCursor->setVisible(true);
369 402
370 403 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
371 404 auto text
372 405 = DateUtils::dateTime(axis->pixelToCoord(position)).toString(CURSOR_LABELS_DATETIME_FORMAT);
373 406 impl->m_VerticalCursor->setLabelText(text);
374 407 }
375 408
376 409 void VisualizationGraphWidget::removeVerticalCursor()
377 410 {
378 411 impl->m_VerticalCursor->setVisible(false);
379 412 plot().replot(QCustomPlot::rpQueuedReplot);
380 413 }
381 414
382 415 void VisualizationGraphWidget::addHorizontalCursor(double value)
383 416 {
384 417 impl->m_HorizontalCursor->setPosition(value);
385 418 impl->m_HorizontalCursor->setVisible(true);
386 419 impl->m_HorizontalCursor->setLabelText(QString::number(value));
387 420 }
388 421
389 422 void VisualizationGraphWidget::addHorizontalCursorAtViewportPosition(double position)
390 423 {
391 424 impl->m_HorizontalCursor->setAbsolutePosition(position);
392 425 impl->m_HorizontalCursor->setVisible(true);
393 426
394 427 auto axis = plot().axisRect()->axis(QCPAxis::atLeft);
395 428 impl->m_HorizontalCursor->setLabelText(QString::number(axis->pixelToCoord(position)));
396 429 }
397 430
398 431 void VisualizationGraphWidget::removeHorizontalCursor()
399 432 {
400 433 impl->m_HorizontalCursor->setVisible(false);
401 434 plot().replot(QCustomPlot::rpQueuedReplot);
402 435 }
403 436
404 437 void VisualizationGraphWidget::closeEvent(QCloseEvent *event)
405 438 {
406 439 Q_UNUSED(event);
407 440
408 441 // Prevents that all variables will be removed from graph when it will be closed
409 442 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
410 443 emit variableAboutToBeRemoved(variableEntry.first);
411 444 }
412 445 }
413 446
414 447 void VisualizationGraphWidget::enterEvent(QEvent *event)
415 448 {
416 449 Q_UNUSED(event);
417 450 impl->m_RenderingDelegate->showGraphOverlay(true);
418 451 }
419 452
420 453 void VisualizationGraphWidget::leaveEvent(QEvent *event)
421 454 {
422 455 Q_UNUSED(event);
423 456 impl->m_RenderingDelegate->showGraphOverlay(false);
424 457
425 458 if (auto parentZone = parentZoneWidget()) {
426 459 parentZone->notifyMouseLeaveGraph(this);
427 460 }
428 461 else {
429 462 qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget";
430 463 }
464
465 if (impl->m_HoveredZone) {
466 impl->m_HoveredZone->setHovered(false);
467 impl->m_HoveredZone = nullptr;
468 }
431 469 }
432 470
433 471 QCustomPlot &VisualizationGraphWidget::plot() noexcept
434 472 {
435 473 return *ui->widget;
436 474 }
437 475
438 476 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept
439 477 {
440 478 QMenu graphMenu{};
441 479
442 480 // Iterates on variables (unique keys)
443 481 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
444 482 end = impl->m_VariableToPlotMultiMap.cend();
445 483 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
446 484 // 'Remove variable' action
447 485 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
448 486 [ this, var = it->first ]() { removeVariable(var); });
449 487 }
450 488
451 489 if (!graphMenu.isEmpty()) {
452 490 graphMenu.exec(QCursor::pos());
453 491 }
454 492 }
455 493
456 494 void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange &t2)
457 495 {
458 496 qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: VisualizationGraphWidget::onRangeChanged")
459 497 << QThread::currentThread()->objectName() << "DoAcqui"
460 498 << impl->m_DoAcquisition;
461 499
462 500 auto graphRange = SqpRange{t1.lower, t1.upper};
463 501 auto oldGraphRange = SqpRange{t2.lower, t2.upper};
464 502
465 503 if (impl->m_DoAcquisition) {
466 504 QVector<std::shared_ptr<Variable> > variableUnderGraphVector;
467 505
468 506 for (auto it = impl->m_VariableToPlotMultiMap.begin(),
469 507 end = impl->m_VariableToPlotMultiMap.end();
470 508 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
471 509 variableUnderGraphVector.push_back(it->first);
472 510 }
473 511 emit requestDataLoading(std::move(variableUnderGraphVector), graphRange,
474 512 !impl->m_IsCalibration);
475 513
476 514 if (!impl->m_IsCalibration) {
477 515 qCDebug(LOG_VisualizationGraphWidget())
478 516 << tr("TORM: VisualizationGraphWidget::Synchronize notify !!")
479 517 << QThread::currentThread()->objectName() << graphRange << oldGraphRange;
480 518 emit synchronize(graphRange, oldGraphRange);
481 519 }
482 520 }
483 521
484 522 auto pos = mapFromGlobal(QCursor::pos());
485 523 auto axisPos = impl->posToAxisPos(pos, plot());
486 524 if (auto parentZone = parentZoneWidget()) {
487 525 if (impl->pointIsInAxisRect(axisPos, plot())) {
488 526 parentZone->notifyMouseMoveInGraph(pos, axisPos, this);
489 527 }
490 528 else {
491 529 parentZone->notifyMouseLeaveGraph(this);
492 530 }
493 531 }
494 532 else {
495 533 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
496 534 }
497 535 }
498 536
499 537 void VisualizationGraphWidget::onMouseDoubleClick(QMouseEvent *event) noexcept
500 538 {
501 539 impl->m_RenderingDelegate->onMouseDoubleClick(event);
502 540 }
503 541
504 542 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
505 543 {
506 544 // Handles plot rendering when mouse is moving
507 545 impl->m_RenderingDelegate->onMouseMove(event);
508 546
509 547 auto axisPos = impl->posToAxisPos(event->pos(), plot());
510 548
511 if (impl->m_DrawingRect) {
512 impl->m_DrawingRect->bottomRight->setCoords(axisPos);
549 // Zoom box and zone drawing
550 if (impl->m_DrawingZoomRect) {
551 impl->m_DrawingZoomRect->bottomRight->setCoords(axisPos);
552 }
553 else if (impl->m_DrawingZone) {
554 impl->m_DrawingZone->setEnd(axisPos.x());
513 555 }
514 556
557 // Cursor
515 558 if (auto parentZone = parentZoneWidget()) {
516 559 if (impl->pointIsInAxisRect(axisPos, plot())) {
517 560 parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this);
518 561 }
519 562 else {
520 563 parentZone->notifyMouseLeaveGraph(this);
521 564 }
522 565 }
523 566 else {
524 567 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
525 568 }
526 569
570 // Search for the selection zone under the mouse
571 VisualizationSelectionZoneItem *selectionZoneItemUnderCursor = nullptr;
572 auto minDistanceToZone = -1;
573 for (auto zone : impl->m_SelectionZones) {
574 auto distanceToZone = zone->selectTest(event->pos(), true);
575 if ((minDistanceToZone < 0 || distanceToZone <= minDistanceToZone) && distanceToZone >= 0
576 && distanceToZone < plot().selectionTolerance()) {
577 selectionZoneItemUnderCursor = zone;
578 }
579 }
580
581 if (selectionZoneItemUnderCursor && !impl->m_DrawingZone
582 && sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones) {
583
584 // Sets the appropriate cursor shape
585 auto cursorShape = selectionZoneItemUnderCursor->curshorShapeForPosition(event->pos());
586 setCursor(cursorShape);
587
588 // Manages the hovered zone
589 if (selectionZoneItemUnderCursor != impl->m_HoveredZone) {
590 if (impl->m_HoveredZone) {
591 impl->m_HoveredZone->setHovered(false);
592 }
593 selectionZoneItemUnderCursor->setHovered(true);
594 impl->m_HoveredZone = selectionZoneItemUnderCursor;
595 plot().replot(QCustomPlot::rpQueuedReplot);
596 }
597 }
598 else {
599 // There is no zone under the mouse or the interaction mode is not "selection zones"
600 if (impl->m_HoveredZone) {
601 impl->m_HoveredZone->setHovered(false);
602 impl->m_HoveredZone = nullptr;
603 }
604
605 setCursor(Qt::ArrowCursor);
606 }
607
527 608 VisualizationDragWidget::mouseMoveEvent(event);
528 609 }
529 610
530 611 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
531 612 {
532 613 auto value = event->angleDelta().x() + event->angleDelta().y();
533 614 if (value != 0) {
534 615
535 616 auto direction = value > 0 ? 1.0 : -1.0;
536 617 auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER);
537 618 auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER);
538 619 impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER);
539 620
540 621 auto zoomOrientations = QFlags<Qt::Orientation>{};
541 622 zoomOrientations.setFlag(Qt::Horizontal, isZoomX);
542 623 zoomOrientations.setFlag(Qt::Vertical, isZoomY);
543 624
544 625 ui->widget->axisRect()->setRangeZoom(zoomOrientations);
545 626
546 627 if (!isZoomX && !isZoomY) {
547 628 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
548 629 auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0));
549 630
550 631 axis->setRange(axis->range() + diff);
551 632
552 633 if (plot().noAntialiasingOnDrag()) {
553 634 plot().setNotAntialiasedElements(QCP::aeAll);
554 635 }
555 636
556 637 plot().replot(QCustomPlot::rpQueuedReplot);
557 638 }
558 639 }
559 640 }
560 641
561 642 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
562 643 {
563 644 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox) {
645 // Starts a zoom box
564 646 impl->startDrawingRect(event->pos(), plot());
565 647 }
648 else if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
649 && impl->m_DrawingZone == nullptr) {
650 // Starts a new selection zone
651 auto itemAtPos = plot().itemAt(event->pos(), true);
652 if (!itemAtPos) {
653 impl->startDrawingZone(event->pos(), plot());
654 }
655 }
656
657 // Allows mouse panning only in default mode
658 plot().setInteraction(QCP::iRangeDrag, sqpApp->plotsInteractionMode()
659 == SqpApplication::PlotsInteractionMode::None);
660
661 // Allows zone edition only in selection zone mode
662 impl->setSelectionZonesEditionEnabled(sqpApp->plotsInteractionMode()
663 == SqpApplication::PlotsInteractionMode::SelectionZones);
566 664
567 665 VisualizationDragWidget::mousePressEvent(event);
568 666 }
569 667
570 668 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
571 669 {
572 if (impl->m_DrawingRect) {
670 if (impl->m_DrawingZoomRect) {
573 671
574 672 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
575 673 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
576 674
577 auto newAxisXRange = QCPRange{impl->m_DrawingRect->topLeft->coords().x(),
578 impl->m_DrawingRect->bottomRight->coords().x()};
675 auto newAxisXRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().x(),
676 impl->m_DrawingZoomRect->bottomRight->coords().x()};
579 677
580 auto newAxisYRange = QCPRange{impl->m_DrawingRect->topLeft->coords().y(),
581 impl->m_DrawingRect->bottomRight->coords().y()};
678 auto newAxisYRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().y(),
679 impl->m_DrawingZoomRect->bottomRight->coords().y()};
582 680
583 681 impl->removeDrawingRect(plot());
584 682
585 683 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
586 684 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) {
587 685 axisX->setRange(newAxisXRange);
588 686 axisY->setRange(newAxisYRange);
589 687
590 688 plot().replot(QCustomPlot::rpQueuedReplot);
591 689 }
592 690 }
593 691
692 impl->endDrawingZone(plot());
693
594 694 impl->m_IsCalibration = false;
595 695 }
596 696
597 697 void VisualizationGraphWidget::onDataCacheVariableUpdated()
598 698 {
599 699 auto graphRange = ui->widget->xAxis->range();
600 700 auto dateTime = SqpRange{graphRange.lower, graphRange.upper};
601 701
602 702 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
603 703 auto variable = variableEntry.first;
604 704 qCDebug(LOG_VisualizationGraphWidget())
605 705 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
606 706 qCDebug(LOG_VisualizationGraphWidget())
607 707 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
608 708 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range())) {
609 709 impl->updateData(variableEntry.second, variable->dataSeries(), variable->range());
610 710 }
611 711 }
612 712 }
613 713
614 714 void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr<Variable> variable,
615 715 const SqpRange &range)
616 716 {
617 717 auto it = impl->m_VariableToPlotMultiMap.find(variable);
618 718 if (it != impl->m_VariableToPlotMultiMap.end()) {
619 719 impl->updateData(it->second, variable->dataSeries(), range);
620 720 }
621 721 }
General Comments 3
Under Review
author

Auto status change to "Under Review"

Approved

Status change > Approved

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