VisualizationGraphHelper.cpp
361 lines
| 12.4 KiB
| text/x-c
|
CppLexer
r243 | #include "Visualization/VisualizationGraphHelper.h" | |||
|
r181 | #include "Visualization/qcustomplot.h" | ||
|
r995 | #include <Data/DataSeriesUtils.h> | ||
|
r182 | #include <Data/ScalarSeries.h> | ||
|
r902 | #include <Data/SpectrogramSeries.h> | ||
|
r583 | #include <Data/VectorSeries.h> | ||
|
r182 | |||
|
r181 | #include <Variable/Variable.h> | ||
r243 | Q_LOGGING_CATEGORY(LOG_VisualizationGraphHelper, "VisualizationGraphHelper") | |||
|
r181 | |||
|
r182 | namespace { | ||
r364 | class SqpDataContainer : public QCPGraphDataContainer { | |||
public: | ||||
|
r453 | void appendGraphData(const QCPGraphData &data) { mData.append(data); } | ||
r364 | }; | |||
|
r583 | /** | ||
r591 | * Struct used to create plottables, depending on the type of the data series from which to create | |||
* them | ||||
|
r583 | * @tparam T the data series' type | ||
* @remarks Default implementation can't create plottables | ||||
*/ | ||||
template <typename T, typename Enabled = void> | ||||
struct PlottablesCreator { | ||||
|
r1280 | static PlottablesMap createPlottables(QCustomPlot &) | ||
|
r583 | { | ||
qCCritical(LOG_DataSeries()) | ||||
<< QObject::tr("Can't create plottables: unmanaged data series type"); | ||||
return {}; | ||||
r235 | } | |||
|
r583 | }; | ||
r235 | ||||
|
r1280 | PlottablesMap createGraphs(QCustomPlot &plot, int nbGraphs) | ||
{ | ||||
PlottablesMap result{}; | ||||
// Creates {nbGraphs} QCPGraph to add to the plot | ||||
for (auto i = 0; i < nbGraphs; ++i) { | ||||
auto graph = plot.addGraph(); | ||||
result.insert({i, graph}); | ||||
} | ||||
plot.replot(); | ||||
return result; | ||||
} | ||||
|
r583 | /** | ||
|
r1280 | * Specialization of PlottablesCreator for scalars | ||
|
r583 | * @sa ScalarSeries | ||
*/ | ||||
template <typename T> | ||||
|
r1280 | struct PlottablesCreator<T, typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value> > { | ||
static PlottablesMap createPlottables(QCustomPlot &plot) { return createGraphs(plot, 1); } | ||||
}; | ||||
|
r583 | |||
|
r1280 | /** | ||
* Specialization of PlottablesCreator for vectors | ||||
* @sa VectorSeries | ||||
*/ | ||||
template <typename T> | ||||
struct PlottablesCreator<T, typename std::enable_if_t<std::is_base_of<VectorSeries, T>::value> > { | ||||
static PlottablesMap createPlottables(QCustomPlot &plot) { return createGraphs(plot, 3); } | ||||
|
r583 | }; | ||
r548 | ||||
|
r902 | /** | ||
* Specialization of PlottablesCreator for spectrograms | ||||
* @sa SpectrogramSeries | ||||
*/ | ||||
template <typename T> | ||||
struct PlottablesCreator<T, | ||||
typename std::enable_if_t<std::is_base_of<SpectrogramSeries, T>::value> > { | ||||
|
r1280 | static PlottablesMap createPlottables(QCustomPlot &plot) | ||
|
r902 | { | ||
PlottablesMap result{}; | ||||
result.insert({0, new QCPColorMap{plot.xAxis, plot.yAxis}}); | ||||
plot.replot(); | ||||
return result; | ||||
} | ||||
}; | ||||
|
r583 | /** | ||
r591 | * Struct used to update plottables, depending on the type of the data series from which to update | |||
* them | ||||
|
r583 | * @tparam T the data series' type | ||
* @remarks Default implementation can't update plottables | ||||
*/ | ||||
template <typename T, typename Enabled = void> | ||||
struct PlottablesUpdater { | ||||
|
r900 | static void setPlotYAxisRange(T &, const SqpRange &, QCustomPlot &) | ||
{ | ||||
|
r905 | qCCritical(LOG_VisualizationGraphHelper()) | ||
|
r900 | << QObject::tr("Can't set plot y-axis range: unmanaged data series type"); | ||
} | ||||
|
r583 | static void updatePlottables(T &, PlottablesMap &, const SqpRange &, bool) | ||
{ | ||||
|
r905 | qCCritical(LOG_VisualizationGraphHelper()) | ||
|
r583 | << QObject::tr("Can't update plottables: unmanaged data series type"); | ||
} | ||||
}; | ||||
|
r182 | |||
|
r583 | /** | ||
* Specialization of PlottablesUpdater for scalars and vectors | ||||
* @sa ScalarSeries | ||||
* @sa VectorSeries | ||||
*/ | ||||
template <typename T> | ||||
struct PlottablesUpdater<T, | ||||
typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value | ||||
or std::is_base_of<VectorSeries, T>::value> > { | ||||
|
r900 | static void setPlotYAxisRange(T &dataSeries, const SqpRange &xAxisRange, QCustomPlot &plot) | ||
{ | ||||
auto minValue = 0., maxValue = 0.; | ||||
dataSeries.lockRead(); | ||||
auto valuesBounds = dataSeries.valuesBounds(xAxisRange.m_TStart, xAxisRange.m_TEnd); | ||||
auto end = dataSeries.cend(); | ||||
if (valuesBounds.first != end && valuesBounds.second != end) { | ||||
auto rangeValue = [](const auto &value) { return std::isnan(value) ? 0. : value; }; | ||||
minValue = rangeValue(valuesBounds.first->minValue()); | ||||
maxValue = rangeValue(valuesBounds.second->maxValue()); | ||||
} | ||||
dataSeries.unlock(); | ||||
plot.yAxis->setRange(QCPRange{minValue, maxValue}); | ||||
} | ||||
|
r583 | static void updatePlottables(T &dataSeries, PlottablesMap &plottables, const SqpRange &range, | ||
bool rescaleAxes) | ||||
{ | ||||
// For each plottable to update, resets its data | ||||
std::map<int, QSharedPointer<SqpDataContainer> > dataContainers{}; | ||||
for (const auto &plottable : plottables) { | ||||
if (auto graph = dynamic_cast<QCPGraph *>(plottable.second)) { | ||||
auto dataContainer = QSharedPointer<SqpDataContainer>::create(); | ||||
graph->setData(dataContainer); | ||||
dataContainers.insert({plottable.first, dataContainer}); | ||||
} | ||||
} | ||||
r636 | dataSeries.lockRead(); | |||
|
r182 | |||
|
r583 | // - Gets the data of the series included in the current range | ||
r591 | // - Updates each plottable by adding, for each data item, a point that takes x-axis data | |||
// and value data. The correct value is retrieved according to the index of the component | ||||
|
r605 | auto subDataIts = dataSeries.xAxisRange(range.m_TStart, range.m_TEnd); | ||
|
r583 | for (auto it = subDataIts.first; it != subDataIts.second; ++it) { | ||
for (const auto &dataContainer : dataContainers) { | ||||
auto componentIndex = dataContainer.first; | ||||
dataContainer.second->appendGraphData( | ||||
QCPGraphData(it->x(), it->value(componentIndex))); | ||||
} | ||||
} | ||||
r235 | ||||
|
r583 | dataSeries.unlock(); | ||
|
r183 | |||
|
r583 | if (!plottables.empty()) { | ||
auto plot = plottables.begin()->second->parentPlot(); | ||||
|
r183 | |||
|
r583 | if (rescaleAxes) { | ||
plot->rescaleAxes(); | ||||
} | ||||
} | ||||
|
r182 | } | ||
|
r583 | }; | ||
|
r903 | /** | ||
* Specialization of PlottablesUpdater for spectrograms | ||||
* @sa SpectrogramSeries | ||||
*/ | ||||
template <typename T> | ||||
struct PlottablesUpdater<T, | ||||
typename std::enable_if_t<std::is_base_of<SpectrogramSeries, T>::value> > { | ||||
|
r904 | static void setPlotYAxisRange(T &dataSeries, const SqpRange &xAxisRange, QCustomPlot &plot) | ||
{ | ||||
double min, max; | ||||
|
r988 | std::tie(min, max) = dataSeries.yBounds(); | ||
|
r904 | |||
if (!std::isnan(min) && !std::isnan(max)) { | ||||
plot.yAxis->setRange(QCPRange{min, max}); | ||||
} | ||||
} | ||||
|
r903 | static void updatePlottables(T &dataSeries, PlottablesMap &plottables, const SqpRange &range, | ||
bool rescaleAxes) | ||||
{ | ||||
if (plottables.empty()) { | ||||
qCDebug(LOG_VisualizationGraphHelper()) | ||||
<< QObject::tr("Can't update spectrogram: no colormap has been associated"); | ||||
return; | ||||
} | ||||
// Gets the colormap to update (normally there is only one colormap) | ||||
Q_ASSERT(plottables.size() == 1); | ||||
auto colormap = dynamic_cast<QCPColorMap *>(plottables.at(0)); | ||||
Q_ASSERT(colormap != nullptr); | ||||
dataSeries.lockRead(); | ||||
|
r995 | // Processing spectrogram data for display in QCustomPlot | ||
|
r903 | auto its = dataSeries.xAxisRange(range.m_TStart, range.m_TEnd); | ||
|
r995 | // Computes logarithmic y-axis resolution for the spectrogram | ||
auto yData = its.first->y(); | ||||
auto yResolution = DataSeriesUtils::resolution(yData.begin(), yData.end(), true); | ||||
// Generates mesh for colormap | ||||
auto mesh = DataSeriesUtils::regularMesh( | ||||
its.first, its.second, DataSeriesUtils::Resolution{dataSeries.xResolution()}, | ||||
yResolution); | ||||
dataSeries.unlock(); | ||||
|
r903 | |||
|
r995 | colormap->data()->setSize(mesh.m_NbX, mesh.m_NbY); | ||
if (!mesh.isEmpty()) { | ||||
colormap->data()->setRange( | ||||
QCPRange{mesh.m_XMin, mesh.xMax()}, | ||||
// y-axis range is converted to linear values | ||||
QCPRange{std::pow(10, mesh.m_YMin), std::pow(10, mesh.yMax())}); | ||||
|
r922 | |||
|
r995 | // Sets values | ||
auto index = 0; | ||||
for (auto it = mesh.m_Data.begin(), end = mesh.m_Data.end(); it != end; ++it, ++index) { | ||||
auto xIndex = index % mesh.m_NbX; | ||||
auto yIndex = index / mesh.m_NbX; | ||||
|
r922 | |||
|
r995 | colormap->data()->setCell(xIndex, yIndex, *it); | ||
// Makes the NaN values to be transparent in the colormap | ||||
if (std::isnan(*it)) { | ||||
|
r922 | colormap->data()->setAlpha(xIndex, yIndex, 0); | ||
} | ||||
|
r903 | } | ||
} | ||||
// Rescales axes | ||||
auto plot = colormap->parentPlot(); | ||||
if (rescaleAxes) { | ||||
plot->rescaleAxes(); | ||||
} | ||||
} | ||||
}; | ||||
|
r583 | /** | ||
* Helper used to create/update plottables | ||||
*/ | ||||
struct IPlottablesHelper { | ||||
virtual ~IPlottablesHelper() noexcept = default; | ||||
virtual PlottablesMap create(QCustomPlot &plot) const = 0; | ||||
|
r900 | virtual void setYAxisRange(const SqpRange &xAxisRange, QCustomPlot &plot) const = 0; | ||
|
r583 | virtual void update(PlottablesMap &plottables, const SqpRange &range, | ||
bool rescaleAxes = false) const = 0; | ||||
}; | ||||
/** | ||||
* Default implementation of IPlottablesHelper, which takes data series to create/update plottables | ||||
* @tparam T the data series' type | ||||
*/ | ||||
template <typename T> | ||||
struct PlottablesHelper : public IPlottablesHelper { | ||||
|
r1280 | explicit PlottablesHelper(std::shared_ptr<T> dataSeries) : m_DataSeries{dataSeries} {} | ||
|
r583 | |||
PlottablesMap create(QCustomPlot &plot) const override | ||||
{ | ||||
|
r1280 | return PlottablesCreator<T>::createPlottables(plot); | ||
|
r182 | } | ||
|
r583 | void update(PlottablesMap &plottables, const SqpRange &range, bool rescaleAxes) const override | ||
{ | ||||
|
r1280 | if (m_DataSeries) { | ||
PlottablesUpdater<T>::updatePlottables(*m_DataSeries, plottables, range, rescaleAxes); | ||||
} | ||||
else { | ||||
qCCritical(LOG_VisualizationGraphHelper()) << "Can't update plottables: inconsistency " | ||||
"between the type of data series and the " | ||||
"type supposed"; | ||||
} | ||||
|
r583 | } | ||
|
r182 | |||
|
r900 | void setYAxisRange(const SqpRange &xAxisRange, QCustomPlot &plot) const override | ||
{ | ||||
|
r1280 | if (m_DataSeries) { | ||
PlottablesUpdater<T>::setPlotYAxisRange(*m_DataSeries, xAxisRange, plot); | ||||
} | ||||
else { | ||||
qCCritical(LOG_VisualizationGraphHelper()) << "Can't update plottables: inconsistency " | ||||
"between the type of data series and the " | ||||
"type supposed"; | ||||
} | ||||
|
r900 | } | ||
|
r1280 | std::shared_ptr<T> m_DataSeries; | ||
|
r583 | }; | ||
|
r182 | |||
|
r1280 | /// Creates IPlottablesHelper according to the type of data series a variable holds | ||
std::unique_ptr<IPlottablesHelper> createHelper(std::shared_ptr<Variable> variable) noexcept | ||||
r548 | { | |||
|
r1280 | switch (variable->type()) { | ||
case DataSeriesType::SCALAR: | ||||
return std::make_unique<PlottablesHelper<ScalarSeries> >( | ||||
std::dynamic_pointer_cast<ScalarSeries>(variable->dataSeries())); | ||||
case DataSeriesType::SPECTROGRAM: | ||||
return std::make_unique<PlottablesHelper<SpectrogramSeries> >( | ||||
std::dynamic_pointer_cast<SpectrogramSeries>(variable->dataSeries())); | ||||
case DataSeriesType::VECTOR: | ||||
return std::make_unique<PlottablesHelper<VectorSeries> >( | ||||
std::dynamic_pointer_cast<VectorSeries>(variable->dataSeries())); | ||||
default: | ||||
// Creates default helper | ||||
break; | ||||
r548 | } | |||
|
r1280 | |||
return std::make_unique<PlottablesHelper<IDataSeries> >(nullptr); | ||||
r548 | } | |||
|
r583 | } // namespace | ||
|
r181 | |||
|
r583 | PlottablesMap VisualizationGraphHelper::create(std::shared_ptr<Variable> variable, | ||
QCustomPlot &plot) noexcept | ||||
{ | ||||
|
r181 | if (variable) { | ||
|
r1280 | auto helper = createHelper(variable); | ||
|
r583 | auto plottables = helper->create(plot); | ||
return plottables; | ||||
|
r181 | } | ||
else { | ||||
r243 | qCDebug(LOG_VisualizationGraphHelper()) | |||
|
r181 | << QObject::tr("Can't create graph plottables : the variable is null"); | ||
|
r583 | return PlottablesMap{}; | ||
|
r181 | } | ||
} | ||||
r235 | ||||
|
r900 | void VisualizationGraphHelper::setYAxisRange(std::shared_ptr<Variable> variable, | ||
QCustomPlot &plot) noexcept | ||||
{ | ||||
if (variable) { | ||||
|
r1280 | auto helper = createHelper(variable); | ||
|
r900 | helper->setYAxisRange(variable->range(), plot); | ||
} | ||||
else { | ||||
qCDebug(LOG_VisualizationGraphHelper()) | ||||
<< QObject::tr("Can't set y-axis range of plot: the variable is null"); | ||||
} | ||||
} | ||||
|
r587 | void VisualizationGraphHelper::updateData(PlottablesMap &plottables, | ||
|
r1280 | std::shared_ptr<Variable> variable, | ||
r542 | const SqpRange &dateTime) | |||
r235 | { | |||
|
r1280 | auto helper = createHelper(variable); | ||
|
r583 | helper->update(plottables, dateTime); | ||
r235 | } | |||