##// END OF EJS Templates
Mesh generation for QColorMap (4)...
Alexandre Leroux -
r995:bf7c3257108b
parent child
Show More
@@ -1,344 +1,344
1 1 #include "Visualization/VisualizationGraphHelper.h"
2 2 #include "Visualization/qcustomplot.h"
3 3
4 #include <Data/DataSeriesUtils.h>
4 5 #include <Data/ScalarSeries.h>
5 6 #include <Data/SpectrogramSeries.h>
6 7 #include <Data/VectorSeries.h>
7 8
8 9 #include <Variable/Variable.h>
9 10
10 11 Q_LOGGING_CATEGORY(LOG_VisualizationGraphHelper, "VisualizationGraphHelper")
11 12
12 13 namespace {
13 14
14 15 class SqpDataContainer : public QCPGraphDataContainer {
15 16 public:
16 17 void appendGraphData(const QCPGraphData &data) { mData.append(data); }
17 18 };
18 19
19 20 /**
20 21 * Struct used to create plottables, depending on the type of the data series from which to create
21 22 * them
22 23 * @tparam T the data series' type
23 24 * @remarks Default implementation can't create plottables
24 25 */
25 26 template <typename T, typename Enabled = void>
26 27 struct PlottablesCreator {
27 28 static PlottablesMap createPlottables(T &, QCustomPlot &)
28 29 {
29 30 qCCritical(LOG_DataSeries())
30 31 << QObject::tr("Can't create plottables: unmanaged data series type");
31 32 return {};
32 33 }
33 34 };
34 35
35 36 /**
36 37 * Specialization of PlottablesCreator for scalars and vectors
37 38 * @sa ScalarSeries
38 39 * @sa VectorSeries
39 40 */
40 41 template <typename T>
41 42 struct PlottablesCreator<T,
42 43 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
43 44 or std::is_base_of<VectorSeries, T>::value> > {
44 45 static PlottablesMap createPlottables(T &dataSeries, QCustomPlot &plot)
45 46 {
46 47 PlottablesMap result{};
47 48
48 49 // Gets the number of components of the data series
49 50 dataSeries.lockRead();
50 51 auto componentCount = dataSeries.valuesData()->componentCount();
51 52 dataSeries.unlock();
52 53
53 54 // For each component of the data series, creates a QCPGraph to add to the plot
54 55 for (auto i = 0; i < componentCount; ++i) {
55 56 auto graph = plot.addGraph();
56 57 result.insert({i, graph});
57 58 }
58 59
59 60 plot.replot();
60 61
61 62 return result;
62 63 }
63 64 };
64 65
65 66 /**
66 67 * Specialization of PlottablesCreator for spectrograms
67 68 * @sa SpectrogramSeries
68 69 */
69 70 template <typename T>
70 71 struct PlottablesCreator<T,
71 72 typename std::enable_if_t<std::is_base_of<SpectrogramSeries, T>::value> > {
72 73 static PlottablesMap createPlottables(T &dataSeries, QCustomPlot &plot)
73 74 {
74 75 PlottablesMap result{};
75 76 result.insert({0, new QCPColorMap{plot.xAxis, plot.yAxis}});
76 77
77 78 plot.replot();
78 79
79 80 return result;
80 81 }
81 82 };
82 83
83 84 /**
84 85 * Struct used to update plottables, depending on the type of the data series from which to update
85 86 * them
86 87 * @tparam T the data series' type
87 88 * @remarks Default implementation can't update plottables
88 89 */
89 90 template <typename T, typename Enabled = void>
90 91 struct PlottablesUpdater {
91 92 static void setPlotYAxisRange(T &, const SqpRange &, QCustomPlot &)
92 93 {
93 94 qCCritical(LOG_VisualizationGraphHelper())
94 95 << QObject::tr("Can't set plot y-axis range: unmanaged data series type");
95 96 }
96 97
97 98 static void updatePlottables(T &, PlottablesMap &, const SqpRange &, bool)
98 99 {
99 100 qCCritical(LOG_VisualizationGraphHelper())
100 101 << QObject::tr("Can't update plottables: unmanaged data series type");
101 102 }
102 103 };
103 104
104 105 /**
105 106 * Specialization of PlottablesUpdater for scalars and vectors
106 107 * @sa ScalarSeries
107 108 * @sa VectorSeries
108 109 */
109 110 template <typename T>
110 111 struct PlottablesUpdater<T,
111 112 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
112 113 or std::is_base_of<VectorSeries, T>::value> > {
113 114 static void setPlotYAxisRange(T &dataSeries, const SqpRange &xAxisRange, QCustomPlot &plot)
114 115 {
115 116 auto minValue = 0., maxValue = 0.;
116 117
117 118 dataSeries.lockRead();
118 119 auto valuesBounds = dataSeries.valuesBounds(xAxisRange.m_TStart, xAxisRange.m_TEnd);
119 120 auto end = dataSeries.cend();
120 121 if (valuesBounds.first != end && valuesBounds.second != end) {
121 122 auto rangeValue = [](const auto &value) { return std::isnan(value) ? 0. : value; };
122 123
123 124 minValue = rangeValue(valuesBounds.first->minValue());
124 125 maxValue = rangeValue(valuesBounds.second->maxValue());
125 126 }
126 127 dataSeries.unlock();
127 128
128 129 plot.yAxis->setRange(QCPRange{minValue, maxValue});
129 130 }
130 131
131 132 static void updatePlottables(T &dataSeries, PlottablesMap &plottables, const SqpRange &range,
132 133 bool rescaleAxes)
133 134 {
134 135
135 136 // For each plottable to update, resets its data
136 137 std::map<int, QSharedPointer<SqpDataContainer> > dataContainers{};
137 138 for (const auto &plottable : plottables) {
138 139 if (auto graph = dynamic_cast<QCPGraph *>(plottable.second)) {
139 140 auto dataContainer = QSharedPointer<SqpDataContainer>::create();
140 141 graph->setData(dataContainer);
141 142
142 143 dataContainers.insert({plottable.first, dataContainer});
143 144 }
144 145 }
145 146 dataSeries.lockRead();
146 147
147 148 // - Gets the data of the series included in the current range
148 149 // - Updates each plottable by adding, for each data item, a point that takes x-axis data
149 150 // and value data. The correct value is retrieved according to the index of the component
150 151 auto subDataIts = dataSeries.xAxisRange(range.m_TStart, range.m_TEnd);
151 152 for (auto it = subDataIts.first; it != subDataIts.second; ++it) {
152 153 for (const auto &dataContainer : dataContainers) {
153 154 auto componentIndex = dataContainer.first;
154 155 dataContainer.second->appendGraphData(
155 156 QCPGraphData(it->x(), it->value(componentIndex)));
156 157 }
157 158 }
158 159
159 160 dataSeries.unlock();
160 161
161 162 if (!plottables.empty()) {
162 163 auto plot = plottables.begin()->second->parentPlot();
163 164
164 165 if (rescaleAxes) {
165 166 plot->rescaleAxes();
166 167 }
167 168
168 169 plot->replot();
169 170 }
170 171 }
171 172 };
172 173
173 174 /**
174 175 * Specialization of PlottablesUpdater for spectrograms
175 176 * @sa SpectrogramSeries
176 177 */
177 178 template <typename T>
178 179 struct PlottablesUpdater<T,
179 180 typename std::enable_if_t<std::is_base_of<SpectrogramSeries, T>::value> > {
180 181 static void setPlotYAxisRange(T &dataSeries, const SqpRange &xAxisRange, QCustomPlot &plot)
181 182 {
182 183 double min, max;
183 184 std::tie(min, max) = dataSeries.yBounds();
184 185
185 186 if (!std::isnan(min) && !std::isnan(max)) {
186 187 plot.yAxis->setRange(QCPRange{min, max});
187 188 }
188 189 }
189 190
190 191 static void updatePlottables(T &dataSeries, PlottablesMap &plottables, const SqpRange &range,
191 192 bool rescaleAxes)
192 193 {
193 194 if (plottables.empty()) {
194 195 qCDebug(LOG_VisualizationGraphHelper())
195 196 << QObject::tr("Can't update spectrogram: no colormap has been associated");
196 197 return;
197 198 }
198 199
199 200 // Gets the colormap to update (normally there is only one colormap)
200 201 Q_ASSERT(plottables.size() == 1);
201 202 auto colormap = dynamic_cast<QCPColorMap *>(plottables.at(0));
202 203 Q_ASSERT(colormap != nullptr);
203 204
204 205 dataSeries.lockRead();
205 206
207 // Processing spectrogram data for display in QCustomPlot
206 208 auto its = dataSeries.xAxisRange(range.m_TStart, range.m_TEnd);
207 /// @todo ALX: use iterators here
208 auto yAxis = dataSeries.yAxis();
209
210 // Gets properties of x-axis and y-axis to set size and range of the colormap
211 auto nbX = std::distance(its.first, its.second);
212 auto xMin = nbX != 0 ? its.first->x() : 0.;
213 auto xMax = nbX != 0 ? (its.second - 1)->x() : 0.;
214
215 auto nbY = yAxis.size();
216 auto yMin = 0., yMax = 0.;
217 if (nbY != 0) {
218 std::tie(yMin, yMax) = yAxis.bounds();
219 }
220 209
221 colormap->data()->setSize(nbX, nbY);
222 colormap->data()->setRange(QCPRange{xMin, xMax}, QCPRange{yMin, yMax});
210 // Computes logarithmic y-axis resolution for the spectrogram
211 auto yData = its.first->y();
212 auto yResolution = DataSeriesUtils::resolution(yData.begin(), yData.end(), true);
213
214 // Generates mesh for colormap
215 auto mesh = DataSeriesUtils::regularMesh(
216 its.first, its.second, DataSeriesUtils::Resolution{dataSeries.xResolution()},
217 yResolution);
218
219 dataSeries.unlock();
223 220
224 // Sets values
225 auto xIndex = 0;
226 for (auto it = its.first; it != its.second; ++it, ++xIndex) {
227 for (auto yIndex = 0; yIndex < nbY; ++yIndex) {
228 auto value = it->value(yIndex);
221 colormap->data()->setSize(mesh.m_NbX, mesh.m_NbY);
222 if (!mesh.isEmpty()) {
223 colormap->data()->setRange(
224 QCPRange{mesh.m_XMin, mesh.xMax()},
225 // y-axis range is converted to linear values
226 QCPRange{std::pow(10, mesh.m_YMin), std::pow(10, mesh.yMax())});
229 227
230 colormap->data()->setCell(xIndex, yIndex, value);
228 // Sets values
229 auto index = 0;
230 for (auto it = mesh.m_Data.begin(), end = mesh.m_Data.end(); it != end; ++it, ++index) {
231 auto xIndex = index % mesh.m_NbX;
232 auto yIndex = index / mesh.m_NbX;
231 233
232 // Processing spectrogram data for display in QCustomPlot
233 /// For the moment, we just make the NaN values to be transparent in the colormap
234 /// @todo ALX: complete treatments (mesh generation, etc.)
235 if (std::isnan(value)) {
234 colormap->data()->setCell(xIndex, yIndex, *it);
235
236 // Makes the NaN values to be transparent in the colormap
237 if (std::isnan(*it)) {
236 238 colormap->data()->setAlpha(xIndex, yIndex, 0);
237 239 }
238 240 }
239 241 }
240 242
241 dataSeries.unlock();
242
243 243 // Rescales axes
244 244 auto plot = colormap->parentPlot();
245 245
246 246 if (rescaleAxes) {
247 247 plot->rescaleAxes();
248 248 }
249 249
250 250 plot->replot();
251 251 }
252 252 };
253 253
254 254 /**
255 255 * Helper used to create/update plottables
256 256 */
257 257 struct IPlottablesHelper {
258 258 virtual ~IPlottablesHelper() noexcept = default;
259 259 virtual PlottablesMap create(QCustomPlot &plot) const = 0;
260 260 virtual void setYAxisRange(const SqpRange &xAxisRange, QCustomPlot &plot) const = 0;
261 261 virtual void update(PlottablesMap &plottables, const SqpRange &range,
262 262 bool rescaleAxes = false) const = 0;
263 263 };
264 264
265 265 /**
266 266 * Default implementation of IPlottablesHelper, which takes data series to create/update plottables
267 267 * @tparam T the data series' type
268 268 */
269 269 template <typename T>
270 270 struct PlottablesHelper : public IPlottablesHelper {
271 271 explicit PlottablesHelper(T &dataSeries) : m_DataSeries{dataSeries} {}
272 272
273 273 PlottablesMap create(QCustomPlot &plot) const override
274 274 {
275 275 return PlottablesCreator<T>::createPlottables(m_DataSeries, plot);
276 276 }
277 277
278 278 void update(PlottablesMap &plottables, const SqpRange &range, bool rescaleAxes) const override
279 279 {
280 280 PlottablesUpdater<T>::updatePlottables(m_DataSeries, plottables, range, rescaleAxes);
281 281 }
282 282
283 283 void setYAxisRange(const SqpRange &xAxisRange, QCustomPlot &plot) const override
284 284 {
285 285 return PlottablesUpdater<T>::setPlotYAxisRange(m_DataSeries, xAxisRange, plot);
286 286 }
287 287
288 288 T &m_DataSeries;
289 289 };
290 290
291 291 /// Creates IPlottablesHelper according to a data series
292 292 std::unique_ptr<IPlottablesHelper> createHelper(std::shared_ptr<IDataSeries> dataSeries) noexcept
293 293 {
294 294 if (auto scalarSeries = std::dynamic_pointer_cast<ScalarSeries>(dataSeries)) {
295 295 return std::make_unique<PlottablesHelper<ScalarSeries> >(*scalarSeries);
296 296 }
297 297 else if (auto spectrogramSeries = std::dynamic_pointer_cast<SpectrogramSeries>(dataSeries)) {
298 298 return std::make_unique<PlottablesHelper<SpectrogramSeries> >(*spectrogramSeries);
299 299 }
300 300 else if (auto vectorSeries = std::dynamic_pointer_cast<VectorSeries>(dataSeries)) {
301 301 return std::make_unique<PlottablesHelper<VectorSeries> >(*vectorSeries);
302 302 }
303 303 else {
304 304 return std::make_unique<PlottablesHelper<IDataSeries> >(*dataSeries);
305 305 }
306 306 }
307 307
308 308 } // namespace
309 309
310 310 PlottablesMap VisualizationGraphHelper::create(std::shared_ptr<Variable> variable,
311 311 QCustomPlot &plot) noexcept
312 312 {
313 313 if (variable) {
314 314 auto helper = createHelper(variable->dataSeries());
315 315 auto plottables = helper->create(plot);
316 316 return plottables;
317 317 }
318 318 else {
319 319 qCDebug(LOG_VisualizationGraphHelper())
320 320 << QObject::tr("Can't create graph plottables : the variable is null");
321 321 return PlottablesMap{};
322 322 }
323 323 }
324 324
325 325 void VisualizationGraphHelper::setYAxisRange(std::shared_ptr<Variable> variable,
326 326 QCustomPlot &plot) noexcept
327 327 {
328 328 if (variable) {
329 329 auto helper = createHelper(variable->dataSeries());
330 330 helper->setYAxisRange(variable->range(), plot);
331 331 }
332 332 else {
333 333 qCDebug(LOG_VisualizationGraphHelper())
334 334 << QObject::tr("Can't set y-axis range of plot: the variable is null");
335 335 }
336 336 }
337 337
338 338 void VisualizationGraphHelper::updateData(PlottablesMap &plottables,
339 339 std::shared_ptr<IDataSeries> dataSeries,
340 340 const SqpRange &dateTime)
341 341 {
342 342 auto helper = createHelper(dataSeries);
343 343 helper->update(plottables, dateTime);
344 344 }
General Comments 0
You need to be logged in to leave comments. Login now