qsplineseries.cpp
148 lines
| 4.9 KiB
| text/x-c
|
CppLexer
|
r295 | #include "qsplineseries.h" | ||
|
r433 | /*! | ||
\class QSplineSeries | ||||
\brief Series type used to store data needed to draw a spline. | ||||
QSplineSeries stores the data points along with the segment control points needed by QPainterPath to draw spline | ||||
Control points are automatically calculated when data changes. The algorithm computes the points so that the normal spline can be drawn. | ||||
*/ | ||||
/*! | ||||
\fn QSeriesType QSplineSeries::type() const | ||||
Returns the type of the series | ||||
*/ | ||||
/*! | ||||
\fn QSeriesType QSplineSeries::controlPoint(int index) const | ||||
Returns the control point specified by \a index | ||||
*/ | ||||
|
r401 | QTCOMMERCIALCHART_BEGIN_NAMESPACE | ||
|
r433 | /*! | ||
Constructs empty series object which is a child of \a parent. | ||||
When series object is added to QChartView or QChart instance then the ownerships is transfered. | ||||
*/ | ||||
|
r295 | QSplineSeries::QSplineSeries(QObject *parent) : | ||
|
r470 | QLineSeries(parent) | ||
|
r295 | { | ||
|
r431 | connect(this,SIGNAL(pointAdded(int)), this, SLOT(updateControlPoints())); | ||
connect(this,SIGNAL(pointRemoved(int)), this, SLOT(updateControlPoints())); | ||||
connect(this,SIGNAL(pointReplaced(int)), this, SLOT(updateControlPoints())); | ||||
|
r295 | } | ||
|
r305 | |||
|
r433 | /*! | ||
\internal | ||||
Calculates control points which are needed by QPainterPath.cubicTo function to draw the cubic Bezier cureve between two points. | ||||
*/ | ||||
|
r401 | void QSplineSeries::calculateControlPoints() | ||
{ | ||||
// Based on http://www.codeproject.com/Articles/31859/Draw-a-Smooth-Curve-through-a-Set-of-2D-Points-wit | ||||
|
r433 | // CPOL License | ||
|
r401 | |||
|
r519 | int n = count() - 1; | ||
|
r401 | if (n == 1) | ||
{ // Special case: Bezier curve should be a straight line. | ||||
// firstControlPoints = new Point[1]; | ||||
// 3P1 = 2P0 + P3 | ||||
|
r519 | m_controlPoints.append(QPointF((2 * x(0) + x(1)) / 3, (2 * y(0) + y(1)) / 3)); | ||
|
r401 | |||
// P2 = 2P1 P0 | ||||
|
r519 | m_controlPoints.append(QPointF(2 * m_controlPoints[0].x() - x(0), 2 * m_controlPoints[0].y() - y(0))); | ||
|
r401 | return; | ||
|
r318 | } | ||
|
r401 | // Calculate first Bezier control points | ||
// Right hand side vector | ||||
// Set of equations for P0 to Pn points. | ||||
// | ||||
// | 2 1 0 0 ... 0 0 0 ... 0 0 0 | | P1_1 | | P0 + 2 * P1 | | ||||
// | 1 4 1 0 ... 0 0 0 ... 0 0 0 | | P1_2 | | 4 * P1 + 2 * P2 | | ||||
// | 0 1 4 1 ... 0 0 0 ... 0 0 0 | | P1_3 | | 4 * P2 + 2 * P3 | | ||||
// | . . . . . . . . . . . . | | ... | | ... | | ||||
// | 0 0 0 0 ... 1 4 1 ... 0 0 0 | * | P1_i | = | 4 * P(i-1) + 2 * Pi | | ||||
// | . . . . . . . . . . . . | | ... | | ... | | ||||
// | 0 0 0 0 0 0 0 0 ... 1 4 1 | | P1_(n-1)| | 4 * P(n-2) + 2 * P(n-1) | | ||||
// | 0 0 0 0 0 0 0 0 ... 0 2 7 | | P1_n | | 8 * P(n-1) + Pn | | ||||
// | ||||
QList<qreal> rhs; | ||||
|
r519 | rhs.append(x(0) + 2 * x(1)); | ||
|
r401 | |||
// Set right hand side X values | ||||
|
r429 | for (int i = 1; i < n - 1; ++i) | ||
|
r519 | rhs.append(4 * x(i) + 2 * x(i + 1)); | ||
|
r401 | |||
|
r519 | rhs.append((8 * x(n - 1) + x(n)) / 2.0); | ||
|
r401 | // Get first control points X-values | ||
|
r519 | QList<qreal> xControl = getFirstControlPoints(rhs); | ||
rhs[0] = y(0) + 2 * y(1); | ||||
|
r401 | |||
// Set right hand side Y values | ||||
|
r429 | for (int i = 1; i < n - 1; ++i) | ||
|
r519 | rhs[i] = 4 * y(i) + 2 * y(i + 1); | ||
|
r401 | |||
|
r519 | rhs[n - 1] = (8 * y(n - 1) + y(n)) / 2.0; | ||
|
r401 | // Get first control points Y-values | ||
|
r519 | QList<qreal> yControl = getFirstControlPoints(rhs); | ||
|
r401 | |||
// Fill output arrays. | ||||
|
r429 | for (int i = 0; i < n; ++i) | ||
|
r318 | { | ||
|
r401 | // First control point | ||
|
r519 | m_controlPoints.append(QPointF(xControl[i], yControl[i])); | ||
|
r401 | // Second control point | ||
if (i < n - 1) | ||||
|
r519 | m_controlPoints.append(QPointF(2 * x(i + 1) - xControl[i + 1], 2 * y(i + 1) - yControl[i + 1])); | ||
|
r401 | else | ||
|
r519 | m_controlPoints.append(QPointF((x(n) + xControl[n - 1]) / 2, (y(n) + yControl[n - 1]) / 2)); | ||
|
r318 | } | ||
|
r401 | } | ||
|
r433 | /*! | ||
\internal | ||||
*/ | ||||
|
r401 | QList<qreal> QSplineSeries::getFirstControlPoints(QList<qreal> rhs) | ||
{ | ||||
QList<qreal> x; // Solution vector. | ||||
QList<qreal> tmp; // Temp workspace. | ||||
qreal b = 2.0; | ||||
x.append(rhs[0] / b); | ||||
tmp.append(0); | ||||
for (int i = 1; i < rhs.size(); i++) // Decomposition and forward substitution. | ||||
{ | ||||
tmp.append(1 / b); | ||||
b = (i < rhs.size() - 1 ? 4.0 : 3.5) - tmp[i]; | ||||
x.append((rhs[i] - x[i - 1]) / b); | ||||
} | ||||
for (int i = 1; i < rhs.size(); i++) | ||||
x[rhs.size() - i - 1] -= tmp[rhs.size() - i] * x[rhs.size() - i]; // Backsubstitution. | ||||
return x; | ||||
} | ||||
|
r431 | |||
|
r433 | /*! | ||
\internal | ||||
Updates the control points, besed on currently avaiable knots. | ||||
*/ | ||||
|
r431 | void QSplineSeries::updateControlPoints() | ||
{ | ||||
|
r519 | if(count() > 1) | ||
|
r431 | { | ||
m_controlPoints.clear(); | ||||
calculateControlPoints(); | ||||
} | ||||
} | ||||
|
r519 | bool QSplineSeries::setModel(QAbstractItemModel* model) | ||
{ | ||||
QXYSeries::setModel(model); | ||||
calculateControlPoints(); | ||||
} | ||||
|
r401 | #include "moc_qsplineseries.cpp" | ||
QTCOMMERCIALCHART_END_NAMESPACE | ||||