1 #include "DocumentModelCoords.h"
2 #include "EngaugeAssert.h"
3 #include "GridInitializer.h"
4 #include "Logger.h"
5 #include <math.h>
6 #include <qmath.h>
7 #include "Transformation.h"
8 
GridInitializer()9 GridInitializer::GridInitializer ()
10 {
11 }
12 
axisScale(double xMin,double xMax,bool linearAxis,double & xStart,double & xStop,double & xDelta,int & xCount) const13 void GridInitializer::axisScale (double xMin,
14                                  double xMax,
15                                  bool linearAxis,
16                                  double &xStart,
17                                  double &xStop,
18                                  double &xDelta,
19                                  int &xCount) const
20 {
21   const double range_epsilon = 0.00000000001;
22   double xAverage, xAverageRoundedUp, xRange;
23   int nDigitRange;
24 
25   // Define number of digits of precision. although value of 10 seems
26   // desirable, the sprintf statements elsewhere in this file, which
27   // operate on values with the specified precision, just lose it
28   // for more than 8 digits. example '%.7lg' on 40.000005 gives 40.00001
29   const int nDigitsPrecision = 8;
30 
31   // sort the input values
32   if (xMin > xMax) {
33     double xTemp = xMin;
34     xMin = xMax;
35     xMax = xTemp;
36   }
37 
38   // Scale the coordinates logarithmically if log flag is set
39   if (!linearAxis) {
40     ENGAUGE_ASSERT(xMin > 0);
41     ENGAUGE_ASSERT(xMax > 0);
42     xMin = log10(xMin);
43     xMax = log10(xMax);
44   }
45 
46   // Round off average to first significant digit of range
47   xAverage = (xMin + xMax) / 2.0;
48   xRange = xMax - xMin;
49   if (qAbs (xRange) <= 0) {
50     xRange = fabs (xAverage / 10.0); // for null range use arbitrary range
51   }
52   nDigitRange = valuePower (xRange);
53   xDelta = pow (10.0, double (nDigitRange));
54   xAverageRoundedUp = xDelta * floor ((xAverage + xDelta / 2.0) / xDelta);
55 
56   if (xRange > range_epsilon) {
57     // Adjust stepsize if more points are needed, accounting for roundoff
58     while (fabs (xRange / xDelta) <= 2.000001) {
59       xDelta /= 2.0;
60     }
61   }
62 
63   // Go down until min point is included
64   xStart = xAverageRoundedUp;
65   while (xStart > xMin) {
66     xStart -= xDelta;
67   }
68 
69   // Go up until max point is included
70   xStop = xAverageRoundedUp;
71   while (xStop < xMax) {
72     xStop += xDelta;
73   }
74 
75   xCount = 1 + qFloor ((xStop - xStart) / xDelta + 0.5);
76 
77   if (!linearAxis) {
78 
79     // Convert from log scale back to linear scale. We make sure to keep numbers like 10^-8 unmolested
80     xStart = pow(10.0, xStart);
81     xStop = pow(10.0, xStop);
82     xDelta = pow(10.0, xDelta);
83 
84   } else {
85 
86     // Roundoff to eliminate epsilons of 10^-10
87     int power = valuePower (xDelta) - nDigitsPrecision;
88     xStart = roundOffToPower(xStart, power);
89     xStop = roundOffToPower(xStop, power);
90     xDelta = roundOffToPower(xDelta, power);
91 
92   }
93 }
94 
computeCount(bool linearAxis,double start,double stop,double step) const95 int GridInitializer::computeCount (bool linearAxis,
96                                    double start,
97                                    double stop,
98                                    double step) const
99 {
100   int count;
101 
102   if (linearAxis) {
103     if (qAbs (step) <= 0) {
104       count = 1;
105     } else {
106       count = qFloor (1.0 + (stop - start) / step);
107     }
108   } else {
109     if ((start <= 0) || (step <= 0.0)) {
110       count = 1;
111     } else {
112       count = qFloor (1.0 + log10 (stop / start) / log10 (step));
113     }
114   }
115 
116   return count;
117 }
118 
computeStart(bool linearAxis,double stop,double step,int count) const119 double GridInitializer::computeStart (bool linearAxis,
120                                       double stop,
121                                       double step,
122                                       int count) const
123 {
124   double start;
125 
126   if (linearAxis) {
127     start = stop - step * (count - 1);
128   } else {
129     start = stop / pow (step, double (count - 1));
130   }
131 
132   return start;
133 }
134 
computeStep(bool linearAxis,double start,double stop,int count) const135 double GridInitializer::computeStep (bool linearAxis,
136                                      double start,
137                                      double stop,
138                                      int count) const
139 {
140   double step;
141 
142   if (linearAxis) {
143     if (count > 1) {
144       step = (stop - start) / (count - 1);
145     } else {
146       step = stop - start;
147     }
148   } else {
149     if (start <= 0.0) {
150       step = 1.0;
151     } else {
152       if (count > 1) {
153         step = pow (stop / start, 1.0 / double (count - 1));
154       } else {
155         step = stop / start;
156       }
157     }
158   }
159 
160   return step;
161 }
162 
computeStop(bool linearAxis,double start,double step,int count) const163 double GridInitializer::computeStop (bool linearAxis,
164                                      double start,
165                                      double step,
166                                      int count) const
167 {
168   double stop;
169 
170   if (linearAxis) {
171     stop = start + step * (count - 1);
172   } else {
173     stop = start * pow (step, double (count - 1));
174   }
175 
176   return stop;
177 }
178 
initializeWithNarrowCoverage(const QPointF & boundingRectGraphMin,const QPointF & boundingRectGraphMax,const DocumentModelCoords & modelCoords) const179 DocumentModelGridDisplay GridInitializer::initializeWithNarrowCoverage (const QPointF &boundingRectGraphMin,
180                                                                         const QPointF &boundingRectGraphMax,
181                                                                         const DocumentModelCoords &modelCoords) const
182 {
183   LOG4CPP_INFO_S ((*mainCat)) << "GridInitializer::initializeWithNarrowCoverage";
184 
185   DocumentModelGridDisplay modelGridDisplay;
186 
187   int count;
188   double start, stop, step;
189 
190   // X/theta coordinate
191   axisScale (boundingRectGraphMin.x(),
192              boundingRectGraphMax.x(),
193              (modelCoords.coordScaleXTheta() == COORD_SCALE_LINEAR),
194              start,
195              stop,
196              step,
197              count);
198 
199   modelGridDisplay.setDisableX (GRID_COORD_DISABLE_COUNT);
200   modelGridDisplay.setCountX (unsigned (count));
201   modelGridDisplay.setStartX (start);
202   modelGridDisplay.setStepX (step);
203   modelGridDisplay.setStopX (stop);
204 
205   // Y/radius coordinate
206   axisScale (boundingRectGraphMin.y(),
207              boundingRectGraphMax.y(),
208              (modelCoords.coordScaleYRadius() == COORD_SCALE_LINEAR),
209              start,
210              stop,
211              step,
212              count);
213 
214   modelGridDisplay.setDisableY (GRID_COORD_DISABLE_COUNT);
215   modelGridDisplay.setCountY (unsigned (count));
216   modelGridDisplay.setStartY (start);
217   modelGridDisplay.setStepY (step);
218   modelGridDisplay.setStopY (stop);
219 
220   modelGridDisplay.setStable (true);
221 
222   return modelGridDisplay;
223 }
224 
initializeWithWidePolarCoverage(const QPointF & boundingRectGraphMin,const QPointF & boundingRectGraphMax,const DocumentModelCoords & modelCoords,const Transformation & transformation,const QSize & imageSize) const225 DocumentModelGridDisplay GridInitializer::initializeWithWidePolarCoverage (const QPointF &boundingRectGraphMin,
226                                                                            const QPointF &boundingRectGraphMax,
227                                                                            const DocumentModelCoords &modelCoords,
228                                                                            const Transformation &transformation,
229                                                                            const QSize &imageSize) const
230 {
231   LOG4CPP_INFO_S ((*mainCat)) << "GridInitializer::initializeWithWidePolarCoverage";
232 
233   DocumentModelGridDisplay modelGridDisplay = initializeWithNarrowCoverage (boundingRectGraphMin,
234                                                                             boundingRectGraphMax,
235                                                                             modelCoords);
236 
237   if (modelCoords.coordsType() == COORDS_TYPE_POLAR) {
238 
239     overridePolarCoordinateSettings (modelCoords,
240                                      transformation,
241                                      modelGridDisplay,
242                                      imageSize);
243   }
244 
245   return modelGridDisplay;
246 }
247 
overridePolarCoordinateSettings(const DocumentModelCoords & modelCoords,const Transformation & transformation,DocumentModelGridDisplay & modelGridDisplay,const QSize & imageSize) const248 void GridInitializer::overridePolarCoordinateSettings (const DocumentModelCoords &modelCoords,
249                                                        const Transformation &transformation,
250                                                        DocumentModelGridDisplay &modelGridDisplay,
251                                                        const QSize &imageSize) const
252 {
253   ENGAUGE_ASSERT (modelCoords.coordsType() == COORDS_TYPE_POLAR);
254 
255   // We make sure the angular range is over the entire circle, which is probably useful
256   // unless the orgin is very close to a corner of the graph, in which case the large range does not hurt anything
257   double startX = 0.0;
258   double stopX = 360.0;
259   double stepX = 30.0;
260   int countX = qFloor (0.5 + (stopX - startX) / stepX);
261   modelGridDisplay.setStartX (startX);
262   modelGridDisplay.setStepX (stepX);
263   modelGridDisplay.setStopX (stopX);
264   modelGridDisplay.setCountX (unsigned (countX));
265 
266   // We extend the range to cover the four corners of the image, since otherwise
267   // areas around at least some graph corners are not covered by the grid lines
268   QPointF posTL, posBL, posTR, posBR;
269   transformation.transformScreenToRawGraph (QPointF (0                 , imageSize.height ()), posTL);
270   transformation.transformScreenToRawGraph (QPointF (0                 , 0                  ), posBL);
271   transformation.transformScreenToRawGraph (QPointF (imageSize.width (), imageSize.height ()), posTR);
272   transformation.transformScreenToRawGraph (QPointF (imageSize.width (), 0                  ), posBR);
273 
274   double radiusTL = qSqrt (posTL.x () * posTL.x () + posTL.y () * posTL.y ());
275   double radiusBL = qSqrt (posBL.x () * posBL.x () + posBL.y () * posBL.y ());
276   double radiusTR = qSqrt (posTR.x () * posTR.x () + posTR.y () * posTR.y ());
277   double radiusBR = qSqrt (posBR.x () * posBR.x () + posBR.y () * posBR.y ());
278 
279   double radius = qMax (qMax (qMax (radiusTL, radiusBL), radiusTR), radiusBR);
280 
281   double startY = (modelCoords.coordScaleYRadius() == COORD_SCALE_LINEAR ?
282                      0.0 :
283                      modelCoords.originRadius());
284   double stopY = radius;
285   double stepY = modelGridDisplay.stepY ();
286   double denominator = (modelCoords.coordScaleYRadius() == COORD_SCALE_LINEAR ?
287                         stepY :
288                         qLn (stepY));
289   int countY = 1;
290   if (qAbs (denominator) > 0) {
291     countY = (modelCoords.coordScaleYRadius() == COORD_SCALE_LINEAR ?
292               qFloor (0.5 + (stopY - startY) / denominator) :
293               qFloor (0.5 + (qLn (stopY) - qLn (startY)) / denominator));
294   }
295 
296   modelGridDisplay.setStartY (startY);
297   modelGridDisplay.setStopY (stopY);
298   modelGridDisplay.setCountY (unsigned (countY));
299 }
300 
roundOffToPower(double arg,int power) const301 double GridInitializer::roundOffToPower(double arg,
302                                         int power) const
303 {
304   double powerOf10 = pow (10.0, power);
305   return powerOf10 * floor (arg / powerOf10 + 0.5);
306 }
307 
valuePower(double value) const308 int GridInitializer::valuePower(double value) const
309 {
310   const int minPower = -30; // MAX_DOUBLE is 10^38
311 
312   double avalue = fabs(value);
313   if (avalue < pow(10.0, minPower)) {
314     return minPower;
315   } else {
316     return qFloor (log10 (avalue));
317   }
318 }
319