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 "CallbackAxisPointsAbstract.h"
8 #include "EngaugeAssert.h"
9 #include "Logger.h"
10 #include "Point.h"
11 #include <qmath.h>
12 #include "QtToString.h"
13 #include "Transformation.h"
14 
15 // Epsilon test values
16 const double ONE_PIXEL = 1.0; // Screen coordinates
17 const double ZERO_EPSILON = 0.0; // Graph coordinates
18 
CallbackAxisPointsAbstract(const DocumentModelCoords & modelCoords,DocumentAxesPointsRequired documentAxesPointsRequired)19 CallbackAxisPointsAbstract::CallbackAxisPointsAbstract(const DocumentModelCoords &modelCoords,
20                                                        DocumentAxesPointsRequired documentAxesPointsRequired) :
21   m_modelCoords (modelCoords),
22   m_isError (false),
23   m_documentAxesPointsRequired (documentAxesPointsRequired)
24 {
25 }
26 
CallbackAxisPointsAbstract(const DocumentModelCoords & modelCoords,const QString pointIdentifierOverride,const QPointF & posScreenOverride,const QPointF & posGraphOverride,DocumentAxesPointsRequired documentAxesPointsRequired)27 CallbackAxisPointsAbstract::CallbackAxisPointsAbstract(const DocumentModelCoords &modelCoords,
28                                                        const QString pointIdentifierOverride,
29                                                        const QPointF &posScreenOverride,
30                                                        const QPointF &posGraphOverride,
31                                                        DocumentAxesPointsRequired documentAxesPointsRequired) :
32   m_modelCoords (modelCoords),
33   m_pointIdentifierOverride (pointIdentifierOverride),
34   m_posScreenOverride (posScreenOverride),
35   m_posGraphOverride (posGraphOverride),
36   m_isError (false),
37   m_documentAxesPointsRequired (documentAxesPointsRequired)
38 {
39 }
40 
anyPointsRepeatPair(const CoordPairVector & vector,double epsilon) const41 bool CallbackAxisPointsAbstract::anyPointsRepeatPair (const CoordPairVector &vector,
42                                                       double epsilon) const
43 {
44   for (int pointLeft = 0; pointLeft < vector.count(); pointLeft++) {
45     for (int pointRight = pointLeft + 1; pointRight < vector.count(); pointRight++) {
46 
47       if (qAbs (vector.at(pointLeft).x() - vector.at(pointRight).x()) <= epsilon &&
48           qAbs (vector.at(pointLeft).y() - vector.at(pointRight).y()) <= epsilon) {
49 
50         // Points pointLeft and pointRight repeat each other, which means matrix cannot be inverted
51         return true;
52       }
53     }
54   }
55 
56   // No columns repeat
57   return false;
58 }
59 
anyPointsRepeatSingle(const CoordSingleVector & vector,double epsilon) const60 bool CallbackAxisPointsAbstract::anyPointsRepeatSingle (const CoordSingleVector &vector,
61                                                         double epsilon) const
62 {
63   for (int pointLeft = 0; pointLeft < vector.count(); pointLeft++) {
64     for (int pointRight = pointLeft + 1; pointRight < vector.count(); pointRight++) {
65 
66       if (qAbs (vector.at(pointLeft) - vector.at(pointRight)) <= epsilon) {
67 
68         // Points pointLeft and pointRight repeat each other, which means matrix cannot be inverted
69         return true;
70       }
71     }
72   }
73 
74   // No columns repeat
75   return false;
76 }
77 
callback(const QString &,const Point & point)78 CallbackSearchReturn CallbackAxisPointsAbstract::callback (const QString & /* curveName */,
79                                                            const Point &point)
80 {
81   QPointF posScreen = point.posScreen ();
82   QPointF posGraph = point.posGraph ();
83 
84   if (m_pointIdentifierOverride == point.identifier ()) {
85 
86     // Override the old point coordinates with its new (if all tests are passed) coordinates
87     posScreen = m_posScreenOverride;
88     posGraph = m_posGraphOverride;
89   }
90 
91   // Try to compute transform
92   if (m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_2) {
93     return callbackRequire2AxisPoints (posScreen,
94                                        posGraph);
95   } else if (m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_3) {
96     return callbackRequire3AxisPoints (posScreen,
97                                        posGraph);
98   } else {
99     return callbackRequire4AxisPoints (point.isXOnly(),
100                                        posScreen,
101                                        posGraph);
102   }
103 }
104 
callbackRequire2AxisPoints(const QPointF & posScreen,const QPointF & posGraph)105 CallbackSearchReturn CallbackAxisPointsAbstract::callbackRequire2AxisPoints (const QPointF &posScreen,
106                                                                              const QPointF &posGraph)
107 {
108   CallbackSearchReturn rtn = CALLBACK_SEARCH_RETURN_CONTINUE;
109 
110   // Update range variables. The same nonzero length value is stored in every x and y coordinate of every axis point
111   m_xGraphLow = 0;
112   m_yGraphLow = 0;
113   m_xGraphHigh = posGraph.x();
114   m_yGraphHigh = posGraph.x();
115 
116   int numberPoints = m_screenInputs.count();
117   if (numberPoints < 2) {
118 
119     // Append new point
120     m_screenInputs.push_back (posScreen);
121     m_graphOutputs.push_back (posGraph);
122     numberPoints = m_screenInputs.count();
123 
124     if (numberPoints == 2) {
125       loadTransforms2 ();
126     }
127 
128     // Error checking
129     if (anyPointsRepeatPair (m_screenInputs,
130                              ONE_PIXEL)) {
131 
132       m_isError = true;
133       m_errorMessage = QObject::tr ("New axis point cannot be at the same screen position as an existing axis point");
134       rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
135 
136     }
137   }
138 
139   if (m_screenInputs.count() > 1) {
140 
141     // There are enough axis points so quit
142     rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
143 
144   }
145 
146   return rtn;
147 }
148 
callbackRequire3AxisPoints(const QPointF & posScreen,const QPointF & posGraph)149 CallbackSearchReturn CallbackAxisPointsAbstract::callbackRequire3AxisPoints (const QPointF &posScreen,
150                                                                              const QPointF &posGraph)
151 {
152   CallbackSearchReturn rtn = CALLBACK_SEARCH_RETURN_CONTINUE;
153 
154   // Update range variables
155   int numberPoints = m_screenInputs.count();
156   if ((numberPoints == 0) || (posGraph.x () < m_xGraphLow)) { m_xGraphLow = posGraph.x (); }
157   if ((numberPoints == 0) || (posGraph.y () < m_yGraphLow)) { m_yGraphLow = posGraph.y (); }
158   if ((numberPoints == 0) || (posGraph.x () > m_xGraphHigh)) { m_xGraphHigh = posGraph.x (); }
159   if ((numberPoints == 0) || (posGraph.y () > m_yGraphHigh)) { m_yGraphHigh = posGraph.y (); }
160 
161   if (numberPoints < 3) {
162 
163     // Append new point
164     m_screenInputs.push_back (posScreen);
165     m_graphOutputs.push_back (posGraph);
166     numberPoints = m_screenInputs.count();
167 
168     if (numberPoints == 3) {
169       loadTransforms3 ();
170     }
171 
172     // Error checking
173     if ((m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_2 ||
174          m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_3) &&
175         anyPointsRepeatPair (m_screenInputs, ONE_PIXEL)) {
176 
177       m_isError = true;
178       m_errorMessage = QObject::tr ("New axis point cannot be at the same screen position as an existing axis point");
179       rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
180 
181     } else if ((m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_2 ||
182                 m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_3) &&
183                anyPointsRepeatPair (m_graphOutputs, ZERO_EPSILON)) {
184 
185       m_isError = true;
186       m_errorMessage = QObject::tr ("New axis point cannot have the same graph coordinates as an existing axis point");
187       rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
188 
189     } else if ((numberPoints == 3) && threePointsAreCollinear (m_screenInputsTransform,
190                                                                COORD_IS_LINEAR,
191                                                                COORD_IS_LINEAR)) {
192 
193       m_isError = true;
194       m_errorMessage = QObject::tr ("No more than two axis points can lie along the same line on the screen");
195       rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
196 
197     } else if ((numberPoints == 3) && threePointsAreCollinear (m_graphOutputsTransform,
198                                                                logXGraph (),
199                                                                logYGraph ())) {
200 
201       m_isError = true;
202       m_errorMessage = QObject::tr ("No more than two axis points can lie along the same line in graph coordinates");
203       rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
204 
205     }
206   }
207 
208   if (m_screenInputs.count() > 2) {
209 
210     // There are enough axis points so quit
211     rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
212 
213   }
214 
215   return rtn;
216 }
217 
callbackRequire4AxisPoints(bool isXOnly,const QPointF & posScreen,const QPointF & posGraph)218 CallbackSearchReturn CallbackAxisPointsAbstract::callbackRequire4AxisPoints (bool isXOnly,
219                                                                              const QPointF &posScreen,
220                                                                              const QPointF &posGraph)
221 {
222   CallbackSearchReturn rtn = CALLBACK_SEARCH_RETURN_CONTINUE;
223 
224   // Update range variables
225   int numberPoints = m_screenInputsX.count() + m_screenInputsY.count();
226   if ((numberPoints == 0) || (posGraph.x () < m_xGraphLow)) { m_xGraphLow = posGraph.x (); }
227   if ((numberPoints == 0) || (posGraph.y () < m_yGraphLow)) { m_yGraphLow = posGraph.y (); }
228   if ((numberPoints == 0) || (posGraph.x () > m_xGraphHigh)) { m_xGraphHigh = posGraph.x (); }
229   if ((numberPoints == 0) || (posGraph.y () > m_yGraphHigh)) { m_yGraphHigh = posGraph.y (); }
230 
231   if (numberPoints < 4) {
232 
233     // Append the new point
234     if (isXOnly) {
235 
236       m_screenInputsX.push_back (posScreen);
237       m_graphOutputsX.push_back (posGraph.x());
238 
239     } else {
240 
241       m_screenInputsY.push_back (posScreen);
242       m_graphOutputsY.push_back (posGraph.y());
243 
244     }
245 
246     numberPoints = m_screenInputsX.count() + m_screenInputsY.count();
247     if (numberPoints == 4) {
248       loadTransforms4 ();
249     }
250   }
251 
252   if (m_screenInputsX.count() > 2) {
253 
254     m_isError = true;
255     m_errorMessage = QObject::tr ("Too many x axis points. There should only be two");
256     rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
257 
258   } else if (m_screenInputsY.count() > 2) {
259 
260     m_isError = true;
261     m_errorMessage = QObject::tr ("Too many y axis points. There should only be two");
262     rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
263 
264   } else {
265 
266     if ((m_screenInputsX.count() == 2) &&
267         (m_screenInputsY.count() == 2)) {
268 
269       // Done, although an error may intrude
270       rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
271     }
272 
273     // Error checking
274     if (anyPointsRepeatPair (m_screenInputsX,
275                              ONE_PIXEL) ||
276         anyPointsRepeatPair (m_screenInputsY,
277                              ONE_PIXEL)) {
278 
279       m_isError = true;
280       m_errorMessage = QObject::tr ("New axis point cannot be at the same screen position as an existing axis point");
281       rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
282 
283     } else if (anyPointsRepeatSingle (m_graphOutputsX,
284                                       ZERO_EPSILON) ||
285                anyPointsRepeatSingle (m_graphOutputsY,
286                                       ZERO_EPSILON)) {
287 
288       m_isError = true;
289       m_errorMessage = QObject::tr ("New axis point cannot have the same graph coordinates as an existing axis point");
290       rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
291 
292     } else if ((numberPoints == 4) && threePointsAreCollinear (m_screenInputsTransform,
293                                                                COORD_IS_LINEAR,
294                                                                COORD_IS_LINEAR)) {
295 
296       m_isError = true;
297       m_errorMessage = QObject::tr ("No more than two axis points can lie along the same line on the screen");
298       rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
299 
300     } else if ((numberPoints == 4) && threePointsAreCollinear (m_graphOutputsTransform,
301                                                                logXGraph (),
302                                                                logYGraph ())) {
303 
304       m_isError = true;
305       m_errorMessage = QObject::tr ("No more than two axis points can lie along the same line in graph coordinates");
306       rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
307 
308     }
309   }
310 
311   return rtn;
312 }
313 
documentAxesPointsRequired() const314 DocumentAxesPointsRequired CallbackAxisPointsAbstract::documentAxesPointsRequired() const
315 {
316   return m_documentAxesPointsRequired;
317 }
318 
loadTransforms2()319 void CallbackAxisPointsAbstract::loadTransforms2 ()
320 {
321   // To get a third point from two existing points we compute the vector between the first 2 points and then take
322   // the cross product with the out-of-plane unit vector to get the perpendicular vector, and the endpoint of that
323   // is used as the third point. This implicitly assumes that the graph-to-screen coordinates scaling is the
324   // same in both directions. The advantage of this approach is that no assumptions are made about the inputs
325 
326   double d0To1ScreenX = m_screenInputs.at (1).x () - m_screenInputs.at (0).x ();
327   double d0To1ScreenY = m_screenInputs.at (1).y () - m_screenInputs.at (0).y ();
328   double d0To1ScreenZ = 0;
329   double d0To1GraphX = m_graphOutputs.at (1).x () - m_graphOutputs.at (0).x ();
330   double d0To1GraphY = m_graphOutputs.at (1).y () - m_graphOutputs.at (0).y ();
331   double d0To1GraphZ = 0;
332 
333   double unitNormalX = 0;
334   double unitNormalY = 0;
335   double unitNormalZ = 1;
336 
337   double d0To2ScreenX = unitNormalY * d0To1ScreenZ - unitNormalZ * d0To1ScreenY;
338   double d0To2ScreenY = unitNormalZ * d0To1ScreenX - unitNormalX * d0To1ScreenZ;
339   double d0To2GraphX = unitNormalY * d0To1GraphZ - unitNormalZ * d0To1GraphY;
340   double d0To2GraphY = unitNormalZ * d0To1GraphX - unitNormalX * d0To1GraphZ;
341 
342   // Hack since +Y for screen coordinates is down but up for graph coordinates. Users expect +Y to be up
343   // so we rotate screen delta by 180 degrees
344   const double FLIP_Y_SCREEN = -1.0;
345 
346   double screenX2 = m_screenInputs.at (0).x () + FLIP_Y_SCREEN * d0To2ScreenX;
347   double screenY2 = m_screenInputs.at (0).y () + FLIP_Y_SCREEN * d0To2ScreenY;
348   double graphX2 = m_graphOutputs.at (0).x () + d0To2GraphX;
349   double graphY2 = m_graphOutputs.at (0).y () + d0To2GraphY;
350 
351   // Screen coordinates
352   m_screenInputsTransform = QTransform (m_screenInputs.at(0).x(), m_screenInputs.at(1).x(), screenX2,
353                                         m_screenInputs.at(0).y(), m_screenInputs.at(1).y(), screenY2,
354                                         1.0                     , 1.0                     , 1.0     );
355 
356   // Graph coordinates
357   m_graphOutputsTransform = QTransform (m_graphOutputs.at(0).x(), m_graphOutputs.at(1).x(), graphX2,
358                                         m_graphOutputs.at(0).y(), m_graphOutputs.at(1).y(), graphY2,
359                                         1.0                     , 1.0               , 1.0          );
360 }
361 
loadTransforms3()362 void CallbackAxisPointsAbstract::loadTransforms3 ()
363 {
364   // Screen coordinates
365   m_screenInputsTransform = QTransform (m_screenInputs.at(0).x(), m_screenInputs.at(1).x(), m_screenInputs.at(2).x(),
366                                         m_screenInputs.at(0).y(), m_screenInputs.at(1).y(), m_screenInputs.at(2).y(),
367                                         1.0                     , 1.0                     , 1.0                     );
368 
369   // Graph coordinates
370   m_graphOutputsTransform = QTransform (m_graphOutputs.at(0).x(), m_graphOutputs.at(1).x(), m_graphOutputs.at(2).x(),
371                                         m_graphOutputs.at(0).y(), m_graphOutputs.at(1).y(), m_graphOutputs.at(2).y(),
372                                         1.0                     , 1.0                     , 1.0                     );
373 }
374 
loadTransforms4()375 void CallbackAxisPointsAbstract::loadTransforms4 ()
376 {
377   double x1Screen = m_screenInputsX.at(0).x();
378   double y1Screen = m_screenInputsX.at(0).y();
379   double x2Screen = m_screenInputsX.at(1).x();
380   double y2Screen = m_screenInputsX.at(1).y();
381   double x3Screen = m_screenInputsY.at(0).x();
382   double y3Screen = m_screenInputsY.at(0).y();
383   double x4Screen = m_screenInputsY.at(1).x();
384   double y4Screen = m_screenInputsY.at(1).y();
385 
386   // Each of the four axes points has only one coordinate
387   double x1Graph = m_graphOutputsX.at(0);
388   double x2Graph = m_graphOutputsX.at(1);
389   double y3Graph = m_graphOutputsY.at(0);
390   double y4Graph = m_graphOutputsY.at(1);
391 
392   // Intersect the two lines of the two axes. The lines are defined parametrically for the screen coordinates, with
393   // points 1 and 2 on the x axis and points 3 and 4 on the y axis, as:
394   //   x = (1 - sx) * x1 + sx * x2
395   //   y = (1 - sx) * y1 + sx * y2
396   //   x = (1 - sy) * x3 + sy * x4
397   //   y = (1 - sy) * y3 + sy * y4
398   // Intersection of the 2 lines is at (x,y). Solving for sx and sy using Cramer's rule where Ax=b
399   // (x1 - x3)   (x1 - x2     x4 - x3) (sx)
400   // (y1 - y3) = (y1 - y2     y4 - y3) (sy)
401   double A00 = x1Screen - x2Screen;
402   double A01 = x4Screen - x3Screen;
403   double A10 = y1Screen - y2Screen;
404   double A11 = y4Screen - y3Screen;
405   double b0 = x1Screen - x3Screen;
406   double b1 = y1Screen - y3Screen;
407   double numeratorx = (b0 * A11 - A01 * b1);
408   double numeratory = (A00 * b1 - b0 * A10);
409   double denominator = (A00 * A11 - A01 * A10);
410   double sx = numeratorx / denominator;
411   double sy = numeratory / denominator;
412 
413   // Intersection point. For the graph coordinates, the initial implementation assumes cartesian coordinates
414   double xIntScreen = (1.0 - sx) * x1Screen + sx * x2Screen;
415   double yIntScreen = (1.0 - sy) * y3Screen + sy * y4Screen;
416   double xIntGraph, yIntGraph;
417   if (m_modelCoords.coordScaleXTheta() == COORD_SCALE_LINEAR) {
418     xIntGraph = (1.0 - sx) * x1Graph + sx * x2Graph;
419   } else {
420     xIntGraph = qExp ((1.0 - sx) * qLn (x1Graph) + sx * qLn (x2Graph));
421   }
422   if (m_modelCoords.coordScaleYRadius() == COORD_SCALE_LINEAR) {
423     yIntGraph = (1.0 - sy) * y3Graph + sy * y4Graph;
424   } else {
425     yIntGraph = qExp ((1.0 - sy) * qLn (y3Graph) + sy * qLn (y4Graph));
426   }
427 
428   // Distances of 4 axis points from interception
429   double distance1 = qSqrt ((x1Screen - xIntScreen) * (x1Screen - xIntScreen) +
430                             (y1Screen - yIntScreen) * (y1Screen - yIntScreen));
431   double distance2 = qSqrt ((x2Screen - xIntScreen) * (x2Screen - xIntScreen) +
432                             (y2Screen - yIntScreen) * (y2Screen - yIntScreen));
433   double distance3 = qSqrt ((x3Screen - xIntScreen) * (x3Screen - xIntScreen) +
434                             (y3Screen - yIntScreen) * (y3Screen - yIntScreen));
435   double distance4 = qSqrt ((x4Screen - xIntScreen) * (x4Screen - xIntScreen) +
436                             (y4Screen - yIntScreen) * (y4Screen - yIntScreen));
437 
438   // We now have too many data points with both x and y coordinates:
439   // (xInt,yInt) (xInt,y3) (xInt,y4) (x1,yInt) (x2,yInt)
440   // so we pick just 3, making sure that those 3 are widely separated
441   // (xInt,yInt) (x axis point furthest from xInt,yInt) (y axis point furthest from xInt,yInt)
442   double xFurthestXAxisScreen, yFurthestXAxisScreen, xFurthestYAxisScreen, yFurthestYAxisScreen;
443   double xFurthestXAxisGraph, yFurthestXAxisGraph, xFurthestYAxisGraph, yFurthestYAxisGraph;
444   if (distance1 < distance2) {
445     xFurthestXAxisScreen = x2Screen;
446     yFurthestXAxisScreen = y2Screen;
447     xFurthestXAxisGraph = x2Graph;
448     yFurthestXAxisGraph = yIntGraph;
449   } else {
450     xFurthestXAxisScreen = x1Screen;
451     yFurthestXAxisScreen = y1Screen;
452     xFurthestXAxisGraph = x1Graph;
453     yFurthestXAxisGraph = yIntGraph;
454   }
455   if (distance3 < distance4) {
456     xFurthestYAxisScreen = x4Screen;
457     yFurthestYAxisScreen = y4Screen;
458     xFurthestYAxisGraph = xIntGraph;
459     yFurthestYAxisGraph = y4Graph;
460   } else {
461     xFurthestYAxisScreen = x3Screen;
462     yFurthestYAxisScreen = y3Screen;
463     xFurthestYAxisGraph = xIntGraph;
464     yFurthestYAxisGraph = y3Graph;
465   }
466 
467   // Screen coordinates
468   m_screenInputsTransform = QTransform (xIntScreen, xFurthestXAxisScreen, xFurthestYAxisScreen,
469                                         yIntScreen, yFurthestXAxisScreen, yFurthestYAxisScreen,
470                                         1.0       , 1.0                 , 1.0                 );
471 
472   // Graph coordinates
473   m_graphOutputsTransform = QTransform (xIntGraph, xFurthestXAxisGraph, xFurthestYAxisGraph,
474                                         yIntGraph, yFurthestXAxisGraph, yFurthestYAxisGraph,
475                                         1.0      , 1.0                , 1.0                );
476 }
477 
logXGraph() const478 CallbackAxisPointsAbstract::LinearOrLog CallbackAxisPointsAbstract::logXGraph () const
479 {
480   return m_modelCoords.coordScaleXTheta() == COORD_SCALE_LOG ? COORD_IS_LOG : COORD_IS_LINEAR;
481 }
482 
logYGraph() const483 CallbackAxisPointsAbstract::LinearOrLog CallbackAxisPointsAbstract::logYGraph () const
484 {
485   return m_modelCoords.coordScaleYRadius() == COORD_SCALE_LOG ? COORD_IS_LOG : COORD_IS_LINEAR;
486 }
487 
matrixGraph() const488 QTransform CallbackAxisPointsAbstract::matrixGraph () const
489 {
490   return m_graphOutputsTransform;
491 }
492 
matrixScreen() const493 QTransform CallbackAxisPointsAbstract::matrixScreen () const
494 {
495   return m_screenInputsTransform;
496 }
497 
numberAxisPoints() const498 unsigned int CallbackAxisPointsAbstract::numberAxisPoints () const
499 {
500   if (m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_2) {
501     return unsigned (m_screenInputs.count());
502   } else if (m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_3) {
503     return unsigned (m_screenInputs.count());
504   } else {
505     return unsigned (m_screenInputsX.count() + m_screenInputsY.count());
506   }
507 }
508 
threePointsAreCollinear(const QTransform & transformIn,LinearOrLog logX,LinearOrLog logY) const509 bool CallbackAxisPointsAbstract::threePointsAreCollinear (const QTransform &transformIn,
510                                                           LinearOrLog logX,
511                                                           LinearOrLog logY) const
512 {
513   double m11 = (logX == COORD_IS_LOG) ? qLn (transformIn.m11()) : transformIn.m11();
514   double m12 = (logX == COORD_IS_LOG) ? qLn (transformIn.m12()) : transformIn.m12();
515   double m13 = (logX == COORD_IS_LOG) ? qLn (transformIn.m13()) : transformIn.m13();
516   double m21 = (logY == COORD_IS_LOG) ? qLn (transformIn.m21()) : transformIn.m21();
517   double m22 = (logY == COORD_IS_LOG) ? qLn (transformIn.m22()) : transformIn.m22();
518   double m23 = (logY == COORD_IS_LOG) ? qLn (transformIn.m23()) : transformIn.m23();
519   double m31 = transformIn.m31();
520   double m32 = transformIn.m32();
521   double m33 = transformIn.m33();
522 
523   QTransform transform (m11, m12, m13,
524                         m21, m22, m23,
525                         m31, m32, m33);
526 
527   return !transform.isInvertible ();
528 }
529