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