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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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