Engauge Digitizer  2
Checker.cpp
Go to the documentation of this file.
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 
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 
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
const int CHECKER_POINTS_WIDTH
Definition: Checker.cpp:32
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
const int NUM_AXES_POINTS_4
Definition: Checker.cpp:25
double thetaPeriod() const
Return the period of the theta value for polar coordinates, consistent with CoordThetaUnits.
#define LOG4CPP_INFO_S(logger)
Definition: convenience.h:18
virtual ~Checker()
Definition: Checker.cpp:39
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
Affine transformation between screen and graph coordinates, based on digitized axis points...
const int NUM_AXES_POINTS_2
Definition: Checker.cpp:23
QColor ColorPaletteToQColor(ColorPalette color)
Definition: EnumsToQt.cpp:15
ColorPalette lineColor() const
Get method for line color.
const QString DUMMY_CURVE_NAME
double angleBetweenVectors(const QPointF &v1, const QPointF &v2)
Angle between two vectors. Direction is unimportant, so result is between 0 to pi radians...
Definition: mmsubs.cpp:15
Model for DlgSettingsCoords and CmdSettingsCoords.
void setVisible(bool visible)
Make all grid lines visible or hidden.
Definition: GridLines.cpp:41
const int NUM_AXES_POINTS_3
Definition: Checker.cpp:24
double angleFromVectorToVector(const QPointF &vFrom, const QPointF &vTo)
Angle between two vectors. Direction is positive when rotation is about +z vector, so result is betwen -pi to pi radians.
Definition: mmsubs.cpp:32
Model for DlgSettingsAxesChecker and CmdSettingsAxesChecker.
log4cpp::Category * mainCat
Definition: Logger.cpp:14
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: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
double originRadius() const
Get method for origin radius in polar mode.
DocumentAxesPointsRequired
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.
#define ENGAUGE_ASSERT(cond)
Drop in replacement for Q_ASSERT if defined(QT_NO_DEBUG) && !defined(QT_FORCE_ASSERTS) define ENGAUGE...
Definition: EngaugeAssert.h:20