##// END OF EJS Templates
Fixes compilation
Alexandre Leroux -
r1040:bcdcc2f6c21f
parent child
Show More
@@ -1,227 +1,228
1 #ifndef SCIQLOP_DATASERIESUTILS_H
1 #ifndef SCIQLOP_DATASERIESUTILS_H
2 #define SCIQLOP_DATASERIESUTILS_H
2 #define SCIQLOP_DATASERIESUTILS_H
3
3
4 #include "CoreGlobal.h"
4 #include "CoreGlobal.h"
5
5
6 #include <Data/DataSeriesIterator.h>
6 #include <Data/DataSeriesIterator.h>
7
7
8 #include <cmath>
8 #include <QLoggingCategory>
9 #include <QLoggingCategory>
9
10
10 Q_DECLARE_LOGGING_CATEGORY(LOG_DataSeriesUtils)
11 Q_DECLARE_LOGGING_CATEGORY(LOG_DataSeriesUtils)
11
12
12 /**
13 /**
13 * Utility class with methods for data series
14 * Utility class with methods for data series
14 */
15 */
15 struct SCIQLOP_CORE_EXPORT DataSeriesUtils {
16 struct SCIQLOP_CORE_EXPORT DataSeriesUtils {
16 /**
17 /**
17 * Define a meshs.
18 * Define a meshs.
18 *
19 *
19 * A mesh is a regular grid representing cells of the same width (in x) and of the same height
20 * A mesh is a regular grid representing cells of the same width (in x) and of the same height
20 * (in y). At each mesh point is associated a value.
21 * (in y). At each mesh point is associated a value.
21 *
22 *
22 * Each axis of the mesh is defined by a minimum value, a number of values is a mesh step.
23 * Each axis of the mesh is defined by a minimum value, a number of values is a mesh step.
23 * For example: if min = 1, nbValues = 5 and step = 2 => the axis of the mesh will be [1, 3, 5,
24 * For example: if min = 1, nbValues = 5 and step = 2 => the axis of the mesh will be [1, 3, 5,
24 * 7, 9].
25 * 7, 9].
25 *
26 *
26 * The values are defined in an array of size {nbX * nbY}. The data is stored along the X axis.
27 * The values are defined in an array of size {nbX * nbY}. The data is stored along the X axis.
27 *
28 *
28 * For example, the mesh:
29 * For example, the mesh:
29 * Y = 2 [ 7 ; 8 ; 9
30 * Y = 2 [ 7 ; 8 ; 9
30 * Y = 1 4 ; 5 ; 6
31 * Y = 1 4 ; 5 ; 6
31 * Y = 0 1 ; 2 ; 3 ]
32 * Y = 0 1 ; 2 ; 3 ]
32 * X = 0 X = 1 X = 2
33 * X = 0 X = 1 X = 2
33 *
34 *
34 * will be represented by data [1, 2, 3, 4, 5, 6, 7, 8, 9]
35 * will be represented by data [1, 2, 3, 4, 5, 6, 7, 8, 9]
35 */
36 */
36 struct Mesh {
37 struct Mesh {
37 explicit Mesh() = default;
38 explicit Mesh() = default;
38 explicit Mesh(int nbX, double xMin, double xStep, int nbY, double yMin, double yStep)
39 explicit Mesh(int nbX, double xMin, double xStep, int nbY, double yMin, double yStep)
39 : m_NbX{nbX},
40 : m_NbX{nbX},
40 m_XMin{xMin},
41 m_XMin{xMin},
41 m_XStep{xStep},
42 m_XStep{xStep},
42 m_NbY{nbY},
43 m_NbY{nbY},
43 m_YMin{yMin},
44 m_YMin{yMin},
44 m_YStep{yStep},
45 m_YStep{yStep},
45 m_Data(nbX * nbY)
46 m_Data(nbX * nbY)
46 {
47 {
47 }
48 }
48
49
49 inline bool isEmpty() const { return m_Data.size() == 0; }
50 inline bool isEmpty() const { return m_Data.size() == 0; }
50 inline double xMax() const { return m_XMin + (m_NbX - 1) * m_XStep; }
51 inline double xMax() const { return m_XMin + (m_NbX - 1) * m_XStep; }
51 inline double yMax() const { return m_YMin + (m_NbY - 1) * m_YStep; }
52 inline double yMax() const { return m_YMin + (m_NbY - 1) * m_YStep; }
52
53
53 int m_NbX{0};
54 int m_NbX{0};
54 double m_XMin{};
55 double m_XMin{};
55 double m_XStep{};
56 double m_XStep{};
56 int m_NbY{0};
57 int m_NbY{0};
57 double m_YMin{};
58 double m_YMin{};
58 double m_YStep{};
59 double m_YStep{};
59 std::vector<double> m_Data{};
60 std::vector<double> m_Data{};
60 };
61 };
61
62
62 /**
63 /**
63 * Represents a resolution used to generate the data of a mesh on the x-axis or in Y.
64 * Represents a resolution used to generate the data of a mesh on the x-axis or in Y.
64 *
65 *
65 * A resolution is represented by a value and flag indicating if it's in the logarithmic scale
66 * A resolution is represented by a value and flag indicating if it's in the logarithmic scale
66 * @sa Mesh
67 * @sa Mesh
67 */
68 */
68 struct Resolution {
69 struct Resolution {
69 double m_Val{std::numeric_limits<double>::quiet_NaN()};
70 double m_Val{std::numeric_limits<double>::quiet_NaN()};
70 bool m_Logarithmic{false};
71 bool m_Logarithmic{false};
71 };
72 };
72
73
73 /**
74 /**
74 * Processes data from a data series to complete the data holes with a fill value.
75 * Processes data from a data series to complete the data holes with a fill value.
75 *
76 *
76 * A data hole is determined by the resolution passed in parameter: if, between two continuous
77 * A data hole is determined by the resolution passed in parameter: if, between two continuous
77 * data on the x-axis, the difference between these data is greater than the resolution, then
78 * data on the x-axis, the difference between these data is greater than the resolution, then
78 * there is one or more holes between them. The holes are filled by adding:
79 * there is one or more holes between them. The holes are filled by adding:
79 * - for the x-axis, new data corresponding to the 'step resolution' starting from the first
80 * - for the x-axis, new data corresponding to the 'step resolution' starting from the first
80 * data;
81 * data;
81 * - for values, a default value (fill value) for each new data added on the x-axis.
82 * - for values, a default value (fill value) for each new data added on the x-axis.
82 *
83 *
83 * For example, with :
84 * For example, with :
84 * - xAxisData = [0, 1, 5, 7, 14 ]
85 * - xAxisData = [0, 1, 5, 7, 14 ]
85 * - valuesData = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] (two components per x-axis data)
86 * - valuesData = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] (two components per x-axis data)
86 * - fillValue = NaN
87 * - fillValue = NaN
87 * - and resolution = 2;
88 * - and resolution = 2;
88 *
89 *
89 * For the x axis, we calculate as data holes: [3, 9, 11, 13]. These holes are added to the
90 * For the x axis, we calculate as data holes: [3, 9, 11, 13]. These holes are added to the
90 * x-axis data, and NaNs (two per x-axis data) are added to the values:
91 * x-axis data, and NaNs (two per x-axis data) are added to the values:
91 * => xAxisData = [0, 1, 3, 5, 7, 9, 11, 13, 14 ]
92 * => xAxisData = [0, 1, 3, 5, 7, 9, 11, 13, 14 ]
92 * => valuesData = [0, 1, 2, 3, NaN, NaN, 4, 5, 6, 7, NaN, NaN, NaN, NaN, NaN, NaN, 8, 9]
93 * => valuesData = [0, 1, 2, 3, NaN, NaN, 4, 5, 6, 7, NaN, NaN, NaN, NaN, NaN, NaN, 8, 9]
93 *
94 *
94 * It is also possible to set bounds for the data series. If these bounds are defined and exceed
95 * It is also possible to set bounds for the data series. If these bounds are defined and exceed
95 * the limits of the data series, data holes are added to the series at the beginning and/or the
96 * the limits of the data series, data holes are added to the series at the beginning and/or the
96 * end.
97 * end.
97 *
98 *
98 * The generation of data holes at the beginning/end of the data series is performed starting
99 * The generation of data holes at the beginning/end of the data series is performed starting
99 * from the x-axis series limit and adding data holes at each 'resolution step' as long as the
100 * from the x-axis series limit and adding data holes at each 'resolution step' as long as the
100 * new bound is not reached.
101 * new bound is not reached.
101 *
102 *
102 * For example, with :
103 * For example, with :
103 * - xAxisData = [3, 4, 5, 6, 7 ]
104 * - xAxisData = [3, 4, 5, 6, 7 ]
104 * - valuesData = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
105 * - valuesData = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
105 * - fillValue = NaN
106 * - fillValue = NaN
106 * - minBound = 0
107 * - minBound = 0
107 * - maxBound = 12
108 * - maxBound = 12
108 * - and resolution = 2;
109 * - and resolution = 2;
109 *
110 *
110 * => Starting from 3 and decreasing 2 by 2 until reaching 0 : a data hole at value 1 will be
111 * => Starting from 3 and decreasing 2 by 2 until reaching 0 : a data hole at value 1 will be
111 * added to the beginning of the series
112 * added to the beginning of the series
112 * => Starting from 7 and increasing 2 by 2 until reaching 12 : data holes at values 9 and 11
113 * => Starting from 7 and increasing 2 by 2 until reaching 12 : data holes at values 9 and 11
113 * will be added to the end of the series
114 * will be added to the end of the series
114 *
115 *
115 * So :
116 * So :
116 * => xAxisData = [1, 3, 4, 5, 6, 7, 9, 11 ]
117 * => xAxisData = [1, 3, 4, 5, 6, 7, 9, 11 ]
117 * => valuesData = [NaN, NaN, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, NaN, NaN, NaN, NaN]
118 * => valuesData = [NaN, NaN, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, NaN, NaN, NaN, NaN]
118 *
119 *
119 * @param xAxisData the x-axis data of the data series
120 * @param xAxisData the x-axis data of the data series
120 * @param valuesData the values data of the data series
121 * @param valuesData the values data of the data series
121 * @param resolution the resoultion (on x-axis) used to determinate data holes
122 * @param resolution the resoultion (on x-axis) used to determinate data holes
122 * @param fillValue the fill value used for data holes in the values data
123 * @param fillValue the fill value used for data holes in the values data
123 * @param minBound the limit at which to start filling data holes for the series. If set to NaN,
124 * @param minBound the limit at which to start filling data holes for the series. If set to NaN,
124 * the limit is not used
125 * the limit is not used
125 * @param maxBound the limit at which to end filling data holes for the series. If set to NaN,
126 * @param maxBound the limit at which to end filling data holes for the series. If set to NaN,
126 * the limit is not used
127 * the limit is not used
127 *
128 *
128 * @remarks There is no control over the consistency between x-axis data and values data. The
129 * @remarks There is no control over the consistency between x-axis data and values data. The
129 * method considers that the data is well formed (the total number of values data is a multiple
130 * method considers that the data is well formed (the total number of values data is a multiple
130 * of the number of x-axis data)
131 * of the number of x-axis data)
131 */
132 */
132 static void fillDataHoles(std::vector<double> &xAxisData, std::vector<double> &valuesData,
133 static void fillDataHoles(std::vector<double> &xAxisData, std::vector<double> &valuesData,
133 double resolution,
134 double resolution,
134 double fillValue = std::numeric_limits<double>::quiet_NaN(),
135 double fillValue = std::numeric_limits<double>::quiet_NaN(),
135 double minBound = std::numeric_limits<double>::quiet_NaN(),
136 double minBound = std::numeric_limits<double>::quiet_NaN(),
136 double maxBound = std::numeric_limits<double>::quiet_NaN());
137 double maxBound = std::numeric_limits<double>::quiet_NaN());
137 /**
138 /**
138 * Computes the resolution of a dataset passed as a parameter.
139 * Computes the resolution of a dataset passed as a parameter.
139 *
140 *
140 * The resolution of a dataset is the minimum difference between two values that follow in the
141 * The resolution of a dataset is the minimum difference between two values that follow in the
141 * set.
142 * set.
142 * For example:
143 * For example:
143 * - for the set [0, 2, 4, 8, 10, 11, 13] => the resolution is 1 (difference between 10 and 11).
144 * - for the set [0, 2, 4, 8, 10, 11, 13] => the resolution is 1 (difference between 10 and 11).
144 *
145 *
145 * A resolution can be calculated on the logarithmic scale (base of 10). In this case, the
146 * A resolution can be calculated on the logarithmic scale (base of 10). In this case, the
146 * dataset is first converted to logarithmic values.
147 * dataset is first converted to logarithmic values.
147 * For example:
148 * For example:
148 * - for the set [10, 100, 10000, 1000000], the values are converted to [1, 2, 4, 6] => the
149 * - for the set [10, 100, 10000, 1000000], the values are converted to [1, 2, 4, 6] => the
149 * logarithmic resolution is 1 (difference between 1 and 2).
150 * logarithmic resolution is 1 (difference between 1 and 2).
150 *
151 *
151 * @param begin the iterator pointing to the beginning of the dataset
152 * @param begin the iterator pointing to the beginning of the dataset
152 * @param end the iterator pointing to the end of the dataset
153 * @param end the iterator pointing to the end of the dataset
153 * @param logarithmic computes a logarithmic resolution or not
154 * @param logarithmic computes a logarithmic resolution or not
154 * @return the resolution computed
155 * @return the resolution computed
155 * @warning the method considers the dataset as sorted and doesn't control it.
156 * @warning the method considers the dataset as sorted and doesn't control it.
156 */
157 */
157 template <typename Iterator>
158 template <typename Iterator>
158 static Resolution resolution(Iterator begin, Iterator end, bool logarithmic = false);
159 static Resolution resolution(Iterator begin, Iterator end, bool logarithmic = false);
159
160
160 /**
161 /**
161 * Computes a regular mesh for a data series, according to resolutions for x-axis and y-axis
162 * Computes a regular mesh for a data series, according to resolutions for x-axis and y-axis
162 * passed as parameters.
163 * passed as parameters.
163 *
164 *
164 * The mesh is created from the resolutions in x and y and the boundaries delimiting the data
165 * The mesh is created from the resolutions in x and y and the boundaries delimiting the data
165 * series. If the resolutions do not allow to obtain a regular mesh, they are recalculated.
166 * series. If the resolutions do not allow to obtain a regular mesh, they are recalculated.
166 *
167 *
167 * For example :
168 * For example :
168 * Let x-axis data = [0, 1, 3, 5, 9], its associated values ​​= [0, 10, 30, 50, 90] and
169 * Let x-axis data = [0, 1, 3, 5, 9], its associated values ​​= [0, 10, 30, 50, 90] and
169 * xResolution = 2.
170 * xResolution = 2.
170 * Based on the resolution, the mesh would be [0, 2, 4, 6, 8, 10] and would be invalid because
171 * Based on the resolution, the mesh would be [0, 2, 4, 6, 8, 10] and would be invalid because
171 * it exceeds the maximum bound of the data. The resolution is thus recalculated so that the
172 * it exceeds the maximum bound of the data. The resolution is thus recalculated so that the
172 * mesh holds between the data terminals.
173 * mesh holds between the data terminals.
173 * So => resolution is 1.8 and the mesh is [0, 1.8, 3.6, 5.4, 7.2, 9].
174 * So => resolution is 1.8 and the mesh is [0, 1.8, 3.6, 5.4, 7.2, 9].
174 *
175 *
175 * Once the mesh is generated in x and y, the values ​​are associated with each mesh point,
176 * Once the mesh is generated in x and y, the values ​​are associated with each mesh point,
176 * based on the data in the series, finding the existing data at which the mesh point would be
177 * based on the data in the series, finding the existing data at which the mesh point would be
177 * or would be closest to, without exceeding it.
178 * or would be closest to, without exceeding it.
178 *
179 *
179 * In the example, we determine the value of each mesh point:
180 * In the example, we determine the value of each mesh point:
180 * - x = 0 => value = 0 (existing x in the data series)
181 * - x = 0 => value = 0 (existing x in the data series)
181 * - x = 1.8 => value = 10 (the closest existing x: 1)
182 * - x = 1.8 => value = 10 (the closest existing x: 1)
182 * - x = 3.6 => value = 30 (the closest existing x: 3)
183 * - x = 3.6 => value = 30 (the closest existing x: 3)
183 * - x = 5.4 => value = 50 (the closest existing x: 5)
184 * - x = 5.4 => value = 50 (the closest existing x: 5)
184 * - x = 7.2 => value = 50 (the closest existing x: 5)
185 * - x = 7.2 => value = 50 (the closest existing x: 5)
185 * - x = 9 => value = 90 (existing x in the data series)
186 * - x = 9 => value = 90 (existing x in the data series)
186 *
187 *
187 * Same algorithm is applied for y-axis.
188 * Same algorithm is applied for y-axis.
188 *
189 *
189 * @param begin the iterator pointing to the beginning of the data series
190 * @param begin the iterator pointing to the beginning of the data series
190 * @param end the iterator pointing to the end of the data series
191 * @param end the iterator pointing to the end of the data series
191 * @param xResolution the resolution expected for the mesh's x-axis
192 * @param xResolution the resolution expected for the mesh's x-axis
192 * @param yResolution the resolution expected for the mesh's y-axis
193 * @param yResolution the resolution expected for the mesh's y-axis
193 * @return the mesh created, an empty mesh if the input data do not allow to generate a regular
194 * @return the mesh created, an empty mesh if the input data do not allow to generate a regular
194 * mesh (empty data, null resolutions, logarithmic x-axis)
195 * mesh (empty data, null resolutions, logarithmic x-axis)
195 * @warning the method considers the dataset as sorted and doesn't control it.
196 * @warning the method considers the dataset as sorted and doesn't control it.
196 */
197 */
197 static Mesh regularMesh(DataSeriesIterator begin, DataSeriesIterator end,
198 static Mesh regularMesh(DataSeriesIterator begin, DataSeriesIterator end,
198 Resolution xResolution, Resolution yResolution);
199 Resolution xResolution, Resolution yResolution);
199 };
200 };
200
201
201 template <typename Iterator>
202 template <typename Iterator>
202 DataSeriesUtils::Resolution DataSeriesUtils::resolution(Iterator begin, Iterator end,
203 DataSeriesUtils::Resolution DataSeriesUtils::resolution(Iterator begin, Iterator end,
203 bool logarithmic)
204 bool logarithmic)
204 {
205 {
205 // Retrieves data into a work dataset
206 // Retrieves data into a work dataset
206 using ValueType = typename Iterator::value_type;
207 using ValueType = typename Iterator::value_type;
207 std::vector<ValueType> values{};
208 std::vector<ValueType> values{};
208 std::copy(begin, end, std::back_inserter(values));
209 std::copy(begin, end, std::back_inserter(values));
209
210
210 // Converts data if logarithmic flag is activated
211 // Converts data if logarithmic flag is activated
211 if (logarithmic) {
212 if (logarithmic) {
212 std::for_each(values.begin(), values.end(),
213 std::for_each(values.begin(), values.end(),
213 [logarithmic](auto &val) { val = std::log10(val); });
214 [logarithmic](auto &val) { val = std::log10(val); });
214 }
215 }
215
216
216 // Computes the differences between the values in the dataset
217 // Computes the differences between the values in the dataset
217 std::adjacent_difference(values.begin(), values.end(), values.begin());
218 std::adjacent_difference(values.begin(), values.end(), values.begin());
218
219
219 // Retrieves the smallest difference
220 // Retrieves the smallest difference
220 auto resolutionIt = std::min_element(values.begin(), values.end());
221 auto resolutionIt = std::min_element(values.begin(), values.end());
221 auto resolution
222 auto resolution
222 = resolutionIt != values.end() ? *resolutionIt : std::numeric_limits<double>::quiet_NaN();
223 = resolutionIt != values.end() ? *resolutionIt : std::numeric_limits<double>::quiet_NaN();
223
224
224 return Resolution{resolution, logarithmic};
225 return Resolution{resolution, logarithmic};
225 }
226 }
226
227
227 #endif // SCIQLOP_DATASERIESUTILS_H
228 #endif // SCIQLOP_DATASERIESUTILS_H
@@ -1,196 +1,194
1 #include "Data/DataSeriesUtils.h"
1 #include "Data/DataSeriesUtils.h"
2
2
3 #include <cmath>
4
5 Q_LOGGING_CATEGORY(LOG_DataSeriesUtils, "DataSeriesUtils")
3 Q_LOGGING_CATEGORY(LOG_DataSeriesUtils, "DataSeriesUtils")
6
4
7 void DataSeriesUtils::fillDataHoles(std::vector<double> &xAxisData, std::vector<double> &valuesData,
5 void DataSeriesUtils::fillDataHoles(std::vector<double> &xAxisData, std::vector<double> &valuesData,
8 double resolution, double fillValue, double minBound,
6 double resolution, double fillValue, double minBound,
9 double maxBound)
7 double maxBound)
10 {
8 {
11 if (resolution == 0. || std::isnan(resolution)) {
9 if (resolution == 0. || std::isnan(resolution)) {
12 qCWarning(LOG_DataSeriesUtils())
10 qCWarning(LOG_DataSeriesUtils())
13 << "Can't fill data holes with a null resolution, no changes will be made";
11 << "Can't fill data holes with a null resolution, no changes will be made";
14 return;
12 return;
15 }
13 }
16
14
17 if (xAxisData.empty()) {
15 if (xAxisData.empty()) {
18 qCWarning(LOG_DataSeriesUtils())
16 qCWarning(LOG_DataSeriesUtils())
19 << "Can't fill data holes for empty data, no changes will be made";
17 << "Can't fill data holes for empty data, no changes will be made";
20 return;
18 return;
21 }
19 }
22
20
23 // Gets the number of values per x-axis data
21 // Gets the number of values per x-axis data
24 auto nbComponents = valuesData.size() / xAxisData.size();
22 auto nbComponents = valuesData.size() / xAxisData.size();
25
23
26 // Generates fill values that will be used to complete values data
24 // Generates fill values that will be used to complete values data
27 std::vector<double> fillValues(nbComponents, fillValue);
25 std::vector<double> fillValues(nbComponents, fillValue);
28
26
29 // Checks if there are data holes on the beginning of the data and generates the hole at the
27 // 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
28 // extremity if it's the case
31 auto minXAxisData = xAxisData.front();
29 auto minXAxisData = xAxisData.front();
32 if (!std::isnan(minBound) && minBound < minXAxisData) {
30 if (!std::isnan(minBound) && minBound < minXAxisData) {
33 auto holeSize = static_cast<int>((minXAxisData - minBound) / resolution);
31 auto holeSize = static_cast<int>((minXAxisData - minBound) / resolution);
34 if (holeSize > 0) {
32 if (holeSize > 0) {
35 xAxisData.insert(xAxisData.begin(), minXAxisData - holeSize * resolution);
33 xAxisData.insert(xAxisData.begin(), minXAxisData - holeSize * resolution);
36 valuesData.insert(valuesData.begin(), fillValues.begin(), fillValues.end());
34 valuesData.insert(valuesData.begin(), fillValues.begin(), fillValues.end());
37 }
35 }
38 }
36 }
39
37
40 // Same for the end of the data
38 // Same for the end of the data
41 auto maxXAxisData = xAxisData.back();
39 auto maxXAxisData = xAxisData.back();
42 if (!std::isnan(maxBound) && maxBound > maxXAxisData) {
40 if (!std::isnan(maxBound) && maxBound > maxXAxisData) {
43 auto holeSize = static_cast<int>((maxBound - maxXAxisData) / resolution);
41 auto holeSize = static_cast<int>((maxBound - maxXAxisData) / resolution);
44 if (holeSize > 0) {
42 if (holeSize > 0) {
45 xAxisData.insert(xAxisData.end(), maxXAxisData + holeSize * resolution);
43 xAxisData.insert(xAxisData.end(), maxXAxisData + holeSize * resolution);
46 valuesData.insert(valuesData.end(), fillValues.begin(), fillValues.end());
44 valuesData.insert(valuesData.end(), fillValues.begin(), fillValues.end());
47 }
45 }
48 }
46 }
49
47
50 // Generates other data holes
48 // Generates other data holes
51 auto xAxisIt = xAxisData.begin();
49 auto xAxisIt = xAxisData.begin();
52 while (xAxisIt != xAxisData.end()) {
50 while (xAxisIt != xAxisData.end()) {
53 // Stops at first value which has a gap greater than resolution with the value next to it
51 // Stops at first value which has a gap greater than resolution with the value next to it
54 xAxisIt = std::adjacent_find(
52 xAxisIt = std::adjacent_find(
55 xAxisIt, xAxisData.end(),
53 xAxisIt, xAxisData.end(),
56 [resolution](const auto &a, const auto &b) { return (b - a) > resolution; });
54 [resolution](const auto &a, const auto &b) { return (b - a) > resolution; });
57
55
58 if (xAxisIt != xAxisData.end()) {
56 if (xAxisIt != xAxisData.end()) {
59 auto nextXAxisIt = xAxisIt + 1;
57 auto nextXAxisIt = xAxisIt + 1;
60
58
61 // Gets the values that has a gap greater than resolution between them
59 // Gets the values that has a gap greater than resolution between them
62 auto lowValue = *xAxisIt;
60 auto lowValue = *xAxisIt;
63 auto highValue = *nextXAxisIt;
61 auto highValue = *nextXAxisIt;
64
62
65 // Completes holes between the two values by creating new values (according to the
63 // Completes holes between the two values by creating new values (according to the
66 // resolution)
64 // resolution)
67 for (auto i = lowValue + resolution; i < highValue; i += resolution) {
65 for (auto i = lowValue + resolution; i < highValue; i += resolution) {
68 // Gets the iterator of values data from which to insert fill values
66 // Gets the iterator of values data from which to insert fill values
69 auto nextValuesIt = valuesData.begin()
67 auto nextValuesIt = valuesData.begin()
70 + std::distance(xAxisData.begin(), nextXAxisIt) * nbComponents;
68 + std::distance(xAxisData.begin(), nextXAxisIt) * nbComponents;
71
69
72 // New value is inserted before nextXAxisIt
70 // New value is inserted before nextXAxisIt
73 nextXAxisIt = xAxisData.insert(nextXAxisIt, i) + 1;
71 nextXAxisIt = xAxisData.insert(nextXAxisIt, i) + 1;
74
72
75 // New values are inserted before nextValuesIt
73 // New values are inserted before nextValuesIt
76 valuesData.insert(nextValuesIt, fillValues.begin(), fillValues.end());
74 valuesData.insert(nextValuesIt, fillValues.begin(), fillValues.end());
77 }
75 }
78
76
79 // Moves to the next value to continue loop on the x-axis data
77 // Moves to the next value to continue loop on the x-axis data
80 xAxisIt = nextXAxisIt;
78 xAxisIt = nextXAxisIt;
81 }
79 }
82 }
80 }
83 }
81 }
84
82
85 namespace {
83 namespace {
86
84
87 /**
85 /**
88 * Generates axis's mesh properties according to data and resolution
86 * Generates axis's mesh properties according to data and resolution
89 * @param begin the iterator pointing to the beginning of the data
87 * @param begin the iterator pointing to the beginning of the data
90 * @param end the iterator pointing to the end of the data
88 * @param end the iterator pointing to the end of the data
91 * @param fun the function to retrieve data from the data iterators
89 * @param fun the function to retrieve data from the data iterators
92 * @param resolution the resolution to use for the axis' mesh
90 * @param resolution the resolution to use for the axis' mesh
93 * @return a tuple representing the mesh properties : <nb values, min value, value step>
91 * @return a tuple representing the mesh properties : <nb values, min value, value step>
94 */
92 */
95 template <typename Iterator, typename IteratorFun>
93 template <typename Iterator, typename IteratorFun>
96 std::tuple<int, double, double> meshProperties(Iterator begin, Iterator end, IteratorFun fun,
94 std::tuple<int, double, double> meshProperties(Iterator begin, Iterator end, IteratorFun fun,
97 double resolution)
95 double resolution)
98 {
96 {
99 // Computes the gap between min and max data. This will be used to determinate the step between
97 // Computes the gap between min and max data. This will be used to determinate the step between
100 // each data of the mesh
98 // each data of the mesh
101 auto min = fun(begin);
99 auto min = fun(begin);
102 auto max = fun(end - 1);
100 auto max = fun(end - 1);
103 auto gap = max - min;
101 auto gap = max - min;
104
102
105 // Computes the step trying to use the fixed resolution. If the resolution doesn't separate the
103 // Computes the step trying to use the fixed resolution. If the resolution doesn't separate the
106 // values evenly , it is recalculated.
104 // values evenly , it is recalculated.
107 // For example, for a resolution of 2.0:
105 // 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]
106 // - 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
107 // - 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,
108 // 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]
109 // 5.4, 7.2, 9]
112 auto nbVal = static_cast<int>(std::ceil(gap / resolution));
110 auto nbVal = static_cast<int>(std::ceil(gap / resolution));
113 auto step = gap / nbVal;
111 auto step = gap / nbVal;
114
112
115 // last data is included in the total number of values
113 // last data is included in the total number of values
116 return std::make_tuple(nbVal + 1, min, step);
114 return std::make_tuple(nbVal + 1, min, step);
117 }
115 }
118
116
119 } // namespace
117 } // namespace
120
118
121 DataSeriesUtils::Mesh DataSeriesUtils::regularMesh(DataSeriesIterator begin, DataSeriesIterator end,
119 DataSeriesUtils::Mesh DataSeriesUtils::regularMesh(DataSeriesIterator begin, DataSeriesIterator end,
122 Resolution xResolution, Resolution yResolution)
120 Resolution xResolution, Resolution yResolution)
123 {
121 {
124 // Checks preconditions
122 // Checks preconditions
125 if (xResolution.m_Val == 0. || std::isnan(xResolution.m_Val) || yResolution.m_Val == 0.
123 if (xResolution.m_Val == 0. || std::isnan(xResolution.m_Val) || yResolution.m_Val == 0.
126 || std::isnan(yResolution.m_Val)) {
124 || std::isnan(yResolution.m_Val)) {
127 qCWarning(LOG_DataSeriesUtils()) << "Can't generate mesh with a null resolution";
125 qCWarning(LOG_DataSeriesUtils()) << "Can't generate mesh with a null resolution";
128 return Mesh{};
126 return Mesh{};
129 }
127 }
130
128
131 if (xResolution.m_Logarithmic) {
129 if (xResolution.m_Logarithmic) {
132 qCWarning(LOG_DataSeriesUtils())
130 qCWarning(LOG_DataSeriesUtils())
133 << "Can't generate mesh with a logarithmic x-axis resolution";
131 << "Can't generate mesh with a logarithmic x-axis resolution";
134 return Mesh{};
132 return Mesh{};
135 }
133 }
136
134
137 if (std::distance(begin, end) == 0) {
135 if (std::distance(begin, end) == 0) {
138 qCWarning(LOG_DataSeriesUtils()) << "Can't generate mesh for empty data";
136 qCWarning(LOG_DataSeriesUtils()) << "Can't generate mesh for empty data";
139 return Mesh{};
137 return Mesh{};
140 }
138 }
141
139
142 auto yData = begin->y();
140 auto yData = begin->y();
143 if (yData.empty()) {
141 if (yData.empty()) {
144 qCWarning(LOG_DataSeriesUtils()) << "Can't generate mesh for data with no y-axis";
142 qCWarning(LOG_DataSeriesUtils()) << "Can't generate mesh for data with no y-axis";
145 return Mesh{};
143 return Mesh{};
146 }
144 }
147
145
148 // Converts y-axis and its resolution to logarithmic values
146 // Converts y-axis and its resolution to logarithmic values
149 if (yResolution.m_Logarithmic) {
147 if (yResolution.m_Logarithmic) {
150 std::for_each(yData.begin(), yData.end(), [](auto &val) { val = std::log10(val); });
148 std::for_each(yData.begin(), yData.end(), [](auto &val) { val = std::log10(val); });
151 }
149 }
152
150
153 // Computes mesh properties
151 // Computes mesh properties
154 int nbX, nbY;
152 int nbX, nbY;
155 double xMin, xStep, yMin, yStep;
153 double xMin, xStep, yMin, yStep;
156 std::tie(nbX, xMin, xStep)
154 std::tie(nbX, xMin, xStep)
157 = meshProperties(begin, end, [](const auto &it) { return it->x(); }, xResolution.m_Val);
155 = meshProperties(begin, end, [](const auto &it) { return it->x(); }, xResolution.m_Val);
158 std::tie(nbY, yMin, yStep) = meshProperties(
156 std::tie(nbY, yMin, yStep) = meshProperties(
159 yData.begin(), yData.end(), [](const auto &it) { return *it; }, yResolution.m_Val);
157 yData.begin(), yData.end(), [](const auto &it) { return *it; }, yResolution.m_Val);
160
158
161 // Generates mesh according to the x-axis and y-axis steps
159 // Generates mesh according to the x-axis and y-axis steps
162 Mesh result{nbX, xMin, xStep, nbY, yMin, yStep};
160 Mesh result{nbX, xMin, xStep, nbY, yMin, yStep};
163
161
164 for (auto meshXIndex = 0; meshXIndex < nbX; ++meshXIndex) {
162 for (auto meshXIndex = 0; meshXIndex < nbX; ++meshXIndex) {
165 auto meshX = xMin + meshXIndex * xStep;
163 auto meshX = xMin + meshXIndex * xStep;
166 // According to current x-axis of the mesh, finds in the data series the interval in which
164 // 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).
165 // 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,
166 // 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].
167 // 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,
168 auto xIt = std::lower_bound(begin, end, meshX,
171 [xResolution](const auto &it, const auto &val) {
169 [xResolution](const auto &it, const auto &val) {
172 return it.x() - xResolution.m_Val / 2. < val;
170 return it.x() - xResolution.m_Val / 2. < val;
173 })
171 })
174 - 1;
172 - 1;
175
173
176 // When the corresponding entry of the data series is found, generates the values of the
174 // 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
175 // mesh by retrieving the values of the entry, for each y-axis value of the mesh
178 auto values = xIt->values();
176 auto values = xIt->values();
179
177
180 for (auto meshYIndex = 0; meshYIndex < nbY; ++meshYIndex) {
178 for (auto meshYIndex = 0; meshYIndex < nbY; ++meshYIndex) {
181 auto meshY = yMin + meshYIndex * yStep;
179 auto meshY = yMin + meshYIndex * yStep;
182
180
183 auto yBegin = yData.begin();
181 auto yBegin = yData.begin();
184 auto yIt = std::lower_bound(yBegin, yData.end(), meshY,
182 auto yIt = std::lower_bound(yBegin, yData.end(), meshY,
185 [yResolution](const auto &it, const auto &val) {
183 [yResolution](const auto &it, const auto &val) {
186 return it - yResolution.m_Val / 2. < val;
184 return it - yResolution.m_Val / 2. < val;
187 })
185 })
188 - 1;
186 - 1;
189
187
190 auto valueIndex = std::distance(yBegin, yIt);
188 auto valueIndex = std::distance(yBegin, yIt);
191 result.m_Data[result.m_NbX * meshYIndex + meshXIndex] = values.at(valueIndex);
189 result.m_Data[result.m_NbX * meshYIndex + meshXIndex] = values.at(valueIndex);
192 }
190 }
193 }
191 }
194
192
195 return result;
193 return result;
196 }
194 }
General Comments 0
You need to be logged in to leave comments. Login now