From 7eb12825208de8aa5f5b9ee7cc37d1b0439d17e4 2017-08-02 09:31:38 From: Alexandre Leroux Date: 2017-08-02 09:31:38 Subject: [PATCH] Merge branch 'feature/GraphTooltip' into develop --- diff --git a/gui/include/Visualization/VisualizationGraphRenderingDelegate.h b/gui/include/Visualization/VisualizationGraphRenderingDelegate.h new file mode 100644 index 0000000..7bdd366 --- /dev/null +++ b/gui/include/Visualization/VisualizationGraphRenderingDelegate.h @@ -0,0 +1,20 @@ +#ifndef SCIQLOP_VISUALIZATIONGRAPHRENDERINGDELEGATE_H +#define SCIQLOP_VISUALIZATIONGRAPHRENDERINGDELEGATE_H + +#include + +class QCustomPlot; +class QMouseEvent; + +class VisualizationGraphRenderingDelegate { +public: + explicit VisualizationGraphRenderingDelegate(QCustomPlot &plot); + + void onMouseMove(QMouseEvent *event) noexcept; + +private: + class VisualizationGraphRenderingDelegatePrivate; + spimpl::unique_impl_ptr impl; +}; + +#endif // SCIQLOP_VISUALIZATIONGRAPHRENDERINGDELEGATE_H diff --git a/gui/include/Visualization/VisualizationGraphWidget.h b/gui/include/Visualization/VisualizationGraphWidget.h index 1f31ed2..a015f24 100644 --- a/gui/include/Visualization/VisualizationGraphWidget.h +++ b/gui/include/Visualization/VisualizationGraphWidget.h @@ -69,6 +69,8 @@ private slots: /// Rescale the X axe to range parameter void onRangeChanged(const QCPRange &t1, const QCPRange &t2); + /// Slot called when a mouse move was made + void onMouseMove(QMouseEvent *event) noexcept; /// Slot called when a mouse wheel was made, to perform some processing before the zoom is done void onMouseWheel(QWheelEvent *event) noexcept; /// Slot called when a mouse press was made, to activate the calibration of a graph diff --git a/gui/src/Visualization/VisualizationGraphRenderingDelegate.cpp b/gui/src/Visualization/VisualizationGraphRenderingDelegate.cpp new file mode 100644 index 0000000..3161f83 --- /dev/null +++ b/gui/src/Visualization/VisualizationGraphRenderingDelegate.cpp @@ -0,0 +1,118 @@ +#include "Visualization/VisualizationGraphRenderingDelegate.h" +#include "Visualization/qcustomplot.h" + +namespace { + +const auto DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd hh:mm:ss:zzz"); + +const auto TEXT_TRACER_FORMAT = QStringLiteral("key: %1\nvalue: %2"); + +/// Timeout after which a tracer is displayed +const auto TRACER_TIMEOUT = 500; + +/// Formats a data value according to the axis on which it is present +QString formatValue(double value, const QCPAxis &axis) +{ + // If the axis is a time axis, formats the value as a date + return qSharedPointerDynamicCast(axis.ticker()) + ? QCPAxisTickerDateTime::keyToDateTime(value).toString(DATETIME_FORMAT) + : QString::number(value); +} + +void initPointTracerStyle(QCPItemTracer &tracer) noexcept +{ + tracer.setInterpolating(false); + tracer.setStyle(QCPItemTracer::tsPlus); + tracer.setPen(QPen(Qt::black)); + tracer.setBrush(Qt::black); + tracer.setSize(10); +} + +void initTextTracerStyle(QCPItemText &tracer) noexcept +{ + tracer.setPen(QPen{Qt::gray}); + tracer.setBrush(Qt::white); + tracer.setPadding(QMargins{6, 6, 6, 6}); + tracer.setPositionAlignment(Qt::AlignTop | Qt::AlignLeft); + tracer.setTextAlignment(Qt::AlignLeft); +} + +} // namespace + +struct VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegatePrivate { + explicit VisualizationGraphRenderingDelegatePrivate(QCustomPlot &plot) + : m_Plot{plot}, + m_PointTracer{new QCPItemTracer{&plot}}, + m_TextTracer{new QCPItemText{&plot}}, + m_TracerTimer{} + { + initPointTracerStyle(*m_PointTracer); + initTextTracerStyle(*m_TextTracer); + + m_TracerTimer.setInterval(TRACER_TIMEOUT); + m_TracerTimer.setSingleShot(true); + } + + QCustomPlot &m_Plot; + QCPItemTracer *m_PointTracer; + QCPItemText *m_TextTracer; + QTimer m_TracerTimer; +}; + +VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegate(QCustomPlot &plot) + : impl{spimpl::make_unique_impl(plot)} +{ +} + +void VisualizationGraphRenderingDelegate::onMouseMove(QMouseEvent *event) noexcept +{ + // Cancels pending refresh + impl->m_TracerTimer.disconnect(); + + auto showTracers = [ eventPos = event->pos(), this ]() + { + // Lambda function to display a tracer + auto displayTracer = [this](auto &tracer) { + // Tracer is set on top of the plot's main layer + tracer.setLayer(impl->m_Plot.layer("main")); + tracer.setVisible(true); + impl->m_Plot.replot(); + }; + + // Reinits tracers + impl->m_PointTracer->setGraph(nullptr); + impl->m_PointTracer->setVisible(false); + impl->m_TextTracer->setVisible(false); + impl->m_Plot.replot(); + + // Gets the graph under the mouse position + if (auto graph = qobject_cast(impl->m_Plot.plottableAt(eventPos))) { + auto mouseKey = graph->keyAxis()->pixelToCoord(eventPos.x()); + auto graphData = graph->data(); + + // Gets the closest data point to the mouse + auto graphDataIt = graphData->findBegin(mouseKey); + if (graphDataIt != graphData->constEnd()) { + auto key = formatValue(graphDataIt->key, *graph->keyAxis()); + auto value = formatValue(graphDataIt->value, *graph->valueAxis()); + impl->m_TextTracer->setText(TEXT_TRACER_FORMAT.arg(key, value)); + + // Displays point tracer + impl->m_PointTracer->setGraph(graph); + impl->m_PointTracer->setGraphKey(mouseKey); + displayTracer(*impl->m_PointTracer); + + // Displays text tracer + auto tracerPosition = impl->m_TextTracer->position; + tracerPosition->setAxes(graph->keyAxis(), graph->valueAxis()); + tracerPosition->setCoords(impl->m_PointTracer->position->key(), + impl->m_PointTracer->position->value()); + displayTracer(*impl->m_TextTracer); + } + } + }; + + // Starts the timer to display tracers at timeout + QObject::connect(&impl->m_TracerTimer, &QTimer::timeout, showTracers); + impl->m_TracerTimer.start(); +} diff --git a/gui/src/Visualization/VisualizationGraphWidget.cpp b/gui/src/Visualization/VisualizationGraphWidget.cpp index 275a284..eb70fb8 100644 --- a/gui/src/Visualization/VisualizationGraphWidget.cpp +++ b/gui/src/Visualization/VisualizationGraphWidget.cpp @@ -1,6 +1,7 @@ #include "Visualization/VisualizationGraphWidget.h" #include "Visualization/IVisualizationWidgetVisitor.h" #include "Visualization/VisualizationGraphHelper.h" +#include "Visualization/VisualizationGraphRenderingDelegate.h" #include "ui_VisualizationGraphWidget.h" #include @@ -33,17 +34,21 @@ double toleranceValue(const QString &key, double defaultValue) noexcept struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate { - explicit VisualizationGraphWidgetPrivate() : m_DoSynchronize{true}, m_IsCalibration{false} {} - + explicit VisualizationGraphWidgetPrivate() + : m_DoSynchronize{true}, m_IsCalibration{false}, m_RenderingDelegate{nullptr} + { + } // Return the operation when range changed VisualizationGraphWidgetZoomType getZoomType(const QCPRange &t1, const QCPRange &t2); // 1 variable -> n qcpplot std::multimap, QCPAbstractPlottable *> m_VariableToPlotMultiMap; - bool m_DoSynchronize; bool m_IsCalibration; + QCPItemTracer *m_TextTracer; + /// Delegate used to attach rendering features to the plot + std::unique_ptr m_RenderingDelegate; }; VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent) @@ -53,6 +58,9 @@ VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget { ui->setupUi(this); + // The delegate must be initialized after the ui as it uses the plot + impl->m_RenderingDelegate = std::make_unique(*ui->widget); + ui->graphNameLabel->setText(name); // 'Close' options : widget is deleted when closed @@ -65,9 +73,11 @@ VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation ui->widget->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom); ui->widget->axisRect()->setRangeDrag(Qt::Horizontal); + connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress); connect(ui->widget, &QCustomPlot::mouseRelease, this, &VisualizationGraphWidget::onMouseRelease); + connect(ui->widget, &QCustomPlot::mouseMove, this, &VisualizationGraphWidget::onMouseMove); connect(ui->widget, &QCustomPlot::mouseWheel, this, &VisualizationGraphWidget::onMouseWheel); connect(ui->widget->xAxis, static_cast( &QCPAxis::rangeChanged), @@ -329,6 +339,12 @@ void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange } } +void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept +{ + // Handles plot rendering when mouse is moving + impl->m_RenderingDelegate->onMouseMove(event); +} + void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept { auto zoomOrientations = QFlags{};