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 "Curve.h"
8 #include "CurvesGraphs.h"
9 #include "CurveStyle.h"
10 #include "DocumentSerialize.h"
11 #include "EngaugeAssert.h"
12 #include "Logger.h"
13 #include "MigrateToVersion6.h"
14 #include "Point.h"
15 #include "PointComparator.h"
16 #include <QDataStream>
17 #include <QDebug>
18 #include <qmath.h>
19 #include <QMultiMap>
20 #include <QTextStream>
21 #include <QXmlStreamReader>
22 #include <QXmlStreamWriter>
23 #include "Transformation.h"
24 #include "Xml.h"
25 
26 const QString AXIS_CURVE_NAME ("Axes");
27 const QString DEFAULT_GRAPH_CURVE_NAME ("Curve1");
28 const QString DUMMY_CURVE_NAME ("dummy");
29 const QString SCALE_CURVE_NAME ("Scale"); // Used for pre-version 6 input files
30 const QString TAB_DELIMITER ("\t");
31 
32 // This has to be a multimap instead of a map since some users may mistakenly allow multiple points with the
33 // same x coordinate in their functions even though that should not happen
34 typedef QMultiMap<double, QString> XOrThetaToPointIdentifier;
35 
Curve(const QString & curveName,const ColorFilterSettings & colorFilterSettings,const CurveStyle & curveStyle)36 Curve::Curve(const QString &curveName,
37              const ColorFilterSettings &colorFilterSettings,
38              const CurveStyle &curveStyle) :
39   m_curveName (curveName),
40   m_colorFilterSettings (colorFilterSettings),
41   m_curveStyle (curveStyle)
42 {
43 }
44 
Curve(const Curve & curve)45 Curve::Curve (const Curve &curve) :
46   m_curveName (curve.curveName ()),
47   m_points (curve.points ()),
48   m_colorFilterSettings (curve.colorFilterSettings ()),
49   m_curveStyle (curve.curveStyle ())
50 {
51 }
52 
Curve(QDataStream & str)53 Curve::Curve (QDataStream &str)
54 {
55   const int CONVERT_ENUM_TO_RADIUS = 6;
56   MigrateToVersion6 migrate;
57 
58   qint32 int32, xScreen, yScreen;
59   double xGraph, yGraph;
60 
61   str >> m_curveName;
62 
63   // Scale bar points are handled as if they are axis points
64   if (m_curveName == SCALE_CURVE_NAME) {
65     m_curveName = AXIS_CURVE_NAME;
66   }
67 
68   str >> int32;
69   m_curveStyle.setPointShape(migrate.pointShape (int32));
70   str >> int32;
71   m_curveStyle.setPointRadius(int32 + CONVERT_ENUM_TO_RADIUS);
72   str >> int32;
73   m_curveStyle.setPointLineWidth (int32);
74   str >> int32;
75   m_curveStyle.setPointColor(migrate.colorPalette (int32));
76   str >> int32; // Point interior color
77   str >> int32;
78   m_curveStyle.setLineWidth(int32);
79   str >> int32;
80   if (m_curveName == AXIS_CURVE_NAME) {
81     m_curveStyle.setLineColor(migrate.colorPalette (int32));
82   } else {
83     m_curveStyle.setLineColor(COLOR_PALETTE_TRANSPARENT);
84   }
85   str >> int32;
86   m_curveStyle.setLineConnectAs(migrate.curveConnectAs (int32));
87 
88   str >> int32;
89   int count = int32;
90   int ordinal = 0;
91   for (int i = 0; i < count; i++) {
92 
93     str >> xScreen;
94     str >> yScreen;
95     str >> xGraph;
96     str >> yGraph;
97     if (m_curveName == AXIS_CURVE_NAME) {
98 
99       // Axis point, with graph coordinates set by user and managed here
100       Point point (m_curveName,
101                    QPointF (xScreen, yScreen),
102                    QPointF (xGraph, yGraph),
103                    ordinal++,
104                    false);
105 
106       addPoint(point);
107     } else {
108 
109       // Curve point, with graph coordinates managed elsewhere
110       Point point (m_curveName,
111                    QPointF (xScreen, yScreen));
112       point.setOrdinal (ordinal++);
113 
114       addPoint(point);
115     }
116   }
117 }
118 
Curve(QXmlStreamReader & reader)119 Curve::Curve (QXmlStreamReader &reader)
120 {
121   loadXml(reader);
122 }
123 
operator =(const Curve & curve)124 Curve &Curve::operator=(const Curve &curve)
125 {
126   m_curveName = curve.curveName ();
127   m_points = curve.points ();
128   m_colorFilterSettings = curve.colorFilterSettings ();
129   m_curveStyle = curve.curveStyle ();
130 
131   return *this;
132 }
133 
addPoint(const Point & point)134 void Curve::addPoint (const Point &point)
135 {
136   m_points.push_back (point);
137 }
138 
colorFilterSettings() const139 ColorFilterSettings Curve::colorFilterSettings () const
140 {
141   return m_colorFilterSettings;
142 }
143 
curveName() const144 QString Curve::curveName () const
145 {
146   return  m_curveName;
147 }
148 
curveStyle() const149 CurveStyle Curve::curveStyle() const
150 {
151   return m_curveStyle;
152 }
153 
editPointAxis(const QPointF & posGraph,const QString & identifier)154 void Curve::editPointAxis (const QPointF &posGraph,
155                            const QString &identifier)
156 {
157   // Search for the point with matching identifier
158   QList<Point>::iterator itr;
159   for (itr = m_points.begin (); itr != m_points.end (); itr++) {
160 
161     Point &point = *itr;
162     if (point.identifier () == identifier) {
163 
164       point.setPosGraph (posGraph);
165       break;
166 
167     }
168   }
169 }
170 
editPointGraph(bool isX,bool isY,double x,double y,const QStringList & identifiers,const Transformation & transformation)171 void Curve::editPointGraph (bool isX,
172                             bool isY,
173                             double x,
174                             double y,
175                             const QStringList &identifiers,
176                             const Transformation &transformation)
177 {
178   LOG4CPP_INFO_S ((*mainCat)) << "Curve::editPointGraph"
179                               << " identifiers=" << identifiers.join(" ").toLatin1().data();
180 
181   if (transformation.transformIsDefined()) {
182 
183     // Search for the point with matching identifier
184     QList<Point>::iterator itr;
185     for (itr = m_points.begin(); itr != m_points.end(); itr++) {
186 
187       Point &point = *itr;
188 
189       if (identifiers.contains (point.identifier ())) {
190 
191         // Although one or more graph coordinates are specified, it is the screen coordinates that must be
192         // moved. This is because only the screen coordinates of the graph points are tracked (not the graph coordinates).
193         // So we compute posScreen and call Point::setPosScreen instead of Point::setPosGraph
194 
195         // Get original graph coordinates
196         QPointF posScreen = point.posScreen ();
197         QPointF posGraph;
198         transformation.transformScreenToRawGraph (posScreen,
199                                                   posGraph);
200 
201         // Override one or both coordinates
202         if (isX) {
203           posGraph.setX (x);
204         }
205 
206         if (isY) {
207           posGraph.setY (y);
208         }
209 
210         // Set the screen coordinates
211         transformation.transformRawGraphToScreen(posGraph,
212                                                  posScreen);
213 
214         point.setPosScreen (posScreen);
215       }
216     }
217   }
218 }
219 
exportToClipboard(const QHash<QString,bool> & selectedHash,const Transformation & transformation,QTextStream & strCsv,QTextStream & strHtml,CurvesGraphs & curvesGraphs) const220 void Curve::exportToClipboard (const QHash<QString, bool> &selectedHash,
221                                const Transformation &transformation,
222                                QTextStream &strCsv,
223                                QTextStream &strHtml,
224                                CurvesGraphs &curvesGraphs) const
225 {
226   LOG4CPP_INFO_S ((*mainCat)) << "Curve::exportToClipboard"
227                               << " hashCount=" << selectedHash.count();
228 
229   // This method assumes Copy is only allowed when Transformation is valid
230 
231   bool isFirst = true;
232   QList<Point>::const_iterator itr;
233   for (itr = m_points.begin (); itr != m_points.end (); itr++) {
234 
235     const Point &point = *itr;
236     if (selectedHash.contains (point.identifier ())) {
237 
238       if (isFirst) {
239 
240         // Insert headers to identify the points that follow
241         isFirst = false;
242         strCsv << "X" << TAB_DELIMITER << m_curveName << "\n";
243         strHtml << "<table>\n"
244                 << "<tr><th>X</th><th>" << m_curveName << "</th></tr>\n";
245       }
246 
247       // Default curve style
248       CurveStyle curveStyleDefault;
249       curveStyleDefault.setLineStyle(LineStyle::defaultAxesCurve());
250       curveStyleDefault.setPointStyle(PointStyle::defaultGraphCurve (curvesGraphs.numCurves ()));
251 
252       // Check if this curve already exists from a previously exported point
253       if (curvesGraphs.curveForCurveName (m_curveName) == nullptr) {
254         Curve curve(m_curveName,
255                     ColorFilterSettings::defaultFilter (),
256                     curveStyleDefault);
257         curvesGraphs.addGraphCurveAtEnd(curve);
258       }
259 
260       // Start with screen coordinates
261       QPointF pos = point.posScreen();
262       if (transformation.transformIsDefined()) {
263 
264         // Replace with graph coordinates which are almost always more useful
265         QPointF posGraph;
266         transformation.transformScreenToRawGraph(pos,
267                                                  posGraph);
268         pos = posGraph;
269       }
270 
271       // Add point to text going to clipboard
272       strCsv << pos.x() << TAB_DELIMITER << pos.y() << "\n";
273       strHtml << "<tr><td>" << pos.x() << "</td><td>" << pos.y() << "</td></tr>\n";
274 
275       // Add point to list for undo/redo
276       curvesGraphs.curveForCurveName (m_curveName)->addPoint (point);
277     }
278   }
279 
280   if (!isFirst) {
281     strHtml << "</table>\n";
282   }
283 }
284 
isXOnly(const QString & pointIdentifier) const285 bool Curve::isXOnly(const QString &pointIdentifier) const
286 {
287   // Search for point with matching identifier
288   Points::const_iterator itr;
289   for (itr = m_points.begin (); itr != m_points.end (); itr++) {
290     const Point &point = *itr;
291     if (pointIdentifier == point.identifier ()) {
292       return point.isXOnly();
293     }
294   }
295 
296   LOG4CPP_ERROR_S ((*mainCat)) << "Curve::isXOnly encountered unknown indentifier "
297                                << pointIdentifier.toLatin1().data();
298   ENGAUGE_ASSERT (false);
299 
300   return false;
301 }
302 
iterateThroughCurvePoints(const Functor2wRet<const QString &,const Point &,CallbackSearchReturn> & ftorWithCallback) const303 void Curve::iterateThroughCurvePoints (const Functor2wRet<const QString &, const Point&, CallbackSearchReturn> &ftorWithCallback) const
304 {
305   QList<Point>::const_iterator itr;
306   for (itr = m_points.begin (); itr != m_points.end (); itr++) {
307 
308     const Point &point = *itr;
309 
310     CallbackSearchReturn rtn = ftorWithCallback (m_curveName, point);
311 
312     if (rtn == CALLBACK_SEARCH_RETURN_INTERRUPT) {
313       break;
314     }
315   }
316 }
317 
iterateThroughCurveSegments(const Functor2wRet<const Point &,const Point &,CallbackSearchReturn> & ftorWithCallback) const318 void Curve::iterateThroughCurveSegments (const Functor2wRet<const Point&, const Point&, CallbackSearchReturn> &ftorWithCallback) const
319 {
320   // Loop through Points. They are assumed to be already sorted by their ordinals, but we do NOT
321   // check the ordinal ordering since this could be called before, or while, the ordinal sorting is done
322   QList<Point>::const_iterator itr;
323   const Point *pointBefore = nullptr;
324   for (itr = m_points.begin(); itr != m_points.end(); itr++) {
325 
326     const Point &point = *itr;
327 
328     if (pointBefore != nullptr) {
329 
330       CallbackSearchReturn rtn = ftorWithCallback (*pointBefore,
331                                                    point);
332 
333       if (rtn == CALLBACK_SEARCH_RETURN_INTERRUPT) {
334         break;
335       }
336 
337     }
338     pointBefore = &point;
339   }
340 }
341 
loadCurvePoints(QXmlStreamReader & reader)342 void Curve::loadCurvePoints(QXmlStreamReader &reader)
343 {
344   LOG4CPP_INFO_S ((*mainCat)) << "Curve::loadCurvePoints";
345 
346   bool success = true;
347 
348   while ((reader.tokenType() != QXmlStreamReader::EndElement) ||
349          (reader.name() != DOCUMENT_SERIALIZE_CURVE_POINTS)) {
350 
351     QXmlStreamReader::TokenType tokenType = loadNextFromReader(reader);
352 
353     if (reader.atEnd()) {
354       success = false;
355       break;
356     }
357 
358     if (tokenType == QXmlStreamReader::StartElement) {
359 
360       if (reader.name () == DOCUMENT_SERIALIZE_POINT) {
361 
362         Point point (reader);
363         m_points.push_back (point);
364       }
365     }
366   }
367 
368   if (!success) {
369     reader.raiseError(QObject::tr ("Cannot read curve data"));
370   }
371 }
372 
loadXml(QXmlStreamReader & reader)373 void Curve::loadXml(QXmlStreamReader &reader)
374 {
375   LOG4CPP_INFO_S ((*mainCat)) << "Curve::loadXml";
376 
377   bool success = true;
378 
379   QXmlStreamAttributes attributes = reader.attributes();
380 
381   if (attributes.hasAttribute (DOCUMENT_SERIALIZE_CURVE_NAME)) {
382 
383     setCurveName (attributes.value (DOCUMENT_SERIALIZE_CURVE_NAME).toString());
384 
385     // Read until end of this subtree
386     while ((reader.tokenType() != QXmlStreamReader::EndElement) ||
387            (reader.name() != DOCUMENT_SERIALIZE_CURVE)){
388 
389       QXmlStreamReader::TokenType tokenType = loadNextFromReader(reader);
390 
391       if (reader.atEnd()) {
392         success = false;
393         break;
394       }
395 
396       if (tokenType == QXmlStreamReader::StartElement) {
397 
398         if (reader.name() == DOCUMENT_SERIALIZE_COLOR_FILTER) {
399           m_colorFilterSettings.loadXml(reader);
400         } else if (reader.name() == DOCUMENT_SERIALIZE_CURVE_POINTS) {
401           loadCurvePoints(reader);
402         } else if (reader.name() == DOCUMENT_SERIALIZE_CURVE_STYLE) {
403           m_curveStyle.loadXml(reader);
404         } else {
405           success = false;
406           break;
407         }
408       }
409 
410       if (reader.hasError()) {
411         // No need to set success flag to indicate failure, which raises the error, since the error was already raised. Just
412         // need to exit the loop immediately
413         break;
414       }
415     }
416   } else {
417     success = false;
418   }
419 
420   if (!success) {
421     reader.raiseError (QObject::tr ("Cannot read curve data"));
422   }
423 }
424 
movePoint(const QString & pointIdentifier,const QPointF & deltaScreen)425 void Curve::movePoint (const QString &pointIdentifier,
426                        const QPointF &deltaScreen)
427 {
428   Point *point = pointForPointIdentifier (pointIdentifier);
429 
430   QPointF posScreen = deltaScreen + point->posScreen ();
431   point->setPosScreen (posScreen);
432 }
433 
numPoints() const434 int Curve::numPoints () const
435 {
436   return m_points.count ();
437 }
438 
pointForPointIdentifier(const QString pointIdentifier)439 Point *Curve::pointForPointIdentifier (const QString pointIdentifier)
440 {
441   Points::iterator itr;
442   for (itr = m_points.begin (); itr != m_points.end (); itr++) {
443     Point &point = *itr;
444     if (pointIdentifier == point.identifier ()) {
445       return &point;
446     }
447   }
448 
449   LOG4CPP_ERROR_S ((*mainCat)) << "Curve::pointForPointIdentifier encountered unknown indentifier "
450                                << pointIdentifier.toLatin1().data();
451   ENGAUGE_ASSERT (false);
452   return nullptr;
453 }
454 
points() const455 const Points Curve::points () const
456 {
457   return m_points;
458 }
459 
positionGraph(const QString & pointIdentifier) const460 QPointF Curve::positionGraph (const QString &pointIdentifier) const
461 {
462   QPointF posGraph;
463 
464   // Search for point with matching identifier
465   Points::const_iterator itr;
466   for (itr = m_points.begin (); itr != m_points.end (); itr++) {
467     const Point &point = *itr;
468     if (pointIdentifier == point.identifier ()) {
469       posGraph = point.posGraph ();
470       break;
471     }
472   }
473 
474   return posGraph;
475 }
476 
positionScreen(const QString & pointIdentifier) const477 QPointF Curve::positionScreen (const QString &pointIdentifier) const
478 {
479   QPointF posScreen;
480 
481   // Search for point with matching identifier
482   Points::const_iterator itr;
483   for (itr = m_points.begin (); itr != m_points.end (); itr++) {
484     const Point &point = *itr;
485     if (pointIdentifier == point.identifier ()) {
486       posScreen = point.posScreen ();
487       break;
488     }
489   }
490 
491   return posScreen;
492 }
493 
printStream(QString indentation,QTextStream & str) const494 void Curve::printStream (QString indentation,
495                          QTextStream &str) const
496 {
497   str << indentation << "Curve=" << m_curveName << "\n";
498 
499   indentation += INDENTATION_DELTA;
500 
501   Points::const_iterator itr;
502   for (itr = m_points.begin (); itr != m_points.end (); itr++) {
503     const Point &point = *itr;
504     point.printStream (indentation,
505                        str);
506   }
507 
508   m_colorFilterSettings.printStream (indentation,
509                                      str);
510   m_curveStyle.printStream (indentation,
511                             str);
512 }
513 
removePoint(const QString & identifier)514 void Curve::removePoint (const QString &identifier)
515 {
516   // Search for point with matching identifier
517   Points::iterator itr;
518   for (itr = m_points.begin (); itr != m_points.end (); itr++) {
519     Point point = *itr;
520     if (point.identifier () == identifier) {
521       m_points.erase (itr);
522       break;
523     }
524   }
525 }
526 
saveXml(QXmlStreamWriter & writer) const527 void Curve::saveXml(QXmlStreamWriter &writer) const
528 {
529   LOG4CPP_INFO_S ((*mainCat)) << "Curve::saveXml";
530 
531   writer.writeStartElement(DOCUMENT_SERIALIZE_CURVE);
532   writer.writeAttribute(DOCUMENT_SERIALIZE_CURVE_NAME, m_curveName);
533   m_colorFilterSettings.saveXml (writer,
534                                  m_curveName);
535   m_curveStyle.saveXml (writer,
536                         m_curveName);
537 
538   // Loop through points
539   writer.writeStartElement(DOCUMENT_SERIALIZE_CURVE_POINTS);
540   Points::const_iterator itr;
541   for (itr = m_points.begin (); itr != m_points.end (); itr++) {
542     const Point &point = *itr;
543     point.saveXml (writer);
544   }
545   writer.writeEndElement();
546 
547   writer.writeEndElement();
548 }
549 
setColorFilterSettings(const ColorFilterSettings & colorFilterSettings)550 void Curve::setColorFilterSettings (const ColorFilterSettings &colorFilterSettings)
551 {
552   m_colorFilterSettings = colorFilterSettings;
553 }
554 
setCurveName(const QString & curveName)555 void Curve::setCurveName (const QString &curveName)
556 {
557   m_curveName = curveName;
558 
559   // Pass to member objects
560   QList<Point>::iterator itr;
561   for (itr = m_points.begin(); itr != m_points.end(); itr++) {
562     Point &point = *itr;
563     point.setCurveName (curveName);
564   }
565 }
566 
setCurveStyle(const CurveStyle & curveStyle)567 void Curve::setCurveStyle (const CurveStyle &curveStyle)
568 {
569   m_curveStyle = curveStyle;
570 }
571 
updatePointOrdinals(const Transformation & transformation)572 void Curve::updatePointOrdinals (const Transformation &transformation)
573 {
574   CurveConnectAs curveConnectAs = m_curveStyle.lineStyle().curveConnectAs();
575 
576   LOG4CPP_INFO_S ((*mainCat)) << "Curve::updatePointOrdinals"
577                               << " curve=" << m_curveName.toLatin1().data()
578                               << " connectAs=" << curveConnectAsToString(curveConnectAs).toLatin1().data();
579 
580   // Make sure ordinals are properly ordered. Sorting is done afterward
581 
582   if (curveConnectAs == CONNECT_AS_FUNCTION_SMOOTH ||
583       curveConnectAs == CONNECT_AS_FUNCTION_STRAIGHT) {
584 
585     updatePointOrdinalsFunctions (transformation);
586 
587   } else if (curveConnectAs == CONNECT_AS_RELATION_SMOOTH ||
588              curveConnectAs == CONNECT_AS_RELATION_STRAIGHT) {
589 
590     updatePointOrdinalsRelations ();
591 
592   } else {
593 
594     LOG4CPP_ERROR_S ((*mainCat)) << "Curve::updatePointOrdinals encountered unexpected connection configuration";
595     ENGAUGE_ASSERT (false);
596 
597   }
598 
599   std::sort (m_points.begin(),
600              m_points.end(),
601              PointComparator());
602 }
603 
updatePointOrdinalsFunctions(const Transformation & transformation)604 void Curve::updatePointOrdinalsFunctions (const Transformation &transformation)
605 {
606   CurveConnectAs curveConnectAs = m_curveStyle.lineStyle().curveConnectAs();
607 
608   LOG4CPP_INFO_S ((*mainCat)) << "Curve::updatePointOrdinalsFunctions"
609                               << " curve=" << m_curveName.toLatin1().data()
610                               << " connectAs=" << curveConnectAsToString(curveConnectAs).toLatin1().data();
611 
612   // Get a map of x/theta values as keys with point identifiers as the values. This has to be a multimap since
613   // some users will have two (or maybe more) points with the same x coordinate, even though true functions should
614   // never have that happen
615   XOrThetaToPointIdentifier xOrThetaToPointIdentifier;
616   Points::iterator itr;
617   for (itr = m_points.begin (); itr != m_points.end (); itr++) {
618     Point &point = *itr;
619 
620     QPointF posGraph;
621     if (transformation.transformIsDefined()) {
622 
623       // Transformation is available so use it
624       transformation.transformScreenToRawGraph (point.posScreen (),
625                                                 posGraph);
626     } else {
627 
628       // Transformation is not available so we just use the screen coordinates. Effectively, the
629       // transformation is the identity matrix
630       posGraph= point.posScreen();
631     }
632 
633     xOrThetaToPointIdentifier.insert (posGraph.x(),
634                                       point.identifier());
635   }
636 
637   // Every point in m_points must be in the map. Failure to perform this check will probably result in the assert
638   // below getting triggered
639   ENGAUGE_ASSERT (xOrThetaToPointIdentifier.count () == m_points.count ());
640 
641   // Since m_points is a list (and therefore does not provide direct access to elements), we build a temporary map of
642   // point identifier to ordinal, by looping through the sorted x/theta values. Since QMap is used, the x/theta keys are sorted
643   QMap<QString, double> pointIdentifierToOrdinal;
644   int ordinal = 0;
645   XOrThetaToPointIdentifier::const_iterator itrX;
646   for (itrX = xOrThetaToPointIdentifier.begin(); itrX != xOrThetaToPointIdentifier.end(); itrX++) {
647 
648     QString pointIdentifier = itrX.value();
649     pointIdentifierToOrdinal [pointIdentifier] = ordinal++;
650   }
651 
652   // Override the old ordinal values
653   for (itr = m_points.begin(); itr != m_points.end(); itr++) {
654     Point &point = *itr;
655 
656     // Make sure point is in the map list. If this test is skipped then the square bracket operator
657     // will insert an entry with a zero ordinal, and the presence of multiple points with the same zero ordinal will
658     // cause problems downstream
659     ENGAUGE_ASSERT (pointIdentifierToOrdinal.contains (point.identifier()));
660 
661     // Point is to be included since it is in the map list.
662     int ordinalNew = qFloor (pointIdentifierToOrdinal [point.identifier()]);
663     point.setOrdinal (ordinalNew);
664   }
665 }
666 
updatePointOrdinalsRelations()667 void Curve::updatePointOrdinalsRelations ()
668 {
669   CurveConnectAs curveConnectAs = m_curveStyle.lineStyle().curveConnectAs();
670 
671   LOG4CPP_INFO_S ((*mainCat)) << "Curve::updatePointOrdinalsRelations"
672                               << " curve=" << m_curveName.toLatin1().data()
673                               << " connectAs=" << curveConnectAsToString(curveConnectAs).toLatin1().data();
674 
675     // Keep the ordinal numbering, but make sure the ordinals are evenly spaced
676     Points::iterator itr;
677     int ordinal = 0;
678     for (itr = m_points.begin(); itr != m_points.end(); itr++) {
679       Point &point = *itr;
680       point.setOrdinal (ordinal++);
681     }
682 }
683