diff --git a/app/src/MainWindow.cpp b/app/src/MainWindow.cpp index 4e11f1c..3dcbf5e 100644 --- a/app/src/MainWindow.cpp +++ b/app/src/MainWindow.cpp @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include @@ -172,6 +174,10 @@ MainWindow::MainWindow(QWidget *parent) connect(&sqpApp->dataSourceController(), SIGNAL(dataSourceItemSet(DataSourceItem *)), m_Ui->dataSourceWidget, SLOT(addDataSource(DataSourceItem *))); + qRegisterMetaType >(); + connect(&sqpApp->visualizationController(), SIGNAL(variableCreated(std::shared_ptr)), + m_Ui->view, SLOT(displayVariable(std::shared_ptr))); + /* QLopGUI::registerMenuBar(menuBar()); this->setWindowIcon(QIcon(":/sciqlopLOGO.svg")); this->m_progressWidget = new QWidget(); diff --git a/core/include/Data/DataSeries.h b/core/include/Data/DataSeries.h index f4315f0..19df6c8 100644 --- a/core/include/Data/DataSeries.h +++ b/core/include/Data/DataSeries.h @@ -21,30 +21,30 @@ public: std::shared_ptr > xAxisData() override { return m_XAxisData; } /// @sa IDataSeries::xAxisUnit() - QString xAxisUnit() const override { return m_XAxisUnit; } + Unit xAxisUnit() const override { return m_XAxisUnit; } /// @return the values dataset std::shared_ptr > valuesData() const { return m_ValuesData; } /// @sa IDataSeries::valuesUnit() - QString valuesUnit() const override { return m_ValuesUnit; } + Unit valuesUnit() const override { return m_ValuesUnit; } protected: /// Protected ctor (DataSeries is abstract) - explicit DataSeries(std::shared_ptr > xAxisData, const QString &xAxisUnit, - std::shared_ptr > valuesData, const QString &valuesUnit) + explicit DataSeries(std::shared_ptr > xAxisData, Unit xAxisUnit, + std::shared_ptr > valuesData, Unit valuesUnit) : m_XAxisData{xAxisData}, - m_XAxisUnit{xAxisUnit}, + m_XAxisUnit{std::move(xAxisUnit)}, m_ValuesData{valuesData}, - m_ValuesUnit{valuesUnit} + m_ValuesUnit{std::move(valuesUnit)} { } private: std::shared_ptr > m_XAxisData; - QString m_XAxisUnit; + Unit m_XAxisUnit; std::shared_ptr > m_ValuesData; - QString m_ValuesUnit; + Unit m_ValuesUnit; }; #endif // SCIQLOP_DATASERIES_H diff --git a/core/include/Data/IDataSeries.h b/core/include/Data/IDataSeries.h index 0278a8e..26a80a1 100644 --- a/core/include/Data/IDataSeries.h +++ b/core/include/Data/IDataSeries.h @@ -8,6 +8,16 @@ template class ArrayData; +struct Unit { + explicit Unit(const QString &name = {}, bool timeUnit = false) + : m_Name{name}, m_TimeUnit{timeUnit} + { + } + + QString m_Name; ///< Unit name + bool m_TimeUnit; ///< The unit is a unit of time +}; + /** * @brief The IDataSeries aims to declare a data series. * @@ -29,9 +39,9 @@ public: /// Returns the x-axis dataset virtual std::shared_ptr > xAxisData() = 0; - virtual QString xAxisUnit() const = 0; + virtual Unit xAxisUnit() const = 0; - virtual QString valuesUnit() const = 0; + virtual Unit valuesUnit() const = 0; }; #endif // SCIQLOP_IDATASERIES_H diff --git a/core/include/Data/ScalarSeries.h b/core/include/Data/ScalarSeries.h index 5dd39b0..e740bec 100644 --- a/core/include/Data/ScalarSeries.h +++ b/core/include/Data/ScalarSeries.h @@ -14,7 +14,7 @@ public: * @param xAxisUnit x-axis unit * @param valuesUnit values unit */ - explicit ScalarSeries(int size, const QString &xAxisUnit, const QString &valuesUnit); + explicit ScalarSeries(int size, Unit xAxisUnit, Unit valuesUnit); /** * Sets data for a specific index. The index has to be valid to be effective diff --git a/core/include/Variable/Variable.h b/core/include/Variable/Variable.h index d67e115..228202c 100644 --- a/core/include/Variable/Variable.h +++ b/core/include/Variable/Variable.h @@ -21,6 +21,9 @@ public: void addDataSeries(std::unique_ptr dataSeries) noexcept; + /// @return the data of the variable, nullptr if there is no data + IDataSeries *dataSeries() const noexcept; + private: class VariablePrivate; spimpl::unique_impl_ptr impl; diff --git a/core/include/Visualization/VisualizationController.h b/core/include/Visualization/VisualizationController.h index 5a34df3..9664fe6 100644 --- a/core/include/Visualization/VisualizationController.h +++ b/core/include/Visualization/VisualizationController.h @@ -25,10 +25,11 @@ public: explicit VisualizationController(QObject *parent = 0); virtual ~VisualizationController(); -public slots: - /// Slot called when a variable has been created in SciQlop - void onVariableCreated(std::shared_ptr variable) noexcept; +signals: + /// Signal emitted when a variable has been created in SciQlop + void variableCreated(std::shared_ptr variable); +public slots: /// Manage init/end of the controller void initialize(); void finalize(); diff --git a/core/src/Data/ScalarSeries.cpp b/core/src/Data/ScalarSeries.cpp index f3f4fc2..bd07739 100644 --- a/core/src/Data/ScalarSeries.cpp +++ b/core/src/Data/ScalarSeries.cpp @@ -1,8 +1,8 @@ #include -ScalarSeries::ScalarSeries(int size, const QString &xAxisUnit, const QString &valuesUnit) - : DataSeries{std::make_shared >(size), xAxisUnit, - std::make_shared >(size), valuesUnit} +ScalarSeries::ScalarSeries(int size, Unit xAxisUnit, Unit valuesUnit) + : DataSeries{std::make_shared >(size), std::move(xAxisUnit), + std::make_shared >(size), std::move(valuesUnit)} { } diff --git a/core/src/Variable/Variable.cpp b/core/src/Variable/Variable.cpp index 02a2713..544d73d 100644 --- a/core/src/Variable/Variable.cpp +++ b/core/src/Variable/Variable.cpp @@ -41,3 +41,8 @@ void Variable::addDataSeries(std::unique_ptr dataSeries) noexcept } /// @todo : else, merge the two data series (if possible) } + +IDataSeries *Variable::dataSeries() const noexcept +{ + return impl->m_DataSeries.get(); +} diff --git a/core/src/Visualization/VisualizationController.cpp b/core/src/Visualization/VisualizationController.cpp index 6e3bcc6..0f4d9fd 100644 --- a/core/src/Visualization/VisualizationController.cpp +++ b/core/src/Visualization/VisualizationController.cpp @@ -29,12 +29,6 @@ VisualizationController::~VisualizationController() this->waitForFinish(); } -void VisualizationController::onVariableCreated(std::shared_ptr variable) noexcept -{ - /// @todo ALX : make new graph for the variable - qCDebug(LOG_VisualizationController()) << "new variable to display"; -} - void VisualizationController::initialize() { qCDebug(LOG_VisualizationController()) << tr("VisualizationController init") diff --git a/gui/include/Visualization/GraphPlottablesFactory.h b/gui/include/Visualization/GraphPlottablesFactory.h new file mode 100644 index 0000000..82c06ac --- /dev/null +++ b/gui/include/Visualization/GraphPlottablesFactory.h @@ -0,0 +1,30 @@ +#ifndef SCIQLOP_GRAPHPLOTTABLESFACTORY_H +#define SCIQLOP_GRAPHPLOTTABLESFACTORY_H + +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(LOG_GraphPlottablesFactory) + +class QCPAbstractPlottable; +class QCustomPlot; +class Variable; + +/** + * @brief The GraphPlottablesFactory class aims to create the QCustomPlot components relative to a + * variable, depending on the data series of this variable + */ +struct GraphPlottablesFactory { + /** + * Creates (if possible) the QCustomPlot components relative to the variable passed in + * parameter, and adds these to the plot passed in parameter. + * @param variable the variable for which to create the components + * @param plot the plot in which to add the created components. It takes ownership of these + * components. + * @return the list of the components created + */ + static QVector create(const Variable *variable, + QCustomPlot &plot) noexcept; +}; + +#endif // SCIQLOP_GRAPHPLOTTABLESFACTORY_H diff --git a/gui/include/Visualization/VisualizationGraphWidget.h b/gui/include/Visualization/VisualizationGraphWidget.h index fc927c9..cee73b7 100644 --- a/gui/include/Visualization/VisualizationGraphWidget.h +++ b/gui/include/Visualization/VisualizationGraphWidget.h @@ -34,6 +34,10 @@ private: class VisualizationGraphWidgetPrivate; spimpl::unique_impl_ptr impl; + +private slots: + /// Slot called when a mouse wheel was made, to perform some processing before the zoom is done + void onMouseWheel(QWheelEvent *event) noexcept; }; #endif // SCIQLOP_VISUALIZATIONGRAPHWIDGET_H diff --git a/gui/include/Visualization/VisualizationWidget.h b/gui/include/Visualization/VisualizationWidget.h index b4af372..4d12b0e 100644 --- a/gui/include/Visualization/VisualizationWidget.h +++ b/gui/include/Visualization/VisualizationWidget.h @@ -6,6 +6,7 @@ #include #include +class Variable; class VisualizationTabWidget; Q_DECLARE_LOGGING_CATEGORY(LOG_VisualizationWidget) @@ -35,6 +36,15 @@ public: void close() override; QString name() const; +public slots: + /** + * Displays a variable in a new graph of a new zone of the current tab + * @param variable the variable to display + * @todo this is a temporary method that will be replaced by own actions for each type of + * visualization widget + */ + void displayVariable(std::shared_ptr variable) noexcept; + private: Ui::VisualizationWidget *ui; }; diff --git a/gui/src/SqpApplication.cpp b/gui/src/SqpApplication.cpp index 8f2d0fa..5e46e93 100644 --- a/gui/src/SqpApplication.cpp +++ b/gui/src/SqpApplication.cpp @@ -31,7 +31,7 @@ public: qRegisterMetaType >(); connect(m_VariableController.get(), SIGNAL(variableCreated(std::shared_ptr)), m_VisualizationController.get(), - SLOT(onVariableCreated(std::shared_ptr))); + SIGNAL(variableCreated(std::shared_ptr))); m_DataSourceController->moveToThread(&m_DataSourceControllerThread); m_VariableController->moveToThread(&m_VariableControllerThread); diff --git a/gui/src/Visualization/GraphPlottablesFactory.cpp b/gui/src/Visualization/GraphPlottablesFactory.cpp new file mode 100644 index 0000000..89a3e01 --- /dev/null +++ b/gui/src/Visualization/GraphPlottablesFactory.cpp @@ -0,0 +1,91 @@ +#include "Visualization/GraphPlottablesFactory.h" +#include "Visualization/qcustomplot.h" + +#include + +#include + +Q_LOGGING_CATEGORY(LOG_GraphPlottablesFactory, "GraphPlottablesFactory") + +namespace { + +/// Format for datetimes on a axis +const auto DATETIME_TICKER_FORMAT = QStringLiteral("yyyy/MM/dd \nhh:mm:ss"); + +/// Generates the appropriate ticker for an axis, depending on whether the axis displays time or +/// non-time data +QSharedPointer axisTicker(bool isTimeAxis) +{ + if (isTimeAxis) { + auto dateTicker = QSharedPointer::create(); + dateTicker->setDateTimeFormat(DATETIME_TICKER_FORMAT); + + return dateTicker; + } + else { + // default ticker + return QSharedPointer::create(); + } +} + +QCPAbstractPlottable *createScalarSeriesComponent(ScalarSeries &scalarSeries, QCustomPlot &plot) +{ + auto component = plot.addGraph(); + + if (component) { + // Graph data + component->setData(scalarSeries.xAxisData()->data(), scalarSeries.valuesData()->data(), + true); + + // Axes properties + /// @todo : for the moment, no control is performed on the axes: the units and the tickers + /// are fixed for the default x-axis and y-axis of the plot, and according to the new graph + + auto setAxisProperties = [](auto axis, const auto &unit) { + // label (unit name) + axis->setLabel(unit.m_Name); + + // ticker (depending on the type of unit) + axis->setTicker(axisTicker(unit.m_TimeUnit)); + }; + setAxisProperties(plot.xAxis, scalarSeries.xAxisUnit()); + setAxisProperties(plot.yAxis, scalarSeries.valuesUnit()); + + // Display all data + component->rescaleAxes(); + + plot.replot(); + } + else { + qCDebug(LOG_GraphPlottablesFactory()) + << QObject::tr("Can't create graph for the scalar series"); + } + + return component; +} + +} // namespace + +QVector GraphPlottablesFactory::create(const Variable *variable, + QCustomPlot &plot) noexcept +{ + auto result = QVector{}; + + if (variable) { + // Gets the data series of the variable to call the creation of the right components + // according to its type + if (auto scalarSeries = dynamic_cast(variable->dataSeries())) { + result.append(createScalarSeriesComponent(*scalarSeries, plot)); + } + else { + qCDebug(LOG_GraphPlottablesFactory()) + << QObject::tr("Can't create graph plottables : unmanaged data series type"); + } + } + else { + qCDebug(LOG_GraphPlottablesFactory()) + << QObject::tr("Can't create graph plottables : the variable is null"); + } + + return result; +} diff --git a/gui/src/Visualization/VisualizationGraphWidget.cpp b/gui/src/Visualization/VisualizationGraphWidget.cpp index 792c566..ea311cf 100644 --- a/gui/src/Visualization/VisualizationGraphWidget.cpp +++ b/gui/src/Visualization/VisualizationGraphWidget.cpp @@ -1,15 +1,25 @@ #include "Visualization/VisualizationGraphWidget.h" +#include "Visualization/GraphPlottablesFactory.h" #include "ui_VisualizationGraphWidget.h" #include #include +namespace { + +/// Key pressed to enable zoom on horizontal axis +const auto HORIZONTAL_ZOOM_MODIFIER = Qt::NoModifier; + +/// Key pressed to enable zoom on vertical axis +const auto VERTICAL_ZOOM_MODIFIER = Qt::ControlModifier; + +} // namespace + struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate { // 1 variable -> n qcpplot - std::unordered_map, std::unique_ptr > - m_VariableToPlotMap; + std::unordered_map, QCPAbstractPlottable *> m_VariableToPlotMap; }; VisualizationGraphWidget::VisualizationGraphWidget(QWidget *parent) @@ -18,6 +28,13 @@ VisualizationGraphWidget::VisualizationGraphWidget(QWidget *parent) impl{spimpl::make_unique_impl()} { ui->setupUi(this); + + // Set qcpplot properties : + // - Drag (on x-axis) and zoom are enabled + // - 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::mouseWheel, this, &VisualizationGraphWidget::onMouseWheel); } VisualizationGraphWidget::~VisualizationGraphWidget() @@ -27,7 +44,12 @@ VisualizationGraphWidget::~VisualizationGraphWidget() void VisualizationGraphWidget::addVariable(std::shared_ptr variable) { - // todo: first check is variable contains data then check how many plot have to be created + // Uses delegate to create the qcpplot components according to the variable + auto createdPlottables = GraphPlottablesFactory::create(variable.get(), *ui->widget); + + for (auto createdPlottable : qAsConst(createdPlottables)) { + impl->m_VariableToPlotMap.insert({variable, createdPlottable}); + } } void VisualizationGraphWidget::accept(IVisualizationWidget *visitor) @@ -45,3 +67,20 @@ QString VisualizationGraphWidget::name() const { return QStringLiteral("MainView"); } + +void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept +{ + auto zoomOrientations = QFlags{}; + + // Lambda that enables a zoom orientation if the key modifier related to this orientation has + // been pressed + auto enableOrientation + = [&zoomOrientations, event](const auto &orientation, const auto &modifier) { + auto orientationEnabled = event->modifiers().testFlag(modifier); + zoomOrientations.setFlag(orientation, orientationEnabled); + }; + enableOrientation(Qt::Vertical, VERTICAL_ZOOM_MODIFIER); + enableOrientation(Qt::Horizontal, HORIZONTAL_ZOOM_MODIFIER); + + ui->widget->axisRect()->setRangeZoom(zoomOrientations); +} diff --git a/gui/src/Visualization/VisualizationWidget.cpp b/gui/src/Visualization/VisualizationWidget.cpp index 5fa0cda..3ca7b00 100644 --- a/gui/src/Visualization/VisualizationWidget.cpp +++ b/gui/src/Visualization/VisualizationWidget.cpp @@ -1,5 +1,7 @@ #include "Visualization/VisualizationWidget.h" +#include "Visualization/VisualizationGraphWidget.h" #include "Visualization/VisualizationTabWidget.h" +#include "Visualization/VisualizationZoneWidget.h" #include "Visualization/qcustomplot.h" #include "ui_VisualizationWidget.h" @@ -78,3 +80,26 @@ QString VisualizationWidget::name() const { return QStringLiteral("MainView"); } + +void VisualizationWidget::displayVariable(std::shared_ptr variable) noexcept +{ + if (auto currentTab = dynamic_cast(ui->tabWidget->currentWidget())) { + if (auto newZone = currentTab->createZone()) { + if (auto newGraph = newZone->createGraph()) { + newGraph->addVariable(variable); + } + else { + qCDebug(LOG_VisualizationWidget()) + << tr("Can't display the variable : can't create the graph"); + } + } + else { + qCDebug(LOG_VisualizationWidget()) + << tr("Can't display the variable : can't create a new zone in the current tab"); + } + } + else { + qCDebug(LOG_VisualizationWidget()) + << tr("Can't display the variable : there is no current tab"); + } +} diff --git a/plugins/mockplugin/src/CosinusProvider.cpp b/plugins/mockplugin/src/CosinusProvider.cpp index 8240f30..24cc69a 100644 --- a/plugins/mockplugin/src/CosinusProvider.cpp +++ b/plugins/mockplugin/src/CosinusProvider.cpp @@ -19,7 +19,7 @@ CosinusProvider::retrieveData(const DataProviderParameters ¶meters) const // Generates scalar series containing cosinus values (one value per second) auto scalarSeries - = std::make_unique(end - start, QStringLiteral("t"), QStringLiteral("")); + = std::make_unique(end - start, Unit{QStringLiteral("t"), true}, Unit{}); auto dataIndex = 0; for (auto time = start; time < end; ++time, ++dataIndex) {