Engauge Digitizer 2
Loading...
Searching...
No Matches
ExportXThetaValuesMergedFunctions.cpp
Go to the documentation of this file.
1/******************************************************************************************************
2 * (C) 2014 markummitchell@github.com. This file is part of Engauge Digitizer, which is released *
3 * under GNU General Public License version 2 (GPLv2) or (at your option) any later version. See file *
4 * LICENSE or go to gnu.org/licenses for details. Distribution requires prior written permission. *
5 ******************************************************************************************************/
6
7#include "ExportAlignLinear.h"
8#include "ExportAlignLog.h"
12#include "Logger.h"
13#include "Point.h"
14#include <qmath.h>
15#include "Transformation.h"
16
17using namespace std;
18
20 const MainWindowModel &modelMainWindow,
21 const ValuesVectorXOrY &xThetaValuesRaw,
22 const Transformation &transformation) :
23 m_modelExport (modelExport),
24 m_modelMainWindow (modelMainWindow),
25 m_xThetaValuesRaw (xThetaValuesRaw),
26 m_transformation (transformation)
27{
28}
29
30bool ExportXThetaValuesMergedFunctions::breakForPointOverrun (unsigned int curveSize,
31 bool &isOverrun) const
32{
33 // Break if we will overrun the maximum point limit. As per issue #401 a change of scale
34 // can result in a huge (new) range with a small (old) delta - resulting in so many values
35 // that this loop effectively hangs. Set flag if overrun occurred
36 isOverrun = (curveSize > (unsigned int) m_modelMainWindow.maximumExportedPointsPerCurve ());
37 return isOverrun;
38}
39
40void ExportXThetaValuesMergedFunctions::firstSimplestNumberLinear (double &xThetaFirstSimplestNumber,
41 double &xThetaMin,
42 double &xThetaMax) const
43{
44 LOG4CPP_INFO_S ((*mainCat)) << "ExportXThetaValuesMergedFunctions::firstSimplestNumberLinear";
45
46 // X/theta range
47 xThetaMin = m_xThetaValuesRaw.firstKey();
48 xThetaMax = m_xThetaValuesRaw.lastKey();
49
50 // Compute offset that gives the simplest numbers
51 ExportAlignLinear alignLinear (xThetaMin,
52 xThetaMax);
53
54 xThetaFirstSimplestNumber = alignLinear.firstSimplestNumber ();
55}
56
57void ExportXThetaValuesMergedFunctions::firstSimplestNumberLog (double &xThetaFirstSimplestNumber,
58 double &xThetaMin,
59 double &xThetaMax) const
60{
61 LOG4CPP_INFO_S ((*mainCat)) << "ExportXThetaValuesMergedFunctions::firstSimplestNumberLog";
62
63 // X/theta range
64 xThetaMin = m_xThetaValuesRaw.firstKey();
65 xThetaMax = m_xThetaValuesRaw.lastKey();
66
67 // Compute offset that gives the simplest numbers
68 ExportAlignLog alignLog (xThetaMin,
69 xThetaMax);
70
71 xThetaFirstSimplestNumber = alignLog.firstSimplestNumber();
72}
73
74ExportValuesXOrY ExportXThetaValuesMergedFunctions::periodicLinear(bool &isOverrun) const
75{
76 LOG4CPP_INFO_S ((*mainCat)) << "ExportXThetaValuesMergedFunctions::periodicLinear";
77
78 if (m_xThetaValuesRaw.count () > 0) {
79
80 double xThetaFirstSimplestNumber, xThetaMin, xThetaMax;
81 firstSimplestNumberLinear (xThetaFirstSimplestNumber,
82 xThetaMin,
83 xThetaMax);
84
85 // Assuming user picks an appropriate interval increment, numbering starting at xThetaFirstSimplestNumber
86 // will give nice x/theta numbers
87 if (m_modelExport.pointsIntervalUnitsFunctions() == EXPORT_POINTS_INTERVAL_UNITS_GRAPH) {
88 return periodicLinearGraph(xThetaFirstSimplestNumber,
89 xThetaMin,
90 xThetaMax,
91 isOverrun);
92 } else {
93 return periodicLinearScreen(xThetaMin,
94 xThetaMax,
95 isOverrun);
96 }
97 } else {
98
99 ExportValuesXOrY emptyList;
100 return emptyList;
101 }
102}
103
104ExportValuesXOrY ExportXThetaValuesMergedFunctions::periodicLinearGraph(double xThetaFirstSimplestNumber,
105 double xThetaMin,
106 double xThetaMax,
107 bool &isOverrun) const
108{
109 LOG4CPP_INFO_S ((*mainCat)) << "ExportXThetaValuesMergedFunctions::periodicLinearGraph";
110
111 // Convert the gathered values into a periodic sequence
112 ValuesVectorXOrY values;
113 double xTheta = xThetaFirstSimplestNumber;
114 while (xTheta > xThetaMin) {
115 xTheta -= m_modelExport.pointsIntervalFunctions(); // Go backwards until reaching or passing minimum
116 }
117 if (xTheta < xThetaMin) {
118 values [xThetaMin] = true; // We passed minimum so insert point right at xThetaMin
119 }
120
121 xTheta += m_modelExport.pointsIntervalFunctions();
122 while (xTheta <= xThetaMax) {
123
124 values [xTheta] = true;
125 xTheta += m_modelExport.pointsIntervalFunctions(); // Insert point at a simple number
126
127 if (breakForPointOverrun (values.count(),
128 isOverrun)) {
129 break;
130 }
131 }
132
133 if (xTheta > xThetaMax) {
134 values [xThetaMax] = true; // We passed maximum so insert point right at xThetaMax
135 }
136
137 return values.keys();
138}
139
140ExportValuesXOrY ExportXThetaValuesMergedFunctions::periodicLinearScreen (double xThetaMin,
141 double xThetaMax,
142 bool &isOverrun) const
143{
144 LOG4CPP_INFO_S ((*mainCat)) << "ExportXThetaValuesMergedFunctions::periodicLinearScreen";
145
146 // This must be greater than zero. Otherwise, logarithmic y axis will trigger errors in the
147 // transform, which cascades into NaN values for the x coordinates below
148 const double ARBITRARY_Y = 1.0;
149
150 // Screen coordinates of endpoints
151 QPointF posScreenFirst, posScreenLast;
152 m_transformation.transformRawGraphToScreen(QPointF (xThetaMin,
153 ARBITRARY_Y),
154 posScreenFirst);
155 m_transformation.transformRawGraphToScreen(QPointF (xThetaMax,
156 ARBITRARY_Y),
157 posScreenLast);
158 double deltaScreenX = posScreenLast.x() - posScreenFirst.x();
159
160 // Need calculations to find the scaling to be applied to successive points
161 double s = 1.0;
162 double interval = m_modelExport.pointsIntervalFunctions();
163 if ((interval > 0) &&
164 (interval < deltaScreenX)) {
165 s = interval / deltaScreenX;
166 }
167
168 // Example: xThetaMin=0.1 and xThetaMax=100 (points are 0.1, 1, 10, 100) with s=1/3 so scale should be 10
169 // which multiples 0.1 to get 1. This uses s=(log(xNext)-log(xMin))/(log(xMax)-log(xMin))
170 double xNext = xThetaMin + s * (xThetaMax - xThetaMin);
171 double delta = xNext - xThetaMin;
172
173 ValuesVectorXOrY values;
174
175 double xTheta = xThetaMin;
176 while (xTheta <= xThetaMax) {
177
178 values [xTheta] = true;
179 xTheta += delta;
180
181 if (breakForPointOverrun (values.count(),
182 isOverrun)) {
183 break;
184 }
185 }
186
187 return values.keys();
188}
189
190ExportValuesXOrY ExportXThetaValuesMergedFunctions::periodicLog(bool &isOverrun) const
191{
192 LOG4CPP_INFO_S ((*mainCat)) << "ExportXThetaValuesMergedFunctions::periodicLog";
193
194 double xThetaFirstSimplestNumber, xThetaMin, xThetaMax;
195 firstSimplestNumberLog (xThetaFirstSimplestNumber,
196 xThetaMin,
197 xThetaMax);
198
199 // Assuming user picks an appropriate interval increment, numbering starting at xThetaFirstSimplestNumber
200 // will give nice x/theta numbers
201 if (m_modelExport.pointsIntervalUnitsFunctions() == EXPORT_POINTS_INTERVAL_UNITS_GRAPH) {
202 return periodicLogGraph(xThetaFirstSimplestNumber,
203 xThetaMin,
204 xThetaMax,
205 isOverrun);
206 } else {
207 return periodicLogScreen(xThetaMin,
208 xThetaMax,
209 isOverrun);
210 }
211}
212
213ExportValuesXOrY ExportXThetaValuesMergedFunctions::periodicLogGraph (double xThetaFirstSimplestNumber,
214 double xThetaMin,
215 double xThetaMax,
216 bool &isOverrun) const
217{
218 LOG4CPP_INFO_S ((*mainCat)) << "ExportXThetaValuesMergedFunctions::periodicLogGraph";
219
220 // Convert the gathered values into a periodic sequence
221 ValuesVectorXOrY values;
222 double xTheta = xThetaFirstSimplestNumber;
223 if (m_modelExport.pointsIntervalFunctions() > 1) { // Safe to iterate
224 while (xTheta > xThetaMin) {
225 xTheta /= m_modelExport.pointsIntervalFunctions(); // Go backwards until reaching or passing minimum
226 }
227 }
228 if (xTheta < xThetaMin) {
229 values [xThetaMin] = true; // We passed minimum so insert point right at xThetaMin
230 }
231
232 if (m_modelExport.pointsIntervalFunctions() > 1) { // Safe to iterate
233 xTheta *= m_modelExport.pointsIntervalFunctions();
234 while (xTheta <= xThetaMax) {
235
236 values [xTheta] = true;
237 xTheta *= m_modelExport.pointsIntervalFunctions(); // Insert point at a simple number
238
239 if (breakForPointOverrun (values.count(),
240 isOverrun)) {
241 break;
242 }
243 }
244 }
245
246 if (xTheta > xThetaMax) {
247 values [xThetaMax] = true; // We passed maximum so insert point right at xThetaMax
248 }
249
250 return values.keys();
251}
252
253ExportValuesXOrY ExportXThetaValuesMergedFunctions::periodicLogScreen (double xThetaMin,
254 double xThetaMax,
255 bool &isOverrun) const
256{
257 LOG4CPP_INFO_S ((*mainCat)) << "ExportXThetaValuesMergedFunctions::periodicLogScreen";
258
259 const double ARBITRARY_Y = 0.0;
260
261 // Screen coordinates of endpoints
262 QPointF posScreenFirst, posScreenLast;
263 m_transformation.transformRawGraphToScreen(QPointF (xThetaMin,
264 ARBITRARY_Y),
265 posScreenFirst);
266 m_transformation.transformRawGraphToScreen(QPointF (xThetaMax,
267 ARBITRARY_Y),
268 posScreenLast);
269 double deltaScreenX = posScreenLast.x() - posScreenFirst.x();
270 double deltaScreenY = posScreenLast.y() - posScreenFirst.y();
271 double deltaScreen = qSqrt (deltaScreenX * deltaScreenX + deltaScreenY * deltaScreenY);
272
273 // Need calculations to find the scaling to be applied to successive points
274 double s = 1.0;
275 double interval = m_modelExport.pointsIntervalFunctions();
276 if ((interval > 0) &&
277 (interval < deltaScreen)) {
278 s = interval / deltaScreen;
279 }
280
281 // Example: xThetaMin=0.1 and xThetaMax=100 (points are 0.1, 1, 10, 100) with s=1/3 so scale should be 10
282 // which multiples 0.1 to get 1. This uses s=(log(xNext)-log(xMin))/(log(xMax)-log(xMin))
283 double xNext = qExp (qLn (xThetaMin) + s * (qLn (xThetaMax) - qLn (xThetaMin)));
284 double scale = xNext / xThetaMin;
285
286 ValuesVectorXOrY values;
287
288 double xTheta = xThetaMin;
289 while (xTheta <= xThetaMax) {
290
291 values [xTheta] = true;
292 xTheta *= scale;
293
294 if (breakForPointOverrun (values.count(),
295 isOverrun)) {
296 break;
297 }
298 }
299
300 return values.keys();
301}
302
304{
305 LOG4CPP_INFO_S ((*mainCat)) << "ExportXThetaValuesMergedFunctions::xThetaValues";
306
307 if (m_modelExport.pointsSelectionFunctions() == EXPORT_POINTS_SELECTION_FUNCTIONS_INTERPOLATE_PERIODIC) {
308
309 ExportValuesXOrY values;
310
311 // Special case that occurs when there are no points
312 if (qAbs (m_modelExport.pointsIntervalFunctions()) <= 0) {
313
314 // Return empty array
315
316 } else {
317
318 bool isLinear = (m_transformation.modelCoords().coordScaleXTheta() == COORD_SCALE_LINEAR);
319
320 if (isLinear) {
321 values = periodicLinear (isOverrun);
322 } else {
323 values = periodicLog (isOverrun);
324 }
325
326 if (isOverrun) {
327
328 // Empty out the array since it is, in the case of overrun, incomplete and therefore confusing at best
329 values.clear();
330
331 }
332 }
333
334 return values;
335
336 } else {
337
338 // Return the gathered values
339 return m_xThetaValuesRaw.keys();
340
341 }
342}
@ COORD_SCALE_LINEAR
Definition CoordScale.h:13
@ EXPORT_POINTS_INTERVAL_UNITS_GRAPH
@ EXPORT_POINTS_SELECTION_FUNCTIONS_INTERPOLATE_PERIODIC
QList< double > ExportValuesXOrY
log4cpp::Category * mainCat
Definition Logger.cpp:14
QMap< double, bool > ValuesVectorXOrY
Model for DlgSettingsExportFormat and CmdSettingsExportFormat.
ExportXThetaValuesMergedFunctions(const DocumentModelExportFormat &modelExport, const MainWindowModel &modelMainWindow, const ValuesVectorXOrY &xThetaValuesRaw, const Transformation &transformation)
Single constructor.
ExportValuesXOrY xThetaValues(bool &isOverrun) const
Resulting x/theta values for all included functions.
Model for DlgSettingsMainWindow.
int maximumExportedPointsPerCurve() const
Get method for maximum number of exported points per curve.
Affine transformation between screen and graph coordinates, based on digitized axis points.
#define LOG4CPP_INFO_S(logger)
Definition convenience.h:18