##// END OF EJS Templates
Tooltip for spectrograms (4)...
Alexandre Leroux -
r1068:e3aea966fca1
parent child
Show More
@@ -1,173 +1,176
1 1 #include "Visualization/AxisRenderingUtils.h"
2 2
3 3 #include <Data/ScalarSeries.h>
4 4 #include <Data/SpectrogramSeries.h>
5 5 #include <Data/VectorSeries.h>
6 6
7 7 #include <Visualization/SqpColorScale.h>
8 8 #include <Visualization/qcustomplot.h>
9 9
10 10 Q_LOGGING_CATEGORY(LOG_AxisRenderingUtils, "AxisRenderingUtils")
11 11
12 12 namespace {
13 13
14 14 const auto DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd hh:mm:ss:zzz");
15 15
16 16 /// Format for datetimes on a axis
17 17 const auto DATETIME_TICKER_FORMAT = QStringLiteral("yyyy/MM/dd \nhh:mm:ss");
18 18
19 const auto NUMBER_FORMAT = 'g';
20 const auto NUMBER_PRECISION = 9;
21
19 22 /// Generates the appropriate ticker for an axis, depending on whether the axis displays time or
20 23 /// non-time data
21 24 QSharedPointer<QCPAxisTicker> axisTicker(bool isTimeAxis, QCPAxis::ScaleType scaleType)
22 25 {
23 26 if (isTimeAxis) {
24 27 auto dateTicker = QSharedPointer<QCPAxisTickerDateTime>::create();
25 28 dateTicker->setDateTimeFormat(DATETIME_TICKER_FORMAT);
26 29 dateTicker->setDateTimeSpec(Qt::UTC);
27 30
28 31 return dateTicker;
29 32 }
30 33 else if (scaleType == QCPAxis::stLogarithmic) {
31 34 return QSharedPointer<QCPAxisTickerLog>::create();
32 35 }
33 36 else {
34 37 // default ticker
35 38 return QSharedPointer<QCPAxisTicker>::create();
36 39 }
37 40 }
38 41
39 42 /**
40 43 * Sets properties of the axis passed as parameter
41 44 * @param axis the axis to set
42 45 * @param unit the unit to set for the axis
43 46 * @param scaleType the scale type to set for the axis
44 47 */
45 48 void setAxisProperties(QCPAxis &axis, const Unit &unit,
46 49 QCPAxis::ScaleType scaleType = QCPAxis::stLinear)
47 50 {
48 51 // label (unit name)
49 52 axis.setLabel(unit.m_Name);
50 53
51 54 // scale type
52 55 axis.setScaleType(scaleType);
53 56 if (scaleType == QCPAxis::stLogarithmic) {
54 57 // Scientific notation
55 58 axis.setNumberPrecision(0);
56 59 axis.setNumberFormat("eb");
57 60 }
58 61
59 62 // ticker (depending on the type of unit)
60 63 axis.setTicker(axisTicker(unit.m_TimeUnit, scaleType));
61 64 }
62 65
63 66 /**
64 67 * Delegate used to set axes properties
65 68 */
66 69 template <typename T, typename Enabled = void>
67 70 struct AxisSetter {
68 71 static void setProperties(T &, QCustomPlot &, SqpColorScale &)
69 72 {
70 73 // Default implementation does nothing
71 74 qCCritical(LOG_AxisRenderingUtils()) << "Can't set axis properties: unmanaged type of data";
72 75 }
73 76 };
74 77
75 78 /**
76 79 * Specialization of AxisSetter for scalars and vectors
77 80 * @sa ScalarSeries
78 81 * @sa VectorSeries
79 82 */
80 83 template <typename T>
81 84 struct AxisSetter<T, typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
82 85 or std::is_base_of<VectorSeries, T>::value> > {
83 86 static void setProperties(T &dataSeries, QCustomPlot &plot, SqpColorScale &)
84 87 {
85 88 dataSeries.lockRead();
86 89 auto xAxisUnit = dataSeries.xAxisUnit();
87 90 auto valuesUnit = dataSeries.valuesUnit();
88 91 dataSeries.unlock();
89 92
90 93 setAxisProperties(*plot.xAxis, xAxisUnit);
91 94 setAxisProperties(*plot.yAxis, valuesUnit);
92 95 }
93 96 };
94 97
95 98 /**
96 99 * Specialization of AxisSetter for spectrograms
97 100 * @sa SpectrogramSeries
98 101 */
99 102 template <typename T>
100 103 struct AxisSetter<T, typename std::enable_if_t<std::is_base_of<SpectrogramSeries, T>::value> > {
101 104 static void setProperties(T &dataSeries, QCustomPlot &plot, SqpColorScale &colorScale)
102 105 {
103 106 dataSeries.lockRead();
104 107 auto xAxisUnit = dataSeries.xAxisUnit();
105 108 auto yAxisUnit = dataSeries.yAxisUnit();
106 109 auto valuesUnit = dataSeries.valuesUnit();
107 110 dataSeries.unlock();
108 111
109 112 setAxisProperties(*plot.xAxis, xAxisUnit);
110 113 setAxisProperties(*plot.yAxis, yAxisUnit, QCPAxis::stLogarithmic);
111 114
112 115 // Displays color scale in plot
113 116 plot.plotLayout()->insertRow(0);
114 117 plot.plotLayout()->addElement(0, 0, colorScale.m_Scale);
115 118 colorScale.m_Scale->setType(QCPAxis::atTop);
116 119 colorScale.m_Scale->setMinimumMargins(QMargins{0, 0, 0, 0});
117 120
118 121 // Aligns color scale with axes
119 122 auto marginGroups = plot.axisRect()->marginGroups();
120 123 for (auto it = marginGroups.begin(), end = marginGroups.end(); it != end; ++it) {
121 124 colorScale.m_Scale->setMarginGroup(it.key(), it.value());
122 125 }
123 126
124 127 // Set color scale properties
125 128 setAxisProperties(*colorScale.m_Scale->axis(), valuesUnit, QCPAxis::stLogarithmic);
126 129 }
127 130 };
128 131
129 132 /**
130 133 * Default implementation of IAxisHelper, which takes data series to set axes properties
131 134 * @tparam T the data series' type
132 135 */
133 136 template <typename T>
134 137 struct AxisHelper : public IAxisHelper {
135 138 explicit AxisHelper(T &dataSeries) : m_DataSeries{dataSeries} {}
136 139
137 140 void setProperties(QCustomPlot &plot, SqpColorScale &colorScale) override
138 141 {
139 142 AxisSetter<T>::setProperties(m_DataSeries, plot, colorScale);
140 143 }
141 144
142 145 T &m_DataSeries;
143 146 };
144 147
145 148 } // namespace
146 149
147 150 QString formatValue(double value, const QCPAxis &axis)
148 151 {
149 152 // If the axis is a time axis, formats the value as a date
150 153 if (auto axisTicker = qSharedPointerDynamicCast<QCPAxisTickerDateTime>(axis.ticker())) {
151 154 return DateUtils::dateTime(value, axisTicker->dateTimeSpec()).toString(DATETIME_FORMAT);
152 155 }
153 156 else {
154 return QString::number(value);
157 return QString::number(value, NUMBER_FORMAT, NUMBER_PRECISION);
155 158 }
156 159 }
157 160
158 161 std::unique_ptr<IAxisHelper>
159 162 IAxisHelperFactory::create(std::shared_ptr<IDataSeries> dataSeries) noexcept
160 163 {
161 164 if (auto scalarSeries = std::dynamic_pointer_cast<ScalarSeries>(dataSeries)) {
162 165 return std::make_unique<AxisHelper<ScalarSeries> >(*scalarSeries);
163 166 }
164 167 else if (auto spectrogramSeries = std::dynamic_pointer_cast<SpectrogramSeries>(dataSeries)) {
165 168 return std::make_unique<AxisHelper<SpectrogramSeries> >(*spectrogramSeries);
166 169 }
167 170 else if (auto vectorSeries = std::dynamic_pointer_cast<VectorSeries>(dataSeries)) {
168 171 return std::make_unique<AxisHelper<VectorSeries> >(*vectorSeries);
169 172 }
170 173 else {
171 174 return std::make_unique<AxisHelper<IDataSeries> >(*dataSeries);
172 175 }
173 176 }
@@ -1,325 +1,331
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 /// Tooltip format for colormaps
33 const auto COLORMAP_TOOLTIP_FORMAT = QStringLiteral("x: %1\ny: %2\nvalue: %3");
32 34
33 35 /// Offset used to shift the tooltip of the mouse
34 36 const auto TOOLTIP_OFFSET = QPoint{20, 20};
35 37
36 38 /// Tooltip display rectangle (the tooltip is hidden when the mouse leaves this rectangle)
37 39 const auto TOOLTIP_RECT = QRect{10, 10, 10, 10};
38 40
39 41 /// Timeout after which the tooltip is displayed
40 42 const auto TOOLTIP_TIMEOUT = 500;
41 43
42 44 void initPointTracerStyle(QCPItemTracer &tracer) noexcept
43 45 {
44 46 tracer.setInterpolating(false);
45 47 tracer.setStyle(QCPItemTracer::tsCircle);
46 48 tracer.setSize(3);
47 49 tracer.setPen(QPen(Qt::black));
48 50 tracer.setBrush(Qt::black);
49 51 }
50 52
51 53 QPixmap pixmap(const QString &iconPath) noexcept
52 54 {
53 55 return QIcon{iconPath}.pixmap(QSize{16, 16});
54 56 }
55 57
56 58 void initClosePixmapStyle(QCPItemPixmap &pixmap) noexcept
57 59 {
58 60 // Icon
59 61 pixmap.setPixmap(
60 62 sqpApp->style()->standardIcon(QStyle::SP_TitleBarCloseButton).pixmap(QSize{16, 16}));
61 63
62 64 // Position
63 65 pixmap.topLeft->setType(QCPItemPosition::ptAxisRectRatio);
64 66 pixmap.topLeft->setCoords(1, 0);
65 67 pixmap.setClipToAxisRect(false);
66 68
67 69 // Can be selected
68 70 pixmap.setSelectable(true);
69 71 }
70 72
71 73 void initXAxisPixmapStyle(QCPItemPixmap &itemPixmap) noexcept
72 74 {
73 75 // Icon
74 76 itemPixmap.setPixmap(pixmap(HIDE_AXIS_ICON_PATH));
75 77
76 78 // Position
77 79 itemPixmap.topLeft->setType(QCPItemPosition::ptAxisRectRatio);
78 80 itemPixmap.topLeft->setCoords(0, 1);
79 81 itemPixmap.setClipToAxisRect(false);
80 82
81 83 // Can be selected
82 84 itemPixmap.setSelectable(true);
83 85 }
84 86
85 87 void initTitleTextStyle(QCPItemText &text) noexcept
86 88 {
87 89 // Font and background styles
88 90 text.setColor(Qt::gray);
89 91 text.setBrush(Qt::white);
90 92
91 93 // Position
92 94 text.setPositionAlignment(Qt::AlignTop | Qt::AlignLeft);
93 95 text.position->setType(QCPItemPosition::ptAxisRectRatio);
94 96 text.position->setCoords(0.5, 0);
95 97 }
96 98
97 99 /**
98 100 * Returns the cell index (x or y) of a colormap according to the coordinate passed in parameter.
99 101 * This method handles the fact that a colormap axis can be logarithmic or linear.
100 102 * @param colormap the colormap for which to calculate the index
101 103 * @param coord the coord to convert to cell index
102 104 * @param xCoord calculates the x index if true, calculates y index if false
103 105 * @return the cell index
104 106 */
105 107 int colorMapCellIndex(const QCPColorMap &colormap, double coord, bool xCoord)
106 108 {
107 109 // Determines the axis of the colormap according to xCoord, and whether it is logarithmic or not
108 110 auto isLogarithmic = (xCoord ? colormap.keyAxis() : colormap.valueAxis())->scaleType()
109 111 == QCPAxis::stLogarithmic;
110 112
111 113 if (isLogarithmic) {
112 114 // For a logarithmic axis we can't use the conversion method of colormap, so we calculate
113 115 // the index manually based on the position of the coordinate on the axis
114 116
115 117 // Gets the axis range and the number of values between range bounds to calculate the step
116 118 // between each value of the range
117 119 auto range = xCoord ? colormap.data()->keyRange() : colormap.data()->valueRange();
118 120 auto nbValues = (xCoord ? colormap.data()->keySize() : colormap.data()->valueSize()) - 1;
119 121 auto valueStep
120 122 = (std::log10(range.upper) - std::log10(range.lower)) / static_cast<double>(nbValues);
121 123
122 124 // According to the coord position, calculates the closest index in the range
123 125 return std::round((std::log10(coord) - std::log10(range.lower)) / valueStep);
124 126 }
125 127 else {
126 128 // For a linear axis, we use the conversion method of colormap
127 129 int index;
128 130 if (xCoord) {
129 131 colormap.data()->coordToCell(coord, 0., &index, nullptr);
130 132 }
131 133 else {
132 134 colormap.data()->coordToCell(0., coord, nullptr, &index);
133 135 }
134 136
135 137 return index;
136 138 }
137 139 }
138 140
139 141 } // namespace
140 142
141 143 struct VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegatePrivate {
142 144 explicit VisualizationGraphRenderingDelegatePrivate(VisualizationGraphWidget &graphWidget)
143 145 : m_Plot{graphWidget.plot()},
144 146 m_PointTracer{new QCPItemTracer{&m_Plot}},
145 147 m_TracerTimer{},
146 148 m_ClosePixmap{new QCPItemPixmap{&m_Plot}},
147 149 m_TitleText{new QCPItemText{&m_Plot}},
148 150 m_XAxisPixmap{new QCPItemPixmap{&m_Plot}},
149 151 m_ShowXAxis{true},
150 152 m_XAxisLabel{},
151 153 m_ColorScale{SqpColorScale{m_Plot}}
152 154 {
153 155 initPointTracerStyle(*m_PointTracer);
154 156
155 157 m_TracerTimer.setInterval(TOOLTIP_TIMEOUT);
156 158 m_TracerTimer.setSingleShot(true);
157 159
158 160 // Inits "close button" in plot overlay
159 161 m_ClosePixmap->setLayer(OVERLAY_LAYER);
160 162 initClosePixmapStyle(*m_ClosePixmap);
161 163
162 164 // Connects pixmap selection to graph widget closing
163 165 QObject::connect(m_ClosePixmap, &QCPItemPixmap::selectionChanged,
164 166 [&graphWidget](bool selected) {
165 167 if (selected) {
166 168 graphWidget.close();
167 169 }
168 170 });
169 171
170 172 // Inits graph name in plot overlay
171 173 m_TitleText->setLayer(OVERLAY_LAYER);
172 174 m_TitleText->setText(graphWidget.name());
173 175 initTitleTextStyle(*m_TitleText);
174 176
175 177 // Inits "show x-axis button" in plot overlay
176 178 m_XAxisPixmap->setLayer(OVERLAY_LAYER);
177 179 initXAxisPixmapStyle(*m_XAxisPixmap);
178 180
179 181 // Connects pixmap selection to graph x-axis showing/hiding
180 182 QObject::connect(m_XAxisPixmap, &QCPItemPixmap::selectionChanged, [this]() {
181 183 if (m_XAxisPixmap->selected()) {
182 184 // Changes the selection state and refreshes the x-axis
183 185 m_ShowXAxis = !m_ShowXAxis;
184 186 updateXAxisState();
185 187 m_Plot.layer(AXES_LAYER)->replot();
186 188
187 189 // Deselects the x-axis pixmap and updates icon
188 190 m_XAxisPixmap->setSelected(false);
189 191 m_XAxisPixmap->setPixmap(
190 192 pixmap(m_ShowXAxis ? HIDE_AXIS_ICON_PATH : SHOW_AXIS_ICON_PATH));
191 193 m_Plot.layer(OVERLAY_LAYER)->replot();
192 194 }
193 195 });
194 196 }
195 197
196 198 /// Updates state of x-axis according to the current selection of x-axis pixmap
197 199 /// @remarks the method doesn't call plot refresh
198 200 void updateXAxisState() noexcept
199 201 {
200 202 m_Plot.xAxis->setTickLabels(m_ShowXAxis);
201 203 m_Plot.xAxis->setLabel(m_ShowXAxis ? m_XAxisLabel : QString{});
202 204 }
203 205
204 206 QCustomPlot &m_Plot;
205 207 QCPItemTracer *m_PointTracer;
206 208 QTimer m_TracerTimer;
207 209 QCPItemPixmap *m_ClosePixmap; /// Graph's close button
208 210 QCPItemText *m_TitleText; /// Graph's title
209 211 QCPItemPixmap *m_XAxisPixmap;
210 212 bool m_ShowXAxis; /// X-axis properties are shown or hidden
211 213 QString m_XAxisLabel;
212 214 SqpColorScale m_ColorScale; /// Color scale used for some types of graphs (as spectrograms)
213 215 };
214 216
215 217 VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegate(
216 218 VisualizationGraphWidget &graphWidget)
217 219 : impl{spimpl::make_unique_impl<VisualizationGraphRenderingDelegatePrivate>(graphWidget)}
218 220 {
219 221 }
220 222
221 223 void VisualizationGraphRenderingDelegate::onMouseDoubleClick(QMouseEvent *event) noexcept
222 224 {
223 225 // Opens color scale editor if color scale is double clicked
224 226 auto colorScale = dynamic_cast<QCPColorScale *>(impl->m_Plot.layoutElementAt(event->pos()));
225 227 if (impl->m_ColorScale.m_Scale == colorScale) {
226 228 if (ColorScaleEditor{impl->m_ColorScale}.exec() == QDialog::Accepted) {
227 229 impl->m_Plot.replot();
228 230 }
229 231 }
230 232 }
231 233
232 234 void VisualizationGraphRenderingDelegate::onMouseMove(QMouseEvent *event) noexcept
233 235 {
234 236 // Cancels pending refresh
235 237 impl->m_TracerTimer.disconnect();
236 238
237 239 // Reinits tracers
238 240 impl->m_PointTracer->setGraph(nullptr);
239 241 impl->m_PointTracer->setVisible(false);
240 242 impl->m_Plot.replot();
241 243
242 244 QString tooltip{};
243 245
244 246 // Gets the graph under the mouse position
245 247 auto eventPos = event->pos();
246 248 if (auto graph = qobject_cast<QCPGraph *>(impl->m_Plot.plottableAt(eventPos))) {
247 249 auto mouseKey = graph->keyAxis()->pixelToCoord(eventPos.x());
248 250 auto graphData = graph->data();
249 251
250 252 // Gets the closest data point to the mouse
251 253 auto graphDataIt = graphData->findBegin(mouseKey);
252 254 if (graphDataIt != graphData->constEnd()) {
253 255 // Sets tooltip
254 256 auto key = formatValue(graphDataIt->key, *graph->keyAxis());
255 257 auto value = formatValue(graphDataIt->value, *graph->valueAxis());
256 258 tooltip = GRAPH_TOOLTIP_FORMAT.arg(key, value);
257 259
258 260 // Displays point tracer
259 261 impl->m_PointTracer->setGraph(graph);
260 262 impl->m_PointTracer->setGraphKey(graphDataIt->key);
261 263 impl->m_PointTracer->setLayer(
262 264 impl->m_Plot.layer("main")); // Tracer is set on top of the plot's main layer
263 265 impl->m_PointTracer->setVisible(true);
264 266 impl->m_Plot.replot();
265 267 }
266 268 }
267 269 else if (auto colorMap = qobject_cast<QCPColorMap *>(impl->m_Plot.plottableAt(eventPos))) {
268 270 // Gets x and y coords
269 271 auto x = colorMap->keyAxis()->pixelToCoord(eventPos.x());
270 272 auto y = colorMap->valueAxis()->pixelToCoord(eventPos.y());
271 273
272 274 // Calculates x and y cell indexes, and retrieves the underlying value
273 275 auto xCellIndex = colorMapCellIndex(*colorMap, x, true);
274 276 auto yCellIndex = colorMapCellIndex(*colorMap, y, false);
275 277 auto value = colorMap->data()->cell(xCellIndex, yCellIndex);
276 278
279 // Sets tooltips
280 tooltip = COLORMAP_TOOLTIP_FORMAT.arg(formatValue(x, *colorMap->keyAxis()),
281 formatValue(y, *colorMap->valueAxis()),
282 formatValue(value, *colorMap->colorScale()->axis()));
277 283 }
278 284
279 285 if (!tooltip.isEmpty()) {
280 286 // Starts timer to show tooltip after timeout
281 287 auto showTooltip = [tooltip, eventPos, this]() {
282 288 QToolTip::showText(impl->m_Plot.mapToGlobal(eventPos) + TOOLTIP_OFFSET, tooltip,
283 289 &impl->m_Plot, TOOLTIP_RECT);
284 290 };
285 291
286 292 QObject::connect(&impl->m_TracerTimer, &QTimer::timeout, showTooltip);
287 293 impl->m_TracerTimer.start();
288 294 }
289 295 }
290 296
291 297 void VisualizationGraphRenderingDelegate::onPlotUpdated() noexcept
292 298 {
293 299 // Updates color scale bounds
294 300 impl->m_ColorScale.updateDataRange();
295 301 impl->m_Plot.replot();
296 302 }
297 303
298 304 void VisualizationGraphRenderingDelegate::setAxesProperties(
299 305 std::shared_ptr<IDataSeries> dataSeries) noexcept
300 306 {
301 307 // Stores x-axis label to be able to retrieve it when x-axis pixmap is unselected
302 308 impl->m_XAxisLabel = dataSeries->xAxisUnit().m_Name;
303 309
304 310 auto axisHelper = IAxisHelperFactory::create(dataSeries);
305 311 axisHelper->setProperties(impl->m_Plot, impl->m_ColorScale);
306 312
307 313 // Updates x-axis state
308 314 impl->updateXAxisState();
309 315
310 316 impl->m_Plot.layer(AXES_LAYER)->replot();
311 317 }
312 318
313 319 void VisualizationGraphRenderingDelegate::setPlottablesProperties(
314 320 std::shared_ptr<IDataSeries> dataSeries, PlottablesMap &plottables) noexcept
315 321 {
316 322 auto plottablesHelper = IPlottablesHelperFactory::create(dataSeries);
317 323 plottablesHelper->setProperties(plottables);
318 324 }
319 325
320 326 void VisualizationGraphRenderingDelegate::showGraphOverlay(bool show) noexcept
321 327 {
322 328 auto overlay = impl->m_Plot.layer(OVERLAY_LAYER);
323 329 overlay->setVisible(show);
324 330 overlay->replot();
325 331 }
General Comments 0
You need to be logged in to leave comments. Login now