Engauge Digitizer  2
 All Classes Files Functions Variables Enumerations Enumerator Friends Pages
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 class requires at least one point
86  if (xy.size() > 0) {
87 
88  // Spline through points
89  Spline spline (t, xy);
90 
91  // Drawing from point i-1 to this point i uses the control points from point i-1
92  int segmentEndingAtPointI = 0;
93 
94  // Create QPainterPath through the points
95  bool isFirst = true;
96  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
97 
98  const GraphicsPoint *point = itr.value();
99 
100  if (isFirst) {
101  isFirst = false;
102  path.moveTo (point->pos());
103  } else {
104 
105  QPointF p1 (spline.p1 (segmentEndingAtPointI).x(),
106  spline.p1 (segmentEndingAtPointI).y());
107  QPointF p2 (spline.p2 (segmentEndingAtPointI).x(),
108  spline.p2 (segmentEndingAtPointI).y());
109 
110  path.cubicTo (p1,
111  p2,
112  point->pos ());
113 
114  ++segmentEndingAtPointI;
115  }
116  }
117  }
118 
119  return path;
120 }
121 
122 QPainterPath GraphicsLinesForCurve::drawLinesStraight ()
123 {
124  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::drawLinesStraight"
125  << " curve=" << m_curveName.toLatin1().data();
126 
127  QPainterPath path;
128 
129  // Create QPainterPath through the points
130  bool isFirst = true;
131  OrdinalToGraphicsPoint::const_iterator itr;
132  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
133 
134  const GraphicsPoint *point = itr.value();
135 
136  if (isFirst) {
137  isFirst = false;
138  path.moveTo (point->pos ());
139  } else {
140  path.lineTo (point->pos ());
141  }
142  }
143 
144  return path;
145 }
146 
147 double GraphicsLinesForCurve::identifierToOrdinal (const QString &identifier) const
148 {
149  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::identifierToOrdinal"
150  << " identifier=" << identifier.toLatin1().data();
151 
152  OrdinalToGraphicsPoint::const_iterator itr;
153  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
154 
155  const GraphicsPoint *point = itr.value();
156 
157  if (point->data (DATA_KEY_IDENTIFIER) == identifier) {
158  return itr.key();
159  }
160  }
161 
162  ENGAUGE_ASSERT (false);
163 
164  return 0;
165 }
166 
168 {
169  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::lineMembershipPurge"
170  << " curve=" << m_curveName.toLatin1().data();
171 
172  OrdinalToGraphicsPoint::iterator itr, itrNext;
173  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr = itrNext) {
174 
175  itrNext = itr;
176  ++itrNext;
177 
178  GraphicsPoint *point = *itr;
179 
180  if (!point->wanted ()) {
181 
182  double ordinal = itr.key ();
183 
184  delete point;
185  m_graphicsPoints.remove (ordinal);
186  }
187  }
188 
189  // Apply line style
190  QPen pen;
191  if (lineStyle.paletteColor() == COLOR_PALETTE_TRANSPARENT) {
192 
193  pen = QPen (Qt::NoPen);
194 
195  } else {
196 
197  pen = QPen (QBrush (ColorPaletteToQColor (lineStyle.paletteColor())),
198  lineStyle.width());
199 
200  }
201 
202  setPen (pen);
203 
205 }
206 
208 {
209  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::lineMembershipReset"
210  << " curve=" << m_curveName.toLatin1().data();
211 
212  OrdinalToGraphicsPoint::iterator itr;
213  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
214 
215  GraphicsPoint *point = itr.value();
216 
217  point->reset ();
218  }
219 }
220 
221 bool GraphicsLinesForCurve::needOrdinalRenumbering () const
222 {
223  // Ordinals should be 0, 1, ...
224  bool needRenumbering = false;
225  for (int ordinalKeyWanted = 0; ordinalKeyWanted < m_graphicsPoints.count(); ordinalKeyWanted++) {
226 
227  double ordinalKeyGot = m_graphicsPoints.keys().at (ordinalKeyWanted);
228 
229  // Sanity checks
230  ENGAUGE_ASSERT (ordinalKeyGot != Point::UNDEFINED_ORDINAL ());
231 
232  if (ordinalKeyWanted != ordinalKeyGot) {
233  needRenumbering = true;
234  break;
235  }
236  }
237 
238  return needRenumbering;
239 }
240 
241 void GraphicsLinesForCurve::printStream (QString indentation,
242  QTextStream &str) const
243 {
244  DataKey type = (DataKey) data (DATA_KEY_GRAPHICS_ITEM_TYPE).toInt();
245 
246  str << indentation << "GraphicsLinesForCurve=" << m_curveName
247  << " dataIdentifier=" << data (DATA_KEY_IDENTIFIER).toString().toLatin1().data()
248  << " dataType=" << dataKeyToString (type).toLatin1().data() << "\n";
249 
250  indentation += INDENTATION_DELTA;
251 
252  OrdinalToGraphicsPoint::const_iterator itr;
253  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
254 
255  double ordinalKey = itr.key();
256  const GraphicsPoint *point = itr.value();
257 
258  point->printStream (indentation,
259  str,
260  ordinalKey);
261  }
262 }
263 
265 {
266  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::removePoint"
267  << " point=" << ordinal
268  << " pointCount=" << m_graphicsPoints.count();
269 
270  ENGAUGE_ASSERT (m_graphicsPoints.contains (ordinal));
271  GraphicsPoint *graphicsPoint = m_graphicsPoints [ordinal];
272 
273  m_graphicsPoints.remove (ordinal);
274 
275  delete graphicsPoint;
276 }
277 
279 {
280  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::removeTemporaryPointIfExists";
281 
282  OrdinalToGraphicsPoint::iterator itr;
283  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
284 
285  GraphicsPoint *graphicsPoint = itr.value();
286 
287  m_graphicsPoints.remove (itr.key());
288 
289  delete graphicsPoint;
290 
291  break;
292  }
293 }
294 
295 void GraphicsLinesForCurve::renumberOrdinals ()
296 {
297  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::renumberOrdinals";
298 
299  int ordinalKeyWanted;
300 
301  // Ordinals should be 0, 1, and so on. Assigning a list to QMap::keys has no effect, so the
302  // approach is to copy to a temporary list and then copy back
303  QList<GraphicsPoint*> points;
304  for (ordinalKeyWanted = 0; ordinalKeyWanted < m_graphicsPoints.count(); ordinalKeyWanted++) {
305 
306  GraphicsPoint *graphicsPoint = m_graphicsPoints.values().at (ordinalKeyWanted);
307  points << graphicsPoint;
308  }
309 
310  m_graphicsPoints.clear ();
311 
312  for (ordinalKeyWanted = 0; ordinalKeyWanted < points.count(); ordinalKeyWanted++) {
313 
314  GraphicsPoint *graphicsPoint = points.at (ordinalKeyWanted);
315  m_graphicsPoints [ordinalKeyWanted] = graphicsPoint;
316  }
317 }
318 
320  const PointStyle &pointStyle,
321  const Point &point)
322 {
323  LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsLinesForCurve::updateAfterCommand"
324  << " curve=" << m_curveName.toLatin1().data()
325  << " pointCount=" << m_graphicsPoints.count();
326 
327  GraphicsPoint *graphicsPoint = 0;
328  if (m_graphicsPoints.contains (point.ordinal())) {
329 
330  graphicsPoint = m_graphicsPoints [point.ordinal()];
331 
332  // Due to ordinal renumbering, the coordinates may belong to some other point so we override
333  // them for consistent ordinal-position mapping. Updating the identifier also was added for
334  // better logging (i.e. consistency between Document and GraphicsScene dumps), but happened
335  // to fix a bug with the wrong set of points getting deleted from Cut and Delete
336  graphicsPoint->setPos (point.posScreen());
337  graphicsPoint->setData (DATA_KEY_IDENTIFIER, point.identifier());
338 
339  } else {
340 
341  // Point does not exist in scene so create it
342  graphicsPoint = scene.createPoint (point.identifier (),
343  pointStyle,
344  point.posScreen());
345  m_graphicsPoints [point.ordinal ()] = graphicsPoint;
346 
347  }
348 
349  // Mark point as wanted
350  ENGAUGE_CHECK_PTR (graphicsPoint);
351  graphicsPoint->setWanted ();
352 }
353 
355 {
356  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::updateCurveStyle";
357 
358  OrdinalToGraphicsPoint::const_iterator itr;
359  for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
360 
361  GraphicsPoint *point = itr.value();
362  point->updateCurveStyle (curveStyle);
363  }
364 }
365 
367 {
368  // LOG4CPP_INFO_S is below
369 
370  bool needRenumbering = needOrdinalRenumbering ();
371  if (needRenumbering) {
372 
373  renumberOrdinals();
374 
375  }
376 
377  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::updateGraphicsLinesToMatchGraphicsPoints"
378  << " numberPoints=" << m_graphicsPoints.count()
379  << " ordinalRenumbering=" << (needRenumbering ? "true" : "false");
380 
381  if (lineStyle.curveConnectAs() != CONNECT_SKIP_FOR_AXIS_CURVE) {
382 
383  // Draw as either straight or smoothed. The function/relation differences were handled already with ordinals. The
384  // Spline algorithm will crash with fewer than three points so it is only called when there are enough points
385  QPainterPath path;
386  if (lineStyle.curveConnectAs() == CONNECT_AS_FUNCTION_STRAIGHT ||
387  lineStyle.curveConnectAs() == CONNECT_AS_RELATION_STRAIGHT ||
388  m_graphicsPoints.count () < 3) {
389 
390  path = drawLinesStraight ();
391  } else {
392  path = drawLinesSmooth ();
393  }
394 
395  setPath (path);
396  }
397 }
398 
400  const Transformation &transformation)
401 {
402  CurveConnectAs curveConnectAs = lineStyle.curveConnectAs();
403 
404  LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsLinesForCurve::updateGraphicsLinesToMatchGraphicsPoints"
405  << " curve=" << m_curveName.toLatin1().data()
406  << " curveConnectAs=" << curveConnectAsToString(curveConnectAs).toLatin1().data();
407 
408  if (curveConnectAs == CONNECT_AS_FUNCTION_SMOOTH ||
409  curveConnectAs == CONNECT_AS_FUNCTION_STRAIGHT) {
410 
411  // Make sure ordinals are properly ordered
412 
413  // Get a map of x/theta values as keys with point identifiers as the values
414  XOrThetaToOrdinal xOrThetaToOrdinal;
415  OrdinalToGraphicsPoint::iterator itrP;
416  for (itrP = m_graphicsPoints.begin(); itrP != m_graphicsPoints.end(); itrP++) {
417 
418  double ordinal = itrP.key();
419  const GraphicsPoint *point = itrP.value();
420 
421  // Convert screen coordinate to graph coordinates, which gives us x/theta
422  QPointF pointGraph;
423  transformation.transformScreenToRawGraph(point->pos (),
424  pointGraph);
425 
426  xOrThetaToOrdinal [pointGraph.x()] = ordinal;
427  }
428 
429  // Loop through the sorted x/theta values. Since QMap is used, the x/theta keys are sorted
430  OrdinalToGraphicsPoint temporaryList;
431  int ordinalNew = 0;
432  XOrThetaToOrdinal::const_iterator itrX;
433  for (itrX = xOrThetaToOrdinal.begin(); itrX != xOrThetaToOrdinal.end(); itrX++) {
434 
435  double ordinalOld = *itrX;
436  GraphicsPoint *point = m_graphicsPoints [ordinalOld];
437 
438  temporaryList [ordinalNew++] = point;
439  }
440 
441  // Copy from temporary back to original map
442  m_graphicsPoints.clear();
443  for (itrP = temporaryList.begin(); itrP != temporaryList.end(); itrP++) {
444 
445  double ordinal = itrP.key();
446  GraphicsPoint *point = itrP.value();
447 
448  m_graphicsPoints [ordinal] = point;
449  }
450  }
451 }
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.