Engauge Digitizer  2
ExportXThetaValuesMergedFunctions.cpp
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"
9 #include "ExportLayoutFunctions.h"
10 #include "ExportPointsSelectionFunctions.h"
11 #include "ExportXThetaValuesMergedFunctions.h"
12 #include "Logger.h"
13 #include "Point.h"
14 #include <qmath.h>
15 #include "Transformation.h"
16 
17 using namespace std;
18 
20  const ValuesVectorXOrY &xThetaValuesRaw,
21  const Transformation &transformation) :
22  m_modelExport (modelExport),
23  m_xThetaValuesRaw (xThetaValuesRaw),
24  m_transformation (transformation)
25 {
26 }
27 
28 void ExportXThetaValuesMergedFunctions::firstSimplestNumberLinear (double &xThetaFirstSimplestNumber,
29  double &xThetaMin,
30  double &xThetaMax) const
31 {
32  LOG4CPP_INFO_S ((*mainCat)) << "ExportXThetaValuesMergedFunctions::firstSimplestNumberLinear";
33 
34  // X/theta range
35  xThetaMin = m_xThetaValuesRaw.firstKey();
36  xThetaMax = m_xThetaValuesRaw.lastKey();
37 
38  // Compute offset that gives the simplest numbers
39  ExportAlignLinear alignLinear (xThetaMin,
40  xThetaMax);
41 
42  xThetaFirstSimplestNumber = alignLinear.firstSimplestNumber ();
43 }
44 
45 void ExportXThetaValuesMergedFunctions::firstSimplestNumberLog (double &xThetaFirstSimplestNumber,
46  double &xThetaMin,
47  double &xThetaMax) const
48 {
49  LOG4CPP_INFO_S ((*mainCat)) << "ExportXThetaValuesMergedFunctions::firstSimplestNumberLog";
50 
51  // X/theta range
52  xThetaMin = m_xThetaValuesRaw.firstKey();
53  xThetaMax = m_xThetaValuesRaw.lastKey();
54 
55  // Compute offset that gives the simplest numbers
56  ExportAlignLog alignLog (xThetaMin,
57  xThetaMax);
58 
59  xThetaFirstSimplestNumber = alignLog.firstSimplestNumber();
60 }
61 
62 ExportValuesXOrY ExportXThetaValuesMergedFunctions::periodicLinear() const
63 {
64  LOG4CPP_INFO_S ((*mainCat)) << "ExportXThetaValuesMergedFunctions::periodicLinear";
65 
66  if (m_xThetaValuesRaw.count () > 0) {
67 
68  double xThetaFirstSimplestNumber, xThetaMin, xThetaMax;
69  firstSimplestNumberLinear (xThetaFirstSimplestNumber,
70  xThetaMin,
71  xThetaMax);
72 
73  // Assuming user picks an appropriate interval increment, numbering starting at xThetaFirstSimplestNumber
74  // will give nice x/theta numbers
75  if (m_modelExport.pointsIntervalUnitsFunctions() == EXPORT_POINTS_INTERVAL_UNITS_GRAPH) {
76  return periodicLinearGraph(xThetaFirstSimplestNumber,
77  xThetaMin,
78  xThetaMax);
79  } else {
80  return periodicLinearScreen(xThetaMin,
81  xThetaMax);
82  }
83  } else {
84 
85  ExportValuesXOrY emptyList;
86  return emptyList;
87  }
88 }
89 
90 ExportValuesXOrY ExportXThetaValuesMergedFunctions::periodicLinearGraph(double xThetaFirstSimplestNumber,
91  double xThetaMin,
92  double xThetaMax) const
93 {
94  LOG4CPP_INFO_S ((*mainCat)) << "ExportXThetaValuesMergedFunctions::periodicLinearGraph";
95 
96  // Convert the gathered values into a periodic sequence
97  ValuesVectorXOrY values;
98  double xTheta = xThetaFirstSimplestNumber;
99  while (xTheta > xThetaMin) {
100  xTheta -= m_modelExport.pointsIntervalFunctions(); // Go backwards until reaching or passing minimum
101  }
102  if (xTheta < xThetaMin) {
103  values [xThetaMin] = true; // We passed minimum so insert point right at xThetaMin
104  }
105 
106  xTheta += m_modelExport.pointsIntervalFunctions();
107  while (xTheta <= xThetaMax) {
108  values [xTheta] = true;
109  xTheta += m_modelExport.pointsIntervalFunctions(); // Insert point at a simple number
110  }
111 
112  if (xTheta > xThetaMax) {
113  values [xThetaMax] = true; // We passed maximum so insert point right at xThetaMax
114  }
115 
116  return values.keys();
117 }
118 
119 ExportValuesXOrY ExportXThetaValuesMergedFunctions::periodicLinearScreen (double xThetaMin,
120  double xThetaMax) const
121 {
122  LOG4CPP_INFO_S ((*mainCat)) << "ExportXThetaValuesMergedFunctions::periodicLinearScreen";
123 
124  // This must be greater than zero. Otherwise, logarithmic y axis will trigger errors in the
125  // transform, which cascades into NaN values for the x coordinates below
126  const double ARBITRARY_Y = 1.0;
127 
128  // Screen coordinates of endpoints
129  QPointF posScreenFirst, posScreenLast;
130  m_transformation.transformRawGraphToScreen(QPointF (xThetaMin,
131  ARBITRARY_Y),
132  posScreenFirst);
133  m_transformation.transformRawGraphToScreen(QPointF (xThetaMax,
134  ARBITRARY_Y),
135  posScreenLast);
136  double deltaScreenX = posScreenLast.x() - posScreenFirst.x();
137 
138  // Need calculations to find the scaling to be applied to successive points
139  double s = 1.0;
140  double interval = m_modelExport.pointsIntervalFunctions();
141  if ((interval > 0) &&
142  (interval < deltaScreenX)) {
143  s = interval / deltaScreenX;
144  }
145 
146  // Example: xThetaMin=0.1 and xThetaMax=100 (points are 0.1, 1, 10, 100) with s=1/3 so scale should be 10
147  // which multiples 0.1 to get 1. This uses s=(log(xNext)-log(xMin))/(log(xMax)-log(xMin))
148  double xNext = xThetaMin + s * (xThetaMax - xThetaMin);
149  double delta = xNext - xThetaMin;
150 
151  ValuesVectorXOrY values;
152 
153  double xTheta = xThetaMin;
154  while (xTheta <= xThetaMax) {
155 
156  values [xTheta] = true;
157 
158  xTheta += delta;
159  }
160 
161  return values.keys();
162 }
163 
164 ExportValuesXOrY ExportXThetaValuesMergedFunctions::periodicLog() const
165 {
166  LOG4CPP_INFO_S ((*mainCat)) << "ExportXThetaValuesMergedFunctions::periodicLog";
167 
168  double xThetaFirstSimplestNumber, xThetaMin, xThetaMax;
169  firstSimplestNumberLog (xThetaFirstSimplestNumber,
170  xThetaMin,
171  xThetaMax);
172 
173  // Assuming user picks an appropriate interval increment, numbering starting at xThetaFirstSimplestNumber
174  // will give nice x/theta numbers
175  if (m_modelExport.pointsIntervalUnitsFunctions() == EXPORT_POINTS_INTERVAL_UNITS_GRAPH) {
176  return periodicLogGraph(xThetaFirstSimplestNumber,
177  xThetaMin,
178  xThetaMax);
179  } else {
180  return periodicLogScreen(xThetaMin,
181  xThetaMax);
182  }
183 }
184 
185 ExportValuesXOrY ExportXThetaValuesMergedFunctions::periodicLogGraph (double xThetaFirstSimplestNumber,
186  double xThetaMin,
187  double xThetaMax) const
188 {
189  LOG4CPP_INFO_S ((*mainCat)) << "ExportXThetaValuesMergedFunctions::periodicLogGraph";
190 
191  // Convert the gathered values into a periodic sequence
192  ValuesVectorXOrY values;
193  double xTheta = xThetaFirstSimplestNumber;
194  if (m_modelExport.pointsIntervalFunctions() > 1) { // Safe to iterate
195  while (xTheta > xThetaMin) {
196  xTheta /= m_modelExport.pointsIntervalFunctions(); // Go backwards until reaching or passing minimum
197  }
198  }
199  if (xTheta < xThetaMin) {
200  values [xThetaMin] = true; // We passed minimum so insert point right at xThetaMin
201  }
202 
203  if (m_modelExport.pointsIntervalFunctions() > 1) { // Safe to iterate
204  xTheta *= m_modelExport.pointsIntervalFunctions();
205  while (xTheta <= xThetaMax) {
206  values [xTheta] = true;
207  xTheta *= m_modelExport.pointsIntervalFunctions(); // Insert point at a simple number
208  }
209  }
210 
211  if (xTheta > xThetaMax) {
212  values [xThetaMax] = true; // We passed maximum so insert point right at xThetaMax
213  }
214 
215  return values.keys();
216 }
217 
218 ExportValuesXOrY ExportXThetaValuesMergedFunctions::periodicLogScreen (double xThetaMin,
219  double xThetaMax) const
220 {
221  LOG4CPP_INFO_S ((*mainCat)) << "ExportXThetaValuesMergedFunctions::periodicLogScreen";
222 
223  const double ARBITRARY_Y = 0.0;
224 
225  // Screen coordinates of endpoints
226  QPointF posScreenFirst, posScreenLast;
227  m_transformation.transformRawGraphToScreen(QPointF (xThetaMin,
228  ARBITRARY_Y),
229  posScreenFirst);
230  m_transformation.transformRawGraphToScreen(QPointF (xThetaMax,
231  ARBITRARY_Y),
232  posScreenLast);
233  double deltaScreenX = posScreenLast.x() - posScreenFirst.x();
234  double deltaScreenY = posScreenLast.y() - posScreenFirst.y();
235  double deltaScreen = qSqrt (deltaScreenX * deltaScreenX + deltaScreenY * deltaScreenY);
236 
237  // Need calculations to find the scaling to be applied to successive points
238  double s = 1.0;
239  double interval = m_modelExport.pointsIntervalFunctions();
240  if ((interval > 0) &&
241  (interval < deltaScreen)) {
242  s = interval / deltaScreen;
243  }
244 
245  // Example: xThetaMin=0.1 and xThetaMax=100 (points are 0.1, 1, 10, 100) with s=1/3 so scale should be 10
246  // which multiples 0.1 to get 1. This uses s=(log(xNext)-log(xMin))/(log(xMax)-log(xMin))
247  double xNext = qExp (qLn (xThetaMin) + s * (qLn (xThetaMax) - qLn (xThetaMin)));
248  double scale = xNext / xThetaMin;
249 
250  ValuesVectorXOrY values;
251 
252  double xTheta = xThetaMin;
253  while (xTheta <= xThetaMax) {
254 
255  values [xTheta] = true;
256 
257  xTheta *= scale;
258  }
259 
260  return values.keys();
261 }
262 
264 {
265  LOG4CPP_INFO_S ((*mainCat)) << "ExportXThetaValuesMergedFunctions::xThetaValues";
266 
267  if (m_modelExport.pointsSelectionFunctions() == EXPORT_POINTS_SELECTION_FUNCTIONS_INTERPOLATE_PERIODIC) {
268 
269  // Special case that occurs when there are no points
270  if (qAbs (m_modelExport.pointsIntervalFunctions()) <= 0) {
271 
272  ExportValuesXOrY empty;
273  return empty;
274 
275  } else {
276 
277  bool isLinear = (m_transformation.modelCoords().coordScaleXTheta() == COORD_SCALE_LINEAR);
278  if (isLinear) {
279  return periodicLinear ();
280  } else {
281  return periodicLog ();
282  }
283  }
284  } else {
285 
286  // Return the gathered values
287  return m_xThetaValuesRaw.keys();
288 
289  }
290 }
ExportValuesXOrY xThetaValues() const
Resulting x/theta values for all included functions.
ExportXThetaValuesMergedFunctions(const DocumentModelExportFormat &modelExport, const ValuesVectorXOrY &xThetaValuesRaw, const Transformation &transformation)
Single constructor.
Model for DlgSettingsExportFormat and CmdSettingsExportFormat.
Pick first simplest x value between specified min and max, for linear scaling.
void transformRawGraphToScreen(const QPointF &pointRaw, QPointF &pointScreen) const
Transform from raw graph coordinates to linear cartesian graph coordinates, then to screen coordinate...
double pointsIntervalFunctions() const
Get method for points interval for functions.
DocumentModelCoords modelCoords() const
Get method for DocumentModelCoords.
Pick first simplest x value between specified min and max, for log scaling.
Affine transformation between screen and graph coordinates, based on digitized axis points.
CoordScale coordScaleXTheta() const
Get method for linear/log scale on x/theta.
ExportPointsIntervalUnits pointsIntervalUnitsFunctions() const
Get method for points interval units for functions.
ExportPointsSelectionFunctions pointsSelectionFunctions() const
Get method for point selection for functions.