1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <QtGraphs/qsplineseries.h>
5#include <private/qgraphsview_p.h>
6#include <private/qsplineseries_p.h>
7#include <private/qxypoint_p.h>
8
9QT_BEGIN_NAMESPACE
10
11/*!
12 \class QSplineSeries
13 \inmodule QtGraphs
14 \ingroup graphs_2D
15 \brief QSplineSeries presents data in spline graphs.
16
17 The graph displays smooth spline segments that moves through all the points
18 defined in the graph.
19 */
20/*!
21 \qmltype SplineSeries
22 \nativetype QSplineSeries
23 \inqmlmodule QtGraphs
24 \ingroup graphs_qml_2D
25 \inherits XYSeries
26
27 \brief SplineSeries presents data in spline graphs.
28
29 The graph displays smooth spline segments that moves through all the points
30 defined in the graph.
31
32 \image graphs2d-spline.png
33*/
34
35/*!
36 \qmlproperty real SplineSeries::width
37 The width of the line. By default, the width is 2.0.
38*/
39
40/*!
41 \qmlproperty Qt::PenCapStyle SplineSeries::capStyle
42 Controls the cap style of the line. Set to one of \l{Qt::FlatCap}{Qt.FlatCap},
43 \l{Qt::SquareCap}{Qt.SquareCap} or \l{Qt::RoundCap}{Qt.RoundCap}. By
44 default the cap style is Qt.SquareCap.
45
46 \sa Qt::PenCapStyle
47*/
48
49/*!
50 \qmlsignal SplineSeries::widthChanged()
51 This signal is emitted when the spline series width changes.
52*/
53
54/*!
55 \qmlsignal SplineSeries::capStyleChanged()
56 This signal is emitted when the spline series cap style changes.
57*/
58
59QSplineSeries::QSplineSeries(QObject *parent)
60 : QXYSeries(*(new QSplineSeriesPrivate()), parent)
61{}
62
63QSplineSeries::~QSplineSeries() {}
64
65QSplineSeries::QSplineSeries(QSplineSeriesPrivate &dd, QObject *parent)
66 : QXYSeries(dd, parent)
67{}
68
69void QSplineSeries::componentComplete()
70{
71 Q_D(QSplineSeries);
72
73 for (auto *child : children()) {
74 if (auto point = qobject_cast<QXYPoint *>(object: child))
75 append(x: point->x(), y: point->y());
76 }
77
78 d->calculateSplinePoints();
79
80 if (d->m_graphTransition)
81 d->m_graphTransition->initialize();
82
83 connect(sender: this, signal: &QSplineSeries::pointAdded, context: this, slot: [d]([[maybe_unused]] int index) {
84 d->calculateSplinePoints();
85 });
86
87 connect(sender: this, signal: &QSplineSeries::pointRemoved, context: this, slot: [d]([[maybe_unused]] int index) {
88 d->calculateSplinePoints();
89 });
90
91 connect(sender: this, signal: &QSplineSeries::pointsRemoved, context: this
92 , slot: [d]([[maybe_unused]] int index, [[maybe_unused]] int count) {
93 d->calculateSplinePoints();
94 });
95
96 connect(sender: this, signal: &QSplineSeries::pointReplaced, context: this, slot: [d]([[maybe_unused]] int index) {
97 d->calculateSplinePoints();
98 });
99
100 connect(sender: this, signal: &QSplineSeries::pointsReplaced, context: this, slot: [d]() { d->calculateSplinePoints(); });
101
102 QAbstractSeries::componentComplete();
103}
104
105QAbstractSeries::SeriesType QSplineSeries::type() const
106{
107 return QAbstractSeries::SeriesType::Spline;
108}
109
110QList<QPointF> &QSplineSeries::getControlPoints()
111{
112 Q_D(QSplineSeries);
113 return d->m_controlPoints;
114}
115
116qreal QSplineSeries::width() const
117{
118 Q_D(const QSplineSeries);
119 return d->m_width;
120}
121
122void QSplineSeries::setWidth(qreal newWidth)
123{
124 Q_D(QSplineSeries);
125
126 if (newWidth < 0)
127 newWidth = 0;
128
129 if (qFuzzyCompare(p1: d->m_width, p2: newWidth))
130 return;
131 d->m_width = newWidth;
132 emit widthChanged();
133 emit update();
134}
135
136Qt::PenCapStyle QSplineSeries::capStyle() const
137{
138 Q_D(const QSplineSeries);
139 return d->m_capStyle;
140}
141
142void QSplineSeries::setCapStyle(Qt::PenCapStyle newCapStyle)
143{
144 Q_D(QSplineSeries);
145 if (d->m_capStyle == newCapStyle)
146 return;
147 d->m_capStyle = newCapStyle;
148 emit capStyleChanged();
149 emit update();
150}
151
152QSplineSeriesPrivate::QSplineSeriesPrivate()
153 : QXYSeriesPrivate()
154 , m_width(1.0)
155 , m_capStyle(Qt::PenCapStyle::SquareCap)
156 , m_controlPoints()
157{}
158
159void QSplineSeriesPrivate::calculateSplinePoints()
160{
161 if (m_points.size() == 0) {
162 m_controlPoints.clear();
163 return;
164 } else if (m_points.size() == 1) {
165 m_controlPoints = {m_points[0], m_points[0]};
166 return;
167 }
168
169 QList<QPointF> controlPoints;
170 controlPoints.resize(size: m_points.size() * 2 - 2);
171
172 qsizetype n = m_points.size() - 1;
173
174 if (n == 1) {
175 //for n==1
176 controlPoints[0].setX((2 * m_points[0].x() + m_points[1].x()) / 3);
177 controlPoints[0].setY((2 * m_points[0].y() + m_points[1].y()) / 3);
178 controlPoints[1].setX(2 * controlPoints[0].x() - m_points[0].x());
179 controlPoints[1].setY(2 * controlPoints[0].y() - m_points[0].y());
180 m_controlPoints = controlPoints;
181 }
182
183 // Calculate first Bezier control points
184 // Set of equations for P0 to Pn points.
185 //
186 // | 2 1 0 0 ... 0 0 0 ... 0 0 0 | | P1_1 | | P1 + 2 * P0 |
187 // | 1 4 1 0 ... 0 0 0 ... 0 0 0 | | P1_2 | | 4 * P1 + 2 * P2 |
188 // | 0 1 4 1 ... 0 0 0 ... 0 0 0 | | P1_3 | | 4 * P2 + 2 * P3 |
189 // | . . . . . . . . . . . . | | ... | | ... |
190 // | 0 0 0 0 ... 1 4 1 ... 0 0 0 | * | P1_i | = | 4 * P(i-1) + 2 * Pi |
191 // | . . . . . . . . . . . . | | ... | | ... |
192 // | 0 0 0 0 0 0 0 0 ... 1 4 1 | | P1_(n-1)| | 4 * P(n-2) + 2 * P(n-1) |
193 // | 0 0 0 0 0 0 0 0 ... 0 2 7 | | P1_n | | 8 * P(n-1) + Pn |
194 //
195 QList<qreal> list;
196 list.resize(size: n);
197
198 list[0] = m_points[0].x() + 2 * m_points[1].x();
199
200 for (int i = 1; i < n - 1; ++i)
201 list[i] = 4 * m_points[i].x() + 2 * m_points[i + 1].x();
202
203 list[n - 1] = (8 * m_points[n - 1].x() + m_points[n].x()) / 2.0;
204
205 const QList<qreal> xControl = calculateControlPoints(list);
206
207 list[0] = m_points[0].y() + 2 * m_points[1].y();
208
209 for (int i = 1; i < n - 1; ++i)
210 list[i] = 4 * m_points[i].y() + 2 * m_points[i + 1].y();
211
212 list[n - 1] = (8 * m_points[n - 1].y() + m_points[n].y()) / 2.0;
213
214 const QList<qreal> yControl = calculateControlPoints(list);
215
216 for (int i = 0, j = 0; i < n; ++i, ++j) {
217 controlPoints[j].setX(xControl[i]);
218 controlPoints[j].setY(yControl[i]);
219
220 j++;
221
222 if (i < n - 1) {
223 controlPoints[j].setX(2 * m_points[i + 1].x() - xControl[i + 1]);
224 controlPoints[j].setY(2 * m_points[i + 1].y() - yControl[i + 1]);
225 } else {
226 controlPoints[j].setX((m_points[n].x() + xControl[n - 1]) / 2);
227 controlPoints[j].setY((m_points[n].y() + yControl[n - 1]) / 2);
228 }
229 }
230
231 m_controlPoints = controlPoints;
232}
233
234QList<qreal> QSplineSeriesPrivate::calculateControlPoints(const QList<qreal> &list)
235{
236 QList<qreal> result;
237
238 qsizetype count = list.size();
239 result.resize(size: count);
240 result[0] = list[0] / 2.0;
241
242 QList<qreal> temp;
243 temp.resize(size: count);
244 temp[0] = 0;
245
246 qreal b = 2.0;
247
248 for (int i = 1; i < count; i++) {
249 temp[i] = 1 / b;
250 b = (i < count - 1 ? 4.0 : 3.5) - temp[i];
251 result[i] = (list[i] - result[i - 1]) / b;
252 }
253
254 for (int i = 1; i < count; i++)
255 result[count - i - 1] -= temp[count - i] * result[count - i];
256
257 return result;
258}
259
260QT_END_NAMESPACE
261

source code of qtgraphs/src/graphs2d/splinechart/qsplineseries.cpp