Engauge Digitizer  2
 All Classes Functions Variables Typedefs Enumerations Friends Pages
Checker.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 "Checker.h"
8 #include "DocumentModelCoords.h"
9 #include "EngaugeAssert.h"
10 #include "EnumsToQt.h"
11 #include "GridLineFactory.h"
12 #include "Logger.h"
13 #include "mmsubs.h"
14 #include <QDebug>
15 #include <QGraphicsItem>
16 #include <QGraphicsScene>
17 #include <qmath.h>
18 #include <QPen>
19 #include <QTextStream>
20 #include "QtToString.h"
21 #include "Transformation.h"
22 
23 const int NUM_AXES_POINTS_2 = 2;
24 const int NUM_AXES_POINTS_3 = 3;
25 const int NUM_AXES_POINTS_4 = 4;
26 
27 extern const QString DUMMY_CURVE_NAME;
28 
29 // One-pixel wide line (produced by setting width=0) is too small. 5 is big enough to be always noticeable,
30 // but such a thick line obscures the axes points. To keep the axes points visible, we remove portions of
31 // the line nearer to an axes point than the point radius.
32 const int CHECKER_POINTS_WIDTH = 5;
33 
34 Checker::Checker(QGraphicsScene &scene) :
35  m_scene (scene)
36 {
37 }
38 
39 Checker::~Checker()
40 {
41  m_gridLines.clear ();
42 }
43 
44 void Checker::adjustPolarAngleRanges (const DocumentModelCoords &modelCoords,
45  const Transformation &transformation,
46  const QList<Point> &points,
47  double &xMin,
48  double &xMax,
49  double &yMin) const
50 {
51  LOG4CPP_INFO_S ((*mainCat)) << "Checker::adjustPolarAngleRanges transformation=" << transformation;
52 
53  const double UNIT_LENGTH = 1.0;
54 
55  QString path; // For logging
56  if (modelCoords.coordsType() == COORDS_TYPE_POLAR) {
57 
58  // Range minimum is at origin
59  yMin = modelCoords.originRadius();
60 
61  path = QString ("yMin=%1 ").arg (yMin); // For logging
62 
63  // Perform special processing to account for periodicity of polar coordinates. Start with unit vectors
64  // in the directions of the three axis points
65  double angle0 = points.at(0).posGraph().x();
66  double angle1 = points.at(1).posGraph().x();
67  double angle2 = points.at(2).posGraph().x();
68  QPointF pos0 = transformation.cartesianFromCartesianOrPolar(modelCoords,
69  QPointF (angle0, UNIT_LENGTH));
70  QPointF pos1 = transformation.cartesianFromCartesianOrPolar(modelCoords,
71  QPointF (angle1, UNIT_LENGTH));
72  QPointF pos2 = transformation.cartesianFromCartesianOrPolar(modelCoords,
73  QPointF (angle2, UNIT_LENGTH));
74 
75  // Identify the axis point that is more in the center of the other two axis points. The arc is then drawn
76  // from one of the other two points to the other. Center point has smaller angles with the other points
77  double sumAngle0 = angleBetweenVectors(pos0, pos1) + angleBetweenVectors(pos0, pos2);
78  double sumAngle1 = angleBetweenVectors(pos1, pos0) + angleBetweenVectors(pos1, pos2);
79  double sumAngle2 = angleBetweenVectors(pos2, pos0) + angleBetweenVectors(pos2, pos1);
80  if ((sumAngle0 <= sumAngle1) && (sumAngle0 <= sumAngle2)) {
81 
82  // Point 0 is in the middle. Either or neither of points 1 and 2 may be along point 0
83  if ((angleFromVectorToVector (pos0, pos1) < 0) ||
84  (angleFromVectorToVector (pos0, pos2) > 0)) {
85  path += QString ("from 1=%1 through 0 to 2=%2").arg (angle1).arg (angle2);
86  xMin = angle1;
87  xMax = angle2;
88  } else {
89  path += QString ("from 2=%1 through 0 to 1=%2").arg (angle2).arg (angle1);
90  xMin = angle2;
91  xMax = angle1;
92  }
93  } else if ((sumAngle1 <= sumAngle0) && (sumAngle1 <= sumAngle2)) {
94 
95  // Point 1 is in the middle. Either or neither of points 0 and 2 may be along point 1
96  if ((angleFromVectorToVector (pos1, pos0) < 0) ||
97  (angleFromVectorToVector (pos1, pos2) > 0)) {
98  path += QString ("from 0=%1 through 1 to 2=%2").arg (angle0).arg (angle2);
99  xMin = angle0;
100  xMax = angle2;
101  } else {
102  path += QString ("from 2=%1 through 1 to 0=%2").arg (angle2).arg (angle0);
103  xMin = angle2;
104  xMax = angle0;
105  }
106  } else {
107 
108  // Point 2 is in the middle. Either or neither of points 0 and 1 may be along point 2
109  if ((angleFromVectorToVector (pos2, pos0) < 0) ||
110  (angleFromVectorToVector (pos2, pos1) > 0)) {
111  path += QString ("from 0=%1 through 2 to 1=%2").arg (angle0).arg (angle1);
112  xMin = angle0;
113  xMax = angle1;
114  } else {
115  path += QString ("from 1=%1 through 2 to 0=%2").arg (angle1).arg (angle0);
116  xMin = angle1;
117  xMax = angle0;
118  }
119  }
120 
121  // Make sure theta is increasing
122  while (xMax < xMin) {
123 
124  double thetaPeriod = modelCoords.thetaPeriod();
125 
126  path += QString (" xMax+=%1").arg (thetaPeriod);
127  xMax += thetaPeriod;
128 
129  }
130  }
131 
132  LOG4CPP_INFO_S ((*mainCat)) << "Checker::adjustPolarAngleRanges path=(" << path.toLatin1().data() << ")";
133 }
134 
135 void Checker::prepareForDisplay (const QPolygonF &polygon,
136  int pointRadius,
137  const DocumentModelAxesChecker &modelAxesChecker,
138  const DocumentModelCoords &modelCoords,
139  DocumentAxesPointsRequired documentAxesPointsRequired)
140 {
141  LOG4CPP_INFO_S ((*mainCat)) << "Checker::prepareForDisplay";
142 
143  ENGAUGE_ASSERT ((polygon.count () == NUM_AXES_POINTS_2) ||
144  (polygon.count () == NUM_AXES_POINTS_3) ||
145  (polygon.count () == NUM_AXES_POINTS_4));
146 
147  // Convert pixel coordinates in QPointF to screen and graph coordinates in Point using
148  // identity transformation, so this routine can reuse computations provided by Transformation
149  QList<Point> points;
150  QPolygonF::const_iterator itr;
151  for (itr = polygon.begin (); itr != polygon.end (); itr++) {
152 
153  const QPointF &pF = *itr;
154 
155  Point p (DUMMY_CURVE_NAME,
156  pF,
157  pF,
158  false);
159  points.push_back (p);
160  }
161 
162  // Screen and graph coordinates are treated as the same, so identity transform is used
163  Transformation transformIdentity;
164  transformIdentity.identity();
165  prepareForDisplay (points,
166  pointRadius,
167  modelAxesChecker,
168  modelCoords,
169  transformIdentity,
170  documentAxesPointsRequired);
171 }
172 
173 void Checker::prepareForDisplay (const QList<Point> &points,
174  int pointRadius,
175  const DocumentModelAxesChecker &modelAxesChecker,
176  const DocumentModelCoords &modelCoords,
177  const Transformation &transformation,
178  DocumentAxesPointsRequired documentAxesPointsRequired)
179 {
180  LOG4CPP_INFO_S ((*mainCat)) << "Checker::prepareForDisplay "
181  << " transformation=" << transformation;
182 
183  ENGAUGE_ASSERT ((points.count () == NUM_AXES_POINTS_2) ||
184  (points.count () == NUM_AXES_POINTS_3) ||
185  (points.count () == NUM_AXES_POINTS_4));
186 
187  // Remove previous lines
188  m_gridLines.clear ();
189 
190  bool fourPoints = (documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_4);
191 
192  // Get the min and max of x and y. We initialize yTo to prevent compiler warning
193  double xFrom = 0, xTo = 0, yFrom = 0, yTo = 0;
194  int i;
195  bool firstX = true;
196  bool firstY = true;
197  for (i = 0; i < points.count(); i++) {
198  if (!fourPoints || (points.at(i).isXOnly() && fourPoints)) {
199 
200  // X coordinate is defined
201  if (firstX) {
202  xFrom = points.at(i).posGraph().x();
203  xTo = points.at(i).posGraph().x();
204  firstX = false;
205  } else {
206  xFrom = qMin (xFrom, points.at(i).posGraph().x());
207  xTo = qMax (xTo , points.at(i).posGraph().x());
208  }
209  }
210 
211  if (!fourPoints || (!points.at(i).isXOnly() && fourPoints)) {
212 
213  // Y coordinate is defined
214  if (firstY) {
215  yFrom = points.at(i).posGraph().y();
216  yTo = points.at(i).posGraph().y();
217  firstY = false;
218  } else {
219  yFrom = qMin (yFrom, points.at(i).posGraph().y());
220  yTo = qMax (yTo , points.at(i).posGraph().y());
221  }
222  }
223  }
224 
225  // Min and max of angles needs special processing since periodicity introduces some ambiguity. This is a noop for rectangular coordinates
226  // and for polar coordinates when periodicity is not an issue
227  adjustPolarAngleRanges (modelCoords,
228  transformation,
229  points,
230  xFrom,
231  xTo,
232  yFrom);
233 
234  // Draw the bounding box as four sides. In polar plots the bottom side is zero-length, with pie shape resulting
235  GridLineFactory factory (m_scene,
236  pointRadius,
237  points,
238  modelCoords);
239  m_gridLines.add (factory.createGridLine (xFrom, yFrom, xFrom, yTo , transformation));
240  m_gridLines.add (factory.createGridLine (xFrom, yTo , xTo , yTo , transformation));
241  m_gridLines.add (factory.createGridLine (xTo , yTo , xTo , yFrom, transformation));
242  m_gridLines.add (factory.createGridLine (xTo , yFrom, xFrom, yFrom, transformation));
243 
244  updateModelAxesChecker (modelAxesChecker);
245 }
246 
247 void Checker::setVisible (bool visible)
248 {
249  m_gridLines.setVisible (visible);
250 }
251 
253 {
254  QColor color = ColorPaletteToQColor (modelAxesChecker.lineColor());
255  QPen pen (QBrush (color), CHECKER_POINTS_WIDTH);
256 
257  m_gridLines.setPen (pen);
258 }
Factory class for generating the points, composed of QGraphicsItem objects, along a GridLine...
static QPointF cartesianFromCartesianOrPolar(const DocumentModelCoords &modelCoords, const QPointF &posGraphIn)
Output cartesian coordinates from input cartesian or polar coordinates. This is static for easier use...
void clear()
Deallocate and remove all grid lines.
Definition: GridLines.cpp:24
void setPen(const QPen &pen)
Set the pen style of each grid line.
Definition: GridLines.cpp:34
virtual void updateModelAxesChecker(const DocumentModelAxesChecker &modelAxesChecker)
Apply the new DocumentModelAxesChecker, to the points already associated with this object...
Definition: Checker.cpp:252
double originRadius() const
Get method for origin radius in polar mode.
ColorPalette lineColor() const
Get method for line color.
Class that represents one digitized point. The screen-to-graph coordinate transformation is always ex...
Definition: Point.h:25
void prepareForDisplay(const QPolygonF &polygon, int pointRadius, const DocumentModelAxesChecker &modelAxesChecker, const DocumentModelCoords &modelCoords, DocumentAxesPointsRequired documentAxesPointsRequired)
Create the polygon from current information, including pixel coordinates, just prior to display...
Definition: Checker.cpp:135
double thetaPeriod() const
Return the period of the theta value for polar coordinates, consistent with CoordThetaUnits.
Affine transformation between screen and graph coordinates, based on digitized axis points...
CoordsType coordsType() const
Get method for coordinates type.
Model for DlgSettingsCoords and CmdSettingsCoords.
void setVisible(bool visible)
Make all grid lines visible or hidden.
Definition: GridLines.cpp:41
Model for DlgSettingsAxesChecker and CmdSettingsAxesChecker.
void add(GridLine *gridLine)
Add specified grid line. Ownership of all allocated QGraphicsItems is passed to new GridLine...
Definition: GridLines.cpp:19
Checker(QGraphicsScene &scene)
Single constructor for DlgSettingsAxesChecker, which does not have an explicit transformation. The identity transformation is assumed.
Definition: Checker.cpp:34
void identity()
Identity transformation.
void setVisible(bool visible)
Show/hide this axes checker.
Definition: Checker.cpp:247
GridLine * createGridLine(double xFrom, double yFrom, double xTo, double yTo, const Transformation &transformation)
Create grid line, either along constant X/theta or constant Y/radius side.