Engauge Digitizer  2
GraphicsLinesForCurve.cpp
1 #include "DataKey.h"
2 #include "EngaugeAssert.h"
3 #include "EnumsToQt.h"
4 #include "GraphicsItemType.h"
5 #include "GraphicsLinesForCurve.h"
6 #include "GraphicsPoint.h"
7 #include "GraphicsScene.h"
8 #include "LineStyle.h"
9 #include "Logger.h"
10 #include "Point.h"
11 #include "PointStyle.h"
12 #include <QGraphicsItem>
13 #include <QMap>
14 #include <QPen>
15 #include <QTextStream>
16 #include "QtToString.h"
17 #include "Spline.h"
18 #include "Transformation.h"
19 
20 using namespace std;
21 
22 typedef QMap<double, double> XOrThetaToOrdinal;
23 
25  m_curveName (curveName)
26 {
27  setData (DATA_KEY_GRAPHICS_ITEM_TYPE,
28  GRAPHICS_ITEM_TYPE_LINE);
29  setData (DATA_KEY_IDENTIFIER,
30  QVariant (m_curveName));
31 }
32 
33 GraphicsLinesForCurve::~GraphicsLinesForCurve()
34 {
35  OrdinalToGraphicsPoint::iterator itr;
36  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
37  GraphicsPoint *point = itr.value();
38  delete point;
39  }
40 
41  m_graphicsPoints.clear();
42 }
43 
44 void GraphicsLinesForCurve::addPoint (const QString &pointIdentifier,
45  double ordinal,
46  GraphicsPoint &graphicsPoint)
47 {
48  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::addPoint"
49  << " curve=" << m_curveName.toLatin1().data()
50  << " identifier=" << pointIdentifier.toLatin1().data()
51  << " ordinal=" << ordinal
52  << " pos=" << QPointFToString (graphicsPoint.pos()).toLatin1().data()
53  << " newPointCount=" << (m_graphicsPoints.count() + 1);
54 
55  m_graphicsPoints [ordinal] = &graphicsPoint;
56 }
57 
58 QPainterPath GraphicsLinesForCurve::drawLinesSmooth ()
59 {
60  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::drawLinesSmooth"
61  << " curve=" << m_curveName.toLatin1().data();
62 
63  QPainterPath path;
64 
65  // Prepare spline inputs. Note that the ordinal values may not start at 0
66  vector<double> t;
67  vector<SplinePair> xy;
68  OrdinalToGraphicsPoint::const_iterator itr;
69  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
70 
71  double ordinal = itr.key();
72  const GraphicsPoint *point = itr.value();
73 
74  t.push_back (ordinal);
75  xy.push_back (SplinePair (point->pos ().x(),
76  point->pos ().y()));
77  }
78 
79  // Spline through points
80  Spline spline (t, xy);
81 
82  // Drawing from point i-1 to this point i uses the control points from point i-1
83  int segmentEndingAtPointI = 0;
84 
85  // Create QPainterPath through the points
86  bool isFirst = true;
87  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
88 
89  const GraphicsPoint *point = itr.value();
90 
91  if (isFirst) {
92  isFirst = false;
93  path.moveTo (point->pos());
94  } else {
95 
96  QPointF p1 (spline.p1 (segmentEndingAtPointI).x(),
97  spline.p1 (segmentEndingAtPointI).y());
98  QPointF p2 (spline.p2 (segmentEndingAtPointI).x(),
99  spline.p2 (segmentEndingAtPointI).y());
100 
101  path.cubicTo (p1,
102  p2,
103  point->pos ());
104 
105  ++segmentEndingAtPointI;
106  }
107  }
108 
109  return path;
110 }
111 
112 QPainterPath GraphicsLinesForCurve::drawLinesStraight ()
113 {
114  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::drawLinesStraight"
115  << " curve=" << m_curveName.toLatin1().data();
116 
117  QPainterPath path;
118 
119  // Create QPainterPath through the points
120  bool isFirst = true;
121  OrdinalToGraphicsPoint::const_iterator itr;
122  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
123 
124  const GraphicsPoint *point = itr.value();
125 
126  if (isFirst) {
127  isFirst = false;
128  path.moveTo (point->pos ());
129  } else {
130  path.lineTo (point->pos ());
131  }
132  }
133 
134  return path;
135 }
136 
137 double GraphicsLinesForCurve::identifierToOrdinal (const QString &identifier) const
138 {
139  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::identifierToOrdinal"
140  << " identifier=" << identifier.toLatin1().data();
141 
142  OrdinalToGraphicsPoint::const_iterator itr;
143  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
144 
145  const GraphicsPoint *point = itr.value();
146 
147  if (point->data (DATA_KEY_IDENTIFIER) == identifier) {
148  return itr.key();
149  }
150  }
151 
152  ENGAUGE_ASSERT (false);
153 
154  return 0;
155 }
156 
158 {
159  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::lineMembershipPurge"
160  << " curve=" << m_curveName.toLatin1().data();
161 
162  OrdinalToGraphicsPoint::iterator itr, itrNext;
163  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr = itrNext) {
164 
165  itrNext = itr;
166  ++itrNext;
167 
168  GraphicsPoint *point = *itr;
169 
170  if (!point->wanted ()) {
171 
172  double ordinal = itr.key ();
173 
174  delete point;
175  m_graphicsPoints.remove (ordinal);
176  }
177  }
178 
179  // Apply line style
180  QPen pen;
181  if (lineStyle.paletteColor() == COLOR_PALETTE_TRANSPARENT) {
182 
183  pen = QPen (Qt::NoPen);
184 
185  } else {
186 
187  pen = QPen (QBrush (ColorPaletteToQColor (lineStyle.paletteColor())),
188  lineStyle.width());
189 
190  }
191 
192  setPen (pen);
193 
195 }
196 
198 {
199  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::lineMembershipReset"
200  << " curve=" << m_curveName.toLatin1().data();
201 
202  OrdinalToGraphicsPoint::iterator itr;
203  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
204 
205  GraphicsPoint *point = itr.value();
206 
207  point->reset ();
208  }
209 }
210 
211 bool GraphicsLinesForCurve::needOrdinalRenumbering () const
212 {
213  // Ordinals should be 0, 1, ...
214  bool needRenumbering = false;
215  for (int ordinalKeyWanted = 0; ordinalKeyWanted < m_graphicsPoints.count(); ordinalKeyWanted++) {
216 
217  double ordinalKeyGot = m_graphicsPoints.keys().at (ordinalKeyWanted);
218 
219  // Sanity checks
220  ENGAUGE_ASSERT (ordinalKeyGot != Point::UNDEFINED_ORDINAL ());
221 
222  if (ordinalKeyWanted != ordinalKeyGot) {
223  needRenumbering = true;
224  break;
225  }
226  }
227 
228  return needRenumbering;
229 }
230 
231 void GraphicsLinesForCurve::printStream (QString indentation,
232  QTextStream &str) const
233 {
234  DataKey type = (DataKey) data (DATA_KEY_GRAPHICS_ITEM_TYPE).toInt();
235 
236  str << indentation << "GraphicsLinesForCurve=" << m_curveName
237  << " dataIdentifier=" << data (DATA_KEY_IDENTIFIER).toString().toLatin1().data()
238  << " dataType=" << dataKeyToString (type).toLatin1().data() << "\n";
239 
240  indentation += INDENTATION_DELTA;
241 
242  OrdinalToGraphicsPoint::const_iterator itr;
243  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
244 
245  double ordinalKey = itr.key();
246  const GraphicsPoint *point = itr.value();
247 
248  point->printStream (indentation,
249  str,
250  ordinalKey);
251  }
252 }
253 
255 {
256  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::removePoint"
257  << " point=" << ordinal
258  << " pointCount=" << m_graphicsPoints.count();
259 
260  ENGAUGE_ASSERT (m_graphicsPoints.contains (ordinal));
261  GraphicsPoint *graphicsPoint = m_graphicsPoints [ordinal];
262 
263  m_graphicsPoints.remove (ordinal);
264 
265  delete graphicsPoint;
266 }
267 
269 {
270  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::removeTemporaryPointIfExists";
271 
272  OrdinalToGraphicsPoint::iterator itr;
273  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
274 
275  GraphicsPoint *graphicsPoint = itr.value();
276 
277  m_graphicsPoints.remove (itr.key());
278 
279  delete graphicsPoint;
280 
281  break;
282  }
283 }
284 
285 void GraphicsLinesForCurve::renumberOrdinals ()
286 {
287  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::renumberOrdinals";
288 
289  int ordinalKeyWanted;
290 
291  // Ordinals should be 0, 1, and so on. Assigning a list to QMap::keys has no effect, so the
292  // approach is to copy to a temporary list and then copy back
293  QList<GraphicsPoint*> points;
294  for (ordinalKeyWanted = 0; ordinalKeyWanted < m_graphicsPoints.count(); ordinalKeyWanted++) {
295 
296  GraphicsPoint *graphicsPoint = m_graphicsPoints.values().at (ordinalKeyWanted);
297  points << graphicsPoint;
298  }
299 
300  m_graphicsPoints.clear ();
301 
302  for (ordinalKeyWanted = 0; ordinalKeyWanted < points.count(); ordinalKeyWanted++) {
303 
304  GraphicsPoint *graphicsPoint = points.at (ordinalKeyWanted);
305  m_graphicsPoints [ordinalKeyWanted] = graphicsPoint;
306  }
307 }
308 
310  const PointStyle &pointStyle,
311  const Point &point)
312 {
313  LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsLinesForCurve::updateAfterCommand"
314  << " curve=" << m_curveName.toLatin1().data()
315  << " pointCount=" << m_graphicsPoints.count();
316 
317  GraphicsPoint *graphicsPoint = 0;
318  if (m_graphicsPoints.contains (point.ordinal())) {
319 
320  graphicsPoint = m_graphicsPoints [point.ordinal()];
321 
322  // Due to ordinal renumbering, the coordinates may belong to some other point so we override
323  // them for consistent ordinal-position mapping. Updating the identifier also was added for
324  // better logging (i.e. consistency between Document and GraphicsScene dumps), but happened
325  // to fix a bug with the wrong set of points getting deleted from Cut and Delete
326  graphicsPoint->setPos (point.posScreen());
327  graphicsPoint->setData (DATA_KEY_IDENTIFIER, point.identifier());
328 
329  } else {
330 
331  // Point does not exist in scene so create it
332  graphicsPoint = scene.createPoint (point.identifier (),
333  pointStyle,
334  point.posScreen());
335  m_graphicsPoints [point.ordinal ()] = graphicsPoint;
336 
337  }
338 
339  // Mark point as wanted
340  ENGAUGE_CHECK_PTR (graphicsPoint);
341  graphicsPoint->setWanted ();
342 }
343 
345 {
346  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::updateCurveStyle";
347 
348  OrdinalToGraphicsPoint::const_iterator itr;
349  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
350 
351  GraphicsPoint *point = itr.value();
352  point->updateCurveStyle (curveStyle);
353  }
354 }
355 
357 {
358  // LOG4CPP_INFO_S is below
359 
360  bool needRenumbering = needOrdinalRenumbering ();
361  if (needRenumbering) {
362 
363  renumberOrdinals();
364 
365  }
366 
367  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::updateGraphicsLinesToMatchGraphicsPoints"
368  << " numberPoints=" << m_graphicsPoints.count()
369  << " ordinalRenumbering=" << (needRenumbering ? "true" : "false");
370 
371  if (lineStyle.curveConnectAs() != CONNECT_SKIP_FOR_AXIS_CURVE) {
372 
373  // Draw as either straight or smoothed. The function/relation differences were handled already with ordinals. The
374  // Spline algorithm will crash with fewer than three points so it is only called when there are enough points
375  QPainterPath path;
376  if (lineStyle.curveConnectAs() == CONNECT_AS_FUNCTION_STRAIGHT ||
377  lineStyle.curveConnectAs() == CONNECT_AS_RELATION_STRAIGHT ||
378  m_graphicsPoints.count () < 3) {
379 
380  path = drawLinesStraight ();
381  } else {
382  path = drawLinesSmooth ();
383  }
384 
385  setPath (path);
386  }
387 }
388 
390  const Transformation &transformation)
391 {
392  CurveConnectAs curveConnectAs = lineStyle.curveConnectAs();
393 
394  LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsLinesForCurve::updateGraphicsLinesToMatchGraphicsPoints"
395  << " curve=" << m_curveName.toLatin1().data()
396  << " curveConnectAs=" << curveConnectAsToString(curveConnectAs).toLatin1().data();
397 
398  if (curveConnectAs == CONNECT_AS_FUNCTION_SMOOTH ||
399  curveConnectAs == CONNECT_AS_FUNCTION_STRAIGHT) {
400 
401  // Make sure ordinals are properly ordered
402 
403  // Get a map of x/theta values as keys with point identifiers as the values
404  XOrThetaToOrdinal xOrThetaToOrdinal;
405  OrdinalToGraphicsPoint::iterator itrP;
406  for (itrP = m_graphicsPoints.begin(); itrP != m_graphicsPoints.end(); itrP++) {
407 
408  double ordinal = itrP.key();
409  const GraphicsPoint *point = itrP.value();
410 
411  // Convert screen coordinate to graph coordinates, which gives us x/theta
412  QPointF pointGraph;
413  transformation.transformScreenToRawGraph(point->pos (),
414  pointGraph);
415 
416  xOrThetaToOrdinal [pointGraph.x()] = ordinal;
417  }
418 
419  // Loop through the sorted x/theta values. Since QMap is used, the x/theta keys are sorted
420  OrdinalToGraphicsPoint temporaryList;
421  int ordinalNew = 0;
422  XOrThetaToOrdinal::const_iterator itrX;
423  for (itrX = xOrThetaToOrdinal.begin(); itrX != xOrThetaToOrdinal.end(); itrX++) {
424 
425  double ordinalOld = *itrX;
426  GraphicsPoint *point = m_graphicsPoints [ordinalOld];
427 
428  temporaryList [ordinalNew++] = point;
429  }
430 
431  // Copy from temporary back to original map
432  m_graphicsPoints.clear();
433  for (itrP = temporaryList.begin(); itrP != temporaryList.end(); itrP++) {
434 
435  double ordinal = itrP.key();
436  GraphicsPoint *point = itrP.value();
437 
438  m_graphicsPoints [ordinal] = point;
439  }
440  }
441 }
void transformScreenToRawGraph(const QPointF &coordScreen, QPointF &coordGraph) const
Transform from cartesian pixel screen coordinates to cartesian/polar graph coordinates.
void lineMembershipReset()
Mark points as unwanted. Afterwards, lineMembershipPurge gets called.
unsigned int width() const
Width of line.
Definition: LineStyle.cpp:131
Cubic interpolation given independent and dependent value vectors.
Definition: Spline.h:15
void setWanted()
Mark point as wanted. Marking as unwanted is done by the reset function.
void updateCurveStyle(const CurveStyle &curveStyle)
Update the curve style for this curve.
double identifierToOrdinal(const QString &identifier) const
Get ordinal for specified identifier.
void printStream(QString indentation, QTextStream &str, double ordinalKey) const
Debugging method that supports print method of this class and printStream method of some other class(...
Class that represents one digitized point. The screen-to-graph coordinate transformation is always ex...
Definition: Point.h:17
void setData(int key, const QVariant &data)
Proxy method for QGraphicsItem::setData.
QPointF posScreen() const
Accessor for screen position.
Definition: Point.cpp:344
void setPos(const QPointF pos)
Update the position.
bool wanted() const
Identify point as wanted//unwanted.
GraphicsLinesForCurve(const QString &curveName)
Single constructor.
void updateGraphicsLinesToMatchGraphicsPoints(const LineStyle &lineStyle)
Calls to moveLinesWithDraggedPoint have finished so update the lines correspondingly.
void updateCurveStyle(const CurveStyle &curveStyle)
Update point and line styles that comprise the curve style.
QString identifier() const
Unique identifier for a specific Point.
Definition: Point.cpp:220
void updatePointOrdinalsAfterDrag(const LineStyle &lineStyle, const Transformation &transformation)
See GraphicsScene::updateOrdinalsAfterDrag. Pretty much the same steps as Curve::updatePointOrdinals...
Affine transformation between screen and graph coordinates, based on digitized axis points...
Details for a specific Point.
Definition: PointStyle.h:14
void removeTemporaryPointIfExists()
Remove temporary point if it exists.
void printStream(QString indentation, QTextStream &str) const
Debugging method that supports print method of this class and printStream method of some other class(...
static double UNDEFINED_ORDINAL()
Get method for undefined ordinal constant.
Definition: Point.h:117
Container for LineStyle and PointStyle for one Curve.
Definition: CurveStyle.h:12
Details for a specific Line.
Definition: LineStyle.h:13
Graphics item for drawing a circular or polygonal Point.
Definition: GraphicsPoint.h:33
void updateAfterCommand(GraphicsScene &scene, const PointStyle &pointStyle, const Point &point)
Update the GraphicsScene with the specified Point from the Document. If it does not exist yet in the ...
ColorPalette paletteColor() const
Line color.
Definition: LineStyle.cpp:86
void addPoint(const QString &pointIdentifier, double ordinal, GraphicsPoint &point)
Add new line.
double ordinal(ApplyHasCheck applyHasCheck=KEEP_HAS_CHECK) const
Get method for ordinal. Skip check if copying one instance to another.
Definition: Point.cpp:326
GraphicsPoint * createPoint(const QString &identifier, const PointStyle &pointStyle, const QPointF &posScreen)
Create one QGraphicsItem-based object that represents one Point. It is NOT added to m_graphicsLinesFo...
QPointF pos() const
Proxy method for QGraphicsItem::pos.
QVariant data(int key) const
Proxy method for QGraphicsItem::data.
CurveConnectAs curveConnectAs() const
Get method for connect type.
Definition: LineStyle.cpp:43
Add point and line handling to generic QGraphicsScene.
Definition: GraphicsScene.h:25
void lineMembershipPurge(const LineStyle &lineStyle)
Mark the end of addPoint calls. Remove stale lines, insert missing lines, and draw the graphics lines...
void removePoint(double ordinal)
Remove the specified point. The act of deleting it will automatically remove it from the GraphicsScen...
Single X/Y pair for cubic spline interpolation initialization and calculations.
Definition: SplinePair.h:5
void reset()
Mark point as unwanted, and unbind any bound lines.