qsplineseries.cpp
222 lines
| 7.6 KiB
| text/x-c
|
CppLexer
Jani Honkonen
|
r794 | /**************************************************************************** | ||
** | ||||
** Copyright (C) 2012 Digia Plc | ||||
** All rights reserved. | ||||
** For any questions to Digia, please use contact form at http://qt.digia.com | ||||
** | ||||
** This file is part of the Qt Commercial Charts Add-on. | ||||
** | ||||
** $QT_BEGIN_LICENSE$ | ||||
** Licensees holding valid Qt Commercial licenses may use this file in | ||||
** accordance with the Qt Commercial License Agreement provided with the | ||||
** Software or, alternatively, in accordance with the terms contained in | ||||
** a written agreement between you and Digia. | ||||
** | ||||
** If you have questions regarding the use of this file, please use | ||||
** contact form at http://qt.digia.com | ||||
** $QT_END_LICENSE$ | ||||
** | ||||
****************************************************************************/ | ||||
Marek Rosa
|
r295 | #include "qsplineseries.h" | ||
Michal Klocek
|
r938 | #include "qsplineseries_p.h" | ||
Michal Klocek
|
r943 | #include "splinechartitem_p.h" | ||
#include "chartdataset_p.h" | ||||
#include "charttheme_p.h" | ||||
#include "chartanimator_p.h" | ||||
Marek Rosa
|
r295 | |||
Marek Rosa
|
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. | ||||
Tero Ahola
|
r995 | |||
\image examples_splinechart.png | ||||
Creating basic spline chart is simple: | ||||
\code | ||||
QSplineSeries* series = new QSplineSeries(); | ||||
series->append(0, 6); | ||||
series->append(2, 4); | ||||
... | ||||
chart->addSeries(series); | ||||
\endcode | ||||
Marek Rosa
|
r433 | */ | ||
/*! | ||||
\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 | ||||
*/ | ||||
Marek Rosa
|
r401 | QTCOMMERCIALCHART_BEGIN_NAMESPACE | ||
Marek Rosa
|
r433 | /*! | ||
Constructs empty series object which is a child of \a parent. | ||||
Michal Klocek
|
r974 | When series object is added to QChartView or QChart instance then the ownerships is transferred. | ||
Marek Rosa
|
r433 | */ | ||
Marek Rosa
|
r295 | QSplineSeries::QSplineSeries(QObject *parent) : | ||
Michal Klocek
|
r938 | QLineSeries(*new QSplineSeriesPrivate(this),parent) | ||
Marek Rosa
|
r295 | { | ||
} | ||||
Marek Rosa
|
r305 | |||
Tero Ahola
|
r988 | QAbstractSeries::QSeriesType QSplineSeries::type() const | ||
Michal Klocek
|
r938 | { | ||
Tero Ahola
|
r988 | return QAbstractSeries::SeriesTypeSpline; | ||
Michal Klocek
|
r938 | } | ||
QPointF QSplineSeries::controlPoint(int index) const | ||||
{ | ||||
Q_D(const QSplineSeries); | ||||
return d->m_controlPoints[index]; | ||||
} | ||||
Marek Rosa
|
r992 | /*! | ||
\fn bool QSplineSeries::setModelMappingRange(int first, int count) | ||||
Allows limiting the model mapping. | ||||
Parameter \a first specifies which element of the model should be used as a first one of the series. | ||||
Parameter \a count specifies how many elements should be mapped. If count is not specified (defaults to -1) | ||||
then all the items following \a first item in a model are used. | ||||
\sa setModel(), setModelMapping() | ||||
*/ | ||||
void QSplineSeries::setModelMapping(int modelX, int modelY, Qt::Orientation orientation) | ||||
{ | ||||
Q_D(QSplineSeries); | ||||
QXYSeries::setModelMapping(modelX, modelY, orientation); | ||||
d->calculateControlPoints(); | ||||
} | ||||
Michal Klocek
|
r938 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | ||
QSplineSeriesPrivate::QSplineSeriesPrivate(QSplineSeries* q):QLineSeriesPrivate(q) | ||||
{ | ||||
QObject::connect(this,SIGNAL(pointAdded(int)), this, SLOT(updateControlPoints())); | ||||
QObject::connect(this,SIGNAL(pointRemoved(int)), this, SLOT(updateControlPoints())); | ||||
QObject::connect(this,SIGNAL(pointReplaced(int)), this, SLOT(updateControlPoints())); | ||||
}; | ||||
Marek Rosa
|
r433 | /*! | ||
\internal | ||||
Calculates control points which are needed by QPainterPath.cubicTo function to draw the cubic Bezier cureve between two points. | ||||
*/ | ||||
Michal Klocek
|
r938 | void QSplineSeriesPrivate::calculateControlPoints() | ||
Marek Rosa
|
r401 | { | ||
Michal Klocek
|
r938 | Q_Q(QSplineSeries); | ||
Marek Rosa
|
r401 | // Based on http://www.codeproject.com/Articles/31859/Draw-a-Smooth-Curve-through-a-Set-of-2D-Points-wit | ||
Marek Rosa
|
r433 | // CPOL License | ||
Marek Rosa
|
r401 | |||
Michal Klocek
|
r938 | int n = q->count() - 1; | ||
Marek Rosa
|
r401 | if (n == 1) | ||
{ // Special case: Bezier curve should be a straight line. | ||||
// firstControlPoints = new Point[1]; | ||||
// 3P1 = 2P0 + P3 | ||||
Michal Klocek
|
r938 | m_controlPoints.append(QPointF((2 * q->x(0) + q->x(1)) / 3, (2 * q->y(0) + q->y(1)) / 3)); | ||
Marek Rosa
|
r401 | |||
// P2 = 2P1 P0 | ||||
Michal Klocek
|
r938 | m_controlPoints.append(QPointF(2 * m_controlPoints[0].x() - q->x(0), 2 * m_controlPoints[0].y() - q->y(0))); | ||
Marek Rosa
|
r401 | return; | ||
Marek Rosa
|
r318 | } | ||
Marek Rosa
|
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; | ||||
Michal Klocek
|
r938 | rhs.append(q->x(0) + 2 * q->x(1)); | ||
Marek Rosa
|
r401 | |||
// Set right hand side X values | ||||
Marek Rosa
|
r429 | for (int i = 1; i < n - 1; ++i) | ||
Michal Klocek
|
r938 | rhs.append(4 * q->x(i) + 2 * q->x(i + 1)); | ||
Marek Rosa
|
r401 | |||
Michal Klocek
|
r938 | rhs.append((8 * q->x(n - 1) + q->x(n)) / 2.0); | ||
Marek Rosa
|
r401 | // Get first control points X-values | ||
Marek Rosa
|
r519 | QList<qreal> xControl = getFirstControlPoints(rhs); | ||
Michal Klocek
|
r938 | rhs[0] = q->y(0) + 2 * q->y(1); | ||
Marek Rosa
|
r401 | |||
// Set right hand side Y values | ||||
Marek Rosa
|
r429 | for (int i = 1; i < n - 1; ++i) | ||
Michal Klocek
|
r938 | rhs[i] = 4 * q->y(i) + 2 * q->y(i + 1); | ||
Marek Rosa
|
r401 | |||
Michal Klocek
|
r938 | rhs[n - 1] = (8 * q->y(n - 1) + q->y(n)) / 2.0; | ||
Marek Rosa
|
r401 | // Get first control points Y-values | ||
Marek Rosa
|
r519 | QList<qreal> yControl = getFirstControlPoints(rhs); | ||
Marek Rosa
|
r401 | |||
// Fill output arrays. | ||||
sauimone
|
r743 | for (int i = 0; i < n; ++i) { | ||
Marek Rosa
|
r401 | // First control point | ||
Marek Rosa
|
r519 | m_controlPoints.append(QPointF(xControl[i], yControl[i])); | ||
Marek Rosa
|
r401 | // Second control point | ||
if (i < n - 1) | ||||
Michal Klocek
|
r938 | m_controlPoints.append(QPointF(2 * q->x(i + 1) - xControl[i + 1], 2 * q->y(i + 1) - yControl[i + 1])); | ||
Marek Rosa
|
r401 | else | ||
Michal Klocek
|
r938 | m_controlPoints.append(QPointF((q->x(n) + xControl[n - 1]) / 2, (q->y(n) + yControl[n - 1]) / 2)); | ||
Marek Rosa
|
r318 | } | ||
Marek Rosa
|
r401 | } | ||
Marek Rosa
|
r433 | /*! | ||
\internal | ||||
*/ | ||||
Michal Klocek
|
r938 | QList<qreal> QSplineSeriesPrivate::getFirstControlPoints(QList<qreal> rhs) | ||
Marek Rosa
|
r401 | { | ||
QList<qreal> x; // Solution vector. | ||||
QList<qreal> tmp; // Temp workspace. | ||||
qreal b = 2.0; | ||||
x.append(rhs[0] / b); | ||||
tmp.append(0); | ||||
sauimone
|
r743 | for (int i = 1; i < rhs.size(); i++) { | ||
// Decomposition and forward substitution. | ||||
Marek Rosa
|
r401 | 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; | ||||
} | ||||
Marek Rosa
|
r431 | |||
Marek Rosa
|
r433 | /*! | ||
\internal | ||||
Updates the control points, besed on currently avaiable knots. | ||||
*/ | ||||
Michal Klocek
|
r938 | void QSplineSeriesPrivate::updateControlPoints() | ||
Marek Rosa
|
r431 | { | ||
Michal Klocek
|
r938 | Q_Q(QSplineSeries); | ||
if (q->count() > 1) { | ||||
Marek Rosa
|
r431 | m_controlPoints.clear(); | ||
calculateControlPoints(); | ||||
} | ||||
} | ||||
Michal Klocek
|
r943 | Chart* QSplineSeriesPrivate::createGraphics(ChartPresenter* presenter) | ||
{ | ||||
Q_Q(QSplineSeries); | ||||
SplineChartItem* spline = new SplineChartItem(q,presenter); | ||||
if(presenter->animationOptions().testFlag(QChart::SeriesAnimations)) { | ||||
presenter->animator()->addAnimation(spline); | ||||
} | ||||
presenter->chartTheme()->decorate(q, presenter->dataSet()->seriesIndex(q)); | ||||
return spline; | ||||
} | ||||
Marek Rosa
|
r401 | #include "moc_qsplineseries.cpp" | ||
Michal Klocek
|
r938 | #include "moc_qsplineseries_p.cpp" | ||
Marek Rosa
|
r401 | |||
QTCOMMERCIALCHART_END_NAMESPACE | ||||