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