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 <QPainterPath>
22 #include <QPen>
23 #include <QTextStream>
24 #include "QtToString.h"
25 #include "Spline.h"
26 #include "SplineDrawer.h"
27 #include "Transformation.h"
28 #include "ZValues.h"
29 
30 using namespace std;
31 
32 typedef QMap<double, double> XOrThetaToOrdinal;
33 
35  m_curveName (curveName)
36 {
37  setZValue (Z_VALUE_CURVE);
38  setData (DATA_KEY_GRAPHICS_ITEM_TYPE,
39  GRAPHICS_ITEM_TYPE_LINE);
40  setData (DATA_KEY_IDENTIFIER,
41  QVariant (m_curveName));
42 }
43 
44 GraphicsLinesForCurve::~GraphicsLinesForCurve()
45 {
46  OrdinalToGraphicsPoint::iterator itr;
47  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
48  GraphicsPoint *point = itr.value();
49  delete point;
50  }
51 
52  m_graphicsPoints.clear();
53 }
54 
55 void GraphicsLinesForCurve::addPoint (const QString &pointIdentifier,
56  double ordinal,
57  GraphicsPoint &graphicsPoint)
58 {
59  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::addPoint"
60  << " curve=" << m_curveName.toLatin1().data()
61  << " identifier=" << pointIdentifier.toLatin1().data()
62  << " ordinal=" << ordinal
63  << " pos=" << QPointFToString (graphicsPoint.pos()).toLatin1().data()
64  << " newPointCount=" << (m_graphicsPoints.count() + 1);
65 
66  m_graphicsPoints [ordinal] = &graphicsPoint;
67 }
68 
69 QPainterPath GraphicsLinesForCurve::drawLinesSmooth (const LineStyle &lineStyle,
70  SplineDrawer &splineDrawer,
71  QPainterPath &pathMultiValued,
72  LineStyle &lineMultiValued)
73 {
74  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::drawLinesSmooth"
75  << " curve=" << m_curveName.toLatin1().data();
76 
77  QPainterPath path;
78 
79  // Prepare spline inputs. Note that the ordinal values may not start at 0
80  vector<double> t;
81  vector<SplinePair> xy;
82  OrdinalToGraphicsPoint::const_iterator itr;
83  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
84 
85  double ordinal = itr.key();
86  const GraphicsPoint *point = itr.value();
87 
88  t.push_back (ordinal);
89  xy.push_back (SplinePair (point->pos ().x(),
90  point->pos ().y()));
91  }
92 
93  // Spline class requires at least one point
94  if (xy.size() > 0) {
95 
96  // Spline through points
97  Spline spline (t, xy);
98 
99  splineDrawer.bindToSpline (m_graphicsPoints.count(),
100  spline);
101 
102  // Create QPainterPath through the points. Loop has one segment per stop point,
103  // with first point handled outside first
104  int segment; // Only incremented after a draw, corresponding to finishing a segment
105  OrdinalToGraphicsPoint::const_iterator itr = m_graphicsPoints.begin();
106 
107  const GraphicsPoint *point = itr.value();
108  path.moveTo (point->pos ());
109  pathMultiValued.moveTo (point->pos ());
110  ++itr;
111 
112  for (segment = 0;
113  itr != m_graphicsPoints.end();
114  segment++, itr++) {
115 
116  const GraphicsPoint *point = itr.value();
117 
118  SplineDrawerOperation operation = splineDrawer.segmentOperation (segment);
119 
120  QPointF p1 (spline.p1 (segment).x(),
121  spline.p1 (segment).y());
122  QPointF p2 (spline.p2 (segment).x(),
123  spline.p2 (segment).y());
124 
125  switch (operation) {
126  case SPLINE_DRAWER_ENUM_VISIBLE_DRAW:
127  {
128  // Show this segment
129  path.cubicTo (p1,
130  p2,
131  point->pos ());
132  }
133  break;
134 
135  case SPLINE_DRAWER_ENUM_INVISIBLE_MOVE:
136 
137  // Hide this segment as a regular curve, and show it as the error curve
138  path.moveTo (point->pos ());
139 
140  // Show curveMultiValued instead in what would have been the original curve's path
141  OrdinalToGraphicsPoint::const_iterator itrBefore = itr - 1;
142  const GraphicsPoint *pointBefore = itrBefore.value();
143  pathMultiValued.moveTo (pointBefore->pos ());
144  pathMultiValued.cubicTo (p1,
145  p2,
146  point->pos ());
147  lineMultiValued = lineStyle; // Remember to not use the same line style
148  break;
149 
150  }
151 
152  // Always move to next point for curveMultiValued
153  pathMultiValued.moveTo (point->pos ());
154  }
155  }
156 
157  return path;
158 }
159 
160 QPainterPath GraphicsLinesForCurve::drawLinesStraight (QPainterPath & /* pathMultiValued */)
161 {
162  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::drawLinesStraight"
163  << " curve=" << m_curveName.toLatin1().data();
164 
165  QPainterPath path;
166 
167  // Create QPainterPath through the points
168  bool isFirst = true;
169  OrdinalToGraphicsPoint::const_iterator itr;
170  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
171 
172  const GraphicsPoint *point = itr.value();
173 
174  if (isFirst) {
175  isFirst = false;
176  path.moveTo (point->pos ());
177  } else {
178  path.lineTo (point->pos ());
179  }
180  }
181 
182  return path;
183 }
184 
185 double GraphicsLinesForCurve::identifierToOrdinal (const QString &identifier) const
186 {
187  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::identifierToOrdinal"
188  << " identifier=" << identifier.toLatin1().data();
189 
190  OrdinalToGraphicsPoint::const_iterator itr;
191  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
192 
193  const GraphicsPoint *point = itr.value();
194 
195  if (point->data (DATA_KEY_IDENTIFIER) == identifier) {
196  return itr.key();
197  }
198  }
199 
200  ENGAUGE_ASSERT (false);
201 
202  return 0;
203 }
204 
206  SplineDrawer &splineDrawer,
207  QPainterPath &pathMultiValued,
208  LineStyle &lineMultiValued)
209 {
210  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::lineMembershipPurge"
211  << " curve=" << m_curveName.toLatin1().data();
212 
213  OrdinalToGraphicsPoint::iterator itr, itrNext;
214  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr = itrNext) {
215 
216  itrNext = itr;
217  ++itrNext;
218 
219  GraphicsPoint *point = *itr;
220 
221  if (!point->wanted ()) {
222 
223  double ordinal = itr.key ();
224 
225  delete point;
226  m_graphicsPoints.remove (ordinal);
227  }
228  }
229 
230  // Apply line style
231  QPen pen;
232  if (lineStyle.paletteColor() == COLOR_PALETTE_TRANSPARENT) {
233 
234  pen = QPen (Qt::NoPen);
235 
236  } else {
237 
238  pen = QPen (QBrush (ColorPaletteToQColor (lineStyle.paletteColor())),
239  lineStyle.width());
240 
241  }
242 
243  setPen (pen);
244 
246  splineDrawer,
247  pathMultiValued,
248  lineMultiValued);
249 }
250 
252 {
253  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::lineMembershipReset"
254  << " curve=" << m_curveName.toLatin1().data();
255 
256  OrdinalToGraphicsPoint::iterator itr;
257  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
258 
259  GraphicsPoint *point = itr.value();
260 
261  point->reset ();
262  }
263 }
264 
265 bool GraphicsLinesForCurve::needOrdinalRenumbering () const
266 {
267  // Ordinals should be 0, 1, ...
268  bool needRenumbering = false;
269  for (int ordinalKeyWanted = 0; ordinalKeyWanted < m_graphicsPoints.count(); ordinalKeyWanted++) {
270 
271  double ordinalKeyGot = m_graphicsPoints.keys().at (ordinalKeyWanted);
272 
273  // Sanity checks
274  ENGAUGE_ASSERT (ordinalKeyGot != Point::UNDEFINED_ORDINAL ());
275 
276  if (ordinalKeyWanted != ordinalKeyGot) {
277  needRenumbering = true;
278  break;
279  }
280  }
281 
282  return needRenumbering;
283 }
284 
285 void GraphicsLinesForCurve::printStream (QString indentation,
286  QTextStream &str) const
287 {
288  DataKey type = (DataKey) data (DATA_KEY_GRAPHICS_ITEM_TYPE).toInt();
289 
290  str << indentation << "GraphicsLinesForCurve=" << m_curveName
291  << " dataIdentifier=" << data (DATA_KEY_IDENTIFIER).toString().toLatin1().data()
292  << " dataType=" << dataKeyToString (type).toLatin1().data() << "\n";
293 
294  indentation += INDENTATION_DELTA;
295 
296  OrdinalToGraphicsPoint::const_iterator itr;
297  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
298 
299  double ordinalKey = itr.key();
300  const GraphicsPoint *point = itr.value();
301 
302  point->printStream (indentation,
303  str,
304  ordinalKey);
305  }
306 }
307 
309 {
310  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::removePoint"
311  << " point=" << ordinal
312  << " pointCount=" << m_graphicsPoints.count();
313 
314  ENGAUGE_ASSERT (m_graphicsPoints.contains (ordinal));
315  GraphicsPoint *graphicsPoint = m_graphicsPoints [ordinal];
316 
317  m_graphicsPoints.remove (ordinal);
318 
319  delete graphicsPoint;
320 }
321 
323 {
324  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::removeTemporaryPointIfExists";
325 
326  OrdinalToGraphicsPoint::iterator itr;
327  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
328 
329  GraphicsPoint *graphicsPoint = itr.value();
330 
331  m_graphicsPoints.remove (itr.key());
332 
333  delete graphicsPoint;
334 
335  break;
336  }
337 }
338 
339 void GraphicsLinesForCurve::renumberOrdinals ()
340 {
341  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::renumberOrdinals";
342 
343  int ordinalKeyWanted;
344 
345  // Ordinals should be 0, 1, and so on. Assigning a list to QMap::keys has no effect, so the
346  // approach is to copy to a temporary list and then copy back
347  QList<GraphicsPoint*> points;
348  for (ordinalKeyWanted = 0; ordinalKeyWanted < m_graphicsPoints.count(); ordinalKeyWanted++) {
349 
350  GraphicsPoint *graphicsPoint = m_graphicsPoints.values().at (ordinalKeyWanted);
351  points << graphicsPoint;
352  }
353 
354  m_graphicsPoints.clear ();
355 
356  for (ordinalKeyWanted = 0; ordinalKeyWanted < points.count(); ordinalKeyWanted++) {
357 
358  GraphicsPoint *graphicsPoint = points.at (ordinalKeyWanted);
359  m_graphicsPoints [ordinalKeyWanted] = graphicsPoint;
360  }
361 }
362 
364  const PointStyle &pointStyle,
365  const Point &point,
366  GeometryWindow *geometryWindow)
367 {
368  LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsLinesForCurve::updateAfterCommand"
369  << " curve=" << m_curveName.toLatin1().data()
370  << " pointCount=" << m_graphicsPoints.count();
371 
372  GraphicsPoint *graphicsPoint = 0;
373  if (m_graphicsPoints.contains (point.ordinal())) {
374 
375  graphicsPoint = m_graphicsPoints [point.ordinal()];
376 
377  // Due to ordinal renumbering, the coordinates may belong to some other point so we override
378  // them for consistent ordinal-position mapping. Updating the identifier also was added for
379  // better logging (i.e. consistency between Document and GraphicsScene dumps), but happened
380  // to fix a bug with the wrong set of points getting deleted from Cut and Delete
381  graphicsPoint->setPos (point.posScreen());
382  graphicsPoint->setData (DATA_KEY_IDENTIFIER, point.identifier());
383 
384  } else {
385 
386  // Point does not exist in scene so create it
387  graphicsPoint = scene.createPoint (point.identifier (),
388  pointStyle,
389  point.posScreen(),
390  geometryWindow);
391  m_graphicsPoints [point.ordinal ()] = graphicsPoint;
392 
393  }
394 
395  // Mark point as wanted
396  ENGAUGE_CHECK_PTR (graphicsPoint);
397  graphicsPoint->setWanted ();
398 }
399 
401 {
402  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::updateCurveStyle";
403 
404  OrdinalToGraphicsPoint::const_iterator itr;
405  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
406 
407  GraphicsPoint *point = itr.value();
408  point->updateCurveStyle (curveStyle);
409  }
410 }
411 
412 void GraphicsLinesForCurve::updateHighlightOpacity (double highlightOpacity)
413 {
414  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::updateCurveStyle"
415  << " curve=" << m_curveName.toLatin1().data()
416  << " highlightOpacity=" << highlightOpacity;
417 
418  OrdinalToGraphicsPoint::const_iterator itr;
419  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
420 
421  GraphicsPoint *point = itr.value();
422  point->setHighlightOpacity (highlightOpacity);
423  }
424 }
425 
427  SplineDrawer &splineDrawer,
428  QPainterPath &pathMultiValued,
429  LineStyle &lineMultiValued)
430 {
431  // LOG4CPP_INFO_S is below
432 
433  bool needRenumbering = needOrdinalRenumbering ();
434  if (needRenumbering) {
435 
436  renumberOrdinals();
437 
438  }
439 
440  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::updateGraphicsLinesToMatchGraphicsPoints"
441  << " numberPoints=" << m_graphicsPoints.count()
442  << " ordinalRenumbering=" << (needRenumbering ? "true" : "false");
443 
444  if (lineStyle.curveConnectAs() != CONNECT_SKIP_FOR_AXIS_CURVE) {
445 
446  // Draw as either straight or smoothed. The function/relation differences were handled already with ordinals. The
447  // Spline algorithm will crash with fewer than three points so it is only called when there are enough points
448  QPainterPath path;
449  if (lineStyle.curveConnectAs() == CONNECT_AS_FUNCTION_STRAIGHT ||
450  lineStyle.curveConnectAs() == CONNECT_AS_RELATION_STRAIGHT ||
451  m_graphicsPoints.count () < 3) {
452 
453  path = drawLinesStraight (pathMultiValued);
454  } else {
455  path = drawLinesSmooth (lineStyle,
456  splineDrawer,
457  pathMultiValued,
458  lineMultiValued);
459  }
460 
461  setPath (path);
462  }
463 }
464 
466  const Transformation &transformation)
467 {
468  CurveConnectAs curveConnectAs = lineStyle.curveConnectAs();
469 
470  LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsLinesForCurve::updatePointOrdinalsAfterDrag"
471  << " curve=" << m_curveName.toLatin1().data()
472  << " curveConnectAs=" << curveConnectAsToString(curveConnectAs).toLatin1().data();
473 
474  if (curveConnectAs == CONNECT_AS_FUNCTION_SMOOTH ||
475  curveConnectAs == CONNECT_AS_FUNCTION_STRAIGHT) {
476 
477  // Make sure ordinals are properly ordered
478 
479  // Get a map of x/theta values as keys with point identifiers as the values
480  XOrThetaToOrdinal xOrThetaToOrdinal;
481  OrdinalToGraphicsPoint::iterator itrP;
482  for (itrP = m_graphicsPoints.begin(); itrP != m_graphicsPoints.end(); itrP++) {
483 
484  double ordinal = itrP.key();
485  const GraphicsPoint *point = itrP.value();
486 
487  // Convert screen coordinate to graph coordinates, which gives us x/theta
488  QPointF pointGraph;
489  transformation.transformScreenToRawGraph(point->pos (),
490  pointGraph);
491 
492  xOrThetaToOrdinal [pointGraph.x()] = ordinal;
493  }
494 
495  // Loop through the sorted x/theta values. Since QMap is used, the x/theta keys are sorted
496  OrdinalToGraphicsPoint temporaryList;
497  int ordinalNew = 0;
498  XOrThetaToOrdinal::const_iterator itrX;
499  for (itrX = xOrThetaToOrdinal.begin(); itrX != xOrThetaToOrdinal.end(); itrX++) {
500 
501  double ordinalOld = *itrX;
502  GraphicsPoint *point = m_graphicsPoints [ordinalOld];
503 
504  temporaryList [ordinalNew++] = point;
505  }
506 
507  // Copy from temporary back to original map
508  m_graphicsPoints.clear();
509  for (itrP = temporaryList.begin(); itrP != temporaryList.end(); itrP++) {
510 
511  double ordinal = itrP.key();
512  GraphicsPoint *point = itrP.value();
513 
514  m_graphicsPoints [ordinal] = point;
515  }
516  }
517 }
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:29
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:25
Window that displays the geometry information, as a table, for the current curve. ...
void bindToSpline(int numSegments, const Spline &spline)
Analyze each segment in the Spline.
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:404
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:386
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:268
ColorPalette paletteColor() const
Line color.
Definition: LineStyle.cpp:128
void lineMembershipPurge(const LineStyle &lineStyle, SplineDrawer &splineDrawer, QPainterPath &pathMultiValued, LineStyle &lineMultiValued)
Mark the end of addPoint calls. Remove stale lines, insert missing lines, and draw the graphics lines...
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:134
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
SplineDrawerOperation segmentOperation(int segment) const
Indicate if, and how, segment is to be drawn.
void transformScreenToRawGraph(const QPointF &coordScreen, QPointF &coordGraph) const
Transform from cartesian pixel screen coordinates to cartesian/polar graph coordinates.
void addPoint(const QString &pointIdentifier, double ordinal, GraphicsPoint &point)
Add new line.
Add point and line handling to generic QGraphicsScene.
Definition: GraphicsScene.h:36
This class takes the output from Spline and uses that to draw the curve in the graphics window...
Definition: SplineDrawer.h:34
void updateGraphicsLinesToMatchGraphicsPoints(const LineStyle &lineStyle, SplineDrawer &splineDrawer, QPainterPath &pathMultiValued, LineStyle &lineMultiValued)
Calls to moveLinesWithDraggedPoint have finished so update the lines correspondingly.
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:13
double identifierToOrdinal(const QString &identifier) const
Get ordinal for specified identifier.
void reset()
Mark point as unwanted, and unbind any bound lines.