#include "Data/DataSeriesUtils.h" #include Q_LOGGING_CATEGORY(LOG_DataSeriesUtils, "DataSeriesUtils") void DataSeriesUtils::fillDataHoles(std::vector &xAxisData, std::vector &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 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((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((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 : */ template std::tuple 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(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); }