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