| 1 | /**************************************************************************** | 
| 2 | ** | 
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. | 
| 4 | ** Contact: https://www.qt.io/licensing/ | 
| 5 | ** | 
| 6 | ** This file is part of the Qt Charts module of the Qt Toolkit. | 
| 7 | ** | 
| 8 | ** $QT_BEGIN_LICENSE:GPL$ | 
| 9 | ** Commercial License Usage | 
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in | 
| 11 | ** accordance with the commercial license agreement provided with the | 
| 12 | ** Software or, alternatively, in accordance with the terms contained in | 
| 13 | ** a written agreement between you and The Qt Company. For licensing terms | 
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further | 
| 15 | ** information use the contact form at https://www.qt.io/contact-us. | 
| 16 | ** | 
| 17 | ** GNU General Public License Usage | 
| 18 | ** Alternatively, this file may be used under the terms of the GNU | 
| 19 | ** General Public License version 3 or (at your option) any later version | 
| 20 | ** approved by the KDE Free Qt Foundation. The licenses are as published by | 
| 21 | ** the Free Software Foundation and appearing in the file LICENSE.GPL3 | 
| 22 | ** included in the packaging of this file. Please review the following | 
| 23 | ** information to ensure the GNU General Public License requirements will | 
| 24 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. | 
| 25 | ** | 
| 26 | ** $QT_END_LICENSE$ | 
| 27 | ** | 
| 28 | ****************************************************************************/ | 
| 29 |  | 
| 30 | #include <private/splinechartitem_p.h> | 
| 31 | #include <private/qsplineseries_p.h> | 
| 32 | #include <private/chartpresenter_p.h> | 
| 33 | #include <private/splineanimation_p.h> | 
| 34 | #include <private/polardomain_p.h> | 
| 35 | #include <QtGui/QPainter> | 
| 36 | #include <QtWidgets/QGraphicsSceneMouseEvent> | 
| 37 |  | 
| 38 | QT_CHARTS_BEGIN_NAMESPACE | 
| 39 |  | 
| 40 | SplineChartItem::SplineChartItem(QSplineSeries *series, QGraphicsItem *item) | 
| 41 |     : XYChart(series,item), | 
| 42 |       m_series(series), | 
| 43 |       m_pointsVisible(false), | 
| 44 |       m_animation(0), | 
| 45 |       m_pointLabelsVisible(false), | 
| 46 |       m_pointLabelsFormat(series->pointLabelsFormat()), | 
| 47 |       m_pointLabelsFont(series->pointLabelsFont()), | 
| 48 |       m_pointLabelsColor(series->pointLabelsColor()), | 
| 49 |       m_pointLabelsClipping(true), | 
| 50 |       m_mousePressed(false) | 
| 51 | { | 
| 52 |     setAcceptHoverEvents(true); | 
| 53 |     setFlag(flag: QGraphicsItem::ItemIsSelectable); | 
| 54 |     setZValue(ChartPresenter::SplineChartZValue); | 
| 55 |     QObject::connect(sender: m_series->d_func(), SIGNAL(updated()), receiver: this, SLOT(handleUpdated())); | 
| 56 |     QObject::connect(sender: series, SIGNAL(visibleChanged()), receiver: this, SLOT(handleUpdated())); | 
| 57 |     QObject::connect(sender: series, SIGNAL(opacityChanged()), receiver: this, SLOT(handleUpdated())); | 
| 58 |     QObject::connect(sender: series, SIGNAL(pointLabelsFormatChanged(QString)), | 
| 59 |                      receiver: this, SLOT(handleUpdated())); | 
| 60 |     QObject::connect(sender: series, SIGNAL(pointLabelsVisibilityChanged(bool)), | 
| 61 |                      receiver: this, SLOT(handleUpdated())); | 
| 62 |     QObject::connect(sender: series, SIGNAL(pointLabelsFontChanged(QFont)), receiver: this, SLOT(handleUpdated())); | 
| 63 |     QObject::connect(sender: series, SIGNAL(pointLabelsColorChanged(QColor)), receiver: this, SLOT(handleUpdated())); | 
| 64 |     QObject::connect(sender: series, SIGNAL(pointLabelsClippingChanged(bool)), receiver: this, SLOT(handleUpdated())); | 
| 65 |     handleUpdated(); | 
| 66 | } | 
| 67 |  | 
| 68 | QRectF SplineChartItem::boundingRect() const | 
| 69 | { | 
| 70 |     return m_rect; | 
| 71 | } | 
| 72 |  | 
| 73 | QPainterPath SplineChartItem::shape() const | 
| 74 | { | 
| 75 |     return m_fullPath; | 
| 76 | } | 
| 77 |  | 
| 78 | void SplineChartItem::setAnimation(SplineAnimation *animation) | 
| 79 | { | 
| 80 |     m_animation = animation; | 
| 81 |     XYChart::setAnimation(animation); | 
| 82 | } | 
| 83 |  | 
| 84 | ChartAnimation *SplineChartItem::animation() const | 
| 85 | { | 
| 86 |     return m_animation; | 
| 87 | } | 
| 88 |  | 
| 89 | void SplineChartItem::setControlGeometryPoints(QVector<QPointF>& points) | 
| 90 | { | 
| 91 |     m_controlPoints = points; | 
| 92 | } | 
| 93 |  | 
| 94 | QVector<QPointF> SplineChartItem::controlGeometryPoints() const | 
| 95 | { | 
| 96 |     return m_controlPoints; | 
| 97 | } | 
| 98 |  | 
| 99 | void SplineChartItem::updateChart(QVector<QPointF> &oldPoints, QVector<QPointF> &newPoints, int index) | 
| 100 | { | 
| 101 |     QVector<QPointF> controlPoints; | 
| 102 |     if (newPoints.count() >= 2) | 
| 103 |         controlPoints = calculateControlPoints(points: newPoints); | 
| 104 |  | 
| 105 |     if (m_animation) | 
| 106 |         m_animation->setup(oldPoints, newPoints, oldContorlPoints&: m_controlPoints, newControlPoints&: controlPoints, index); | 
| 107 |  | 
| 108 |     m_points = newPoints; | 
| 109 |     m_controlPoints = controlPoints; | 
| 110 |     setDirty(false); | 
| 111 |  | 
| 112 |     if (m_animation) | 
| 113 |         presenter()->startAnimation(animation: m_animation); | 
| 114 |     else | 
| 115 |         updateGeometry(); | 
| 116 | } | 
| 117 |  | 
| 118 | void SplineChartItem::updateGeometry() | 
| 119 | { | 
| 120 |     const QVector<QPointF> &points = m_points; | 
| 121 |     const QVector<QPointF> &controlPoints = m_controlPoints; | 
| 122 |  | 
| 123 |     if ((points.size() < 2) || (controlPoints.size() < 2)) { | 
| 124 |         prepareGeometryChange(); | 
| 125 |         m_path = QPainterPath(); | 
| 126 |         m_rect = QRect(); | 
| 127 |         return; | 
| 128 |     } | 
| 129 |  | 
| 130 |     Q_ASSERT(points.count() * 2 - 2 == controlPoints.count()); | 
| 131 |  | 
| 132 |     QPainterPath splinePath; | 
| 133 |     QPainterPath fullPath; | 
| 134 |     // Use worst case scenario to determine required margin. | 
| 135 |     qreal margin = m_linePen.width() * 1.42; | 
| 136 |  | 
| 137 |     if (m_series->chart()->chartType() == QChart::ChartTypePolar) { | 
| 138 |         QPainterPath splinePathLeft; | 
| 139 |         QPainterPath splinePathRight; | 
| 140 |         QPainterPath *currentSegmentPath = 0; | 
| 141 |         QPainterPath *previousSegmentPath = 0; | 
| 142 |         qreal minX = domain()->minX(); | 
| 143 |         qreal maxX = domain()->maxX(); | 
| 144 |         qreal minY = domain()->minY(); | 
| 145 |         QPointF currentSeriesPoint = m_series->at(index: 0); | 
| 146 |         QPointF currentGeometryPoint = points.at(i: 0); | 
| 147 |         QPointF previousGeometryPoint = points.at(i: 0); | 
| 148 |         bool pointOffGrid = false; | 
| 149 |         bool previousPointWasOffGrid = (currentSeriesPoint.x() < minX || currentSeriesPoint.x() > maxX); | 
| 150 |         m_visiblePoints.clear(); | 
| 151 |         m_visiblePoints.reserve(asize: points.size()); | 
| 152 |  | 
| 153 |         qreal domainRadius = domain()->size().height() / 2.0; | 
| 154 |         const QPointF centerPoint(domainRadius, domainRadius); | 
| 155 |  | 
| 156 |         if (!previousPointWasOffGrid) { | 
| 157 |             fullPath.moveTo(p: points.at(i: 0)); | 
| 158 |             // Do not draw points for points below minimum Y. | 
| 159 |             if (m_pointsVisible && currentSeriesPoint.y() >= minY) | 
| 160 |                 m_visiblePoints.append(t: currentGeometryPoint); | 
| 161 |         } | 
| 162 |  | 
| 163 |         qreal leftMarginLine = centerPoint.x() - margin; | 
| 164 |         qreal rightMarginLine = centerPoint.x() + margin; | 
| 165 |         qreal horizontal = centerPoint.y(); | 
| 166 |  | 
| 167 |         // See ScatterChartItem::updateGeometry() for explanation why seriesLastIndex is needed | 
| 168 |         const int seriesLastIndex = m_series->count() - 1; | 
| 169 |  | 
| 170 |         for (int i = 1; i < points.size(); i++) { | 
| 171 |             // Interpolating spline fragments accurately is not trivial, and would anyway be ugly | 
| 172 |             // when thick pen is used, so we work around it by utilizing three separate | 
| 173 |             // paths for spline segments and clip those with custom regions at paint time. | 
| 174 |             // "Right" path contains segments that cross the axis line with visible point on the | 
| 175 |             // right side of the axis line, as well as segments that have one point within the margin | 
| 176 |             // on the right side of the axis line and another point on the right side of the chart. | 
| 177 |             // "Left" path contains points with similarly on the left side. | 
| 178 |             // "Full" path contains rest of the points. | 
| 179 |             // This doesn't yield perfect results always. E.g. when segment covers more than 90 | 
| 180 |             // degrees and both of the points are within the margin, one in the top half and one in the | 
| 181 |             // bottom half of the chart, the bottom one gets clipped incorrectly. | 
| 182 |             // However, this should be rare occurrence in any sensible chart. | 
| 183 |             currentSeriesPoint = m_series->at(index: qMin(a: seriesLastIndex, b: i)); | 
| 184 |             currentGeometryPoint = points.at(i); | 
| 185 |             pointOffGrid = (currentSeriesPoint.x() < minX || currentSeriesPoint.x() > maxX); | 
| 186 |  | 
| 187 |             // Draw something unless both off-grid | 
| 188 |             if (!pointOffGrid || !previousPointWasOffGrid) { | 
| 189 |                 bool dummyOk; // We know points are ok, but this is needed | 
| 190 |                 qreal currentAngle = static_cast<PolarDomain *>(domain())->toAngularCoordinate(value: currentSeriesPoint.x(), ok&: dummyOk); | 
| 191 |                 qreal previousAngle = static_cast<PolarDomain *>(domain())->toAngularCoordinate(value: m_series->at(index: i - 1).x(), ok&: dummyOk); | 
| 192 |  | 
| 193 |                 if ((qAbs(t: currentAngle - previousAngle) > 180.0)) { | 
| 194 |                     // If the angle between two points is over 180 degrees (half X range), | 
| 195 |                     // any direct segment between them becomes meaningless. | 
| 196 |                     // In this case two line segments are drawn instead, from previous | 
| 197 |                     // point to the center and from center to current point. | 
| 198 |                     if ((previousAngle < 0.0 || (previousAngle <= 180.0 && previousGeometryPoint.x() < rightMarginLine)) | 
| 199 |                         && previousGeometryPoint.y() < horizontal) { | 
| 200 |                         currentSegmentPath = &splinePathRight; | 
| 201 |                     } else if ((previousAngle > 360.0 || (previousAngle > 180.0 && previousGeometryPoint.x() > leftMarginLine)) | 
| 202 |                                 && previousGeometryPoint.y() < horizontal) { | 
| 203 |                         currentSegmentPath = &splinePathLeft; | 
| 204 |                     } else if (previousAngle > 0.0 && previousAngle < 360.0) { | 
| 205 |                         currentSegmentPath = &splinePath; | 
| 206 |                     } else { | 
| 207 |                         currentSegmentPath = 0; | 
| 208 |                     } | 
| 209 |  | 
| 210 |                     if (currentSegmentPath) { | 
| 211 |                         if (previousSegmentPath != currentSegmentPath) | 
| 212 |                             currentSegmentPath->moveTo(p: previousGeometryPoint); | 
| 213 |                         if (!previousSegmentPath) | 
| 214 |                             fullPath.moveTo(p: previousGeometryPoint); | 
| 215 |  | 
| 216 |                         currentSegmentPath->lineTo(p: centerPoint); | 
| 217 |                         fullPath.lineTo(p: centerPoint); | 
| 218 |                     } | 
| 219 |  | 
| 220 |                     previousSegmentPath = currentSegmentPath; | 
| 221 |  | 
| 222 |                     if ((currentAngle < 0.0 || (currentAngle <= 180.0 && currentGeometryPoint.x() < rightMarginLine)) | 
| 223 |                         && currentGeometryPoint.y() < horizontal) { | 
| 224 |                         currentSegmentPath = &splinePathRight; | 
| 225 |                     } else if ((currentAngle > 360.0 || (currentAngle > 180.0 &¤tGeometryPoint.x() > leftMarginLine)) | 
| 226 |                                 && currentGeometryPoint.y() < horizontal) { | 
| 227 |                         currentSegmentPath = &splinePathLeft; | 
| 228 |                     } else if (currentAngle > 0.0 && currentAngle < 360.0) { | 
| 229 |                         currentSegmentPath = &splinePath; | 
| 230 |                     } else { | 
| 231 |                         currentSegmentPath = 0; | 
| 232 |                     } | 
| 233 |  | 
| 234 |                     if (currentSegmentPath) { | 
| 235 |                         if (previousSegmentPath != currentSegmentPath) | 
| 236 |                             currentSegmentPath->moveTo(p: centerPoint); | 
| 237 |                         if (!previousSegmentPath) | 
| 238 |                             fullPath.moveTo(p: centerPoint); | 
| 239 |  | 
| 240 |                         currentSegmentPath->lineTo(p: currentGeometryPoint); | 
| 241 |                         fullPath.lineTo(p: currentGeometryPoint); | 
| 242 |                     } | 
| 243 |                 } else { | 
| 244 |                     QPointF cp1 = controlPoints[2 * (i - 1)]; | 
| 245 |                     QPointF cp2 = controlPoints[(2 * i) - 1]; | 
| 246 |  | 
| 247 |                     if (previousAngle < 0.0 || currentAngle < 0.0 | 
| 248 |                         || ((previousAngle <= 180.0 && currentAngle <= 180.0) | 
| 249 |                             && ((previousGeometryPoint.x() < rightMarginLine && previousGeometryPoint.y() < horizontal) | 
| 250 |                                 || (currentGeometryPoint.x() < rightMarginLine && currentGeometryPoint.y() < horizontal)))) { | 
| 251 |                         currentSegmentPath = &splinePathRight; | 
| 252 |                     } else if (previousAngle > 360.0 || currentAngle > 360.0 | 
| 253 |                                || ((previousAngle > 180.0 && currentAngle > 180.0) | 
| 254 |                                    && ((previousGeometryPoint.x() > leftMarginLine && previousGeometryPoint.y() < horizontal) | 
| 255 |                                        || (currentGeometryPoint.x() > leftMarginLine && currentGeometryPoint.y() < horizontal)))) { | 
| 256 |                         currentSegmentPath = &splinePathLeft; | 
| 257 |                     } else { | 
| 258 |                         currentSegmentPath = &splinePath; | 
| 259 |                     } | 
| 260 |  | 
| 261 |                     if (currentSegmentPath != previousSegmentPath) | 
| 262 |                         currentSegmentPath->moveTo(p: previousGeometryPoint); | 
| 263 |                     if (!previousSegmentPath) | 
| 264 |                         fullPath.moveTo(p: previousGeometryPoint); | 
| 265 |  | 
| 266 |                     fullPath.cubicTo(ctrlPt1: cp1, ctrlPt2: cp2, endPt: currentGeometryPoint); | 
| 267 |                     currentSegmentPath->cubicTo(ctrlPt1: cp1, ctrlPt2: cp2, endPt: currentGeometryPoint); | 
| 268 |                 } | 
| 269 |             } else { | 
| 270 |                 currentSegmentPath = 0; | 
| 271 |             } | 
| 272 |  | 
| 273 |             previousPointWasOffGrid = pointOffGrid; | 
| 274 |             if (!pointOffGrid && m_pointsVisible && currentSeriesPoint.y() >= minY) | 
| 275 |                 m_visiblePoints.append(t: currentGeometryPoint); | 
| 276 |             previousSegmentPath = currentSegmentPath; | 
| 277 |             previousGeometryPoint = currentGeometryPoint; | 
| 278 |         } | 
| 279 |  | 
| 280 |         m_pathPolarRight = splinePathRight; | 
| 281 |         m_pathPolarLeft = splinePathLeft; | 
| 282 |         // Note: This construction of m_fullpath is not perfect. The partial segments that are | 
| 283 |         // outside left/right clip regions at axis boundary still generate hover/click events, | 
| 284 |         // because shape doesn't get clipped. It doesn't seem possible to do sensibly. | 
| 285 |     } else { // not polar | 
| 286 |         splinePath.moveTo(p: points.at(i: 0)); | 
| 287 |         for (int i = 0; i < points.size() - 1; i++) { | 
| 288 |             const QPointF &point = points.at(i: i + 1); | 
| 289 |             splinePath.cubicTo(ctrlPt1: controlPoints[2 * i], ctrlPt2: controlPoints[2 * i + 1], endPt: point); | 
| 290 |         } | 
| 291 |         fullPath = splinePath; | 
| 292 |     } | 
| 293 |  | 
| 294 |     QPainterPathStroker stroker; | 
| 295 |     // The full path is comprised of three separate paths. | 
| 296 |     // This is why we are prepared for the "worst case" scenario, i.e. use always MiterJoin and | 
| 297 |     // multiply line width with square root of two when defining shape and bounding rectangle. | 
| 298 |     stroker.setWidth(margin); | 
| 299 |     stroker.setJoinStyle(Qt::MiterJoin); | 
| 300 |     stroker.setCapStyle(Qt::SquareCap); | 
| 301 |     stroker.setMiterLimit(m_linePen.miterLimit()); | 
| 302 |  | 
| 303 |     // Only zoom in if the bounding rects of the path fit inside int limits. QWidget::update() uses | 
| 304 |     // a region that has to be compatible with QRect. | 
| 305 |     QPainterPath checkShapePath = stroker.createStroke(path: fullPath); | 
| 306 |     if (checkShapePath.boundingRect().height() <= INT_MAX | 
| 307 |             && checkShapePath.boundingRect().width() <= INT_MAX | 
| 308 |             && splinePath.boundingRect().height() <= INT_MAX | 
| 309 |             && splinePath.boundingRect().width() <= INT_MAX) { | 
| 310 |         m_path = splinePath; | 
| 311 |  | 
| 312 |         prepareGeometryChange(); | 
| 313 |  | 
| 314 |         m_fullPath = checkShapePath; | 
| 315 |         m_rect = m_fullPath.boundingRect(); | 
| 316 |     } | 
| 317 | } | 
| 318 |  | 
| 319 | /*! | 
| 320 |   Calculates control points which are needed by QPainterPath.cubicTo function to draw the cubic Bezier cureve between two points. | 
| 321 |   */ | 
| 322 | QVector<QPointF> SplineChartItem::calculateControlPoints(const QVector<QPointF> &points) | 
| 323 | { | 
| 324 |     QVector<QPointF> controlPoints; | 
| 325 |     controlPoints.resize(asize: points.count() * 2 - 2); | 
| 326 |  | 
| 327 |     int n = points.count() - 1; | 
| 328 |  | 
| 329 |     if (n == 1) { | 
| 330 |         //for n==1 | 
| 331 |         controlPoints[0].setX((2 * points[0].x() + points[1].x()) / 3); | 
| 332 |         controlPoints[0].setY((2 * points[0].y() + points[1].y()) / 3); | 
| 333 |         controlPoints[1].setX(2 * controlPoints[0].x() - points[0].x()); | 
| 334 |         controlPoints[1].setY(2 * controlPoints[0].y() - points[0].y()); | 
| 335 |         return controlPoints; | 
| 336 |     } | 
| 337 |  | 
| 338 |     // Calculate first Bezier control points | 
| 339 |     // Set of equations for P0 to Pn points. | 
| 340 |     // | 
| 341 |     //  |   2   1   0   0   ... 0   0   0   ... 0   0   0   |   |   P1_1    |   |   P0 + 2 * P1             | | 
| 342 |     //  |   1   4   1   0   ... 0   0   0   ... 0   0   0   |   |   P1_2    |   |   4 * P1 + 2 * P2         | | 
| 343 |     //  |   0   1   4   1   ... 0   0   0   ... 0   0   0   |   |   P1_3    |   |   4 * P2 + 2 * P3         | | 
| 344 |     //  |   .   .   .   .   .   .   .   .   .   .   .   .   |   |   ...     |   |   ...                     | | 
| 345 |     //  |   0   0   0   0   ... 1   4   1   ... 0   0   0   | * |   P1_i    | = |   4 * P(i-1) + 2 * Pi     | | 
| 346 |     //  |   .   .   .   .   .   .   .   .   .   .   .   .   |   |   ...     |   |   ...                     | | 
| 347 |     //  |   0   0   0   0   0   0   0   0   ... 1   4   1   |   |   P1_(n-1)|   |   4 * P(n-2) + 2 * P(n-1) | | 
| 348 |     //  |   0   0   0   0   0   0   0   0   ... 0   2   7   |   |   P1_n    |   |   8 * P(n-1) + Pn         | | 
| 349 |     // | 
| 350 |     QVector<qreal> vector; | 
| 351 |     vector.resize(asize: n); | 
| 352 |  | 
| 353 |     vector[0] = points[0].x() + 2 * points[1].x(); | 
| 354 |  | 
| 355 |  | 
| 356 |     for (int i = 1; i < n - 1; ++i) | 
| 357 |         vector[i] = 4 * points[i].x() + 2 * points[i + 1].x(); | 
| 358 |  | 
| 359 |     vector[n - 1] = (8 * points[n - 1].x() + points[n].x()) / 2.0; | 
| 360 |  | 
| 361 |     QVector<qreal> xControl = firstControlPoints(vector); | 
| 362 |  | 
| 363 |     vector[0] = points[0].y() + 2 * points[1].y(); | 
| 364 |  | 
| 365 |     for (int i = 1; i < n - 1; ++i) | 
| 366 |         vector[i] = 4 * points[i].y() + 2 * points[i + 1].y(); | 
| 367 |  | 
| 368 |     vector[n - 1] = (8 * points[n - 1].y() + points[n].y()) / 2.0; | 
| 369 |  | 
| 370 |     QVector<qreal> yControl = firstControlPoints(vector); | 
| 371 |  | 
| 372 |     for (int i = 0, j = 0; i < n; ++i, ++j) { | 
| 373 |  | 
| 374 |         controlPoints[j].setX(xControl[i]); | 
| 375 |         controlPoints[j].setY(yControl[i]); | 
| 376 |  | 
| 377 |         j++; | 
| 378 |  | 
| 379 |         if (i < n - 1) { | 
| 380 |             controlPoints[j].setX(2 * points[i + 1].x() - xControl[i + 1]); | 
| 381 |             controlPoints[j].setY(2 * points[i + 1].y() - yControl[i + 1]); | 
| 382 |         } else { | 
| 383 |             controlPoints[j].setX((points[n].x() + xControl[n - 1]) / 2); | 
| 384 |             controlPoints[j].setY((points[n].y() + yControl[n - 1]) / 2); | 
| 385 |         } | 
| 386 |     } | 
| 387 |     return controlPoints; | 
| 388 | } | 
| 389 |  | 
| 390 | QVector<qreal> SplineChartItem::firstControlPoints(const QVector<qreal>& vector) | 
| 391 | { | 
| 392 |     QVector<qreal> result; | 
| 393 |  | 
| 394 |     int count = vector.count(); | 
| 395 |     result.resize(asize: count); | 
| 396 |     result[0] = vector[0] / 2.0; | 
| 397 |  | 
| 398 |     QVector<qreal> temp; | 
| 399 |     temp.resize(asize: count); | 
| 400 |     temp[0] = 0; | 
| 401 |  | 
| 402 |     qreal b = 2.0; | 
| 403 |  | 
| 404 |     for (int i = 1; i < count; i++) { | 
| 405 |         temp[i] = 1 / b; | 
| 406 |         b = (i < count - 1 ? 4.0 : 3.5) - temp[i]; | 
| 407 |         result[i] = (vector[i] - result[i - 1]) / b; | 
| 408 |     } | 
| 409 |  | 
| 410 |     for (int i = 1; i < count; i++) | 
| 411 |         result[count - i - 1] -= temp[count - i] * result[count - i]; | 
| 412 |  | 
| 413 |     return result; | 
| 414 | } | 
| 415 |  | 
| 416 | //handlers | 
| 417 |  | 
| 418 | void SplineChartItem::handleUpdated() | 
| 419 | { | 
| 420 |     setVisible(m_series->isVisible()); | 
| 421 |     setOpacity(m_series->opacity()); | 
| 422 |     m_pointsVisible = m_series->pointsVisible(); | 
| 423 |     m_linePen = m_series->pen(); | 
| 424 |     m_pointPen = m_series->pen(); | 
| 425 |     m_pointPen.setWidthF(2 * m_pointPen.width()); | 
| 426 |     m_pointLabelsFormat = m_series->pointLabelsFormat(); | 
| 427 |     m_pointLabelsVisible = m_series->pointLabelsVisible(); | 
| 428 |     m_pointLabelsFont = m_series->pointLabelsFont(); | 
| 429 |     m_pointLabelsColor = m_series->pointLabelsColor(); | 
| 430 |     bool labelClippingChanged = m_pointLabelsClipping != m_series->pointLabelsClipping(); | 
| 431 |     m_pointLabelsClipping = m_series->pointLabelsClipping(); | 
| 432 |     // Update whole chart in case label clipping changed as labels can be outside series area | 
| 433 |     if (labelClippingChanged) | 
| 434 |         m_series->chart()->update(); | 
| 435 |     else | 
| 436 |         update(); | 
| 437 | } | 
| 438 |  | 
| 439 | //painter | 
| 440 |  | 
| 441 | void SplineChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) | 
| 442 | { | 
| 443 |     Q_UNUSED(widget) | 
| 444 |     Q_UNUSED(option) | 
| 445 |  | 
| 446 |     QRectF clipRect = QRectF(QPointF(0, 0), domain()->size()); | 
| 447 |  | 
| 448 |     painter->save(); | 
| 449 |     painter->setPen(m_linePen); | 
| 450 |     painter->setBrush(Qt::NoBrush); | 
| 451 |  | 
| 452 |     if (m_series->chart()->chartType() == QChart::ChartTypePolar) { | 
| 453 |         qreal halfWidth = domain()->size().width() / 2.0; | 
| 454 |         QRectF clipRectLeft = QRectF(0, 0, halfWidth, domain()->size().height()); | 
| 455 |         QRectF clipRectRight = QRectF(halfWidth, 0, halfWidth, domain()->size().height()); | 
| 456 |         QRegion fullPolarClipRegion(clipRect.toRect(), QRegion::Ellipse); | 
| 457 |         QRegion clipRegionLeft(fullPolarClipRegion.intersected(r: clipRectLeft.toRect())); | 
| 458 |         QRegion clipRegionRight(fullPolarClipRegion.intersected(r: clipRectRight.toRect())); | 
| 459 |         painter->setClipRegion(clipRegionLeft); | 
| 460 |         painter->drawPath(path: m_pathPolarLeft); | 
| 461 |         painter->setClipRegion(clipRegionRight); | 
| 462 |         painter->drawPath(path: m_pathPolarRight); | 
| 463 |         painter->setClipRegion(fullPolarClipRegion); | 
| 464 |     } else { | 
| 465 |         painter->setClipRect(clipRect); | 
| 466 |     } | 
| 467 |  | 
| 468 |     painter->drawPath(path: m_path); | 
| 469 |  | 
| 470 |     if (m_pointsVisible) { | 
| 471 |         painter->setPen(m_pointPen); | 
| 472 |         if (m_series->chart()->chartType() == QChart::ChartTypePolar) | 
| 473 |             painter->drawPoints(points: m_visiblePoints); | 
| 474 |         else | 
| 475 |             painter->drawPoints(points: geometryPoints()); | 
| 476 |     } | 
| 477 |  | 
| 478 |     if (m_pointLabelsVisible) { | 
| 479 |         if (m_pointLabelsClipping) | 
| 480 |             painter->setClipping(true); | 
| 481 |         else | 
| 482 |             painter->setClipping(false); | 
| 483 |         m_series->d_func()->drawSeriesPointLabels(painter, points: m_points, offset: m_linePen.width() / 2); | 
| 484 |     } | 
| 485 |  | 
| 486 |     painter->restore(); | 
| 487 | } | 
| 488 |  | 
| 489 | void SplineChartItem::mousePressEvent(QGraphicsSceneMouseEvent *event) | 
| 490 | { | 
| 491 |     emit XYChart::pressed(point: domain()->calculateDomainPoint(point: event->pos())); | 
| 492 |     m_lastMousePos = event->pos(); | 
| 493 |     m_mousePressed = true; | 
| 494 |     QGraphicsItem::mousePressEvent(event); | 
| 495 | } | 
| 496 |  | 
| 497 | void SplineChartItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) | 
| 498 | { | 
| 499 |     emit XYChart::hovered(point: domain()->calculateDomainPoint(point: event->pos()), state: true); | 
| 500 |     QGraphicsItem::hoverEnterEvent(event); | 
| 501 | } | 
| 502 |  | 
| 503 | void SplineChartItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) | 
| 504 | { | 
| 505 |     emit XYChart::hovered(point: domain()->calculateDomainPoint(point: event->pos()), state: false); | 
| 506 |     QGraphicsItem::hoverLeaveEvent(event); | 
| 507 | } | 
| 508 |  | 
| 509 | void SplineChartItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) | 
| 510 | { | 
| 511 |     emit XYChart::released(point: domain()->calculateDomainPoint(point: m_lastMousePos)); | 
| 512 |     if (m_mousePressed) | 
| 513 |         emit XYChart::clicked(point: domain()->calculateDomainPoint(point: m_lastMousePos)); | 
| 514 |     m_mousePressed = false; | 
| 515 |     QGraphicsItem::mouseReleaseEvent(event); | 
| 516 | } | 
| 517 |  | 
| 518 | void SplineChartItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) | 
| 519 | { | 
| 520 |     emit XYChart::doubleClicked(point: domain()->calculateDomainPoint(point: m_lastMousePos)); | 
| 521 |     QGraphicsItem::mouseDoubleClickEvent(event); | 
| 522 | } | 
| 523 |  | 
| 524 | QT_CHARTS_END_NAMESPACE | 
| 525 |  | 
| 526 | #include "moc_splinechartitem_p.cpp" | 
| 527 |  |