SmoothCurveGraph.cpp 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. #include "SmoothCurveGraph.h"
  2. #include <QPointF>
  3. #include <QVector>
  4. class SmoothCurveGenerator
  5. {
  6. protected:
  7. static QPainterPath generateSmoothCurveImp(const QVector<QPointF> &points)
  8. {
  9. QPainterPath path;
  10. int len = points.size();
  11. if (len < 2) {
  12. return path;
  13. }
  14. QVector<QPointF> firstControlPoints;
  15. QVector<QPointF> secondControlPoints;
  16. calculateControlPoints(points, &firstControlPoints, &secondControlPoints);
  17. path.moveTo(points[0].x(), points[0].y());
  18. // Using bezier curve to generate a smooth curve.
  19. for (int i = 0; i < len - 1; ++i) {
  20. path.cubicTo(firstControlPoints[i], secondControlPoints[i], points[i + 1]);
  21. }
  22. return path;
  23. }
  24. public:
  25. static QPainterPath generateSmoothCurve(const QVector<QPointF> &points)
  26. {
  27. QPainterPath result;
  28. int segmentStart = 0;
  29. int i = 0;
  30. int pointSize = points.size();
  31. while (i < pointSize) {
  32. if (qIsNaN(points.at(i).y()) || qIsNaN(points.at(i).x()) || qIsInf(points.at(i).y())) {
  33. QVector<QPointF> lineData; // QVector<QPointF>(points.constBegin() + segmentStart, points.constBegin()
  34. // + i - segmentStart);
  35. for (int s = segmentStart; s < i - segmentStart; ++s) {
  36. lineData << points.at(s);
  37. }
  38. result.addPath(generateSmoothCurveImp(lineData));
  39. segmentStart = i + 1;
  40. }
  41. ++i;
  42. }
  43. QVector<QPointF> lineData; //(QVector<QPointF>(points.constBegin() + segmentStart, points.constEnd()));
  44. for (int s = segmentStart; s < points.size(); ++s) {
  45. lineData << points.at(s);
  46. }
  47. result.addPath(generateSmoothCurveImp(lineData));
  48. return result;
  49. }
  50. static QPainterPath generateSmoothCurve(const QPainterPath &basePath, const QVector<QPointF> &points)
  51. {
  52. if (points.isEmpty())
  53. return basePath;
  54. QPainterPath path = basePath;
  55. int len = points.size();
  56. if (len == 1) {
  57. path.lineTo(points.at(0));
  58. return path;
  59. }
  60. QVector<QPointF> firstControlPoints;
  61. QVector<QPointF> secondControlPoints;
  62. calculateControlPoints(points, &firstControlPoints, &secondControlPoints);
  63. path.lineTo(points.at(0));
  64. for (int i = 0; i < len - 1; ++i)
  65. path.cubicTo(firstControlPoints[i], secondControlPoints[i], points[i + 1]);
  66. return path;
  67. }
  68. static void calculateFirstControlPoints(double *&result, const double *rhs, int n)
  69. {
  70. result = new double[n];
  71. double *tmp = new double[n];
  72. double b = 2.0;
  73. result[0] = rhs[0] / b;
  74. // Decomposition and forward substitution.
  75. for (int i = 1; i < n; i++) {
  76. tmp[i] = 1 / b;
  77. b = (i < n - 1 ? 4.0 : 3.5) - tmp[i];
  78. result[i] = (rhs[i] - result[i - 1]) / b;
  79. }
  80. for (int i = 1; i < n; i++) {
  81. result[n - i - 1] -= tmp[n - i] * result[n - i]; // Backsubstitution.
  82. }
  83. delete[] tmp;
  84. }
  85. static void calculateControlPoints(const QVector<QPointF> &knots, QVector<QPointF> *firstControlPoints,
  86. QVector<QPointF> *secondControlPoints)
  87. {
  88. int n = knots.size() - 1;
  89. firstControlPoints->reserve(n);
  90. secondControlPoints->reserve(n);
  91. for (int i = 0; i < n; ++i) {
  92. firstControlPoints->append(QPointF());
  93. secondControlPoints->append(QPointF());
  94. }
  95. if (n == 1) {
  96. // Special case: Bezier curve should be a straight line.
  97. // P1 = (2P0 + P3) / 3
  98. (*firstControlPoints)[0].rx() = (2 * knots[0].x() + knots[1].x()) / 3;
  99. (*firstControlPoints)[0].ry() = (2 * knots[0].y() + knots[1].y()) / 3;
  100. // P2 = 2P1 – P0
  101. (*secondControlPoints)[0].rx() = 2 * (*firstControlPoints)[0].x() - knots[0].x();
  102. (*secondControlPoints)[0].ry() = 2 * (*firstControlPoints)[0].y() - knots[0].y();
  103. return;
  104. }
  105. // Calculate first Bezier control points
  106. double *xs = nullptr;
  107. double *ys = nullptr;
  108. double *rhsx = new double[n]; // Right hand side vector
  109. double *rhsy = new double[n]; // Right hand side vector
  110. // Set right hand side values
  111. for (int i = 1; i < n - 1; ++i) {
  112. rhsx[i] = 4 * knots[i].x() + 2 * knots[i + 1].x();
  113. rhsy[i] = 4 * knots[i].y() + 2 * knots[i + 1].y();
  114. }
  115. rhsx[0] = knots[0].x() + 2 * knots[1].x();
  116. rhsx[n - 1] = (8 * knots[n - 1].x() + knots[n].x()) / 2.0;
  117. rhsy[0] = knots[0].y() + 2 * knots[1].y();
  118. rhsy[n - 1] = (8 * knots[n - 1].y() + knots[n].y()) / 2.0;
  119. // Calculate first control points coordinates
  120. calculateFirstControlPoints(xs, rhsx, n);
  121. calculateFirstControlPoints(ys, rhsy, n);
  122. // Fill output control points.
  123. for (int i = 0; i < n; ++i) {
  124. (*firstControlPoints)[i].rx() = xs[i];
  125. (*firstControlPoints)[i].ry() = ys[i];
  126. if (i < n - 1) {
  127. (*secondControlPoints)[i].rx() = 2 * knots[i + 1].x() - xs[i + 1];
  128. (*secondControlPoints)[i].ry() = 2 * knots[i + 1].y() - ys[i + 1];
  129. } else {
  130. (*secondControlPoints)[i].rx() = (knots[n].x() + xs[n - 1]) / 2;
  131. (*secondControlPoints)[i].ry() = (knots[n].y() + ys[n - 1]) / 2;
  132. }
  133. }
  134. delete xs;
  135. delete ys;
  136. delete[] rhsx;
  137. delete[] rhsy;
  138. }
  139. };
  140. SmoothCurveGraph::SmoothCurveGraph(QCPAxis *keyAxis, QCPAxis *valueAxis) : QCPGraph(keyAxis, valueAxis), m_smooth(true)
  141. {
  142. }
  143. void SmoothCurveGraph::setSmooth(bool smooth)
  144. {
  145. m_smooth = smooth;
  146. }
  147. void SmoothCurveGraph::drawLinePlot(QCPPainter *painter, const QVector<QPointF> &lines) const
  148. {
  149. if (painter->pen().style() != Qt::NoPen && painter->pen().color().alpha() != 0) {
  150. applyDefaultAntialiasingHint(painter);
  151. if (m_smooth && mLineStyle == lsLine)
  152. painter->drawPath(SmoothCurveGenerator::generateSmoothCurve(lines));
  153. else
  154. drawPolyline(painter, lines);
  155. }
  156. }