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