##// END OF EJS Templates
Merge pull request 333 from SciQLop-fork develop...
trabillard -
r926:7347d93117b4 merge
parent child
Show More
@@ -0,0 +1,34
1 #ifndef SCIQLOP_AXISRENDERINGUTILS_H
2 #define SCIQLOP_AXISRENDERINGUTILS_H
3
4 #include <memory>
5
6 #include <QtCore/QString>
7
8 class IDataSeries;
9 class QCPAxis;
10 class QCPColorScale;
11 class QCustomPlot;
12
13 /// Formats a data value according to the axis on which it is present
14 QString formatValue(double value, const QCPAxis &axis);
15
16 /**
17 * Helper used to handle axes rendering
18 */
19 struct IAxisHelper {
20 virtual ~IAxisHelper() noexcept = default;
21
22 /// Set properties of the plot's axes and the color scale associated to plot passed as
23 /// parameters
24 /// @param plot the plot for which to set axe properties
25 /// @param colorScale the color scale for which to set properties
26 virtual void setProperties(QCustomPlot &plot, QCPColorScale &colorScale) = 0;
27 };
28
29 struct IAxisHelperFactory {
30 /// Creates IAxisHelper according to a data series
31 static std::unique_ptr<IAxisHelper> create(std::shared_ptr<IDataSeries> dataSeries) noexcept;
32 };
33
34 #endif // SCIQLOP_AXISRENDERINGUTILS_H
@@ -0,0 +1,29
1 #ifndef SCIQLOP_PLOTTABLESRENDERINGUTILS_H
2 #define SCIQLOP_PLOTTABLESRENDERINGUTILS_H
3
4 #include <Visualization/VisualizationDefs.h>
5
6 #include <memory>
7
8 class IDataSeries;
9 class QCPColorScale;
10 class QCustomPlot;
11
12 /**
13 * Helper used to handle plottables rendering
14 */
15 struct IPlottablesHelper {
16 virtual ~IPlottablesHelper() noexcept = default;
17
18 /// Set properties of the plottables passed as parameter
19 /// @param plottables the plottables for which to set properties
20 virtual void setProperties(PlottablesMap &plottables) = 0;
21 };
22
23 struct IPlottablesHelperFactory {
24 /// Creates IPlottablesHelper according to a data series
25 static std::unique_ptr<IPlottablesHelper>
26 create(std::shared_ptr<IDataSeries> dataSeries) noexcept;
27 };
28
29 #endif // SCIQLOP_PLOTTABLESRENDERINGUTILS_H
@@ -0,0 +1,163
1 #include "Visualization/AxisRenderingUtils.h"
2
3 #include <Data/ScalarSeries.h>
4 #include <Data/SpectrogramSeries.h>
5 #include <Data/VectorSeries.h>
6
7 #include <Visualization/qcustomplot.h>
8
9 namespace {
10
11 const auto DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd hh:mm:ss:zzz");
12
13 /// Format for datetimes on a axis
14 const auto DATETIME_TICKER_FORMAT = QStringLiteral("yyyy/MM/dd \nhh:mm:ss");
15
16 /// Generates the appropriate ticker for an axis, depending on whether the axis displays time or
17 /// non-time data
18 QSharedPointer<QCPAxisTicker> axisTicker(bool isTimeAxis)
19 {
20 if (isTimeAxis) {
21 auto dateTicker = QSharedPointer<QCPAxisTickerDateTime>::create();
22 dateTicker->setDateTimeFormat(DATETIME_TICKER_FORMAT);
23 dateTicker->setDateTimeSpec(Qt::UTC);
24
25 return dateTicker;
26 }
27 else {
28 // default ticker
29 return QSharedPointer<QCPAxisTicker>::create();
30 }
31 }
32
33 /**
34 * Sets properties of the axis passed as parameter
35 * @param axis the axis to set
36 * @param unit the unit to set for the axis
37 * @param scaleType the scale type to set for the axis
38 */
39 void setAxisProperties(QCPAxis &axis, const Unit &unit,
40 QCPAxis::ScaleType scaleType = QCPAxis::stLinear)
41 {
42 // label (unit name)
43 axis.setLabel(unit.m_Name);
44
45 // scale type
46 axis.setScaleType(scaleType);
47
48 // ticker (depending on the type of unit)
49 axis.setTicker(axisTicker(unit.m_TimeUnit));
50 }
51
52 /**
53 * Delegate used to set axes properties
54 */
55 template <typename T, typename Enabled = void>
56 struct AxisSetter {
57 static void setProperties(T &, QCustomPlot &, QCPColorScale &)
58 {
59 // Default implementation does nothing
60 }
61 };
62
63 /**
64 * Specialization of AxisSetter for scalars and vectors
65 * @sa ScalarSeries
66 * @sa VectorSeries
67 */
68 template <typename T>
69 struct AxisSetter<T, typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
70 or std::is_base_of<VectorSeries, T>::value> > {
71 static void setProperties(T &dataSeries, QCustomPlot &plot, QCPColorScale &)
72 {
73 dataSeries.lockRead();
74 auto xAxisUnit = dataSeries.xAxisUnit();
75 auto valuesUnit = dataSeries.valuesUnit();
76 dataSeries.unlock();
77
78 setAxisProperties(*plot.xAxis, xAxisUnit);
79 setAxisProperties(*plot.yAxis, valuesUnit);
80 }
81 };
82
83 /**
84 * Specialization of AxisSetter for spectrograms
85 * @sa SpectrogramSeries
86 */
87 template <typename T>
88 struct AxisSetter<T, typename std::enable_if_t<std::is_base_of<SpectrogramSeries, T>::value> > {
89 static void setProperties(T &dataSeries, QCustomPlot &plot, QCPColorScale &colorScale)
90 {
91 dataSeries.lockRead();
92 auto xAxisUnit = dataSeries.xAxisUnit();
93 /// @todo ALX: use iterators here
94 auto yAxisUnit = dataSeries.yAxis().unit();
95 auto valuesUnit = dataSeries.valuesUnit();
96 dataSeries.unlock();
97
98 setAxisProperties(*plot.xAxis, xAxisUnit);
99 setAxisProperties(*plot.yAxis, yAxisUnit);
100
101 // Displays color scale in plot
102 plot.plotLayout()->insertRow(0);
103 plot.plotLayout()->addElement(0, 0, &colorScale);
104 colorScale.setType(QCPAxis::atTop);
105 colorScale.setMinimumMargins(QMargins{0, 0, 0, 0});
106
107 // Aligns color scale with axes
108 auto marginGroups = plot.axisRect()->marginGroups();
109 for (auto it = marginGroups.begin(), end = marginGroups.end(); it != end; ++it) {
110 colorScale.setMarginGroup(it.key(), it.value());
111 }
112
113 // Set color scale properties
114 colorScale.setLabel(valuesUnit.m_Name);
115 colorScale.setDataScaleType(QCPAxis::stLogarithmic); // Logarithmic scale
116 }
117 };
118
119 /**
120 * Default implementation of IAxisHelper, which takes data series to set axes properties
121 * @tparam T the data series' type
122 */
123 template <typename T>
124 struct AxisHelper : public IAxisHelper {
125 explicit AxisHelper(T &dataSeries) : m_DataSeries{dataSeries} {}
126
127 void setProperties(QCustomPlot &plot, QCPColorScale &colorScale) override
128 {
129 AxisSetter<T>::setProperties(m_DataSeries, plot, colorScale);
130 }
131
132 T &m_DataSeries;
133 };
134
135 } // namespace
136
137 QString formatValue(double value, const QCPAxis &axis)
138 {
139 // If the axis is a time axis, formats the value as a date
140 if (auto axisTicker = qSharedPointerDynamicCast<QCPAxisTickerDateTime>(axis.ticker())) {
141 return DateUtils::dateTime(value, axisTicker->dateTimeSpec()).toString(DATETIME_FORMAT);
142 }
143 else {
144 return QString::number(value);
145 }
146 }
147
148 std::unique_ptr<IAxisHelper>
149 IAxisHelperFactory::create(std::shared_ptr<IDataSeries> dataSeries) noexcept
150 {
151 if (auto scalarSeries = std::dynamic_pointer_cast<ScalarSeries>(dataSeries)) {
152 return std::make_unique<AxisHelper<ScalarSeries> >(*scalarSeries);
153 }
154 else if (auto spectrogramSeries = std::dynamic_pointer_cast<SpectrogramSeries>(dataSeries)) {
155 return std::make_unique<AxisHelper<SpectrogramSeries> >(*spectrogramSeries);
156 }
157 else if (auto vectorSeries = std::dynamic_pointer_cast<VectorSeries>(dataSeries)) {
158 return std::make_unique<AxisHelper<VectorSeries> >(*vectorSeries);
159 }
160 else {
161 return std::make_unique<AxisHelper<IDataSeries> >(*dataSeries);
162 }
163 }
@@ -0,0 +1,120
1 #include "Visualization/PlottablesRenderingUtils.h"
2
3 #include <Common/ColorUtils.h>
4
5 #include <Data/ScalarSeries.h>
6 #include <Data/SpectrogramSeries.h>
7 #include <Data/VectorSeries.h>
8
9 #include <Visualization/qcustomplot.h>
10
11 namespace {
12
13 /// Default gradient used for colormap
14 const auto DEFAULT_COLORMAP_GRADIENT = QCPColorGradient::gpJet;
15
16 /**
17 * Delegate used to set plottables properties
18 */
19 template <typename T, typename Enabled = void>
20 struct PlottablesSetter {
21 static void setProperties(T &, PlottablesMap &)
22 {
23 // Default implementation does nothing
24 }
25 };
26
27 /**
28 * Specialization of PlottablesSetter for scalars and vectors
29 * @sa ScalarSeries
30 * @sa VectorSeries
31 */
32 template <typename T>
33 struct PlottablesSetter<T, typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
34 or std::is_base_of<VectorSeries, T>::value> > {
35 static void setProperties(T &dataSeries, PlottablesMap &plottables)
36 {
37 // Gets the number of components of the data series
38 dataSeries.lockRead();
39 auto componentCount = dataSeries.valuesData()->componentCount();
40 dataSeries.unlock();
41
42 // Generates colors for each component
43 auto colors = ColorUtils::colors(Qt::blue, Qt::red, componentCount);
44
45 // For each component of the data series, creates a QCPGraph to add to the plot
46 for (auto i = 0; i < componentCount; ++i) {
47 auto graph = plottables.at(i);
48 graph->setPen(QPen{colors.at(i)});
49 }
50 }
51 };
52
53 /**
54 * Specialization of PlottablesSetter for spectrograms
55 * @sa SpectrogramSeries
56 */
57 template <typename T>
58 struct PlottablesSetter<T,
59 typename std::enable_if_t<std::is_base_of<SpectrogramSeries, T>::value> > {
60 static void setProperties(T &, PlottablesMap &plottables)
61 {
62 // Checks that for a spectrogram there is only one plottable, that is a colormap
63 if (plottables.size() != 1) {
64 return;
65 }
66
67 if (auto colormap = dynamic_cast<QCPColorMap *>(plottables.begin()->second)) {
68 colormap->setInterpolate(false); // No value interpolation
69 colormap->setTightBoundary(true);
70
71 // Finds color scale in the colormap's plot to associate with it
72 auto plot = colormap->parentPlot();
73 auto plotElements = plot->plotLayout()->elements(false);
74 for (auto plotElement : plotElements) {
75 if (auto colorScale = dynamic_cast<QCPColorScale *>(plotElement)) {
76 colormap->setColorScale(colorScale);
77 }
78 }
79
80 // Sets gradient used for color scale
81 colormap->setGradient(DEFAULT_COLORMAP_GRADIENT);
82 colormap->rescaleDataRange();
83 }
84 }
85 };
86
87 /**
88 * Default implementation of IPlottablesHelper, which takes data series to set plottables properties
89 * @tparam T the data series' type
90 */
91 template <typename T>
92 struct PlottablesHelper : public IPlottablesHelper {
93 explicit PlottablesHelper(T &dataSeries) : m_DataSeries{dataSeries} {}
94
95 void setProperties(PlottablesMap &plottables) override
96 {
97 PlottablesSetter<T>::setProperties(m_DataSeries, plottables);
98 }
99
100 T &m_DataSeries;
101 };
102
103 } // namespace
104
105 std::unique_ptr<IPlottablesHelper>
106 IPlottablesHelperFactory::create(std::shared_ptr<IDataSeries> dataSeries) noexcept
107 {
108 if (auto scalarSeries = std::dynamic_pointer_cast<ScalarSeries>(dataSeries)) {
109 return std::make_unique<PlottablesHelper<ScalarSeries> >(*scalarSeries);
110 }
111 else if (auto spectrogramSeries = std::dynamic_pointer_cast<SpectrogramSeries>(dataSeries)) {
112 return std::make_unique<PlottablesHelper<SpectrogramSeries> >(*spectrogramSeries);
113 }
114 else if (auto vectorSeries = std::dynamic_pointer_cast<VectorSeries>(dataSeries)) {
115 return std::make_unique<PlottablesHelper<VectorSeries> >(*vectorSeries);
116 }
117 else {
118 return std::make_unique<PlottablesHelper<IDataSeries> >(*dataSeries);
119 }
120 }
@@ -1,99 +1,101
1 1 #include <Data/OptionalAxis.h>
2 2
3 3 #include "Data/ArrayData.h"
4 4
5 5 OptionalAxis::OptionalAxis() : m_Defined{false}, m_Data{nullptr}, m_Unit{}
6 6 {
7 7 }
8 8
9 9 OptionalAxis::OptionalAxis(std::shared_ptr<ArrayData<1> > data, Unit unit)
10 10 : m_Defined{true}, m_Data{data}, m_Unit{std::move(unit)}
11 11 {
12 12 if (m_Data == nullptr) {
13 13 throw std::invalid_argument{"Data can't be null for a defined axis"};
14 14 }
15 15 }
16 16
17 17 OptionalAxis::OptionalAxis(const OptionalAxis &other)
18 18 : m_Defined{other.m_Defined},
19 19 m_Data{other.m_Data ? std::make_shared<ArrayData<1> >(*other.m_Data) : nullptr},
20 20 m_Unit{other.m_Unit}
21 21 {
22 22 }
23 23
24 24 OptionalAxis &OptionalAxis::operator=(OptionalAxis other)
25 25 {
26 26 std::swap(m_Defined, other.m_Defined);
27 27 std::swap(m_Data, other.m_Data);
28 28 std::swap(m_Unit, other.m_Unit);
29
30 return *this;
29 31 }
30 32
31 33 bool OptionalAxis::isDefined() const
32 34 {
33 35 return m_Defined;
34 36 }
35 37
36 38 double OptionalAxis::at(int index) const
37 39 {
38 40 if (m_Defined) {
39 41 return (index >= 0 && index < m_Data->size()) ? m_Data->at(index)
40 42 : std::numeric_limits<double>::quiet_NaN();
41 43 }
42 44 else {
43 45 return std::numeric_limits<double>::quiet_NaN();
44 46 }
45 47 }
46 48
47 49 std::pair<double, double> OptionalAxis::bounds() const
48 50 {
49 51 if (!m_Defined || m_Data->size() == 0) {
50 52 return std::make_pair(std::numeric_limits<double>::quiet_NaN(),
51 53 std::numeric_limits<double>::quiet_NaN());
52 54 }
53 55 else {
54 56
55 57 auto minIt = std::min_element(
56 58 m_Data->cbegin(), m_Data->cend(), [](const auto &it1, const auto &it2) {
57 59 return SortUtils::minCompareWithNaN(it1.first(), it2.first());
58 60 });
59 61
60 62 // Gets the iterator on the max of all values data
61 63 auto maxIt = std::max_element(
62 64 m_Data->cbegin(), m_Data->cend(), [](const auto &it1, const auto &it2) {
63 65 return SortUtils::maxCompareWithNaN(it1.first(), it2.first());
64 66 });
65 67
66 68 auto pair = std::make_pair(minIt->first(), maxIt->first());
67 69
68 70 return std::make_pair(minIt->first(), maxIt->first());
69 71 }
70 72 }
71 73
72 74 int OptionalAxis::size() const
73 75 {
74 76 return m_Defined ? m_Data->size() : 0;
75 77 }
76 78
77 79 Unit OptionalAxis::unit() const
78 80 {
79 81 return m_Defined ? m_Unit : Unit{};
80 82 }
81 83
82 84 bool OptionalAxis::operator==(const OptionalAxis &other)
83 85 {
84 86 // Axis not defined
85 87 if (!m_Defined) {
86 88 return !other.m_Defined;
87 89 }
88 90
89 91 // Axis defined
90 92 return m_Unit == other.m_Unit
91 93 && std::equal(
92 94 m_Data->cbegin(), m_Data->cend(), other.m_Data->cbegin(), other.m_Data->cend(),
93 95 [](const auto &it1, const auto &it2) { return it1.values() == it2.values(); });
94 96 }
95 97
96 98 bool OptionalAxis::operator!=(const OptionalAxis &other)
97 99 {
98 100 return !(*this == other);
99 101 }
@@ -1,31 +1,39
1 1 #ifndef SCIQLOP_VISUALIZATIONGRAPHRENDERINGDELEGATE_H
2 2 #define SCIQLOP_VISUALIZATIONGRAPHRENDERINGDELEGATE_H
3 3
4 4 #include <Common/spimpl.h>
5 5
6 #include <Visualization/VisualizationDefs.h>
7
8 class IDataSeries;
6 9 class QCustomPlot;
7 10 class QMouseEvent;
8 11 class Unit;
9 12 class VisualizationGraphWidget;
10 13
11 14 class VisualizationGraphRenderingDelegate {
12 15 public:
13 16 /// Ctor
14 17 /// @param graphWidget the graph widget to which the delegate is associated
15 18 /// @remarks the graph widget must exist throughout the life cycle of the delegate
16 19 explicit VisualizationGraphRenderingDelegate(VisualizationGraphWidget &graphWidget);
17 20
18 21 void onMouseMove(QMouseEvent *event) noexcept;
19 22
20 /// Sets properties of the plot's axes
21 void setAxesProperties(const Unit &xAxisUnit, const Unit &valuesUnit) noexcept;
23 /// Sets properties of the plot's axes from the data series passed as parameter
24 void setAxesProperties(std::shared_ptr<IDataSeries> dataSeries) noexcept;
25
26 /// Sets rendering properties of the plottables passed as parameter, from the data series that
27 /// generated these
28 void setPlottablesProperties(std::shared_ptr<IDataSeries> dataSeries,
29 PlottablesMap &plottables) noexcept;
22 30
23 31 /// Shows or hides graph overlay (name, close button, etc.)
24 32 void showGraphOverlay(bool show) noexcept;
25 33
26 34 private:
27 35 class VisualizationGraphRenderingDelegatePrivate;
28 36 spimpl::unique_impl_ptr<VisualizationGraphRenderingDelegatePrivate> impl;
29 37 };
30 38
31 39 #endif // SCIQLOP_VISUALIZATIONGRAPHRENDERINGDELEGATE_H
@@ -1,95 +1,97
1 1
2 2 gui_moc_headers = [
3 3 'include/DataSource/DataSourceWidget.h',
4 4 'include/DataSource/DataSourceTreeWidget.h',
5 5 'include/Settings/SqpSettingsDialog.h',
6 6 'include/Settings/SqpSettingsGeneralWidget.h',
7 7 'include/SidePane/SqpSidePane.h',
8 8 'include/SqpApplication.h',
9 9 'include/DragAndDrop/DragDropHelper.h',
10 10 'include/DragAndDrop/DragDropScroller.h',
11 11 'include/DragAndDrop/DragDropTabSwitcher.h',
12 12 'include/TimeWidget/TimeWidget.h',
13 13 'include/Variable/VariableInspectorWidget.h',
14 14 'include/Variable/VariableInspectorTableView.h',
15 15 'include/Variable/RenameVariableDialog.h',
16 16 'include/Visualization/qcustomplot.h',
17 17 'include/Visualization/VisualizationGraphWidget.h',
18 18 'include/Visualization/VisualizationTabWidget.h',
19 19 'include/Visualization/VisualizationWidget.h',
20 20 'include/Visualization/VisualizationZoneWidget.h',
21 21 'include/Visualization/VisualizationDragDropContainer.h',
22 22 'include/Visualization/VisualizationDragWidget.h'
23 23 ]
24 24
25 25 gui_ui_files = [
26 26 'ui/DataSource/DataSourceWidget.ui',
27 27 'ui/Settings/SqpSettingsDialog.ui',
28 28 'ui/Settings/SqpSettingsGeneralWidget.ui',
29 29 'ui/SidePane/SqpSidePane.ui',
30 30 'ui/TimeWidget/TimeWidget.ui',
31 31 'ui/Variable/VariableInspectorWidget.ui',
32 32 'ui/Variable/RenameVariableDialog.ui',
33 33 'ui/Variable/VariableMenuHeaderWidget.ui',
34 34 'ui/Visualization/VisualizationGraphWidget.ui',
35 35 'ui/Visualization/VisualizationTabWidget.ui',
36 36 'ui/Visualization/VisualizationWidget.ui',
37 37 'ui/Visualization/VisualizationZoneWidget.ui'
38 38 ]
39 39
40 40 gui_qresources = ['resources/sqpguiresources.qrc']
41 41
42 42 gui_moc_files = qt5.preprocess(moc_headers : gui_moc_headers,
43 43 ui_files : gui_ui_files,
44 44 qresources : gui_qresources)
45 45
46 46 gui_sources = [
47 47 'src/SqpApplication.cpp',
48 48 'src/DragAndDrop/DragDropHelper.cpp',
49 49 'src/DragAndDrop/DragDropScroller.cpp',
50 50 'src/DragAndDrop/DragDropTabSwitcher.cpp',
51 51 'src/Common/ColorUtils.cpp',
52 52 'src/Common/VisualizationDef.cpp',
53 53 'src/DataSource/DataSourceTreeWidgetItem.cpp',
54 54 'src/DataSource/DataSourceTreeWidgetHelper.cpp',
55 55 'src/DataSource/DataSourceWidget.cpp',
56 56 'src/DataSource/DataSourceTreeWidget.cpp',
57 57 'src/Settings/SqpSettingsDialog.cpp',
58 58 'src/Settings/SqpSettingsGeneralWidget.cpp',
59 59 'src/SidePane/SqpSidePane.cpp',
60 60 'src/TimeWidget/TimeWidget.cpp',
61 61 'src/Variable/VariableInspectorWidget.cpp',
62 62 'src/Variable/VariableInspectorTableView.cpp',
63 63 'src/Variable/VariableMenuHeaderWidget.cpp',
64 64 'src/Variable/RenameVariableDialog.cpp',
65 65 'src/Visualization/VisualizationGraphHelper.cpp',
66 66 'src/Visualization/VisualizationGraphRenderingDelegate.cpp',
67 67 'src/Visualization/VisualizationGraphWidget.cpp',
68 68 'src/Visualization/VisualizationTabWidget.cpp',
69 69 'src/Visualization/VisualizationWidget.cpp',
70 70 'src/Visualization/VisualizationZoneWidget.cpp',
71 71 'src/Visualization/qcustomplot.cpp',
72 72 'src/Visualization/QCustomPlotSynchronizer.cpp',
73 73 'src/Visualization/operations/FindVariableOperation.cpp',
74 74 'src/Visualization/operations/GenerateVariableMenuOperation.cpp',
75 75 'src/Visualization/operations/MenuBuilder.cpp',
76 76 'src/Visualization/operations/RemoveVariableOperation.cpp',
77 77 'src/Visualization/operations/RescaleAxeOperation.cpp',
78 78 'src/Visualization/VisualizationDragDropContainer.cpp',
79 79 'src/Visualization/VisualizationDragWidget.cpp'
80 'src/Visualization/AxisRenderingUtils.cpp',
81 'src/Visualization/PlottablesRenderingUtils.cpp'
80 82 ]
81 83
82 84 gui_inc = include_directories(['include'])
83 85
84 86 sciqlop_gui_lib = library('sciqlopgui',
85 87 gui_sources,
86 88 gui_moc_files,
87 89 include_directories : [gui_inc],
88 90 dependencies : [ qt5printsupport, qt5gui, qt5widgets, qt5svg, sciqlop_core],
89 91 install : true
90 92 )
91 93
92 94 sciqlop_gui = declare_dependency(link_with : sciqlop_gui_lib,
93 95 include_directories : gui_inc,
94 96 dependencies : [qt5printsupport, qt5gui, qt5widgets, qt5svg, sciqlop_core])
95 97
@@ -1,342 +1,345
1 1 #include "Visualization/VisualizationGraphHelper.h"
2 2 #include "Visualization/qcustomplot.h"
3 3
4 #include <Common/ColorUtils.h>
5
6 4 #include <Data/ScalarSeries.h>
7 5 #include <Data/SpectrogramSeries.h>
8 6 #include <Data/VectorSeries.h>
9 7
10 8 #include <Variable/Variable.h>
11 9
12 10 Q_LOGGING_CATEGORY(LOG_VisualizationGraphHelper, "VisualizationGraphHelper")
13 11
14 12 namespace {
15 13
16 14 class SqpDataContainer : public QCPGraphDataContainer {
17 15 public:
18 16 void appendGraphData(const QCPGraphData &data) { mData.append(data); }
19 17 };
20 18
21 19 /**
22 20 * Struct used to create plottables, depending on the type of the data series from which to create
23 21 * them
24 22 * @tparam T the data series' type
25 23 * @remarks Default implementation can't create plottables
26 24 */
27 25 template <typename T, typename Enabled = void>
28 26 struct PlottablesCreator {
29 27 static PlottablesMap createPlottables(T &, QCustomPlot &)
30 28 {
31 29 qCCritical(LOG_DataSeries())
32 30 << QObject::tr("Can't create plottables: unmanaged data series type");
33 31 return {};
34 32 }
35 33 };
36 34
37 35 /**
38 36 * Specialization of PlottablesCreator for scalars and vectors
39 37 * @sa ScalarSeries
40 38 * @sa VectorSeries
41 39 */
42 40 template <typename T>
43 41 struct PlottablesCreator<T,
44 42 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
45 43 or std::is_base_of<VectorSeries, T>::value> > {
46 44 static PlottablesMap createPlottables(T &dataSeries, QCustomPlot &plot)
47 45 {
48 46 PlottablesMap result{};
49 47
50 48 // Gets the number of components of the data series
51 49 dataSeries.lockRead();
52 50 auto componentCount = dataSeries.valuesData()->componentCount();
53 51 dataSeries.unlock();
54 52
55 auto colors = ColorUtils::colors(Qt::blue, Qt::red, componentCount);
56
57 53 // For each component of the data series, creates a QCPGraph to add to the plot
58 54 for (auto i = 0; i < componentCount; ++i) {
59 55 auto graph = plot.addGraph();
60 graph->setPen(QPen{colors.at(i)});
61
62 56 result.insert({i, graph});
63 57 }
64 58
65 59 plot.replot();
66 60
67 61 return result;
68 62 }
69 63 };
70 64
71 65 /**
72 66 * Specialization of PlottablesCreator for spectrograms
73 67 * @sa SpectrogramSeries
74 68 */
75 69 template <typename T>
76 70 struct PlottablesCreator<T,
77 71 typename std::enable_if_t<std::is_base_of<SpectrogramSeries, T>::value> > {
78 72 static PlottablesMap createPlottables(T &dataSeries, QCustomPlot &plot)
79 73 {
80 74 PlottablesMap result{};
81 75 result.insert({0, new QCPColorMap{plot.xAxis, plot.yAxis}});
82 76
83 77 plot.replot();
84 78
85 79 return result;
86 80 }
87 81 };
88 82
89 83 /**
90 84 * Struct used to update plottables, depending on the type of the data series from which to update
91 85 * them
92 86 * @tparam T the data series' type
93 87 * @remarks Default implementation can't update plottables
94 88 */
95 89 template <typename T, typename Enabled = void>
96 90 struct PlottablesUpdater {
97 91 static void setPlotYAxisRange(T &, const SqpRange &, QCustomPlot &)
98 92 {
99 93 qCCritical(LOG_VisualizationGraphHelper())
100 94 << QObject::tr("Can't set plot y-axis range: unmanaged data series type");
101 95 }
102 96
103 97 static void updatePlottables(T &, PlottablesMap &, const SqpRange &, bool)
104 98 {
105 99 qCCritical(LOG_VisualizationGraphHelper())
106 100 << QObject::tr("Can't update plottables: unmanaged data series type");
107 101 }
108 102 };
109 103
110 104 /**
111 105 * Specialization of PlottablesUpdater for scalars and vectors
112 106 * @sa ScalarSeries
113 107 * @sa VectorSeries
114 108 */
115 109 template <typename T>
116 110 struct PlottablesUpdater<T,
117 111 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
118 112 or std::is_base_of<VectorSeries, T>::value> > {
119 113 static void setPlotYAxisRange(T &dataSeries, const SqpRange &xAxisRange, QCustomPlot &plot)
120 114 {
121 115 auto minValue = 0., maxValue = 0.;
122 116
123 117 dataSeries.lockRead();
124 118 auto valuesBounds = dataSeries.valuesBounds(xAxisRange.m_TStart, xAxisRange.m_TEnd);
125 119 auto end = dataSeries.cend();
126 120 if (valuesBounds.first != end && valuesBounds.second != end) {
127 121 auto rangeValue = [](const auto &value) { return std::isnan(value) ? 0. : value; };
128 122
129 123 minValue = rangeValue(valuesBounds.first->minValue());
130 124 maxValue = rangeValue(valuesBounds.second->maxValue());
131 125 }
132 126 dataSeries.unlock();
133 127
134 128 plot.yAxis->setRange(QCPRange{minValue, maxValue});
135 129 }
136 130
137 131 static void updatePlottables(T &dataSeries, PlottablesMap &plottables, const SqpRange &range,
138 132 bool rescaleAxes)
139 133 {
140 134
141 135 // For each plottable to update, resets its data
142 136 std::map<int, QSharedPointer<SqpDataContainer> > dataContainers{};
143 137 for (const auto &plottable : plottables) {
144 138 if (auto graph = dynamic_cast<QCPGraph *>(plottable.second)) {
145 139 auto dataContainer = QSharedPointer<SqpDataContainer>::create();
146 140 graph->setData(dataContainer);
147 141
148 142 dataContainers.insert({plottable.first, dataContainer});
149 143 }
150 144 }
151 145 dataSeries.lockRead();
152 146
153 147 // - Gets the data of the series included in the current range
154 148 // - Updates each plottable by adding, for each data item, a point that takes x-axis data
155 149 // and value data. The correct value is retrieved according to the index of the component
156 150 auto subDataIts = dataSeries.xAxisRange(range.m_TStart, range.m_TEnd);
157 151 for (auto it = subDataIts.first; it != subDataIts.second; ++it) {
158 152 for (const auto &dataContainer : dataContainers) {
159 153 auto componentIndex = dataContainer.first;
160 154 dataContainer.second->appendGraphData(
161 155 QCPGraphData(it->x(), it->value(componentIndex)));
162 156 }
163 157 }
164 158
165 159 dataSeries.unlock();
166 160
167 161 if (!plottables.empty()) {
168 162 auto plot = plottables.begin()->second->parentPlot();
169 163
170 164 if (rescaleAxes) {
171 165 plot->rescaleAxes();
172 166 }
173 167
174 168 plot->replot();
175 169 }
176 170 }
177 171 };
178 172
179 173 /**
180 174 * Specialization of PlottablesUpdater for spectrograms
181 175 * @sa SpectrogramSeries
182 176 */
183 177 template <typename T>
184 178 struct PlottablesUpdater<T,
185 179 typename std::enable_if_t<std::is_base_of<SpectrogramSeries, T>::value> > {
186 180 static void setPlotYAxisRange(T &dataSeries, const SqpRange &xAxisRange, QCustomPlot &plot)
187 181 {
188 182 double min, max;
189 183 /// @todo ALX: use iterators here
190 184 std::tie(min, max) = dataSeries.yAxis().bounds();
191 185
192 186 if (!std::isnan(min) && !std::isnan(max)) {
193 187 plot.yAxis->setRange(QCPRange{min, max});
194 188 }
195 189 }
196 190
197 191 static void updatePlottables(T &dataSeries, PlottablesMap &plottables, const SqpRange &range,
198 192 bool rescaleAxes)
199 193 {
200 194 if (plottables.empty()) {
201 195 qCDebug(LOG_VisualizationGraphHelper())
202 196 << QObject::tr("Can't update spectrogram: no colormap has been associated");
203 197 return;
204 198 }
205 199
206 200 // Gets the colormap to update (normally there is only one colormap)
207 201 Q_ASSERT(plottables.size() == 1);
208 202 auto colormap = dynamic_cast<QCPColorMap *>(plottables.at(0));
209 203 Q_ASSERT(colormap != nullptr);
210 204
211 205 dataSeries.lockRead();
212 206
213 207 auto its = dataSeries.xAxisRange(range.m_TStart, range.m_TEnd);
214 208 /// @todo ALX: use iterators here
215 209 auto yAxis = dataSeries.yAxis();
216 210
217 211 // Gets properties of x-axis and y-axis to set size and range of the colormap
218 212 auto nbX = std::distance(its.first, its.second);
219 213 auto xMin = nbX != 0 ? its.first->x() : 0.;
220 214 auto xMax = nbX != 0 ? (its.second - 1)->x() : 0.;
221 215
222 216 auto nbY = yAxis.size();
223 217 auto yMin = 0., yMax = 0.;
224 218 if (nbY != 0) {
225 219 std::tie(yMin, yMax) = yAxis.bounds();
226 220 }
227 221
228 222 colormap->data()->setSize(nbX, nbY);
229 223 colormap->data()->setRange(QCPRange{xMin, xMax}, QCPRange{yMin, yMax});
230 224
231 225 // Sets values
232 226 auto xIndex = 0;
233 227 for (auto it = its.first; it != its.second; ++it, ++xIndex) {
234 228 for (auto yIndex = 0; yIndex < nbY; ++yIndex) {
235 colormap->data()->setCell(xIndex, yIndex, it->value(yIndex));
229 auto value = it->value(yIndex);
230
231 colormap->data()->setCell(xIndex, yIndex, value);
232
233 // Processing spectrogram data for display in QCustomPlot
234 /// For the moment, we just make the NaN values to be transparent in the colormap
235 /// @todo ALX: complete treatments (mesh generation, etc.)
236 if (std::isnan(value)) {
237 colormap->data()->setAlpha(xIndex, yIndex, 0);
238 }
236 239 }
237 240 }
238 241
239 242 dataSeries.unlock();
240 243
241 244 // Rescales axes
242 245 auto plot = colormap->parentPlot();
243 246
244 247 if (rescaleAxes) {
245 248 plot->rescaleAxes();
246 249 }
247 250
248 251 plot->replot();
249 252 }
250 253 };
251 254
252 255 /**
253 256 * Helper used to create/update plottables
254 257 */
255 258 struct IPlottablesHelper {
256 259 virtual ~IPlottablesHelper() noexcept = default;
257 260 virtual PlottablesMap create(QCustomPlot &plot) const = 0;
258 261 virtual void setYAxisRange(const SqpRange &xAxisRange, QCustomPlot &plot) const = 0;
259 262 virtual void update(PlottablesMap &plottables, const SqpRange &range,
260 263 bool rescaleAxes = false) const = 0;
261 264 };
262 265
263 266 /**
264 267 * Default implementation of IPlottablesHelper, which takes data series to create/update plottables
265 268 * @tparam T the data series' type
266 269 */
267 270 template <typename T>
268 271 struct PlottablesHelper : public IPlottablesHelper {
269 272 explicit PlottablesHelper(T &dataSeries) : m_DataSeries{dataSeries} {}
270 273
271 274 PlottablesMap create(QCustomPlot &plot) const override
272 275 {
273 276 return PlottablesCreator<T>::createPlottables(m_DataSeries, plot);
274 277 }
275 278
276 279 void update(PlottablesMap &plottables, const SqpRange &range, bool rescaleAxes) const override
277 280 {
278 281 PlottablesUpdater<T>::updatePlottables(m_DataSeries, plottables, range, rescaleAxes);
279 282 }
280 283
281 284 void setYAxisRange(const SqpRange &xAxisRange, QCustomPlot &plot) const override
282 285 {
283 286 return PlottablesUpdater<T>::setPlotYAxisRange(m_DataSeries, xAxisRange, plot);
284 287 }
285 288
286 289 T &m_DataSeries;
287 290 };
288 291
289 292 /// Creates IPlottablesHelper according to a data series
290 293 std::unique_ptr<IPlottablesHelper> createHelper(std::shared_ptr<IDataSeries> dataSeries) noexcept
291 294 {
292 295 if (auto scalarSeries = std::dynamic_pointer_cast<ScalarSeries>(dataSeries)) {
293 296 return std::make_unique<PlottablesHelper<ScalarSeries> >(*scalarSeries);
294 297 }
295 298 else if (auto spectrogramSeries = std::dynamic_pointer_cast<SpectrogramSeries>(dataSeries)) {
296 299 return std::make_unique<PlottablesHelper<SpectrogramSeries> >(*spectrogramSeries);
297 300 }
298 301 else if (auto vectorSeries = std::dynamic_pointer_cast<VectorSeries>(dataSeries)) {
299 302 return std::make_unique<PlottablesHelper<VectorSeries> >(*vectorSeries);
300 303 }
301 304 else {
302 305 return std::make_unique<PlottablesHelper<IDataSeries> >(*dataSeries);
303 306 }
304 307 }
305 308
306 309 } // namespace
307 310
308 311 PlottablesMap VisualizationGraphHelper::create(std::shared_ptr<Variable> variable,
309 312 QCustomPlot &plot) noexcept
310 313 {
311 314 if (variable) {
312 315 auto helper = createHelper(variable->dataSeries());
313 316 auto plottables = helper->create(plot);
314 317 return plottables;
315 318 }
316 319 else {
317 320 qCDebug(LOG_VisualizationGraphHelper())
318 321 << QObject::tr("Can't create graph plottables : the variable is null");
319 322 return PlottablesMap{};
320 323 }
321 324 }
322 325
323 326 void VisualizationGraphHelper::setYAxisRange(std::shared_ptr<Variable> variable,
324 327 QCustomPlot &plot) noexcept
325 328 {
326 329 if (variable) {
327 330 auto helper = createHelper(variable->dataSeries());
328 331 helper->setYAxisRange(variable->range(), plot);
329 332 }
330 333 else {
331 334 qCDebug(LOG_VisualizationGraphHelper())
332 335 << QObject::tr("Can't set y-axis range of plot: the variable is null");
333 336 }
334 337 }
335 338
336 339 void VisualizationGraphHelper::updateData(PlottablesMap &plottables,
337 340 std::shared_ptr<IDataSeries> dataSeries,
338 341 const SqpRange &dateTime)
339 342 {
340 343 auto helper = createHelper(dataSeries);
341 344 helper->update(plottables, dateTime);
342 345 }
@@ -1,275 +1,245
1 1 #include "Visualization/VisualizationGraphRenderingDelegate.h"
2 #include "Visualization/AxisRenderingUtils.h"
3 #include "Visualization/PlottablesRenderingUtils.h"
2 4 #include "Visualization/VisualizationGraphWidget.h"
3 5 #include "Visualization/qcustomplot.h"
4 6
5 7 #include <Common/DateUtils.h>
6 8
7 9 #include <Data/IDataSeries.h>
8 10
9 11 #include <SqpApplication.h>
10 12
11 13 namespace {
12 14
13 15 /// Name of the axes layer in QCustomPlot
14 16 const auto AXES_LAYER = QStringLiteral("axes");
15 17
16 const auto DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd hh:mm:ss:zzz");
17
18 /// Format for datetimes on a axis
19 const auto DATETIME_TICKER_FORMAT = QStringLiteral("yyyy/MM/dd \nhh:mm:ss");
20
21 18 /// Icon used to show x-axis properties
22 19 const auto HIDE_AXIS_ICON_PATH = QStringLiteral(":/icones/down.png");
23 20
24 21 /// Name of the overlay layer in QCustomPlot
25 22 const auto OVERLAY_LAYER = QStringLiteral("overlay");
26 23
27 24 /// Pixmap used to show x-axis properties
28 25 const auto SHOW_AXIS_ICON_PATH = QStringLiteral(":/icones/up.png");
29 26
30 27 const auto TOOLTIP_FORMAT = QStringLiteral("key: %1\nvalue: %2");
31 28
32 29 /// Offset used to shift the tooltip of the mouse
33 30 const auto TOOLTIP_OFFSET = QPoint{20, 20};
34 31
35 32 /// Tooltip display rectangle (the tooltip is hidden when the mouse leaves this rectangle)
36 33 const auto TOOLTIP_RECT = QRect{10, 10, 10, 10};
37 34
38 35 /// Timeout after which the tooltip is displayed
39 36 const auto TOOLTIP_TIMEOUT = 500;
40 37
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
58 /// Formats a data value according to the axis on which it is present
59 QString formatValue(double value, const QCPAxis &axis)
60 {
61 // If the axis is a time axis, formats the value as a date
62 if (auto axisTicker = qSharedPointerDynamicCast<QCPAxisTickerDateTime>(axis.ticker())) {
63 return DateUtils::dateTime(value, axisTicker->dateTimeSpec()).toString(DATETIME_FORMAT);
64 }
65 else {
66 return QString::number(value);
67 }
68 }
69
70 38 void initPointTracerStyle(QCPItemTracer &tracer) noexcept
71 39 {
72 40 tracer.setInterpolating(false);
73 41 tracer.setStyle(QCPItemTracer::tsCircle);
74 42 tracer.setSize(3);
75 43 tracer.setPen(QPen(Qt::black));
76 44 tracer.setBrush(Qt::black);
77 45 }
78 46
79 47 QPixmap pixmap(const QString &iconPath) noexcept
80 48 {
81 49 return QIcon{iconPath}.pixmap(QSize{16, 16});
82 50 }
83 51
84 52 void initClosePixmapStyle(QCPItemPixmap &pixmap) noexcept
85 53 {
86 54 // Icon
87 55 pixmap.setPixmap(
88 56 sqpApp->style()->standardIcon(QStyle::SP_TitleBarCloseButton).pixmap(QSize{16, 16}));
89 57
90 58 // Position
91 59 pixmap.topLeft->setType(QCPItemPosition::ptAxisRectRatio);
92 60 pixmap.topLeft->setCoords(1, 0);
93 61 pixmap.setClipToAxisRect(false);
94 62
95 63 // Can be selected
96 64 pixmap.setSelectable(true);
97 65 }
98 66
99 67 void initXAxisPixmapStyle(QCPItemPixmap &itemPixmap) noexcept
100 68 {
101 69 // Icon
102 70 itemPixmap.setPixmap(pixmap(HIDE_AXIS_ICON_PATH));
103 71
104 72 // Position
105 73 itemPixmap.topLeft->setType(QCPItemPosition::ptAxisRectRatio);
106 74 itemPixmap.topLeft->setCoords(0, 1);
107 75 itemPixmap.setClipToAxisRect(false);
108 76
109 77 // Can be selected
110 78 itemPixmap.setSelectable(true);
111 79 }
112 80
113 81 void initTitleTextStyle(QCPItemText &text) noexcept
114 82 {
115 83 // Font and background styles
116 84 text.setColor(Qt::gray);
117 85 text.setBrush(Qt::white);
118 86
119 87 // Position
120 88 text.setPositionAlignment(Qt::AlignTop | Qt::AlignLeft);
121 89 text.position->setType(QCPItemPosition::ptAxisRectRatio);
122 90 text.position->setCoords(0.5, 0);
123 91 }
124 92
125 93 } // namespace
126 94
127 95 struct VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegatePrivate {
128 96 explicit VisualizationGraphRenderingDelegatePrivate(VisualizationGraphWidget &graphWidget)
129 97 : m_Plot{graphWidget.plot()},
130 98 m_PointTracer{new QCPItemTracer{&m_Plot}},
131 99 m_TracerTimer{},
132 100 m_ClosePixmap{new QCPItemPixmap{&m_Plot}},
133 101 m_TitleText{new QCPItemText{&m_Plot}},
134 102 m_XAxisPixmap{new QCPItemPixmap{&m_Plot}},
135 103 m_ShowXAxis{true},
136 m_XAxisLabel{}
104 m_XAxisLabel{},
105 m_ColorScale{new QCPColorScale{&m_Plot}}
137 106 {
138 107 initPointTracerStyle(*m_PointTracer);
139 108
140 109 m_TracerTimer.setInterval(TOOLTIP_TIMEOUT);
141 110 m_TracerTimer.setSingleShot(true);
142 111
143 112 // Inits "close button" in plot overlay
144 113 m_ClosePixmap->setLayer(OVERLAY_LAYER);
145 114 initClosePixmapStyle(*m_ClosePixmap);
146 115
147 116 // Connects pixmap selection to graph widget closing
148 117 QObject::connect(m_ClosePixmap, &QCPItemPixmap::selectionChanged,
149 118 [&graphWidget](bool selected) {
150 119 if (selected) {
151 120 graphWidget.close();
152 121 }
153 122 });
154 123
155 124 // Inits graph name in plot overlay
156 125 m_TitleText->setLayer(OVERLAY_LAYER);
157 126 m_TitleText->setText(graphWidget.name());
158 127 initTitleTextStyle(*m_TitleText);
159 128
160 129 // Inits "show x-axis button" in plot overlay
161 130 m_XAxisPixmap->setLayer(OVERLAY_LAYER);
162 131 initXAxisPixmapStyle(*m_XAxisPixmap);
163 132
164 133 // Connects pixmap selection to graph x-axis showing/hiding
165 134 QObject::connect(m_XAxisPixmap, &QCPItemPixmap::selectionChanged, [this]() {
166 135 if (m_XAxisPixmap->selected()) {
167 136 // Changes the selection state and refreshes the x-axis
168 137 m_ShowXAxis = !m_ShowXAxis;
169 138 updateXAxisState();
170 139 m_Plot.layer(AXES_LAYER)->replot();
171 140
172 141 // Deselects the x-axis pixmap and updates icon
173 142 m_XAxisPixmap->setSelected(false);
174 143 m_XAxisPixmap->setPixmap(
175 144 pixmap(m_ShowXAxis ? HIDE_AXIS_ICON_PATH : SHOW_AXIS_ICON_PATH));
176 145 m_Plot.layer(OVERLAY_LAYER)->replot();
177 146 }
178 147 });
179 148 }
180 149
181 150 /// Updates state of x-axis according to the current selection of x-axis pixmap
182 151 /// @remarks the method doesn't call plot refresh
183 152 void updateXAxisState() noexcept
184 153 {
185 154 m_Plot.xAxis->setTickLabels(m_ShowXAxis);
186 155 m_Plot.xAxis->setLabel(m_ShowXAxis ? m_XAxisLabel : QString{});
187 156 }
188 157
189 158 QCustomPlot &m_Plot;
190 159 QCPItemTracer *m_PointTracer;
191 160 QTimer m_TracerTimer;
192 161 QCPItemPixmap *m_ClosePixmap; /// Graph's close button
193 162 QCPItemText *m_TitleText; /// Graph's title
194 163 QCPItemPixmap *m_XAxisPixmap;
195 164 bool m_ShowXAxis; /// X-axis properties are shown or hidden
196 165 QString m_XAxisLabel;
166 QCPColorScale *m_ColorScale; /// Color scale used for some types of graphs (as spectrograms)
197 167 };
198 168
199 169 VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegate(
200 170 VisualizationGraphWidget &graphWidget)
201 171 : impl{spimpl::make_unique_impl<VisualizationGraphRenderingDelegatePrivate>(graphWidget)}
202 172 {
203 173 }
204 174
205 175 void VisualizationGraphRenderingDelegate::onMouseMove(QMouseEvent *event) noexcept
206 176 {
207 177 // Cancels pending refresh
208 178 impl->m_TracerTimer.disconnect();
209 179
210 180 // Reinits tracers
211 181 impl->m_PointTracer->setGraph(nullptr);
212 182 impl->m_PointTracer->setVisible(false);
213 183 impl->m_Plot.replot();
214 184
215 185 // Gets the graph under the mouse position
216 186 auto eventPos = event->pos();
217 187 if (auto graph = qobject_cast<QCPGraph *>(impl->m_Plot.plottableAt(eventPos))) {
218 188 auto mouseKey = graph->keyAxis()->pixelToCoord(eventPos.x());
219 189 auto graphData = graph->data();
220 190
221 191 // Gets the closest data point to the mouse
222 192 auto graphDataIt = graphData->findBegin(mouseKey);
223 193 if (graphDataIt != graphData->constEnd()) {
224 194 auto key = formatValue(graphDataIt->key, *graph->keyAxis());
225 195 auto value = formatValue(graphDataIt->value, *graph->valueAxis());
226 196
227 197 // Displays point tracer
228 198 impl->m_PointTracer->setGraph(graph);
229 199 impl->m_PointTracer->setGraphKey(graphDataIt->key);
230 200 impl->m_PointTracer->setLayer(
231 201 impl->m_Plot.layer("main")); // Tracer is set on top of the plot's main layer
232 202 impl->m_PointTracer->setVisible(true);
233 203 impl->m_Plot.replot();
234 204
235 205 // Starts timer to show tooltip after timeout
236 206 auto showTooltip = [ tooltip = TOOLTIP_FORMAT.arg(key, value), eventPos, this ]()
237 207 {
238 208 QToolTip::showText(impl->m_Plot.mapToGlobal(eventPos) + TOOLTIP_OFFSET, tooltip,
239 209 &impl->m_Plot, TOOLTIP_RECT);
240 210 };
241 211
242 212 QObject::connect(&impl->m_TracerTimer, &QTimer::timeout, showTooltip);
243 213 impl->m_TracerTimer.start();
244 214 }
245 215 }
246 216 }
247 217
248 void VisualizationGraphRenderingDelegate::setAxesProperties(const Unit &xAxisUnit,
249 const Unit &valuesUnit) noexcept
218 void VisualizationGraphRenderingDelegate::setAxesProperties(
219 std::shared_ptr<IDataSeries> dataSeries) noexcept
250 220 {
251 221 // Stores x-axis label to be able to retrieve it when x-axis pixmap is unselected
252 impl->m_XAxisLabel = xAxisUnit.m_Name;
222 impl->m_XAxisLabel = dataSeries->xAxisUnit().m_Name;
253 223
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);
224 auto axisHelper = IAxisHelperFactory::create(dataSeries);
225 axisHelper->setProperties(impl->m_Plot, *impl->m_ColorScale);
263 226
264 227 // Updates x-axis state
265 228 impl->updateXAxisState();
266 229
267 230 impl->m_Plot.layer(AXES_LAYER)->replot();
268 231 }
269 232
233 void VisualizationGraphRenderingDelegate::setPlottablesProperties(
234 std::shared_ptr<IDataSeries> dataSeries, PlottablesMap &plottables) noexcept
235 {
236 auto plottablesHelper = IPlottablesHelperFactory::create(dataSeries);
237 plottablesHelper->setProperties(plottables);
238 }
239
270 240 void VisualizationGraphRenderingDelegate::showGraphOverlay(bool show) noexcept
271 241 {
272 242 auto overlay = impl->m_Plot.layer(OVERLAY_LAYER);
273 243 overlay->setVisible(show);
274 244 overlay->replot();
275 245 }
@@ -1,405 +1,402
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 "Visualization/VisualizationZoneWidget.h"
7 7 #include "ui_VisualizationGraphWidget.h"
8 8
9 9 #include <Common/MimeTypesDef.h>
10 10 #include <Data/ArrayData.h>
11 11 #include <Data/IDataSeries.h>
12 12 #include <DragAndDrop/DragDropHelper.h>
13 13 #include <Settings/SqpSettingsDefs.h>
14 14 #include <SqpApplication.h>
15 15 #include <Time/TimeController.h>
16 16 #include <Variable/Variable.h>
17 17 #include <Variable/VariableController.h>
18 18
19 19 #include <unordered_map>
20 20
21 21 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
22 22
23 23 namespace {
24 24
25 25 /// Key pressed to enable zoom on horizontal axis
26 26 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::NoModifier;
27 27
28 28 /// Key pressed to enable zoom on vertical axis
29 29 const auto VERTICAL_ZOOM_MODIFIER = Qt::ControlModifier;
30 30
31 31 } // namespace
32 32
33 33 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate {
34 34
35 35 explicit VisualizationGraphWidgetPrivate(const QString &name)
36 36 : m_Name{name},
37 37 m_DoAcquisition{true},
38 38 m_IsCalibration{false},
39 39 m_RenderingDelegate{nullptr}
40 40 {
41 41 }
42 42
43 43 QString m_Name;
44 44 // 1 variable -> n qcpplot
45 45 std::map<std::shared_ptr<Variable>, PlottablesMap> m_VariableToPlotMultiMap;
46 46 bool m_DoAcquisition;
47 47 bool m_IsCalibration;
48 48 /// Delegate used to attach rendering features to the plot
49 49 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
50 50 };
51 51
52 52 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
53 53 : VisualizationDragWidget{parent},
54 54 ui{new Ui::VisualizationGraphWidget},
55 55 impl{spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name)}
56 56 {
57 57 ui->setupUi(this);
58 58
59 59 // 'Close' options : widget is deleted when closed
60 60 setAttribute(Qt::WA_DeleteOnClose);
61 61
62 62 // Set qcpplot properties :
63 63 // - Drag (on x-axis) and zoom are enabled
64 64 // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation
65 65 ui->widget->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectItems);
66 66 ui->widget->axisRect()->setRangeDrag(Qt::Horizontal);
67 67
68 68 // The delegate must be initialized after the ui as it uses the plot
69 69 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
70 70
71 71 connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress);
72 72 connect(ui->widget, &QCustomPlot::mouseRelease, this,
73 73 &VisualizationGraphWidget::onMouseRelease);
74 74 connect(ui->widget, &QCustomPlot::mouseMove, this, &VisualizationGraphWidget::onMouseMove);
75 75 connect(ui->widget, &QCustomPlot::mouseWheel, this, &VisualizationGraphWidget::onMouseWheel);
76 76 connect(ui->widget->xAxis, static_cast<void (QCPAxis::*)(const QCPRange &, const QCPRange &)>(
77 77 &QCPAxis::rangeChanged),
78 78 this, &VisualizationGraphWidget::onRangeChanged, Qt::DirectConnection);
79 79
80 80 // Activates menu when right clicking on the graph
81 81 ui->widget->setContextMenuPolicy(Qt::CustomContextMenu);
82 82 connect(ui->widget, &QCustomPlot::customContextMenuRequested, this,
83 83 &VisualizationGraphWidget::onGraphMenuRequested);
84 84
85 85 connect(this, &VisualizationGraphWidget::requestDataLoading, &sqpApp->variableController(),
86 86 &VariableController::onRequestDataLoading);
87 87
88 88 connect(&sqpApp->variableController(), &VariableController::updateVarDisplaying, this,
89 89 &VisualizationGraphWidget::onUpdateVarDisplaying);
90 90 }
91 91
92 92
93 93 VisualizationGraphWidget::~VisualizationGraphWidget()
94 94 {
95 95 delete ui;
96 96 }
97 97
98 98 VisualizationZoneWidget *VisualizationGraphWidget::parentZoneWidget() const noexcept
99 99 {
100 100 auto parent = parentWidget();
101 101 while (parent != nullptr && !qobject_cast<VisualizationZoneWidget *>(parent)) {
102 102 parent = parent->parentWidget();
103 103 }
104 104
105 105 return qobject_cast<VisualizationZoneWidget *>(parent);
106 106 }
107 107
108 108 void VisualizationGraphWidget::enableAcquisition(bool enable)
109 109 {
110 110 impl->m_DoAcquisition = enable;
111 111 }
112 112
113 113 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable> variable, SqpRange range)
114 114 {
115 115 // Uses delegate to create the qcpplot components according to the variable
116 116 auto createdPlottables = VisualizationGraphHelper::create(variable, *ui->widget);
117 impl->m_VariableToPlotMultiMap.insert({variable, std::move(createdPlottables)});
118 117
118 if (auto dataSeries = variable->dataSeries()) {
119 119 // Set axes properties according to the units of the data series
120 /// @todo : for the moment, no control is performed on the axes: the units and the tickers
121 /// are fixed for the default x-axis and y-axis of the plot, and according to the new graph
122 auto xAxisUnit = Unit{};
123 auto valuesUnit = Unit{};
120 impl->m_RenderingDelegate->setAxesProperties(dataSeries);
124 121
125 if (auto dataSeries = variable->dataSeries()) {
126 dataSeries->lockRead();
127 xAxisUnit = dataSeries->xAxisUnit();
128 valuesUnit = dataSeries->valuesUnit();
129 dataSeries->unlock();
122 // Sets rendering properties for the new plottables
123 // Warning: this method must be called after setAxesProperties(), as it can access to some
124 // axes properties that have to be initialized
125 impl->m_RenderingDelegate->setPlottablesProperties(dataSeries, createdPlottables);
130 126 }
131 impl->m_RenderingDelegate->setAxesProperties(xAxisUnit, valuesUnit);
127
128 impl->m_VariableToPlotMultiMap.insert({variable, std::move(createdPlottables)});
132 129
133 130 connect(variable.get(), SIGNAL(updated()), this, SLOT(onDataCacheVariableUpdated()));
134 131
135 132 this->enableAcquisition(false);
136 133 this->setGraphRange(range);
137 134 this->enableAcquisition(true);
138 135
139 136 emit requestDataLoading(QVector<std::shared_ptr<Variable> >() << variable, range, false);
140 137
141 138 emit variableAdded(variable);
142 139 }
143 140
144 141 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable> variable) noexcept
145 142 {
146 143 // Each component associated to the variable :
147 144 // - is removed from qcpplot (which deletes it)
148 145 // - is no longer referenced in the map
149 146 auto variableIt = impl->m_VariableToPlotMultiMap.find(variable);
150 147 if (variableIt != impl->m_VariableToPlotMultiMap.cend()) {
151 148 emit variableAboutToBeRemoved(variable);
152 149
153 150 auto &plottablesMap = variableIt->second;
154 151
155 152 for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend();
156 153 plottableIt != plottableEnd;) {
157 154 ui->widget->removePlottable(plottableIt->second);
158 155 plottableIt = plottablesMap.erase(plottableIt);
159 156 }
160 157
161 158 impl->m_VariableToPlotMultiMap.erase(variableIt);
162 159 }
163 160
164 161 // Updates graph
165 162 ui->widget->replot();
166 163 }
167 164
168 165 QList<std::shared_ptr<Variable> > VisualizationGraphWidget::variables() const
169 166 {
170 167 auto variables = QList<std::shared_ptr<Variable> >{};
171 168 for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap);
172 169 it != std::cend(impl->m_VariableToPlotMultiMap); ++it) {
173 170 variables << it->first;
174 171 }
175 172
176 173 return variables;
177 174 }
178 175
179 176 void VisualizationGraphWidget::setYRange(std::shared_ptr<Variable> variable)
180 177 {
181 178 if (!variable) {
182 179 qCCritical(LOG_VisualizationGraphWidget()) << "Can't set y-axis range: variable is null";
183 180 return;
184 181 }
185 182
186 183 VisualizationGraphHelper::setYAxisRange(variable, *ui->widget);
187 184 }
188 185
189 186 SqpRange VisualizationGraphWidget::graphRange() const noexcept
190 187 {
191 188 auto graphRange = ui->widget->xAxis->range();
192 189 return SqpRange{graphRange.lower, graphRange.upper};
193 190 }
194 191
195 192 void VisualizationGraphWidget::setGraphRange(const SqpRange &range)
196 193 {
197 194 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange START");
198 195 ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd);
199 196 ui->widget->replot();
200 197 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange END");
201 198 }
202 199
203 200 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor)
204 201 {
205 202 if (visitor) {
206 203 visitor->visit(this);
207 204 }
208 205 else {
209 206 qCCritical(LOG_VisualizationGraphWidget())
210 207 << tr("Can't visit widget : the visitor is null");
211 208 }
212 209 }
213 210
214 211 bool VisualizationGraphWidget::canDrop(const Variable &variable) const
215 212 {
216 213 /// @todo : for the moment, a graph can always accomodate a variable
217 214 Q_UNUSED(variable);
218 215 return true;
219 216 }
220 217
221 218 bool VisualizationGraphWidget::contains(const Variable &variable) const
222 219 {
223 220 // Finds the variable among the keys of the map
224 221 auto variablePtr = &variable;
225 222 auto findVariable
226 223 = [variablePtr](const auto &entry) { return variablePtr == entry.first.get(); };
227 224
228 225 auto end = impl->m_VariableToPlotMultiMap.cend();
229 226 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
230 227 return it != end;
231 228 }
232 229
233 230 QString VisualizationGraphWidget::name() const
234 231 {
235 232 return impl->m_Name;
236 233 }
237 234
238 235 QMimeData *VisualizationGraphWidget::mimeData() const
239 236 {
240 237 auto mimeData = new QMimeData;
241 238 mimeData->setData(MIME_TYPE_GRAPH, QByteArray{});
242 239
243 240 auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange());
244 241 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
245 242
246 243 return mimeData;
247 244 }
248 245
249 246 bool VisualizationGraphWidget::isDragAllowed() const
250 247 {
251 248 return true;
252 249 }
253 250
254 251 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
255 252 {
256 253 if (highlighted) {
257 254 plot().setBackground(QBrush(QColor("#BBD5EE")));
258 255 }
259 256 else {
260 257 plot().setBackground(QBrush(Qt::white));
261 258 }
262 259
263 260 plot().update();
264 261 }
265 262
266 263 void VisualizationGraphWidget::closeEvent(QCloseEvent *event)
267 264 {
268 265 Q_UNUSED(event);
269 266
270 267 // Prevents that all variables will be removed from graph when it will be closed
271 268 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
272 269 emit variableAboutToBeRemoved(variableEntry.first);
273 270 }
274 271 }
275 272
276 273 void VisualizationGraphWidget::enterEvent(QEvent *event)
277 274 {
278 275 Q_UNUSED(event);
279 276 impl->m_RenderingDelegate->showGraphOverlay(true);
280 277 }
281 278
282 279 void VisualizationGraphWidget::leaveEvent(QEvent *event)
283 280 {
284 281 Q_UNUSED(event);
285 282 impl->m_RenderingDelegate->showGraphOverlay(false);
286 283 }
287 284
288 285 QCustomPlot &VisualizationGraphWidget::plot() noexcept
289 286 {
290 287 return *ui->widget;
291 288 }
292 289
293 290 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept
294 291 {
295 292 QMenu graphMenu{};
296 293
297 294 // Iterates on variables (unique keys)
298 295 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
299 296 end = impl->m_VariableToPlotMultiMap.cend();
300 297 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
301 298 // 'Remove variable' action
302 299 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
303 300 [ this, var = it->first ]() { removeVariable(var); });
304 301 }
305 302
306 303 if (!graphMenu.isEmpty()) {
307 304 graphMenu.exec(QCursor::pos());
308 305 }
309 306 }
310 307
311 308 void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange &t2)
312 309 {
313 310 qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: VisualizationGraphWidget::onRangeChanged")
314 311 << QThread::currentThread()->objectName() << "DoAcqui"
315 312 << impl->m_DoAcquisition;
316 313
317 314 auto graphRange = SqpRange{t1.lower, t1.upper};
318 315 auto oldGraphRange = SqpRange{t2.lower, t2.upper};
319 316
320 317 if (impl->m_DoAcquisition) {
321 318 QVector<std::shared_ptr<Variable> > variableUnderGraphVector;
322 319
323 320 for (auto it = impl->m_VariableToPlotMultiMap.begin(),
324 321 end = impl->m_VariableToPlotMultiMap.end();
325 322 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
326 323 variableUnderGraphVector.push_back(it->first);
327 324 }
328 325 emit requestDataLoading(std::move(variableUnderGraphVector), graphRange,
329 326 !impl->m_IsCalibration);
330 327
331 328 if (!impl->m_IsCalibration) {
332 329 qCDebug(LOG_VisualizationGraphWidget())
333 330 << tr("TORM: VisualizationGraphWidget::Synchronize notify !!")
334 331 << QThread::currentThread()->objectName() << graphRange << oldGraphRange;
335 332 emit synchronize(graphRange, oldGraphRange);
336 333 }
337 334 }
338 335 }
339 336
340 337 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
341 338 {
342 339 // Handles plot rendering when mouse is moving
343 340 impl->m_RenderingDelegate->onMouseMove(event);
344 341
345 342 VisualizationDragWidget::mouseMoveEvent(event);
346 343 }
347 344
348 345 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
349 346 {
350 347 auto zoomOrientations = QFlags<Qt::Orientation>{};
351 348
352 349 // Lambda that enables a zoom orientation if the key modifier related to this orientation
353 350 // has
354 351 // been pressed
355 352 auto enableOrientation
356 353 = [&zoomOrientations, event](const auto &orientation, const auto &modifier) {
357 354 auto orientationEnabled = event->modifiers().testFlag(modifier);
358 355 zoomOrientations.setFlag(orientation, orientationEnabled);
359 356 };
360 357 enableOrientation(Qt::Vertical, VERTICAL_ZOOM_MODIFIER);
361 358 enableOrientation(Qt::Horizontal, HORIZONTAL_ZOOM_MODIFIER);
362 359
363 360 ui->widget->axisRect()->setRangeZoom(zoomOrientations);
364 361 }
365 362
366 363 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
367 364 {
368 365 impl->m_IsCalibration = event->modifiers().testFlag(Qt::ControlModifier);
369 366
370 367 plot().setInteraction(QCP::iRangeDrag, !event->modifiers().testFlag(Qt::AltModifier));
371 368
372 369 VisualizationDragWidget::mousePressEvent(event);
373 370 }
374 371
375 372 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
376 373 {
377 374 impl->m_IsCalibration = false;
378 375 }
379 376
380 377 void VisualizationGraphWidget::onDataCacheVariableUpdated()
381 378 {
382 379 auto graphRange = ui->widget->xAxis->range();
383 380 auto dateTime = SqpRange{graphRange.lower, graphRange.upper};
384 381
385 382 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
386 383 auto variable = variableEntry.first;
387 384 qCDebug(LOG_VisualizationGraphWidget())
388 385 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
389 386 qCDebug(LOG_VisualizationGraphWidget())
390 387 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
391 388 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range())) {
392 389 VisualizationGraphHelper::updateData(variableEntry.second, variable->dataSeries(),
393 390 variable->range());
394 391 }
395 392 }
396 393 }
397 394
398 395 void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr<Variable> variable,
399 396 const SqpRange &range)
400 397 {
401 398 auto it = impl->m_VariableToPlotMultiMap.find(variable);
402 399 if (it != impl->m_VariableToPlotMultiMap.end()) {
403 400 VisualizationGraphHelper::updateData(it->second, variable->dataSeries(), range);
404 401 }
405 402 }
@@ -1,274 +1,291
1 1 #include "CosinusProvider.h"
2 2 #include "MockDefs.h"
3 3
4 4 #include <Data/DataProviderParameters.h>
5 5 #include <Data/ScalarSeries.h>
6 6 #include <Data/SpectrogramSeries.h>
7 7 #include <Data/VectorSeries.h>
8 8
9 9 #include <cmath>
10 #include <set>
10 11
11 12 #include <QFuture>
12 13 #include <QThread>
13 14 #include <QtConcurrent/QtConcurrent>
14 15
15 16 Q_LOGGING_CATEGORY(LOG_CosinusProvider, "CosinusProvider")
16 17
17 18 namespace {
18 19
19 20 /// Number of bands generated for a spectrogram
20 21 const auto SPECTROGRAM_NUMBER_BANDS = 30;
21 22
23 /// Bands for which to generate NaN values for a spectrogram
24 const auto SPECTROGRAM_NAN_BANDS = std::set<int>{1, 3, 10, 20};
25
26 /// Bands for which to generate zeros for a spectrogram
27 const auto SPECTROGRAM_ZERO_BANDS = std::set<int>{2, 15, 19, 29};
28
22 29 /// Abstract cosinus type
23 30 struct ICosinusType {
24 31 virtual ~ICosinusType() = default;
25 32 /// @return the number of components generated for the type
26 33 virtual int componentCount() const = 0;
27 34 /// @return the data series created for the type
28 35 virtual std::shared_ptr<IDataSeries> createDataSeries(std::vector<double> xAxisData,
29 std::vector<double> valuesData,
30 Unit xAxisUnit,
31 Unit valuesUnit) const = 0;
36 std::vector<double> valuesData) const = 0;
32 37 /// Generates values (one value per component)
33 38 /// @param x the x-axis data used to generate values
34 39 /// @param values the vector in which to insert the generated values
35 40 /// @param dataIndex the index of insertion of the generated values
36 41 ///
37 42 virtual void generateValues(double x, std::vector<double> &values, int dataIndex) const = 0;
38 43 };
39 44
40 45 struct ScalarCosinus : public ICosinusType {
41 46 int componentCount() const override { return 1; }
42 47
43 48 std::shared_ptr<IDataSeries> createDataSeries(std::vector<double> xAxisData,
44 std::vector<double> valuesData, Unit xAxisUnit,
45 Unit valuesUnit) const override
49 std::vector<double> valuesData) const override
46 50 {
47 51 return std::make_shared<ScalarSeries>(std::move(xAxisData), std::move(valuesData),
48 xAxisUnit, valuesUnit);
52 Unit{QStringLiteral("t"), true}, Unit{});
49 53 }
50 54
51 55 void generateValues(double x, std::vector<double> &values, int dataIndex) const override
52 56 {
53 57 values[dataIndex] = std::cos(x);
54 58 }
55 59 };
56 60
57 61 struct SpectrogramCosinus : public ICosinusType {
58 62 /// Ctor with y-axis
59 explicit SpectrogramCosinus(std::vector<double> yAxisData, Unit yAxisUnit)
60 : m_YAxisData{std::move(yAxisData)}, m_YAxisUnit{std::move(yAxisUnit)}
63 explicit SpectrogramCosinus(std::vector<double> yAxisData, Unit yAxisUnit, Unit valuesUnit)
64 : m_YAxisData{std::move(yAxisData)},
65 m_YAxisUnit{std::move(yAxisUnit)},
66 m_ValuesUnit{std::move(valuesUnit)}
61 67 {
62 68 }
63 69
64 70 int componentCount() const override { return m_YAxisData.size(); }
65 71
66 72 std::shared_ptr<IDataSeries> createDataSeries(std::vector<double> xAxisData,
67 std::vector<double> valuesData, Unit xAxisUnit,
68 Unit valuesUnit) const override
73 std::vector<double> valuesData) const override
69 74 {
70 return std::make_shared<SpectrogramSeries>(std::move(xAxisData), m_YAxisData,
71 std::move(valuesData), xAxisUnit, m_YAxisUnit,
72 valuesUnit);
75 return std::make_shared<SpectrogramSeries>(
76 std::move(xAxisData), m_YAxisData, std::move(valuesData),
77 Unit{QStringLiteral("t"), true}, m_YAxisUnit, m_ValuesUnit);
73 78 }
74 79
75 80 void generateValues(double x, std::vector<double> &values, int dataIndex) const override
76 81 {
77 82 auto componentCount = this->componentCount();
78 83 for (int i = 0; i < componentCount; ++i) {
79 84 auto y = m_YAxisData[i];
85
86 double value;
87
88 if (SPECTROGRAM_ZERO_BANDS.find(y) != SPECTROGRAM_ZERO_BANDS.end()) {
89 value = 0.;
90 }
91 else if (SPECTROGRAM_NAN_BANDS.find(y) != SPECTROGRAM_NAN_BANDS.end()) {
92 value = std::numeric_limits<double>::quiet_NaN();
93 }
94 else {
95 // Generates value for non NaN/zero bands
80 96 auto r = 3 * std::sqrt(x * x + y * y) + 1e-2;
81 auto value = 2 * x * (std::cos(r + 2) / r - std::sin(r + 2) / r);
97 value = 2 * x * (std::cos(r + 2) / r - std::sin(r + 2) / r);
98 }
82 99
83 100 values[componentCount * dataIndex + i] = value;
84 101 }
85 102 }
86 103
87 104 std::vector<double> m_YAxisData;
88 105 Unit m_YAxisUnit;
106 Unit m_ValuesUnit;
89 107 };
90 108
91 109 struct VectorCosinus : public ICosinusType {
92 110 int componentCount() const override { return 3; }
93 111
94 112 std::shared_ptr<IDataSeries> createDataSeries(std::vector<double> xAxisData,
95 std::vector<double> valuesData, Unit xAxisUnit,
96 Unit valuesUnit) const override
113 std::vector<double> valuesData) const override
97 114 {
98 115 return std::make_shared<VectorSeries>(std::move(xAxisData), std::move(valuesData),
99 xAxisUnit, valuesUnit);
116 Unit{QStringLiteral("t"), true}, Unit{});
100 117 }
101 118
102 119 void generateValues(double x, std::vector<double> &values, int dataIndex) const override
103 120 {
104 121 // Generates value for each component: cos(x), cos(x)/2, cos(x)/3
105 122 auto xValue = std::cos(x);
106 123 auto componentCount = this->componentCount();
107 124 for (auto i = 0; i < componentCount; ++i) {
108 125 values[componentCount * dataIndex + i] = xValue / (i + 1);
109 126 }
110 127 }
111 128 };
112 129
113 130 /// Converts string to cosinus type
114 131 /// @return the cosinus type if the string could be converted, nullptr otherwise
115 132 std::unique_ptr<ICosinusType> cosinusType(const QString &type) noexcept
116 133 {
117 134 if (type.compare(QStringLiteral("scalar"), Qt::CaseInsensitive) == 0) {
118 135 return std::make_unique<ScalarCosinus>();
119 136 }
120 137 else if (type.compare(QStringLiteral("spectrogram"), Qt::CaseInsensitive) == 0) {
121 138 // Generates default y-axis data for spectrogram [0., 1., 2., ...]
122 139 std::vector<double> yAxisData(SPECTROGRAM_NUMBER_BANDS);
123 140 std::iota(yAxisData.begin(), yAxisData.end(), 0.);
124 141
125 return std::make_unique<SpectrogramCosinus>(std::move(yAxisData), Unit{"eV"});
142 return std::make_unique<SpectrogramCosinus>(std::move(yAxisData), Unit{"eV"},
143 Unit{"eV/(cm^2-s-sr-eV)"});
126 144 }
127 145 else if (type.compare(QStringLiteral("vector"), Qt::CaseInsensitive) == 0) {
128 146 return std::make_unique<VectorCosinus>();
129 147 }
130 148 else {
131 149 return nullptr;
132 150 }
133 151 }
134 152
135 153 } // namespace
136 154
137 155 std::shared_ptr<IDataProvider> CosinusProvider::clone() const
138 156 {
139 157 // No copy is made in clone
140 158 return std::make_shared<CosinusProvider>();
141 159 }
142 160
143 161 std::shared_ptr<IDataSeries> CosinusProvider::retrieveData(QUuid acqIdentifier,
144 162 const SqpRange &dataRangeRequested,
145 163 const QVariantHash &data)
146 164 {
147 165 // TODO: Add Mutex
148 166 auto dataIndex = 0;
149 167
150 168 // Retrieves cosinus type
151 169 auto typeVariant = data.value(COSINUS_TYPE_KEY, COSINUS_TYPE_DEFAULT_VALUE);
152 170 if (!typeVariant.canConvert<QString>()) {
153 171 qCCritical(LOG_CosinusProvider()) << tr("Can't retrieve data: invalid type");
154 172 return nullptr;
155 173 }
156 174
157 175 auto type = cosinusType(typeVariant.toString());
158 176 if (!type) {
159 177 qCCritical(LOG_CosinusProvider()) << tr("Can't retrieve data: unknown type");
160 178 return nullptr;
161 179 }
162 180
163 181 // Retrieves frequency
164 182 auto freqVariant = data.value(COSINUS_FREQUENCY_KEY, COSINUS_FREQUENCY_DEFAULT_VALUE);
165 183 if (!freqVariant.canConvert<double>()) {
166 184 qCCritical(LOG_CosinusProvider()) << tr("Can't retrieve data: invalid frequency");
167 185 return nullptr;
168 186 }
169 187
170 188 // Gets the timerange from the parameters
171 189 double freq = freqVariant.toDouble();
172 190 double start = std::ceil(dataRangeRequested.m_TStart * freq);
173 191 double end = std::floor(dataRangeRequested.m_TEnd * freq);
174 192
175 193 // We assure that timerange is valid
176 194 if (end < start) {
177 195 std::swap(start, end);
178 196 }
179 197
180 198 // Generates scalar series containing cosinus values (one value per second, end value is
181 199 // included)
182 200 auto dataCount = end - start + 1;
183 201
184 202 // Number of components (depending on the cosinus type)
185 203 auto componentCount = type->componentCount();
186 204
187 205 auto xAxisData = std::vector<double>{};
188 206 xAxisData.resize(dataCount);
189 207
190 208 auto valuesData = std::vector<double>{};
191 209 valuesData.resize(dataCount * componentCount);
192 210
193 211 int progress = 0;
194 212 auto progressEnd = dataCount;
195 213 for (auto time = start; time <= end; ++time, ++dataIndex) {
196 214 auto it = m_VariableToEnableProvider.find(acqIdentifier);
197 215 if (it != m_VariableToEnableProvider.end() && it.value()) {
198 216 const auto x = time / freq;
199 217
200 218 xAxisData[dataIndex] = x;
201 219
202 220 // Generates values (depending on the type)
203 221 type->generateValues(x, valuesData, dataIndex);
204 222
205 223 // progression
206 224 int currentProgress = (time - start) * 100.0 / progressEnd;
207 225 if (currentProgress != progress) {
208 226 progress = currentProgress;
209 227
210 228 emit dataProvidedProgress(acqIdentifier, progress);
211 229 qCDebug(LOG_CosinusProvider()) << "TORM: CosinusProvider::retrieveData"
212 230 << QThread::currentThread()->objectName()
213 231 << progress;
214 232 // NOTE: Try to use multithread if possible
215 233 }
216 234 }
217 235 else {
218 236 if (!it.value()) {
219 237 qCDebug(LOG_CosinusProvider())
220 238 << "CosinusProvider::retrieveData: ARRET De l'acquisition detectΓ©"
221 239 << end - time;
222 240 }
223 241 }
224 242 }
225 243 if (progress != 100) {
226 244 // We can close progression beacause all data has been retrieved
227 245 emit dataProvidedProgress(acqIdentifier, 100);
228 246 }
229 return type->createDataSeries(std::move(xAxisData), std::move(valuesData),
230 Unit{QStringLiteral("t"), true}, Unit{});
247 return type->createDataSeries(std::move(xAxisData), std::move(valuesData));
231 248 }
232 249
233 250 void CosinusProvider::requestDataLoading(QUuid acqIdentifier,
234 251 const DataProviderParameters &parameters)
235 252 {
236 253 // TODO: Add Mutex
237 254 m_VariableToEnableProvider[acqIdentifier] = true;
238 255 qCDebug(LOG_CosinusProvider()) << "TORM: CosinusProvider::requestDataLoading"
239 256 << QThread::currentThread()->objectName();
240 257 // NOTE: Try to use multithread if possible
241 258 const auto times = parameters.m_Times;
242 259
243 260 for (const auto &dateTime : qAsConst(times)) {
244 261 if (m_VariableToEnableProvider[acqIdentifier]) {
245 262 auto scalarSeries = this->retrieveData(acqIdentifier, dateTime, parameters.m_Data);
246 263 emit dataProvided(acqIdentifier, scalarSeries, dateTime);
247 264 }
248 265 }
249 266 }
250 267
251 268 void CosinusProvider::requestDataAborting(QUuid acqIdentifier)
252 269 {
253 270 qCDebug(LOG_CosinusProvider()) << "CosinusProvider::requestDataAborting" << acqIdentifier
254 271 << QThread::currentThread()->objectName();
255 272 auto it = m_VariableToEnableProvider.find(acqIdentifier);
256 273 if (it != m_VariableToEnableProvider.end()) {
257 274 it.value() = false;
258 275 }
259 276 else {
260 277 qCDebug(LOG_CosinusProvider())
261 278 << tr("Aborting progression of inexistant identifier detected !!!");
262 279 }
263 280 }
264 281
265 282 std::shared_ptr<IDataSeries> CosinusProvider::provideDataSeries(const SqpRange &dataRangeRequested,
266 283 const QVariantHash &data)
267 284 {
268 285 auto uid = QUuid::createUuid();
269 286 m_VariableToEnableProvider[uid] = true;
270 287 auto dataSeries = this->retrieveData(uid, dataRangeRequested, data);
271 288
272 289 m_VariableToEnableProvider.remove(uid);
273 290 return dataSeries;
274 291 }
General Comments 0
You need to be logged in to leave comments. Login now