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 qCDebug(lcSeries2D, "append points x: %f y: %f to splineSeries",
77 point->x(),
78 point->y());
79 }
80 }
81
82 d->calculateSplinePoints();
83
84 if (d->m_graphTransition)
85 d->m_graphTransition->initialize();
86
87 connect(sender: this, signal: &QSplineSeries::pointAdded, context: this, slot: [d]([[maybe_unused]] int index) {
88 d->calculateSplinePoints();
89 });
90
91 connect(sender: this, signal: &QSplineSeries::pointRemoved, context: this, slot: [d]([[maybe_unused]] int index) {
92 d->calculateSplinePoints();
93 });
94
95 connect(sender: this, signal: &QSplineSeries::pointsRemoved, context: this
96 , slot: [d]([[maybe_unused]] int index, [[maybe_unused]] int count) {
97 d->calculateSplinePoints();
98 });
99
100 connect(sender: this, signal: &QSplineSeries::pointReplaced, context: this, slot: [d]([[maybe_unused]] int index) {
101 d->calculateSplinePoints();
102 });
103
104 connect(sender: this, signal: &QSplineSeries::pointsReplaced, context: this, slot: [d]() { d->calculateSplinePoints(); });
105
106 qCDebug(lcEvents2D, "QSplineSeries::componentComplete.");
107
108 QAbstractSeries::componentComplete();
109}
110
111QAbstractSeries::SeriesType QSplineSeries::type() const
112{
113 return QAbstractSeries::SeriesType::Spline;
114}
115
116QList<QPointF> &QSplineSeries::getControlPoints()
117{
118 Q_D(QSplineSeries);
119 return d->m_controlPoints;
120}
121
122qreal QSplineSeries::width() const
123{
124 Q_D(const QSplineSeries);
125 return d->m_width;
126}
127
128void QSplineSeries::setWidth(qreal newWidth)
129{
130 Q_D(QSplineSeries);
131
132 if (newWidth < 0) {
133 qCWarning(lcProperties2D, "QSplineSeries::setWidth. Tried to use invalid width,"
134 "width has been automatically set to 0");
135 newWidth = 0;
136 }
137
138 if (qFuzzyCompare(p1: d->m_width, p2: newWidth)) {
139 qCDebug(lcProperties2D, "QSplineSeries::setWidth. Width is already set to: %f",
140 newWidth);
141 return;
142 }
143 d->m_width = newWidth;
144 emit widthChanged();
145 emit update();
146}
147
148Qt::PenCapStyle QSplineSeries::capStyle() const
149{
150 Q_D(const QSplineSeries);
151 return d->m_capStyle;
152}
153
154void QSplineSeries::setCapStyle(Qt::PenCapStyle newCapStyle)
155{
156 Q_D(QSplineSeries);
157 if (d->m_capStyle == newCapStyle) {
158 qCDebug(lcProperties2D) << "QSplineSeries::setCapStyle. CapStyle is already set to:"
159 << newCapStyle;
160 return;
161 }
162
163 d->m_capStyle = newCapStyle;
164 emit capStyleChanged();
165 emit update();
166}
167
168QSplineSeriesPrivate::QSplineSeriesPrivate()
169 : QXYSeriesPrivate(QAbstractSeries::SeriesType::Spline)
170 , m_width(1.0)
171 , m_capStyle(Qt::PenCapStyle::SquareCap)
172 , m_controlPoints()
173{}
174
175void QSplineSeriesPrivate::calculateSplinePoints()
176{
177 if (m_points.size() == 0) {
178 m_controlPoints.clear();
179 qCWarning(lcSeries2D, "points list size is 0, can't calculate spline points.");
180 return;
181 } else if (m_points.size() == 1) {
182 qCWarning(lcSeries2D, "points list size is 1, can't calculate spline points.");
183 m_controlPoints = {m_points[0], m_points[0]};
184 return;
185 }
186
187 QList<QPointF> controlPoints;
188 controlPoints.resize(size: m_points.size() * 2 - 2);
189
190 qsizetype n = m_points.size() - 1;
191
192 if (n == 1) {
193 //for n==1
194 controlPoints[0].setX((2 * m_points[0].x() + m_points[1].x()) / 3);
195 controlPoints[0].setY((2 * m_points[0].y() + m_points[1].y()) / 3);
196 controlPoints[1].setX(2 * controlPoints[0].x() - m_points[0].x());
197 controlPoints[1].setY(2 * controlPoints[0].y() - m_points[0].y());
198 m_controlPoints = controlPoints;
199 }
200
201 // Calculate first Bezier control points
202 // Set of equations for P0 to Pn points.
203 //
204 // | 2 1 0 0 ... 0 0 0 ... 0 0 0 | | P1_1 | | P1 + 2 * P0 |
205 // | 1 4 1 0 ... 0 0 0 ... 0 0 0 | | P1_2 | | 4 * P1 + 2 * P2 |
206 // | 0 1 4 1 ... 0 0 0 ... 0 0 0 | | P1_3 | | 4 * P2 + 2 * P3 |
207 // | . . . . . . . . . . . . | | ... | | ... |
208 // | 0 0 0 0 ... 1 4 1 ... 0 0 0 | * | P1_i | = | 4 * P(i-1) + 2 * Pi |
209 // | . . . . . . . . . . . . | | ... | | ... |
210 // | 0 0 0 0 0 0 0 0 ... 1 4 1 | | P1_(n-1)| | 4 * P(n-2) + 2 * P(n-1) |
211 // | 0 0 0 0 0 0 0 0 ... 0 2 7 | | P1_n | | 8 * P(n-1) + Pn |
212 //
213 QList<qreal> list;
214 list.resize(size: n);
215
216 list[0] = m_points[0].x() + 2 * m_points[1].x();
217
218 for (int i = 1; i < n - 1; ++i)
219 list[i] = 4 * m_points[i].x() + 2 * m_points[i + 1].x();
220
221 list[n - 1] = (8 * m_points[n - 1].x() + m_points[n].x()) / 2.0;
222
223 const QList<qreal> xControl = calculateControlPoints(list);
224
225 list[0] = m_points[0].y() + 2 * m_points[1].y();
226
227 for (int i = 1; i < n - 1; ++i)
228 list[i] = 4 * m_points[i].y() + 2 * m_points[i + 1].y();
229
230 list[n - 1] = (8 * m_points[n - 1].y() + m_points[n].y()) / 2.0;
231
232 const QList<qreal> yControl = calculateControlPoints(list);
233
234 for (int i = 0, j = 0; i < n; ++i, ++j) {
235 controlPoints[j].setX(xControl[i]);
236 controlPoints[j].setY(yControl[i]);
237
238 j++;
239
240 if (i < n - 1) {
241 controlPoints[j].setX(2 * m_points[i + 1].x() - xControl[i + 1]);
242 controlPoints[j].setY(2 * m_points[i + 1].y() - yControl[i + 1]);
243 } else {
244 controlPoints[j].setX((m_points[n].x() + xControl[n - 1]) / 2);
245 controlPoints[j].setY((m_points[n].y() + yControl[n - 1]) / 2);
246 }
247 }
248
249 m_controlPoints = controlPoints;
250}
251
252QList<qreal> QSplineSeriesPrivate::calculateControlPoints(const QList<qreal> &list)
253{
254 QList<qreal> result;
255
256 qsizetype count = list.size();
257 result.resize(size: count);
258 result[0] = list[0] / 2.0;
259
260 QList<qreal> temp;
261 temp.resize(size: count);
262 temp[0] = 0;
263
264 qreal b = 2.0;
265
266 for (int i = 1; i < count; i++) {
267 temp[i] = 1 / b;
268 b = (i < count - 1 ? 4.0 : 3.5) - temp[i];
269 result[i] = (list[i] - result[i - 1]) / b;
270 }
271
272 for (int i = 1; i < count; i++)
273 result[count - i - 1] -= temp[count - i] * result[count - i];
274
275 return result;
276}
277
278QT_END_NAMESPACE
279

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