Engauge Digitizer 2
Loading...
Searching...
No Matches
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
26using namespace std;
27
28typedef 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
39GraphicsLinesForCurve::~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
50void 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
64QPainterPath 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
118QPainterPath 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
143double 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
217bool 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
237void 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
291void 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}
Container for LineStyle and PointStyle for one Curve.
Definition CurveStyle.h:19
double identifierToOrdinal(const QString &identifier) const
Get ordinal for specified identifier.
void updateCurveStyle(const CurveStyle &curveStyle)
Update the curve style for this curve.
void removePoint(double ordinal)
Remove the specified point. The act of deleting it will automatically remove it from the GraphicsScen...
void addPoint(const QString &pointIdentifier, double ordinal, GraphicsPoint &point)
Add new line.
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 ...
void lineMembershipReset()
Mark points as unwanted. Afterwards, lineMembershipPurge gets called.
void removeTemporaryPointIfExists()
Remove temporary point if it exists.
void lineMembershipPurge(const LineStyle &lineStyle)
Mark the end of addPoint calls. Remove stale lines, insert missing lines, and draw the graphics lines...
void updateGraphicsLinesToMatchGraphicsPoints(const LineStyle &lineStyle)
Calls to moveLinesWithDraggedPoint have finished so update the lines correspondingly.
void updatePointOrdinalsAfterDrag(const LineStyle &lineStyle, const Transformation &transformation)
See GraphicsScene::updateOrdinalsAfterDrag. Pretty much the same steps as Curve::updatePointOrdinals.
GraphicsLinesForCurve(const QString &curveName)
Single constructor.
void printStream(QString indentation, QTextStream &str) const
Debugging method that supports print method of this class and printStream method of some other class(...
Graphics item for drawing a circular or polygonal Point.
QPointF pos() const
Proxy method for QGraphicsItem::pos.
void setWanted()
Mark point as wanted. Marking as unwanted is done by the reset function.
void setData(int key, const QVariant &data)
Proxy method for QGraphicsItem::setData.
void updateCurveStyle(const CurveStyle &curveStyle)
Update point and line styles that comprise the curve style.
void reset()
Mark point as unwanted, and unbind any bound lines.
bool wanted() const
Identify point as wanted//unwanted.
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 setPos(const QPointF pos)
Update the position.
QVariant data(int key) const
Proxy method for QGraphicsItem::data.
Add point and line handling to generic QGraphicsScene.
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...
Details for a specific Line.
Definition LineStyle.h:20
CurveConnectAs curveConnectAs() const
Get method for connect type.
Definition LineStyle.cpp:63
unsigned int width() const
Width of line.
ColorPalette paletteColor() const
Line color.
Details for a specific Point.
Definition PointStyle.h:21
Class that represents one digitized point. The screen-to-graph coordinate transformation is always ex...
Definition Point.h:24
QPointF posScreen() const
Accessor for screen position.
Definition Point.cpp:392
QString identifier() const
Unique identifier for a specific Point.
Definition Point.cpp:256
double ordinal(ApplyHasCheck applyHasCheck=KEEP_HAS_CHECK) const
Get method for ordinal. Skip check if copying one instance to another.
Definition Point.cpp:374
static double UNDEFINED_ORDINAL()
Get method for undefined ordinal constant.
Definition Point.h:132
Single X/Y pair for cubic spline interpolation initialization and calculations.
Definition SplinePair.h:12
Cubic interpolation given independent and dependent value vectors.
Definition Spline.h:22
Affine transformation between screen and graph coordinates, based on digitized axis points.
void transformScreenToRawGraph(const QPointF &coordScreen, QPointF &coordGraph) const
Transform from cartesian pixel screen coordinates to cartesian/polar graph coordinates.