##// END OF EJS Templates
Fix a problem with the update of a graph or a variable
Fix a problem with the update of a graph or a variable

File last commit:

r998:bcdcc2f6c21f
r1139:e6af7b17a39a
Show More
DataSeriesUtils.cpp
194 lines | 8.1 KiB | text/x-c | CppLexer
/ core / src / Data / DataSeriesUtils.cpp
#include "Data/DataSeriesUtils.h"
Q_LOGGING_CATEGORY(LOG_DataSeriesUtils, "DataSeriesUtils")
void DataSeriesUtils::fillDataHoles(std::vector<double> &xAxisData, std::vector<double> &valuesData,
double resolution, double fillValue, double minBound,
double maxBound)
{
if (resolution == 0. || std::isnan(resolution)) {
qCWarning(LOG_DataSeriesUtils())
<< "Can't fill data holes with a null resolution, no changes will be made";
return;
}
if (xAxisData.empty()) {
qCWarning(LOG_DataSeriesUtils())
<< "Can't fill data holes for empty data, no changes will be made";
return;
}
// Gets the number of values per x-axis data
auto nbComponents = valuesData.size() / xAxisData.size();
// Generates fill values that will be used to complete values data
std::vector<double> fillValues(nbComponents, fillValue);
// Checks if there are data holes on the beginning of the data and generates the hole at the
// extremity if it's the case
auto minXAxisData = xAxisData.front();
if (!std::isnan(minBound) && minBound < minXAxisData) {
auto holeSize = static_cast<int>((minXAxisData - minBound) / resolution);
if (holeSize > 0) {
xAxisData.insert(xAxisData.begin(), minXAxisData - holeSize * resolution);
valuesData.insert(valuesData.begin(), fillValues.begin(), fillValues.end());
}
}
// Same for the end of the data
auto maxXAxisData = xAxisData.back();
if (!std::isnan(maxBound) && maxBound > maxXAxisData) {
auto holeSize = static_cast<int>((maxBound - maxXAxisData) / resolution);
if (holeSize > 0) {
xAxisData.insert(xAxisData.end(), maxXAxisData + holeSize * resolution);
valuesData.insert(valuesData.end(), fillValues.begin(), fillValues.end());
}
}
// Generates other data holes
auto xAxisIt = xAxisData.begin();
while (xAxisIt != xAxisData.end()) {
// Stops at first value which has a gap greater than resolution with the value next to it
xAxisIt = std::adjacent_find(
xAxisIt, xAxisData.end(),
[resolution](const auto &a, const auto &b) { return (b - a) > resolution; });
if (xAxisIt != xAxisData.end()) {
auto nextXAxisIt = xAxisIt + 1;
// Gets the values that has a gap greater than resolution between them
auto lowValue = *xAxisIt;
auto highValue = *nextXAxisIt;
// Completes holes between the two values by creating new values (according to the
// resolution)
for (auto i = lowValue + resolution; i < highValue; i += resolution) {
// Gets the iterator of values data from which to insert fill values
auto nextValuesIt = valuesData.begin()
+ std::distance(xAxisData.begin(), nextXAxisIt) * nbComponents;
// New value is inserted before nextXAxisIt
nextXAxisIt = xAxisData.insert(nextXAxisIt, i) + 1;
// New values are inserted before nextValuesIt
valuesData.insert(nextValuesIt, fillValues.begin(), fillValues.end());
}
// Moves to the next value to continue loop on the x-axis data
xAxisIt = nextXAxisIt;
}
}
}
namespace {
/**
* Generates axis's mesh properties according to data and resolution
* @param begin the iterator pointing to the beginning of the data
* @param end the iterator pointing to the end of the data
* @param fun the function to retrieve data from the data iterators
* @param resolution the resolution to use for the axis' mesh
* @return a tuple representing the mesh properties : <nb values, min value, value step>
*/
template <typename Iterator, typename IteratorFun>
std::tuple<int, double, double> meshProperties(Iterator begin, Iterator end, IteratorFun fun,
double resolution)
{
// Computes the gap between min and max data. This will be used to determinate the step between
// each data of the mesh
auto min = fun(begin);
auto max = fun(end - 1);
auto gap = max - min;
// Computes the step trying to use the fixed resolution. If the resolution doesn't separate the
// values evenly , it is recalculated.
// For example, for a resolution of 2.0:
// - for interval [0; 8] => resolution is valid, the generated mesh will be [0, 2, 4, 6, 8]
// - for interval [0; 9] => it's impossible to create a regular mesh with this resolution
// The resolution is recalculated and is worth 1.8. The generated mesh will be [0, 1.8, 3.6,
// 5.4, 7.2, 9]
auto nbVal = static_cast<int>(std::ceil(gap / resolution));
auto step = gap / nbVal;
// last data is included in the total number of values
return std::make_tuple(nbVal + 1, min, step);
}
} // namespace
DataSeriesUtils::Mesh DataSeriesUtils::regularMesh(DataSeriesIterator begin, DataSeriesIterator end,
Resolution xResolution, Resolution yResolution)
{
// Checks preconditions
if (xResolution.m_Val == 0. || std::isnan(xResolution.m_Val) || yResolution.m_Val == 0.
|| std::isnan(yResolution.m_Val)) {
qCWarning(LOG_DataSeriesUtils()) << "Can't generate mesh with a null resolution";
return Mesh{};
}
if (xResolution.m_Logarithmic) {
qCWarning(LOG_DataSeriesUtils())
<< "Can't generate mesh with a logarithmic x-axis resolution";
return Mesh{};
}
if (std::distance(begin, end) == 0) {
qCWarning(LOG_DataSeriesUtils()) << "Can't generate mesh for empty data";
return Mesh{};
}
auto yData = begin->y();
if (yData.empty()) {
qCWarning(LOG_DataSeriesUtils()) << "Can't generate mesh for data with no y-axis";
return Mesh{};
}
// Converts y-axis and its resolution to logarithmic values
if (yResolution.m_Logarithmic) {
std::for_each(yData.begin(), yData.end(), [](auto &val) { val = std::log10(val); });
}
// Computes mesh properties
int nbX, nbY;
double xMin, xStep, yMin, yStep;
std::tie(nbX, xMin, xStep)
= meshProperties(begin, end, [](const auto &it) { return it->x(); }, xResolution.m_Val);
std::tie(nbY, yMin, yStep) = meshProperties(
yData.begin(), yData.end(), [](const auto &it) { return *it; }, yResolution.m_Val);
// Generates mesh according to the x-axis and y-axis steps
Mesh result{nbX, xMin, xStep, nbY, yMin, yStep};
for (auto meshXIndex = 0; meshXIndex < nbX; ++meshXIndex) {
auto meshX = xMin + meshXIndex * xStep;
// According to current x-axis of the mesh, finds in the data series the interval in which
// the data is or gets closer (without exceeding it).
// An interval is defined by a value and extends to +/- 50% of the resolution. For example,
// for a value of 3 and a resolution of 1, the associated interval is [2.5, 3.5].
auto xIt = std::lower_bound(begin, end, meshX,
[xResolution](const auto &it, const auto &val) {
return it.x() - xResolution.m_Val / 2. < val;
})
- 1;
// When the corresponding entry of the data series is found, generates the values of the
// mesh by retrieving the values of the entry, for each y-axis value of the mesh
auto values = xIt->values();
for (auto meshYIndex = 0; meshYIndex < nbY; ++meshYIndex) {
auto meshY = yMin + meshYIndex * yStep;
auto yBegin = yData.begin();
auto yIt = std::lower_bound(yBegin, yData.end(), meshY,
[yResolution](const auto &it, const auto &val) {
return it - yResolution.m_Val / 2. < val;
})
- 1;
auto valueIndex = std::distance(yBegin, yIt);
result.m_Data[result.m_NbX * meshYIndex + meshXIndex] = values.at(valueIndex);
}
}
return result;
}