VisualizationGraphHelper.cpp
240 lines
| 8.1 KiB
| text/x-c
|
CppLexer
r227 | #include "Visualization/VisualizationGraphHelper.h" | |||
|
r168 | #include "Visualization/qcustomplot.h" | ||
|
r548 | #include <Common/ColorUtils.h> | ||
|
r169 | #include <Data/ScalarSeries.h> | ||
|
r547 | #include <Data/VectorSeries.h> | ||
|
r169 | |||
|
r168 | #include <Variable/Variable.h> | ||
r227 | Q_LOGGING_CATEGORY(LOG_VisualizationGraphHelper, "VisualizationGraphHelper") | |||
|
r168 | |||
|
r169 | namespace { | ||
r336 | class SqpDataContainer : public QCPGraphDataContainer { | |||
public: | ||||
|
r419 | void appendGraphData(const QCPGraphData &data) { mData.append(data); } | ||
r336 | }; | |||
|
r170 | /// Format for datetimes on a axis | ||
const auto DATETIME_TICKER_FORMAT = QStringLiteral("yyyy/MM/dd \nhh:mm:ss"); | ||||
/// Generates the appropriate ticker for an axis, depending on whether the axis displays time or | ||||
/// non-time data | ||||
QSharedPointer<QCPAxisTicker> axisTicker(bool isTimeAxis) | ||||
{ | ||||
if (isTimeAxis) { | ||||
auto dateTicker = QSharedPointer<QCPAxisTickerDateTime>::create(); | ||||
dateTicker->setDateTimeFormat(DATETIME_TICKER_FORMAT); | ||||
|
r452 | dateTicker->setDateTimeSpec(Qt::UTC); | ||
|
r170 | |||
return dateTicker; | ||||
} | ||||
else { | ||||
// default ticker | ||||
return QSharedPointer<QCPAxisTicker>::create(); | ||||
} | ||||
} | ||||
|
r169 | |||
|
r547 | /// Sets axes properties according to the properties of a data series | ||
template <int Dim> | ||||
void setAxesProperties(const DataSeries<Dim> &dataSeries, QCustomPlot &plot) noexcept | ||||
r219 | { | |||
|
r547 | /// @todo : for the moment, no control is performed on the axes: the units and the tickers | ||
/// are fixed for the default x-axis and y-axis of the plot, and according to the new graph | ||||
auto setAxisProperties = [](auto axis, const auto &unit) { | ||||
// label (unit name) | ||||
axis->setLabel(unit.m_Name); | ||||
// ticker (depending on the type of unit) | ||||
axis->setTicker(axisTicker(unit.m_TimeUnit)); | ||||
}; | ||||
setAxisProperties(plot.xAxis, dataSeries.xAxisUnit()); | ||||
setAxisProperties(plot.yAxis, dataSeries.valuesUnit()); | ||||
} | ||||
r290 | ||||
|
r547 | /** | ||
* 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 <typename T, typename Enabled = void> | ||||
struct PlottablesCreator { | ||||
static PlottablesMap createPlottables(T &, QCustomPlot &) | ||||
{ | ||||
qCCritical(LOG_DataSeries()) | ||||
<< QObject::tr("Can't create plottables: unmanaged data series type"); | ||||
return {}; | ||||
r219 | } | |||
|
r547 | }; | ||
r219 | ||||
|
r547 | /** | ||
* Specialization of PlottablesCreator for scalars and vectors | ||||
* @sa ScalarSeries | ||||
* @sa VectorSeries | ||||
*/ | ||||
template <typename T> | ||||
struct PlottablesCreator<T, | ||||
typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value | ||||
or std::is_base_of<VectorSeries, T>::value> > { | ||||
static PlottablesMap createPlottables(T &dataSeries, QCustomPlot &plot) | ||||
{ | ||||
PlottablesMap result{}; | ||||
// Gets the number of components of the data series | ||||
auto componentCount = dataSeries.valuesData()->componentCount(); | ||||
|
r548 | auto colors = ColorUtils::colors(Qt::blue, Qt::red, componentCount); | ||
|
r547 | // For each component of the data series, creates a QCPGraph to add to the plot | ||
for (auto i = 0; i < componentCount; ++i) { | ||||
auto graph = plot.addGraph(); | ||||
|
r548 | graph->setPen(QPen{colors.at(i)}); | ||
|
r547 | |||
result.insert({i, graph}); | ||||
} | ||||
r518 | ||||
// Axes properties | ||||
|
r547 | setAxesProperties(dataSeries, plot); | ||
plot.replot(); | ||||
return result; | ||||
r518 | } | |||
|
r547 | }; | ||
r518 | ||||
|
r547 | /** | ||
* 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 <typename T, typename Enabled = void> | ||||
struct PlottablesUpdater { | ||||
static void updatePlottables(T &, PlottablesMap &, const SqpRange &, bool) | ||||
{ | ||||
qCCritical(LOG_DataSeries()) | ||||
<< QObject::tr("Can't update plottables: unmanaged data series type"); | ||||
} | ||||
}; | ||||
|
r169 | |||
|
r547 | /** | ||
* 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> > { | ||||
static void updatePlottables(T &dataSeries, PlottablesMap &plottables, const SqpRange &range, | ||||
bool rescaleAxes) | ||||
{ | ||||
dataSeries.lockRead(); | ||||
// 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}); | ||||
} | ||||
} | ||||
|
r169 | |||
|
r547 | // - Gets the data of the series included in the current range | ||
// - 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 | ||||
auto subDataIts = dataSeries.subData(range.m_TStart, range.m_TEnd); | ||||
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))); | ||||
} | ||||
} | ||||
r219 | ||||
|
r547 | dataSeries.unlock(); | ||
|
r170 | |||
|
r547 | if (!plottables.empty()) { | ||
auto plot = plottables.begin()->second->parentPlot(); | ||||
|
r170 | |||
|
r547 | if (rescaleAxes) { | ||
plot->rescaleAxes(); | ||||
} | ||||
|
r170 | |||
|
r547 | plot->replot(); | ||
} | ||||
|
r169 | } | ||
|
r547 | }; | ||
/** | ||||
* Helper used to create/update plottables | ||||
*/ | ||||
struct IPlottablesHelper { | ||||
virtual ~IPlottablesHelper() noexcept = default; | ||||
virtual PlottablesMap create(QCustomPlot &plot) const = 0; | ||||
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 { | ||||
explicit PlottablesHelper(T &dataSeries) : m_DataSeries{dataSeries} {} | ||||
PlottablesMap create(QCustomPlot &plot) const override | ||||
{ | ||||
return PlottablesCreator<T>::createPlottables(m_DataSeries, plot); | ||||
|
r169 | } | ||
|
r547 | void update(PlottablesMap &plottables, const SqpRange &range, bool rescaleAxes) const override | ||
{ | ||||
PlottablesUpdater<T>::updatePlottables(m_DataSeries, plottables, range, rescaleAxes); | ||||
} | ||||
|
r169 | |||
|
r547 | T &m_DataSeries; | ||
}; | ||||
|
r169 | |||
|
r547 | /// Creates IPlottablesHelper according to a data series | ||
|
r551 | std::unique_ptr<IPlottablesHelper> createHelper(std::shared_ptr<IDataSeries> dataSeries) noexcept | ||
r518 | { | |||
|
r551 | if (auto scalarSeries = std::dynamic_pointer_cast<ScalarSeries>(dataSeries)) { | ||
|
r547 | return std::make_unique<PlottablesHelper<ScalarSeries> >(*scalarSeries); | ||
} | ||||
|
r551 | else if (auto vectorSeries = std::dynamic_pointer_cast<VectorSeries>(dataSeries)) { | ||
|
r547 | return std::make_unique<PlottablesHelper<VectorSeries> >(*vectorSeries); | ||
r518 | } | |||
else { | ||||
|
r547 | return std::make_unique<PlottablesHelper<IDataSeries> >(*dataSeries); | ||
r518 | } | |||
} | ||||
|
r547 | } // namespace | ||
|
r168 | |||
|
r547 | PlottablesMap VisualizationGraphHelper::create(std::shared_ptr<Variable> variable, | ||
QCustomPlot &plot) noexcept | ||||
{ | ||||
|
r168 | if (variable) { | ||
|
r551 | auto helper = createHelper(variable->dataSeries()); | ||
|
r547 | auto plottables = helper->create(plot); | ||
return plottables; | ||||
|
r168 | } | ||
else { | ||||
r227 | qCDebug(LOG_VisualizationGraphHelper()) | |||
|
r168 | << QObject::tr("Can't create graph plottables : the variable is null"); | ||
|
r547 | return PlottablesMap{}; | ||
|
r168 | } | ||
} | ||||
r219 | ||||
|
r551 | void VisualizationGraphHelper::updateData(PlottablesMap &plottables, | ||
std::shared_ptr<IDataSeries> dataSeries, | ||||
r513 | const SqpRange &dateTime) | |||
r219 | { | |||
|
r547 | auto helper = createHelper(dataSeries); | ||
helper->update(plottables, dateTime); | ||||
r219 | } | |||