#include "Visualization/VisualizationGraphHelper.h" #include "Visualization/qcustomplot.h" #include #include #include #include Q_LOGGING_CATEGORY(LOG_VisualizationGraphHelper, "VisualizationGraphHelper") namespace { class SqpDataContainer : public QCPGraphDataContainer { public: void appendGraphData(const QCPGraphData& data) { mData.append(data); } }; /** * Struct used to create plottables, depending on the type of the data series from which to create * them * @tparam T the data series' type * @remarks Default implementation can't create plottables */ template struct PlottablesCreator { static PlottablesMap createPlottables(QCustomPlot&, const std::shared_ptr& dataSeries) { return {}; } }; 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; } /** * Specialization of PlottablesCreator for scalars * @sa ScalarSeries */ template struct PlottablesCreator::value>> { static PlottablesMap createPlottables(QCustomPlot& plot, const std::shared_ptr& dataSeries) { return createGraphs(plot, 1); } }; /** * Specialization of PlottablesCreator for vectors * @sa VectorSeries */ template struct PlottablesCreator::value>> { static PlottablesMap createPlottables(QCustomPlot& plot, const std::shared_ptr& dataSeries) { return createGraphs(plot, 3); } }; /** * Specialization of PlottablesCreator for MultiComponentTimeSeries * @sa VectorSeries */ template struct PlottablesCreator::value>> { static PlottablesMap createPlottables(QCustomPlot& plot, const std::shared_ptr& dataSeries) { return createGraphs(plot, dataSeries->size(1)); } }; /** * Specialization of PlottablesCreator for spectrograms * @sa SpectrogramSeries */ template struct PlottablesCreator::value>> { static PlottablesMap createPlottables(QCustomPlot& plot, const std::shared_ptr& dataSeries) { PlottablesMap result {}; result.insert({ 0, new QCPColorMap { plot.xAxis, plot.yAxis } }); plot.replot(); return result; } }; /** * Struct used to update plottables, depending on the type of the data series from which to update * them * @tparam T the data series' type * @remarks Default implementation can't update plottables */ template struct PlottablesUpdater { static void setPlotYAxisRange(T&, const DateTimeRange&, QCustomPlot&) { qCCritical(LOG_VisualizationGraphHelper()) << QObject::tr("Can't set plot y-axis range: unmanaged data series type"); } static void updatePlottables(T&, PlottablesMap&, const DateTimeRange&, bool) { qCCritical(LOG_VisualizationGraphHelper()) << QObject::tr("Can't update plottables: unmanaged data series type"); } }; /** * Specialization of PlottablesUpdater for scalars and vectors * @sa ScalarSeries * @sa VectorSeries */ template struct PlottablesUpdater::value>> { static void setPlotYAxisRange(T& dataSeries, const DateTimeRange& xAxisRange, QCustomPlot& plot) { auto minValue = 0., maxValue = 0.; if (auto serie = dynamic_cast(&dataSeries)) { if (serie->size()) { maxValue = (*std::max_element(std::begin(*serie), std::end(*serie))).v(); minValue = (*std::min_element(std::begin(*serie), std::end(*serie))).v(); } } plot.yAxis->setRange(QCPRange { minValue, maxValue }); } static void updatePlottables( T& dataSeries, PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes) { // For each plottable to update, resets its data for (const auto& plottable : plottables) { if (auto graph = dynamic_cast(plottable.second)) { auto dataContainer = QSharedPointer::create(); if (auto serie = dynamic_cast(&dataSeries)) { std::for_each( std::begin(*serie), std::end(*serie), [&dataContainer](const auto& value) { dataContainer->appendGraphData(QCPGraphData(value.t(), value.v())); }); } graph->setData(dataContainer); } } if (!plottables.empty()) { auto plot = plottables.begin()->second->parentPlot(); if (rescaleAxes) { plot->rescaleAxes(); } } } }; template struct PlottablesUpdater::value>> { static void setPlotYAxisRange(T& dataSeries, const DateTimeRange& xAxisRange, QCustomPlot& plot) { double minValue = 0., maxValue = 0.; if (auto serie = dynamic_cast(&dataSeries)) { std::for_each( std::begin(*serie), std::end(*serie), [&minValue, &maxValue](const auto& v) { minValue = std::min({ minValue, v.v().x, v.v().y, v.v().z }); maxValue = std::max({ maxValue, v.v().x, v.v().y, v.v().z }); }); } plot.yAxis->setRange(QCPRange { minValue, maxValue }); } static void updatePlottables( T& dataSeries, PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes) { // For each plottable to update, resets its data for (const auto& plottable : plottables) { if (auto graph = dynamic_cast(plottable.second)) { auto dataContainer = QSharedPointer::create(); if (auto serie = dynamic_cast(&dataSeries)) { switch (plottable.first) { case 0: std::for_each(std::begin(*serie), std::end(*serie), [&dataContainer](const auto& value) { dataContainer->appendGraphData( QCPGraphData(value.t(), value.v().x)); }); break; case 1: std::for_each(std::begin(*serie), std::end(*serie), [&dataContainer](const auto& value) { dataContainer->appendGraphData( QCPGraphData(value.t(), value.v().y)); }); break; case 2: std::for_each(std::begin(*serie), std::end(*serie), [&dataContainer](const auto& value) { dataContainer->appendGraphData( QCPGraphData(value.t(), value.v().z)); }); break; default: break; } } graph->setData(dataContainer); } } if (!plottables.empty()) { auto plot = plottables.begin()->second->parentPlot(); if (rescaleAxes) { plot->rescaleAxes(); } } } }; template struct PlottablesUpdater::value>> { static void setPlotYAxisRange(T& dataSeries, const DateTimeRange& xAxisRange, QCustomPlot& plot) { double minValue = 0., maxValue = 0.; if (auto serie = dynamic_cast(&dataSeries)) { std::for_each( std::begin(*serie), std::end(*serie), [&minValue, &maxValue](const auto& v) { minValue = std::min(minValue, std::min_element(v.begin(), v.end())->v()); maxValue = std::max(maxValue, std::max_element(v.begin(), v.end())->v()); }); } plot.yAxis->setRange(QCPRange { minValue, maxValue }); } static void updatePlottables( T& dataSeries, PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes) { for (const auto& plottable : plottables) { if (auto graph = dynamic_cast(plottable.second)) { auto dataContainer = QSharedPointer::create(); if (auto serie = dynamic_cast(&dataSeries)) { // TODO std::for_each(std::begin(*serie), std::end(*serie), [&dataContainer, component = plottable.first](const auto& value) { dataContainer->appendGraphData( QCPGraphData(value.t(), value[component])); }); } graph->setData(dataContainer); } } if (!plottables.empty()) { auto plot = plottables.begin()->second->parentPlot(); if (rescaleAxes) { plot->rescaleAxes(); } } } }; /** * Specialization of PlottablesUpdater for spectrograms * @sa SpectrogramSeries */ template struct PlottablesUpdater::value>> { static void setPlotYAxisRange(T& dataSeries, const DateTimeRange& xAxisRange, QCustomPlot& plot) { // TODO // double min, max; // std::tie(min, max) = dataSeries.yBounds(); // if (!std::isnan(min) && !std::isnan(max)) // { // plot.yAxis->setRange(QCPRange { min, max }); // } double minValue = 0., maxValue = 0.; if (auto serie = dynamic_cast(&dataSeries)) { auto& yAxis = serie->axis(1); if (yAxis.size()) { minValue = *std::min_element(std::cbegin(yAxis), std::cend(yAxis)); maxValue = *std::max_element(std::cbegin(yAxis), std::cend(yAxis)); } } plot.yAxis->setRange(QCPRange { minValue, maxValue }); } static void updatePlottables( T& dataSeries, PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes) { // TODO 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(plottables.at(0)); Q_ASSERT(colormap != nullptr); if (auto serie = dynamic_cast(&dataSeries)) { colormap->data()->setSize(serie->shape()[0], serie->shape()[1]); if (serie->size(0)) { colormap->data()->setRange( QCPRange { serie->begin()->t(), (serie->end() - 1)->t() }, QCPRange { 1., 1000. }); for (int x_index = 0; x_index < serie->shape()[0]; x_index++) { auto pixline = (*serie)[x_index]; for (int y_index = 0; y_index < serie->shape()[1]; y_index++) { auto value = pixline[y_index]; colormap->data()->setCell(x_index, y_index, value); if (std::isnan(value)) { colormap->data()->setAlpha(x_index, y_index, 0); } } } } } // dataSeries.lockRead(); // // Processing spectrogram data for display in QCustomPlot // auto its = dataSeries.xAxisRange(range.m_TStart, range.m_TEnd); // // 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(); // 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()) }); // // 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; // colormap->data()->setCell(xIndex, yIndex, *it); // // Makes the NaN values to be transparent in the colormap // if (std::isnan(*it)) // { // colormap->data()->setAlpha(xIndex, yIndex, 0); // } // } // } // // Rescales axes auto plot = colormap->parentPlot(); setPlotYAxisRange(dataSeries, {}, *plot); if (rescaleAxes) { plot->rescaleAxes(); } } }; /** * Helper used to create/update plottables */ struct IPlottablesHelper { virtual ~IPlottablesHelper() noexcept = default; virtual PlottablesMap create(QCustomPlot& plot) const = 0; virtual void setYAxisRange(const DateTimeRange& xAxisRange, QCustomPlot& plot) const = 0; virtual void update( PlottablesMap& plottables, const DateTimeRange& 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 struct PlottablesHelper : public IPlottablesHelper { explicit PlottablesHelper(std::shared_ptr dataSeries) : m_DataSeries { dataSeries } {} PlottablesMap create(QCustomPlot& plot) const override { return PlottablesCreator::createPlottables(plot, m_DataSeries); } void update( PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes) const override { if (m_DataSeries) { PlottablesUpdater::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"; } } void setYAxisRange(const DateTimeRange& xAxisRange, QCustomPlot& plot) const override { if (m_DataSeries) { PlottablesUpdater::setPlotYAxisRange(*m_DataSeries, xAxisRange, plot); } else { qCCritical(LOG_VisualizationGraphHelper()) << "Can't update plottables: inconsistency " "between the type of data series and the " "type supposed"; } } std::shared_ptr m_DataSeries; }; /// Creates IPlottablesHelper according to the type of data series a variable holds std::unique_ptr createHelper(std::shared_ptr variable) noexcept { switch (variable->type()) { case DataSeriesType::SCALAR: return std::make_unique>( std::dynamic_pointer_cast(variable->data())); case DataSeriesType::SPECTROGRAM: return std::make_unique>( std::dynamic_pointer_cast(variable->data())); case DataSeriesType::VECTOR: return std::make_unique>( std::dynamic_pointer_cast(variable->data())); case DataSeriesType::MULTICOMPONENT: return std::make_unique>( std::dynamic_pointer_cast(variable->data())); default: // Creates default helper break; } return std::make_unique>(nullptr); } } // namespace PlottablesMap VisualizationGraphHelper::create( std::shared_ptr variable, QCustomPlot& plot) noexcept { if (variable) { auto helper = createHelper(variable); auto plottables = helper->create(plot); return plottables; } else { qCDebug(LOG_VisualizationGraphHelper()) << QObject::tr("Can't create graph plottables : the variable is null"); return PlottablesMap {}; } } void VisualizationGraphHelper::setYAxisRange( std::shared_ptr variable, QCustomPlot& plot) noexcept { if (variable) { auto helper = createHelper(variable); helper->setYAxisRange(variable->range(), plot); } else { qCDebug(LOG_VisualizationGraphHelper()) << QObject::tr("Can't set y-axis range of plot: the variable is null"); } } void VisualizationGraphHelper::updateData( PlottablesMap& plottables, std::shared_ptr variable, const DateTimeRange& dateTime) { auto helper = createHelper(variable); helper->update(plottables, dateTime); }