##// END OF EJS Templates
Fix the close button on the graphs and multi selection in the same graph
Fix the close button on the graphs and multi selection in the same graph

File last commit:

r998:bcdcc2f6c21f
r1054:f391b1d9fb19
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;
}