##// END OF EJS Templates
Mesh generation for QColorMap (3)...
Alexandre Leroux -
r968:6a9d119e7c40
parent child
Show More
@@ -1,161 +1,196
1 #include "Data/DataSeriesUtils.h"
1 #include "Data/DataSeriesUtils.h"
2
2
3 #include <cmath>
3 #include <cmath>
4
4
5 Q_LOGGING_CATEGORY(LOG_DataSeriesUtils, "DataSeriesUtils")
5 Q_LOGGING_CATEGORY(LOG_DataSeriesUtils, "DataSeriesUtils")
6
6
7 void DataSeriesUtils::fillDataHoles(std::vector<double> &xAxisData, std::vector<double> &valuesData,
7 void DataSeriesUtils::fillDataHoles(std::vector<double> &xAxisData, std::vector<double> &valuesData,
8 double resolution, double fillValue, double minBound,
8 double resolution, double fillValue, double minBound,
9 double maxBound)
9 double maxBound)
10 {
10 {
11 if (resolution == 0. || std::isnan(resolution)) {
11 if (resolution == 0. || std::isnan(resolution)) {
12 qCWarning(LOG_DataSeriesUtils())
12 qCWarning(LOG_DataSeriesUtils())
13 << "Can't fill data holes with a null resolution, no changes will be made";
13 << "Can't fill data holes with a null resolution, no changes will be made";
14 return;
14 return;
15 }
15 }
16
16
17 if (xAxisData.empty()) {
17 if (xAxisData.empty()) {
18 qCWarning(LOG_DataSeriesUtils())
18 qCWarning(LOG_DataSeriesUtils())
19 << "Can't fill data holes for empty data, no changes will be made";
19 << "Can't fill data holes for empty data, no changes will be made";
20 return;
20 return;
21 }
21 }
22
22
23 // Gets the number of values per x-axis data
23 // Gets the number of values per x-axis data
24 auto nbComponents = valuesData.size() / xAxisData.size();
24 auto nbComponents = valuesData.size() / xAxisData.size();
25
25
26 // Generates fill values that will be used to complete values data
26 // Generates fill values that will be used to complete values data
27 std::vector<double> fillValues(nbComponents, fillValue);
27 std::vector<double> fillValues(nbComponents, fillValue);
28
28
29 // Checks if there are data holes on the beginning of the data and generates the hole at the
29 // Checks if there are data holes on the beginning of the data and generates the hole at the
30 // extremity if it's the case
30 // extremity if it's the case
31 auto minXAxisData = xAxisData.front();
31 auto minXAxisData = xAxisData.front();
32 if (!std::isnan(minBound) && minBound < minXAxisData) {
32 if (!std::isnan(minBound) && minBound < minXAxisData) {
33 auto holeSize = static_cast<int>((minXAxisData - minBound) / resolution);
33 auto holeSize = static_cast<int>((minXAxisData - minBound) / resolution);
34 if (holeSize > 0) {
34 if (holeSize > 0) {
35 xAxisData.insert(xAxisData.begin(), minXAxisData - holeSize * resolution);
35 xAxisData.insert(xAxisData.begin(), minXAxisData - holeSize * resolution);
36 valuesData.insert(valuesData.begin(), fillValues.begin(), fillValues.end());
36 valuesData.insert(valuesData.begin(), fillValues.begin(), fillValues.end());
37 }
37 }
38 }
38 }
39
39
40 // Same for the end of the data
40 // Same for the end of the data
41 auto maxXAxisData = xAxisData.back();
41 auto maxXAxisData = xAxisData.back();
42 if (!std::isnan(maxBound) && maxBound > maxXAxisData) {
42 if (!std::isnan(maxBound) && maxBound > maxXAxisData) {
43 auto holeSize = static_cast<int>((maxBound - maxXAxisData) / resolution);
43 auto holeSize = static_cast<int>((maxBound - maxXAxisData) / resolution);
44 if (holeSize > 0) {
44 if (holeSize > 0) {
45 xAxisData.insert(xAxisData.end(), maxXAxisData + holeSize * resolution);
45 xAxisData.insert(xAxisData.end(), maxXAxisData + holeSize * resolution);
46 valuesData.insert(valuesData.end(), fillValues.begin(), fillValues.end());
46 valuesData.insert(valuesData.end(), fillValues.begin(), fillValues.end());
47 }
47 }
48 }
48 }
49
49
50 // Generates other data holes
50 // Generates other data holes
51 auto xAxisIt = xAxisData.begin();
51 auto xAxisIt = xAxisData.begin();
52 while (xAxisIt != xAxisData.end()) {
52 while (xAxisIt != xAxisData.end()) {
53 // Stops at first value which has a gap greater than resolution with the value next to it
53 // Stops at first value which has a gap greater than resolution with the value next to it
54 xAxisIt = std::adjacent_find(
54 xAxisIt = std::adjacent_find(
55 xAxisIt, xAxisData.end(),
55 xAxisIt, xAxisData.end(),
56 [resolution](const auto &a, const auto &b) { return (b - a) > resolution; });
56 [resolution](const auto &a, const auto &b) { return (b - a) > resolution; });
57
57
58 if (xAxisIt != xAxisData.end()) {
58 if (xAxisIt != xAxisData.end()) {
59 auto nextXAxisIt = xAxisIt + 1;
59 auto nextXAxisIt = xAxisIt + 1;
60
60
61 // Gets the values that has a gap greater than resolution between them
61 // Gets the values that has a gap greater than resolution between them
62 auto lowValue = *xAxisIt;
62 auto lowValue = *xAxisIt;
63 auto highValue = *nextXAxisIt;
63 auto highValue = *nextXAxisIt;
64
64
65 // Completes holes between the two values by creating new values (according to the
65 // Completes holes between the two values by creating new values (according to the
66 // resolution)
66 // resolution)
67 for (auto i = lowValue + resolution; i < highValue; i += resolution) {
67 for (auto i = lowValue + resolution; i < highValue; i += resolution) {
68 // Gets the iterator of values data from which to insert fill values
68 // Gets the iterator of values data from which to insert fill values
69 auto nextValuesIt = valuesData.begin()
69 auto nextValuesIt = valuesData.begin()
70 + std::distance(xAxisData.begin(), nextXAxisIt) * nbComponents;
70 + std::distance(xAxisData.begin(), nextXAxisIt) * nbComponents;
71
71
72 // New value is inserted before nextXAxisIt
72 // New value is inserted before nextXAxisIt
73 nextXAxisIt = xAxisData.insert(nextXAxisIt, i) + 1;
73 nextXAxisIt = xAxisData.insert(nextXAxisIt, i) + 1;
74
74
75 // New values are inserted before nextValuesIt
75 // New values are inserted before nextValuesIt
76 valuesData.insert(nextValuesIt, fillValues.begin(), fillValues.end());
76 valuesData.insert(nextValuesIt, fillValues.begin(), fillValues.end());
77 }
77 }
78
78
79 // Moves to the next value to continue loop on the x-axis data
79 // Moves to the next value to continue loop on the x-axis data
80 xAxisIt = nextXAxisIt;
80 xAxisIt = nextXAxisIt;
81 }
81 }
82 }
82 }
83 }
83 }
84
84
85 namespace {
85 namespace {
86
86
87 /**
87 /**
88 * Generates axis's mesh properties according to data and resolution
88 * Generates axis's mesh properties according to data and resolution
89 * @param begin the iterator pointing to the beginning of the data
89 * @param begin the iterator pointing to the beginning of the data
90 * @param end the iterator pointing to the end of the data
90 * @param end the iterator pointing to the end of the data
91 * @param fun the function to retrieve data from the data iterators
91 * @param fun the function to retrieve data from the data iterators
92 * @param resolution the resolution to use for the axis' mesh
92 * @param resolution the resolution to use for the axis' mesh
93 * @return a tuple representing the mesh properties : <nb values, min value, value step>
93 * @return a tuple representing the mesh properties : <nb values, min value, value step>
94 */
94 */
95 template <typename Iterator, typename IteratorFun>
95 template <typename Iterator, typename IteratorFun>
96 std::tuple<int, double, double> meshProperties(Iterator begin, Iterator end, IteratorFun fun,
96 std::tuple<int, double, double> meshProperties(Iterator begin, Iterator end, IteratorFun fun,
97 double resolution)
97 double resolution)
98 {
98 {
99 // Computes the gap between min and max data. This will be used to determinate the step between
99 // Computes the gap between min and max data. This will be used to determinate the step between
100 // each data of the mesh
100 // each data of the mesh
101 auto min = fun(begin);
101 auto min = fun(begin);
102 auto max = fun(end - 1);
102 auto max = fun(end - 1);
103 auto gap = max - min;
103 auto gap = max - min;
104
104
105 // Computes the step trying to use the fixed resolution. If the resolution doesn't separate the
105 // Computes the step trying to use the fixed resolution. If the resolution doesn't separate the
106 // values evenly , it is recalculated.
106 // values evenly , it is recalculated.
107 // For example, for a resolution of 2.0:
107 // For example, for a resolution of 2.0:
108 // - for interval [0; 8] => resolution is valid, the generated mesh will be [0, 2, 4, 6, 8]
108 // - for interval [0; 8] => resolution is valid, the generated mesh will be [0, 2, 4, 6, 8]
109 // - for interval [0; 9] => it's impossible to create a regular mesh with this resolution
109 // - for interval [0; 9] => it's impossible to create a regular mesh with this resolution
110 // The resolution is recalculated and is worth 1.8. The generated mesh will be [0, 1.8, 3.6,
110 // The resolution is recalculated and is worth 1.8. The generated mesh will be [0, 1.8, 3.6,
111 // 5.4, 7.2, 9]
111 // 5.4, 7.2, 9]
112 auto nbVal = static_cast<int>(std::ceil(gap / resolution));
112 auto nbVal = static_cast<int>(std::ceil(gap / resolution));
113 auto step = gap / nbVal;
113 auto step = gap / nbVal;
114
114
115 // last data is included in the total number of values
115 // last data is included in the total number of values
116 return std::make_tuple(nbVal + 1, min, step);
116 return std::make_tuple(nbVal + 1, min, step);
117 }
117 }
118
118
119 } // namespace
119 } // namespace
120
120
121 DataSeriesUtils::Mesh DataSeriesUtils::regularMesh(DataSeriesIterator begin, DataSeriesIterator end,
121 DataSeriesUtils::Mesh DataSeriesUtils::regularMesh(DataSeriesIterator begin, DataSeriesIterator end,
122 Resolution xResolution, Resolution yResolution)
122 Resolution xResolution, Resolution yResolution)
123 {
123 {
124 // Checks preconditions
124 // Checks preconditions
125 if (xResolution.m_Val == 0. || std::isnan(xResolution.m_Val) || yResolution.m_Val == 0.
125 if (xResolution.m_Val == 0. || std::isnan(xResolution.m_Val) || yResolution.m_Val == 0.
126 || std::isnan(yResolution.m_Val)) {
126 || std::isnan(yResolution.m_Val)) {
127 qCWarning(LOG_DataSeriesUtils()) << "Can't generate mesh with a null resolution";
127 qCWarning(LOG_DataSeriesUtils()) << "Can't generate mesh with a null resolution";
128 return Mesh{};
128 return Mesh{};
129 }
129 }
130
130
131 if (xResolution.m_Logarithmic) {
131 if (xResolution.m_Logarithmic) {
132 qCWarning(LOG_DataSeriesUtils())
132 qCWarning(LOG_DataSeriesUtils())
133 << "Can't generate mesh with a logarithmic x-axis resolution";
133 << "Can't generate mesh with a logarithmic x-axis resolution";
134 return Mesh{};
134 return Mesh{};
135 }
135 }
136
136
137 if (std::distance(begin, end) == 0) {
137 if (std::distance(begin, end) == 0) {
138 qCWarning(LOG_DataSeriesUtils()) << "Can't generate mesh for empty data";
138 qCWarning(LOG_DataSeriesUtils()) << "Can't generate mesh for empty data";
139 return Mesh{};
139 return Mesh{};
140 }
140 }
141
141
142 auto yData = begin->y();
142 auto yData = begin->y();
143 if (yData.empty()) {
143 if (yData.empty()) {
144 qCWarning(LOG_DataSeriesUtils()) << "Can't generate mesh for data with no y-axis";
144 qCWarning(LOG_DataSeriesUtils()) << "Can't generate mesh for data with no y-axis";
145 return Mesh{};
145 return Mesh{};
146 }
146 }
147
147
148 // Converts y-axis and its resolution to logarithmic values
148 // Converts y-axis and its resolution to logarithmic values
149 if (yResolution.m_Logarithmic) {
149 if (yResolution.m_Logarithmic) {
150 std::for_each(yData.begin(), yData.end(), [](auto &val) { val = std::log10(val); });
150 std::for_each(yData.begin(), yData.end(), [](auto &val) { val = std::log10(val); });
151 }
151 }
152
152
153 // Computes mesh properties
153 // Computes mesh properties
154 int nbX, nbY;
154 int nbX, nbY;
155 double xMin, xStep, yMin, yStep;
155 double xMin, xStep, yMin, yStep;
156 std::tie(nbX, xMin, xStep)
156 std::tie(nbX, xMin, xStep)
157 = meshProperties(begin, end, [](const auto &it) { return it->x(); }, xResolution.m_Val);
157 = meshProperties(begin, end, [](const auto &it) { return it->x(); }, xResolution.m_Val);
158 std::tie(nbY, yMin, yStep) = meshProperties(
158 std::tie(nbY, yMin, yStep) = meshProperties(
159 yData.begin(), yData.end(), [](const auto &it) { return *it; }, yResolution.m_Val);
159 yData.begin(), yData.end(), [](const auto &it) { return *it; }, yResolution.m_Val);
160
160
161 // Generates mesh according to the x-axis and y-axis steps
162 Mesh result{nbX, xMin, xStep, nbY, yMin, yStep};
163
164 for (auto meshXIndex = 0; meshXIndex < nbX; ++meshXIndex) {
165 auto meshX = xMin + meshXIndex * xStep;
166 // According to current x-axis of the mesh, finds in the data series the interval in which
167 // the data is or gets closer (without exceeding it).
168 // An interval is defined by a value and extends to +/- 50% of the resolution. For example,
169 // for a value of 3 and a resolution of 1, the associated interval is [2.5, 3.5].
170 auto xIt = std::lower_bound(begin, end, meshX,
171 [xResolution](const auto &it, const auto &val) {
172 return it.x() - xResolution.m_Val / 2. < val;
173 })
174 - 1;
175
176 // When the corresponding entry of the data series is found, generates the values of the
177 // mesh by retrieving the values of the entry, for each y-axis value of the mesh
178 auto values = xIt->values();
179
180 for (auto meshYIndex = 0; meshYIndex < nbY; ++meshYIndex) {
181 auto meshY = yMin + meshYIndex * yStep;
182
183 auto yBegin = yData.begin();
184 auto yIt = std::lower_bound(yBegin, yData.end(), meshY,
185 [yResolution](const auto &it, const auto &val) {
186 return it - yResolution.m_Val / 2. < val;
187 })
188 - 1;
189
190 auto valueIndex = std::distance(yBegin, yIt);
191 result.m_Data[result.m_NbX * meshYIndex + meshXIndex] = values.at(valueIndex);
192 }
193 }
194
195 return result;
161 }
196 }
General Comments 0
You need to be logged in to leave comments. Login now