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