##// END OF EJS Templates
Adds button on plot overlay to show/hide x-axis properties
Alexandre Leroux -
r729:94000392d1e8
parent child
Show More
1 NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
@@ -1,27 +1,31
1 1 #ifndef SCIQLOP_VISUALIZATIONGRAPHRENDERINGDELEGATE_H
2 2 #define SCIQLOP_VISUALIZATIONGRAPHRENDERINGDELEGATE_H
3 3
4 4 #include <Common/spimpl.h>
5 5
6 6 class QCustomPlot;
7 7 class QMouseEvent;
8 class Unit;
8 9 class VisualizationGraphWidget;
9 10
10 11 class VisualizationGraphRenderingDelegate {
11 12 public:
12 13 /// Ctor
13 14 /// @param graphWidget the graph widget to which the delegate is associated
14 15 /// @remarks the graph widget must exist throughout the life cycle of the delegate
15 16 explicit VisualizationGraphRenderingDelegate(VisualizationGraphWidget &graphWidget);
16 17
17 18 void onMouseMove(QMouseEvent *event) noexcept;
18 19
20 /// Sets properties of the plot's axes
21 void setAxesProperties(const Unit &xAxisUnit, const Unit &valuesUnit) noexcept;
22
19 23 /// Shows or hides graph overlay (name, close button, etc.)
20 24 void showGraphOverlay(bool show) noexcept;
21 25
22 26 private:
23 27 class VisualizationGraphRenderingDelegatePrivate;
24 28 spimpl::unique_impl_ptr<VisualizationGraphRenderingDelegatePrivate> impl;
25 29 };
26 30
27 31 #endif // SCIQLOP_VISUALIZATIONGRAPHRENDERINGDELEGATE_H
@@ -1,14 +1,16
1 1 <RCC>
2 2 <qresource prefix="/">
3 3 <file>icones/dataSourceComponent.png</file>
4 4 <file>icones/dataSourceNode.png</file>
5 5 <file>icones/dataSourceProduct.png</file>
6 6 <file>icones/dataSourceRoot.png</file>
7 7 <file>icones/delete.png</file>
8 <file>icones/down.png</file>
8 9 <file>icones/openInspector.png</file>
9 10 <file>icones/next.png</file>
10 11 <file>icones/plot.png</file>
11 12 <file>icones/previous.png</file>
12 13 <file>icones/unplot.png</file>
14 <file>icones/up.png</file>
13 15 </qresource>
14 16 </RCC>
@@ -1,247 +1,204
1 1 #include "Visualization/VisualizationGraphHelper.h"
2 2 #include "Visualization/qcustomplot.h"
3 3
4 4 #include <Common/ColorUtils.h>
5 5
6 6 #include <Data/ScalarSeries.h>
7 7 #include <Data/VectorSeries.h>
8 8
9 9 #include <Variable/Variable.h>
10 10
11 11 Q_LOGGING_CATEGORY(LOG_VisualizationGraphHelper, "VisualizationGraphHelper")
12 12
13 13 namespace {
14 14
15 15 class SqpDataContainer : public QCPGraphDataContainer {
16 16 public:
17 17 void appendGraphData(const QCPGraphData &data) { mData.append(data); }
18 18 };
19 19
20
21 /// Format for datetimes on a axis
22 const auto DATETIME_TICKER_FORMAT = QStringLiteral("yyyy/MM/dd \nhh:mm:ss");
23
24 /// Generates the appropriate ticker for an axis, depending on whether the axis displays time or
25 /// non-time data
26 QSharedPointer<QCPAxisTicker> axisTicker(bool isTimeAxis)
27 {
28 if (isTimeAxis) {
29 auto dateTicker = QSharedPointer<QCPAxisTickerDateTime>::create();
30 dateTicker->setDateTimeFormat(DATETIME_TICKER_FORMAT);
31 dateTicker->setDateTimeSpec(Qt::UTC);
32
33 return dateTicker;
34 }
35 else {
36 // default ticker
37 return QSharedPointer<QCPAxisTicker>::create();
38 }
39 }
40
41 /// Sets axes properties according to the properties of a data series. Not thread safe
42 template <int Dim>
43 void setAxesProperties(const DataSeries<Dim> &dataSeries, QCustomPlot &plot) noexcept
44 {
45 /// @todo : for the moment, no control is performed on the axes: the units and the tickers
46 /// are fixed for the default x-axis and y-axis of the plot, and according to the new graph
47 auto setAxisProperties = [](auto axis, const auto &unit) {
48 // label (unit name)
49 axis->setLabel(unit.m_Name);
50
51 // ticker (depending on the type of unit)
52 axis->setTicker(axisTicker(unit.m_TimeUnit));
53 };
54 setAxisProperties(plot.xAxis, dataSeries.xAxisUnit());
55 setAxisProperties(plot.yAxis, dataSeries.valuesUnit());
56 }
57
58 20 /**
59 21 * Struct used to create plottables, depending on the type of the data series from which to create
60 22 * them
61 23 * @tparam T the data series' type
62 24 * @remarks Default implementation can't create plottables
63 25 */
64 26 template <typename T, typename Enabled = void>
65 27 struct PlottablesCreator {
66 28 static PlottablesMap createPlottables(T &, QCustomPlot &)
67 29 {
68 30 qCCritical(LOG_DataSeries())
69 31 << QObject::tr("Can't create plottables: unmanaged data series type");
70 32 return {};
71 33 }
72 34 };
73 35
74 36 /**
75 37 * Specialization of PlottablesCreator for scalars and vectors
76 38 * @sa ScalarSeries
77 39 * @sa VectorSeries
78 40 */
79 41 template <typename T>
80 42 struct PlottablesCreator<T,
81 43 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
82 44 or std::is_base_of<VectorSeries, T>::value> > {
83 45 static PlottablesMap createPlottables(T &dataSeries, QCustomPlot &plot)
84 46 {
85 47 PlottablesMap result{};
86 48
87 49 // Gets the number of components of the data series
88 50 dataSeries.lockRead();
89 51 auto componentCount = dataSeries.valuesData()->componentCount();
90 52 dataSeries.unlock();
91 53
92 54 auto colors = ColorUtils::colors(Qt::blue, Qt::red, componentCount);
93 55
94 56 // For each component of the data series, creates a QCPGraph to add to the plot
95 57 for (auto i = 0; i < componentCount; ++i) {
96 58 auto graph = plot.addGraph();
97 59 graph->setPen(QPen{colors.at(i)});
98 60
99 61 result.insert({i, graph});
100 62 }
101 63
102 // Axes properties
103 dataSeries.lockRead();
104 setAxesProperties(dataSeries, plot);
105 dataSeries.unlock();
106
107 64 plot.replot();
108 65
109 66 return result;
110 67 }
111 68 };
112 69
113 70 /**
114 71 * Struct used to update plottables, depending on the type of the data series from which to update
115 72 * them
116 73 * @tparam T the data series' type
117 74 * @remarks Default implementation can't update plottables
118 75 */
119 76 template <typename T, typename Enabled = void>
120 77 struct PlottablesUpdater {
121 78 static void updatePlottables(T &, PlottablesMap &, const SqpRange &, bool)
122 79 {
123 80 qCCritical(LOG_DataSeries())
124 81 << QObject::tr("Can't update plottables: unmanaged data series type");
125 82 }
126 83 };
127 84
128 85 /**
129 86 * Specialization of PlottablesUpdater for scalars and vectors
130 87 * @sa ScalarSeries
131 88 * @sa VectorSeries
132 89 */
133 90 template <typename T>
134 91 struct PlottablesUpdater<T,
135 92 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
136 93 or std::is_base_of<VectorSeries, T>::value> > {
137 94 static void updatePlottables(T &dataSeries, PlottablesMap &plottables, const SqpRange &range,
138 95 bool rescaleAxes)
139 96 {
140 97
141 98 // For each plottable to update, resets its data
142 99 std::map<int, QSharedPointer<SqpDataContainer> > dataContainers{};
143 100 for (const auto &plottable : plottables) {
144 101 if (auto graph = dynamic_cast<QCPGraph *>(plottable.second)) {
145 102 auto dataContainer = QSharedPointer<SqpDataContainer>::create();
146 103 graph->setData(dataContainer);
147 104
148 105 dataContainers.insert({plottable.first, dataContainer});
149 106 }
150 107 }
151 108 dataSeries.lockRead();
152 109
153 110 // - Gets the data of the series included in the current range
154 111 // - Updates each plottable by adding, for each data item, a point that takes x-axis data
155 112 // and value data. The correct value is retrieved according to the index of the component
156 113 auto subDataIts = dataSeries.xAxisRange(range.m_TStart, range.m_TEnd);
157 114 for (auto it = subDataIts.first; it != subDataIts.second; ++it) {
158 115 for (const auto &dataContainer : dataContainers) {
159 116 auto componentIndex = dataContainer.first;
160 117 dataContainer.second->appendGraphData(
161 118 QCPGraphData(it->x(), it->value(componentIndex)));
162 119 }
163 120 }
164 121
165 122 dataSeries.unlock();
166 123
167 124 if (!plottables.empty()) {
168 125 auto plot = plottables.begin()->second->parentPlot();
169 126
170 127 if (rescaleAxes) {
171 128 plot->rescaleAxes();
172 129 }
173 130
174 131 plot->replot();
175 132 }
176 133 }
177 134 };
178 135
179 136 /**
180 137 * Helper used to create/update plottables
181 138 */
182 139 struct IPlottablesHelper {
183 140 virtual ~IPlottablesHelper() noexcept = default;
184 141 virtual PlottablesMap create(QCustomPlot &plot) const = 0;
185 142 virtual void update(PlottablesMap &plottables, const SqpRange &range,
186 143 bool rescaleAxes = false) const = 0;
187 144 };
188 145
189 146 /**
190 147 * Default implementation of IPlottablesHelper, which takes data series to create/update plottables
191 148 * @tparam T the data series' type
192 149 */
193 150 template <typename T>
194 151 struct PlottablesHelper : public IPlottablesHelper {
195 152 explicit PlottablesHelper(T &dataSeries) : m_DataSeries{dataSeries} {}
196 153
197 154 PlottablesMap create(QCustomPlot &plot) const override
198 155 {
199 156 return PlottablesCreator<T>::createPlottables(m_DataSeries, plot);
200 157 }
201 158
202 159 void update(PlottablesMap &plottables, const SqpRange &range, bool rescaleAxes) const override
203 160 {
204 161 PlottablesUpdater<T>::updatePlottables(m_DataSeries, plottables, range, rescaleAxes);
205 162 }
206 163
207 164 T &m_DataSeries;
208 165 };
209 166
210 167 /// Creates IPlottablesHelper according to a data series
211 168 std::unique_ptr<IPlottablesHelper> createHelper(std::shared_ptr<IDataSeries> dataSeries) noexcept
212 169 {
213 170 if (auto scalarSeries = std::dynamic_pointer_cast<ScalarSeries>(dataSeries)) {
214 171 return std::make_unique<PlottablesHelper<ScalarSeries> >(*scalarSeries);
215 172 }
216 173 else if (auto vectorSeries = std::dynamic_pointer_cast<VectorSeries>(dataSeries)) {
217 174 return std::make_unique<PlottablesHelper<VectorSeries> >(*vectorSeries);
218 175 }
219 176 else {
220 177 return std::make_unique<PlottablesHelper<IDataSeries> >(*dataSeries);
221 178 }
222 179 }
223 180
224 181 } // namespace
225 182
226 183 PlottablesMap VisualizationGraphHelper::create(std::shared_ptr<Variable> variable,
227 184 QCustomPlot &plot) noexcept
228 185 {
229 186 if (variable) {
230 187 auto helper = createHelper(variable->dataSeries());
231 188 auto plottables = helper->create(plot);
232 189 return plottables;
233 190 }
234 191 else {
235 192 qCDebug(LOG_VisualizationGraphHelper())
236 193 << QObject::tr("Can't create graph plottables : the variable is null");
237 194 return PlottablesMap{};
238 195 }
239 196 }
240 197
241 198 void VisualizationGraphHelper::updateData(PlottablesMap &plottables,
242 199 std::shared_ptr<IDataSeries> dataSeries,
243 200 const SqpRange &dateTime)
244 201 {
245 202 auto helper = createHelper(dataSeries);
246 203 helper->update(plottables, dateTime);
247 204 }
@@ -1,169 +1,275
1 1 #include "Visualization/VisualizationGraphRenderingDelegate.h"
2 2 #include "Visualization/VisualizationGraphWidget.h"
3 3 #include "Visualization/qcustomplot.h"
4 4
5 5 #include <Common/DateUtils.h>
6 6
7 #include <Data/IDataSeries.h>
8
7 9 #include <SqpApplication.h>
8 10
9 11 namespace {
10 12
13 /// Name of the axes layer in QCustomPlot
14 const auto AXES_LAYER = QStringLiteral("axes");
15
11 16 const auto DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd hh:mm:ss:zzz");
12 17
18 /// Format for datetimes on a axis
19 const auto DATETIME_TICKER_FORMAT = QStringLiteral("yyyy/MM/dd \nhh:mm:ss");
20
21 /// Icon used to show x-axis properties
22 const auto HIDE_AXIS_ICON_PATH = QStringLiteral(":/icones/down.png");
23
13 24 /// Name of the overlay layer in QCustomPlot
14 25 const auto OVERLAY_LAYER = QStringLiteral("overlay");
15 26
27 /// Pixmap used to show x-axis properties
28 const auto SHOW_AXIS_ICON_PATH = QStringLiteral(":/icones/up.png");
29
16 30 const auto TOOLTIP_FORMAT = QStringLiteral("key: %1\nvalue: %2");
17 31
18 32 /// Offset used to shift the tooltip of the mouse
19 33 const auto TOOLTIP_OFFSET = QPoint{20, 20};
20 34
21 35 /// Tooltip display rectangle (the tooltip is hidden when the mouse leaves this rectangle)
22 36 const auto TOOLTIP_RECT = QRect{10, 10, 10, 10};
23 37
24 38 /// Timeout after which the tooltip is displayed
25 39 const auto TOOLTIP_TIMEOUT = 500;
26 40
41 /// Generates the appropriate ticker for an axis, depending on whether the axis displays time or
42 /// non-time data
43 QSharedPointer<QCPAxisTicker> axisTicker(bool isTimeAxis)
44 {
45 if (isTimeAxis) {
46 auto dateTicker = QSharedPointer<QCPAxisTickerDateTime>::create();
47 dateTicker->setDateTimeFormat(DATETIME_TICKER_FORMAT);
48 dateTicker->setDateTimeSpec(Qt::UTC);
49
50 return dateTicker;
51 }
52 else {
53 // default ticker
54 return QSharedPointer<QCPAxisTicker>::create();
55 }
56 }
57
27 58 /// Formats a data value according to the axis on which it is present
28 59 QString formatValue(double value, const QCPAxis &axis)
29 60 {
30 61 // If the axis is a time axis, formats the value as a date
31 62 if (auto axisTicker = qSharedPointerDynamicCast<QCPAxisTickerDateTime>(axis.ticker())) {
32 63 return DateUtils::dateTime(value, axisTicker->dateTimeSpec()).toString(DATETIME_FORMAT);
33 64 }
34 65 else {
35 66 return QString::number(value);
36 67 }
37 68 }
38 69
39 70 void initPointTracerStyle(QCPItemTracer &tracer) noexcept
40 71 {
41 72 tracer.setInterpolating(false);
42 73 tracer.setStyle(QCPItemTracer::tsCircle);
43 74 tracer.setSize(3);
44 75 tracer.setPen(QPen(Qt::black));
45 76 tracer.setBrush(Qt::black);
46 77 }
47 78
79 QPixmap pixmap(const QString &iconPath) noexcept
80 {
81 return QIcon{iconPath}.pixmap(QSize{16, 16});
82 }
83
48 84 void initClosePixmapStyle(QCPItemPixmap &pixmap) noexcept
49 85 {
50 86 // Icon
51 87 pixmap.setPixmap(
52 88 sqpApp->style()->standardIcon(QStyle::SP_TitleBarCloseButton).pixmap(QSize{16, 16}));
53 89
54 90 // Position
55 91 pixmap.topLeft->setType(QCPItemPosition::ptAxisRectRatio);
56 92 pixmap.topLeft->setCoords(1, 0);
57 93 pixmap.setClipToAxisRect(false);
58 94
59 95 // Can be selected
60 96 pixmap.setSelectable(true);
61 97 }
62 98
99 void initXAxisPixmapStyle(QCPItemPixmap &itemPixmap) noexcept
100 {
101 // Icon
102 itemPixmap.setPixmap(pixmap(HIDE_AXIS_ICON_PATH));
103
104 // Position
105 itemPixmap.topLeft->setType(QCPItemPosition::ptAxisRectRatio);
106 itemPixmap.topLeft->setCoords(0, 1);
107 itemPixmap.setClipToAxisRect(false);
108
109 // Can be selected
110 itemPixmap.setSelectable(true);
111 }
112
63 113 void initTitleTextStyle(QCPItemText &text) noexcept
64 114 {
65 115 // Font and background styles
66 116 text.setColor(Qt::gray);
67 117 text.setBrush(Qt::white);
68 118
69 119 // Position
70 120 text.setPositionAlignment(Qt::AlignTop | Qt::AlignLeft);
71 121 text.position->setType(QCPItemPosition::ptAxisRectRatio);
72 122 text.position->setCoords(0.5, 0);
73 123 }
74 124
75 125 } // namespace
76 126
77 127 struct VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegatePrivate {
78 128 explicit VisualizationGraphRenderingDelegatePrivate(VisualizationGraphWidget &graphWidget)
79 129 : m_Plot{graphWidget.plot()},
80 130 m_PointTracer{new QCPItemTracer{&m_Plot}},
81 131 m_TracerTimer{},
82 132 m_ClosePixmap{new QCPItemPixmap{&m_Plot}},
83 m_TitleText{new QCPItemText{&m_Plot}}
133 m_TitleText{new QCPItemText{&m_Plot}},
134 m_XAxisPixmap{new QCPItemPixmap{&m_Plot}},
135 m_ShowXAxis{true},
136 m_XAxisLabel{}
84 137 {
85 138 initPointTracerStyle(*m_PointTracer);
86 139
87 140 m_TracerTimer.setInterval(TOOLTIP_TIMEOUT);
88 141 m_TracerTimer.setSingleShot(true);
89 142
90 143 // Inits "close button" in plot overlay
91 144 m_ClosePixmap->setLayer(OVERLAY_LAYER);
92 145 initClosePixmapStyle(*m_ClosePixmap);
93 146
94 147 // Connects pixmap selection to graph widget closing
95 148 QObject::connect(m_ClosePixmap, &QCPItemPixmap::selectionChanged,
96 149 [&graphWidget](bool selected) {
97 150 if (selected) {
98 151 graphWidget.close();
99 152 }
100 153 });
101 154
102 155 // Inits graph name in plot overlay
103 156 m_TitleText->setLayer(OVERLAY_LAYER);
104 157 m_TitleText->setText(graphWidget.name());
105 158 initTitleTextStyle(*m_TitleText);
159
160 // Inits "show x-axis button" in plot overlay
161 m_XAxisPixmap->setLayer(OVERLAY_LAYER);
162 initXAxisPixmapStyle(*m_XAxisPixmap);
163
164 // Connects pixmap selection to graph x-axis showing/hiding
165 QObject::connect(m_XAxisPixmap, &QCPItemPixmap::selectionChanged, [this]() {
166 if (m_XAxisPixmap->selected()) {
167 // Changes the selection state and refreshes the x-axis
168 m_ShowXAxis = !m_ShowXAxis;
169 updateXAxisState();
170 m_Plot.layer(AXES_LAYER)->replot();
171
172 // Deselects the x-axis pixmap and updates icon
173 m_XAxisPixmap->setSelected(false);
174 m_XAxisPixmap->setPixmap(
175 pixmap(m_ShowXAxis ? HIDE_AXIS_ICON_PATH : SHOW_AXIS_ICON_PATH));
176 m_Plot.layer(OVERLAY_LAYER)->replot();
177 }
178 });
179 }
180
181 /// Updates state of x-axis according to the current selection of x-axis pixmap
182 /// @remarks the method doesn't call plot refresh
183 void updateXAxisState() noexcept
184 {
185 m_Plot.xAxis->setTickLabels(m_ShowXAxis);
186 m_Plot.xAxis->setLabel(m_ShowXAxis ? m_XAxisLabel : QString{});
106 187 }
107 188
108 189 QCustomPlot &m_Plot;
109 190 QCPItemTracer *m_PointTracer;
110 191 QTimer m_TracerTimer;
111 192 QCPItemPixmap *m_ClosePixmap; /// Graph's close button
112 193 QCPItemText *m_TitleText; /// Graph's title
194 QCPItemPixmap *m_XAxisPixmap;
195 bool m_ShowXAxis; /// X-axis properties are shown or hidden
196 QString m_XAxisLabel;
113 197 };
114 198
115 199 VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegate(
116 200 VisualizationGraphWidget &graphWidget)
117 201 : impl{spimpl::make_unique_impl<VisualizationGraphRenderingDelegatePrivate>(graphWidget)}
118 202 {
119 203 }
120 204
121 205 void VisualizationGraphRenderingDelegate::onMouseMove(QMouseEvent *event) noexcept
122 206 {
123 207 // Cancels pending refresh
124 208 impl->m_TracerTimer.disconnect();
125 209
126 210 // Reinits tracers
127 211 impl->m_PointTracer->setGraph(nullptr);
128 212 impl->m_PointTracer->setVisible(false);
129 213 impl->m_Plot.replot();
130 214
131 215 // Gets the graph under the mouse position
132 216 auto eventPos = event->pos();
133 217 if (auto graph = qobject_cast<QCPGraph *>(impl->m_Plot.plottableAt(eventPos))) {
134 218 auto mouseKey = graph->keyAxis()->pixelToCoord(eventPos.x());
135 219 auto graphData = graph->data();
136 220
137 221 // Gets the closest data point to the mouse
138 222 auto graphDataIt = graphData->findBegin(mouseKey);
139 223 if (graphDataIt != graphData->constEnd()) {
140 224 auto key = formatValue(graphDataIt->key, *graph->keyAxis());
141 225 auto value = formatValue(graphDataIt->value, *graph->valueAxis());
142 226
143 227 // Displays point tracer
144 228 impl->m_PointTracer->setGraph(graph);
145 229 impl->m_PointTracer->setGraphKey(graphDataIt->key);
146 230 impl->m_PointTracer->setLayer(
147 231 impl->m_Plot.layer("main")); // Tracer is set on top of the plot's main layer
148 232 impl->m_PointTracer->setVisible(true);
149 233 impl->m_Plot.replot();
150 234
151 235 // Starts timer to show tooltip after timeout
152 236 auto showTooltip = [ tooltip = TOOLTIP_FORMAT.arg(key, value), eventPos, this ]()
153 237 {
154 238 QToolTip::showText(impl->m_Plot.mapToGlobal(eventPos) + TOOLTIP_OFFSET, tooltip,
155 239 &impl->m_Plot, TOOLTIP_RECT);
156 240 };
157 241
158 242 QObject::connect(&impl->m_TracerTimer, &QTimer::timeout, showTooltip);
159 243 impl->m_TracerTimer.start();
160 244 }
161 245 }
162 246 }
163 247
248 void VisualizationGraphRenderingDelegate::setAxesProperties(const Unit &xAxisUnit,
249 const Unit &valuesUnit) noexcept
250 {
251 // Stores x-axis label to be able to retrieve it when x-axis pixmap is unselected
252 impl->m_XAxisLabel = xAxisUnit.m_Name;
253
254 auto setAxisProperties = [](auto axis, const auto &unit) {
255 // label (unit name)
256 axis->setLabel(unit.m_Name);
257
258 // ticker (depending on the type of unit)
259 axis->setTicker(axisTicker(unit.m_TimeUnit));
260 };
261 setAxisProperties(impl->m_Plot.xAxis, xAxisUnit);
262 setAxisProperties(impl->m_Plot.yAxis, valuesUnit);
263
264 // Updates x-axis state
265 impl->updateXAxisState();
266
267 impl->m_Plot.layer(AXES_LAYER)->replot();
268 }
269
164 270 void VisualizationGraphRenderingDelegate::showGraphOverlay(bool show) noexcept
165 271 {
166 272 auto overlay = impl->m_Plot.layer(OVERLAY_LAYER);
167 273 overlay->setVisible(show);
168 274 overlay->replot();
169 275 }
@@ -1,330 +1,344
1 1 #include "Visualization/VisualizationGraphWidget.h"
2 2 #include "Visualization/IVisualizationWidgetVisitor.h"
3 3 #include "Visualization/VisualizationDefs.h"
4 4 #include "Visualization/VisualizationGraphHelper.h"
5 5 #include "Visualization/VisualizationGraphRenderingDelegate.h"
6 6 #include "ui_VisualizationGraphWidget.h"
7 7
8 8 #include <Data/ArrayData.h>
9 9 #include <Data/IDataSeries.h>
10 10 #include <Settings/SqpSettingsDefs.h>
11 11 #include <SqpApplication.h>
12 12 #include <Variable/Variable.h>
13 13 #include <Variable/VariableController.h>
14 14
15 15 #include <unordered_map>
16 16
17 17 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
18 18
19 19 namespace {
20 20
21 21 /// Key pressed to enable zoom on horizontal axis
22 22 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::NoModifier;
23 23
24 24 /// Key pressed to enable zoom on vertical axis
25 25 const auto VERTICAL_ZOOM_MODIFIER = Qt::ControlModifier;
26 26
27 27 } // namespace
28 28
29 29 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate {
30 30
31 31 explicit VisualizationGraphWidgetPrivate(const QString &name)
32 32 : m_Name{name},
33 33 m_DoAcquisition{true},
34 34 m_IsCalibration{false},
35 35 m_RenderingDelegate{nullptr}
36 36 {
37 37 }
38 38
39 39 QString m_Name;
40 40 // 1 variable -> n qcpplot
41 41 std::map<std::shared_ptr<Variable>, PlottablesMap> m_VariableToPlotMultiMap;
42 42 bool m_DoAcquisition;
43 43 bool m_IsCalibration;
44 44 QCPItemTracer *m_TextTracer;
45 45 /// Delegate used to attach rendering features to the plot
46 46 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
47 47 };
48 48
49 49 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
50 50 : QWidget{parent},
51 51 ui{new Ui::VisualizationGraphWidget},
52 52 impl{spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name)}
53 53 {
54 54 ui->setupUi(this);
55 55
56 56 // 'Close' options : widget is deleted when closed
57 57 setAttribute(Qt::WA_DeleteOnClose);
58 58
59 59 // Set qcpplot properties :
60 60 // - Drag (on x-axis) and zoom are enabled
61 61 // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation
62 62 ui->widget->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectItems);
63 63 ui->widget->axisRect()->setRangeDrag(Qt::Horizontal);
64 64
65 65 // The delegate must be initialized after the ui as it uses the plot
66 66 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
67 67
68 68 connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress);
69 69 connect(ui->widget, &QCustomPlot::mouseRelease, this,
70 70 &VisualizationGraphWidget::onMouseRelease);
71 71 connect(ui->widget, &QCustomPlot::mouseMove, this, &VisualizationGraphWidget::onMouseMove);
72 72 connect(ui->widget, &QCustomPlot::mouseWheel, this, &VisualizationGraphWidget::onMouseWheel);
73 73 connect(ui->widget->xAxis, static_cast<void (QCPAxis::*)(const QCPRange &, const QCPRange &)>(
74 74 &QCPAxis::rangeChanged),
75 75 this, &VisualizationGraphWidget::onRangeChanged, Qt::DirectConnection);
76 76
77 77 // Activates menu when right clicking on the graph
78 78 ui->widget->setContextMenuPolicy(Qt::CustomContextMenu);
79 79 connect(ui->widget, &QCustomPlot::customContextMenuRequested, this,
80 80 &VisualizationGraphWidget::onGraphMenuRequested);
81 81
82 82 connect(this, &VisualizationGraphWidget::requestDataLoading, &sqpApp->variableController(),
83 83 &VariableController::onRequestDataLoading);
84 84
85 85 connect(&sqpApp->variableController(), &VariableController::updateVarDisplaying, this,
86 86 &VisualizationGraphWidget::onUpdateVarDisplaying);
87 87 }
88 88
89 89
90 90 VisualizationGraphWidget::~VisualizationGraphWidget()
91 91 {
92 92 delete ui;
93 93 }
94 94
95 95 void VisualizationGraphWidget::enableAcquisition(bool enable)
96 96 {
97 97 impl->m_DoAcquisition = enable;
98 98 }
99 99
100 100 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable> variable, SqpRange range)
101 101 {
102 102 // Uses delegate to create the qcpplot components according to the variable
103 103 auto createdPlottables = VisualizationGraphHelper::create(variable, *ui->widget);
104 104 impl->m_VariableToPlotMultiMap.insert({variable, std::move(createdPlottables)});
105 105
106 // Set axes properties according to the units of the data series
107 /// @todo : for the moment, no control is performed on the axes: the units and the tickers
108 /// are fixed for the default x-axis and y-axis of the plot, and according to the new graph
109 auto xAxisUnit = Unit{};
110 auto valuesUnit = Unit{};
111
112 if (auto dataSeries = variable->dataSeries()) {
113 dataSeries->lockRead();
114 xAxisUnit = dataSeries->xAxisUnit();
115 valuesUnit = dataSeries->valuesUnit();
116 dataSeries->unlock();
117 }
118 impl->m_RenderingDelegate->setAxesProperties(xAxisUnit, valuesUnit);
119
106 120 connect(variable.get(), SIGNAL(updated()), this, SLOT(onDataCacheVariableUpdated()));
107 121
108 122 auto varRange = variable->range();
109 123
110 124 this->enableAcquisition(false);
111 125 this->setGraphRange(range);
112 126 this->enableAcquisition(true);
113 127
114 128 emit requestDataLoading(QVector<std::shared_ptr<Variable> >() << variable, range, varRange,
115 129 false);
116 130
117 131 emit variableAdded(variable);
118 132 }
119 133
120 134 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable> variable) noexcept
121 135 {
122 136 // Each component associated to the variable :
123 137 // - is removed from qcpplot (which deletes it)
124 138 // - is no longer referenced in the map
125 139 auto variableIt = impl->m_VariableToPlotMultiMap.find(variable);
126 140 if (variableIt != impl->m_VariableToPlotMultiMap.cend()) {
127 141 auto &plottablesMap = variableIt->second;
128 142
129 143 for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend();
130 144 plottableIt != plottableEnd;) {
131 145 ui->widget->removePlottable(plottableIt->second);
132 146 plottableIt = plottablesMap.erase(plottableIt);
133 147 }
134 148
135 149 impl->m_VariableToPlotMultiMap.erase(variableIt);
136 150 }
137 151
138 152 // Updates graph
139 153 ui->widget->replot();
140 154 }
141 155
142 156 void VisualizationGraphWidget::setRange(std::shared_ptr<Variable> variable, const SqpRange &range)
143 157 {
144 158 // Note: in case of different axes that depends on variable, we could start with a code like
145 159 // that:
146 160 // auto componentsIt = impl->m_VariableToPlotMultiMap.equal_range(variable);
147 161 // for (auto it = componentsIt.first; it != componentsIt.second;) {
148 162 // }
149 163 ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd);
150 164 ui->widget->replot();
151 165 }
152 166
153 167 void VisualizationGraphWidget::setYRange(const SqpRange &range)
154 168 {
155 169 ui->widget->yAxis->setRange(range.m_TStart, range.m_TEnd);
156 170 }
157 171
158 172 SqpRange VisualizationGraphWidget::graphRange() const noexcept
159 173 {
160 174 auto graphRange = ui->widget->xAxis->range();
161 175 return SqpRange{graphRange.lower, graphRange.upper};
162 176 }
163 177
164 178 void VisualizationGraphWidget::setGraphRange(const SqpRange &range)
165 179 {
166 180 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange START");
167 181 ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd);
168 182 ui->widget->replot();
169 183 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange END");
170 184 }
171 185
172 186 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor)
173 187 {
174 188 if (visitor) {
175 189 visitor->visit(this);
176 190 }
177 191 else {
178 192 qCCritical(LOG_VisualizationGraphWidget())
179 193 << tr("Can't visit widget : the visitor is null");
180 194 }
181 195 }
182 196
183 197 bool VisualizationGraphWidget::canDrop(const Variable &variable) const
184 198 {
185 199 /// @todo : for the moment, a graph can always accomodate a variable
186 200 Q_UNUSED(variable);
187 201 return true;
188 202 }
189 203
190 204 bool VisualizationGraphWidget::contains(const Variable &variable) const
191 205 {
192 206 // Finds the variable among the keys of the map
193 207 auto variablePtr = &variable;
194 208 auto findVariable
195 209 = [variablePtr](const auto &entry) { return variablePtr == entry.first.get(); };
196 210
197 211 auto end = impl->m_VariableToPlotMultiMap.cend();
198 212 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
199 213 return it != end;
200 214 }
201 215
202 216 QString VisualizationGraphWidget::name() const
203 217 {
204 218 return impl->m_Name;
205 219 }
206 220
207 221 void VisualizationGraphWidget::enterEvent(QEvent *event)
208 222 {
209 223 Q_UNUSED(event);
210 224 impl->m_RenderingDelegate->showGraphOverlay(true);
211 225 }
212 226
213 227 void VisualizationGraphWidget::leaveEvent(QEvent *event)
214 228 {
215 229 Q_UNUSED(event);
216 230 impl->m_RenderingDelegate->showGraphOverlay(false);
217 231 }
218 232
219 233 QCustomPlot &VisualizationGraphWidget::plot() noexcept
220 234 {
221 235 return *ui->widget;
222 236 }
223 237
224 238 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept
225 239 {
226 240 QMenu graphMenu{};
227 241
228 242 // Iterates on variables (unique keys)
229 243 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
230 244 end = impl->m_VariableToPlotMultiMap.cend();
231 245 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
232 246 // 'Remove variable' action
233 247 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
234 248 [ this, var = it->first ]() { removeVariable(var); });
235 249 }
236 250
237 251 if (!graphMenu.isEmpty()) {
238 252 graphMenu.exec(QCursor::pos());
239 253 }
240 254 }
241 255
242 256 void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange &t2)
243 257 {
244 258 qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: VisualizationGraphWidget::onRangeChanged")
245 259 << QThread::currentThread()->objectName() << "DoAcqui"
246 260 << impl->m_DoAcquisition;
247 261
248 262 auto graphRange = SqpRange{t1.lower, t1.upper};
249 263 auto oldGraphRange = SqpRange{t2.lower, t2.upper};
250 264
251 265 if (impl->m_DoAcquisition) {
252 266 QVector<std::shared_ptr<Variable> > variableUnderGraphVector;
253 267
254 268 for (auto it = impl->m_VariableToPlotMultiMap.begin(),
255 269 end = impl->m_VariableToPlotMultiMap.end();
256 270 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
257 271 variableUnderGraphVector.push_back(it->first);
258 272 }
259 273 emit requestDataLoading(std::move(variableUnderGraphVector), graphRange, oldGraphRange,
260 274 !impl->m_IsCalibration);
261 275
262 276 if (!impl->m_IsCalibration) {
263 277 qCDebug(LOG_VisualizationGraphWidget())
264 278 << tr("TORM: VisualizationGraphWidget::Synchronize notify !!")
265 279 << QThread::currentThread()->objectName() << graphRange << oldGraphRange;
266 280 emit synchronize(graphRange, oldGraphRange);
267 281 }
268 282 }
269 283 }
270 284
271 285 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
272 286 {
273 287 // Handles plot rendering when mouse is moving
274 288 impl->m_RenderingDelegate->onMouseMove(event);
275 289 }
276 290
277 291 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
278 292 {
279 293 auto zoomOrientations = QFlags<Qt::Orientation>{};
280 294
281 295 // Lambda that enables a zoom orientation if the key modifier related to this orientation
282 296 // has
283 297 // been pressed
284 298 auto enableOrientation
285 299 = [&zoomOrientations, event](const auto &orientation, const auto &modifier) {
286 300 auto orientationEnabled = event->modifiers().testFlag(modifier);
287 301 zoomOrientations.setFlag(orientation, orientationEnabled);
288 302 };
289 303 enableOrientation(Qt::Vertical, VERTICAL_ZOOM_MODIFIER);
290 304 enableOrientation(Qt::Horizontal, HORIZONTAL_ZOOM_MODIFIER);
291 305
292 306 ui->widget->axisRect()->setRangeZoom(zoomOrientations);
293 307 }
294 308
295 309 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
296 310 {
297 311 impl->m_IsCalibration = event->modifiers().testFlag(Qt::ControlModifier);
298 312 }
299 313
300 314 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
301 315 {
302 316 impl->m_IsCalibration = false;
303 317 }
304 318
305 319 void VisualizationGraphWidget::onDataCacheVariableUpdated()
306 320 {
307 321 auto graphRange = ui->widget->xAxis->range();
308 322 auto dateTime = SqpRange{graphRange.lower, graphRange.upper};
309 323
310 324 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
311 325 auto variable = variableEntry.first;
312 326 qCDebug(LOG_VisualizationGraphWidget())
313 327 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
314 328 qCDebug(LOG_VisualizationGraphWidget())
315 329 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
316 330 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range())) {
317 331 VisualizationGraphHelper::updateData(variableEntry.second, variable->dataSeries(),
318 332 variable->range());
319 333 }
320 334 }
321 335 }
322 336
323 337 void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr<Variable> variable,
324 338 const SqpRange &range)
325 339 {
326 340 auto it = impl->m_VariableToPlotMultiMap.find(variable);
327 341 if (it != impl->m_VariableToPlotMultiMap.end()) {
328 342 VisualizationGraphHelper::updateData(it->second, variable->dataSeries(), range);
329 343 }
330 344 }
General Comments 0
You need to be logged in to leave comments. Login now