| 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 |  | 
| 9 | QT_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 |  | 
| 59 | QSplineSeries::QSplineSeries(QObject *parent) | 
| 60 |     : QXYSeries(*(new QSplineSeriesPrivate()), parent) | 
| 61 | {} | 
| 62 |  | 
| 63 | QSplineSeries::~QSplineSeries() {} | 
| 64 |  | 
| 65 | QSplineSeries::QSplineSeries(QSplineSeriesPrivate &dd, QObject *parent) | 
| 66 |     : QXYSeries(dd, parent) | 
| 67 | {} | 
| 68 |  | 
| 69 | void 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 |  | 
| 105 | QAbstractSeries::SeriesType QSplineSeries::type() const | 
| 106 | { | 
| 107 |     return QAbstractSeries::SeriesType::Spline; | 
| 108 | } | 
| 109 |  | 
| 110 | QList<QPointF> &QSplineSeries::getControlPoints() | 
| 111 | { | 
| 112 |     Q_D(QSplineSeries); | 
| 113 |     return d->m_controlPoints; | 
| 114 | } | 
| 115 |  | 
| 116 | qreal QSplineSeries::width() const | 
| 117 | { | 
| 118 |     Q_D(const QSplineSeries); | 
| 119 |     return d->m_width; | 
| 120 | } | 
| 121 |  | 
| 122 | void 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 |  | 
| 136 | Qt::PenCapStyle QSplineSeries::capStyle() const | 
| 137 | { | 
| 138 |     Q_D(const QSplineSeries); | 
| 139 |     return d->m_capStyle; | 
| 140 | } | 
| 141 |  | 
| 142 | void 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 |  | 
| 152 | QSplineSeriesPrivate::QSplineSeriesPrivate() | 
| 153 |     : QXYSeriesPrivate() | 
| 154 |     , m_width(1.0) | 
| 155 |     , m_capStyle(Qt::PenCapStyle::SquareCap) | 
| 156 |     , m_controlPoints() | 
| 157 | {} | 
| 158 |  | 
| 159 | void 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 |  | 
| 234 | QList<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 |  | 
| 260 | QT_END_NAMESPACE | 
| 261 |  |