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