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